wtf
Chris McDonough
2012-03-27 18f2334aa041f69436aabdcec0e1a53dba587273
commit | author | age
b4ca9f 1 #!/usr/bin/env python
CM 2
3 """
4 A git filter for automated manifest regeneration at "git add" time.  See
5 http://progit.org/book/ch7-2.html for more info about git filters.  This
6 script just echoes back what comes to it on stdin to stdout, but it also
10d85b 7 regenerates a MANIFEST file as a side effect.
b4ca9f 8
CM 9 Add this script somewhere to your operating system's $PATH (executable as
10 ``manifestgen.py``).
11
12 Then add the following to your ~/.gitconfig::
13
14   [filter "manifestgen"]
15       clean = manifestgen.py
16       smudge = cat
17
18 Then add a ``.gitattributes`` file to the root of your project something like
19 this:
20
21     * filter=manifestgen
22
10d85b 23 Whenever you use ``git add`` in the project, the MANIFEST file at the project
CM 24 root will be regenerated (and will include the file you've just added).  This
25 will happen for each and every file added *except* MANIFEST itself.
b4ca9f 26
10d85b 27 The generated MANIFEST will have a comment at the top of it with the epoch
b4ca9f 28 time it was generated.
CM 29
30 Setting the OS environment variable DEBUG_MANIFESTGEN will send informational
31 output to stderr during its run.
32 """
33
34 import os
35 import re
36 import sys
37 import time
38
39 try:
40     from subprocess import check_output, PIPE
41 except ImportError:
42     # BBB for python <2.7
43     def check_output(*popenargs, **kwargs):
44         from subprocess import CalledProcessError
45         from subprocess import Popen
46         if 'stdout' in kwargs:
47             raise ValueError(
48                     'stdout argument not allowed, it will be overridden.')
49         process = Popen(stdout=PIPE, *popenargs, **kwargs)
50         output, unused_err = process.communicate()
51         retcode = process.poll()
52         if retcode:
53             cmd = kwargs.get("args")
54             if cmd is None:
55                 cmd = popenargs[0]
56             raise CalledProcessError(retcode, cmd)
57         return output
58
59 def list_git_files(cwd):
60     # NB: passing the "-z" option to "git ls-files" below returns the
61     # output as a blob of null-terminated filenames without
62     # canonicalization or use of "-quoting.
63     #
64     # So we'll get back e.g.:
65     #
66     #'pyramid/tests/fixtures/static/h\xc3\xa9h\xc3\xa9.html'
67     #
68     # instead of:
69     #
70     #'"pyramid/tests/fixtures/static/h\\303\\251h\\303\\251.html"'
71     #
72     # for each file
73     git_top = check_output(['git', 'rev-parse', '--show-toplevel'],
74                            stderr=PIPE, cwd=cwd).strip()
75     ls = check_output(['git', 'ls-files', '-z'], cwd=git_top, stderr=PIPE)
76     filenames = filter(None, ls.split('\x00')) # filter None for trailing \x00
77     return filenames
78
79 marker = '###--- generated by manifestgen at %s ---###\n'
80 marker_re = re.compile(marker % '\\d+')
10d85b 81 manifest = 'MANIFEST'
b4ca9f 82
CM 83 def debug(msg):
84     if 'DEBUG_MANIFESTGEN' in os.environ:
85         sys.stderr.write('manifestgen: ' + msg + '\n')
86         sys.stderr.flush()
87
88 def main():
89     firstline = sys.stdin.readline()
90     sys.stdout.write(firstline)
91     for line in sys.stdin:
92         sys.stdout.write(line)
93         sys.stdout.flush()
94     if marker_re.match(firstline): # dont overwrite manifest when adding it
95         debug('not overwriting %s' % manifest)
96     else:
97         here = os.getcwd() # always root dir of project
b5a1fc 98         new = '\n'.join(list_git_files(here)) + '\n'
b4ca9f 99         mf = os.path.join(here, manifest)
bef31f 100         try:
CM 101             mfp = open(mf, 'r')
102             old = mfp.read()
103         except IOError:
104             old = ''
105         old = '\n'.join(old.split('\n')[1:])
106         if old == new:
107             debug('manifest NOT changed, NOT overwriting %s' % manifest)
108         else:
109             debug('manifest changed, overwriting %s' % manifest)
110             mfp = open(mf, 'w')
111             now = time.time()
112             mfp.write(marker % int(now))
113             mfp.write(new)
b4ca9f 114         mfp.close()
CM 115
116 if __name__ == '__main__':
117     main()