Improve error reporting of ValueExceptions emitted by rcsparse.
[cvs2svn.git] / contrib / renumber_branch.py
blobe929e3216b79594f3c7471e637fa0252bfe5797e
1 #! /usr/bin/python
3 # (Be in -*- python -*- mode.)
5 # ====================================================================
6 # Copyright (C) 2010 Cabot Communications Ltd. All rights reserved.
8 # This software is licensed as described in the file COPYING, which
9 # you should have received as part of this distribution. The terms
10 # are also available at http://subversion.tigris.org/license-1.html.
11 # If newer versions of this license are posted there, you may use a
12 # newer version instead, at your option.
14 # This software consists of voluntary contributions made by many
15 # individuals. For exact contribution history, see the revision
16 # history and logs, available at http://cvs2svn.tigris.org/.
17 # ====================================================================
19 """Usage: renumber_branch.py OLDREVNUM NEWREVNUM PATH...
21 WARNING: This modifies RCS files in-place. Make sure you only run
22 it on a _copy_ of your repository. And have backups.
24 Modify RCS files in PATH to renumber a revision and/or branch.
26 Will also renumber any revisions on the branch and any branches from
27 a renumbered revision. E.g. if you ask to renumber branch 1.3.5
28 to 1.3.99, it will also renumber revision 1.3.5.1 to 1.3.99.1, and
29 renumber branch 1.3.5.1.7 to 1.3.99.1.7. This is usually what you
30 want.
32 Originally written to correct a non-standard vendor branch number,
33 by renumbering the 1.1.2 branch to 1.1.1. This allows cvs2svn to
34 detect that it's a vendor branch.
36 This doesn't enforce all the rules about revision numbers. It is
37 possible to make invalid repositories using this tool.
39 This does try to detect if the specified revision number is already
40 in use, and fail in that case.
42 """
44 import sys
45 import os
47 sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
49 import cvs2svn_rcsparse
50 from rcs_file_filter import WriteRCSFileSink, FilterSink
52 class RenumberingFilter(FilterSink):
53 '''A filter that transforms all revision numbers using a
54 function provided to the constructor.'''
56 def __init__(self, sink, transform_revision_func):
57 '''Constructor.
59 SINK is the object we're wrapping. It must implement the
60 cvs2svn_rcsparse.Sink interface.
61 TRANSFORM_REVISION_FUNC is a function that takes a single
62 CVS revision number, as a string, and returns the
63 possibly-transformed revision number in the same format.
64 '''
65 FilterSink.__init__(self, sink)
66 self.transform_rev = transform_revision_func
68 def set_head_revision(self, revision):
69 FilterSink.set_head_revision(self, self.transform_rev(revision))
71 def set_principal_branch(self, branch_name):
72 FilterSink.set_principal_branch(self, self.transform_rev(branch_name))
74 def define_tag(self, name, revision):
75 FilterSink.define_tag(self, name, self.transform_rev(revision))
77 def define_revision(
78 self, revision, timestamp, author, state, branches, next
80 revision = self.transform_rev(revision)
81 branches = [self.transform_rev(b) for b in branches]
82 if next is not None:
83 next = self.transform_rev(next)
84 FilterSink.define_revision(
85 self, revision, timestamp, author, state, branches, next
88 def set_revision_info(self, revision, log, text):
89 FilterSink.set_revision_info(self, self.transform_rev(revision),
90 log, text)
93 def get_transform_func(rev_from, rev_to, force):
94 rev_from_z = '%s.0.%s' % tuple(rev_from.rsplit('.', 1))
95 rev_to_z = '%s.0.%s' % tuple(rev_to.rsplit('.', 1))
96 def transform_revision(revision):
97 if revision == rev_from or revision.startswith(rev_from + '.'):
98 revision = rev_to + revision[len(rev_from):]
99 elif revision == rev_from_z:
100 revision = rev_to_z
101 elif not force and (revision == rev_to or revision == rev_to_z
102 or revision.startswith(rev_to + '.')):
103 raise Exception('Target branch already exists')
104 return revision
105 return transform_revision
107 def process_file(filename, rev_from, rev_to, force):
108 func = get_transform_func(rev_from, rev_to, force)
109 tmp_filename = filename + '.tmp'
110 infp = open(filename, 'rb')
111 outfp = open(tmp_filename, 'wb')
112 try:
113 writer = WriteRCSFileSink(outfp)
114 revfilter = RenumberingFilter(writer, func)
115 cvs2svn_rcsparse.parse(infp, revfilter)
116 finally:
117 outfp.close()
118 infp.close()
119 os.rename(tmp_filename, filename)
121 def iter_files_in_dir(top_path):
122 for (dirpath, dirnames, filenames) in os.walk(top_path):
123 for name in filenames:
124 yield os.path.join(dirpath, name)
126 def iter_rcs_files(list_of_starting_paths, verbose=False):
127 for base_path in list_of_starting_paths:
128 if os.path.isfile(base_path) and base_path.endswith(',v'):
129 yield base_path
130 elif os.path.isdir(base_path):
131 for file_path in iter_files_in_dir(base_path):
132 if file_path.endswith(',v'):
133 yield file_path
134 elif verbose:
135 sys.stdout.write('File %s is being ignored.\n' % file_path)
136 elif verbose:
137 sys.stdout.write('PATH %s is being ignored.\n' % base_path)
139 def main():
140 if len(sys.argv) < 4 or '.' not in sys.argv[1] or '.' not in sys.argv[2]:
141 sys.stderr.write('Usage: %s OLDREVNUM NEWREVNUM PATH...\n' % (sys.argv[0],))
142 sys.exit(1)
144 rev_from = sys.argv[1]
145 rev_to = sys.argv[2]
146 force = False
147 for path in iter_rcs_files(sys.argv[3:], verbose=True):
148 sys.stdout.write('Processing %s...' % path)
149 process_file(path, rev_from, rev_to, force)
150 sys.stdout.write('done.\n')
152 if __name__ == '__main__':
153 main()