blob: 40d9e7c594e590bd97069e29096dc34f3e642ed9 [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
Lars Schneidera5db4b12015-09-26 09:55:03 +020025import zipfile
26import zlib
Dennis Kaarsemaker4b07cd22015-10-20 21:31:46 +020027import ctypes
Luke Diamanddf8a9e82016-12-17 01:00:40 +000028import errno
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -030029
Luke Diamandefdcc992018-06-19 09:04:09 +010030# support basestring in python3
31try:
32 unicode = unicode
33except NameError:
34 # 'unicode' is undefined, must be Python 3
35 str = str
36 unicode = str
37 bytes = bytes
38 basestring = (str,bytes)
39else:
40 # 'unicode' exists, must be Python 2
41 str = str
42 unicode = unicode
43 bytes = str
44 basestring = basestring
45
Brandon Caseya235e852013-01-26 11:14:33 -080046try:
47 from subprocess import CalledProcessError
48except ImportError:
49 # from python2.7:subprocess.py
50 # Exception classes used by this module.
51 class CalledProcessError(Exception):
52 """This exception is raised when a process run by check_call() returns
53 a non-zero exit status. The exit status will be stored in the
54 returncode attribute."""
55 def __init__(self, returncode, cmd):
56 self.returncode = returncode
57 self.cmd = cmd
58 def __str__(self):
59 return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
60
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030061verbose = False
Simon Hausmann86949ee2007-03-19 20:59:12 +010062
Luke Diamand06804c72012-04-11 17:21:24 +020063# Only labels/tags matching this will be imported/exported
Luke Diamandc8942a22012-04-11 17:21:24 +020064defaultLabelRegexp = r'[a-zA-Z0-9_\-.]+$'
Anand Kumria21a50752008-08-10 19:26:28 +010065
Luke Diamand3deed5e2018-06-08 21:32:48 +010066# The block size is reduced automatically if required
67defaultBlockSize = 1<<20
Luke Diamand1051ef02015-06-10 08:30:59 +010068
Luke Diamand0ef67ac2018-06-08 21:32:45 +010069p4_access_checked = False
Anand Kumria21a50752008-08-10 19:26:28 +010070
71def p4_build_cmd(cmd):
72 """Build a suitable p4 command line.
73
74 This consolidates building and returning a p4 command line into one
75 location. It means that hooking into the environment, or other configuration
76 can be done more easily.
77 """
Luke Diamand6de040d2011-10-16 10:47:52 -040078 real_cmd = ["p4"]
Anand Kumriaabcaf072008-08-10 19:26:31 +010079
80 user = gitConfig("git-p4.user")
81 if len(user) > 0:
Luke Diamand6de040d2011-10-16 10:47:52 -040082 real_cmd += ["-u",user]
Anand Kumriaabcaf072008-08-10 19:26:31 +010083
84 password = gitConfig("git-p4.password")
85 if len(password) > 0:
Luke Diamand6de040d2011-10-16 10:47:52 -040086 real_cmd += ["-P", password]
Anand Kumriaabcaf072008-08-10 19:26:31 +010087
88 port = gitConfig("git-p4.port")
89 if len(port) > 0:
Luke Diamand6de040d2011-10-16 10:47:52 -040090 real_cmd += ["-p", port]
Anand Kumriaabcaf072008-08-10 19:26:31 +010091
92 host = gitConfig("git-p4.host")
93 if len(host) > 0:
Russell Myers41799aa2012-02-22 11:16:05 -080094 real_cmd += ["-H", host]
Anand Kumriaabcaf072008-08-10 19:26:31 +010095
96 client = gitConfig("git-p4.client")
97 if len(client) > 0:
Luke Diamand6de040d2011-10-16 10:47:52 -040098 real_cmd += ["-c", client]
Anand Kumriaabcaf072008-08-10 19:26:31 +010099
Lars Schneider89a6ecc2016-12-04 15:03:11 +0100100 retries = gitConfigInt("git-p4.retries")
101 if retries is None:
102 # Perform 3 retries by default
103 retries = 3
Igor Kushnirbc233522016-12-29 12:22:23 +0200104 if retries > 0:
105 # Provide a way to not pass this option by setting git-p4.retries to 0
106 real_cmd += ["-r", str(retries)]
Luke Diamand6de040d2011-10-16 10:47:52 -0400107
108 if isinstance(cmd,basestring):
109 real_cmd = ' '.join(real_cmd) + ' ' + cmd
110 else:
111 real_cmd += cmd
Luke Diamand0ef67ac2018-06-08 21:32:45 +0100112
113 # now check that we can actually talk to the server
114 global p4_access_checked
115 if not p4_access_checked:
116 p4_access_checked = True # suppress access checks in p4_check_access itself
117 p4_check_access()
118
Anand Kumria21a50752008-08-10 19:26:28 +0100119 return real_cmd
120
Luke Diamand378f7be2016-12-13 21:51:28 +0000121def git_dir(path):
122 """ Return TRUE if the given path is a git directory (/path/to/dir/.git).
123 This won't automatically add ".git" to a directory.
124 """
125 d = read_pipe(["git", "--git-dir", path, "rev-parse", "--git-dir"], True).strip()
126 if not d or len(d) == 0:
127 return None
128 else:
129 return d
130
Miklós Fazekasbbd84862013-03-11 17:45:29 -0400131def chdir(path, is_client_path=False):
132 """Do chdir to the given path, and set the PWD environment
133 variable for use by P4. It does not look at getcwd() output.
134 Since we're not using the shell, it is necessary to set the
135 PWD environment variable explicitly.
136
137 Normally, expand the path to force it to be absolute. This
138 addresses the use of relative path names inside P4 settings,
139 e.g. P4CONFIG=.p4config. P4 does not simply open the filename
140 as given; it looks for .p4config using PWD.
141
142 If is_client_path, the path was handed to us directly by p4,
143 and may be a symbolic link. Do not call os.getcwd() in this
144 case, because it will cause p4 to think that PWD is not inside
145 the client path.
146 """
147
148 os.chdir(path)
149 if not is_client_path:
150 path = os.getcwd()
151 os.environ['PWD'] = path
Robert Blum053fd0c2008-08-01 12:50:03 -0700152
Lars Schneider4d25dc42015-09-26 09:55:02 +0200153def calcDiskFree():
154 """Return free space in bytes on the disk of the given dirname."""
155 if platform.system() == 'Windows':
156 free_bytes = ctypes.c_ulonglong(0)
157 ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(os.getcwd()), None, None, ctypes.pointer(free_bytes))
158 return free_bytes.value
159 else:
160 st = os.statvfs(os.getcwd())
161 return st.f_bavail * st.f_frsize
162
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -0300163def die(msg):
164 if verbose:
165 raise Exception(msg)
166 else:
167 sys.stderr.write(msg + "\n")
168 sys.exit(1)
169
Ben Keenee2aed5f2019-12-16 14:02:19 +0000170def prompt(prompt_text):
171 """ Prompt the user to choose one of the choices
172
173 Choices are identified in the prompt_text by square brackets around
174 a single letter option.
175 """
176 choices = set(m.group(1) for m in re.finditer(r"\[(.)\]", prompt_text))
177 while True:
178 response = raw_input(prompt_text).strip().lower()
179 if not response:
180 continue
181 response = response[0]
182 if response in choices:
183 return response
184
Luke Diamand6de040d2011-10-16 10:47:52 -0400185def write_pipe(c, stdin):
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -0300186 if verbose:
Luke Diamand6de040d2011-10-16 10:47:52 -0400187 sys.stderr.write('Writing pipe: %s\n' % str(c))
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300188
Luke Diamand6de040d2011-10-16 10:47:52 -0400189 expand = isinstance(c,basestring)
190 p = subprocess.Popen(c, stdin=subprocess.PIPE, shell=expand)
191 pipe = p.stdin
192 val = pipe.write(stdin)
193 pipe.close()
194 if p.wait():
195 die('Command failed: %s' % str(c))
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300196
197 return val
198
Luke Diamand6de040d2011-10-16 10:47:52 -0400199def p4_write_pipe(c, stdin):
Anand Kumriad9429192008-08-14 23:40:38 +0100200 real_cmd = p4_build_cmd(c)
Luke Diamand6de040d2011-10-16 10:47:52 -0400201 return write_pipe(real_cmd, stdin)
Anand Kumriad9429192008-08-14 23:40:38 +0100202
Luke Diamand78871bf2017-04-15 11:36:08 +0100203def read_pipe_full(c):
204 """ Read output from command. Returns a tuple
205 of the return status, stdout text and stderr
206 text.
207 """
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -0300208 if verbose:
Luke Diamand6de040d2011-10-16 10:47:52 -0400209 sys.stderr.write('Reading pipe: %s\n' % str(c))
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -0300210
Luke Diamand6de040d2011-10-16 10:47:52 -0400211 expand = isinstance(c,basestring)
Lars Schneider1f5f3902015-09-21 12:01:41 +0200212 p = subprocess.Popen(c, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=expand)
213 (out, err) = p.communicate()
Luke Diamand78871bf2017-04-15 11:36:08 +0100214 return (p.returncode, out, err)
215
216def read_pipe(c, ignore_error=False):
217 """ Read output from command. Returns the output text on
218 success. On failure, terminates execution, unless
219 ignore_error is True, when it returns an empty string.
220 """
221 (retcode, out, err) = read_pipe_full(c)
222 if retcode != 0:
223 if ignore_error:
224 out = ""
225 else:
226 die('Command failed: %s\nError: %s' % (str(c), err))
Lars Schneider1f5f3902015-09-21 12:01:41 +0200227 return out
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300228
Luke Diamand78871bf2017-04-15 11:36:08 +0100229def read_pipe_text(c):
230 """ Read output from a command with trailing whitespace stripped.
231 On error, returns None.
232 """
233 (retcode, out, err) = read_pipe_full(c)
234 if retcode != 0:
235 return None
236 else:
237 return out.rstrip()
238
Anand Kumriad9429192008-08-14 23:40:38 +0100239def p4_read_pipe(c, ignore_error=False):
240 real_cmd = p4_build_cmd(c)
241 return read_pipe(real_cmd, ignore_error)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300242
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -0300243def read_pipe_lines(c):
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -0300244 if verbose:
Luke Diamand6de040d2011-10-16 10:47:52 -0400245 sys.stderr.write('Reading pipe: %s\n' % str(c))
246
247 expand = isinstance(c, basestring)
248 p = subprocess.Popen(c, stdout=subprocess.PIPE, shell=expand)
249 pipe = p.stdout
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300250 val = pipe.readlines()
Luke Diamand6de040d2011-10-16 10:47:52 -0400251 if pipe.close() or p.wait():
252 die('Command failed: %s' % str(c))
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300253
254 return val
Simon Hausmanncaace112007-05-15 14:57:57 +0200255
Anand Kumria23181212008-08-10 19:26:24 +0100256def p4_read_pipe_lines(c):
257 """Specifically invoke p4 on the command supplied. """
Anand Kumria155af832008-08-10 19:26:30 +0100258 real_cmd = p4_build_cmd(c)
Anand Kumria23181212008-08-10 19:26:24 +0100259 return read_pipe_lines(real_cmd)
260
Gary Gibbons8e9497c2012-07-12 19:29:00 -0400261def p4_has_command(cmd):
262 """Ask p4 for help on this command. If it returns an error, the
263 command does not exist in this version of p4."""
264 real_cmd = p4_build_cmd(["help", cmd])
265 p = subprocess.Popen(real_cmd, stdout=subprocess.PIPE,
266 stderr=subprocess.PIPE)
267 p.communicate()
268 return p.returncode == 0
269
Pete Wyckoff249da4c2012-11-23 17:35:35 -0500270def p4_has_move_command():
271 """See if the move command exists, that it supports -k, and that
272 it has not been administratively disabled. The arguments
273 must be correct, but the filenames do not have to exist. Use
274 ones with wildcards so even if they exist, it will fail."""
275
276 if not p4_has_command("move"):
277 return False
278 cmd = p4_build_cmd(["move", "-k", "@from", "@to"])
279 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
280 (out, err) = p.communicate()
281 # return code will be 1 in either case
282 if err.find("Invalid option") >= 0:
283 return False
284 if err.find("disabled") >= 0:
285 return False
286 # assume it failed because @... was invalid changelist
287 return True
288
Luke Diamandcbff4b22015-11-21 09:54:40 +0000289def system(cmd, ignore_error=False):
Luke Diamand6de040d2011-10-16 10:47:52 -0400290 expand = isinstance(cmd,basestring)
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -0300291 if verbose:
Luke Diamand6de040d2011-10-16 10:47:52 -0400292 sys.stderr.write("executing %s\n" % str(cmd))
Brandon Caseya235e852013-01-26 11:14:33 -0800293 retcode = subprocess.call(cmd, shell=expand)
Luke Diamandcbff4b22015-11-21 09:54:40 +0000294 if retcode and not ignore_error:
Brandon Caseya235e852013-01-26 11:14:33 -0800295 raise CalledProcessError(retcode, cmd)
Han-Wen Nienhuys6754a292007-05-23 17:41:50 -0300296
Luke Diamandcbff4b22015-11-21 09:54:40 +0000297 return retcode
298
Anand Kumriabf9320f2008-08-10 19:26:26 +0100299def p4_system(cmd):
300 """Specifically invoke p4 as the system command. """
Anand Kumria155af832008-08-10 19:26:30 +0100301 real_cmd = p4_build_cmd(cmd)
Luke Diamand6de040d2011-10-16 10:47:52 -0400302 expand = isinstance(real_cmd, basestring)
Brandon Caseya235e852013-01-26 11:14:33 -0800303 retcode = subprocess.call(real_cmd, shell=expand)
304 if retcode:
305 raise CalledProcessError(retcode, real_cmd)
Luke Diamand6de040d2011-10-16 10:47:52 -0400306
Luke Diamand0ef67ac2018-06-08 21:32:45 +0100307def die_bad_access(s):
308 die("failure accessing depot: {0}".format(s.rstrip()))
309
310def p4_check_access(min_expiration=1):
311 """ Check if we can access Perforce - account still logged in
312 """
313 results = p4CmdList(["login", "-s"])
314
315 if len(results) == 0:
316 # should never get here: always get either some results, or a p4ExitCode
317 assert("could not parse response from perforce")
318
319 result = results[0]
320
321 if 'p4ExitCode' in result:
322 # p4 returned non-zero status, e.g. P4PORT invalid, or p4 not in path
323 die_bad_access("could not run p4")
324
325 code = result.get("code")
326 if not code:
327 # we get here if we couldn't connect and there was nothing to unmarshal
328 die_bad_access("could not connect")
329
330 elif code == "stat":
331 expiry = result.get("TicketExpiration")
332 if expiry:
333 expiry = int(expiry)
334 if expiry > min_expiration:
335 # ok to carry on
336 return
337 else:
338 die_bad_access("perforce ticket expires in {0} seconds".format(expiry))
339
340 else:
341 # account without a timeout - all ok
342 return
343
344 elif code == "error":
345 data = result.get("data")
346 if data:
347 die_bad_access("p4 error: {0}".format(data))
348 else:
349 die_bad_access("unknown error")
Peter Osterlundd4990d52019-01-07 21:51:38 +0100350 elif code == "info":
351 return
Luke Diamand0ef67ac2018-06-08 21:32:45 +0100352 else:
353 die_bad_access("unknown error code {0}".format(code))
354
Pete Wyckoff7f0e5962013-01-26 22:11:13 -0500355_p4_version_string = None
356def p4_version_string():
357 """Read the version string, showing just the last line, which
358 hopefully is the interesting version bit.
359
360 $ p4 -V
361 Perforce - The Fast Software Configuration Management System.
362 Copyright 1995-2011 Perforce Software. All rights reserved.
363 Rev. P4/NTX86/2011.1/393975 (2011/12/16).
364 """
365 global _p4_version_string
366 if not _p4_version_string:
367 a = p4_read_pipe_lines(["-V"])
368 _p4_version_string = a[-1].rstrip()
369 return _p4_version_string
370
Luke Diamand6de040d2011-10-16 10:47:52 -0400371def p4_integrate(src, dest):
Pete Wyckoff9d7d4462012-04-29 20:57:17 -0400372 p4_system(["integrate", "-Dt", wildcard_encode(src), wildcard_encode(dest)])
Luke Diamand6de040d2011-10-16 10:47:52 -0400373
Pete Wyckoff8d7ec362012-04-29 20:57:14 -0400374def p4_sync(f, *options):
Pete Wyckoff9d7d4462012-04-29 20:57:17 -0400375 p4_system(["sync"] + list(options) + [wildcard_encode(f)])
Luke Diamand6de040d2011-10-16 10:47:52 -0400376
377def p4_add(f):
Pete Wyckoff9d7d4462012-04-29 20:57:17 -0400378 # forcibly add file names with wildcards
379 if wildcard_present(f):
380 p4_system(["add", "-f", f])
381 else:
382 p4_system(["add", f])
Luke Diamand6de040d2011-10-16 10:47:52 -0400383
384def p4_delete(f):
Pete Wyckoff9d7d4462012-04-29 20:57:17 -0400385 p4_system(["delete", wildcard_encode(f)])
Luke Diamand6de040d2011-10-16 10:47:52 -0400386
Romain Picarda02b8bc2016-01-12 13:43:47 +0100387def p4_edit(f, *options):
388 p4_system(["edit"] + list(options) + [wildcard_encode(f)])
Luke Diamand6de040d2011-10-16 10:47:52 -0400389
390def p4_revert(f):
Pete Wyckoff9d7d4462012-04-29 20:57:17 -0400391 p4_system(["revert", wildcard_encode(f)])
Luke Diamand6de040d2011-10-16 10:47:52 -0400392
Pete Wyckoff9d7d4462012-04-29 20:57:17 -0400393def p4_reopen(type, f):
394 p4_system(["reopen", "-t", type, wildcard_encode(f)])
Anand Kumriabf9320f2008-08-10 19:26:26 +0100395
Luke Diamand46c609e2016-12-02 22:43:19 +0000396def p4_reopen_in_change(changelist, files):
397 cmd = ["reopen", "-c", str(changelist)] + files
398 p4_system(cmd)
399
Gary Gibbons8e9497c2012-07-12 19:29:00 -0400400def p4_move(src, dest):
401 p4_system(["move", "-k", wildcard_encode(src), wildcard_encode(dest)])
402
Luke Diamand1051ef02015-06-10 08:30:59 +0100403def p4_last_change():
Miguel Torroja1997e912017-07-13 09:00:35 +0200404 results = p4CmdList(["changes", "-m", "1"], skip_info=True)
Luke Diamand1051ef02015-06-10 08:30:59 +0100405 return int(results[0]['change'])
406
Luke Diamand123f6312018-05-23 23:20:26 +0100407def p4_describe(change, shelved=False):
Pete Wyckoff18fa13d2012-11-23 17:35:34 -0500408 """Make sure it returns a valid result by checking for
409 the presence of field "time". Return a dict of the
410 results."""
411
Luke Diamand123f6312018-05-23 23:20:26 +0100412 cmd = ["describe", "-s"]
413 if shelved:
414 cmd += ["-S"]
415 cmd += [str(change)]
416
417 ds = p4CmdList(cmd, skip_info=True)
Pete Wyckoff18fa13d2012-11-23 17:35:34 -0500418 if len(ds) != 1:
419 die("p4 describe -s %d did not return 1 result: %s" % (change, str(ds)))
420
421 d = ds[0]
422
423 if "p4ExitCode" in d:
424 die("p4 describe -s %d exited with %d: %s" % (change, d["p4ExitCode"],
425 str(d)))
426 if "code" in d:
427 if d["code"] == "error":
428 die("p4 describe -s %d returned error code: %s" % (change, str(d)))
429
430 if "time" not in d:
431 die("p4 describe -s %d returned no \"time\": %s" % (change, str(d)))
432
433 return d
434
Pete Wyckoff9cffb8c2011-10-16 10:45:01 -0400435#
436# Canonicalize the p4 type and return a tuple of the
437# base type, plus any modifiers. See "p4 help filetypes"
438# for a list and explanation.
439#
440def split_p4_type(p4type):
David Brownb9fc6ea2007-09-19 13:12:48 -0700441
Pete Wyckoff9cffb8c2011-10-16 10:45:01 -0400442 p4_filetypes_historical = {
443 "ctempobj": "binary+Sw",
444 "ctext": "text+C",
445 "cxtext": "text+Cx",
446 "ktext": "text+k",
447 "kxtext": "text+kx",
448 "ltext": "text+F",
449 "tempobj": "binary+FSw",
450 "ubinary": "binary+F",
451 "uresource": "resource+F",
452 "uxbinary": "binary+Fx",
453 "xbinary": "binary+x",
454 "xltext": "text+Fx",
455 "xtempobj": "binary+Swx",
456 "xtext": "text+x",
457 "xunicode": "unicode+x",
458 "xutf16": "utf16+x",
459 }
460 if p4type in p4_filetypes_historical:
461 p4type = p4_filetypes_historical[p4type]
462 mods = ""
463 s = p4type.split("+")
464 base = s[0]
465 mods = ""
466 if len(s) > 1:
467 mods = s[1]
468 return (base, mods)
469
Luke Diamand60df0712012-02-23 07:51:30 +0000470#
471# return the raw p4 type of a file (text, text+ko, etc)
472#
Pete Wyckoff79467e62014-01-21 18:16:45 -0500473def p4_type(f):
474 results = p4CmdList(["fstat", "-T", "headType", wildcard_encode(f)])
Luke Diamand60df0712012-02-23 07:51:30 +0000475 return results[0]['headType']
476
477#
478# Given a type base and modifier, return a regexp matching
479# the keywords that can be expanded in the file
480#
481def p4_keywords_regexp_for_type(base, type_mods):
482 if base in ("text", "unicode", "binary"):
483 kwords = None
484 if "ko" in type_mods:
485 kwords = 'Id|Header'
486 elif "k" in type_mods:
487 kwords = 'Id|Header|Author|Date|DateTime|Change|File|Revision'
488 else:
489 return None
490 pattern = r"""
491 \$ # Starts with a dollar, followed by...
492 (%s) # one of the keywords, followed by...
Pete Wyckoff6b2bf412012-11-04 17:04:02 -0500493 (:[^$\n]+)? # possibly an old expansion, followed by...
Luke Diamand60df0712012-02-23 07:51:30 +0000494 \$ # another dollar
495 """ % kwords
496 return pattern
497 else:
498 return None
499
500#
501# Given a file, return a regexp matching the possible
502# RCS keywords that will be expanded, or None for files
503# with kw expansion turned off.
504#
505def p4_keywords_regexp_for_file(file):
506 if not os.path.exists(file):
507 return None
508 else:
509 (type_base, type_mods) = split_p4_type(p4_type(file))
510 return p4_keywords_regexp_for_type(type_base, type_mods)
David Brownb9fc6ea2007-09-19 13:12:48 -0700511
Chris Pettittc65b6702007-11-01 20:43:14 -0700512def setP4ExecBit(file, mode):
513 # Reopens an already open file and changes the execute bit to match
514 # the execute bit setting in the passed in mode.
515
516 p4Type = "+x"
517
518 if not isModeExec(mode):
519 p4Type = getP4OpenedType(file)
520 p4Type = re.sub('^([cku]?)x(.*)', '\\1\\2', p4Type)
521 p4Type = re.sub('(.*?\+.*?)x(.*?)', '\\1\\2', p4Type)
522 if p4Type[-1] == "+":
523 p4Type = p4Type[0:-1]
524
Luke Diamand6de040d2011-10-16 10:47:52 -0400525 p4_reopen(p4Type, file)
Chris Pettittc65b6702007-11-01 20:43:14 -0700526
527def getP4OpenedType(file):
528 # Returns the perforce file type for the given file.
529
Pete Wyckoff9d7d4462012-04-29 20:57:17 -0400530 result = p4_read_pipe(["opened", wildcard_encode(file)])
Blair Holloway34a0dbf2015-04-04 09:46:03 +0100531 match = re.match(".*\((.+)\)( \*exclusive\*)?\r?$", result)
Chris Pettittc65b6702007-11-01 20:43:14 -0700532 if match:
533 return match.group(1)
534 else:
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100535 die("Could not determine file type for %s (result: '%s')" % (file, result))
Chris Pettittc65b6702007-11-01 20:43:14 -0700536
Luke Diamand06804c72012-04-11 17:21:24 +0200537# Return the set of all p4 labels
538def getP4Labels(depotPaths):
539 labels = set()
540 if isinstance(depotPaths,basestring):
541 depotPaths = [depotPaths]
542
543 for l in p4CmdList(["labels"] + ["%s..." % p for p in depotPaths]):
544 label = l['label']
545 labels.add(label)
546
547 return labels
548
549# Return the set of all git tags
550def getGitTags():
551 gitTags = set()
552 for line in read_pipe_lines(["git", "tag"]):
553 tag = line.strip()
554 gitTags.add(tag)
555 return gitTags
556
Chris Pettittb43b0a32007-11-01 20:43:13 -0700557def diffTreePattern():
558 # This is a simple generator for the diff tree regex pattern. This could be
559 # a class variable if this and parseDiffTreeEntry were a part of a class.
560 pattern = re.compile(':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)')
561 while True:
562 yield pattern
563
564def parseDiffTreeEntry(entry):
565 """Parses a single diff tree entry into its component elements.
566
567 See git-diff-tree(1) manpage for details about the format of the diff
568 output. This method returns a dictionary with the following elements:
569
570 src_mode - The mode of the source file
571 dst_mode - The mode of the destination file
572 src_sha1 - The sha1 for the source file
573 dst_sha1 - The sha1 fr the destination file
574 status - The one letter status of the diff (i.e. 'A', 'M', 'D', etc)
575 status_score - The score for the status (applicable for 'C' and 'R'
576 statuses). This is None if there is no score.
577 src - The path for the source file.
578 dst - The path for the destination file. This is only present for
579 copy or renames. If it is not present, this is None.
580
581 If the pattern is not matched, None is returned."""
582
583 match = diffTreePattern().next().match(entry)
584 if match:
585 return {
586 'src_mode': match.group(1),
587 'dst_mode': match.group(2),
588 'src_sha1': match.group(3),
589 'dst_sha1': match.group(4),
590 'status': match.group(5),
591 'status_score': match.group(6),
592 'src': match.group(7),
593 'dst': match.group(10)
594 }
595 return None
596
Chris Pettittc65b6702007-11-01 20:43:14 -0700597def isModeExec(mode):
598 # Returns True if the given git mode represents an executable file,
599 # otherwise False.
600 return mode[-3:] == "755"
601
Luke Diamand55bb3e32018-06-08 21:32:46 +0100602class P4Exception(Exception):
603 """ Base class for exceptions from the p4 client """
604 def __init__(self, exit_code):
605 self.p4ExitCode = exit_code
606
607class P4ServerException(P4Exception):
608 """ Base class for exceptions where we get some kind of marshalled up result from the server """
609 def __init__(self, exit_code, p4_result):
610 super(P4ServerException, self).__init__(exit_code)
611 self.p4_result = p4_result
612 self.code = p4_result[0]['code']
613 self.data = p4_result[0]['data']
614
615class P4RequestSizeException(P4ServerException):
616 """ One of the maxresults or maxscanrows errors """
617 def __init__(self, exit_code, p4_result, limit):
618 super(P4RequestSizeException, self).__init__(exit_code, p4_result)
619 self.limit = limit
620
Chris Pettittc65b6702007-11-01 20:43:14 -0700621def isModeExecChanged(src_mode, dst_mode):
622 return isModeExec(src_mode) != isModeExec(dst_mode)
623
Luke Diamand55bb3e32018-06-08 21:32:46 +0100624def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
625 errors_as_exceptions=False):
Luke Diamand6de040d2011-10-16 10:47:52 -0400626
627 if isinstance(cmd,basestring):
628 cmd = "-G " + cmd
629 expand = True
630 else:
631 cmd = ["-G"] + cmd
632 expand = False
633
634 cmd = p4_build_cmd(cmd)
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300635 if verbose:
Luke Diamand6de040d2011-10-16 10:47:52 -0400636 sys.stderr.write("Opening pipe: %s\n" % str(cmd))
Scott Lamb9f90c732007-07-15 20:58:10 -0700637
638 # Use a temporary file to avoid deadlocks without
639 # subprocess.communicate(), which would put another copy
640 # of stdout into memory.
641 stdin_file = None
642 if stdin is not None:
643 stdin_file = tempfile.TemporaryFile(prefix='p4-stdin', mode=stdin_mode)
Luke Diamand6de040d2011-10-16 10:47:52 -0400644 if isinstance(stdin,basestring):
645 stdin_file.write(stdin)
646 else:
647 for i in stdin:
648 stdin_file.write(i + '\n')
Scott Lamb9f90c732007-07-15 20:58:10 -0700649 stdin_file.flush()
650 stdin_file.seek(0)
651
Luke Diamand6de040d2011-10-16 10:47:52 -0400652 p4 = subprocess.Popen(cmd,
653 shell=expand,
Scott Lamb9f90c732007-07-15 20:58:10 -0700654 stdin=stdin_file,
655 stdout=subprocess.PIPE)
Simon Hausmann86949ee2007-03-19 20:59:12 +0100656
657 result = []
658 try:
659 while True:
Scott Lamb9f90c732007-07-15 20:58:10 -0700660 entry = marshal.load(p4.stdout)
Miguel Torroja1997e912017-07-13 09:00:35 +0200661 if skip_info:
662 if 'code' in entry and entry['code'] == 'info':
663 continue
Andrew Garberc3f61632011-04-07 02:01:21 -0400664 if cb is not None:
665 cb(entry)
666 else:
667 result.append(entry)
Simon Hausmann86949ee2007-03-19 20:59:12 +0100668 except EOFError:
669 pass
Scott Lamb9f90c732007-07-15 20:58:10 -0700670 exitCode = p4.wait()
671 if exitCode != 0:
Luke Diamand55bb3e32018-06-08 21:32:46 +0100672 if errors_as_exceptions:
673 if len(result) > 0:
674 data = result[0].get('data')
675 if data:
676 m = re.search('Too many rows scanned \(over (\d+)\)', data)
677 if not m:
678 m = re.search('Request too large \(over (\d+)\)', data)
679
680 if m:
681 limit = int(m.group(1))
682 raise P4RequestSizeException(exitCode, result, limit)
683
684 raise P4ServerException(exitCode, result)
685 else:
686 raise P4Exception(exitCode)
687 else:
688 entry = {}
689 entry["p4ExitCode"] = exitCode
690 result.append(entry)
Simon Hausmann86949ee2007-03-19 20:59:12 +0100691
692 return result
693
694def p4Cmd(cmd):
695 list = p4CmdList(cmd)
696 result = {}
697 for entry in list:
698 result.update(entry)
699 return result;
700
Simon Hausmanncb2c9db2007-03-24 09:15:11 +0100701def p4Where(depotPath):
702 if not depotPath.endswith("/"):
703 depotPath += "/"
Vitor Antunescd884102015-04-21 23:49:30 +0100704 depotPathLong = depotPath + "..."
705 outputList = p4CmdList(["where", depotPathLong])
Tor Arvid Lund7f705dc2008-12-04 14:37:33 +0100706 output = None
707 for entry in outputList:
Tor Arvid Lund75bc9572008-12-09 16:41:50 +0100708 if "depotFile" in entry:
Vitor Antunescd884102015-04-21 23:49:30 +0100709 # Search for the base client side depot path, as long as it starts with the branch's P4 path.
710 # The base path always ends with "/...".
711 if entry["depotFile"].find(depotPath) == 0 and entry["depotFile"][-4:] == "/...":
Tor Arvid Lund75bc9572008-12-09 16:41:50 +0100712 output = entry
713 break
714 elif "data" in entry:
715 data = entry.get("data")
716 space = data.find(" ")
717 if data[:space] == depotPath:
718 output = entry
719 break
Tor Arvid Lund7f705dc2008-12-04 14:37:33 +0100720 if output == None:
721 return ""
Simon Hausmanndc524032007-05-21 09:34:56 +0200722 if output["code"] == "error":
723 return ""
Simon Hausmanncb2c9db2007-03-24 09:15:11 +0100724 clientPath = ""
725 if "path" in output:
726 clientPath = output.get("path")
727 elif "data" in output:
728 data = output.get("data")
729 lastSpace = data.rfind(" ")
730 clientPath = data[lastSpace + 1:]
731
732 if clientPath.endswith("..."):
733 clientPath = clientPath[:-3]
734 return clientPath
735
Simon Hausmann86949ee2007-03-19 20:59:12 +0100736def currentGitBranch():
Luke Diamandeff45112017-04-15 11:36:09 +0100737 return read_pipe_text(["git", "symbolic-ref", "--short", "-q", "HEAD"])
Simon Hausmann86949ee2007-03-19 20:59:12 +0100738
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100739def isValidGitDir(path):
Luke Diamand378f7be2016-12-13 21:51:28 +0000740 return git_dir(path) != None
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100741
Simon Hausmann463e8af2007-05-17 09:13:54 +0200742def parseRevision(ref):
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -0300743 return read_pipe("git rev-parse %s" % ref).strip()
Simon Hausmann463e8af2007-05-17 09:13:54 +0200744
Pete Wyckoff28755db2011-12-24 21:07:40 -0500745def branchExists(ref):
746 rev = read_pipe(["git", "rev-parse", "-q", "--verify", ref],
747 ignore_error=True)
748 return len(rev) > 0
749
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100750def extractLogMessageFromGitCommit(commit):
751 logMessage = ""
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300752
753 ## fixme: title is first line of commit, not 1st paragraph.
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100754 foundTitle = False
Mike Muellerc3f23582019-05-28 11:15:46 -0700755 for log in read_pipe_lines(["git", "cat-file", "commit", commit]):
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100756 if not foundTitle:
757 if len(log) == 1:
Simon Hausmann1c094182007-05-01 23:15:48 +0200758 foundTitle = True
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100759 continue
760
761 logMessage += log
762 return logMessage
763
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300764def extractSettingsGitLog(log):
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100765 values = {}
766 for line in log.split("\n"):
767 line = line.strip()
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300768 m = re.search (r"^ *\[git-p4: (.*)\]$", line)
769 if not m:
770 continue
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100771
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300772 assignments = m.group(1).split (':')
773 for a in assignments:
774 vals = a.split ('=')
775 key = vals[0].strip()
776 val = ('='.join (vals[1:])).strip()
777 if val.endswith ('\"') and val.startswith('"'):
778 val = val[1:-1]
779
780 values[key] = val
781
Simon Hausmann845b42c2007-06-07 09:19:34 +0200782 paths = values.get("depot-paths")
783 if not paths:
784 paths = values.get("depot-path")
Simon Hausmanna3fdd572007-06-07 22:54:32 +0200785 if paths:
786 values['depot-paths'] = paths.split(',')
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300787 return values
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100788
Simon Hausmann8136a632007-03-22 21:27:14 +0100789def gitBranchExists(branch):
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300790 proc = subprocess.Popen(["git", "rev-parse", branch],
791 stderr=subprocess.PIPE, stdout=subprocess.PIPE);
Simon Hausmanncaace112007-05-15 14:57:57 +0200792 return proc.wait() == 0;
Simon Hausmann8136a632007-03-22 21:27:14 +0100793
Luke Diamand123f6312018-05-23 23:20:26 +0100794def gitUpdateRef(ref, newvalue):
795 subprocess.check_call(["git", "update-ref", ref, newvalue])
796
797def gitDeleteRef(ref):
798 subprocess.check_call(["git", "update-ref", "-d", ref])
799
John Chapman36bd8442008-11-08 14:22:49 +1100800_gitConfig = {}
Pete Wyckoffb345d6c2013-01-26 22:11:23 -0500801
Lars Schneider692e1792015-09-26 09:54:58 +0200802def gitConfig(key, typeSpecifier=None):
Luke Diamanddba1c9d2018-06-19 09:04:07 +0100803 if key not in _gitConfig:
Lars Schneider692e1792015-09-26 09:54:58 +0200804 cmd = [ "git", "config" ]
805 if typeSpecifier:
806 cmd += [ typeSpecifier ]
807 cmd += [ key ]
Pete Wyckoffb345d6c2013-01-26 22:11:23 -0500808 s = read_pipe(cmd, ignore_error=True)
809 _gitConfig[key] = s.strip()
John Chapman36bd8442008-11-08 14:22:49 +1100810 return _gitConfig[key]
Simon Hausmann01265102007-05-25 10:36:10 +0200811
Pete Wyckoff0d609032013-01-26 22:11:24 -0500812def gitConfigBool(key):
813 """Return a bool, using git config --bool. It is True only if the
814 variable is set to true, and False if set to false or not present
815 in the config."""
816
Luke Diamanddba1c9d2018-06-19 09:04:07 +0100817 if key not in _gitConfig:
Lars Schneider692e1792015-09-26 09:54:58 +0200818 _gitConfig[key] = gitConfig(key, '--bool') == "true"
Simon Hausmann062410b2007-07-18 10:56:31 +0200819 return _gitConfig[key]
820
Lars Schneidercb1dafd2015-09-26 09:54:59 +0200821def gitConfigInt(key):
Luke Diamanddba1c9d2018-06-19 09:04:07 +0100822 if key not in _gitConfig:
Lars Schneidercb1dafd2015-09-26 09:54:59 +0200823 cmd = [ "git", "config", "--int", key ]
Simon Hausmannb9847332007-03-20 20:54:23 +0100824 s = read_pipe(cmd, ignore_error=True)
Simon Hausmann062410b2007-07-18 10:56:31 +0200825 v = s.strip()
Lars Schneidercb1dafd2015-09-26 09:54:59 +0200826 try:
827 _gitConfig[key] = int(gitConfig(key, '--int'))
828 except ValueError:
829 _gitConfig[key] = None
Simon Hausmann062410b2007-07-18 10:56:31 +0200830 return _gitConfig[key]
831
Vitor Antunes7199cf12011-08-19 00:44:05 +0100832def gitConfigList(key):
Luke Diamanddba1c9d2018-06-19 09:04:07 +0100833 if key not in _gitConfig:
Pete Wyckoff2abba302013-01-26 22:11:22 -0500834 s = read_pipe(["git", "config", "--get-all", key], ignore_error=True)
George Vanburghc3c2b052017-01-25 09:17:29 +0000835 _gitConfig[key] = s.strip().splitlines()
Lars Schneider7960e702015-09-26 09:55:00 +0200836 if _gitConfig[key] == ['']:
837 _gitConfig[key] = []
Vitor Antunes7199cf12011-08-19 00:44:05 +0100838 return _gitConfig[key]
839
Pete Wyckoff2c8037e2013-01-14 19:46:57 -0500840def p4BranchesInGit(branchesAreInRemotes=True):
841 """Find all the branches whose names start with "p4/", looking
842 in remotes or heads as specified by the argument. Return
843 a dictionary of { branch: revision } for each one found.
844 The branch names are the short names, without any
845 "p4/" prefix."""
846
Simon Hausmann062410b2007-07-18 10:56:31 +0200847 branches = {}
848
849 cmdline = "git rev-parse --symbolic "
850 if branchesAreInRemotes:
Pete Wyckoff2c8037e2013-01-14 19:46:57 -0500851 cmdline += "--remotes"
Simon Hausmann062410b2007-07-18 10:56:31 +0200852 else:
Pete Wyckoff2c8037e2013-01-14 19:46:57 -0500853 cmdline += "--branches"
Simon Hausmann062410b2007-07-18 10:56:31 +0200854
855 for line in read_pipe_lines(cmdline):
856 line = line.strip()
857
Pete Wyckoff2c8037e2013-01-14 19:46:57 -0500858 # only import to p4/
859 if not line.startswith('p4/'):
Simon Hausmann062410b2007-07-18 10:56:31 +0200860 continue
Pete Wyckoff2c8037e2013-01-14 19:46:57 -0500861 # special symbolic ref to p4/master
862 if line == "p4/HEAD":
863 continue
Simon Hausmann062410b2007-07-18 10:56:31 +0200864
Pete Wyckoff2c8037e2013-01-14 19:46:57 -0500865 # strip off p4/ prefix
866 branch = line[len("p4/"):]
Simon Hausmann062410b2007-07-18 10:56:31 +0200867
868 branches[branch] = parseRevision(line)
Pete Wyckoff2c8037e2013-01-14 19:46:57 -0500869
Simon Hausmann062410b2007-07-18 10:56:31 +0200870 return branches
871
Pete Wyckoff5a8e84c2013-01-14 19:47:05 -0500872def branch_exists(branch):
873 """Make sure that the given ref name really exists."""
874
875 cmd = [ "git", "rev-parse", "--symbolic", "--verify", branch ]
876 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
877 out, _ = p.communicate()
878 if p.returncode:
879 return False
880 # expect exactly one line of output: the branch name
881 return out.rstrip() == branch
882
Simon Hausmann9ceab362007-06-22 00:01:57 +0200883def findUpstreamBranchPoint(head = "HEAD"):
Simon Hausmann86506fe2007-07-18 12:40:12 +0200884 branches = p4BranchesInGit()
885 # map from depot-path to branch name
886 branchByDepotPath = {}
887 for branch in branches.keys():
888 tip = branches[branch]
889 log = extractLogMessageFromGitCommit(tip)
890 settings = extractSettingsGitLog(log)
Luke Diamanddba1c9d2018-06-19 09:04:07 +0100891 if "depot-paths" in settings:
Simon Hausmann86506fe2007-07-18 12:40:12 +0200892 paths = ",".join(settings["depot-paths"])
893 branchByDepotPath[paths] = "remotes/p4/" + branch
894
Simon Hausmann27d2d812007-06-12 14:31:59 +0200895 settings = None
Simon Hausmann27d2d812007-06-12 14:31:59 +0200896 parent = 0
897 while parent < 65535:
Simon Hausmann9ceab362007-06-22 00:01:57 +0200898 commit = head + "~%s" % parent
Simon Hausmann27d2d812007-06-12 14:31:59 +0200899 log = extractLogMessageFromGitCommit(commit)
900 settings = extractSettingsGitLog(log)
Luke Diamanddba1c9d2018-06-19 09:04:07 +0100901 if "depot-paths" in settings:
Simon Hausmann86506fe2007-07-18 12:40:12 +0200902 paths = ",".join(settings["depot-paths"])
Luke Diamanddba1c9d2018-06-19 09:04:07 +0100903 if paths in branchByDepotPath:
Simon Hausmann86506fe2007-07-18 12:40:12 +0200904 return [branchByDepotPath[paths], settings]
Simon Hausmann27d2d812007-06-12 14:31:59 +0200905
Simon Hausmann86506fe2007-07-18 12:40:12 +0200906 parent = parent + 1
Simon Hausmann27d2d812007-06-12 14:31:59 +0200907
Simon Hausmann86506fe2007-07-18 12:40:12 +0200908 return ["", settings]
Simon Hausmann27d2d812007-06-12 14:31:59 +0200909
Simon Hausmann5ca44612007-08-24 17:44:16 +0200910def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent=True):
911 if not silent:
Luke Diamandf2606b12018-06-19 09:04:10 +0100912 print("Creating/updating branch(es) in %s based on origin branch(es)"
Simon Hausmann5ca44612007-08-24 17:44:16 +0200913 % localRefPrefix)
914
915 originPrefix = "origin/p4/"
916
917 for line in read_pipe_lines("git rev-parse --symbolic --remotes"):
918 line = line.strip()
919 if (not line.startswith(originPrefix)) or line.endswith("HEAD"):
920 continue
921
922 headName = line[len(originPrefix):]
923 remoteHead = localRefPrefix + headName
924 originHead = line
925
926 original = extractSettingsGitLog(extractLogMessageFromGitCommit(originHead))
Luke Diamanddba1c9d2018-06-19 09:04:07 +0100927 if ('depot-paths' not in original
928 or 'change' not in original):
Simon Hausmann5ca44612007-08-24 17:44:16 +0200929 continue
930
931 update = False
932 if not gitBranchExists(remoteHead):
933 if verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +0100934 print("creating %s" % remoteHead)
Simon Hausmann5ca44612007-08-24 17:44:16 +0200935 update = True
936 else:
937 settings = extractSettingsGitLog(extractLogMessageFromGitCommit(remoteHead))
Luke Diamanddba1c9d2018-06-19 09:04:07 +0100938 if 'change' in settings:
Simon Hausmann5ca44612007-08-24 17:44:16 +0200939 if settings['depot-paths'] == original['depot-paths']:
940 originP4Change = int(original['change'])
941 p4Change = int(settings['change'])
942 if originP4Change > p4Change:
Luke Diamandf2606b12018-06-19 09:04:10 +0100943 print("%s (%s) is newer than %s (%s). "
Simon Hausmann5ca44612007-08-24 17:44:16 +0200944 "Updating p4 branch from origin."
945 % (originHead, originP4Change,
946 remoteHead, p4Change))
947 update = True
948 else:
Luke Diamandf2606b12018-06-19 09:04:10 +0100949 print("Ignoring: %s was imported from %s while "
Simon Hausmann5ca44612007-08-24 17:44:16 +0200950 "%s was imported from %s"
951 % (originHead, ','.join(original['depot-paths']),
952 remoteHead, ','.join(settings['depot-paths'])))
953
954 if update:
955 system("git update-ref %s %s" % (remoteHead, originHead))
956
957def originP4BranchesExist():
958 return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
959
Simon Hausmann4f6432d2007-08-26 15:56:36 +0200960
Luke Diamand1051ef02015-06-10 08:30:59 +0100961def p4ParseNumericChangeRange(parts):
962 changeStart = int(parts[0][1:])
963 if parts[1] == '#head':
964 changeEnd = p4_last_change()
965 else:
966 changeEnd = int(parts[1])
967
968 return (changeStart, changeEnd)
969
970def chooseBlockSize(blockSize):
971 if blockSize:
972 return blockSize
973 else:
974 return defaultBlockSize
975
976def p4ChangesForPaths(depotPaths, changeRange, requestedBlockSize):
977 assert depotPaths
978
979 # Parse the change range into start and end. Try to find integer
980 # revision ranges as these can be broken up into blocks to avoid
981 # hitting server-side limits (maxrows, maxscanresults). But if
982 # that doesn't work, fall back to using the raw revision specifier
983 # strings, without using block mode.
984
Lex Spoon96b2d542015-04-20 11:00:20 -0400985 if changeRange is None or changeRange == '':
Luke Diamand1051ef02015-06-10 08:30:59 +0100986 changeStart = 1
987 changeEnd = p4_last_change()
988 block_size = chooseBlockSize(requestedBlockSize)
Lex Spoon96b2d542015-04-20 11:00:20 -0400989 else:
990 parts = changeRange.split(',')
991 assert len(parts) == 2
Luke Diamand1051ef02015-06-10 08:30:59 +0100992 try:
993 (changeStart, changeEnd) = p4ParseNumericChangeRange(parts)
994 block_size = chooseBlockSize(requestedBlockSize)
Luke Diamand8fa0abf2018-06-08 21:32:47 +0100995 except ValueError:
Luke Diamand1051ef02015-06-10 08:30:59 +0100996 changeStart = parts[0][1:]
997 changeEnd = parts[1]
998 if requestedBlockSize:
999 die("cannot use --changes-block-size with non-numeric revisions")
1000 block_size = None
Lex Spoon96b2d542015-04-20 11:00:20 -04001001
George Vanburgh9943e5b2016-12-17 22:11:23 +00001002 changes = set()
Lex Spoon96b2d542015-04-20 11:00:20 -04001003
Sam Hocevar1f90a642015-12-19 09:39:40 +00001004 # Retrieve changes a block at a time, to prevent running
Luke Diamand3deed5e2018-06-08 21:32:48 +01001005 # into a MaxResults/MaxScanRows error from the server. If
1006 # we _do_ hit one of those errors, turn down the block size
Luke Diamand1051ef02015-06-10 08:30:59 +01001007
Sam Hocevar1f90a642015-12-19 09:39:40 +00001008 while True:
1009 cmd = ['changes']
Luke Diamand1051ef02015-06-10 08:30:59 +01001010
Sam Hocevar1f90a642015-12-19 09:39:40 +00001011 if block_size:
1012 end = min(changeEnd, changeStart + block_size)
1013 revisionRange = "%d,%d" % (changeStart, end)
1014 else:
1015 revisionRange = "%s,%s" % (changeStart, changeEnd)
Luke Diamand1051ef02015-06-10 08:30:59 +01001016
Sam Hocevar1f90a642015-12-19 09:39:40 +00001017 for p in depotPaths:
Luke Diamand1051ef02015-06-10 08:30:59 +01001018 cmd += ["%s...@%s" % (p, revisionRange)]
1019
Luke Diamand3deed5e2018-06-08 21:32:48 +01001020 # fetch the changes
1021 try:
1022 result = p4CmdList(cmd, errors_as_exceptions=True)
1023 except P4RequestSizeException as e:
1024 if not block_size:
1025 block_size = e.limit
1026 elif block_size > e.limit:
1027 block_size = e.limit
1028 else:
1029 block_size = max(2, block_size // 2)
1030
1031 if verbose: print("block size error, retrying with block size {0}".format(block_size))
1032 continue
1033 except P4Exception as e:
1034 die('Error retrieving changes description ({0})'.format(e.p4ExitCode))
1035
Sam Hocevar1f90a642015-12-19 09:39:40 +00001036 # Insert changes in chronological order
Luke Diamand3deed5e2018-06-08 21:32:48 +01001037 for entry in reversed(result):
Luke Diamanddba1c9d2018-06-19 09:04:07 +01001038 if 'change' not in entry:
Miguel Torrojab596b3b2017-07-13 09:00:34 +02001039 continue
1040 changes.add(int(entry['change']))
Luke Diamand1051ef02015-06-10 08:30:59 +01001041
Sam Hocevar1f90a642015-12-19 09:39:40 +00001042 if not block_size:
1043 break
Luke Diamand1051ef02015-06-10 08:30:59 +01001044
Sam Hocevar1f90a642015-12-19 09:39:40 +00001045 if end >= changeEnd:
1046 break
Luke Diamand1051ef02015-06-10 08:30:59 +01001047
Sam Hocevar1f90a642015-12-19 09:39:40 +00001048 changeStart = end + 1
Simon Hausmann4f6432d2007-08-26 15:56:36 +02001049
Sam Hocevar1f90a642015-12-19 09:39:40 +00001050 changes = sorted(changes)
1051 return changes
Simon Hausmann4f6432d2007-08-26 15:56:36 +02001052
Tor Arvid Lundd53de8b2011-03-15 13:08:02 +01001053def p4PathStartsWith(path, prefix):
1054 # This method tries to remedy a potential mixed-case issue:
1055 #
1056 # If UserA adds //depot/DirA/file1
1057 # and UserB adds //depot/dira/file2
1058 #
1059 # we may or may not have a problem. If you have core.ignorecase=true,
1060 # we treat DirA and dira as the same directory
Pete Wyckoff0d609032013-01-26 22:11:24 -05001061 if gitConfigBool("core.ignorecase"):
Tor Arvid Lundd53de8b2011-03-15 13:08:02 +01001062 return path.lower().startswith(prefix.lower())
1063 return path.startswith(prefix)
1064
Pete Wyckoff543987b2012-02-25 20:06:25 -05001065def getClientSpec():
1066 """Look at the p4 client spec, create a View() object that contains
1067 all the mappings, and return it."""
1068
1069 specList = p4CmdList("client -o")
1070 if len(specList) != 1:
1071 die('Output from "client -o" is %d lines, expecting 1' %
1072 len(specList))
1073
1074 # dictionary of all client parameters
1075 entry = specList[0]
1076
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09001077 # the //client/ name
1078 client_name = entry["Client"]
1079
Pete Wyckoff543987b2012-02-25 20:06:25 -05001080 # just the keys that start with "View"
1081 view_keys = [ k for k in entry.keys() if k.startswith("View") ]
1082
1083 # hold this new View
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09001084 view = View(client_name)
Pete Wyckoff543987b2012-02-25 20:06:25 -05001085
1086 # append the lines, in order, to the view
1087 for view_num in range(len(view_keys)):
1088 k = "View%d" % view_num
1089 if k not in view_keys:
1090 die("Expected view key %s missing" % k)
1091 view.append(entry[k])
1092
1093 return view
1094
1095def getClientRoot():
1096 """Grab the client directory."""
1097
1098 output = p4CmdList("client -o")
1099 if len(output) != 1:
1100 die('Output from "client -o" is %d lines, expecting 1' % len(output))
1101
1102 entry = output[0]
1103 if "Root" not in entry:
1104 die('Client has no "Root"')
1105
1106 return entry["Root"]
1107
Pete Wyckoff9d7d4462012-04-29 20:57:17 -04001108#
1109# P4 wildcards are not allowed in filenames. P4 complains
1110# if you simply add them, but you can force it with "-f", in
1111# which case it translates them into %xx encoding internally.
1112#
1113def wildcard_decode(path):
1114 # Search for and fix just these four characters. Do % last so
1115 # that fixing it does not inadvertently create new %-escapes.
1116 # Cannot have * in a filename in windows; untested as to
1117 # what p4 would do in such a case.
1118 if not platform.system() == "Windows":
1119 path = path.replace("%2A", "*")
1120 path = path.replace("%23", "#") \
1121 .replace("%40", "@") \
1122 .replace("%25", "%")
1123 return path
1124
1125def wildcard_encode(path):
1126 # do % first to avoid double-encoding the %s introduced here
1127 path = path.replace("%", "%25") \
1128 .replace("*", "%2A") \
1129 .replace("#", "%23") \
1130 .replace("@", "%40")
1131 return path
1132
1133def wildcard_present(path):
Brandon Casey598354c2013-01-26 11:14:32 -08001134 m = re.search("[*#@%]", path)
1135 return m is not None
Pete Wyckoff9d7d4462012-04-29 20:57:17 -04001136
Lars Schneidera5db4b12015-09-26 09:55:03 +02001137class LargeFileSystem(object):
1138 """Base class for large file system support."""
1139
1140 def __init__(self, writeToGitStream):
1141 self.largeFiles = set()
1142 self.writeToGitStream = writeToGitStream
1143
1144 def generatePointer(self, cloneDestination, contentFile):
1145 """Return the content of a pointer file that is stored in Git instead of
1146 the actual content."""
1147 assert False, "Method 'generatePointer' required in " + self.__class__.__name__
1148
1149 def pushFile(self, localLargeFile):
1150 """Push the actual content which is not stored in the Git repository to
1151 a server."""
1152 assert False, "Method 'pushFile' required in " + self.__class__.__name__
1153
1154 def hasLargeFileExtension(self, relPath):
1155 return reduce(
1156 lambda a, b: a or b,
1157 [relPath.endswith('.' + e) for e in gitConfigList('git-p4.largeFileExtensions')],
1158 False
1159 )
1160
1161 def generateTempFile(self, contents):
1162 contentFile = tempfile.NamedTemporaryFile(prefix='git-p4-large-file', delete=False)
1163 for d in contents:
1164 contentFile.write(d)
1165 contentFile.close()
1166 return contentFile.name
1167
1168 def exceedsLargeFileThreshold(self, relPath, contents):
1169 if gitConfigInt('git-p4.largeFileThreshold'):
1170 contentsSize = sum(len(d) for d in contents)
1171 if contentsSize > gitConfigInt('git-p4.largeFileThreshold'):
1172 return True
1173 if gitConfigInt('git-p4.largeFileCompressedThreshold'):
1174 contentsSize = sum(len(d) for d in contents)
1175 if contentsSize <= gitConfigInt('git-p4.largeFileCompressedThreshold'):
1176 return False
1177 contentTempFile = self.generateTempFile(contents)
Philip.McGrawde5abb52019-08-27 06:43:58 +03001178 compressedContentFile = tempfile.NamedTemporaryFile(prefix='git-p4-large-file', delete=True)
1179 with zipfile.ZipFile(compressedContentFile, mode='w') as zf:
1180 zf.write(contentTempFile, compress_type=zipfile.ZIP_DEFLATED)
1181 compressedContentsSize = zf.infolist()[0].compress_size
Lars Schneidera5db4b12015-09-26 09:55:03 +02001182 os.remove(contentTempFile)
Lars Schneidera5db4b12015-09-26 09:55:03 +02001183 if compressedContentsSize > gitConfigInt('git-p4.largeFileCompressedThreshold'):
1184 return True
1185 return False
1186
1187 def addLargeFile(self, relPath):
1188 self.largeFiles.add(relPath)
1189
1190 def removeLargeFile(self, relPath):
1191 self.largeFiles.remove(relPath)
1192
1193 def isLargeFile(self, relPath):
1194 return relPath in self.largeFiles
1195
1196 def processContent(self, git_mode, relPath, contents):
1197 """Processes the content of git fast import. This method decides if a
1198 file is stored in the large file system and handles all necessary
1199 steps."""
1200 if self.exceedsLargeFileThreshold(relPath, contents) or self.hasLargeFileExtension(relPath):
1201 contentTempFile = self.generateTempFile(contents)
Lars Schneiderd5eb3cf2016-12-04 17:03:37 +01001202 (pointer_git_mode, contents, localLargeFile) = self.generatePointer(contentTempFile)
1203 if pointer_git_mode:
1204 git_mode = pointer_git_mode
1205 if localLargeFile:
1206 # Move temp file to final location in large file system
1207 largeFileDir = os.path.dirname(localLargeFile)
1208 if not os.path.isdir(largeFileDir):
1209 os.makedirs(largeFileDir)
1210 shutil.move(contentTempFile, localLargeFile)
1211 self.addLargeFile(relPath)
1212 if gitConfigBool('git-p4.largeFilePush'):
1213 self.pushFile(localLargeFile)
1214 if verbose:
1215 sys.stderr.write("%s moved to large file system (%s)\n" % (relPath, localLargeFile))
Lars Schneidera5db4b12015-09-26 09:55:03 +02001216 return (git_mode, contents)
1217
1218class MockLFS(LargeFileSystem):
1219 """Mock large file system for testing."""
1220
1221 def generatePointer(self, contentFile):
1222 """The pointer content is the original content prefixed with "pointer-".
1223 The local filename of the large file storage is derived from the file content.
1224 """
1225 with open(contentFile, 'r') as f:
1226 content = next(f)
1227 gitMode = '100644'
1228 pointerContents = 'pointer-' + content
1229 localLargeFile = os.path.join(os.getcwd(), '.git', 'mock-storage', 'local', content[:-1])
1230 return (gitMode, pointerContents, localLargeFile)
1231
1232 def pushFile(self, localLargeFile):
1233 """The remote filename of the large file storage is the same as the local
1234 one but in a different directory.
1235 """
1236 remotePath = os.path.join(os.path.dirname(localLargeFile), '..', 'remote')
1237 if not os.path.exists(remotePath):
1238 os.makedirs(remotePath)
1239 shutil.copyfile(localLargeFile, os.path.join(remotePath, os.path.basename(localLargeFile)))
1240
Lars Schneiderb47d8072015-09-26 09:55:04 +02001241class GitLFS(LargeFileSystem):
1242 """Git LFS as backend for the git-p4 large file system.
1243 See https://git-lfs.github.com/ for details."""
1244
1245 def __init__(self, *args):
1246 LargeFileSystem.__init__(self, *args)
1247 self.baseGitAttributes = []
1248
1249 def generatePointer(self, contentFile):
1250 """Generate a Git LFS pointer for the content. Return LFS Pointer file
1251 mode and content which is stored in the Git repository instead of
1252 the actual content. Return also the new location of the actual
1253 content.
1254 """
Lars Schneiderd5eb3cf2016-12-04 17:03:37 +01001255 if os.path.getsize(contentFile) == 0:
1256 return (None, '', None)
1257
Lars Schneiderb47d8072015-09-26 09:55:04 +02001258 pointerProcess = subprocess.Popen(
1259 ['git', 'lfs', 'pointer', '--file=' + contentFile],
1260 stdout=subprocess.PIPE
1261 )
1262 pointerFile = pointerProcess.stdout.read()
1263 if pointerProcess.wait():
1264 os.remove(contentFile)
1265 die('git-lfs pointer command failed. Did you install the extension?')
Lars Schneider82f25672016-04-28 08:26:33 +02001266
1267 # Git LFS removed the preamble in the output of the 'pointer' command
1268 # starting from version 1.2.0. Check for the preamble here to support
1269 # earlier versions.
1270 # c.f. https://github.com/github/git-lfs/commit/da2935d9a739592bc775c98d8ef4df9c72ea3b43
1271 if pointerFile.startswith('Git LFS pointer for'):
1272 pointerFile = re.sub(r'Git LFS pointer for.*\n\n', '', pointerFile)
1273
1274 oid = re.search(r'^oid \w+:(\w+)', pointerFile, re.MULTILINE).group(1)
r.burenkovea94b162019-12-11 09:47:23 -08001275 # if someone use external lfs.storage ( not in local repo git )
1276 lfs_path = gitConfig('lfs.storage')
1277 if not lfs_path:
1278 lfs_path = 'lfs'
1279 if not os.path.isabs(lfs_path):
1280 lfs_path = os.path.join(os.getcwd(), '.git', lfs_path)
Lars Schneiderb47d8072015-09-26 09:55:04 +02001281 localLargeFile = os.path.join(
r.burenkovea94b162019-12-11 09:47:23 -08001282 lfs_path,
1283 'objects', oid[:2], oid[2:4],
Lars Schneiderb47d8072015-09-26 09:55:04 +02001284 oid,
1285 )
1286 # LFS Spec states that pointer files should not have the executable bit set.
1287 gitMode = '100644'
Lars Schneider82f25672016-04-28 08:26:33 +02001288 return (gitMode, pointerFile, localLargeFile)
Lars Schneiderb47d8072015-09-26 09:55:04 +02001289
1290 def pushFile(self, localLargeFile):
1291 uploadProcess = subprocess.Popen(
1292 ['git', 'lfs', 'push', '--object-id', 'origin', os.path.basename(localLargeFile)]
1293 )
1294 if uploadProcess.wait():
1295 die('git-lfs push command failed. Did you define a remote?')
1296
1297 def generateGitAttributes(self):
1298 return (
1299 self.baseGitAttributes +
1300 [
1301 '\n',
1302 '#\n',
1303 '# Git LFS (see https://git-lfs.github.com/)\n',
1304 '#\n',
1305 ] +
Lars Schneider862f9312016-12-18 20:01:40 +01001306 ['*.' + f.replace(' ', '[[:space:]]') + ' filter=lfs diff=lfs merge=lfs -text\n'
Lars Schneiderb47d8072015-09-26 09:55:04 +02001307 for f in sorted(gitConfigList('git-p4.largeFileExtensions'))
1308 ] +
Lars Schneider862f9312016-12-18 20:01:40 +01001309 ['/' + f.replace(' ', '[[:space:]]') + ' filter=lfs diff=lfs merge=lfs -text\n'
Lars Schneiderb47d8072015-09-26 09:55:04 +02001310 for f in sorted(self.largeFiles) if not self.hasLargeFileExtension(f)
1311 ]
1312 )
1313
1314 def addLargeFile(self, relPath):
1315 LargeFileSystem.addLargeFile(self, relPath)
1316 self.writeToGitStream('100644', '.gitattributes', self.generateGitAttributes())
1317
1318 def removeLargeFile(self, relPath):
1319 LargeFileSystem.removeLargeFile(self, relPath)
1320 self.writeToGitStream('100644', '.gitattributes', self.generateGitAttributes())
1321
1322 def processContent(self, git_mode, relPath, contents):
1323 if relPath == '.gitattributes':
1324 self.baseGitAttributes = contents
1325 return (git_mode, self.generateGitAttributes())
1326 else:
1327 return LargeFileSystem.processContent(self, git_mode, relPath, contents)
1328
Simon Hausmannb9847332007-03-20 20:54:23 +01001329class Command:
Luke Diamand89143ac2018-10-15 12:14:08 +01001330 delete_actions = ( "delete", "move/delete", "purge" )
Simon Williams0108f472019-05-22 07:21:20 +01001331 add_actions = ( "add", "branch", "move/add" )
Luke Diamand89143ac2018-10-15 12:14:08 +01001332
Simon Hausmannb9847332007-03-20 20:54:23 +01001333 def __init__(self):
1334 self.usage = "usage: %prog [options]"
Simon Hausmann8910ac02007-03-26 08:18:55 +02001335 self.needsGit = True
Luke Diamand6a10b6a2012-04-24 09:33:23 +01001336 self.verbose = False
Simon Hausmannb9847332007-03-20 20:54:23 +01001337
Mazo, Andreyff8c50e2019-04-01 18:02:26 +00001338 # This is required for the "append" update_shelve action
Luke Diamand8cf422d2017-12-21 11:06:14 +00001339 def ensure_value(self, attr, value):
1340 if not hasattr(self, attr) or getattr(self, attr) is None:
1341 setattr(self, attr, value)
1342 return getattr(self, attr)
1343
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001344class P4UserMap:
1345 def __init__(self):
1346 self.userMapFromPerforceServer = False
Luke Diamandaffb4742012-01-19 09:52:27 +00001347 self.myP4UserId = None
1348
1349 def p4UserId(self):
1350 if self.myP4UserId:
1351 return self.myP4UserId
1352
1353 results = p4CmdList("user -o")
1354 for r in results:
Luke Diamanddba1c9d2018-06-19 09:04:07 +01001355 if 'User' in r:
Luke Diamandaffb4742012-01-19 09:52:27 +00001356 self.myP4UserId = r['User']
1357 return r['User']
1358 die("Could not find your p4 user id")
1359
1360 def p4UserIsMe(self, p4User):
1361 # return True if the given p4 user is actually me
1362 me = self.p4UserId()
1363 if not p4User or p4User != me:
1364 return False
1365 else:
1366 return True
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001367
1368 def getUserCacheFilename(self):
1369 home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
1370 return home + "/.gitp4-usercache.txt"
1371
1372 def getUserMapFromPerforceServer(self):
1373 if self.userMapFromPerforceServer:
1374 return
1375 self.users = {}
1376 self.emails = {}
1377
1378 for output in p4CmdList("users"):
Luke Diamanddba1c9d2018-06-19 09:04:07 +01001379 if "User" not in output:
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001380 continue
1381 self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
1382 self.emails[output["Email"]] = output["User"]
1383
Lars Schneider10d08a12016-03-01 11:49:56 +01001384 mapUserConfigRegex = re.compile(r"^\s*(\S+)\s*=\s*(.+)\s*<(\S+)>\s*$", re.VERBOSE)
1385 for mapUserConfig in gitConfigList("git-p4.mapUser"):
1386 mapUser = mapUserConfigRegex.findall(mapUserConfig)
1387 if mapUser and len(mapUser[0]) == 3:
1388 user = mapUser[0][0]
1389 fullname = mapUser[0][1]
1390 email = mapUser[0][2]
1391 self.users[user] = fullname + " <" + email + ">"
1392 self.emails[email] = user
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001393
1394 s = ''
1395 for (key, val) in self.users.items():
1396 s += "%s\t%s\n" % (key.expandtabs(1), val.expandtabs(1))
1397
1398 open(self.getUserCacheFilename(), "wb").write(s)
1399 self.userMapFromPerforceServer = True
1400
1401 def loadUserMapFromCache(self):
1402 self.users = {}
1403 self.userMapFromPerforceServer = False
1404 try:
1405 cache = open(self.getUserCacheFilename(), "rb")
1406 lines = cache.readlines()
1407 cache.close()
1408 for line in lines:
1409 entry = line.strip().split("\t")
1410 self.users[entry[0]] = entry[1]
1411 except IOError:
1412 self.getUserMapFromPerforceServer()
1413
Simon Hausmannb9847332007-03-20 20:54:23 +01001414class P4Debug(Command):
Simon Hausmann86949ee2007-03-19 20:59:12 +01001415 def __init__(self):
Simon Hausmann6ae8de82007-03-22 21:10:25 +01001416 Command.__init__(self)
Luke Diamand6a10b6a2012-04-24 09:33:23 +01001417 self.options = []
Simon Hausmannc8c39112007-03-19 21:02:30 +01001418 self.description = "A tool to debug the output of p4 -G."
Simon Hausmann8910ac02007-03-26 08:18:55 +02001419 self.needsGit = False
Simon Hausmann86949ee2007-03-19 20:59:12 +01001420
1421 def run(self, args):
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -03001422 j = 0
Luke Diamand6de040d2011-10-16 10:47:52 -04001423 for output in p4CmdList(args):
Luke Diamandf2606b12018-06-19 09:04:10 +01001424 print('Element: %d' % j)
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -03001425 j += 1
Luke Diamandf2606b12018-06-19 09:04:10 +01001426 print(output)
Simon Hausmannb9847332007-03-20 20:54:23 +01001427 return True
Simon Hausmann86949ee2007-03-19 20:59:12 +01001428
Simon Hausmann58346842007-05-21 22:57:06 +02001429class P4RollBack(Command):
1430 def __init__(self):
1431 Command.__init__(self)
1432 self.options = [
Simon Hausmann0c66a782007-05-23 20:07:57 +02001433 optparse.make_option("--local", dest="rollbackLocalBranches", action="store_true")
Simon Hausmann58346842007-05-21 22:57:06 +02001434 ]
1435 self.description = "A tool to debug the multi-branch import. Don't use :)"
Simon Hausmann0c66a782007-05-23 20:07:57 +02001436 self.rollbackLocalBranches = False
Simon Hausmann58346842007-05-21 22:57:06 +02001437
1438 def run(self, args):
1439 if len(args) != 1:
1440 return False
1441 maxChange = int(args[0])
Simon Hausmann0c66a782007-05-23 20:07:57 +02001442
Simon Hausmannad192f22007-05-23 23:44:19 +02001443 if "p4ExitCode" in p4Cmd("changes -m 1"):
Simon Hausmann66a2f522007-05-23 23:40:48 +02001444 die("Problems executing p4");
1445
Simon Hausmann0c66a782007-05-23 20:07:57 +02001446 if self.rollbackLocalBranches:
1447 refPrefix = "refs/heads/"
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -03001448 lines = read_pipe_lines("git rev-parse --symbolic --branches")
Simon Hausmann0c66a782007-05-23 20:07:57 +02001449 else:
1450 refPrefix = "refs/remotes/"
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -03001451 lines = read_pipe_lines("git rev-parse --symbolic --remotes")
Simon Hausmann0c66a782007-05-23 20:07:57 +02001452
1453 for line in lines:
1454 if self.rollbackLocalBranches or (line.startswith("p4/") and line != "p4/HEAD\n"):
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -03001455 line = line.strip()
1456 ref = refPrefix + line
Simon Hausmann58346842007-05-21 22:57:06 +02001457 log = extractLogMessageFromGitCommit(ref)
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001458 settings = extractSettingsGitLog(log)
1459
1460 depotPaths = settings['depot-paths']
1461 change = settings['change']
1462
Simon Hausmann58346842007-05-21 22:57:06 +02001463 changed = False
Simon Hausmann52102d42007-05-21 23:44:24 +02001464
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001465 if len(p4Cmd("changes -m 1 " + ' '.join (['%s...@%s' % (p, maxChange)
1466 for p in depotPaths]))) == 0:
Luke Diamandf2606b12018-06-19 09:04:10 +01001467 print("Branch %s did not exist at change %s, deleting." % (ref, maxChange))
Simon Hausmann52102d42007-05-21 23:44:24 +02001468 system("git update-ref -d %s `git rev-parse %s`" % (ref, ref))
1469 continue
1470
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001471 while change and int(change) > maxChange:
Simon Hausmann58346842007-05-21 22:57:06 +02001472 changed = True
Simon Hausmann52102d42007-05-21 23:44:24 +02001473 if self.verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01001474 print("%s is at %s ; rewinding towards %s" % (ref, change, maxChange))
Simon Hausmann58346842007-05-21 22:57:06 +02001475 system("git update-ref %s \"%s^\"" % (ref, ref))
1476 log = extractLogMessageFromGitCommit(ref)
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001477 settings = extractSettingsGitLog(log)
1478
1479
1480 depotPaths = settings['depot-paths']
1481 change = settings['change']
Simon Hausmann58346842007-05-21 22:57:06 +02001482
1483 if changed:
Luke Diamandf2606b12018-06-19 09:04:10 +01001484 print("%s rewound to %s" % (ref, change))
Simon Hausmann58346842007-05-21 22:57:06 +02001485
1486 return True
1487
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001488class P4Submit(Command, P4UserMap):
Pete Wyckoff6bbfd132012-09-09 16:16:13 -04001489
1490 conflict_behavior_choices = ("ask", "skip", "quit")
1491
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001492 def __init__(self):
Simon Hausmannb9847332007-03-20 20:54:23 +01001493 Command.__init__(self)
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001494 P4UserMap.__init__(self)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001495 self.options = [
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001496 optparse.make_option("--origin", dest="origin"),
Vitor Antunesae901092011-02-20 01:18:24 +00001497 optparse.make_option("-M", dest="detectRenames", action="store_true"),
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001498 # preserve the user, requires relevant p4 permissions
1499 optparse.make_option("--preserve-user", dest="preserveUser", action="store_true"),
Luke Diamand06804c72012-04-11 17:21:24 +02001500 optparse.make_option("--export-labels", dest="exportLabels", action="store_true"),
Pete Wyckoffef739f02012-09-09 16:16:11 -04001501 optparse.make_option("--dry-run", "-n", dest="dry_run", action="store_true"),
Pete Wyckoff728b7ad2012-09-09 16:16:12 -04001502 optparse.make_option("--prepare-p4-only", dest="prepare_p4_only", action="store_true"),
Pete Wyckoff6bbfd132012-09-09 16:16:13 -04001503 optparse.make_option("--conflict", dest="conflict_behavior",
Pete Wyckoff44e8d262013-01-14 19:47:08 -05001504 choices=self.conflict_behavior_choices),
1505 optparse.make_option("--branch", dest="branch"),
Vinicius Kursancewb34fa572016-11-28 09:33:18 +00001506 optparse.make_option("--shelve", dest="shelve", action="store_true",
1507 help="Shelve instead of submit. Shelved files are reverted, "
1508 "restoring the workspace to the state before the shelve"),
Luke Diamand8cf422d2017-12-21 11:06:14 +00001509 optparse.make_option("--update-shelve", dest="update_shelve", action="append", type="int",
Luke Diamand46c609e2016-12-02 22:43:19 +00001510 metavar="CHANGELIST",
Luke Diamand8cf422d2017-12-21 11:06:14 +00001511 help="update an existing shelved changelist, implies --shelve, "
Romain Merlandf55b87c2018-06-01 09:46:14 +02001512 "repeat in-order for multiple shelved changelists"),
1513 optparse.make_option("--commit", dest="commit", metavar="COMMIT",
1514 help="submit only the specified commit(s), one commit or xxx..xxx"),
1515 optparse.make_option("--disable-rebase", dest="disable_rebase", action="store_true",
1516 help="Disable rebase after submit is completed. Can be useful if you "
Luke Diamandb9d34db2018-06-08 21:32:44 +01001517 "work from a local git branch that is not master"),
1518 optparse.make_option("--disable-p4sync", dest="disable_p4sync", action="store_true",
1519 help="Skip Perforce sync of p4/master after submit or shelve"),
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001520 ]
Chen Bin251c8c52018-07-27 21:22:22 +10001521 self.description = """Submit changes from git to the perforce depot.\n
1522 The `p4-pre-submit` hook is executed if it exists and is executable.
1523 The hook takes no parameters and nothing from standard input. Exiting with
1524 non-zero status from this script prevents `git-p4 submit` from launching.
1525
1526 One usage scenario is to run unit tests in the hook."""
1527
Simon Hausmannc9b50e62007-03-29 19:15:24 +02001528 self.usage += " [name of git branch to submit into perforce depot]"
Simon Hausmann95124972007-03-23 09:16:07 +01001529 self.origin = ""
Vitor Antunesae901092011-02-20 01:18:24 +00001530 self.detectRenames = False
Pete Wyckoff0d609032013-01-26 22:11:24 -05001531 self.preserveUser = gitConfigBool("git-p4.preserveUser")
Pete Wyckoffef739f02012-09-09 16:16:11 -04001532 self.dry_run = False
Vinicius Kursancewb34fa572016-11-28 09:33:18 +00001533 self.shelve = False
Luke Diamand8cf422d2017-12-21 11:06:14 +00001534 self.update_shelve = list()
Romain Merlandf55b87c2018-06-01 09:46:14 +02001535 self.commit = ""
Luke Diamand3b3477e2018-06-08 21:32:43 +01001536 self.disable_rebase = gitConfigBool("git-p4.disableRebase")
Luke Diamandb9d34db2018-06-08 21:32:44 +01001537 self.disable_p4sync = gitConfigBool("git-p4.disableP4Sync")
Pete Wyckoff728b7ad2012-09-09 16:16:12 -04001538 self.prepare_p4_only = False
Pete Wyckoff6bbfd132012-09-09 16:16:13 -04001539 self.conflict_behavior = None
Marius Storm-Olsenf7baba82007-06-07 14:07:01 +02001540 self.isWindows = (platform.system() == "Windows")
Luke Diamand06804c72012-04-11 17:21:24 +02001541 self.exportLabels = False
Pete Wyckoff249da4c2012-11-23 17:35:35 -05001542 self.p4HasMoveCommand = p4_has_move_command()
Pete Wyckoff44e8d262013-01-14 19:47:08 -05001543 self.branch = None
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001544
Lars Schneidera5db4b12015-09-26 09:55:03 +02001545 if gitConfig('git-p4.largeFileSystem'):
1546 die("Large file system not supported for git-p4 submit command. Please remove it from config.")
1547
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001548 def check(self):
1549 if len(p4CmdList("opened ...")) > 0:
1550 die("You have files opened with perforce! Close them before starting the sync.")
1551
Pete Wyckofff19cb0a2012-07-04 09:34:20 -04001552 def separate_jobs_from_description(self, message):
1553 """Extract and return a possible Jobs field in the commit
1554 message. It goes into a separate section in the p4 change
1555 specification.
1556
1557 A jobs line starts with "Jobs:" and looks like a new field
1558 in a form. Values are white-space separated on the same
1559 line or on following lines that start with a tab.
1560
1561 This does not parse and extract the full git commit message
1562 like a p4 form. It just sees the Jobs: line as a marker
1563 to pass everything from then on directly into the p4 form,
1564 but outside the description section.
1565
1566 Return a tuple (stripped log message, jobs string)."""
1567
1568 m = re.search(r'^Jobs:', message, re.MULTILINE)
1569 if m is None:
1570 return (message, None)
1571
1572 jobtext = message[m.start():]
1573 stripped_message = message[:m.start()].rstrip()
1574 return (stripped_message, jobtext)
1575
1576 def prepareLogMessage(self, template, message, jobs):
1577 """Edits the template returned from "p4 change -o" to insert
1578 the message in the Description field, and the jobs text in
1579 the Jobs field."""
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001580 result = ""
1581
Simon Hausmannedae1e22008-02-19 09:29:06 +01001582 inDescriptionSection = False
1583
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001584 for line in template.split("\n"):
1585 if line.startswith("#"):
1586 result += line + "\n"
1587 continue
1588
Simon Hausmannedae1e22008-02-19 09:29:06 +01001589 if inDescriptionSection:
Michael Horowitzc9dbab02011-02-25 21:31:13 -05001590 if line.startswith("Files:") or line.startswith("Jobs:"):
Simon Hausmannedae1e22008-02-19 09:29:06 +01001591 inDescriptionSection = False
Pete Wyckofff19cb0a2012-07-04 09:34:20 -04001592 # insert Jobs section
1593 if jobs:
1594 result += jobs + "\n"
Simon Hausmannedae1e22008-02-19 09:29:06 +01001595 else:
1596 continue
1597 else:
1598 if line.startswith("Description:"):
1599 inDescriptionSection = True
1600 line += "\n"
1601 for messageLine in message.split("\n"):
1602 line += "\t" + messageLine + "\n"
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001603
Simon Hausmannedae1e22008-02-19 09:29:06 +01001604 result += line + "\n"
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001605
1606 return result
1607
Luke Diamand60df0712012-02-23 07:51:30 +00001608 def patchRCSKeywords(self, file, pattern):
1609 # Attempt to zap the RCS keywords in a p4 controlled file matching the given pattern
1610 (handle, outFileName) = tempfile.mkstemp(dir='.')
1611 try:
1612 outFile = os.fdopen(handle, "w+")
1613 inFile = open(file, "r")
1614 regexp = re.compile(pattern, re.VERBOSE)
1615 for line in inFile.readlines():
1616 line = regexp.sub(r'$\1$', line)
1617 outFile.write(line)
1618 inFile.close()
1619 outFile.close()
1620 # Forcibly overwrite the original file
1621 os.unlink(file)
1622 shutil.move(outFileName, file)
1623 except:
1624 # cleanup our temporary file
1625 os.unlink(outFileName)
Luke Diamandf2606b12018-06-19 09:04:10 +01001626 print("Failed to strip RCS keywords in %s" % file)
Luke Diamand60df0712012-02-23 07:51:30 +00001627 raise
1628
Luke Diamandf2606b12018-06-19 09:04:10 +01001629 print("Patched up RCS keywords in %s" % file)
Luke Diamand60df0712012-02-23 07:51:30 +00001630
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001631 def p4UserForCommit(self,id):
1632 # Return the tuple (perforce user,git email) for a given git commit id
1633 self.getUserMapFromPerforceServer()
Pete Wyckoff9bf28852013-01-26 22:11:20 -05001634 gitEmail = read_pipe(["git", "log", "--max-count=1",
1635 "--format=%ae", id])
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001636 gitEmail = gitEmail.strip()
Luke Diamanddba1c9d2018-06-19 09:04:07 +01001637 if gitEmail not in self.emails:
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001638 return (None,gitEmail)
1639 else:
1640 return (self.emails[gitEmail],gitEmail)
1641
1642 def checkValidP4Users(self,commits):
1643 # check if any git authors cannot be mapped to p4 users
1644 for id in commits:
1645 (user,email) = self.p4UserForCommit(id)
1646 if not user:
1647 msg = "Cannot find p4 user for email %s in commit %s." % (email, id)
Pete Wyckoff0d609032013-01-26 22:11:24 -05001648 if gitConfigBool("git-p4.allowMissingP4Users"):
Luke Diamandf2606b12018-06-19 09:04:10 +01001649 print("%s" % msg)
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001650 else:
1651 die("Error: %s\nSet git-p4.allowMissingP4Users to true to allow this." % msg)
1652
1653 def lastP4Changelist(self):
1654 # Get back the last changelist number submitted in this client spec. This
1655 # then gets used to patch up the username in the change. If the same
1656 # client spec is being used by multiple processes then this might go
1657 # wrong.
1658 results = p4CmdList("client -o") # find the current client
1659 client = None
1660 for r in results:
Luke Diamanddba1c9d2018-06-19 09:04:07 +01001661 if 'Client' in r:
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001662 client = r['Client']
1663 break
1664 if not client:
1665 die("could not get client spec")
Luke Diamand6de040d2011-10-16 10:47:52 -04001666 results = p4CmdList(["changes", "-c", client, "-m", "1"])
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001667 for r in results:
Luke Diamanddba1c9d2018-06-19 09:04:07 +01001668 if 'change' in r:
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001669 return r['change']
1670 die("Could not get changelist number for last submit - cannot patch up user details")
1671
1672 def modifyChangelistUser(self, changelist, newUser):
1673 # fixup the user field of a changelist after it has been submitted.
1674 changes = p4CmdList("change -o %s" % changelist)
Luke Diamandecdba362011-05-07 11:19:43 +01001675 if len(changes) != 1:
1676 die("Bad output from p4 change modifying %s to user %s" %
1677 (changelist, newUser))
1678
1679 c = changes[0]
1680 if c['User'] == newUser: return # nothing to do
1681 c['User'] = newUser
1682 input = marshal.dumps(c)
1683
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001684 result = p4CmdList("change -f -i", stdin=input)
1685 for r in result:
Luke Diamanddba1c9d2018-06-19 09:04:07 +01001686 if 'code' in r:
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001687 if r['code'] == 'error':
1688 die("Could not modify user field of changelist %s to %s:%s" % (changelist, newUser, r['data']))
Luke Diamanddba1c9d2018-06-19 09:04:07 +01001689 if 'data' in r:
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001690 print("Updated user field for changelist %s to %s" % (changelist, newUser))
1691 return
1692 die("Could not modify user field of changelist %s to %s" % (changelist, newUser))
1693
1694 def canChangeChangelists(self):
1695 # check to see if we have p4 admin or super-user permissions, either of
1696 # which are required to modify changelists.
Luke Diamand52a48802012-01-19 09:52:25 +00001697 results = p4CmdList(["protects", self.depotPath])
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001698 for r in results:
Luke Diamanddba1c9d2018-06-19 09:04:07 +01001699 if 'perm' in r:
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001700 if r['perm'] == 'admin':
1701 return 1
1702 if r['perm'] == 'super':
1703 return 1
1704 return 0
1705
Luke Diamand46c609e2016-12-02 22:43:19 +00001706 def prepareSubmitTemplate(self, changelist=None):
Pete Wyckofff19cb0a2012-07-04 09:34:20 -04001707 """Run "p4 change -o" to grab a change specification template.
1708 This does not use "p4 -G", as it is nice to keep the submission
1709 template in original order, since a human might edit it.
1710
1711 Remove lines in the Files section that show changes to files
1712 outside the depot path we're committing into."""
1713
Sam Hocevarcbc69242015-12-19 09:39:39 +00001714 [upstream, settings] = findUpstreamBranchPoint()
1715
Miguel Torrojab596b3b2017-07-13 09:00:34 +02001716 template = """\
1717# A Perforce Change Specification.
1718#
1719# Change: The change number. 'new' on a new changelist.
1720# Date: The date this specification was last modified.
1721# Client: The client on which the changelist was created. Read-only.
1722# User: The user who created the changelist.
1723# Status: Either 'pending' or 'submitted'. Read-only.
1724# Type: Either 'public' or 'restricted'. Default is 'public'.
1725# Description: Comments about the changelist. Required.
1726# Jobs: What opened jobs are to be closed by this changelist.
1727# You may delete jobs from this list. (New changelists only.)
1728# Files: What opened files from the default changelist are to be added
1729# to this changelist. You may delete files from this list.
1730# (New changelists only.)
1731"""
1732 files_list = []
Simon Hausmannea99c3a2007-08-08 17:06:55 +02001733 inFilesSection = False
Miguel Torrojab596b3b2017-07-13 09:00:34 +02001734 change_entry = None
Luke Diamand46c609e2016-12-02 22:43:19 +00001735 args = ['change', '-o']
1736 if changelist:
1737 args.append(str(changelist))
Miguel Torrojab596b3b2017-07-13 09:00:34 +02001738 for entry in p4CmdList(args):
Luke Diamanddba1c9d2018-06-19 09:04:07 +01001739 if 'code' not in entry:
Miguel Torrojab596b3b2017-07-13 09:00:34 +02001740 continue
1741 if entry['code'] == 'stat':
1742 change_entry = entry
1743 break
1744 if not change_entry:
1745 die('Failed to decode output of p4 change -o')
1746 for key, value in change_entry.iteritems():
1747 if key.startswith('File'):
Luke Diamanddba1c9d2018-06-19 09:04:07 +01001748 if 'depot-paths' in settings:
Miguel Torrojab596b3b2017-07-13 09:00:34 +02001749 if not [p for p in settings['depot-paths']
1750 if p4PathStartsWith(value, p)]:
1751 continue
Simon Hausmannea99c3a2007-08-08 17:06:55 +02001752 else:
Miguel Torrojab596b3b2017-07-13 09:00:34 +02001753 if not p4PathStartsWith(value, self.depotPath):
1754 continue
1755 files_list.append(value)
1756 continue
1757 # Output in the order expected by prepareLogMessage
1758 for key in ['Change', 'Client', 'User', 'Status', 'Description', 'Jobs']:
Luke Diamanddba1c9d2018-06-19 09:04:07 +01001759 if key not in change_entry:
Miguel Torrojab596b3b2017-07-13 09:00:34 +02001760 continue
1761 template += '\n'
1762 template += key + ':'
1763 if key == 'Description':
1764 template += '\n'
1765 for field_line in change_entry[key].splitlines():
1766 template += '\t'+field_line+'\n'
1767 if len(files_list) > 0:
1768 template += '\n'
1769 template += 'Files:\n'
1770 for path in files_list:
1771 template += '\t'+path+'\n'
Simon Hausmannea99c3a2007-08-08 17:06:55 +02001772 return template
1773
Pete Wyckoff7c766e52011-12-04 19:22:45 -05001774 def edit_template(self, template_file):
1775 """Invoke the editor to let the user change the submission
1776 message. Return true if okay to continue with the submit."""
1777
1778 # if configured to skip the editing part, just submit
Pete Wyckoff0d609032013-01-26 22:11:24 -05001779 if gitConfigBool("git-p4.skipSubmitEdit"):
Pete Wyckoff7c766e52011-12-04 19:22:45 -05001780 return True
1781
1782 # look at the modification time, to check later if the user saved
1783 # the file
1784 mtime = os.stat(template_file).st_mtime
1785
1786 # invoke the editor
Luke Diamanddba1c9d2018-06-19 09:04:07 +01001787 if "P4EDITOR" in os.environ and (os.environ.get("P4EDITOR") != ""):
Pete Wyckoff7c766e52011-12-04 19:22:45 -05001788 editor = os.environ.get("P4EDITOR")
1789 else:
1790 editor = read_pipe("git var GIT_EDITOR").strip()
Luke Diamand2dade7a2015-05-19 23:23:17 +01001791 system(["sh", "-c", ('%s "$@"' % editor), editor, template_file])
Pete Wyckoff7c766e52011-12-04 19:22:45 -05001792
1793 # If the file was not saved, prompt to see if this patch should
1794 # be skipped. But skip this verification step if configured so.
Pete Wyckoff0d609032013-01-26 22:11:24 -05001795 if gitConfigBool("git-p4.skipSubmitEditCheck"):
Pete Wyckoff7c766e52011-12-04 19:22:45 -05001796 return True
1797
Pete Wyckoffd1652042011-12-17 12:39:03 -05001798 # modification time updated means user saved the file
1799 if os.stat(template_file).st_mtime > mtime:
1800 return True
1801
Ben Keenee2aed5f2019-12-16 14:02:19 +00001802 response = prompt("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ")
1803 if response == 'y':
1804 return True
1805 if response == 'n':
1806 return False
Pete Wyckoff7c766e52011-12-04 19:22:45 -05001807
Luke Diamanddf8a9e82016-12-17 01:00:40 +00001808 def get_diff_description(self, editedFiles, filesToAdd, symlinks):
Maxime Costeb4073bb2014-05-24 18:40:35 +01001809 # diff
Luke Diamanddba1c9d2018-06-19 09:04:07 +01001810 if "P4DIFF" in os.environ:
Maxime Costeb4073bb2014-05-24 18:40:35 +01001811 del(os.environ["P4DIFF"])
1812 diff = ""
1813 for editedFile in editedFiles:
1814 diff += p4_read_pipe(['diff', '-du',
1815 wildcard_encode(editedFile)])
1816
1817 # new file diff
1818 newdiff = ""
1819 for newFile in filesToAdd:
1820 newdiff += "==== new file ====\n"
1821 newdiff += "--- /dev/null\n"
1822 newdiff += "+++ %s\n" % newFile
Luke Diamanddf8a9e82016-12-17 01:00:40 +00001823
1824 is_link = os.path.islink(newFile)
1825 expect_link = newFile in symlinks
1826
1827 if is_link and expect_link:
1828 newdiff += "+%s\n" % os.readlink(newFile)
1829 else:
1830 f = open(newFile, "r")
1831 for line in f.readlines():
1832 newdiff += "+" + line
1833 f.close()
Maxime Costeb4073bb2014-05-24 18:40:35 +01001834
Maxime Costee2a892e2014-06-11 14:09:59 +01001835 return (diff + newdiff).replace('\r\n', '\n')
Maxime Costeb4073bb2014-05-24 18:40:35 +01001836
Han-Wen Nienhuys7cb5cbe2007-05-23 16:55:48 -03001837 def applyCommit(self, id):
Pete Wyckoff67b0fe22012-09-09 16:16:03 -04001838 """Apply one commit, return True if it succeeded."""
1839
Luke Diamandf2606b12018-06-19 09:04:10 +01001840 print("Applying", read_pipe(["git", "show", "-s",
1841 "--format=format:%h %s", id]))
Vitor Antunesae901092011-02-20 01:18:24 +00001842
Luke Diamand848de9c2011-05-13 20:46:00 +01001843 (p4User, gitEmail) = self.p4UserForCommit(id)
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001844
Gary Gibbons84cb0002012-07-04 09:40:19 -04001845 diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (self.diffOpts, id, id))
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001846 filesToAdd = set()
Romain Picarda02b8bc2016-01-12 13:43:47 +01001847 filesToChangeType = set()
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001848 filesToDelete = set()
Simon Hausmannd336c152007-05-16 09:41:26 +02001849 editedFiles = set()
Pete Wyckoffb6ad6dc2012-04-29 20:57:16 -04001850 pureRenameCopy = set()
Luke Diamanddf8a9e82016-12-17 01:00:40 +00001851 symlinks = set()
Chris Pettittc65b6702007-11-01 20:43:14 -07001852 filesToChangeExecBit = {}
Luke Diamand46c609e2016-12-02 22:43:19 +00001853 all_files = list()
Luke Diamand60df0712012-02-23 07:51:30 +00001854
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001855 for line in diff:
Chris Pettittb43b0a32007-11-01 20:43:13 -07001856 diff = parseDiffTreeEntry(line)
1857 modifier = diff['status']
1858 path = diff['src']
Luke Diamand46c609e2016-12-02 22:43:19 +00001859 all_files.append(path)
1860
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001861 if modifier == "M":
Luke Diamand6de040d2011-10-16 10:47:52 -04001862 p4_edit(path)
Chris Pettittc65b6702007-11-01 20:43:14 -07001863 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
1864 filesToChangeExecBit[path] = diff['dst_mode']
Simon Hausmannd336c152007-05-16 09:41:26 +02001865 editedFiles.add(path)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001866 elif modifier == "A":
1867 filesToAdd.add(path)
Chris Pettittc65b6702007-11-01 20:43:14 -07001868 filesToChangeExecBit[path] = diff['dst_mode']
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001869 if path in filesToDelete:
1870 filesToDelete.remove(path)
Luke Diamanddf8a9e82016-12-17 01:00:40 +00001871
1872 dst_mode = int(diff['dst_mode'], 8)
Luke Diamanddb2d9972018-06-19 09:04:11 +01001873 if dst_mode == 0o120000:
Luke Diamanddf8a9e82016-12-17 01:00:40 +00001874 symlinks.add(path)
1875
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001876 elif modifier == "D":
1877 filesToDelete.add(path)
1878 if path in filesToAdd:
1879 filesToAdd.remove(path)
Vitor Antunes4fddb412011-02-20 01:18:25 +00001880 elif modifier == "C":
1881 src, dest = diff['src'], diff['dst']
Luke Diamand7a109462019-01-18 09:36:56 +00001882 all_files.append(dest)
Luke Diamand6de040d2011-10-16 10:47:52 -04001883 p4_integrate(src, dest)
Pete Wyckoffb6ad6dc2012-04-29 20:57:16 -04001884 pureRenameCopy.add(dest)
Vitor Antunes4fddb412011-02-20 01:18:25 +00001885 if diff['src_sha1'] != diff['dst_sha1']:
Luke Diamand6de040d2011-10-16 10:47:52 -04001886 p4_edit(dest)
Pete Wyckoffb6ad6dc2012-04-29 20:57:16 -04001887 pureRenameCopy.discard(dest)
Vitor Antunes4fddb412011-02-20 01:18:25 +00001888 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
Luke Diamand6de040d2011-10-16 10:47:52 -04001889 p4_edit(dest)
Pete Wyckoffb6ad6dc2012-04-29 20:57:16 -04001890 pureRenameCopy.discard(dest)
Vitor Antunes4fddb412011-02-20 01:18:25 +00001891 filesToChangeExecBit[dest] = diff['dst_mode']
Pete Wyckoffd20f0f82013-01-26 22:11:19 -05001892 if self.isWindows:
1893 # turn off read-only attribute
1894 os.chmod(dest, stat.S_IWRITE)
Vitor Antunes4fddb412011-02-20 01:18:25 +00001895 os.unlink(dest)
1896 editedFiles.add(dest)
Chris Pettittd9a5f252007-10-15 22:15:06 -07001897 elif modifier == "R":
Chris Pettittb43b0a32007-11-01 20:43:13 -07001898 src, dest = diff['src'], diff['dst']
Luke Diamand7a109462019-01-18 09:36:56 +00001899 all_files.append(dest)
Gary Gibbons8e9497c2012-07-12 19:29:00 -04001900 if self.p4HasMoveCommand:
1901 p4_edit(src) # src must be open before move
1902 p4_move(src, dest) # opens for (move/delete, move/add)
Pete Wyckoffb6ad6dc2012-04-29 20:57:16 -04001903 else:
Gary Gibbons8e9497c2012-07-12 19:29:00 -04001904 p4_integrate(src, dest)
1905 if diff['src_sha1'] != diff['dst_sha1']:
1906 p4_edit(dest)
1907 else:
1908 pureRenameCopy.add(dest)
Chris Pettittc65b6702007-11-01 20:43:14 -07001909 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
Gary Gibbons8e9497c2012-07-12 19:29:00 -04001910 if not self.p4HasMoveCommand:
1911 p4_edit(dest) # with move: already open, writable
Chris Pettittc65b6702007-11-01 20:43:14 -07001912 filesToChangeExecBit[dest] = diff['dst_mode']
Gary Gibbons8e9497c2012-07-12 19:29:00 -04001913 if not self.p4HasMoveCommand:
Pete Wyckoffd20f0f82013-01-26 22:11:19 -05001914 if self.isWindows:
1915 os.chmod(dest, stat.S_IWRITE)
Gary Gibbons8e9497c2012-07-12 19:29:00 -04001916 os.unlink(dest)
1917 filesToDelete.add(src)
Chris Pettittd9a5f252007-10-15 22:15:06 -07001918 editedFiles.add(dest)
Romain Picarda02b8bc2016-01-12 13:43:47 +01001919 elif modifier == "T":
1920 filesToChangeType.add(path)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001921 else:
1922 die("unknown modifier %s for %s" % (modifier, path))
1923
Tolga Ceylan749b6682014-05-06 22:48:54 -07001924 diffcmd = "git diff-tree --full-index -p \"%s\"" % (id)
Simon Hausmann47a130b2007-05-20 16:33:21 +02001925 patchcmd = diffcmd + " | git apply "
Simon Hausmannc1b296b2007-05-20 16:55:05 +02001926 tryPatchCmd = patchcmd + "--check -"
1927 applyPatchCmd = patchcmd + "--check --apply -"
Luke Diamand60df0712012-02-23 07:51:30 +00001928 patch_succeeded = True
Simon Hausmann51a26402007-04-15 09:59:56 +02001929
Simon Hausmann47a130b2007-05-20 16:33:21 +02001930 if os.system(tryPatchCmd) != 0:
Luke Diamand60df0712012-02-23 07:51:30 +00001931 fixed_rcs_keywords = False
1932 patch_succeeded = False
Luke Diamandf2606b12018-06-19 09:04:10 +01001933 print("Unfortunately applying the change failed!")
Luke Diamand60df0712012-02-23 07:51:30 +00001934
1935 # Patch failed, maybe it's just RCS keyword woes. Look through
1936 # the patch to see if that's possible.
Pete Wyckoff0d609032013-01-26 22:11:24 -05001937 if gitConfigBool("git-p4.attemptRCSCleanup"):
Luke Diamand60df0712012-02-23 07:51:30 +00001938 file = None
1939 pattern = None
1940 kwfiles = {}
1941 for file in editedFiles | filesToDelete:
1942 # did this file's delta contain RCS keywords?
1943 pattern = p4_keywords_regexp_for_file(file)
1944
1945 if pattern:
1946 # this file is a possibility...look for RCS keywords.
1947 regexp = re.compile(pattern, re.VERBOSE)
1948 for line in read_pipe_lines(["git", "diff", "%s^..%s" % (id, id), file]):
1949 if regexp.search(line):
1950 if verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01001951 print("got keyword match on %s in %s in %s" % (pattern, line, file))
Luke Diamand60df0712012-02-23 07:51:30 +00001952 kwfiles[file] = pattern
1953 break
1954
1955 for file in kwfiles:
1956 if verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01001957 print("zapping %s with %s" % (line,pattern))
Pete Wyckoffd20f0f82013-01-26 22:11:19 -05001958 # File is being deleted, so not open in p4. Must
1959 # disable the read-only bit on windows.
1960 if self.isWindows and file not in editedFiles:
1961 os.chmod(file, stat.S_IWRITE)
Luke Diamand60df0712012-02-23 07:51:30 +00001962 self.patchRCSKeywords(file, kwfiles[file])
1963 fixed_rcs_keywords = True
1964
1965 if fixed_rcs_keywords:
Luke Diamandf2606b12018-06-19 09:04:10 +01001966 print("Retrying the patch with RCS keywords cleaned up")
Luke Diamand60df0712012-02-23 07:51:30 +00001967 if os.system(tryPatchCmd) == 0:
1968 patch_succeeded = True
1969
1970 if not patch_succeeded:
Pete Wyckoff7e5dd9f2012-09-09 16:16:05 -04001971 for f in editedFiles:
1972 p4_revert(f)
Pete Wyckoff7e5dd9f2012-09-09 16:16:05 -04001973 return False
Simon Hausmann51a26402007-04-15 09:59:56 +02001974
Pete Wyckoff55ac2ed2012-09-09 16:16:08 -04001975 #
1976 # Apply the patch for real, and do add/delete/+x handling.
1977 #
Simon Hausmann47a130b2007-05-20 16:33:21 +02001978 system(applyPatchCmd)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001979
Romain Picarda02b8bc2016-01-12 13:43:47 +01001980 for f in filesToChangeType:
1981 p4_edit(f, "-t", "auto")
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001982 for f in filesToAdd:
Luke Diamand6de040d2011-10-16 10:47:52 -04001983 p4_add(f)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001984 for f in filesToDelete:
Luke Diamand6de040d2011-10-16 10:47:52 -04001985 p4_revert(f)
1986 p4_delete(f)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001987
Chris Pettittc65b6702007-11-01 20:43:14 -07001988 # Set/clear executable bits
1989 for f in filesToChangeExecBit.keys():
1990 mode = filesToChangeExecBit[f]
1991 setP4ExecBit(f, mode)
1992
Luke Diamand8cf422d2017-12-21 11:06:14 +00001993 update_shelve = 0
1994 if len(self.update_shelve) > 0:
1995 update_shelve = self.update_shelve.pop(0)
1996 p4_reopen_in_change(update_shelve, all_files)
Luke Diamand46c609e2016-12-02 22:43:19 +00001997
Pete Wyckoff55ac2ed2012-09-09 16:16:08 -04001998 #
1999 # Build p4 change description, starting with the contents
2000 # of the git commit message.
2001 #
Simon Hausmann0e36f2d2008-02-19 09:33:08 +01002002 logMessage = extractLogMessageFromGitCommit(id)
Simon Hausmann0e36f2d2008-02-19 09:33:08 +01002003 logMessage = logMessage.strip()
Pete Wyckofff19cb0a2012-07-04 09:34:20 -04002004 (logMessage, jobs) = self.separate_jobs_from_description(logMessage)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01002005
Luke Diamand8cf422d2017-12-21 11:06:14 +00002006 template = self.prepareSubmitTemplate(update_shelve)
Pete Wyckofff19cb0a2012-07-04 09:34:20 -04002007 submitTemplate = self.prepareLogMessage(template, logMessage, jobs)
Pete Wyckoffc47178d2012-07-04 09:34:18 -04002008
2009 if self.preserveUser:
Pete Wyckoff55ac2ed2012-09-09 16:16:08 -04002010 submitTemplate += "\n######## Actual user %s, modified after commit\n" % p4User
Pete Wyckoffc47178d2012-07-04 09:34:18 -04002011
Pete Wyckoff55ac2ed2012-09-09 16:16:08 -04002012 if self.checkAuthorship and not self.p4UserIsMe(p4User):
2013 submitTemplate += "######## git author %s does not match your p4 account.\n" % gitEmail
2014 submitTemplate += "######## Use option --preserve-user to modify authorship.\n"
2015 submitTemplate += "######## Variable git-p4.skipUserNameCheck hides this message.\n"
2016
2017 separatorLine = "######## everything below this line is just the diff #######\n"
Maxime Costeb4073bb2014-05-24 18:40:35 +01002018 if not self.prepare_p4_only:
2019 submitTemplate += separatorLine
Luke Diamanddf8a9e82016-12-17 01:00:40 +00002020 submitTemplate += self.get_diff_description(editedFiles, filesToAdd, symlinks)
Pete Wyckoff55ac2ed2012-09-09 16:16:08 -04002021
Pete Wyckoffc47178d2012-07-04 09:34:18 -04002022 (handle, fileName) = tempfile.mkstemp()
Maxime Costee2a892e2014-06-11 14:09:59 +01002023 tmpFile = os.fdopen(handle, "w+b")
Pete Wyckoffc47178d2012-07-04 09:34:18 -04002024 if self.isWindows:
2025 submitTemplate = submitTemplate.replace("\n", "\r\n")
Maxime Costeb4073bb2014-05-24 18:40:35 +01002026 tmpFile.write(submitTemplate)
Pete Wyckoffc47178d2012-07-04 09:34:18 -04002027 tmpFile.close()
2028
Pete Wyckoff728b7ad2012-09-09 16:16:12 -04002029 if self.prepare_p4_only:
2030 #
2031 # Leave the p4 tree prepared, and the submit template around
2032 # and let the user decide what to do next
2033 #
Luke Diamandf2606b12018-06-19 09:04:10 +01002034 print()
2035 print("P4 workspace prepared for submission.")
2036 print("To submit or revert, go to client workspace")
2037 print(" " + self.clientPath)
2038 print()
2039 print("To submit, use \"p4 submit\" to write a new description,")
2040 print("or \"p4 submit -i <%s\" to use the one prepared by" \
2041 " \"git p4\"." % fileName)
2042 print("You can delete the file \"%s\" when finished." % fileName)
Pete Wyckoff728b7ad2012-09-09 16:16:12 -04002043
2044 if self.preserveUser and p4User and not self.p4UserIsMe(p4User):
Luke Diamandf2606b12018-06-19 09:04:10 +01002045 print("To preserve change ownership by user %s, you must\n" \
Pete Wyckoff728b7ad2012-09-09 16:16:12 -04002046 "do \"p4 change -f <change>\" after submitting and\n" \
Luke Diamandf2606b12018-06-19 09:04:10 +01002047 "edit the User field.")
Pete Wyckoff728b7ad2012-09-09 16:16:12 -04002048 if pureRenameCopy:
Luke Diamandf2606b12018-06-19 09:04:10 +01002049 print("After submitting, renamed files must be re-synced.")
2050 print("Invoke \"p4 sync -f\" on each of these files:")
Pete Wyckoff728b7ad2012-09-09 16:16:12 -04002051 for f in pureRenameCopy:
Luke Diamandf2606b12018-06-19 09:04:10 +01002052 print(" " + f)
Pete Wyckoff728b7ad2012-09-09 16:16:12 -04002053
Luke Diamandf2606b12018-06-19 09:04:10 +01002054 print()
2055 print("To revert the changes, use \"p4 revert ...\", and delete")
2056 print("the submit template file \"%s\"" % fileName)
Pete Wyckoff728b7ad2012-09-09 16:16:12 -04002057 if filesToAdd:
Luke Diamandf2606b12018-06-19 09:04:10 +01002058 print("Since the commit adds new files, they must be deleted:")
Pete Wyckoff728b7ad2012-09-09 16:16:12 -04002059 for f in filesToAdd:
Luke Diamandf2606b12018-06-19 09:04:10 +01002060 print(" " + f)
2061 print()
Pete Wyckoff728b7ad2012-09-09 16:16:12 -04002062 return True
2063
Pete Wyckoff55ac2ed2012-09-09 16:16:08 -04002064 #
2065 # Let the user edit the change description, then submit it.
2066 #
GIRARD Etienneb7638fe2015-11-24 07:43:59 +00002067 submitted = False
Luke Diamandecdba362011-05-07 11:19:43 +01002068
GIRARD Etienneb7638fe2015-11-24 07:43:59 +00002069 try:
2070 if self.edit_template(fileName):
2071 # read the edited message and submit
2072 tmpFile = open(fileName, "rb")
2073 message = tmpFile.read()
2074 tmpFile.close()
2075 if self.isWindows:
2076 message = message.replace("\r\n", "\n")
2077 submitTemplate = message[:message.index(separatorLine)]
Luke Diamand46c609e2016-12-02 22:43:19 +00002078
Luke Diamand8cf422d2017-12-21 11:06:14 +00002079 if update_shelve:
Luke Diamand46c609e2016-12-02 22:43:19 +00002080 p4_write_pipe(['shelve', '-r', '-i'], submitTemplate)
2081 elif self.shelve:
Vinicius Kursancewb34fa572016-11-28 09:33:18 +00002082 p4_write_pipe(['shelve', '-i'], submitTemplate)
2083 else:
2084 p4_write_pipe(['submit', '-i'], submitTemplate)
2085 # The rename/copy happened by applying a patch that created a
2086 # new file. This leaves it writable, which confuses p4.
2087 for f in pureRenameCopy:
2088 p4_sync(f, "-f")
Luke Diamandecdba362011-05-07 11:19:43 +01002089
GIRARD Etienneb7638fe2015-11-24 07:43:59 +00002090 if self.preserveUser:
2091 if p4User:
2092 # Get last changelist number. Cannot easily get it from
2093 # the submit command output as the output is
2094 # unmarshalled.
2095 changelist = self.lastP4Changelist()
2096 self.modifyChangelistUser(changelist, p4User)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01002097
GIRARD Etienneb7638fe2015-11-24 07:43:59 +00002098 submitted = True
2099
2100 finally:
Pete Wyckoffc47178d2012-07-04 09:34:18 -04002101 # skip this patch
Vinicius Kursancewb34fa572016-11-28 09:33:18 +00002102 if not submitted or self.shelve:
2103 if self.shelve:
2104 print ("Reverting shelved files.")
2105 else:
2106 print ("Submission cancelled, undoing p4 changes.")
2107 for f in editedFiles | filesToDelete:
GIRARD Etienneb7638fe2015-11-24 07:43:59 +00002108 p4_revert(f)
2109 for f in filesToAdd:
2110 p4_revert(f)
2111 os.remove(f)
Pete Wyckoffc47178d2012-07-04 09:34:18 -04002112
2113 os.remove(fileName)
GIRARD Etienneb7638fe2015-11-24 07:43:59 +00002114 return submitted
Simon Hausmann4f5cf762007-03-19 22:25:17 +01002115
Luke Diamand06804c72012-04-11 17:21:24 +02002116 # Export git tags as p4 labels. Create a p4 label and then tag
2117 # with that.
2118 def exportGitTags(self, gitTags):
Luke Diamandc8942a22012-04-11 17:21:24 +02002119 validLabelRegexp = gitConfig("git-p4.labelExportRegexp")
2120 if len(validLabelRegexp) == 0:
2121 validLabelRegexp = defaultLabelRegexp
2122 m = re.compile(validLabelRegexp)
Luke Diamand06804c72012-04-11 17:21:24 +02002123
2124 for name in gitTags:
2125
2126 if not m.match(name):
2127 if verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01002128 print("tag %s does not match regexp %s" % (name, validLabelRegexp))
Luke Diamand06804c72012-04-11 17:21:24 +02002129 continue
2130
2131 # Get the p4 commit this corresponds to
Luke Diamandc8942a22012-04-11 17:21:24 +02002132 logMessage = extractLogMessageFromGitCommit(name)
2133 values = extractSettingsGitLog(logMessage)
Luke Diamand06804c72012-04-11 17:21:24 +02002134
Luke Diamanddba1c9d2018-06-19 09:04:07 +01002135 if 'change' not in values:
Luke Diamand06804c72012-04-11 17:21:24 +02002136 # a tag pointing to something not sent to p4; ignore
2137 if verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01002138 print("git tag %s does not give a p4 commit" % name)
Luke Diamand06804c72012-04-11 17:21:24 +02002139 continue
Luke Diamandc8942a22012-04-11 17:21:24 +02002140 else:
2141 changelist = values['change']
Luke Diamand06804c72012-04-11 17:21:24 +02002142
2143 # Get the tag details.
2144 inHeader = True
2145 isAnnotated = False
2146 body = []
2147 for l in read_pipe_lines(["git", "cat-file", "-p", name]):
2148 l = l.strip()
2149 if inHeader:
2150 if re.match(r'tag\s+', l):
2151 isAnnotated = True
2152 elif re.match(r'\s*$', l):
2153 inHeader = False
2154 continue
2155 else:
2156 body.append(l)
2157
2158 if not isAnnotated:
2159 body = ["lightweight tag imported by git p4\n"]
2160
2161 # Create the label - use the same view as the client spec we are using
2162 clientSpec = getClientSpec()
2163
2164 labelTemplate = "Label: %s\n" % name
2165 labelTemplate += "Description:\n"
2166 for b in body:
2167 labelTemplate += "\t" + b + "\n"
2168 labelTemplate += "View:\n"
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09002169 for depot_side in clientSpec.mappings:
2170 labelTemplate += "\t%s\n" % depot_side
Luke Diamand06804c72012-04-11 17:21:24 +02002171
Pete Wyckoffef739f02012-09-09 16:16:11 -04002172 if self.dry_run:
Luke Diamandf2606b12018-06-19 09:04:10 +01002173 print("Would create p4 label %s for tag" % name)
Pete Wyckoff728b7ad2012-09-09 16:16:12 -04002174 elif self.prepare_p4_only:
Luke Diamandf2606b12018-06-19 09:04:10 +01002175 print("Not creating p4 label %s for tag due to option" \
2176 " --prepare-p4-only" % name)
Pete Wyckoffef739f02012-09-09 16:16:11 -04002177 else:
2178 p4_write_pipe(["label", "-i"], labelTemplate)
Luke Diamand06804c72012-04-11 17:21:24 +02002179
Pete Wyckoffef739f02012-09-09 16:16:11 -04002180 # Use the label
2181 p4_system(["tag", "-l", name] +
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09002182 ["%s@%s" % (depot_side, changelist) for depot_side in clientSpec.mappings])
Luke Diamand06804c72012-04-11 17:21:24 +02002183
Pete Wyckoffef739f02012-09-09 16:16:11 -04002184 if verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01002185 print("created p4 label for tag %s" % name)
Luke Diamand06804c72012-04-11 17:21:24 +02002186
Simon Hausmann4f5cf762007-03-19 22:25:17 +01002187 def run(self, args):
Simon Hausmannc9b50e62007-03-29 19:15:24 +02002188 if len(args) == 0:
2189 self.master = currentGitBranch()
Simon Hausmannc9b50e62007-03-29 19:15:24 +02002190 elif len(args) == 1:
2191 self.master = args[0]
Pete Wyckoff28755db2011-12-24 21:07:40 -05002192 if not branchExists(self.master):
2193 die("Branch %s does not exist" % self.master)
Simon Hausmannc9b50e62007-03-29 19:15:24 +02002194 else:
2195 return False
2196
Luke Diamand8cf422d2017-12-21 11:06:14 +00002197 for i in self.update_shelve:
2198 if i <= 0:
2199 sys.exit("invalid changelist %d" % i)
2200
Luke Diamand00ad6e32015-11-21 09:54:41 +00002201 if self.master:
2202 allowSubmit = gitConfig("git-p4.allowSubmit")
2203 if len(allowSubmit) > 0 and not self.master in allowSubmit.split(","):
2204 die("%s is not in git-p4.allowSubmit" % self.master)
Jing Xue4c2d5d72008-06-22 14:12:39 -04002205
Simon Hausmann27d2d812007-06-12 14:31:59 +02002206 [upstream, settings] = findUpstreamBranchPoint()
Simon Hausmannea99c3a2007-08-08 17:06:55 +02002207 self.depotPath = settings['depot-paths'][0]
Simon Hausmann27d2d812007-06-12 14:31:59 +02002208 if len(self.origin) == 0:
2209 self.origin = upstream
Simon Hausmanna3fdd572007-06-07 22:54:32 +02002210
Luke Diamand8cf422d2017-12-21 11:06:14 +00002211 if len(self.update_shelve) > 0:
Luke Diamand46c609e2016-12-02 22:43:19 +00002212 self.shelve = True
2213
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01002214 if self.preserveUser:
2215 if not self.canChangeChangelists():
2216 die("Cannot preserve user names without p4 super-user or admin permissions")
2217
Pete Wyckoff6bbfd132012-09-09 16:16:13 -04002218 # if not set from the command line, try the config file
2219 if self.conflict_behavior is None:
2220 val = gitConfig("git-p4.conflict")
2221 if val:
2222 if val not in self.conflict_behavior_choices:
2223 die("Invalid value '%s' for config git-p4.conflict" % val)
2224 else:
2225 val = "ask"
2226 self.conflict_behavior = val
2227
Simon Hausmanna3fdd572007-06-07 22:54:32 +02002228 if self.verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01002229 print("Origin branch is " + self.origin)
Simon Hausmann95124972007-03-23 09:16:07 +01002230
Simon Hausmannea99c3a2007-08-08 17:06:55 +02002231 if len(self.depotPath) == 0:
Luke Diamandf2606b12018-06-19 09:04:10 +01002232 print("Internal error: cannot locate perforce depot path from existing branches")
Simon Hausmann95124972007-03-23 09:16:07 +01002233 sys.exit(128)
2234
Pete Wyckoff543987b2012-02-25 20:06:25 -05002235 self.useClientSpec = False
Pete Wyckoff0d609032013-01-26 22:11:24 -05002236 if gitConfigBool("git-p4.useclientspec"):
Pete Wyckoff543987b2012-02-25 20:06:25 -05002237 self.useClientSpec = True
2238 if self.useClientSpec:
2239 self.clientSpecDirs = getClientSpec()
Simon Hausmann95124972007-03-23 09:16:07 +01002240
Ville Skyttä2e3a16b2016-08-09 11:53:38 +03002241 # Check for the existence of P4 branches
Vitor Antunescd884102015-04-21 23:49:30 +01002242 branchesDetected = (len(p4BranchesInGit().keys()) > 1)
2243
2244 if self.useClientSpec and not branchesDetected:
Pete Wyckoff543987b2012-02-25 20:06:25 -05002245 # all files are relative to the client spec
2246 self.clientPath = getClientRoot()
2247 else:
2248 self.clientPath = p4Where(self.depotPath)
2249
2250 if self.clientPath == "":
2251 die("Error: Cannot locate perforce checkout of %s in client view" % self.depotPath)
Simon Hausmann95124972007-03-23 09:16:07 +01002252
Luke Diamandf2606b12018-06-19 09:04:10 +01002253 print("Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath))
Simon Hausmann7944f142007-05-21 11:04:26 +02002254 self.oldWorkingDirectory = os.getcwd()
Simon Hausmannc1b296b2007-05-20 16:55:05 +02002255
Gary Gibbons0591cfa2011-12-09 18:48:14 -05002256 # ensure the clientPath exists
Pete Wyckoff8d7ec362012-04-29 20:57:14 -04002257 new_client_dir = False
Gary Gibbons0591cfa2011-12-09 18:48:14 -05002258 if not os.path.exists(self.clientPath):
Pete Wyckoff8d7ec362012-04-29 20:57:14 -04002259 new_client_dir = True
Gary Gibbons0591cfa2011-12-09 18:48:14 -05002260 os.makedirs(self.clientPath)
2261
Miklós Fazekasbbd84862013-03-11 17:45:29 -04002262 chdir(self.clientPath, is_client_path=True)
Pete Wyckoffef739f02012-09-09 16:16:11 -04002263 if self.dry_run:
Luke Diamandf2606b12018-06-19 09:04:10 +01002264 print("Would synchronize p4 checkout in %s" % self.clientPath)
Pete Wyckoff8d7ec362012-04-29 20:57:14 -04002265 else:
Luke Diamandf2606b12018-06-19 09:04:10 +01002266 print("Synchronizing p4 checkout...")
Pete Wyckoffef739f02012-09-09 16:16:11 -04002267 if new_client_dir:
2268 # old one was destroyed, and maybe nobody told p4
2269 p4_sync("...", "-f")
2270 else:
2271 p4_sync("...")
Simon Hausmann4f5cf762007-03-19 22:25:17 +01002272 self.check()
Simon Hausmann4f5cf762007-03-19 22:25:17 +01002273
Simon Hausmann4c750c02008-02-19 09:37:16 +01002274 commits = []
Luke Diamand00ad6e32015-11-21 09:54:41 +00002275 if self.master:
Ævar Arnfjörð Bjarmason89f32a92018-05-10 12:43:00 +00002276 committish = self.master
Luke Diamand00ad6e32015-11-21 09:54:41 +00002277 else:
Ævar Arnfjörð Bjarmason89f32a92018-05-10 12:43:00 +00002278 committish = 'HEAD'
Luke Diamand00ad6e32015-11-21 09:54:41 +00002279
Romain Merlandf55b87c2018-06-01 09:46:14 +02002280 if self.commit != "":
2281 if self.commit.find("..") != -1:
2282 limits_ish = self.commit.split("..")
2283 for line in read_pipe_lines(["git", "rev-list", "--no-merges", "%s..%s" % (limits_ish[0], limits_ish[1])]):
2284 commits.append(line.strip())
2285 commits.reverse()
2286 else:
2287 commits.append(self.commit)
2288 else:
Junio C Hamanoe6388992018-06-18 10:18:41 -07002289 for line in read_pipe_lines(["git", "rev-list", "--no-merges", "%s..%s" % (self.origin, committish)]):
Romain Merlandf55b87c2018-06-01 09:46:14 +02002290 commits.append(line.strip())
2291 commits.reverse()
Simon Hausmann4f5cf762007-03-19 22:25:17 +01002292
Pete Wyckoff0d609032013-01-26 22:11:24 -05002293 if self.preserveUser or gitConfigBool("git-p4.skipUserNameCheck"):
Luke Diamand848de9c2011-05-13 20:46:00 +01002294 self.checkAuthorship = False
2295 else:
2296 self.checkAuthorship = True
2297
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01002298 if self.preserveUser:
2299 self.checkValidP4Users(commits)
2300
Gary Gibbons84cb0002012-07-04 09:40:19 -04002301 #
2302 # Build up a set of options to be passed to diff when
2303 # submitting each commit to p4.
2304 #
2305 if self.detectRenames:
2306 # command-line -M arg
2307 self.diffOpts = "-M"
2308 else:
2309 # If not explicitly set check the config variable
2310 detectRenames = gitConfig("git-p4.detectRenames")
2311
2312 if detectRenames.lower() == "false" or detectRenames == "":
2313 self.diffOpts = ""
2314 elif detectRenames.lower() == "true":
2315 self.diffOpts = "-M"
2316 else:
2317 self.diffOpts = "-M%s" % detectRenames
2318
2319 # no command-line arg for -C or --find-copies-harder, just
2320 # config variables
2321 detectCopies = gitConfig("git-p4.detectCopies")
2322 if detectCopies.lower() == "false" or detectCopies == "":
2323 pass
2324 elif detectCopies.lower() == "true":
2325 self.diffOpts += " -C"
2326 else:
2327 self.diffOpts += " -C%s" % detectCopies
2328
Pete Wyckoff0d609032013-01-26 22:11:24 -05002329 if gitConfigBool("git-p4.detectCopiesHarder"):
Gary Gibbons84cb0002012-07-04 09:40:19 -04002330 self.diffOpts += " --find-copies-harder"
2331
Luke Diamand8cf422d2017-12-21 11:06:14 +00002332 num_shelves = len(self.update_shelve)
2333 if num_shelves > 0 and num_shelves != len(commits):
2334 sys.exit("number of commits (%d) must match number of shelved changelist (%d)" %
2335 (len(commits), num_shelves))
2336
Chen Bin251c8c52018-07-27 21:22:22 +10002337 hooks_path = gitConfig("core.hooksPath")
2338 if len(hooks_path) <= 0:
2339 hooks_path = os.path.join(os.environ.get("GIT_DIR", ".git"), "hooks")
2340
2341 hook_file = os.path.join(hooks_path, "p4-pre-submit")
2342 if os.path.isfile(hook_file) and os.access(hook_file, os.X_OK) and subprocess.call([hook_file]) != 0:
2343 sys.exit(1)
2344
Pete Wyckoff7e5dd9f2012-09-09 16:16:05 -04002345 #
2346 # Apply the commits, one at a time. On failure, ask if should
2347 # continue to try the rest of the patches, or quit.
2348 #
Pete Wyckoffef739f02012-09-09 16:16:11 -04002349 if self.dry_run:
Luke Diamandf2606b12018-06-19 09:04:10 +01002350 print("Would apply")
Pete Wyckoff67b0fe22012-09-09 16:16:03 -04002351 applied = []
Pete Wyckoff7e5dd9f2012-09-09 16:16:05 -04002352 last = len(commits) - 1
2353 for i, commit in enumerate(commits):
Pete Wyckoffef739f02012-09-09 16:16:11 -04002354 if self.dry_run:
Luke Diamandf2606b12018-06-19 09:04:10 +01002355 print(" ", read_pipe(["git", "show", "-s",
2356 "--format=format:%h %s", commit]))
Pete Wyckoffef739f02012-09-09 16:16:11 -04002357 ok = True
2358 else:
2359 ok = self.applyCommit(commit)
Pete Wyckoff67b0fe22012-09-09 16:16:03 -04002360 if ok:
2361 applied.append(commit)
Pete Wyckoff7e5dd9f2012-09-09 16:16:05 -04002362 else:
Pete Wyckoff728b7ad2012-09-09 16:16:12 -04002363 if self.prepare_p4_only and i < last:
Luke Diamandf2606b12018-06-19 09:04:10 +01002364 print("Processing only the first commit due to option" \
2365 " --prepare-p4-only")
Pete Wyckoff728b7ad2012-09-09 16:16:12 -04002366 break
Pete Wyckoff7e5dd9f2012-09-09 16:16:05 -04002367 if i < last:
Ben Keenee2aed5f2019-12-16 14:02:19 +00002368 # prompt for what to do, or use the option/variable
2369 if self.conflict_behavior == "ask":
2370 print("What do you want to do?")
2371 response = prompt("[s]kip this commit but apply the rest, or [q]uit? ")
2372 elif self.conflict_behavior == "skip":
2373 response = "s"
2374 elif self.conflict_behavior == "quit":
2375 response = "q"
2376 else:
2377 die("Unknown conflict_behavior '%s'" %
2378 self.conflict_behavior)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01002379
Ben Keenee2aed5f2019-12-16 14:02:19 +00002380 if response == "s":
2381 print("Skipping this commit, but applying the rest")
2382 if response == "q":
2383 print("Quitting")
Pete Wyckoff7e5dd9f2012-09-09 16:16:05 -04002384 break
Simon Hausmann4f5cf762007-03-19 22:25:17 +01002385
Pete Wyckoff67b0fe22012-09-09 16:16:03 -04002386 chdir(self.oldWorkingDirectory)
Vinicius Kursancewb34fa572016-11-28 09:33:18 +00002387 shelved_applied = "shelved" if self.shelve else "applied"
Pete Wyckoffef739f02012-09-09 16:16:11 -04002388 if self.dry_run:
2389 pass
Pete Wyckoff728b7ad2012-09-09 16:16:12 -04002390 elif self.prepare_p4_only:
2391 pass
Pete Wyckoffef739f02012-09-09 16:16:11 -04002392 elif len(commits) == len(applied):
Luke Diamandf2606b12018-06-19 09:04:10 +01002393 print("All commits {0}!".format(shelved_applied))
Simon Hausmann14594f42007-08-22 09:07:15 +02002394
Simon Hausmann4c750c02008-02-19 09:37:16 +01002395 sync = P4Sync()
Pete Wyckoff44e8d262013-01-14 19:47:08 -05002396 if self.branch:
2397 sync.branch = self.branch
Luke Diamandb9d34db2018-06-08 21:32:44 +01002398 if self.disable_p4sync:
2399 sync.sync_origin_only()
2400 else:
2401 sync.run([])
Simon Hausmann14594f42007-08-22 09:07:15 +02002402
Luke Diamandb9d34db2018-06-08 21:32:44 +01002403 if not self.disable_rebase:
2404 rebase = P4Rebase()
2405 rebase.rebase()
Simon Hausmann4f5cf762007-03-19 22:25:17 +01002406
Pete Wyckoff67b0fe22012-09-09 16:16:03 -04002407 else:
2408 if len(applied) == 0:
Luke Diamandf2606b12018-06-19 09:04:10 +01002409 print("No commits {0}.".format(shelved_applied))
Pete Wyckoff67b0fe22012-09-09 16:16:03 -04002410 else:
Luke Diamandf2606b12018-06-19 09:04:10 +01002411 print("{0} only the commits marked with '*':".format(shelved_applied.capitalize()))
Pete Wyckoff67b0fe22012-09-09 16:16:03 -04002412 for c in commits:
2413 if c in applied:
2414 star = "*"
2415 else:
2416 star = " "
Luke Diamandf2606b12018-06-19 09:04:10 +01002417 print(star, read_pipe(["git", "show", "-s",
2418 "--format=format:%h %s", c]))
2419 print("You will have to do 'git p4 sync' and rebase.")
Pete Wyckoff67b0fe22012-09-09 16:16:03 -04002420
Pete Wyckoff0d609032013-01-26 22:11:24 -05002421 if gitConfigBool("git-p4.exportLabels"):
Luke Diamand06dcd152012-05-11 07:25:18 +01002422 self.exportLabels = True
Luke Diamand06804c72012-04-11 17:21:24 +02002423
2424 if self.exportLabels:
2425 p4Labels = getP4Labels(self.depotPath)
2426 gitTags = getGitTags()
2427
2428 missingGitTags = gitTags - p4Labels
2429 self.exportGitTags(missingGitTags)
2430
Ondřej Bílka98e023d2013-07-29 10:18:21 +02002431 # exit with error unless everything applied perfectly
Pete Wyckoff67b0fe22012-09-09 16:16:03 -04002432 if len(commits) != len(applied):
2433 sys.exit(1)
2434
Simon Hausmannb9847332007-03-20 20:54:23 +01002435 return True
2436
Pete Wyckoffecb7cf92012-01-02 18:05:53 -05002437class View(object):
2438 """Represent a p4 view ("p4 help views"), and map files in a
2439 repo according to the view."""
2440
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09002441 def __init__(self, client_name):
Pete Wyckoffecb7cf92012-01-02 18:05:53 -05002442 self.mappings = []
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09002443 self.client_prefix = "//%s/" % client_name
2444 # cache results of "p4 where" to lookup client file locations
2445 self.client_spec_path_cache = {}
Pete Wyckoffecb7cf92012-01-02 18:05:53 -05002446
2447 def append(self, view_line):
2448 """Parse a view line, splitting it into depot and client
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09002449 sides. Append to self.mappings, preserving order. This
2450 is only needed for tag creation."""
Pete Wyckoffecb7cf92012-01-02 18:05:53 -05002451
2452 # Split the view line into exactly two words. P4 enforces
2453 # structure on these lines that simplifies this quite a bit.
2454 #
2455 # Either or both words may be double-quoted.
2456 # Single quotes do not matter.
2457 # Double-quote marks cannot occur inside the words.
2458 # A + or - prefix is also inside the quotes.
2459 # There are no quotes unless they contain a space.
2460 # The line is already white-space stripped.
2461 # The two words are separated by a single space.
2462 #
2463 if view_line[0] == '"':
2464 # First word is double quoted. Find its end.
2465 close_quote_index = view_line.find('"', 1)
2466 if close_quote_index <= 0:
2467 die("No first-word closing quote found: %s" % view_line)
2468 depot_side = view_line[1:close_quote_index]
2469 # skip closing quote and space
2470 rhs_index = close_quote_index + 1 + 1
2471 else:
2472 space_index = view_line.find(" ")
2473 if space_index <= 0:
2474 die("No word-splitting space found: %s" % view_line)
2475 depot_side = view_line[0:space_index]
2476 rhs_index = space_index + 1
2477
Pete Wyckoffecb7cf92012-01-02 18:05:53 -05002478 # prefix + means overlay on previous mapping
Pete Wyckoffecb7cf92012-01-02 18:05:53 -05002479 if depot_side.startswith("+"):
Pete Wyckoffecb7cf92012-01-02 18:05:53 -05002480 depot_side = depot_side[1:]
2481
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09002482 # prefix - means exclude this path, leave out of mappings
Pete Wyckoffecb7cf92012-01-02 18:05:53 -05002483 exclude = False
2484 if depot_side.startswith("-"):
2485 exclude = True
2486 depot_side = depot_side[1:]
2487
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09002488 if not exclude:
2489 self.mappings.append(depot_side)
2490
2491 def convert_client_path(self, clientFile):
2492 # chop off //client/ part to make it relative
2493 if not clientFile.startswith(self.client_prefix):
2494 die("No prefix '%s' on clientFile '%s'" %
2495 (self.client_prefix, clientFile))
2496 return clientFile[len(self.client_prefix):]
2497
2498 def update_client_spec_path_cache(self, files):
2499 """ Caching file paths by "p4 where" batch query """
2500
2501 # List depot file paths exclude that already cached
2502 fileArgs = [f['path'] for f in files if f['path'] not in self.client_spec_path_cache]
2503
2504 if len(fileArgs) == 0:
2505 return # All files in cache
2506
2507 where_result = p4CmdList(["-x", "-", "where"], stdin=fileArgs)
2508 for res in where_result:
2509 if "code" in res and res["code"] == "error":
2510 # assume error is "... file(s) not in client view"
2511 continue
2512 if "clientFile" not in res:
Pete Wyckoff20005442014-01-21 18:16:46 -05002513 die("No clientFile in 'p4 where' output")
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09002514 if "unmap" in res:
2515 # it will list all of them, but only one not unmap-ped
2516 continue
Lars Schneidera0a50d82015-08-28 14:00:34 +02002517 if gitConfigBool("core.ignorecase"):
2518 res['depotFile'] = res['depotFile'].lower()
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09002519 self.client_spec_path_cache[res['depotFile']] = self.convert_client_path(res["clientFile"])
2520
2521 # not found files or unmap files set to ""
2522 for depotFile in fileArgs:
Lars Schneidera0a50d82015-08-28 14:00:34 +02002523 if gitConfigBool("core.ignorecase"):
2524 depotFile = depotFile.lower()
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09002525 if depotFile not in self.client_spec_path_cache:
2526 self.client_spec_path_cache[depotFile] = ""
Pete Wyckoffecb7cf92012-01-02 18:05:53 -05002527
2528 def map_in_client(self, depot_path):
2529 """Return the relative location in the client where this
2530 depot file should live. Returns "" if the file should
2531 not be mapped in the client."""
2532
Lars Schneidera0a50d82015-08-28 14:00:34 +02002533 if gitConfigBool("core.ignorecase"):
2534 depot_path = depot_path.lower()
2535
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09002536 if depot_path in self.client_spec_path_cache:
2537 return self.client_spec_path_cache[depot_path]
Pete Wyckoffecb7cf92012-01-02 18:05:53 -05002538
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09002539 die( "Error: %s is not found in client spec path" % depot_path )
2540 return ""
Pete Wyckoffecb7cf92012-01-02 18:05:53 -05002541
Mazo, Andreyff8c50e2019-04-01 18:02:26 +00002542def cloneExcludeCallback(option, opt_str, value, parser):
2543 # prepend "/" because the first "/" was consumed as part of the option itself.
2544 # ("-//depot/A/..." becomes "/depot/A/..." after option parsing)
2545 parser.values.cloneExclude += ["/" + re.sub(r"\.\.\.$", "", value)]
2546
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01002547class P4Sync(Command, P4UserMap):
Pete Wyckoff56c09342011-02-19 08:17:57 -05002548
Simon Hausmannb9847332007-03-20 20:54:23 +01002549 def __init__(self):
2550 Command.__init__(self)
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01002551 P4UserMap.__init__(self)
Simon Hausmannb9847332007-03-20 20:54:23 +01002552 self.options = [
2553 optparse.make_option("--branch", dest="branch"),
2554 optparse.make_option("--detect-branches", dest="detectBranches", action="store_true"),
2555 optparse.make_option("--changesfile", dest="changesFile"),
2556 optparse.make_option("--silent", dest="silent", action="store_true"),
Simon Hausmannef48f902007-05-17 22:17:49 +02002557 optparse.make_option("--detect-labels", dest="detectLabels", action="store_true"),
Luke Diamand06804c72012-04-11 17:21:24 +02002558 optparse.make_option("--import-labels", dest="importLabels", action="store_true"),
Han-Wen Nienhuysd2c6dd32007-05-23 18:49:35 -03002559 optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false",
2560 help="Import into refs/heads/ , not refs/remotes"),
Lex Spoon96b2d542015-04-20 11:00:20 -04002561 optparse.make_option("--max-changes", dest="maxChanges",
2562 help="Maximum number of changes to import"),
2563 optparse.make_option("--changes-block-size", dest="changes_block_size", type="int",
2564 help="Internal block size to use when iteratively calling p4 changes"),
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03002565 optparse.make_option("--keep-path", dest="keepRepoPath", action='store_true',
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +01002566 help="Keep entire BRANCH/DIR/SUBDIR prefix during import"),
2567 optparse.make_option("--use-client-spec", dest="useClientSpec", action='store_true',
Luke Diamand51334bb2015-01-17 20:56:38 +00002568 help="Only sync files that are included in the Perforce Client Spec"),
2569 optparse.make_option("-/", dest="cloneExclude",
Mazo, Andreyff8c50e2019-04-01 18:02:26 +00002570 action="callback", callback=cloneExcludeCallback, type="string",
Luke Diamand51334bb2015-01-17 20:56:38 +00002571 help="exclude depot path"),
Simon Hausmannb9847332007-03-20 20:54:23 +01002572 ]
2573 self.description = """Imports from Perforce into a git repository.\n
2574 example:
2575 //depot/my/project/ -- to import the current head
2576 //depot/my/project/@all -- to import everything
2577 //depot/my/project/@1,6 -- to import only from revision 1 to 6
2578
2579 (a ... is not needed in the path p4 specification, it's added implicitly)"""
2580
2581 self.usage += " //depot/path[@revRange]"
Simon Hausmannb9847332007-03-20 20:54:23 +01002582 self.silent = False
Reilly Grant1d7367d2009-09-10 00:02:38 -07002583 self.createdBranches = set()
2584 self.committedChanges = set()
Simon Hausmann569d1bd2007-03-22 21:34:16 +01002585 self.branch = ""
Simon Hausmannb9847332007-03-20 20:54:23 +01002586 self.detectBranches = False
Simon Hausmanncb53e1f2007-04-08 00:12:02 +02002587 self.detectLabels = False
Luke Diamand06804c72012-04-11 17:21:24 +02002588 self.importLabels = False
Simon Hausmannb9847332007-03-20 20:54:23 +01002589 self.changesFile = ""
Simon Hausmann01265102007-05-25 10:36:10 +02002590 self.syncWithOrigin = True
Simon Hausmanna028a982007-05-23 00:03:08 +02002591 self.importIntoRemotes = True
Simon Hausmann01a9c9c2007-05-23 00:07:35 +02002592 self.maxChanges = ""
Luke Diamand1051ef02015-06-10 08:30:59 +01002593 self.changes_block_size = None
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -03002594 self.keepRepoPath = False
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03002595 self.depotPaths = None
Simon Hausmann3c699642007-06-16 13:09:21 +02002596 self.p4BranchesInGit = []
Tommy Thorn354081d2008-02-03 10:38:51 -08002597 self.cloneExclude = []
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +01002598 self.useClientSpec = False
Pete Wyckoffa93d33e2012-02-25 20:06:24 -05002599 self.useClientSpec_from_options = False
Pete Wyckoffecb7cf92012-01-02 18:05:53 -05002600 self.clientSpecDirs = None
Vitor Antunesfed23692012-01-25 23:48:22 +00002601 self.tempBranches = []
Lars Schneiderd6041762016-06-29 09:35:27 +02002602 self.tempBranchLocation = "refs/git-p4-tmp"
Lars Schneidera5db4b12015-09-26 09:55:03 +02002603 self.largeFileSystem = None
Luke Diamand123f6312018-05-23 23:20:26 +01002604 self.suppress_meta_comment = False
Lars Schneidera5db4b12015-09-26 09:55:03 +02002605
2606 if gitConfig('git-p4.largeFileSystem'):
2607 largeFileSystemConstructor = globals()[gitConfig('git-p4.largeFileSystem')]
2608 self.largeFileSystem = largeFileSystemConstructor(
2609 lambda git_mode, relPath, contents: self.writeToGitStream(git_mode, relPath, contents)
2610 )
Simon Hausmannb9847332007-03-20 20:54:23 +01002611
Simon Hausmann01265102007-05-25 10:36:10 +02002612 if gitConfig("git-p4.syncFromOrigin") == "false":
2613 self.syncWithOrigin = False
2614
Luke Diamand123f6312018-05-23 23:20:26 +01002615 self.depotPaths = []
2616 self.changeRange = ""
2617 self.previousDepotPaths = []
2618 self.hasOrigin = False
2619
2620 # map from branch depot path to parent branch
2621 self.knownBranches = {}
2622 self.initialParents = {}
2623
2624 self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
2625 self.labels = {}
2626
Vitor Antunesfed23692012-01-25 23:48:22 +00002627 # Force a checkpoint in fast-import and wait for it to finish
2628 def checkpoint(self):
2629 self.gitStream.write("checkpoint\n\n")
2630 self.gitStream.write("progress checkpoint\n\n")
2631 out = self.gitOutput.readline()
2632 if self.verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01002633 print("checkpoint finished: " + out)
Vitor Antunesfed23692012-01-25 23:48:22 +00002634
Mazo, Andreya2bee102019-04-01 18:02:32 +00002635 def isPathWanted(self, path):
2636 for p in self.cloneExclude:
2637 if p.endswith("/"):
2638 if p4PathStartsWith(path, p):
2639 return False
2640 # "-//depot/file1" without a trailing "/" should only exclude "file1", but not "file111" or "file1_dir/file2"
2641 elif path.lower() == p.lower():
2642 return False
2643 for p in self.depotPaths:
2644 if p4PathStartsWith(path, p):
2645 return True
2646 return False
2647
Luke Diamand89143ac2018-10-15 12:14:08 +01002648 def extractFilesFromCommit(self, commit, shelved=False, shelved_cl = 0):
Simon Hausmannb9847332007-03-20 20:54:23 +01002649 files = []
2650 fnum = 0
Luke Diamanddba1c9d2018-06-19 09:04:07 +01002651 while "depotFile%s" % fnum in commit:
Simon Hausmannb9847332007-03-20 20:54:23 +01002652 path = commit["depotFile%s" % fnum]
Mazo, Andreya2bee102019-04-01 18:02:32 +00002653 found = self.isPathWanted(path)
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03002654 if not found:
Simon Hausmannb9847332007-03-20 20:54:23 +01002655 fnum = fnum + 1
2656 continue
2657
2658 file = {}
2659 file["path"] = path
2660 file["rev"] = commit["rev%s" % fnum]
2661 file["action"] = commit["action%s" % fnum]
2662 file["type"] = commit["type%s" % fnum]
Luke Diamand123f6312018-05-23 23:20:26 +01002663 if shelved:
2664 file["shelved_cl"] = int(shelved_cl)
Simon Hausmannb9847332007-03-20 20:54:23 +01002665 files.append(file)
2666 fnum = fnum + 1
2667 return files
2668
Jan Durovec26e6a272016-04-19 19:49:41 +00002669 def extractJobsFromCommit(self, commit):
2670 jobs = []
2671 jnum = 0
Luke Diamanddba1c9d2018-06-19 09:04:07 +01002672 while "job%s" % jnum in commit:
Jan Durovec26e6a272016-04-19 19:49:41 +00002673 job = commit["job%s" % jnum]
2674 jobs.append(job)
2675 jnum = jnum + 1
2676 return jobs
2677
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03002678 def stripRepoPath(self, path, prefixes):
Pete Wyckoff21ef5df2012-08-11 12:55:04 -04002679 """When streaming files, this is called to map a p4 depot path
2680 to where it should go in git. The prefixes are either
2681 self.depotPaths, or self.branchPrefixes in the case of
2682 branch detection."""
2683
Ian Wienand39527102011-02-11 16:33:48 -08002684 if self.useClientSpec:
Pete Wyckoff21ef5df2012-08-11 12:55:04 -04002685 # branch detection moves files up a level (the branch name)
2686 # from what client spec interpretation gives
Pete Wyckoff0d1696e2012-08-11 12:55:03 -04002687 path = self.clientSpecDirs.map_in_client(path)
Pete Wyckoff21ef5df2012-08-11 12:55:04 -04002688 if self.detectBranches:
2689 for b in self.knownBranches:
Mazo, Andreyf2768cb2019-04-01 18:02:24 +00002690 if p4PathStartsWith(path, b + "/"):
Pete Wyckoff21ef5df2012-08-11 12:55:04 -04002691 path = path[len(b)+1:]
2692
2693 elif self.keepRepoPath:
2694 # Preserve everything in relative path name except leading
2695 # //depot/; just look at first prefix as they all should
2696 # be in the same depot.
2697 depot = re.sub("^(//[^/]+/).*", r'\1', prefixes[0])
2698 if p4PathStartsWith(path, depot):
2699 path = path[len(depot):]
Ian Wienand39527102011-02-11 16:33:48 -08002700
Pete Wyckoff0d1696e2012-08-11 12:55:03 -04002701 else:
Pete Wyckoff0d1696e2012-08-11 12:55:03 -04002702 for p in prefixes:
2703 if p4PathStartsWith(path, p):
2704 path = path[len(p):]
Pete Wyckoff21ef5df2012-08-11 12:55:04 -04002705 break
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03002706
Pete Wyckoff0d1696e2012-08-11 12:55:03 -04002707 path = wildcard_decode(path)
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03002708 return path
Han-Wen Nienhuys6754a292007-05-23 17:41:50 -03002709
Simon Hausmann71b112d2007-05-19 11:54:11 +02002710 def splitFilesIntoBranches(self, commit):
Pete Wyckoff21ef5df2012-08-11 12:55:04 -04002711 """Look at each depotFile in the commit to figure out to what
2712 branch it belongs."""
2713
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09002714 if self.clientSpecDirs:
2715 files = self.extractFilesFromCommit(commit)
2716 self.clientSpecDirs.update_client_spec_path_cache(files)
2717
Simon Hausmannd5904672007-05-19 11:07:32 +02002718 branches = {}
Simon Hausmann71b112d2007-05-19 11:54:11 +02002719 fnum = 0
Luke Diamanddba1c9d2018-06-19 09:04:07 +01002720 while "depotFile%s" % fnum in commit:
Simon Hausmann71b112d2007-05-19 11:54:11 +02002721 path = commit["depotFile%s" % fnum]
Mazo, Andreyd15068a2019-04-01 18:02:38 +00002722 found = self.isPathWanted(path)
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03002723 if not found:
Simon Hausmann71b112d2007-05-19 11:54:11 +02002724 fnum = fnum + 1
2725 continue
2726
2727 file = {}
2728 file["path"] = path
2729 file["rev"] = commit["rev%s" % fnum]
2730 file["action"] = commit["action%s" % fnum]
2731 file["type"] = commit["type%s" % fnum]
2732 fnum = fnum + 1
2733
Pete Wyckoff21ef5df2012-08-11 12:55:04 -04002734 # start with the full relative path where this file would
2735 # go in a p4 client
2736 if self.useClientSpec:
2737 relPath = self.clientSpecDirs.map_in_client(path)
2738 else:
2739 relPath = self.stripRepoPath(path, self.depotPaths)
Simon Hausmannb9847332007-03-20 20:54:23 +01002740
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02002741 for branch in self.knownBranches.keys():
Pete Wyckoff21ef5df2012-08-11 12:55:04 -04002742 # add a trailing slash so that a commit into qt/4.2foo
2743 # doesn't end up in qt/4.2, e.g.
Mazo, Andreyf2768cb2019-04-01 18:02:24 +00002744 if p4PathStartsWith(relPath, branch + "/"):
Simon Hausmannd5904672007-05-19 11:07:32 +02002745 if branch not in branches:
2746 branches[branch] = []
Simon Hausmann71b112d2007-05-19 11:54:11 +02002747 branches[branch].append(file)
Simon Hausmann6555b2c2007-06-17 11:25:34 +02002748 break
Simon Hausmannb9847332007-03-20 20:54:23 +01002749
2750 return branches
2751
Lars Schneidera5db4b12015-09-26 09:55:03 +02002752 def writeToGitStream(self, gitMode, relPath, contents):
2753 self.gitStream.write('M %s inline %s\n' % (gitMode, relPath))
2754 self.gitStream.write('data %d\n' % sum(len(d) for d in contents))
2755 for d in contents:
2756 self.gitStream.write(d)
2757 self.gitStream.write('\n')
2758
Lars Schneidera8b05162017-02-09 16:06:56 +01002759 def encodeWithUTF8(self, path):
2760 try:
2761 path.decode('ascii')
2762 except:
2763 encoding = 'utf8'
2764 if gitConfig('git-p4.pathEncoding'):
2765 encoding = gitConfig('git-p4.pathEncoding')
2766 path = path.decode(encoding, 'replace').encode('utf8', 'replace')
2767 if self.verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01002768 print('Path with non-ASCII characters detected. Used %s to encode: %s ' % (encoding, path))
Lars Schneidera8b05162017-02-09 16:06:56 +01002769 return path
2770
Luke Diamandb9327052009-07-30 00:13:46 +01002771 # output one file from the P4 stream
2772 # - helper for streamP4Files
2773
2774 def streamOneP4File(self, file, contents):
Luke Diamandb9327052009-07-30 00:13:46 +01002775 relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes)
Lars Schneidera8b05162017-02-09 16:06:56 +01002776 relPath = self.encodeWithUTF8(relPath)
Luke Diamandb9327052009-07-30 00:13:46 +01002777 if verbose:
Luke Diamand0742b7c2018-10-12 06:28:31 +01002778 if 'fileSize' in self.stream_file:
2779 size = int(self.stream_file['fileSize'])
2780 else:
2781 size = 0 # deleted files don't get a fileSize apparently
Lars Schneiderd2176a52015-09-26 09:55:01 +02002782 sys.stdout.write('\r%s --> %s (%i MB)\n' % (file['depotFile'], relPath, size/1024/1024))
2783 sys.stdout.flush()
Luke Diamandb9327052009-07-30 00:13:46 +01002784
Pete Wyckoff9cffb8c2011-10-16 10:45:01 -04002785 (type_base, type_mods) = split_p4_type(file["type"])
2786
2787 git_mode = "100644"
2788 if "x" in type_mods:
2789 git_mode = "100755"
2790 if type_base == "symlink":
2791 git_mode = "120000"
Alexandru Juncu1292df12013-08-08 16:17:38 +03002792 # p4 print on a symlink sometimes contains "target\n";
2793 # if it does, remove the newline
Evan Powersb39c3612010-02-16 00:44:08 -08002794 data = ''.join(contents)
Pete Wyckoff40f846c2014-01-21 18:16:40 -05002795 if not data:
2796 # Some version of p4 allowed creating a symlink that pointed
2797 # to nothing. This causes p4 errors when checking out such
2798 # a change, and errors here too. Work around it by ignoring
2799 # the bad symlink; hopefully a future change fixes it.
Luke Diamandf2606b12018-06-19 09:04:10 +01002800 print("\nIgnoring empty symlink in %s" % file['depotFile'])
Pete Wyckoff40f846c2014-01-21 18:16:40 -05002801 return
2802 elif data[-1] == '\n':
Alexandru Juncu1292df12013-08-08 16:17:38 +03002803 contents = [data[:-1]]
2804 else:
2805 contents = [data]
Luke Diamandb9327052009-07-30 00:13:46 +01002806
Pete Wyckoff9cffb8c2011-10-16 10:45:01 -04002807 if type_base == "utf16":
Pete Wyckoff55aa5712011-09-17 19:16:14 -04002808 # p4 delivers different text in the python output to -G
2809 # than it does when using "print -o", or normal p4 client
2810 # operations. utf16 is converted to ascii or utf8, perhaps.
2811 # But ascii text saved as -t utf16 is completely mangled.
2812 # Invoke print -o to get the real contents.
Pete Wyckoff7f0e5962013-01-26 22:11:13 -05002813 #
2814 # On windows, the newlines will always be mangled by print, so put
2815 # them back too. This is not needed to the cygwin windows version,
2816 # just the native "NT" type.
2817 #
Lars Schneider1f5f3902015-09-21 12:01:41 +02002818 try:
2819 text = p4_read_pipe(['print', '-q', '-o', '-', '%s@%s' % (file['depotFile'], file['change'])])
2820 except Exception as e:
2821 if 'Translation of file content failed' in str(e):
2822 type_base = 'binary'
2823 else:
2824 raise e
2825 else:
2826 if p4_version_string().find('/NT') >= 0:
2827 text = text.replace('\r\n', '\n')
2828 contents = [ text ]
Pete Wyckoff55aa5712011-09-17 19:16:14 -04002829
Pete Wyckoff9f7ef0e2011-11-05 13:36:07 -04002830 if type_base == "apple":
2831 # Apple filetype files will be streamed as a concatenation of
2832 # its appledouble header and the contents. This is useless
2833 # on both macs and non-macs. If using "print -q -o xx", it
2834 # will create "xx" with the data, and "%xx" with the header.
2835 # This is also not very useful.
2836 #
2837 # Ideally, someday, this script can learn how to generate
2838 # appledouble files directly and import those to git, but
2839 # non-mac machines can never find a use for apple filetype.
Luke Diamandf2606b12018-06-19 09:04:10 +01002840 print("\nIgnoring apple filetype file %s" % file['depotFile'])
Pete Wyckoff9f7ef0e2011-11-05 13:36:07 -04002841 return
2842
Pete Wyckoff55aa5712011-09-17 19:16:14 -04002843 # Note that we do not try to de-mangle keywords on utf16 files,
2844 # even though in theory somebody may want that.
Luke Diamand60df0712012-02-23 07:51:30 +00002845 pattern = p4_keywords_regexp_for_type(type_base, type_mods)
2846 if pattern:
2847 regexp = re.compile(pattern, re.VERBOSE)
2848 text = ''.join(contents)
2849 text = regexp.sub(r'$\1$', text)
2850 contents = [ text ]
Luke Diamandb9327052009-07-30 00:13:46 +01002851
Lars Schneidera5db4b12015-09-26 09:55:03 +02002852 if self.largeFileSystem:
2853 (git_mode, contents) = self.largeFileSystem.processContent(git_mode, relPath, contents)
Luke Diamandb9327052009-07-30 00:13:46 +01002854
Lars Schneidera5db4b12015-09-26 09:55:03 +02002855 self.writeToGitStream(git_mode, relPath, contents)
Luke Diamandb9327052009-07-30 00:13:46 +01002856
2857 def streamOneP4Deletion(self, file):
2858 relPath = self.stripRepoPath(file['path'], self.branchPrefixes)
Lars Schneidera8b05162017-02-09 16:06:56 +01002859 relPath = self.encodeWithUTF8(relPath)
Luke Diamandb9327052009-07-30 00:13:46 +01002860 if verbose:
Lars Schneiderd2176a52015-09-26 09:55:01 +02002861 sys.stdout.write("delete %s\n" % relPath)
2862 sys.stdout.flush()
Luke Diamandb9327052009-07-30 00:13:46 +01002863 self.gitStream.write("D %s\n" % relPath)
2864
Lars Schneidera5db4b12015-09-26 09:55:03 +02002865 if self.largeFileSystem and self.largeFileSystem.isLargeFile(relPath):
2866 self.largeFileSystem.removeLargeFile(relPath)
2867
Luke Diamandb9327052009-07-30 00:13:46 +01002868 # handle another chunk of streaming data
2869 def streamP4FilesCb(self, marshalled):
2870
Pete Wyckoff78189be2012-11-23 17:35:36 -05002871 # catch p4 errors and complain
2872 err = None
2873 if "code" in marshalled:
2874 if marshalled["code"] == "error":
2875 if "data" in marshalled:
2876 err = marshalled["data"].rstrip()
Lars Schneider4d25dc42015-09-26 09:55:02 +02002877
2878 if not err and 'fileSize' in self.stream_file:
2879 required_bytes = int((4 * int(self.stream_file["fileSize"])) - calcDiskFree())
2880 if required_bytes > 0:
2881 err = 'Not enough space left on %s! Free at least %i MB.' % (
2882 os.getcwd(), required_bytes/1024/1024
2883 )
2884
Pete Wyckoff78189be2012-11-23 17:35:36 -05002885 if err:
2886 f = None
2887 if self.stream_have_file_info:
2888 if "depotFile" in self.stream_file:
2889 f = self.stream_file["depotFile"]
2890 # force a failure in fast-import, else an empty
2891 # commit will be made
2892 self.gitStream.write("\n")
2893 self.gitStream.write("die-now\n")
2894 self.gitStream.close()
2895 # ignore errors, but make sure it exits first
2896 self.importProcess.wait()
2897 if f:
2898 die("Error from p4 print for %s: %s" % (f, err))
2899 else:
2900 die("Error from p4 print: %s" % err)
2901
Luke Diamanddba1c9d2018-06-19 09:04:07 +01002902 if 'depotFile' in marshalled and self.stream_have_file_info:
Andrew Garberc3f61632011-04-07 02:01:21 -04002903 # start of a new file - output the old one first
2904 self.streamOneP4File(self.stream_file, self.stream_contents)
2905 self.stream_file = {}
2906 self.stream_contents = []
2907 self.stream_have_file_info = False
Luke Diamandb9327052009-07-30 00:13:46 +01002908
Andrew Garberc3f61632011-04-07 02:01:21 -04002909 # pick up the new file information... for the
2910 # 'data' field we need to append to our array
2911 for k in marshalled.keys():
2912 if k == 'data':
Lars Schneiderd2176a52015-09-26 09:55:01 +02002913 if 'streamContentSize' not in self.stream_file:
2914 self.stream_file['streamContentSize'] = 0
2915 self.stream_file['streamContentSize'] += len(marshalled['data'])
Andrew Garberc3f61632011-04-07 02:01:21 -04002916 self.stream_contents.append(marshalled['data'])
2917 else:
2918 self.stream_file[k] = marshalled[k]
Luke Diamandb9327052009-07-30 00:13:46 +01002919
Lars Schneiderd2176a52015-09-26 09:55:01 +02002920 if (verbose and
2921 'streamContentSize' in self.stream_file and
2922 'fileSize' in self.stream_file and
2923 'depotFile' in self.stream_file):
2924 size = int(self.stream_file["fileSize"])
2925 if size > 0:
2926 progress = 100*self.stream_file['streamContentSize']/size
2927 sys.stdout.write('\r%s %d%% (%i MB)' % (self.stream_file['depotFile'], progress, int(size/1024/1024)))
2928 sys.stdout.flush()
2929
Andrew Garberc3f61632011-04-07 02:01:21 -04002930 self.stream_have_file_info = True
Luke Diamandb9327052009-07-30 00:13:46 +01002931
2932 # Stream directly from "p4 files" into "git fast-import"
2933 def streamP4Files(self, files):
Simon Hausmann30b59402008-03-03 11:55:48 +01002934 filesForCommit = []
2935 filesToRead = []
Luke Diamandb9327052009-07-30 00:13:46 +01002936 filesToDelete = []
Simon Hausmann30b59402008-03-03 11:55:48 +01002937
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +01002938 for f in files:
Pete Wyckoffecb7cf92012-01-02 18:05:53 -05002939 filesForCommit.append(f)
2940 if f['action'] in self.delete_actions:
2941 filesToDelete.append(f)
2942 else:
2943 filesToRead.append(f)
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03002944
Luke Diamandb9327052009-07-30 00:13:46 +01002945 # deleted files...
2946 for f in filesToDelete:
2947 self.streamOneP4Deletion(f)
2948
Simon Hausmann30b59402008-03-03 11:55:48 +01002949 if len(filesToRead) > 0:
Luke Diamandb9327052009-07-30 00:13:46 +01002950 self.stream_file = {}
2951 self.stream_contents = []
2952 self.stream_have_file_info = False
2953
Andrew Garberc3f61632011-04-07 02:01:21 -04002954 # curry self argument
2955 def streamP4FilesCbSelf(entry):
2956 self.streamP4FilesCb(entry)
Luke Diamandb9327052009-07-30 00:13:46 +01002957
Luke Diamand123f6312018-05-23 23:20:26 +01002958 fileArgs = []
2959 for f in filesToRead:
2960 if 'shelved_cl' in f:
2961 # Handle shelved CLs using the "p4 print file@=N" syntax to print
2962 # the contents
2963 fileArg = '%s@=%d' % (f['path'], f['shelved_cl'])
2964 else:
2965 fileArg = '%s#%s' % (f['path'], f['rev'])
2966
2967 fileArgs.append(fileArg)
Luke Diamand6de040d2011-10-16 10:47:52 -04002968
2969 p4CmdList(["-x", "-", "print"],
2970 stdin=fileArgs,
2971 cb=streamP4FilesCbSelf)
Han-Wen Nienhuysf2eda792007-05-23 18:49:35 -03002972
Luke Diamandb9327052009-07-30 00:13:46 +01002973 # do the last chunk
Luke Diamanddba1c9d2018-06-19 09:04:07 +01002974 if 'depotFile' in self.stream_file:
Luke Diamandb9327052009-07-30 00:13:46 +01002975 self.streamOneP4File(self.stream_file, self.stream_contents)
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03002976
Luke Diamandaffb4742012-01-19 09:52:27 +00002977 def make_email(self, userid):
2978 if userid in self.users:
2979 return self.users[userid]
2980 else:
2981 return "%s <a@b>" % userid
2982
Luke Diamand06804c72012-04-11 17:21:24 +02002983 def streamTag(self, gitStream, labelName, labelDetails, commit, epoch):
Luke Diamandb43702a2015-08-27 08:18:58 +01002984 """ Stream a p4 tag.
2985 commit is either a git commit, or a fast-import mark, ":<p4commit>"
2986 """
2987
Luke Diamand06804c72012-04-11 17:21:24 +02002988 if verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01002989 print("writing tag %s for commit %s" % (labelName, commit))
Luke Diamand06804c72012-04-11 17:21:24 +02002990 gitStream.write("tag %s\n" % labelName)
2991 gitStream.write("from %s\n" % commit)
2992
Luke Diamanddba1c9d2018-06-19 09:04:07 +01002993 if 'Owner' in labelDetails:
Luke Diamand06804c72012-04-11 17:21:24 +02002994 owner = labelDetails["Owner"]
2995 else:
2996 owner = None
2997
2998 # Try to use the owner of the p4 label, or failing that,
2999 # the current p4 user id.
3000 if owner:
3001 email = self.make_email(owner)
3002 else:
3003 email = self.make_email(self.p4UserId())
3004 tagger = "%s %s %s" % (email, epoch, self.tz)
3005
3006 gitStream.write("tagger %s\n" % tagger)
3007
Luke Diamandf2606b12018-06-19 09:04:10 +01003008 print("labelDetails=",labelDetails)
Luke Diamanddba1c9d2018-06-19 09:04:07 +01003009 if 'Description' in labelDetails:
Luke Diamand06804c72012-04-11 17:21:24 +02003010 description = labelDetails['Description']
3011 else:
3012 description = 'Label from git p4'
3013
3014 gitStream.write("data %d\n" % len(description))
3015 gitStream.write(description)
3016 gitStream.write("\n")
3017
Lars Schneider4ae048e2015-12-08 10:36:22 +01003018 def inClientSpec(self, path):
3019 if not self.clientSpecDirs:
3020 return True
3021 inClientSpec = self.clientSpecDirs.map_in_client(path)
3022 if not inClientSpec and self.verbose:
3023 print('Ignoring file outside of client spec: {0}'.format(path))
3024 return inClientSpec
3025
3026 def hasBranchPrefix(self, path):
3027 if not self.branchPrefixes:
3028 return True
3029 hasPrefix = [p for p in self.branchPrefixes
3030 if p4PathStartsWith(path, p)]
Andrew Oakley09667d02016-06-22 10:26:11 +01003031 if not hasPrefix and self.verbose:
Lars Schneider4ae048e2015-12-08 10:36:22 +01003032 print('Ignoring file outside of prefix: {0}'.format(path))
3033 return hasPrefix
3034
Luke Diamand89143ac2018-10-15 12:14:08 +01003035 def commit(self, details, files, branch, parent = "", allow_empty=False):
Simon Hausmannb9847332007-03-20 20:54:23 +01003036 epoch = details["time"]
3037 author = details["user"]
Jan Durovec26e6a272016-04-19 19:49:41 +00003038 jobs = self.extractJobsFromCommit(details)
Simon Hausmannb9847332007-03-20 20:54:23 +01003039
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02003040 if self.verbose:
Lars Schneider4ae048e2015-12-08 10:36:22 +01003041 print('commit into {0}'.format(branch))
Han-Wen Nienhuys96e07dd2007-05-23 18:49:35 -03003042
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09003043 if self.clientSpecDirs:
3044 self.clientSpecDirs.update_client_spec_path_cache(files)
3045
Lars Schneider4ae048e2015-12-08 10:36:22 +01003046 files = [f for f in files
3047 if self.inClientSpec(f['path']) and self.hasBranchPrefix(f['path'])]
3048
Luke Diamand89143ac2018-10-15 12:14:08 +01003049 if gitConfigBool('git-p4.keepEmptyCommits'):
3050 allow_empty = True
3051
3052 if not files and not allow_empty:
Lars Schneider4ae048e2015-12-08 10:36:22 +01003053 print('Ignoring revision {0} as it would produce an empty commit.'
3054 .format(details['change']))
3055 return
3056
Simon Hausmannb9847332007-03-20 20:54:23 +01003057 self.gitStream.write("commit %s\n" % branch)
Luke Diamandb43702a2015-08-27 08:18:58 +01003058 self.gitStream.write("mark :%s\n" % details["change"])
Simon Hausmannb9847332007-03-20 20:54:23 +01003059 self.committedChanges.add(int(details["change"]))
3060 committer = ""
Simon Hausmannb607e712007-05-20 10:55:54 +02003061 if author not in self.users:
3062 self.getUserMapFromPerforceServer()
Luke Diamandaffb4742012-01-19 09:52:27 +00003063 committer = "%s %s %s" % (self.make_email(author), epoch, self.tz)
Simon Hausmannb9847332007-03-20 20:54:23 +01003064
3065 self.gitStream.write("committer %s\n" % committer)
3066
3067 self.gitStream.write("data <<EOT\n")
3068 self.gitStream.write(details["desc"])
Jan Durovec26e6a272016-04-19 19:49:41 +00003069 if len(jobs) > 0:
3070 self.gitStream.write("\nJobs: %s" % (' '.join(jobs)))
Luke Diamand123f6312018-05-23 23:20:26 +01003071
3072 if not self.suppress_meta_comment:
3073 self.gitStream.write("\n[git-p4: depot-paths = \"%s\": change = %s" %
3074 (','.join(self.branchPrefixes), details["change"]))
3075 if len(details['options']) > 0:
3076 self.gitStream.write(": options = %s" % details['options'])
3077 self.gitStream.write("]\n")
3078
3079 self.gitStream.write("EOT\n\n")
Simon Hausmannb9847332007-03-20 20:54:23 +01003080
3081 if len(parent) > 0:
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02003082 if self.verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01003083 print("parent %s" % parent)
Simon Hausmannb9847332007-03-20 20:54:23 +01003084 self.gitStream.write("from %s\n" % parent)
3085
Lars Schneider4ae048e2015-12-08 10:36:22 +01003086 self.streamP4Files(files)
Simon Hausmannb9847332007-03-20 20:54:23 +01003087 self.gitStream.write("\n")
3088
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02003089 change = int(details["change"])
3090
Luke Diamanddba1c9d2018-06-19 09:04:07 +01003091 if change in self.labels:
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02003092 label = self.labels[change]
3093 labelDetails = label[0]
3094 labelRevisions = label[1]
Simon Hausmann71b112d2007-05-19 11:54:11 +02003095 if self.verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01003096 print("Change %s is labelled %s" % (change, labelDetails))
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02003097
Luke Diamand6de040d2011-10-16 10:47:52 -04003098 files = p4CmdList(["files"] + ["%s...@%s" % (p, change)
Pete Wyckoffe63231e2012-08-11 12:55:02 -04003099 for p in self.branchPrefixes])
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02003100
3101 if len(files) == len(labelRevisions):
3102
3103 cleanedFiles = {}
3104 for info in files:
Pete Wyckoff56c09342011-02-19 08:17:57 -05003105 if info["action"] in self.delete_actions:
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02003106 continue
3107 cleanedFiles[info["depotFile"]] = info["rev"]
3108
3109 if cleanedFiles == labelRevisions:
Luke Diamand06804c72012-04-11 17:21:24 +02003110 self.streamTag(self.gitStream, 'tag_%s' % labelDetails['label'], labelDetails, branch, epoch)
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02003111
3112 else:
Simon Hausmanna46668f2007-03-28 17:05:38 +02003113 if not self.silent:
Luke Diamandf2606b12018-06-19 09:04:10 +01003114 print("Tag %s does not match with change %s: files do not match."
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03003115 % (labelDetails["label"], change))
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02003116
3117 else:
Simon Hausmanna46668f2007-03-28 17:05:38 +02003118 if not self.silent:
Luke Diamandf2606b12018-06-19 09:04:10 +01003119 print("Tag %s does not match with change %s: file count is different."
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03003120 % (labelDetails["label"], change))
Simon Hausmannb9847332007-03-20 20:54:23 +01003121
Luke Diamand06804c72012-04-11 17:21:24 +02003122 # Build a dictionary of changelists and labels, for "detect-labels" option.
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02003123 def getLabels(self):
3124 self.labels = {}
3125
Luke Diamand52a48802012-01-19 09:52:25 +00003126 l = p4CmdList(["labels"] + ["%s..." % p for p in self.depotPaths])
Simon Hausmann10c32112007-04-08 10:15:47 +02003127 if len(l) > 0 and not self.silent:
Luke Diamand4d885192018-06-19 09:04:08 +01003128 print("Finding files belonging to labels in %s" % self.depotPaths)
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02003129
3130 for output in l:
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02003131 label = output["label"]
3132 revisions = {}
3133 newestChange = 0
Simon Hausmann71b112d2007-05-19 11:54:11 +02003134 if self.verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01003135 print("Querying files for label %s" % label)
Luke Diamand6de040d2011-10-16 10:47:52 -04003136 for file in p4CmdList(["files"] +
3137 ["%s...@%s" % (p, label)
3138 for p in self.depotPaths]):
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02003139 revisions[file["depotFile"]] = file["rev"]
3140 change = int(file["change"])
3141 if change > newestChange:
3142 newestChange = change
3143
Simon Hausmann9bda3a82007-05-19 12:05:40 +02003144 self.labels[newestChange] = [output, revisions]
3145
3146 if self.verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01003147 print("Label changes: %s" % self.labels.keys())
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02003148
Luke Diamand06804c72012-04-11 17:21:24 +02003149 # Import p4 labels as git tags. A direct mapping does not
3150 # exist, so assume that if all the files are at the same revision
3151 # then we can use that, or it's something more complicated we should
3152 # just ignore.
3153 def importP4Labels(self, stream, p4Labels):
3154 if verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01003155 print("import p4 labels: " + ' '.join(p4Labels))
Luke Diamand06804c72012-04-11 17:21:24 +02003156
3157 ignoredP4Labels = gitConfigList("git-p4.ignoredP4Labels")
Luke Diamandc8942a22012-04-11 17:21:24 +02003158 validLabelRegexp = gitConfig("git-p4.labelImportRegexp")
Luke Diamand06804c72012-04-11 17:21:24 +02003159 if len(validLabelRegexp) == 0:
3160 validLabelRegexp = defaultLabelRegexp
3161 m = re.compile(validLabelRegexp)
3162
3163 for name in p4Labels:
3164 commitFound = False
3165
3166 if not m.match(name):
3167 if verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01003168 print("label %s does not match regexp %s" % (name,validLabelRegexp))
Luke Diamand06804c72012-04-11 17:21:24 +02003169 continue
3170
3171 if name in ignoredP4Labels:
3172 continue
3173
3174 labelDetails = p4CmdList(['label', "-o", name])[0]
3175
3176 # get the most recent changelist for each file in this label
3177 change = p4Cmd(["changes", "-m", "1"] + ["%s...@%s" % (p, name)
3178 for p in self.depotPaths])
3179
Luke Diamanddba1c9d2018-06-19 09:04:07 +01003180 if 'change' in change:
Luke Diamand06804c72012-04-11 17:21:24 +02003181 # find the corresponding git commit; take the oldest commit
3182 changelist = int(change['change'])
Luke Diamandb43702a2015-08-27 08:18:58 +01003183 if changelist in self.committedChanges:
3184 gitCommit = ":%d" % changelist # use a fast-import mark
Luke Diamand06804c72012-04-11 17:21:24 +02003185 commitFound = True
Luke Diamandb43702a2015-08-27 08:18:58 +01003186 else:
3187 gitCommit = read_pipe(["git", "rev-list", "--max-count=1",
3188 "--reverse", ":/\[git-p4:.*change = %d\]" % changelist], ignore_error=True)
3189 if len(gitCommit) == 0:
Luke Diamandf2606b12018-06-19 09:04:10 +01003190 print("importing label %s: could not find git commit for changelist %d" % (name, changelist))
Luke Diamandb43702a2015-08-27 08:18:58 +01003191 else:
3192 commitFound = True
3193 gitCommit = gitCommit.strip()
3194
3195 if commitFound:
Luke Diamand06804c72012-04-11 17:21:24 +02003196 # Convert from p4 time format
3197 try:
3198 tmwhen = time.strptime(labelDetails['Update'], "%Y/%m/%d %H:%M:%S")
3199 except ValueError:
Luke Diamandf2606b12018-06-19 09:04:10 +01003200 print("Could not convert label time %s" % labelDetails['Update'])
Luke Diamand06804c72012-04-11 17:21:24 +02003201 tmwhen = 1
3202
3203 when = int(time.mktime(tmwhen))
3204 self.streamTag(stream, name, labelDetails, gitCommit, when)
3205 if verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01003206 print("p4 label %s mapped to git commit %s" % (name, gitCommit))
Luke Diamand06804c72012-04-11 17:21:24 +02003207 else:
3208 if verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01003209 print("Label %s has no changelists - possibly deleted?" % name)
Luke Diamand06804c72012-04-11 17:21:24 +02003210
3211 if not commitFound:
3212 # We can't import this label; don't try again as it will get very
3213 # expensive repeatedly fetching all the files for labels that will
3214 # never be imported. If the label is moved in the future, the
3215 # ignore will need to be removed manually.
3216 system(["git", "config", "--add", "git-p4.ignoredP4Labels", name])
3217
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03003218 def guessProjectName(self):
3219 for p in self.depotPaths:
Simon Hausmann6e5295c2007-06-11 08:50:57 +02003220 if p.endswith("/"):
3221 p = p[:-1]
3222 p = p[p.strip().rfind("/") + 1:]
3223 if not p.endswith("/"):
3224 p += "/"
3225 return p
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03003226
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02003227 def getBranchMapping(self):
Simon Hausmann6555b2c2007-06-17 11:25:34 +02003228 lostAndFoundBranches = set()
3229
Vitor Antunes8ace74c2011-08-19 00:44:04 +01003230 user = gitConfig("git-p4.branchUser")
3231 if len(user) > 0:
3232 command = "branches -u %s" % user
3233 else:
3234 command = "branches"
3235
3236 for info in p4CmdList(command):
Luke Diamand52a48802012-01-19 09:52:25 +00003237 details = p4Cmd(["branch", "-o", info["branch"]])
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02003238 viewIdx = 0
Luke Diamanddba1c9d2018-06-19 09:04:07 +01003239 while "View%s" % viewIdx in details:
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02003240 paths = details["View%s" % viewIdx].split(" ")
3241 viewIdx = viewIdx + 1
3242 # require standard //depot/foo/... //depot/bar/... mapping
3243 if len(paths) != 2 or not paths[0].endswith("/...") or not paths[1].endswith("/..."):
3244 continue
3245 source = paths[0]
3246 destination = paths[1]
Simon Hausmann6509e192007-06-07 09:41:53 +02003247 ## HACK
Tor Arvid Lundd53de8b2011-03-15 13:08:02 +01003248 if p4PathStartsWith(source, self.depotPaths[0]) and p4PathStartsWith(destination, self.depotPaths[0]):
Simon Hausmann6509e192007-06-07 09:41:53 +02003249 source = source[len(self.depotPaths[0]):-4]
3250 destination = destination[len(self.depotPaths[0]):-4]
Simon Hausmann6555b2c2007-06-17 11:25:34 +02003251
Simon Hausmann1a2edf42007-06-17 15:10:24 +02003252 if destination in self.knownBranches:
3253 if not self.silent:
Luke Diamandf2606b12018-06-19 09:04:10 +01003254 print("p4 branch %s defines a mapping from %s to %s" % (info["branch"], source, destination))
3255 print("but there exists another mapping from %s to %s already!" % (self.knownBranches[destination], destination))
Simon Hausmann1a2edf42007-06-17 15:10:24 +02003256 continue
3257
Simon Hausmann6555b2c2007-06-17 11:25:34 +02003258 self.knownBranches[destination] = source
3259
3260 lostAndFoundBranches.discard(destination)
3261
Simon Hausmann29bdbac2007-05-19 10:23:12 +02003262 if source not in self.knownBranches:
Simon Hausmann6555b2c2007-06-17 11:25:34 +02003263 lostAndFoundBranches.add(source)
3264
Vitor Antunes7199cf12011-08-19 00:44:05 +01003265 # Perforce does not strictly require branches to be defined, so we also
3266 # check git config for a branch list.
3267 #
3268 # Example of branch definition in git config file:
3269 # [git-p4]
3270 # branchList=main:branchA
3271 # branchList=main:branchB
3272 # branchList=branchA:branchC
3273 configBranches = gitConfigList("git-p4.branchList")
3274 for branch in configBranches:
3275 if branch:
3276 (source, destination) = branch.split(":")
3277 self.knownBranches[destination] = source
3278
3279 lostAndFoundBranches.discard(destination)
3280
3281 if source not in self.knownBranches:
3282 lostAndFoundBranches.add(source)
3283
Simon Hausmann6555b2c2007-06-17 11:25:34 +02003284
3285 for branch in lostAndFoundBranches:
3286 self.knownBranches[branch] = branch
Simon Hausmann29bdbac2007-05-19 10:23:12 +02003287
Simon Hausmann38f9f5e2007-11-15 10:38:45 +01003288 def getBranchMappingFromGitBranches(self):
3289 branches = p4BranchesInGit(self.importIntoRemotes)
3290 for branch in branches.keys():
3291 if branch == "master":
3292 branch = "main"
3293 else:
3294 branch = branch[len(self.projectName):]
3295 self.knownBranches[branch] = branch
3296
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003297 def updateOptionDict(self, d):
3298 option_keys = {}
3299 if self.keepRepoPath:
3300 option_keys['keepRepoPath'] = 1
3301
3302 d["options"] = ' '.join(sorted(option_keys.keys()))
3303
3304 def readOptions(self, d):
Luke Diamanddba1c9d2018-06-19 09:04:07 +01003305 self.keepRepoPath = ('options' in d
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003306 and ('keepRepoPath' in d['options']))
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003307
Simon Hausmann8134f692007-08-26 16:44:55 +02003308 def gitRefForBranch(self, branch):
3309 if branch == "main":
3310 return self.refPrefix + "master"
3311
3312 if len(branch) <= 0:
3313 return branch
3314
3315 return self.refPrefix + self.projectName + branch
3316
Simon Hausmann1ca3d712007-08-26 17:36:55 +02003317 def gitCommitByP4Change(self, ref, change):
3318 if self.verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01003319 print("looking in ref " + ref + " for change %s using bisect..." % change)
Simon Hausmann1ca3d712007-08-26 17:36:55 +02003320
3321 earliestCommit = ""
3322 latestCommit = parseRevision(ref)
3323
3324 while True:
3325 if self.verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01003326 print("trying: earliest %s latest %s" % (earliestCommit, latestCommit))
Simon Hausmann1ca3d712007-08-26 17:36:55 +02003327 next = read_pipe("git rev-list --bisect %s %s" % (latestCommit, earliestCommit)).strip()
3328 if len(next) == 0:
3329 if self.verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01003330 print("argh")
Simon Hausmann1ca3d712007-08-26 17:36:55 +02003331 return ""
3332 log = extractLogMessageFromGitCommit(next)
3333 settings = extractSettingsGitLog(log)
3334 currentChange = int(settings['change'])
3335 if self.verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01003336 print("current change %s" % currentChange)
Simon Hausmann1ca3d712007-08-26 17:36:55 +02003337
3338 if currentChange == change:
3339 if self.verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01003340 print("found %s" % next)
Simon Hausmann1ca3d712007-08-26 17:36:55 +02003341 return next
3342
3343 if currentChange < change:
3344 earliestCommit = "^%s" % next
3345 else:
Mazo, Andrey2dda7412019-04-01 18:02:17 +00003346 if next == latestCommit:
3347 die("Infinite loop while looking in ref %s for change %s. Check your branch mappings" % (ref, change))
3348 latestCommit = "%s^@" % next
Simon Hausmann1ca3d712007-08-26 17:36:55 +02003349
3350 return ""
3351
3352 def importNewBranch(self, branch, maxChange):
3353 # make fast-import flush all changes to disk and update the refs using the checkpoint
3354 # command so that we can try to find the branch parent in the git history
3355 self.gitStream.write("checkpoint\n\n");
3356 self.gitStream.flush();
3357 branchPrefix = self.depotPaths[0] + branch + "/"
3358 range = "@1,%s" % maxChange
3359 #print "prefix" + branchPrefix
Lex Spoon96b2d542015-04-20 11:00:20 -04003360 changes = p4ChangesForPaths([branchPrefix], range, self.changes_block_size)
Simon Hausmann1ca3d712007-08-26 17:36:55 +02003361 if len(changes) <= 0:
3362 return False
3363 firstChange = changes[0]
3364 #print "first change in branch: %s" % firstChange
3365 sourceBranch = self.knownBranches[branch]
3366 sourceDepotPath = self.depotPaths[0] + sourceBranch
3367 sourceRef = self.gitRefForBranch(sourceBranch)
3368 #print "source " + sourceBranch
3369
Luke Diamand52a48802012-01-19 09:52:25 +00003370 branchParentChange = int(p4Cmd(["changes", "-m", "1", "%s...@1,%s" % (sourceDepotPath, firstChange)])["change"])
Simon Hausmann1ca3d712007-08-26 17:36:55 +02003371 #print "branch parent: %s" % branchParentChange
3372 gitParent = self.gitCommitByP4Change(sourceRef, branchParentChange)
3373 if len(gitParent) > 0:
3374 self.initialParents[self.gitRefForBranch(branch)] = gitParent
3375 #print "parent git commit: %s" % gitParent
3376
3377 self.importChanges(changes)
3378 return True
3379
Vitor Antunesfed23692012-01-25 23:48:22 +00003380 def searchParent(self, parent, branch, target):
3381 parentFound = False
Pete Wyckoffc7d34882013-01-26 22:11:21 -05003382 for blob in read_pipe_lines(["git", "rev-list", "--reverse",
3383 "--no-merges", parent]):
Vitor Antunesfed23692012-01-25 23:48:22 +00003384 blob = blob.strip()
3385 if len(read_pipe(["git", "diff-tree", blob, target])) == 0:
3386 parentFound = True
3387 if self.verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01003388 print("Found parent of %s in commit %s" % (branch, blob))
Vitor Antunesfed23692012-01-25 23:48:22 +00003389 break
3390 if parentFound:
3391 return blob
3392 else:
3393 return None
3394
Luke Diamand89143ac2018-10-15 12:14:08 +01003395 def importChanges(self, changes, origin_revision=0):
Simon Hausmanne87f37a2007-08-26 16:00:52 +02003396 cnt = 1
3397 for change in changes:
Luke Diamand89143ac2018-10-15 12:14:08 +01003398 description = p4_describe(change)
Simon Hausmanne87f37a2007-08-26 16:00:52 +02003399 self.updateOptionDict(description)
3400
3401 if not self.silent:
3402 sys.stdout.write("\rImporting revision %s (%s%%)" % (change, cnt * 100 / len(changes)))
3403 sys.stdout.flush()
3404 cnt = cnt + 1
3405
3406 try:
3407 if self.detectBranches:
3408 branches = self.splitFilesIntoBranches(description)
3409 for branch in branches.keys():
3410 ## HACK --hwn
3411 branchPrefix = self.depotPaths[0] + branch + "/"
Pete Wyckoffe63231e2012-08-11 12:55:02 -04003412 self.branchPrefixes = [ branchPrefix ]
Simon Hausmanne87f37a2007-08-26 16:00:52 +02003413
3414 parent = ""
3415
3416 filesForCommit = branches[branch]
3417
3418 if self.verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01003419 print("branch is %s" % branch)
Simon Hausmanne87f37a2007-08-26 16:00:52 +02003420
3421 self.updatedBranches.add(branch)
3422
3423 if branch not in self.createdBranches:
3424 self.createdBranches.add(branch)
3425 parent = self.knownBranches[branch]
3426 if parent == branch:
3427 parent = ""
Simon Hausmann1ca3d712007-08-26 17:36:55 +02003428 else:
3429 fullBranch = self.projectName + branch
3430 if fullBranch not in self.p4BranchesInGit:
3431 if not self.silent:
3432 print("\n Importing new branch %s" % fullBranch);
3433 if self.importNewBranch(branch, change - 1):
3434 parent = ""
3435 self.p4BranchesInGit.append(fullBranch)
3436 if not self.silent:
3437 print("\n Resuming with change %s" % change);
3438
3439 if self.verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01003440 print("parent determined through known branches: %s" % parent)
Simon Hausmanne87f37a2007-08-26 16:00:52 +02003441
Simon Hausmann8134f692007-08-26 16:44:55 +02003442 branch = self.gitRefForBranch(branch)
3443 parent = self.gitRefForBranch(parent)
Simon Hausmanne87f37a2007-08-26 16:00:52 +02003444
3445 if self.verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01003446 print("looking for initial parent for %s; current parent is %s" % (branch, parent))
Simon Hausmanne87f37a2007-08-26 16:00:52 +02003447
3448 if len(parent) == 0 and branch in self.initialParents:
3449 parent = self.initialParents[branch]
3450 del self.initialParents[branch]
3451
Vitor Antunesfed23692012-01-25 23:48:22 +00003452 blob = None
3453 if len(parent) > 0:
Pete Wyckoff4f9273d2013-01-26 22:11:04 -05003454 tempBranch = "%s/%d" % (self.tempBranchLocation, change)
Vitor Antunesfed23692012-01-25 23:48:22 +00003455 if self.verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01003456 print("Creating temporary branch: " + tempBranch)
Pete Wyckoffe63231e2012-08-11 12:55:02 -04003457 self.commit(description, filesForCommit, tempBranch)
Vitor Antunesfed23692012-01-25 23:48:22 +00003458 self.tempBranches.append(tempBranch)
3459 self.checkpoint()
3460 blob = self.searchParent(parent, branch, tempBranch)
3461 if blob:
Pete Wyckoffe63231e2012-08-11 12:55:02 -04003462 self.commit(description, filesForCommit, branch, blob)
Vitor Antunesfed23692012-01-25 23:48:22 +00003463 else:
3464 if self.verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01003465 print("Parent of %s not found. Committing into head of %s" % (branch, parent))
Pete Wyckoffe63231e2012-08-11 12:55:02 -04003466 self.commit(description, filesForCommit, branch, parent)
Simon Hausmanne87f37a2007-08-26 16:00:52 +02003467 else:
Luke Diamand89143ac2018-10-15 12:14:08 +01003468 files = self.extractFilesFromCommit(description)
Pete Wyckoffe63231e2012-08-11 12:55:02 -04003469 self.commit(description, files, self.branch,
Simon Hausmanne87f37a2007-08-26 16:00:52 +02003470 self.initialParent)
Pete Wyckoff47497842013-01-14 19:47:04 -05003471 # only needed once, to connect to the previous commit
Simon Hausmanne87f37a2007-08-26 16:00:52 +02003472 self.initialParent = ""
3473 except IOError:
Luke Diamandf2606b12018-06-19 09:04:10 +01003474 print(self.gitError.read())
Simon Hausmanne87f37a2007-08-26 16:00:52 +02003475 sys.exit(1)
3476
Luke Diamandb9d34db2018-06-08 21:32:44 +01003477 def sync_origin_only(self):
3478 if self.syncWithOrigin:
3479 self.hasOrigin = originP4BranchesExist()
3480 if self.hasOrigin:
3481 if not self.silent:
Luke Diamandf2606b12018-06-19 09:04:10 +01003482 print('Syncing with origin first, using "git fetch origin"')
Luke Diamandb9d34db2018-06-08 21:32:44 +01003483 system("git fetch origin")
3484
Simon Hausmannc208a242007-08-26 16:07:18 +02003485 def importHeadRevision(self, revision):
Luke Diamandf2606b12018-06-19 09:04:10 +01003486 print("Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch))
Simon Hausmannc208a242007-08-26 16:07:18 +02003487
Pete Wyckoff4e2e6ce2011-07-31 09:45:55 -04003488 details = {}
3489 details["user"] = "git perforce import user"
Pete Wyckoff1494fcb2011-02-19 08:17:56 -05003490 details["desc"] = ("Initial import of %s from the state at revision %s\n"
Simon Hausmannc208a242007-08-26 16:07:18 +02003491 % (' '.join(self.depotPaths), revision))
3492 details["change"] = revision
3493 newestRevision = 0
3494
3495 fileCnt = 0
Luke Diamand6de040d2011-10-16 10:47:52 -04003496 fileArgs = ["%s...%s" % (p,revision) for p in self.depotPaths]
3497
3498 for info in p4CmdList(["files"] + fileArgs):
Simon Hausmannc208a242007-08-26 16:07:18 +02003499
Pete Wyckoff68b28592011-02-19 08:17:55 -05003500 if 'code' in info and info['code'] == 'error':
Simon Hausmannc208a242007-08-26 16:07:18 +02003501 sys.stderr.write("p4 returned an error: %s\n"
3502 % info['data'])
Pete Wyckoffd88e7072011-02-19 08:17:58 -05003503 if info['data'].find("must refer to client") >= 0:
3504 sys.stderr.write("This particular p4 error is misleading.\n")
3505 sys.stderr.write("Perhaps the depot path was misspelled.\n");
3506 sys.stderr.write("Depot path: %s\n" % " ".join(self.depotPaths))
Simon Hausmannc208a242007-08-26 16:07:18 +02003507 sys.exit(1)
Pete Wyckoff68b28592011-02-19 08:17:55 -05003508 if 'p4ExitCode' in info:
3509 sys.stderr.write("p4 exitcode: %s\n" % info['p4ExitCode'])
Simon Hausmannc208a242007-08-26 16:07:18 +02003510 sys.exit(1)
3511
3512
3513 change = int(info["change"])
3514 if change > newestRevision:
3515 newestRevision = change
3516
Pete Wyckoff56c09342011-02-19 08:17:57 -05003517 if info["action"] in self.delete_actions:
Simon Hausmannc208a242007-08-26 16:07:18 +02003518 # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
3519 #fileCnt = fileCnt + 1
3520 continue
3521
3522 for prop in ["depotFile", "rev", "action", "type" ]:
3523 details["%s%s" % (prop, fileCnt)] = info[prop]
3524
3525 fileCnt = fileCnt + 1
3526
3527 details["change"] = newestRevision
Pete Wyckoff4e2e6ce2011-07-31 09:45:55 -04003528
Pete Wyckoff9dcb9f22012-04-08 20:18:01 -04003529 # Use time from top-most change so that all git p4 clones of
Pete Wyckoff4e2e6ce2011-07-31 09:45:55 -04003530 # the same p4 repo have the same commit SHA1s.
Pete Wyckoff18fa13d2012-11-23 17:35:34 -05003531 res = p4_describe(newestRevision)
3532 details["time"] = res["time"]
Pete Wyckoff4e2e6ce2011-07-31 09:45:55 -04003533
Simon Hausmannc208a242007-08-26 16:07:18 +02003534 self.updateOptionDict(details)
3535 try:
Pete Wyckoffe63231e2012-08-11 12:55:02 -04003536 self.commit(details, self.extractFilesFromCommit(details), self.branch)
Philip.McGrawde5abb52019-08-27 06:43:58 +03003537 except IOError as err:
Luke Diamandf2606b12018-06-19 09:04:10 +01003538 print("IO error with git fast-import. Is your git version recent enough?")
Philip.McGrawde5abb52019-08-27 06:43:58 +03003539 print("IO error details: {}".format(err))
Luke Diamandf2606b12018-06-19 09:04:10 +01003540 print(self.gitError.read())
Simon Hausmannc208a242007-08-26 16:07:18 +02003541
Luke Diamand123f6312018-05-23 23:20:26 +01003542 def openStreams(self):
3543 self.importProcess = subprocess.Popen(["git", "fast-import"],
3544 stdin=subprocess.PIPE,
3545 stdout=subprocess.PIPE,
3546 stderr=subprocess.PIPE);
3547 self.gitOutput = self.importProcess.stdout
3548 self.gitStream = self.importProcess.stdin
3549 self.gitError = self.importProcess.stderr
3550
3551 def closeStreams(self):
3552 self.gitStream.close()
3553 if self.importProcess.wait() != 0:
3554 die("fast-import failed: %s" % self.gitError.read())
3555 self.gitOutput.close()
3556 self.gitError.close()
Simon Hausmannc208a242007-08-26 16:07:18 +02003557
Simon Hausmannb9847332007-03-20 20:54:23 +01003558 def run(self, args):
Simon Hausmanna028a982007-05-23 00:03:08 +02003559 if self.importIntoRemotes:
3560 self.refPrefix = "refs/remotes/p4/"
3561 else:
Marius Storm-Olsendb775552007-06-07 15:13:59 +02003562 self.refPrefix = "refs/heads/p4/"
Simon Hausmanna028a982007-05-23 00:03:08 +02003563
Luke Diamandb9d34db2018-06-08 21:32:44 +01003564 self.sync_origin_only()
Simon Hausmann10f880f2007-05-24 22:28:28 +02003565
Pete Wyckoff5a8e84c2013-01-14 19:47:05 -05003566 branch_arg_given = bool(self.branch)
Simon Hausmann569d1bd2007-03-22 21:34:16 +01003567 if len(self.branch) == 0:
Marius Storm-Olsendb775552007-06-07 15:13:59 +02003568 self.branch = self.refPrefix + "master"
Simon Hausmanna028a982007-05-23 00:03:08 +02003569 if gitBranchExists("refs/heads/p4") and self.importIntoRemotes:
Simon Hausmann48df6fd2007-05-17 21:18:53 +02003570 system("git update-ref %s refs/heads/p4" % self.branch)
Pete Wyckoff55d12432013-01-14 19:46:59 -05003571 system("git branch -D p4")
Simon Hausmann179caeb2007-03-22 22:17:42 +01003572
Pete Wyckoffa93d33e2012-02-25 20:06:24 -05003573 # accept either the command-line option, or the configuration variable
3574 if self.useClientSpec:
3575 # will use this after clone to set the variable
3576 self.useClientSpec_from_options = True
3577 else:
Pete Wyckoff0d609032013-01-26 22:11:24 -05003578 if gitConfigBool("git-p4.useclientspec"):
Pete Wyckoff09fca772011-12-24 21:07:39 -05003579 self.useClientSpec = True
3580 if self.useClientSpec:
Pete Wyckoff543987b2012-02-25 20:06:25 -05003581 self.clientSpecDirs = getClientSpec()
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +01003582
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03003583 # TODO: should always look at previous commits,
3584 # merge with previous imports, if possible.
3585 if args == []:
Simon Hausmannd414c742007-05-25 11:36:42 +02003586 if self.hasOrigin:
Simon Hausmann5ca44612007-08-24 17:44:16 +02003587 createOrUpdateBranchesFromOrigin(self.refPrefix, self.silent)
Pete Wyckoff3b650fc2013-01-14 19:46:58 -05003588
3589 # branches holds mapping from branch name to sha1
3590 branches = p4BranchesInGit(self.importIntoRemotes)
Pete Wyckoff8c9e8b62013-01-14 19:47:06 -05003591
3592 # restrict to just this one, disabling detect-branches
3593 if branch_arg_given:
3594 short = self.branch.split("/")[-1]
3595 if short in branches:
3596 self.p4BranchesInGit = [ short ]
3597 else:
3598 self.p4BranchesInGit = branches.keys()
Simon Hausmannabcd7902007-05-24 22:25:36 +02003599
3600 if len(self.p4BranchesInGit) > 1:
3601 if not self.silent:
Luke Diamandf2606b12018-06-19 09:04:10 +01003602 print("Importing from/into multiple branches")
Simon Hausmannabcd7902007-05-24 22:25:36 +02003603 self.detectBranches = True
Pete Wyckoff8c9e8b62013-01-14 19:47:06 -05003604 for branch in branches.keys():
3605 self.initialParents[self.refPrefix + branch] = \
3606 branches[branch]
Simon Hausmann967f72e2007-03-23 09:30:41 +01003607
Simon Hausmann29bdbac2007-05-19 10:23:12 +02003608 if self.verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01003609 print("branches: %s" % self.p4BranchesInGit)
Simon Hausmann29bdbac2007-05-19 10:23:12 +02003610
3611 p4Change = 0
3612 for branch in self.p4BranchesInGit:
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03003613 logMsg = extractLogMessageFromGitCommit(self.refPrefix + branch)
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003614
3615 settings = extractSettingsGitLog(logMsg)
Simon Hausmann29bdbac2007-05-19 10:23:12 +02003616
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003617 self.readOptions(settings)
Luke Diamanddba1c9d2018-06-19 09:04:07 +01003618 if ('depot-paths' in settings
3619 and 'change' in settings):
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003620 change = int(settings['change']) + 1
Simon Hausmann29bdbac2007-05-19 10:23:12 +02003621 p4Change = max(p4Change, change)
3622
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003623 depotPaths = sorted(settings['depot-paths'])
3624 if self.previousDepotPaths == []:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003625 self.previousDepotPaths = depotPaths
Simon Hausmann29bdbac2007-05-19 10:23:12 +02003626 else:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003627 paths = []
3628 for (prev, cur) in zip(self.previousDepotPaths, depotPaths):
Vitor Antunes04d277b2011-08-19 00:44:03 +01003629 prev_list = prev.split("/")
3630 cur_list = cur.split("/")
3631 for i in range(0, min(len(cur_list), len(prev_list))):
Luke Diamandfc35c9d2018-06-19 09:04:06 +01003632 if cur_list[i] != prev_list[i]:
Simon Hausmann583e1702007-06-07 09:37:13 +02003633 i = i - 1
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003634 break
3635
Vitor Antunes04d277b2011-08-19 00:44:03 +01003636 paths.append ("/".join(cur_list[:i + 1]))
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003637
3638 self.previousDepotPaths = paths
Simon Hausmann29bdbac2007-05-19 10:23:12 +02003639
3640 if p4Change > 0:
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003641 self.depotPaths = sorted(self.previousDepotPaths)
Simon Hausmannd5904672007-05-19 11:07:32 +02003642 self.changeRange = "@%s,#head" % p4Change
Simon Hausmann341dc1c2007-05-21 00:39:16 +02003643 if not self.silent and not self.detectBranches:
Luke Diamandf2606b12018-06-19 09:04:10 +01003644 print("Performing incremental import into %s git branch" % self.branch)
Simon Hausmann569d1bd2007-03-22 21:34:16 +01003645
Pete Wyckoff40d69ac2013-01-14 19:47:03 -05003646 # accept multiple ref name abbreviations:
3647 # refs/foo/bar/branch -> use it exactly
3648 # p4/branch -> prepend refs/remotes/ or refs/heads/
3649 # branch -> prepend refs/remotes/p4/ or refs/heads/p4/
Simon Hausmannf9162f62007-05-17 09:02:45 +02003650 if not self.branch.startswith("refs/"):
Pete Wyckoff40d69ac2013-01-14 19:47:03 -05003651 if self.importIntoRemotes:
3652 prepend = "refs/remotes/"
3653 else:
3654 prepend = "refs/heads/"
3655 if not self.branch.startswith("p4/"):
3656 prepend += "p4/"
3657 self.branch = prepend + self.branch
Simon Hausmann179caeb2007-03-22 22:17:42 +01003658
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003659 if len(args) == 0 and self.depotPaths:
Simon Hausmannb9847332007-03-20 20:54:23 +01003660 if not self.silent:
Luke Diamandf2606b12018-06-19 09:04:10 +01003661 print("Depot paths: %s" % ' '.join(self.depotPaths))
Simon Hausmannb9847332007-03-20 20:54:23 +01003662 else:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003663 if self.depotPaths and self.depotPaths != args:
Luke Diamandf2606b12018-06-19 09:04:10 +01003664 print("previous import used depot path %s and now %s was specified. "
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003665 "This doesn't work!" % (' '.join (self.depotPaths),
3666 ' '.join (args)))
Simon Hausmannb9847332007-03-20 20:54:23 +01003667 sys.exit(1)
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003668
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003669 self.depotPaths = sorted(args)
Simon Hausmannb9847332007-03-20 20:54:23 +01003670
Simon Hausmann1c49fc12007-08-26 16:04:34 +02003671 revision = ""
Simon Hausmannb9847332007-03-20 20:54:23 +01003672 self.users = {}
Simon Hausmannb9847332007-03-20 20:54:23 +01003673
Pete Wyckoff58c8bc72011-12-24 21:07:35 -05003674 # Make sure no revision specifiers are used when --changesfile
3675 # is specified.
3676 bad_changesfile = False
3677 if len(self.changesFile) > 0:
3678 for p in self.depotPaths:
3679 if p.find("@") >= 0 or p.find("#") >= 0:
3680 bad_changesfile = True
3681 break
3682 if bad_changesfile:
3683 die("Option --changesfile is incompatible with revision specifiers")
3684
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003685 newPaths = []
3686 for p in self.depotPaths:
3687 if p.find("@") != -1:
3688 atIdx = p.index("@")
3689 self.changeRange = p[atIdx:]
3690 if self.changeRange == "@all":
3691 self.changeRange = ""
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03003692 elif ',' not in self.changeRange:
Simon Hausmann1c49fc12007-08-26 16:04:34 +02003693 revision = self.changeRange
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003694 self.changeRange = ""
Han-Wen Nienhuys7fcff9d2007-07-23 15:56:37 -07003695 p = p[:atIdx]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003696 elif p.find("#") != -1:
3697 hashIdx = p.index("#")
Simon Hausmann1c49fc12007-08-26 16:04:34 +02003698 revision = p[hashIdx:]
Han-Wen Nienhuys7fcff9d2007-07-23 15:56:37 -07003699 p = p[:hashIdx]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003700 elif self.previousDepotPaths == []:
Pete Wyckoff58c8bc72011-12-24 21:07:35 -05003701 # pay attention to changesfile, if given, else import
3702 # the entire p4 tree at the head revision
3703 if len(self.changesFile) == 0:
3704 revision = "#head"
Simon Hausmannb9847332007-03-20 20:54:23 +01003705
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003706 p = re.sub ("\.\.\.$", "", p)
3707 if not p.endswith("/"):
3708 p += "/"
3709
3710 newPaths.append(p)
3711
3712 self.depotPaths = newPaths
3713
Pete Wyckoffe63231e2012-08-11 12:55:02 -04003714 # --detect-branches may change this for each branch
3715 self.branchPrefixes = self.depotPaths
3716
Simon Hausmannb607e712007-05-20 10:55:54 +02003717 self.loadUserMapFromCache()
Simon Hausmanncb53e1f2007-04-08 00:12:02 +02003718 self.labels = {}
3719 if self.detectLabels:
3720 self.getLabels();
Simon Hausmannb9847332007-03-20 20:54:23 +01003721
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02003722 if self.detectBranches:
Simon Hausmanndf450922007-06-08 08:49:22 +02003723 ## FIXME - what's a P4 projectName ?
3724 self.projectName = self.guessProjectName()
3725
Simon Hausmann38f9f5e2007-11-15 10:38:45 +01003726 if self.hasOrigin:
3727 self.getBranchMappingFromGitBranches()
3728 else:
3729 self.getBranchMapping()
Simon Hausmann29bdbac2007-05-19 10:23:12 +02003730 if self.verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01003731 print("p4-git branches: %s" % self.p4BranchesInGit)
3732 print("initial parents: %s" % self.initialParents)
Simon Hausmann29bdbac2007-05-19 10:23:12 +02003733 for b in self.p4BranchesInGit:
3734 if b != "master":
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003735
3736 ## FIXME
Simon Hausmann29bdbac2007-05-19 10:23:12 +02003737 b = b[len(self.projectName):]
3738 self.createdBranches.add(b)
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02003739
Luke Diamand123f6312018-05-23 23:20:26 +01003740 self.openStreams()
Simon Hausmannb9847332007-03-20 20:54:23 +01003741
Simon Hausmann1c49fc12007-08-26 16:04:34 +02003742 if revision:
Simon Hausmannc208a242007-08-26 16:07:18 +02003743 self.importHeadRevision(revision)
Simon Hausmannb9847332007-03-20 20:54:23 +01003744 else:
3745 changes = []
3746
Simon Hausmann0828ab12007-03-20 20:59:30 +01003747 if len(self.changesFile) > 0:
Simon Hausmannb9847332007-03-20 20:54:23 +01003748 output = open(self.changesFile).readlines()
Reilly Grant1d7367d2009-09-10 00:02:38 -07003749 changeSet = set()
Simon Hausmannb9847332007-03-20 20:54:23 +01003750 for line in output:
3751 changeSet.add(int(line))
3752
3753 for change in changeSet:
3754 changes.append(change)
3755
3756 changes.sort()
3757 else:
Pete Wyckoff9dcb9f22012-04-08 20:18:01 -04003758 # catch "git p4 sync" with no new branches, in a repo that
3759 # does not have any existing p4 branches
Pete Wyckoff5a8e84c2013-01-14 19:47:05 -05003760 if len(args) == 0:
3761 if not self.p4BranchesInGit:
3762 die("No remote p4 branches. Perhaps you never did \"git p4 clone\" in here.")
3763
3764 # The default branch is master, unless --branch is used to
3765 # specify something else. Make sure it exists, or complain
3766 # nicely about how to use --branch.
3767 if not self.detectBranches:
3768 if not branch_exists(self.branch):
3769 if branch_arg_given:
3770 die("Error: branch %s does not exist." % self.branch)
3771 else:
3772 die("Error: no branch %s; perhaps specify one with --branch." %
3773 self.branch)
3774
Simon Hausmann29bdbac2007-05-19 10:23:12 +02003775 if self.verbose:
Luke Diamandf2606b12018-06-19 09:04:10 +01003776 print("Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
3777 self.changeRange))
Lex Spoon96b2d542015-04-20 11:00:20 -04003778 changes = p4ChangesForPaths(self.depotPaths, self.changeRange, self.changes_block_size)
Simon Hausmannb9847332007-03-20 20:54:23 +01003779
Simon Hausmann01a9c9c2007-05-23 00:07:35 +02003780 if len(self.maxChanges) > 0:
Han-Wen Nienhuys7fcff9d2007-07-23 15:56:37 -07003781 changes = changes[:min(int(self.maxChanges), len(changes))]
Simon Hausmann01a9c9c2007-05-23 00:07:35 +02003782
Simon Hausmannb9847332007-03-20 20:54:23 +01003783 if len(changes) == 0:
Simon Hausmann0828ab12007-03-20 20:59:30 +01003784 if not self.silent:
Luke Diamandf2606b12018-06-19 09:04:10 +01003785 print("No changes to import!")
Luke Diamand06804c72012-04-11 17:21:24 +02003786 else:
3787 if not self.silent and not self.detectBranches:
Luke Diamandf2606b12018-06-19 09:04:10 +01003788 print("Import destination: %s" % self.branch)
Simon Hausmannb9847332007-03-20 20:54:23 +01003789
Luke Diamand06804c72012-04-11 17:21:24 +02003790 self.updatedBranches = set()
Simon Hausmanna9d1a272007-06-11 23:28:03 +02003791
Pete Wyckoff47497842013-01-14 19:47:04 -05003792 if not self.detectBranches:
3793 if args:
3794 # start a new branch
3795 self.initialParent = ""
3796 else:
3797 # build on a previous revision
3798 self.initialParent = parseRevision(self.branch)
3799
Luke Diamand06804c72012-04-11 17:21:24 +02003800 self.importChanges(changes)
Simon Hausmann341dc1c2007-05-21 00:39:16 +02003801
Luke Diamand06804c72012-04-11 17:21:24 +02003802 if not self.silent:
Luke Diamandf2606b12018-06-19 09:04:10 +01003803 print("")
Luke Diamand06804c72012-04-11 17:21:24 +02003804 if len(self.updatedBranches) > 0:
3805 sys.stdout.write("Updated branches: ")
3806 for b in self.updatedBranches:
3807 sys.stdout.write("%s " % b)
3808 sys.stdout.write("\n")
Simon Hausmannb9847332007-03-20 20:54:23 +01003809
Pete Wyckoff0d609032013-01-26 22:11:24 -05003810 if gitConfigBool("git-p4.importLabels"):
Luke Diamand06dcd152012-05-11 07:25:18 +01003811 self.importLabels = True
Luke Diamand06804c72012-04-11 17:21:24 +02003812
3813 if self.importLabels:
3814 p4Labels = getP4Labels(self.depotPaths)
3815 gitTags = getGitTags()
3816
3817 missingP4Labels = p4Labels - gitTags
3818 self.importP4Labels(self.gitStream, missingP4Labels)
Simon Hausmannb9847332007-03-20 20:54:23 +01003819
Luke Diamand123f6312018-05-23 23:20:26 +01003820 self.closeStreams()
Simon Hausmannb9847332007-03-20 20:54:23 +01003821
Vitor Antunesfed23692012-01-25 23:48:22 +00003822 # Cleanup temporary branches created during import
3823 if self.tempBranches != []:
3824 for branch in self.tempBranches:
3825 read_pipe("git update-ref -d %s" % branch)
3826 os.rmdir(os.path.join(os.environ.get("GIT_DIR", ".git"), self.tempBranchLocation))
3827
Pete Wyckoff55d12432013-01-14 19:46:59 -05003828 # Create a symbolic ref p4/HEAD pointing to p4/<branch> to allow
3829 # a convenient shortcut refname "p4".
3830 if self.importIntoRemotes:
3831 head_ref = self.refPrefix + "HEAD"
3832 if not gitBranchExists(head_ref) and gitBranchExists(self.branch):
3833 system(["git", "symbolic-ref", head_ref, self.branch])
3834
Simon Hausmannb9847332007-03-20 20:54:23 +01003835 return True
3836
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02003837class P4Rebase(Command):
3838 def __init__(self):
3839 Command.__init__(self)
Luke Diamand06804c72012-04-11 17:21:24 +02003840 self.options = [
3841 optparse.make_option("--import-labels", dest="importLabels", action="store_true"),
Luke Diamand06804c72012-04-11 17:21:24 +02003842 ]
Luke Diamand06804c72012-04-11 17:21:24 +02003843 self.importLabels = False
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03003844 self.description = ("Fetches the latest revision from perforce and "
3845 + "rebases the current work (branch) against it")
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02003846
3847 def run(self, args):
3848 sync = P4Sync()
Luke Diamand06804c72012-04-11 17:21:24 +02003849 sync.importLabels = self.importLabels
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02003850 sync.run([])
Simon Hausmannd7e38682007-06-12 14:34:46 +02003851
Simon Hausmann14594f42007-08-22 09:07:15 +02003852 return self.rebase()
3853
3854 def rebase(self):
Simon Hausmann36ee4ee2008-01-07 14:21:45 +01003855 if os.system("git update-index --refresh") != 0:
Martin Ågren7560f542017-08-23 19:49:35 +02003856 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.");
Simon Hausmann36ee4ee2008-01-07 14:21:45 +01003857 if len(read_pipe("git diff-index HEAD --")) > 0:
Veres Lajosf7e604e2013-06-19 07:37:24 +02003858 die("You have uncommitted changes. Please commit them before rebasing or stash them away with git stash.");
Simon Hausmann36ee4ee2008-01-07 14:21:45 +01003859
Simon Hausmannd7e38682007-06-12 14:34:46 +02003860 [upstream, settings] = findUpstreamBranchPoint()
3861 if len(upstream) == 0:
3862 die("Cannot find upstream branchpoint for rebase")
3863
3864 # the branchpoint may be p4/foo~3, so strip off the parent
3865 upstream = re.sub("~[0-9]+$", "", upstream)
3866
Luke Diamandf2606b12018-06-19 09:04:10 +01003867 print("Rebasing the current branch onto %s" % upstream)
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -03003868 oldHead = read_pipe("git rev-parse HEAD").strip()
Simon Hausmannd7e38682007-06-12 14:34:46 +02003869 system("git rebase %s" % upstream)
Vlad Dogaru4e49d952014-04-07 16:19:11 +03003870 system("git diff-tree --stat --summary -M %s HEAD --" % oldHead)
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02003871 return True
3872
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02003873class P4Clone(P4Sync):
3874 def __init__(self):
3875 P4Sync.__init__(self)
3876 self.description = "Creates a new git repository and imports from Perforce into it"
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003877 self.usage = "usage: %prog [options] //depot/path[@revRange]"
Tommy Thorn354081d2008-02-03 10:38:51 -08003878 self.options += [
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003879 optparse.make_option("--destination", dest="cloneDestination",
3880 action='store', default=None,
Tommy Thorn354081d2008-02-03 10:38:51 -08003881 help="where to leave result of the clone"),
Pete Wyckoff38200072011-02-19 08:18:01 -05003882 optparse.make_option("--bare", dest="cloneBare",
3883 action="store_true", default=False),
Tommy Thorn354081d2008-02-03 10:38:51 -08003884 ]
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003885 self.cloneDestination = None
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02003886 self.needsGit = False
Pete Wyckoff38200072011-02-19 08:18:01 -05003887 self.cloneBare = False
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02003888
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03003889 def defaultDestination(self, args):
3890 ## TODO: use common prefix of args?
3891 depotPath = args[0]
3892 depotDir = re.sub("(@[^@]*)$", "", depotPath)
3893 depotDir = re.sub("(#[^#]*)$", "", depotDir)
Toby Allsopp053d9e42008-02-05 09:41:43 +13003894 depotDir = re.sub(r"\.\.\.$", "", depotDir)
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03003895 depotDir = re.sub(r"/$", "", depotDir)
3896 return os.path.split(depotDir)[1]
3897
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02003898 def run(self, args):
3899 if len(args) < 1:
3900 return False
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003901
3902 if self.keepRepoPath and not self.cloneDestination:
3903 sys.stderr.write("Must specify destination for --keep-path\n")
3904 sys.exit(1)
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02003905
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003906 depotPaths = args
Simon Hausmann5e100b52007-06-07 21:12:25 +02003907
3908 if not self.cloneDestination and len(depotPaths) > 1:
3909 self.cloneDestination = depotPaths[-1]
3910 depotPaths = depotPaths[:-1]
3911
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003912 for p in depotPaths:
3913 if not p.startswith("//"):
Pete Wyckoff0f487d32013-01-26 22:11:06 -05003914 sys.stderr.write('Depot paths must start with "//": %s\n' % p)
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003915 return False
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02003916
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003917 if not self.cloneDestination:
Marius Storm-Olsen98ad4fa2007-06-07 15:08:33 +02003918 self.cloneDestination = self.defaultDestination(args)
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02003919
Luke Diamandf2606b12018-06-19 09:04:10 +01003920 print("Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination))
Pete Wyckoff38200072011-02-19 08:18:01 -05003921
Kevin Greenc3bf3f12007-06-11 16:48:07 -04003922 if not os.path.exists(self.cloneDestination):
3923 os.makedirs(self.cloneDestination)
Robert Blum053fd0c2008-08-01 12:50:03 -07003924 chdir(self.cloneDestination)
Pete Wyckoff38200072011-02-19 08:18:01 -05003925
3926 init_cmd = [ "git", "init" ]
3927 if self.cloneBare:
3928 init_cmd.append("--bare")
Brandon Caseya235e852013-01-26 11:14:33 -08003929 retcode = subprocess.call(init_cmd)
3930 if retcode:
3931 raise CalledProcessError(retcode, init_cmd)
Pete Wyckoff38200072011-02-19 08:18:01 -05003932
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003933 if not P4Sync.run(self, depotPaths):
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02003934 return False
Pete Wyckoffc5959562013-01-14 19:47:01 -05003935
3936 # create a master branch and check out a work tree
3937 if gitBranchExists(self.branch):
3938 system([ "git", "branch", "master", self.branch ])
3939 if not self.cloneBare:
3940 system([ "git", "checkout", "-f" ])
3941 else:
Luke Diamandf2606b12018-06-19 09:04:10 +01003942 print('Not checking out any branch, use ' \
3943 '"git checkout -q -b master <branch>"')
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03003944
Pete Wyckoffa93d33e2012-02-25 20:06:24 -05003945 # auto-set this variable if invoked with --use-client-spec
3946 if self.useClientSpec_from_options:
3947 system("git config --bool git-p4.useclientspec true")
3948
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02003949 return True
3950
Luke Diamand123f6312018-05-23 23:20:26 +01003951class P4Unshelve(Command):
3952 def __init__(self):
3953 Command.__init__(self)
3954 self.options = []
3955 self.origin = "HEAD"
3956 self.description = "Unshelve a P4 changelist into a git commit"
3957 self.usage = "usage: %prog [options] changelist"
3958 self.options += [
3959 optparse.make_option("--origin", dest="origin",
3960 help="Use this base revision instead of the default (%s)" % self.origin),
3961 ]
3962 self.verbose = False
3963 self.noCommit = False
Luke Diamand08813122018-10-15 12:14:07 +01003964 self.destbranch = "refs/remotes/p4-unshelved"
Luke Diamand123f6312018-05-23 23:20:26 +01003965
3966 def renameBranch(self, branch_name):
3967 """ Rename the existing branch to branch_name.N
3968 """
3969
3970 found = True
3971 for i in range(0,1000):
3972 backup_branch_name = "{0}.{1}".format(branch_name, i)
3973 if not gitBranchExists(backup_branch_name):
3974 gitUpdateRef(backup_branch_name, branch_name) # copy ref to backup
3975 gitDeleteRef(branch_name)
3976 found = True
3977 print("renamed old unshelve branch to {0}".format(backup_branch_name))
3978 break
3979
3980 if not found:
3981 sys.exit("gave up trying to rename existing branch {0}".format(sync.branch))
3982
3983 def findLastP4Revision(self, starting_point):
3984 """ Look back from starting_point for the first commit created by git-p4
3985 to find the P4 commit we are based on, and the depot-paths.
3986 """
3987
3988 for parent in (range(65535)):
3989 log = extractLogMessageFromGitCommit("{0}^{1}".format(starting_point, parent))
3990 settings = extractSettingsGitLog(log)
Luke Diamanddba1c9d2018-06-19 09:04:07 +01003991 if 'change' in settings:
Luke Diamand123f6312018-05-23 23:20:26 +01003992 return settings
3993
3994 sys.exit("could not find git-p4 commits in {0}".format(self.origin))
3995
Luke Diamand89143ac2018-10-15 12:14:08 +01003996 def createShelveParent(self, change, branch_name, sync, origin):
3997 """ Create a commit matching the parent of the shelved changelist 'change'
3998 """
3999 parent_description = p4_describe(change, shelved=True)
4000 parent_description['desc'] = 'parent for shelved changelist {}\n'.format(change)
4001 files = sync.extractFilesFromCommit(parent_description, shelved=False, shelved_cl=change)
4002
4003 parent_files = []
4004 for f in files:
4005 # if it was added in the shelved changelist, it won't exist in the parent
4006 if f['action'] in self.add_actions:
4007 continue
4008
4009 # if it was deleted in the shelved changelist it must not be deleted
4010 # in the parent - we might even need to create it if the origin branch
4011 # does not have it
4012 if f['action'] in self.delete_actions:
4013 f['action'] = 'add'
4014
4015 parent_files.append(f)
4016
4017 sync.commit(parent_description, parent_files, branch_name,
4018 parent=origin, allow_empty=True)
4019 print("created parent commit for {0} based on {1} in {2}".format(
4020 change, self.origin, branch_name))
4021
Luke Diamand123f6312018-05-23 23:20:26 +01004022 def run(self, args):
4023 if len(args) != 1:
4024 return False
4025
4026 if not gitBranchExists(self.origin):
4027 sys.exit("origin branch {0} does not exist".format(self.origin))
4028
4029 sync = P4Sync()
4030 changes = args
Luke Diamand123f6312018-05-23 23:20:26 +01004031
Luke Diamand89143ac2018-10-15 12:14:08 +01004032 # only one change at a time
Luke Diamand123f6312018-05-23 23:20:26 +01004033 change = changes[0]
4034
4035 # if the target branch already exists, rename it
4036 branch_name = "{0}/{1}".format(self.destbranch, change)
4037 if gitBranchExists(branch_name):
4038 self.renameBranch(branch_name)
4039 sync.branch = branch_name
4040
4041 sync.verbose = self.verbose
4042 sync.suppress_meta_comment = True
4043
4044 settings = self.findLastP4Revision(self.origin)
Luke Diamand123f6312018-05-23 23:20:26 +01004045 sync.depotPaths = settings['depot-paths']
4046 sync.branchPrefixes = sync.depotPaths
4047
4048 sync.openStreams()
4049 sync.loadUserMapFromCache()
4050 sync.silent = True
Luke Diamand89143ac2018-10-15 12:14:08 +01004051
4052 # create a commit for the parent of the shelved changelist
4053 self.createShelveParent(change, branch_name, sync, self.origin)
4054
4055 # create the commit for the shelved changelist itself
4056 description = p4_describe(change, True)
4057 files = sync.extractFilesFromCommit(description, True, change)
4058
4059 sync.commit(description, files, branch_name, "")
Luke Diamand123f6312018-05-23 23:20:26 +01004060 sync.closeStreams()
4061
4062 print("unshelved changelist {0} into {1}".format(change, branch_name))
4063
4064 return True
4065
Simon Hausmann09d89de2007-06-20 23:10:28 +02004066class P4Branches(Command):
4067 def __init__(self):
4068 Command.__init__(self)
4069 self.options = [ ]
4070 self.description = ("Shows the git branches that hold imports and their "
4071 + "corresponding perforce depot paths")
4072 self.verbose = False
4073
4074 def run(self, args):
Simon Hausmann5ca44612007-08-24 17:44:16 +02004075 if originP4BranchesExist():
4076 createOrUpdateBranchesFromOrigin()
4077
Simon Hausmann09d89de2007-06-20 23:10:28 +02004078 cmdline = "git rev-parse --symbolic "
4079 cmdline += " --remotes"
4080
4081 for line in read_pipe_lines(cmdline):
4082 line = line.strip()
4083
4084 if not line.startswith('p4/') or line == "p4/HEAD":
4085 continue
4086 branch = line
4087
4088 log = extractLogMessageFromGitCommit("refs/remotes/%s" % branch)
4089 settings = extractSettingsGitLog(log)
4090
Luke Diamandf2606b12018-06-19 09:04:10 +01004091 print("%s <= %s (%s)" % (branch, ",".join(settings["depot-paths"]), settings["change"]))
Simon Hausmann09d89de2007-06-20 23:10:28 +02004092 return True
4093
Simon Hausmannb9847332007-03-20 20:54:23 +01004094class HelpFormatter(optparse.IndentedHelpFormatter):
4095 def __init__(self):
4096 optparse.IndentedHelpFormatter.__init__(self)
4097
4098 def format_description(self, description):
4099 if description:
4100 return description + "\n"
4101 else:
4102 return ""
Simon Hausmann4f5cf762007-03-19 22:25:17 +01004103
Simon Hausmann86949ee2007-03-19 20:59:12 +01004104def printUsage(commands):
Luke Diamandf2606b12018-06-19 09:04:10 +01004105 print("usage: %s <command> [options]" % sys.argv[0])
4106 print("")
4107 print("valid commands: %s" % ", ".join(commands))
4108 print("")
4109 print("Try %s <command> --help for command specific help." % sys.argv[0])
4110 print("")
Simon Hausmann86949ee2007-03-19 20:59:12 +01004111
4112commands = {
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03004113 "debug" : P4Debug,
4114 "submit" : P4Submit,
Marius Storm-Olsena9834f52007-10-09 16:16:09 +02004115 "commit" : P4Submit,
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03004116 "sync" : P4Sync,
4117 "rebase" : P4Rebase,
4118 "clone" : P4Clone,
Simon Hausmann09d89de2007-06-20 23:10:28 +02004119 "rollback" : P4RollBack,
Luke Diamand123f6312018-05-23 23:20:26 +01004120 "branches" : P4Branches,
4121 "unshelve" : P4Unshelve,
Simon Hausmann86949ee2007-03-19 20:59:12 +01004122}
4123
Simon Hausmann86949ee2007-03-19 20:59:12 +01004124
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03004125def main():
4126 if len(sys.argv[1:]) == 0:
4127 printUsage(commands.keys())
4128 sys.exit(2)
Simon Hausmann86949ee2007-03-19 20:59:12 +01004129
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03004130 cmdName = sys.argv[1]
4131 try:
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03004132 klass = commands[cmdName]
4133 cmd = klass()
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03004134 except KeyError:
Luke Diamandf2606b12018-06-19 09:04:10 +01004135 print("unknown command %s" % cmdName)
4136 print("")
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03004137 printUsage(commands.keys())
4138 sys.exit(2)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01004139
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03004140 options = cmd.options
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03004141 cmd.gitdir = os.environ.get("GIT_DIR", None)
Simon Hausmann86949ee2007-03-19 20:59:12 +01004142
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03004143 args = sys.argv[2:]
Simon Hausmanne20a9e52007-03-26 00:13:51 +02004144
Pete Wyckoffb0ccc802012-09-09 16:16:10 -04004145 options.append(optparse.make_option("--verbose", "-v", dest="verbose", action="store_true"))
Luke Diamand6a10b6a2012-04-24 09:33:23 +01004146 if cmd.needsGit:
4147 options.append(optparse.make_option("--git-dir", dest="gitdir"))
Simon Hausmanne20a9e52007-03-26 00:13:51 +02004148
Luke Diamand6a10b6a2012-04-24 09:33:23 +01004149 parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName),
4150 options,
4151 description = cmd.description,
4152 formatter = HelpFormatter())
Simon Hausmann86949ee2007-03-19 20:59:12 +01004153
Ben Keene608e3802019-12-16 14:02:20 +00004154 try:
4155 (cmd, args) = parser.parse_args(sys.argv[2:], cmd);
4156 except:
4157 parser.print_help()
4158 raise
4159
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03004160 global verbose
4161 verbose = cmd.verbose
4162 if cmd.needsGit:
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03004163 if cmd.gitdir == None:
4164 cmd.gitdir = os.path.abspath(".git")
4165 if not isValidGitDir(cmd.gitdir):
Luke Diamand378f7be2016-12-13 21:51:28 +00004166 # "rev-parse --git-dir" without arguments will try $PWD/.git
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03004167 cmd.gitdir = read_pipe("git rev-parse --git-dir").strip()
4168 if os.path.exists(cmd.gitdir):
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03004169 cdup = read_pipe("git rev-parse --show-cdup").strip()
4170 if len(cdup) > 0:
Robert Blum053fd0c2008-08-01 12:50:03 -07004171 chdir(cdup);
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03004172
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03004173 if not isValidGitDir(cmd.gitdir):
4174 if isValidGitDir(cmd.gitdir + "/.git"):
4175 cmd.gitdir += "/.git"
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03004176 else:
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03004177 die("fatal: cannot locate git repository at %s" % cmd.gitdir)
Simon Hausmann8910ac02007-03-26 08:18:55 +02004178
Luke Diamand378f7be2016-12-13 21:51:28 +00004179 # so git commands invoked from the P4 workspace will succeed
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03004180 os.environ["GIT_DIR"] = cmd.gitdir
Simon Hausmann4f5cf762007-03-19 22:25:17 +01004181
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03004182 if not cmd.run(args):
4183 parser.print_help()
Pete Wyckoff09fca772011-12-24 21:07:39 -05004184 sys.exit(2)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01004185
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03004186
4187if __name__ == '__main__':
4188 main()