Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(526)

Side by Side Diff: scripts/slave/bot_update.py

Issue 248303002: Revert of Bot Update support on official builders (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/build.git@master
Patch Set: Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright 2014 The Chromium Authors. All rights reserved. 2 # Copyright 2014 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 import codecs 6 import codecs
7 import copy 7 import copy
8 import cStringIO 8 import cStringIO
9 import ctypes 9 import ctypes
10 import json 10 import json
(...skipping 13 matching lines...) Expand all
24 import os.path as path 24 import os.path as path
25 25
26 from common import chromium_utils 26 from common import chromium_utils
27 27
28 28
29 CHROMIUM_SRC_URL = 'https://chromium.googlesource.com/chromium/src.git' 29 CHROMIUM_SRC_URL = 'https://chromium.googlesource.com/chromium/src.git'
30 30
31 RECOGNIZED_PATHS = { 31 RECOGNIZED_PATHS = {
32 # If SVN path matches key, the entire URL is rewritten to the Git url. 32 # If SVN path matches key, the entire URL is rewritten to the Git url.
33 '/chrome/trunk/src': 33 '/chrome/trunk/src':
34 CHROMIUM_SRC_URL, 34 CHROMIUM_SRC_URL,
35 '/chrome-internal/trunk/src-internal': 35 '/chrome-internal/trunk/src-internal':
36 'https://chrome-internal.googlesource.com/chrome/src-internal.git', 36 'https://chrome-internal.googlesource.com/chrome/src-internal.git'
37 } 37 }
38 38
39 # Official builds use buildspecs, so this is a special case.
40 BUILDSPEC_RE = r'^/chrome-internal/trunk/tools/buildspec/build/(.*)$'
41 GIT_BUILDSPEC_PATH = ('https://chrome-internal.googlesource.com/chrome/tools/'
42 'buildspec')
43
44 # This is the repository that the buildspecs2git cron job mirrors
45 # all buildspecs into. When we see an svn buildspec, we rely on the
46 # buildspecs2git cron job to produce the git version of the buildspec.
47 GIT_BUILDSPEC_REPO = (
48 'https://chrome-internal.googlesource.com/chrome/tools/git_buildspecs')
49
50 # Copied from scripts/recipes/chromium.py. 39 # Copied from scripts/recipes/chromium.py.
51 GOT_REVISION_MAPPINGS = { 40 GOT_REVISION_MAPPINGS = {
52 '/chrome/trunk/src': { 41 '/chrome/trunk/src': {
53 'src/': 'got_revision', 42 'src/': 'got_revision',
54 'src/native_client/': 'got_nacl_revision', 43 'src/native_client/': 'got_nacl_revision',
55 'src/tools/swarm_client/': 'got_swarm_client_revision', 44 'src/tools/swarm_client/': 'got_swarm_client_revision',
56 'src/tools/swarming_client/': 'got_swarming_client_revision', 45 'src/tools/swarming_client/': 'got_swarming_client_revision',
57 'src/third_party/WebKit/': 'got_webkit_revision', 46 'src/third_party/WebKit/': 'got_webkit_revision',
58 'src/third_party/webrtc/': 'got_webrtc_revision', 47 'src/third_party/webrtc/': 'got_webrtc_revision',
59 } 48 }
60 } 49 }
61 50
62 51
52
63 BOT_UPDATE_MESSAGE = """ 53 BOT_UPDATE_MESSAGE = """
64 What is the "Bot Update" step? 54 What is the "Bot Update" step?
65 ============================== 55 ==============================
66 56
67 This step ensures that the source checkout on the bot (e.g. Chromium's src/ and 57 This step ensures that the source checkout on the bot (e.g. Chromium's src/ and
68 its dependencies) is checked out in a consistent state. This means that all of 58 its dependencies) is checked out in a consistent state. This means that all of
69 the necessary repositories are checked out, no extra repositories are checked 59 the necessary repositories are checked out, no extra repositories are checked
70 out, and no locally modified files are present. 60 out, and no locally modified files are present.
71 61
72 These actions used to be taken care of by the "gclient revert" and "update" 62 These actions used to be taken care of by the "gclient revert" and "update"
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
109 99
110 100
111 GCLIENT_TEMPLATE = """solutions = %(solutions)s 101 GCLIENT_TEMPLATE = """solutions = %(solutions)s
112 102
113 cache_dir = %(cache_dir)s 103 cache_dir = %(cache_dir)s
114 %(target_os)s 104 %(target_os)s
115 """ 105 """
116 106
117 # IMPORTANT: If you're trying to enable a RECIPE bot, you'll need to 107 # IMPORTANT: If you're trying to enable a RECIPE bot, you'll need to
118 # edit recipe_modules/bot_update/api.py instead. 108 # edit recipe_modules/bot_update/api.py instead.
119 ENABLED_MASTERS = ['chromium.git', 'chrome_git'] 109 ENABLED_MASTERS = ['chromium.git']
120 ENABLED_BUILDERS = { 110 ENABLED_BUILDERS = {
121 'tryserver.chromium': ['linux_rel_alt'], 111 'tryserver.chromium': ['linux_rel_alt'],
122 } 112 }
123 ENABLED_SLAVES = { 113 ENABLED_SLAVES = {
124 # This is enabled on a bot-to-bot basis to ensure that we don't have 114 # This is enabled on a bot-to-bot basis to ensure that we don't have
125 # bots that have mixed configs. 115 # bots that have mixed configs.
126 'chromium.fyi': [ 116 'chromium.fyi': [
127 'build1-m1', # Chromium Builder / Chromium Builder (dbg) 117 'build1-m1', # Chromium Builder / Chromium Builder (dbg)
128 'vm928-m1', # Chromium Linux Buildrunner 118 'vm928-m1', # Chromium Linux Buildrunner
129 'vm859-m1', # Chromium Linux Redux 119 'vm859-m1', # Chromium Linux Redux
(...skipping 175 matching lines...) Expand 10 before | Expand all | Expand 10 after
305 295
306 296
307 def solutions_to_git(input_solutions): 297 def solutions_to_git(input_solutions):
308 """Modifies urls in solutions to point at Git repos. 298 """Modifies urls in solutions to point at Git repos.
309 299
310 returns: (git solution, svn root of first solution) tuple. 300 returns: (git solution, svn root of first solution) tuple.
311 """ 301 """
312 assert input_solutions 302 assert input_solutions
313 solutions = copy.deepcopy(input_solutions) 303 solutions = copy.deepcopy(input_solutions)
314 first_solution = True 304 first_solution = True
315 buildspec_name = False
316 for solution in solutions: 305 for solution in solutions:
317 original_url = solution['url'] 306 original_url = solution['url']
318 parsed_url = urlparse.urlparse(original_url) 307 parsed_url = urlparse.urlparse(original_url)
319 parsed_path = parsed_url.path 308 parsed_path = parsed_url.path
320 309 if first_solution:
321 # Rewrite SVN urls into Git urls. 310 root = parsed_path
322 buildspec_m = re.match(BUILDSPEC_RE, parsed_path) 311 first_solution = False
323 if first_solution and buildspec_m: 312 if parsed_path in RECOGNIZED_PATHS:
324 solution['url'] = GIT_BUILDSPEC_PATH
325 buildspec_name = buildspec_m.group(1)
326 elif parsed_path in RECOGNIZED_PATHS:
327 solution['url'] = RECOGNIZED_PATHS[parsed_path] 313 solution['url'] = RECOGNIZED_PATHS[parsed_path]
328 else: 314 else:
329 print 'Warning: path %s not recognized' % parsed_path 315 print 'Warning: path %s not recognized' % parsed_path
330 316 if solution.get('deps_file', 'DEPS') == 'DEPS':
331 if first_solution: 317 solution['deps_file'] = '.DEPS.git'
332 root = parsed_path
333 first_solution = False
334
335
336 solution['managed'] = False 318 solution['managed'] = False
337 # We don't want gclient to be using a safesync URL. Instead it should 319 # We don't want gclient to be using a safesync URL. Instead it should
338 # using the lkgr/lkcr branch/tags. 320 # using the lkgr/lkcr branch/tags.
339 if 'safesync_url' in solution: 321 if 'safesync_url' in solution:
340 print 'Removing safesync url %s from %s' % (solution['safesync_url'], 322 print 'Removing safesync url %s from %s' % (solution['safesync_url'],
341 parsed_path) 323 parsed_path)
342 del solution['safesync_url'] 324 del solution['safesync_url']
343 return solutions, root, buildspec_name 325 return solutions, root
344 326
345 327
346 def ensure_no_checkout(dir_names, scm_dirname): 328 def ensure_no_checkout(dir_names, scm_dirname):
347 """Ensure that there is no undesired checkout under build/. 329 """Ensure that there is no undesired checkout under build/.
348 330
349 If there is an incorrect checkout under build/, then 331 If there is an incorrect checkout under build/, then
350 move build/ to build.dead/ 332 move build/ to build.dead/
351 This function will check each directory in dir_names. 333 This function will check each directory in dir_names.
352 334
353 scm_dirname is expected to be either ['.svn', '.git'] 335 scm_dirname is expected to be either ['.svn', '.git']
(...skipping 17 matching lines...) Expand all
371 chromium_utils.RemoveFile(deletion_target) 353 chromium_utils.RemoveFile(deletion_target)
372 print 'done' 354 print 'done'
373 355
374 356
375 def gclient_configure(solutions, target_os): 357 def gclient_configure(solutions, target_os):
376 """Should do the same thing as gclient --spec='...'.""" 358 """Should do the same thing as gclient --spec='...'."""
377 with codecs.open('.gclient', mode='w', encoding='utf-8') as f: 359 with codecs.open('.gclient', mode='w', encoding='utf-8') as f:
378 f.write(get_gclient_spec(solutions, target_os)) 360 f.write(get_gclient_spec(solutions, target_os))
379 361
380 362
381 def gclient_sync(output_json, buildspec_name): 363 def gclient_sync(output_json):
382 gclient_bin = 'gclient.bat' if sys.platform.startswith('win') else 'gclient' 364 gclient_bin = 'gclient.bat' if sys.platform.startswith('win') else 'gclient'
383 cmd = [gclient_bin, 'sync', '--verbose', '--reset', '--force', 365 call(gclient_bin, 'sync', '--verbose', '--reset', '--force',
384 '--output-json', output_json] 366 '--nohooks', '--noprehooks', '--output-json', output_json)
385 if buildspec_name:
386 cmd += ['--with_branch_heads']
387 else:
388 cmd += ['--nohooks', '--noprehooks']
389 call(*cmd)
390 with open(output_json) as f: 367 with open(output_json) as f:
391 return json.load(f) 368 return json.load(f)
392 369
393 370
394 def create_less_than_or_equal_regex(number): 371 def create_less_than_or_equal_regex(number):
395 """ Return a regular expression to test whether an integer less than or equal 372 """ Return a regular expression to test whether an integer less than or equal
396 to 'number' is present in a given string. 373 to 'number' is present in a given string.
397 """ 374 """
398 375
399 # In three parts, build a regular expression that match any numbers smaller 376 # In three parts, build a regular expression that match any numbers smaller
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after
492 def _last_commit_for_file(filename, repo_base): 469 def _last_commit_for_file(filename, repo_base):
493 cmd = ['log', '--format=%H', '--max-count=1', '--', filename] 470 cmd = ['log', '--format=%H', '--max-count=1', '--', filename]
494 return git(*cmd, cwd=repo_base).strip() 471 return git(*cmd, cwd=repo_base).strip()
495 472
496 473
497 def need_to_run_deps2git(repo_base, deps_file, deps_git_file): 474 def need_to_run_deps2git(repo_base, deps_file, deps_git_file):
498 """Checks to see if we need to run deps2git. 475 """Checks to see if we need to run deps2git.
499 476
500 Returns True if there was a DEPS change after the last .DEPS.git update. 477 Returns True if there was a DEPS change after the last .DEPS.git update.
501 """ 478 """
502 print 'Checking if %s exists' % deps_git_file
503 if not path.isfile(deps_git_file): 479 if not path.isfile(deps_git_file):
504 # .DEPS.git doesn't exist but DEPS does? We probably want to generate one. 480 # .DEPS.git doesn't exist but DEPS does? We probably want to generate one.
505 print 'it exists!'
506 return True 481 return True
507 482
508 last_known_deps_ref = _last_commit_for_file(deps_file, repo_base) 483 last_known_deps_ref = _last_commit_for_file(deps_file, repo_base)
509 last_known_deps_git_ref = _last_commit_for_file(deps_git_file, repo_base) 484 last_known_deps_git_ref = _last_commit_for_file(deps_git_file, repo_base)
510 merge_base_ref = git('merge-base', last_known_deps_ref, 485 merge_base_ref = git('merge-base', last_known_deps_ref,
511 last_known_deps_git_ref, cwd=repo_base).strip() 486 last_known_deps_git_ref, cwd=repo_base).strip()
512 487
513 # If the merge base of the last DEPS and last .DEPS.git file is not 488 # If the merge base of the last DEPS and last .DEPS.git file is not
514 # equivilent to the hash of the last DEPS file, that means the DEPS file 489 # equivilent to the hash of the last DEPS file, that means the DEPS file
515 # was committed after the last .DEPS.git file. 490 # was committed after the last .DEPS.git file.
516 return last_known_deps_ref != merge_base_ref 491 return last_known_deps_ref != merge_base_ref
517 492
518 493
519 def get_git_buildspec(version):
520 """Get the git buildspec of a version, return its contents.
521
522 The contents are returned instead of the file so that we can check the
523 repository into a temp directory and confine the cleanup logic here.
524 """
525 git('cache', 'populate', '-v', '--cache-dir', CACHE_DIR, GIT_BUILDSPEC_REPO)
526 mirror_dir = git(
527 'cache', 'exists', '--cache-dir', CACHE_DIR, GIT_BUILDSPEC_REPO).strip()
528 TOTAL_TRIES = 30
529 for tries in range(TOTAL_TRIES):
530 try:
531 return git('show', 'master:%s/DEPS' % version, cwd=mirror_dir)
532 except SubprocessFailed:
533 if tries < TOTAL_TRIES - 1:
534 print 'Buildspec for %s not committed yet, waiting 5 seconds...'
535 time.sleep(5)
536 git('cache', 'populate', '-v', '--cache-dir',
537 CACHE_DIR, GIT_BUILDSPEC_REPO)
538 else:
539 print >> sys.stderr, '%s not found, ' % version,
540 print >> sys.stderr, 'the buildspec2git cron job probably is not ',
541 print >> sys.stderr, 'running correctly, please contact mmoss@.'
542 raise
543
544
545 def buildspecs2git(sln_dir, buildspec_name):
546 """This is like deps2git, but for buildspecs.
547
548 Because buildspecs are vastly different than normal DEPS files, we cannot
549 use deps2git.py to generate git versions of the git DEPS. Fortunately
550 we don't have buildspec trybots, and there is already a service that
551 generates git DEPS for every buildspec commit already, so we can leverage
552 that service so that we don't need to run buildspec2git.py serially.
553
554 This checks the commit message of the current DEPS file for the release
555 number, waits in a busy loop for the coorisponding .DEPS.git file to be
556 committed into the git_buildspecs repository.
557 """
558 repo_base = path.join(os.getcwd(), sln_dir)
559 deps_file = path.join(repo_base, 'build', buildspec_name, 'DEPS')
560 deps_git_file = path.join(repo_base, 'build', buildspec_name, '.DEPS.git')
561 deps_log = git('log', '-1', '--format=%B', deps_file, cwd=repo_base)
562 m = re.search(r'Buildspec for\s+version (\d+\.\d+\.\d+\.\d+)', deps_log)
563 version = m.group(1)
564 git_buildspec = get_git_buildspec(version)
565 with open(deps_git_file, 'wb') as f:
566 f.write(git_buildspec)
567
568
569 def ensure_deps2git(sln_dir, shallow): 494 def ensure_deps2git(sln_dir, shallow):
570 repo_base = path.join(os.getcwd(), sln_dir) 495 repo_base = path.join(os.getcwd(), sln_dir)
571 deps_file = path.join(repo_base, 'DEPS') 496 deps_file = path.join(repo_base, 'DEPS')
572 deps_git_file = path.join(repo_base, '.DEPS.git') 497 deps_git_file = path.join(repo_base, '.DEPS.git')
573 print 'Checking if %s is newer than %s' % (deps_file, deps_git_file)
574 if not path.isfile(deps_file): 498 if not path.isfile(deps_file):
575 return 499 return
576 500
577 if not need_to_run_deps2git(repo_base, deps_file, deps_git_file): 501 if not need_to_run_deps2git(repo_base, deps_file, deps_git_file):
578 return 502 return
579 503
580 print '===DEPS file modified, need to run deps2git===' 504 print '===DEPS file modified, need to run deps2git==='
581 # Magic to get deps2git to work with internal DEPS. 505 # Magic to get deps2git to work with internal DEPS.
582 shutil.copyfile(S2G_INTERNAL_FROM_PATH, S2G_INTERNAL_DEST_PATH) 506 shutil.copyfile(S2G_INTERNAL_FROM_PATH, S2G_INTERNAL_DEST_PATH)
583 507
584 # TODO(hinoka): This might need to be smarter if we need to deal with 508 # TODO(hinoka): This might need to be smarter if we need to deal with
585 # DEPS changes that are in an internal repository. 509 # DEPS changes that are in an internal repository.
586 repo_type = 'public' 510 repo_type = 'internal' if 'internal' in sln_dir else 'public'
587 if sln_dir in ['src-internal']:
588 repo_type = 'internal'
589 cmd = [sys.executable, DEPS2GIT_PATH, 511 cmd = [sys.executable, DEPS2GIT_PATH,
590 '-t', repo_type, 512 '-t', repo_type,
591 '--cache_dir=%s' % CACHE_DIR, 513 '--cache_dir=%s' % CACHE_DIR,
592 '--deps=%s' % deps_file, 514 '--deps=%s' % deps_file,
593 '--out=%s' % deps_git_file] 515 '--out=%s' % deps_git_file]
594 if shallow: 516 if shallow:
595 cmd.append('--shallow') 517 cmd.append('--shallow')
596 call(*cmd) 518 call(*cmd)
597 519
598 520
(...skipping 247 matching lines...) Expand 10 before | Expand all | Expand 10 after
846 'slave': slave or 'Not specified', 768 'slave': slave or 'Not specified',
847 'recipe': recipe_force, 769 'recipe': recipe_force,
848 }, 770 },
849 # Print to stderr so that it shows up red on win/mac. 771 # Print to stderr so that it shows up red on win/mac.
850 print ACTIVATED_MESSAGE if active else NOT_ACTIVATED_MESSAGE 772 print ACTIVATED_MESSAGE if active else NOT_ACTIVATED_MESSAGE
851 773
852 # Parse, munipulate, and print the gclient solutions. 774 # Parse, munipulate, and print the gclient solutions.
853 specs = {} 775 specs = {}
854 exec(options.specs, specs) 776 exec(options.specs, specs)
855 svn_solutions = specs.get('solutions', []) 777 svn_solutions = specs.get('solutions', [])
856 git_solutions, svn_root, buildspec_name = solutions_to_git(svn_solutions) 778 git_solutions, svn_root = solutions_to_git(svn_solutions)
857 solutions_printer(git_solutions) 779 solutions_printer(git_solutions)
858 780
859 dir_names = [sln.get('name') for sln in svn_solutions if 'name' in sln] 781 dir_names = [sln.get('name') for sln in svn_solutions if 'name' in sln]
860 # If we're active now, but the flag file doesn't exist (we weren't active last 782 # If we're active now, but the flag file doesn't exist (we weren't active last
861 # run) or vice versa, blow away all checkouts. 783 # run) or vice versa, blow away all checkouts.
862 if bool(active) != bool(check_flag(options.flag_file)): 784 if bool(active) != bool(check_flag(options.flag_file)):
863 ensure_no_checkout(dir_names, '*') 785 ensure_no_checkout(dir_names, '*')
864 if options.output_json: 786 if options.output_json:
865 # Make sure we tell recipes that we didn't run if the script exits here. 787 # Make sure we tell recipes that we didn't run if the script exits here.
866 emit_json(options.output_json, did_run=active) 788 emit_json(options.output_json, did_run=active)
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
898 # If either patch_url or issue is passed in, then we need to apply a patch. 820 # If either patch_url or issue is passed in, then we need to apply a patch.
899 if options.patch_url: 821 if options.patch_url:
900 # patch_url takes precidence since its only passed in on gcl try/git try. 822 # patch_url takes precidence since its only passed in on gcl try/git try.
901 apply_issue_svn(options.root, options.patch_url) 823 apply_issue_svn(options.root, options.patch_url)
902 elif options.issue: 824 elif options.issue:
903 apply_issue_rietveld(options.issue, options.patchset, options.root, 825 apply_issue_rietveld(options.issue, options.patchset, options.root,
904 options.rietveld_server, options.revision_mapping, 826 options.rietveld_server, options.revision_mapping,
905 git_ref) 827 git_ref)
906 828
907 # Run deps2git if there is a DEPS commit after the last .DEPS.git commit. 829 # Run deps2git if there is a DEPS commit after the last .DEPS.git commit.
908 if buildspec_name: 830 ensure_deps2git(options.root, options.shallow)
909 buildspecs2git(options.root, buildspec_name)
910 else:
911 ensure_deps2git(options.root, options.shallow)
912 831
913 # Ensure our build/ directory is set up with the correct .gclient file. 832 # Ensure our build/ directory is set up with the correct .gclient file.
914 gclient_configure(git_solutions, specs.get('target_os', [])) 833 gclient_configure(git_solutions, specs.get('target_os', []))
915 834
916 # Let gclient do the DEPS syncing. Also we can get "got revision" data 835 # Let gclient do the DEPS syncing. Also we can get "got revision" data
917 # from gclient by passing in --output-json. In our case, we can just reuse 836 # from gclient by passing in --output-json. In our case, we can just reuse
918 # the temp file that 837 # the temp file that
919 _, gclient_output_file = tempfile.mkstemp(suffix='.json') 838 _, gclient_output_file = tempfile.mkstemp(suffix='.json')
920 gclient_output = gclient_sync(gclient_output_file, buildspec_name) 839 gclient_output = gclient_sync(gclient_output_file)
921 840
922 # If we're fed an svn revision number as --revision, then our got_revision 841 # If we're fed an svn revision number as --revision, then our got_revision
923 # output should be in svn revs. Otherwise it'll be in git hashes. 842 # output should be in svn revs. Otherwise it'll be in git hashes.
924 use_svn_rev = (options.revision and options.revision.isdigit() and 843 use_svn_rev = (options.revision and options.revision.isdigit() and
925 len(options.revision) < 40) 844 len(options.revision) < 40)
926 # Take care of got_revisions outputs. 845 # Take care of got_revisions outputs.
927 revision_mapping = get_revision_mapping(svn_root, options.revision_mapping) 846 revision_mapping = get_revision_mapping(svn_root, options.revision_mapping)
928 got_revisions = parse_got_revision(gclient_output, revision_mapping, 847 got_revisions = parse_got_revision(gclient_output, revision_mapping,
929 use_svn_rev) 848 use_svn_rev)
930 849
931 if options.output_json: 850 if options.output_json:
932 # Tell recipes information such as root, got_revision, etc. 851 # Tell recipes information such as root, got_revision, etc.
933 emit_json(options.output_json, 852 emit_json(options.output_json,
934 did_run=True, 853 did_run=True,
935 root=options.root, 854 root=options.root,
936 step_text=step_text, 855 step_text=step_text,
937 properties=got_revisions) 856 properties=got_revisions)
938 else: 857 else:
939 # If we're not on recipes, tell annotator about our got_revisions. 858 # If we're not on recipes, tell annotator about our got_revisions.
940 emit_properties(got_revisions) 859 emit_properties(got_revisions)
941 860
942 861
943 if __name__ == '__main__': 862 if __name__ == '__main__':
944 sys.exit(main()) 863 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698