@ -2,21 +2,46 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import optparse
import os
import re
import subprocess
import sys
import re
import os
import webbrowser
USAGE = """
WARNING : Please use this tool in an empty directory
( or at least one that you don ' t mind clobbering.)
REQUIRES : SVN 1.5 +
NOTE : NO NEED TO CHECKOUT ANYTHING IN ADVANCE OF USING THIS TOOL . "
Valid parameters :
[ Merge from trunk to branch ]
< revision > - - merge < branch_num >
Example : % ( app ) s 12345 - - merge 187
[ Merge from trunk to branch , ignoring revision history ]
< revision > - - mplus < branch_num >
Example : % ( app ) s 12345 - - mplus 187
[ Revert from trunk ]
< revision > - - revert
Example : % ( app ) s 12345 - - revert
[ Revert from branch ]
< revision > - - revert < branch_num >
Example : % ( app ) s 12345 - - revert 187
"""
export_map_ = None
files_info_ = None
delete_map_ = None
file_pattern_ = r " [ ]+([MADUC])[ ]+/((?:trunk|branches/ \ d+)/src(.*)/(.*)) "
def deltree ( root ) :
"""
Removes a given directory
"""
""" Removes a given directory """
if ( not os . path . exists ( root ) ) :
return
@ -24,24 +49,22 @@ def deltree(root):
os . system ( ' rmdir /S /Q ' + root . replace ( ' / ' , ' \\ ' ) )
else :
for name in os . listdir ( root ) :
path = os . path . join ( root , name )
if os . path . isdir ( path ) :
deltree ( path )
else :
os . unlink ( path )
path = os . path . join ( root , name )
if os . path . isdir ( path ) :
deltree ( path )
else :
os . unlink ( path )
os . rmdir ( root )
def clobberDir ( dir ) :
"""
Removes a given directory
"""
""" Removes a given directory """
if ( os . path . exists ( dir ) ) :
print dir + " directory found, deleting "
# The following line was removed due to access controls in Windows
# which make os.unlink(path) calls impossible.
#deltree(dir)
os . system ( ' rmdir /S /Q ' + dir . replace ( ' / ' , ' \\ ' ) )
print dir + " directory found, deleting "
# The following line was removed due to access controls in Windows
# which make os.unlink(path) calls impossible.
#TODO(laforge) : Is this correct?
deltree ( dir )
def gclUpload ( revision , author ) :
command = ( " gcl upload " + str ( revision ) +
@ -54,56 +77,49 @@ def getSVNInfo(url, revision):
shell = True ,
stdout = subprocess . PIPE ,
stderr = subprocess . PIPE ) . stdout . readlines ( )
rtn = { }
info = { }
for line in svn_info :
match = re . search ( r " (.*?):(.*) " , line )
if match :
rtn [ match . group ( 1 ) . strip ( ) ] = match . group ( 2 ) . strip ( )
info [ match . group ( 1 ) . strip ( ) ] = match . group ( 2 ) . strip ( )
return rtn
return info
def getAuthor ( url , revision ) :
info = getSVNInfo ( url , revision )
if ( info . has_key ( " Last Changed Author " ) ) :
return info [ " Last Changed Author " ]
return None
def isSVNFile ( url , revision ) :
info = getSVNInfo ( url , revision )
if ( info . has_key ( " Node Kind " ) ) :
if ( info [ " Node Kind " ] == " file " ) : return True
if ( info [ " Node Kind " ] == " file " ) :
return True
return False
def isSVNDirectory ( url , revision ) :
info = getSVNInfo ( url , revision )
if ( info . has_key ( " Node Kind " ) ) :
if ( info [ " Node Kind " ] == " directory " ) : return True
if ( info [ " Node Kind " ] == " directory " ) :
return True
return False
def getRevisionLog ( url , revision ) :
"""
Takes an svn url and gets the associated revision .
"""
""" Takes an svn url and gets the associated revision. """
command = ' svn log ' + url + " -r " + str ( revision )
svn_ info = subprocess . Popen ( command ,
shell = True ,
stdout = subprocess . PIPE ,
stderr = subprocess . PIPE ) . stdout . readlines ( )
rtn = " "
svn_log = subprocess . Popen ( command ,
shell = True ,
stdout = subprocess . PIPE ,
stderr = subprocess . PIPE ) . stdout . readlines ( )
log = " "
pos = 0
for line in svn_ info :
for line in svn_ log :
if ( pos > 2 ) :
rtn + = line . replace ( ' - ' , ' ' ) . replace ( ' \r ' , ' ' )
log + = line . replace ( ' - ' , ' ' ) . replace ( ' \r ' , ' ' )
else :
pos = pos + 1
return rtn
return log
def checkoutRevision ( url , revision , branch_url , revert = False ) :
files_info = getFileInfo ( url , revision )
@ -119,12 +135,12 @@ def checkoutRevision(url, revision, branch_url, revert=False):
if match :
os . chdir ( match . group ( 1 ) )
# This line is extremely important due to the way svn behaves in the set-depths
# action. If parents aren't handled before children, the child directories get
# clobbered and the merge step fails.
# This line is extremely important due to the way svn behaves in the
# set-depths action. If parents aren't handled before children, the child
# directories get clobbered and the merge step fails.
paths . sort ( )
# Checkout the directories that already exist
# Checkout the directories that already exist
for path in paths :
if ( export_map . has_key ( path ) and not revert ) :
print " Exclude new directory " + path
@ -134,7 +150,7 @@ def checkoutRevision(url, revision, branch_url, revert=False):
base = ' '
for subpath in subpaths :
base + = ' / ' + subpath
# This logic ensures that you don't empty out any directories
# This logic ensures that you don't empty out any directories
if not os . path . exists ( " . " + base ) :
command = ( ' svn update --depth empty ' + " . " + base )
print command
@ -146,7 +162,7 @@ def checkoutRevision(url, revision, branch_url, revert=False):
files = getExistingFilesInRevision ( files_info )
for file in files :
# Prevent the tool from clobbering the src directory
# Prevent the tool from clobbering the src directory
if ( file == " " ) :
continue
command = ( ' svn up " . ' + file + ' " ' )
@ -168,16 +184,15 @@ def mergeRevision(url, revision):
def exportRevision ( url , revision ) :
paths = getBestExportPathsMap ( url , revision ) . keys ( )
paths . sort ( )
for path in paths :
command = ( ' svn export -N ' + url + path + " @ " + str ( revision ) + " . "
+ path )
command = ( ' svn export -N ' + url + path + " @ " + str ( revision ) + " . " +
path )
print command
os . system ( command )
command = ( ' svn add . ' + path )
command = ' svn add . ' + path
print command
os . system ( command )
@ -187,18 +202,17 @@ def deleteRevision(url, revision):
paths . reverse ( )
for path in paths :
command = ( " svn delete . " + path )
command = " svn delete . " + path
print command
os . system ( command )
def revertExportRevision ( url , revision ) :
paths = getBestExportPathsMap ( url , revision ) . keys ( )
paths . sort ( )
paths . reverse ( )
for path in paths :
command = ( " svn delete . " + path )
command = " svn delete . " + path
print command
os . system ( command )
@ -218,35 +232,31 @@ def getFileInfo(url, revision):
command = ' svn log ' + url + " -r " + str ( revision ) + " -v "
svn_log = subprocess . Popen ( command ,
shell = True ,
stdout = subprocess . PIPE ,
stderr = subprocess . PIPE ) . stdout . readlines ( )
shell = True ,
stdout = subprocess . PIPE ,
stderr = subprocess . PIPE ) . stdout . readlines ( )
rtn = [ ]
info = [ ]
for line in svn_log :
# A workaround to dump the (from .*) stuff, regex not so friendly in the 2nd
# pass...
# A workaround to dump the (from .*) stuff, regex not so friendly in the 2nd
# pass...
match = re . search ( r " (.*) \ (from.* \ ) " , line )
if match :
line = match . group ( 1 )
match = re . search ( file_pattern_ , line )
if match :
rtn . append ( [ match . group ( 1 ) . strip ( ) , match . group ( 2 ) . strip ( ) ,
match . group ( 3 ) . strip ( ) , match . group ( 4 ) . strip ( ) ] )
info . append ( [ match . group ( 1 ) . strip ( ) , match . group ( 2 ) . strip ( ) ,
match . group ( 3 ) . strip ( ) , match . group ( 4 ) . strip ( ) ] )
files_info_ = rtn
files_info_ = info
return rtn
def getBestMergePaths ( url , revision ) :
"""
Takes an svn url and gets the associated revision .
"""
""" Takes an svn url and gets the associated revision. """
return getBestMergePaths2 ( getFileInfo ( url , revision ) , revision )
def getBestMergePaths2 ( files_info , revision ) :
"""
Takes an svn url and gets the associated revision .
"""
""" Takes an svn url and gets the associated revision. """
map = dict ( )
for file_info in files_info :
@ -258,9 +268,7 @@ def getBestExportPathsMap(url, revision):
return getBestExportPathsMap2 ( getFileInfo ( url , revision ) , revision )
def getBestExportPathsMap2 ( files_info , revision ) :
"""
Takes an svn url and gets the associated revision .
"""
""" Takes an svn url and gets the associated revision. """
global export_map_
if export_map_ :
@ -273,16 +281,13 @@ def getBestExportPathsMap2(files_info, revision):
map [ file_info [ 2 ] + " / " + file_info [ 3 ] ] = " "
export_map_ = map
return map
def getBestDeletePathsMap ( url , revision ) :
return getBestDeletePathsMap2 ( getFileInfo ( url , revision ) , revision )
def getBestDeletePathsMap2 ( files_info , revision ) :
"""
Takes an svn url and gets the associated revision .
"""
""" Takes an svn url and gets the associated revision. """
global delete_map_
if delete_map_ :
@ -295,13 +300,13 @@ def getBestDeletePathsMap2(files_info, revision):
map [ file_info [ 2 ] + " / " + file_info [ 3 ] ] = " "
delete_map_ = map
return map
def getExistingFilesInRevision ( files_info ) :
"""
Checks for existing files in the revision , anything that ' s A will require
special treatment ( either a merge or an export + add )
""" Checks for existing files in the revision.
Anything that ' s A will require special treatment (either a merge or an
export + add )
"""
map = [ ]
for file_info in files_info :
@ -311,35 +316,35 @@ def getExistingFilesInRevision(files_info):
return map
def getAllFilesInRevision ( files_info ) :
"""
Checks for existing files in the revision , anything that ' s A will require
special treatment ( either a merge or an export + add )
""" Checks for existing files in the revision.
Anything that ' s A will require special treatment (either a merge or an
export + add )
"""
map = [ ]
for file_info in files_info :
map . append ( file_info [ 2 ] + " / " + file_info [ 3 ] )
return map
def prompt ( question ) :
p = None
answer = None
while not p :
print question + " [y|n]: "
p = sys . stdin . readline ( )
if p . lower ( ) . startswith ( ' n ' ) :
answer = sys . stdin . readline ( )
if answer . lower ( ) . startswith ( ' n ' ) :
return False
elif p . lower ( ) . startswith ( ' y ' ) :
elif answer . lower ( ) . startswith ( ' y ' ) :
return True
else :
p = None
answer = None
def text_prompt ( question , default ) :
print question + " [ " + default + " ]: "
p = sys . stdin . readline ( )
if p . strip ( ) == " " :
answer = sys . stdin . readline ( )
if answer . strip ( ) == " " :
return default
return p
return answer
def main ( argv = None ) :
BASE_URL = " svn://chrome-svn/chrome "
@ -349,6 +354,7 @@ def main(argv=None):
SKIP_CHECK_WORKING = True
PROMPT_FOR_AUTHOR = False
# Override the default properties if there is a drover.properties file.
global file_pattern_
if os . path . exists ( " drover.properties " ) :
file = open ( " drover.properties " )
@ -358,23 +364,7 @@ def main(argv=None):
file_pattern_ = FILE_PATTERN
if ( len ( sys . argv ) == 1 ) :
print " WARNING: Please use this tool in an empty directory (or at least one "
print " that you don ' t mind clobbering. "
print " REQUIRES: SVN 1.5+ "
print " NOTE: NO NEED TO CHECKOUT ANYTHING IN ADVANCE OF USING THIS TOOL. "
print " \n Valid parameters: "
print " \n [Merge from trunk to branch] "
print " <revision> --merge <branch_num> "
print " Example " + sys . argv [ 0 ] + " 12345 --merge 187 "
print " \n [Merge from trunk to branch, ignoring revision history] "
print " <revision> --mplus <branch_num> "
print " Example " + sys . argv [ 0 ] + " 12345 --mplus 187 "
print " \n [Revert from trunk] "
print " <revision> --revert "
print " Example " + sys . argv [ 0 ] + " 12345 --revert "
print " \n [Revert from branch] "
print " <revision> --revert <branch_num> "
print " Example " + sys . argv [ 0 ] + " 12345 --revert 187 "
print USAGE % { " app " : sys . argv [ 0 ] }
sys . exit ( 0 )
revision = int ( sys . argv [ 1 ] )
@ -407,14 +397,14 @@ def main(argv=None):
sys . exit ( 0 )
branch_url = BRANCH_URL . replace ( " $branch " , sys . argv [ 3 ] )
# Checkout everything but stuff that got added into a new dir
# Checkout everything but stuff that got added into a new dir
checkoutRevision ( url , revision , branch_url )
# Merge everything that changed
# Merge everything that changed
mergeRevision ( url , revision )
# "Export" files that were added from the source and add them to branch
# "Export" files that were added from the source and add them to branch
exportRevision ( url , revision )
# Delete directories that were deleted (file deletes are handled in the
# merge).
# Delete directories that were deleted (file deletes are handled in the
# merge).
deleteRevision ( url , revision )
elif sys . argv [ 2 ] in [ ' --revert ' , ' -r ' ] :
if ( len ( sys . argv ) == 4 ) :
@ -427,7 +417,7 @@ def main(argv=None):
print " Unknown parameter " + sys . argv [ 2 ]
sys . exit ( 0 )
# Check the base url so we actually find the author who made the change
# Check the base url so we actually find the author who made the change
author = getAuthor ( TRUNK_URL , revision )
filename = str ( revision ) + " .txt "