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

Side by Side Diff: gclient_scm.py

Issue 393001: Split scm-specific functions out of gclient_scm.py to scm.py. (Closed)
Patch Set: Created 11 years, 1 month 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 | « PRESUBMIT.py ('k') | scm.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2009 Google Inc. All Rights Reserved. 1 # Copyright 2009 Google Inc. All Rights Reserved.
2 # 2 #
3 # Licensed under the Apache License, Version 2.0 (the "License"); 3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License. 4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at 5 # You may obtain a copy of the License at
6 # 6 #
7 # http://www.apache.org/licenses/LICENSE-2.0 7 # http://www.apache.org/licenses/LICENSE-2.0
8 # 8 #
9 # Unless required by applicable law or agreed to in writing, software 9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS, 10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and 12 # See the License for the specific language governing permissions and
13 # limitations under the License. 13 # limitations under the License.
14 14
15 """Gclient-specific SCM-specific operations."""
15 16
16 import logging 17 import logging
17 import os 18 import os
18 import re 19 import re
19 import subprocess 20 import subprocess
20 import sys 21 import sys
21 import xml.dom.minidom 22 import xml.dom.minidom
22 23
23 import gclient_utils 24 import gclient_utils
24 25 # TODO(maruel): Temporary.
25 SVN_COMMAND = "svn" 26 from scm import CaptureGit, CaptureGitStatus, CaptureSVN
26 GIT_COMMAND = "git" 27 from scm import CaptureSVNHeadRevision, CaptureSVNInfo, CaptureSVNStatus
28 from scm import RunSVN, RunSVNAndFilterOutput, RunSVNAndGetFileList
27 29
28 30
29 ### SCM abstraction layer 31 ### SCM abstraction layer
30 32
31 33
32 # Factory Method for SCM wrapper creation 34 # Factory Method for SCM wrapper creation
33 35
34 def CreateSCM(url=None, root_dir=None, relpath=None, scm_name='svn'): 36 def CreateSCM(url=None, root_dir=None, relpath=None, scm_name='svn'):
35 # TODO(maruel): Deduce the SCM from the url. 37 # TODO(maruel): Deduce the SCM from the url.
36 scm_map = { 38 scm_map = {
(...skipping 442 matching lines...) Expand 10 before | Expand all | Expand 10 after
479 self.ReplaceAndPrint(line) 481 self.ReplaceAndPrint(line)
480 else: 482 else:
481 if (line.startswith(self.original_prefix) or 483 if (line.startswith(self.original_prefix) or
482 line.startswith(self.working_prefix)): 484 line.startswith(self.working_prefix)):
483 self.ReplaceAndPrint(line) 485 self.ReplaceAndPrint(line)
484 else: 486 else:
485 print line 487 print line
486 488
487 filterer = DiffFilterer(self.relpath) 489 filterer = DiffFilterer(self.relpath)
488 RunSVNAndFilterOutput(command, path, False, False, filterer.Filter) 490 RunSVNAndFilterOutput(command, path, False, False, filterer.Filter)
489
490
491 # -----------------------------------------------------------------------------
492 # Git utils:
493
494
495 def CaptureGit(args, in_directory=None, print_error=True):
496 """Runs git, capturing output sent to stdout as a string.
497
498 Args:
499 args: A sequence of command line parameters to be passed to git.
500 in_directory: The directory where git is to be run.
501
502 Returns:
503 The output sent to stdout as a string.
504 """
505 c = [GIT_COMMAND]
506 c.extend(args)
507
508 # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for
509 # the git.exe executable, but shell=True makes subprocess on Linux fail
510 # when it's called with a list because it only tries to execute the
511 # first string ("git").
512 stderr = None
513 if not print_error:
514 stderr = subprocess.PIPE
515 return subprocess.Popen(c,
516 cwd=in_directory,
517 shell=sys.platform.startswith('win'),
518 stdout=subprocess.PIPE,
519 stderr=stderr).communicate()[0]
520
521
522 def CaptureGitStatus(files, upstream_branch='origin'):
523 """Returns git status.
524
525 @files can be a string (one file) or a list of files.
526
527 Returns an array of (status, file) tuples."""
528 command = ["diff", "--name-status", "-r", "%s.." % upstream_branch]
529 if not files:
530 pass
531 elif isinstance(files, basestring):
532 command.append(files)
533 else:
534 command.extend(files)
535
536 status = CaptureGit(command).rstrip()
537 results = []
538 if status:
539 for statusline in status.split('\n'):
540 m = re.match('^(\w)\t(.+)$', statusline)
541 if not m:
542 raise Exception("status currently unsupported: %s" % statusline)
543 results.append(('%s ' % m.group(1), m.group(2)))
544 return results
545
546
547 # -----------------------------------------------------------------------------
548 # SVN utils:
549
550
551 def RunSVN(args, in_directory):
552 """Runs svn, sending output to stdout.
553
554 Args:
555 args: A sequence of command line parameters to be passed to svn.
556 in_directory: The directory where svn is to be run.
557
558 Raises:
559 Error: An error occurred while running the svn command.
560 """
561 c = [SVN_COMMAND]
562 c.extend(args)
563
564 gclient_utils.SubprocessCall(c, in_directory)
565
566
567 def CaptureSVN(args, in_directory=None, print_error=True):
568 """Runs svn, capturing output sent to stdout as a string.
569
570 Args:
571 args: A sequence of command line parameters to be passed to svn.
572 in_directory: The directory where svn is to be run.
573
574 Returns:
575 The output sent to stdout as a string.
576 """
577 c = [SVN_COMMAND]
578 c.extend(args)
579
580 # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for
581 # the svn.exe executable, but shell=True makes subprocess on Linux fail
582 # when it's called with a list because it only tries to execute the
583 # first string ("svn").
584 stderr = None
585 if not print_error:
586 stderr = subprocess.PIPE
587 return subprocess.Popen(c,
588 cwd=in_directory,
589 shell=(sys.platform == 'win32'),
590 stdout=subprocess.PIPE,
591 stderr=stderr).communicate()[0]
592
593
594 def RunSVNAndGetFileList(options, args, in_directory, file_list):
595 """Runs svn checkout, update, or status, output to stdout.
596
597 The first item in args must be either "checkout", "update", or "status".
598
599 svn's stdout is parsed to collect a list of files checked out or updated.
600 These files are appended to file_list. svn's stdout is also printed to
601 sys.stdout as in RunSVN.
602
603 Args:
604 options: command line options to gclient
605 args: A sequence of command line parameters to be passed to svn.
606 in_directory: The directory where svn is to be run.
607
608 Raises:
609 Error: An error occurred while running the svn command.
610 """
611 command = [SVN_COMMAND]
612 command.extend(args)
613
614 # svn update and svn checkout use the same pattern: the first three columns
615 # are for file status, property status, and lock status. This is followed
616 # by two spaces, and then the path to the file.
617 update_pattern = '^... (.*)$'
618
619 # The first three columns of svn status are the same as for svn update and
620 # svn checkout. The next three columns indicate addition-with-history,
621 # switch, and remote lock status. This is followed by one space, and then
622 # the path to the file.
623 status_pattern = '^...... (.*)$'
624
625 # args[0] must be a supported command. This will blow up if it's something
626 # else, which is good. Note that the patterns are only effective when
627 # these commands are used in their ordinary forms, the patterns are invalid
628 # for "svn status --show-updates", for example.
629 pattern = {
630 'checkout': update_pattern,
631 'status': status_pattern,
632 'update': update_pattern,
633 }[args[0]]
634
635 compiled_pattern = re.compile(pattern)
636
637 def CaptureMatchingLines(line):
638 match = compiled_pattern.search(line)
639 if match:
640 file_list.append(match.group(1))
641
642 RunSVNAndFilterOutput(args,
643 in_directory,
644 options.verbose,
645 True,
646 CaptureMatchingLines)
647
648 def RunSVNAndFilterOutput(args,
649 in_directory,
650 print_messages,
651 print_stdout,
652 filter):
653 """Runs svn checkout, update, status, or diff, optionally outputting
654 to stdout.
655
656 The first item in args must be either "checkout", "update",
657 "status", or "diff".
658
659 svn's stdout is passed line-by-line to the given filter function. If
660 print_stdout is true, it is also printed to sys.stdout as in RunSVN.
661
662 Args:
663 args: A sequence of command line parameters to be passed to svn.
664 in_directory: The directory where svn is to be run.
665 print_messages: Whether to print status messages to stdout about
666 which Subversion commands are being run.
667 print_stdout: Whether to forward Subversion's output to stdout.
668 filter: A function taking one argument (a string) which will be
669 passed each line (with the ending newline character removed) of
670 Subversion's output for filtering.
671
672 Raises:
673 Error: An error occurred while running the svn command.
674 """
675 command = [SVN_COMMAND]
676 command.extend(args)
677
678 gclient_utils.SubprocessCallAndFilter(command,
679 in_directory,
680 print_messages,
681 print_stdout,
682 filter=filter)
683
684 def CaptureSVNInfo(relpath, in_directory=None, print_error=True):
685 """Returns a dictionary from the svn info output for the given file.
686
687 Args:
688 relpath: The directory where the working copy resides relative to
689 the directory given by in_directory.
690 in_directory: The directory where svn is to be run.
691 """
692 output = CaptureSVN(["info", "--xml", relpath], in_directory, print_error)
693 dom = gclient_utils.ParseXML(output)
694 result = {}
695 if dom:
696 GetNamedNodeText = gclient_utils.GetNamedNodeText
697 GetNodeNamedAttributeText = gclient_utils.GetNodeNamedAttributeText
698 def C(item, f):
699 if item is not None: return f(item)
700 # /info/entry/
701 # url
702 # reposityory/(root|uuid)
703 # wc-info/(schedule|depth)
704 # commit/(author|date)
705 # str() the results because they may be returned as Unicode, which
706 # interferes with the higher layers matching up things in the deps
707 # dictionary.
708 # TODO(maruel): Fix at higher level instead (!)
709 result['Repository Root'] = C(GetNamedNodeText(dom, 'root'), str)
710 result['URL'] = C(GetNamedNodeText(dom, 'url'), str)
711 result['UUID'] = C(GetNamedNodeText(dom, 'uuid'), str)
712 result['Revision'] = C(GetNodeNamedAttributeText(dom, 'entry', 'revision'),
713 int)
714 result['Node Kind'] = C(GetNodeNamedAttributeText(dom, 'entry', 'kind'),
715 str)
716 result['Schedule'] = C(GetNamedNodeText(dom, 'schedule'), str)
717 result['Path'] = C(GetNodeNamedAttributeText(dom, 'entry', 'path'), str)
718 result['Copied From URL'] = C(GetNamedNodeText(dom, 'copy-from-url'), str)
719 result['Copied From Rev'] = C(GetNamedNodeText(dom, 'copy-from-rev'), str)
720 return result
721
722
723 def CaptureSVNHeadRevision(url):
724 """Get the head revision of a SVN repository.
725
726 Returns:
727 Int head revision
728 """
729 info = CaptureSVN(["info", "--xml", url], os.getcwd())
730 dom = xml.dom.minidom.parseString(info)
731 return dom.getElementsByTagName('entry')[0].getAttribute('revision')
732
733
734 def CaptureSVNStatus(files):
735 """Returns the svn 1.5 svn status emulated output.
736
737 @files can be a string (one file) or a list of files.
738
739 Returns an array of (status, file) tuples."""
740 command = ["status", "--xml"]
741 if not files:
742 pass
743 elif isinstance(files, basestring):
744 command.append(files)
745 else:
746 command.extend(files)
747
748 status_letter = {
749 None: ' ',
750 '': ' ',
751 'added': 'A',
752 'conflicted': 'C',
753 'deleted': 'D',
754 'external': 'X',
755 'ignored': 'I',
756 'incomplete': '!',
757 'merged': 'G',
758 'missing': '!',
759 'modified': 'M',
760 'none': ' ',
761 'normal': ' ',
762 'obstructed': '~',
763 'replaced': 'R',
764 'unversioned': '?',
765 }
766 dom = gclient_utils.ParseXML(CaptureSVN(command))
767 results = []
768 if dom:
769 # /status/target/entry/(wc-status|commit|author|date)
770 for target in dom.getElementsByTagName('target'):
771 for entry in target.getElementsByTagName('entry'):
772 file_path = entry.getAttribute('path')
773 wc_status = entry.getElementsByTagName('wc-status')
774 assert len(wc_status) == 1
775 # Emulate svn 1.5 status ouput...
776 statuses = [' '] * 7
777 # Col 0
778 xml_item_status = wc_status[0].getAttribute('item')
779 if xml_item_status in status_letter:
780 statuses[0] = status_letter[xml_item_status]
781 else:
782 raise Exception('Unknown item status "%s"; please implement me!' %
783 xml_item_status)
784 # Col 1
785 xml_props_status = wc_status[0].getAttribute('props')
786 if xml_props_status == 'modified':
787 statuses[1] = 'M'
788 elif xml_props_status == 'conflicted':
789 statuses[1] = 'C'
790 elif (not xml_props_status or xml_props_status == 'none' or
791 xml_props_status == 'normal'):
792 pass
793 else:
794 raise Exception('Unknown props status "%s"; please implement me!' %
795 xml_props_status)
796 # Col 2
797 if wc_status[0].getAttribute('wc-locked') == 'true':
798 statuses[2] = 'L'
799 # Col 3
800 if wc_status[0].getAttribute('copied') == 'true':
801 statuses[3] = '+'
802 # Col 4
803 if wc_status[0].getAttribute('switched') == 'true':
804 statuses[4] = 'S'
805 # TODO(maruel): Col 5 and 6
806 item = (''.join(statuses), file_path)
807 results.append(item)
808 return results
OLDNEW
« no previous file with comments | « PRESUBMIT.py ('k') | scm.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698