/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 breezy/patch.py

  • Committer: Jelmer Vernooij
  • Date: 2020-02-07 02:14:30 UTC
  • mto: This revision was merged to the branch mainline in revision 7492.
  • Revision ID: jelmer@jelmer.uk-20200207021430-m49iq3x4x8xlib6x
Drop python2 support.

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
import os
 
24
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
 
 
51
 
 
52
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)
 
63
    stdout, stderr = process.communicate(input)
 
64
    status = process.wait()
 
65
    if status < 0:
 
66
        raise Exception("%s killed by signal %i" % (args[0], -status))
 
67
    return stdout, stderr, status
 
68
 
 
69
 
 
70
def patch(patch_contents, filename, output_filename=None, reverse=False):
 
71
    """Apply a patch to a file, to produce another output file.  This is should
 
72
    be suitable for our limited purposes.
 
73
 
 
74
    :param patch_contents: The contents of the patch to apply
 
75
    :type patch_contents: str
 
76
    :param filename: the name of the file to apply the patch to
 
77
    :type filename: str
 
78
    :param output_filename: The filename to produce.  If None, file is \
 
79
    modified in-place
 
80
    :type output_filename: str or NoneType
 
81
    :param reverse: If true, apply the patch in reverse
 
82
    :type reverse: bool
 
83
    :return: 0 on success, 1 if some hunks failed
 
84
    """
 
85
    args = ["patch", "-f", "-s", "--posix", "--binary"]
 
86
    if reverse:
 
87
        args.append("--reverse")
 
88
    if output_filename is not None:
 
89
        args.extend(("-o", output_filename))
 
90
    args.append(filename)
 
91
    stdout, stderr, status = write_to_cmd(args, patch_contents)
 
92
    return status
 
93
 
 
94
 
 
95
def diff3(out_file, mine_path, older_path, yours_path):
 
96
    def add_label(args, label):
 
97
        args.extend(("-L", label))
 
98
    check_text_path(mine_path)
 
99
    check_text_path(older_path)
 
100
    check_text_path(yours_path)
 
101
    args = ['diff3', "-E", "--merge"]
 
102
    add_label(args, "TREE")
 
103
    add_label(args, "ANCESTOR")
 
104
    add_label(args, "MERGE-SOURCE")
 
105
    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
 
113
    if status not in (0, 1):
 
114
        raise Exception(stderr)
 
115
    with open(out_file, 'wb') as f:
 
116
        f.write(output)
 
117
    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)