~ubuntu-branches/debian/sid/subversion/sid

« back to all changes in this revision

Viewing changes to tools/server-side/svnpredumpfilter.py

  • Committer: Package Import Robot
  • Author(s): James McCoy, Peter Samuelson, James McCoy
  • Date: 2014-01-12 19:48:33 UTC
  • mfrom: (0.2.10)
  • Revision ID: package-import@ubuntu.com-20140112194833-w3axfwksn296jn5x
Tags: 1.8.5-1
[ Peter Samuelson ]
* New upstream release.  (Closes: #725787) Rediff patches:
  - Remove apr-abi1 (applied upstream), rename apr-abi2 to apr-abi
  - Remove loosen-sqlite-version-check (shouldn't be needed)
  - Remove java-osgi-metadata (applied upstream)
  - svnmucc prompts for a changelog if none is provided. (Closes: #507430)
  - Remove fix-bdb-version-detection, upstream uses "apu-config --dbm-libs"
  - Remove ruby-test-wc (applied upstream)
  - Fix “svn diff -r N file” when file has svn:mime-type set.
    (Closes: #734163)
  - Support specifying an encoding for mod_dav_svn's environment in which
    hooks are run.  (Closes: #601544)
  - Fix ordering of “svnadmin dump” paths with certain APR versions.
    (Closes: #687291)
  - Provide a better error message when authentication fails with an
    svn+ssh:// URL.  (Closes: #273874)
  - Updated Polish translations.  (Closes: #690815)

[ James McCoy ]
* Remove all traces of libneon, replaced by libserf.
* patches/sqlite_3.8.x_workaround: Upstream fix for wc-queries-test test
  failurse.
* Run configure with --with-apache-libexecdir, which allows removing part of
  patches/rpath.
* Re-enable auth-test as upstream has fixed the problem of picking up
  libraries from the environment rather than the build tree.
  (Closes: #654172)
* Point LD_LIBRARY_PATH at the built auth libraries when running the svn
  command during the build.  (Closes: #678224)
* Add a NEWS entry describing how to configure mod_dav_svn to understand
  UTF-8.  (Closes: #566148)
* Remove ancient transitional package, libsvn-ruby.
* Enable compatibility with Sqlite3 versions back to Wheezy.
* Enable hardening flags.  (Closes: #734918)
* patches/build-fixes: Enable verbose build logs.
* Build against the default ruby version.  (Closes: #722393)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
 
 
3
# ====================================================================
 
4
#    Licensed to the Apache Software Foundation (ASF) under one
 
5
#    or more contributor license agreements.  See the NOTICE file
 
6
#    distributed with this work for additional information
 
7
#    regarding copyright ownership.  The ASF licenses this file
 
8
#    to you under the Apache License, Version 2.0 (the
 
9
#    "License"); you may not use this file except in compliance
 
10
#    with the License.  You may obtain a copy of the License at
 
11
#
 
12
#      http://www.apache.org/licenses/LICENSE-2.0
 
13
#
 
14
#    Unless required by applicable law or agreed to in writing,
 
15
#    software distributed under the License is distributed on an
 
16
#    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 
17
#    KIND, either express or implied.  See the License for the
 
18
#    specific language governing permissions and limitations
 
19
#    under the License.
 
20
# ====================================================================
 
21
"""\
 
22
Usage:  1. {PROGRAM} [OPTIONS] include INCLUDE-PATH ...
 
23
        2. {PROGRAM} [OPTIONS] exclude EXCLUDE-PATH ...
 
24
 
 
25
Read a Subversion revision log output stream from stdin, analyzing its
 
26
revision log history to see what paths would need to be additionally
 
27
provided as part of the list of included/excluded paths if trying to
 
28
use Subversion's 'svndumpfilter' program to include/exclude paths from
 
29
a full dump of a repository's history.
 
30
 
 
31
The revision log stream should be the result of 'svn log -v' or 'svn
 
32
log -vq' when run against the root of the repository whose history
 
33
will be filtered by a user with universal read access to the
 
34
repository's data.  Do not use the --use-merge-history (-g) or
 
35
--stop-on-copy when generating this revision log stream.
 
36
Use the default ordering of revisions (that is, '-r HEAD:0').
 
37
 
 
38
Return errorcode 0 if there are no additional dependencies found, 1 if
 
39
there were; any other errorcode indicates a fatal error.
 
40
 
 
41
Options:
 
42
 
 
43
   --help (-h)           Show this usage message and exit.
 
44
 
 
45
   --targets FILE        Read INCLUDE-PATHs and EXCLUDE-PATHs from FILE,
 
46
                         one path per line.
 
47
 
 
48
   --verbose (-v)        Provide more information.  May be used multiple
 
49
                         times for additional levels of information (-vv).
 
50
"""
 
51
import sys
 
52
import os
 
53
import getopt
 
54
import string
 
55
 
 
56
verbosity = 0
 
57
 
 
58
class LogStreamError(Exception): pass
 
59
class EOFError(Exception): pass
 
60
 
 
61
EXIT_SUCCESS = 0
 
62
EXIT_MOREDEPS = 1
 
63
EXIT_FAILURE = 2
 
64
 
 
65
def sanitize_path(path):
 
66
  return '/'.join(filter(None, path.split('/')))
 
67
 
 
68
def subsumes(path, maybe_child):
 
69
  if path == maybe_child:
 
70
    return True
 
71
  if maybe_child.find(path + '/') == 0:
 
72
    return True
 
73
  return False
 
74
 
 
75
def compare_paths(path1, path2):
 
76
  # Are the paths exactly the same?
 
77
  if path1 == path2:
 
78
    return 0
 
79
 
 
80
  # Skip past common prefix
 
81
  path1_len = len(path1);
 
82
  path2_len = len(path2);
 
83
  min_len = min(path1_len, path2_len)
 
84
  i = 0
 
85
  while (i < min_len) and (path1[i] == path2[i]):
 
86
    i = i + 1
 
87
 
 
88
  # Children of paths are greater than their parents, but less than
 
89
  # greater siblings of their parents
 
90
  char1 = '\0'
 
91
  char2 = '\0'
 
92
  if (i < path1_len):
 
93
    char1 = path1[i]
 
94
  if (i < path2_len):
 
95
    char2 = path2[i]
 
96
 
 
97
  if (char1 == '/') and (i == path2_len):
 
98
    return 1
 
99
  if (char2 == '/') and (i == path1_len):
 
100
    return -1
 
101
  if (i < path1_len) and (char1 == '/'):
 
102
    return -1
 
103
  if (i < path2_len) and (char2 == '/'):
 
104
    return 1
 
105
 
 
106
  # Common prefix was skipped above, next character is compared to
 
107
  # determine order
 
108
  return cmp(char1, char2)
 
109
 
 
110
def log(msg, min_verbosity):
 
111
  if verbosity >= min_verbosity:
 
112
    if min_verbosity == 1:
 
113
      sys.stderr.write("[* ] ")
 
114
    elif min_verbosity == 2:
 
115
      sys.stderr.write("[**] ")
 
116
    sys.stderr.write(msg + "\n")
 
117
 
 
118
class DependencyTracker:
 
119
  def __init__(self, include_paths):
 
120
    self.include_paths = include_paths[:]
 
121
    self.dependent_paths = []
 
122
 
 
123
  def path_included(self, path):
 
124
    for include_path in self.include_paths + self.dependent_paths:
 
125
      if subsumes(include_path, path):
 
126
        return True
 
127
    return False
 
128
 
 
129
  def handle_changes(self, path_copies):
 
130
    for path, copyfrom_path in path_copies.items():
 
131
      if self.path_included(path) and copyfrom_path:
 
132
        if not self.path_included(copyfrom_path):
 
133
          self.dependent_paths.append(copyfrom_path)
 
134
 
 
135
def readline(stream):
 
136
  line = stream.readline()
 
137
  if not line:
 
138
    raise EOFError("Unexpected end of stream")
 
139
  line = line.rstrip('\n\r')
 
140
  log(line, 2)
 
141
  return line
 
142
 
 
143
def svn_log_stream_get_dependencies(stream, included_paths):
 
144
  import re
 
145
 
 
146
  dt = DependencyTracker(included_paths)
 
147
 
 
148
  header_re = re.compile(r'^r([0-9]+) \|.*$')
 
149
  action_re = re.compile(r'^   [ADMR] /(.*)$')
 
150
  copy_action_re = re.compile(r'^   [AR] /(.*) \(from /(.*):[0-9]+\)$')
 
151
  line_buf = None
 
152
  last_revision = 0
 
153
  eof = False
 
154
  path_copies = {}
 
155
  found_changed_path = False
 
156
 
 
157
  while not eof:
 
158
    try:
 
159
      line = line_buf is not None and line_buf or readline(stream)
 
160
    except EOFError:
 
161
      break
 
162
 
 
163
    # We should be sitting at a log divider line.
 
164
    if line != '-' * 72:
 
165
      raise LogStreamError("Expected log divider line; not found.")
 
166
 
 
167
    # Next up is a log header line.
 
168
    try:
 
169
      line = readline(stream)
 
170
    except EOFError:
 
171
      break
 
172
    match = header_re.search(line)
 
173
    if not match:
 
174
      raise LogStreamError("Expected log header line; not found.")
 
175
    pieces = map(string.strip, line.split('|'))
 
176
    revision = int(pieces[0][1:])
 
177
    if last_revision and revision >= last_revision:
 
178
      raise LogStreamError("Revisions are misordered.  Make sure log stream "
 
179
                           "is from 'svn log' with the youngest revisions "
 
180
                           "before the oldest ones (the default ordering).")
 
181
    log("Parsing revision %d" % (revision), 1)
 
182
    last_revision = revision
 
183
    idx = pieces[-1].find(' line')
 
184
    if idx != -1:
 
185
      log_lines = int(pieces[-1][:idx])
 
186
    else:
 
187
      log_lines = 0
 
188
 
 
189
    # Now see if there are any changed paths.  If so, parse and process them.
 
190
    line = readline(stream)
 
191
    if line == 'Changed paths:':
 
192
      while 1:
 
193
        try:
 
194
          line = readline(stream)
 
195
        except EOFError:
 
196
          eof = True
 
197
          break
 
198
        match = action_re.search(line)
 
199
        if match:
 
200
          found_changed_path = True
 
201
          match = copy_action_re.search(line)
 
202
          if match:
 
203
            path_copies[sanitize_path(match.group(1))] = \
 
204
              sanitize_path(match.group(2))
 
205
        else:
 
206
          break
 
207
      dt.handle_changes(path_copies)
 
208
 
 
209
    # Finally, skip any log message lines.  (If there are none,
 
210
    # remember the last line we read, because it probably has
 
211
    # something important in it.)
 
212
    if log_lines:
 
213
      for i in range(log_lines):
 
214
        readline(stream)
 
215
      line_buf = None
 
216
    else:
 
217
      line_buf = line
 
218
 
 
219
  if not found_changed_path:
 
220
    raise LogStreamError("No changed paths found; did you remember to run "
 
221
                         "'svn log' with the --verbose (-v) option when "
 
222
                         "generating the input to this script?")
 
223
 
 
224
  return dt
 
225
 
 
226
def analyze_logs(included_paths):
 
227
  print "Initial include paths:"
 
228
  for path in included_paths:
 
229
    print " + /%s" % (path)
 
230
 
 
231
  dt = svn_log_stream_get_dependencies(sys.stdin, included_paths)
 
232
 
 
233
  if dt.dependent_paths:
 
234
    found_new_deps = True
 
235
    print "Dependent include paths found:"
 
236
    for path in dt.dependent_paths:
 
237
      print " + /%s" % (path)
 
238
    print "You need to also include them (or one of their parents)."
 
239
  else:
 
240
    found_new_deps = False
 
241
    print "No new dependencies found!"
 
242
    parents = {}
 
243
    for path in dt.include_paths:
 
244
      while 1:
 
245
        parent = os.path.dirname(path)
 
246
        if not parent:
 
247
          break
 
248
        parents[parent] = 1
 
249
        path = parent
 
250
    parents = parents.keys()
 
251
    if parents:
 
252
      print "You might still need to manually create parent directories " \
 
253
            "for the included paths before loading a filtered dump:"
 
254
      parents.sort(compare_paths)
 
255
      for parent in parents:
 
256
        print "   /%s" % (parent)
 
257
 
 
258
  return found_new_deps and EXIT_MOREDEPS or EXIT_SUCCESS
 
259
 
 
260
def usage_and_exit(errmsg=None):
 
261
  program = os.path.basename(sys.argv[0])
 
262
  stream = errmsg and sys.stderr or sys.stdout
 
263
  stream.write(__doc__.replace("{PROGRAM}", program))
 
264
  if errmsg:
 
265
    stream.write("\nERROR: %s\n" % (errmsg))
 
266
  sys.exit(errmsg and EXIT_FAILURE or EXIT_SUCCESS)
 
267
 
 
268
def main():
 
269
  config_dir = None
 
270
  targets_file = None
 
271
 
 
272
  try:
 
273
    opts, args = getopt.getopt(sys.argv[1:], "hv",
 
274
                               ["help", "verbose", "targets="])
 
275
  except getopt.GetoptError, e:
 
276
    usage_and_exit(str(e))
 
277
 
 
278
  for option, value in opts:
 
279
    if option in ['-h', '--help']:
 
280
      usage_and_exit()
 
281
    elif option in ['-v', '--verbose']:
 
282
      global verbosity
 
283
      verbosity = verbosity + 1
 
284
    elif option in ['--targets']:
 
285
      targets_file = value
 
286
 
 
287
  if len(args) == 0:
 
288
    usage_and_exit("Not enough arguments")
 
289
 
 
290
  if targets_file is None:
 
291
    targets = args[1:]
 
292
  else:
 
293
    targets = map(lambda x: x.rstrip('\n\r'),
 
294
                  open(targets_file, 'r').readlines())
 
295
  if not targets:
 
296
    usage_and_exit("No target paths specified")
 
297
 
 
298
  try:
 
299
    if args[0] == 'include':
 
300
      sys.exit(analyze_logs(map(sanitize_path, targets)))
 
301
    elif args[0] == 'exclude':
 
302
      usage_and_exit("Feature not implemented")
 
303
    else:
 
304
      usage_and_exit("Valid subcommands are 'include' and 'exclude'")
 
305
  except SystemExit:
 
306
    raise
 
307
  except (LogStreamError, EOFError), e:
 
308
    log("ERROR: " + str(e), 0)
 
309
    sys.exit(EXIT_FAILURE)
 
310
  except:
 
311
    import traceback
 
312
    exc_type, exc, exc_tb = sys.exc_info()
 
313
    tb = traceback.format_exception(exc_type, exc, exc_tb)
 
314
    sys.stderr.write(''.join(tb))
 
315
    sys.exit(EXIT_FAILURE)
 
316
 
 
317
 
 
318
if __name__ == "__main__":
 
319
    main()