Merge lp:~jelmer/brz/bundle-upload into lp:brz
- bundle-upload
- Merge into trunk
Proposed by
Jelmer Vernooij
Status: | Merged |
---|---|
Approved by: | Jelmer Vernooij |
Approved revision: | no longer in the source branch. |
Merge reported by: | The Breezy Bot |
Merged at revision: | not available |
Proposed branch: | lp:~jelmer/brz/bundle-upload |
Merge into: | lp:brz |
Diff against target: |
2005 lines (+1954/-0) 9 files modified
breezy/plugins/upload/.bzrignore (+3/-0) breezy/plugins/upload/NEWS (+157/-0) breezy/plugins/upload/README (+21/-0) breezy/plugins/upload/__init__.py (+238/-0) breezy/plugins/upload/cmds.py (+560/-0) breezy/plugins/upload/tests/__init__.py (+28/-0) breezy/plugins/upload/tests/test_auto_upload_hook.py (+87/-0) breezy/plugins/upload/tests/test_upload.py (+857/-0) doc/en/release-notes/brz-3.0.txt (+3/-0) |
To merge this branch: | bzr merge lp:~jelmer/brz/bundle-upload |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Martin Packman | Approve | ||
Review via email: mp+324986@code.launchpad.net |
Commit message
Bundle the 'upload' plugin.
Description of the change
Bundle the 'upload' plugin.
To post a comment you must log in.
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : | # |
Running landing tests failed
http://
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : | # |
Merging failed
http://
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : | # |
Merging failed
http://
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : | # |
Running landing tests failed
http://
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : | # |
Running landing tests failed
http://
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : | # |
Running landing tests failed
http://
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : | # |
Running landing tests failed
http://
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added directory 'breezy/plugins/upload' |
2 | === added file 'breezy/plugins/upload/.bzrignore' |
3 | --- breezy/plugins/upload/.bzrignore 1970-01-01 00:00:00 +0000 |
4 | +++ breezy/plugins/upload/.bzrignore 2017-06-02 20:49:44 +0000 |
5 | @@ -0,0 +1,3 @@ |
6 | +./build |
7 | +./dist |
8 | +./MANIFEST |
9 | |
10 | === added file 'breezy/plugins/upload/NEWS' |
11 | --- breezy/plugins/upload/NEWS 1970-01-01 00:00:00 +0000 |
12 | +++ breezy/plugins/upload/NEWS 2017-06-02 20:49:44 +0000 |
13 | @@ -0,0 +1,157 @@ |
14 | +######################## |
15 | +bzr-upload Release Notes |
16 | +######################## |
17 | + |
18 | +.. contents:: List of Releases |
19 | + :depth: 1 |
20 | + |
21 | +bzr-upload 1.2.0 |
22 | +################ |
23 | + |
24 | +:1.2.0: NOT RELEASED YET |
25 | + |
26 | +New Features |
27 | +************ |
28 | + |
29 | +.. New commands, options, etc that users may wish to try out. |
30 | + |
31 | +Improvements |
32 | +************ |
33 | + |
34 | +* Use bzr config stacks, bzr >= 2.5 is now required. (Vincent Ladeuil) |
35 | + |
36 | +Bug Fixes |
37 | +********* |
38 | + |
39 | +.. Fixes for situations where bzr would previously crash or give incorrect |
40 | + or undesirable results. |
41 | + |
42 | +Documentation |
43 | +************* |
44 | + |
45 | +.. Improved or updated documentation. |
46 | + |
47 | +Testing |
48 | +******* |
49 | + |
50 | +.. Fixes and changes that are only relevant to bzr's test framework and |
51 | + suite. This can include new facilities for writing tests, fixes to |
52 | + spurious test failures and changes to the way things should be tested. |
53 | + |
54 | + |
55 | +bzr-upload 1.1.0 |
56 | +################ |
57 | + |
58 | +:1.1.0: 2012-03-15 |
59 | + |
60 | +New Features |
61 | +************ |
62 | + |
63 | +.. New commands, options, etc that users may wish to try out. |
64 | + |
65 | +Improvements |
66 | +************ |
67 | + |
68 | +.. Improvements to existing commands, especially improved performance |
69 | + or memory usage, or better results. |
70 | + |
71 | +Bug Fixes |
72 | +********* |
73 | + |
74 | +.. Fixes for situations where bzr would previously crash or give incorrect |
75 | + or undesirable results. |
76 | + |
77 | +Documentation |
78 | +************* |
79 | + |
80 | +.. Improved or updated documentation. |
81 | + |
82 | +Testing |
83 | +******* |
84 | + |
85 | +* Now requires bzr >= 2.5 for testing. The plugin itself should still work |
86 | + with previous versions. (Vincent Ladeuil) |
87 | + |
88 | +* Avoid deprecation warning with bzr-2.5 by using tree.iter_entries_by_dir |
89 | + avoiding direct inventory access, this should still be compatible with |
90 | + older bzr versions. (Vincent Ladeuil) |
91 | + |
92 | +bzr-upload 1.0.1 |
93 | +################ |
94 | + |
95 | +:1.0.1: 2012-03-15 |
96 | + |
97 | +Bug Fixes |
98 | +********* |
99 | + |
100 | +* Fix a typo to avoid crashing when encountering symlinks during |
101 | + a full upload. (Jonathan Paugh) |
102 | + |
103 | +Testing |
104 | +******* |
105 | + |
106 | +* Use assertPathDoesNotExist and assertPathExist instead of failIfExists and |
107 | + failUnlessExists in the test suite. This requires bzr-2.4 to run the tests |
108 | + but doesn't affect the plugin compatibility itself with previous verions |
109 | + of bzr. (Vincent Ladeuil) |
110 | + |
111 | + |
112 | +bzr-upload 1.0.0 |
113 | +################ |
114 | + |
115 | +:1.0.0: 2010-12-10 |
116 | + |
117 | +New Features |
118 | +************ |
119 | + |
120 | +* ``.bzrignore-upload`` can be used to avoid uploading some files or |
121 | + directories. It uses the same syntax as ``.bzrignore`` including regular |
122 | + expressions. |
123 | + (Martin Albisetti, Vincent Ladeuil, #499525, #499941) |
124 | + |
125 | +* Remote branches can be used to upload from. |
126 | + (Gary van der Merwe, Vincent Ladeuil) |
127 | + |
128 | +* The auto hook verbosity is now controlled by the 'upload_auto_quiet' |
129 | + config variable. If defaults to False if not set. |
130 | + (Vincent Ladeuil, #312686) |
131 | + |
132 | +* The file where the revision id is stored on the remote server is now |
133 | + controlled by the 'upload_revid_location' configuration variable. It |
134 | + defaults to '.bzr-upload.revid'. |
135 | + (Vincent Ladeuil, #423331) |
136 | + |
137 | +* Upload now checks that the revision we are uploading is a descendent |
138 | + from the revision that was uploaded, and hence that the branchs that |
139 | + they were uploaded from have not diverged. This can be ignored by passing |
140 | + the --overwrite option. (Gary van der Merwe) |
141 | + |
142 | + |
143 | +Bug Fixes |
144 | +********* |
145 | + |
146 | +* Fix auto hook trying to display an url without using the right encoding. |
147 | + (Vincent Ladeuil, #312686) |
148 | + |
149 | +* Fix compatibility with bzr versions that don't provide |
150 | + get_user_option_as_bool(). |
151 | + (Vincent Ladeuil, #423791) |
152 | + |
153 | +* Emit warnings instead of backtrace when symlinks are encountered. |
154 | + (Vincent Ladeuil, #477224) |
155 | + |
156 | +Documentation |
157 | +************* |
158 | + |
159 | +* Clarify 'changes' definition in online help. |
160 | + (Vincent Ladeuil, #275538) |
161 | + |
162 | +* Move the README file into the module doc string so that it |
163 | + becomes available through the 'bzr help plugins/upload' |
164 | + command. (Vincent Ladeuil, #424193) |
165 | + |
166 | +Testing |
167 | +******* |
168 | + |
169 | +* Make tests requiring a unicode file system skip where applicable. |
170 | + (Vincent Ladeuil, #671964) |
171 | |
172 | === added file 'breezy/plugins/upload/README' |
173 | --- breezy/plugins/upload/README 1970-01-01 00:00:00 +0000 |
174 | +++ breezy/plugins/upload/README 2017-06-02 20:49:44 +0000 |
175 | @@ -0,0 +1,21 @@ |
176 | +Bazaar Upload |
177 | +============= |
178 | + |
179 | +Overview |
180 | +-------- |
181 | + |
182 | +bzr-upload is a plugin for Bazaar which lets you upload your working tree to |
183 | +a remote location using ftp/sftp. |
184 | + |
185 | +The main target audience is web developers who keep their web pages version |
186 | +controlled with bzr. |
187 | + |
188 | +Please report any bugs to: http://bugs.launchpad.net/bzr-upload/ |
189 | + |
190 | +Our home page is located at: https://launchpad.net/bzr-upload/ |
191 | +The original authors are: |
192 | + |
193 | +Vincent Ladeuil <v.ladeuil+lp@free.fr> |
194 | +Martin Albisetti <argentina@gmail.com> |
195 | + |
196 | +Use bzr help plugins/upload to get more info. |
197 | |
198 | === added file 'breezy/plugins/upload/__init__.py' |
199 | --- breezy/plugins/upload/__init__.py 1970-01-01 00:00:00 +0000 |
200 | +++ breezy/plugins/upload/__init__.py 2017-06-02 20:49:44 +0000 |
201 | @@ -0,0 +1,238 @@ |
202 | +# Copyright (C) 2008-2012 Canonical Ltd |
203 | +# |
204 | +# This program is free software; you can redistribute it and/or modify |
205 | +# it under the terms of the GNU General Public License as published by |
206 | +# the Free Software Foundation; either version 2 of the License, or |
207 | +# (at your option) any later version. |
208 | +# |
209 | +# This program is distributed in the hope that it will be useful, |
210 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
211 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
212 | +# GNU General Public License for more details. |
213 | +# |
214 | +# You should have received a copy of the GNU General Public License |
215 | +# along with this program; if not, write to the Free Software |
216 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
217 | + |
218 | +"""Upload a working tree, incrementally. |
219 | + |
220 | +Quickstart |
221 | +---------- |
222 | + |
223 | +To get started, it's as simple as running:: |
224 | + |
225 | + brz upload sftp://user@host/location/on/webserver |
226 | + |
227 | +This will initially upload the whole working tree, and leave a file on the |
228 | +remote location indicating the last revision that was uploaded |
229 | +(.bzr-upload.revid), in order to avoid uploading unnecessary information the |
230 | +next time. |
231 | + |
232 | +If you would like to upload a specific revision, you just do: |
233 | + |
234 | + brz upload -r X sftp://user@host/location/on/webserver |
235 | + |
236 | +bzr-upload, just as brz does, will remember the location where you upload the |
237 | +first time, so you don't need to specify it every time. |
238 | + |
239 | +If you need to re-upload the whole working tree for some reason, you can: |
240 | + |
241 | + brz upload --full sftp://user@host/location/on/webserver |
242 | + |
243 | +This command only works on the revision beening uploaded is a decendent of the |
244 | +revision that was previously uploaded, and that they are hence from branches |
245 | +that have not diverged. Branches are considered diverged if the destination |
246 | +branch's most recent commit is one that has not been merged (directly or |
247 | +indirectly) by the source branch. |
248 | + |
249 | +If branches have diverged, you can use 'brz upload --overwrite' to replace |
250 | +the other branch completely, discarding its unmerged changes. |
251 | + |
252 | + |
253 | +Automatically Uploading |
254 | +----------------------- |
255 | + |
256 | +bzr-upload comes with a hook that can be used to trigger an upload whenever |
257 | +the tip of the branch changes, including on commit, push, uncommit etc. This |
258 | +would allow you to keep the code on the target up to date automatically. |
259 | + |
260 | +The easiest way to enable this is to run upload with the --auto option. |
261 | + |
262 | + brz upload --auto |
263 | + |
264 | +will enable the hook for this branch. If you were to do a commit in this branch |
265 | +now you would see it trigger the upload automatically. |
266 | + |
267 | +If you wish to disable this for a branch again then you can use the --no-auto |
268 | +option. |
269 | + |
270 | + brz upload --no-auto |
271 | + |
272 | +will disable the feature for that branch. |
273 | + |
274 | +Since the auto hook is triggered automatically, you can't use the --quiet |
275 | +option available for the upload command. Instead, you can set the |
276 | +'upload_auto_quiet' configuration variable to True or False in either |
277 | +bazaar.conf, locations.conf or branch.conf. |
278 | + |
279 | + |
280 | +Storing the '.bzr-upload.revid' file |
281 | +------------------------------------ |
282 | + |
283 | +The only bzr-related info uploaded with the working tree is the corresponding |
284 | +revision id. The uploaded working tree is not linked to any other brz data. |
285 | + |
286 | +If the layout of your remote server is such that you can't write in the |
287 | +root directory but only in the directories inside that root, you will need |
288 | +to use the 'upload_revid_location' configuration variable to specify the |
289 | +relative path to be used. That configuration variable can be specified in |
290 | +locations.conf or branch.conf. |
291 | + |
292 | +For example, given the following layout: |
293 | + |
294 | + Project/ |
295 | + private/ |
296 | + public/ |
297 | + |
298 | +you may have write access in 'private' and 'public' but in 'Project' |
299 | +itself. In that case, you can add the following in your locations.conf or |
300 | +branch.conf file: |
301 | + |
302 | + upload_revid_location = private/.bzr-upload.revid |
303 | + |
304 | + |
305 | +Upload from Remote Location |
306 | +--------------------------- |
307 | + |
308 | +It is possible to upload to a remote location from another remote location by |
309 | +specifying it with the --directory option: |
310 | + |
311 | + brz upload ftp://public.example.com --directory sftp://private.example.com |
312 | + |
313 | +This, together with --auto, can be used to upload when you push to your |
314 | +central branch, rather than when you commit to your local branch. |
315 | + |
316 | +Note that you will consume more bandwith this way than uploading from a local |
317 | +branch. |
318 | + |
319 | +Ignoring certain files |
320 | +----------------------- |
321 | + |
322 | +If you want to version a file, but not upload it, you can create a file called |
323 | +.bzrignore-upload, which works in the same way as the regular .bzrignore file, |
324 | +but only applies to bzr-upload. |
325 | + |
326 | + |
327 | +Collaborating |
328 | +------------- |
329 | + |
330 | +While we don't have any platform setup, you can branch from trunk: |
331 | + |
332 | + brz branch lp:bzr-upload |
333 | + |
334 | +And change anything you'd like, and file a merge proposal on Launchpad. |
335 | + |
336 | + |
337 | +Known Issues |
338 | +------------ |
339 | + |
340 | + * Symlinks are not supported (warnings are emitted when they are encountered). |
341 | + |
342 | + |
343 | +""" |
344 | + |
345 | +from __future__ import absolute_import |
346 | + |
347 | +# TODO: the chmod bits *can* be supported via the upload protocols |
348 | +# (i.e. poorly), but since the web developers use these protocols to upload |
349 | +# manually, it is expected that the associated web server is coherent with |
350 | +# their presence/absence. In other words, if a web hosting provider requires |
351 | +# chmod bits but don't provide an ftp server that support them, well, better |
352 | +# find another provider ;-) |
353 | + |
354 | +# TODO: The message emitted in verbose mode displays local paths. That may be |
355 | +# scary for the user when we say 'Deleting <path>' and are referring to |
356 | +# remote files... |
357 | + |
358 | + |
359 | +import breezy |
360 | +from ... import ( |
361 | + commands, |
362 | + config, |
363 | + hooks, |
364 | + ) |
365 | + |
366 | +from ...hooks import install_lazy_named_hook |
367 | + |
368 | +from ... import version_info |
369 | + |
370 | + |
371 | +def register_option(key, member): |
372 | + """Lazily register an option.""" |
373 | + config.option_registry.register_lazy( |
374 | + key, 'breezy.plugins.upload.cmds', member) |
375 | + |
376 | + |
377 | +register_option('upload_auto', 'auto_option') |
378 | +register_option('upload_auto_quiet', 'auto_quiet_option') |
379 | +register_option('upload_location', 'location_option') |
380 | +register_option('upload_revid_location', 'revid_location_option') |
381 | + |
382 | + |
383 | +commands.plugin_cmds.register_lazy( |
384 | + 'cmd_upload', [], 'breezy.plugins.upload.cmds') |
385 | + |
386 | + |
387 | +def auto_upload_hook(params): |
388 | + from ... import ( |
389 | + osutils, |
390 | + trace, |
391 | + transport, |
392 | + urlutils, |
393 | + ) |
394 | + from .cmds import ( |
395 | + BzrUploader, |
396 | + ) |
397 | + import sys |
398 | + source_branch = params.branch |
399 | + conf = source_branch.get_config_stack() |
400 | + destination = conf.get('upload_location') |
401 | + if destination is None: |
402 | + return |
403 | + auto_upload = conf.get('upload_auto') |
404 | + if not auto_upload: |
405 | + return |
406 | + quiet = conf.get('upload_auto_quiet') |
407 | + if not quiet: |
408 | + display_url = urlutils.unescape_for_display( |
409 | + destination, osutils.get_terminal_encoding()) |
410 | + trace.note('Automatically uploading to %s', display_url) |
411 | + to_transport = transport.get_transport(destination) |
412 | + last_revision = source_branch.last_revision() |
413 | + last_tree = source_branch.repository.revision_tree(last_revision) |
414 | + uploader = BzrUploader(source_branch, to_transport, sys.stdout, |
415 | + last_tree, last_revision, quiet=quiet) |
416 | + uploader.upload_tree() |
417 | + |
418 | + |
419 | +def install_auto_upload_hook(): |
420 | + hooks.install_lazy_named_hook( |
421 | + 'breezy.branch', 'Branch.hooks', |
422 | + 'post_change_branch_tip', auto_upload_hook, |
423 | + 'Auto upload code from a branch when it is changed.') |
424 | + |
425 | + |
426 | +install_auto_upload_hook() |
427 | + |
428 | + |
429 | +def load_tests(loader, basic_tests, pattern): |
430 | + # This module shouldn't define any tests but I don't know how to report |
431 | + # that. I prefer to update basic_tests with the other tests to detect |
432 | + # unwanted tests and I think that's sufficient. |
433 | + |
434 | + testmod_names = [ |
435 | + 'tests', |
436 | + ] |
437 | + basic_tests.addTest(loader.loadTestsFromModuleNames( |
438 | + ["%s.%s" % (__name__, tmn) for tmn in testmod_names])) |
439 | + return basic_tests |
440 | |
441 | === added file 'breezy/plugins/upload/cmds.py' |
442 | --- breezy/plugins/upload/cmds.py 1970-01-01 00:00:00 +0000 |
443 | +++ breezy/plugins/upload/cmds.py 2017-06-02 20:49:44 +0000 |
444 | @@ -0,0 +1,560 @@ |
445 | +# Copyright (C) 2011, 2012 Canonical Ltd |
446 | +# |
447 | +# This program is free software; you can redistribute it and/or modify |
448 | +# it under the terms of the GNU General Public License as published by |
449 | +# the Free Software Foundation; either version 2 of the License, or |
450 | +# (at your option) any later version. |
451 | +# |
452 | +# This program is distributed in the hope that it will be useful, |
453 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
454 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
455 | +# GNU General Public License for more details. |
456 | +# |
457 | +# You should have received a copy of the GNU General Public License |
458 | +# along with this program; if not, write to the Free Software |
459 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
460 | + |
461 | +"""bzr-upload command implementations.""" |
462 | + |
463 | +from __future__ import absolute_import |
464 | + |
465 | +from ... import ( |
466 | + branch, |
467 | + commands, |
468 | + config, |
469 | + lazy_import, |
470 | + option, |
471 | + ) |
472 | +lazy_import.lazy_import(globals(), """ |
473 | +import stat |
474 | +import sys |
475 | + |
476 | +from breezy import ( |
477 | + bzrdir, |
478 | + errors, |
479 | + globbing, |
480 | + ignores, |
481 | + osutils, |
482 | + revision, |
483 | + revisionspec, |
484 | + trace, |
485 | + transport, |
486 | + urlutils, |
487 | + workingtree, |
488 | + ) |
489 | +""") |
490 | + |
491 | +auto_option = config.Option( |
492 | + 'upload_auto', default=False, from_unicode=config.bool_from_store, |
493 | + help="""\ |
494 | +Whether upload should occur when the tip of the branch changes. |
495 | +""") |
496 | +auto_quiet_option = config.Option( |
497 | + 'upload_auto_quiet', default=False, from_unicode=config.bool_from_store, |
498 | + help="""\ |
499 | +Whether upload should occur quietly. |
500 | +""") |
501 | +location_option = config.Option( |
502 | + 'upload_location', default=None, |
503 | + help="""\ |
504 | +The url to upload the working tree to. |
505 | +""") |
506 | +revid_location_option = config.Option( |
507 | + 'upload_revid_location', default=u'.bzr-upload.revid', |
508 | + help="""\ |
509 | +The relative path to be used to store the uploaded revid. |
510 | + |
511 | +The only bzr-related info uploaded with the working tree is the corresponding |
512 | +revision id. The uploaded working tree is not linked to any other bzr data. |
513 | + |
514 | +If the layout of your remote server is such that you can't write in the |
515 | +root directory but only in the directories inside that root, you will need |
516 | +to use the 'upload_revid_location' configuration variable to specify the |
517 | +relative path to be used. That configuration variable can be specified in |
518 | +locations.conf or branch.conf. |
519 | + |
520 | +For example, given the following layout: |
521 | + |
522 | + Project/ |
523 | + private/ |
524 | + public/ |
525 | + |
526 | +you may have write access in 'private' and 'public' but in 'Project' |
527 | +itself. In that case, you can add the following in your locations.conf or |
528 | +branch.conf file: |
529 | + |
530 | + upload_revid_location = private/.bzr-upload.revid |
531 | +""") |
532 | + |
533 | + |
534 | +# FIXME: Add more tests around invalid paths or relative paths that doesn't |
535 | +# exist on remote (if only to get proper error messages) for |
536 | +# 'upload_revid_location' |
537 | + |
538 | + |
539 | +class BzrUploader(object): |
540 | + |
541 | + def __init__(self, branch, to_transport, outf, tree, rev_id, |
542 | + quiet=False): |
543 | + self.branch = branch |
544 | + self.to_transport = to_transport |
545 | + self.outf = outf |
546 | + self.tree = tree |
547 | + self.rev_id = rev_id |
548 | + self.quiet = quiet |
549 | + self._pending_deletions = [] |
550 | + self._pending_renames = [] |
551 | + self._uploaded_revid = None |
552 | + self._ignored = None |
553 | + |
554 | + def _up_stat(self, relpath): |
555 | + return self.to_transport.stat(urlutils.escape(relpath)) |
556 | + |
557 | + def _up_rename(self, old_path, new_path): |
558 | + return self.to_transport.rename(urlutils.escape(old_path), |
559 | + urlutils.escape(new_path)) |
560 | + |
561 | + def _up_delete(self, relpath): |
562 | + return self.to_transport.delete(urlutils.escape(relpath)) |
563 | + |
564 | + def _up_delete_tree(self, relpath): |
565 | + return self.to_transport.delete_tree(urlutils.escape(relpath)) |
566 | + |
567 | + def _up_mkdir(self, relpath, mode): |
568 | + return self.to_transport.mkdir(urlutils.escape(relpath), mode) |
569 | + |
570 | + def _up_rmdir(self, relpath): |
571 | + return self.to_transport.rmdir(urlutils.escape(relpath)) |
572 | + |
573 | + def _up_put_bytes(self, relpath, bytes, mode): |
574 | + self.to_transport.put_bytes(urlutils.escape(relpath), bytes, mode) |
575 | + |
576 | + def _up_get_bytes(self, relpath): |
577 | + return self.to_transport.get_bytes(urlutils.escape(relpath)) |
578 | + |
579 | + def set_uploaded_revid(self, rev_id): |
580 | + # XXX: Add tests for concurrent updates, etc. |
581 | + revid_path = self.branch.get_config_stack().get('upload_revid_location') |
582 | + self.to_transport.put_bytes(urlutils.escape(revid_path), rev_id) |
583 | + self._uploaded_revid = rev_id |
584 | + |
585 | + def get_uploaded_revid(self): |
586 | + if self._uploaded_revid is None: |
587 | + revid_path = self.branch.get_config_stack( |
588 | + ).get('upload_revid_location') |
589 | + try: |
590 | + self._uploaded_revid = self._up_get_bytes(revid_path) |
591 | + except errors.NoSuchFile: |
592 | + # We have not uploaded to here. |
593 | + self._uploaded_revid = revision.NULL_REVISION |
594 | + return self._uploaded_revid |
595 | + |
596 | + def _get_ignored(self): |
597 | + if self._ignored is None: |
598 | + try: |
599 | + ignore_file_path = '.bzrignore-upload' |
600 | + ignore_file_id = self.tree.path2id(ignore_file_path) |
601 | + ignore_file = self.tree.get_file(ignore_file_id, |
602 | + ignore_file_path) |
603 | + ignored_patterns = ignores.parse_ignore_file(ignore_file) |
604 | + except errors.NoSuchId: |
605 | + ignored_patterns = [] |
606 | + self._ignored = globbing.Globster(ignored_patterns) |
607 | + return self._ignored |
608 | + |
609 | + def is_ignored(self, relpath): |
610 | + glob = self._get_ignored() |
611 | + ignored = glob.match(relpath) |
612 | + import os |
613 | + if not ignored: |
614 | + # We still need to check that all parents are not ignored |
615 | + dir = os.path.dirname(relpath) |
616 | + while dir and not ignored: |
617 | + ignored = glob.match(dir) |
618 | + if not ignored: |
619 | + dir = os.path.dirname(dir) |
620 | + return ignored |
621 | + |
622 | + def upload_file(self, relpath, id, mode=None): |
623 | + if mode is None: |
624 | + if self.tree.is_executable(id): |
625 | + mode = 0775 |
626 | + else: |
627 | + mode = 0664 |
628 | + if not self.quiet: |
629 | + self.outf.write('Uploading %s\n' % relpath) |
630 | + self._up_put_bytes(relpath, self.tree.get_file_text(id), mode) |
631 | + |
632 | + def upload_file_robustly(self, relpath, id, mode=None): |
633 | + """Upload a file, clearing the way on the remote side. |
634 | + |
635 | + When doing a full upload, it may happen that a directory exists where |
636 | + we want to put our file. |
637 | + """ |
638 | + try: |
639 | + st = self._up_stat(relpath) |
640 | + if stat.S_ISDIR(st.st_mode): |
641 | + # A simple rmdir may not be enough |
642 | + if not self.quiet: |
643 | + self.outf.write('Clearing %s/%s\n' % ( |
644 | + self.to_transport.external_url(), relpath)) |
645 | + self._up_delete_tree(relpath) |
646 | + except errors.PathError: |
647 | + pass |
648 | + self.upload_file(relpath, id, mode) |
649 | + |
650 | + def make_remote_dir(self, relpath, mode=None): |
651 | + if mode is None: |
652 | + mode = 0775 |
653 | + self._up_mkdir(relpath, mode) |
654 | + |
655 | + def make_remote_dir_robustly(self, relpath, mode=None): |
656 | + """Create a remote directory, clearing the way on the remote side. |
657 | + |
658 | + When doing a full upload, it may happen that a file exists where we |
659 | + want to create our directory. |
660 | + """ |
661 | + try: |
662 | + st = self._up_stat(relpath) |
663 | + if not stat.S_ISDIR(st.st_mode): |
664 | + if not self.quiet: |
665 | + self.outf.write('Deleting %s/%s\n' % ( |
666 | + self.to_transport.external_url(), relpath)) |
667 | + self._up_delete(relpath) |
668 | + else: |
669 | + # Ok the remote dir already exists, nothing to do |
670 | + return |
671 | + except errors.PathError: |
672 | + pass |
673 | + self.make_remote_dir(relpath, mode) |
674 | + |
675 | + def delete_remote_file(self, relpath): |
676 | + if not self.quiet: |
677 | + self.outf.write('Deleting %s\n' % relpath) |
678 | + self._up_delete(relpath) |
679 | + |
680 | + def delete_remote_dir(self, relpath): |
681 | + if not self.quiet: |
682 | + self.outf.write('Deleting %s\n' % relpath) |
683 | + self._up_rmdir(relpath) |
684 | + # XXX: Add a test where a subdir is ignored but we still want to |
685 | + # delete the dir -- vila 100106 |
686 | + |
687 | + def delete_remote_dir_maybe(self, relpath): |
688 | + """Try to delete relpath, keeping failures to retry later.""" |
689 | + try: |
690 | + self._up_rmdir(relpath) |
691 | + # any kind of PathError would be OK, though we normally expect |
692 | + # DirectoryNotEmpty |
693 | + except errors.PathError: |
694 | + self._pending_deletions.append(relpath) |
695 | + |
696 | + def finish_deletions(self): |
697 | + if self._pending_deletions: |
698 | + # Process the previously failed deletions in reverse order to |
699 | + # delete children before parents |
700 | + for relpath in reversed(self._pending_deletions): |
701 | + self._up_rmdir(relpath) |
702 | + # The following shouldn't be needed since we use it once per |
703 | + # upload, but better safe than sorry ;-) |
704 | + self._pending_deletions = [] |
705 | + |
706 | + def rename_remote(self, old_relpath, new_relpath): |
707 | + """Rename a remote file or directory taking care of collisions. |
708 | + |
709 | + To avoid collisions during bulk renames, each renamed target is |
710 | + temporarily assigned a unique name. When all renames have been done, |
711 | + each target get its proper name. |
712 | + """ |
713 | + # We generate a sufficiently random name to *assume* that |
714 | + # no collisions will occur and don't worry about it (nor |
715 | + # handle it). |
716 | + import os |
717 | + import random |
718 | + import time |
719 | + |
720 | + stamp = '.tmp.%.9f.%d.%d' % (time.time(), |
721 | + os.getpid(), |
722 | + random.randint(0,0x7FFFFFFF)) |
723 | + if not self.quiet: |
724 | + self.outf.write('Renaming %s to %s\n' % (old_relpath, new_relpath)) |
725 | + self._up_rename(old_relpath, stamp) |
726 | + self._pending_renames.append((stamp, new_relpath)) |
727 | + |
728 | + def finish_renames(self): |
729 | + for (stamp, new_path) in self._pending_renames: |
730 | + self._up_rename(stamp, new_path) |
731 | + # The following shouldn't be needed since we use it once per upload, |
732 | + # but better safe than sorry ;-) |
733 | + self._pending_renames = [] |
734 | + |
735 | + def upload_full_tree(self): |
736 | + self.to_transport.ensure_base() # XXX: Handle errors (add |
737 | + # --create-prefix option ?) |
738 | + self.tree.lock_read() |
739 | + try: |
740 | + for relpath, ie in self.tree.iter_entries_by_dir(): |
741 | + if relpath in ('', '.bzrignore', '.bzrignore-upload'): |
742 | + # skip root ('') |
743 | + # .bzrignore and .bzrignore-upload have no meaning outside |
744 | + # a working tree so do not upload them |
745 | + continue |
746 | + if self.is_ignored(relpath): |
747 | + if not self.quiet: |
748 | + self.outf.write('Ignoring %s\n' % relpath) |
749 | + continue |
750 | + if ie.kind == 'file': |
751 | + self.upload_file_robustly(relpath, ie.file_id) |
752 | + elif ie.kind == 'directory': |
753 | + self.make_remote_dir_robustly(relpath) |
754 | + elif ie.kind == 'symlink': |
755 | + if not self.quiet: |
756 | + target = self.tree.path_content_summary(relpath)[3] |
757 | + self.outf.write('Not uploading symlink %s -> %s\n' |
758 | + % (relpath, target)) |
759 | + else: |
760 | + raise NotImplementedError |
761 | + self.set_uploaded_revid(self.rev_id) |
762 | + finally: |
763 | + self.tree.unlock() |
764 | + |
765 | + def upload_tree(self): |
766 | + # If we can't find the revid file on the remote location, upload the |
767 | + # full tree instead |
768 | + rev_id = self.get_uploaded_revid() |
769 | + |
770 | + if rev_id == revision.NULL_REVISION: |
771 | + if not self.quiet: |
772 | + self.outf.write('No uploaded revision id found,' |
773 | + ' switching to full upload\n') |
774 | + self.upload_full_tree() |
775 | + # We're done |
776 | + return |
777 | + |
778 | + # Check if the revision hasn't already been uploaded |
779 | + if rev_id == self.rev_id: |
780 | + if not self.quiet: |
781 | + self.outf.write('Remote location already up to date\n') |
782 | + |
783 | + from_tree = self.branch.repository.revision_tree(rev_id) |
784 | + self.to_transport.ensure_base() # XXX: Handle errors (add |
785 | + # --create-prefix option ?) |
786 | + changes = self.tree.changes_from(from_tree) |
787 | + self.tree.lock_read() |
788 | + try: |
789 | + for (path, id, kind) in changes.removed: |
790 | + if self.is_ignored(path): |
791 | + if not self.quiet: |
792 | + self.outf.write('Ignoring %s\n' % path) |
793 | + continue |
794 | + if kind is 'file': |
795 | + self.delete_remote_file(path) |
796 | + elif kind is 'directory': |
797 | + self.delete_remote_dir_maybe(path) |
798 | + elif kind == 'symlink': |
799 | + if not self.quiet: |
800 | + target = self.tree.path_content_summary(path)[3] |
801 | + self.outf.write('Not deleting remote symlink %s -> %s\n' |
802 | + % (path, target)) |
803 | + else: |
804 | + raise NotImplementedError |
805 | + |
806 | + for (old_path, new_path, id, kind, |
807 | + content_change, exec_change) in changes.renamed: |
808 | + if self.is_ignored(old_path) and self.is_ignored(new_path): |
809 | + if not self.quiet: |
810 | + self.outf.write('Ignoring %s\n' % old_path) |
811 | + self.outf.write('Ignoring %s\n' % new_path) |
812 | + continue |
813 | + if content_change: |
814 | + # We update the old_path content because renames and |
815 | + # deletions are differed. |
816 | + self.upload_file(old_path, id) |
817 | + if kind == 'symlink': |
818 | + if not self.quiet: |
819 | + self.outf.write('Not renaming remote symlink %s to %s\n' |
820 | + % (old_path, new_path)) |
821 | + else: |
822 | + self.rename_remote(old_path, new_path) |
823 | + self.finish_renames() |
824 | + self.finish_deletions() |
825 | + |
826 | + for (path, id, old_kind, new_kind) in changes.kind_changed: |
827 | + if self.is_ignored(path): |
828 | + if not self.quiet: |
829 | + self.outf.write('Ignoring %s\n' % path) |
830 | + continue |
831 | + if old_kind == 'file': |
832 | + self.delete_remote_file(path) |
833 | + elif old_kind == 'directory': |
834 | + self.delete_remote_dir(path) |
835 | + else: |
836 | + raise NotImplementedError |
837 | + |
838 | + if new_kind == 'file': |
839 | + self.upload_file(path, id) |
840 | + elif new_kind is 'directory': |
841 | + self.make_remote_dir(path) |
842 | + else: |
843 | + raise NotImplementedError |
844 | + |
845 | + for (path, id, kind) in changes.added: |
846 | + if self.is_ignored(path): |
847 | + if not self.quiet: |
848 | + self.outf.write('Ignoring %s\n' % path) |
849 | + continue |
850 | + if kind == 'file': |
851 | + self.upload_file(path, id) |
852 | + elif kind == 'directory': |
853 | + self.make_remote_dir(path) |
854 | + elif kind == 'symlink': |
855 | + if not self.quiet: |
856 | + target = self.tree.path_content_summary(path)[3] |
857 | + self.outf.write('Not uploading symlink %s -> %s\n' |
858 | + % (path, target)) |
859 | + else: |
860 | + raise NotImplementedError |
861 | + |
862 | + # XXX: Add a test for exec_change |
863 | + for (path, id, kind, |
864 | + content_change, exec_change) in changes.modified: |
865 | + if self.is_ignored(path): |
866 | + if not self.quiet: |
867 | + self.outf.write('Ignoring %s\n' % path) |
868 | + continue |
869 | + if kind is 'file': |
870 | + self.upload_file(path, id) |
871 | + else: |
872 | + raise NotImplementedError |
873 | + |
874 | + self.set_uploaded_revid(self.rev_id) |
875 | + finally: |
876 | + self.tree.unlock() |
877 | + |
878 | + |
879 | +class CannotUploadToWorkingTree(errors.BzrCommandError): |
880 | + |
881 | + _fmt = 'Cannot upload to a bzr managed working tree: %(url)s".' |
882 | + |
883 | + |
884 | +class DivergedUploadedTree(errors.BzrCommandError): |
885 | + |
886 | + _fmt = ("Your branch (%(revid)s)" |
887 | + " and the uploaded tree (%(uploaded_revid)s) have diverged: ") |
888 | + |
889 | + |
890 | +class cmd_upload(commands.Command): |
891 | + """Upload a working tree, as a whole or incrementally. |
892 | + |
893 | + If no destination is specified use the last one used. |
894 | + If no revision is specified upload the changes since the last upload. |
895 | + |
896 | + Changes include files added, renamed, modified or removed. |
897 | + """ |
898 | + _see_also = ['plugins/upload'] |
899 | + takes_args = ['location?'] |
900 | + takes_options = [ |
901 | + 'revision', |
902 | + 'remember', |
903 | + 'overwrite', |
904 | + option.Option('full', 'Upload the full working tree.'), |
905 | + option.Option('quiet', 'Do not output what is being done.', |
906 | + short_name='q'), |
907 | + option.Option('directory', |
908 | + help='Branch to upload from, ' |
909 | + 'rather than the one containing the working directory.', |
910 | + short_name='d', |
911 | + type=unicode, |
912 | + ), |
913 | + option.Option('auto', |
914 | + 'Trigger an upload from this branch whenever the tip ' |
915 | + 'revision changes.') |
916 | + ] |
917 | + |
918 | + def run(self, location=None, full=False, revision=None, remember=None, |
919 | + directory=None, quiet=False, auto=None, overwrite=False |
920 | + ): |
921 | + if directory is None: |
922 | + directory = u'.' |
923 | + |
924 | + (wt, branch, |
925 | + relpath) = bzrdir.BzrDir.open_containing_tree_or_branch(directory) |
926 | + |
927 | + if wt: |
928 | + wt.lock_read() |
929 | + locked = wt |
930 | + else: |
931 | + branch.lock_read() |
932 | + locked = branch |
933 | + try: |
934 | + if wt: |
935 | + changes = wt.changes_from(wt.basis_tree()) |
936 | + |
937 | + if revision is None and changes.has_changed(): |
938 | + raise errors.UncommittedChanges(wt) |
939 | + |
940 | + conf = branch.get_config_stack() |
941 | + if location is None: |
942 | + stored_loc = conf.get('upload_location') |
943 | + if stored_loc is None: |
944 | + raise errors.BzrCommandError( |
945 | + 'No upload location known or specified.') |
946 | + else: |
947 | + # FIXME: Not currently tested |
948 | + display_url = urlutils.unescape_for_display(stored_loc, |
949 | + self.outf.encoding) |
950 | + self.outf.write("Using saved location: %s\n" % display_url) |
951 | + location = stored_loc |
952 | + |
953 | + to_transport = transport.get_transport(location) |
954 | + |
955 | + # Check that we are not uploading to a existing working tree. |
956 | + try: |
957 | + to_bzr_dir = bzrdir.BzrDir.open_from_transport(to_transport) |
958 | + has_wt = to_bzr_dir.has_workingtree() |
959 | + except errors.NotBranchError: |
960 | + has_wt = False |
961 | + except errors.NotLocalUrl: |
962 | + # The exception raised is a bit weird... but that's life. |
963 | + has_wt = True |
964 | + |
965 | + if has_wt: |
966 | + raise CannotUploadToWorkingTree(url=location) |
967 | + if revision is None: |
968 | + rev_id = branch.last_revision() |
969 | + else: |
970 | + if len(revision) != 1: |
971 | + raise errors.BzrCommandError( |
972 | + 'bzr upload --revision takes exactly 1 argument') |
973 | + rev_id = revision[0].in_history(branch).rev_id |
974 | + |
975 | + tree = branch.repository.revision_tree(rev_id) |
976 | + |
977 | + uploader = BzrUploader(branch, to_transport, self.outf, tree, |
978 | + rev_id, quiet=quiet) |
979 | + |
980 | + if not overwrite: |
981 | + prev_uploaded_rev_id = uploader.get_uploaded_revid() |
982 | + graph = branch.repository.get_graph() |
983 | + if not graph.is_ancestor(prev_uploaded_rev_id, rev_id): |
984 | + raise DivergedUploadedTree( |
985 | + revid=rev_id, uploaded_revid=prev_uploaded_rev_id) |
986 | + if full: |
987 | + uploader.upload_full_tree() |
988 | + else: |
989 | + uploader.upload_tree() |
990 | + finally: |
991 | + locked.unlock() |
992 | + |
993 | + # We uploaded successfully, remember it |
994 | + branch.lock_write() |
995 | + try: |
996 | + upload_location = conf.get('upload_location') |
997 | + if upload_location is None or remember: |
998 | + conf.set('upload_location', |
999 | + urlutils.unescape(to_transport.base)) |
1000 | + if auto is not None: |
1001 | + conf.set('upload_auto', auto) |
1002 | + finally: |
1003 | + branch.unlock() |
1004 | + |
1005 | |
1006 | === added directory 'breezy/plugins/upload/tests' |
1007 | === added file 'breezy/plugins/upload/tests/__init__.py' |
1008 | --- breezy/plugins/upload/tests/__init__.py 1970-01-01 00:00:00 +0000 |
1009 | +++ breezy/plugins/upload/tests/__init__.py 2017-06-02 20:49:44 +0000 |
1010 | @@ -0,0 +1,28 @@ |
1011 | +# Copyright (C) 2008 by Canonical Ltd |
1012 | +# |
1013 | +# This program is free software; you can redistribute it and/or modify |
1014 | +# it under the terms of the GNU General Public License as published by |
1015 | +# the Free Software Foundation; either version 2 of the License, or |
1016 | +# (at your option) any later version. |
1017 | +# |
1018 | +# This program is distributed in the hope that it will be useful, |
1019 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1020 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1021 | +# GNU General Public License for more details. |
1022 | +# |
1023 | +# You should have received a copy of the GNU General Public License |
1024 | +# along with this program; if not, write to the Free Software |
1025 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
1026 | + |
1027 | +def load_tests(loader, basic_tests, pattern): |
1028 | + # This module shouldn't define any tests but I don't know how to report |
1029 | + # that. I prefer to update basic_tests with the other tests to detect |
1030 | + # unwanted tests and I think that's sufficient. |
1031 | + |
1032 | + testmod_names = [ |
1033 | + 'test_auto_upload_hook', |
1034 | + 'test_upload', |
1035 | + ] |
1036 | + basic_tests.addTest(loader.loadTestsFromModuleNames( |
1037 | + ["%s.%s" % (__name__, tmn) for tmn in testmod_names])) |
1038 | + return basic_tests |
1039 | |
1040 | === added file 'breezy/plugins/upload/tests/test_auto_upload_hook.py' |
1041 | --- breezy/plugins/upload/tests/test_auto_upload_hook.py 1970-01-01 00:00:00 +0000 |
1042 | +++ breezy/plugins/upload/tests/test_auto_upload_hook.py 2017-06-02 20:49:44 +0000 |
1043 | @@ -0,0 +1,87 @@ |
1044 | +# Copyright (C) 2008, 2009, 2011, 2012 Canonical Ltd |
1045 | +# |
1046 | +# This program is free software; you can redistribute it and/or modify |
1047 | +# it under the terms of the GNU General Public License as published by |
1048 | +# the Free Software Foundation; either version 2 of the License, or |
1049 | +# (at your option) any later version. |
1050 | +# |
1051 | +# This program is distributed in the hope that it will be useful, |
1052 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1053 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1054 | +# GNU General Public License for more details. |
1055 | +# |
1056 | +# You should have received a copy of the GNU General Public License |
1057 | +# along with this program; if not, write to the Free Software |
1058 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
1059 | + |
1060 | +import os |
1061 | + |
1062 | +from .... import ( |
1063 | + tests, |
1064 | + ) |
1065 | + |
1066 | +from ... import ( |
1067 | + upload, |
1068 | + ) |
1069 | +from .. import ( |
1070 | + cmds, |
1071 | + ) |
1072 | + |
1073 | + |
1074 | +class AutoPushHookTests(tests.TestCaseWithTransport): |
1075 | + |
1076 | + def setUp(self): |
1077 | + super(AutoPushHookTests, self).setUp() |
1078 | + upload.install_auto_upload_hook() |
1079 | + |
1080 | + def make_start_branch(self): |
1081 | + self.wt = self.make_branch_and_tree('.') |
1082 | + self.build_tree(['a']) |
1083 | + self.wt.add(['a']) |
1084 | + self.wt.commit("one") |
1085 | + |
1086 | + |
1087 | +class AutoPushWithLocation(AutoPushHookTests): |
1088 | + |
1089 | + def setUp(self): |
1090 | + super(AutoPushWithLocation, self).setUp() |
1091 | + self.make_start_branch() |
1092 | + conf = self.wt.branch.get_config_stack() |
1093 | + conf.set('upload_auto', True) |
1094 | + conf.set('upload_location', self.get_url('target')) |
1095 | + conf.set('upload_auto_quiet', True) |
1096 | + |
1097 | + def test_auto_push_on_commit(self): |
1098 | + self.assertPathDoesNotExist('target') |
1099 | + self.build_tree(['b']) |
1100 | + self.wt.add(['b']) |
1101 | + self.wt.commit("two") |
1102 | + self.assertPathExists('target') |
1103 | + self.assertPathExists(os.path.join('target', 'a')) |
1104 | + self.assertPathExists(os.path.join('target', 'b')) |
1105 | + |
1106 | + def test_disable_auto_push(self): |
1107 | + self.assertPathDoesNotExist('target') |
1108 | + self.build_tree(['b']) |
1109 | + self.wt.add(['b']) |
1110 | + self.wt.commit("two") |
1111 | + self.wt.branch.get_config_stack().set('upload_auto', False) |
1112 | + self.build_tree(['c']) |
1113 | + self.wt.add(['c']) |
1114 | + self.wt.commit("three") |
1115 | + self.assertPathDoesNotExist(os.path.join('target', 'c')) |
1116 | + |
1117 | + |
1118 | +class AutoPushWithoutLocation(AutoPushHookTests): |
1119 | + |
1120 | + def setUp(self): |
1121 | + super(AutoPushWithoutLocation, self).setUp() |
1122 | + self.make_start_branch() |
1123 | + self.wt.branch.get_config_stack().set('upload_auto', True) |
1124 | + |
1125 | + def test_dont_push_if_no_location(self): |
1126 | + self.assertPathDoesNotExist('target') |
1127 | + self.build_tree(['b']) |
1128 | + self.wt.add(['b']) |
1129 | + self.wt.commit("two") |
1130 | + self.assertPathDoesNotExist('target') |
1131 | |
1132 | === added file 'breezy/plugins/upload/tests/test_upload.py' |
1133 | --- breezy/plugins/upload/tests/test_upload.py 1970-01-01 00:00:00 +0000 |
1134 | +++ breezy/plugins/upload/tests/test_upload.py 2017-06-02 20:49:44 +0000 |
1135 | @@ -0,0 +1,857 @@ |
1136 | +# Copyright (C) 2008-2012 Canonical Ltd |
1137 | +# |
1138 | +# This program is free software; you can redistribute it and/or modify |
1139 | +# it under the terms of the GNU General Public License as published by |
1140 | +# the Free Software Foundation; either version 2 of the License, or |
1141 | +# (at your option) any later version. |
1142 | +# |
1143 | +# This program is distributed in the hope that it will be useful, |
1144 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1145 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1146 | +# GNU General Public License for more details. |
1147 | +# |
1148 | +# You should have received a copy of the GNU General Public License |
1149 | +# along with this program; if not, write to the Free Software |
1150 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
1151 | + |
1152 | +import os |
1153 | +import sys |
1154 | + |
1155 | + |
1156 | +from .... import ( |
1157 | + bzrdir, |
1158 | + config, |
1159 | + errors, |
1160 | + osutils, |
1161 | + revisionspec, |
1162 | + tests, |
1163 | + transport, |
1164 | + workingtree, |
1165 | + uncommit, |
1166 | + ) |
1167 | +from ....tests import ( |
1168 | + features, |
1169 | + per_branch, |
1170 | + per_transport, |
1171 | + stub_sftp, |
1172 | + ) |
1173 | +from ....transport import ( |
1174 | + ftp, |
1175 | + sftp, |
1176 | + ) |
1177 | +from .. import ( |
1178 | + cmds, |
1179 | + ) |
1180 | + |
1181 | + |
1182 | +def get_transport_scenarios(): |
1183 | + result = [] |
1184 | + basis = per_transport.transport_test_permutations() |
1185 | + # Keep only the interesting ones for upload |
1186 | + for name, d in basis: |
1187 | + t_class = d['transport_class'] |
1188 | + if t_class in (ftp.FtpTransport, sftp.SFTPTransport): |
1189 | + result.append((name, d)) |
1190 | + try: |
1191 | + import breezy.plugins.local_test_server |
1192 | + from breezy.plugins.local_test_server import test_server |
1193 | + if False: |
1194 | + # XXX: Disable since we can't get chmod working for anonymous |
1195 | + # user |
1196 | + scenario = ('vsftpd', |
1197 | + {'transport_class': test_server.FtpTransport, |
1198 | + 'transport_server': test_server.Vsftpd, |
1199 | + }) |
1200 | + result.append(scenario) |
1201 | + from test_server import ProftpdFeature |
1202 | + if ProftpdFeature().available(): |
1203 | + scenario = ('proftpd', |
1204 | + {'transport_class': test_server.FtpTransport, |
1205 | + 'transport_server': test_server.Proftpd, |
1206 | + }) |
1207 | + result.append(scenario) |
1208 | + # XXX: add support for pyftpdlib |
1209 | + except ImportError: |
1210 | + pass |
1211 | + return result |
1212 | + |
1213 | + |
1214 | +def load_tests(loader, standard_tests, pattern): |
1215 | + """Multiply tests for tranport implementations.""" |
1216 | + result = loader.suiteClass() |
1217 | + |
1218 | + # one for each transport implementation |
1219 | + t_tests, remaining_tests = tests.split_suite_by_condition( |
1220 | + standard_tests, tests.condition_isinstance(( |
1221 | + TestFullUpload, |
1222 | + TestIncrementalUpload, |
1223 | + TestUploadFromRemoteBranch, |
1224 | + ))) |
1225 | + tests.multiply_tests(t_tests, get_transport_scenarios(), result) |
1226 | + |
1227 | + # one for each branch format |
1228 | + b_tests, remaining_tests = tests.split_suite_by_condition( |
1229 | + remaining_tests, tests.condition_isinstance(( |
1230 | + TestBranchUploadLocations, |
1231 | + ))) |
1232 | + tests.multiply_tests(b_tests, per_branch.branch_scenarios(), |
1233 | + result) |
1234 | + |
1235 | + # No parametrization for the remaining tests |
1236 | + result.addTests(remaining_tests) |
1237 | + |
1238 | + return result |
1239 | + |
1240 | + |
1241 | +class UploadUtilsMixin(object): |
1242 | + """Helper class to write upload tests. |
1243 | + |
1244 | + This class provides helpers to simplify test writing. The emphasis is on |
1245 | + easy test writing, so each tree modification is committed. This doesn't |
1246 | + preclude writing tests spawning several revisions to upload more complex |
1247 | + changes. |
1248 | + """ |
1249 | + |
1250 | + upload_dir = 'upload' |
1251 | + branch_dir = 'branch' |
1252 | + |
1253 | + def make_branch_and_working_tree(self): |
1254 | + t = transport.get_transport(self.branch_dir) |
1255 | + t.ensure_base() |
1256 | + branch = bzrdir.BzrDir.create_branch_convenience( |
1257 | + t.base, |
1258 | + format=bzrdir.format_registry.make_bzrdir('default'), |
1259 | + force_new_tree=False) |
1260 | + self.tree = branch.bzrdir.create_workingtree() |
1261 | + self.tree.commit('initial empty tree') |
1262 | + |
1263 | + def assertUpFileEqual(self, content, path, base=upload_dir): |
1264 | + self.assertFileEqual(content, osutils.pathjoin(base, path)) |
1265 | + |
1266 | + def assertUpPathModeEqual(self, path, expected_mode, base=upload_dir): |
1267 | + # FIXME: the tests needing that assertion should depend on the server |
1268 | + # ability to handle chmod so that they don't fail (or be skipped) |
1269 | + # against servers that can't. Note that some breezy transports define |
1270 | + # _can_roundtrip_unix_modebits in a incomplete way, this property |
1271 | + # should depend on both the client and the server, not the client only. |
1272 | + # But the client will know or can find if the server support chmod so |
1273 | + # that's the client that will report it anyway. |
1274 | + full_path = osutils.pathjoin(base, path) |
1275 | + st = os.stat(full_path) |
1276 | + mode = st.st_mode & 0777 |
1277 | + if expected_mode == mode: |
1278 | + return |
1279 | + raise AssertionError( |
1280 | + 'For path %s, mode is %s not %s' % |
1281 | + (full_path, oct(mode), oct(expected_mode))) |
1282 | + |
1283 | + def assertUpPathDoesNotExist(self, path, base=upload_dir): |
1284 | + self.assertPathDoesNotExist(osutils.pathjoin(base, path)) |
1285 | + |
1286 | + def assertUpPathExists(self, path, base=upload_dir): |
1287 | + self.assertPathExists(osutils.pathjoin(base, path)) |
1288 | + |
1289 | + def set_file_content(self, path, content, base=branch_dir): |
1290 | + f = file(osutils.pathjoin(base, path), 'wb') |
1291 | + try: |
1292 | + f.write(content) |
1293 | + finally: |
1294 | + f.close() |
1295 | + |
1296 | + def add_file(self, path, content, base=branch_dir): |
1297 | + self.set_file_content(path, content, base) |
1298 | + self.tree.add(path) |
1299 | + self.tree.commit('add file %s' % path) |
1300 | + |
1301 | + def modify_file(self, path, content, base=branch_dir): |
1302 | + self.set_file_content(path, content, base) |
1303 | + self.tree.commit('modify file %s' % path) |
1304 | + |
1305 | + def chmod_file(self, path, mode, base=branch_dir): |
1306 | + full_path = osutils.pathjoin(base, path) |
1307 | + os.chmod(full_path, mode) |
1308 | + self.tree.commit('change file %s mode to %s' % (path, oct(mode))) |
1309 | + |
1310 | + def delete_any(self, path, base=branch_dir): |
1311 | + self.tree.remove([path], keep_files=False) |
1312 | + self.tree.commit('delete %s' % path) |
1313 | + |
1314 | + def add_dir(self, path, base=branch_dir): |
1315 | + os.mkdir(osutils.pathjoin(base, path)) |
1316 | + self.tree.add(path) |
1317 | + self.tree.commit('add directory %s' % path) |
1318 | + |
1319 | + def rename_any(self, old_path, new_path): |
1320 | + self.tree.rename_one(old_path, new_path) |
1321 | + self.tree.commit('rename %s into %s' % (old_path, new_path)) |
1322 | + |
1323 | + def transform_dir_into_file(self, path, content, base=branch_dir): |
1324 | + osutils.delete_any(osutils.pathjoin(base, path)) |
1325 | + self.set_file_content(path, content, base) |
1326 | + self.tree.commit('change %s from dir to file' % path) |
1327 | + |
1328 | + def transform_file_into_dir(self, path, base=branch_dir): |
1329 | + # bzr can't handle that kind change in a single commit without an |
1330 | + # intervening bzr status (see bug #205636). |
1331 | + self.tree.remove([path], keep_files=False) |
1332 | + os.mkdir(osutils.pathjoin(base, path)) |
1333 | + self.tree.add(path) |
1334 | + self.tree.commit('change %s from file to dir' % path) |
1335 | + |
1336 | + def add_symlink(self, path, target, base=branch_dir): |
1337 | + self.requireFeature(features.SymlinkFeature) |
1338 | + os.symlink(target, osutils.pathjoin(base, path)) |
1339 | + self.tree.add(path) |
1340 | + self.tree.commit('add symlink %s -> %s' % (path, target)) |
1341 | + |
1342 | + def modify_symlink(self, path, target, base=branch_dir): |
1343 | + self.requireFeature(features.SymlinkFeature) |
1344 | + full_path = osutils.pathjoin(base, path) |
1345 | + os.unlink(full_path) |
1346 | + os.symlink(target, full_path) |
1347 | + self.tree.commit('modify symlink %s -> %s' % (path, target)) |
1348 | + |
1349 | + def _get_cmd_upload(self): |
1350 | + cmd = cmds.cmd_upload() |
1351 | + # We don't want to use run_bzr here because redirected output are a |
1352 | + # pain to debug. But we need to provides a valid outf. |
1353 | + # XXX: Should a bug against bzr be filled about that ? |
1354 | + |
1355 | + # Short story: we don't expect any output so we may just use stdout |
1356 | + cmd.outf = sys.stdout |
1357 | + return cmd |
1358 | + |
1359 | + def do_full_upload(self, *args, **kwargs): |
1360 | + upload = self._get_cmd_upload() |
1361 | + up_url = self.get_url(self.upload_dir) |
1362 | + if kwargs.get('directory', None) is None: |
1363 | + kwargs['directory'] = self.branch_dir |
1364 | + kwargs['full'] = True |
1365 | + kwargs['quiet'] = True |
1366 | + upload.run(up_url, *args, **kwargs) |
1367 | + |
1368 | + def do_incremental_upload(self, *args, **kwargs): |
1369 | + upload = self._get_cmd_upload() |
1370 | + up_url = self.get_url(self.upload_dir) |
1371 | + if kwargs.get('directory', None) is None: |
1372 | + kwargs['directory'] = self.branch_dir |
1373 | + kwargs['quiet'] = True |
1374 | + upload.run(up_url, *args, **kwargs) |
1375 | + |
1376 | + |
1377 | +class TestUploadMixin(UploadUtilsMixin): |
1378 | + """Helper class to share tests between full and incremental uploads.""" |
1379 | + |
1380 | + def _test_create_file(self, file_name): |
1381 | + self.make_branch_and_working_tree() |
1382 | + self.do_full_upload() |
1383 | + self.add_file(file_name, 'foo') |
1384 | + |
1385 | + self.do_upload() |
1386 | + |
1387 | + self.assertUpFileEqual('foo', file_name) |
1388 | + |
1389 | + def test_create_file(self): |
1390 | + self._test_create_file('hello') |
1391 | + |
1392 | + def test_unicode_create_file(self): |
1393 | + self.requireFeature(features.UnicodeFilenameFeature) |
1394 | + self._test_create_file(u'hell\u00d8') |
1395 | + |
1396 | + def _test_create_file_in_dir(self, dir_name, file_name): |
1397 | + self.make_branch_and_working_tree() |
1398 | + self.do_full_upload() |
1399 | + self.add_dir(dir_name) |
1400 | + fpath = '%s/%s' % (dir_name, file_name) |
1401 | + self.add_file(fpath, 'baz') |
1402 | + |
1403 | + self.assertUpPathDoesNotExist(fpath) |
1404 | + |
1405 | + self.do_upload() |
1406 | + |
1407 | + self.assertUpFileEqual('baz', fpath) |
1408 | + self.assertUpPathModeEqual(dir_name, 0775) |
1409 | + |
1410 | + def test_create_file_in_dir(self): |
1411 | + self._test_create_file_in_dir('dir', 'goodbye') |
1412 | + |
1413 | + def test_unicode_create_file_in_dir(self): |
1414 | + self.requireFeature(features.UnicodeFilenameFeature) |
1415 | + self._test_create_file_in_dir(u'dir\u00d8', u'goodbye\u00d8') |
1416 | + |
1417 | + def test_modify_file(self): |
1418 | + self.make_branch_and_working_tree() |
1419 | + self.add_file('hello', 'foo') |
1420 | + self.do_full_upload() |
1421 | + self.modify_file('hello', 'bar') |
1422 | + |
1423 | + self.assertUpFileEqual('foo', 'hello') |
1424 | + |
1425 | + self.do_upload() |
1426 | + |
1427 | + self.assertUpFileEqual('bar', 'hello') |
1428 | + |
1429 | + def _test_rename_one_file(self, old_name, new_name): |
1430 | + self.make_branch_and_working_tree() |
1431 | + self.add_file(old_name, 'foo') |
1432 | + self.do_full_upload() |
1433 | + self.rename_any(old_name, new_name) |
1434 | + |
1435 | + self.assertUpFileEqual('foo', old_name) |
1436 | + |
1437 | + self.do_upload() |
1438 | + |
1439 | + self.assertUpFileEqual('foo', new_name) |
1440 | + |
1441 | + def test_rename_one_file(self): |
1442 | + self._test_rename_one_file('hello', 'goodbye') |
1443 | + |
1444 | + def test_unicode_rename_one_file(self): |
1445 | + self.requireFeature(features.UnicodeFilenameFeature) |
1446 | + self._test_rename_one_file(u'hello\u00d8', u'goodbye\u00d8') |
1447 | + |
1448 | + def test_rename_and_change_file(self): |
1449 | + self.make_branch_and_working_tree() |
1450 | + self.add_file('hello', 'foo') |
1451 | + self.do_full_upload() |
1452 | + self.rename_any('hello', 'goodbye') |
1453 | + self.modify_file('goodbye', 'bar') |
1454 | + |
1455 | + self.assertUpFileEqual('foo', 'hello') |
1456 | + |
1457 | + self.do_upload() |
1458 | + |
1459 | + self.assertUpFileEqual('bar', 'goodbye') |
1460 | + |
1461 | + def test_rename_two_files(self): |
1462 | + self.make_branch_and_working_tree() |
1463 | + self.add_file('a', 'foo') |
1464 | + self.add_file('b', 'qux') |
1465 | + self.do_full_upload() |
1466 | + # We rely on the assumption that bzr will topologically sort the |
1467 | + # renames which will cause a -> b to appear *before* b -> c |
1468 | + self.rename_any('b', 'c') |
1469 | + self.rename_any('a', 'b') |
1470 | + |
1471 | + self.assertUpFileEqual('foo', 'a') |
1472 | + self.assertUpFileEqual('qux', 'b') |
1473 | + |
1474 | + self.do_upload() |
1475 | + |
1476 | + self.assertUpFileEqual('foo', 'b') |
1477 | + self.assertUpFileEqual('qux', 'c') |
1478 | + |
1479 | + def test_upload_revision(self): |
1480 | + self.make_branch_and_working_tree() # rev1 |
1481 | + self.do_full_upload() |
1482 | + self.add_file('hello', 'foo') # rev2 |
1483 | + self.modify_file('hello', 'bar') # rev3 |
1484 | + |
1485 | + self.assertUpPathDoesNotExist('hello') |
1486 | + |
1487 | + revspec = revisionspec.RevisionSpec.from_string('2') |
1488 | + self.do_upload(revision=[revspec]) |
1489 | + |
1490 | + self.assertUpFileEqual('foo', 'hello') |
1491 | + |
1492 | + def test_no_upload_when_changes(self): |
1493 | + self.make_branch_and_working_tree() |
1494 | + self.add_file('a', 'foo') |
1495 | + self.set_file_content('a', 'bar') |
1496 | + |
1497 | + self.assertRaises(errors.UncommittedChanges, self.do_upload) |
1498 | + |
1499 | + def test_no_upload_when_conflicts(self): |
1500 | + self.make_branch_and_working_tree() |
1501 | + self.add_file('a', 'foo') |
1502 | + self.run_bzr('branch branch other') |
1503 | + self.modify_file('a', 'bar') |
1504 | + other_tree = workingtree.WorkingTree.open('other') |
1505 | + self.set_file_content('a', 'baz', 'other/') |
1506 | + other_tree.commit('modify file a') |
1507 | + |
1508 | + self.run_bzr('merge -d branch other', retcode=1) |
1509 | + |
1510 | + self.assertRaises(errors.UncommittedChanges, self.do_upload) |
1511 | + |
1512 | + def _test_change_file_into_dir(self, file_name): |
1513 | + self.make_branch_and_working_tree() |
1514 | + self.add_file(file_name, 'foo') |
1515 | + self.do_full_upload() |
1516 | + self.transform_file_into_dir(file_name) |
1517 | + fpath = '%s/%s' % (file_name, 'file') |
1518 | + self.add_file(fpath, 'bar') |
1519 | + |
1520 | + self.assertUpFileEqual('foo', file_name) |
1521 | + |
1522 | + self.do_upload() |
1523 | + |
1524 | + self.assertUpFileEqual('bar', fpath) |
1525 | + |
1526 | + def test_change_file_into_dir(self): |
1527 | + self._test_change_file_into_dir('hello') |
1528 | + |
1529 | + def test_unicode_change_file_into_dir(self): |
1530 | + self.requireFeature(features.UnicodeFilenameFeature) |
1531 | + self._test_change_file_into_dir(u'hello\u00d8') |
1532 | + |
1533 | + def test_change_dir_into_file(self): |
1534 | + self.make_branch_and_working_tree() |
1535 | + self.add_dir('hello') |
1536 | + self.add_file('hello/file', 'foo') |
1537 | + self.do_full_upload() |
1538 | + self.delete_any('hello/file') |
1539 | + self.transform_dir_into_file('hello', 'bar') |
1540 | + |
1541 | + self.assertUpFileEqual('foo', 'hello/file') |
1542 | + |
1543 | + self.do_upload() |
1544 | + |
1545 | + self.assertUpFileEqual('bar', 'hello') |
1546 | + |
1547 | + def _test_make_file_executable(self, file_name): |
1548 | + self.make_branch_and_working_tree() |
1549 | + self.add_file(file_name, 'foo') |
1550 | + self.chmod_file(file_name, 0664) |
1551 | + self.do_full_upload() |
1552 | + self.chmod_file(file_name, 0755) |
1553 | + |
1554 | + self.assertUpPathModeEqual(file_name, 0664) |
1555 | + |
1556 | + self.do_upload() |
1557 | + |
1558 | + self.assertUpPathModeEqual(file_name, 0775) |
1559 | + |
1560 | + def test_make_file_executable(self): |
1561 | + self._test_make_file_executable('hello') |
1562 | + |
1563 | + def test_unicode_make_file_executable(self): |
1564 | + self.requireFeature(features.UnicodeFilenameFeature) |
1565 | + self._test_make_file_executable(u'hello\u00d8') |
1566 | + |
1567 | + def test_create_symlink(self): |
1568 | + self.make_branch_and_working_tree() |
1569 | + self.do_full_upload() |
1570 | + self.add_symlink('link', 'target') |
1571 | + |
1572 | + self.do_upload() |
1573 | + |
1574 | + self.assertUpPathDoesNotExist('link') |
1575 | + |
1576 | + def test_rename_symlink(self): |
1577 | + self.make_branch_and_working_tree() |
1578 | + old_name, new_name = 'old-link', 'new-link' |
1579 | + self.add_symlink(old_name, 'target') |
1580 | + self.do_full_upload() |
1581 | + self.rename_any(old_name, new_name) |
1582 | + |
1583 | + self.do_upload() |
1584 | + |
1585 | + self.assertUpPathDoesNotExist(old_name) |
1586 | + self.assertUpPathDoesNotExist(new_name) |
1587 | + |
1588 | + def get_upload_auto(self): |
1589 | + # We need a fresh branch to check what has been saved on disk |
1590 | + b = bzrdir.BzrDir.open(self.tree.basedir).open_branch() |
1591 | + return b.get_config_stack().get('upload_auto') |
1592 | + |
1593 | + def test_upload_auto(self): |
1594 | + """Test that upload --auto sets the upload_auto option""" |
1595 | + self.make_branch_and_working_tree() |
1596 | + |
1597 | + self.add_file('hello', 'foo') |
1598 | + self.assertFalse(self.get_upload_auto()) |
1599 | + self.do_full_upload(auto=True) |
1600 | + self.assertUpFileEqual('foo', 'hello') |
1601 | + self.assertTrue(self.get_upload_auto()) |
1602 | + |
1603 | + # and check that it stays set until it is unset |
1604 | + self.add_file('bye', 'bar') |
1605 | + self.do_full_upload() |
1606 | + self.assertUpFileEqual('bar', 'bye') |
1607 | + self.assertTrue(self.get_upload_auto()) |
1608 | + |
1609 | + def test_upload_noauto(self): |
1610 | + """Test that upload --no-auto unsets the upload_auto option""" |
1611 | + self.make_branch_and_working_tree() |
1612 | + |
1613 | + self.add_file('hello', 'foo') |
1614 | + self.do_full_upload(auto=True) |
1615 | + self.assertUpFileEqual('foo', 'hello') |
1616 | + self.assertTrue(self.get_upload_auto()) |
1617 | + |
1618 | + self.add_file('bye', 'bar') |
1619 | + self.do_full_upload(auto=False) |
1620 | + self.assertUpFileEqual('bar', 'bye') |
1621 | + self.assertFalse(self.get_upload_auto()) |
1622 | + |
1623 | + # and check that it stays unset until it is set |
1624 | + self.add_file('again', 'baz') |
1625 | + self.do_full_upload() |
1626 | + self.assertUpFileEqual('baz', 'again') |
1627 | + self.assertFalse(self.get_upload_auto()) |
1628 | + |
1629 | + def test_upload_from_subdir(self): |
1630 | + self.make_branch_and_working_tree() |
1631 | + self.build_tree(['branch/foo/', 'branch/foo/bar']) |
1632 | + self.tree.add(['foo/', 'foo/bar']) |
1633 | + self.tree.commit("Add directory") |
1634 | + self.do_full_upload(directory='branch/foo') |
1635 | + |
1636 | + def test_upload_revid_path_in_dir(self): |
1637 | + self.make_branch_and_working_tree() |
1638 | + self.add_dir('dir') |
1639 | + self.add_file('dir/goodbye', 'baz') |
1640 | + |
1641 | + revid_path = 'dir/revid-path' |
1642 | + self.tree.branch.get_config_stack( |
1643 | + ).set('upload_revid_location', revid_path) |
1644 | + self.assertUpPathDoesNotExist(revid_path) |
1645 | + |
1646 | + self.do_full_upload() |
1647 | + |
1648 | + self.add_file('dir/hello', 'foo') |
1649 | + |
1650 | + self.do_upload() |
1651 | + |
1652 | + self.assertUpPathExists(revid_path) |
1653 | + self.assertUpFileEqual('baz', 'dir/goodbye') |
1654 | + self.assertUpFileEqual('foo', 'dir/hello') |
1655 | + |
1656 | + def test_ignore_file(self): |
1657 | + self.make_branch_and_working_tree() |
1658 | + self.do_full_upload() |
1659 | + self.add_file('.bzrignore-upload', 'foo') |
1660 | + self.add_file('foo', 'bar') |
1661 | + |
1662 | + self.do_upload() |
1663 | + |
1664 | + self.assertUpPathDoesNotExist('foo') |
1665 | + |
1666 | + def test_ignore_regexp(self): |
1667 | + self.make_branch_and_working_tree() |
1668 | + self.do_full_upload() |
1669 | + self.add_file('.bzrignore-upload', 'f*') |
1670 | + self.add_file('foo', 'bar') |
1671 | + |
1672 | + self.do_upload() |
1673 | + |
1674 | + self.assertUpPathDoesNotExist('foo') |
1675 | + |
1676 | + def test_ignore_directory(self): |
1677 | + self.make_branch_and_working_tree() |
1678 | + self.do_full_upload() |
1679 | + self.add_file('.bzrignore-upload', 'dir') |
1680 | + self.add_dir('dir') |
1681 | + |
1682 | + self.do_upload() |
1683 | + |
1684 | + self.assertUpPathDoesNotExist('dir') |
1685 | + |
1686 | + def test_ignore_nested_directory(self): |
1687 | + self.make_branch_and_working_tree() |
1688 | + self.do_full_upload() |
1689 | + self.add_file('.bzrignore-upload', 'dir') |
1690 | + self.add_dir('dir') |
1691 | + self.add_dir('dir/foo') |
1692 | + self.add_file('dir/foo/bar', 'bar contents') |
1693 | + |
1694 | + self.do_upload() |
1695 | + |
1696 | + self.assertUpPathDoesNotExist('dir') |
1697 | + self.assertUpPathDoesNotExist('dir/foo/bar') |
1698 | + |
1699 | + def test_ignore_change_file_into_dir(self): |
1700 | + self.make_branch_and_working_tree() |
1701 | + self.add_file('hello', 'foo') |
1702 | + self.do_full_upload() |
1703 | + self.add_file('.bzrignore-upload', 'hello') |
1704 | + self.transform_file_into_dir('hello') |
1705 | + self.add_file('hello/file', 'bar') |
1706 | + |
1707 | + self.assertUpFileEqual('foo', 'hello') |
1708 | + |
1709 | + self.do_upload() |
1710 | + |
1711 | + self.assertUpFileEqual('foo', 'hello') |
1712 | + |
1713 | + def test_ignore_change_dir_into_file(self): |
1714 | + self.make_branch_and_working_tree() |
1715 | + self.add_dir('hello') |
1716 | + self.add_file('hello/file', 'foo') |
1717 | + self.do_full_upload() |
1718 | + |
1719 | + self.add_file('.bzrignore-upload', 'hello') |
1720 | + self.delete_any('hello/file') |
1721 | + self.transform_dir_into_file('hello', 'bar') |
1722 | + |
1723 | + self.assertUpFileEqual('foo', 'hello/file') |
1724 | + |
1725 | + self.do_upload() |
1726 | + |
1727 | + self.assertUpFileEqual('foo', 'hello/file') |
1728 | + |
1729 | + def test_ignore_delete_dir_in_subdir(self): |
1730 | + self.make_branch_and_working_tree() |
1731 | + self.add_dir('dir') |
1732 | + self.add_dir('dir/subdir') |
1733 | + self.add_file('dir/subdir/a', 'foo') |
1734 | + self.do_full_upload() |
1735 | + self.add_file('.bzrignore-upload', 'dir/subdir') |
1736 | + self.rename_any('dir/subdir/a', 'dir/a') |
1737 | + self.delete_any('dir/subdir') |
1738 | + |
1739 | + self.assertUpFileEqual('foo', 'dir/subdir/a') |
1740 | + |
1741 | + self.do_upload() |
1742 | + |
1743 | + # The file in the dir is not ignored. This a bit contrived but |
1744 | + # indicates that we may encounter problems when ignored items appear |
1745 | + # and disappear... -- vila 100106 |
1746 | + self.assertUpFileEqual('foo', 'dir/a') |
1747 | + |
1748 | + |
1749 | +class TestFullUpload(tests.TestCaseWithTransport, TestUploadMixin): |
1750 | + |
1751 | + do_upload = TestUploadMixin.do_full_upload |
1752 | + |
1753 | + def test_full_upload_empty_tree(self): |
1754 | + self.make_branch_and_working_tree() |
1755 | + |
1756 | + self.do_full_upload() |
1757 | + |
1758 | + revid_path = self.tree.branch.get_config_stack( |
1759 | + ).get('upload_revid_location') |
1760 | + self.assertUpPathExists(revid_path) |
1761 | + |
1762 | + def test_invalid_revspec(self): |
1763 | + self.make_branch_and_working_tree() |
1764 | + rev1 = revisionspec.RevisionSpec.from_string('1') |
1765 | + rev2 = revisionspec.RevisionSpec.from_string('2') |
1766 | + |
1767 | + self.assertRaises(errors.BzrCommandError, |
1768 | + self.do_incremental_upload, revision=[rev1, rev2]) |
1769 | + |
1770 | + def test_create_remote_dir_twice(self): |
1771 | + self.make_branch_and_working_tree() |
1772 | + self.add_dir('dir') |
1773 | + self.do_full_upload() |
1774 | + self.add_file('dir/goodbye', 'baz') |
1775 | + |
1776 | + self.assertUpPathDoesNotExist('dir/goodbye') |
1777 | + |
1778 | + self.do_full_upload() |
1779 | + |
1780 | + self.assertUpFileEqual('baz', 'dir/goodbye') |
1781 | + self.assertUpPathModeEqual('dir', 0775) |
1782 | + |
1783 | + |
1784 | +class TestIncrementalUpload(tests.TestCaseWithTransport, TestUploadMixin): |
1785 | + |
1786 | + do_upload = TestUploadMixin.do_incremental_upload |
1787 | + |
1788 | + # XXX: full upload doesn't handle deletions.... |
1789 | + |
1790 | + def test_delete_one_file(self): |
1791 | + self.make_branch_and_working_tree() |
1792 | + self.add_file('hello', 'foo') |
1793 | + self.do_full_upload() |
1794 | + self.delete_any('hello') |
1795 | + |
1796 | + self.assertUpFileEqual('foo', 'hello') |
1797 | + |
1798 | + self.do_upload() |
1799 | + |
1800 | + self.assertUpPathDoesNotExist('hello') |
1801 | + |
1802 | + def test_delete_dir_and_subdir(self): |
1803 | + self.make_branch_and_working_tree() |
1804 | + self.add_dir('dir') |
1805 | + self.add_dir('dir/subdir') |
1806 | + self.add_file('dir/subdir/a', 'foo') |
1807 | + self.do_full_upload() |
1808 | + self.rename_any('dir/subdir/a', 'a') |
1809 | + self.delete_any('dir/subdir') |
1810 | + self.delete_any('dir') |
1811 | + |
1812 | + self.assertUpFileEqual('foo', 'dir/subdir/a') |
1813 | + |
1814 | + self.do_upload() |
1815 | + |
1816 | + self.assertUpPathDoesNotExist('dir/subdir/a') |
1817 | + self.assertUpPathDoesNotExist('dir/subdir') |
1818 | + self.assertUpPathDoesNotExist('dir') |
1819 | + self.assertUpFileEqual('foo', 'a') |
1820 | + |
1821 | + def test_delete_one_file_rename_to_deleted(self): |
1822 | + self.make_branch_and_working_tree() |
1823 | + self.add_file('a', 'foo') |
1824 | + self.add_file('b', 'bar') |
1825 | + self.do_full_upload() |
1826 | + self.delete_any('a') |
1827 | + self.rename_any('b', 'a') |
1828 | + |
1829 | + self.assertUpFileEqual('foo', 'a') |
1830 | + |
1831 | + self.do_upload() |
1832 | + |
1833 | + self.assertUpPathDoesNotExist('b') |
1834 | + self.assertUpFileEqual('bar', 'a') |
1835 | + |
1836 | + def test_rename_outside_dir_delete_dir(self): |
1837 | + self.make_branch_and_working_tree() |
1838 | + self.add_dir('dir') |
1839 | + self.add_file('dir/a', 'foo') |
1840 | + self.do_full_upload() |
1841 | + self.rename_any('dir/a', 'a') |
1842 | + self.delete_any('dir') |
1843 | + |
1844 | + self.assertUpFileEqual('foo', 'dir/a') |
1845 | + |
1846 | + self.do_upload() |
1847 | + |
1848 | + self.assertUpPathDoesNotExist('dir/a') |
1849 | + self.assertUpPathDoesNotExist('dir') |
1850 | + self.assertUpFileEqual('foo', 'a') |
1851 | + |
1852 | + def test_delete_symlink(self): |
1853 | + self.make_branch_and_working_tree() |
1854 | + self.add_symlink('link', 'target') |
1855 | + self.do_full_upload() |
1856 | + self.delete_any('link') |
1857 | + |
1858 | + self.do_upload() |
1859 | + |
1860 | + self.assertUpPathDoesNotExist('link') |
1861 | + |
1862 | + def test_upload_for_the_first_time_do_a_full_upload(self): |
1863 | + self.make_branch_and_working_tree() |
1864 | + self.add_file('hello', 'bar') |
1865 | + |
1866 | + revid_path = self.tree.branch.get_config_stack( |
1867 | + ).get('upload_revid_location') |
1868 | + self.assertUpPathDoesNotExist(revid_path) |
1869 | + |
1870 | + self.do_upload() |
1871 | + |
1872 | + self.assertUpFileEqual('bar', 'hello') |
1873 | + |
1874 | + def test_ignore_delete_one_file(self): |
1875 | + self.make_branch_and_working_tree() |
1876 | + self.add_file('hello', 'foo') |
1877 | + self.do_full_upload() |
1878 | + self.add_file('.bzrignore-upload', 'hello') |
1879 | + self.delete_any('hello') |
1880 | + |
1881 | + self.assertUpFileEqual('foo', 'hello') |
1882 | + |
1883 | + self.do_upload() |
1884 | + |
1885 | + self.assertUpFileEqual('foo', 'hello') |
1886 | + |
1887 | + |
1888 | +class TestBranchUploadLocations(per_branch.TestCaseWithBranch): |
1889 | + |
1890 | + def test_get_upload_location_unset(self): |
1891 | + conf = self.get_branch().get_config_stack() |
1892 | + self.assertEqual(None, conf.get('upload_location')) |
1893 | + |
1894 | + def test_get_push_location_exact(self): |
1895 | + config.ensure_config_dir_exists() |
1896 | + fn = config.locations_config_filename() |
1897 | + b = self.get_branch() |
1898 | + with open(fn, 'wt') as f: |
1899 | + f.write(("[%s]\n" "upload_location=foo\n" % b.base.rstrip("/"))) |
1900 | + self.assertEqual("foo", b.get_config_stack().get('upload_location')) |
1901 | + |
1902 | + def test_set_push_location(self): |
1903 | + conf = self.get_branch().get_config_stack() |
1904 | + conf.set('upload_location', 'foo') |
1905 | + self.assertEqual('foo', conf.get('upload_location')) |
1906 | + |
1907 | + |
1908 | +class TestUploadFromRemoteBranch(tests.TestCaseWithTransport, UploadUtilsMixin): |
1909 | + |
1910 | + remote_branch_dir = 'remote_branch' |
1911 | + |
1912 | + def setUp(self): |
1913 | + super(TestUploadFromRemoteBranch, self).setUp() |
1914 | + self.remote_branch_url = self.make_remote_branch_without_working_tree() |
1915 | + |
1916 | + def make_remote_branch_without_working_tree(self): |
1917 | + """Creates a branch without working tree to upload from. |
1918 | + |
1919 | + It's created from the existing self.branch_dir one which still has its |
1920 | + working tree. |
1921 | + """ |
1922 | + self.make_branch_and_working_tree() |
1923 | + self.add_file('hello', 'foo') |
1924 | + |
1925 | + remote_branch_url = self.get_url(self.remote_branch_dir) |
1926 | + if self.transport_server is stub_sftp.SFTPHomeDirServer: |
1927 | + # FIXME: Some policy search ends up above the user home directory |
1928 | + # and are seen as attemps to escape test isolation |
1929 | + raise tests.TestNotApplicable('Escaping test isolation') |
1930 | + self.run_bzr(['push', remote_branch_url, |
1931 | + '--directory', self.branch_dir]) |
1932 | + return remote_branch_url |
1933 | + |
1934 | + def test_no_upload_to_remote_working_tree(self): |
1935 | + cmd = self._get_cmd_upload() |
1936 | + up_url = self.get_url(self.branch_dir) |
1937 | + # Let's try to upload from the just created remote branch into the |
1938 | + # branch (which has a working tree). |
1939 | + self.assertRaises(cmds.CannotUploadToWorkingTree, |
1940 | + cmd.run, up_url, directory=self.remote_branch_url) |
1941 | + |
1942 | + def test_upload_without_working_tree(self): |
1943 | + self.do_full_upload(directory=self.remote_branch_url) |
1944 | + self.assertUpFileEqual('foo', 'hello') |
1945 | + |
1946 | + |
1947 | +class TestUploadDiverged(tests.TestCaseWithTransport, UploadUtilsMixin): |
1948 | + |
1949 | + def setUp(self): |
1950 | + super(TestUploadDiverged, self).setUp() |
1951 | + self.diverged_tree = self.make_diverged_tree_and_upload_location() |
1952 | + |
1953 | + def make_diverged_tree_and_upload_location(self): |
1954 | + tree_a = self.make_branch_and_tree('tree_a') |
1955 | + tree_a.commit('message 1', rev_id='rev1') |
1956 | + tree_a.commit('message 2', rev_id='rev2a') |
1957 | + tree_b = tree_a.bzrdir.sprout('tree_b').open_workingtree() |
1958 | + uncommit.uncommit(tree_b.branch, tree=tree_b) |
1959 | + tree_b.commit('message 2', rev_id='rev2b') |
1960 | + # upload tree a |
1961 | + self.do_full_upload(directory=tree_a.basedir) |
1962 | + return tree_b |
1963 | + |
1964 | + def assertRevidUploaded(self, revid): |
1965 | + t = self.get_transport(self.upload_dir) |
1966 | + uploaded_revid = t.get_bytes('.bzr-upload.revid') |
1967 | + self.assertEqual(revid, uploaded_revid) |
1968 | + |
1969 | + def test_cant_upload_diverged(self): |
1970 | + self.assertRaises(cmds.DivergedUploadedTree, |
1971 | + self.do_incremental_upload, |
1972 | + directory=self.diverged_tree.basedir) |
1973 | + self.assertRevidUploaded('rev2a') |
1974 | + |
1975 | + def test_upload_diverged_with_overwrite(self): |
1976 | + self.do_incremental_upload(directory=self.diverged_tree.basedir, |
1977 | + overwrite=True) |
1978 | + self.assertRevidUploaded('rev2b') |
1979 | + |
1980 | + |
1981 | +class TestUploadBadRemoteReivd(tests.TestCaseWithTransport, UploadUtilsMixin): |
1982 | + |
1983 | + def test_raises_on_wrong_revid(self): |
1984 | + tree = self.make_branch_and_working_tree() |
1985 | + self.do_full_upload() |
1986 | + # Put a fake revid on the remote branch |
1987 | + t = self.get_transport(self.upload_dir) |
1988 | + t.put_bytes('.bzr-upload.revid', 'fake') |
1989 | + # Make a change |
1990 | + self.add_file('foo', 'bar\n') |
1991 | + self.assertRaises(cmds.DivergedUploadedTree, self.do_full_upload) |
1992 | + |
1993 | |
1994 | === modified file 'doc/en/release-notes/brz-3.0.txt' |
1995 | --- doc/en/release-notes/brz-3.0.txt 2017-06-02 11:26:27 +0000 |
1996 | +++ doc/en/release-notes/brz-3.0.txt 2017-06-02 20:49:44 +0000 |
1997 | @@ -42,6 +42,9 @@ |
1998 | * The 'stats' plugin is now bundled with Bazaar. |
1999 | (Jelmer Vernooij) |
2000 | |
2001 | + * The 'upload' plugin is now bundled with Bazaar. |
2002 | + (Jelmer Vernooij) |
2003 | + |
2004 | * The 'import' command is now bundled with brz. |
2005 | Imported from bzrtools by Aaron Bentley. (Jelmer Vernooij, #773241) |
2006 |
Looks good.