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
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.
47 sys
.path
.insert(0, os
.path
.dirname(os
.path
.dirname(os
.path
.abspath(sys
.argv
[0]))))
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
):
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.
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
))
78 self
, revision
, timestamp
, author
, state
, branches
, next
80 revision
= self
.transform_rev(revision
)
81 branches
= [self
.transform_rev(b
) for b
in branches
]
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
),
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
:
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')
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')
113 writer
= WriteRCSFileSink(outfp
)
114 revfilter
= RenumberingFilter(writer
, func
)
115 cvs2svn_rcsparse
.parse(infp
, revfilter
)
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'):
130 elif os
.path
.isdir(base_path
):
131 for file_path
in iter_files_in_dir(base_path
):
132 if file_path
.endswith(',v'):
135 sys
.stdout
.write('File %s is being ignored.\n' % file_path
)
137 sys
.stdout
.write('PATH %s is being ignored.\n' % base_path
)
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],))
144 rev_from
= sys
.argv
[1]
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__':