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

Side by Side Diff: gclient.py

Issue 193004: Added "gclient pack" subcommand, which generates a patch relative to... (Closed) Base URL: svn://chrome-svn/chrome/trunk/tools/depot_tools/
Patch Set: '' Created 11 years, 3 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 | Annotate | Revision Log
« no previous file with comments | « no previous file | presubmit_canned_checks.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 #!/usr/bin/python 1 #!/usr/bin/python
2 # 2 #
3 # Copyright 2008 Google Inc. All Rights Reserved. 3 # Copyright 2008 Google Inc. All Rights Reserved.
4 # 4 #
5 # Licensed under the Apache License, Version 2.0 (the "License"); 5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License. 6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at 7 # You may obtain a copy of the License at
8 # 8 #
9 # http://www.apache.org/licenses/LICENSE-2.0 9 # http://www.apache.org/licenses/LICENSE-2.0
10 # 10 #
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
88 DEFAULT_USAGE_TEXT = ( 88 DEFAULT_USAGE_TEXT = (
89 """usage: %prog <subcommand> [options] [--] [svn options/args...] 89 """usage: %prog <subcommand> [options] [--] [svn options/args...]
90 a wrapper for managing a set of client modules in svn. 90 a wrapper for managing a set of client modules in svn.
91 Version """ + __version__ + """ 91 Version """ + __version__ + """
92 92
93 subcommands: 93 subcommands:
94 cleanup 94 cleanup
95 config 95 config
96 diff 96 diff
97 export 97 export
98 pack
98 revert 99 revert
99 status 100 status
100 sync 101 sync
101 update 102 update
102 runhooks 103 runhooks
103 revinfo 104 revinfo
104 105
105 Options and extra arguments can be passed to invoked svn commands by 106 Options and extra arguments can be passed to invoked svn commands by
106 appending them to the command line. Note that if the first such 107 appending them to the command line. Note that if the first such
107 appended option starts with a dash (-) then the options must be 108 appended option starts with a dash (-) then the options must be
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after
189 gclient diff 190 gclient diff
190 simple 'svn diff' for configured client and dependences 191 simple 'svn diff' for configured client and dependences
191 gclient diff -- -x -b 192 gclient diff -- -x -b
192 use 'svn diff -x -b' to suppress whitespace-only differences 193 use 'svn diff -x -b' to suppress whitespace-only differences
193 gclient diff -- -r HEAD -x -b 194 gclient diff -- -r HEAD -x -b
194 diff versus the latest version of each module 195 diff versus the latest version of each module
195 """, 196 """,
196 "export": 197 "export":
197 """Wrapper for svn export for all managed directories 198 """Wrapper for svn export for all managed directories
198 """, 199 """,
200 "pack":
201
sgk 2009/09/04 00:25:11 No extra newline here (consistency with other entr
202 """Generate a patch which can be applied at the root of the tree.
203 Internally, runs 'svn diff' on each checked out module and
204 dependencies, and performs minimal postprocessing of the output. The
205 resulting patch is printed to stdout and can be applied to a freshly
206 checked out tree via 'patch -p0 < patchfile'. Additional args and
207 options to 'svn diff' can be passed after gclient options.
208
209 usage: pack [options] [--] [svn args/options]
210
211 Valid options:
212 --verbose : output additional diagnostics
213
214 Examples:
215 gclient pack > patch.txt
216 generate simple patch for configured client and dependences
217 gclient pack -- -x -b > patch.txt
218 generate patch using 'svn diff -x -b' to suppress
219 whitespace-only differences
220 gclient pack -- -r HEAD -x -b > patch.txt
221 generate patch, diffing each file versus the latest version of
222 each module
223 """,
199 "revert": 224 "revert":
200 """Revert every file in every managed directory in the client view. 225 """Revert every file in every managed directory in the client view.
201 226
202 usage: revert 227 usage: revert
203 """, 228 """,
204 "status": 229 "status":
205 """Show the status of client and dependent modules, using 'svn diff' 230 """Show the status of client and dependent modules, using 'svn diff'
206 for each module. Additional options and args may be passed to 'svn diff'. 231 for each module. Additional options and args may be passed to 'svn diff'.
207 232
208 usage: status [options] [--] [svn diff args/options] 233 usage: status [options] [--] [svn diff args/options]
(...skipping 192 matching lines...) Expand 10 before | Expand all | Expand 10 after
401 if e.errno != errno.EACCES or sys.platform != 'win32': 426 if e.errno != errno.EACCES or sys.platform != 'win32':
402 raise 427 raise
403 print 'Failed to remove %s: trying again' % file_path 428 print 'Failed to remove %s: trying again' % file_path
404 time.sleep(0.1) 429 time.sleep(0.1)
405 os.rmdir(file_path) 430 os.rmdir(file_path)
406 431
407 432
408 def SubprocessCall(command, in_directory, fail_status=None): 433 def SubprocessCall(command, in_directory, fail_status=None):
409 """Runs command, a list, in directory in_directory. 434 """Runs command, a list, in directory in_directory.
410 435
411 This function wraps SubprocessCallAndCapture, but does not perform the 436 This function wraps SubprocessCallAndFilter, but does not perform the
412 capturing functions. See that function for a more complete usage 437 filtering functions. See that function for a more complete usage
413 description. 438 description.
414 """ 439 """
415 # Call subprocess and capture nothing: 440 # Call subprocess and capture nothing:
416 SubprocessCallAndCapture(command, in_directory, fail_status) 441 SubprocessCallAndFilter(command, in_directory, True, True, fail_status)
417 442
418 443
419 def SubprocessCallAndCapture(command, in_directory, fail_status=None, 444 def SubprocessCallAndFilter(command,
420 pattern=None, capture_list=None): 445 in_directory,
446 print_messages,
447 print_stdout,
448 fail_status=None, filter=None):
421 """Runs command, a list, in directory in_directory. 449 """Runs command, a list, in directory in_directory.
422 450
423 A message indicating what is being done, as well as the command's stdout, 451 If print_messages is true, a message indicating what is being done
424 is printed to out. 452 is printed to stdout. If print_stdout is true, the command's stdout
453 is also forwarded to stdout.
425 454
426 If a pattern is specified, any line in the output matching pattern will have 455 If a filter function is specified, it is expected to take a single
427 its first match group appended to capture_list. 456 string argument, and it will be called with each line of the
457 subprocess's output. Each line has had the trailing newline character
458 trimmed.
428 459
429 If the command fails, as indicated by a nonzero exit status, gclient will 460 If the command fails, as indicated by a nonzero exit status, gclient will
430 exit with an exit status of fail_status. If fail_status is None (the 461 exit with an exit status of fail_status. If fail_status is None (the
431 default), gclient will raise an Error exception. 462 default), gclient will raise an Error exception.
432 """ 463 """
433 464
434 print("\n________ running \'%s\' in \'%s\'" 465 if print_messages:
435 % (' '.join(command), in_directory)) 466 print("\n________ running \'%s\' in \'%s\'"
467 % (' '.join(command), in_directory))
436 468
437 # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for the 469 # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for the
438 # executable, but shell=True makes subprocess on Linux fail when it's called 470 # executable, but shell=True makes subprocess on Linux fail when it's called
439 # with a list because it only tries to execute the first item in the list. 471 # with a list because it only tries to execute the first item in the list.
440 kid = subprocess.Popen(command, bufsize=0, cwd=in_directory, 472 kid = subprocess.Popen(command, bufsize=0, cwd=in_directory,
441 shell=(sys.platform == 'win32'), stdout=subprocess.PIPE) 473 shell=(sys.platform == 'win32'), stdout=subprocess.PIPE)
442 474
443 if pattern:
444 compiled_pattern = re.compile(pattern)
445
446 # Also, we need to forward stdout to prevent weird re-ordering of output. 475 # Also, we need to forward stdout to prevent weird re-ordering of output.
447 # This has to be done on a per byte basis to make sure it is not buffered: 476 # This has to be done on a per byte basis to make sure it is not buffered:
448 # normally buffering is done for each line, but if svn requests input, no 477 # normally buffering is done for each line, but if svn requests input, no
449 # end-of-line character is output after the prompt and it would not show up. 478 # end-of-line character is output after the prompt and it would not show up.
450 in_byte = kid.stdout.read(1) 479 in_byte = kid.stdout.read(1)
451 in_line = "" 480 in_line = ""
452 while in_byte: 481 while in_byte:
453 if in_byte != "\r": 482 if in_byte != "\r":
454 sys.stdout.write(in_byte) 483 if print_stdout:
455 in_line += in_byte 484 sys.stdout.write(in_byte)
456 if in_byte == "\n" and pattern: 485 if in_byte != "\n":
457 match = compiled_pattern.search(in_line[:-1]) 486 in_line += in_byte
458 if match: 487 if in_byte == "\n" and filter:
459 capture_list.append(match.group(1)) 488 filter(in_line)
sgk 2009/09/04 00:25:11 Good, more flexible than just the pattern.
460 in_line = "" 489 in_line = ""
461 in_byte = kid.stdout.read(1) 490 in_byte = kid.stdout.read(1)
462 rv = kid.wait() 491 rv = kid.wait()
463 492
464 if rv: 493 if rv:
465 msg = "failed to run command: %s" % " ".join(command) 494 msg = "failed to run command: %s" % " ".join(command)
466 495
467 if fail_status != None: 496 if fail_status != None:
468 print >>sys.stderr, msg 497 print >>sys.stderr, msg
469 sys.exit(fail_status) 498 sys.exit(fail_status)
(...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after
559 # args[0] must be a supported command. This will blow up if it's something 588 # args[0] must be a supported command. This will blow up if it's something
560 # else, which is good. Note that the patterns are only effective when 589 # else, which is good. Note that the patterns are only effective when
561 # these commands are used in their ordinary forms, the patterns are invalid 590 # these commands are used in their ordinary forms, the patterns are invalid
562 # for "svn status --show-updates", for example. 591 # for "svn status --show-updates", for example.
563 pattern = { 592 pattern = {
564 'checkout': update_pattern, 593 'checkout': update_pattern,
565 'status': status_pattern, 594 'status': status_pattern,
566 'update': update_pattern, 595 'update': update_pattern,
567 }[args[0]] 596 }[args[0]]
568 597
569 SubprocessCallAndCapture(command, 598 compiled_pattern = re.compile(pattern)
570 in_directory,
571 pattern=pattern,
572 capture_list=file_list)
573 599
600 def CaptureMatchingLines(line):
601 match = compiled_pattern.search(line)
602 if match:
603 file_list.append(match.group(1))
604
605 RunSVNAndFilterOutput(args,
606 in_directory,
607 True,
608 True,
609 CaptureMatchingLines)
610
611 def RunSVNAndFilterOutput(args,
612 in_directory,
613 print_messages,
614 print_stdout,
615 filter):
616 """Runs svn checkout, update, status, or diff, optionally outputting
617 to stdout.
618
619 The first item in args must be either "checkout", "update",
620 "status", or "diff".
621
622 svn's stdout is passed line-by-line to the given filter function. If
623 print_stdout is true, it is also printed to sys.stdout as in RunSVN.
624
625 Args:
626 args: A sequence of command line parameters to be passed to svn.
627 in_directory: The directory where svn is to be run.
628 print_messages: Whether to print status messages to stdout about
629 which Subversion commands are being run.
630 print_stdout: Whether to forward Subversion's output to stdout.
631 filter: A function taking one argument (a string) which will be
632 passed each line (with the ending newline character removed) of
633 Subversion's output for filtering.
634
635 Raises:
636 Error: An error occurred while running the svn command.
637 """
638 command = [SVN_COMMAND]
639 command.extend(args)
640
641 SubprocessCallAndFilter(command,
642 in_directory,
643 print_messages,
644 print_stdout,
645 filter=filter)
574 646
575 def CaptureSVNInfo(relpath, in_directory=None, print_error=True): 647 def CaptureSVNInfo(relpath, in_directory=None, print_error=True):
576 """Returns a dictionary from the svn info output for the given file. 648 """Returns a dictionary from the svn info output for the given file.
577 649
578 Args: 650 Args:
579 relpath: The directory where the working copy resides relative to 651 relpath: The directory where the working copy resides relative to
580 the directory given by in_directory. 652 the directory given by in_directory.
581 in_directory: The directory where svn is to be run. 653 in_directory: The directory where svn is to be run.
582 """ 654 """
583 output = CaptureSVN(["info", "--xml", relpath], in_directory, print_error) 655 output = CaptureSVN(["info", "--xml", relpath], in_directory, print_error)
(...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after
725 if file_list == None: 797 if file_list == None:
726 file_list = [] 798 file_list = []
727 799
728 commands = { 800 commands = {
729 'cleanup': self.cleanup, 801 'cleanup': self.cleanup,
730 'export': self.export, 802 'export': self.export,
731 'update': self.update, 803 'update': self.update,
732 'revert': self.revert, 804 'revert': self.revert,
733 'status': self.status, 805 'status': self.status,
734 'diff': self.diff, 806 'diff': self.diff,
807 'pack': self.pack,
735 'runhooks': self.status, 808 'runhooks': self.status,
736 } 809 }
737 810
738 if not command in commands: 811 if not command in commands:
739 raise Error('Unknown command %s' % command) 812 raise Error('Unknown command %s' % command)
740 813
741 return commands[command](options, args, file_list) 814 return commands[command](options, args, file_list)
742 815
743 def cleanup(self, options, args, file_list): 816 def cleanup(self, options, args, file_list):
744 """Cleanup working copy.""" 817 """Cleanup working copy."""
(...skipping 188 matching lines...) Expand 10 before | Expand all | Expand 10 after
933 command.extend(args) 1006 command.extend(args)
934 if not os.path.isdir(path): 1007 if not os.path.isdir(path):
935 # svn status won't work if the directory doesn't exist. 1008 # svn status won't work if the directory doesn't exist.
936 print("\n________ couldn't run \'%s\' in \'%s\':\nThe directory " 1009 print("\n________ couldn't run \'%s\' in \'%s\':\nThe directory "
937 "does not exist." 1010 "does not exist."
938 % (' '.join(command), path)) 1011 % (' '.join(command), path))
939 # There's no file list to retrieve. 1012 # There's no file list to retrieve.
940 else: 1013 else:
941 RunSVNAndGetFileList(command, path, file_list) 1014 RunSVNAndGetFileList(command, path, file_list)
942 1015
1016 def pack(self, options, args, file_list):
1017 """Generates a patch file which can be applied to the root of the
1018 repository."""
1019 path = os.path.join(self._root_dir, self.relpath)
1020 command = ['diff']
1021 command.extend(args)
1022 # Simple class which tracks which file is being diffed and
1023 # replaces instances of its file name in the original and
1024 # working copy lines of the svn diff output.
1025 class DiffFilterer(object):
1026 index_string = "Index: "
1027 original_prefix = "--- "
1028 working_prefix = "+++ "
1029
1030 def __init__(self, relpath):
1031 # Note that we always use '/' as the path separator to be
1032 # consistent with svn's cygwin-style output on Windows
1033 self._relpath = relpath.replace("\\", "/")
1034 self._current_file = ""
1035 self._replacement_file = ""
1036
1037 def SetCurrentFile(self, file):
1038 self._current_file = file
1039 # Note that we always use '/' as the path separator to be
1040 # consistent with svn's cygwin-style output on Windows
1041 self._replacement_file = self._relpath + '/' + file
1042
1043 def ReplaceAndPrint(self, line):
1044 print(line.replace(self._current_file, self._replacement_file))
1045
1046 def Filter(self, line):
1047 if (line.startswith(self.index_string)):
1048 self.SetCurrentFile(line[len(self.index_string):])
1049 self.ReplaceAndPrint(line)
1050 else:
1051 if (line.startswith(self.original_prefix) or
1052 line.startswith(self.working_prefix)):
1053 self.ReplaceAndPrint(line)
1054 else:
1055 print line
1056
1057 filterer = DiffFilterer(self.relpath)
1058 RunSVNAndFilterOutput(command, path, False, False, filterer.Filter)
943 1059
944 ## GClient implementation. 1060 ## GClient implementation.
945 1061
946 1062
947 class GClient(object): 1063 class GClient(object):
948 """Object that represent a gclient checkout.""" 1064 """Object that represent a gclient checkout."""
949 1065
950 supported_commands = [ 1066 supported_commands = [
951 'cleanup', 'diff', 'export', 'revert', 'status', 'update', 'runhooks' 1067 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update',
1068 'runhooks'
952 ] 1069 ]
953 1070
954 def __init__(self, root_dir, options): 1071 def __init__(self, root_dir, options):
955 self._root_dir = root_dir 1072 self._root_dir = root_dir
956 self._options = options 1073 self._options = options
957 self._config_content = None 1074 self._config_content = None
958 self._config_dict = {} 1075 self._config_dict = {}
959 self._deps_hooks = [] 1076 self._deps_hooks = []
960 1077
961 def SetConfig(self, content): 1078 def SetConfig(self, content):
(...skipping 630 matching lines...) Expand 10 before | Expand all | Expand 10 after
1592 1709
1593 Raises: 1710 Raises:
1594 Error: if the command is unknown. 1711 Error: if the command is unknown.
1595 """ 1712 """
1596 if len(args) == 1 and args[0] in COMMAND_USAGE_TEXT: 1713 if len(args) == 1 and args[0] in COMMAND_USAGE_TEXT:
1597 print(COMMAND_USAGE_TEXT[args[0]]) 1714 print(COMMAND_USAGE_TEXT[args[0]])
1598 else: 1715 else:
1599 raise Error("unknown subcommand '%s'; see 'gclient help'" % args[0]) 1716 raise Error("unknown subcommand '%s'; see 'gclient help'" % args[0])
1600 1717
1601 1718
1719 def DoPack(options, args):
1720 """Handle the pack subcommand.
1721
1722 Raises:
1723 Error: if client isn't configured properly.
1724 """
1725 client = GClient.LoadCurrentConfig(options)
1726 if not client:
1727 raise Error("client not configured; see 'gclient config'")
1728 if options.verbose:
1729 # Print out the .gclient file. This is longer than if we just printed the
1730 # client dict, but more legible, and it might contain helpful comments.
1731 print(client.ConfigContent())
1732 options.verbose = True
1733 return client.RunOnDeps('pack', args)
1734
1735
1602 def DoStatus(options, args): 1736 def DoStatus(options, args):
1603 """Handle the status subcommand. 1737 """Handle the status subcommand.
1604 1738
1605 Raises: 1739 Raises:
1606 Error: if client isn't configured properly. 1740 Error: if client isn't configured properly.
1607 """ 1741 """
1608 client = GClient.LoadCurrentConfig(options) 1742 client = GClient.LoadCurrentConfig(options)
1609 if not client: 1743 if not client:
1610 raise Error("client not configured; see 'gclient config'") 1744 raise Error("client not configured; see 'gclient config'")
1611 if options.verbose: 1745 if options.verbose:
(...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after
1711 raise Error("client not configured; see 'gclient config'") 1845 raise Error("client not configured; see 'gclient config'")
1712 client.PrintRevInfo() 1846 client.PrintRevInfo()
1713 1847
1714 1848
1715 gclient_command_map = { 1849 gclient_command_map = {
1716 "cleanup": DoCleanup, 1850 "cleanup": DoCleanup,
1717 "config": DoConfig, 1851 "config": DoConfig,
1718 "diff": DoDiff, 1852 "diff": DoDiff,
1719 "export": DoExport, 1853 "export": DoExport,
1720 "help": DoHelp, 1854 "help": DoHelp,
1855 "pack": DoPack,
1721 "status": DoStatus, 1856 "status": DoStatus,
1722 "sync": DoUpdate, 1857 "sync": DoUpdate,
1723 "update": DoUpdate, 1858 "update": DoUpdate,
1724 "revert": DoRevert, 1859 "revert": DoRevert,
1725 "runhooks": DoRunHooks, 1860 "runhooks": DoRunHooks,
1726 "revinfo" : DoRevInfo, 1861 "revinfo" : DoRevInfo,
1727 } 1862 }
1728 1863
1729 1864
1730 def DispatchCommand(command, options, args, command_map=None): 1865 def DispatchCommand(command, options, args, command_map=None):
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after
1809 1944
1810 if "__main__" == __name__: 1945 if "__main__" == __name__:
1811 try: 1946 try:
1812 result = Main(sys.argv) 1947 result = Main(sys.argv)
1813 except Error, e: 1948 except Error, e:
1814 print >> sys.stderr, "Error: %s" % str(e) 1949 print >> sys.stderr, "Error: %s" % str(e)
1815 result = 1 1950 result = 1
1816 sys.exit(result) 1951 sys.exit(result)
1817 1952
1818 # vim: ts=2:sw=2:tw=80:et: 1953 # vim: ts=2:sw=2:tw=80:et:
OLDNEW
« no previous file with comments | « no previous file | presubmit_canned_checks.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698