Merge lp:~stefan.goetz-deactivatedaccount/hipl/commitguard into lp:hipl

Proposed by Stefan Götz
Status: Superseded
Proposed branch: lp:~stefan.goetz-deactivatedaccount/hipl/commitguard
Merge into: lp:hipl
Diff against target: 535 lines (+510/-0)
5 files modified
.commitguards/author-attribution.guard (+31/-0)
.commitguards/copyright-guard-space.guard (+27/-0)
.commitguards/copyright-guard-space.py (+65/-0)
.commitguards/copyright-year.guard (+36/-0)
tools/bazaar/plugins/commitguard.py (+351/-0)
To merge this branch: bzr merge lp:~stefan.goetz-deactivatedaccount/hipl/commitguard
Reviewer Review Type Date Requested Status
Miika Komu Approve
Diego Biurrun Needs Fixing
Review via email: mp+89191@code.launchpad.net

This proposal has been superseded by a proposal from 2012-01-23.

Description of the change

Adds a bazaar pre-commit hook for checking file contents before a commit and to aborting the commit if necessary. Also includes a few guards that enforce some aspects of HIPL code style policy.

To post a comment you must log in.
Revision history for this message
Diego Biurrun (diego-biurrun) wrote :

 review needs-fixing

On Thu, Jan 19, 2012 at 07:36:24AM +0000, Stefan Götz wrote:
> Stefan Götz has proposed merging lp:~stefan.goetz/hipl/commitguard into lp:hipl.

Looks mostly good to me. I don't speak Python, so this review is
necessarily superficial.

> --- .commitguards/author-attribution.guard 1970-01-01 00:00:00 +0000
> +++ .commitguards/author-attribution.guard 2012-01-19 07:35:28 +0000
> @@ -0,0 +1,4 @@
> +[guard]
> +files=\.(c|h|am|ac|in|py|sh)$
> +actions=amr
> +command=if egrep -q "\*.*@author\b" "{}"; then echo "{}: files should not contain author attributions"; false; fi

Do we really need to restrict this to certain file types?

> --- .commitguards/copyright-guard-space.py 1970-01-01 00:00:00 +0000
> +++ .commitguards/copyright-guard-space.py 2012-01-19 07:35:28 +0000
> @@ -0,0 +1,68 @@
> +#!/usr/bin/env python
> +# -*- coding: latin-1 -*-

Please use UTF-8, this is the 21st century (for some time already).
I'm also not sure if you need to tell emacs about this, the file
should be ASCII anyway.

> --- .commitguards/copyright-year.guard 1970-01-01 00:00:00 +0000
> +++ .commitguards/copyright-year.guard 2012-01-19 07:35:28 +0000
> @@ -0,0 +1,4 @@
> +[guard]
> +files=\.(c|h|am|ac|in|py|sh)$
> +actions=amr
> +command=CR_TOKEN=" Copyright \(c\) "; CR_HOLDER=" Aalto "; Y=$(date +%Y); if ! egrep "$CR_TOKEN.*$CR_HOLDER" "{}" | egrep -q "\b$Y\b"; then echo "{}: The copyright statement is not up-to-date"; sed -r "/ Copyright \(c\) / { /\b$Y\b/ ! s/ ([0-9, -]+) ([a-zA-Z]+)/ $Y \2/ }" "{}" | diff -u "{}" - >&2; false; fi
>
> === added file 'tools/bazaar/plugins/commitguard.py'
> --- tools/bazaar/plugins/commitguard.py 1970-01-01 00:00:00 +0000
> +++ tools/bazaar/plugins/commitguard.py 2012-01-19 07:35:28 +0000
> @@ -0,0 +1,353 @@
> +#!/usr/bin/env python
> +# -*- coding: latin-1 -*-

ditto

> +_commitguard_tutorial = """
> +Bazaar plugin for checking code modifications at pre-commit time.
> +
> +About
> +-----
> +
> +Installation
> +------------
> +Copy this file into the bazaar plugin directory (usually .bazaar/plugins/ in

Bazaar

> + The commitguard plugin offers the user to apply such a patch automatically
> + for convenience.

For added convenience the commitguard plugin offers the user the option
of applying such a patch automatically.

Diego

review: Needs Fixing
Revision history for this message
Stefan Götz (stefan.goetz-deactivatedaccount) wrote :

> Looks mostly good to me. I don't speak Python, so this review is
> necessarily superficial.

Thanks for the review(s)!

>> --- .commitguards/author-attribution.guard 1970-01-01 00:00:00 +0000
>> +++ .commitguards/author-attribution.guard 2012-01-19 07:35:28 +0000
>> @@ -0,0 +1,4 @@
>> +[guard]
>> +files=\.(c|h|am|ac|in|py|sh)$
>> +actions=amr
>> +command=if egrep -q "\*.*@author\b" "{}"; then echo "{}: files should not contain author attributions"; false; fi
>
> Do we really need to restrict this to certain file types?

Not really, no. I changed it so it applies to all files.

>> --- .commitguards/copyright-guard-space.py 1970-01-01 00:00:00 +0000
>> +++ .commitguards/copyright-guard-space.py 2012-01-19 07:35:28 +0000
>> @@ -0,0 +1,68 @@
>> +#!/usr/bin/env python
>> +# -*- coding: latin-1 -*-
>
> Please use UTF-8, this is the 21st century (for some time already).

You're right. Done (removed).

> I'm also not sure if you need to tell emacs about this, the file
> should be ASCII anyway.

This comment is also obeyed by the python interpreter. In fact, it is
necessary as soon as non-plain-ASCII characters are in a python script.

>> bazaar
> Bazaar
>
>> + The commitguard plugin offers the user to apply such a patch automatically
>> + for convenience.
>
> For added convenience the commitguard plugin offers the user the option
> of applying such a patch automatically.

Done.

Cheers,
 Stefan

Revision history for this message
Miika Komu (miika-iki) wrote :

How does the differ from tools/bazaar/plugins/stylecheck.py or what is the relationship with it? And why do we need now two different hooks?

Revision history for this message
Stefan Götz (stefan.goetz-deactivatedaccount) wrote :

> How does the differ from tools/bazaar/plugins/stylecheck.py or what is the relationship with it?

stylecheck.py is specifically tailored towards running a code
beautifier, uncrustify in our case.

commitguard.py makes it much simpler to run arbitrary commands at
pre-commit time and adds a little bit of user convenience. This is
useful for checks that uncrustify cannot perform and is very easy to extend.

> And why do we need now two different hooks?

It would be possible to run uncrustify via the commitguard plugin
instead of the stylecheck plugin. However, some stylecheck features make
it much more usable with uncrustify than commitguard. In particular,
uncrustify can operate on multiple files with stylecheck while
commitguard aborts the commit after the first inconsistent file found,
which can be annoying if there several files need uncrustification.

I'd say that both plugins have their use.

Cheers,
 Stefan

Revision history for this message
Miika Komu (miika-iki) wrote :

Hi,

On 01/19/2012 06:16 PM, Stefan Götz wrote:
>> How does the differ from tools/bazaar/plugins/stylecheck.py or what is the relationship with it?
>
> stylecheck.py is specifically tailored towards running a code
> beautifier, uncrustify in our case.
>
> commitguard.py makes it much simpler to run arbitrary commands at
> pre-commit time and adds a little bit of user convenience. This is
> useful for checks that uncrustify cannot perform and is very easy to extend.
>
>> And why do we need now two different hooks?
>
> It would be possible to run uncrustify via the commitguard plugin
> instead of the stylecheck plugin. However, some stylecheck features make
> it much more usable with uncrustify than commitguard. In particular,
> uncrustify can operate on multiple files with stylecheck while
> commitguard aborts the commit after the first inconsistent file found,
> which can be annoying if there several files need uncrustification.
>
> I'd say that both plugins have their use.

sorry if I forgot some earlier discussions on the topic but until I see
a clear and concrete plan yet to replace stylecheck with commitguard, I
would suggest to sticking to a single tool (stylecheck.py) instead of
introducing another alternative as an extra maintenance burden.

Revision history for this message
Stefan Götz (stefan.goetz-deactivatedaccount) wrote :

> sorry if I forgot some earlier discussions on the topic but until I see
> a clear and concrete plan yet to replace stylecheck with commitguard, I
> would suggest to sticking to a single tool (stylecheck.py) instead of
> introducing another alternative as an extra maintenance burden.

Running uncrustify via commitguard results in a definite and annoying
loss of usability. Thus I strongly recommend keeping stylecheck around.
Otherwise, I'd be worried that users abandon the use of pre-commit hooks
altogether because using uncrustify became more cumbersome.

Note that the commitguard plugin, as is, runs checks that are orthogonal
to what uncrustify does. So this plugin is useful to HIPL developers in
itself. I'm not sure whether this became sufficiently clear.

How about this: I'll create a commit guard that runs uncrustify but
leave it disabled. Should the maintenance burden of having both
commitguard and stylecheck become a real issue, we could always drop
stylecheck at that point. Ok?

Stefan

Revision history for this message
Miika Komu (miika-iki) wrote :

Hi,

On 20/01/12 16:52, Stefan Götz wrote:
>> sorry if I forgot some earlier discussions on the topic but until I see
>> a clear and concrete plan yet to replace stylecheck with commitguard, I
>> would suggest to sticking to a single tool (stylecheck.py) instead of
>> introducing another alternative as an extra maintenance burden.
>
> Running uncrustify via commitguard results in a definite and annoying
> loss of usability. Thus I strongly recommend keeping stylecheck around.
> Otherwise, I'd be worried that users abandon the use of pre-commit hooks
> altogether because using uncrustify became more cumbersome.
>
> Note that the commitguard plugin, as is, runs checks that are orthogonal
> to what uncrustify does. So this plugin is useful to HIPL developers in
> itself. I'm not sure whether this became sufficiently clear.

thanks for making this explicit, I understood this now better. I guess
there's more policies coming to the commitguard plugin?

> How about this: I'll create a commit guard that runs uncrustify but
> leave it disabled.

If it's orthogonal instead of redundant, then no need to.

> Should the maintenance burden of having both
> commitguard and stylecheck become a real issue, we could always drop
> stylecheck at that point. Ok?

Ok.

Revision history for this message
Miika Komu (miika-iki) wrote :

So go ahead with this one.

review: Approve
Revision history for this message
René Hummen (rene-hummen) wrote :

Thanks a lot for the automated commit guards. Very good documentation and extensibility!

I just got a suggested workflow that would allow merging of the stylechecker with this script:
1) Make a local copy of the files touched in the current commit.
2) Apply the commit guards (including uncrustify) one after the other to each of the local copies.
2.1) Tell the user which guard failed on which file.
2.2) Apply an eventual change of a commit guard to the local copy.
3) After all commit guards have been executed (and the corresponding changes have been applied to the local copies incrementally), show the user the diff between the local files and the files in the commit.
3.1) Apply diff, if accepted by the user.

I know that this workflow would require some restructuring of the code. Hence, I don't mind having two scripts for now. However, the benefit would be a single pre-commit hook and the check of all committed files with _all_ hooks in one go. Right now, we need to commit as often as the commit contains "errors" until finally the commit is pushed to the repository.

Additionally, it might be worth having an install_bzr_hooks.sh script that copies the scripts to one of the two valid bazaar plugin locations in order to increase usage and acceptance.

Revision history for this message
Diego Biurrun (diego-biurrun) wrote :

On Thu, Jan 19, 2012 at 04:16:56PM +0000, Stefan Götz wrote:
> > And why do we need now two different hooks?
>
> It would be possible to run uncrustify via the commitguard plugin
> instead of the stylecheck plugin. However, some stylecheck features make
> it much more usable with uncrustify than commitguard. In particular,
> uncrustify can operate on multiple files with stylecheck while
> commitguard aborts the commit after the first inconsistent file found,
> which can be annoying if there several files need uncrustification.

This is a considerable shortcoming IMO. Ideally all issues in all
files should be detected and be complained about. Otherwise you have
to run through multiple iterations of commit guard as you fix issue
after issue...

Diego

Revision history for this message
Stefan Götz (stefan.goetz-deactivatedaccount) wrote :

Hi Rene!

> I just got a suggested workflow that would allow merging of the stylechecker with this script:
> 1) Make a local copy of the files touched in the current commit.
> 2) Apply the commit guards (including uncrustify) one after the other to each of the local copies.
> 2.1) Tell the user which guard failed on which file.
> 2.2) Apply an eventual change of a commit guard to the local copy.
> 3) After all commit guards have been executed (and the corresponding changes have been applied to the local copies incrementally), show the user the diff between the local files and the files in the commit.
> 3.1) Apply diff, if accepted by the user.

That makes a lot of sense and is certainly more usable than the current
approach. I'll work on it on the usability branch.

> Additionally, it might be worth having an install_bzr_hooks.sh script that copies the scripts to one of the two valid bazaar plugin locations in order to increase usage and acceptance.

Yeah, well, I don't think the difference between a single cp command and a
script will change something about usage and acceptance - so I'll let someone
else write that script :)

Stefan

Revision history for this message
Miika Komu (miika-iki) wrote :

I just realized that the only the documentation is in the script itself and there's nothing in the HACKING - is this intentional?

6250. By Diego Biurrun

Ignore test/check_hipd unit test executable.

6251. By Diego Biurrun

hipd: Fix some typos that the DEB package lintian program complained about.

6252. By Diego Biurrun

debian: Abort on errors during dnsproxy postinst script.

6253. By Diego Biurrun

debian: Register HIPL documentation with doc-base.

6254. By Diego Biurrun

debian: Extend package descriptions.

6255. By Diego Biurrun

packaging: Add dependency of hipdnsproxy on hipd.

hipdnsproxy reads etc/hip/hosts, which only exists if hipd is installed.

6256. By Diego Biurrun

hadb: Vastly simplify match_peer_addr().

6257. By Diego Biurrun

Rename nsupdate.pl ---> nsupdate.

It's more sensible to leave out the unnecessary language extension from the
program name. It does not really add information and would have to be
changed in the case the program was rewritten some day.

6258. By Miika Komu

A bug fix to hipfw unit tests on 32-bit machines

Fixed bug id #918374 in launchpad. Now two failing hipfw tests are
disabled on 32-bit machines. Tested on both 32-bit and 64-bit machines.

6259. By Stefan Götz

Compare host IDs across all address bytes instead of comparing only the first few bytes equal to the width of a pointer.

6260. By Stefan Götz

Merge lp:~stefan.goetz/hipl/hidb-db, approved by Rene and Diego

6281. By Stefan Götz

Briefly mention the commitguard (and the stylecheck) plugin in doc/HACKING

6282. By Stefan Götz

Remove empty leading and trailing comment lines in copyright blocks.

6283. By Stefan Götz

Remove GNU-isms from scripts

6284. By Stefan Götz

Fix grammar in documentation

6285. By Stefan Götz

Place references to shell variables into curly braces to avoid expansion problems

6286. By Stefan Götz

Remove '\b' from regular expression for portability

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory '.commitguards'
2=== added file '.commitguards/author-attribution.guard'
3--- .commitguards/author-attribution.guard 1970-01-01 00:00:00 +0000
4+++ .commitguards/author-attribution.guard 2012-01-22 08:57:26 +0000
5@@ -0,0 +1,31 @@
6+# Copyright (c) 2012 Aalto University and RWTH Aachen University.
7+#
8+# Permission is hereby granted, free of charge, to any person
9+# obtaining a copy of this software and associated documentation
10+# files (the "Software"), to deal in the Software without
11+# restriction, including without limitation the rights to use,
12+# copy, modify, merge, publish, distribute, sublicense, and/or sell
13+# copies of the Software, and to permit persons to whom the
14+# Software is furnished to do so, subject to the following
15+# conditions:
16+#
17+# The above copyright notice and this permission notice shall be
18+# included in all copies or substantial portions of the Software.
19+#
20+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27+# OTHER DEALINGS IN THE SOFTWARE.
28+
29+[guard]
30+files=.*
31+actions=amr
32+command=
33+ if AUTHORS="$(egrep "@author\b" "{}")"; then
34+ echo "{}: files should not contain author attributions such as '$(echo "$AUTHORS" | head -n 1)'"
35+ false
36+ fi
37
38=== added file '.commitguards/copyright-guard-space.guard'
39--- .commitguards/copyright-guard-space.guard 1970-01-01 00:00:00 +0000
40+++ .commitguards/copyright-guard-space.guard 2012-01-22 08:57:26 +0000
41@@ -0,0 +1,27 @@
42+# Copyright (c) 2012 Aalto University and RWTH Aachen University.
43+#
44+# Permission is hereby granted, free of charge, to any person
45+# obtaining a copy of this software and associated documentation
46+# files (the "Software"), to deal in the Software without
47+# restriction, including without limitation the rights to use,
48+# copy, modify, merge, publish, distribute, sublicense, and/or sell
49+# copies of the Software, and to permit persons to whom the
50+# Software is furnished to do so, subject to the following
51+# conditions:
52+#
53+# The above copyright notice and this permission notice shall be
54+# included in all copies or substantial portions of the Software.
55+#
56+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
57+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
58+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
59+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
60+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
61+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
62+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
63+# OTHER DEALINGS IN THE SOFTWARE.
64+
65+[guard]
66+files=\.(c|h|am|ac|in|py|sh|guard)$
67+actions=amr
68+command=copyright-guard-space.py "{}"
69
70=== added file '.commitguards/copyright-guard-space.py'
71--- .commitguards/copyright-guard-space.py 1970-01-01 00:00:00 +0000
72+++ .commitguards/copyright-guard-space.py 2012-01-22 08:57:26 +0000
73@@ -0,0 +1,65 @@
74+#!/usr/bin/env python
75+
76+#
77+# Copyright (c) 2012 Aalto University and RWTH Aachen University.
78+#
79+# Permission is hereby granted, free of charge, to any person
80+# obtaining a copy of this software and associated documentation
81+# files (the "Software"), to deal in the Software without
82+# restriction, including without limitation the rights to use,
83+# copy, modify, merge, publish, distribute, sublicense, and/or sell
84+# copies of the Software, and to permit persons to whom the
85+# Software is furnished to do so, subject to the following
86+# conditions:
87+#
88+# The above copyright notice and this permission notice shall be
89+# included in all copies or substantial portions of the Software.
90+#
91+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
92+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
93+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
94+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
95+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
96+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
97+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
98+# OTHER DEALINGS IN THE SOFTWARE.
99+#
100+
101+#
102+# Test whether a copyright comment block is followed by a blank line
103+#
104+
105+import os, re, sys
106+
107+class CommentStyle:
108+ def __init__(self, header, body, footer):
109+ self.header = header
110+ self.body = body
111+ self.footer = footer
112+
113+
114+c_style = CommentStyle(' \* Copyright ', ' \*', ' \*/')
115+hash_style = CommentStyle('# Copyright ', '#', '#.*')
116+
117+styles = {
118+ '.c': c_style,
119+ '.h': c_style,
120+ '.am': hash_style,
121+ '.ac': hash_style,
122+ '.in': hash_style,
123+ '.py': hash_style,
124+ '.sh': hash_style,
125+}
126+
127+extension = os.path.splitext(sys.argv[1])[1]
128+if extension in styles:
129+ style = styles[extension]
130+ pattern = re.compile('^' + style.header + '[^\n]*\n(^' + style.body + '[^\n]*\n)+^' + style.footer + '\n\n', re.MULTILINE)
131+
132+ fh = open(sys.argv[1], 'r')
133+ match = pattern.search(fh.read())
134+ fh.close()
135+
136+ if not match:
137+ print 'The copyright header in file %s should be followed by an empty line' % (sys.argv[1])
138+ sys.exit(1)
139
140=== added file '.commitguards/copyright-year.guard'
141--- .commitguards/copyright-year.guard 1970-01-01 00:00:00 +0000
142+++ .commitguards/copyright-year.guard 2012-01-22 08:57:26 +0000
143@@ -0,0 +1,36 @@
144+# Copyright (c) 2012 Aalto University and RWTH Aachen University.
145+#
146+# Permission is hereby granted, free of charge, to any person
147+# obtaining a copy of this software and associated documentation
148+# files (the "Software"), to deal in the Software without
149+# restriction, including without limitation the rights to use,
150+# copy, modify, merge, publish, distribute, sublicense, and/or sell
151+# copies of the Software, and to permit persons to whom the
152+# Software is furnished to do so, subject to the following
153+# conditions:
154+#
155+# The above copyright notice and this permission notice shall be
156+# included in all copies or substantial portions of the Software.
157+#
158+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
159+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
160+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
161+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
162+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
163+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
164+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
165+# OTHER DEALINGS IN THE SOFTWARE.
166+
167+[guard]
168+files=\.(c|h|am|ac|in|py|sh|guard)$
169+actions=amr
170+command=
171+ CR_TOKEN=" Copyright \(c\) "
172+ CR_HOLDER=" Aalto "
173+ Y=$(date +%Y)
174+ if ! egrep "$CR_TOKEN.*$CR_HOLDER" "{}" | egrep -q "\b$Y\b"; then
175+ echo "{}: The copyright statement is not up-to-date"
176+ L=$(( $Y - 1 ))
177+ sed -r "/ Copyright \(c\) / { /\b$Y\b/ ! { /-$L / s/-$L /-$Y /; s/ $L / $L-$Y /; /\b$Y\b/ ! s/ ([0-9, -]+) ([a-zA-Z]+)/ \1, $Y \2/ } }" "{}" | diff -u "{}" - >&2
178+ false
179+ fi
180
181=== added file 'tools/bazaar/plugins/commitguard.py'
182--- tools/bazaar/plugins/commitguard.py 1970-01-01 00:00:00 +0000
183+++ tools/bazaar/plugins/commitguard.py 2012-01-22 08:57:26 +0000
184@@ -0,0 +1,351 @@
185+#!/usr/bin/env python
186+
187+#
188+# Copyright (c) 2012 Aalto University and RWTH Aachen University.
189+#
190+# Permission is hereby granted, free of charge, to any person
191+# obtaining a copy of this software and associated documentation
192+# files (the "Software"), to deal in the Software without
193+# restriction, including without limitation the rights to use,
194+# copy, modify, merge, publish, distribute, sublicense, and/or sell
195+# copies of the Software, and to permit persons to whom the
196+# Software is furnished to do so, subject to the following
197+# conditions:
198+#
199+# The above copyright notice and this permission notice shall be
200+# included in all copies or substantial portions of the Software.
201+#
202+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
203+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
204+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
205+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
206+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
207+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
208+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
209+# OTHER DEALINGS IN THE SOFTWARE.
210+#
211+
212+_commitguard_tutorial = """
213+Bazaar plugin for checking code modifications at pre-commit time.
214+
215+About
216+-----
217+
218+Installation
219+------------
220+Copy this file into the Bazaar plugin directory (usually .bazaar/plugins/ in
221+your home directory) or add the directory this file resides in to the
222+environment variable BZR_PLUGIN_PATH (export BZR_PLUGIN_PATH="<dir>") before
223+running 'bzr commit'.
224+
225+The pre-commit checks (or 'guards') are described in configuration files with
226+the extension .guard. This script looks for such files in two directories:
227+ 1) $HOME/.bazaar/plugins/commitguards/
228+ 2) <repository>/.commitguards/
229+This allows to set up commit guards both per user and per repository.
230+
231+Usage
232+-----
233+This hook is called automatically when running the command 'bzr commit' after
234+the commit message has been provided. All available commit guards are then
235+applied to the files in the commit. If a file fails a commit guard, the
236+commit is aborted with an error message detailing the failure. The commit
237+message is printed again for convenience. A commit guard can optionally
238+suggest a patch to the file so that it meets the guard's requirements.
239+
240+At the moment, a commit is aborted on the first failing commit guard.
241+
242+Commit Guards
243+-------------
244+Commit guards are described in plain-text configuration files in .ini
245+style with the extension .guard. The basic format of these files is
246+
247+[guard]
248+files=<value>
249+actions=<value>
250+command=<value>
251+
252+Each guard configuration file provides three pieces of information about a
253+commit guard:
254+
255+1) A set of file-name patterns the guard is to be applied to.
256+ The configuration key is 'files'.
257+ The configuration value is a regular expression.
258+ For example, the entry 'files=\.(c|C)$' makes sure that the guard is applied
259+ to all files with the extension .c or .C
260+
261+2) A set of commit actions specifying if the guard is to be applied to
262+ added, modified, and/or renamed files.
263+ The configuration key is 'actions'.
264+ The configuration value is a string consisting of one or more of the
265+ characters a, m, and r, representing added, modified, and renamed files
266+ respectively.
267+ For example, the entry 'actions=am' makes sure that the guard is applied to
268+ new and modified files in a commit but not to renamed files.
269+
270+3) A command that is executed and which implements the actual guard action.
271+ The configuration key is 'command'.
272+ The configuration value is a string containing a shell command.
273+ Before executing this command, all occurrences of the sub-string {} are
274+ replaced with the name of the (added, modified, or renamed) file in the
275+ repository and commit to apply the guard to.
276+ If the guard considers the given file valid as is, the command should exit
277+ with an exit code of 0.
278+ In this case, the remaining guards are applied to the remaining files in the
279+ commit.
280+ If the guard considers a given file as invalid, it should exit with a
281+ non-zero exit code.
282+ In this case, it may print a message to stdout to inform the user why the
283+ commit was aborted.
284+ Optionally, the command may print a patch in unified diff format on stderr
285+ that, if applied, would bring the file into compliance with the guard's
286+ requirements.
287+ For added convenience, the commitguard plugin offers the user to apply such
288+ a patch straight away.
289+ Helper executables can be placed into the same directory as a .guard file.
290+ The guard command can then invoke these executables as if they were system
291+ commands.
292+
293+
294+
295+"""
296+
297+version_info = (0, 1, 0, 'dev', 0)
298+plugin_name = 'commitguard'
299+
300+from bzrlib import branch, help_topics
301+import bzrlib
302+import subprocess
303+import os
304+import os.path
305+import sys
306+import tempfile
307+import re
308+import ConfigParser
309+
310+
311+def get_local_file_name(branch, file_name):
312+ """Determine the absolute path of the file in a local Bazaar working branch.
313+
314+ Arguments:
315+ branch -- an instance of bzrlib's Branch representing a local working
316+ branch. file_name is resolved relative to the base location of branch.
317+ file_name -- a string containing the relative path of a file in the file
318+ tree of branch.
319+
320+ This function returns a string that contains the absolute path of the
321+ specified file. This file is the actual on-disk working copy and can be
322+ accessed and modified. If file_name does not exist in branch or the
323+ corresponding local file cannot be found, a RuntimeError is raised.
324+
325+ """
326+ base = branch.base
327+ if base.startswith('file://'):
328+ base = base[7:]
329+ local_file_name = os.path.join(base, file_name)
330+ if os.path.exists(local_file_name):
331+ return local_file_name
332+ else:
333+ raise RuntimeError("The file '%s' in branch '%s' could not be found at \
334+'%s' as expected!" % (file_name, branch, local_file_name))
335+
336+
337+
338+class Guard(object):
339+ """A tool that inspects the new state a commit introduces and allows or
340+ aborts the commit.
341+
342+ Each instance of this class wraps a 'guard' tool which may analyse and check
343+ the set of modifications of a commit. It may then let the commit pass or
344+ prevent it.
345+
346+ """
347+ def __init__(self, cfg_file_name):
348+ """Creates and initializes a Guard object and its attributes.
349+
350+ If the initialization fails, e.g., because a tool is not available or
351+ the configuration is invalid, an Exception is raised.
352+
353+ cfg_file_name -- The path of a guard configuration file from which to
354+ initialize this object.
355+
356+ """
357+ self.configure(cfg_file_name)
358+
359+ def configure(self, cfg_file_name):
360+ """Initialize a Guard object from a configuration file.
361+
362+ cfg_file_name -- The path of a guard configuration file from which to
363+ initialize this object.
364+
365+ """
366+ config = ConfigParser.RawConfigParser()
367+ config.read(cfg_file_name)
368+ self.files = re.compile(config.get('guard', 'files'), re.IGNORECASE)
369+ self.actions = config.get('guard', 'actions')
370+ self.command = unicode(config.get('guard', 'command'), encoding='utf-8')
371+ self.filename = cfg_file_name
372+ self.dirname = os.path.dirname(cfg_file_name)
373+
374+ def applies_to(self, file_name, file_action):
375+ """Checks whether this guard applies to a given file or not.
376+
377+ file_name -- the name of the file in the working repository which is
378+ compared against this guard's file name pattern (not case sensitive).
379+ file_action -- one of the characters 'a', 'm', or 'r' to indicate
380+ whether a commit adds, modifies, or renames the given file. In the case
381+ of a renamed file, the file_name parameter is expected to be the new
382+ name of the file.
383+
384+ This function returns True if the given file name and action match this
385+ guard and False otherwise.
386+
387+ """
388+ return file_action in self.actions and self.files.search(file_name) is not None
389+
390+ def run(self, local_file_name, action, msg, diff):
391+ """Execute a guard to determine whether a commit may proceed or not.
392+
393+ Arguments:
394+ local_file_name -- the path of a local file to run the guard on. The
395+ guard may not modify this file.
396+ file_action -- one of the characters 'a', 'm', or 'r' to indicate
397+ whether a commit adds, modifies, or renames the given file. In the case
398+ of a renamed file, the local_file_name parameter is expected to be the
399+ new name of the file.
400+ msg -- a file-like object to which the guard may write a message for the
401+ user.
402+ diff -- a file-like object to which the guard may write a patch in
403+ unified diff format which shows how to modify the file contents in order
404+ to comply with this guard and let the commit proceed.
405+
406+ If this function returns true, the commit may proceed. If it returns
407+ false, the commit is aborted.
408+
409+ """
410+ if self.applies_to(local_file_name, action):
411+ cmd = self.command.replace('{}', local_file_name)
412+ env = os.environ
413+ if not self.dirname in env['PATH']:
414+ env['PATH'] += (':' + self.dirname)
415+ p = subprocess.Popen(cmd,
416+ shell = True,
417+ stdout = msg,
418+ stderr = diff,
419+ env = env)
420+ return p.wait() == 0
421+ else:
422+ return True
423+
424+
425+def create_guards_from_dir(cfgdir, guards):
426+ for wroot, wdirs, wfiles in os.walk(cfgdir):
427+ for f in wfiles:
428+ if f.endswith('.guard'):
429+ guards.append(Guard(os.path.join(wroot, f)))
430+ return guards
431+
432+
433+def create_guards(branch):
434+ guards = []
435+ cfgdirs = [os.path.join(os.path.expanduser('~'), '.bazaar/plugins/commitguards')]
436+ try:
437+ cfgdirs.append(get_local_file_name(branch, '.commitguards'))
438+ except:
439+ pass
440+ for cfgdir in cfgdirs:
441+ if os.path.exists(cfgdir):
442+ create_guards_from_dir(cfgdir, guards)
443+ return guards
444+
445+
446+def get_commit_files(tree_delta):
447+ """From all modifications in a commit, retrieve those files which should be
448+ run through the guards.
449+
450+ That is: added, modified, and renamed files, ignoring meta-data changes.
451+
452+ The return value is a list of tuples. Each tuple consists of a the path name
453+ of a file in the Bazaar repository tree as the first element and the
454+ character 'a', 'm', or 'r' indicating the action this commit performs on the
455+ file.
456+
457+ """
458+ # include added, modified, and renamed, skip removed and changed
459+ files = [(path, 'a') for path, file_id, kind in tree_delta.added if kind == 'file']
460+ files.extend([(path, 'm') for (path, file_id, kind, text_modified, _) in
461+ tree_delta.modified if kind == 'file'])
462+ files.extend([(newpath, 'r') for (oldpath, newpath, file_id, kind, text_modified, _) in
463+ tree_delta.renamed if kind == 'file'])
464+ return files
465+
466+
467+def get_commit_message(local, master, revid):
468+ """Returns the commit message of a branch revision."""
469+ branch = local or master
470+ revision = branch.repository.get_revision(revid)
471+ return revision.message
472+
473+
474+def apply_patch(diff_file):
475+ """Apply a patch to a local Bazaar branch.
476+
477+ diff_file -- a file-like object from which the patch can be read. The patch
478+ is expected to be in unified diff format.
479+
480+ If applying the beautification fails, a CalledProcessError is raised.
481+
482+ """
483+ subprocess.check_call(['patch', '-p0', '-i', diff_file.name])
484+
485+
486+def pre_commit_hook(local, master, old_revno, old_revid, future_revno,
487+ future_revid, tree_delta, future_tree):
488+ """Run the files modified by this commit through the configured guards and
489+ abort the commit if the guards veto it.
490+
491+ This is the pre-commit hook interface of bzrlib.
492+
493+ """
494+ guards = create_guards(local or master)
495+
496+ veto = False
497+ diff_file = tempfile.NamedTemporaryFile(prefix = plugin_name + "-diff-")
498+
499+ for tree_file_name, action in get_commit_files(tree_delta):
500+ local_file_name = get_local_file_name(local or master, tree_file_name)
501+ for guard in guards:
502+ if not guard.run(local_file_name, action, sys.stdout, diff_file):
503+ veto = True
504+ break
505+
506+ if veto:
507+ break
508+
509+ if veto:
510+ diff_file.flush()
511+ if diff_file.tell():
512+ print "\nThe following patch is suggested so as to comply with the commit guard requirements:\n"
513+ diff_file.seek(0)
514+ print diff_file.read()
515+ print "\nWould you like to apply these changes to your local branch now? [y/N] ",
516+ reply = sys.stdin.readline()
517+ if reply.strip() == 'y':
518+ apply_patch(diff_file)
519+ print "Changes successfully applied.\n"
520+ diff_file.close()
521+
522+ msg_file = tempfile.NamedTemporaryFile(prefix = 'commit-msg-%d-' % future_revno, delete = False)
523+ msg_file.write(get_commit_message(local, master, future_revid))
524+ msg_file.close()
525+
526+ raise bzrlib.errors.BzrError("This commit has been aborted. The original commit message, stored in %s, was:\n--\n%s\n--" % (msg_file.name, get_commit_message(local, master, future_revid)))
527+ diff_file.close()
528+
529+
530+help_topics.topic_registry.register(plugin_name + '-tutorial',
531+ _commitguard_tutorial,
532+ 'How to use the plugin ' + plugin_name)
533+
534+branch.Branch.hooks.install_named_hook('pre_commit', pre_commit_hook,
535+ plugin_name)

Subscribers

People subscribed via source and target branches

to all changes: