- Notifications
You must be signed in to change notification settings - Fork 31.8k
/
Copy pathlogmerge.py
executable file
·185 lines (166 loc) · 5.45 KB
/
logmerge.py
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
#! /usr/bin/env python
"""Consolidate a bunch of CVS or RCS logs read from stdin.
Input should be the output of a CVS or RCS logging command, e.g.
cvs log -rrelease14:
which dumps all log messages from release1.4 upwards (assuming that
release 1.4 was tagged with tag 'release14'). Note the trailing
colon!
This collects all the revision records and outputs them sorted by date
rather than by file, collapsing duplicate revision record, i.e.,
records with the same message for different files.
The -t option causes it to truncate (discard) the last revision log
entry; this is useful when using something like the above cvs log
command, which shows the revisions including the given tag, while you
probably want everything *since* that tag.
The -r option reverses the output (oldest first; the default is oldest
last).
The -b tag option restricts the output to *only* checkin messages
belonging to the given branch tag. The form -b HEAD restricts the
output to checkin messages belonging to the CVS head (trunk). (It
produces some output if tag is a non-branch tag, but this output is
not very useful.)
-h prints this message and exits.
XXX This code was created by reverse engineering CVS 1.9 and RCS 5.7
from their output.
"""
importsys, errno, getopt, re
sep1='='*77+'\n'# file separator
sep2='-'*28+'\n'# revision separator
defmain():
"""Main program"""
truncate_last=0
reverse=0
branch=None
opts, args=getopt.getopt(sys.argv[1:], "trb:h")
foro, ainopts:
ifo=='-t':
truncate_last=1
elifo=='-r':
reverse=1
elifo=='-b':
branch=a
elifo=='-h':
print__doc__
sys.exit(0)
database= []
while1:
chunk=read_chunk(sys.stdin)
ifnotchunk:
break
records=digest_chunk(chunk, branch)
iftruncate_last:
delrecords[-1]
database[len(database):] =records
database.sort()
ifnotreverse:
database.reverse()
format_output(database)
defread_chunk(fp):
"""Read a chunk -- data for one file, ending with sep1.
Split the chunk in parts separated by sep2.
"""
chunk= []
lines= []
while1:
line=fp.readline()
ifnotline:
break
ifline==sep1:
iflines:
chunk.append(lines)
break
ifline==sep2:
iflines:
chunk.append(lines)
lines= []
else:
lines.append(line)
returnchunk
defdigest_chunk(chunk, branch=None):
"""Digest a chunk -- extract working file name and revisions"""
lines=chunk[0]
key='Working file:'
keylen=len(key)
forlineinlines:
ifline[:keylen] ==key:
working_file=line[keylen:].strip()
break
else:
working_file=None
ifbranchisNone:
pass
elifbranch=="HEAD":
branch=re.compile(r"^\d+\.\d+$")
else:
revisions= {}
key='symbolic names:\n'
found=0
forlineinlines:
ifline==key:
found=1
eliffound:
ifline[0] in'\t ':
tag, rev=line.split()
iftag[-1] ==':':
tag=tag[:-1]
revisions[tag] =rev
else:
found=0
rev=revisions.get(branch)
branch=re.compile(r"^<>$") # <> to force a mismatch by default
ifrev:
ifrev.find('.0.') >=0:
rev=rev.replace('.0.', '.')
branch=re.compile(r"^"+re.escape(rev) +r"\.\d+$")
records= []
forlinesinchunk[1:]:
revline=lines[0]
dateline=lines[1]
text=lines[2:]
words=dateline.split()
author=None
iflen(words) >=3andwords[0] =='date:':
dateword=words[1]
timeword=words[2]
iftimeword[-1:] ==';':
timeword=timeword[:-1]
date=dateword+' '+timeword
iflen(words) >=5andwords[3] =='author:':
author=words[4]
ifauthor[-1:] ==';':
author=author[:-1]
else:
date=None
text.insert(0, revline)
words=revline.split()
iflen(words) >=2andwords[0] =='revision':
rev=words[1]
else:
# No 'revision' line -- weird...
rev=None
text.insert(0, revline)
ifbranch:
ifrevisNoneornotbranch.match(rev):
continue
records.append((date, working_file, rev, author, text))
returnrecords
defformat_output(database):
prevtext=None
prev= []
database.append((None, None, None, None, None)) # Sentinel
for (date, working_file, rev, author, text) indatabase:
iftext!=prevtext:
ifprev:
printsep2,
for (p_date, p_working_file, p_rev, p_author) inprev:
printp_date, p_author, p_working_file, p_rev
sys.stdout.writelines(prevtext)
prev= []
prev.append((date, working_file, rev, author))
prevtext=text
if__name__=='__main__':
try:
main()
exceptIOError, e:
ife.errno!=errno.EPIPE:
raise