]> git.wh0rd.org - home.git/blob - .bin/git-repack
git-repack: clean empty dirs
[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 # Clean empty dirs.
155 cmd = ['find', rundir, '-depth', '-type', 'd', '-exec', 'rmdir', '{}', '+']
156 subprocess.call(cmd, stderr=open('/dev/null', 'w'))
157
158 if tmpdir:
159 cmd = ['rsync', '-a', '--delete', tmpdir + '/', path + '/']
160 print('Syncing back git repo: %s' % ' '.join(cmd))
161 subprocess.check_call(cmd, cwd='/')
162 cmd = ['find', path + '/', '-exec', 'chmod', 'u+rw', '{}', '+']
163 subprocess.check_call(cmd, cwd='/')
164
165 finally:
166 if grafts:
167 open(graft_file, 'w').write(grafts)
168 if alts:
169 open(alt_file, 'w').write(alts)
170 if tmpdir:
171 shutil.rmtree(tmpdir)
172
173
174 def get_parser():
175 """Get the command line parser"""
176 parser = argparse.ArgumentParser(description=__doc__)
177 parser.add_argument('dir', help='The git repo to process')
178 return parser
179
180
181 def main(argv):
182 """The main script entry point"""
183 parser = get_parser()
184 opts = parser.parse_args(argv)
185 repack(opts.dir)
186
187
188 if __name__ == '__main__':
189 exit(main(sys.argv[1:]))