/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/patch.py

[merge] robertc's integration, updated tests to check for retcode=3

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
2
 
# Copyright (C) 2005, 2008 Aaron Bentley, 2006 Michael Ellerman
3
 
#
4
 
# This program is free software; you can redistribute it and/or modify
5
 
# it under the terms of the GNU General Public License as published by
6
 
# the Free Software Foundation; either version 2 of the License, or
7
 
# (at your option) any later version.
8
 
#
9
 
# This program is distributed in the hope that it will be useful,
10
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 
# GNU General Public License for more details.
13
 
#
14
 
# You should have received a copy of the GNU General Public License
15
 
# along with this program; if not, write to the Free Software
16
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
 
 
18
 
from __future__ import absolute_import
19
 
 
20
 
"""Diff and patch functionality"""
21
 
 
22
 
import errno
23
1
import os
24
2
from subprocess import Popen, PIPE
25
 
import sys
26
 
import tempfile
27
 
 
28
 
from .errors import NoDiff3, BzrError
29
 
from .textfile import check_text_path
30
 
 
31
 
class PatchFailed(BzrError):
32
 
 
33
 
    _fmt = """Patch application failed"""
34
 
 
35
 
 
36
 
class PatchInvokeError(BzrError):
37
 
 
38
 
    _fmt = """Error invoking patch: %(errstr)s%(stderr)s"""
39
 
    internal_error = False
40
 
 
41
 
    def __init__(self, e, stderr=''):
42
 
        self.exception = e
43
 
        self.errstr = os.strerror(e.errno)
44
 
        self.stderr = '\n' + stderr
45
 
 
46
 
 
47
 
_do_close_fds = True
48
 
if os.name == 'nt':
49
 
    _do_close_fds = False
50
 
 
 
3
"""
 
4
Diff and patch functionality
 
5
"""
 
6
__docformat__ = "restructuredtext"
51
7
 
52
8
def write_to_cmd(args, input=""):
53
 
    """Spawn a process, and wait for the result
54
 
 
55
 
    If the process is killed, an exception is raised
56
 
 
57
 
    :param args: The command line, the first entry should be the program name
58
 
    :param input: [optional] The text to send the process on stdin
59
 
    :return: (stdout, stderr, status)
60
 
    """
61
 
    process = Popen(args, bufsize=len(input), stdin=PIPE, stdout=PIPE,
62
 
                    stderr=PIPE, close_fds=_do_close_fds)
 
9
    if os.name != 'nt':
 
10
        process = Popen(args, bufsize=len(input), stdin=PIPE, stdout=PIPE,
 
11
                        stderr=PIPE, close_fds=True)
 
12
    else:
 
13
        process = Popen(args, bufsize=len(input), stdin=PIPE, stdout=PIPE,
 
14
                        stderr=PIPE)
 
15
 
63
16
    stdout, stderr = process.communicate(input)
64
17
    status = process.wait()
65
18
    if status < 0:
66
 
        raise Exception("%s killed by signal %i" % (args[0], -status))
 
19
        raise Exception("%s killed by signal %i" (args[0], -status))
67
20
    return stdout, stderr, status
68
 
 
 
21
    
69
22
 
70
23
def patch(patch_contents, filename, output_filename=None, reverse=False):
71
24
    """Apply a patch to a file, to produce another output file.  This is should
89
42
        args.extend(("-o", output_filename))
90
43
    args.append(filename)
91
44
    stdout, stderr, status = write_to_cmd(args, patch_contents)
92
 
    return status
 
45
    return status 
93
46
 
94
47
 
95
48
def diff3(out_file, mine_path, older_path, yours_path):
96
49
    def add_label(args, label):
97
50
        args.extend(("-L", label))
98
 
    check_text_path(mine_path)
99
 
    check_text_path(older_path)
100
 
    check_text_path(yours_path)
101
51
    args = ['diff3', "-E", "--merge"]
102
52
    add_label(args, "TREE")
103
53
    add_label(args, "ANCESTOR")
104
54
    add_label(args, "MERGE-SOURCE")
105
55
    args.extend((mine_path, older_path, yours_path))
106
 
    try:
107
 
        output, stderr, status = write_to_cmd(args)
108
 
    except OSError as e:
109
 
        if e.errno == errno.ENOENT:
110
 
            raise NoDiff3
111
 
        else:
112
 
            raise
 
56
    output, stderr, status = write_to_cmd(args)
113
57
    if status not in (0, 1):
114
58
        raise Exception(stderr)
115
 
    with open(out_file, 'wb') as f:
116
 
        f.write(output)
 
59
    file(out_file, "wb").write(output)
117
60
    return status
118
 
 
119
 
 
120
 
def patch_tree(tree, patches, strip=0, reverse=False, dry_run=False,
121
 
               quiet=False, out=None):
122
 
    """Apply a patch to a tree.
123
 
 
124
 
    Args:
125
 
      tree: A MutableTree object
126
 
      patches: list of patches as bytes
127
 
      strip: Strip X segments of paths
128
 
      reverse: Apply reversal of patch
129
 
      dry_run: Dry run
130
 
    """
131
 
    return run_patch(tree.basedir, patches, strip, reverse, dry_run,
132
 
                     quiet, out=out)
133
 
 
134
 
 
135
 
def run_patch(directory, patches, strip=0, reverse=False, dry_run=False,
136
 
              quiet=False, _patch_cmd='patch', target_file=None, out=None):
137
 
    args = [_patch_cmd, '-d', directory, '-s', '-p%d' % strip, '-f']
138
 
    if quiet:
139
 
        args.append('--quiet')
140
 
 
141
 
    if sys.platform == "win32":
142
 
        args.append('--binary')
143
 
 
144
 
    if reverse:
145
 
        args.append('-R')
146
 
    if dry_run:
147
 
        if sys.platform.startswith('freebsd'):
148
 
            args.append('--check')
149
 
        else:
150
 
            args.append('--dry-run')
151
 
        stderr = PIPE
152
 
    else:
153
 
        stderr = None
154
 
    if target_file is not None:
155
 
        args.append(target_file)
156
 
 
157
 
    try:
158
 
        process = Popen(args, stdin=PIPE, stdout=PIPE, stderr=stderr)
159
 
    except OSError as e:
160
 
        raise PatchInvokeError(e)
161
 
    try:
162
 
        for patch in patches:
163
 
            process.stdin.write(bytes(patch))
164
 
        process.stdin.close()
165
 
 
166
 
    except IOError as e:
167
 
        raise PatchInvokeError(e, process.stderr.read())
168
 
 
169
 
    result = process.wait()
170
 
    if not dry_run:
171
 
        if out is not None:
172
 
            out.write(process.stdout.read())
173
 
        else:
174
 
            process.stdout.read()
175
 
    if result != 0:
176
 
        raise PatchFailed()
177
 
 
178
 
    return result
179
 
 
180
 
 
181
 
def iter_patched_from_hunks(orig_lines, hunks):
182
 
    """Iterate through a series of lines with a patch applied.
183
 
    This handles a single file, and does exact, not fuzzy patching.
184
 
 
185
 
    :param orig_lines: The unpatched lines.
186
 
    :param hunks: An iterable of Hunk instances.
187
 
 
188
 
    This is different from breezy.patches in that it invokes the patch
189
 
    command.
190
 
    """
191
 
    with tempfile.NamedTemporaryFile() as f:
192
 
        f.writelines(orig_lines)
193
 
        f.flush()
194
 
        # TODO(jelmer): Stream patch contents to command, rather than
195
 
        # serializing the entire patch upfront.
196
 
        serialized = b''.join([hunk.as_bytes() for hunk in hunks])
197
 
        args = ["patch", "-f", "-s", "--posix", "--binary",
198
 
                "-o", "-", f.name, "-r", "-"]
199
 
        stdout, stderr, status = write_to_cmd(args, serialized)
200
 
    if status == 0:
201
 
        return [stdout]
202
 
    raise PatchFailed(stderr)