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(__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
):
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.
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
))
80 self
, revision
, timestamp
, author
, state
, branches
, next
82 revision
= self
.transform_rev(revision
)
83 branches
= [self
.transform_rev(b
) for b
in branches
]
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
),
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
:
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')
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')
115 writer
= WriteRCSFileSink(outfp
)
116 revfilter
= RenumberingFilter(writer
, func
)
117 parse(infp
, revfilter
)
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'):
132 elif os
.path
.isdir(base_path
):
133 for file_path
in iter_files_in_dir(base_path
):
134 if file_path
.endswith(',v'):
137 sys
.stdout
.write('File %s is being ignored.\n' % file_path
)
139 sys
.stdout
.write('PATH %s is being ignored.\n' % base_path
)
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],))
146 rev_from
= sys
.argv
[1]
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__':