/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/bzr/smart/branch.py

  • Committer: Jelmer Vernooij
  • Date: 2020-05-24 00:39:50 UTC
  • mto: This revision was merged to the branch mainline in revision 7504.
  • Revision ID: jelmer@jelmer.uk-20200524003950-bbc545r76vc5yajg
Add github action.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006-2010 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""Server-side branch related request implmentations."""
 
18
 
 
19
from ... import (
 
20
    bencode,
 
21
    errors,
 
22
    revision as _mod_revision,
 
23
    )
 
24
from ...controldir import ControlDir
 
25
from .request import (
 
26
    FailedSmartServerResponse,
 
27
    SmartServerRequest,
 
28
    SuccessfulSmartServerResponse,
 
29
    )
 
30
 
 
31
 
 
32
class SmartServerBranchRequest(SmartServerRequest):
 
33
    """Base class for handling common branch request logic.
 
34
    """
 
35
 
 
36
    def do(self, path, *args):
 
37
        """Execute a request for a branch at path.
 
38
 
 
39
        All Branch requests take a path to the branch as their first argument.
 
40
 
 
41
        If the branch is a branch reference, NotBranchError is raised.
 
42
 
 
43
        :param path: The path for the repository as received from the
 
44
            client.
 
45
        :return: A SmartServerResponse from self.do_with_branch().
 
46
        """
 
47
        transport = self.transport_from_client_path(path)
 
48
        controldir = ControlDir.open_from_transport(transport)
 
49
        if controldir.get_branch_reference() is not None:
 
50
            raise errors.NotBranchError(transport.base)
 
51
        branch = controldir.open_branch(ignore_fallbacks=True)
 
52
        return self.do_with_branch(branch, *args)
 
53
 
 
54
 
 
55
class SmartServerLockedBranchRequest(SmartServerBranchRequest):
 
56
    """Base class for handling common branch request logic for requests that
 
57
    need a write lock.
 
58
    """
 
59
 
 
60
    def do_with_branch(self, branch, branch_token, repo_token, *args):
 
61
        """Execute a request for a branch.
 
62
 
 
63
        A write lock will be acquired with the given tokens for the branch and
 
64
        repository locks.  The lock will be released once the request is
 
65
        processed.  The physical lock state won't be changed.
 
66
        """
 
67
        # XXX: write a test for LockContention
 
68
        with branch.repository.lock_write(token=repo_token), \
 
69
                branch.lock_write(token=branch_token):
 
70
            return self.do_with_locked_branch(branch, *args)
 
71
 
 
72
 
 
73
class SmartServerBranchBreakLock(SmartServerBranchRequest):
 
74
 
 
75
    def do_with_branch(self, branch):
 
76
        """Break a branch lock.
 
77
        """
 
78
        branch.break_lock()
 
79
        return SuccessfulSmartServerResponse((b'ok', ), )
 
80
 
 
81
 
 
82
class SmartServerBranchGetConfigFile(SmartServerBranchRequest):
 
83
 
 
84
    def do_with_branch(self, branch):
 
85
        """Return the content of branch.conf
 
86
 
 
87
        The body is not utf8 decoded - its the literal bytestream from disk.
 
88
        """
 
89
        try:
 
90
            content = branch.control_transport.get_bytes('branch.conf')
 
91
        except errors.NoSuchFile:
 
92
            content = b''
 
93
        return SuccessfulSmartServerResponse((b'ok', ), content)
 
94
 
 
95
 
 
96
class SmartServerBranchPutConfigFile(SmartServerBranchRequest):
 
97
    """Set the configuration data for a branch.
 
98
 
 
99
    New in 2.5.
 
100
    """
 
101
 
 
102
    def do_with_branch(self, branch, branch_token, repo_token):
 
103
        """Set the content of branch.conf.
 
104
 
 
105
        The body is not utf8 decoded - its the literal bytestream for disk.
 
106
        """
 
107
        self._branch = branch
 
108
        self._branch_token = branch_token
 
109
        self._repo_token = repo_token
 
110
        # Signal we want a body
 
111
        return None
 
112
 
 
113
    def do_body(self, body_bytes):
 
114
        with self._branch.repository.lock_write(token=self._repo_token), \
 
115
                self._branch.lock_write(token=self._branch_token):
 
116
            self._branch.control_transport.put_bytes(
 
117
                'branch.conf', body_bytes)
 
118
        return SuccessfulSmartServerResponse((b'ok', ))
 
119
 
 
120
 
 
121
class SmartServerBranchGetParent(SmartServerBranchRequest):
 
122
 
 
123
    def do_with_branch(self, branch):
 
124
        """Return the parent of branch."""
 
125
        parent = branch._get_parent_location() or ''
 
126
        return SuccessfulSmartServerResponse((parent.encode('utf-8'),))
 
127
 
 
128
 
 
129
class SmartServerBranchGetTagsBytes(SmartServerBranchRequest):
 
130
 
 
131
    def do_with_branch(self, branch):
 
132
        """Return the _get_tags_bytes for a branch."""
 
133
        bytes = branch._get_tags_bytes()
 
134
        return SuccessfulSmartServerResponse((bytes,))
 
135
 
 
136
 
 
137
class SmartServerBranchSetTagsBytes(SmartServerLockedBranchRequest):
 
138
 
 
139
    def __init__(self, backing_transport, root_client_path='/', jail_root=None):
 
140
        SmartServerLockedBranchRequest.__init__(
 
141
            self, backing_transport, root_client_path, jail_root)
 
142
        self.locked = False
 
143
 
 
144
    def do_with_locked_branch(self, branch):
 
145
        """Call _set_tags_bytes for a branch.
 
146
 
 
147
        New in 1.18.
 
148
        """
 
149
        # We need to keep this branch locked until we get a body with the tags
 
150
        # bytes.
 
151
        self.branch = branch
 
152
        self.branch.lock_write()
 
153
        self.locked = True
 
154
 
 
155
    def do_body(self, bytes):
 
156
        self.branch._set_tags_bytes(bytes)
 
157
        return SuccessfulSmartServerResponse(())
 
158
 
 
159
    def do_end(self):
 
160
        # TODO: this request shouldn't have to do this housekeeping manually.
 
161
        # Some of this logic probably belongs in a base class.
 
162
        if not self.locked:
 
163
            # We never acquired the branch successfully in the first place, so
 
164
            # there's nothing more to do.
 
165
            return
 
166
        try:
 
167
            return SmartServerLockedBranchRequest.do_end(self)
 
168
        finally:
 
169
            # Only try unlocking if we locked successfully in the first place
 
170
            self.branch.unlock()
 
171
 
 
172
 
 
173
class SmartServerBranchHeadsToFetch(SmartServerBranchRequest):
 
174
 
 
175
    def do_with_branch(self, branch):
 
176
        """Return the heads-to-fetch for a Branch as two bencoded lists.
 
177
 
 
178
        See Branch.heads_to_fetch.
 
179
 
 
180
        New in 2.4.
 
181
        """
 
182
        must_fetch, if_present_fetch = branch.heads_to_fetch()
 
183
        return SuccessfulSmartServerResponse(
 
184
            (list(must_fetch), list(if_present_fetch)))
 
185
 
 
186
 
 
187
class SmartServerBranchRequestGetStackedOnURL(SmartServerBranchRequest):
 
188
 
 
189
    def do_with_branch(self, branch):
 
190
        stacked_on_url = branch.get_stacked_on_url()
 
191
        return SuccessfulSmartServerResponse((b'ok', stacked_on_url.encode('ascii')))
 
192
 
 
193
 
 
194
class SmartServerRequestRevisionHistory(SmartServerBranchRequest):
 
195
 
 
196
    def do_with_branch(self, branch):
 
197
        """Get the revision history for the branch.
 
198
 
 
199
        The revision list is returned as the body content,
 
200
        with each revision utf8 encoded and \x00 joined.
 
201
        """
 
202
        with branch.lock_read():
 
203
            graph = branch.repository.get_graph()
 
204
            stop_revisions = (None, _mod_revision.NULL_REVISION)
 
205
            history = list(graph.iter_lefthand_ancestry(
 
206
                branch.last_revision(), stop_revisions))
 
207
        return SuccessfulSmartServerResponse(
 
208
            (b'ok', ), (b'\x00'.join(reversed(history))))
 
209
 
 
210
 
 
211
class SmartServerBranchRequestLastRevisionInfo(SmartServerBranchRequest):
 
212
 
 
213
    def do_with_branch(self, branch):
 
214
        """Return branch.last_revision_info().
 
215
 
 
216
        The revno is encoded in decimal, the revision_id is encoded as utf8.
 
217
        """
 
218
        revno, last_revision = branch.last_revision_info()
 
219
        return SuccessfulSmartServerResponse(
 
220
            (b'ok', str(revno).encode('ascii'), last_revision))
 
221
 
 
222
 
 
223
class SmartServerBranchRequestRevisionIdToRevno(SmartServerBranchRequest):
 
224
 
 
225
    def do_with_branch(self, branch, revid):
 
226
        """Return branch.revision_id_to_revno().
 
227
 
 
228
        New in 2.5.
 
229
 
 
230
        The revno is encoded in decimal, the revision_id is encoded as utf8.
 
231
        """
 
232
        try:
 
233
            dotted_revno = branch.revision_id_to_dotted_revno(revid)
 
234
        except errors.NoSuchRevision:
 
235
            return FailedSmartServerResponse((b'NoSuchRevision', revid))
 
236
        except errors.GhostRevisionsHaveNoRevno as e:
 
237
            return FailedSmartServerResponse(
 
238
                (b'GhostRevisionsHaveNoRevno', e.revision_id,
 
239
                    e.ghost_revision_id))
 
240
        return SuccessfulSmartServerResponse(
 
241
            (b'ok', ) + tuple([b'%d' % x for x in dotted_revno]))
 
242
 
 
243
 
 
244
class SmartServerSetTipRequest(SmartServerLockedBranchRequest):
 
245
    """Base class for handling common branch request logic for requests that
 
246
    update the branch tip.
 
247
    """
 
248
 
 
249
    def do_with_locked_branch(self, branch, *args):
 
250
        try:
 
251
            return self.do_tip_change_with_locked_branch(branch, *args)
 
252
        except errors.TipChangeRejected as e:
 
253
            msg = e.msg
 
254
            if isinstance(msg, str):
 
255
                msg = msg.encode('utf-8')
 
256
            return FailedSmartServerResponse((b'TipChangeRejected', msg))
 
257
 
 
258
 
 
259
class SmartServerBranchRequestSetConfigOption(SmartServerLockedBranchRequest):
 
260
    """Set an option in the branch configuration."""
 
261
 
 
262
    def do_with_locked_branch(self, branch, value, name, section):
 
263
        if not section:
 
264
            section = None
 
265
        branch._get_config().set_option(
 
266
            value.decode('utf-8'), name.decode('utf-8'),
 
267
            section.decode('utf-8') if section is not None else None)
 
268
        return SuccessfulSmartServerResponse(())
 
269
 
 
270
 
 
271
class SmartServerBranchRequestSetConfigOptionDict(SmartServerLockedBranchRequest):
 
272
    """Set an option in the branch configuration.
 
273
 
 
274
    New in 2.2.
 
275
    """
 
276
 
 
277
    def do_with_locked_branch(self, branch, value_dict, name, section):
 
278
        utf8_dict = bencode.bdecode(value_dict)
 
279
        value_dict = {}
 
280
        for key, value in utf8_dict.items():
 
281
            value_dict[key.decode('utf8')] = value.decode('utf8')
 
282
        if not section:
 
283
            section = None
 
284
        else:
 
285
            section = section.decode('utf-8')
 
286
        branch._get_config().set_option(value_dict, name.decode('utf-8'), section)
 
287
        return SuccessfulSmartServerResponse(())
 
288
 
 
289
 
 
290
class SmartServerBranchRequestSetLastRevision(SmartServerSetTipRequest):
 
291
 
 
292
    def do_tip_change_with_locked_branch(self, branch, new_last_revision_id):
 
293
        if new_last_revision_id == b'null:':
 
294
            branch.set_last_revision_info(0, new_last_revision_id)
 
295
        else:
 
296
            if not branch.repository.has_revision(new_last_revision_id):
 
297
                return FailedSmartServerResponse(
 
298
                    (b'NoSuchRevision', new_last_revision_id))
 
299
            branch.generate_revision_history(new_last_revision_id, None, None)
 
300
        return SuccessfulSmartServerResponse((b'ok',))
 
301
 
 
302
 
 
303
class SmartServerBranchRequestSetLastRevisionEx(SmartServerSetTipRequest):
 
304
 
 
305
    def do_tip_change_with_locked_branch(self, branch, new_last_revision_id,
 
306
                                         allow_divergence, allow_overwrite_descendant):
 
307
        """Set the last revision of the branch.
 
308
 
 
309
        New in 1.6.
 
310
 
 
311
        :param new_last_revision_id: the revision ID to set as the last
 
312
            revision of the branch.
 
313
        :param allow_divergence: A flag.  If non-zero, change the revision ID
 
314
            even if the new_last_revision_id's ancestry has diverged from the
 
315
            current last revision.  If zero, a 'Diverged' error will be
 
316
            returned if new_last_revision_id is not a descendant of the current
 
317
            last revision.
 
318
        :param allow_overwrite_descendant:  A flag.  If zero and
 
319
            new_last_revision_id is not a descendant of the current last
 
320
            revision, then the last revision will not be changed.  If non-zero
 
321
            and there is no divergence, then the last revision is always
 
322
            changed.
 
323
 
 
324
        :returns: on success, a tuple of ('ok', revno, revision_id), where
 
325
            revno and revision_id are the new values of the current last
 
326
            revision info.  The revision_id might be different to the
 
327
            new_last_revision_id if allow_overwrite_descendant was not set.
 
328
        """
 
329
        do_not_overwrite_descendant = not allow_overwrite_descendant
 
330
        try:
 
331
            last_revno, last_rev = branch.last_revision_info()
 
332
            graph = branch.repository.get_graph()
 
333
            if not allow_divergence or do_not_overwrite_descendant:
 
334
                relation = branch._revision_relations(
 
335
                    last_rev, new_last_revision_id, graph)
 
336
                if relation == 'diverged' and not allow_divergence:
 
337
                    return FailedSmartServerResponse((b'Diverged',))
 
338
                if relation == 'a_descends_from_b' and do_not_overwrite_descendant:
 
339
                    return SuccessfulSmartServerResponse(
 
340
                        (b'ok', last_revno, last_rev))
 
341
            new_revno = graph.find_distance_to_null(
 
342
                new_last_revision_id, [(last_rev, last_revno)])
 
343
            branch.set_last_revision_info(new_revno, new_last_revision_id)
 
344
        except errors.GhostRevisionsHaveNoRevno:
 
345
            return FailedSmartServerResponse(
 
346
                (b'NoSuchRevision', new_last_revision_id))
 
347
        return SuccessfulSmartServerResponse(
 
348
            (b'ok', new_revno, new_last_revision_id))
 
349
 
 
350
 
 
351
class SmartServerBranchRequestSetLastRevisionInfo(SmartServerSetTipRequest):
 
352
    """Branch.set_last_revision_info.  Sets the revno and the revision ID of
 
353
    the specified branch.
 
354
 
 
355
    New in breezy 1.4.
 
356
    """
 
357
 
 
358
    def do_tip_change_with_locked_branch(self, branch, new_revno,
 
359
                                         new_last_revision_id):
 
360
        try:
 
361
            branch.set_last_revision_info(int(new_revno), new_last_revision_id)
 
362
        except errors.NoSuchRevision:
 
363
            return FailedSmartServerResponse(
 
364
                (b'NoSuchRevision', new_last_revision_id))
 
365
        return SuccessfulSmartServerResponse((b'ok',))
 
366
 
 
367
 
 
368
class SmartServerBranchRequestSetParentLocation(SmartServerLockedBranchRequest):
 
369
    """Set the parent location for a branch.
 
370
 
 
371
    Takes a location to set, which must be utf8 encoded.
 
372
    """
 
373
 
 
374
    def do_with_locked_branch(self, branch, location):
 
375
        branch._set_parent_location(location.decode('utf-8'))
 
376
        return SuccessfulSmartServerResponse(())
 
377
 
 
378
 
 
379
class SmartServerBranchRequestLockWrite(SmartServerBranchRequest):
 
380
 
 
381
    def do_with_branch(self, branch, branch_token=b'', repo_token=b''):
 
382
        if branch_token == b'':
 
383
            branch_token = None
 
384
        if repo_token == b'':
 
385
            repo_token = None
 
386
        try:
 
387
            repo_token = branch.repository.lock_write(
 
388
                token=repo_token).repository_token
 
389
            try:
 
390
                branch_token = branch.lock_write(
 
391
                    token=branch_token).token
 
392
            finally:
 
393
                # this leaves the repository with 1 lock
 
394
                branch.repository.unlock()
 
395
        except errors.LockContention:
 
396
            return FailedSmartServerResponse((b'LockContention',))
 
397
        except errors.TokenMismatch:
 
398
            return FailedSmartServerResponse((b'TokenMismatch',))
 
399
        except errors.UnlockableTransport:
 
400
            return FailedSmartServerResponse((b'UnlockableTransport',))
 
401
        except errors.LockFailed as e:
 
402
            return FailedSmartServerResponse((b'LockFailed',
 
403
                                              str(e.lock).encode('utf-8'), str(e.why).encode('utf-8')))
 
404
        if repo_token is None:
 
405
            repo_token = b''
 
406
        else:
 
407
            branch.repository.leave_lock_in_place()
 
408
        branch.leave_lock_in_place()
 
409
        branch.unlock()
 
410
        return SuccessfulSmartServerResponse((b'ok', branch_token, repo_token))
 
411
 
 
412
 
 
413
class SmartServerBranchRequestUnlock(SmartServerBranchRequest):
 
414
 
 
415
    def do_with_branch(self, branch, branch_token, repo_token):
 
416
        try:
 
417
            with branch.repository.lock_write(token=repo_token):
 
418
                branch.lock_write(token=branch_token)
 
419
        except errors.TokenMismatch:
 
420
            return FailedSmartServerResponse((b'TokenMismatch',))
 
421
        if repo_token:
 
422
            branch.repository.dont_leave_lock_in_place()
 
423
        branch.dont_leave_lock_in_place()
 
424
        branch.unlock()
 
425
        return SuccessfulSmartServerResponse((b'ok',))
 
426
 
 
427
 
 
428
class SmartServerBranchRequestGetPhysicalLockStatus(SmartServerBranchRequest):
 
429
    """Get the physical lock status for a branch.
 
430
 
 
431
    New in 2.5.
 
432
    """
 
433
 
 
434
    def do_with_branch(self, branch):
 
435
        if branch.get_physical_lock_status():
 
436
            return SuccessfulSmartServerResponse((b'yes',))
 
437
        else:
 
438
            return SuccessfulSmartServerResponse((b'no',))
 
439
 
 
440
 
 
441
class SmartServerBranchRequestGetAllReferenceInfo(SmartServerBranchRequest):
 
442
    """Get the reference information.
 
443
 
 
444
    New in 3.1.
 
445
    """
 
446
 
 
447
    def do_with_branch(self, branch):
 
448
        all_reference_info = branch._get_all_reference_info()
 
449
        content = bencode.bencode([
 
450
            (key, value[0].encode('utf-8'), value[1].encode('utf-8') if value[1] else b'')
 
451
            for (key, value) in all_reference_info.items()])
 
452
        return SuccessfulSmartServerResponse((b'ok', ), content)