@ -1,4 +1,5 @@
#!/usr/bin/env python
#!/usr/bin/env python
# coding: utf-8
#
#
# Copyright 2007 Google Inc.
# Copyright 2007 Google Inc.
#
#
@ -215,7 +216,7 @@ class AbstractRpcServer(object):
def _CreateRequest ( self , url , data = None ) :
def _CreateRequest ( self , url , data = None ) :
""" Creates a new urllib request. """
""" Creates a new urllib request. """
logging . debug ( " Creating request for: ' %s ' with payload: \n %s " , url , data )
logging . debug ( " Creating request for: ' %s ' with payload: \n %s " , url , data )
req = urllib2 . Request ( url , data = data )
req = urllib2 . Request ( url , data = data , headers = { " Accept " : " text/plain " } )
if self . host_override :
if self . host_override :
req . add_header ( " Host " , self . host_override )
req . add_header ( " Host " , self . host_override )
for key , value in self . extra_headers . iteritems ( ) :
for key , value in self . extra_headers . iteritems ( ) :
@ -399,14 +400,13 @@ class AbstractRpcServer(object):
raise
raise
elif e . code == 401 or e . code == 302 :
elif e . code == 401 or e . code == 302 :
self . _Authenticate ( )
self . _Authenticate ( )
## elif e.code >= 500 and e.code < 600:
## # Server Error - try again.
## continue
elif e . code == 301 :
elif e . code == 301 :
# Handle permanent redirect manually.
# Handle permanent redirect manually.
url = e . info ( ) [ " location " ]
url = e . info ( ) [ " location " ]
url_loc = urlparse . urlparse ( url )
url_loc = urlparse . urlparse ( url )
self . host = ' %s :// %s ' % ( url_loc [ 0 ] , url_loc [ 1 ] )
self . host = ' %s :// %s ' % ( url_loc [ 0 ] , url_loc [ 1 ] )
elif e . code > = 500 :
ErrorExit ( e . read ( ) )
else :
else :
raise
raise
finally :
finally :
@ -532,14 +532,13 @@ group.add_option("--account_type", action="store", dest="account_type",
" valid choices are ' GOOGLE ' and ' HOSTED ' ). " ) )
" valid choices are ' GOOGLE ' and ' HOSTED ' ). " ) )
# Issue
# Issue
group = parser . add_option_group ( " Issue options " )
group = parser . add_option_group ( " Issue options " )
group . add_option ( " -d " , " --description " , action = " store " , dest = " description " ,
group . add_option ( " -t " , " --title " , action = " store " , dest = " title " ,
metavar = " DESCRIPTION " , default = None ,
help = " New issue subject or new patch set title " )
help = " Optional description when creating an issue. " )
group . add_option ( " -m " , " --message " , action = " store " , dest = " message " ,
group . add_option ( " -f " , " --description_file " , action = " store " ,
dest = " description_file " , metavar = " DESCRIPTION_FILE " ,
default = None ,
default = None ,
help = " Optional path of a file that contains "
help = " New issue description or new patch set message " )
" the description when creating an issue. " )
group . add_option ( " -F " , " --file " , action = " store " , dest = " file " ,
default = None , help = " Read the message above from file. " )
group . add_option ( " -r " , " --reviewers " , action = " store " , dest = " reviewers " ,
group . add_option ( " -r " , " --reviewers " , action = " store " , dest = " reviewers " ,
metavar = " REVIEWERS " , default = None ,
metavar = " REVIEWERS " , default = None ,
help = " Add reviewers (comma separated email addresses). " )
help = " Add reviewers (comma separated email addresses). " )
@ -551,10 +550,6 @@ group.add_option("--private", action="store_true", dest="private",
help = " Make the issue restricted to reviewers and those CCed " )
help = " Make the issue restricted to reviewers and those CCed " )
# Upload options
# Upload options
group = parser . add_option_group ( " Patch options " )
group = parser . add_option_group ( " Patch options " )
group . add_option ( " -m " , " --message " , action = " store " , dest = " message " ,
metavar = " MESSAGE " , default = None ,
help = " A message to identify the patch. "
" Will prompt if omitted. " )
group . add_option ( " -i " , " --issue " , type = " int " , action = " store " ,
group . add_option ( " -i " , " --issue " , type = " int " , action = " store " ,
metavar = " ISSUE " , default = None ,
metavar = " ISSUE " , default = None ,
help = " Issue number to which to add. Defaults to new issue. " )
help = " Issue number to which to add. Defaults to new issue. " )
@ -1310,7 +1305,7 @@ class GitVCS(VersionControlSystem):
# then the diff of everything except deleted files with rename and copy
# then the diff of everything except deleted files with rename and copy
# support enabled.
# support enabled.
cmd = [
cmd = [
" git " , " diff " , " --no- ext-diff" , " --full-index " , " --ignore-submodules "
" git " , " diff " , " --no- color" , " --no- ext-diff" , " --full-index " , " --ignore-submodules "
]
]
diff = RunShell ( cmd + [ " --diff-filter=D " ] + extra_args , env = env ,
diff = RunShell ( cmd + [ " --diff-filter=D " ] + extra_args , env = env ,
silent_ok = True )
silent_ok = True )
@ -1464,11 +1459,13 @@ class MercurialVCS(VersionControlSystem):
def _GetRelPath ( self , filename ) :
def _GetRelPath ( self , filename ) :
""" Get relative path of a file according to the current directory,
""" Get relative path of a file according to the current directory,
given its logical path in the repo . """
given its logical path in the repo . """
assert filename . startswith ( self . subdir ) , ( filename , self . subdir )
absname = os . path . join ( self . repo_dir , filename )
return filename[ len ( self . subdir ) : ] . lstrip ( r " \ / " )
return os. path . relpath ( absname )
def GenerateDiff ( self , extra_args ) :
def GenerateDiff ( self , extra_args ) :
cmd = [ " hg " , " diff " , " --git " , " -r " , self . base_rev ] + extra_args
cmd = [
" hg " , " diff " , " --color " , " never " , " --git " , " -r " , self . base_rev
] + extra_args
data = RunShell ( cmd , silent_ok = True )
data = RunShell ( cmd , silent_ok = True )
svndiff = [ ]
svndiff = [ ]
filecount = 0
filecount = 0
@ -1494,7 +1491,8 @@ class MercurialVCS(VersionControlSystem):
def GetUnknownFiles ( self ) :
def GetUnknownFiles ( self ) :
""" Return a list of files unknown to the VCS. """
""" Return a list of files unknown to the VCS. """
args = [ ]
args = [ ]
status = RunShell ( [ " hg " , " status " , " --rev " , self . base_rev , " -u " , " . " ] ,
status = RunShell (
[ " hg " , " status " , " --color " , " never " , " --rev " , self . base_rev , " -u " , " . " ] ,
silent_ok = True )
silent_ok = True )
unknown_files = [ ]
unknown_files = [ ]
for line in status . splitlines ( ) :
for line in status . splitlines ( ) :
@ -1504,15 +1502,16 @@ class MercurialVCS(VersionControlSystem):
return unknown_files
return unknown_files
def GetBaseFile ( self , filename ) :
def GetBaseFile ( self , filename ) :
# "hg status" and "hg cat" both take a path relative to the current subdir
# "hg status" and "hg cat" both take a path relative to the current subdir,
# rather than to the repo root, but "hg diff" has given us the full path
# but "hg diff" has given us the path relative to the repo root.
# to the repo root.
base_content = " "
base_content = " "
new_content = None
new_content = None
is_binary = False
is_binary = False
oldrelpath = relpath = self . _GetRelPath ( filename )
oldrelpath = relpath = self . _GetRelPath ( filename )
# "hg status -C" returns two lines for moved/copied files, one otherwise
# "hg status -C" returns two lines for moved/copied files, one otherwise
out = RunShell ( [ " hg " , " status " , " -C " , " --rev " , self . base_rev , relpath ] )
out = RunShell (
[ " hg " , " status " , " --color " , " never " , " -C " , " --rev " , self . base_rev ,
relpath ] )
out = out . splitlines ( )
out = out . splitlines ( )
# HACK: strip error message about missing file/directory if it isn't in
# HACK: strip error message about missing file/directory if it isn't in
# the working copy
# the working copy
@ -1575,15 +1574,15 @@ class PerforceVCS(VersionControlSystem):
ConfirmLogin ( )
ConfirmLogin ( )
if not options . messag e:
if not options . titl e:
description = self . RunPerforceCommand ( [ " describe " , self . p4_changelist ] ,
description = self . RunPerforceCommand ( [ " describe " , self . p4_changelist ] ,
marshal_output = True )
marshal_output = True )
if description and " desc " in description :
if description and " desc " in description :
# Rietveld doesn't support multi-line descriptions
# Rietveld doesn't support multi-line descriptions
raw_ messag e = description [ " desc " ] . strip ( )
raw_ titl e = description [ " desc " ] . strip ( )
lines = raw_ messag e. splitlines ( )
lines = raw_ titl e. splitlines ( )
if len ( lines ) :
if len ( lines ) :
options . messag e = lines [ 0 ]
options . titl e = lines [ 0 ]
def GetGUID ( self ) :
def GetGUID ( self ) :
""" For now we don ' t know how to get repository ID for Perforce """
""" For now we don ' t know how to get repository ID for Perforce """
@ -1974,10 +1973,12 @@ def GuessVCSName(options):
if res != None :
if res != None :
return res
return res
# Subversion has a .svn in all working directories.
# Subversion from 1.7 has a single centralized .svn folder
if os . path . isdir ( ' .svn ' ) :
# ( see http://subversion.apache.org/docs/release-notes/1.7.html#wc-ng )
logging . info ( " Guessed VCS = Subversion " )
# That's why we use 'svn info' instead of checking for .svn dir
return ( VCS_SUBVERSION , None )
res = RunDetectCommand ( VCS_SUBVERSION , [ " svn " , " info " ] )
if res != None :
return res
# Git has a command to test if you're in a git tree.
# Git has a command to test if you're in a git tree.
# Try running it, but don't die if we don't have git installed.
# Try running it, but don't die if we don't have git installed.
@ -2219,19 +2220,12 @@ def RealMain(argv, data=None):
files = vcs . GetBaseFiles ( data )
files = vcs . GetBaseFiles ( data )
if verbosity > = 1 :
if verbosity > = 1 :
print " Upload server: " , options . server , " (change with -s/--server) "
print " Upload server: " , options . server , " (change with -s/--server) "
if options . issue :
prompt = " Message describing this patch set: "
else :
prompt = " New issue subject: "
message = options . message or raw_input ( prompt ) . strip ( )
if not message :
ErrorExit ( " A non-empty message is required " )
rpc_server = GetRpcServer ( options . server ,
rpc_server = GetRpcServer ( options . server ,
options . email ,
options . email ,
options . host ,
options . host ,
options . save_cookies ,
options . save_cookies ,
options . account_type )
options . account_type )
form_fields = [ (" subject " , message ) ]
form_fields = [ ]
repo_guid = vcs . GetGUID ( )
repo_guid = vcs . GetGUID ( )
if repo_guid :
if repo_guid :
@ -2256,15 +2250,40 @@ def RealMain(argv, data=None):
for cc in options . cc . split ( ' , ' ) :
for cc in options . cc . split ( ' , ' ) :
CheckReviewer ( cc )
CheckReviewer ( cc )
form_fields . append ( ( " cc " , options . cc ) )
form_fields . append ( ( " cc " , options . cc ) )
description = options . description
if options . description_file :
# Process --message, --title and --file.
if options . description :
message = options . message or " "
ErrorExit ( " Can ' t specify description and description_file " )
title = options . title or " "
file = open ( options . description_file , ' r ' )
if options . file :
description = file . read ( )
if options . message :
ErrorExit ( " Can ' t specify both message and message file options " )
file = open ( options . file , ' r ' )
message = file . read ( )
file . close ( )
file . close ( )
if description :
if options . issue :
form_fields . append ( ( " description " , description ) )
prompt = " Title describing this patch set: "
else :
prompt = " New issue subject: "
title = (
title or message . split ( ' \n ' , 1 ) [ 0 ] . strip ( ) or raw_input ( prompt ) . strip ( ) )
if not title and not options . issue :
ErrorExit ( " A non-empty title is required for a new issue " )
# For existing issues, it's fine to give a patchset an empty name. Rietveld
# doesn't accept that so use a whitespace.
title = title or " "
if len ( title ) > 100 :
title = title [ : 99 ] + ' … '
if title and not options . issue :
message = message or title
form_fields . append ( ( " subject " , title ) )
if message :
if not options . issue :
form_fields . append ( ( " description " , message ) )
else :
# TODO: [ ] Use /<issue>/publish to add a comment.
pass
# Send a hash of all the base file so the server can determine if a copy
# Send a hash of all the base file so the server can determine if a copy
# already exists in an earlier patchset.
# already exists in an earlier patchset.
base_hashes = " "
base_hashes = " "
@ -2282,10 +2301,6 @@ def RealMain(argv, data=None):
form_fields . append ( ( " private " , " 1 " ) )
form_fields . append ( ( " private " , " 1 " ) )
if options . send_patch :
if options . send_patch :
options . send_mail = True
options . send_mail = True
# If we're uploading base files, don't send the email before the uploads, so
# that it contains the file status.
if options . send_mail and options . download_base :
form_fields . append ( ( " send_mail " , " 1 " ) )
if not options . download_base :
if not options . download_base :
form_fields . append ( ( " content_upload " , " 1 " ) )
form_fields . append ( ( " content_upload " , " 1 " ) )
if len ( data ) > MAX_UPLOAD_SIZE :
if len ( data ) > MAX_UPLOAD_SIZE :
@ -2320,11 +2335,15 @@ def RealMain(argv, data=None):
if not options . download_base :
if not options . download_base :
vcs . UploadBaseFiles ( issue , rpc_server , patches , patchset , options , files )
vcs . UploadBaseFiles ( issue , rpc_server , patches , patchset , options , files )
payload = { } # payload for final request
if options . send_mail :
if options . send_mail :
payload = " "
payload [ " send_mail " ] = " yes "
if options . send_patch :
if options . send_patch :
payload = urllib . urlencode ( { " attach_patch " : " yes " } )
payload [ " attach_patch " ] = " yes "
rpc_server . Send ( " / " + issue + " /mail " , payload = payload )
payload = urllib . urlencode ( payload )
rpc_server . Send ( " / " + issue + " /upload_complete/ " + ( patchset or " " ) ,
payload = payload )
return issue , patchset
return issue , patchset