~ubuntu-branches/ubuntu/natty/bzr-builddeb/natty-proposed

« back to all changes in this revision

Viewing changes to merge_changelog.py

  • Committer: Bazaar Package Importer
  • Author(s): Jelmer Vernooij, James Westby, Jelmer Vernooij
  • Date: 2010-02-13 00:44:03 UTC
  • mfrom: (6.1.1 experimental)
  • Revision ID: james.westby@ubuntu.com-20100213004403-9ug5ihttyyomebf6
Tags: 2.3
[ James Westby ]
* Some support for v3 source formats (Closes: #562991)
  - Those that look quite a lot like v1 are supported well.
  - .tar.bz2 tarballs are supported for import, building, merge-upstream,
    etc., but only enabled for the latter with a --v3 switch for now.
  - Multiple orig.tar.gz is not supported.
  - .tar.lzma is not supported awaiting pristine-tar support.
* New "dh-make" command ("dh_make" alias) that allows you to start
  packaging, either in an empty branch, or based on an upstream branch.
* Fix merge-package for native packages (LP: #476348)
* debian/changelog merge hook to reduce the manual conflict resolution
  required there. Thanks to John Arbash Meinel and Andrew Bennetts
  (LP: #501754).
  - Requires newer bzr.
* Fix merge-package outside a shared repo (LP: #493462)
* Fix exporting of symlinks (LP: #364671)
* Add --force option to merge-upstream which may help certain people.
* Use system configobj if the bzr copy isn't available. Thanks Jelmer.
* Make merging multiple-root branches work. Thanks Robert Collins.
* Disentangle from bzrtools. Thanks Max Bowser.

[ Jelmer Vernooij ]
* Bump standards version to 3.8.4.
* Fix formatting in doc-base.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# -*- coding: utf-8 -*-
 
3
# Copyright ? 2008 Canonical Ltd.
 
4
# Author: Scott James Remnant <scott@ubuntu.com>.
 
5
# Hacked up by: Bryce Harrington <bryce@ubuntu.com>
 
6
#
 
7
# This program is free software: you can redistribute it and/or modify
 
8
# it under the terms of version 3 of the GNU General Public License as
 
9
# published by the Free Software Foundation.
 
10
#
 
11
# This program is distributed in the hope that it will be useful,
 
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
14
# GNU General Public License for more details.
 
15
#
 
16
# You should have received a copy of the GNU General Public License
 
17
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
18
 
 
19
import re
 
20
 
 
21
from bzrlib import (
 
22
    merge,
 
23
    )
 
24
 
 
25
class ChangeLogFileMerge(merge.ConfigurableFileMerger):
 
26
 
 
27
    name_prefix = 'deb_changelog'
 
28
    default_files = ['debian/changelog']
 
29
 
 
30
    def merge_text(self, params):
 
31
        return 'success', merge_changelog(params.this_lines, params.other_lines)
 
32
 
 
33
 
 
34
########################################################################
 
35
# Changelog Management
 
36
########################################################################
 
37
 
 
38
# Regular expression for top of debian/changelog
 
39
CL_RE = re.compile(r'^(\w[-+0-9a-z.]*) \(([^\(\) \t]+)\)((\s+[-0-9a-z]+)+)\;',
 
40
                   re.IGNORECASE)
 
41
 
 
42
def merge_changelog(left_changelog_lines, right_changelog_lines):
 
43
    """Merge a changelog file."""
 
44
 
 
45
    left_cl = read_changelog(left_changelog_lines)
 
46
    right_cl = read_changelog(right_changelog_lines)
 
47
 
 
48
    content = []
 
49
    # TODO: This is not a 3-way merge, but a 2-way merge
 
50
    #       The resolution is currently 'if left and right have texts that have
 
51
    #       the same "version" string, use left', aka "prefer-mine".
 
52
    #       We could introduce BASE, and cause conflicts, or appropriately
 
53
    #       resolve, etc.
 
54
    #       Note also that this code is only invoked when there is a
 
55
    #       left-and-right change, so merging a pure-right change will take all
 
56
    #       changes.
 
57
    for right_ver, right_text in right_cl:
 
58
        while len(left_cl) and left_cl[0][0] > right_ver:
 
59
            (left_ver, left_text) = left_cl.pop(0)
 
60
            content.append(left_text)
 
61
            content.append('\n')
 
62
 
 
63
        while len(left_cl) and left_cl[0][0] == right_ver:
 
64
            (left_ver, left_text) = left_cl.pop(0)
 
65
 
 
66
        content.append(right_text)
 
67
        content.append('\n')
 
68
 
 
69
    for left_ver, left_text in left_cl:
 
70
        content.append(left_text)
 
71
        content.append('\n')
 
72
            
 
73
    return content
 
74
 
 
75
 
 
76
def read_changelog(lines):
 
77
    """Return a parsed changelog file."""
 
78
    entries = []
 
79
 
 
80
    (ver, text) = (None, "")
 
81
    for line in lines:
 
82
        match = CL_RE.search(line)
 
83
        if match:
 
84
            try:
 
85
                ver = Version(match.group(2))
 
86
            except ValueError:
 
87
                ver = None
 
88
 
 
89
            text += line
 
90
        elif line.startswith(" -- "):
 
91
            if ver is None:
 
92
                ver = Version("0")
 
93
 
 
94
            text += line
 
95
            entries.append((ver, text))
 
96
            (ver, text) = (None, "")
 
97
        elif len(line.strip()) or ver is not None:
 
98
            text += line
 
99
 
 
100
    if len(text):
 
101
        entries.append((ver, text))
 
102
 
 
103
    return entries
 
104
 
 
105
########################################################################
 
106
# Version parsing code
 
107
########################################################################
 
108
# Regular expressions make validating things easy
 
109
valid_epoch = re.compile(r'^[0-9]+$')
 
110
valid_upstream = re.compile(r'^[A-Za-z0-9+:.~-]*$')
 
111
valid_revision = re.compile(r'^[A-Za-z0-9+.~]+$')
 
112
 
 
113
# Character comparison table for upstream and revision components
 
114
cmp_table = "~ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+-.:"
 
115
 
 
116
 
 
117
class Version(object):
 
118
    """Debian version number.
 
119
 
 
120
    This class is designed to be reasonably transparent and allow you
 
121
    to write code like:
 
122
 
 
123
    |   s.version >= '1.100-1'
 
124
 
 
125
    The comparison will be done according to Debian rules, so '1.2' will
 
126
    compare lower.
 
127
 
 
128
    Properties:
 
129
      epoch       Epoch
 
130
      upstream    Upstream version
 
131
      revision    Debian/local revision
 
132
    """
 
133
 
 
134
    def __init__(self, ver):
 
135
        """Parse a string or number into the three components."""
 
136
        self.epoch = 0
 
137
        self.upstream = None
 
138
        self.revision = None
 
139
 
 
140
        ver = str(ver)
 
141
        if not len(ver):
 
142
            raise ValueError
 
143
 
 
144
        # Epoch is component before first colon
 
145
        idx = ver.find(":")
 
146
        if idx != -1:
 
147
            self.epoch = ver[:idx]
 
148
            if not len(self.epoch):
 
149
                raise ValueError
 
150
            if not valid_epoch.search(self.epoch):
 
151
                raise ValueError
 
152
            ver = ver[idx+1:]
 
153
 
 
154
        # Revision is component after last hyphen
 
155
        idx = ver.rfind("-")
 
156
        if idx != -1:
 
157
            self.revision = ver[idx+1:]
 
158
            if not len(self.revision):
 
159
                raise ValueError
 
160
            if not valid_revision.search(self.revision):
 
161
                raise ValueError
 
162
            ver = ver[:idx]
 
163
 
 
164
        # Remaining component is upstream
 
165
        self.upstream = ver
 
166
        if not len(self.upstream):
 
167
            raise ValueError
 
168
        if not valid_upstream.search(self.upstream):
 
169
            raise ValueError
 
170
 
 
171
        self.epoch = int(self.epoch)
 
172
 
 
173
    def getWithoutEpoch(self):
 
174
        """Return the version without the epoch."""
 
175
        str = self.upstream
 
176
        if self.revision is not None:
 
177
            str += "-%s" % (self.revision,)
 
178
        return str
 
179
 
 
180
    without_epoch = property(getWithoutEpoch)
 
181
 
 
182
    def __str__(self):
 
183
        """Return the class as a string for printing."""
 
184
        str = ""
 
185
        if self.epoch > 0:
 
186
            str += "%d:" % (self.epoch,)
 
187
        str += self.upstream
 
188
        if self.revision is not None:
 
189
            str += "-%s" % (self.revision,)
 
190
        return str
 
191
 
 
192
    def __repr__(self):
 
193
        """Return a debugging representation of the object."""
 
194
        return "<%s epoch: %d, upstream: %r, revision: %r>" \
 
195
               % (self.__class__.__name__, self.epoch,
 
196
                  self.upstream, self.revision)
 
197
 
 
198
    def __cmp__(self, other):
 
199
        """Compare two Version classes."""
 
200
        other = Version(other)
 
201
 
 
202
        result = cmp(self.epoch, other.epoch)
 
203
        if result != 0: return result
 
204
 
 
205
        result = deb_cmp(self.upstream, other.upstream)
 
206
        if result != 0: return result
 
207
 
 
208
        result = deb_cmp(self.revision or "", other.revision or "")
 
209
        if result != 0: return result
 
210
 
 
211
        return 0
 
212
 
 
213
 
 
214
def strcut(str, idx, accept):
 
215
    """Cut characters from str that are entirely in accept."""
 
216
    ret = ""
 
217
    while idx < len(str) and str[idx] in accept:
 
218
        ret += str[idx]
 
219
        idx += 1
 
220
 
 
221
    return (ret, idx)
 
222
 
 
223
def deb_order(str, idx):
 
224
    """Return the comparison order of two characters."""
 
225
    if idx >= len(str):
 
226
        return 0
 
227
    elif str[idx] == "~":
 
228
        return -1
 
229
    else:
 
230
        return cmp_table.index(str[idx])
 
231
 
 
232
def deb_cmp_str(x, y):
 
233
    """Compare two strings in a deb version."""
 
234
    idx = 0
 
235
    while (idx < len(x)) or (idx < len(y)):
 
236
        result = deb_order(x, idx) - deb_order(y, idx)
 
237
        if result < 0:
 
238
            return -1
 
239
        elif result > 0:
 
240
            return 1
 
241
 
 
242
        idx += 1
 
243
 
 
244
    return 0
 
245
 
 
246
def deb_cmp(x, y):
 
247
    """Implement the string comparison outlined by Debian policy."""
 
248
    x_idx = y_idx = 0
 
249
    while x_idx < len(x) or y_idx < len(y):
 
250
        # Compare strings
 
251
        (x_str, x_idx) = strcut(x, x_idx, cmp_table)
 
252
        (y_str, y_idx) = strcut(y, y_idx, cmp_table)
 
253
        result = deb_cmp_str(x_str, y_str)
 
254
        if result != 0: return result
 
255
 
 
256
        # Compare numbers
 
257
        (x_str, x_idx) = strcut(x, x_idx, "0123456789")
 
258
        (y_str, y_idx) = strcut(y, y_idx, "0123456789")
 
259
        result = cmp(int(x_str or "0"), int(y_str or "0"))
 
260
        if result != 0: return result
 
261
 
 
262
    return 0