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
189
190
191
192
193
194
195
|
local log = io.stderr
local main
function printHelp()
io.stdout:write(" \n"
.." Helper to extract essential data from a gitflow log which potentially\n"
.." is useful to write a CHANGELOG from.\n"
.." \n"
.." Options:\n"
.." \n"
.." --since <date>\n"
.." Ignore commits with this ISO date and older.\n"
.." \n"
.." --remote <str>\n"
.." Name of the git remote to use. Defaults to 'upstream'.\n"
.." \n"
.." --no-fetch\n"
.." Do NOT update refs from remote. Just use what we have local.\n"
.." \n"
)
end
function parseArgs( app )
local iA = 0
while true do iA = iA + 1
local arg = _ENV.arg[iA]
if not arg then
break
elseif arg == "--since" then
iA = iA + 1; arg = _ENV.arg[iA]
if not arg then log:write("EINVAL: --since needs value\n")return end
app.since = arg
elseif arg == "--remote" then
iA = iA + 1; arg = _ENV.arg[iA]
if not arg then log:write("EINVAL: --remote needs value\n")return end
app.remoteName = arg
elseif arg == "--no-fetch" then
app.isFetch = false
elseif arg == "--help" then
app.isHelp = true; return 0
else
log:write("EINVAL: ".. arg .."\n")return
end
end
if not app.since then log:write("EINVAL: --since missing\n")return end
if not app.remoteName then app.remoteName = "upstream" end
return 0
end
function readCommitHdr( app )
--log:write("[DEBUG] parse hdr from '".. app.fullHistory:sub(app.fullHistoryRdBeg, app.fullHistoryRdBeg+256) .."...'\n")
local f, t = app.fullHistory:find("^"
.."commit ........................................[^\n]*\n"
.."Merge: [0-9a-z]+ [0-9a-z]+\n"
.."Author: [^\n]+\n"
.."Date: [^\n]+\n"
.."\n"
, app.fullHistoryRdBeg)
if not f then f, t = app.fullHistory:find("^"
.."commit ........................................[^\n]*\n"
.."Author: [^\n]+\n"
.."Date: [^\n]+\n"
.."\n"
, app.fullHistoryRdBeg) end
if not f then
assert(app.fullHistory:len() == app.fullHistoryRdBeg-1, app.fullHistory:len()..", "..app.fullHistoryRdBeg)
app.parseFn = false
return
end
app.commitHdr = assert(app.fullHistory:sub(f, t-1))
--log:write("hdrBeginsWith '"..(app.commitHdr:sub(1, 32)).."...'\n")
app.fullHistoryRdBeg = t + 1
--log:write("hdr parsed. rdCursr now points to '".. app.fullHistory:sub(app.fullHistoryRdBeg, app.fullHistoryRdBeg+16) .."...'\n")
app.parseFn = assert(readCommitMsg)
end
function readCommitMsg( app )
local idxOfC = app.fullHistoryRdBeg
local chrPrev = false
while true do idxOfC = idxOfC + 1
local chr = app.fullHistory:byte(idxOfC)
--log:write("CHR '"..tostring(app.fullHistory:sub(idxOfC, idxOfC)).."'\n")
if (chr == 0x63) and chrPrev == 0x0A then
idxOfC = idxOfC - 1
break -- LF followed by 'c' (aka 'commit') found
elseif not chr then
idxOfC = idxOfC - 1
break
else
chrPrev = assert(chr)
end
end
local mtch = app.fullHistory:sub(app.fullHistoryRdBeg, idxOfC - 1)
assert(mtch)
while mtch:byte(mtch:len()) == 0x0A do mtch = mtch:sub(1, -2) end
mtch = mtch:gsub("\n ", "\n"):gsub("^ ", "")
app.commitMsg = mtch
app.fullHistoryRdBeg = idxOfC + 1
app.parseFn = readCommitHdr
--log:write("msg parsed. rdCursr now points to '".. app.fullHistory:sub(app.fullHistoryRdBeg, app.fullHistoryRdBeg+16) .."...'\n")
table.insert(app.commits, {
hdr = assert(app.commitHdr),
msg = assert(app.commitMsg),
})
end
function run( app )
local snk = io.stdout
if app.isFetch then
-- Make sure refs are up-to-date
local gitFetch = "git fetch \"".. app.remoteName .."\""
log:write("[DEBUG] ".. gitFetch .."\n")
local gitFetch = io.popen(gitFetch)
while true do
local buf = gitFetch:read(1<<16)
if not buf then break end
log:write(buf)
end
end
-- Collect input
local git = "git log --date-order --first-parent --decorate --since \"".. app.since.."\""
.." \"".. app.remoteName .."/master\""
.." \"".. app.remoteName .."/develop\""
log:write("[DEBUG] ".. git .."\n")
local git = io.popen(git)
while true do
local buf = git:read(1<<16)
if not buf then break end
--io.stdout:write(buf)
table.insert(app.fullHistory, buf)
end
-- Parse raw commits
app.fullHistory = table.concat(app.fullHistory)
app.parseFn = assert(readCommitHdr)
while app.parseFn do app.parseFn(app) end
-- Prepare output
local prevDate = "0000-00-00"
local version, prevVersion = "v_._._", false
local dateEntry = false
local entries = {}
for k, v in ipairs(app.commits) do
local date = assert(v.hdr:match("\nDate: +([0-9-]+) "))
local author = assert(v.hdr:match("\nAuthor: +([^\n]+)\n"))
local prNr, short = v.msg:match("Pull request #(%d+): ([^\n]+)\n")
prevVersion = version
_, version = v.hdr:match("^([^\n]+)\n"):match("tag: ([a-z]+)-([^,]+)[,)]")
if not version then version = prevVersion end
if version ~= prevVersion or not dateEntry then
if dateEntry then table.insert(entries, dateEntry) end
dateEntry = {
txt = date .." - ".. version .."\n\nResolved issues:\n\n"
}
prevDate = date
end
if prNr then
dateEntry.txt = dateEntry.txt .. short .." (PR ".. prNr ..")\n"
else
dateEntry.txt = dateEntry.txt .. v.msg .."\n"
end
end
if dateEntry then table.insert(entries, dateEntry) end
-- output
for k, v in ipairs(entries) do
snk:write("\n\n")
snk:write(v.txt)
snk:write("\n")
end
end
function main()
local app = {
since = false,
remoteName = false,
isFetch = true,
fullHistory = {},
fullHistoryRdBeg = 1,
commits = {},
parseFn = false,
}
if parseArgs(app) ~= 0 then os.exit(1) end
if app.isHelp then printHelp() return end
run(app)
end
main()
|