OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # Copyright (c) 2010 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2010 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 """A wrapper script to manage a set of client modules in different SCM. | 6 """A wrapper script to manage a set of client modules in different SCM. |
7 | 7 |
8 This script is intended to be used to help basic management of client | 8 This script is intended to be used to help basic management of client |
9 program sources residing in one or more Subversion modules and Git | 9 program sources residing in one or more Subversion modules and Git |
10 repositories, along with other modules it depends on, also in Subversion or Git, | 10 repositories, along with other modules it depends on, also in Subversion or Git, |
(...skipping 485 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
496 return | 496 return |
497 | 497 |
498 # Run hooks on the basis of whether the files from the gclient operation | 498 # Run hooks on the basis of whether the files from the gclient operation |
499 # match each hook's pattern. | 499 # match each hook's pattern. |
500 for hook_dict in hooks: | 500 for hook_dict in hooks: |
501 pattern = re.compile(hook_dict['pattern']) | 501 pattern = re.compile(hook_dict['pattern']) |
502 matching_file_list = [f for f in file_list if pattern.search(f)] | 502 matching_file_list = [f for f in file_list if pattern.search(f)] |
503 if matching_file_list: | 503 if matching_file_list: |
504 self._RunHookAction(hook_dict, matching_file_list) | 504 self._RunHookAction(hook_dict, matching_file_list) |
505 | 505 |
| 506 def _EnforceRevisions(self, solutions): |
| 507 """Checks for revision overrides.""" |
| 508 revision_overrides = {} |
| 509 solutions = [s['name'] for s in solutions] |
| 510 if self._options.revisions: |
| 511 revision = self._options.revisions[0] |
| 512 # Ignore solution@ |
| 513 rev = revision |
| 514 if '@' in revision: |
| 515 rev = revision.split('@', 1)[1] |
| 516 revision_overrides[solutions[0]] = rev |
| 517 |
| 518 if len(self._options.revisions) > 1: |
| 519 # Enforce solution@rev format for following params. |
| 520 for revision in self._options.revisions[1:]: |
| 521 if not '@' in revision: |
| 522 raise gclient_utils.Error( |
| 523 'Specify the full dependency when specifying a revision number ' |
| 524 'for non-primary solution.') |
| 525 sol, rev = revision.split("@", 1) |
| 526 # Disallow conflicting revs |
| 527 if revision_overrides.get(sol, rev) != rev: |
| 528 raise gclient_utils.Error( |
| 529 'Conflicting revision numbers specified for %s: %s and %s.' % |
| 530 (sol, revision_overrides[sol], rev)) |
| 531 if not sol in solutions: |
| 532 raise gclient_utils.Error('%s is not a valid solution.' % sol) |
| 533 revision_overrides[sol] = rev |
| 534 return revision_overrides |
| 535 |
506 def RunOnDeps(self, command, args): | 536 def RunOnDeps(self, command, args): |
507 """Runs a command on each dependency in a client and its dependencies. | 537 """Runs a command on each dependency in a client and its dependencies. |
508 | 538 |
509 The module's dependencies are specified in its top-level DEPS files. | 539 The module's dependencies are specified in its top-level DEPS files. |
510 | 540 |
511 Args: | 541 Args: |
512 command: The command to use (e.g., 'status' or 'diff') | 542 command: The command to use (e.g., 'status' or 'diff') |
513 args: list of str - extra arguments to add to the command line. | 543 args: list of str - extra arguments to add to the command line. |
514 | 544 |
515 Raises: | 545 Raises: |
516 Error: If the client has conflicting entries. | 546 Error: If the client has conflicting entries. |
517 """ | 547 """ |
518 if not command in self.supported_commands: | 548 if not command in self.supported_commands: |
519 raise gclient_utils.Error("'%s' is an unsupported command" % command) | 549 raise gclient_utils.Error("'%s' is an unsupported command" % command) |
520 | 550 |
521 # Check for revision overrides. | |
522 revision_overrides = {} | |
523 for revision in self._options.revisions: | |
524 if revision.find("@") == -1: | |
525 raise gclient_utils.Error( | |
526 "Specify the full dependency when specifying a revision number.") | |
527 revision_elem = revision.split("@") | |
528 # Disallow conflicting revs | |
529 if revision_overrides.has_key(revision_elem[0]) and \ | |
530 revision_overrides[revision_elem[0]] != revision_elem[1]: | |
531 raise gclient_utils.Error( | |
532 "Conflicting revision numbers specified.") | |
533 revision_overrides[revision_elem[0]] = revision_elem[1] | |
534 | |
535 solutions = self.GetVar("solutions") | 551 solutions = self.GetVar("solutions") |
536 if not solutions: | 552 if not solutions: |
537 raise gclient_utils.Error("No solution specified") | 553 raise gclient_utils.Error("No solution specified") |
| 554 revision_overrides = self._EnforceRevisions(solutions) |
538 | 555 |
539 # When running runhooks --force, there's no need to consult the SCM. | 556 # When running runhooks --force, there's no need to consult the SCM. |
540 # All known hooks are expected to run unconditionally regardless of working | 557 # All known hooks are expected to run unconditionally regardless of working |
541 # copy state, so skip the SCM status check. | 558 # copy state, so skip the SCM status check. |
542 run_scm = not (command == 'runhooks' and self._options.force) | 559 run_scm = not (command == 'runhooks' and self._options.force) |
543 | 560 |
544 entries = {} | 561 entries = {} |
545 entries_deps_content = {} | 562 entries_deps_content = {} |
546 file_list = [] | 563 file_list = [] |
547 # Run on the base solutions first. | 564 # Run on the base solutions first. |
(...skipping 124 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
672 print("\n________ deleting \'%s\' " + | 689 print("\n________ deleting \'%s\' " + |
673 "in \'%s\'") % (entry_fixed, self._root_dir) | 690 "in \'%s\'") % (entry_fixed, self._root_dir) |
674 gclient_utils.RemoveDirectory(e_dir) | 691 gclient_utils.RemoveDirectory(e_dir) |
675 # record the current list of entries for next time | 692 # record the current list of entries for next time |
676 self._SaveEntries(entries) | 693 self._SaveEntries(entries) |
677 | 694 |
678 def PrintRevInfo(self): | 695 def PrintRevInfo(self): |
679 """Output revision info mapping for the client and its dependencies. | 696 """Output revision info mapping for the client and its dependencies. |
680 | 697 |
681 This allows the capture of an overall "revision" for the source tree that | 698 This allows the capture of an overall "revision" for the source tree that |
682 can be used to reproduce the same tree in the future. The actual output | 699 can be used to reproduce the same tree in the future. It is only useful for |
683 contains enough information (source paths, svn server urls and revisions) | 700 "unpinned dependencies", i.e. DEPS/deps references without a svn revision |
684 that it can be used either to generate external svn/git commands (without | 701 number or a git hash. A git branch name isn't "pinned" since the actual |
685 gclient) or as input to gclient's --rev option (with some massaging of | 702 commit can change. |
686 the data). | |
687 | |
688 Since we care about the revision of the current source tree, for git | |
689 repositories this command uses the revision of the HEAD. For subversion we | |
690 use BASE. | |
691 | 703 |
692 The --snapshot option allows creating a .gclient file to reproduce the tree. | 704 The --snapshot option allows creating a .gclient file to reproduce the tree. |
693 | |
694 Raises: | |
695 Error: If the client has conflicting entries. | |
696 """ | 705 """ |
697 # Check for revision overrides. | |
698 revision_overrides = {} | |
699 for revision in self._options.revisions: | |
700 if revision.find("@") < 0: | |
701 raise gclient_utils.Error( | |
702 "Specify the full dependency when specifying a revision number.") | |
703 revision_elem = revision.split("@") | |
704 # Disallow conflicting revs | |
705 if revision_overrides.has_key(revision_elem[0]) and \ | |
706 revision_overrides[revision_elem[0]] != revision_elem[1]: | |
707 raise gclient_utils.Error( | |
708 "Conflicting revision numbers specified.") | |
709 revision_overrides[revision_elem[0]] = revision_elem[1] | |
710 | |
711 solutions = self.GetVar("solutions") | 706 solutions = self.GetVar("solutions") |
712 if not solutions: | 707 if not solutions: |
713 raise gclient_utils.Error("No solution specified") | 708 raise gclient_utils.Error("No solution specified") |
714 | 709 |
715 # Inner helper to generate base url and rev tuple | 710 # Inner helper to generate base url and rev tuple |
716 def GetURLAndRev(name, original_url): | 711 def GetURLAndRev(name, original_url): |
717 url, _ = gclient_utils.SplitUrlRevision(original_url) | 712 url, _ = gclient_utils.SplitUrlRevision(original_url) |
718 scm = gclient_scm.CreateSCM(original_url, self._root_dir, name) | 713 scm = gclient_scm.CreateSCM(original_url, self._root_dir, name) |
719 return (url, scm.revinfo(self._options, [], None)) | 714 return (url, scm.revinfo(self._options, [], None)) |
720 | 715 |
(...skipping 205 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
926 update src directory to r31000 | 921 update src directory to r31000 |
927 """) | 922 """) |
928 def CMDsync(parser, args): | 923 def CMDsync(parser, args): |
929 """Checkout/update all modules.""" | 924 """Checkout/update all modules.""" |
930 parser.add_option("--force", action="store_true", | 925 parser.add_option("--force", action="store_true", |
931 help="force update even for unchanged modules") | 926 help="force update even for unchanged modules") |
932 parser.add_option("--nohooks", action="store_true", | 927 parser.add_option("--nohooks", action="store_true", |
933 help="don't run hooks after the update is complete") | 928 help="don't run hooks after the update is complete") |
934 parser.add_option("-r", "--revision", action="append", | 929 parser.add_option("-r", "--revision", action="append", |
935 dest="revisions", metavar="REV", default=[], | 930 dest="revisions", metavar="REV", default=[], |
936 help="update given solution to specified revision, " | 931 help="Enforces revision/hash for the primary solution. " |
937 "can be used multiple times for each solution, " | 932 "To modify a secondary solution, use it at least once " |
938 "e.g. -r src@123, -r internal@32") | 933 "for the primary solution and then use the format " |
| 934 "solution@rev/hash, e.g. -r src@123") |
939 parser.add_option("--head", action="store_true", | 935 parser.add_option("--head", action="store_true", |
940 help="skips any safesync_urls specified in " | 936 help="skips any safesync_urls specified in " |
941 "configured solutions and sync to head instead") | 937 "configured solutions and sync to head instead") |
942 parser.add_option("--delete_unversioned_trees", action="store_true", | 938 parser.add_option("--delete_unversioned_trees", action="store_true", |
943 help="delete any unexpected unversioned trees " | 939 help="delete any unexpected unversioned trees " |
944 "that are in the checkout") | 940 "that are in the checkout") |
945 parser.add_option("--reset", action="store_true", | 941 parser.add_option("--reset", action="store_true", |
946 help="resets any local changes before updating (git only)") | 942 help="resets any local changes before updating (git only)") |
947 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST", | 943 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST", |
948 help="sync deps for the specified (comma-separated) " | 944 help="sync deps for the specified (comma-separated) " |
949 "platform(s); 'all' will sync all platforms") | 945 "platform(s); 'all' will sync all platforms") |
950 parser.add_option("--manually_grab_svn_rev", action="store_true", | 946 parser.add_option("--manually_grab_svn_rev", action="store_true", |
951 help="Skip svn up whenever possible by requesting " | 947 help="Skip svn up whenever possible by requesting " |
952 "actual HEAD revision from the repository") | 948 "actual HEAD revision from the repository") |
953 (options, args) = parser.parse_args(args) | 949 (options, args) = parser.parse_args(args) |
954 client = GClient.LoadCurrentConfig(options) | 950 client = GClient.LoadCurrentConfig(options) |
955 | 951 |
956 if not client: | 952 if not client: |
957 raise gclient_utils.Error("client not configured; see 'gclient config'") | 953 raise gclient_utils.Error("client not configured; see 'gclient config'") |
958 | 954 |
959 if not options.head: | 955 if not options.head: |
960 solutions = client.GetVar('solutions') | 956 solutions = client.GetVar('solutions') |
961 if solutions: | 957 if solutions: |
| 958 first = True |
962 for s in solutions: | 959 for s in solutions: |
963 if s.get('safesync_url', ''): | 960 if s.get('safesync_url', None): |
964 # rip through revisions and make sure we're not over-riding | 961 # rip through revisions and make sure we're not over-riding |
965 # something that was explicitly passed | 962 # something that was explicitly passed |
966 has_key = False | 963 has_key = False |
| 964 r_first = True |
967 for r in options.revisions: | 965 for r in options.revisions: |
968 if r.split('@')[0] == s['name']: | 966 if (first and r_first) or r.split('@', 1)[0] == s['name']: |
969 has_key = True | 967 has_key = True |
970 break | 968 break |
| 969 r_first = False |
971 | 970 |
972 if not has_key: | 971 if not has_key: |
973 handle = urllib.urlopen(s['safesync_url']) | 972 handle = urllib.urlopen(s['safesync_url']) |
974 rev = handle.read().strip() | 973 rev = handle.read().strip() |
975 handle.close() | 974 handle.close() |
976 if len(rev): | 975 if len(rev): |
977 options.revisions.append(s['name']+'@'+rev) | 976 options.revisions.append(s['name']+'@'+rev) |
| 977 first = False |
978 | 978 |
979 if options.verbose: | 979 if options.verbose: |
980 # Print out the .gclient file. This is longer than if we just printed the | 980 # Print out the .gclient file. This is longer than if we just printed the |
981 # client dict, but more legible, and it might contain helpful comments. | 981 # client dict, but more legible, and it might contain helpful comments. |
982 print(client.ConfigContent()) | 982 print(client.ConfigContent()) |
983 return client.RunOnDeps('update', args) | 983 return client.RunOnDeps('update', args) |
984 | 984 |
985 | 985 |
986 def CMDupdate(parser, args): | 986 def CMDupdate(parser, args): |
987 """Alias for the sync command. Deprecated.""" | 987 """Alias for the sync command. Deprecated.""" |
(...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1116 | 1116 |
1117 | 1117 |
1118 if "__main__" == __name__: | 1118 if "__main__" == __name__: |
1119 try: | 1119 try: |
1120 sys.exit(Main(sys.argv[1:])) | 1120 sys.exit(Main(sys.argv[1:])) |
1121 except gclient_utils.Error, e: | 1121 except gclient_utils.Error, e: |
1122 print >> sys.stderr, "Error: %s" % str(e) | 1122 print >> sys.stderr, "Error: %s" % str(e) |
1123 sys.exit(1) | 1123 sys.exit(1) |
1124 | 1124 |
1125 # vim: ts=2:sw=2:tw=80:et: | 1125 # vim: ts=2:sw=2:tw=80:et: |
OLD | NEW |