3
# Copyright (c) 2005 Sony Pictures Imageworks Inc. All rights reserved.
5
# This software/script is free software; you may redistribute it
6
# and/or modify it under the terms of Version 2 or later of the GNU
7
# General Public License ("GPL") as published by the Free Software
10
# This software/script is distributed "AS IS," WITHOUT ANY EXPRESS OR
11
# IMPLIED WARRANTIES OR REPRESENTATIONS OF ANY KIND WHATSOEVER,
12
# including without any implied warranty of MERCHANTABILITY or FITNESS
13
# FOR A PARTICULAR PURPOSE. See the GNU GPL (Version 2 or later) for
14
# details and license obligations.
17
Script to "export" from a Subversion repository a clean directory tree
18
of empty files instead of the content contained in those files in the
19
repository. The directory tree will also omit the .svn directories.
21
The export is done from the repository specified by URL at HEAD into
22
PATH. If PATH is omitted, the last components of the URL is used for
23
the local directory name. If the --delete command line option is
24
given, then files and directories in PATH that do not exist in the
25
Subversion repository are deleted.
27
As Subversion does have any built-in tools to help locate files and
28
directories, in extremely large repositories it can be hard to find
29
what you are looking for. This script was written to create a smaller
30
non-working working copy that can be crawled with find or find's
31
locate utility to make it easier to find files.
33
$HeadURL: http://svn.collab.net/repos/svn/branches/1.2.x/contrib/client-side/svn_export_empty_files.py $
34
$LastChangedRevision: 15821 $
35
$LastChangedDate: 2005-08-19 10:30:13 -0400 (Fri, 19 Aug 2005) $
36
$LastChangedBy: maxb $
41
my_getopt = getopt.gnu_getopt
42
except AttributeError:
43
my_getopt = getopt.getopt
52
"""A container for holding process context."""
54
def recursive_delete(dirname):
55
"""Recursively delete the given directory name."""
57
for filename in os.listdir(dirname):
58
file_or_dir = os.path.join(dirname, filename)
59
if os.path.isdir(file_or_dir) and not os.path.islink(file_or_dir):
60
recursive_delete(file_or_dir)
62
os.unlink(file_or_dir)
65
def check_url_for_export(ctx, url, revision, client_ctx, pool):
66
"""Given a URL to a Subversion repository, check that the URL is
67
in the repository and that it refers to a directory and not a
70
# Try to do a listing on the URL to see if the repository can be
71
# contacted. Do not catch failures here, as they imply that there
72
# is something wrong with the given URL.
73
subpool = svn.core.svn_pool_create(pool)
76
print "Trying to list '%s'" % url
77
svn.client.svn_client_ls(url, revision, 0, client_ctx, subpool)
79
# Given a URL, the svn_client_ls command does not tell you if
80
# you have a directory or a non-directory, so try doing a
81
# listing on the parent URL. If the listing on the parent URL
82
# fails, then assume that the given URL was the top of the
83
# repository and hence a directory.
85
last_slash_index = url.rindex('/')
87
print "Cannot find a / in the URL '%s'" % url
90
parent_url = url[:last_slash_index]
91
path_name = url[last_slash_index+1:]
95
print "Trying to list '%s'" % parent_url
96
remote_ls = svn.client.svn_client_ls(parent_url,
101
except libsvn._core.SubversionException:
103
print "Listing of '%s' failed, assuming URL is top of repos" \
108
path_info = remote_ls[path_name]
110
print "Able to ls '%s' but '%s' not in ls of '%s'" \
111
% (url, path_name, parent_url)
114
if svn.core.svn_node_dir != path_info.kind:
116
print "The URL '%s' is not a directory" % url
120
print "The URL '%s' is a directory" % url
124
svn.core.svn_pool_destroy(subpool)
126
LOCAL_PATH_DIR = 'Directory'
127
LOCAL_PATH_NON_DIR = 'Non-directory'
128
LOCAL_PATH_NONE = 'Nonexistent'
129
def get_local_path_kind(pathname):
130
"""Determine if there is a path in the filesystem and if the path
131
is a directory or non-directory."""
135
if os.path.isdir(pathname):
136
status = LOCAL_PATH_DIR
138
status = LOCAL_PATH_NON_DIR
140
status = LOCAL_PATH_NONE
144
def synchronize_dir(ctx, url, dir_name, revision, client_ctx, pool):
145
"""Synchronize a directory given by a URL to a Subversion
146
repository with a local directory located by the dir_name
151
# Determine if there is a path in the filesystem and if the path
152
# is a directory or non-directory.
153
local_path_kind = get_local_path_kind(dir_name)
155
# If the path on the local filesystem is not a directory, then
156
# delete it if deletes are enabled, otherwise return.
157
if LOCAL_PATH_NON_DIR == local_path_kind:
158
msg = ("'%s' which is a local non-directory but remotely a " +
159
"directory") % dir_name
160
if ctx.delete_local_paths:
161
print "Removing", msg
163
local_path_kind = LOCAL_PATH_NONE
165
print "Need to remove", msg
166
ctx.delete_needed = True
169
if LOCAL_PATH_NONE == local_path_kind:
170
print "Creating directory '%s'" % dir_name
173
subpool = svn.core.svn_pool_create(pool)
174
remote_ls = svn.client.svn_client_ls(url,
181
print "Syncing '%s' to '%s'" % (url, dir_name)
183
remote_pathnames = remote_ls.keys()
184
remote_pathnames.sort()
186
local_pathnames = os.listdir(dir_name)
188
for remote_pathname in remote_pathnames:
189
# For each name in the remote list, remove it from the local
190
# list so that the remaining names may be deleted.
192
local_pathnames.remove(remote_pathname)
196
full_remote_pathname = os.path.join(dir_name, remote_pathname)
198
if remote_pathname in ctx.ignore_names or \
199
full_remote_pathname in ctx.ignore_paths:
200
print "Skipping '%s'" % full_remote_pathname
203
# Get the remote path kind.
204
remote_path_kind = remote_ls[remote_pathname].kind
206
# If the remote path is a directory, then recursively handle
208
if svn.core.svn_node_dir == remote_path_kind:
209
s = synchronize_dir(ctx,
210
os.path.join(url, remote_pathname),
211
full_remote_pathname,
218
# Determine if there is a path in the filesystem and if
219
# the path is a directory or non-directory.
220
local_path_kind = get_local_path_kind(full_remote_pathname)
222
# If the path exists on the local filesystem but its kind
223
# does not match the kind in the Subversion repository,
224
# then either remove it if the local paths should be
225
# deleted or continue to the next path if deletes should
227
if LOCAL_PATH_DIR == local_path_kind:
228
msg = ("'%s' which is a local directory but remotely a " +
229
"non-directory") % full_remote_pathname
230
if ctx.delete_local_paths:
231
print "Removing", msg
232
recursive_delete(full_remote_pathname)
233
local_path_kind = LOCAL_PATH_NONE
235
print "Need to remove", msg
236
ctx.delete_needed = True
239
if LOCAL_PATH_NONE == local_path_kind:
240
print "Creating file '%s'" % full_remote_pathname
241
f = file(full_remote_pathname, 'w')
244
svn.core.svn_pool_destroy(subpool)
246
# Any remaining local paths should be removed.
247
local_pathnames.sort()
248
for local_pathname in local_pathnames:
249
full_local_pathname = os.path.join(dir_name, local_pathname)
250
if os.path.isdir(full_local_pathname):
251
if ctx.delete_local_paths:
252
print "Removing directory '%s'" % full_local_pathname
253
recursive_delete(full_local_pathname)
255
print "Need to remove directory '%s'" % full_local_pathname
256
ctx.delete_needed = True
258
if ctx.delete_local_paths:
259
print "Removing file '%s'" % full_local_pathname
260
os.unlink(full_local_pathname)
262
print "Need to remove file '%s'" % full_local_pathname
263
ctx.delete_needed = True
267
def main(pool, ctx, url, export_pathname):
268
# Create a client context to run all Subversion client commands
270
client_ctx = svn.client.svn_client_create_context(pool)
272
# Give the client context baton a suite of authentication
275
svn.client.svn_client_get_simple_provider(pool),
276
svn.client.svn_client_get_ssl_client_cert_file_provider(pool),
277
svn.client.svn_client_get_ssl_client_cert_pw_file_provider(pool),
278
svn.client.svn_client_get_ssl_server_trust_file_provider(pool),
279
svn.client.svn_client_get_username_provider(pool),
281
client_ctx.auth_baton = svn.core.svn_auth_open(providers, pool)
283
# Load the configuration information from the configuration files.
284
client_ctx.config = svn.core.svn_config_get_config(None, pool)
286
# Use the HEAD revision to check out.
287
head_revision = svn.core.svn_opt_revision_t()
288
head_revision.kind = svn.core.svn_opt_revision_head
290
# Check that the URL refers to a directory in the repository and
291
# not non-directory (file, special, etc).
292
status = check_url_for_export(ctx, url, head_revision, client_ctx, pool)
296
# Synchronize the current working directory with the given URL and
297
# descend recursively into the repository.
298
status = synchronize_dir(ctx,
305
if ctx.delete_needed:
306
print "There are files and directories in the local filesystem"
307
print "that do not exist in the Subversion repository that were"
308
print "not deleted. ",
309
if ctx.delete_needed:
310
print "Please pass the --delete command line option"
311
print "to have this script delete those files and directories."
320
def usage(verbose_usage):
322
"""usage: %s [options] URL [PATH]
324
--delete delete files and directories that don't exist in repos
325
-h (--help) show this message
326
-n (--name) arg add arg to the list of file or dir names to ignore
327
-p (--path) arg add arg to the list of file or dir paths to ignore
328
-v (--verbose) be verbose in output"""
331
"""Script to "export" from a Subversion repository a clean directory tree
332
of empty files instead of the content contained in those files in the
333
repository. The directory tree will also omit the .svn directories.
335
The export is done from the repository specified by URL at HEAD into
336
PATH. If PATH is omitted, the last components of the URL is used for
337
the local directory name. If the --delete command line option is
338
given, then files and directories in PATH that do not exist in the
339
Subversion repository are deleted.
341
As Subversion does have any built-in tools to help locate files and
342
directories, in extremely large repositories it can be hard to find
343
what you are looking for. This script was written to create a smaller
344
non-working working copy that can be crawled with find or find's
345
locate utility to make it easier to find files."""
347
print >>sys.stderr, message1 % sys.argv[0]
349
print >>sys.stderr, message2
352
if __name__ == '__main__':
355
# Context storing command line options settings.
356
ctx.delete_local_paths = False
357
ctx.ignore_names = []
358
ctx.ignore_paths = []
361
# Context storing state from running the sync.
362
ctx.delete_needed = False
365
opts, args = my_getopt(sys.argv[1:],
373
except getopt.GetoptError:
375
if len(args) < 1 or len(args) > 2:
376
print >>sys.stderr, "Incorrect number of arguments"
380
if o in ('--delete',):
381
ctx.delete_local_paths = True
383
if o in ('-h', '--help'):
386
if o in ('-n', '--name'):
387
ctx.ignore_names += [a]
389
if o in ('-p', '--path'):
390
ctx.ignore_paths += [a]
392
if o in ('-v', '--verbose'):
396
# Get the URL to export and remove any trailing /'s from it.
399
while url[-1] == '/':
402
# Get the local path to export into.
404
export_pathname = args[0]
408
last_slash_index = url.rindex('/')
410
print >>sys.stderr, "Cannot find a / in the URL '%s'" % url
412
export_pathname = url[last_slash_index+1:]
414
sys.exit(svn.core.run_app(main, ctx, url, export_pathname))