/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

  • Committer: Robert Collins
  • Date: 2005-12-24 02:20:45 UTC
  • mto: (1185.50.57 bzr-jam-integration)
  • mto: This revision was merged to the branch mainline in revision 1550.
  • Revision ID: robertc@robertcollins.net-20051224022045-14efc8dfa0e1a4e9
Start tests for api usage.

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