File: incremental-update.py

package info (click to toggle)
subversion 1.6.12dfsg-7
  • links: PTS, VCS
  • area: main
  • in suites: squeeze
  • size: 48,292 kB
  • ctags: 47,714
  • sloc: ansic: 578,414; python: 77,551; sh: 13,100; ruby: 12,194; cpp: 10,097; java: 8,428; lisp: 7,702; perl: 7,320; makefile: 1,035; xml: 759; sql: 62
file content (188 lines) | stat: -rwxr-xr-x 6,038 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
#!/usr/bin/env python

# ====================================================================
#
# incremental-update.py
#
# This script performs updates of a single working copy tree piece by
# piece, starting with deep subdirectores, and working its way up
# toward the root of the working copy.  Why?  Because for working
# copies that have significantly mixed revisions, the size and
# complexity of the report that Subversion has to transmit to the
# server can be prohibitive, even triggering server-configured limits
# for such things.  But doing an incremental update, you lessen the
# chance of hitting such a limit.
#
# ====================================================================
# Copyright (c) 2007 CollabNet.  All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution.  The terms
# are also available at http://subversion.tigris.org/license-1.html.
# If newer versions of this license are posted there, you may use a
# newer version instead, at your option.
#
# This software consists of voluntary contributions made by many
# individuals.  For exact contribution history, see the revision
# history and logs, available at http://subversion.tigris.org/.
# ====================================================================

# --------------------------------------------------------------------
# Configuration (oooh... so complex...)
#

SVN_BINARY='svn'

#
# --------------------------------------------------------------------

import sys
import os
import re


def print_error(err):
    sys.stderr.write("ERROR: %s\n\n" % (err))

def usage_and_exit(err=None):
    if err:
        stream = sys.stderr
        print_error(err)
    else:
        stream = sys.stdout
    stream.write("""Usage: %s [OPTIONS] WC-DIR

Update WC-DIR in an incremental fashion, starting will smaller
subtrees of it, and working up toward WC-DIR itself.  SVN_UP_ARGS are
command-line parameters passed straight through to the Subversion
command-line client (svn) as parameters to its update command.

WARNING: Speed of operation is explicitly *NOT* of interest to this
script.  Use it only when a typical 'svn update' isn't working for you
due to the complexity of your working copy's mixed-revision state.

Options:

    --username USER      Specify the username used to connect to the repository
    --password PASS      Specify the PASSWORD used to connect to the repository

""" % (os.path.basename(sys.argv[0])))
    sys.exit(err and 1 or 0)


def get_head_revision(path, args):
    """Return the current HEAD revision for the repository associated
    with PATH.  ARGS are extra arguments to provide to the svn
    client."""

    lines = os.popen('%s status --show-updates --non-recursive %s %s'
                     % (SVN_BINARY, args, path)).readlines()
    if lines and lines[-1].startswith('Status against revision:'):
        return int(lines[-1][24:].strip())
    raise Exception, "Unable to fetch HEAD revision number."


def compare_paths(path1, path2):
    """This is a sort() helper function for two paths."""

    path1_len = len (path1);
    path2_len = len (path2);
    min_len = min(path1_len, path2_len)
    i = 0

    # Are the paths exactly the same?
    if path1 == path2:
      return 0

    # Skip past common prefix
    while (i < min_len) and (path1[i] == path2[i]):
      i = i + 1

    # Children of paths are greater than their parents, but less than
    # greater siblings of their parents
    char1 = '\0'
    char2 = '\0'
    if (i < path1_len):
      char1 = path1[i]
    if (i < path2_len):
      char2 = path2[i]

    if (char1 == '/') and (i == path2_len):
      return 1
    if (char2 == '/') and (i == path1_len):
      return -1
    if (i < path1_len) and (char1 == '/'):
      return -1
    if (i < path2_len) and (char2 == '/'):
      return 1

    # Common prefix was skipped above, next character is compared to
    # determine order
    return cmp(char1, char2)


def harvest_dirs(path):
    """Return a list of versioned directories under working copy
    directory PATH, inclusive."""

    # 'svn status' output line matcher, taken from the Subversion test suite
    rm = re.compile('^([!MACDRUG_ ][MACDRUG_ ])([L ])([+ ])([S ])([KOBT ]) ' \
                    '([* ])   [^0-9-]*(\d+|-|\?) +(\d|-|\?)+ +(\S+) +(.+)')
    dirs = []
    fp = os.popen('%s status --verbose %s' % (SVN_BINARY, path))
    while 1:
        line = fp.readline()
        if not line:
            break
        line = line.rstrip()
        if line.startswith('Performing'):
            break
        match = rm.search(line)
        if match:
            stpath = match.group(10)
            try:
                if os.path.isdir(stpath):
                    dirs.append(stpath)
            except:
                pass
    return dirs


def main():
    argc = len(sys.argv)
    if argc < 2:
        usage_and_exit("No working copy directory specified")
    if '--help' in sys.argv:
        usage_and_exit(None)
    path = sys.argv[-1]
    args = ' '.join(sys.argv[1:-1] + ['--non-interactive'])
    print "Fetch HEAD revision...",
    head_revision = get_head_revision(path, args)
    print "done."
    print "Updating to revision %d" % (head_revision)
    print "Harvesting the list of subdirectories...",
    dirs = harvest_dirs(path)
    print "done."
    dirs.sort(compare_paths)
    dirs.reverse()
    print "Update the tree, one subdirectory at a time.  This could take " \
          "a while."
    num_dirs = len(dirs)
    width = len(str(num_dirs))
    format_string = '[%%%dd/%%%dd] Updating %%s' % (width, width)
    current = 0
    for dir in dirs:
        current = current + 1
        print format_string % (current, num_dirs, dir)
        os.system('%s update --quiet --revision %d %s %s'
                  % (SVN_BINARY, head_revision, args, dir))


if __name__ == "__main__":
    try:
        main()
    except SystemExit:
        raise
    except Exception, e:
        print_error(str(e))
        sys.exit(1)