topgit_msg: expose bare branches
[topgit/pro.git] / awk / topgit_msg.awk
blob67b232c781e3cc1342cc00c2bb801f7f070c02a7
1 #!/usr/bin/awk -f
3 # topgit_msg - TopGit awk utility script used by tg--awksome
4 # Copyright (C) 2017 Kyle J. McKay <mackyle@gmail.com>
5 # All rights reserved.
6 # License GPLv2
8 # topgit_msg
10 # variable arguments (-v):
12 # withan if true, include annihilated branches in output
13 # withmt empty branches: "" (like withan), true (include) false (exclude)
14 # nokind exclude the kind (2nd) field from any output lines
15 # noname exclude the name (1st) field from any output line
16 # only1 exit successfully after outputting first result
17 # colfmt do some simple column formatting if more than one output column
18 # kwregex case-insensitive keyword to match instead of "Subject"
19 # exclbr whitespace separated list of names to exclude
20 # inclbr whitespace separated list of names to include
22 # Note that if kwregex is non-empty and not "Subject" fancy missing
23 # descriptions will be omitted and an empty string will be used
25 # kwregex must match the entire keyword or it will not be considered a match
27 # if kwregex starts with a "+" the "+" will be stripped and each matching
28 # line will have the "pretty" keyword plus ": " prefixed to it (the "pretty"
29 # keyword is all lowercased except for the first and any chars following an
30 # internal "-" except that "ID" and "MIME" are always all uppercased)
32 # use of the "+"<regex> form will cause multiple matches for the same keyword
33 # to all be output in the order encountered (otherwise just the first match is
34 # output)
36 # if inclbr is non-empty a branch name must be listed to appear on stdout
38 # if a branch name appears in exclbr it is omitted from stdout trumping inclbr
40 # input must be result of the git --batch output as described for
41 # awk_topgit_msg_prepare
43 # Note that if only1 is true or kwregex starts with a "+" then nothing at
44 # all (normally there would at least be a blank line) will be output when
45 # there are no matches
47 # output is 0 or more branch lines with .topmsg "Subject:" descriptions
48 # in the same order they appear on the input in this format:
50 # <TopGit_branch_name> K description of the TopGit branch
52 # But if nokind is true the "K" field will be omitted; K has the same semantics
53 # as described for awk_topgit_msg_prepare output
55 # If noname is true the branch name field will be omitted (typically this is
56 # only ever useful when processing a single branch in which case a faked length
57 # may be used on the input as long as it includes at least the last character
58 # in the subject string -- it may even be much bigger than the actual data
59 # length with no problem if used together with only1 set to true)
61 # If withmt is empty then empty branches (K == 3) will be treated exactly the
62 # same as annihilated (K == 2) branches; otherwise if withmt is true empty
63 # branches will be included regardless of the withan value; otherwise if
64 # withmt is false (but not "") empty branches will be excluded regardless of
65 # the withan value
67 # If withan is true annihilated branches will be included (and empty branches
68 # if withmt is "") otherwise if withan is false annihilated branches will be
69 # excluded (and empty branches if withmt is "") from the output
71 # note that if branches were excluded during the prepare phase they will
72 # continue to be excluded here regardless of any withan/withmt values as this
73 # script lacks the ability to resurrect them in that case
75 # Some valid TopGit branches may not have a .topmsg file and annihilated
76 # branches certainly do not so setting withan/withmt true will only give good
77 # results if awk_topgit_msg_prepare was passed the empty blob's hash for its
78 # "missing" variable
80 # ALWAYS PASS THE EMPTY BLOB'S HASH AS awk_topgit_msg_prepare's missing VALUE!
82 # Read the previous paragraphs again; unless you are 100% certain that every
83 # branch you want to appear in the output has a .topmsg file even if only an
84 # empty one, do what it says to do when running the prepare step
87 BEGIN { exitcode = "" }
88 function exitnow(e) { exitcode=e; exit e }
89 END { if (exitcode != "") exit exitcode }
91 BEGIN {
92 inconly = 0
93 cnt = split(inclbr, scratch, " ")
94 if (cnt) {
95 inconly = 1
96 for (i = 1; i <= cnt; ++i) incnames[scratch[i]] = 1
98 cnt = split(exclbr, scratch, " ")
99 for (i = 1; i <= cnt; ++i) excnames[scratch[i]] = 1
100 if (kwregex == "") kwregex = "subject"
101 kwregex = tolower(kwregex)
102 subjrx = kwregex == "subject"
103 if (substr(kwregex, 1, 1) == "+") {
104 inclkw = 1
105 kwregex = substr(kwregex, 2)
107 if (index(kwregex, "|") &&
108 (substr(kwregex, 1, 1) != "(" || substr(kwregex, length(kwregex)) != ")"))
109 kwregex = "(" kwregex ")"
110 if (substr(kwregex, 1, 1) != "^") kwregex = "^" kwregex
111 if (substr(kwregex, length(kwregex)) != "$") kwregex = kwregex "$"
112 if (OFS == "") OFS = " "
113 doblank = !only1 && !inclkw
116 function included(abranch) {
117 return (!inconly || incnames[abranch]) && !excnames[abranch]
120 function wanted(abranch, akind) {
121 return !akind || akind == 1 || akind == 4 ||
122 (withmt != "" && akind == 3 && withmt) ||
123 ((akind != 3 || withmt == "") && withan)
126 function strapp(str1, str2) {
127 if (str1 == "") return str2
128 if (str2 == "") return str1
129 return str1 " " str2
132 function trimsp(str) {
133 gsub(/[ \t\r\n]+/, " ", str)
134 sub(/^[ \t\r\n]+/, "", str)
135 sub(/[ \t\r\n]+$/, "", str)
136 return str
139 function prettykw(k, _kparts, _i, _c, _ans, _kpart) {
140 _ans = ""
141 _c = split(tolower(k), _kparts, /-/)
142 for (_i=1; _i<=_c; ++_i) {
143 _kpart = _kparts[_i]
144 if (_kpart == "id") _kpart = "ID"
145 else if (_kpart == "mime") _kpart = "MIME"
146 else _kpart = toupper(substr(_kpart, 1, 1)) substr(_kpart, 2)
147 _ans = _ans "-" _kpart
149 return substr(_ans, 2)
152 NF == 4 && $4 != "" && $3 != "" && $2 != "missing" && $1 != "" &&
153 $3 ~ /^[01234]$/ && $2 ~ /^[0-9]+$/ {
154 bn = $4
155 kind = $3
156 datalen = $2 + 1
157 curlen = 0
158 cnt = 0
159 err = 0
160 inbody = $1 != "blob"
161 insubj = 0
162 subj = ""
163 while (curlen < datalen && (err = getline) > 0) {
164 curlen += length($0) + 1
165 if (!inbody) {
166 if (/^[ \t]*$/) inbody = 1
167 else if (insubj) {
168 if (/^[ \t\r\n]/) subj = strapp(subj, trimsp($0))
169 else if (inclkw) insubj = 0
170 else inbody = 1
173 if (inbody || insubj) continue
174 if (match($0, /^[^ \t\r\n:]+:/) &&
175 match((kw=tolower(substr($0, RSTART, RLENGTH - 1))), kwregex)) {
176 insubj = 1
177 oldsubj = subj
178 subj = trimsp(substr($0, RLENGTH + 2))
179 if (inclkw) {
180 subj = strapp(prettykw(kw) ":", subj)
181 if (oldsubj != "") subj = oldsubj "\n" subj
185 if (included(bn) && wanted(bn, kind)) {
186 if (subjrx) {
187 if (!kind) {
188 if (insubj) {
189 if (subj == "")
190 subj = "branch " bn " (empty \"Subject:\" in .topmsg)"
191 } else {
192 subj = "branch " bn " (missing \"Subject:\" in .topmsg)"
194 } else if (kind == 1) {
195 subj = "branch " bn " (missing .topmsg)"
196 } else if (kind == 4) {
197 subj = "branch " bn " (bare branch)"
198 } else if (kind == 3) {
199 subj = "branch " bn " (no commits)"
200 } else {
201 subj = "branch " bn " (annihilated)"
204 outline = ""
205 if (!noname || !nokind) {
206 if (!noname) {
207 outline = bn
208 if (!nokind) outline = outline " " kind
209 } else outline = kind
210 if (colfmt) outline = sprintf("%-39s\t", outline)
211 else outline = outline OFS
213 if (doblank || subj != "" || outline != "")
214 print outline subj
215 if (only1) exitnow(0)
217 if (err < 0) exitnow(2)