]> git.wh0rd.org Git - home.git/blob - .bin/git-repack
imgcrush: parse multiple arguments correctly
[home.git] / .bin / git-repack
1 #!/usr/bin/python
2
3 """Repack git repos fully the way I like them."""
4
5 from __future__ import print_function
6
7 import argparse
8 import glob
9 import os
10 import shutil
11 import subprocess
12 import sys
13 import tempfile
14
15
16 def mount_settings():
17     """Return dict mapping path to its type"""
18     ret = {}
19     with open('/proc/mounts') as fp:
20         for line in fp:
21             ele = line.split()
22             ret[ele[1]] = ele[2]
23     return ret
24
25
26 def find_git_dir(path):
27     """Try to find the .git dir to operate on"""
28     orig_path = path
29     real_path = path = os.path.realpath(path)
30     while True:
31         curr_path = path
32         if os.path.isdir(os.path.join(path, '.git')):
33             curr_path = os.path.join(path, '.git')
34
35         if (os.path.isdir(os.path.join(curr_path, 'refs')) and
36             os.path.isdir(os.path.join(curr_path, 'objects')) and
37             os.path.isfile(os.path.join(curr_path, 'config'))):
38             return curr_path
39
40         path = os.path.dirname(path)
41
42         if path == '/':
43             raise ValueError('could not locate .git dir: %s (%s)' %
44                              (orig_path, real_path))
45
46
47 def find_temp_dir():
48     """Find a good temp dir (one backed by tmpfs)"""
49     SEARCH_PATHS = (
50         '/var/tmp/portage',
51         '/var/tmp',
52         '/tmp',
53         tempfile.gettempdir(),
54     )
55     mounts = mount_settings()
56     for path in SEARCH_PATHS:
57         if mounts.get(path) == 'tmpfs':
58             return path
59     return None
60
61
62 def readfile(path):
63     """Read |path| and return its data"""
64     if os.path.isfile(path):
65         return open(path).read()
66     return ''
67
68
69 def unlink(path):
70     """Unlink |path| if it exists else do nothing"""
71     if os.path.isfile(path):
72         os.unlink(path)
73
74
75 def clean_hooks(path):
76     """Strip out sample files from hooks/"""
77     hooks_path = os.path.join(path, 'hooks')
78     for hook in glob.glob(os.path.join(hooks_path, '*.sample')):
79         print('Trimming hook: %s' % hook)
80         os.unlink(hook)
81
82
83 def clean_packs(path):
84     """Strip out temp files from objects/packs/"""
85     packs_path = os.path.join(path, 'objects', 'packs')
86     for pack in glob.glob(os.path.join(packs_path, 'tmp_pack_*')):
87         print('Trimming pack: %s' % pack)
88         os.unlink(pack)
89
90
91 def is_packed(path):
92     """See if the git repo is already packed"""
93     if set(('info', 'pack')) != set(os.listdir(path)):
94         return False
95     packs = os.listdir(os.path.join(path, 'pack'))
96     if len(packs) != 2:
97         return False
98     return True
99
100
101 def repack(path):
102     """Clean up and trim cruft and repack |path|"""
103     path = find_git_dir(path)
104     print('Repacking %s' % path)
105
106     tmpdir = find_temp_dir()
107     if tmpdir:
108         tmpdir = tempfile.mkdtemp(prefix='git-repack.', dir=tmpdir)
109         print('Using tempdir: %s' % tmpdir)
110         os.rmdir(tmpdir)
111
112     grafts = alts = None
113     try:
114         # Push/pop the graft & alternate paths so we don't read them.
115         # XXX: In some cases, this is bad, but I don't use them that way ...
116         graft_file = os.path.join(path, 'info', 'grafts')
117         grafts = readfile(graft_file)
118         unlink(graft_file)
119
120         alt_file = os.path.join(path, 'objects', 'info', 'alternates')
121         alts = readfile(alt_file)
122         unlink(alt_file)
123
124         clean_hooks(path)
125
126         origin_path = os.path.join(path, 'refs', 'remotes', 'origin')
127         packed_refs = readfile(os.path.join(path, 'packed-refs'))
128         if os.path.exists(origin_path) or 'refs/remotes/origin/' in packed_refs:
129             cmd = ['git', '--git-dir', path, 'remote', 'prune', 'origin']
130             subprocess.check_call(cmd, cwd='/')
131
132         clean_packs(path)
133
134         if is_packed(path):
135             print('Git repo is already packed; nothing to do')
136             return
137
138         if tmpdir:
139             print('Syncing git repo to tempdir')
140             shutil.copytree(path, tmpdir, symlinks=True)
141             rundir = tmpdir
142         else:
143             rundir = path
144
145         cmd = ['git', '--git-dir', rundir, 'reflog', 'expire', '--all', '--stale-fix']
146         print('Cleaning reflog: %s' % ' '.join(cmd))
147         subprocess.check_call(cmd, cwd='/')
148
149         # This also packs refs/tags for us.
150         cmd = ['git', '--git-dir', rundir, 'gc', '--aggressive', '--prune=all']
151         print('Repacking git repo: %s' % ' '.join(cmd))
152         subprocess.check_call(cmd, cwd='/')
153
154         if tmpdir:
155             cmd = ['rsync', '-a', '--delete', tmpdir + '/', path + '/']
156             print('Syncing back git repo: %s' % ' '.join(cmd))
157             subprocess.check_call(cmd, cwd='/')
158             cmd = ['find', path + '/', '-exec', 'chmod', 'u+rw', '{}', '+']
159             subprocess.check_call(cmd, cwd='/')
160
161     finally:
162         if grafts:
163             open(graft_file, 'w').write(grafts)
164         if alts:
165             open(alt_file, 'w').write(alts)
166         if tmpdir:
167             shutil.rmtree(tmpdir)
168
169
170 def get_parser():
171     """Get the command line parser"""
172     parser = argparse.ArgumentParser(description=__doc__)
173     parser.add_argument('dir', help='The git repo to process')
174     return parser
175
176
177 def main(argv):
178     """The main script entry point"""
179     parser = get_parser()
180     opts = parser.parse_args(argv)
181     repack(opts.dir)
182
183
184 if __name__ == '__main__':
185     exit(main(sys.argv[1:]))