]>
git.ipfire.org Git - thirdparty/git.git/blob - contrib/fast-import/git-p4
3 # git-p4.py -- A tool for bidirectional operation between a Perforce depot and git.
5 # Author: Simon Hausmann <hausmann@kde.org>
6 # Copyright: 2007 Simon Hausmann <hausmann@kde.org>
8 # License: MIT <http://www.opensource.org/licenses/mit-license.php>
10 # TODO: * Consider making --with-origin the default, assuming that the git
11 # protocol is always more efficient. (needs manual testing first :)
14 import optparse
, sys
, os
, marshal
, popen2
, subprocess
, shelve
15 import tempfile
, getopt
, sha
, os
. path
, time
, platform
18 gitdir
= os
. environ
. get ( "GIT_DIR" , "" )
21 return os
. popen ( command
, "rb" );
24 cmd
= "p4 -G %s " % cmd
25 pipe
= os
. popen ( cmd
, "rb" )
30 entry
= marshal
. load ( pipe
)
34 exitCode
= pipe
. close ()
37 entry
[ "p4ExitCode" ] = exitCode
49 def p4Where ( depotPath
):
50 if not depotPath
. endswith ( "/" ):
52 output
= p4Cmd ( "where %s ..." % depotPath
)
53 if output
[ "code" ] == "error" :
57 clientPath
= output
. get ( "path" )
58 elif "data" in output
:
59 data
= output
. get ( "data" )
60 lastSpace
= data
. rfind ( " " )
61 clientPath
= data
[ lastSpace
+ 1 :]
63 if clientPath
. endswith ( "..." ):
64 clientPath
= clientPath
[:- 3 ]
68 sys
. stderr
. write ( msg
+ " \n " )
71 def currentGitBranch ():
72 return mypopen ( "git name-rev HEAD" ). read (). split ( " " )[ 1 ][:- 1 ]
74 def isValidGitDir ( path
):
75 if os
. path
. exists ( path
+ "/HEAD" ) and os
. path
. exists ( path
+ "/refs" ) and os
. path
. exists ( path
+ "/objects" ):
79 def parseRevision ( ref
):
80 return mypopen ( "git rev-parse %s " % ref
). read ()[:- 1 ]
83 if os
. system ( cmd
) != 0 :
84 die ( "command failed: %s " % cmd
)
86 def extractLogMessageFromGitCommit ( commit
):
89 for log
in mypopen ( "git cat-file commit %s " % commit
). readlines ():
98 def extractDepotPathAndChangeFromGitLog ( log
):
100 for line
in log
. split ( " \n " ):
102 if line
. startswith ( "[git-p4:" ) and line
. endswith ( "]" ):
103 line
= line
[ 8 :- 1 ]. strip ()
104 for assignment
in line
. split ( ":" ):
105 variable
= assignment
. strip ()
107 equalPos
= assignment
. find ( "=" )
109 variable
= assignment
[: equalPos
]. strip ()
110 value
= assignment
[ equalPos
+ 1 :]. strip ()
111 if value
. startswith ( " \" " ) and value
. endswith ( " \" " ):
113 values
[ variable
] = value
115 return values
. get ( "depot-path" ), values
. get ( "change" )
117 def gitBranchExists ( branch
):
118 proc
= subprocess
. Popen ([ "git" , "rev-parse" , branch
], stderr
= subprocess
. PIPE
, stdout
= subprocess
. PIPE
);
119 return proc
. wait () == 0 ;
123 self
. usage
= "usage: %prog [options]"
126 class P4Debug ( Command
):
128 Command
.__ init
__ ( self
)
131 self
. description
= "A tool to debug the output of p4 -G."
132 self
. needsGit
= False
135 for output
in p4CmdList ( " " . join ( args
)):
139 class P4RollBack ( Command
):
141 Command
.__ init
__ ( self
)
143 optparse
. make_option ( "--verbose" , dest
= "verbose" , action
= "store_true" ),
144 optparse
. make_option ( "--local" , dest
= "rollbackLocalBranches" , action
= "store_true" )
146 self
. description
= "A tool to debug the multi-branch import. Don't use :)"
148 self
. rollbackLocalBranches
= False
153 maxChange
= int ( args
[ 0 ])
155 if "p4ExitCode" in p4Cmd ( "p4 changes -m 1" ):
156 die ( "Problems executing p4" );
158 if self
. rollbackLocalBranches
:
159 refPrefix
= "refs/heads/"
160 lines
= mypopen ( "git rev-parse --symbolic --branches" ). readlines ()
162 refPrefix
= "refs/remotes/"
163 lines
= mypopen ( "git rev-parse --symbolic --remotes" ). readlines ()
166 if self
. rollbackLocalBranches
or ( line
. startswith ( "p4/" ) and line
!= "p4/HEAD \n " ):
167 ref
= refPrefix
+ line
[:- 1 ]
168 log
= extractLogMessageFromGitCommit ( ref
)
169 depotPath
, change
= extractDepotPathAndChangeFromGitLog ( log
)
172 if len ( p4Cmd ( "changes -m 1 %s ...@ %s " % ( depotPath
, maxChange
))) == 0 :
173 print "Branch %s did not exist at change %s , deleting." % ( ref
, maxChange
)
174 system ( "git update-ref -d %s `git rev-parse %s `" % ( ref
, ref
))
177 while len ( change
) > 0 and int ( change
) > maxChange
:
180 print " %s is at %s ; rewinding towards %s " % ( ref
, change
, maxChange
)
181 system ( "git update-ref %s \" %s ^ \" " % ( ref
, ref
))
182 log
= extractLogMessageFromGitCommit ( ref
)
183 depotPath
, change
= extractDepotPathAndChangeFromGitLog ( log
)
186 print " %s rewound to %s " % ( ref
, change
)
190 class P4Submit ( Command
):
192 Command
.__ init
__ ( self
)
194 optparse
. make_option ( "--continue" , action
= "store_false" , dest
= "firstTime" ),
195 optparse
. make_option ( "--origin" , dest
= "origin" ),
196 optparse
. make_option ( "--reset" , action
= "store_true" , dest
= "reset" ),
197 optparse
. make_option ( "--log-substitutions" , dest
= "substFile" ),
198 optparse
. make_option ( "--noninteractive" , action
= "store_false" ),
199 optparse
. make_option ( "--dry-run" , action
= "store_true" ),
200 optparse
. make_option ( "--direct" , dest
= "directSubmit" , action
= "store_true" ),
202 self
. description
= "Submit changes from git to the perforce depot."
203 self
. usage
+= " [name of git branch to submit into perforce depot]"
204 self
. firstTime
= True
206 self
. interactive
= True
209 self
. firstTime
= True
211 self
. directSubmit
= False
213 self
. logSubstitutions
= {}
214 self
. logSubstitutions
[ "<enter description here>" ] = "%log%"
215 self
. logSubstitutions
[ " \t Details:" ] = " \t Details: %log%"
218 if len ( p4CmdList ( "opened ..." )) > 0 :
219 die ( "You have files opened with perforce! Close them before starting the sync." )
222 if len ( self
. config
) > 0 and not self
. reset
:
223 die ( "Cannot start sync. Previous sync config found at %s \n If you want to start submitting again from scratch maybe you want to call git-p4 submit --reset" % self
. configFile
)
226 if self
. directSubmit
:
229 for line
in mypopen ( "git rev-list --no-merges %s .. %s " % ( self
. origin
, self
. master
)). readlines ():
230 commits
. append ( line
[:- 1 ])
233 self
. config
[ "commits" ] = commits
235 def prepareLogMessage ( self
, template
, message
):
238 for line
in template
. split ( " \n " ):
239 if line
. startswith ( "#" ):
240 result
+= line
+ " \n "
244 for key
in self
. logSubstitutions
. keys ():
245 if line
. find ( key
) != - 1 :
246 value
= self
. logSubstitutions
[ key
]
247 value
= value
. replace ( "%log%" , message
)
248 if value
!= "@remove@" :
249 result
+= line
. replace ( key
, value
) + " \n "
254 result
+= line
+ " \n "
259 if self
. directSubmit
:
260 print "Applying local change in working directory/index"
261 diff
= self
. diffStatus
263 print "Applying %s " % ( mypopen ( "git log --max-count=1 --pretty=oneline %s " % id ). read ())
264 diff
= mypopen ( "git diff-tree -r --name-status \" %s ^ \" \" %s \" " % ( id , id )). readlines ()
266 filesToDelete
= set ()
270 path
= line
[ 1 :]. strip ()
272 system ( "p4 edit \" %s \" " % path
)
273 editedFiles
. add ( path
)
274 elif modifier
== "A" :
276 if path
in filesToDelete
:
277 filesToDelete
. remove ( path
)
278 elif modifier
== "D" :
279 filesToDelete
. add ( path
)
280 if path
in filesToAdd
:
281 filesToAdd
. remove ( path
)
283 die ( "unknown modifier %s for %s " % ( modifier
, path
))
285 if self
. directSubmit
:
286 diffcmd
= "cat \" %s \" " % self
. diffFile
288 diffcmd
= "git format-patch -k --stdout \" %s ^ \" .. \" %s \" " % ( id , id )
289 patchcmd
= diffcmd
+ " | git apply "
290 tryPatchCmd
= patchcmd
+ "--check -"
291 applyPatchCmd
= patchcmd
+ "--check --apply -"
293 if os
. system ( tryPatchCmd
) != 0 :
294 print "Unfortunately applying the change failed!"
295 print "What do you want to do?"
297 while response
!= "s" and response
!= "a" and response
!= "w" :
298 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) " )
300 print "Skipping! Good luck with the next patches..."
302 elif response
== "a" :
303 os
. system ( applyPatchCmd
)
304 if len ( filesToAdd
) > 0 :
305 print "You may also want to call p4 add on the following files:"
306 print " " . join ( filesToAdd
)
307 if len ( filesToDelete
):
308 print "The following files should be scheduled for deletion with p4 delete:"
309 print " " . join ( filesToDelete
)
310 die ( "Please resolve and submit the conflict manually and continue afterwards with git-p4 submit --continue" )
311 elif response
== "w" :
312 system ( diffcmd
+ " > patch.txt" )
313 print "Patch saved to patch.txt in %s !" % self
. clientPath
314 die ( "Please resolve and submit the conflict manually and continue afterwards with git-p4 submit --continue" )
316 system ( applyPatchCmd
)
319 system ( "p4 add %s " % f
)
320 for f
in filesToDelete
:
321 system ( "p4 revert %s " % f
)
322 system ( "p4 delete %s " % f
)
325 if not self
. directSubmit
:
326 logMessage
= extractLogMessageFromGitCommit ( id )
327 logMessage
= logMessage
. replace ( " \n " , " \n\t " )
328 logMessage
= logMessage
[:- 1 ]
330 template
= mypopen ( "p4 change -o" ). read ()
333 submitTemplate
= self
. prepareLogMessage ( template
, logMessage
)
334 diff
= mypopen ( "p4 diff -du ..." ). read ()
336 for newFile
in filesToAdd
:
337 diff
+= "==== new file ==== \n "
338 diff
+= "--- /dev/null \n "
339 diff
+= "+++ %s \n " % newFile
340 f
= open ( newFile
, "r" )
341 for line
in f
. readlines ():
345 separatorLine
= "######## everything below this line is just the diff #######"
346 if platform
. system () == "Windows" :
347 separatorLine
+= " \r "
348 separatorLine
+= " \n "
351 firstIteration
= True
352 while response
== "e" :
353 if not firstIteration
:
354 response
= raw_input ( "Do you want to submit this change? [y]es/[e]dit/[n]o/[s]kip " )
355 firstIteration
= False
357 [ handle
, fileName
] = tempfile
. mkstemp ()
358 tmpFile
= os
. fdopen ( handle
, "w+" )
359 tmpFile
. write ( submitTemplate
+ separatorLine
+ diff
)
362 if platform
. system () == "Windows" :
363 defaultEditor
= "notepad"
364 editor
= os
. environ
. get ( "EDITOR" , defaultEditor
);
365 system ( editor
+ " " + fileName
)
366 tmpFile
= open ( fileName
, "rb" )
367 message
= tmpFile
. read ()
370 submitTemplate
= message
[: message
. index ( separatorLine
)]
372 if response
== "y" or response
== "yes" :
375 raw_input ( "Press return to continue..." )
377 if self
. directSubmit
:
378 print "Submitting to git first"
379 os
. chdir ( self
. oldWorkingDirectory
)
380 pipe
= os
. popen ( "git commit -a -F -" , "wb" )
381 pipe
. write ( submitTemplate
)
383 os
. chdir ( self
. clientPath
)
385 pipe
= os
. popen ( "p4 submit -i" , "wb" )
386 pipe
. write ( submitTemplate
)
388 elif response
== "s" :
389 for f
in editedFiles
:
390 system ( "p4 revert \" %s \" " % f
);
392 system ( "p4 revert \" %s \" " % f
);
394 for f
in filesToDelete
:
395 system ( "p4 delete \" %s \" " % f
);
398 print "Not submitting!"
399 self
. interactive
= False
401 fileName
= "submit.txt"
402 file = open ( fileName
, "w+" )
403 file . write ( self
. prepareLogMessage ( template
, logMessage
))
405 print "Perforce submit template written as %s . Please review/edit and then use p4 submit -i < %s to submit directly!" % ( fileName
, fileName
)
409 # make gitdir absolute so we can cd out into the perforce checkout
410 gitdir
= os
. path
. abspath ( gitdir
)
411 os
. environ
[ "GIT_DIR" ] = gitdir
414 self
. master
= currentGitBranch ()
415 if len ( self
. master
) == 0 or not os
. path
. exists ( " %s /refs/heads/ %s " % ( gitdir
, self
. master
)):
416 die ( "Detecting current git branch failed!" )
418 self
. master
= args
[ 0 ]
423 if gitBranchExists ( "p4" ):
424 [ depotPath
, dummy
] = extractDepotPathAndChangeFromGitLog ( extractLogMessageFromGitCommit ( "p4" ))
425 if len ( depotPath
) == 0 and gitBranchExists ( "origin" ):
426 [ depotPath
, dummy
] = extractDepotPathAndChangeFromGitLog ( extractLogMessageFromGitCommit ( "origin" ))
428 if len ( depotPath
) == 0 :
429 print "Internal error: cannot locate perforce depot path from existing branches"
432 self
. clientPath
= p4Where ( depotPath
)
434 if len ( self
. clientPath
) == 0 :
435 print "Error: Cannot locate perforce checkout of %s in client view" % depotPath
438 print "Perforce checkout for depot path %s located at %s " % ( depotPath
, self
. clientPath
)
439 self
. oldWorkingDirectory
= os
. getcwd ()
441 if self
. directSubmit
:
442 self
. diffStatus
= mypopen ( "git diff -r --name-status HEAD" ). readlines ()
443 if len ( self
. diffStatus
) == 0 :
444 print "No changes in working directory to submit."
446 patch
= mypopen ( "git diff -p --binary --diff-filter=ACMRTUXB HEAD" ). read ()
447 self
. diffFile
= gitdir
+ "/p4-git-diff"
448 f
= open ( self
. diffFile
, "wb" )
452 os
. chdir ( self
. clientPath
)
453 response
= raw_input ( "Do you want to sync %s with p4 sync? [y]es/[n]o " % self
. clientPath
)
454 if response
== "y" or response
== "yes" :
455 system ( "p4 sync ..." )
457 if len ( self
. origin
) == 0 :
458 if gitBranchExists ( "p4" ):
461 self
. origin
= "origin"
464 self
. firstTime
= True
466 if len ( self
. substFile
) > 0 :
467 for line
in open ( self
. substFile
, "r" ). readlines ():
468 tokens
= line
[:- 1 ]. split ( "=" )
469 self
. logSubstitutions
[ tokens
[ 0 ]] = tokens
[ 1 ]
472 self
. configFile
= gitdir
+ "/p4-git-sync.cfg"
473 self
. config
= shelve
. open ( self
. configFile
, writeback
= True )
478 commits
= self
. config
. get ( "commits" , [])
480 while len ( commits
) > 0 :
481 self
. firstTime
= False
483 commits
= commits
[ 1 :]
484 self
. config
[ "commits" ] = commits
486 if not self
. interactive
:
491 if self
. directSubmit
:
492 os
. remove ( self
. diffFile
)
494 if len ( commits
) == 0 :
496 print "No changes found to apply between %s and current HEAD" % self
. origin
498 print "All changes applied!"
499 os
. chdir ( self
. oldWorkingDirectory
)
500 response
= raw_input ( "Do you want to sync from Perforce now using git-p4 rebase? [y]es/[n]o " )
501 if response
== "y" or response
== "yes" :
504 os
. remove ( self
. configFile
)
508 class P4Sync ( Command
):
510 Command
.__ init
__ ( self
)
512 optparse
. make_option ( "--branch" , dest
= "branch" ),
513 optparse
. make_option ( "--detect-branches" , dest
= "detectBranches" , action
= "store_true" ),
514 optparse
. make_option ( "--changesfile" , dest
= "changesFile" ),
515 optparse
. make_option ( "--silent" , dest
= "silent" , action
= "store_true" ),
516 optparse
. make_option ( "--detect-labels" , dest
= "detectLabels" , action
= "store_true" ),
517 optparse
. make_option ( "--with-origin" , dest
= "syncWithOrigin" , action
= "store_true" ),
518 optparse
. make_option ( "--verbose" , dest
= "verbose" , action
= "store_true" ),
519 optparse
. make_option ( "--import-local" , dest
= "importIntoRemotes" , action
= "store_false" ),
520 optparse
. make_option ( "--max-changes" , dest
= "maxChanges" )
522 self
. description
= """Imports from Perforce into a git repository. \n
524 //depot/my/project/ -- to import the current head
525 //depot/my/project/@all -- to import everything
526 //depot/my/project/@1,6 -- to import only from revision 1 to 6
528 (a ... is not needed in the path p4 specification, it's added implicitly)"""
530 self
. usage
+= " //depot/path[@revRange]"
533 self
. createdBranches
= Set ()
534 self
. committedChanges
= Set ()
536 self
. detectBranches
= False
537 self
. detectLabels
= False
538 self
. changesFile
= ""
539 self
. syncWithOrigin
= False
541 self
. importIntoRemotes
= True
544 def p4File ( self
, depotPath
):
545 return os
. popen ( "p4 print -q \" %s \" " % depotPath
, "rb" ). read ()
547 def extractFilesFromCommit ( self
, commit
):
550 while commit
. has_key ( "depotFile %s " % fnum
):
551 path
= commit
[ "depotFile %s " % fnum
]
552 if not path
. startswith ( self
. depotPath
):
553 # if not self.silent:
554 # print "\nchanged files: ignoring path %s outside of %s in change %s" % (path, self.depotPath, change)
560 file [ "rev" ] = commit
[ "rev %s " % fnum
]
561 file [ "action" ] = commit
[ "action %s " % fnum
]
562 file [ "type" ] = commit
[ "type %s " % fnum
]
567 def splitFilesIntoBranches ( self
, commit
):
571 while commit
. has_key ( "depotFile %s " % fnum
):
572 path
= commit
[ "depotFile %s " % fnum
]
573 if not path
. startswith ( self
. depotPath
):
574 # if not self.silent:
575 # print "\nchanged files: ignoring path %s outside of %s in change %s" % (path, self.depotPath, change)
581 file [ "rev" ] = commit
[ "rev %s " % fnum
]
582 file [ "action" ] = commit
[ "action %s " % fnum
]
583 file [ "type" ] = commit
[ "type %s " % fnum
]
586 relPath
= path
[ len ( self
. depotPath
):]
588 for branch
in self
. knownBranches
. keys ():
589 if relPath
. startswith ( branch
+ "/" ): # add a trailing slash so that a commit into qt/4.2foo doesn't end up in qt/4.2
590 if branch
not in branches
:
591 branches
[ branch
] = []
592 branches
[ branch
]. append ( file )
596 def commit ( self
, details
, files
, branch
, branchPrefix
, parent
= "" ):
597 epoch
= details
[ "time" ]
598 author
= details
[ "user" ]
601 print "commit into %s " % branch
603 self
. gitStream
. write ( "commit %s \n " % branch
)
604 # gitStream.write("mark :%s\n" % details["change"])
605 self
. committedChanges
. add ( int ( details
[ "change" ]))
607 if author
not in self
. users
:
608 self
. getUserMapFromPerforceServer ()
609 if author
in self
. users
:
610 committer
= " %s %s %s " % ( self
. users
[ author
], epoch
, self
. tz
)
612 committer
= " %s <a@b> %s %s " % ( author
, epoch
, self
. tz
)
614 self
. gitStream
. write ( "committer %s \n " % committer
)
616 self
. gitStream
. write ( "data <<EOT \n " )
617 self
. gitStream
. write ( details
[ "desc" ])
618 self
. gitStream
. write ( " \n [git-p4: depot-path = \" %s \" : change = %s ] \n " % ( branchPrefix
, details
[ "change" ]))
619 self
. gitStream
. write ( "EOT \n\n " )
623 print "parent %s " % parent
624 self
. gitStream
. write ( "from %s \n " % parent
)
628 if not path
. startswith ( branchPrefix
):
630 # print "\nchanged files: ignoring path %s outside of branch prefix %s in change %s" % (path, branchPrefix, details["change"])
633 depotPath
= path
+ "#" + rev
634 relPath
= path
[ len ( branchPrefix
):]
635 action
= file [ "action" ]
637 if file [ "type" ] == "apple" :
638 print " \n file %s is a strange apple file that forks. Ignoring!" % path
641 if action
== "delete" :
642 self
. gitStream
. write ( "D %s \n " % relPath
)
645 if file [ "type" ]. startswith ( "x" ):
648 data
= self
. p4File ( depotPath
)
650 self
. gitStream
. write ( "M %s inline %s \n " % ( mode
, relPath
))
651 self
. gitStream
. write ( "data %s \n " % len ( data
))
652 self
. gitStream
. write ( data
)
653 self
. gitStream
. write ( " \n " )
655 self
. gitStream
. write ( " \n " )
657 change
= int ( details
[ "change" ])
659 if self
. labels
. has_key ( change
):
660 label
= self
. labels
[ change
]
661 labelDetails
= label
[ 0 ]
662 labelRevisions
= label
[ 1 ]
664 print "Change %s is labelled %s " % ( change
, labelDetails
)
666 files
= p4CmdList ( "files %s ...@ %s " % ( branchPrefix
, change
))
668 if len ( files
) == len ( labelRevisions
):
672 if info
[ "action" ] == "delete" :
674 cleanedFiles
[ info
[ "depotFile" ]] = info
[ "rev" ]
676 if cleanedFiles
== labelRevisions
:
677 self
. gitStream
. write ( "tag tag_ %s \n " % labelDetails
[ "label" ])
678 self
. gitStream
. write ( "from %s \n " % branch
)
680 owner
= labelDetails
[ "Owner" ]
682 if author
in self
. users
:
683 tagger
= " %s %s %s " % ( self
. users
[ owner
], epoch
, self
. tz
)
685 tagger
= " %s <a@b> %s %s " % ( owner
, epoch
, self
. tz
)
686 self
. gitStream
. write ( "tagger %s \n " % tagger
)
687 self
. gitStream
. write ( "data <<EOT \n " )
688 self
. gitStream
. write ( labelDetails
[ "Description" ])
689 self
. gitStream
. write ( "EOT \n\n " )
693 print "Tag %s does not match with change %s : files do not match." % ( labelDetails
[ "label" ], change
)
697 print "Tag %s does not match with change %s : file count is different." % ( labelDetails
[ "label" ], change
)
699 def getUserMapFromPerforceServer ( self
):
702 for output
in p4CmdList ( "users" ):
703 if not output
. has_key ( "User" ):
705 self
. users
[ output
[ "User" ]] = output
[ "FullName" ] + " <" + output
[ "Email" ] + ">"
707 cache
= open ( gitdir
+ "/p4-usercache.txt" , "wb" )
708 for user
in self
. users
. keys ():
709 cache
. write ( " %s \t %s \n " % ( user
, self
. users
[ user
]))
712 def loadUserMapFromCache ( self
):
715 cache
= open ( gitdir
+ "/p4-usercache.txt" , "rb" )
716 lines
= cache
. readlines ()
719 entry
= line
[:- 1 ]. split ( " \t " )
720 self
. users
[ entry
[ 0 ]] = entry
[ 1 ]
722 self
. getUserMapFromPerforceServer ()
727 l
= p4CmdList ( "labels %s ..." % self
. depotPath
)
728 if len ( l
) > 0 and not self
. silent
:
729 print "Finding files belonging to labels in %s " % self
. depotPath
732 label
= output
[ "label" ]
736 print "Querying files for label %s " % label
737 for file in p4CmdList ( "files %s ...@ %s " % ( self
. depotPath
, label
)):
738 revisions
[ file [ "depotFile" ]] = file [ "rev" ]
739 change
= int ( file [ "change" ])
740 if change
> newestChange
:
741 newestChange
= change
743 self
. labels
[ newestChange
] = [ output
, revisions
]
746 print "Label changes: %s " % self
. labels
. keys ()
748 def getBranchMapping ( self
):
749 self
. projectName
= self
. depotPath
[ self
. depotPath
[:- 1 ]. rfind ( "/" ) + 1 :]
751 for info
in p4CmdList ( "branches" ):
752 details
= p4Cmd ( "branch -o %s " % info
[ "branch" ])
754 while details
. has_key ( "View %s " % viewIdx
):
755 paths
= details
[ "View %s " % viewIdx
]. split ( " " )
756 viewIdx
= viewIdx
+ 1
757 # require standard //depot/foo/... //depot/bar/... mapping
758 if len ( paths
) != 2 or not paths
[ 0 ]. endswith ( "/..." ) or not paths
[ 1 ]. endswith ( "/..." ):
761 destination
= paths
[ 1 ]
762 if source
. startswith ( self
. depotPath
) and destination
. startswith ( self
. depotPath
):
763 source
= source
[ len ( self
. depotPath
):- 4 ]
764 destination
= destination
[ len ( self
. depotPath
):- 4 ]
765 if destination
not in self
. knownBranches
:
766 self
. knownBranches
[ destination
] = source
767 if source
not in self
. knownBranches
:
768 self
. knownBranches
[ source
] = source
770 def listExistingP4GitBranches ( self
):
771 self
. p4BranchesInGit
= []
773 cmdline
= "git rev-parse --symbolic "
774 if self
. importIntoRemotes
:
775 cmdline
+= " --remotes"
777 cmdline
+= " --branches"
779 for line
in mypopen ( cmdline
). readlines ():
780 if self
. importIntoRemotes
and (( not line
. startswith ( "p4/" )) or line
== "p4/HEAD \n " ):
782 if self
. importIntoRemotes
:
787 self
. p4BranchesInGit
. append ( branch
)
788 self
. initialParents
[ self
. refPrefix
+ branch
] = parseRevision ( line
[:- 1 ])
792 self
. changeRange
= ""
793 self
. initialParent
= ""
794 self
. previousDepotPath
= ""
795 # map from branch depot path to parent branch
796 self
. knownBranches
= {}
797 self
. initialParents
= {}
799 if self
. importIntoRemotes
:
800 self
. refPrefix
= "refs/remotes/p4/"
802 self
. refPrefix
= "refs/heads/"
804 createP4HeadRef
= False ;
806 if self
. syncWithOrigin
and gitBranchExists ( "origin" ) and gitBranchExists ( self
. refPrefix
+ "master" ) and not self
. detectBranches
and self
. importIntoRemotes
:
807 ### needs to be ported to multi branch import
809 print "Syncing with origin first as requested by calling git fetch origin"
810 system ( "git fetch origin" )
811 [ originPreviousDepotPath
, originP4Change
] = extractDepotPathAndChangeFromGitLog ( extractLogMessageFromGitCommit ( "origin" ))
812 [ p4PreviousDepotPath
, p4Change
] = extractDepotPathAndChangeFromGitLog ( extractLogMessageFromGitCommit ( "p4" ))
813 if len ( originPreviousDepotPath
) > 0 and len ( originP4Change
) > 0 and len ( p4Change
) > 0 :
814 if originPreviousDepotPath
== p4PreviousDepotPath
:
815 originP4Change
= int ( originP4Change
)
816 p4Change
= int ( p4Change
)
817 if originP4Change
> p4Change
:
818 print "origin ( %s ) is newer than p4 ( %s ). Updating p4 branch from origin." % ( originP4Change
, p4Change
)
819 system ( "git update-ref " + self
. refPrefix
+ "master origin" );
821 print "Cannot sync with origin. It was imported from %s while remotes/p4 was imported from %s " % ( originPreviousDepotPath
, p4PreviousDepotPath
)
823 if len ( self
. branch
) == 0 :
824 self
. branch
= self
. refPrefix
+ "master"
825 if gitBranchExists ( "refs/heads/p4" ) and self
. importIntoRemotes
:
826 system ( "git update-ref %s refs/heads/p4" % self
. branch
)
827 system ( "git branch -D p4" );
828 # create it /after/ importing, when master exists
829 if not gitBranchExists ( self
. refPrefix
+ "HEAD" ) and self
. importIntoRemotes
:
830 createP4HeadRef
= True
832 # this needs to be called after the conversion from heads/p4 to remotes/p4/master
833 self
. listExistingP4GitBranches ()
834 if len ( self
. p4BranchesInGit
) > 1 and not self
. silent
:
835 print "Importing from/into multiple branches"
836 self
. detectBranches
= True
839 if not gitBranchExists ( self
. branch
) and gitBranchExists ( "origin" ) and not self
. detectBranches
:
840 ### needs to be ported to multi branch import
842 print "Creating %s branch in git repository based on origin" % self
. branch
844 if not branch
. startswith ( "refs" ):
845 branch
= "refs/heads/" + branch
846 system ( "git update-ref %s origin" % branch
)
849 print "branches: %s " % self
. p4BranchesInGit
852 for branch
in self
. p4BranchesInGit
:
853 depotPath
, change
= extractDepotPathAndChangeFromGitLog ( extractLogMessageFromGitCommit ( self
. refPrefix
+ branch
))
856 print "path %s change %s " % ( depotPath
, change
)
858 if len ( depotPath
) > 0 and len ( change
) > 0 :
859 change
= int ( change
) + 1
860 p4Change
= max ( p4Change
, change
)
862 if len ( self
. previousDepotPath
) == 0 :
863 self
. previousDepotPath
= depotPath
866 l
= min ( len ( self
. previousDepotPath
), len ( depotPath
))
867 while i
< l
and self
. previousDepotPath
[ i
] == depotPath
[ i
]:
869 self
. previousDepotPath
= self
. previousDepotPath
[: i
]
872 self
. depotPath
= self
. previousDepotPath
873 self
. changeRange
= "@ %s ,#head" % p4Change
874 self
. initialParent
= parseRevision ( self
. branch
)
875 if not self
. silent
and not self
. detectBranches
:
876 print "Performing incremental import into %s git branch" % self
. branch
878 if not self
. branch
. startswith ( "refs/" ):
879 self
. branch
= "refs/heads/" + self
. branch
881 if len ( self
. depotPath
) != 0 :
882 self
. depotPath
= self
. depotPath
[:- 1 ]
884 if len ( args
) == 0 and len ( self
. depotPath
) != 0 :
886 print "Depot path: %s " % self
. depotPath
890 if len ( self
. depotPath
) != 0 and self
. depotPath
!= args
[ 0 ]:
891 print "previous import used depot path %s and now %s was specified. this doesn't work!" % ( self
. depotPath
, args
[ 0 ])
893 self
. depotPath
= args
[ 0 ]
898 if self
. depotPath
. find ( "@" ) != - 1 :
899 atIdx
= self
. depotPath
. index ( "@" )
900 self
. changeRange
= self
. depotPath
[ atIdx
:]
901 if self
. changeRange
== "@all" :
902 self
. changeRange
= ""
903 elif self
. changeRange
. find ( "," ) == - 1 :
904 self
. revision
= self
. changeRange
905 self
. changeRange
= ""
906 self
. depotPath
= self
. depotPath
[ 0 : atIdx
]
907 elif self
. depotPath
. find ( "#" ) != - 1 :
908 hashIdx
= self
. depotPath
. index ( "#" )
909 self
. revision
= self
. depotPath
[ hashIdx
:]
910 self
. depotPath
= self
. depotPath
[ 0 : hashIdx
]
911 elif len ( self
. previousDepotPath
) == 0 :
912 self
. revision
= "#head"
914 if self
. depotPath
. endswith ( "..." ):
915 self
. depotPath
= self
. depotPath
[:- 3 ]
917 if not self
. depotPath
. endswith ( "/" ):
918 self
. depotPath
+= "/"
920 self
. loadUserMapFromCache ()
922 if self
. detectLabels
:
925 if self
. detectBranches
:
926 self
. getBranchMapping ();
928 print "p4-git branches: %s " % self
. p4BranchesInGit
929 print "initial parents: %s " % self
. initialParents
930 for b
in self
. p4BranchesInGit
:
932 b
= b
[ len ( self
. projectName
):]
933 self
. createdBranches
. add ( b
)
935 self
. tz
= " %+0 3d %0 2d" % (- time
. timezone
/ 3600 , ((- time
. timezone
% 3600 ) / 60 ))
937 importProcess
= subprocess
. Popen ([ "git" , "fast-import" ], stdin
= subprocess
. PIPE
, stdout
= subprocess
. PIPE
, stderr
= subprocess
. PIPE
);
938 self
. gitOutput
= importProcess
. stdout
939 self
. gitStream
= importProcess
. stdin
940 self
. gitError
= importProcess
. stderr
942 if len ( self
. revision
) > 0 :
943 print "Doing initial import of %s from revision %s " % ( self
. depotPath
, self
. revision
)
945 details
= { "user" : "git perforce import user" , "time" : int ( time
. time ()) }
946 details
[ "desc" ] = "Initial import of %s from the state at revision %s " % ( self
. depotPath
, self
. revision
)
947 details
[ "change" ] = self
. revision
951 for info
in p4CmdList ( "files %s ... %s " % ( self
. depotPath
, self
. revision
)):
952 change
= int ( info
[ "change" ])
953 if change
> newestRevision
:
954 newestRevision
= change
956 if info
[ "action" ] == "delete" :
957 # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
958 #fileCnt = fileCnt + 1
961 for prop
in [ "depotFile" , "rev" , "action" , "type" ]:
962 details
[ " %s%s " % ( prop
, fileCnt
)] = info
[ prop
]
964 fileCnt
= fileCnt
+ 1
966 details
[ "change" ] = newestRevision
969 self
. commit ( details
, self
. extractFilesFromCommit ( details
), self
. branch
, self
. depotPath
)
971 print "IO error with git fast-import. Is your git version recent enough?"
972 print self
. gitError
. read ()
977 if len ( self
. changesFile
) > 0 :
978 output
= open ( self
. changesFile
). readlines ()
981 changeSet
. add ( int ( line
))
983 for change
in changeSet
:
984 changes
. append ( change
)
989 print "Getting p4 changes for %s ... %s " % ( self
. depotPath
, self
. changeRange
)
990 output
= mypopen ( "p4 changes %s ... %s " % ( self
. depotPath
, self
. changeRange
)). readlines ()
993 changeNum
= line
. split ( " " )[ 1 ]
994 changes
. append ( changeNum
)
998 if len ( self
. maxChanges
) > 0 :
999 changes
= changes
[ 0 : min ( int ( self
. maxChanges
), len ( changes
))]
1001 if len ( changes
) == 0 :
1003 print "No changes to import!"
1006 self
. updatedBranches
= set ()
1009 for change
in changes
:
1010 description
= p4Cmd ( "describe %s " % change
)
1013 sys
. stdout
. write ( " \r Importing revision %s ( %s%% )" % ( change
, cnt
* 100 / len ( changes
)))
1018 if self
. detectBranches
:
1019 branches
= self
. splitFilesIntoBranches ( description
)
1020 for branch
in branches
. keys ():
1021 branchPrefix
= self
. depotPath
+ branch
+ "/"
1025 filesForCommit
= branches
[ branch
]
1028 print "branch is %s " % branch
1030 self
. updatedBranches
. add ( branch
)
1032 if branch
not in self
. createdBranches
:
1033 self
. createdBranches
. add ( branch
)
1034 parent
= self
. knownBranches
[ branch
]
1035 if parent
== branch
:
1038 print "parent determined through known branches: %s " % parent
1040 # main branch? use master
1041 if branch
== "main" :
1044 branch
= self
. projectName
+ branch
1046 if parent
== "main" :
1048 elif len ( parent
) > 0 :
1049 parent
= self
. projectName
+ parent
1051 branch
= self
. refPrefix
+ branch
1053 parent
= self
. refPrefix
+ parent
1056 print "looking for initial parent for %s ; current parent is %s " % ( branch
, parent
)
1058 if len ( parent
) == 0 and branch
in self
. initialParents
:
1059 parent
= self
. initialParents
[ branch
]
1060 del self
. initialParents
[ branch
]
1062 self
. commit ( description
, filesForCommit
, branch
, branchPrefix
, parent
)
1064 files
= self
. extractFilesFromCommit ( description
)
1065 self
. commit ( description
, files
, self
. branch
, self
. depotPath
, self
. initialParent
)
1066 self
. initialParent
= ""
1068 print self
. gitError
. read ()
1073 if len ( self
. updatedBranches
) > 0 :
1074 sys
. stdout
. write ( "Updated branches: " )
1075 for b
in self
. updatedBranches
:
1076 sys
. stdout
. write ( " %s " % b
)
1077 sys
. stdout
. write ( " \n " )
1080 self
. gitStream
. close ()
1081 if importProcess
. wait () != 0 :
1082 die ( "fast-import failed: %s " % self
. gitError
. read ())
1083 self
. gitOutput
. close ()
1084 self
. gitError
. close ()
1087 system ( "git symbolic-ref %s HEAD %s " % ( self
. refPrefix
, self
. branch
))
1091 class P4Rebase ( Command
):
1093 Command
.__ init
__ ( self
)
1094 self
. options
= [ optparse
. make_option ( "--with-origin" , dest
= "syncWithOrigin" , action
= "store_true" ) ]
1095 self
. description
= "Fetches the latest revision from perforce and rebases the current work (branch) against it"
1096 self
. syncWithOrigin
= False
1098 def run ( self
, args
):
1100 sync
. syncWithOrigin
= self
. syncWithOrigin
1102 print "Rebasing the current branch"
1103 oldHead
= mypopen ( "git rev-parse HEAD" ). read ()[:- 1 ]
1104 system ( "git rebase p4" )
1105 system ( "git diff-tree --stat --summary -M %s HEAD" % oldHead
)
1108 class P4Clone ( P4Sync
):
1110 P4Sync
.__ init
__ ( self
)
1111 self
. description
= "Creates a new git repository and imports from Perforce into it"
1112 self
. usage
= "usage: %prog [options] //depot/path[@revRange] [directory]"
1113 self
. needsGit
= False
1115 def run ( self
, args
):
1127 if not depotPath
. startswith ( "//" ):
1132 atPos
= dir . rfind ( "@" )
1135 hashPos
= dir . rfind ( "#" )
1137 dir = dir [ 0 : hashPos
]
1139 if dir . endswith ( "..." ):
1142 if dir . endswith ( "/" ):
1145 slashPos
= dir . rfind ( "/" )
1147 dir = dir [ slashPos
+ 1 :]
1149 print "Importing from %s into %s " % ( depotPath
, dir )
1153 gitdir
= os
. getcwd () + "/.git"
1154 if not P4Sync
. run ( self
, [ depotPath
]):
1156 if self
. branch
!= "master" :
1157 if gitBranchExists ( "refs/remotes/p4/master" ):
1158 system ( "git branch master refs/remotes/p4/master" )
1159 system ( "git checkout -f" )
1161 print "Could not detect main branch. No checkout/master branch created."
1164 class HelpFormatter ( optparse
. IndentedHelpFormatter
):
1166 optparse
. IndentedHelpFormatter
.__ init
__ ( self
)
1168 def format_description ( self
, description
):
1170 return description
+ " \n "
1174 def printUsage ( commands
):
1175 print "usage: %s <command> [options]" % sys
. argv
[ 0 ]
1177 print "valid commands: %s " % ", " . join ( commands
)
1179 print "Try %s <command> --help for command specific help." % sys
. argv
[ 0 ]
1183 "debug" : P4Debug (),
1184 "submit" : P4Submit (),
1186 "rebase" : P4Rebase (),
1187 "clone" : P4Clone (),
1188 "rollback" : P4RollBack ()
1191 if len ( sys
. argv
[ 1 :]) == 0 :
1192 printUsage ( commands
. keys ())
1196 cmdName
= sys
. argv
[ 1 ]
1198 cmd
= commands
[ cmdName
]
1200 print "unknown command %s " % cmdName
1202 printUsage ( commands
. keys ())
1205 options
= cmd
. options
1210 if len ( options
) > 0 :
1211 options
. append ( optparse
. make_option ( "--git-dir" , dest
= "gitdir" ))
1213 parser
= optparse
. OptionParser ( cmd
. usage
. replace ( "%prog" , "%prog " + cmdName
),
1215 description
= cmd
. description
,
1216 formatter
= HelpFormatter ())
1218 ( cmd
, args
) = parser
. parse_args ( sys
. argv
[ 2 :], cmd
);
1222 if len ( gitdir
) == 0 :
1224 if not isValidGitDir ( gitdir
):
1225 gitdir
= mypopen ( "git rev-parse --git-dir" ). read ()[:- 1 ]
1226 if os
. path
. exists ( gitdir
):
1227 cdup
= mypopen ( "git rev-parse --show-cdup" ). read ()[:- 1 ];
1231 if not isValidGitDir ( gitdir
):
1232 if isValidGitDir ( gitdir
+ "/.git" ):
1235 die ( "fatal: cannot locate git repository at %s " % gitdir
)
1237 os
. environ
[ "GIT_DIR" ] = gitdir
1239 if not cmd
. run ( args
):