1 # (Be in -*- python -*- mode.)
3 # ====================================================================
4 # Copyright (c) 2000-2009 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 """Walk through a CVS project, generating CVSPaths."""
23 from cvs2svn_lib
.common
import path_join
24 from cvs2svn_lib
.common
import FatalError
25 from cvs2svn_lib
.common
import warning_prefix
26 from cvs2svn_lib
.common
import IllegalSVNPathError
27 from cvs2svn_lib
.log
import logger
28 from cvs2svn_lib
.context
import Ctx
29 from cvs2svn_lib
.project
import FileInAndOutOfAtticException
30 from cvs2svn_lib
.cvs_path
import CVSDirectory
31 from cvs2svn_lib
.cvs_path
import CVSFile
34 class _RepositoryWalker(object):
35 def __init__(self
, file_key_generator
, error_handler
):
36 self
.file_key_generator
= file_key_generator
37 self
.error_handler
= error_handler
40 self
, parent_directory
, basename
,
41 file_in_attic
=False, leave_in_attic
=False,
43 """Return a CVSFile describing the file with name BASENAME.
45 PARENT_DIRECTORY is the CVSDirectory instance describing the
46 directory that physically holds this file in the filesystem.
47 BASENAME must be the base name of a *,v file within
50 FILE_IN_ATTIC is a boolean telling whether the specified file is
51 in an Attic subdirectory. If FILE_IN_ATTIC is True, then:
53 - If LEAVE_IN_ATTIC is True, then leave the 'Attic' component in
56 - Otherwise, raise FileInAndOutOfAtticException if a file with the
57 same filename appears outside of Attic.
59 The CVSFile is assigned a new unique id. All of the CVSFile
60 information is filled in except mode (which can only be determined
63 Raise FatalError if the resulting filename would not be legal in
66 filename
= os
.path
.join(parent_directory
.rcs_path
, basename
)
68 Ctx().output_option
.verify_filename_legal(basename
[:-2])
69 except IllegalSVNPathError
, e
:
71 'File %r would result in an illegal SVN filename: %s'
75 if file_in_attic
and not leave_in_attic
:
77 logical_parent_directory
= parent_directory
.parent_directory
79 # If this file also exists outside of the attic, it's a fatal
81 non_attic_filename
= os
.path
.join(
82 logical_parent_directory
.rcs_path
, basename
,
84 if os
.path
.exists(non_attic_filename
):
85 raise FileInAndOutOfAtticException(non_attic_filename
, filename
)
88 logical_parent_directory
= parent_directory
90 file_stat
= os
.stat(filename
)
92 # The size of the file in bytes:
93 file_size
= file_stat
.st_size
95 # Whether or not the executable bit is set:
96 file_executable
= bool(file_stat
.st_mode
& stat
.S_IXUSR
)
98 # mode is not known, so we temporarily set it to None.
100 self
.file_key_generator
.gen_id(),
101 parent_directory
.project
, logical_parent_directory
, basename
[:-2],
102 in_attic
, file_executable
, file_size
, None, None
105 def _get_attic_file(self
, parent_directory
, basename
):
106 """Return a CVSFile object for the Attic file at BASENAME.
108 PARENT_DIRECTORY is the CVSDirectory that physically contains the
109 file on the filesystem (i.e., the Attic directory). It is not
110 necessarily the parent_directory of the CVSFile that will be
113 Return CVSFile, whose parent directory is usually
114 PARENT_DIRECTORY.parent_directory, but might be PARENT_DIRECTORY
115 iff CVSFile will remain in the Attic directory."""
118 return self
._get
_cvs
_file
(
119 parent_directory
, basename
, file_in_attic
=True,
121 except FileInAndOutOfAtticException
, e
:
122 if Ctx().retain_conflicting_attic_files
:
125 " storing the latter into 'Attic' subdirectory.\n"
126 % (warning_prefix
, e
)
129 self
.error_handler(str(e
))
131 # Either way, return a CVSFile object so that the rest of the
132 # file processing can proceed:
133 return self
._get
_cvs
_file
(
134 parent_directory
, basename
, file_in_attic
=True, leave_in_attic
=True,
137 def _generate_attic_cvs_files(self
, cvs_directory
, exclude_paths
):
138 """Generate CVSFiles for the files in Attic directory CVS_DIRECTORY.
140 Also yield CVS_DIRECTORY if any files are being retained in the
143 Silently ignore subdirectories named '.svn', but emit a warning if
144 any other directories are found within the Attic directory."""
146 retained_attic_files
= []
148 fnames
= os
.listdir(cvs_directory
.rcs_path
)
151 pathname
= os
.path
.join(cvs_directory
.rcs_path
, fname
)
152 path_in_repository
= path_join(cvs_directory
.get_cvs_path(), fname
)
153 if path_in_repository
in exclude_paths
:
155 "Excluding file from conversion: %s" % (path_in_repository
,)
157 elif os
.path
.isdir(pathname
):
160 "Directory %s found within Attic; ignoring" % (pathname
,)
164 "Directory %s found within Attic; ignoring" % (pathname
,)
166 elif fname
.endswith(',v'):
167 cvs_file
= self
._get
_attic
_file
(cvs_directory
, fname
)
168 if cvs_file
.parent_directory
== cvs_directory
:
169 # This file will be retained in the Attic directory.
170 retained_attic_files
.append(cvs_file
)
172 # This is a normal Attic file, which is treated as if it
173 # were located one directory up:
176 if retained_attic_files
:
177 # There was at least one file in the attic that will be retained
178 # in the attic. First include the Attic directory itself in the
179 # output, then the retained attic files:
181 for cvs_file
in retained_attic_files
:
184 def generate_cvs_paths(self
, cvs_directory
, exclude_paths
):
185 """Generate the CVSPaths under non-Attic directory CVS_DIRECTORY.
187 Yield CVSDirectory and CVSFile instances as they are found.
188 Process directories recursively, including Attic directories.
189 Also look for conflicts between the filenames that will result
190 from files, attic files, and subdirectories.
192 Silently ignore subdirectories named '.svn', as these don't make
193 much sense in a real conversion, but they are present in our test
198 # Map {cvs_file.rcs_basename : cvs_file.rcs_path} for files
199 # directly in cvs_directory:
204 # Non-Attic subdirectories of cvs_directory (to be recursed into):
207 fnames
= os
.listdir(cvs_directory
.rcs_path
)
210 pathname
= os
.path
.join(cvs_directory
.rcs_path
, fname
)
211 path_in_repository
= path_join(cvs_directory
.get_cvs_path(), fname
)
212 if path_in_repository
in exclude_paths
:
214 "Excluding file from conversion: %s" % (path_in_repository
,)
217 elif os
.path
.isdir(pathname
):
220 elif fname
== '.svn':
221 logger
.debug("Directory %s ignored" % (pathname
,))
224 elif fname
.endswith(',v'):
225 cvs_file
= self
._get
_cvs
_file
(cvs_directory
, fname
)
226 rcsfiles
[cvs_file
.rcs_basename
] = cvs_file
.rcs_path
229 # Silently ignore other files:
232 # Map {cvs_file.rcs_basename : cvs_file.rcs_path} for files in an
233 # Attic directory within cvs_directory:
236 if attic_dir
is not None:
237 attic_directory
= CVSDirectory(
238 self
.file_key_generator
.gen_id(),
239 cvs_directory
.project
, cvs_directory
, 'Attic',
242 for cvs_path
in self
._generate
_attic
_cvs
_files
(attic_directory
, exclude_paths
):
243 if isinstance(cvs_path
, CVSFile
) \
244 and cvs_path
.parent_directory
== cvs_directory
:
245 attic_rcsfiles
[cvs_path
.rcs_basename
] = cvs_path
.rcs_path
249 alldirs
= dirs
+ [attic_dir
]
253 # Check for conflicts between directory names and the filenames
254 # that will result from the rcs files (both in this directory and
255 # in attic). (We recurse into the subdirectories nevertheless, to
256 # try to detect more problems.)
257 for fname
in alldirs
:
258 for rcsfile_list
in [rcsfiles
, attic_rcsfiles
]:
259 if fname
in rcsfile_list
:
261 'Directory name conflicts with filename. Please remove or '
263 'of the following:\n'
266 os
.path
.join(cvs_directory
.rcs_path
, fname
),
271 # Now recurse into the other subdirectories:
273 dirname
= os
.path
.join(cvs_directory
.rcs_path
, fname
)
275 # Verify that the directory name does not contain any illegal
278 Ctx().output_option
.verify_filename_legal(fname
)
279 except IllegalSVNPathError
, e
:
281 'Directory %r would result in an illegal SVN path name: %s'
285 sub_directory
= CVSDirectory(
286 self
.file_key_generator
.gen_id(),
287 cvs_directory
.project
, cvs_directory
, fname
,
290 for cvs_path
in self
.generate_cvs_paths(sub_directory
, exclude_paths
):
294 def walk_repository(project
, file_key_generator
, error_handler
):
295 """Generate CVSDirectories and CVSFiles within PROJECT.
297 Use FILE_KEY_GENERATOR to generate the IDs used for files. If there
298 is a fatal error, register it by calling ERROR_HANDLER with a string
299 argument describing the problem. (The error will be logged but
300 processing will continue through the end of the pass.) Also:
302 * Set PROJECT.root_cvs_directory_id.
304 * Handle files in the Attic by generating CVSFile instances with the
305 _in_attic member set.
307 * Check for naming conflicts that will result from files in and out
308 of the Attic. If Ctx().retain_conflicting_attic_files is set, fix
309 the conflicts by leaving the Attic file in the attic. Otherwise,
310 register a fatal error.
312 * Check for naming conflicts between files (in or out of the Attic)
315 * Check for filenames that contain characters not allowed by
320 root_cvs_directory
= CVSDirectory(
321 file_key_generator
.gen_id(), project
, None, ''
323 project
.root_cvs_directory_id
= root_cvs_directory
.id
324 repository_walker
= _RepositoryWalker(file_key_generator
, error_handler
)
325 for cvs_path
in repository_walker
.generate_cvs_paths(
326 root_cvs_directory
, project
.exclude_paths