2007-03-19 20:59:12 +01:00
#!/usr/bin/env python
#
# git-p4.py -- A tool for bidirectional operation between a Perforce depot and git.
#
2007-05-28 14:43:25 +02:00
# Author: Simon Hausmann <simon@lst.de>
# Copyright: 2007 Simon Hausmann <simon@lst.de>
2007-03-19 22:26:36 +01:00
# 2007 Trolltech ASA
2007-03-19 20:59:12 +01:00
# License: MIT <http://www.opensource.org/licenses/mit-license.php>
#
2009-09-10 09:02:38 +02:00
import optparse , sys , os , marshal , subprocess , shelve
import tempfile , getopt , os . path , time , platform
2012-02-23 08:51:30 +01:00
import re , shutil
2007-05-23 23:20:53 +02:00
2007-05-23 23:49:35 +02:00
verbose = False
2007-03-19 20:59:12 +01:00
2012-04-11 17:21:24 +02:00
# Only labels/tags matching this will be imported/exported
2012-04-11 17:21:24 +02:00
defaultLabelRegexp = r ' [a-zA-Z0-9_ \ -.]+$ '
2008-08-10 20:26:28 +02:00
def p4_build_cmd ( cmd ) :
""" Build a suitable p4 command line.
This consolidates building and returning a p4 command line into one
location . It means that hooking into the environment , or other configuration
can be done more easily .
"""
2011-10-16 16:47:52 +02:00
real_cmd = [ " p4 " ]
2008-08-10 20:26:31 +02:00
user = gitConfig ( " git-p4.user " )
if len ( user ) > 0 :
2011-10-16 16:47:52 +02:00
real_cmd + = [ " -u " , user ]
2008-08-10 20:26:31 +02:00
password = gitConfig ( " git-p4.password " )
if len ( password ) > 0 :
2011-10-16 16:47:52 +02:00
real_cmd + = [ " -P " , password ]
2008-08-10 20:26:31 +02:00
port = gitConfig ( " git-p4.port " )
if len ( port ) > 0 :
2011-10-16 16:47:52 +02:00
real_cmd + = [ " -p " , port ]
2008-08-10 20:26:31 +02:00
host = gitConfig ( " git-p4.host " )
if len ( host ) > 0 :
2012-02-22 20:16:05 +01:00
real_cmd + = [ " -H " , host ]
2008-08-10 20:26:31 +02:00
client = gitConfig ( " git-p4.client " )
if len ( client ) > 0 :
2011-10-16 16:47:52 +02:00
real_cmd + = [ " -c " , client ]
2008-08-10 20:26:31 +02:00
2011-10-16 16:47:52 +02:00
if isinstance ( cmd , basestring ) :
real_cmd = ' ' . join ( real_cmd ) + ' ' + cmd
else :
real_cmd + = cmd
2008-08-10 20:26:28 +02:00
return real_cmd
2008-08-01 21:50:03 +02:00
def chdir ( dir ) :
2011-10-16 16:47:52 +02:00
# P4 uses the PWD environment variable rather than getcwd(). Since we're
2011-12-10 00:48:16 +01:00
# not using the shell, we have to set it ourselves. This path could
# be relative, so go there first, then figure out where we ended up.
2008-08-01 21:50:03 +02:00
os . chdir ( dir )
2011-12-10 00:48:16 +01:00
os . environ [ ' PWD ' ] = os . getcwd ( )
2008-08-01 21:50:03 +02:00
2007-05-23 23:49:35 +02:00
def die ( msg ) :
if verbose :
raise Exception ( msg )
else :
sys . stderr . write ( msg + " \n " )
sys . exit ( 1 )
2011-10-16 16:47:52 +02:00
def write_pipe ( c , stdin ) :
2007-05-23 23:49:35 +02:00
if verbose :
2011-10-16 16:47:52 +02:00
sys . stderr . write ( ' Writing pipe: %s \n ' % str ( c ) )
2007-05-23 22:10:46 +02:00
2011-10-16 16:47:52 +02:00
expand = isinstance ( c , basestring )
p = subprocess . Popen ( c , stdin = subprocess . PIPE , shell = expand )
pipe = p . stdin
val = pipe . write ( stdin )
pipe . close ( )
if p . wait ( ) :
die ( ' Command failed: %s ' % str ( c ) )
2007-05-23 22:10:46 +02:00
return val
2011-10-16 16:47:52 +02:00
def p4_write_pipe ( c , stdin ) :
2008-08-15 00:40:38 +02:00
real_cmd = p4_build_cmd ( c )
2011-10-16 16:47:52 +02:00
return write_pipe ( real_cmd , stdin )
2008-08-15 00:40:38 +02:00
2007-05-23 23:49:35 +02:00
def read_pipe ( c , ignore_error = False ) :
if verbose :
2011-10-16 16:47:52 +02:00
sys . stderr . write ( ' Reading pipe: %s \n ' % str ( c ) )
2007-05-23 23:20:53 +02:00
2011-10-16 16:47:52 +02:00
expand = isinstance ( c , basestring )
p = subprocess . Popen ( c , stdout = subprocess . PIPE , shell = expand )
pipe = p . stdout
2007-05-23 22:10:46 +02:00
val = pipe . read ( )
2011-10-16 16:47:52 +02:00
if p . wait ( ) and not ignore_error :
die ( ' Command failed: %s ' % str ( c ) )
2007-05-23 22:10:46 +02:00
return val
2008-08-15 00:40:38 +02:00
def p4_read_pipe ( c , ignore_error = False ) :
real_cmd = p4_build_cmd ( c )
return read_pipe ( real_cmd , ignore_error )
2007-05-23 22:10:46 +02:00
2007-05-23 22:14:33 +02:00
def read_pipe_lines ( c ) :
2007-05-23 23:49:35 +02:00
if verbose :
2011-10-16 16:47:52 +02:00
sys . stderr . write ( ' Reading pipe: %s \n ' % str ( c ) )
expand = isinstance ( c , basestring )
p = subprocess . Popen ( c , stdout = subprocess . PIPE , shell = expand )
pipe = p . stdout
2007-05-23 22:10:46 +02:00
val = pipe . readlines ( )
2011-10-16 16:47:52 +02:00
if pipe . close ( ) or p . wait ( ) :
die ( ' Command failed: %s ' % str ( c ) )
2007-05-23 22:10:46 +02:00
return val
2007-05-15 14:57:57 +02:00
2008-08-10 20:26:24 +02:00
def p4_read_pipe_lines ( c ) :
""" Specifically invoke p4 on the command supplied. """
2008-08-10 20:26:30 +02:00
real_cmd = p4_build_cmd ( c )
2008-08-10 20:26:24 +02:00
return read_pipe_lines ( real_cmd )
2012-07-13 01:29:00 +02:00
def p4_has_command ( cmd ) :
""" Ask p4 for help on this command. If it returns an error, the
command does not exist in this version of p4 . """
real_cmd = p4_build_cmd ( [ " help " , cmd ] )
p = subprocess . Popen ( real_cmd , stdout = subprocess . PIPE ,
stderr = subprocess . PIPE )
p . communicate ( )
return p . returncode == 0
2007-05-23 22:41:50 +02:00
def system ( cmd ) :
2011-10-16 16:47:52 +02:00
expand = isinstance ( cmd , basestring )
2007-05-23 23:49:35 +02:00
if verbose :
2011-10-16 16:47:52 +02:00
sys . stderr . write ( " executing %s \n " % str ( cmd ) )
subprocess . check_call ( cmd , shell = expand )
2007-05-23 22:41:50 +02:00
2008-08-10 20:26:26 +02:00
def p4_system ( cmd ) :
""" Specifically invoke p4 as the system command. """
2008-08-10 20:26:30 +02:00
real_cmd = p4_build_cmd ( cmd )
2011-10-16 16:47:52 +02:00
expand = isinstance ( real_cmd , basestring )
subprocess . check_call ( real_cmd , shell = expand )
def p4_integrate ( src , dest ) :
2012-04-30 02:57:17 +02:00
p4_system ( [ " integrate " , " -Dt " , wildcard_encode ( src ) , wildcard_encode ( dest ) ] )
2011-10-16 16:47:52 +02:00
2012-04-30 02:57:14 +02:00
def p4_sync ( f , * options ) :
2012-04-30 02:57:17 +02:00
p4_system ( [ " sync " ] + list ( options ) + [ wildcard_encode ( f ) ] )
2011-10-16 16:47:52 +02:00
def p4_add ( f ) :
2012-04-30 02:57:17 +02:00
# forcibly add file names with wildcards
if wildcard_present ( f ) :
p4_system ( [ " add " , " -f " , f ] )
else :
p4_system ( [ " add " , f ] )
2011-10-16 16:47:52 +02:00
def p4_delete ( f ) :
2012-04-30 02:57:17 +02:00
p4_system ( [ " delete " , wildcard_encode ( f ) ] )
2011-10-16 16:47:52 +02:00
def p4_edit ( f ) :
2012-04-30 02:57:17 +02:00
p4_system ( [ " edit " , wildcard_encode ( f ) ] )
2011-10-16 16:47:52 +02:00
def p4_revert ( f ) :
2012-04-30 02:57:17 +02:00
p4_system ( [ " revert " , wildcard_encode ( f ) ] )
2011-10-16 16:47:52 +02:00
2012-04-30 02:57:17 +02:00
def p4_reopen ( type , f ) :
p4_system ( [ " reopen " , " -t " , type , wildcard_encode ( f ) ] )
2008-08-10 20:26:26 +02:00
2012-07-13 01:29:00 +02:00
def p4_move ( src , dest ) :
p4_system ( [ " move " , " -k " , wildcard_encode ( src ) , wildcard_encode ( dest ) ] )
2011-10-16 16:45:01 +02:00
#
# Canonicalize the p4 type and return a tuple of the
# base type, plus any modifiers. See "p4 help filetypes"
# for a list and explanation.
#
def split_p4_type ( p4type ) :
p4_filetypes_historical = {
" ctempobj " : " binary+Sw " ,
" ctext " : " text+C " ,
" cxtext " : " text+Cx " ,
" ktext " : " text+k " ,
" kxtext " : " text+kx " ,
" ltext " : " text+F " ,
" tempobj " : " binary+FSw " ,
" ubinary " : " binary+F " ,
" uresource " : " resource+F " ,
" uxbinary " : " binary+Fx " ,
" xbinary " : " binary+x " ,
" xltext " : " text+Fx " ,
" xtempobj " : " binary+Swx " ,
" xtext " : " text+x " ,
" xunicode " : " unicode+x " ,
" xutf16 " : " utf16+x " ,
}
if p4type in p4_filetypes_historical :
p4type = p4_filetypes_historical [ p4type ]
mods = " "
s = p4type . split ( " + " )
base = s [ 0 ]
mods = " "
if len ( s ) > 1 :
mods = s [ 1 ]
return ( base , mods )
2007-09-19 22:12:48 +02:00
2012-02-23 08:51:30 +01:00
#
# return the raw p4 type of a file (text, text+ko, etc)
#
def p4_type ( file ) :
results = p4CmdList ( [ " fstat " , " -T " , " headType " , file ] )
return results [ 0 ] [ ' headType ' ]
#
# Given a type base and modifier, return a regexp matching
# the keywords that can be expanded in the file
#
def p4_keywords_regexp_for_type ( base , type_mods ) :
if base in ( " text " , " unicode " , " binary " ) :
kwords = None
if " ko " in type_mods :
kwords = ' Id|Header '
elif " k " in type_mods :
kwords = ' Id|Header|Author|Date|DateTime|Change|File|Revision '
else :
return None
pattern = r """
\$ # Starts with a dollar, followed by...
( % s ) # one of the keywords, followed by...
( : [ ^ $ ] + ) ? # possibly an old expansion, followed by...
\$ # another dollar
""" % kwords
return pattern
else :
return None
#
# Given a file, return a regexp matching the possible
# RCS keywords that will be expanded, or None for files
# with kw expansion turned off.
#
def p4_keywords_regexp_for_file ( file ) :
if not os . path . exists ( file ) :
return None
else :
( type_base , type_mods ) = split_p4_type ( p4_type ( file ) )
return p4_keywords_regexp_for_type ( type_base , type_mods )
2007-09-19 22:12:48 +02:00
2007-11-02 04:43:14 +01:00
def setP4ExecBit ( file , mode ) :
# Reopens an already open file and changes the execute bit to match
# the execute bit setting in the passed in mode.
p4Type = " +x "
if not isModeExec ( mode ) :
p4Type = getP4OpenedType ( file )
p4Type = re . sub ( ' ^([cku]?)x(.*) ' , ' \\ 1 \\ 2 ' , p4Type )
p4Type = re . sub ( ' (.*? \ +.*?)x(.*?) ' , ' \\ 1 \\ 2 ' , p4Type )
if p4Type [ - 1 ] == " + " :
p4Type = p4Type [ 0 : - 1 ]
2011-10-16 16:47:52 +02:00
p4_reopen ( p4Type , file )
2007-11-02 04:43:14 +01:00
def getP4OpenedType ( file ) :
# Returns the perforce file type for the given file.
2012-04-30 02:57:17 +02:00
result = p4_read_pipe ( [ " opened " , wildcard_encode ( file ) ] )
2008-03-28 15:40:40 +01:00
match = re . match ( " .* \ ((.+) \ ) \r ?$ " , result )
2007-11-02 04:43:14 +01:00
if match :
return match . group ( 1 )
else :
2008-03-28 15:40:40 +01:00
die ( " Could not determine file type for %s (result: ' %s ' ) " % ( file , result ) )
2007-11-02 04:43:14 +01:00
2012-04-11 17:21:24 +02:00
# Return the set of all p4 labels
def getP4Labels ( depotPaths ) :
labels = set ( )
if isinstance ( depotPaths , basestring ) :
depotPaths = [ depotPaths ]
for l in p4CmdList ( [ " labels " ] + [ " %s ... " % p for p in depotPaths ] ) :
label = l [ ' label ' ]
labels . add ( label )
return labels
# Return the set of all git tags
def getGitTags ( ) :
gitTags = set ( )
for line in read_pipe_lines ( [ " git " , " tag " ] ) :
tag = line . strip ( )
gitTags . add ( tag )
return gitTags
2007-11-02 04:43:13 +01:00
def diffTreePattern ( ) :
# This is a simple generator for the diff tree regex pattern. This could be
# a class variable if this and parseDiffTreeEntry were a part of a class.
pattern = re . compile ( ' :( \ d+) ( \ d+) ( \ w+) ( \ w+) ([A-Z])( \ d+)? \t (.*?)(( \t (.*))|$) ' )
while True :
yield pattern
def parseDiffTreeEntry ( entry ) :
""" Parses a single diff tree entry into its component elements.
See git - diff - tree ( 1 ) manpage for details about the format of the diff
output . This method returns a dictionary with the following elements :
src_mode - The mode of the source file
dst_mode - The mode of the destination file
src_sha1 - The sha1 for the source file
dst_sha1 - The sha1 fr the destination file
status - The one letter status of the diff ( i . e . ' A ' , ' M ' , ' D ' , etc )
status_score - The score for the status ( applicable for ' C ' and ' R '
statuses ) . This is None if there is no score .
src - The path for the source file .
dst - The path for the destination file . This is only present for
copy or renames . If it is not present , this is None .
If the pattern is not matched , None is returned . """
match = diffTreePattern ( ) . next ( ) . match ( entry )
if match :
return {
' src_mode ' : match . group ( 1 ) ,
' dst_mode ' : match . group ( 2 ) ,
' src_sha1 ' : match . group ( 3 ) ,
' dst_sha1 ' : match . group ( 4 ) ,
' status ' : match . group ( 5 ) ,
' status_score ' : match . group ( 6 ) ,
' src ' : match . group ( 7 ) ,
' dst ' : match . group ( 10 )
}
return None
2007-11-02 04:43:14 +01:00
def isModeExec ( mode ) :
# Returns True if the given git mode represents an executable file,
# otherwise False.
return mode [ - 3 : ] == " 755 "
def isModeExecChanged ( src_mode , dst_mode ) :
return isModeExec ( src_mode ) != isModeExec ( dst_mode )
2009-07-30 01:13:46 +02:00
def p4CmdList ( cmd , stdin = None , stdin_mode = ' w+b ' , cb = None ) :
2011-10-16 16:47:52 +02:00
if isinstance ( cmd , basestring ) :
cmd = " -G " + cmd
expand = True
else :
cmd = [ " -G " ] + cmd
expand = False
cmd = p4_build_cmd ( cmd )
2007-05-23 23:49:35 +02:00
if verbose :
2011-10-16 16:47:52 +02:00
sys . stderr . write ( " Opening pipe: %s \n " % str ( cmd ) )
2007-07-16 05:58:10 +02:00
# Use a temporary file to avoid deadlocks without
# subprocess.communicate(), which would put another copy
# of stdout into memory.
stdin_file = None
if stdin is not None :
stdin_file = tempfile . TemporaryFile ( prefix = ' p4-stdin ' , mode = stdin_mode )
2011-10-16 16:47:52 +02:00
if isinstance ( stdin , basestring ) :
stdin_file . write ( stdin )
else :
for i in stdin :
stdin_file . write ( i + ' \n ' )
2007-07-16 05:58:10 +02:00
stdin_file . flush ( )
stdin_file . seek ( 0 )
2011-10-16 16:47:52 +02:00
p4 = subprocess . Popen ( cmd ,
shell = expand ,
2007-07-16 05:58:10 +02:00
stdin = stdin_file ,
stdout = subprocess . PIPE )
2007-03-19 20:59:12 +01:00
result = [ ]
try :
while True :
2007-07-16 05:58:10 +02:00
entry = marshal . load ( p4 . stdout )
2011-04-07 08:01:21 +02:00
if cb is not None :
cb ( entry )
else :
result . append ( entry )
2007-03-19 20:59:12 +01:00
except EOFError :
pass
2007-07-16 05:58:10 +02:00
exitCode = p4 . wait ( )
if exitCode != 0 :
2007-05-23 23:32:32 +02:00
entry = { }
entry [ " p4ExitCode " ] = exitCode
result . append ( entry )
2007-03-19 20:59:12 +01:00
return result
def p4Cmd ( cmd ) :
list = p4CmdList ( cmd )
result = { }
for entry in list :
result . update ( entry )
return result ;
2007-03-24 09:15:11 +01:00
def p4Where ( depotPath ) :
if not depotPath . endswith ( " / " ) :
depotPath + = " / "
2008-12-04 14:37:33 +01:00
depotPath = depotPath + " ... "
2011-10-16 16:47:52 +02:00
outputList = p4CmdList ( [ " where " , depotPath ] )
2008-12-04 14:37:33 +01:00
output = None
for entry in outputList :
2008-12-09 16:41:50 +01:00
if " depotFile " in entry :
if entry [ " depotFile " ] == depotPath :
output = entry
break
elif " data " in entry :
data = entry . get ( " data " )
space = data . find ( " " )
if data [ : space ] == depotPath :
output = entry
break
2008-12-04 14:37:33 +01:00
if output == None :
return " "
2007-05-21 09:34:56 +02:00
if output [ " code " ] == " error " :
return " "
2007-03-24 09:15:11 +01:00
clientPath = " "
if " path " in output :
clientPath = output . get ( " path " )
elif " data " in output :
data = output . get ( " data " )
lastSpace = data . rfind ( " " )
clientPath = data [ lastSpace + 1 : ]
if clientPath . endswith ( " ... " ) :
clientPath = clientPath [ : - 3 ]
return clientPath
2007-03-19 20:59:12 +01:00
def currentGitBranch ( ) :
2007-05-23 23:49:35 +02:00
return read_pipe ( " git name-rev HEAD " ) . split ( " " ) [ 1 ] . strip ( )
2007-03-19 20:59:12 +01:00
2007-03-19 22:25:17 +01:00
def isValidGitDir ( path ) :
2007-05-23 23:49:35 +02:00
if ( os . path . exists ( path + " /HEAD " )
and os . path . exists ( path + " /refs " ) and os . path . exists ( path + " /objects " ) ) :
2007-03-19 22:25:17 +01:00
return True ;
return False
2007-05-17 09:13:54 +02:00
def parseRevision ( ref ) :
2007-05-23 23:49:35 +02:00
return read_pipe ( " git rev-parse %s " % ref ) . strip ( )
2007-05-17 09:13:54 +02:00
2011-12-25 03:07:40 +01:00
def branchExists ( ref ) :
rev = read_pipe ( [ " git " , " rev-parse " , " -q " , " --verify " , ref ] ,
ignore_error = True )
return len ( rev ) > 0
2007-03-22 21:10:25 +01:00
def extractLogMessageFromGitCommit ( commit ) :
logMessage = " "
2007-05-23 22:10:46 +02:00
## fixme: title is first line of commit, not 1st paragraph.
2007-03-22 21:10:25 +01:00
foundTitle = False
2007-05-23 22:10:46 +02:00
for log in read_pipe_lines ( " git cat-file commit %s " % commit ) :
2007-03-22 21:10:25 +01:00
if not foundTitle :
if len ( log ) == 1 :
2007-05-01 23:15:48 +02:00
foundTitle = True
2007-03-22 21:10:25 +01:00
continue
logMessage + = log
return logMessage
2007-05-23 23:49:35 +02:00
def extractSettingsGitLog ( log ) :
2007-03-22 21:10:25 +01:00
values = { }
for line in log . split ( " \n " ) :
line = line . strip ( )
2007-05-23 23:49:35 +02:00
m = re . search ( r " ^ * \ [git-p4: (.*) \ ]$ " , line )
if not m :
continue
assignments = m . group ( 1 ) . split ( ' : ' )
for a in assignments :
vals = a . split ( ' = ' )
key = vals [ 0 ] . strip ( )
val = ( ' = ' . join ( vals [ 1 : ] ) ) . strip ( )
if val . endswith ( ' \" ' ) and val . startswith ( ' " ' ) :
val = val [ 1 : - 1 ]
values [ key ] = val
2007-06-07 09:19:34 +02:00
paths = values . get ( " depot-paths " )
if not paths :
paths = values . get ( " depot-path " )
2007-06-07 22:54:32 +02:00
if paths :
values [ ' depot-paths ' ] = paths . split ( ' , ' )
2007-05-23 23:49:35 +02:00
return values
2007-03-22 21:10:25 +01:00
2007-03-22 21:27:14 +01:00
def gitBranchExists ( branch ) :
2007-05-23 23:49:35 +02:00
proc = subprocess . Popen ( [ " git " , " rev-parse " , branch ] ,
stderr = subprocess . PIPE , stdout = subprocess . PIPE ) ;
2007-05-15 14:57:57 +02:00
return proc . wait ( ) == 0 ;
2007-03-22 21:27:14 +01:00
2008-11-08 04:22:49 +01:00
_gitConfig = { }
2011-03-15 13:08:01 +01:00
def gitConfig ( key , args = None ) : # set args to "--bool", for instance
2008-11-08 04:22:49 +01:00
if not _gitConfig . has_key ( key ) :
2011-03-15 13:08:01 +01:00
argsFilter = " "
if args != None :
argsFilter = " %s " % args
cmd = " git config %s %s " % ( argsFilter , key )
_gitConfig [ key ] = read_pipe ( cmd , ignore_error = True ) . strip ( )
2008-11-08 04:22:49 +01:00
return _gitConfig [ key ]
2007-05-25 10:36:10 +02:00
2011-08-19 01:44:05 +02:00
def gitConfigList ( key ) :
if not _gitConfig . has_key ( key ) :
_gitConfig [ key ] = read_pipe ( " git config --get-all %s " % key , ignore_error = True ) . strip ( ) . split ( os . linesep )
return _gitConfig [ key ]
2007-07-18 10:56:31 +02:00
def p4BranchesInGit ( branchesAreInRemotes = True ) :
branches = { }
cmdline = " git rev-parse --symbolic "
if branchesAreInRemotes :
cmdline + = " --remotes "
else :
cmdline + = " --branches "
for line in read_pipe_lines ( cmdline ) :
line = line . strip ( )
## only import to p4/
if not line . startswith ( ' p4/ ' ) or line == " p4/HEAD " :
continue
branch = line
# strip off p4
branch = re . sub ( " ^p4/ " , " " , line )
branches [ branch ] = parseRevision ( line )
return branches
2007-06-22 00:01:57 +02:00
def findUpstreamBranchPoint ( head = " HEAD " ) :
2007-07-18 12:40:12 +02:00
branches = p4BranchesInGit ( )
# map from depot-path to branch name
branchByDepotPath = { }
for branch in branches . keys ( ) :
tip = branches [ branch ]
log = extractLogMessageFromGitCommit ( tip )
settings = extractSettingsGitLog ( log )
if settings . has_key ( " depot-paths " ) :
paths = " , " . join ( settings [ " depot-paths " ] )
branchByDepotPath [ paths ] = " remotes/p4/ " + branch
2007-06-12 14:31:59 +02:00
settings = None
parent = 0
while parent < 65535 :
2007-06-22 00:01:57 +02:00
commit = head + " ~ %s " % parent
2007-06-12 14:31:59 +02:00
log = extractLogMessageFromGitCommit ( commit )
settings = extractSettingsGitLog ( log )
2007-07-18 12:40:12 +02:00
if settings . has_key ( " depot-paths " ) :
paths = " , " . join ( settings [ " depot-paths " ] )
if branchByDepotPath . has_key ( paths ) :
return [ branchByDepotPath [ paths ] , settings ]
2007-06-12 14:31:59 +02:00
2007-07-18 12:40:12 +02:00
parent = parent + 1
2007-06-12 14:31:59 +02:00
2007-07-18 12:40:12 +02:00
return [ " " , settings ]
2007-06-12 14:31:59 +02:00
2007-08-24 17:44:16 +02:00
def createOrUpdateBranchesFromOrigin ( localRefPrefix = " refs/remotes/p4/ " , silent = True ) :
if not silent :
print ( " Creating/updating branch(es) in %s based on origin branch(es) "
% localRefPrefix )
originPrefix = " origin/p4/ "
for line in read_pipe_lines ( " git rev-parse --symbolic --remotes " ) :
line = line . strip ( )
if ( not line . startswith ( originPrefix ) ) or line . endswith ( " HEAD " ) :
continue
headName = line [ len ( originPrefix ) : ]
remoteHead = localRefPrefix + headName
originHead = line
original = extractSettingsGitLog ( extractLogMessageFromGitCommit ( originHead ) )
if ( not original . has_key ( ' depot-paths ' )
or not original . has_key ( ' change ' ) ) :
continue
update = False
if not gitBranchExists ( remoteHead ) :
if verbose :
print " creating %s " % remoteHead
update = True
else :
settings = extractSettingsGitLog ( extractLogMessageFromGitCommit ( remoteHead ) )
if settings . has_key ( ' change ' ) > 0 :
if settings [ ' depot-paths ' ] == original [ ' depot-paths ' ] :
originP4Change = int ( original [ ' change ' ] )
p4Change = int ( settings [ ' change ' ] )
if originP4Change > p4Change :
print ( " %s ( %s ) is newer than %s ( %s ). "
" Updating p4 branch from origin. "
% ( originHead , originP4Change ,
remoteHead , p4Change ) )
update = True
else :
print ( " Ignoring: %s was imported from %s while "
" %s was imported from %s "
% ( originHead , ' , ' . join ( original [ ' depot-paths ' ] ) ,
remoteHead , ' , ' . join ( settings [ ' depot-paths ' ] ) ) )
if update :
system ( " git update-ref %s %s " % ( remoteHead , originHead ) )
def originP4BranchesExist ( ) :
return gitBranchExists ( " origin " ) or gitBranchExists ( " origin/p4 " ) or gitBranchExists ( " origin/p4/master " )
2007-08-26 15:56:36 +02:00
def p4ChangesForPaths ( depotPaths , changeRange ) :
assert depotPaths
2011-10-16 16:47:52 +02:00
cmd = [ ' changes ' ]
for p in depotPaths :
cmd + = [ " %s ... %s " % ( p , changeRange ) ]
output = p4_read_pipe_lines ( cmd )
2007-08-26 15:56:36 +02:00
2009-02-18 19:12:14 +01:00
changes = { }
2007-08-26 15:56:36 +02:00
for line in output :
2011-04-07 08:01:21 +02:00
changeNum = int ( line . split ( " " ) [ 1 ] )
changes [ changeNum ] = True
2007-08-26 15:56:36 +02:00
2009-02-18 19:12:14 +01:00
changelist = changes . keys ( )
changelist . sort ( )
return changelist
2007-08-26 15:56:36 +02:00
2011-03-15 13:08:02 +01:00
def p4PathStartsWith ( path , prefix ) :
# This method tries to remedy a potential mixed-case issue:
#
# If UserA adds //depot/DirA/file1
# and UserB adds //depot/dira/file2
#
# we may or may not have a problem. If you have core.ignorecase=true,
# we treat DirA and dira as the same directory
ignorecase = gitConfig ( " core.ignorecase " , " --bool " ) == " true "
if ignorecase :
return path . lower ( ) . startswith ( prefix . lower ( ) )
return path . startswith ( prefix )
2012-02-26 02:06:25 +01:00
def getClientSpec ( ) :
""" Look at the p4 client spec, create a View() object that contains
all the mappings , and return it . """
specList = p4CmdList ( " client -o " )
if len ( specList ) != 1 :
die ( ' Output from " client -o " is %d lines, expecting 1 ' %
len ( specList ) )
# dictionary of all client parameters
entry = specList [ 0 ]
# just the keys that start with "View"
view_keys = [ k for k in entry . keys ( ) if k . startswith ( " View " ) ]
# hold this new View
view = View ( )
# append the lines, in order, to the view
for view_num in range ( len ( view_keys ) ) :
k = " View %d " % view_num
if k not in view_keys :
die ( " Expected view key %s missing " % k )
view . append ( entry [ k ] )
return view
def getClientRoot ( ) :
""" Grab the client directory. """
output = p4CmdList ( " client -o " )
if len ( output ) != 1 :
die ( ' Output from " client -o " is %d lines, expecting 1 ' % len ( output ) )
entry = output [ 0 ]
if " Root " not in entry :
die ( ' Client has no " Root " ' )
return entry [ " Root " ]
2012-04-30 02:57:17 +02:00
#
# P4 wildcards are not allowed in filenames. P4 complains
# if you simply add them, but you can force it with "-f", in
# which case it translates them into %xx encoding internally.
#
def wildcard_decode ( path ) :
# Search for and fix just these four characters. Do % last so
# that fixing it does not inadvertently create new %-escapes.
# Cannot have * in a filename in windows; untested as to
# what p4 would do in such a case.
if not platform . system ( ) == " Windows " :
path = path . replace ( " % 2A " , " * " )
path = path . replace ( " % 23 " , " # " ) \
. replace ( " % 40 " , " @ " ) \
. replace ( " % 25 " , " % " )
return path
def wildcard_encode ( path ) :
# do % first to avoid double-encoding the %s introduced here
path = path . replace ( " % " , " % 25 " ) \
. replace ( " * " , " % 2A " ) \
. replace ( " # " , " % 23 " ) \
. replace ( " @ " , " % 40 " )
return path
def wildcard_present ( path ) :
return path . translate ( None , " *#@ % " ) != path
2007-03-20 20:54:23 +01:00
class Command :
def __init__ ( self ) :
self . usage = " usage: % prog [options] "
2007-03-26 08:18:55 +02:00
self . needsGit = True
2012-04-24 10:33:23 +02:00
self . verbose = False
2007-03-20 20:54:23 +01:00
2011-04-21 21:50:23 +02:00
class P4UserMap :
def __init__ ( self ) :
self . userMapFromPerforceServer = False
2012-01-19 10:52:27 +01:00
self . myP4UserId = None
def p4UserId ( self ) :
if self . myP4UserId :
return self . myP4UserId
results = p4CmdList ( " user -o " )
for r in results :
if r . has_key ( ' User ' ) :
self . myP4UserId = r [ ' User ' ]
return r [ ' User ' ]
die ( " Could not find your p4 user id " )
def p4UserIsMe ( self , p4User ) :
# return True if the given p4 user is actually me
me = self . p4UserId ( )
if not p4User or p4User != me :
return False
else :
return True
2011-04-21 21:50:23 +02:00
def getUserCacheFilename ( self ) :
home = os . environ . get ( " HOME " , os . environ . get ( " USERPROFILE " ) )
return home + " /.gitp4-usercache.txt "
def getUserMapFromPerforceServer ( self ) :
if self . userMapFromPerforceServer :
return
self . users = { }
self . emails = { }
for output in p4CmdList ( " users " ) :
if not output . has_key ( " User " ) :
continue
self . users [ output [ " User " ] ] = output [ " FullName " ] + " < " + output [ " Email " ] + " > "
self . emails [ output [ " Email " ] ] = output [ " User " ]
s = ' '
for ( key , val ) in self . users . items ( ) :
s + = " %s \t %s \n " % ( key . expandtabs ( 1 ) , val . expandtabs ( 1 ) )
open ( self . getUserCacheFilename ( ) , " wb " ) . write ( s )
self . userMapFromPerforceServer = True
def loadUserMapFromCache ( self ) :
self . users = { }
self . userMapFromPerforceServer = False
try :
cache = open ( self . getUserCacheFilename ( ) , " rb " )
lines = cache . readlines ( )
cache . close ( )
for line in lines :
entry = line . strip ( ) . split ( " \t " )
self . users [ entry [ 0 ] ] = entry [ 1 ]
except IOError :
self . getUserMapFromPerforceServer ( )
2007-03-20 20:54:23 +01:00
class P4Debug ( Command ) :
2007-03-19 20:59:12 +01:00
def __init__ ( self ) :
2007-03-22 21:10:25 +01:00
Command . __init__ ( self )
2012-04-24 10:33:23 +02:00
self . options = [ ]
2007-03-19 21:02:30 +01:00
self . description = " A tool to debug the output of p4 -G. "
2007-03-26 08:18:55 +02:00
self . needsGit = False
2007-03-19 20:59:12 +01:00
def run ( self , args ) :
2007-05-23 23:49:35 +02:00
j = 0
2011-10-16 16:47:52 +02:00
for output in p4CmdList ( args ) :
2007-05-23 23:49:35 +02:00
print ' Element: %d ' % j
j + = 1
2007-03-19 20:59:12 +01:00
print output
2007-03-20 20:54:23 +01:00
return True
2007-03-19 20:59:12 +01:00
2007-05-21 22:57:06 +02:00
class P4RollBack ( Command ) :
def __init__ ( self ) :
Command . __init__ ( self )
self . options = [
2007-05-23 20:07:57 +02:00
optparse . make_option ( " --local " , dest = " rollbackLocalBranches " , action = " store_true " )
2007-05-21 22:57:06 +02:00
]
self . description = " A tool to debug the multi-branch import. Don ' t use :) "
2007-05-23 20:07:57 +02:00
self . rollbackLocalBranches = False
2007-05-21 22:57:06 +02:00
def run ( self , args ) :
if len ( args ) != 1 :
return False
maxChange = int ( args [ 0 ] )
2007-05-23 20:07:57 +02:00
2007-05-23 23:44:19 +02:00
if " p4ExitCode " in p4Cmd ( " changes -m 1 " ) :
2007-05-23 23:40:48 +02:00
die ( " Problems executing p4 " ) ;
2007-05-23 20:07:57 +02:00
if self . rollbackLocalBranches :
refPrefix = " refs/heads/ "
2007-05-23 22:10:46 +02:00
lines = read_pipe_lines ( " git rev-parse --symbolic --branches " )
2007-05-23 20:07:57 +02:00
else :
refPrefix = " refs/remotes/ "
2007-05-23 22:10:46 +02:00
lines = read_pipe_lines ( " git rev-parse --symbolic --remotes " )
2007-05-23 20:07:57 +02:00
for line in lines :
if self . rollbackLocalBranches or ( line . startswith ( " p4/ " ) and line != " p4/HEAD \n " ) :
2007-05-23 23:49:35 +02:00
line = line . strip ( )
ref = refPrefix + line
2007-05-21 22:57:06 +02:00
log = extractLogMessageFromGitCommit ( ref )
2007-05-23 23:49:35 +02:00
settings = extractSettingsGitLog ( log )
depotPaths = settings [ ' depot-paths ' ]
change = settings [ ' change ' ]
2007-05-21 22:57:06 +02:00
changed = False
2007-05-21 23:44:24 +02:00
2007-05-23 23:49:35 +02:00
if len ( p4Cmd ( " changes -m 1 " + ' ' . join ( [ ' %s ...@ %s ' % ( p , maxChange )
for p in depotPaths ] ) ) ) == 0 :
2007-05-21 23:44:24 +02:00
print " Branch %s did not exist at change %s , deleting. " % ( ref , maxChange )
system ( " git update-ref -d %s `git rev-parse %s ` " % ( ref , ref ) )
continue
2007-05-23 23:49:35 +02:00
while change and int ( change ) > maxChange :
2007-05-21 22:57:06 +02:00
changed = True
2007-05-21 23:44:24 +02:00
if self . verbose :
print " %s is at %s ; rewinding towards %s " % ( ref , change , maxChange )
2007-05-21 22:57:06 +02:00
system ( " git update-ref %s \" %s ^ \" " % ( ref , ref ) )
log = extractLogMessageFromGitCommit ( ref )
2007-05-23 23:49:35 +02:00
settings = extractSettingsGitLog ( log )
depotPaths = settings [ ' depot-paths ' ]
change = settings [ ' change ' ]
2007-05-21 22:57:06 +02:00
if changed :
2007-05-21 23:44:24 +02:00
print " %s rewound to %s " % ( ref , change )
2007-05-21 22:57:06 +02:00
return True
2011-04-21 21:50:23 +02:00
class P4Submit ( Command , P4UserMap ) :
2007-03-19 22:25:17 +01:00
def __init__ ( self ) :
2007-03-20 20:54:23 +01:00
Command . __init__ ( self )
2011-04-21 21:50:23 +02:00
P4UserMap . __init__ ( self )
2007-03-19 22:25:17 +01:00
self . options = [
optparse . make_option ( " --origin " , dest = " origin " ) ,
2011-02-20 02:18:24 +01:00
optparse . make_option ( " -M " , dest = " detectRenames " , action = " store_true " ) ,
2011-04-21 21:50:23 +02:00
# preserve the user, requires relevant p4 permissions
optparse . make_option ( " --preserve-user " , dest = " preserveUser " , action = " store_true " ) ,
2012-04-11 17:21:24 +02:00
optparse . make_option ( " --export-labels " , dest = " exportLabels " , action = " store_true " ) ,
2007-03-19 22:25:17 +01:00
]
self . description = " Submit changes from git to the perforce depot. "
2007-03-29 19:15:24 +02:00
self . usage + = " [name of git branch to submit into perforce depot] "
2007-03-23 09:16:07 +01:00
self . origin = " "
2011-02-20 02:18:24 +01:00
self . detectRenames = False
2011-04-21 21:50:23 +02:00
self . preserveUser = gitConfig ( " git-p4.preserveUser " ) . lower ( ) == " true "
2007-06-07 14:07:01 +02:00
self . isWindows = ( platform . system ( ) == " Windows " )
2012-04-11 17:21:24 +02:00
self . exportLabels = False
2012-07-13 01:29:00 +02:00
self . p4HasMoveCommand = p4_has_command ( " move " )
2007-03-19 22:25:17 +01:00
def check ( self ) :
if len ( p4CmdList ( " opened ... " ) ) > 0 :
die ( " You have files opened with perforce! Close them before starting the sync. " )
2012-07-04 15:34:20 +02:00
def separate_jobs_from_description ( self , message ) :
""" Extract and return a possible Jobs field in the commit
message . It goes into a separate section in the p4 change
specification .
A jobs line starts with " Jobs: " and looks like a new field
in a form . Values are white - space separated on the same
line or on following lines that start with a tab .
This does not parse and extract the full git commit message
like a p4 form . It just sees the Jobs : line as a marker
to pass everything from then on directly into the p4 form ,
but outside the description section .
Return a tuple ( stripped log message , jobs string ) . """
m = re . search ( r ' ^Jobs: ' , message , re . MULTILINE )
if m is None :
return ( message , None )
jobtext = message [ m . start ( ) : ]
stripped_message = message [ : m . start ( ) ] . rstrip ( )
return ( stripped_message , jobtext )
def prepareLogMessage ( self , template , message , jobs ) :
""" Edits the template returned from " p4 change -o " to insert
the message in the Description field , and the jobs text in
the Jobs field . """
2007-03-19 22:25:17 +01:00
result = " "
2008-02-19 09:29:06 +01:00
inDescriptionSection = False
2007-03-19 22:25:17 +01:00
for line in template . split ( " \n " ) :
if line . startswith ( " # " ) :
result + = line + " \n "
continue
2008-02-19 09:29:06 +01:00
if inDescriptionSection :
2011-02-26 03:31:13 +01:00
if line . startswith ( " Files: " ) or line . startswith ( " Jobs: " ) :
2008-02-19 09:29:06 +01:00
inDescriptionSection = False
2012-07-04 15:34:20 +02:00
# insert Jobs section
if jobs :
result + = jobs + " \n "
2008-02-19 09:29:06 +01:00
else :
continue
else :
if line . startswith ( " Description: " ) :
inDescriptionSection = True
line + = " \n "
for messageLine in message . split ( " \n " ) :
line + = " \t " + messageLine + " \n "
result + = line + " \n "
2007-03-19 22:25:17 +01:00
return result
2012-02-23 08:51:30 +01:00
def patchRCSKeywords ( self , file , pattern ) :
# Attempt to zap the RCS keywords in a p4 controlled file matching the given pattern
( handle , outFileName ) = tempfile . mkstemp ( dir = ' . ' )
try :
outFile = os . fdopen ( handle , " w+ " )
inFile = open ( file , " r " )
regexp = re . compile ( pattern , re . VERBOSE )
for line in inFile . readlines ( ) :
line = regexp . sub ( r ' $ \ 1$ ' , line )
outFile . write ( line )
inFile . close ( )
outFile . close ( )
# Forcibly overwrite the original file
os . unlink ( file )
shutil . move ( outFileName , file )
except :
# cleanup our temporary file
os . unlink ( outFileName )
print " Failed to strip RCS keywords in %s " % file
raise
print " Patched up RCS keywords in %s " % file
2011-04-21 21:50:23 +02:00
def p4UserForCommit ( self , id ) :
# Return the tuple (perforce user,git email) for a given git commit id
self . getUserMapFromPerforceServer ( )
gitEmail = read_pipe ( " git log --max-count=1 --format= ' %% ae ' %s " % id )
gitEmail = gitEmail . strip ( )
if not self . emails . has_key ( gitEmail ) :
return ( None , gitEmail )
else :
return ( self . emails [ gitEmail ] , gitEmail )
def checkValidP4Users ( self , commits ) :
# check if any git authors cannot be mapped to p4 users
for id in commits :
( user , email ) = self . p4UserForCommit ( id )
if not user :
msg = " Cannot find p4 user for email %s in commit %s . " % ( email , id )
if gitConfig ( ' git-p4.allowMissingP4Users ' ) . lower ( ) == " true " :
print " %s " % msg
else :
die ( " Error: %s \n Set git-p4.allowMissingP4Users to true to allow this. " % msg )
def lastP4Changelist ( self ) :
# Get back the last changelist number submitted in this client spec. This
# then gets used to patch up the username in the change. If the same
# client spec is being used by multiple processes then this might go
# wrong.
results = p4CmdList ( " client -o " ) # find the current client
client = None
for r in results :
if r . has_key ( ' Client ' ) :
client = r [ ' Client ' ]
break
if not client :
die ( " could not get client spec " )
2011-10-16 16:47:52 +02:00
results = p4CmdList ( [ " changes " , " -c " , client , " -m " , " 1 " ] )
2011-04-21 21:50:23 +02:00
for r in results :
if r . has_key ( ' change ' ) :
return r [ ' change ' ]
die ( " Could not get changelist number for last submit - cannot patch up user details " )
def modifyChangelistUser ( self , changelist , newUser ) :
# fixup the user field of a changelist after it has been submitted.
changes = p4CmdList ( " change -o %s " % changelist )
2011-05-07 12:19:43 +02:00
if len ( changes ) != 1 :
die ( " Bad output from p4 change modifying %s to user %s " %
( changelist , newUser ) )
c = changes [ 0 ]
if c [ ' User ' ] == newUser : return # nothing to do
c [ ' User ' ] = newUser
input = marshal . dumps ( c )
2011-04-21 21:50:23 +02:00
result = p4CmdList ( " change -f -i " , stdin = input )
for r in result :
if r . has_key ( ' code ' ) :
if r [ ' code ' ] == ' error ' :
die ( " Could not modify user field of changelist %s to %s : %s " % ( changelist , newUser , r [ ' data ' ] ) )
if r . has_key ( ' data ' ) :
print ( " Updated user field for changelist %s to %s " % ( changelist , newUser ) )
return
die ( " Could not modify user field of changelist %s to %s " % ( changelist , newUser ) )
def canChangeChangelists ( self ) :
# check to see if we have p4 admin or super-user permissions, either of
# which are required to modify changelists.
2012-01-19 10:52:25 +01:00
results = p4CmdList ( [ " protects " , self . depotPath ] )
2011-04-21 21:50:23 +02:00
for r in results :
if r . has_key ( ' perm ' ) :
if r [ ' perm ' ] == ' admin ' :
return 1
if r [ ' perm ' ] == ' super ' :
return 1
return 0
2007-08-08 17:06:55 +02:00
def prepareSubmitTemplate ( self ) :
2012-07-04 15:34:20 +02:00
""" Run " p4 change -o " to grab a change specification template.
This does not use " p4 -G " , as it is nice to keep the submission
template in original order , since a human might edit it .
Remove lines in the Files section that show changes to files
outside the depot path we ' re committing into. " " "
2007-08-08 17:06:55 +02:00
template = " "
inFilesSection = False
2011-10-16 16:47:52 +02:00
for line in p4_read_pipe_lines ( [ ' change ' , ' -o ' ] ) :
2008-03-28 15:40:40 +01:00
if line . endswith ( " \r \n " ) :
line = line [ : - 2 ] + " \n "
2007-08-08 17:06:55 +02:00
if inFilesSection :
if line . startswith ( " \t " ) :
# path starts and ends with a tab
path = line [ 1 : ]
lastTab = path . rfind ( " \t " )
if lastTab != - 1 :
path = path [ : lastTab ]
2011-03-15 13:08:02 +01:00
if not p4PathStartsWith ( path , self . depotPath ) :
2007-08-08 17:06:55 +02:00
continue
else :
inFilesSection = False
else :
if line . startswith ( " Files: " ) :
inFilesSection = True
template + = line
return template
2011-12-05 01:22:45 +01:00
def edit_template ( self , template_file ) :
""" Invoke the editor to let the user change the submission
message . Return true if okay to continue with the submit . """
# if configured to skip the editing part, just submit
if gitConfig ( " git-p4.skipSubmitEdit " ) == " true " :
return True
# look at the modification time, to check later if the user saved
# the file
mtime = os . stat ( template_file ) . st_mtime
# invoke the editor
2012-04-24 10:33:21 +02:00
if os . environ . has_key ( " P4EDITOR " ) and ( os . environ . get ( " P4EDITOR " ) != " " ) :
2011-12-05 01:22:45 +01:00
editor = os . environ . get ( " P4EDITOR " )
else :
editor = read_pipe ( " git var GIT_EDITOR " ) . strip ( )
system ( editor + " " + template_file )
# If the file was not saved, prompt to see if this patch should
# be skipped. But skip this verification step if configured so.
if gitConfig ( " git-p4.skipSubmitEditCheck " ) == " true " :
return True
2011-12-17 18:39:03 +01:00
# modification time updated means user saved the file
if os . stat ( template_file ) . st_mtime > mtime :
return True
while True :
response = raw_input ( " Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) " )
if response == ' y ' :
return True
if response == ' n ' :
return False
2011-12-05 01:22:45 +01:00
2007-05-23 21:55:48 +02:00
def applyCommit ( self , id ) :
2008-02-19 09:33:08 +01:00
print " Applying %s " % ( read_pipe ( " git log --max-count=1 --pretty=oneline %s " % id ) )
2011-02-20 02:18:24 +01:00
2011-05-13 21:46:00 +02:00
( p4User , gitEmail ) = self . p4UserForCommit ( id )
2011-04-21 21:50:23 +02:00
2012-07-04 15:40:19 +02:00
diff = read_pipe_lines ( " git diff-tree -r %s \" %s ^ \" \" %s \" " % ( self . diffOpts , id , id ) )
2007-03-19 22:25:17 +01:00
filesToAdd = set ( )
filesToDelete = set ( )
2007-05-16 09:41:26 +02:00
editedFiles = set ( )
2012-04-30 02:57:16 +02:00
pureRenameCopy = set ( )
2007-11-02 04:43:14 +01:00
filesToChangeExecBit = { }
2012-02-23 08:51:30 +01:00
2007-03-19 22:25:17 +01:00
for line in diff :
2007-11-02 04:43:13 +01:00
diff = parseDiffTreeEntry ( line )
modifier = diff [ ' status ' ]
path = diff [ ' src ' ]
2007-03-19 22:25:17 +01:00
if modifier == " M " :
2011-10-16 16:47:52 +02:00
p4_edit ( path )
2007-11-02 04:43:14 +01:00
if isModeExecChanged ( diff [ ' src_mode ' ] , diff [ ' dst_mode ' ] ) :
filesToChangeExecBit [ path ] = diff [ ' dst_mode ' ]
2007-05-16 09:41:26 +02:00
editedFiles . add ( path )
2007-03-19 22:25:17 +01:00
elif modifier == " A " :
filesToAdd . add ( path )
2007-11-02 04:43:14 +01:00
filesToChangeExecBit [ path ] = diff [ ' dst_mode ' ]
2007-03-19 22:25:17 +01:00
if path in filesToDelete :
filesToDelete . remove ( path )
elif modifier == " D " :
filesToDelete . add ( path )
if path in filesToAdd :
filesToAdd . remove ( path )
2011-02-20 02:18:25 +01:00
elif modifier == " C " :
src , dest = diff [ ' src ' ] , diff [ ' dst ' ]
2011-10-16 16:47:52 +02:00
p4_integrate ( src , dest )
2012-04-30 02:57:16 +02:00
pureRenameCopy . add ( dest )
2011-02-20 02:18:25 +01:00
if diff [ ' src_sha1 ' ] != diff [ ' dst_sha1 ' ] :
2011-10-16 16:47:52 +02:00
p4_edit ( dest )
2012-04-30 02:57:16 +02:00
pureRenameCopy . discard ( dest )
2011-02-20 02:18:25 +01:00
if isModeExecChanged ( diff [ ' src_mode ' ] , diff [ ' dst_mode ' ] ) :
2011-10-16 16:47:52 +02:00
p4_edit ( dest )
2012-04-30 02:57:16 +02:00
pureRenameCopy . discard ( dest )
2011-02-20 02:18:25 +01:00
filesToChangeExecBit [ dest ] = diff [ ' dst_mode ' ]
os . unlink ( dest )
editedFiles . add ( dest )
2007-10-16 07:15:06 +02:00
elif modifier == " R " :
2007-11-02 04:43:13 +01:00
src , dest = diff [ ' src ' ] , diff [ ' dst ' ]
2012-07-13 01:29:00 +02:00
if self . p4HasMoveCommand :
p4_edit ( src ) # src must be open before move
p4_move ( src , dest ) # opens for (move/delete, move/add)
2012-04-30 02:57:16 +02:00
else :
2012-07-13 01:29:00 +02:00
p4_integrate ( src , dest )
if diff [ ' src_sha1 ' ] != diff [ ' dst_sha1 ' ] :
p4_edit ( dest )
else :
pureRenameCopy . add ( dest )
2007-11-02 04:43:14 +01:00
if isModeExecChanged ( diff [ ' src_mode ' ] , diff [ ' dst_mode ' ] ) :
2012-07-13 01:29:00 +02:00
if not self . p4HasMoveCommand :
p4_edit ( dest ) # with move: already open, writable
2007-11-02 04:43:14 +01:00
filesToChangeExecBit [ dest ] = diff [ ' dst_mode ' ]
2012-07-13 01:29:00 +02:00
if not self . p4HasMoveCommand :
os . unlink ( dest )
filesToDelete . add ( src )
2007-10-16 07:15:06 +02:00
editedFiles . add ( dest )
2007-03-19 22:25:17 +01:00
else :
die ( " unknown modifier %s for %s " % ( modifier , path ) )
2008-02-19 09:33:08 +01:00
diffcmd = " git format-patch -k --stdout \" %s ^ \" .. \" %s \" " % ( id , id )
2007-05-20 16:33:21 +02:00
patchcmd = diffcmd + " | git apply "
2007-05-20 16:55:05 +02:00
tryPatchCmd = patchcmd + " --check - "
applyPatchCmd = patchcmd + " --check --apply - "
2012-02-23 08:51:30 +01:00
patch_succeeded = True
2007-04-15 09:59:56 +02:00
2007-05-20 16:33:21 +02:00
if os . system ( tryPatchCmd ) != 0 :
2012-02-23 08:51:30 +01:00
fixed_rcs_keywords = False
patch_succeeded = False
2007-04-15 09:59:56 +02:00
print " Unfortunately applying the change failed! "
2012-02-23 08:51:30 +01:00
# Patch failed, maybe it's just RCS keyword woes. Look through
# the patch to see if that's possible.
if gitConfig ( " git-p4.attemptRCSCleanup " , " --bool " ) == " true " :
file = None
pattern = None
kwfiles = { }
for file in editedFiles | filesToDelete :
# did this file's delta contain RCS keywords?
pattern = p4_keywords_regexp_for_file ( file )
if pattern :
# this file is a possibility...look for RCS keywords.
regexp = re . compile ( pattern , re . VERBOSE )
for line in read_pipe_lines ( [ " git " , " diff " , " %s ^.. %s " % ( id , id ) , file ] ) :
if regexp . search ( line ) :
if verbose :
print " got keyword match on %s in %s in %s " % ( pattern , line , file )
kwfiles [ file ] = pattern
break
for file in kwfiles :
if verbose :
print " zapping %s with %s " % ( line , pattern )
self . patchRCSKeywords ( file , kwfiles [ file ] )
fixed_rcs_keywords = True
if fixed_rcs_keywords :
print " Retrying the patch with RCS keywords cleaned up "
if os . system ( tryPatchCmd ) == 0 :
patch_succeeded = True
if not patch_succeeded :
2007-04-15 09:59:56 +02:00
print " What do you want to do? "
response = " x "
while response != " s " and response != " a " and response != " w " :
2007-05-23 21:53:11 +02:00
response = raw_input ( " [s]kip this patch / [a]pply the patch forcibly "
" and with .rej files / [w]rite the patch to a file (patch.txt) " )
2007-04-15 09:59:56 +02:00
if response == " s " :
print " Skipping! Good luck with the next patches... "
2007-09-13 22:10:18 +02:00
for f in editedFiles :
2011-10-16 16:47:52 +02:00
p4_revert ( f )
2007-09-13 22:10:18 +02:00
for f in filesToAdd :
2011-10-16 16:47:52 +02:00
os . remove ( f )
2007-04-15 09:59:56 +02:00
return
elif response == " a " :
2007-05-20 16:33:21 +02:00
os . system ( applyPatchCmd )
2007-04-15 09:59:56 +02:00
if len ( filesToAdd ) > 0 :
print " You may also want to call p4 add on the following files: "
print " " . join ( filesToAdd )
if len ( filesToDelete ) :
print " The following files should be scheduled for deletion with p4 delete: "
print " " . join ( filesToDelete )
2007-05-23 21:53:11 +02:00
die ( " Please resolve and submit the conflict manually and "
2012-04-09 02:18:01 +02:00
+ " continue afterwards with git p4 submit --continue " )
2007-04-15 09:59:56 +02:00
elif response == " w " :
system ( diffcmd + " > patch.txt " )
print " Patch saved to patch.txt in %s ! " % self . clientPath
2007-05-23 21:53:11 +02:00
die ( " Please resolve and submit the conflict manually and "
2012-04-09 02:18:01 +02:00
" continue afterwards with git p4 submit --continue " )
2007-04-15 09:59:56 +02:00
2007-05-20 16:33:21 +02:00
system ( applyPatchCmd )
2007-03-19 22:25:17 +01:00
for f in filesToAdd :
2011-10-16 16:47:52 +02:00
p4_add ( f )
2007-03-19 22:25:17 +01:00
for f in filesToDelete :
2011-10-16 16:47:52 +02:00
p4_revert ( f )
p4_delete ( f )
2007-03-19 22:25:17 +01:00
2007-11-02 04:43:14 +01:00
# Set/clear executable bits
for f in filesToChangeExecBit . keys ( ) :
mode = filesToChangeExecBit [ f ]
setP4ExecBit ( f , mode )
2008-02-19 09:33:08 +01:00
logMessage = extractLogMessageFromGitCommit ( id )
logMessage = logMessage . strip ( )
2012-07-04 15:34:20 +02:00
( logMessage , jobs ) = self . separate_jobs_from_description ( logMessage )
2007-03-19 22:25:17 +01:00
2007-08-08 17:06:55 +02:00
template = self . prepareSubmitTemplate ( )
2012-07-04 15:34:20 +02:00
submitTemplate = self . prepareLogMessage ( template , logMessage , jobs )
2011-05-07 12:19:43 +02:00
2012-07-04 15:34:18 +02:00
if self . preserveUser :
submitTemplate = submitTemplate + ( " \n ######## Actual user %s , modified after commit \n " % p4User )
if os . environ . has_key ( " P4DIFF " ) :
del ( os . environ [ " P4DIFF " ] )
diff = " "
for editedFile in editedFiles :
diff + = p4_read_pipe ( [ ' diff ' , ' -du ' ,
wildcard_encode ( editedFile ) ] )
newdiff = " "
for newFile in filesToAdd :
newdiff + = " ==== new file ==== \n "
newdiff + = " --- /dev/null \n "
newdiff + = " +++ %s \n " % newFile
f = open ( newFile , " r " )
for line in f . readlines ( ) :
newdiff + = " + " + line
f . close ( )
if self . checkAuthorship and not self . p4UserIsMe ( p4User ) :
submitTemplate + = " ######## git author %s does not match your p4 account. \n " % gitEmail
submitTemplate + = " ######## Use option --preserve-user to modify authorship. \n "
submitTemplate + = " ######## Variable git-p4.skipUserNameCheck hides this message. \n "
separatorLine = " ######## everything below this line is just the diff ####### \n "
( handle , fileName ) = tempfile . mkstemp ( )
tmpFile = os . fdopen ( handle , " w+ " )
if self . isWindows :
submitTemplate = submitTemplate . replace ( " \n " , " \r \n " )
separatorLine = separatorLine . replace ( " \n " , " \r \n " )
newdiff = newdiff . replace ( " \n " , " \r \n " )
tmpFile . write ( submitTemplate + separatorLine + diff + newdiff )
tmpFile . close ( )
if self . edit_template ( fileName ) :
# read the edited message and submit
tmpFile = open ( fileName , " rb " )
message = tmpFile . read ( )
2008-01-04 14:27:55 +01:00
tmpFile . close ( )
2012-07-04 15:34:18 +02:00
submitTemplate = message [ : message . index ( separatorLine ) ]
if self . isWindows :
submitTemplate = submitTemplate . replace ( " \r \n " , " \n " )
p4_write_pipe ( [ ' submit ' , ' -i ' ] , submitTemplate )
2008-01-04 14:27:55 +01:00
2012-07-04 15:34:18 +02:00
if self . preserveUser :
if p4User :
# Get last changelist number. Cannot easily get it from
# the submit command output as the output is
# unmarshalled.
changelist = self . lastP4Changelist ( )
self . modifyChangelistUser ( changelist , p4User )
# The rename/copy happened by applying a patch that created a
# new file. This leaves it writable, which confuses p4.
for f in pureRenameCopy :
p4_sync ( f , " -f " )
2008-08-27 09:30:29 +02:00
2007-03-19 22:25:17 +01:00
else :
2012-07-04 15:34:18 +02:00
# skip this patch
print " Submission cancelled, undoing p4 changes. "
for f in editedFiles :
p4_revert ( f )
for f in filesToAdd :
p4_revert ( f )
os . remove ( f )
os . remove ( fileName )
2007-03-19 22:25:17 +01:00
2012-04-11 17:21:24 +02:00
# Export git tags as p4 labels. Create a p4 label and then tag
# with that.
def exportGitTags ( self , gitTags ) :
2012-04-11 17:21:24 +02:00
validLabelRegexp = gitConfig ( " git-p4.labelExportRegexp " )
if len ( validLabelRegexp ) == 0 :
validLabelRegexp = defaultLabelRegexp
m = re . compile ( validLabelRegexp )
2012-04-11 17:21:24 +02:00
for name in gitTags :
if not m . match ( name ) :
if verbose :
2012-05-11 08:25:17 +02:00
print " tag %s does not match regexp %s " % ( name , validLabelRegexp )
2012-04-11 17:21:24 +02:00
continue
# Get the p4 commit this corresponds to
2012-04-11 17:21:24 +02:00
logMessage = extractLogMessageFromGitCommit ( name )
values = extractSettingsGitLog ( logMessage )
2012-04-11 17:21:24 +02:00
2012-04-11 17:21:24 +02:00
if not values . has_key ( ' change ' ) :
2012-04-11 17:21:24 +02:00
# a tag pointing to something not sent to p4; ignore
if verbose :
print " git tag %s does not give a p4 commit " % name
continue
2012-04-11 17:21:24 +02:00
else :
changelist = values [ ' change ' ]
2012-04-11 17:21:24 +02:00
# Get the tag details.
inHeader = True
isAnnotated = False
body = [ ]
for l in read_pipe_lines ( [ " git " , " cat-file " , " -p " , name ] ) :
l = l . strip ( )
if inHeader :
if re . match ( r ' tag \ s+ ' , l ) :
isAnnotated = True
elif re . match ( r ' \ s*$ ' , l ) :
inHeader = False
continue
else :
body . append ( l )
if not isAnnotated :
body = [ " lightweight tag imported by git p4 \n " ]
# Create the label - use the same view as the client spec we are using
clientSpec = getClientSpec ( )
labelTemplate = " Label: %s \n " % name
labelTemplate + = " Description: \n "
for b in body :
labelTemplate + = " \t " + b + " \n "
labelTemplate + = " View: \n "
for mapping in clientSpec . mappings :
labelTemplate + = " \t %s \n " % mapping . depot_side . path
p4_write_pipe ( [ " label " , " -i " ] , labelTemplate )
# Use the label
p4_system ( [ " tag " , " -l " , name ] +
[ " %s @ %s " % ( mapping . depot_side . path , changelist ) for mapping in clientSpec . mappings ] )
if verbose :
print " created p4 label for tag %s " % name
2007-03-19 22:25:17 +01:00
def run ( self , args ) :
2007-03-29 19:15:24 +02:00
if len ( args ) == 0 :
self . master = currentGitBranch ( )
2007-05-25 08:49:18 +02:00
if len ( self . master ) == 0 or not gitBranchExists ( " refs/heads/ %s " % self . master ) :
2007-03-29 19:15:24 +02:00
die ( " Detecting current git branch failed! " )
elif len ( args ) == 1 :
self . master = args [ 0 ]
2011-12-25 03:07:40 +01:00
if not branchExists ( self . master ) :
die ( " Branch %s does not exist " % self . master )
2007-03-29 19:15:24 +02:00
else :
return False
2008-06-22 20:12:39 +02:00
allowSubmit = gitConfig ( " git-p4.allowSubmit " )
if len ( allowSubmit ) > 0 and not self . master in allowSubmit . split ( " , " ) :
die ( " %s is not in git-p4.allowSubmit " % self . master )
2007-06-12 14:31:59 +02:00
[ upstream , settings ] = findUpstreamBranchPoint ( )
2007-08-08 17:06:55 +02:00
self . depotPath = settings [ ' depot-paths ' ] [ 0 ]
2007-06-12 14:31:59 +02:00
if len ( self . origin ) == 0 :
self . origin = upstream
2007-06-07 22:54:32 +02:00
2011-04-21 21:50:23 +02:00
if self . preserveUser :
if not self . canChangeChangelists ( ) :
die ( " Cannot preserve user names without p4 super-user or admin permissions " )
2007-06-07 22:54:32 +02:00
if self . verbose :
print " Origin branch is " + self . origin
2007-03-23 09:16:07 +01:00
2007-08-08 17:06:55 +02:00
if len ( self . depotPath ) == 0 :
2007-03-23 09:16:07 +01:00
print " Internal error: cannot locate perforce depot path from existing branches "
sys . exit ( 128 )
2012-02-26 02:06:25 +01:00
self . useClientSpec = False
if gitConfig ( " git-p4.useclientspec " , " --bool " ) == " true " :
self . useClientSpec = True
if self . useClientSpec :
self . clientSpecDirs = getClientSpec ( )
2007-03-23 09:16:07 +01:00
2012-02-26 02:06:25 +01:00
if self . useClientSpec :
# all files are relative to the client spec
self . clientPath = getClientRoot ( )
else :
self . clientPath = p4Where ( self . depotPath )
2007-03-23 09:16:07 +01:00
2012-02-26 02:06:25 +01:00
if self . clientPath == " " :
die ( " Error: Cannot locate perforce checkout of %s in client view " % self . depotPath )
2007-03-23 09:16:07 +01:00
2007-08-08 17:06:55 +02:00
print " Perforce checkout for depot path %s located at %s " % ( self . depotPath , self . clientPath )
2007-05-21 11:04:26 +02:00
self . oldWorkingDirectory = os . getcwd ( )
2007-05-20 16:55:05 +02:00
2011-12-10 00:48:14 +01:00
# ensure the clientPath exists
2012-04-30 02:57:14 +02:00
new_client_dir = False
2011-12-10 00:48:14 +01:00
if not os . path . exists ( self . clientPath ) :
2012-04-30 02:57:14 +02:00
new_client_dir = True
2011-12-10 00:48:14 +01:00
os . makedirs ( self . clientPath )
2008-08-01 21:50:03 +02:00
chdir ( self . clientPath )
2010-03-19 05:39:10 +01:00
print " Synchronizing p4 checkout... "
2012-04-30 02:57:14 +02:00
if new_client_dir :
# old one was destroyed, and maybe nobody told p4
p4_sync ( " ... " , " -f " )
else :
p4_sync ( " ... " )
2007-03-19 22:25:17 +01:00
self . check ( )
2008-02-19 09:37:16 +01:00
commits = [ ]
for line in read_pipe_lines ( " git rev-list --no-merges %s .. %s " % ( self . origin , self . master ) ) :
commits . append ( line . strip ( ) )
commits . reverse ( )
2007-03-19 22:25:17 +01:00
2011-05-13 21:46:00 +02:00
if self . preserveUser or ( gitConfig ( " git-p4.skipUserNameCheck " ) == " true " ) :
self . checkAuthorship = False
else :
self . checkAuthorship = True
2011-04-21 21:50:23 +02:00
if self . preserveUser :
self . checkValidP4Users ( commits )
2012-07-04 15:40:19 +02:00
#
# Build up a set of options to be passed to diff when
# submitting each commit to p4.
#
if self . detectRenames :
# command-line -M arg
self . diffOpts = " -M "
else :
# If not explicitly set check the config variable
detectRenames = gitConfig ( " git-p4.detectRenames " )
if detectRenames . lower ( ) == " false " or detectRenames == " " :
self . diffOpts = " "
elif detectRenames . lower ( ) == " true " :
self . diffOpts = " -M "
else :
self . diffOpts = " -M %s " % detectRenames
# no command-line arg for -C or --find-copies-harder, just
# config variables
detectCopies = gitConfig ( " git-p4.detectCopies " )
if detectCopies . lower ( ) == " false " or detectCopies == " " :
pass
elif detectCopies . lower ( ) == " true " :
self . diffOpts + = " -C "
else :
self . diffOpts + = " -C %s " % detectCopies
if gitConfig ( " git-p4.detectCopiesHarder " , " --bool " ) == " true " :
self . diffOpts + = " --find-copies-harder "
2007-03-19 22:25:17 +01:00
while len ( commits ) > 0 :
commit = commits [ 0 ]
commits = commits [ 1 : ]
2007-05-23 21:55:48 +02:00
self . applyCommit ( commit )
2007-03-19 22:25:17 +01:00
if len ( commits ) == 0 :
2008-02-19 09:37:16 +01:00
print " All changes applied! "
2008-08-01 21:50:03 +02:00
chdir ( self . oldWorkingDirectory )
2007-08-22 09:07:15 +02:00
2008-02-19 09:37:16 +01:00
sync = P4Sync ( )
sync . run ( [ ] )
2007-08-22 09:07:15 +02:00
2008-02-19 09:37:16 +01:00
rebase = P4Rebase ( )
rebase . rebase ( )
2007-03-19 22:25:17 +01:00
2012-04-11 17:21:24 +02:00
if gitConfig ( " git-p4.exportLabels " , " --bool " ) == " true " :
2012-05-11 08:25:18 +02:00
self . exportLabels = True
2012-04-11 17:21:24 +02:00
if self . exportLabels :
p4Labels = getP4Labels ( self . depotPath )
gitTags = getGitTags ( )
missingGitTags = gitTags - p4Labels
self . exportGitTags ( missingGitTags )
2007-03-20 20:54:23 +01:00
return True
2012-01-03 00:05:53 +01:00
class View ( object ) :
""" Represent a p4 view ( " p4 help views " ), and map files in a
repo according to the view . """
class Path ( object ) :
""" A depot or client path, possibly containing wildcards.
The only one supported is . . . at the end , currently .
Initialize with the full path , with / / depot or / / client . """
def __init__ ( self , path , is_depot ) :
self . path = path
self . is_depot = is_depot
self . find_wildcards ( )
# remember the prefix bit, useful for relative mappings
m = re . match ( " (//[^/]+/) " , self . path )
if not m :
die ( " Path %s does not start with //prefix/ " % self . path )
prefix = m . group ( 1 )
if not self . is_depot :
# strip //client/ on client paths
self . path = self . path [ len ( prefix ) : ]
def find_wildcards ( self ) :
""" Make sure wildcards are valid, and set up internal
variables . """
self . ends_triple_dot = False
# There are three wildcards allowed in p4 views
# (see "p4 help views"). This code knows how to
# handle "..." (only at the end), but cannot deal with
# "%%n" or "*". Only check the depot_side, as p4 should
# validate that the client_side matches too.
if re . search ( r ' %% [1-9] ' , self . path ) :
die ( " Can ' t handle %% n wildcards in view: %s " % self . path )
if self . path . find ( " * " ) > = 0 :
die ( " Can ' t handle * wildcards in view: %s " % self . path )
triple_dot_index = self . path . find ( " ... " )
if triple_dot_index > = 0 :
2012-01-12 00:31:06 +01:00
if triple_dot_index != len ( self . path ) - 3 :
die ( " Can handle only single ... wildcard, at end: %s " %
2012-01-03 00:05:53 +01:00
self . path )
self . ends_triple_dot = True
def ensure_compatible ( self , other_path ) :
""" Make sure the wildcards agree. """
if self . ends_triple_dot != other_path . ends_triple_dot :
die ( " Both paths must end with ... if either does; \n " +
" paths: %s %s " % ( self . path , other_path . path ) )
def match_wildcards ( self , test_path ) :
""" See if this test_path matches us, and fill in the value
of the wildcards if so . Returns a tuple of
( True | False , wildcards [ ] ) . For now , only the . . . at end
is supported , so at most one wildcard . """
if self . ends_triple_dot :
dotless = self . path [ : - 3 ]
if test_path . startswith ( dotless ) :
wildcard = test_path [ len ( dotless ) : ]
return ( True , [ wildcard ] )
else :
if test_path == self . path :
return ( True , [ ] )
return ( False , [ ] )
def match ( self , test_path ) :
""" Just return if it matches; don ' t bother with the wildcards. """
b , _ = self . match_wildcards ( test_path )
return b
def fill_in_wildcards ( self , wildcards ) :
""" Return the relative path, with the wildcards filled in
if there are any . """
if self . ends_triple_dot :
return self . path [ : - 3 ] + wildcards [ 0 ]
else :
return self . path
class Mapping ( object ) :
def __init__ ( self , depot_side , client_side , overlay , exclude ) :
# depot_side is without the trailing /... if it had one
self . depot_side = View . Path ( depot_side , is_depot = True )
self . client_side = View . Path ( client_side , is_depot = False )
self . overlay = overlay # started with "+"
self . exclude = exclude # started with "-"
assert not ( self . overlay and self . exclude )
self . depot_side . ensure_compatible ( self . client_side )
def __str__ ( self ) :
c = " "
if self . overlay :
c = " + "
if self . exclude :
c = " - "
return " View.Mapping: %s %s -> %s " % \
2012-01-12 00:31:07 +01:00
( c , self . depot_side . path , self . client_side . path )
2012-01-03 00:05:53 +01:00
def map_depot_to_client ( self , depot_path ) :
""" Calculate the client path if using this mapping on the
given depot path ; does not consider the effect of other
mappings in a view . Even excluded mappings are returned . """
matches , wildcards = self . depot_side . match_wildcards ( depot_path )
if not matches :
return " "
client_path = self . client_side . fill_in_wildcards ( wildcards )
return client_path
#
# View methods
#
def __init__ ( self ) :
self . mappings = [ ]
def append ( self , view_line ) :
""" Parse a view line, splitting it into depot and client
sides . Append to self . mappings , preserving order . """
# Split the view line into exactly two words. P4 enforces
# structure on these lines that simplifies this quite a bit.
#
# Either or both words may be double-quoted.
# Single quotes do not matter.
# Double-quote marks cannot occur inside the words.
# A + or - prefix is also inside the quotes.
# There are no quotes unless they contain a space.
# The line is already white-space stripped.
# The two words are separated by a single space.
#
if view_line [ 0 ] == ' " ' :
# First word is double quoted. Find its end.
close_quote_index = view_line . find ( ' " ' , 1 )
if close_quote_index < = 0 :
die ( " No first-word closing quote found: %s " % view_line )
depot_side = view_line [ 1 : close_quote_index ]
# skip closing quote and space
rhs_index = close_quote_index + 1 + 1
else :
space_index = view_line . find ( " " )
if space_index < = 0 :
die ( " No word-splitting space found: %s " % view_line )
depot_side = view_line [ 0 : space_index ]
rhs_index = space_index + 1
if view_line [ rhs_index ] == ' " ' :
# Second word is double quoted. Make sure there is a
# double quote at the end too.
if not view_line . endswith ( ' " ' ) :
die ( " View line with rhs quote should end with one: %s " %
view_line )
# skip the quotes
client_side = view_line [ rhs_index + 1 : - 1 ]
else :
client_side = view_line [ rhs_index : ]
# prefix + means overlay on previous mapping
overlay = False
if depot_side . startswith ( " + " ) :
overlay = True
depot_side = depot_side [ 1 : ]
# prefix - means exclude this path
exclude = False
if depot_side . startswith ( " - " ) :
exclude = True
depot_side = depot_side [ 1 : ]
m = View . Mapping ( depot_side , client_side , overlay , exclude )
self . mappings . append ( m )
def map_in_client ( self , depot_path ) :
""" Return the relative location in the client where this
depot file should live . Returns " " if the file should
not be mapped in the client . """
paths_filled = [ ]
client_path = " "
# look at later entries first
for m in self . mappings [ : : - 1 ] :
# see where will this path end up in the client
p = m . map_depot_to_client ( depot_path )
if p == " " :
# Depot path does not belong in client. Must remember
# this, as previous items should not cause files to
# exist in this path either. Remember that the list is
# being walked from the end, which has higher precedence.
# Overlap mappings do not exclude previous mappings.
if not m . overlay :
paths_filled . append ( m . client_side )
else :
# This mapping matched; no need to search any further.
# But, the mapping could be rejected if the client path
2012-01-12 00:31:08 +01:00
# has already been claimed by an earlier mapping (i.e.
# one later in the list, which we are walking backwards).
2012-01-03 00:05:53 +01:00
already_mapped_in_client = False
for f in paths_filled :
# this is View.Path.match
if f . match ( p ) :
already_mapped_in_client = True
break
if not already_mapped_in_client :
# Include this file, unless it is from a line that
# explicitly said to exclude it.
if not m . exclude :
client_path = p
# a match, even if rejected, always stops the search
break
return client_path
2011-04-21 21:50:23 +02:00
class P4Sync ( Command , P4UserMap ) :
2011-02-19 14:17:57 +01:00
delete_actions = ( " delete " , " move/delete " , " purge " )
2007-03-20 20:54:23 +01:00
def __init__ ( self ) :
Command . __init__ ( self )
2011-04-21 21:50:23 +02:00
P4UserMap . __init__ ( self )
2007-03-20 20:54:23 +01:00
self . options = [
optparse . make_option ( " --branch " , dest = " branch " ) ,
optparse . make_option ( " --detect-branches " , dest = " detectBranches " , action = " store_true " ) ,
optparse . make_option ( " --changesfile " , dest = " changesFile " ) ,
optparse . make_option ( " --silent " , dest = " silent " , action = " store_true " ) ,
2007-05-17 22:17:49 +02:00
optparse . make_option ( " --detect-labels " , dest = " detectLabels " , action = " store_true " ) ,
2012-04-11 17:21:24 +02:00
optparse . make_option ( " --import-labels " , dest = " importLabels " , action = " store_true " ) ,
2007-05-23 23:49:35 +02:00
optparse . make_option ( " --import-local " , dest = " importIntoRemotes " , action = " store_false " ,
help = " Import into refs/heads/ , not refs/remotes " ) ,
2007-05-23 23:20:53 +02:00
optparse . make_option ( " --max-changes " , dest = " maxChanges " ) ,
2007-05-23 23:49:35 +02:00
optparse . make_option ( " --keep-path " , dest = " keepRepoPath " , action = ' store_true ' ,
2008-02-18 15:22:08 +01:00
help = " Keep entire BRANCH/DIR/SUBDIR prefix during import " ) ,
optparse . make_option ( " --use-client-spec " , dest = " useClientSpec " , action = ' store_true ' ,
help = " Only sync files that are included in the Perforce Client Spec " )
2007-03-20 20:54:23 +01:00
]
self . description = """ Imports from Perforce into a git repository. \n
example :
/ / depot / my / project / - - to import the current head
/ / depot / my / project / @all - - to import everything
/ / depot / my / project / @ 1 , 6 - - to import only from revision 1 to 6
( a . . . is not needed in the path p4 specification , it ' s added implicitly) " " "
self . usage + = " //depot/path[@revRange] "
self . silent = False
2009-09-10 09:02:38 +02:00
self . createdBranches = set ( )
self . committedChanges = set ( )
2007-03-22 21:34:16 +01:00
self . branch = " "
2007-03-20 20:54:23 +01:00
self . detectBranches = False
2007-04-08 00:12:02 +02:00
self . detectLabels = False
2012-04-11 17:21:24 +02:00
self . importLabels = False
2007-03-20 20:54:23 +01:00
self . changesFile = " "
2007-05-25 10:36:10 +02:00
self . syncWithOrigin = True
2007-05-23 00:03:08 +02:00
self . importIntoRemotes = True
2007-05-23 00:07:35 +02:00
self . maxChanges = " "
2007-05-24 14:07:55 +02:00
self . isWindows = ( platform . system ( ) == " Windows " )
2007-05-23 23:20:53 +02:00
self . keepRepoPath = False
2007-05-23 23:49:35 +02:00
self . depotPaths = None
2007-06-16 13:09:21 +02:00
self . p4BranchesInGit = [ ]
2008-02-03 19:38:51 +01:00
self . cloneExclude = [ ]
2008-02-18 15:22:08 +01:00
self . useClientSpec = False
2012-02-26 02:06:24 +01:00
self . useClientSpec_from_options = False
2012-01-03 00:05:53 +01:00
self . clientSpecDirs = None
2012-01-26 00:48:22 +01:00
self . tempBranches = [ ]
self . tempBranchLocation = " git-p4-tmp "
2007-03-20 20:54:23 +01:00
2007-05-25 10:36:10 +02:00
if gitConfig ( " git-p4.syncFromOrigin " ) == " false " :
self . syncWithOrigin = False
2012-01-26 00:48:22 +01:00
# Force a checkpoint in fast-import and wait for it to finish
def checkpoint ( self ) :
self . gitStream . write ( " checkpoint \n \n " )
self . gitStream . write ( " progress checkpoint \n \n " )
out = self . gitOutput . readline ( )
if self . verbose :
print " checkpoint finished: " + out
2007-03-20 20:54:23 +01:00
def extractFilesFromCommit ( self , commit ) :
2008-02-03 19:38:51 +01:00
self . cloneExclude = [ re . sub ( r " \ . \ . \ .$ " , " " , path )
for path in self . cloneExclude ]
2007-03-20 20:54:23 +01:00
files = [ ]
fnum = 0
while commit . has_key ( " depotFile %s " % fnum ) :
path = commit [ " depotFile %s " % fnum ]
2007-05-23 23:49:35 +02:00
2008-02-03 19:38:51 +01:00
if [ p for p in self . cloneExclude
2011-03-15 13:08:02 +01:00
if p4PathStartsWith ( path , p ) ] :
2008-02-03 19:38:51 +01:00
found = False
else :
found = [ p for p in self . depotPaths
2011-03-15 13:08:02 +01:00
if p4PathStartsWith ( path , p ) ]
2007-05-23 23:49:35 +02:00
if not found :
2007-03-20 20:54:23 +01:00
fnum = fnum + 1
continue
file = { }
file [ " path " ] = path
file [ " rev " ] = commit [ " rev %s " % fnum ]
file [ " action " ] = commit [ " action %s " % fnum ]
file [ " type " ] = commit [ " type %s " % fnum ]
files . append ( file )
fnum = fnum + 1
return files
2007-05-23 23:49:35 +02:00
def stripRepoPath ( self , path , prefixes ) :
2012-08-11 18:55:04 +02:00
""" When streaming files, this is called to map a p4 depot path
to where it should go in git . The prefixes are either
self . depotPaths , or self . branchPrefixes in the case of
branch detection . """
2011-02-12 01:33:48 +01:00
if self . useClientSpec :
2012-08-11 18:55:04 +02:00
# branch detection moves files up a level (the branch name)
# from what client spec interpretation gives
2012-08-11 18:55:03 +02:00
path = self . clientSpecDirs . map_in_client ( path )
2012-08-11 18:55:04 +02:00
if self . detectBranches :
for b in self . knownBranches :
if path . startswith ( b + " / " ) :
path = path [ len ( b ) + 1 : ]
elif self . keepRepoPath :
# Preserve everything in relative path name except leading
# //depot/; just look at first prefix as they all should
# be in the same depot.
depot = re . sub ( " ^(//[^/]+/).* " , r ' \ 1 ' , prefixes [ 0 ] )
if p4PathStartsWith ( path , depot ) :
path = path [ len ( depot ) : ]
2011-02-12 01:33:48 +01:00
2012-08-11 18:55:03 +02:00
else :
for p in prefixes :
if p4PathStartsWith ( path , p ) :
path = path [ len ( p ) : ]
2012-08-11 18:55:04 +02:00
break
2007-05-23 23:20:53 +02:00
2012-08-11 18:55:03 +02:00
path = wildcard_decode ( path )
2007-05-23 23:49:35 +02:00
return path
2007-05-23 22:41:50 +02:00
2007-05-19 11:54:11 +02:00
def splitFilesIntoBranches ( self , commit ) :
2012-08-11 18:55:04 +02:00
""" Look at each depotFile in the commit to figure out to what
branch it belongs . """
2007-05-19 11:07:32 +02:00
branches = { }
2007-05-19 11:54:11 +02:00
fnum = 0
while commit . has_key ( " depotFile %s " % fnum ) :
path = commit [ " depotFile %s " % fnum ]
2007-05-23 23:49:35 +02:00
found = [ p for p in self . depotPaths
2011-03-15 13:08:02 +01:00
if p4PathStartsWith ( path , p ) ]
2007-05-23 23:49:35 +02:00
if not found :
2007-05-19 11:54:11 +02:00
fnum = fnum + 1
continue
file = { }
file [ " path " ] = path
file [ " rev " ] = commit [ " rev %s " % fnum ]
file [ " action " ] = commit [ " action %s " % fnum ]
file [ " type " ] = commit [ " type %s " % fnum ]
fnum = fnum + 1
2012-08-11 18:55:04 +02:00
# start with the full relative path where this file would
# go in a p4 client
if self . useClientSpec :
relPath = self . clientSpecDirs . map_in_client ( path )
else :
relPath = self . stripRepoPath ( path , self . depotPaths )
2007-03-20 20:54:23 +01:00
2007-05-18 21:45:23 +02:00
for branch in self . knownBranches . keys ( ) :
2012-08-11 18:55:04 +02:00
# add a trailing slash so that a commit into qt/4.2foo
# doesn't end up in qt/4.2, e.g.
2007-05-23 22:41:50 +02:00
if relPath . startswith ( branch + " / " ) :
2007-05-19 11:07:32 +02:00
if branch not in branches :
branches [ branch ] = [ ]
2007-05-19 11:54:11 +02:00
branches [ branch ] . append ( file )
2007-06-17 11:25:34 +02:00
break
2007-03-20 20:54:23 +01:00
return branches
2009-07-30 01:13:46 +02:00
# output one file from the P4 stream
# - helper for streamP4Files
def streamOneP4File ( self , file , contents ) :
relPath = self . stripRepoPath ( file [ ' depotFile ' ] , self . branchPrefixes )
if verbose :
sys . stderr . write ( " %s \n " % relPath )
2011-10-16 16:45:01 +02:00
( type_base , type_mods ) = split_p4_type ( file [ " type " ] )
git_mode = " 100644 "
if " x " in type_mods :
git_mode = " 100755 "
if type_base == " symlink " :
git_mode = " 120000 "
# p4 print on a symlink contains "target\n"; remove the newline
2010-02-16 09:44:08 +01:00
data = ' ' . join ( contents )
contents = [ data [ : - 1 ] ]
2009-07-30 01:13:46 +02:00
2011-10-16 16:45:01 +02:00
if type_base == " utf16 " :
2011-09-18 01:16:14 +02:00
# p4 delivers different text in the python output to -G
# than it does when using "print -o", or normal p4 client
# operations. utf16 is converted to ascii or utf8, perhaps.
# But ascii text saved as -t utf16 is completely mangled.
# Invoke print -o to get the real contents.
2011-10-16 16:47:52 +02:00
text = p4_read_pipe ( [ ' print ' , ' -q ' , ' -o ' , ' - ' , file [ ' depotFile ' ] ] )
2011-09-18 01:16:14 +02:00
contents = [ text ]
2011-11-05 18:36:07 +01:00
if type_base == " apple " :
# Apple filetype files will be streamed as a concatenation of
# its appledouble header and the contents. This is useless
# on both macs and non-macs. If using "print -q -o xx", it
# will create "xx" with the data, and "%xx" with the header.
# This is also not very useful.
#
# Ideally, someday, this script can learn how to generate
# appledouble files directly and import those to git, but
# non-mac machines can never find a use for apple filetype.
print " \n Ignoring apple filetype file %s " % file [ ' depotFile ' ]
return
2011-10-16 16:45:01 +02:00
# Perhaps windows wants unicode, utf16 newlines translated too;
# but this is not doing it.
if self . isWindows and type_base == " text " :
2009-07-30 01:13:46 +02:00
mangled = [ ]
for data in contents :
data = data . replace ( " \r \n " , " \n " )
mangled . append ( data )
contents = mangled
2011-09-18 01:16:14 +02:00
# Note that we do not try to de-mangle keywords on utf16 files,
# even though in theory somebody may want that.
2012-02-23 08:51:30 +01:00
pattern = p4_keywords_regexp_for_type ( type_base , type_mods )
if pattern :
regexp = re . compile ( pattern , re . VERBOSE )
text = ' ' . join ( contents )
text = regexp . sub ( r ' $ \ 1$ ' , text )
contents = [ text ]
2009-07-30 01:13:46 +02:00
2011-10-16 16:45:01 +02:00
self . gitStream . write ( " M %s inline %s \n " % ( git_mode , relPath ) )
2009-07-30 01:13:46 +02:00
# total length...
length = 0
for d in contents :
length = length + len ( d )
self . gitStream . write ( " data %d \n " % length )
for d in contents :
self . gitStream . write ( d )
self . gitStream . write ( " \n " )
def streamOneP4Deletion ( self , file ) :
relPath = self . stripRepoPath ( file [ ' path ' ] , self . branchPrefixes )
if verbose :
sys . stderr . write ( " delete %s \n " % relPath )
self . gitStream . write ( " D %s \n " % relPath )
# handle another chunk of streaming data
def streamP4FilesCb ( self , marshalled ) :
2011-04-07 08:01:21 +02:00
if marshalled . has_key ( ' depotFile ' ) and self . stream_have_file_info :
# start of a new file - output the old one first
self . streamOneP4File ( self . stream_file , self . stream_contents )
self . stream_file = { }
self . stream_contents = [ ]
self . stream_have_file_info = False
2009-07-30 01:13:46 +02:00
2011-04-07 08:01:21 +02:00
# pick up the new file information... for the
# 'data' field we need to append to our array
for k in marshalled . keys ( ) :
if k == ' data ' :
self . stream_contents . append ( marshalled [ ' data ' ] )
else :
self . stream_file [ k ] = marshalled [ k ]
2009-07-30 01:13:46 +02:00
2011-04-07 08:01:21 +02:00
self . stream_have_file_info = True
2009-07-30 01:13:46 +02:00
# Stream directly from "p4 files" into "git fast-import"
def streamP4Files ( self , files ) :
2008-03-03 11:55:48 +01:00
filesForCommit = [ ]
filesToRead = [ ]
2009-07-30 01:13:46 +02:00
filesToDelete = [ ]
2008-03-03 11:55:48 +01:00
2008-02-18 15:22:08 +01:00
for f in files :
2012-01-03 00:05:53 +01:00
# if using a client spec, only add the files that have
# a path in the client
if self . clientSpecDirs :
if self . clientSpecDirs . map_in_client ( f [ ' path ' ] ) == " " :
continue
2008-02-18 15:22:08 +01:00
2012-01-03 00:05:53 +01:00
filesForCommit . append ( f )
if f [ ' action ' ] in self . delete_actions :
filesToDelete . append ( f )
else :
filesToRead . append ( f )
2007-05-23 23:49:35 +02:00
2009-07-30 01:13:46 +02:00
# deleted files...
for f in filesToDelete :
self . streamOneP4Deletion ( f )
2007-05-23 23:49:35 +02:00
2009-07-30 01:13:46 +02:00
if len ( filesToRead ) > 0 :
self . stream_file = { }
self . stream_contents = [ ]
self . stream_have_file_info = False
2008-03-03 13:42:47 +01:00
2011-04-07 08:01:21 +02:00
# curry self argument
def streamP4FilesCbSelf ( entry ) :
self . streamP4FilesCb ( entry )
2007-05-23 23:49:35 +02:00
2011-10-16 16:47:52 +02:00
fileArgs = [ ' %s # %s ' % ( f [ ' path ' ] , f [ ' rev ' ] ) for f in filesToRead ]
p4CmdList ( [ " -x " , " - " , " print " ] ,
stdin = fileArgs ,
cb = streamP4FilesCbSelf )
2008-03-03 11:55:48 +01:00
2009-07-30 01:13:46 +02:00
# do the last chunk
if self . stream_file . has_key ( ' depotFile ' ) :
self . streamOneP4File ( self . stream_file , self . stream_contents )
2007-05-23 23:49:35 +02:00
2012-01-19 10:52:27 +01:00
def make_email ( self , userid ) :
if userid in self . users :
return self . users [ userid ]
else :
return " %s <a@b> " % userid
2012-04-11 17:21:24 +02:00
# Stream a p4 tag
def streamTag ( self , gitStream , labelName , labelDetails , commit , epoch ) :
if verbose :
print " writing tag %s for commit %s " % ( labelName , commit )
gitStream . write ( " tag %s \n " % labelName )
gitStream . write ( " from %s \n " % commit )
if labelDetails . has_key ( ' Owner ' ) :
owner = labelDetails [ " Owner " ]
else :
owner = None
# Try to use the owner of the p4 label, or failing that,
# the current p4 user id.
if owner :
email = self . make_email ( owner )
else :
email = self . make_email ( self . p4UserId ( ) )
tagger = " %s %s %s " % ( email , epoch , self . tz )
gitStream . write ( " tagger %s \n " % tagger )
print " labelDetails= " , labelDetails
if labelDetails . has_key ( ' Description ' ) :
description = labelDetails [ ' Description ' ]
else :
description = ' Label from git p4 '
gitStream . write ( " data %d \n " % len ( description ) )
gitStream . write ( description )
gitStream . write ( " \n " )
2012-08-11 18:55:02 +02:00
def commit ( self , details , files , branch , parent = " " ) :
2007-03-20 20:54:23 +01:00
epoch = details [ " time " ]
author = details [ " user " ]
2007-05-18 21:45:23 +02:00
if self . verbose :
print " commit into %s " % branch
2007-05-23 23:49:35 +02:00
# start with reading files; if that fails, we should not
# create a commit.
new_files = [ ]
for f in files :
2012-08-11 18:55:02 +02:00
if [ p for p in self . branchPrefixes if p4PathStartsWith ( f [ ' path ' ] , p ) ] :
2007-05-23 23:49:35 +02:00
new_files . append ( f )
else :
2011-03-15 13:08:03 +01:00
sys . stderr . write ( " Ignoring file outside of prefix: %s \n " % f [ ' path ' ] )
2007-05-23 23:49:35 +02:00
2007-03-20 20:54:23 +01:00
self . gitStream . write ( " commit %s \n " % branch )
2007-05-23 23:49:35 +02:00
# gitStream.write("mark :%s\n" % details["change"])
2007-03-20 20:54:23 +01:00
self . committedChanges . add ( int ( details [ " change " ] ) )
committer = " "
2007-05-20 10:55:54 +02:00
if author not in self . users :
self . getUserMapFromPerforceServer ( )
2012-01-19 10:52:27 +01:00
committer = " %s %s %s " % ( self . make_email ( author ) , epoch , self . tz )
2007-03-20 20:54:23 +01:00
self . gitStream . write ( " committer %s \n " % committer )
self . gitStream . write ( " data <<EOT \n " )
self . gitStream . write ( details [ " desc " ] )
2012-08-11 18:55:02 +02:00
self . gitStream . write ( " \n [git-p4: depot-paths = \" %s \" : change = %s " %
( ' , ' . join ( self . branchPrefixes ) , details [ " change " ] ) )
2007-06-11 10:01:58 +02:00
if len ( details [ ' options ' ] ) > 0 :
self . gitStream . write ( " : options = %s " % details [ ' options ' ] )
self . gitStream . write ( " ] \n EOT \n \n " )
2007-03-20 20:54:23 +01:00
if len ( parent ) > 0 :
2007-05-18 21:45:23 +02:00
if self . verbose :
print " parent %s " % parent
2007-03-20 20:54:23 +01:00
self . gitStream . write ( " from %s \n " % parent )
2009-07-30 01:13:46 +02:00
self . streamP4Files ( new_files )
2007-03-20 20:54:23 +01:00
self . gitStream . write ( " \n " )
2007-03-26 22:34:34 +02:00
change = int ( details [ " change " ] )
2007-05-19 12:05:40 +02:00
if self . labels . has_key ( change ) :
2007-03-26 22:34:34 +02:00
label = self . labels [ change ]
labelDetails = label [ 0 ]
labelRevisions = label [ 1 ]
2007-05-19 11:54:11 +02:00
if self . verbose :
print " Change %s is labelled %s " % ( change , labelDetails )
2007-03-26 22:34:34 +02:00
2011-10-16 16:47:52 +02:00
files = p4CmdList ( [ " files " ] + [ " %s ...@ %s " % ( p , change )
2012-08-11 18:55:02 +02:00
for p in self . branchPrefixes ] )
2007-03-26 22:34:34 +02:00
if len ( files ) == len ( labelRevisions ) :
cleanedFiles = { }
for info in files :
2011-02-19 14:17:57 +01:00
if info [ " action " ] in self . delete_actions :
2007-03-26 22:34:34 +02:00
continue
cleanedFiles [ info [ " depotFile " ] ] = info [ " rev " ]
if cleanedFiles == labelRevisions :
2012-04-11 17:21:24 +02:00
self . streamTag ( self . gitStream , ' tag_ %s ' % labelDetails [ ' label ' ] , labelDetails , branch , epoch )
2007-03-26 22:34:34 +02:00
else :
2007-03-28 17:05:38 +02:00
if not self . silent :
2007-05-23 21:53:11 +02:00
print ( " Tag %s does not match with change %s : files do not match. "
% ( labelDetails [ " label " ] , change ) )
2007-03-26 22:34:34 +02:00
else :
2007-03-28 17:05:38 +02:00
if not self . silent :
2007-05-23 21:53:11 +02:00
print ( " Tag %s does not match with change %s : file count is different. "
% ( labelDetails [ " label " ] , change ) )
2007-03-20 20:54:23 +01:00
2012-04-11 17:21:24 +02:00
# Build a dictionary of changelists and labels, for "detect-labels" option.
2007-03-26 22:34:34 +02:00
def getLabels ( self ) :
self . labels = { }
2012-01-19 10:52:25 +01:00
l = p4CmdList ( [ " labels " ] + [ " %s ... " % p for p in self . depotPaths ] )
2007-04-08 10:15:47 +02:00
if len ( l ) > 0 and not self . silent :
2007-11-21 04:01:19 +01:00
print " Finding files belonging to labels in %s " % ` self . depotPaths `
2007-04-07 23:46:50 +02:00
for output in l :
2007-03-26 22:34:34 +02:00
label = output [ " label " ]
revisions = { }
newestChange = 0
2007-05-19 11:54:11 +02:00
if self . verbose :
print " Querying files for label %s " % label
2011-10-16 16:47:52 +02:00
for file in p4CmdList ( [ " files " ] +
[ " %s ...@ %s " % ( p , label )
for p in self . depotPaths ] ) :
2007-03-26 22:34:34 +02:00
revisions [ file [ " depotFile " ] ] = file [ " rev " ]
change = int ( file [ " change " ] )
if change > newestChange :
newestChange = change
2007-05-19 12:05:40 +02:00
self . labels [ newestChange ] = [ output , revisions ]
if self . verbose :
print " Label changes: %s " % self . labels . keys ( )
2007-03-26 22:34:34 +02:00
2012-04-11 17:21:24 +02:00
# Import p4 labels as git tags. A direct mapping does not
# exist, so assume that if all the files are at the same revision
# then we can use that, or it's something more complicated we should
# just ignore.
def importP4Labels ( self , stream , p4Labels ) :
if verbose :
print " import p4 labels: " + ' ' . join ( p4Labels )
ignoredP4Labels = gitConfigList ( " git-p4.ignoredP4Labels " )
2012-04-11 17:21:24 +02:00
validLabelRegexp = gitConfig ( " git-p4.labelImportRegexp " )
2012-04-11 17:21:24 +02:00
if len ( validLabelRegexp ) == 0 :
validLabelRegexp = defaultLabelRegexp
m = re . compile ( validLabelRegexp )
for name in p4Labels :
commitFound = False
if not m . match ( name ) :
if verbose :
print " label %s does not match regexp %s " % ( name , validLabelRegexp )
continue
if name in ignoredP4Labels :
continue
labelDetails = p4CmdList ( [ ' label ' , " -o " , name ] ) [ 0 ]
# get the most recent changelist for each file in this label
change = p4Cmd ( [ " changes " , " -m " , " 1 " ] + [ " %s ...@ %s " % ( p , name )
for p in self . depotPaths ] )
if change . has_key ( ' change ' ) :
# find the corresponding git commit; take the oldest commit
changelist = int ( change [ ' change ' ] )
gitCommit = read_pipe ( [ " git " , " rev-list " , " --max-count=1 " ,
" --reverse " , " :/ \ [git-p4:.*change = %d \ ] " % changelist ] )
if len ( gitCommit ) == 0 :
print " could not find git commit for changelist %d " % changelist
else :
gitCommit = gitCommit . strip ( )
commitFound = True
# Convert from p4 time format
try :
tmwhen = time . strptime ( labelDetails [ ' Update ' ] , " % Y/ % m/ %d % H: % M: % S " )
except ValueError :
print " Could not convert label time %s " % labelDetail [ ' Update ' ]
tmwhen = 1
when = int ( time . mktime ( tmwhen ) )
self . streamTag ( stream , name , labelDetails , gitCommit , when )
if verbose :
print " p4 label %s mapped to git commit %s " % ( name , gitCommit )
else :
if verbose :
print " Label %s has no changelists - possibly deleted? " % name
if not commitFound :
# We can't import this label; don't try again as it will get very
# expensive repeatedly fetching all the files for labels that will
# never be imported. If the label is moved in the future, the
# ignore will need to be removed manually.
system ( [ " git " , " config " , " --add " , " git-p4.ignoredP4Labels " , name ] )
2007-05-23 23:49:35 +02:00
def guessProjectName ( self ) :
for p in self . depotPaths :
2007-06-11 08:50:57 +02:00
if p . endswith ( " / " ) :
p = p [ : - 1 ]
p = p [ p . strip ( ) . rfind ( " / " ) + 1 : ]
if not p . endswith ( " / " ) :
p + = " / "
return p
2007-05-23 23:49:35 +02:00
2007-05-18 21:45:23 +02:00
def getBranchMapping ( self ) :
2007-06-17 11:25:34 +02:00
lostAndFoundBranches = set ( )
2011-08-19 01:44:04 +02:00
user = gitConfig ( " git-p4.branchUser " )
if len ( user ) > 0 :
command = " branches -u %s " % user
else :
command = " branches "
for info in p4CmdList ( command ) :
2012-01-19 10:52:25 +01:00
details = p4Cmd ( [ " branch " , " -o " , info [ " branch " ] ] )
2007-05-18 21:45:23 +02:00
viewIdx = 0
while details . has_key ( " View %s " % viewIdx ) :
paths = details [ " View %s " % viewIdx ] . split ( " " )
viewIdx = viewIdx + 1
# require standard //depot/foo/... //depot/bar/... mapping
if len ( paths ) != 2 or not paths [ 0 ] . endswith ( " /... " ) or not paths [ 1 ] . endswith ( " /... " ) :
continue
source = paths [ 0 ]
destination = paths [ 1 ]
2007-06-07 09:41:53 +02:00
## HACK
2011-03-15 13:08:02 +01:00
if p4PathStartsWith ( source , self . depotPaths [ 0 ] ) and p4PathStartsWith ( destination , self . depotPaths [ 0 ] ) :
2007-06-07 09:41:53 +02:00
source = source [ len ( self . depotPaths [ 0 ] ) : - 4 ]
destination = destination [ len ( self . depotPaths [ 0 ] ) : - 4 ]
2007-06-17 11:25:34 +02:00
2007-06-17 15:10:24 +02:00
if destination in self . knownBranches :
if not self . silent :
print " p4 branch %s defines a mapping from %s to %s " % ( info [ " branch " ] , source , destination )
print " but there exists another mapping from %s to %s already! " % ( self . knownBranches [ destination ] , destination )
continue
2007-06-17 11:25:34 +02:00
self . knownBranches [ destination ] = source
lostAndFoundBranches . discard ( destination )
2007-05-19 10:23:12 +02:00
if source not in self . knownBranches :
2007-06-17 11:25:34 +02:00
lostAndFoundBranches . add ( source )
2011-08-19 01:44:05 +02:00
# Perforce does not strictly require branches to be defined, so we also
# check git config for a branch list.
#
# Example of branch definition in git config file:
# [git-p4]
# branchList=main:branchA
# branchList=main:branchB
# branchList=branchA:branchC
configBranches = gitConfigList ( " git-p4.branchList " )
for branch in configBranches :
if branch :
( source , destination ) = branch . split ( " : " )
self . knownBranches [ destination ] = source
lostAndFoundBranches . discard ( destination )
if source not in self . knownBranches :
lostAndFoundBranches . add ( source )
2007-06-17 11:25:34 +02:00
for branch in lostAndFoundBranches :
self . knownBranches [ branch ] = branch
2007-05-19 10:23:12 +02:00
2007-11-15 10:38:45 +01:00
def getBranchMappingFromGitBranches ( self ) :
branches = p4BranchesInGit ( self . importIntoRemotes )
for branch in branches . keys ( ) :
if branch == " master " :
branch = " main "
else :
branch = branch [ len ( self . projectName ) : ]
self . knownBranches [ branch ] = branch
2007-05-19 10:23:12 +02:00
def listExistingP4GitBranches ( self ) :
2007-07-18 17:27:50 +02:00
# branches holds mapping from name to commit
branches = p4BranchesInGit ( self . importIntoRemotes )
self . p4BranchesInGit = branches . keys ( )
for branch in branches . keys ( ) :
self . initialParents [ self . refPrefix + branch ] = branches [ branch ]
2007-05-18 21:45:23 +02:00
2007-05-23 23:49:35 +02:00
def updateOptionDict ( self , d ) :
option_keys = { }
if self . keepRepoPath :
option_keys [ ' keepRepoPath ' ] = 1
d [ " options " ] = ' ' . join ( sorted ( option_keys . keys ( ) ) )
def readOptions ( self , d ) :
self . keepRepoPath = ( d . has_key ( ' options ' )
and ( ' keepRepoPath ' in d [ ' options ' ] ) )
2007-05-23 23:49:35 +02:00
2007-08-26 16:44:55 +02:00
def gitRefForBranch ( self , branch ) :
if branch == " main " :
return self . refPrefix + " master "
if len ( branch ) < = 0 :
return branch
return self . refPrefix + self . projectName + branch
2007-08-26 17:36:55 +02:00
def gitCommitByP4Change ( self , ref , change ) :
if self . verbose :
print " looking in ref " + ref + " for change %s using bisect... " % change
earliestCommit = " "
latestCommit = parseRevision ( ref )
while True :
if self . verbose :
print " trying: earliest %s latest %s " % ( earliestCommit , latestCommit )
next = read_pipe ( " git rev-list --bisect %s %s " % ( latestCommit , earliestCommit ) ) . strip ( )
if len ( next ) == 0 :
if self . verbose :
print " argh "
return " "
log = extractLogMessageFromGitCommit ( next )
settings = extractSettingsGitLog ( log )
currentChange = int ( settings [ ' change ' ] )
if self . verbose :
print " current change %s " % currentChange
if currentChange == change :
if self . verbose :
print " found %s " % next
return next
if currentChange < change :
earliestCommit = " ^ %s " % next
else :
latestCommit = " %s " % next
return " "
def importNewBranch ( self , branch , maxChange ) :
# make fast-import flush all changes to disk and update the refs using the checkpoint
# command so that we can try to find the branch parent in the git history
self . gitStream . write ( " checkpoint \n \n " ) ;
self . gitStream . flush ( ) ;
branchPrefix = self . depotPaths [ 0 ] + branch + " / "
range = " @1, %s " % maxChange
#print "prefix" + branchPrefix
changes = p4ChangesForPaths ( [ branchPrefix ] , range )
if len ( changes ) < = 0 :
return False
firstChange = changes [ 0 ]
#print "first change in branch: %s" % firstChange
sourceBranch = self . knownBranches [ branch ]
sourceDepotPath = self . depotPaths [ 0 ] + sourceBranch
sourceRef = self . gitRefForBranch ( sourceBranch )
#print "source " + sourceBranch
2012-01-19 10:52:25 +01:00
branchParentChange = int ( p4Cmd ( [ " changes " , " -m " , " 1 " , " %s ...@1, %s " % ( sourceDepotPath , firstChange ) ] ) [ " change " ] )
2007-08-26 17:36:55 +02:00
#print "branch parent: %s" % branchParentChange
gitParent = self . gitCommitByP4Change ( sourceRef , branchParentChange )
if len ( gitParent ) > 0 :
self . initialParents [ self . gitRefForBranch ( branch ) ] = gitParent
#print "parent git commit: %s" % gitParent
self . importChanges ( changes )
return True
2012-01-26 00:48:22 +01:00
def searchParent ( self , parent , branch , target ) :
parentFound = False
for blob in read_pipe_lines ( [ " git " , " rev-list " , " --reverse " , " --no-merges " , parent ] ) :
blob = blob . strip ( )
if len ( read_pipe ( [ " git " , " diff-tree " , blob , target ] ) ) == 0 :
parentFound = True
if self . verbose :
print " Found parent of %s in commit %s " % ( branch , blob )
break
if parentFound :
return blob
else :
return None
2007-08-26 16:00:52 +02:00
def importChanges ( self , changes ) :
cnt = 1
for change in changes :
2012-01-26 00:48:24 +01:00
description = p4Cmd ( [ " describe " , str ( change ) ] )
2007-08-26 16:00:52 +02:00
self . updateOptionDict ( description )
if not self . silent :
sys . stdout . write ( " \r Importing revision %s ( %s %% ) " % ( change , cnt * 100 / len ( changes ) ) )
sys . stdout . flush ( )
cnt = cnt + 1
try :
if self . detectBranches :
branches = self . splitFilesIntoBranches ( description )
for branch in branches . keys ( ) :
## HACK --hwn
branchPrefix = self . depotPaths [ 0 ] + branch + " / "
2012-08-11 18:55:02 +02:00
self . branchPrefixes = [ branchPrefix ]
2007-08-26 16:00:52 +02:00
parent = " "
filesForCommit = branches [ branch ]
if self . verbose :
print " branch is %s " % branch
self . updatedBranches . add ( branch )
if branch not in self . createdBranches :
self . createdBranches . add ( branch )
parent = self . knownBranches [ branch ]
if parent == branch :
parent = " "
2007-08-26 17:36:55 +02:00
else :
fullBranch = self . projectName + branch
if fullBranch not in self . p4BranchesInGit :
if not self . silent :
print ( " \n Importing new branch %s " % fullBranch ) ;
if self . importNewBranch ( branch , change - 1 ) :
parent = " "
self . p4BranchesInGit . append ( fullBranch )
if not self . silent :
print ( " \n Resuming with change %s " % change ) ;
if self . verbose :
print " parent determined through known branches: %s " % parent
2007-08-26 16:00:52 +02:00
2007-08-26 16:44:55 +02:00
branch = self . gitRefForBranch ( branch )
parent = self . gitRefForBranch ( parent )
2007-08-26 16:00:52 +02:00
if self . verbose :
print " looking for initial parent for %s ; current parent is %s " % ( branch , parent )
if len ( parent ) == 0 and branch in self . initialParents :
parent = self . initialParents [ branch ]
del self . initialParents [ branch ]
2012-01-26 00:48:22 +01:00
blob = None
if len ( parent ) > 0 :
tempBranch = os . path . join ( self . tempBranchLocation , " %d " % ( change ) )
if self . verbose :
print " Creating temporary branch: " + tempBranch
2012-08-11 18:55:02 +02:00
self . commit ( description , filesForCommit , tempBranch )
2012-01-26 00:48:22 +01:00
self . tempBranches . append ( tempBranch )
self . checkpoint ( )
blob = self . searchParent ( parent , branch , tempBranch )
if blob :
2012-08-11 18:55:02 +02:00
self . commit ( description , filesForCommit , branch , blob )
2012-01-26 00:48:22 +01:00
else :
if self . verbose :
print " Parent of %s not found. Committing into head of %s " % ( branch , parent )
2012-08-11 18:55:02 +02:00
self . commit ( description , filesForCommit , branch , parent )
2007-08-26 16:00:52 +02:00
else :
files = self . extractFilesFromCommit ( description )
2012-08-11 18:55:02 +02:00
self . commit ( description , files , self . branch ,
2007-08-26 16:00:52 +02:00
self . initialParent )
self . initialParent = " "
except IOError :
print self . gitError . read ( )
sys . exit ( 1 )
2007-08-26 16:07:18 +02:00
def importHeadRevision ( self , revision ) :
print " Doing initial import of %s from revision %s into %s " % ( ' ' . join ( self . depotPaths ) , revision , self . branch )
2011-07-31 15:45:55 +02:00
details = { }
details [ " user " ] = " git perforce import user "
2011-02-19 14:17:56 +01:00
details [ " desc " ] = ( " Initial import of %s from the state at revision %s \n "
2007-08-26 16:07:18 +02:00
% ( ' ' . join ( self . depotPaths ) , revision ) )
details [ " change " ] = revision
newestRevision = 0
fileCnt = 0
2011-10-16 16:47:52 +02:00
fileArgs = [ " %s ... %s " % ( p , revision ) for p in self . depotPaths ]
for info in p4CmdList ( [ " files " ] + fileArgs ) :
2007-08-26 16:07:18 +02:00
2011-02-19 14:17:55 +01:00
if ' code ' in info and info [ ' code ' ] == ' error ' :
2007-08-26 16:07:18 +02:00
sys . stderr . write ( " p4 returned an error: %s \n "
% info [ ' data ' ] )
2011-02-19 14:17:58 +01:00
if info [ ' data ' ] . find ( " must refer to client " ) > = 0 :
sys . stderr . write ( " This particular p4 error is misleading. \n " )
sys . stderr . write ( " Perhaps the depot path was misspelled. \n " ) ;
sys . stderr . write ( " Depot path: %s \n " % " " . join ( self . depotPaths ) )
2007-08-26 16:07:18 +02:00
sys . exit ( 1 )
2011-02-19 14:17:55 +01:00
if ' p4ExitCode ' in info :
sys . stderr . write ( " p4 exitcode: %s \n " % info [ ' p4ExitCode ' ] )
2007-08-26 16:07:18 +02:00
sys . exit ( 1 )
change = int ( info [ " change " ] )
if change > newestRevision :
newestRevision = change
2011-02-19 14:17:57 +01:00
if info [ " action " ] in self . delete_actions :
2007-08-26 16:07:18 +02:00
# don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
#fileCnt = fileCnt + 1
continue
for prop in [ " depotFile " , " rev " , " action " , " type " ] :
details [ " %s %s " % ( prop , fileCnt ) ] = info [ prop ]
fileCnt = fileCnt + 1
details [ " change " ] = newestRevision
2011-07-31 15:45:55 +02:00
2012-04-09 02:18:01 +02:00
# Use time from top-most change so that all git p4 clones of
2011-07-31 15:45:55 +02:00
# the same p4 repo have the same commit SHA1s.
res = p4CmdList ( " describe -s %d " % newestRevision )
newestTime = None
for r in res :
if r . has_key ( ' time ' ) :
newestTime = int ( r [ ' time ' ] )
if newestTime is None :
die ( " \" describe -s \" on newest change %d did not give a time " )
details [ " time " ] = newestTime
2007-08-26 16:07:18 +02:00
self . updateOptionDict ( details )
try :
2012-08-11 18:55:02 +02:00
self . commit ( details , self . extractFilesFromCommit ( details ) , self . branch )
2007-08-26 16:07:18 +02:00
except IOError :
print " IO error with git fast-import. Is your git version recent enough? "
print self . gitError . read ( )
2007-03-20 20:54:23 +01:00
def run ( self , args ) :
2007-05-23 23:49:35 +02:00
self . depotPaths = [ ]
2007-03-22 22:17:42 +01:00
self . changeRange = " "
self . initialParent = " "
2007-05-23 23:49:35 +02:00
self . previousDepotPaths = [ ]
2007-05-23 21:46:29 +02:00
2007-05-19 10:23:12 +02:00
# map from branch depot path to parent branch
self . knownBranches = { }
self . initialParents = { }
2007-08-24 17:44:16 +02:00
self . hasOrigin = originP4BranchesExist ( )
2007-06-11 09:59:27 +02:00
if not self . syncWithOrigin :
self . hasOrigin = False
2007-05-19 10:23:12 +02:00
2007-05-23 00:03:08 +02:00
if self . importIntoRemotes :
self . refPrefix = " refs/remotes/p4/ "
else :
2007-06-07 15:13:59 +02:00
self . refPrefix = " refs/heads/p4/ "
2007-05-23 00:03:08 +02:00
2007-05-23 21:53:11 +02:00
if self . syncWithOrigin and self . hasOrigin :
if not self . silent :
print " Syncing with origin first by calling git fetch origin "
system ( " git fetch origin " )
2007-05-24 22:28:28 +02:00
2007-03-22 21:34:16 +01:00
if len ( self . branch ) == 0 :
2007-06-07 15:13:59 +02:00
self . branch = self . refPrefix + " master "
2007-05-23 00:03:08 +02:00
if gitBranchExists ( " refs/heads/p4 " ) and self . importIntoRemotes :
2007-05-17 21:18:53 +02:00
system ( " git update-ref %s refs/heads/p4 " % self . branch )
system ( " git branch -D p4 " ) ;
2007-05-21 10:05:30 +02:00
# create it /after/ importing, when master exists
2007-08-24 17:46:16 +02:00
if not gitBranchExists ( self . refPrefix + " HEAD " ) and self . importIntoRemotes and gitBranchExists ( self . branch ) :
2007-05-27 15:48:01 +02:00
system ( " git symbolic-ref %s HEAD %s " % ( self . refPrefix , self . branch ) )
2007-03-23 09:30:41 +01:00
2012-02-26 02:06:24 +01:00
# accept either the command-line option, or the configuration variable
if self . useClientSpec :
# will use this after clone to set the variable
self . useClientSpec_from_options = True
else :
2011-12-25 03:07:39 +01:00
if gitConfig ( " git-p4.useclientspec " , " --bool " ) == " true " :
self . useClientSpec = True
if self . useClientSpec :
2012-02-26 02:06:25 +01:00
self . clientSpecDirs = getClientSpec ( )
2008-02-18 15:22:08 +01:00
2007-05-23 23:49:35 +02:00
# TODO: should always look at previous commits,
# merge with previous imports, if possible.
if args == [ ] :
2007-05-25 11:36:42 +02:00
if self . hasOrigin :
2007-08-24 17:44:16 +02:00
createOrUpdateBranchesFromOrigin ( self . refPrefix , self . silent )
2007-05-24 22:25:36 +02:00
self . listExistingP4GitBranches ( )
if len ( self . p4BranchesInGit ) > 1 :
if not self . silent :
print " Importing from/into multiple branches "
self . detectBranches = True
2007-03-23 09:30:41 +01:00
2007-05-19 10:23:12 +02:00
if self . verbose :
print " branches: %s " % self . p4BranchesInGit
p4Change = 0
for branch in self . p4BranchesInGit :
2007-05-23 21:53:11 +02:00
logMsg = extractLogMessageFromGitCommit ( self . refPrefix + branch )
2007-05-23 23:49:35 +02:00
settings = extractSettingsGitLog ( logMsg )
2007-05-19 10:23:12 +02:00
2007-05-23 23:49:35 +02:00
self . readOptions ( settings )
if ( settings . has_key ( ' depot-paths ' )
and settings . has_key ( ' change ' ) ) :
change = int ( settings [ ' change ' ] ) + 1
2007-05-19 10:23:12 +02:00
p4Change = max ( p4Change , change )
2007-05-23 23:49:35 +02:00
depotPaths = sorted ( settings [ ' depot-paths ' ] )
if self . previousDepotPaths == [ ] :
2007-05-23 23:49:35 +02:00
self . previousDepotPaths = depotPaths
2007-05-19 10:23:12 +02:00
else :
2007-05-23 23:49:35 +02:00
paths = [ ]
for ( prev , cur ) in zip ( self . previousDepotPaths , depotPaths ) :
2011-08-19 01:44:03 +02:00
prev_list = prev . split ( " / " )
cur_list = cur . split ( " / " )
for i in range ( 0 , min ( len ( cur_list ) , len ( prev_list ) ) ) :
if cur_list [ i ] < > prev_list [ i ] :
2007-06-07 09:37:13 +02:00
i = i - 1
2007-05-23 23:49:35 +02:00
break
2011-08-19 01:44:03 +02:00
paths . append ( " / " . join ( cur_list [ : i + 1 ] ) )
2007-05-23 23:49:35 +02:00
self . previousDepotPaths = paths
2007-05-19 10:23:12 +02:00
if p4Change > 0 :
2007-05-23 23:49:35 +02:00
self . depotPaths = sorted ( self . previousDepotPaths )
2007-05-19 11:07:32 +02:00
self . changeRange = " @ %s ,#head " % p4Change
2007-06-07 09:39:51 +02:00
if not self . detectBranches :
self . initialParent = parseRevision ( self . branch )
2007-05-21 00:39:16 +02:00
if not self . silent and not self . detectBranches :
2007-03-23 09:30:41 +01:00
print " Performing incremental import into %s git branch " % self . branch
2007-03-22 21:34:16 +01:00
2007-05-17 09:02:45 +02:00
if not self . branch . startswith ( " refs/ " ) :
self . branch = " refs/heads/ " + self . branch
2007-03-22 22:17:42 +01:00
2007-05-23 23:49:35 +02:00
if len ( args ) == 0 and self . depotPaths :
2007-03-20 20:54:23 +01:00
if not self . silent :
2007-05-23 23:49:35 +02:00
print " Depot paths: %s " % ' ' . join ( self . depotPaths )
2007-03-20 20:54:23 +01:00
else :
2007-05-23 23:49:35 +02:00
if self . depotPaths and self . depotPaths != args :
2007-05-23 21:53:11 +02:00
print ( " previous import used depot path %s and now %s was specified. "
2007-05-23 23:49:35 +02:00
" This doesn ' t work! " % ( ' ' . join ( self . depotPaths ) ,
' ' . join ( args ) ) )
2007-03-20 20:54:23 +01:00
sys . exit ( 1 )
2007-05-23 23:49:35 +02:00
2007-05-23 23:49:35 +02:00
self . depotPaths = sorted ( args )
2007-03-20 20:54:23 +01:00
2007-08-26 16:04:34 +02:00
revision = " "
2007-03-20 20:54:23 +01:00
self . users = { }
2011-12-25 03:07:35 +01:00
# Make sure no revision specifiers are used when --changesfile
# is specified.
bad_changesfile = False
if len ( self . changesFile ) > 0 :
for p in self . depotPaths :
if p . find ( " @ " ) > = 0 or p . find ( " # " ) > = 0 :
bad_changesfile = True
break
if bad_changesfile :
die ( " Option --changesfile is incompatible with revision specifiers " )
2007-05-23 23:49:35 +02:00
newPaths = [ ]
for p in self . depotPaths :
if p . find ( " @ " ) != - 1 :
atIdx = p . index ( " @ " )
self . changeRange = p [ atIdx : ]
if self . changeRange == " @all " :
self . changeRange = " "
2007-05-23 23:49:35 +02:00
elif ' , ' not in self . changeRange :
2007-08-26 16:04:34 +02:00
revision = self . changeRange
2007-05-23 23:49:35 +02:00
self . changeRange = " "
2007-07-24 00:56:37 +02:00
p = p [ : atIdx ]
2007-05-23 23:49:35 +02:00
elif p . find ( " # " ) != - 1 :
hashIdx = p . index ( " # " )
2007-08-26 16:04:34 +02:00
revision = p [ hashIdx : ]
2007-07-24 00:56:37 +02:00
p = p [ : hashIdx ]
2007-05-23 23:49:35 +02:00
elif self . previousDepotPaths == [ ] :
2011-12-25 03:07:35 +01:00
# pay attention to changesfile, if given, else import
# the entire p4 tree at the head revision
if len ( self . changesFile ) == 0 :
revision = " #head "
2007-05-23 23:49:35 +02:00
p = re . sub ( " \ . \ . \ .$ " , " " , p )
if not p . endswith ( " / " ) :
p + = " / "
newPaths . append ( p )
self . depotPaths = newPaths
2012-08-11 18:55:02 +02:00
# --detect-branches may change this for each branch
self . branchPrefixes = self . depotPaths
2007-05-20 10:55:54 +02:00
self . loadUserMapFromCache ( )
2007-04-08 00:12:02 +02:00
self . labels = { }
if self . detectLabels :
self . getLabels ( ) ;
2007-03-20 20:54:23 +01:00
2007-05-18 21:45:23 +02:00
if self . detectBranches :
2007-06-08 08:49:22 +02:00
## FIXME - what's a P4 projectName ?
self . projectName = self . guessProjectName ( )
2007-11-15 10:38:45 +01:00
if self . hasOrigin :
self . getBranchMappingFromGitBranches ( )
else :
self . getBranchMapping ( )
2007-05-19 10:23:12 +02:00
if self . verbose :
print " p4-git branches: %s " % self . p4BranchesInGit
print " initial parents: %s " % self . initialParents
for b in self . p4BranchesInGit :
if b != " master " :
2007-05-23 23:49:35 +02:00
## FIXME
2007-05-19 10:23:12 +02:00
b = b [ len ( self . projectName ) : ]
self . createdBranches . add ( b )
2007-05-18 21:45:23 +02:00
2007-04-14 11:21:50 +02:00
self . tz = " %+03d %02d " % ( - time . timezone / 3600 , ( ( - time . timezone % 3600 ) / 60 ) )
2007-03-20 20:54:23 +01:00
2007-05-23 21:53:11 +02:00
importProcess = subprocess . Popen ( [ " git " , " fast-import " ] ,
2007-05-23 23:49:35 +02:00
stdin = subprocess . PIPE , stdout = subprocess . PIPE ,
stderr = subprocess . PIPE ) ;
2007-05-15 14:31:06 +02:00
self . gitOutput = importProcess . stdout
self . gitStream = importProcess . stdin
self . gitError = importProcess . stderr
2007-03-20 20:54:23 +01:00
2007-08-26 16:04:34 +02:00
if revision :
2007-08-26 16:07:18 +02:00
self . importHeadRevision ( revision )
2007-03-20 20:54:23 +01:00
else :
changes = [ ]
2007-03-20 20:59:30 +01:00
if len ( self . changesFile ) > 0 :
2007-03-20 20:54:23 +01:00
output = open ( self . changesFile ) . readlines ( )
2009-09-10 09:02:38 +02:00
changeSet = set ( )
2007-03-20 20:54:23 +01:00
for line in output :
changeSet . add ( int ( line ) )
for change in changeSet :
changes . append ( change )
changes . sort ( )
else :
2012-04-09 02:18:01 +02:00
# catch "git p4 sync" with no new branches, in a repo that
# does not have any existing p4 branches
2011-03-16 21:52:46 +01:00
if len ( args ) == 0 and not self . p4BranchesInGit :
2011-02-19 14:17:59 +01:00
die ( " No remote p4 branches. Perhaps you never did \" git p4 clone \" in here. " ) ;
2007-05-19 10:23:12 +02:00
if self . verbose :
2007-05-23 23:49:35 +02:00
print " Getting p4 changes for %s ... %s " % ( ' , ' . join ( self . depotPaths ) ,
2007-05-23 23:49:35 +02:00
self . changeRange )
2007-08-26 15:56:36 +02:00
changes = p4ChangesForPaths ( self . depotPaths , self . changeRange )
2007-03-20 20:54:23 +01:00
2007-05-23 00:07:35 +02:00
if len ( self . maxChanges ) > 0 :
2007-07-24 00:56:37 +02:00
changes = changes [ : min ( int ( self . maxChanges ) , len ( changes ) ) ]
2007-05-23 00:07:35 +02:00
2007-03-20 20:54:23 +01:00
if len ( changes ) == 0 :
2007-03-20 20:59:30 +01:00
if not self . silent :
2007-05-21 00:39:16 +02:00
print " No changes to import! "
2012-04-11 17:21:24 +02:00
else :
if not self . silent and not self . detectBranches :
print " Import destination: %s " % self . branch
self . updatedBranches = set ( )
2007-03-20 20:54:23 +01:00
2012-04-11 17:21:24 +02:00
self . importChanges ( changes )
2007-06-11 23:28:03 +02:00
2012-04-11 17:21:24 +02:00
if not self . silent :
print " "
if len ( self . updatedBranches ) > 0 :
sys . stdout . write ( " Updated branches: " )
for b in self . updatedBranches :
sys . stdout . write ( " %s " % b )
sys . stdout . write ( " \n " )
2007-05-21 00:39:16 +02:00
2012-04-11 17:21:24 +02:00
if gitConfig ( " git-p4.importLabels " , " --bool " ) == " true " :
2012-05-11 08:25:18 +02:00
self . importLabels = True
2007-03-20 20:54:23 +01:00
2012-04-11 17:21:24 +02:00
if self . importLabels :
p4Labels = getP4Labels ( self . depotPaths )
gitTags = getGitTags ( )
missingP4Labels = p4Labels - gitTags
self . importP4Labels ( self . gitStream , missingP4Labels )
2007-03-20 20:54:23 +01:00
self . gitStream . close ( )
2007-05-19 10:23:12 +02:00
if importProcess . wait ( ) != 0 :
die ( " fast-import failed: %s " % self . gitError . read ( ) )
2007-03-20 20:54:23 +01:00
self . gitOutput . close ( )
self . gitError . close ( )
2012-01-26 00:48:22 +01:00
# Cleanup temporary branches created during import
if self . tempBranches != [ ] :
for branch in self . tempBranches :
read_pipe ( " git update-ref -d %s " % branch )
os . rmdir ( os . path . join ( os . environ . get ( " GIT_DIR " , " .git " ) , self . tempBranchLocation ) )
2007-03-20 20:54:23 +01:00
return True
2007-04-07 23:46:50 +02:00
class P4Rebase ( Command ) :
def __init__ ( self ) :
Command . __init__ ( self )
2012-04-11 17:21:24 +02:00
self . options = [
optparse . make_option ( " --import-labels " , dest = " importLabels " , action = " store_true " ) ,
]
self . importLabels = False
2007-05-23 21:53:11 +02:00
self . description = ( " Fetches the latest revision from perforce and "
+ " rebases the current work (branch) against it " )
2007-04-07 23:46:50 +02:00
def run ( self , args ) :
sync = P4Sync ( )
2012-04-11 17:21:24 +02:00
sync . importLabels = self . importLabels
2007-04-07 23:46:50 +02:00
sync . run ( [ ] )
2007-06-12 14:34:46 +02:00
2007-08-22 09:07:15 +02:00
return self . rebase ( )
def rebase ( self ) :
2008-01-07 14:21:45 +01:00
if os . system ( " git update-index --refresh " ) != 0 :
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. " ) ;
if len ( read_pipe ( " git diff-index HEAD -- " ) ) > 0 :
die ( " You have uncommited changes. Please commit them before rebasing or stash them away with git stash. " ) ;
2007-06-12 14:34:46 +02:00
[ upstream , settings ] = findUpstreamBranchPoint ( )
if len ( upstream ) == 0 :
die ( " Cannot find upstream branchpoint for rebase " )
# the branchpoint may be p4/foo~3, so strip off the parent
upstream = re . sub ( " ~[0-9]+$ " , " " , upstream )
print " Rebasing the current branch onto %s " % upstream
2007-05-23 23:49:35 +02:00
oldHead = read_pipe ( " git rev-parse HEAD " ) . strip ( )
2007-06-12 14:34:46 +02:00
system ( " git rebase %s " % upstream )
2007-04-08 00:07:02 +02:00
system ( " git diff-tree --stat --summary -M %s HEAD " % oldHead )
2007-04-07 23:46:50 +02:00
return True
2007-04-08 10:08:26 +02:00
class P4Clone ( P4Sync ) :
def __init__ ( self ) :
P4Sync . __init__ ( self )
self . description = " Creates a new git repository and imports from Perforce into it "
2007-05-23 23:49:35 +02:00
self . usage = " usage: % prog [options] //depot/path[@revRange] "
2008-02-03 19:38:51 +01:00
self . options + = [
2007-05-23 23:49:35 +02:00
optparse . make_option ( " --destination " , dest = " cloneDestination " ,
action = ' store ' , default = None ,
2008-02-03 19:38:51 +01:00
help = " where to leave result of the clone " ) ,
optparse . make_option ( " -/ " , dest = " cloneExclude " ,
action = " append " , type = " string " ,
2011-02-19 14:18:01 +01:00
help = " exclude depot path " ) ,
optparse . make_option ( " --bare " , dest = " cloneBare " ,
action = " store_true " , default = False ) ,
2008-02-03 19:38:51 +01:00
]
2007-05-23 23:49:35 +02:00
self . cloneDestination = None
2007-04-08 10:08:26 +02:00
self . needsGit = False
2011-02-19 14:18:01 +01:00
self . cloneBare = False
2007-04-08 10:08:26 +02:00
2008-02-03 19:38:51 +01:00
# This is required for the "append" cloneExclude action
def ensure_value ( self , attr , value ) :
if not hasattr ( self , attr ) or getattr ( self , attr ) is None :
setattr ( self , attr , value )
return getattr ( self , attr )
2007-05-23 23:49:35 +02:00
def defaultDestination ( self , args ) :
## TODO: use common prefix of args?
depotPath = args [ 0 ]
depotDir = re . sub ( " (@[^@]*)$ " , " " , depotPath )
depotDir = re . sub ( " (#[^#]*)$ " , " " , depotDir )
2008-02-04 21:41:43 +01:00
depotDir = re . sub ( r " \ . \ . \ .$ " , " " , depotDir )
2007-05-23 23:49:35 +02:00
depotDir = re . sub ( r " /$ " , " " , depotDir )
return os . path . split ( depotDir ) [ 1 ]
2007-04-08 10:08:26 +02:00
def run ( self , args ) :
if len ( args ) < 1 :
return False
2007-05-23 23:49:35 +02:00
if self . keepRepoPath and not self . cloneDestination :
sys . stderr . write ( " Must specify destination for --keep-path \n " )
sys . exit ( 1 )
2007-04-08 10:08:26 +02:00
2007-05-23 23:49:35 +02:00
depotPaths = args
2007-06-07 21:12:25 +02:00
if not self . cloneDestination and len ( depotPaths ) > 1 :
self . cloneDestination = depotPaths [ - 1 ]
depotPaths = depotPaths [ : - 1 ]
2008-02-03 19:38:51 +01:00
self . cloneExclude = [ " / " + p for p in self . cloneExclude ]
2007-05-23 23:49:35 +02:00
for p in depotPaths :
if not p . startswith ( " // " ) :
return False
2007-04-08 10:08:26 +02:00
2007-05-23 23:49:35 +02:00
if not self . cloneDestination :
2007-06-07 15:08:33 +02:00
self . cloneDestination = self . defaultDestination ( args )
2007-04-08 10:08:26 +02:00
2007-05-23 23:49:35 +02:00
print " Importing from %s into %s " % ( ' , ' . join ( depotPaths ) , self . cloneDestination )
2011-02-19 14:18:01 +01:00
2007-06-11 22:48:07 +02:00
if not os . path . exists ( self . cloneDestination ) :
os . makedirs ( self . cloneDestination )
2008-08-01 21:50:03 +02:00
chdir ( self . cloneDestination )
2011-02-19 14:18:01 +01:00
init_cmd = [ " git " , " init " ]
if self . cloneBare :
init_cmd . append ( " --bare " )
subprocess . check_call ( init_cmd )
2007-05-23 23:49:35 +02:00
if not P4Sync . run ( self , depotPaths ) :
2007-04-08 10:08:26 +02:00
return False
if self . branch != " master " :
2008-08-28 00:36:12 +02:00
if self . importIntoRemotes :
masterbranch = " refs/remotes/p4/master "
else :
masterbranch = " refs/heads/p4/master "
if gitBranchExists ( masterbranch ) :
system ( " git branch master %s " % masterbranch )
2011-02-19 14:18:01 +01:00
if not self . cloneBare :
system ( " git checkout -f " )
2007-05-18 22:13:26 +02:00
else :
print " Could not detect main branch. No checkout/master branch created. "
2007-05-23 23:49:35 +02:00
2012-02-26 02:06:24 +01:00
# auto-set this variable if invoked with --use-client-spec
if self . useClientSpec_from_options :
system ( " git config --bool git-p4.useclientspec true " )
2007-04-08 10:08:26 +02:00
return True
2007-06-20 23:10:28 +02:00
class P4Branches ( Command ) :
def __init__ ( self ) :
Command . __init__ ( self )
self . options = [ ]
self . description = ( " Shows the git branches that hold imports and their "
+ " corresponding perforce depot paths " )
self . verbose = False
def run ( self , args ) :
2007-08-24 17:44:16 +02:00
if originP4BranchesExist ( ) :
createOrUpdateBranchesFromOrigin ( )
2007-06-20 23:10:28 +02:00
cmdline = " git rev-parse --symbolic "
cmdline + = " --remotes "
for line in read_pipe_lines ( cmdline ) :
line = line . strip ( )
if not line . startswith ( ' p4/ ' ) or line == " p4/HEAD " :
continue
branch = line
log = extractLogMessageFromGitCommit ( " refs/remotes/ %s " % branch )
settings = extractSettingsGitLog ( log )
print " %s <= %s ( %s ) " % ( branch , " , " . join ( settings [ " depot-paths " ] ) , settings [ " change " ] )
return True
2007-03-20 20:54:23 +01:00
class HelpFormatter ( optparse . IndentedHelpFormatter ) :
def __init__ ( self ) :
optparse . IndentedHelpFormatter . __init__ ( self )
def format_description ( self , description ) :
if description :
return description + " \n "
else :
return " "
2007-03-19 22:25:17 +01:00
2007-03-19 20:59:12 +01:00
def printUsage ( commands ) :
print " usage: %s <command> [options] " % sys . argv [ 0 ]
print " "
print " valid commands: %s " % " , " . join ( commands )
print " "
print " Try %s <command> --help for command specific help. " % sys . argv [ 0 ]
print " "
commands = {
2007-05-23 23:49:35 +02:00
" debug " : P4Debug ,
" submit " : P4Submit ,
2007-10-09 16:16:09 +02:00
" commit " : P4Submit ,
2007-05-23 23:49:35 +02:00
" sync " : P4Sync ,
" rebase " : P4Rebase ,
" clone " : P4Clone ,
2007-06-20 23:10:28 +02:00
" rollback " : P4RollBack ,
" branches " : P4Branches
2007-03-19 20:59:12 +01:00
}
2007-05-23 23:49:35 +02:00
def main ( ) :
if len ( sys . argv [ 1 : ] ) == 0 :
printUsage ( commands . keys ( ) )
sys . exit ( 2 )
2007-03-19 22:25:17 +01:00
2007-05-23 23:49:35 +02:00
cmd = " "
cmdName = sys . argv [ 1 ]
try :
2007-05-23 23:49:35 +02:00
klass = commands [ cmdName ]
cmd = klass ( )
2007-05-23 23:49:35 +02:00
except KeyError :
print " unknown command %s " % cmdName
print " "
printUsage ( commands . keys ( ) )
sys . exit ( 2 )
options = cmd . options
2007-05-23 23:49:35 +02:00
cmd . gitdir = os . environ . get ( " GIT_DIR " , None )
2007-05-23 23:49:35 +02:00
args = sys . argv [ 2 : ]
2012-04-24 10:33:23 +02:00
options . append ( optparse . make_option ( " --verbose " , dest = " verbose " , action = " store_true " ) )
if cmd . needsGit :
options . append ( optparse . make_option ( " --git-dir " , dest = " gitdir " ) )
2007-05-23 23:49:35 +02:00
2012-04-24 10:33:23 +02:00
parser = optparse . OptionParser ( cmd . usage . replace ( " % prog " , " % prog " + cmdName ) ,
options ,
description = cmd . description ,
formatter = HelpFormatter ( ) )
2007-05-23 23:49:35 +02:00
2012-04-24 10:33:23 +02:00
( cmd , args ) = parser . parse_args ( sys . argv [ 2 : ] , cmd ) ;
2007-05-23 23:49:35 +02:00
global verbose
verbose = cmd . verbose
if cmd . needsGit :
2007-05-23 23:49:35 +02:00
if cmd . gitdir == None :
cmd . gitdir = os . path . abspath ( " .git " )
if not isValidGitDir ( cmd . gitdir ) :
cmd . gitdir = read_pipe ( " git rev-parse --git-dir " ) . strip ( )
if os . path . exists ( cmd . gitdir ) :
2007-05-23 23:49:35 +02:00
cdup = read_pipe ( " git rev-parse --show-cdup " ) . strip ( )
if len ( cdup ) > 0 :
2008-08-01 21:50:03 +02:00
chdir ( cdup ) ;
2007-03-26 00:13:51 +02:00
2007-05-23 23:49:35 +02:00
if not isValidGitDir ( cmd . gitdir ) :
if isValidGitDir ( cmd . gitdir + " /.git " ) :
cmd . gitdir + = " /.git "
2007-05-23 23:49:35 +02:00
else :
2007-05-23 23:49:35 +02:00
die ( " fatal: cannot locate git repository at %s " % cmd . gitdir )
2007-03-26 00:13:51 +02:00
2007-05-23 23:49:35 +02:00
os . environ [ " GIT_DIR " ] = cmd . gitdir
2007-03-19 20:59:12 +01:00
2007-05-23 23:49:35 +02:00
if not cmd . run ( args ) :
parser . print_help ( )
2011-12-25 03:07:39 +01:00
sys . exit ( 2 )
2007-03-19 22:25:17 +01:00
2007-05-23 23:49:35 +02:00
if __name__ == ' __main__ ' :
main ( )