]>
Commit | Line | Data |
---|---|---|
c7e7c6e4 MF |
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() | |
aefecd57 | 66 | return '' |
c7e7c6e4 MF |
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 | ||
e8abb43f MF |
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 | ||
c7e7c6e4 MF |
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:])) |