]>
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: Add an option to sync/rebase to fetch and rebase from origin first.
13 import optparse
, sys
, os
, marshal
, popen2
, subprocess
, shelve
14 import tempfile
, getopt
, sha
, os
. path
, time
, platform
17 gitdir
= os
. environ
. get ( "GIT_DIR" , "" )
20 return os
. popen ( command
, "rb" );
23 cmd
= "p4 -G %s " % cmd
24 pipe
= os
. popen ( cmd
, "rb" )
29 entry
= marshal
. load ( pipe
)
44 def p4Where ( depotPath
):
45 if not depotPath
. endswith ( "/" ):
47 output
= p4Cmd ( "where %s ..." % depotPath
)
50 clientPath
= output
. get ( "path" )
51 elif "data" in output
:
52 data
= output
. get ( "data" )
53 lastSpace
= data
. rfind ( " " )
54 clientPath
= data
[ lastSpace
+ 1 :]
56 if clientPath
. endswith ( "..." ):
57 clientPath
= clientPath
[:- 3 ]
61 sys
. stderr
. write ( msg
+ " \n " )
64 def currentGitBranch ():
65 return mypopen ( "git name-rev HEAD" ). read (). split ( " " )[ 1 ][:- 1 ]
67 def isValidGitDir ( path
):
68 if os
. path
. exists ( path
+ "/HEAD" ) and os
. path
. exists ( path
+ "/refs" ) and os
. path
. exists ( path
+ "/objects" ):
73 if os
. system ( cmd
) != 0 :
74 die ( "command failed: %s " % cmd
)
76 def extractLogMessageFromGitCommit ( commit
):
79 for log
in mypopen ( "git cat-file commit %s " % commit
). readlines ():
88 def extractDepotPathAndChangeFromGitLog ( log
):
90 for line
in log
. split ( " \n " ):
92 if line
. startswith ( "[git-p4:" ) and line
. endswith ( "]" ):
93 line
= line
[ 8 :- 1 ]. strip ()
94 for assignment
in line
. split ( ":" ):
95 variable
= assignment
. strip ()
97 equalPos
= assignment
. find ( "=" )
99 variable
= assignment
[: equalPos
]. strip ()
100 value
= assignment
[ equalPos
+ 1 :]. strip ()
101 if value
. startswith ( " \" " ) and value
. endswith ( " \" " ):
103 values
[ variable
] = value
105 return values
. get ( "depot-path" ), values
. get ( "change" )
107 def gitBranchExists ( branch
):
108 proc
= subprocess
. Popen ([ "git" , "rev-parse" , branch
], stderr
= subprocess
. PIPE
, stdout
= subprocess
. PIPE
);
109 return proc
. wait () == 0 ;
113 self
. usage
= "usage: %prog [options]"
116 class P4Debug ( Command
):
118 Command
.__ init
__ ( self
)
121 self
. description
= "A tool to debug the output of p4 -G."
122 self
. needsGit
= False
125 for output
in p4CmdList ( " " . join ( args
)):
129 class P4CleanTags ( Command
):
131 Command
.__ init
__ ( self
)
133 # optparse.make_option("--branch", dest="branch", default="refs/heads/master")
135 self
. description
= "A tool to remove stale unused tags from incremental perforce imports."
137 branch
= currentGitBranch ()
138 print "Cleaning out stale p4 import tags..."
139 sout
, sin
, serr
= popen2
. popen3 ( "git name-rev --tags `git rev-parse %s `" % branch
)
142 tagIdx
= output
. index ( " tags/p4/" )
144 print "Cannot find any p4/* tag. Nothing to do."
148 caretIdx
= output
. index ( "^" )
150 caretIdx
= len ( output
) - 1
151 rev
= int ( output
[ tagIdx
+ 9 : caretIdx
])
153 allTags
= mypopen ( "git tag -l p4/" ). readlines ()
154 for i
in range ( len ( allTags
)):
155 allTags
[ i
] = int ( allTags
[ i
][ 3 :- 1 ])
162 print mypopen ( "git tag -d p4/ %s " % rev
). read ()
164 print " %s tags removed." % len ( allTags
)
167 class P4Submit ( Command
):
169 Command
.__ init
__ ( self
)
171 optparse
. make_option ( "--continue" , action
= "store_false" , dest
= "firstTime" ),
172 optparse
. make_option ( "--origin" , dest
= "origin" ),
173 optparse
. make_option ( "--reset" , action
= "store_true" , dest
= "reset" ),
174 optparse
. make_option ( "--log-substitutions" , dest
= "substFile" ),
175 optparse
. make_option ( "--noninteractive" , action
= "store_false" ),
176 optparse
. make_option ( "--dry-run" , action
= "store_true" ),
178 self
. description
= "Submit changes from git to the perforce depot."
179 self
. usage
+= " [name of git branch to submit into perforce depot]"
180 self
. firstTime
= True
182 self
. interactive
= True
185 self
. firstTime
= True
188 self
. logSubstitutions
= {}
189 self
. logSubstitutions
[ "<enter description here>" ] = "%log%"
190 self
. logSubstitutions
[ " \t Details:" ] = " \t Details: %log%"
193 if len ( p4CmdList ( "opened ..." )) > 0 :
194 die ( "You have files opened with perforce! Close them before starting the sync." )
197 if len ( self
. config
) > 0 and not self
. reset
:
198 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
)
201 for line
in mypopen ( "git rev-list --no-merges %s .. %s " % ( self
. origin
, self
. master
)). readlines ():
202 commits
. append ( line
[:- 1 ])
205 self
. config
[ "commits" ] = commits
207 def prepareLogMessage ( self
, template
, message
):
210 for line
in template
. split ( " \n " ):
211 if line
. startswith ( "#" ):
212 result
+= line
+ " \n "
216 for key
in self
. logSubstitutions
. keys ():
217 if line
. find ( key
) != - 1 :
218 value
= self
. logSubstitutions
[ key
]
219 value
= value
. replace ( "%log%" , message
)
220 if value
!= "@remove@" :
221 result
+= line
. replace ( key
, value
) + " \n "
226 result
+= line
+ " \n "
231 print "Applying %s " % ( mypopen ( "git log --max-count=1 --pretty=oneline %s " % id ). read ())
232 diff
= mypopen ( "git diff-tree -r --name-status \" %s ^ \" \" %s \" " % ( id , id )). readlines ()
234 filesToDelete
= set ()
238 path
= line
[ 1 :]. strip ()
240 system ( "p4 edit \" %s \" " % path
)
241 editedFiles
. add ( path
)
242 elif modifier
== "A" :
244 if path
in filesToDelete
:
245 filesToDelete
. remove ( path
)
246 elif modifier
== "D" :
247 filesToDelete
. add ( path
)
248 if path
in filesToAdd
:
249 filesToAdd
. remove ( path
)
251 die ( "unknown modifier %s for %s " % ( modifier
, path
))
253 diffcmd
= "git diff-tree -p --diff-filter=ACMRTUXB \" %s ^ \" \" %s \" " % ( id , id )
254 patchcmd
= diffcmd
+ " | patch -p1"
256 if os
. system ( patchcmd
+ " --dry-run --silent" ) != 0 :
257 print "Unfortunately applying the change failed!"
258 print "What do you want to do?"
260 while response
!= "s" and response
!= "a" and response
!= "w" :
261 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) " )
263 print "Skipping! Good luck with the next patches..."
265 elif response
== "a" :
267 if len ( filesToAdd
) > 0 :
268 print "You may also want to call p4 add on the following files:"
269 print " " . join ( filesToAdd
)
270 if len ( filesToDelete
):
271 print "The following files should be scheduled for deletion with p4 delete:"
272 print " " . join ( filesToDelete
)
273 die ( "Please resolve and submit the conflict manually and continue afterwards with git-p4 submit --continue" )
274 elif response
== "w" :
275 system ( diffcmd
+ " > patch.txt" )
276 print "Patch saved to patch.txt in %s !" % self
. clientPath
277 die ( "Please resolve and submit the conflict manually and continue afterwards with git-p4 submit --continue" )
282 system ( "p4 add %s " % f
)
283 for f
in filesToDelete
:
284 system ( "p4 revert %s " % f
)
285 system ( "p4 delete %s " % f
)
287 logMessage
= extractLogMessageFromGitCommit ( id )
288 logMessage
= logMessage
. replace ( " \n " , " \n\t " )
289 logMessage
= logMessage
[:- 1 ]
291 template
= mypopen ( "p4 change -o" ). read ()
294 submitTemplate
= self
. prepareLogMessage ( template
, logMessage
)
295 diff
= mypopen ( "p4 diff -du ..." ). read ()
297 for newFile
in filesToAdd
:
298 diff
+= "==== new file ==== \n "
299 diff
+= "--- /dev/null \n "
300 diff
+= "+++ %s \n " % newFile
301 f
= open ( newFile
, "r" )
302 for line
in f
. readlines ():
306 separatorLine
= "######## everything below this line is just the diff #######"
307 if platform
. system () == "Windows" :
308 separatorLine
+= " \r "
309 separatorLine
+= " \n "
312 firstIteration
= True
313 while response
== "e" :
314 if not firstIteration
:
315 response
= raw_input ( "Do you want to submit this change? [y]es/[e]dit/[n]o/[s]kip " )
316 firstIteration
= False
318 [ handle
, fileName
] = tempfile
. mkstemp ()
319 tmpFile
= os
. fdopen ( handle
, "w+" )
320 tmpFile
. write ( submitTemplate
+ separatorLine
+ diff
)
323 if platform
. system () == "Windows" :
324 defaultEditor
= "notepad"
325 editor
= os
. environ
. get ( "EDITOR" , defaultEditor
);
326 system ( editor
+ " " + fileName
)
327 tmpFile
= open ( fileName
, "rb" )
328 message
= tmpFile
. read ()
331 submitTemplate
= message
[: message
. index ( separatorLine
)]
333 if response
== "y" or response
== "yes" :
336 raw_input ( "Press return to continue..." )
338 pipe
= os
. popen ( "p4 submit -i" , "wb" )
339 pipe
. write ( submitTemplate
)
341 elif response
== "s" :
342 for f
in editedFiles
:
343 system ( "p4 revert \" %s \" " % f
);
345 system ( "p4 revert \" %s \" " % f
);
347 for f
in filesToDelete
:
348 system ( "p4 delete \" %s \" " % f
);
351 print "Not submitting!"
352 self
. interactive
= False
354 fileName
= "submit.txt"
355 file = open ( fileName
, "w+" )
356 file . write ( self
. prepareLogMessage ( template
, logMessage
))
358 print "Perforce submit template written as %s . Please review/edit and then use p4 submit -i < %s to submit directly!" % ( fileName
, fileName
)
362 # make gitdir absolute so we can cd out into the perforce checkout
363 gitdir
= os
. path
. abspath ( gitdir
)
364 os
. environ
[ "GIT_DIR" ] = gitdir
367 self
. master
= currentGitBranch ()
368 if len ( self
. master
) == 0 or not os
. path
. exists ( " %s /refs/heads/ %s " % ( gitdir
, self
. master
)):
369 die ( "Detecting current git branch failed!" )
371 self
. master
= args
[ 0 ]
376 if gitBranchExists ( "p4" ):
377 [ depotPath
, dummy
] = extractDepotPathAndChangeFromGitLog ( extractLogMessageFromGitCommit ( "p4" ))
378 if len ( depotPath
) == 0 and gitBranchExists ( "origin" ):
379 [ depotPath
, dummy
] = extractDepotPathAndChangeFromGitLog ( extractLogMessageFromGitCommit ( "origin" ))
381 if len ( depotPath
) == 0 :
382 print "Internal error: cannot locate perforce depot path from existing branches"
385 self
. clientPath
= p4Where ( depotPath
)
387 if len ( self
. clientPath
) == 0 :
388 print "Error: Cannot locate perforce checkout of %s in client view" % depotPath
391 print "Perforce checkout for depot path %s located at %s " % ( depotPath
, self
. clientPath
)
392 oldWorkingDirectory
= os
. getcwd ()
393 os
. chdir ( self
. clientPath
)
394 response
= raw_input ( "Do you want to sync %s with p4 sync? [y]es/[n]o " % self
. clientPath
)
395 if response
== "y" or response
== "yes" :
396 system ( "p4 sync ..." )
398 if len ( self
. origin
) == 0 :
399 if gitBranchExists ( "p4" ):
402 self
. origin
= "origin"
405 self
. firstTime
= True
407 if len ( self
. substFile
) > 0 :
408 for line
in open ( self
. substFile
, "r" ). readlines ():
409 tokens
= line
[:- 1 ]. split ( "=" )
410 self
. logSubstitutions
[ tokens
[ 0 ]] = tokens
[ 1 ]
413 self
. configFile
= gitdir
+ "/p4-git-sync.cfg"
414 self
. config
= shelve
. open ( self
. configFile
, writeback
= True )
419 commits
= self
. config
. get ( "commits" , [])
421 while len ( commits
) > 0 :
422 self
. firstTime
= False
424 commits
= commits
[ 1 :]
425 self
. config
[ "commits" ] = commits
427 if not self
. interactive
:
432 if len ( commits
) == 0 :
434 print "No changes found to apply between %s and current HEAD" % self
. origin
436 print "All changes applied!"
437 response
= raw_input ( "Do you want to sync from Perforce now using git-p4 rebase? [y]es/[n]o " )
438 if response
== "y" or response
== "yes" :
439 os
. chdir ( oldWorkingDirectory
)
442 os
. remove ( self
. configFile
)
446 class P4Sync ( Command
):
448 Command
.__ init
__ ( self
)
450 optparse
. make_option ( "--branch" , dest
= "branch" ),
451 optparse
. make_option ( "--detect-branches" , dest
= "detectBranches" , action
= "store_true" ),
452 optparse
. make_option ( "--changesfile" , dest
= "changesFile" ),
453 optparse
. make_option ( "--silent" , dest
= "silent" , action
= "store_true" ),
454 optparse
. make_option ( "--known-branches" , dest
= "knownBranches" ),
455 optparse
. make_option ( "--data-cache" , dest
= "dataCache" , action
= "store_true" ),
456 optparse
. make_option ( "--command-cache" , dest
= "commandCache" , action
= "store_true" ),
457 optparse
. make_option ( "--detect-labels" , dest
= "detectLabels" , action
= "store_true" )
459 self
. description
= """Imports from Perforce into a git repository. \n
461 //depot/my/project/ -- to import the current head
462 //depot/my/project/@all -- to import everything
463 //depot/my/project/@1,6 -- to import only from revision 1 to 6
465 (a ... is not needed in the path p4 specification, it's added implicitly)"""
467 self
. usage
+= " //depot/path[@revRange]"
469 self
. dataCache
= False
470 self
. commandCache
= False
472 self
. knownBranches
= Set ()
473 self
. createdBranches
= Set ()
474 self
. committedChanges
= Set ()
476 self
. detectBranches
= False
477 self
. detectLabels
= False
478 self
. changesFile
= ""
480 def p4File ( self
, depotPath
):
481 return os
. popen ( "p4 print -q \" %s \" " % depotPath
, "rb" ). read ()
483 def extractFilesFromCommit ( self
, commit
):
486 while commit
. has_key ( "depotFile %s " % fnum
):
487 path
= commit
[ "depotFile %s " % fnum
]
488 if not path
. startswith ( self
. depotPath
):
489 # if not self.silent:
490 # print "\nchanged files: ignoring path %s outside of %s in change %s" % (path, self.depotPath, change)
496 file [ "rev" ] = commit
[ "rev %s " % fnum
]
497 file [ "action" ] = commit
[ "action %s " % fnum
]
498 file [ "type" ] = commit
[ "type %s " % fnum
]
503 def isSubPathOf ( self
, first
, second
):
504 if not first
. startswith ( second
):
508 return first
[ len ( second
)] == "/"
510 def branchesForCommit ( self
, files
):
514 relativePath
= file [ "path" ][ len ( self
. depotPath
):]
515 # strip off the filename
516 relativePath
= relativePath
[ 0 : relativePath
. rfind ( "/" )]
518 # if len(branches) == 0:
519 # branches.add(relativePath)
520 # knownBranches.add(relativePath)
523 ###### this needs more testing :)
525 for branch
in branches
:
526 if relativePath
== branch
:
529 # if relativePath.startswith(branch):
530 if self
. isSubPathOf ( relativePath
, branch
):
533 # if branch.startswith(relativePath):
534 if self
. isSubPathOf ( branch
, relativePath
):
535 branches
. remove ( branch
)
541 for branch
in self
. knownBranches
:
542 #if relativePath.startswith(branch):
543 if self
. isSubPathOf ( relativePath
, branch
):
544 if len ( branches
) == 0 :
545 relativePath
= branch
553 branches
. add ( relativePath
)
554 self
. knownBranches
. add ( relativePath
)
558 def findBranchParent ( self
, branchPrefix
, files
):
561 if not path
. startswith ( branchPrefix
):
563 action
= file [ "action" ]
564 if action
!= "integrate" and action
!= "branch" :
567 depotPath
= path
+ "#" + rev
569 log
= p4CmdList ( "filelog \" %s \" " % depotPath
)
571 print "eek! I got confused by the filelog of %s " % depotPath
575 if log
[ "action0" ] != action
:
576 print "eek! wrong action in filelog for %s : found %s , expected %s " % ( depotPath
, log
[ "action0" ], action
)
579 branchAction
= log
[ "how0,0" ]
580 # if branchAction == "branch into" or branchAction == "ignored":
581 # continue # ignore for branching
583 if not branchAction
. endswith ( " from" ):
584 continue # ignore for branching
585 # print "eek! file %s was not branched from but instead: %s" % (depotPath, branchAction)
588 source
= log
[ "file0,0" ]
589 if source
. startswith ( branchPrefix
):
592 lastSourceRev
= log
[ "erev0,0" ]
594 sourceLog
= p4CmdList ( "filelog -m 1 \" %s%s \" " % ( source
, lastSourceRev
))
595 if len ( sourceLog
) != 1 :
596 print "eek! I got confused by the source filelog of %s%s " % ( source
, lastSourceRev
)
598 sourceLog
= sourceLog
[ 0 ]
600 relPath
= source
[ len ( self
. depotPath
):]
601 # strip off the filename
602 relPath
= relPath
[ 0 : relPath
. rfind ( "/" )]
604 for branch
in self
. knownBranches
:
605 if self
. isSubPathOf ( relPath
, branch
):
606 # print "determined parent branch branch %s due to change in file %s" % (branch, source)
609 # print "%s is not a subpath of branch %s" % (relPath, branch)
613 def commit ( self
, details
, files
, branch
, branchPrefix
, parent
= "" , merged
= "" ):
614 epoch
= details
[ "time" ]
615 author
= details
[ "user" ]
617 self
. gitStream
. write ( "commit %s \n " % branch
)
618 # gitStream.write("mark :%s\n" % details["change"])
619 self
. committedChanges
. add ( int ( details
[ "change" ]))
621 if author
in self
. users
:
622 committer
= " %s %s %s " % ( self
. users
[ author
], epoch
, self
. tz
)
624 committer
= " %s <a@b> %s %s " % ( author
, epoch
, self
. tz
)
626 self
. gitStream
. write ( "committer %s \n " % committer
)
628 self
. gitStream
. write ( "data <<EOT \n " )
629 self
. gitStream
. write ( details
[ "desc" ])
630 self
. gitStream
. write ( " \n [git-p4: depot-path = \" %s \" : change = %s ] \n " % ( branchPrefix
, details
[ "change" ]))
631 self
. gitStream
. write ( "EOT \n\n " )
634 self
. gitStream
. write ( "from %s \n " % parent
)
637 self
. gitStream
. write ( "merge %s \n " % merged
)
641 if not path
. startswith ( branchPrefix
):
643 # print "\nchanged files: ignoring path %s outside of branch prefix %s in change %s" % (path, branchPrefix, details["change"])
646 depotPath
= path
+ "#" + rev
647 relPath
= path
[ len ( branchPrefix
):]
648 action
= file [ "action" ]
650 if file [ "type" ] == "apple" :
651 print " \n file %s is a strange apple file that forks. Ignoring!" % path
654 if action
== "delete" :
655 self
. gitStream
. write ( "D %s \n " % relPath
)
658 if file [ "type" ]. startswith ( "x" ):
661 data
= self
. p4File ( depotPath
)
663 self
. gitStream
. write ( "M %s inline %s \n " % ( mode
, relPath
))
664 self
. gitStream
. write ( "data %s \n " % len ( data
))
665 self
. gitStream
. write ( data
)
666 self
. gitStream
. write ( " \n " )
668 self
. gitStream
. write ( " \n " )
670 change
= int ( details
[ "change" ])
672 self
. lastChange
= change
674 if change
in self
. labels
:
675 label
= self
. labels
[ change
]
676 labelDetails
= label
[ 0 ]
677 labelRevisions
= label
[ 1 ]
679 files
= p4CmdList ( "files %s ...@ %s " % ( branchPrefix
, change
))
681 if len ( files
) == len ( labelRevisions
):
685 if info
[ "action" ] == "delete" :
687 cleanedFiles
[ info
[ "depotFile" ]] = info
[ "rev" ]
689 if cleanedFiles
== labelRevisions
:
690 self
. gitStream
. write ( "tag tag_ %s \n " % labelDetails
[ "label" ])
691 self
. gitStream
. write ( "from %s \n " % branch
)
693 owner
= labelDetails
[ "Owner" ]
695 if author
in self
. users
:
696 tagger
= " %s %s %s " % ( self
. users
[ owner
], epoch
, self
. tz
)
698 tagger
= " %s <a@b> %s %s " % ( owner
, epoch
, self
. tz
)
699 self
. gitStream
. write ( "tagger %s \n " % tagger
)
700 self
. gitStream
. write ( "data <<EOT \n " )
701 self
. gitStream
. write ( labelDetails
[ "Description" ])
702 self
. gitStream
. write ( "EOT \n\n " )
706 print "Tag %s does not match with change %s : files do not match." % ( labelDetails
[ "label" ], change
)
710 print "Tag %s does not match with change %s : file count is different." % ( labelDetails
[ "label" ], change
)
712 def extractFilesInCommitToBranch ( self
, files
, branchPrefix
):
717 if path
. startswith ( branchPrefix
):
718 newFiles
. append ( file )
722 def findBranchSourceHeuristic ( self
, files
, branch
, branchPrefix
):
724 action
= file [ "action" ]
725 if action
!= "integrate" and action
!= "branch" :
729 depotPath
= path
+ "#" + rev
731 log
= p4CmdList ( "filelog \" %s \" " % depotPath
)
733 print "eek! I got confused by the filelog of %s " % depotPath
737 if log
[ "action0" ] != action
:
738 print "eek! wrong action in filelog for %s : found %s , expected %s " % ( depotPath
, log
[ "action0" ], action
)
741 branchAction
= log
[ "how0,0" ]
743 if not branchAction
. endswith ( " from" ):
744 continue # ignore for branching
745 # print "eek! file %s was not branched from but instead: %s" % (depotPath, branchAction)
748 source
= log
[ "file0,0" ]
749 if source
. startswith ( branchPrefix
):
752 lastSourceRev
= log
[ "erev0,0" ]
754 sourceLog
= p4CmdList ( "filelog -m 1 \" %s%s \" " % ( source
, lastSourceRev
))
755 if len ( sourceLog
) != 1 :
756 print "eek! I got confused by the source filelog of %s%s " % ( source
, lastSourceRev
)
758 sourceLog
= sourceLog
[ 0 ]
760 relPath
= source
[ len ( self
. depotPath
):]
761 # strip off the filename
762 relPath
= relPath
[ 0 : relPath
. rfind ( "/" )]
764 for candidate
in self
. knownBranches
:
765 if self
. isSubPathOf ( relPath
, candidate
) and candidate
!= branch
:
770 def changeIsBranchMerge ( self
, sourceBranch
, destinationBranch
, change
):
772 for file in p4CmdList ( "files %s ...@ %s " % ( self
. depotPath
+ sourceBranch
+ "/" , change
)):
773 if file [ "action" ] == "delete" :
775 sourceFiles
[ file [ "depotFile" ]] = file
777 destinationFiles
= {}
778 for file in p4CmdList ( "files %s ...@ %s " % ( self
. depotPath
+ destinationBranch
+ "/" , change
)):
779 destinationFiles
[ file [ "depotFile" ]] = file
781 for fileName
in sourceFiles
. keys ():
785 for integration
in p4CmdList ( "integrated \" %s \" " % fileName
):
786 toFile
= integration
[ "fromFile" ] # yes, it's true, it's fromFile
787 if not toFile
in destinationFiles
:
789 destFile
= destinationFiles
[ toFile
]
790 if destFile
[ "action" ] == "delete" :
791 # print "file %s has been deleted in %s" % (fileName, toFile)
794 integrationCount
+= 1
795 if integration
[ "how" ] == "branch from" :
798 if int ( integration
[ "change" ]) == change
:
799 integrations
. append ( integration
)
801 if int ( integration
[ "change" ]) > change
:
804 destRev
= int ( destFile
[ "rev" ])
806 startRev
= integration
[ "startFromRev" ][ 1 :]
807 if startRev
== "none" :
810 startRev
= int ( startRev
)
812 endRev
= integration
[ "endFromRev" ][ 1 :]
818 initialBranch
= ( destRev
== 1 and integration
[ "how" ] != "branch into" )
819 inRange
= ( destRev
>= startRev
and destRev
<= endRev
)
820 newer
= ( destRev
> startRev
and destRev
> endRev
)
822 if initialBranch
or inRange
or newer
:
823 integrations
. append ( integration
)
828 if len ( integrations
) == 0 and integrationCount
> 1 :
829 print "file %s was not integrated from %s into %s " % ( fileName
, sourceBranch
, destinationBranch
)
834 def getUserMap ( self
):
837 for output
in p4CmdList ( "users" ):
838 if not output
. has_key ( "User" ):
840 self
. users
[ output
[ "User" ]] = output
[ "FullName" ] + " <" + output
[ "Email" ] + ">"
845 l
= p4CmdList ( "labels %s ..." % self
. depotPath
)
846 if len ( l
) > 0 and not self
. silent
:
847 print "Finding files belonging to labels in %s " % self
. depotPath
850 label
= output
[ "label" ]
853 for file in p4CmdList ( "files //...@ %s " % label
):
854 revisions
[ file [ "depotFile" ]] = file [ "rev" ]
855 change
= int ( file [ "change" ])
856 if change
> newestChange
:
857 newestChange
= change
859 self
. labels
[ newestChange
] = [ output
, revisions
]
863 self
. changeRange
= ""
864 self
. initialParent
= ""
865 self
. previousDepotPath
= ""
867 if len ( self
. branch
) == 0 :
868 self
. branch
= "refs/remotes/p4"
871 if not gitBranchExists ( self
. branch
) and gitBranchExists ( "origin" ):
873 print "Creating %s branch in git repository based on origin" % self
. branch
874 system ( "git branch %s origin" % self
. branch
)
876 [ self
. previousDepotPath
, p4Change
] = extractDepotPathAndChangeFromGitLog ( extractLogMessageFromGitCommit ( self
. branch
))
877 if len ( self
. previousDepotPath
) > 0 and len ( p4Change
) > 0 :
878 p4Change
= int ( p4Change
) + 1
879 self
. depotPath
= self
. previousDepotPath
880 self
. changeRange
= "@ %s ,#head" % p4Change
881 self
. initialParent
= mypopen ( "git rev-parse %s " % self
. branch
). read ()[:- 1 ]
883 print "Performing incremental import into %s git branch" % self
. branch
885 if not self
. branch
. startswith ( "refs/" ):
886 self
. branch
= "refs/heads/" + self
. branch
888 if len ( self
. depotPath
) != 0 :
889 self
. depotPath
= self
. depotPath
[:- 1 ]
891 if len ( args
) == 0 and len ( self
. depotPath
) != 0 :
893 print "Depot path: %s " % self
. depotPath
897 if len ( self
. depotPath
) != 0 and self
. depotPath
!= args
[ 0 ]:
898 print "previous import used depot path %s and now %s was specified. this doesn't work!" % ( self
. depotPath
, args
[ 0 ])
900 self
. depotPath
= args
[ 0 ]
906 if self
. depotPath
. find ( "@" ) != - 1 :
907 atIdx
= self
. depotPath
. index ( "@" )
908 self
. changeRange
= self
. depotPath
[ atIdx
:]
909 if self
. changeRange
== "@all" :
910 self
. changeRange
= ""
911 elif self
. changeRange
. find ( "," ) == - 1 :
912 self
. revision
= self
. changeRange
913 self
. changeRange
= ""
914 self
. depotPath
= self
. depotPath
[ 0 : atIdx
]
915 elif self
. depotPath
. find ( "#" ) != - 1 :
916 hashIdx
= self
. depotPath
. index ( "#" )
917 self
. revision
= self
. depotPath
[ hashIdx
:]
918 self
. depotPath
= self
. depotPath
[ 0 : hashIdx
]
919 elif len ( self
. previousDepotPath
) == 0 :
920 self
. revision
= "#head"
922 if self
. depotPath
. endswith ( "..." ):
923 self
. depotPath
= self
. depotPath
[:- 3 ]
925 if not self
. depotPath
. endswith ( "/" ):
926 self
. depotPath
+= "/"
930 if self
. detectLabels
:
933 if len ( self
. changeRange
) == 0 :
935 sout
, sin
, serr
= popen2
. popen3 ( "git name-rev --tags `git rev-parse %s `" % self
. branch
)
937 if output
. endswith ( " \n " ):
939 tagIdx
= output
. index ( " tags/p4/" )
940 caretIdx
= output
. find ( "^" )
944 self
. rev
= int ( output
[ tagIdx
+ 9 : endPos
]) + 1
945 self
. changeRange
= "@ %s ,#head" % self
. rev
946 self
. initialParent
= mypopen ( "git rev-parse %s " % self
. branch
). read ()[:- 1 ]
950 self
. tz
= " %+0 3d %0 2d" % (- time
. timezone
/ 3600 , ((- time
. timezone
% 3600 ) / 60 ))
952 importProcess
= subprocess
. Popen ([ "git" , "fast-import" ], stdin
= subprocess
. PIPE
, stdout
= subprocess
. PIPE
, stderr
= subprocess
. PIPE
);
953 self
. gitOutput
= importProcess
. stdout
954 self
. gitStream
= importProcess
. stdin
955 self
. gitError
= importProcess
. stderr
957 if len ( self
. revision
) > 0 :
958 print "Doing initial import of %s from revision %s " % ( self
. depotPath
, self
. revision
)
960 details
= { "user" : "git perforce import user" , "time" : int ( time
. time ()) }
961 details
[ "desc" ] = "Initial import of %s from the state at revision %s " % ( self
. depotPath
, self
. revision
)
962 details
[ "change" ] = self
. revision
966 for info
in p4CmdList ( "files %s ... %s " % ( self
. depotPath
, self
. revision
)):
967 change
= int ( info
[ "change" ])
968 if change
> newestRevision
:
969 newestRevision
= change
971 if info
[ "action" ] == "delete" :
972 # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
973 #fileCnt = fileCnt + 1
976 for prop
in [ "depotFile" , "rev" , "action" , "type" ]:
977 details
[ " %s%s " % ( prop
, fileCnt
)] = info
[ prop
]
979 fileCnt
= fileCnt
+ 1
981 details
[ "change" ] = newestRevision
984 self
. commit ( details
, self
. extractFilesFromCommit ( details
), self
. branch
, self
. depotPath
)
986 print "IO error with git fast-import. Is your git version recent enough?"
987 print self
. gitError
. read ()
992 if len ( self
. changesFile
) > 0 :
993 output
= open ( self
. changesFile
). readlines ()
996 changeSet
. add ( int ( line
))
998 for change
in changeSet
:
999 changes
. append ( change
)
1003 output
= mypopen ( "p4 changes %s ... %s " % ( self
. depotPath
, self
. changeRange
)). readlines ()
1006 changeNum
= line
. split ( " " )[ 1 ]
1007 changes
. append ( changeNum
)
1011 if len ( changes
) == 0 :
1013 print "no changes to import!"
1017 for change
in changes
:
1018 description
= p4Cmd ( "describe %s " % change
)
1021 sys
. stdout
. write ( " \r importing revision %s ( %s%% )" % ( change
, cnt
* 100 / len ( changes
)))
1026 files
= self
. extractFilesFromCommit ( description
)
1027 if self
. detectBranches
:
1028 for branch
in self
. branchesForCommit ( files
):
1029 self
. knownBranches
. add ( branch
)
1030 branchPrefix
= self
. depotPath
+ branch
+ "/"
1032 filesForCommit
= self
. extractFilesInCommitToBranch ( files
, branchPrefix
)
1036 ########### remove cnt!!!
1037 if branch
not in self
. createdBranches
and cnt
> 2 :
1038 self
. createdBranches
. add ( branch
)
1039 parent
= self
. findBranchParent ( branchPrefix
, files
)
1040 if parent
== branch
:
1042 # elif len(parent) > 0:
1043 # print "%s branched off of %s" % (branch, parent)
1045 if len ( parent
) == 0 :
1046 merged
= self
. findBranchSourceHeuristic ( filesForCommit
, branch
, branchPrefix
)
1048 print "change %s could be a merge from %s into %s " % ( description
[ "change" ], merged
, branch
)
1049 if not self
. changeIsBranchMerge ( merged
, branch
, int ( description
[ "change" ])):
1052 branch
= "refs/heads/" + branch
1054 parent
= "refs/heads/" + parent
1056 merged
= "refs/heads/" + merged
1057 self
. commit ( description
, files
, branch
, branchPrefix
, parent
, merged
)
1059 self
. commit ( description
, files
, self
. branch
, self
. depotPath
, self
. initialParent
)
1060 self
. initialParent
= ""
1062 print self
. gitError
. read ()
1069 self
. gitStream
. close ()
1070 self
. gitOutput
. close ()
1071 self
. gitError
. close ()
1072 importProcess
. wait ()
1076 class P4Rebase ( Command
):
1078 Command
.__ init
__ ( self
)
1080 self
. description
= "Fetches the latest revision from perforce and rebases the current work (branch) against it"
1082 def run ( self
, args
):
1085 print "Rebasing the current branch"
1086 oldHead
= mypopen ( "git rev-parse HEAD" ). read ()[:- 1 ]
1087 system ( "git rebase p4" )
1088 system ( "git diff-tree --stat --summary -M %s HEAD" % oldHead
)
1091 class P4Clone ( P4Sync
):
1093 P4Sync
.__ init
__ ( self
)
1094 self
. description
= "Creates a new git repository and imports from Perforce into it"
1095 self
. usage
= "usage: %prog [options] //depot/path[@revRange] [directory]"
1096 self
. needsGit
= False
1098 def run ( self
, args
):
1108 if not depotPath
. startswith ( "//" ):
1113 atPos
= dir . rfind ( "@" )
1116 hashPos
= dir . rfind ( "#" )
1118 dir = dir [ 0 : hashPos
]
1120 if dir . endswith ( "..." ):
1123 if dir . endswith ( "/" ):
1126 slashPos
= dir . rfind ( "/" )
1128 dir = dir [ slashPos
+ 1 :]
1130 print "Importing from %s into %s " % ( depotPath
, dir )
1134 if not P4Sync
. run ( self
, [ depotPath
]):
1136 if self
. branch
!= "master" :
1137 system ( "git branch master p4" )
1138 system ( "git checkout -f" )
1141 class HelpFormatter ( optparse
. IndentedHelpFormatter
):
1143 optparse
. IndentedHelpFormatter
.__ init
__ ( self
)
1145 def format_description ( self
, description
):
1147 return description
+ " \n "
1151 def printUsage ( commands
):
1152 print "usage: %s <command> [options]" % sys
. argv
[ 0 ]
1154 print "valid commands: %s " % ", " . join ( commands
)
1156 print "Try %s <command> --help for command specific help." % sys
. argv
[ 0 ]
1160 "debug" : P4Debug (),
1161 "clean-tags" : P4CleanTags (),
1162 "submit" : P4Submit (),
1164 "rebase" : P4Rebase (),
1168 if len ( sys
. argv
[ 1 :]) == 0 :
1169 printUsage ( commands
. keys ())
1173 cmdName
= sys
. argv
[ 1 ]
1175 cmd
= commands
[ cmdName
]
1177 print "unknown command %s " % cmdName
1179 printUsage ( commands
. keys ())
1182 options
= cmd
. options
1187 if len ( options
) > 0 :
1188 options
. append ( optparse
. make_option ( "--git-dir" , dest
= "gitdir" ))
1190 parser
= optparse
. OptionParser ( cmd
. usage
. replace ( "%prog" , "%prog " + cmdName
),
1192 description
= cmd
. description
,
1193 formatter
= HelpFormatter ())
1195 ( cmd
, args
) = parser
. parse_args ( sys
. argv
[ 2 :], cmd
);
1199 if len ( gitdir
) == 0 :
1201 if not isValidGitDir ( gitdir
):
1202 gitdir
= mypopen ( "git rev-parse --git-dir" ). read ()[:- 1 ]
1203 if os
. path
. exists ( gitdir
):
1204 cdup
= mypopen ( "git rev-parse --show-cdup" ). read ()[:- 1 ];
1208 if not isValidGitDir ( gitdir
):
1209 if isValidGitDir ( gitdir
+ "/.git" ):
1212 die ( "fatal: cannot locate git repository at %s " % gitdir
)
1214 os
. environ
[ "GIT_DIR" ] = gitdir
1216 if not cmd
. run ( args
):