OLD | NEW |
1 # Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 """SCM-specific utility classes.""" | 5 """SCM-specific utility classes.""" |
6 | 6 |
7 import cStringIO | 7 import cStringIO |
8 import glob | 8 import glob |
9 import os | 9 import os |
10 import re | 10 import re |
11 import shutil | 11 import shutil |
12 import subprocess | 12 import subprocess |
13 import sys | 13 import sys |
14 import tempfile | 14 import tempfile |
15 import time | 15 import time |
16 import xml.dom.minidom | 16 import xml.dom.minidom |
17 | 17 |
18 import gclient_utils | 18 import gclient_utils |
19 | 19 |
20 def ValidateEmail(email): | 20 def ValidateEmail(email): |
21 return (re.match(r"^[a-zA-Z0-9._%-+]+@[a-zA-Z0-9._%-]+.[a-zA-Z]{2,6}$", email) | 21 return (re.match(r"^[a-zA-Z0-9._%-+]+@[a-zA-Z0-9._%-]+.[a-zA-Z]{2,6}$", email) |
22 is not None) | 22 is not None) |
23 | 23 |
24 | 24 |
25 def GetCasedPath(path): | 25 def GetCasedPath(path): |
26 """Elcheapos way to get the real path case on Windows.""" | 26 """Elcheapos way to get the real path case on Windows.""" |
27 if sys.platform.startswith('win') and os.path.exists(path): | 27 if sys.platform.startswith('win') and os.path.exists(path): |
28 # Reconstruct the path. | 28 # Reconstruct the path. |
29 path = os.path.abspath(path) | 29 path = os.path.abspath(path) |
30 paths = path.split('\\') | 30 paths = path.split('\\') |
31 for i in range(len(paths)): | 31 for i in range(len(paths)): |
32 if i == 0: | 32 if i == 0: |
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
114 if not m: | 114 if not m: |
115 raise Exception("status currently unsupported: %s" % statusline) | 115 raise Exception("status currently unsupported: %s" % statusline) |
116 results.append(('%s ' % m.group(1), m.group(2))) | 116 results.append(('%s ' % m.group(1), m.group(2))) |
117 return results | 117 return results |
118 | 118 |
119 @staticmethod | 119 @staticmethod |
120 def RunAndFilterOutput(args, | 120 def RunAndFilterOutput(args, |
121 in_directory, | 121 in_directory, |
122 print_messages, | 122 print_messages, |
123 print_stdout, | 123 print_stdout, |
124 filter): | 124 filter_fn): |
125 """Runs a command, optionally outputting to stdout. | 125 """Runs a command, optionally outputting to stdout. |
126 | 126 |
127 stdout is passed line-by-line to the given filter function. If | 127 stdout is passed line-by-line to the given filter_fn function. If |
128 print_stdout is true, it is also printed to sys.stdout as in Run. | 128 print_stdout is true, it is also printed to sys.stdout as in Run. |
129 | 129 |
130 Args: | 130 Args: |
131 args: A sequence of command line parameters to be passed. | 131 args: A sequence of command line parameters to be passed. |
132 in_directory: The directory where git is to be run. | 132 in_directory: The directory where git is to be run. |
133 print_messages: Whether to print status messages to stdout about | 133 print_messages: Whether to print status messages to stdout about |
134 which commands are being run. | 134 which commands are being run. |
135 print_stdout: Whether to forward program's output to stdout. | 135 print_stdout: Whether to forward program's output to stdout. |
136 filter: A function taking one argument (a string) which will be | 136 filter_fn: A function taking one argument (a string) which will be |
137 passed each line (with the ending newline character removed) of | 137 passed each line (with the ending newline character removed) of |
138 program's output for filtering. | 138 program's output for filtering. |
139 | 139 |
140 Raises: | 140 Raises: |
141 gclient_utils.Error: An error occurred while running the command. | 141 gclient_utils.Error: An error occurred while running the command. |
142 """ | 142 """ |
143 command = [GIT.COMMAND] | 143 command = [GIT.COMMAND] |
144 command.extend(args) | 144 command.extend(args) |
145 gclient_utils.SubprocessCallAndFilter(command, | 145 gclient_utils.SubprocessCallAndFilter(command, |
146 in_directory, | 146 in_directory, |
147 print_messages, | 147 print_messages, |
148 print_stdout, | 148 print_stdout, |
149 filter=filter) | 149 filter_fn=filter_fn) |
150 | 150 |
151 @staticmethod | 151 @staticmethod |
152 def GetEmail(repo_root): | 152 def GetEmail(repo_root): |
153 """Retrieves the user email address if known.""" | 153 """Retrieves the user email address if known.""" |
154 # We could want to look at the svn cred when it has a svn remote but it | 154 # We could want to look at the svn cred when it has a svn remote but it |
155 # should be fine for now, users should simply configure their git settings. | 155 # should be fine for now, users should simply configure their git settings. |
156 return GIT.Capture(['config', 'user.email'], | 156 return GIT.Capture(['config', 'user.email'], |
157 repo_root, error_ok=True)[0].strip() | 157 repo_root, error_ok=True)[0].strip() |
158 | 158 |
159 @staticmethod | 159 @staticmethod |
(...skipping 297 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
457 continue | 457 continue |
458 # No progress was made or an unknown error we aren't sure, bail out. | 458 # No progress was made or an unknown error we aren't sure, bail out. |
459 raise | 459 raise |
460 break | 460 break |
461 | 461 |
462 @staticmethod | 462 @staticmethod |
463 def RunAndFilterOutput(args, | 463 def RunAndFilterOutput(args, |
464 in_directory, | 464 in_directory, |
465 print_messages, | 465 print_messages, |
466 print_stdout, | 466 print_stdout, |
467 filter): | 467 filter_fn): |
468 """Runs a command, optionally outputting to stdout. | 468 """Runs a command, optionally outputting to stdout. |
469 | 469 |
470 stdout is passed line-by-line to the given filter function. If | 470 stdout is passed line-by-line to the given filter_fn function. If |
471 print_stdout is true, it is also printed to sys.stdout as in Run. | 471 print_stdout is true, it is also printed to sys.stdout as in Run. |
472 | 472 |
473 Args: | 473 Args: |
474 args: A sequence of command line parameters to be passed. | 474 args: A sequence of command line parameters to be passed. |
475 in_directory: The directory where svn is to be run. | 475 in_directory: The directory where svn is to be run. |
476 print_messages: Whether to print status messages to stdout about | 476 print_messages: Whether to print status messages to stdout about |
477 which commands are being run. | 477 which commands are being run. |
478 print_stdout: Whether to forward program's output to stdout. | 478 print_stdout: Whether to forward program's output to stdout. |
479 filter: A function taking one argument (a string) which will be | 479 filter_fn: A function taking one argument (a string) which will be |
480 passed each line (with the ending newline character removed) of | 480 passed each line (with the ending newline character removed) of |
481 program's output for filtering. | 481 program's output for filtering. |
482 | 482 |
483 Raises: | 483 Raises: |
484 gclient_utils.Error: An error occurred while running the command. | 484 gclient_utils.Error: An error occurred while running the command. |
485 """ | 485 """ |
486 command = [SVN.COMMAND] | 486 command = [SVN.COMMAND] |
487 command.extend(args) | 487 command.extend(args) |
488 gclient_utils.SubprocessCallAndFilter(command, | 488 gclient_utils.SubprocessCallAndFilter(command, |
489 in_directory, | 489 in_directory, |
490 print_messages, | 490 print_messages, |
491 print_stdout, | 491 print_stdout, |
492 filter=filter) | 492 filter_fn=filter_fn) |
493 | 493 |
494 @staticmethod | 494 @staticmethod |
495 def CaptureInfo(relpath, in_directory=None, print_error=True): | 495 def CaptureInfo(relpath, in_directory=None, print_error=True): |
496 """Returns a dictionary from the svn info output for the given file. | 496 """Returns a dictionary from the svn info output for the given file. |
497 | 497 |
498 Args: | 498 Args: |
499 relpath: The directory where the working copy resides relative to | 499 relpath: The directory where the working copy resides relative to |
500 the directory given by in_directory. | 500 the directory given by in_directory. |
501 in_directory: The directory where svn is to be run. | 501 in_directory: The directory where svn is to be run. |
502 """ | 502 """ |
503 output = SVN.Capture(["info", "--xml", relpath], in_directory, print_error) | 503 output = SVN.Capture(["info", "--xml", relpath], in_directory, print_error) |
504 dom = gclient_utils.ParseXML(output) | 504 dom = gclient_utils.ParseXML(output) |
505 result = {} | 505 result = {} |
506 if dom: | 506 if dom: |
507 GetNamedNodeText = gclient_utils.GetNamedNodeText | 507 GetNamedNodeText = gclient_utils.GetNamedNodeText |
508 GetNodeNamedAttributeText = gclient_utils.GetNodeNamedAttributeText | 508 GetNodeNamedAttributeText = gclient_utils.GetNodeNamedAttributeText |
509 def C(item, f): | 509 def C(item, f): |
510 if item is not None: return f(item) | 510 if item is not None: |
| 511 return f(item) |
511 # /info/entry/ | 512 # /info/entry/ |
512 # url | 513 # url |
513 # reposityory/(root|uuid) | 514 # reposityory/(root|uuid) |
514 # wc-info/(schedule|depth) | 515 # wc-info/(schedule|depth) |
515 # commit/(author|date) | 516 # commit/(author|date) |
516 # str() the results because they may be returned as Unicode, which | 517 # str() the results because they may be returned as Unicode, which |
517 # interferes with the higher layers matching up things in the deps | 518 # interferes with the higher layers matching up things in the deps |
518 # dictionary. | 519 # dictionary. |
519 # TODO(maruel): Fix at higher level instead (!) | |
520 result['Repository Root'] = C(GetNamedNodeText(dom, 'root'), str) | 520 result['Repository Root'] = C(GetNamedNodeText(dom, 'root'), str) |
521 result['URL'] = C(GetNamedNodeText(dom, 'url'), str) | 521 result['URL'] = C(GetNamedNodeText(dom, 'url'), str) |
522 result['UUID'] = C(GetNamedNodeText(dom, 'uuid'), str) | 522 result['UUID'] = C(GetNamedNodeText(dom, 'uuid'), str) |
523 result['Revision'] = C(GetNodeNamedAttributeText(dom, 'entry', | 523 result['Revision'] = C(GetNodeNamedAttributeText(dom, 'entry', |
524 'revision'), | 524 'revision'), |
525 int) | 525 int) |
526 result['Node Kind'] = C(GetNodeNamedAttributeText(dom, 'entry', 'kind'), | 526 result['Node Kind'] = C(GetNodeNamedAttributeText(dom, 'entry', 'kind'), |
527 str) | 527 str) |
528 # Differs across versions. | 528 # Differs across versions. |
529 if result['Node Kind'] == 'dir': | 529 if result['Node Kind'] == 'dir': |
(...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
640 return SVN.IsMovedInfo(SVN.CaptureInfo(filename)) | 640 return SVN.IsMovedInfo(SVN.CaptureInfo(filename)) |
641 | 641 |
642 @staticmethod | 642 @staticmethod |
643 def IsMovedInfo(info): | 643 def IsMovedInfo(info): |
644 """Determine if a file has been added through svn mv""" | 644 """Determine if a file has been added through svn mv""" |
645 return (info.get('Copied From URL') and | 645 return (info.get('Copied From URL') and |
646 info.get('Copied From Rev') and | 646 info.get('Copied From Rev') and |
647 info.get('Schedule') == 'add') | 647 info.get('Schedule') == 'add') |
648 | 648 |
649 @staticmethod | 649 @staticmethod |
650 def GetFileProperty(file, property_name): | 650 def GetFileProperty(filename, property_name): |
651 """Returns the value of an SVN property for the given file. | 651 """Returns the value of an SVN property for the given file. |
652 | 652 |
653 Args: | 653 Args: |
654 file: The file to check | 654 filename: The file to check |
655 property_name: The name of the SVN property, e.g. "svn:mime-type" | 655 property_name: The name of the SVN property, e.g. "svn:mime-type" |
656 | 656 |
657 Returns: | 657 Returns: |
658 The value of the property, which will be the empty string if the property | 658 The value of the property, which will be the empty string if the property |
659 is not set on the file. If the file is not under version control, the | 659 is not set on the file. If the file is not under version control, the |
660 empty string is also returned. | 660 empty string is also returned. |
661 """ | 661 """ |
662 output = SVN.Capture(["propget", property_name, file]) | 662 output = SVN.Capture(["propget", property_name, filename]) |
663 if (output.startswith("svn: ") and | 663 if (output.startswith("svn: ") and |
664 output.endswith("is not under version control")): | 664 output.endswith("is not under version control")): |
665 return "" | 665 return "" |
666 else: | 666 else: |
667 return output | 667 return output |
668 | 668 |
669 @staticmethod | 669 @staticmethod |
670 def DiffItem(filename, full_move=False, revision=None): | 670 def DiffItem(filename, full_move=False, revision=None): |
671 """Diffs a single file. | 671 """Diffs a single file. |
672 | 672 |
673 Should be simple, eh? No it isn't. | 673 Should be simple, eh? No it isn't. |
674 Be sure to be in the appropriate directory before calling to have the | 674 Be sure to be in the appropriate directory before calling to have the |
675 expected relative path. | 675 expected relative path. |
676 full_move means that move or copy operations should completely recreate the | 676 full_move means that move or copy operations should completely recreate the |
677 files, usually in the prospect to apply the patch for a try job.""" | 677 files, usually in the prospect to apply the patch for a try job.""" |
678 # If the user specified a custom diff command in their svn config file, | 678 # If the user specified a custom diff command in their svn config file, |
679 # then it'll be used when we do svn diff, which we don't want to happen | 679 # then it'll be used when we do svn diff, which we don't want to happen |
680 # since we want the unified diff. Using --diff-cmd=diff doesn't always | 680 # since we want the unified diff. Using --diff-cmd=diff doesn't always |
681 # work, since they can have another diff executable in their path that | 681 # work, since they can have another diff executable in their path that |
682 # gives different line endings. So we use a bogus temp directory as the | 682 # gives different line endings. So we use a bogus temp directory as the |
683 # config directory, which gets around these problems. | 683 # config directory, which gets around these problems. |
684 bogus_dir = tempfile.mkdtemp() | 684 bogus_dir = tempfile.mkdtemp() |
685 try: | 685 try: |
686 # Use "svn info" output instead of os.path.isdir because the latter fails | 686 # Use "svn info" output instead of os.path.isdir because the latter fails |
687 # when the file is deleted. | 687 # when the file is deleted. |
688 return SVN._DiffItemInternal(SVN.CaptureInfo(filename), | 688 return SVN._DiffItemInternal(filename, SVN.CaptureInfo(filename), |
| 689 bogus_dir, |
689 full_move=full_move, revision=revision) | 690 full_move=full_move, revision=revision) |
690 finally: | 691 finally: |
691 shutil.rmtree(bogus_dir) | 692 shutil.rmtree(bogus_dir) |
692 | 693 |
693 @staticmethod | 694 @staticmethod |
694 def _DiffItemInternal(filename, info, bogus_dir, full_move=False, | 695 def _DiffItemInternal(filename, info, bogus_dir, full_move=False, |
695 revision=None): | 696 revision=None): |
696 """Grabs the diff data.""" | 697 """Grabs the diff data.""" |
697 command = ["diff", "--config-dir", bogus_dir, filename] | 698 command = ["diff", "--config-dir", bogus_dir, filename] |
698 if revision: | 699 if revision: |
(...skipping 129 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
828 'svn.simple') | 829 'svn.simple') |
829 for credfile in os.listdir(auth_dir): | 830 for credfile in os.listdir(auth_dir): |
830 cred_info = SVN.ReadSimpleAuth(os.path.join(auth_dir, credfile)) | 831 cred_info = SVN.ReadSimpleAuth(os.path.join(auth_dir, credfile)) |
831 if regexp.match(cred_info.get('svn:realmstring')): | 832 if regexp.match(cred_info.get('svn:realmstring')): |
832 return cred_info.get('username') | 833 return cred_info.get('username') |
833 | 834 |
834 @staticmethod | 835 @staticmethod |
835 def ReadSimpleAuth(filename): | 836 def ReadSimpleAuth(filename): |
836 f = open(filename, 'r') | 837 f = open(filename, 'r') |
837 values = {} | 838 values = {} |
838 def ReadOneItem(type): | 839 def ReadOneItem(item_type): |
839 m = re.match(r'%s (\d+)' % type, f.readline()) | 840 m = re.match(r'%s (\d+)' % item_type, f.readline()) |
840 if not m: | 841 if not m: |
841 return None | 842 return None |
842 data = f.read(int(m.group(1))) | 843 data = f.read(int(m.group(1))) |
843 if f.read(1) != '\n': | 844 if f.read(1) != '\n': |
844 return None | 845 return None |
845 return data | 846 return data |
846 | 847 |
847 while True: | 848 while True: |
848 key = ReadOneItem('K') | 849 key = ReadOneItem('K') |
849 if not key: | 850 if not key: |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
885 if not SVN.current_version: | 886 if not SVN.current_version: |
886 SVN.current_version = SVN.Capture(['--version']).split()[2] | 887 SVN.current_version = SVN.Capture(['--version']).split()[2] |
887 current_version_list = map(only_int, SVN.current_version.split('.')) | 888 current_version_list = map(only_int, SVN.current_version.split('.')) |
888 for min_ver in map(int, min_version.split('.')): | 889 for min_ver in map(int, min_version.split('.')): |
889 ver = current_version_list.pop(0) | 890 ver = current_version_list.pop(0) |
890 if ver < min_ver: | 891 if ver < min_ver: |
891 return (False, SVN.current_version) | 892 return (False, SVN.current_version) |
892 elif ver > min_ver: | 893 elif ver > min_ver: |
893 return (True, SVN.current_version) | 894 return (True, SVN.current_version) |
894 return (True, SVN.current_version) | 895 return (True, SVN.current_version) |
OLD | NEW |