-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgit_generated_files.py
181 lines (153 loc) · 6.06 KB
/
git_generated_files.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
"""
git_generated_files.py - Manage generated (large binary)
files in a git branch wihtout history to allow publishing
on GitHub etc. without without repository bloat.
For a repo. ../../myThing, git_generated_files.py (GGF) manages
a separate branch of that repo. in a separate folder,
../../myThing_gen. GGF sets up the ../../myThing_gen folder,
then you copy in the generated files you want to publish (on
the same subpaths). GGF will add / commit / push those. When
they change (in ../../myThing), run GGF again and it will reset
the `_gen` branch to contain a single commit of the latest version
only.
Stages:
0. create empty orphan branch based on existing repo. folder
1. add new files
2. update files that have changed in source
3. push updates
Terry Brown, [email protected], Mon Dec 05 14:24:53 2016
"""
import os
import shlex
import shutil
import subprocess
import sys
from collections import namedtuple, defaultdict
if sys.version_info < (3, 0):
input = raw_input
SUFFIX = "_gen"
def error(msg, level=0):
sys.stderr.write(msg)
if level != 0:
exit(level)
def git(cmd, repo=None):
"""return output from a git command"""
cmd = shlex.split(cmd)
if repo is not None:
cmd = ['-C', repo] + cmd
cmd = ['git'] + cmd
print('GIT: '+' '.join(cmd))
return subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
shell=sys.platform.startswith('win'),
).communicate()[0].strip()
class GGFStage:
def __init__(self, path_real, path_gen):
"""__init__ - init. stage
:param str path_real: path to real branch
:param str path_gen: path to generated files branch
"""
self.path_real = path_real
self.path_gen = path_gen
self.branch_real = os.path.basename(self.path_real)
self.branch_gen = os.path.basename(self.path_gen)
class CreateGen(GGFStage):
"""Create the branch for generated files"""
def run(self):
# don't clone because it sets origin and we don't want that
# git("clone %s %s" % (self.path_real, self.path_gen))
# instead init. and pull
git("init "+self.path_gen)
# except there's really no reason to pull?
# git("pull '%s'" % self.path_real, self.path_gen)
git("checkout --orphan %s" % self.branch_gen, self.path_gen)
# git("rm -rf *", self.path_gen)
git("commit --allow-empty --message 'empty commit'",
self.path_gen)
git("tag gen_empty_commit --message 'empty commit'",
self.path_gen)
remote = git("remote -v", self.path_real).split('\n')
remote = set([i.split(None)[1] for i in remote if i.strip()])
if len(remote) == 1:
git("remote add origin "+remote.pop(), self.path_gen)
# this doesn't work when origin/foo doesn't exist
# git("branch -u origin/"+self.branch_gen)
print("Do initial push to set upstream? (y/n)")
if input().lower() == 'y':
git('push --force --set-upstream origin '+self.branch_gen,
self.path_gen)
else:
print("Can't set up remote for '%s'" % self.branch_gen)
print("Now copy files, maintaining sub-folders, from\n%s\nto\n%s" %
(self.path_real, self.path_gen))
class UpdateGen(GGFStage):
"""Update the branch for generated files"""
def run(self):
# check all files present exist in real branch
tracked = []
for path, dirs, files in os.walk(self.path_gen):
# don't process .paths, like .git
dirs[:] = [i for i in dirs if not i.startswith('.')]
# check files
for filename in files:
rel_path = os.path.relpath(path, self.path_gen)
real_file = os.path.join(self.path_real, rel_path, filename)
if not os.path.isfile(real_file):
error("'%s' not present" % real_file, 10)
tracked.append(os.path.join(rel_path, filename).replace('\\', '/'))
# copy the current version of all files over
for path in tracked:
shutil.copy(
os.path.join(self.path_real, path),
os.path.join(self.path_gen, path)
)
git('add '+path, self.path_gen)
# get list of changed files
changed = git('status --porcelain', self.path_gen).split('\n')
changed = [i.split(None, 1)[1] for i in changed if i.strip()]
if not changed:
print("No changed files")
return
# go back to the empty commit
git('reset --mixed gen_empty_commit', self.path_gen)
# add the files back
for path in tracked:
git('add '+path, self.path_gen)
if path in changed:
print("Updating "+path)
git('commit --message "updated files"', self.path_gen)
print(git('status', self.path_gen))
print("Push (force) changes to remote?(y/n)")
if input().lower() == 'y':
git('push --force', self.path_gen)
def get_stage():
"""get_stage - return a callable that can handle the current
GGF stage
"""
branch_root = git("rev-parse --show-toplevel")
if branch_root.endswith(SUFFIX):
path_gen = branch_root
path_real = branch_root[:-len(SUFFIX)]
else:
path_gen = branch_root + SUFFIX
path_real = branch_root
if not os.path.isdir(path_real):
error("'%s' (real branch) doesn't exist.\n" % path_real, 10)
path_real = os.path.abspath(path_real).replace('\\', '/')
path_gen = os.path.abspath(path_gen).replace('\\', '/')
if not os.path.isdir(path_gen):
return CreateGen(path_real, path_gen)
return UpdateGen(path_real, path_gen)
def main():
stage = get_stage()
if stage is None:
error("ERROR: Couldn't determine stage, quitting\n", 10)
print("Real branch: %s" % stage.path_real)
print("Generated branch: %s" % stage.path_gen)
print("At stage:\n "+stage.__doc__)
print("Continue? (y/n)")
if input().lower() == 'y':
stage.run()
if __name__ == '__main__':
main()