49 from xml.utils
import qp_xml
52 default_domain =
"localhost" 56 max_join_delta = 3 * 60
64 date_rx = re.compile(
r"^(\d+-\d+-\d+T\d+:\d+:\d+)")
67 sys.stderr.write(msg +
"\n")
71 return e.attrs[(
"", n)]
75 if c.name == n:
return 1
80 if c.name == n:
return c
81 die(
"<%s> doesn't have <%s> child" % (e.name, 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)
91 if n.startswith(
"/"): n = n[1:]
94 if n.startswith(pref):
99 if no_author ==
False:
106 return "%s <%s@%s>:" % (u, u, default_domain)
114 for word
in str.split():
118 if len(line +
u" " + word) > (width - start):
125 ret += pref + line +
u"\n" 139 items = re.split(
r"\-\s+",str)
141 for item
in items[1:]:
157 self.
msg += other.msg
160 if len(self.
msg) > 0:
161 if date_only ==
False:
162 tformat =
"%Y-%m-%d %H:%M +0000" 167 out.write(
"%s [r%s-%s] %s\n\n" % \
168 (time.strftime(tformat, time.localtime(self.
beg_tm)), \
171 out.write(
"%s [r%s] %s\n\n" % \
172 (time.strftime(tformat, time.localtime(self.
beg_tm)), \
177 return self.
author == other.author
and abs(self.
tm - other.tm) < max_join_delta
180 rev =
attr(e,
"revision")
182 author =
child(e,
"author").textof()
185 m = date_rx.search(
child(e,
"date").textof())
186 msg =
' ' +
child(e,
"msg").textof()
188 ibegin = string.find(msg,
"/*")
190 iend = string.find(msg,
"*/") + 2
191 msg = msg[0:ibegin] + msg[iend:]
194 tm = time.mktime(time.strptime(m.group(1),
"%Y-%m-%dT%H:%M:%S"))
196 die(
"evil date: %s" %
child(e,
"date").textof())
199 for path
in child(e,
"paths").children:
200 if path.name !=
"path":
die(
"<paths> has non-<path> child")
203 if attr(path,
"action") ==
"D":
204 paths.append(nam +
" (removed)")
205 elif attr(path,
"action") ==
"A":
206 paths.append(nam +
" (added)")
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))
221 parser = qp_xml.Parser()
222 root = parser.parse(fin)
224 if root.name !=
"log":
die(
"root is not <log>")
228 for logentry
in root.children:
229 if logentry.name !=
"logentry":
die(
"non <logentry> <log> child")
240 if cur !=
None: cur.dump(fout)
244 """Usage: %s [OPTIONS] [FILE] 245 Convert specified subversion xml logfile to GNU-style ChangeLog. 248 -p, --prefix=REGEXP set root directory of project (it will be striped off 249 from ChangeLog entries, paths outside it will be 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 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 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 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. 273 john John X. Foo <jfoo@example.org> 274 mark Marcus Blah <mb@example.org> 276 Typical usage of this script is something like this: 278 svn log -v --xml | %s -p '/foo/(branches/[^/]+|trunk)' -u aux/users 280 Please send bug reports and comments to author: 281 Michal Moskal <malekith@pld-linux.org> 283 Regarding -s, -O, -L, -A, -H options see 284 http://www.core.com.pl/svn2log 287 """ % (sys.argv[0], sys.argv[0]))
290 return codecs.open(name, mode, encoding=
"utf-8", errors=
"replace")
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:
304 global kill_prefix_rx, exclude, users, default_domain, reloc, max_join_delta, list_format, strip, date_only, no_files, no_host, no_author
306 if o
in (
"--prefix",
"-p"):
307 kill_prefix_rx = re.compile(
"^" + a)
308 elif o
in (
"--exclude",
"-x"):
310 elif o
in (
"--help",
"-h"):
313 elif o
in (
"--output",
"-o"):
315 elif o
in (
"--domain",
"-d"):
317 elif o
in (
"--strip-comments",
"-s"):
319 elif o
in (
"--only-date",
"-O"):
321 elif o
in (
"--no-files",
"-L"):
323 elif o
in (
"--no-host",
"-H"):
325 elif o
in (
"--no-author",
"-A"):
327 elif o
in (
"--users",
"-u"):
329 for line
in f.xreadlines():
331 if len(line) < 1
or line[0] ==
'#' or len(w) < 2:
333 users[w[0]] =
" ".join(w[1:])
334 elif o
in (
"--relocate",
"-r"):
335 (src, target) = a.split(
"=")
337 elif o
in (
"--delta",
"-D"):
338 max_join_delta = int(a)
339 elif o
in (
"--list-format",
"-F"):
348 fin = open(args[0],
"r") 353 if __name__ ==
"__main__":
354 os.environ[
'TZ'] =
'UTC' 357 except AttributeError:
def wrap_text_line(str, pref, width, start)
def can_join(self, other)
def __init__(self, tm, rev, author, msg)
def wrap_text(str, pref, width, start=0)