~svn/ubuntu/oneiric/subversion/ppa

« back to all changes in this revision

Viewing changes to contrib/client-side/svn_export_empty_files.py

  • Committer: Bazaar Package Importer
  • Author(s): Adam Conrad
  • Date: 2005-12-05 01:26:14 UTC
  • mfrom: (1.1.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20051205012614-qom4xfypgtsqc2xq
Tags: 1.2.3dfsg1-3ubuntu1
Merge with the final Debian release of 1.2.3dfsg1-3, bringing in
fixes to the clean target, better documentation of the libdb4.3
upgrade and build fixes to work with swig1.3_1.3.27.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
 
 
3
# Copyright (c) 2005 Sony Pictures Imageworks Inc.  All rights reserved.
 
4
#
 
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
 
8
# Foundation.
 
9
#
 
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.
 
15
 
 
16
"""
 
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.
 
20
 
 
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.
 
26
 
 
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.
 
32
 
 
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 $
 
37
"""
 
38
 
 
39
import getopt
 
40
try:
 
41
    my_getopt = getopt.gnu_getopt
 
42
except AttributeError:
 
43
    my_getopt = getopt.getopt
 
44
import os
 
45
import sys
 
46
 
 
47
import libsvn._core
 
48
import svn.client
 
49
import svn.core
 
50
 
 
51
class context:
 
52
    """A container for holding process context."""
 
53
 
 
54
def recursive_delete(dirname):
 
55
    """Recursively delete the given directory name."""
 
56
 
 
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)
 
61
        else:
 
62
            os.unlink(file_or_dir)
 
63
    os.rmdir(dirname)
 
64
 
 
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
 
68
    non-directory."""
 
69
 
 
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)
 
74
    try:
 
75
        if ctx.verbose:
 
76
            print "Trying to list '%s'" % url
 
77
        svn.client.svn_client_ls(url, revision, 0, client_ctx, subpool)
 
78
 
 
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.
 
84
        try:
 
85
            last_slash_index = url.rindex('/')
 
86
        except ValueError:
 
87
            print "Cannot find a / in the URL '%s'" % url
 
88
            return False
 
89
 
 
90
        parent_url = url[:last_slash_index]
 
91
        path_name = url[last_slash_index+1:]
 
92
 
 
93
        try:
 
94
            if ctx.verbose:
 
95
                print "Trying to list '%s'" % parent_url
 
96
            remote_ls = svn.client.svn_client_ls(parent_url,
 
97
                                                 revision,
 
98
                                                 0,
 
99
                                                 client_ctx,
 
100
                                                 subpool)
 
101
        except libsvn._core.SubversionException:
 
102
            if ctx.verbose:
 
103
                print "Listing of '%s' failed, assuming URL is top of repos" \
 
104
                      % parent_url
 
105
            return True
 
106
 
 
107
        try:
 
108
            path_info = remote_ls[path_name]
 
109
        except ValueError:
 
110
            print "Able to ls '%s' but '%s' not in ls of '%s'" \
 
111
                  % (url, path_name, parent_url)
 
112
            return False
 
113
 
 
114
        if svn.core.svn_node_dir != path_info.kind:
 
115
            if ctx.verbose:
 
116
                print "The URL '%s' is not a directory" % url
 
117
            return False
 
118
        else:
 
119
            if ctx.verbose:
 
120
                print "The URL '%s' is a directory" % url
 
121
            return True
 
122
 
 
123
    finally:
 
124
        svn.core.svn_pool_destroy(subpool)
 
125
 
 
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."""
 
132
 
 
133
    try:
 
134
        os.stat(pathname)
 
135
        if os.path.isdir(pathname):
 
136
            status = LOCAL_PATH_DIR
 
137
        else:
 
138
            status = LOCAL_PATH_NON_DIR
 
139
    except OSError:
 
140
        status = LOCAL_PATH_NONE
 
141
 
 
142
    return status
 
143
 
 
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
 
147
    argument."""
 
148
 
 
149
    status = True
 
150
 
 
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)
 
154
 
 
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
 
162
            os.unlink(dir_name)
 
163
            local_path_kind = LOCAL_PATH_NONE
 
164
        else:
 
165
            print "Need to remove", msg
 
166
            ctx.delete_needed = True
 
167
            return False
 
168
 
 
169
    if LOCAL_PATH_NONE == local_path_kind:
 
170
        print "Creating directory '%s'" % dir_name
 
171
        os.mkdir(dir_name)
 
172
 
 
173
    subpool = svn.core.svn_pool_create(pool)
 
174
    remote_ls = svn.client.svn_client_ls(url,
 
175
                                         revision,
 
176
                                         0,
 
177
                                         client_ctx,
 
178
                                         subpool)
 
179
 
 
180
    if ctx.verbose:
 
181
        print "Syncing '%s' to '%s'" % (url, dir_name)
 
182
 
 
183
    remote_pathnames = remote_ls.keys()
 
184
    remote_pathnames.sort()
 
185
 
 
186
    local_pathnames = os.listdir(dir_name)
 
187
 
 
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.
 
191
        try:
 
192
            local_pathnames.remove(remote_pathname)
 
193
        except ValueError:
 
194
            pass
 
195
 
 
196
        full_remote_pathname = os.path.join(dir_name, remote_pathname)
 
197
 
 
198
        if remote_pathname in ctx.ignore_names or \
 
199
               full_remote_pathname in ctx.ignore_paths:
 
200
            print "Skipping '%s'" % full_remote_pathname
 
201
            continue
 
202
 
 
203
        # Get the remote path kind.
 
204
        remote_path_kind = remote_ls[remote_pathname].kind
 
205
 
 
206
        # If the remote path is a directory, then recursively handle
 
207
        # that here.
 
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,
 
212
                                revision,
 
213
                                client_ctx,
 
214
                                subpool)
 
215
            status &= s
 
216
 
 
217
        else:
 
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)
 
221
 
 
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
 
226
            # not be done.
 
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
 
234
                else:
 
235
                    print "Need to remove", msg
 
236
                    ctx.delete_needed = True
 
237
                    continue
 
238
 
 
239
            if LOCAL_PATH_NONE == local_path_kind:
 
240
                print "Creating file '%s'" % full_remote_pathname
 
241
                f = file(full_remote_pathname, 'w')
 
242
                f.close()
 
243
 
 
244
    svn.core.svn_pool_destroy(subpool)
 
245
 
 
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)
 
254
            else:
 
255
                print "Need to remove directory '%s'" % full_local_pathname
 
256
                ctx.delete_needed = True
 
257
        else:
 
258
            if ctx.delete_local_paths:
 
259
                print "Removing file '%s'" % full_local_pathname
 
260
                os.unlink(full_local_pathname)
 
261
            else:
 
262
                print "Need to remove file '%s'" % full_local_pathname
 
263
                ctx.delete_needed = True
 
264
 
 
265
    return status
 
266
 
 
267
def main(pool, ctx, url, export_pathname):
 
268
    # Create a client context to run all Subversion client commands
 
269
    # with.
 
270
    client_ctx = svn.client.svn_client_create_context(pool)
 
271
 
 
272
    # Give the client context baton a suite of authentication
 
273
    # providers.
 
274
    providers = [
 
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),
 
280
        ]
 
281
    client_ctx.auth_baton = svn.core.svn_auth_open(providers, pool)
 
282
 
 
283
    # Load the configuration information from the configuration files.
 
284
    client_ctx.config = svn.core.svn_config_get_config(None, pool)
 
285
 
 
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
 
289
 
 
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)
 
293
    if not status:
 
294
        return 1
 
295
 
 
296
    # Synchronize the current working directory with the given URL and
 
297
    # descend recursively into the repository.
 
298
    status = synchronize_dir(ctx,
 
299
                             url,
 
300
                             export_pathname,
 
301
                             head_revision,
 
302
                             client_ctx,
 
303
                             pool)
 
304
 
 
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."
 
312
        else:
 
313
            print ""
 
314
 
 
315
    if status:
 
316
        return 0
 
317
    else:
 
318
        return 1
 
319
 
 
320
def usage(verbose_usage):
 
321
    message1 = \
 
322
"""usage: %s [options] URL [PATH]
 
323
Options include
 
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"""
 
329
 
 
330
    message2 = \
 
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.
 
334
 
 
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.
 
340
 
 
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."""
 
346
 
 
347
    print >>sys.stderr, message1 % sys.argv[0]
 
348
    if verbose_usage:
 
349
        print >>sys.stderr, message2
 
350
    sys.exit(1)
 
351
 
 
352
if __name__ == '__main__':
 
353
    ctx = context()
 
354
 
 
355
    # Context storing command line options settings.
 
356
    ctx.delete_local_paths = False
 
357
    ctx.ignore_names = []
 
358
    ctx.ignore_paths = []
 
359
    ctx.verbose = False
 
360
 
 
361
    # Context storing state from running the sync.
 
362
    ctx.delete_needed = False
 
363
 
 
364
    try:
 
365
        opts, args = my_getopt(sys.argv[1:],
 
366
                               'hn:p:v',
 
367
                               ['delete',
 
368
                                'help',
 
369
                                'name=',
 
370
                                'path=',
 
371
                                'verbose'
 
372
                                ])
 
373
    except getopt.GetoptError:
 
374
        usage(False)
 
375
    if len(args) < 1 or len(args) > 2:
 
376
        print >>sys.stderr, "Incorrect number of arguments"
 
377
        usage(False)
 
378
 
 
379
    for o, a in opts:
 
380
        if o in ('--delete',):
 
381
            ctx.delete_local_paths = True
 
382
            continue
 
383
        if o in ('-h', '--help'):
 
384
            usage(True)
 
385
            continue
 
386
        if o in ('-n', '--name'):
 
387
            ctx.ignore_names += [a]
 
388
            continue
 
389
        if o in ('-p', '--path'):
 
390
            ctx.ignore_paths += [a]
 
391
            continue
 
392
        if o in ('-v', '--verbose'):
 
393
            ctx.verbose = True
 
394
            continue
 
395
 
 
396
    # Get the URL to export and remove any trailing /'s from it.
 
397
    url = args[0]
 
398
    args = args[1:]
 
399
    while url[-1] == '/':
 
400
        url = url[:-1]
 
401
 
 
402
    # Get the local path to export into.
 
403
    if args:
 
404
        export_pathname = args[0]
 
405
        args = args[1:]
 
406
    else:
 
407
        try:
 
408
            last_slash_index = url.rindex('/')
 
409
        except ValueError:
 
410
            print >>sys.stderr, "Cannot find a / in the URL '%s'" % url
 
411
            usage(False)
 
412
        export_pathname = url[last_slash_index+1:]
 
413
 
 
414
    sys.exit(svn.core.run_app(main, ctx, url, export_pathname))