1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
|
# dep3.py -- DEP-3 compatible patch formatting
# Copyright (C) 2011 Canonical Ltd.
#
# This file is part of bzr-builddeb.
#
# bzr-builddeb is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# bzr-builddeb is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with bzr-builddeb; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
"""DEP-3 style patch formatting."""
from email.message import Message
from ... import (
diff,
errors,
)
from breezy.foreign import foreign_vcs_registry
from io import BytesIO
import time
def write_dep3_bug_line(message, bug_url, status):
"""Write a DEP-3 compatible line with a bug link.
:param message: Message object to udpate
:param bug_url: Bug URL
:param status: Bug status (e.g. "fixed")
"""
# For the moment, we only care about fixed bugs
if status != "fixed":
return
if bug_url.startswith("http://bugs.debian.org/"):
message.add_header("Bug-Debian", bug_url)
else:
# FIXME: Filter out Ubuntu bugs on Launchpad
message.add_header("Bug", bug_url)
def write_dep3_patch_header(
f,
description=None,
origin=None,
forwarded=None,
bugs=None,
authors=None,
revision_id=None,
last_update=None,
applied_upstream=None,
):
"""Write a DEP3 patch header.
:param f: File-like object to write to
:param description: Description of the patch
:param origin: Single line describing the origin of the patch
:param forwarded: Single line describing whether and how the patch was
forwarded
:param bugs: Set of bugs fixed in this patch
:param authors: Authors of the patch
:param revision_id: Relevant bzr revision id
:param last_update: Last update timestamp
:param applied_upstream: If the patch is applied upstream,
an informal string describing where it was merged
"""
header = Message()
if description is not None:
description = description.strip("\n")
description = description.replace("\n\n", "\n.\n")
description = description.replace("\n", "\n ")
header["Description"] = description
if origin is not None:
header.add_header("Origin", origin)
if forwarded is not None:
header.add_header("Forwarded", forwarded)
if authors is not None:
for author in authors:
header.add_header("Author", author)
if bugs is not None:
for bug_url, status in bugs:
write_dep3_bug_line(header, bug_url, status)
if last_update is not None:
header.add_header(
"Last-Update", time.strftime("%Y-%m-%d", time.gmtime(last_update))
)
if applied_upstream is not None:
header.add_header("Applied-Upstream", applied_upstream)
if revision_id is not None:
try:
(foreign_revid, mapping) = foreign_vcs_registry.parse_revision_id(
revision_id
)
except errors.InvalidRevisionId:
header.add_header("X-Bzr-Revision-Id", revision_id.decode("utf-8"))
else:
if mapping.vcs.abbreviation == "git":
header.add_header("X-Git-Commit", foreign_revid.decode("utf-8"))
f.write(str(header))
def gather_bugs_and_authors(repository, interesting_revision_ids):
"""Gather bug and author information from revisions.
:param interesting_revision_ids: Iterable of revision ids to check
:return: Tuple of bugs, authors and highest found commit timestamp
"""
authors = set()
bugs = set()
last_update = -0.0
for rev in repository.get_revisions(interesting_revision_ids):
last_update = max(rev.timestamp, last_update)
authors.update(rev.get_apparent_authors())
bugs.update(rev.iter_bugs())
if last_update == -0.0:
last_update = None
return (bugs, authors, last_update)
def determine_applied_upstream(upstream_branch, feature_branch, feature_revid=None):
"""Check if a particular revision has been merged upstream.
:param upstream_branch: Upstream branch object
:param feature_branch: Feature branch
:param feature_revid: Revision id in feature branch to check,
defaults to feature_branch tip.
:return: String that can be used for Applied-Upstream field
"""
if feature_revid is None:
feature_revid = feature_branch.last_revision()
upstream_graph = feature_branch.repository.get_graph(upstream_branch.repository)
merger = upstream_graph.find_lefthand_merger(
feature_revid, upstream_branch.last_revision()
)
if merger is not None:
try:
(foreign_revid, mapping) = foreign_vcs_registry.parse_revision_id(merger)
except errors.InvalidRevisionId:
pass
else:
if mapping.vcs.abbreviation == "git":
return "merged in commit {}".format(foreign_revid.decode("ascii")[:7])
return "merged in revision {}".format(
".".join(
str(x) for x in upstream_branch.revision_id_to_dotted_revno(merger)
)
)
else:
return "no"
def determine_forwarded(upstream_branch, feature_branch, feature_revid):
"""See if a branch has been forwarded to upstream.
:param upstream_branch: Upstream branch object
:param feature_branch: Feature branch
:param feature_revid: Revision id in feature branch to check
:return: String that can be used for Applied-Upstream field
"""
# FIXME: Check for Launchpad merge proposals from feature_branch (or its
# public_branch) to upstream_branch
# Are there any other ways to see that a patch has been forwarded upstream?
return None
def describe_origin(branch, revid):
"""Describe a tree for use in the origin field.
:param branch: Branch to retrieve the revision from
:param revid: Revision id
"""
public_branch_url = branch.get_public_branch()
if public_branch_url is not None:
try:
(foreign_revid, mapping) = foreign_vcs_registry.parse_revision_id(revid)
except errors.InvalidRevisionId:
pass
else:
if mapping.vcs.abbreviation == "git":
return "commit, {}, commit: {}".format(
public_branch_url, foreign_revid.decode("ascii")[:7]
)
return "commit, {}, revision: {}".format(
public_branch_url,
".".join(str(x) for x in branch.revision_id_to_dotted_revno(revid)),
)
else:
try:
(foreign_revid, mapping) = foreign_vcs_registry.parse_revision_id(revid)
except errors.InvalidRevisionId:
pass
else:
if mapping.vcs.abbreviation == "git":
return "commit: {}".format(foreign_revid.decode("ascii")[:7])
return "commit, revision id: %s" % revid.decode("utf-8")
def write_dep3_patch(
f,
branch,
base_revid,
target_revid,
description=None,
origin=None,
forwarded=None,
applied_upstream=None,
bugs=None,
authors=None,
last_update=None,
):
"""Write a DEP-3 compliant patch.
:param f: File-like object to write to
:param repository: Repository to retrieve revisions from
:param base_revid: Base revision id
:param target_revid: Target revision id
:param description: Optional description
:param forwarded: Optional information on if/how the patch was forwarded
:param applied_upstream: Optional information on how whether the patch
was merged upstream
:param bugs: Sequence of bug reports related to this patch
:param authors: Sequence of authors of this patch
:param last_update: Timestamp for last time this patch was updated
"""
write_dep3_patch_header(
f,
bugs=bugs,
authors=authors,
last_update=last_update,
description=description,
revision_id=target_revid,
origin=origin,
applied_upstream=applied_upstream,
forwarded=forwarded,
)
old_tree = branch.repository.revision_tree(base_revid)
new_tree = branch.repository.revision_tree(target_revid)
bf = BytesIO()
diff.show_diff_trees(old_tree, new_tree, bf, old_label="old/", new_label="new/")
# TODO(jelmer)
f.write(bf.getvalue().decode("utf-8"))
|