11592.1.1
by Jeroen Vermeulen
Safer bzr ids in DirectBranchCommit, more appropriate ones in translations_to_branch. |
1 |
# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
|
8687.15.34
by Karl Fogel
Add license header blocks to .py, .zcml, and .pt files that don't have it |
2 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
8657.7.1
by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work. |
3 |
|
4 |
"""Commit files straight to bzr branch."""
|
|
5 |
||
6 |
__metaclass__ = type |
|
7 |
__all__ = [ |
|
8 |
'ConcurrentUpdateError', |
|
9 |
'DirectBranchCommit', |
|
10 |
]
|
|
11 |
||
12 |
||
13 |
import os.path |
|
14 |
||
15 |
from bzrlib.generate_ids import gen_file_id |
|
16 |
from bzrlib.revision import NULL_REVISION |
|
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
17 |
from bzrlib.transform import ( |
18 |
ROOT_PARENT, |
|
19 |
TransformPreview, |
|
20 |
)
|
|
8657.7.1
by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work. |
21 |
|
11592.1.1
by Jeroen Vermeulen
Safer bzr ids in DirectBranchCommit, more appropriate ones in translations_to_branch. |
22 |
from canonical.config import config |
14560.2.29
by Curtis Hovey
Restored lpstorm module name because it lp engineers know that name. |
23 |
from lp.services.database.lpstorm import IMasterObject |
13084.5.2
by Aaron Bentley
Raise StaleLastMirrored as needed. |
24 |
from lp.code.errors import StaleLastMirrored |
25 |
from lp.codehosting.bzrutils import ( |
|
26 |
get_branch_info, |
|
27 |
get_stacked_on_url, |
|
28 |
)
|
|
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
29 |
from lp.services.mail.sendmail import format_address_for_person |
11040.1.12
by Aaron Bentley
Ensure commits work when username is unset. |
30 |
from lp.services.osutils import override_environ |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
31 |
|
8657.7.1
by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work. |
32 |
|
33 |
class ConcurrentUpdateError(Exception): |
|
34 |
"""Bailout exception for concurrent updates.
|
|
35 |
||
36 |
This is raised when committing to a branch would risk overwriting
|
|
37 |
concurrent changes made by another party.
|
|
38 |
"""
|
|
39 |
||
40 |
||
41 |
class DirectBranchCommit: |
|
42 |
"""Commit a set of files straight into a branch.
|
|
43 |
||
44 |
Use this to write a set of files into a branch efficiently, without
|
|
45 |
caring what was in there before. The files may be new to the branch
|
|
46 |
or they may exist there already; in the latter case they will be
|
|
47 |
overwritten.
|
|
48 |
||
49 |
The branch is write-locked for the entire lifetime of this object.
|
|
50 |
Be sure to call unlock() when done. This will be done for you as
|
|
51 |
part of a successful commit, but unlocking more than once will do no
|
|
52 |
harm.
|
|
53 |
||
54 |
The trick for this was invented by Aaron Bentley. It saves having
|
|
55 |
to do a full checkout of the branch.
|
|
56 |
"""
|
|
57 |
is_open = False |
|
58 |
is_locked = False |
|
8963.9.1
by Jeroen Vermeulen
Fixed directory creation/lookup; used to create the same directories again every time they were needed. |
59 |
commit_builder = None |
8657.7.1
by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work. |
60 |
|
11486.4.2
by Aaron Bentley
Incremental diffs ignore merged changes from target/prereq branches. |
61 |
def __init__(self, db_branch, committer=None, no_race_check=False, |
11592.1.4
by Jeroen Vermeulen
Changes for Noodles' review. |
62 |
merge_parents=None, committer_id=None): |
8657.7.1
by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work. |
63 |
"""Create context for direct commit to branch.
|
64 |
||
8871.3.5
by Jeroen Vermeulen
As per mwhudson's suggestion, keep bzrserver out of DirectBranchCommit entirely. |
65 |
Before constructing a `DirectBranchCommit`, set up a server that
|
9590.1.117
by Michael Hudson
kill off get_multi_server |
66 |
allows write access to lp-internal:/// URLs:
|
8871.3.5
by Jeroen Vermeulen
As per mwhudson's suggestion, keep bzrserver out of DirectBranchCommit entirely. |
67 |
|
9590.1.117
by Michael Hudson
kill off get_multi_server |
68 |
bzrserver = get_rw_server()
|
10197.5.13
by Michael Hudson
misc. fixes |
69 |
bzrserver.start_server()
|
8871.3.5
by Jeroen Vermeulen
As per mwhudson's suggestion, keep bzrserver out of DirectBranchCommit entirely. |
70 |
try:
|
71 |
branchcommit = DirectBranchCommit(branch)
|
|
72 |
# ...
|
|
73 |
finally:
|
|
10197.5.13
by Michael Hudson
misc. fixes |
74 |
bzrserver.stop_server()
|
8871.3.5
by Jeroen Vermeulen
As per mwhudson's suggestion, keep bzrserver out of DirectBranchCommit entirely. |
75 |
|
76 |
Or in tests, just call `useBzrBranches` before creating a
|
|
77 |
`DirectBranchCommit`.
|
|
8871.3.3
by Jeroen Vermeulen
Factored out BranchMirrorer factory in codehosting. Fixed up DirectBranchCommit to use it. Also, made test start the server before useBzrBranches. |
78 |
|
8871.3.1
by Jeroen Vermeulen
Implemented changes whispered in my ear by mwhudson. |
79 |
:param db_branch: a Launchpad `Branch` object.
|
11592.1.1
by Jeroen Vermeulen
Safer bzr ids in DirectBranchCommit, more appropriate ones in translations_to_branch. |
80 |
:param committer: the `Person` writing to the branch. Defaults to
|
81 |
the branch owner.
|
|
9590.1.128
by Michael Hudson
add a "no_race_check" to directbranchcommit for use in tests |
82 |
:param no_race_check: don't check for other commits before committing
|
83 |
our changes, for use in tests.
|
|
11592.1.4
by Jeroen Vermeulen
Changes for Noodles' review. |
84 |
:param committer_id: Optional identification (typically with email
|
85 |
address) of the person doing the commit, for use in bzr. If not
|
|
86 |
given, the `committer`'s email address will be used instead.
|
|
8657.7.1
by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work. |
87 |
"""
|
88 |
self.db_branch = db_branch |
|
89 |
||
9375.3.2
by Jeroen Vermeulen
Small fixups. |
90 |
self.last_scanned_id = self.db_branch.last_scanned_id |
9375.3.1
by Jeroen Vermeulen
Extra logging & committing. |
91 |
|
8657.7.1
by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work. |
92 |
if committer is None: |
93 |
committer = db_branch.owner |
|
94 |
self.committer = committer |
|
11592.1.4
by Jeroen Vermeulen
Changes for Noodles' review. |
95 |
self.committer_id = committer_id |
8657.7.1
by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work. |
96 |
|
9590.1.128
by Michael Hudson
add a "no_race_check" to directbranchcommit for use in tests |
97 |
self.no_race_check = no_race_check |
98 |
||
8963.9.2
by Jeroen Vermeulen
Review changes. |
99 |
# Directories we create on the branch, and their ids.
|
8963.9.1
by Jeroen Vermeulen
Fixed directory creation/lookup; used to create the same directories again every time they were needed. |
100 |
self.path_ids = {} |
101 |
||
9590.1.102
by Michael Hudson
undo some preliminary rosetta changes |
102 |
self.bzrbranch = self.db_branch.getBzrBranch() |
9365.3.1
by Jeroen Vermeulen
Document design choice. |
103 |
|
8657.7.1
by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work. |
104 |
self.bzrbranch.lock_write() |
105 |
self.is_locked = True |
|
106 |
try: |
|
107 |
self.revision_tree = self.bzrbranch.basis_tree() |
|
108 |
self.transform_preview = TransformPreview(self.revision_tree) |
|
8963.9.2
by Jeroen Vermeulen
Review changes. |
109 |
assert self.transform_preview.find_conflicts() == [], ( |
110 |
"TransformPreview is not in a consistent state.") |
|
13084.5.2
by Aaron Bentley
Raise StaleLastMirrored as needed. |
111 |
if not no_race_check: |
112 |
last_revision = self.bzrbranch.last_revision() |
|
13084.5.8
by Aaron Bentley
Improve last_mirrored handling. |
113 |
if not self._matchingLastMirrored(last_revision): |
13084.5.2
by Aaron Bentley
Raise StaleLastMirrored as needed. |
114 |
raise StaleLastMirrored( |
115 |
db_branch, get_branch_info(self.bzrbranch)) |
|
8657.7.1
by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work. |
116 |
|
117 |
self.is_open = True |
|
118 |
except: |
|
119 |
self.unlock() |
|
120 |
raise
|
|
121 |
||
122 |
self.files = set() |
|
11486.4.2
by Aaron Bentley
Incremental diffs ignore merged changes from target/prereq branches. |
123 |
self.merge_parents = merge_parents |
8657.7.1
by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work. |
124 |
|
13084.5.8
by Aaron Bentley
Improve last_mirrored handling. |
125 |
def _matchingLastMirrored(self, revision_id): |
126 |
if (self.db_branch.last_mirrored_id is None |
|
127 |
and revision_id == NULL_REVISION): |
|
128 |
return True |
|
129 |
return revision_id == self.db_branch.last_mirrored_id |
|
130 |
||
8657.7.1
by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work. |
131 |
def _getDir(self, path): |
132 |
"""Get trans_id for directory "path." Create if necessary."""
|
|
8963.9.1
by Jeroen Vermeulen
Fixed directory creation/lookup; used to create the same directories again every time they were needed. |
133 |
path_id = self.path_ids.get(path) |
134 |
if path_id: |
|
135 |
# This is a path we've just created in the branch.
|
|
136 |
return path_id |
|
137 |
||
138 |
if self.revision_tree.path2id(path): |
|
139 |
# This is a path that was already in the branch.
|
|
8657.7.3
by Jeroen Vermeulen
Last-minute fix: got id instead of trans id for existing parent directories. |
140 |
return self.transform_preview.trans_id_tree_path(path) |
8657.7.1
by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work. |
141 |
|
8963.9.1
by Jeroen Vermeulen
Fixed directory creation/lookup; used to create the same directories again every time they were needed. |
142 |
# Look up (or create) parent directory.
|
8657.7.1
by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work. |
143 |
parent_dir, dirname = os.path.split(path) |
144 |
if dirname: |
|
145 |
parent_id = self._getDir(parent_dir) |
|
146 |
else: |
|
7675.310.3
by Aaron Bentley
Clean up DirectBranchCommit. |
147 |
parent_id = ROOT_PARENT |
8657.7.1
by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work. |
148 |
|
8963.9.1
by Jeroen Vermeulen
Fixed directory creation/lookup; used to create the same directories again every time they were needed. |
149 |
# Create new directory.
|
8657.7.1
by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work. |
150 |
dirfile_id = gen_file_id(path) |
8963.9.1
by Jeroen Vermeulen
Fixed directory creation/lookup; used to create the same directories again every time they were needed. |
151 |
path_id = self.transform_preview.new_directory( |
8657.7.1
by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work. |
152 |
dirname, parent_id, dirfile_id) |
8963.9.1
by Jeroen Vermeulen
Fixed directory creation/lookup; used to create the same directories again every time they were needed. |
153 |
self.path_ids[path] = path_id |
154 |
return path_id |
|
8657.7.1
by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work. |
155 |
|
156 |
def writeFile(self, path, contents): |
|
157 |
"""Write file to branch; may be an update or a new file.
|
|
158 |
||
159 |
If you write a file multiple times, the first one is used and
|
|
160 |
the rest ignored.
|
|
161 |
"""
|
|
162 |
assert self.is_open, "Writing file to closed DirectBranchCommit." |
|
163 |
||
164 |
if path in self.files: |
|
165 |
# We already have this file. Ignore second write.
|
|
166 |
return
|
|
167 |
||
168 |
file_id = self.revision_tree.path2id(path) |
|
169 |
if file_id is None: |
|
170 |
parent_path, name = os.path.split(path) |
|
171 |
parent_id = self._getDir(parent_path) |
|
172 |
file_id = gen_file_id(name) |
|
173 |
self.transform_preview.new_file( |
|
174 |
name, parent_id, [contents], file_id) |
|
175 |
else: |
|
176 |
trans_id = self.transform_preview.trans_id_tree_path(path) |
|
177 |
# Delete old contents. It doesn't actually matter whether
|
|
178 |
# we do this before creating the new contents.
|
|
179 |
self.transform_preview.delete_contents(trans_id) |
|
180 |
self.transform_preview.create_file([contents], trans_id) |
|
181 |
||
182 |
self.files.add(path) |
|
183 |
||
184 |
def _checkForRace(self): |
|
185 |
"""Check if bzrbranch has any changes waiting to be scanned.
|
|
186 |
||
187 |
If it does, raise `ConcurrentUpdateError`.
|
|
188 |
"""
|
|
189 |
assert self.is_locked, "Getting revision on un-locked branch." |
|
9590.1.128
by Michael Hudson
add a "no_race_check" to directbranchcommit for use in tests |
190 |
if self.no_race_check: |
191 |
return
|
|
8657.7.1
by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work. |
192 |
last_revision = self.bzrbranch.last_revision() |
9375.3.1
by Jeroen Vermeulen
Extra logging & committing. |
193 |
if last_revision != self.last_scanned_id: |
8657.7.1
by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work. |
194 |
raise ConcurrentUpdateError( |
195 |
"Branch has been changed. Not committing.") |
|
196 |
||
11592.1.1
by Jeroen Vermeulen
Safer bzr ids in DirectBranchCommit, more appropriate ones in translations_to_branch. |
197 |
def getBzrCommitterID(self): |
198 |
"""Find the committer id to pass to bzr."""
|
|
11592.1.4
by Jeroen Vermeulen
Changes for Noodles' review. |
199 |
if self.committer_id is not None: |
200 |
return self.committer_id |
|
11592.1.1
by Jeroen Vermeulen
Safer bzr ids in DirectBranchCommit, more appropriate ones in translations_to_branch. |
201 |
elif self.committer.preferredemail is not None: |
202 |
return format_address_for_person(self.committer) |
|
203 |
else: |
|
204 |
return '"%s" <%s>' % ( |
|
205 |
self.committer.displayname, |
|
206 |
config.canonical.noreply_from_address) |
|
207 |
||
9375.3.1
by Jeroen Vermeulen
Extra logging & committing. |
208 |
def commit(self, commit_message, txn=None): |
209 |
"""Commit to branch.
|
|
210 |
||
211 |
:param commit_message: Message for branch's commit log.
|
|
212 |
:param txn: Transaction to commit. Can be helpful in avoiding
|
|
213 |
long idle times in database transactions. May be committed
|
|
214 |
more than once.
|
|
215 |
"""
|
|
8657.7.1
by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work. |
216 |
assert self.is_open, "Committing closed DirectBranchCommit." |
217 |
assert self.is_locked, "Not locked at commit time." |
|
218 |
||
219 |
try: |
|
220 |
self._checkForRace() |
|
221 |
||
9375.3.1
by Jeroen Vermeulen
Extra logging & committing. |
222 |
if txn: |
223 |
txn.commit() |
|
224 |
||
8657.7.1
by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work. |
225 |
rev_id = self.revision_tree.get_revision_id() |
226 |
if rev_id == NULL_REVISION: |
|
7675.310.3
by Aaron Bentley
Clean up DirectBranchCommit. |
227 |
if list(self.transform_preview.iter_changes()) == []: |
228 |
return
|
|
11592.1.1
by Jeroen Vermeulen
Safer bzr ids in DirectBranchCommit, more appropriate ones in translations_to_branch. |
229 |
committer_id = self.getBzrCommitterID() |
11040.1.12
by Aaron Bentley
Ensure commits work when username is unset. |
230 |
# XXX: AaronBentley 2010-08-06 bug=614404: a bzr username is
|
231 |
# required to generate the revision-id.
|
|
232 |
with override_environ(BZR_EMAIL=committer_id): |
|
233 |
new_rev_id = self.transform_preview.commit( |
|
11486.4.2
by Aaron Bentley
Incremental diffs ignore merged changes from target/prereq branches. |
234 |
self.bzrbranch, commit_message, self.merge_parents, |
235 |
committer=committer_id) |
|
10850.3.1
by Jeroen Vermeulen
Porting production-devel fix to devel. |
236 |
IMasterObject(self.db_branch).branchChanged( |
237 |
get_stacked_on_url(self.bzrbranch), new_rev_id, |
|
238 |
self.db_branch.control_format, self.db_branch.branch_format, |
|
239 |
self.db_branch.repository_format) |
|
8871.3.9
by Jeroen Vermeulen
requestMirror() after committing. Requires write privs on Branch. |
240 |
|
9375.3.1
by Jeroen Vermeulen
Extra logging & committing. |
241 |
if txn: |
242 |
txn.commit() |
|
243 |
||
8657.7.1
by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work. |
244 |
finally: |
245 |
self.unlock() |
|
246 |
self.is_open = False |
|
9222.3.6
by Aaron Bentley
Merge preserving changes. |
247 |
return new_rev_id |
8657.7.1
by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work. |
248 |
|
249 |
def unlock(self): |
|
250 |
"""Release commit lock, if held."""
|
|
251 |
if self.is_locked: |
|
8963.9.1
by Jeroen Vermeulen
Fixed directory creation/lookup; used to create the same directories again every time they were needed. |
252 |
self.transform_preview.finalize() |
8657.7.1
by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work. |
253 |
self.bzrbranch.unlock() |
254 |
self.is_locked = False |