11#!/usr/bin/env python
22#
3- # Copyright 2006 Google Inc. All Rights Reserved.
3+ # Copyright 2006, 2007 Google Inc. All Rights Reserved.
44# Author: danderson@google.com (David Anderson)
55#
66# Script for uploading files to a Google Code project.
2323# This is the password you use on googlecode.com for committing to
2424# Subversion and uploading files. You can find your password by going
2525# to http://code.google.com/hosting/settings when logged in with your
26- # Gmail account.
26+ # Gmail account. If you have already committed to your project's
27+ # Subversion repository, the script will automatically retrieve your
28+ # credentials from there (unless disabled, see the output of '--help'
29+ # for details).
2730#
2831# If you are looking at this script as a reference for implementing
2932# your own Google Code file uploader, then you should take a look at
3841#
3942# Questions, comments, feature requests and patches are most welcome.
4043# Please direct all of these to the Google Code users group:
41- # http://groups-beta .google.com/group/google-code-hosting
44+ # http://groups.google.com/group/google-code-hosting
4245
4346"""Google Code file uploader script.
4447"""
5053import optparse
5154import getpass
5255import base64
56+ import sys
57+
58+
59+ def get_svn_config_dir ():
60+ """Return user's Subversion configuration directory."""
61+ try :
62+ from win32com .shell .shell import SHGetFolderPath
63+ import win32com .shell .shellcon
64+ except ImportError :
65+ # If we can't import the win32api, just use ~; this is right on unix, and
66+ # returns not entirely unreasonable results on Windows.
67+ return os .path .expanduser ('~/.subversion' )
68+
69+ # We're on Windows with win32api; use APPDATA.
70+ return os .path .join (SHGetFolderPath (0 , win32com .shell .shellcon .CSIDL_APPDATA ,
71+ 0 , 0 ).encode ('utf-8' ),
72+ 'Subversion' )
73+
74+
75+ def get_svn_auth (project_name , config_dir ):
76+ """Return (username, password) for project_name in config_dir."""
77+
78+ # Default to returning nothing.
79+ result = (None , None )
80+
81+ try :
82+ from svn .core import SVN_AUTH_CRED_SIMPLE , svn_config_read_auth_data
83+ from svn .core import SubversionException
84+ except ImportError :
85+ return result
86+
87+ realm = ('<https://%s.googlecode.com:443> Google Code Subversion Repository'
88+ % project_name )
89+
90+ # auth may be none even if no exception is raised, e.g. if config_dir does
91+ # not exist, or exists but has no entry for realm.
92+ try :
93+ auth = svn_config_read_auth_data (SVN_AUTH_CRED_SIMPLE , realm , config_dir )
94+ except SubversionException :
95+ auth = None
96+
97+ if auth is not None :
98+ try :
99+ result = (auth ['username' ], auth ['password' ])
100+ except KeyError :
101+ # Missing the keys, so return nothing.
102+ pass
103+
104+ return result
53105
54106
55107def upload (file , project_name , user_name , password , summary , labels = None ):
@@ -71,7 +123,11 @@ def upload(file, project_name, user_name, password, summary, labels=None):
71123 file_url: If the upload succeeded, the URL of the file on Google
72124 Code, None otherwise.
73125 """
74- if '@' in user_name : user_name = user_name .index [: user_name .index ('@' )]
126+ # The login is the user part of user@gmail.com. If the login provided
127+ # is in the full user@domain form, strip it down.
128+ if user_name .endswith ('@gmail.com' ):
129+ user_name = user_name [:user_name .index ('@gmail.com' )]
130+
75131 form_fields = [('summary' , summary )]
76132 if labels is not None :
77133 form_fields .extend ([('label' , l .strip ()) for l in labels ])
@@ -124,7 +180,7 @@ def encode_upload_request(fields, file_path):
124180
125181 # Now add the file itself
126182 file_name = os .path .basename (file_path )
127- f = open (file_path )
183+ f = open (file_path , 'rb' )
128184 file_content = f .read ()
129185 f .close ()
130186
@@ -144,9 +200,74 @@ def encode_upload_request(fields, file_path):
144200 return 'multipart/form-data; boundary=%s' % BOUNDARY , CRLF .join (body )
145201
146202
203+ def upload_find_auth (file_path , project_name , summary , labels = None ,
204+ config_dir = None , user_name = None , tries = 3 ):
205+ """Find credentials and upload a file to a Google Code project's file server.
206+
207+ file_path, project_name, summary, and labels are passed as-is to upload.
208+
209+ If config_dir is None, try get_svn_config_dir(); if it is 'none', skip
210+ trying the Subversion configuration entirely. If user_name is not None, use
211+ it for the first attempt; prompt for subsequent attempts.
212+
213+ Args:
214+ file_path: The local path to the file.
215+ project_name: The name of your project on Google Code.
216+ summary: A small description for the file.
217+ labels: an optional list of label strings with which to tag the file.
218+ config_dir: Path to Subversion configuration directory, 'none', or None.
219+ user_name: Your Google account name.
220+ tries: How many attempts to make.
221+ """
222+
223+ if config_dir != 'none' :
224+ # Try to load username/password from svn config for first try.
225+ if config_dir is None :
226+ config_dir = get_svn_config_dir ()
227+ (svn_username , password ) = get_svn_auth (project_name , config_dir )
228+ if user_name is None :
229+ # If username was not supplied by caller, use svn config.
230+ user_name = svn_username
231+ else :
232+ # Just initialize password for the first try.
233+ password = None
234+
235+ while tries > 0 :
236+ if user_name is None :
237+ # Read username if not specified or loaded from svn config, or on
238+ # subsequent tries.
239+ sys .stdout .write ('Please enter your googlecode.com username: ' )
240+ sys .stdout .flush ()
241+ user_name = sys .stdin .readline ().rstrip ()
242+ if password is None :
243+ # Read password if not loaded from svn config, or on subsequent tries.
244+ print 'Please enter your googlecode.com password.'
245+ print '** Note that this is NOT your Gmail account password! **'
246+ print 'It is the password you use to access Subversion repositories,'
247+ print 'and can be found here: http://code.google.com/hosting/settings'
248+ password = getpass .getpass ()
249+
250+ status , reason , url = upload (file_path , project_name , user_name , password ,
251+ summary , labels )
252+ # Returns 403 Forbidden instead of 401 Unauthorized for bad
253+ # credentials as of 2007-07-17.
254+ if status in [httplib .FORBIDDEN , httplib .UNAUTHORIZED ]:
255+ # Rest for another try.
256+ user_name = password = None
257+ tries = tries - 1
258+ else :
259+ # We're done.
260+ break
261+
262+ return status , reason , url
263+
264+
147265def main ():
148266 parser = optparse .OptionParser (usage = 'googlecode-upload.py -s SUMMARY '
149- '-p PROJECT -u USERNAME FILE' )
267+ '-p PROJECT [options] FILE' )
268+ parser .add_option ('--config-dir' , dest = 'config_dir' , metavar = 'DIR' ,
269+ help = 'read svn auth data from DIR'
270+ ' ("none" means not to use svn auth data)' )
150271 parser .add_option ('-s' , '--summary' , dest = 'summary' ,
151272 help = 'Short description of the file' )
152273 parser .add_option ('-p' , '--project' , dest = 'project' ,
@@ -162,31 +283,30 @@ def main():
162283 parser .error ('File summary is missing.' )
163284 elif not options .project :
164285 parser .error ('Project name is missing.' )
165- elif not options .user :
166- parser .error ('User name is missing.' )
167286 elif len (args ) < 1 :
168287 parser .error ('File to upload not provided.' )
288+ elif len (args ) > 1 :
289+ parser .error ('Only one file may be specified.' )
169290
170- print 'Please enter your googlecode.com password.'
171- print 'Note that this is NOT your main Gmail account password!'
172- password = getpass .getpass ()
173291 file_path = args [0 ]
174292
175293 if options .labels :
176294 labels = options .labels .split (',' )
177295 else :
178296 labels = None
179297
180- status , reason , url = upload (file_path , options .project ,
181- options .user , password ,
182- options .summary , labels )
298+ status , reason , url = upload_find_auth (file_path , options .project ,
299+ options .summary , labels ,
300+ options .config_dir , options . user )
183301 if url :
184302 print 'The file was uploaded successfully.'
185303 print 'URL: %s' % url
304+ return 0
186305 else :
187306 print 'An error occurred. Your file was not uploaded.'
188307 print 'Google Code upload server said: %s (%s)' % (reason , status )
308+ return 1
189309
190310
191311if __name__ == '__main__' :
192- main ()
312+ sys . exit ( main () )
0 commit comments