#!/usr/bin/env python """ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Copyright 2004-2005 Christopher Baus christopher@baus.net http://www.baus.net/svnpyblosxom Module: svnbirthday Author: C. Michael Pilato copyright 2004-2005 Christopher Baus Description: Obtain the date of the first revision in the life of a Subversion versioned resource. """ import sys import os.path from svn import core, client class NotFoundException(Exception): """The path provided was not found, either on disk, or as a URL.""" pass class NotVersionedException(Exception): """The path provided does not represent a Subversion versioned resource.""" pass class SubversionErrorException(Exception): """Some other Subversion problem occurred.""" pass def _get_birthday(path, pool): """Do the real work of getting the birthday for PATH, using POOL for allocations.""" # Ensure there is a runtime configuration to use. core.svn_config_ensure(None, pool) # Create client context baton. ctx_t = client.svn_client_ctx_t() # Tell the client context baton about a suite of rather useful # authentication providers providers = [client.svn_client_get_simple_provider(pool), client.svn_client_get_ssl_client_cert_file_provider(pool), client.svn_client_get_ssl_client_cert_pw_file_provider(pool), client.svn_client_get_ssl_server_trust_file_provider(pool), client.svn_client_get_username_provider(pool), ### add more here as they become available ] ctx_t.auth_baton = core.svn_auth_open(providers, pool) # Speaking of that runtime configuration -- let's make it # available to the client libraries. ctx_t.config = core.svn_config_get_config(None, pool) # See if the path looks like a Subversion URL. is_url = 0 idx = path.find('://') if idx != -1 and \ path[:idx] in ['http', 'https', 'file', 'svn', 'svn+ssh', ### add more here as they become available ]: is_url = 1 # If the path is a URL, we'll use HEAD as our "peg revision". If # it isn't, we'll use BASE. if is_url: end_rev = core.svn_opt_revision_t() end_rev.kind = core.svn_opt_revision_head else: end_rev = core.svn_opt_revision_t() end_rev.kind = core.svn_opt_revision_base # We'll always search back as far as we can (to revision 0). start_rev = core.svn_opt_revision_t() start_rev.kind = core.svn_opt_revision_number start_rev.value.number = 0 # Implement the log callback. We stuff our first piece of # revision data into an array, and ignore others. birthday = [None] def _log_cb(paths, revision, author, date, message, pool): birthday[0] = core.secs_from_timestr(date, pool) # Get the logs for this sucker. Now, this is cool -- if we are # using a new enough Subversion, we can really optimize this # bad-boy by using limits, and if not we just fall back to the old # Subversion 1.0 interface. try: client.svn_client_log2([path], start_rev, end_rev, 1, 0, 0, _log_cb, ctx_t, pool) except AttributeError: client.svn_client_log([path], end_rev, start_rev, 0, 0, _log_cb, ctx_t, pool) except core.SubversionException, e: if e.apr_err == core.SVN_ERR_RA_DAV_PATH_NOT_FOUND or \ e.apr_err == core.SVN_ERR_FS_NOT_FOUND or \ e.apr_err == core.SVN_ERR_WC_PATH_NOT_FOUND: raise NotFoundException(e) elif e.apr_err == core.SVN_ERR_ENTRY_NOT_FOUND or \ e.apr_err == core.SVN_ERR_UNVERSIONED_RESOURCE: raise NotVersionedException(e) else: raise SubversionErrorException(e) birthday = birthday[0] if not birthday: raise Exception("Unable to obtain a valid date.") return birthday def get_birthday(path): """Main interface (but only a wrapper that seeks to ensure proper APR initialization and teardown.""" pool = None teardown = 0 birthday = None try: # Initialize APR, create a top-level pool, and get to work. core.apr_initialize() teardown = 1 pool = core.svn_pool_create(None) birthday = _get_birthday(path, pool) finally: # Cleanup our pool (if we have one) and teardown APR (if it # was ever initialized). if pool: core.svn_pool_destroy(pool) if teardown: core.apr_terminate() return birthday def _bail(message): sys.stderr.write(message) sys.exit(1) if __name__ == "__main__": import time if len(sys.argv) < 2: _bail( """Usage: %s [PATH-OR-URL] Print the datestamp (in UTC) of the revision in which PATH-OR-URL first appeared under Subversion version control. """ % (os.path.basename(sys.argv[0]))) path = sys.argv[1] if path == '.': path = '' elif path[-1] == '/': path = path[:-1] try: birthday = get_birthday(path) sys.stdout.write("%s UTC\n" % (time.asctime(time.gmtime(birthday)))) sys.exit(0) except NotFoundException: _bail("Unable to find path '%s'.\n" % (path)) except NotVersionedException: _bail("Path '%s' is not a versioned resource.\n" % (path)) except SubversionErrorException, e: _bail("An unexcepted Subversion error occurred: %s\n" % e[0][0])