@ -2,129 +2,145 @@
# 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
if sys . platform == ' win32 ' :
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 ) +
command = ( " gcl upload " + str ( revision ) +
" --send_mail --no_try --no_presubmit --reviewers= " + author )
os . system ( command )
def getSVNInfo ( url , revision ) :
command = ' svn info ' + url + " @ " + str ( revision )
svn_info = subprocess . Popen ( command ,
shell = True ,
stdout = subprocess . PIPE ,
stderr = subprocess . PIPE ) . stdout . readlines ( )
rtn = { }
svn_info = subprocess . Popen ( command ,
shell = True ,
stdout = subprocess . PIPE ,
stderr = subprocess . PIPE ) . stdout . readlines ( )
info = { }
for line in svn_info :
match = re . search ( r " (.*?):(.*) " , line )
if match :
rtn [ match . group ( 1 ) . strip ( ) ] = match . group ( 2 ) . strip ( )
return rtn
info [ match . group ( 1 ) . strip ( ) ] = match . group ( 2 ) . strip ( )
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
return False
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
return False
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 )
files_info = getFileInfo ( url , revision )
paths = getBestMergePaths2 ( files_info , revision )
export_map = getBestExportPathsMap2 ( files_info , revision )
command = ' svn checkout -N ' + branch_url
print command
os . system ( command )
match = re . search ( r " svn://.*/(.*) " , branch_url )
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,50 +150,49 @@ 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 )
command = ( ' svn update --depth empty ' + " . " + base )
print command
os . system ( command )
if ( revert ) :
files = getAllFilesInRevision ( files_info )
else :
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 + ' " ' )
print command
os . system ( command )
def mergeRevision ( url , revision ) :
paths = getBestMergePaths ( url , revision )
export_map = getBestExportPathsMap ( url , revision )
for path in paths :
if export_map . has_key ( path ) :
continue
command = ( ' svn merge -N -r ' + str ( revision - 1 ) + " : " + str ( revision ) + " " )
command = command + url + path + " @ " + str ( revision ) + " . " + path
print command
os . system ( command )
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 )
@ -185,20 +200,19 @@ def deleteRevision(url, revision):
paths = getBestDeletePathsMap ( url , revision ) . keys ( )
paths . sort ( )
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 )
@ -206,47 +220,43 @@ def revertRevision(url, revision):
paths = getBestMergePaths ( url , revision )
for path in paths :
command = ( ' svn merge -N -r ' + str ( revision ) + " : " + str ( revision - 1 ) +
" " + url + path + " . " + path )
" " + url + path + " . " + path )
print command
os . system ( command )
def getFileInfo ( url , revision ) :
global files_info_ , file_pattern_
if ( files_info_ != None ) :
return files_info_
command = ' svn log ' + url + " -r " + str ( revision ) + " -v "
svn_log = 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 ( )
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 :
@ -256,13 +266,11 @@ def getBestMergePaths2(files_info, revision):
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_ :
return export_map_
@ -273,18 +281,15 @@ 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 )
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_ :
return 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,36 +316,36 @@ 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 "
TRUNK_URL = BASE_URL + " /trunk/src "
@ -348,7 +353,8 @@ def main(argv=None):
DEFAULT_WORKING = " working "
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 ] )
@ -383,39 +373,39 @@ def main(argv=None):
else :
url = TRUNK_URL
action = " Merge "
working = DEFAULT_WORKING
command = ' svn log ' + url + " -r " + str ( revision ) + " -v "
os . system ( command )
if not prompt ( " Is this the correct revision? " ) :
sys . exit ( 0 )
if ( os . path . exists ( working ) ) :
if not ( SKIP_CHECK_WORKING or prompt ( " Working directory: ' " + working + " ' already exists, clobber? " ) ) :
sys . exit ( 0 )
deltree ( working )
os . makedirs ( working )
os . makedirs ( working )
os . chdir ( working )
if ( len ( sys . argv ) > 1 ) :
if sys . argv [ 2 ] in [ ' --merge ' , ' -m ' ] :
if ( len ( sys . argv ) != 4 ) :
print " Please specify the branch # you want (i.e. 182) after --merge "
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).
deleteRevision ( url , revision )
# 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 ) :
url = BRANCH_URL . replace ( " $branch " , sys . argv [ 3 ] )
@ -426,10 +416,10 @@ def main(argv=None):
else :
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 "
out = open ( filename , " w " )
out . write ( action + " " + str ( revision ) + " - " )
@ -437,12 +427,12 @@ def main(argv=None):
if ( author ) :
out . write ( " TBR= " + author )
out . close ( )
os . system ( ' gcl change ' + str ( revision ) + " " + filename )
os . unlink ( filename )
print author
print revision
print ( " gcl upload " + str ( revision ) +
print ( " gcl upload " + str ( revision ) +
" --send_mail --no_try --no_presubmit --reviewers= " + author )
print " gcl commit " + str ( revision ) + " --no_presubmit --force "
print " gcl delete " + str ( revision )
@ -460,6 +450,6 @@ def main(argv=None):
os . system ( " gcl commit " + str ( revision ) + " --no_presubmit --force " )
else :
sys . exit ( 0 )
if __name__ == " __main__ " :
sys . exit ( main ( ) )