]>
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() | |
66 | ||
67 | ||
68 | def unlink(path): | |
69 | """Unlink |path| if it exists else do nothing""" | |
70 | if os.path.isfile(path): | |
71 | os.unlink(path) | |
72 | ||
73 | ||
74 | def 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 | ||
82 | def 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 | ||
90 | def 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 | ||
100 | def 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 | ||
169 | def 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 | ||
176 | def main(argv): | |
177 | """The main script entry point""" | |
178 | parser = get_parser() | |
179 | opts = parser.parse_args(argv) | |
180 | repack(opts.dir) | |
181 | ||
182 | ||
183 | if __name__ == '__main__': | |
184 | exit(main(sys.argv[1:])) |