* cvs2svn: Use gnu_getopt when available (Python >= 2.3) for more flexible
[cvs2svn.git] / cvs2svn_lib / cvs_repository.py
blobcec40f97026578f042e854a4121aba8199c3a4ff
1 # (Be in -*- python -*- mode.)
3 # ====================================================================
4 # Copyright (c) 2000-2006 CollabNet. All rights reserved.
6 # This software is licensed as described in the file COPYING, which
7 # you should have received as part of this distribution. The terms
8 # are also available at http://subversion.tigris.org/license-1.html.
9 # If newer versions of this license are posted there, you may use a
10 # newer version instead, at your option.
12 # This software consists of voluntary contributions made by many
13 # individuals. For exact contribution history, see the revision
14 # history and logs, available at http://cvs2svn.tigris.org/.
15 # ====================================================================
17 """This module provides access to the CVS repository for cvs2svn."""
20 import os
21 import re
23 from boolean import *
24 from common import FatalError
25 from process import \
26 check_command_runs, \
27 SimplePopen, \
28 CommandFailedException
31 class CVSRepository:
32 """A CVS repository from which data can be extracted."""
34 def __init__(self, cvs_repos_path):
35 """CVS_REPOS_PATH is the top of the CVS repository (at least as
36 far as this run is concerned)."""
38 if not os.path.isdir(cvs_repos_path):
39 raise FatalError("The specified CVS repository path '%s' is not an "
40 "existing directory." % cvs_repos_path)
42 self.cvs_repos_path = os.path.normpath(cvs_repos_path)
43 self.cvs_prefix_re = re.compile(
44 r'^' + re.escape(self.cvs_repos_path)
45 + r'(' + re.escape(os.sep) + r'|$)')
47 def get_cvs_path(self, fname):
48 """Return the path to FNAME relative to cvs_repos_path, with ',v' removed.
50 FNAME is a filesystem name that has to be within
51 self.cvs_repos_path. Return the filename relative to
52 self.cvs_repos_path, with ',v' striped off if present, and with
53 os.sep converted to '/'."""
55 (tail, n) = self.cvs_prefix_re.subn('', fname, 1)
56 if n != 1:
57 raise FatalError(
58 "get_cvs_path: '%s' is not a sub-path of '%s'"
59 % (fname, self.cvs_repos_path,))
60 if tail.endswith(',v'):
61 tail = tail[:-2]
62 return tail.replace(os.sep, '/')
64 def get_co_pipe(self, c_rev, suppress_keyword_substitution=False):
65 """Return a command string, and a pipe from which the file
66 contents of C_REV can be read. C_REV is a CVSRevision. If
67 SUPPRESS_KEYWORD_SUBSTITUTION is True, then suppress the
68 substitution of RCS/CVS keywords in the output. Standard output
69 of the pipe returns the text of that CVS Revision.
71 The command string that is returned is provided for use in error
72 messages; it is not escaped in such a way that it could
73 necessarily be executed."""
75 raise NotImplementedError
78 class CVSRepositoryViaRCS(CVSRepository):
79 """A CVSRepository accessed via RCS."""
81 def __init__(self, cvs_repos_path):
82 CVSRepository.__init__(self, cvs_repos_path)
83 try:
84 check_command_runs([ 'co', '-V' ], 'co')
85 except CommandFailedException, e:
86 raise FatalError('%s\n'
87 'Please check that co is installed and in your PATH\n'
88 '(it is a part of the RCS software).' % (e,))
90 def get_co_pipe(self, c_rev, suppress_keyword_substitution=False):
91 pipe_cmd = [ 'co', '-q', '-x,v', '-p' + c_rev.rev ]
92 if suppress_keyword_substitution:
93 pipe_cmd.append('-kk')
94 pipe_cmd.append(c_rev.cvs_file.filename)
95 pipe = SimplePopen(pipe_cmd, True)
96 pipe.stdin.close()
97 return ' '.join(pipe_cmd), pipe
100 class CVSRepositoryViaCVS(CVSRepository):
101 """A CVSRepository accessed via CVS."""
103 def __init__(self, cvs_repos_path):
104 CVSRepository.__init__(self, cvs_repos_path)
105 # Ascend above the specified root if necessary, to find the
106 # cvs_repository_root (a directory containing a CVSROOT directory)
107 # and the cvs_module (the path of the conversion root within the
108 # cvs repository) NB: cvs_module must be seperated by '/' *not* by
109 # os.sep .
110 def is_cvs_repository_root(path):
111 return os.path.isdir(os.path.join(path, 'CVSROOT'))
113 self.cvs_repository_root = os.path.abspath(self.cvs_repos_path)
114 self.cvs_module = ""
115 while not is_cvs_repository_root(self.cvs_repository_root):
116 # Step up one directory:
117 prev_cvs_repository_root = self.cvs_repository_root
118 self.cvs_repository_root, module_component = \
119 os.path.split(self.cvs_repository_root)
120 if self.cvs_repository_root == prev_cvs_repository_root:
121 # Hit the root (of the drive, on Windows) without finding a
122 # CVSROOT dir.
123 raise FatalError(
124 "the path '%s' is not a CVS repository, nor a path "
125 "within a CVS repository. A CVS repository contains "
126 "a CVSROOT directory within its root directory."
127 % (self.cvs_repos_path,))
129 self.cvs_module = module_component + "/" + self.cvs_module
131 os.environ['CVSROOT'] = self.cvs_repository_root
133 def cvs_ok(global_arguments):
134 check_command_runs(
135 [ 'cvs' ] + global_arguments + [ '--version' ], 'cvs')
137 self.global_arguments = [ "-q", "-R" ]
138 try:
139 cvs_ok(self.global_arguments)
140 except CommandFailedException, e:
141 self.global_arguments = [ "-q" ]
142 try:
143 cvs_ok(self.global_arguments)
144 except CommandFailedException, e:
145 raise FatalError(
146 '%s\n'
147 'Please check that cvs is installed and in your PATH.' % (e,))
149 def get_co_pipe(self, c_rev, suppress_keyword_substitution=False):
150 pipe_cmd = [ 'cvs' ] + self.global_arguments + \
151 [ 'co', '-r' + c_rev.rev, '-p' ]
152 if suppress_keyword_substitution:
153 pipe_cmd.append('-kk')
154 pipe_cmd.append(self.cvs_module + c_rev.cvs_path)
155 pipe = SimplePopen(pipe_cmd, True)
156 pipe.stdin.close()
157 return ' '.join(pipe_cmd), pipe