1 | #!/usr/bin/env python |
---|
2 | |
---|
3 | # log-police.py: Ensure that log messages end with a single newline. |
---|
4 | # See usage() function for details, or just run with no arguments. |
---|
5 | |
---|
6 | import os |
---|
7 | import sys |
---|
8 | import getopt |
---|
9 | try: |
---|
10 | my_getopt = getopt.gnu_getopt |
---|
11 | except AttributeError: |
---|
12 | my_getopt = getopt.getopt |
---|
13 | |
---|
14 | import svn |
---|
15 | import svn.fs |
---|
16 | import svn.repos |
---|
17 | import svn.core |
---|
18 | |
---|
19 | |
---|
20 | def fix_log_message(log_message): |
---|
21 | """Return a fixed version of LOG_MESSAGE. By default, this just |
---|
22 | means ensuring that the result ends with exactly one newline and no |
---|
23 | other whitespace. But if you want to do other kinds of fixups, this |
---|
24 | function is the place to implement them -- all log message fixing in |
---|
25 | this script happens here.""" |
---|
26 | return log_message.rstrip() + "\n" |
---|
27 | |
---|
28 | |
---|
29 | def fix_txn(fs, txn_name): |
---|
30 | "Fix up the log message for txn TXN_NAME in FS. See fix_log_message()." |
---|
31 | txn = svn.fs.svn_fs_open_txn(fs, txn_name) |
---|
32 | log_message = svn.fs.svn_fs_txn_prop(txn, "svn:log") |
---|
33 | if log_message is not None: |
---|
34 | new_message = fix_log_message(log_message) |
---|
35 | if new_message != log_message: |
---|
36 | svn.fs.svn_fs_change_txn_prop(txn, "svn:log", new_message) |
---|
37 | |
---|
38 | |
---|
39 | def fix_rev(fs, revnum): |
---|
40 | "Fix up the log message for revision REVNUM in FS. See fix_log_message()." |
---|
41 | log_message = svn.fs.svn_fs_revision_prop(fs, revnum, 'svn:log') |
---|
42 | if log_message is not None: |
---|
43 | new_message = fix_log_message(log_message) |
---|
44 | if new_message != log_message: |
---|
45 | svn.fs.svn_fs_change_rev_prop(fs, revnum, "svn:log", new_message) |
---|
46 | |
---|
47 | |
---|
48 | def usage_and_exit(error_msg=None): |
---|
49 | """Write usage information and exit. If ERROR_MSG is provide, that |
---|
50 | error message is printed first (to stderr), the usage info goes to |
---|
51 | stderr, and the script exits with a non-zero status. Otherwise, |
---|
52 | usage info goes to stdout and the script exits with a zero status.""" |
---|
53 | import os.path |
---|
54 | stream = error_msg and sys.stderr or sys.stdout |
---|
55 | if error_msg: |
---|
56 | stream.write("ERROR: %s\n\n" % error_msg) |
---|
57 | stream.write("USAGE: %s [-t TXN_NAME | -r REV_NUM | --all-revs] REPOS\n" |
---|
58 | % (os.path.basename(sys.argv[0]))) |
---|
59 | stream.write(""" |
---|
60 | Ensure that log messages end with exactly one newline and no other |
---|
61 | whitespace characters. Use as a pre-commit hook by passing '-t TXN_NAME'; |
---|
62 | fix up a single revision by passing '-r REV_NUM'; fix up all revisions by |
---|
63 | passing '--all-revs'. (When used as a pre-commit hook, may modify the |
---|
64 | svn:log property on the txn.) |
---|
65 | """) |
---|
66 | sys.exit(error_msg and 1 or 0) |
---|
67 | |
---|
68 | |
---|
69 | def main(ignored_pool, argv): |
---|
70 | repos_path = None |
---|
71 | txn_name = None |
---|
72 | rev_name = None |
---|
73 | all_revs = False |
---|
74 | |
---|
75 | try: |
---|
76 | opts, args = my_getopt(argv[1:], 't:r:h?', ["help", "all-revs"]) |
---|
77 | except: |
---|
78 | usage_and_exit("problem processing arguments / options.") |
---|
79 | for opt, value in opts: |
---|
80 | if opt == '--help' or opt == '-h' or opt == '-?': |
---|
81 | usage_and_exit() |
---|
82 | elif opt == '-t': |
---|
83 | txn_name = value |
---|
84 | elif opt == '-r': |
---|
85 | rev_name = value |
---|
86 | elif opt == '--all-revs': |
---|
87 | all_revs = True |
---|
88 | else: |
---|
89 | usage_and_exit("unknown option '%s'." % opt) |
---|
90 | |
---|
91 | if txn_name is not None and rev_name is not None: |
---|
92 | usage_and_exit("cannot pass both -t and -r.") |
---|
93 | if txn_name is not None and all_revs: |
---|
94 | usage_and_exit("cannot pass --all-revs with -t.") |
---|
95 | if rev_name is not None and all_revs: |
---|
96 | usage_and_exit("cannot pass --all-revs with -r.") |
---|
97 | if rev_name is None and txn_name is None and not all_revs: |
---|
98 | usage_and_exit("must provide exactly one of -r, -t, or --all-revs.") |
---|
99 | if len(args) != 1: |
---|
100 | usage_and_exit("only one argument allowed (the repository).") |
---|
101 | |
---|
102 | repos_path = svn.core.svn_path_canonicalize(args[0]) |
---|
103 | |
---|
104 | # A non-bindings version of this could be implemented by calling out |
---|
105 | # to 'svnlook getlog' and 'svnadmin setlog'. However, using the |
---|
106 | # bindings results in much simpler code. |
---|
107 | |
---|
108 | fs = svn.repos.svn_repos_fs(svn.repos.svn_repos_open(repos_path)) |
---|
109 | if txn_name is not None: |
---|
110 | fix_txn(fs, txn_name) |
---|
111 | elif rev_name is not None: |
---|
112 | fix_rev(fs, int(rev_name)) |
---|
113 | elif all_revs: |
---|
114 | # Do it such that if we're running on a live repository, we'll |
---|
115 | # catch up even with commits that came in after we started. |
---|
116 | last_youngest = 0 |
---|
117 | while True: |
---|
118 | youngest = svn.fs.svn_fs_youngest_rev(fs) |
---|
119 | if youngest >= last_youngest: |
---|
120 | for this_rev in range(last_youngest, youngest + 1): |
---|
121 | fix_rev(fs, this_rev) |
---|
122 | last_youngest = youngest + 1 |
---|
123 | else: |
---|
124 | break |
---|
125 | |
---|
126 | |
---|
127 | if __name__ == '__main__': |
---|
128 | sys.exit(svn.core.run_app(main, sys.argv)) |
---|