svn2log.py
Go to the documentation of this file.
1 #!/usr/bin/python
2 #
3 # Copyright (c) 2003 The University of Wroclaw.
4 # All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
8 # are met:
9 # 1. Redistributions of source code must retain the above copyright
10 # notice, this list of conditions and the following disclaimer.
11 # 2. Redistributions in binary form must reproduce the above copyright
12 # notice, this list of conditions and the following disclaimer in the
13 # documentation and/or other materials provided with the distribution.
14 # 3. The name of the University may not be used to endorse or promote
15 # products derived from this software without specific prior
16 # written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY ``AS IS'' AND ANY EXPRESS OR
19 # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
21 # NO EVENT SHALL THE UNIVERSITY BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
23 # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 #
29 # History:
30 #
31 # 2006-08-03 Przedsiebiorstwo Informatyczne CORE <biuro at core.com.pl>:
32 # * Following options were added:
33 # -s, --strip-comments strip /* ... */ comments in log
34 # -O, --only-date generate only dates (without time)
35 # -L, --no-files generate log without filenames
36 # -A, --no-author generate log without author names
37 # -H, --no-host generate author name without hostname
38 #
39 
40 
41 import sys
42 import os
43 import time
44 import re
45 import getopt
46 import string
47 import codecs
48 
49 from xml.utils import qp_xml
50 
51 kill_prefix_rx = None
52 default_domain = "localhost"
53 exclude = []
54 users = { }
55 reloc = { }
56 max_join_delta = 3 * 60
57 list_format = False
58 strip = False
59 date_only = False
60 no_files = False
61 no_host = False
62 no_author = False
63 
64 date_rx = re.compile(r"^(\d+-\d+-\d+T\d+:\d+:\d+)")
65 
66 def die(msg):
67  sys.stderr.write(msg + "\n")
68  sys.exit(1)
69 
70 def attr(e, n):
71  return e.attrs[("", n)]
72 
73 def has_child(e, n):
74  for c in e.children:
75  if c.name == n: return 1
76  return 0
77 
78 def child(e, n):
79  for c in e.children:
80  if c.name == n: return c
81  die("<%s> doesn't have <%s> child" % (e.name, n))
82 
83 def convert_path(n):
84  for src in reloc.keys():
85  n = string.replace(n, src, reloc[src])
86  if kill_prefix_rx != None:
87  if kill_prefix_rx.search(n):
88  n = kill_prefix_rx.sub("", n)
89  else:
90  return None
91  if n.startswith("/"): n = n[1:]
92  if n == "": n = "/"
93  for pref in exclude:
94  if n.startswith(pref):
95  return None
96  return n
97 
98 def convert_user(u):
99  if no_author == False:
100  if users.has_key(u):
101  return users[u]
102  else:
103  if no_host:
104  return u + ":"
105  else:
106  return "%s <%s@%s>:" % (u, u, default_domain)
107  else:
108  return ''
109 
110 def wrap_text_line(str, pref, width, start):
111  ret = u""
112  line = u""
113  first_line = True
114  for word in str.split():
115  if line == u"":
116  line = word
117  else:
118  if len(line + u" " + word) > (width - start):
119  if first_line:
120  ret += line + u"\n"
121  first_line = False
122  start = 0
123  line = word
124  else:
125  ret += pref + line + u"\n"
126  line = word
127  else:
128  line += u" " + word
129  if first_line:
130  ret += line
131  else:
132  ret += pref + line
133  return ret
134 
135 def wrap_text(str, pref, width, start = 0):
136  if not list_format:
137  return wrap_text_line(str,pref,width,start)
138  else:
139  items = re.split(r"\-\s+",str)
140  ret = wrap_text_line(items[0],pref,width,start)
141  for item in items[1:]:
142  ret += pref + u"- " + wrap_text_line(item,pref+" ",width,start)
143  return ret
144 
145 class Entry:
146  def __init__(self, tm, rev, author, msg):
147  self.tm = tm
148  self.rev = rev
149  self.author = author
150  self.msg = msg
151  self.beg_tm = tm
152  self.beg_rev = rev
153 
154  def join(self, other):
155  self.tm = other.tm
156  self.rev = other.rev
157  self.msg += other.msg
158 
159  def dump(self, out):
160  if len(self.msg) > 0:
161  if date_only == False:
162  tformat = "%Y-%m-%d %H:%M +0000"
163  else:
164  tformat = "%Y-%m-%d"
165 
166  if self.rev != self.beg_rev:
167  out.write("%s [r%s-%s] %s\n\n" % \
168  (time.strftime(tformat, time.localtime(self.beg_tm)), \
169  self.rev, self.beg_rev, convert_user(self.author)))
170  else:
171  out.write("%s [r%s] %s\n\n" % \
172  (time.strftime(tformat, time.localtime(self.beg_tm)), \
173  self.rev, convert_user(self.author)))
174  out.write(self.msg)
175 
176  def can_join(self, other):
177  return self.author == other.author and abs(self.tm - other.tm) < max_join_delta
178 
180  rev = attr(e, "revision")
181  if has_child(e, "author"):
182  author = child(e, "author").textof()
183  else:
184  author = "anonymous"
185  m = date_rx.search(child(e, "date").textof())
186  msg = ' ' + child(e, "msg").textof()
187  if strip == True:
188  ibegin = string.find(msg, "/*")
189  if ibegin > 0:
190  iend = string.find(msg, "*/") + 2
191  msg = msg[0:ibegin] + msg[iend:]
192 
193  if m:
194  tm = time.mktime(time.strptime(m.group(1), "%Y-%m-%dT%H:%M:%S"))
195  else:
196  die("evil date: %s" % child(e, "date").textof())
197  paths = []
198  if len(msg) > 1:
199  for path in child(e, "paths").children:
200  if path.name != "path": die("<paths> has non-<path> child")
201  nam = convert_path(path.textof())
202  if nam != None:
203  if attr(path, "action") == "D":
204  paths.append(nam + " (removed)")
205  elif attr(path, "action") == "A":
206  paths.append(nam + " (added)")
207  else:
208  paths.append(nam)
209 
210  if paths != [] and no_files == False:
211  pathlines = wrap_text(", ".join(paths),"\t* ", 65)
212  start = len(pathlines) - pathlines.rfind("\n") + 1
213  message = wrap_text(": " + msg, "\t ", 65, start )
214  return Entry(tm, rev, author, "\t* %s %s\n\n" % (pathlines, message))
215  elif paths != [] and no_files == True:
216  return Entry(tm, rev, author, "\t* %s\n\n" % wrap_text(msg, "\t ", 65))
217 
218  return None
219 
220 def process(fin, fout):
221  parser = qp_xml.Parser()
222  root = parser.parse(fin)
223 
224  if root.name != "log": die("root is not <log>")
225 
226  cur = None
227 
228  for logentry in root.children:
229  if logentry.name != "logentry": die("non <logentry> <log> child")
230  e = process_entry(logentry)
231  if e != None:
232  if cur != None:
233  if cur.can_join(e):
234  cur.join(e)
235  else:
236  cur.dump(fout)
237  cur = e
238  else: cur = e
239 
240  if cur != None: cur.dump(fout)
241 
242 def usage():
243  sys.stderr.write(\
244 """Usage: %s [OPTIONS] [FILE]
245 Convert specified subversion xml logfile to GNU-style ChangeLog.
246 
247 Options:
248  -p, --prefix=REGEXP set root directory of project (it will be striped off
249  from ChangeLog entries, paths outside it will be
250  ignored)
251  -x, --exclude=DIR exclude DIR from ChangeLog (relative to prefix)
252  -o, --output set output file (defaults to 'ChangeLog')
253  -d, --domain=DOMAIN set default domain for logins not listed in users file
254  -u, --users=FILE read logins from specified file
255  -F, --list-format format commit logs with enumerated change list (items
256  prefixed by '- ')
257  -r, --relocate=X=Y before doing any other operations on paths, replace
258  X with Y (useful for directory moves)
259  -D, --delta=SECS when log entries differ by less then SECS seconds and
260  have the same author -- they are merged, it defaults
261  to 180 seconds
262  -h, --help print this information
263  -s, --strip-comments strip /* ... */ comments in log
264  -O, --only-date generate only dates (without time)
265  -L, --no-files generate log without filenames
266  -A, --no-author generate log without author names
267  -H, --no-host generate author name without hostname
268 
269 Users file is used to map svn logins to real names to appear in ChangeLog.
270 If login is not found in users file "login <login@domain>" is used.
271 
272 Example users file:
273 john John X. Foo <jfoo@example.org>
274 mark Marcus Blah <mb@example.org>
275 
276 Typical usage of this script is something like this:
277 
278  svn log -v --xml | %s -p '/foo/(branches/[^/]+|trunk)' -u aux/users
279 
280 Please send bug reports and comments to author:
281  Michal Moskal <malekith@pld-linux.org>
282 
283 Regarding -s, -O, -L, -A, -H options see
284  http://www.core.com.pl/svn2log
285 
286 
287 """ % (sys.argv[0], sys.argv[0]))
288 
289 def utf_open(name, mode):
290  return codecs.open(name, mode, encoding="utf-8", errors="replace")
291 
293  try:
294  opts, args = getopt.gnu_getopt(sys.argv[1:], "o:u:p:x:d:r:d:D:FhsOLHA",
295  ["users=", "prefix=", "domain=", "delta=",
296  "exclude=", "help", "output=", "relocate=",
297  "list-format","strip-comments", "only-date", "no-files",
298  "no-host", "no-author"])
299  except getopt.GetoptError:
300  usage()
301  sys.exit(2)
302  fin = sys.stdin
303  fout = None
304  global kill_prefix_rx, exclude, users, default_domain, reloc, max_join_delta, list_format, strip, date_only, no_files, no_host, no_author
305  for o, a in opts:
306  if o in ("--prefix", "-p"):
307  kill_prefix_rx = re.compile("^" + a)
308  elif o in ("--exclude", "-x"):
309  exclude.append(a)
310  elif o in ("--help", "-h"):
311  usage()
312  sys.exit(0)
313  elif o in ("--output", "-o"):
314  fout = utf_open(a, "w")
315  elif o in ("--domain", "-d"):
316  default_domain = a
317  elif o in ("--strip-comments", "-s"):
318  strip = True
319  elif o in ("--only-date", "-O"):
320  date_only = True
321  elif o in ("--no-files", "-L"):
322  no_files = True
323  elif o in ("--no-host", "-H"):
324  no_host = True
325  elif o in ("--no-author", "-A"):
326  no_author = True
327  elif o in ("--users", "-u"):
328  f = utf_open(a, "r")
329  for line in f.xreadlines():
330  w = line.split()
331  if len(line) < 1 or line[0] == '#' or len(w) < 2:
332  continue
333  users[w[0]] = " ".join(w[1:])
334  elif o in ("--relocate", "-r"):
335  (src, target) = a.split("=")
336  reloc[src] = target
337  elif o in ("--delta", "-D"):
338  max_join_delta = int(a)
339  elif o in ("--list-format", "-F"):
340  list_format = True
341  else:
342  usage()
343  sys.exit(2)
344  if len(args) > 1:
345  usage()
346  sys.exit(2)
347  if len(args) == 1:
348  fin = open(args[0], "r")
349  if fout == None:
350  fout = utf_open("ChangeLog", "w")
351  process(fin, fout)
352 
353 if __name__ == "__main__":
354  os.environ['TZ'] = 'UTC'
355  try:
356  time.tzset()
357  except AttributeError:
358  pass
359  process_opts()
def child(e, n)
Definition: svn2log.py:78
def wrap_text_line(str, pref, width, start)
Definition: svn2log.py:110
def process(fin, fout)
Definition: svn2log.py:220
def utf_open(name, mode)
Definition: svn2log.py:289
def join(self, other)
Definition: svn2log.py:154
def has_child(e, n)
Definition: svn2log.py:73
def convert_user(u)
Definition: svn2log.py:98
def can_join(self, other)
Definition: svn2log.py:176
def process_opts()
Definition: svn2log.py:292
def dump(self, out)
Definition: svn2log.py:159
def usage()
Definition: svn2log.py:242
def convert_path(n)
Definition: svn2log.py:83
def __init__(self, tm, rev, author, msg)
Definition: svn2log.py:146
def wrap_text(str, pref, width, start=0)
Definition: svn2log.py:135
def die(msg)
Definition: svn2log.py:66
def attr(e, n)
Definition: svn2log.py:70
def process_entry(e)
Definition: svn2log.py:179


rtt
Author(s): RTT Developers
autogenerated on Tue Jun 25 2019 19:33:36