Bring CHANGES up to date.
[cvs2svn.git] / contrib / renumber_branch.py
blob9d539410ac8f8fac059d3b0c3ddecad2a6b8f4f6
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 from cvs2svn_lib.rcsparser import parse
50 from rcs_file_filter import WriteRCSFileSink
51 from rcs_file_filter import FilterSink
54 class RenumberingFilter(FilterSink):
55 '''A filter that transforms all revision numbers using a
56 function provided to the constructor.'''
58 def __init__(self, sink, transform_revision_func):
59 '''Constructor.
61 SINK is the object we're wrapping. It must implement the
62 cvs2svn_rcsparse.Sink interface.
63 TRANSFORM_REVISION_FUNC is a function that takes a single
64 CVS revision number, as a string, and returns the
65 possibly-transformed revision number in the same format.
66 '''
67 FilterSink.__init__(self, sink)
68 self.transform_rev = transform_revision_func
70 def set_head_revision(self, revision):
71 FilterSink.set_head_revision(self, self.transform_rev(revision))
73 def set_principal_branch(self, branch_name):
74 FilterSink.set_principal_branch(self, self.transform_rev(branch_name))
76 def define_tag(self, name, revision):
77 FilterSink.define_tag(self, name, self.transform_rev(revision))
79 def define_revision(
80 self, revision, timestamp, author, state, branches, next
82 revision = self.transform_rev(revision)
83 branches = [self.transform_rev(b) for b in branches]
84 if next is not None:
85 next = self.transform_rev(next)
86 FilterSink.define_revision(
87 self, revision, timestamp, author, state, branches, next
90 def set_revision_info(self, revision, log, text):
91 FilterSink.set_revision_info(self, self.transform_rev(revision),
92 log, text)
95 def get_transform_func(rev_from, rev_to, force):
96 rev_from_z = '%s.0.%s' % tuple(rev_from.rsplit('.', 1))
97 rev_to_z = '%s.0.%s' % tuple(rev_to.rsplit('.', 1))
98 def transform_revision(revision):
99 if revision == rev_from or revision.startswith(rev_from + '.'):
100 revision = rev_to + revision[len(rev_from):]
101 elif revision == rev_from_z:
102 revision = rev_to_z
103 elif not force and (revision == rev_to or revision == rev_to_z
104 or revision.startswith(rev_to + '.')):
105 raise Exception('Target branch already exists')
106 return revision
107 return transform_revision
109 def process_file(filename, rev_from, rev_to, force):
110 func = get_transform_func(rev_from, rev_to, force)
111 tmp_filename = filename + '.tmp'
112 infp = open(filename, 'rb')
113 outfp = open(tmp_filename, 'wb')
114 try:
115 writer = WriteRCSFileSink(outfp)
116 revfilter = RenumberingFilter(writer, func)
117 parse(infp, revfilter)
118 finally:
119 outfp.close()
120 infp.close()
121 os.rename(tmp_filename, filename)
123 def iter_files_in_dir(top_path):
124 for (dirpath, dirnames, filenames) in os.walk(top_path):
125 for name in filenames:
126 yield os.path.join(dirpath, name)
128 def iter_rcs_files(list_of_starting_paths, verbose=False):
129 for base_path in list_of_starting_paths:
130 if os.path.isfile(base_path) and base_path.endswith(',v'):
131 yield base_path
132 elif os.path.isdir(base_path):
133 for file_path in iter_files_in_dir(base_path):
134 if file_path.endswith(',v'):
135 yield file_path
136 elif verbose:
137 sys.stdout.write('File %s is being ignored.\n' % file_path)
138 elif verbose:
139 sys.stdout.write('PATH %s is being ignored.\n' % base_path)
141 def main():
142 if len(sys.argv) < 4 or '.' not in sys.argv[1] or '.' not in sys.argv[2]:
143 sys.stderr.write('Usage: %s OLDREVNUM NEWREVNUM PATH...\n' % (sys.argv[0],))
144 sys.exit(1)
146 rev_from = sys.argv[1]
147 rev_to = sys.argv[2]
148 force = False
149 for path in iter_rcs_files(sys.argv[3:], verbose=True):
150 sys.stdout.write('Processing %s...' % path)
151 process_file(path, rev_from, rev_to, force)
152 sys.stdout.write('done.\n')
154 if __name__ == '__main__':
155 main()