]> git.wh0rd.org - home.git/blame - .bin/git-repack
git-repack: trim reflog history too
[home.git] / .bin / git-repack
CommitLineData
c7e7c6e4
MF
1#!/usr/bin/python
2
3"""Repack git repos fully the way I like them."""
4
5from __future__ import print_function
6
7import argparse
8import glob
9import os
10import shutil
11import subprocess
12import sys
13import tempfile
14
15
16def 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
26def 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
47def 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
62def readfile(path):
63 """Read |path| and return its data"""
64 if os.path.isfile(path):
65 return open(path).read()
66
67
68def unlink(path):
69 """Unlink |path| if it exists else do nothing"""
70 if os.path.isfile(path):
71 os.unlink(path)
72
73
74def clean_hooks(path):
75 """Strip out sample files from hooks/"""
76 hooks_path = os.path.join(path, 'hooks')
77 for hook in glob.glob(os.path.join(hooks_path, '*.sample')):
78 print('Trimming hook: %s' % hook)
79 os.unlink(hook)
80
81
82def clean_packs(path):
83 """Strip out temp files from objects/packs/"""
84 packs_path = os.path.join(path, 'objects', 'packs')
85 for pack in glob.glob(os.path.join(packs_path, 'tmp_pack_*')):
86 print('Trimming pack: %s' % pack)
87 os.unlink(pack)
88
89
90def is_packed(path):
91 """See if the git repo is already packed"""
92 if set(('info', 'pack')) != set(os.listdir(path)):
93 return False
94 packs = os.listdir(os.path.join(path, 'pack'))
95 if len(packs) != 2:
96 return False
97 return True
98
99
100def repack(path):
101 """Clean up and trim cruft and repack |path|"""
102 path = find_git_dir(path)
103 print('Repacking %s' % path)
104
105 tmpdir = find_temp_dir()
106 if tmpdir:
107 tmpdir = tempfile.mkdtemp(prefix='git-repack.', dir=tmpdir)
108 print('Using tempdir: %s' % tmpdir)
109 os.rmdir(tmpdir)
110
111 grafts = alts = None
112 try:
113 # Push/pop the graft & alternate paths so we don't read them.
114 # XXX: In some cases, this is bad, but I don't use them that way ...
115 graft_file = os.path.join(path, 'info', 'grafts')
116 grafts = readfile(graft_file)
117 unlink(graft_file)
118
119 alt_file = os.path.join(path, 'objects', 'info', 'alternates')
120 alts = readfile(alt_file)
121 unlink(alt_file)
122
123 clean_hooks(path)
124
125 origin_path = os.path.join(path, 'refs', 'remotes', 'origin')
126 packed_refs = readfile(os.path.join(path, 'packed-refs'))
127 if os.path.exists(origin_path) or 'refs/remotes/origin/' in packed_refs:
128 cmd = ['git', '--git-dir', path, 'remote', 'prune', 'origin']
129 subprocess.check_call(cmd, cwd='/')
130
131 clean_packs(path)
132
133 if is_packed(path):
134 print('Git repo is already packed; nothing to do')
135 return
136
137 if tmpdir:
138 print('Syncing git repo to tempdir')
139 shutil.copytree(path, tmpdir, symlinks=True)
140 rundir = tmpdir
141 else:
142 rundir = path
143
e8abb43f
MF
144 cmd = ['git', '--git-dir', rundir, 'reflog', 'expire', '--all', '--stale-fix']
145 print('Cleaning reflog: %s' % ' '.join(cmd))
146 subprocess.check_call(cmd, cwd='/')
147
c7e7c6e4
MF
148 # This also packs refs/tags for us.
149 cmd = ['git', '--git-dir', rundir, 'gc', '--aggressive', '--prune=all']
150 print('Repacking git repo: %s' % ' '.join(cmd))
151 subprocess.check_call(cmd, cwd='/')
152
153 if tmpdir:
154 cmd = ['rsync', '-a', '--delete', tmpdir + '/', path + '/']
155 print('Syncing back git repo: %s' % ' '.join(cmd))
156 subprocess.check_call(cmd, cwd='/')
157 cmd = ['find', path + '/', '-exec', 'chmod', 'u+rw', '{}', '+']
158 subprocess.check_call(cmd, cwd='/')
159
160 finally:
161 if grafts:
162 open(graft_file, 'w').write(grafts)
163 if alts:
164 open(alt_file, 'w').write(alts)
165 if tmpdir:
166 shutil.rmtree(tmpdir)
167
168
169def get_parser():
170 """Get the command line parser"""
171 parser = argparse.ArgumentParser(description=__doc__)
172 parser.add_argument('dir', help='The git repo to process')
173 return parser
174
175
176def main(argv):
177 """The main script entry point"""
178 parser = get_parser()
179 opts = parser.parse_args(argv)
180 repack(opts.dir)
181
182
183if __name__ == '__main__':
184 exit(main(sys.argv[1:]))