#!/usr/bin/env python # # (c) 2010 Allan Anderson # # quick and dirty subversion to perforce importer # # requires pysvn (http://pysvn.tigris.org/) and P4Python # import pysvn from P4 import P4,P4Exception from shutil import copyfile, copytree, ignore_patterns, move, rmtree from datetime import datetime import os import logging import logging.config from itertools import izip, tee # Set the following variables to match your environment # # SVN working directory svn_path = '/home/a/Source/svnproject/trunk' # P4 workspace root p4client_path = '/home/a/Source/p4root' # first revision we care about importing START_REVISION = 1 REVISON_FILE = './completed_revision' # Connect to Perforce # must create the clientspec ahead of time p4 = P4() p4.port = "localhost:1666" p4.user = "allan_anderson" p4.client = "importworkspace" # No need to edit below this line try: p4.connect() except P4Exception: for e in p4.errors: print e sys.exit(1) logging.config.fileConfig("svn2pylogging.conf") logger = logging.getLogger("svn2p4") # snagged from the itertools recipes page def pairwise(iterable): "s -> (s0,s1), (s1,s2), (s2, s3), ..." a, b = tee(iterable) next(b, None) return izip(a, b) svn = pysvn.Client() first_revision = pysvn.Revision(pysvn.opt_revision_kind.number,START_REVISION) if not os.path.exists(REVISON_FILE): working_revision = svn.update(svn_path,revision=first_revision) # initial copy and add if os.path.exists(p4client_path): #p4.run('revert',p4client_path) rmtree(p4client_path) copytree(svn_path,p4client_path, ignore=ignore_patterns('.svn*')) for root, dirs, files in os.walk(p4client_path): if files: try: p4.run('add', [os.path.join(root,name) for name in files]) except P4Exception: for e in p4.errors: logger.error(e) svn_log = \ svn.log( svn_path, revision_start=first_revision, revision_end=first_revision,) change_description = 'Revision: ' + str(working_revision[0].number) + \ '\t\tDate: ' + datetime.fromtimestamp(svn_log[0].data['date']).isoformat() + \ '\nAuthor: ' + svn_log[0].data['author'] + \ '\nMessage: ' + svn_log[0].data['message'] p4.run_submit('-d',change_description) logger.info("Submitted first Perforce change, Svn Rev " + str(working_revision[0].number) + \ " by "+ svn_log[0].data['author']) else: with open(REVISON_FILE) as revision_file: for last_completed_revision in revision_file: first_revision = pysvn.Revision(pysvn.opt_revision_kind.number,last_completed_revision) # Move on to the rest of the changes head_revision = pysvn.Revision(pysvn.opt_revision_kind.head) svn_log = \ svn.log( svn_path, revision_start=first_revision, revision_end=head_revision,) # list of pysvn Revision objects in the svn directory to be imported dm_revisions = [ log.data['revision'] for log in svn_log ] # loop over the revisions and compare them like this: 1vs2, 2vs3, 3vs4... for previous_rev, current_rev in pairwise(dm_revisions): # find which files have changed between the last revision # and the one we are importing summary = \ svn.diff_summarize( svn_path, revision1=previous_rev, revision2=current_rev) # extract lists of the added, modified, and deleted files (not dirs) svn_added = [ diff.data['path'] for diff in summary if diff.data['summarize_kind']==pysvn.diff_summarize_kind.added and diff.data['node_kind']==pysvn.node_kind.file] svn_modified = [ diff.data['path'] for diff in summary if diff.data['summarize_kind']==pysvn.diff_summarize_kind.modified and diff.data['node_kind']==pysvn.node_kind.file] svn_delete = [ diff.data['path'] for diff in summary if diff.data['summarize_kind']==pysvn.diff_summarize_kind.delete and diff.data['node_kind']==pysvn.node_kind.file] # Update the local SVN working directory to the next revision # so we can start copying over the files to the p4 workspace working_revision = svn.update(svn_path,revision=current_rev) # Apply the Svn file changes to the P4 workspace for deleted_file in svn_delete: p4_deleted_file = os.path.join(p4client_path, deleted_file) try: p4.run('delete', p4_deleted_file) except P4Exception: for e in p4.errors: logger.error(e) for added_file in svn_added: p4_added_file = os.path.join(p4client_path, added_file) if not os.path.exists(os.path.dirname(p4_added_file)): os.makedirs(os.path.dirname(p4_added_file)) copyfile(os.path.join(svn_path, added_file), p4_added_file) try: p4.run('add', p4_added_file) except P4Exception: for e in p4.errors: logger.error(e) for edited_file in svn_modified: p4_edited_file = os.path.join(p4client_path, edited_file) if not os.path.exists(os.path.dirname(p4_edited_file)): os.makedirs(os.path.dirname(p4_edited_file)) try: p4.run('edit', p4_edited_file) except P4Exception: for e in p4.errors: logger.error(e) p4.run('add', p4_edited_file) # should never have to do this copyfile(os.path.join(svn_path, edited_file), p4_edited_file) # Now that we've deleted, added, and edited all files changed in this revision, # get the log info and submit svn_log = \ svn.log( svn_path, revision_start=current_rev, revision_end=current_rev,) change_description = 'Revision: ' + str(current_rev.number) + \ '\t\tDate: ' + datetime.fromtimestamp(svn_log[0].data['date']).isoformat() + \ '\nAuthor: ' + svn_log[0].data['author'] + \ '\nMessage: ' + svn_log[0].data['message'] try: p4.run_submit('-d',change_description) except P4Exception: for e in p4.errors: logger.error(e) logger.info("Submitted Perforce change for Svn Rev " + str(current_rev.number) + \ " by " + svn_log[0].data['author']) # keep track of last imported revision so we can restart the import rev_file = open(REVISON_FILE,'w') rev_file.write(str(current_rev.number)) rev_file.close() logger.info("Import complete!")
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 7612 | Allan Anderson | Rename/move file(s) to avoid confusion with another earlier project | ||
//guest/allan_anderson/svn2p4/svn2p4.py | |||||
#3 | 7560 | Allan Anderson | And fix an indentation bug. | ||
#2 | 7559 | Allan Anderson |
Tidy up. Forgot to use the filename stored in a variable rather than re-construct it. |
||
#1 | 7558 | Allan Anderson |
This is my quick and dirty script to import Svn revisions -> P4 changelists. It doesn't handle branches or tags; it is intended to simply pick up a project and place it somewhere in a Perforce depot. In our case, we have used it to preserve changes history when moving another group to the main company repository. It's not the complete respository migration tool that the Perl-based svn2p4 is, but it may be useful to some people. It owes some ideas to the Perl script, including the extremely useful trick of saving the last successfully imported revision number in a file to allow interrupted imports to resume. |