Index: gclient.py |
=================================================================== |
--- gclient.py (revision 25297) |
+++ gclient.py (working copy) |
@@ -95,6 +95,7 @@ |
config |
diff |
export |
+ pack |
revert |
status |
sync |
@@ -196,6 +197,30 @@ |
"export": |
"""Wrapper for svn export for all managed directories |
""", |
+ "pack": |
+ |
sgk
2009/09/04 00:25:11
No extra newline here (consistency with other entr
|
+ """Generate a patch which can be applied at the root of the tree. |
+Internally, runs 'svn diff' on each checked out module and |
+dependencies, and performs minimal postprocessing of the output. The |
+resulting patch is printed to stdout and can be applied to a freshly |
+checked out tree via 'patch -p0 < patchfile'. Additional args and |
+options to 'svn diff' can be passed after gclient options. |
+ |
+usage: pack [options] [--] [svn args/options] |
+ |
+Valid options: |
+ --verbose : output additional diagnostics |
+ |
+Examples: |
+ gclient pack > patch.txt |
+ generate simple patch for configured client and dependences |
+ gclient pack -- -x -b > patch.txt |
+ generate patch using 'svn diff -x -b' to suppress |
+ whitespace-only differences |
+ gclient pack -- -r HEAD -x -b > patch.txt |
+ generate patch, diffing each file versus the latest version of |
+ each module |
+""", |
"revert": |
"""Revert every file in every managed directory in the client view. |
@@ -408,31 +433,38 @@ |
def SubprocessCall(command, in_directory, fail_status=None): |
"""Runs command, a list, in directory in_directory. |
- This function wraps SubprocessCallAndCapture, but does not perform the |
- capturing functions. See that function for a more complete usage |
+ This function wraps SubprocessCallAndFilter, but does not perform the |
+ filtering functions. See that function for a more complete usage |
description. |
""" |
# Call subprocess and capture nothing: |
- SubprocessCallAndCapture(command, in_directory, fail_status) |
+ SubprocessCallAndFilter(command, in_directory, True, True, fail_status) |
-def SubprocessCallAndCapture(command, in_directory, fail_status=None, |
- pattern=None, capture_list=None): |
+def SubprocessCallAndFilter(command, |
+ in_directory, |
+ print_messages, |
+ print_stdout, |
+ fail_status=None, filter=None): |
"""Runs command, a list, in directory in_directory. |
- A message indicating what is being done, as well as the command's stdout, |
- is printed to out. |
+ If print_messages is true, a message indicating what is being done |
+ is printed to stdout. If print_stdout is true, the command's stdout |
+ is also forwarded to stdout. |
- If a pattern is specified, any line in the output matching pattern will have |
- its first match group appended to capture_list. |
+ If a filter function is specified, it is expected to take a single |
+ string argument, and it will be called with each line of the |
+ subprocess's output. Each line has had the trailing newline character |
+ trimmed. |
If the command fails, as indicated by a nonzero exit status, gclient will |
exit with an exit status of fail_status. If fail_status is None (the |
default), gclient will raise an Error exception. |
""" |
- print("\n________ running \'%s\' in \'%s\'" |
- % (' '.join(command), in_directory)) |
+ if print_messages: |
+ print("\n________ running \'%s\' in \'%s\'" |
+ % (' '.join(command), in_directory)) |
# *Sigh*: Windows needs shell=True, or else it won't search %PATH% for the |
# executable, but shell=True makes subprocess on Linux fail when it's called |
@@ -440,9 +472,6 @@ |
kid = subprocess.Popen(command, bufsize=0, cwd=in_directory, |
shell=(sys.platform == 'win32'), stdout=subprocess.PIPE) |
- if pattern: |
- compiled_pattern = re.compile(pattern) |
- |
# Also, we need to forward stdout to prevent weird re-ordering of output. |
# This has to be done on a per byte basis to make sure it is not buffered: |
# normally buffering is done for each line, but if svn requests input, no |
@@ -451,12 +480,12 @@ |
in_line = "" |
while in_byte: |
if in_byte != "\r": |
- sys.stdout.write(in_byte) |
- in_line += in_byte |
- if in_byte == "\n" and pattern: |
- match = compiled_pattern.search(in_line[:-1]) |
- if match: |
- capture_list.append(match.group(1)) |
+ if print_stdout: |
+ sys.stdout.write(in_byte) |
+ if in_byte != "\n": |
+ in_line += in_byte |
+ if in_byte == "\n" and filter: |
+ filter(in_line) |
sgk
2009/09/04 00:25:11
Good, more flexible than just the pattern.
|
in_line = "" |
in_byte = kid.stdout.read(1) |
rv = kid.wait() |
@@ -566,12 +595,55 @@ |
'update': update_pattern, |
}[args[0]] |
- SubprocessCallAndCapture(command, |
- in_directory, |
- pattern=pattern, |
- capture_list=file_list) |
+ compiled_pattern = re.compile(pattern) |
+ def CaptureMatchingLines(line): |
+ match = compiled_pattern.search(line) |
+ if match: |
+ file_list.append(match.group(1)) |
+ RunSVNAndFilterOutput(args, |
+ in_directory, |
+ True, |
+ True, |
+ CaptureMatchingLines) |
+ |
+def RunSVNAndFilterOutput(args, |
+ in_directory, |
+ print_messages, |
+ print_stdout, |
+ filter): |
+ """Runs svn checkout, update, status, or diff, optionally outputting |
+ to stdout. |
+ |
+ The first item in args must be either "checkout", "update", |
+ "status", or "diff". |
+ |
+ svn's stdout is passed line-by-line to the given filter function. If |
+ print_stdout is true, it is also printed to sys.stdout as in RunSVN. |
+ |
+ Args: |
+ args: A sequence of command line parameters to be passed to svn. |
+ in_directory: The directory where svn is to be run. |
+ print_messages: Whether to print status messages to stdout about |
+ which Subversion commands are being run. |
+ print_stdout: Whether to forward Subversion's output to stdout. |
+ filter: A function taking one argument (a string) which will be |
+ passed each line (with the ending newline character removed) of |
+ Subversion's output for filtering. |
+ |
+ Raises: |
+ Error: An error occurred while running the svn command. |
+ """ |
+ command = [SVN_COMMAND] |
+ command.extend(args) |
+ |
+ SubprocessCallAndFilter(command, |
+ in_directory, |
+ print_messages, |
+ print_stdout, |
+ filter=filter) |
+ |
def CaptureSVNInfo(relpath, in_directory=None, print_error=True): |
"""Returns a dictionary from the svn info output for the given file. |
@@ -732,6 +804,7 @@ |
'revert': self.revert, |
'status': self.status, |
'diff': self.diff, |
+ 'pack': self.pack, |
'runhooks': self.status, |
} |
@@ -940,7 +1013,50 @@ |
else: |
RunSVNAndGetFileList(command, path, file_list) |
+ def pack(self, options, args, file_list): |
+ """Generates a patch file which can be applied to the root of the |
+ repository.""" |
+ path = os.path.join(self._root_dir, self.relpath) |
+ command = ['diff'] |
+ command.extend(args) |
+ # Simple class which tracks which file is being diffed and |
+ # replaces instances of its file name in the original and |
+ # working copy lines of the svn diff output. |
+ class DiffFilterer(object): |
+ index_string = "Index: " |
+ original_prefix = "--- " |
+ working_prefix = "+++ " |
+ def __init__(self, relpath): |
+ # Note that we always use '/' as the path separator to be |
+ # consistent with svn's cygwin-style output on Windows |
+ self._relpath = relpath.replace("\\", "/") |
+ self._current_file = "" |
+ self._replacement_file = "" |
+ |
+ def SetCurrentFile(self, file): |
+ self._current_file = file |
+ # Note that we always use '/' as the path separator to be |
+ # consistent with svn's cygwin-style output on Windows |
+ self._replacement_file = self._relpath + '/' + file |
+ |
+ def ReplaceAndPrint(self, line): |
+ print(line.replace(self._current_file, self._replacement_file)) |
+ |
+ def Filter(self, line): |
+ if (line.startswith(self.index_string)): |
+ self.SetCurrentFile(line[len(self.index_string):]) |
+ self.ReplaceAndPrint(line) |
+ else: |
+ if (line.startswith(self.original_prefix) or |
+ line.startswith(self.working_prefix)): |
+ self.ReplaceAndPrint(line) |
+ else: |
+ print line |
+ |
+ filterer = DiffFilterer(self.relpath) |
+ RunSVNAndFilterOutput(command, path, False, False, filterer.Filter) |
+ |
## GClient implementation. |
@@ -948,7 +1064,8 @@ |
"""Object that represent a gclient checkout.""" |
supported_commands = [ |
- 'cleanup', 'diff', 'export', 'revert', 'status', 'update', 'runhooks' |
+ 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update', |
+ 'runhooks' |
] |
def __init__(self, root_dir, options): |
@@ -1599,6 +1716,23 @@ |
raise Error("unknown subcommand '%s'; see 'gclient help'" % args[0]) |
+def DoPack(options, args): |
+ """Handle the pack subcommand. |
+ |
+ Raises: |
+ Error: if client isn't configured properly. |
+ """ |
+ client = GClient.LoadCurrentConfig(options) |
+ if not client: |
+ raise Error("client not configured; see 'gclient config'") |
+ if options.verbose: |
+ # Print out the .gclient file. This is longer than if we just printed the |
+ # client dict, but more legible, and it might contain helpful comments. |
+ print(client.ConfigContent()) |
+ options.verbose = True |
+ return client.RunOnDeps('pack', args) |
+ |
+ |
def DoStatus(options, args): |
"""Handle the status subcommand. |
@@ -1718,6 +1852,7 @@ |
"diff": DoDiff, |
"export": DoExport, |
"help": DoHelp, |
+ "pack": DoPack, |
"status": DoStatus, |
"sync": DoUpdate, |
"update": DoUpdate, |