diff options
author | Adrian Dudau <adrian.dudau@enea.com> | 2014-06-26 14:36:22 +0200 |
---|---|---|
committer | Adrian Dudau <adrian.dudau@enea.com> | 2014-06-26 15:32:53 +0200 |
commit | f4cf9fe05bb3f32fabea4e54dd92d368967a80da (patch) | |
tree | 487180fa9866985ea7b28e625651765d86f515c3 /scripts/combo-layer | |
download | poky-f4cf9fe05bb3f32fabea4e54dd92d368967a80da.tar.gz |
initial commit for Enea Linux 4.0
Migrated from the internal git server on the daisy-enea branch
Signed-off-by: Adrian Dudau <adrian.dudau@enea.com>
Diffstat (limited to 'scripts/combo-layer')
-rwxr-xr-x | scripts/combo-layer | 600 |
1 files changed, 600 insertions, 0 deletions
diff --git a/scripts/combo-layer b/scripts/combo-layer new file mode 100755 index 0000000000..9da1d3a89e --- /dev/null +++ b/scripts/combo-layer | |||
@@ -0,0 +1,600 @@ | |||
1 | #!/usr/bin/env python | ||
2 | # ex:ts=4:sw=4:sts=4:et | ||
3 | # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- | ||
4 | # | ||
5 | # Copyright 2011 Intel Corporation | ||
6 | # Authored-by: Yu Ke <ke.yu@intel.com> | ||
7 | # Paul Eggleton <paul.eggleton@intel.com> | ||
8 | # Richard Purdie <richard.purdie@intel.com> | ||
9 | # | ||
10 | # This program is free software; you can redistribute it and/or modify | ||
11 | # it under the terms of the GNU General Public License version 2 as | ||
12 | # published by the Free Software Foundation. | ||
13 | # | ||
14 | # This program is distributed in the hope that it will be useful, | ||
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
17 | # GNU General Public License for more details. | ||
18 | # | ||
19 | # You should have received a copy of the GNU General Public License along | ||
20 | # with this program; if not, write to the Free Software Foundation, Inc., | ||
21 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
22 | |||
23 | import os, sys | ||
24 | import optparse | ||
25 | import logging | ||
26 | import subprocess | ||
27 | import ConfigParser | ||
28 | import re | ||
29 | |||
30 | __version__ = "0.2.1" | ||
31 | |||
32 | def logger_create(): | ||
33 | logger = logging.getLogger("") | ||
34 | loggerhandler = logging.StreamHandler() | ||
35 | loggerhandler.setFormatter(logging.Formatter("[%(asctime)s] %(message)s","%H:%M:%S")) | ||
36 | logger.addHandler(loggerhandler) | ||
37 | logger.setLevel(logging.INFO) | ||
38 | return logger | ||
39 | |||
40 | logger = logger_create() | ||
41 | |||
42 | def get_current_branch(repodir=None): | ||
43 | try: | ||
44 | if not os.path.exists(os.path.join(repodir if repodir else '', ".git")): | ||
45 | # Repo not created yet (i.e. during init) so just assume master | ||
46 | return "master" | ||
47 | branchname = runcmd("git symbolic-ref HEAD 2>/dev/null", repodir).strip() | ||
48 | if branchname.startswith("refs/heads/"): | ||
49 | branchname = branchname[11:] | ||
50 | return branchname | ||
51 | except subprocess.CalledProcessError: | ||
52 | return "" | ||
53 | |||
54 | class Configuration(object): | ||
55 | """ | ||
56 | Manages the configuration | ||
57 | |||
58 | For an example config file, see combo-layer.conf.example | ||
59 | |||
60 | """ | ||
61 | def __init__(self, options): | ||
62 | for key, val in options.__dict__.items(): | ||
63 | setattr(self, key, val) | ||
64 | |||
65 | def readsection(parser, section, repo): | ||
66 | for (name, value) in parser.items(section): | ||
67 | if value.startswith("@"): | ||
68 | self.repos[repo][name] = eval(value.strip("@")) | ||
69 | else: | ||
70 | self.repos[repo][name] = value | ||
71 | |||
72 | logger.debug("Loading config file %s" % self.conffile) | ||
73 | self.parser = ConfigParser.ConfigParser() | ||
74 | with open(self.conffile) as f: | ||
75 | self.parser.readfp(f) | ||
76 | |||
77 | self.repos = {} | ||
78 | for repo in self.parser.sections(): | ||
79 | self.repos[repo] = {} | ||
80 | readsection(self.parser, repo, repo) | ||
81 | |||
82 | # Load local configuration, if available | ||
83 | self.localconffile = None | ||
84 | self.localparser = None | ||
85 | self.combobranch = None | ||
86 | if self.conffile.endswith('.conf'): | ||
87 | lcfile = self.conffile.replace('.conf', '-local.conf') | ||
88 | if os.path.exists(lcfile): | ||
89 | # Read combo layer branch | ||
90 | self.combobranch = get_current_branch() | ||
91 | logger.debug("Combo layer branch is %s" % self.combobranch) | ||
92 | |||
93 | self.localconffile = lcfile | ||
94 | logger.debug("Loading local config file %s" % self.localconffile) | ||
95 | self.localparser = ConfigParser.ConfigParser() | ||
96 | with open(self.localconffile) as f: | ||
97 | self.localparser.readfp(f) | ||
98 | |||
99 | for section in self.localparser.sections(): | ||
100 | if '|' in section: | ||
101 | sectionvals = section.split('|') | ||
102 | repo = sectionvals[0] | ||
103 | if sectionvals[1] != self.combobranch: | ||
104 | continue | ||
105 | else: | ||
106 | repo = section | ||
107 | if repo in self.repos: | ||
108 | readsection(self.localparser, section, repo) | ||
109 | |||
110 | def update(self, repo, option, value, initmode=False): | ||
111 | if self.localparser: | ||
112 | parser = self.localparser | ||
113 | section = "%s|%s" % (repo, self.combobranch) | ||
114 | conffile = self.localconffile | ||
115 | if initmode and not parser.has_section(section): | ||
116 | parser.add_section(section) | ||
117 | else: | ||
118 | parser = self.parser | ||
119 | section = repo | ||
120 | conffile = self.conffile | ||
121 | parser.set(section, option, value) | ||
122 | with open(conffile, "w") as f: | ||
123 | parser.write(f) | ||
124 | |||
125 | def sanity_check(self, initmode=False): | ||
126 | required_options=["src_uri", "local_repo_dir", "dest_dir", "last_revision"] | ||
127 | if initmode: | ||
128 | required_options.remove("last_revision") | ||
129 | msg = "" | ||
130 | missing_options = [] | ||
131 | for name in self.repos: | ||
132 | for option in required_options: | ||
133 | if option not in self.repos[name]: | ||
134 | msg = "%s\nOption %s is not defined for component %s" %(msg, option, name) | ||
135 | missing_options.append(option) | ||
136 | if msg != "": | ||
137 | logger.error("configuration file %s has the following error: %s" % (self.conffile,msg)) | ||
138 | if self.localconffile and 'last_revision' in missing_options: | ||
139 | logger.error("local configuration file %s may be missing configuration for combo branch %s" % (self.localconffile, self.combobranch)) | ||
140 | sys.exit(1) | ||
141 | |||
142 | # filterdiff is required by action_splitpatch, so check its availability | ||
143 | if subprocess.call("which filterdiff > /dev/null 2>&1", shell=True) != 0: | ||
144 | logger.error("ERROR: patchutils package is missing, please install it (e.g. # apt-get install patchutils)") | ||
145 | sys.exit(1) | ||
146 | |||
147 | def runcmd(cmd,destdir=None,printerr=True): | ||
148 | """ | ||
149 | execute command, raise CalledProcessError if fail | ||
150 | return output if succeed | ||
151 | """ | ||
152 | logger.debug("run cmd '%s' in %s" % (cmd, os.getcwd() if destdir is None else destdir)) | ||
153 | out = os.tmpfile() | ||
154 | try: | ||
155 | subprocess.check_call(cmd, stdout=out, stderr=out, cwd=destdir, shell=True) | ||
156 | except subprocess.CalledProcessError,e: | ||
157 | out.seek(0) | ||
158 | if printerr: | ||
159 | logger.error("%s" % out.read()) | ||
160 | raise e | ||
161 | |||
162 | out.seek(0) | ||
163 | output = out.read() | ||
164 | logger.debug("output: %s" % output ) | ||
165 | return output | ||
166 | |||
167 | def action_init(conf, args): | ||
168 | """ | ||
169 | Clone component repositories | ||
170 | Check git is initialised; if not, copy initial data from component repos | ||
171 | """ | ||
172 | for name in conf.repos: | ||
173 | ldir = conf.repos[name]['local_repo_dir'] | ||
174 | if not os.path.exists(ldir): | ||
175 | logger.info("cloning %s to %s" %(conf.repos[name]['src_uri'], ldir)) | ||
176 | subprocess.check_call("git clone %s %s" % (conf.repos[name]['src_uri'], ldir), shell=True) | ||
177 | if not os.path.exists(".git"): | ||
178 | runcmd("git init") | ||
179 | for name in conf.repos: | ||
180 | repo = conf.repos[name] | ||
181 | ldir = repo['local_repo_dir'] | ||
182 | branch = repo.get('branch', "master") | ||
183 | lastrev = repo.get('last_revision', None) | ||
184 | if lastrev and lastrev != "HEAD": | ||
185 | initialrev = lastrev | ||
186 | if branch: | ||
187 | if not check_rev_branch(name, ldir, lastrev, branch): | ||
188 | sys.exit(1) | ||
189 | logger.info("Copying data from %s at specified revision %s..." % (name, lastrev)) | ||
190 | else: | ||
191 | lastrev = None | ||
192 | initialrev = branch | ||
193 | logger.info("Copying data from %s..." % name) | ||
194 | dest_dir = repo['dest_dir'] | ||
195 | if dest_dir and dest_dir != ".": | ||
196 | extract_dir = os.path.join(os.getcwd(), dest_dir) | ||
197 | if not os.path.exists(extract_dir): | ||
198 | os.makedirs(extract_dir) | ||
199 | else: | ||
200 | extract_dir = os.getcwd() | ||
201 | file_filter = repo.get('file_filter', "") | ||
202 | runcmd("git archive %s | tar -x -C %s %s" % (initialrev, extract_dir, file_filter), ldir) | ||
203 | if not lastrev: | ||
204 | lastrev = runcmd("git rev-parse %s" % initialrev, ldir).strip() | ||
205 | conf.update(name, "last_revision", lastrev, initmode=True) | ||
206 | runcmd("git add .") | ||
207 | if conf.localconffile: | ||
208 | localadded = True | ||
209 | try: | ||
210 | runcmd("git rm --cached %s" % conf.localconffile, printerr=False) | ||
211 | except subprocess.CalledProcessError: | ||
212 | localadded = False | ||
213 | if localadded: | ||
214 | localrelpath = os.path.relpath(conf.localconffile) | ||
215 | runcmd("grep -q %s .gitignore || echo %s >> .gitignore" % (localrelpath, localrelpath)) | ||
216 | runcmd("git add .gitignore") | ||
217 | logger.info("Added local configuration file %s to .gitignore", localrelpath) | ||
218 | logger.info("Initial combo layer repository data has been created; please make any changes if desired and then use 'git commit' to make the initial commit.") | ||
219 | else: | ||
220 | logger.info("Repository already initialised, nothing to do.") | ||
221 | |||
222 | |||
223 | def check_repo_clean(repodir): | ||
224 | """ | ||
225 | check if the repo is clean | ||
226 | exit if repo is dirty | ||
227 | """ | ||
228 | output=runcmd("git status --porcelain", repodir) | ||
229 | r = re.compile('\?\? patch-.*/') | ||
230 | dirtyout = [item for item in output.splitlines() if not r.match(item)] | ||
231 | if dirtyout: | ||
232 | logger.error("git repo %s is dirty, please fix it first", repodir) | ||
233 | sys.exit(1) | ||
234 | |||
235 | def check_patch(patchfile): | ||
236 | f = open(patchfile) | ||
237 | ln = f.readline() | ||
238 | of = None | ||
239 | in_patch = False | ||
240 | beyond_msg = False | ||
241 | pre_buf = '' | ||
242 | while ln: | ||
243 | if not beyond_msg: | ||
244 | if ln == '---\n': | ||
245 | if not of: | ||
246 | break | ||
247 | in_patch = False | ||
248 | beyond_msg = True | ||
249 | elif ln.startswith('--- '): | ||
250 | # We have a diff in the commit message | ||
251 | in_patch = True | ||
252 | if not of: | ||
253 | print('WARNING: %s contains a diff in its commit message, indenting to avoid failure during apply' % patchfile) | ||
254 | of = open(patchfile + '.tmp', 'w') | ||
255 | of.write(pre_buf) | ||
256 | pre_buf = '' | ||
257 | elif in_patch and not ln[0] in '+-@ \n\r': | ||
258 | in_patch = False | ||
259 | if of: | ||
260 | if in_patch: | ||
261 | of.write(' ' + ln) | ||
262 | else: | ||
263 | of.write(ln) | ||
264 | else: | ||
265 | pre_buf += ln | ||
266 | ln = f.readline() | ||
267 | f.close() | ||
268 | if of: | ||
269 | of.close() | ||
270 | os.rename(patchfile + '.tmp', patchfile) | ||
271 | |||
272 | def drop_to_shell(workdir=None): | ||
273 | shell = os.environ.get('SHELL', 'bash') | ||
274 | print('Dropping to shell "%s"\n' \ | ||
275 | 'When you are finished, run the following to continue:\n' \ | ||
276 | ' exit -- continue to apply the patches\n' \ | ||
277 | ' exit 1 -- abort\n' % shell); | ||
278 | ret = subprocess.call([shell], cwd=workdir) | ||
279 | if ret != 0: | ||
280 | print "Aborting" | ||
281 | return False | ||
282 | else: | ||
283 | return True | ||
284 | |||
285 | def check_rev_branch(component, repodir, rev, branch): | ||
286 | try: | ||
287 | actualbranch = runcmd("git branch --contains %s" % rev, repodir, printerr=False) | ||
288 | except subprocess.CalledProcessError as e: | ||
289 | if e.returncode == 129: | ||
290 | actualbranch = "" | ||
291 | else: | ||
292 | raise | ||
293 | |||
294 | if not actualbranch: | ||
295 | logger.error("%s: specified revision %s is invalid!" % (component, rev)) | ||
296 | return False | ||
297 | |||
298 | branches = [] | ||
299 | branchlist = actualbranch.split("\n") | ||
300 | for b in branchlist: | ||
301 | branches.append(b.strip().split(' ')[-1]) | ||
302 | |||
303 | if branch not in branches: | ||
304 | logger.error("%s: specified revision %s is not on specified branch %s!" % (component, rev, branch)) | ||
305 | return False | ||
306 | return True | ||
307 | |||
308 | def get_repos(conf, args): | ||
309 | repos = [] | ||
310 | if len(args) > 1: | ||
311 | for arg in args[1:]: | ||
312 | if arg.startswith('-'): | ||
313 | break | ||
314 | else: | ||
315 | repos.append(arg) | ||
316 | for repo in repos: | ||
317 | if not repo in conf.repos: | ||
318 | logger.error("Specified component '%s' not found in configuration" % repo) | ||
319 | sys.exit(0) | ||
320 | |||
321 | if not repos: | ||
322 | repos = conf.repos | ||
323 | |||
324 | return repos | ||
325 | |||
326 | def action_pull(conf, args): | ||
327 | """ | ||
328 | update the component repos only | ||
329 | """ | ||
330 | repos = get_repos(conf, args) | ||
331 | |||
332 | # make sure all repos are clean | ||
333 | for name in repos: | ||
334 | check_repo_clean(conf.repos[name]['local_repo_dir']) | ||
335 | |||
336 | for name in repos: | ||
337 | repo = conf.repos[name] | ||
338 | ldir = repo['local_repo_dir'] | ||
339 | branch = repo.get('branch', "master") | ||
340 | runcmd("git checkout %s" % branch, ldir) | ||
341 | logger.info("git pull for component repo %s in %s ..." % (name, ldir)) | ||
342 | output=runcmd("git pull", ldir) | ||
343 | logger.info(output) | ||
344 | |||
345 | def action_update(conf, args): | ||
346 | """ | ||
347 | update the component repos | ||
348 | generate the patch list | ||
349 | apply the generated patches | ||
350 | """ | ||
351 | repos = get_repos(conf, args) | ||
352 | |||
353 | # make sure combo repo is clean | ||
354 | check_repo_clean(os.getcwd()) | ||
355 | |||
356 | import uuid | ||
357 | patch_dir = "patch-%s" % uuid.uuid4() | ||
358 | if not os.path.exists(patch_dir): | ||
359 | os.mkdir(patch_dir) | ||
360 | |||
361 | # Step 1: update the component repos | ||
362 | if conf.nopull: | ||
363 | logger.info("Skipping pull (-n)") | ||
364 | else: | ||
365 | action_pull(conf, args) | ||
366 | |||
367 | for name in repos: | ||
368 | repo = conf.repos[name] | ||
369 | ldir = repo['local_repo_dir'] | ||
370 | dest_dir = repo['dest_dir'] | ||
371 | branch = repo.get('branch', "master") | ||
372 | repo_patch_dir = os.path.join(os.getcwd(), patch_dir, name) | ||
373 | |||
374 | # Step 2: generate the patch list and store to patch dir | ||
375 | logger.info("Generating patches from %s..." % name) | ||
376 | if dest_dir != ".": | ||
377 | prefix = "--src-prefix=a/%s/ --dst-prefix=b/%s/" % (dest_dir, dest_dir) | ||
378 | else: | ||
379 | prefix = "" | ||
380 | if repo['last_revision'] == "": | ||
381 | logger.info("Warning: last_revision of component %s is not set, starting from the first commit" % name) | ||
382 | patch_cmd_range = "--root %s" % branch | ||
383 | rev_cmd_range = branch | ||
384 | else: | ||
385 | if not check_rev_branch(name, ldir, repo['last_revision'], branch): | ||
386 | sys.exit(1) | ||
387 | patch_cmd_range = "%s..%s" % (repo['last_revision'], branch) | ||
388 | rev_cmd_range = patch_cmd_range | ||
389 | |||
390 | file_filter = repo.get('file_filter',"") | ||
391 | |||
392 | patch_cmd = "git format-patch -N %s --output-directory %s %s -- %s" % \ | ||
393 | (prefix,repo_patch_dir, patch_cmd_range, file_filter) | ||
394 | output = runcmd(patch_cmd, ldir) | ||
395 | logger.debug("generated patch set:\n%s" % output) | ||
396 | patchlist = output.splitlines() | ||
397 | |||
398 | rev_cmd = 'git rev-list --no-merges ' + rev_cmd_range | ||
399 | revlist = runcmd(rev_cmd, ldir).splitlines() | ||
400 | |||
401 | # Step 3: Call repo specific hook to adjust patch | ||
402 | if 'hook' in repo: | ||
403 | # hook parameter is: ./hook patchpath revision reponame | ||
404 | count=len(revlist)-1 | ||
405 | for patch in patchlist: | ||
406 | runcmd("%s %s %s %s" % (repo['hook'], patch, revlist[count], name)) | ||
407 | count=count-1 | ||
408 | |||
409 | # Step 4: write patch list and revision list to file, for user to edit later | ||
410 | patchlist_file = os.path.join(os.getcwd(), patch_dir, "patchlist-%s" % name) | ||
411 | repo['patchlist'] = patchlist_file | ||
412 | f = open(patchlist_file, 'w') | ||
413 | count=len(revlist)-1 | ||
414 | for patch in patchlist: | ||
415 | f.write("%s %s\n" % (patch, revlist[count])) | ||
416 | check_patch(os.path.join(patch_dir, patch)) | ||
417 | count=count-1 | ||
418 | f.close() | ||
419 | |||
420 | # Step 5: invoke bash for user to edit patch and patch list | ||
421 | if conf.interactive: | ||
422 | print('You may now edit the patch and patch list in %s\n' \ | ||
423 | 'For example, you can remove unwanted patch entries from patchlist-*, so that they will be not applied later' % patch_dir); | ||
424 | if not drop_to_shell(patch_dir): | ||
425 | sys.exit(0) | ||
426 | |||
427 | # Step 6: apply the generated and revised patch | ||
428 | apply_patchlist(conf, repos) | ||
429 | runcmd("rm -rf %s" % patch_dir) | ||
430 | |||
431 | # Step 7: commit the updated config file if it's being tracked | ||
432 | relpath = os.path.relpath(conf.conffile) | ||
433 | try: | ||
434 | output = runcmd("git status --porcelain %s" % relpath, printerr=False) | ||
435 | except: | ||
436 | # Outside the repository | ||
437 | output = None | ||
438 | if output: | ||
439 | logger.info("Committing updated configuration file") | ||
440 | if output.lstrip().startswith("M"): | ||
441 | runcmd('git commit -m "Automatic commit to update last_revision" %s' % relpath) | ||
442 | |||
443 | def apply_patchlist(conf, repos): | ||
444 | """ | ||
445 | apply the generated patch list to combo repo | ||
446 | """ | ||
447 | for name in repos: | ||
448 | repo = conf.repos[name] | ||
449 | lastrev = repo["last_revision"] | ||
450 | prevrev = lastrev | ||
451 | |||
452 | # Get non-blank lines from patch list file | ||
453 | patchlist = [] | ||
454 | if os.path.exists(repo['patchlist']) or not conf.interactive: | ||
455 | # Note: we want this to fail here if the file doesn't exist and we're not in | ||
456 | # interactive mode since the file should exist in this case | ||
457 | with open(repo['patchlist']) as f: | ||
458 | for line in f: | ||
459 | line = line.rstrip() | ||
460 | if line: | ||
461 | patchlist.append(line) | ||
462 | |||
463 | if patchlist: | ||
464 | logger.info("Applying patches from %s..." % name) | ||
465 | linecount = len(patchlist) | ||
466 | i = 1 | ||
467 | for line in patchlist: | ||
468 | patchfile = line.split()[0] | ||
469 | lastrev = line.split()[1] | ||
470 | patchdisp = os.path.relpath(patchfile) | ||
471 | if os.path.getsize(patchfile) == 0: | ||
472 | logger.info("(skipping %d/%d %s - no changes)" % (i, linecount, patchdisp)) | ||
473 | else: | ||
474 | cmd = "git am --keep-cr -s -p1 %s" % patchfile | ||
475 | logger.info("Applying %d/%d: %s" % (i, linecount, patchdisp)) | ||
476 | try: | ||
477 | runcmd(cmd) | ||
478 | except subprocess.CalledProcessError: | ||
479 | logger.info('Running "git am --abort" to cleanup repo') | ||
480 | runcmd("git am --abort") | ||
481 | logger.error('"%s" failed' % cmd) | ||
482 | logger.info("Please manually apply patch %s" % patchdisp) | ||
483 | logger.info("Note: if you exit and continue applying without manually applying the patch, it will be skipped") | ||
484 | if not drop_to_shell(): | ||
485 | if prevrev != repo['last_revision']: | ||
486 | conf.update(name, "last_revision", prevrev) | ||
487 | sys.exit(0) | ||
488 | prevrev = lastrev | ||
489 | i += 1 | ||
490 | else: | ||
491 | logger.info("No patches to apply from %s" % name) | ||
492 | ldir = conf.repos[name]['local_repo_dir'] | ||
493 | branch = conf.repos[name].get('branch', "master") | ||
494 | lastrev = runcmd("git rev-parse %s" % branch, ldir).strip() | ||
495 | |||
496 | if lastrev != repo['last_revision']: | ||
497 | conf.update(name, "last_revision", lastrev) | ||
498 | |||
499 | def action_splitpatch(conf, args): | ||
500 | """ | ||
501 | generate the commit patch and | ||
502 | split the patch per repo | ||
503 | """ | ||
504 | logger.debug("action_splitpatch") | ||
505 | if len(args) > 1: | ||
506 | commit = args[1] | ||
507 | else: | ||
508 | commit = "HEAD" | ||
509 | patchdir = "splitpatch-%s" % commit | ||
510 | if not os.path.exists(patchdir): | ||
511 | os.mkdir(patchdir) | ||
512 | |||
513 | # filerange_root is for the repo whose dest_dir is root "." | ||
514 | # and it should be specified by excluding all other repo dest dir | ||
515 | # like "-x repo1 -x repo2 -x repo3 ..." | ||
516 | filerange_root = "" | ||
517 | for name in conf.repos: | ||
518 | dest_dir = conf.repos[name]['dest_dir'] | ||
519 | if dest_dir != ".": | ||
520 | filerange_root = '%s -x "%s/*"' % (filerange_root, dest_dir) | ||
521 | |||
522 | for name in conf.repos: | ||
523 | dest_dir = conf.repos[name]['dest_dir'] | ||
524 | patch_filename = "%s/%s.patch" % (patchdir, name) | ||
525 | if dest_dir == ".": | ||
526 | cmd = "git format-patch -n1 --stdout %s^..%s | filterdiff -p1 %s > %s" % (commit, commit, filerange_root, patch_filename) | ||
527 | else: | ||
528 | cmd = "git format-patch --no-prefix -n1 --stdout %s^..%s -- %s > %s" % (commit, commit, dest_dir, patch_filename) | ||
529 | runcmd(cmd) | ||
530 | # Detect empty patches (including those produced by filterdiff above | ||
531 | # that contain only preamble text) | ||
532 | if os.path.getsize(patch_filename) == 0 or runcmd("filterdiff %s" % patch_filename) == "": | ||
533 | os.remove(patch_filename) | ||
534 | logger.info("(skipping %s - no changes)", name) | ||
535 | else: | ||
536 | logger.info(patch_filename) | ||
537 | |||
538 | def action_error(conf, args): | ||
539 | logger.info("invalid action %s" % args[0]) | ||
540 | |||
541 | actions = { | ||
542 | "init": action_init, | ||
543 | "update": action_update, | ||
544 | "pull": action_pull, | ||
545 | "splitpatch": action_splitpatch, | ||
546 | } | ||
547 | |||
548 | def main(): | ||
549 | parser = optparse.OptionParser( | ||
550 | version = "Combo Layer Repo Tool version %s" % __version__, | ||
551 | usage = """%prog [options] action | ||
552 | |||
553 | Create and update a combination layer repository from multiple component repositories. | ||
554 | |||
555 | Action: | ||
556 | init initialise the combo layer repo | ||
557 | update [components] get patches from component repos and apply them to the combo repo | ||
558 | pull [components] just pull component repos only | ||
559 | splitpatch [commit] generate commit patch and split per component, default commit is HEAD""") | ||
560 | |||
561 | parser.add_option("-c", "--conf", help = "specify the config file (conf/combo-layer.conf is the default).", | ||
562 | action = "store", dest = "conffile", default = "conf/combo-layer.conf") | ||
563 | |||
564 | parser.add_option("-i", "--interactive", help = "interactive mode, user can edit the patch list and patches", | ||
565 | action = "store_true", dest = "interactive", default = False) | ||
566 | |||
567 | parser.add_option("-D", "--debug", help = "output debug information", | ||
568 | action = "store_true", dest = "debug", default = False) | ||
569 | |||
570 | parser.add_option("-n", "--no-pull", help = "skip pulling component repos during update", | ||
571 | action = "store_true", dest = "nopull", default = False) | ||
572 | |||
573 | options, args = parser.parse_args(sys.argv) | ||
574 | |||
575 | # Dispatch to action handler | ||
576 | if len(args) == 1: | ||
577 | logger.error("No action specified, exiting") | ||
578 | parser.print_help() | ||
579 | elif args[1] not in actions: | ||
580 | logger.error("Unsupported action %s, exiting\n" % (args[1])) | ||
581 | parser.print_help() | ||
582 | elif not os.path.exists(options.conffile): | ||
583 | logger.error("No valid config file, exiting\n") | ||
584 | parser.print_help() | ||
585 | else: | ||
586 | if options.debug: | ||
587 | logger.setLevel(logging.DEBUG) | ||
588 | confdata = Configuration(options) | ||
589 | initmode = (args[1] == 'init') | ||
590 | confdata.sanity_check(initmode) | ||
591 | actions.get(args[1], action_error)(confdata, args[1:]) | ||
592 | |||
593 | if __name__ == "__main__": | ||
594 | try: | ||
595 | ret = main() | ||
596 | except Exception: | ||
597 | ret = 1 | ||
598 | import traceback | ||
599 | traceback.print_exc(5) | ||
600 | sys.exit(ret) | ||