]> arthur.ath.cx Git - bup.git/blob - test/ext/test_get.py
86cc756b48ed745882ff9c8b3c674798b2e8903a
[bup.git] / test / ext / test_get.py
1
2 from __future__ import print_function
3 from errno import ENOENT
4 from itertools import product
5 from os import chdir, mkdir, rename
6 from shutil import rmtree
7 from subprocess import PIPE
8 import pytest, re, sys
9
10 from bup import compat, path
11 from bup.compat import environ, getcwd, items
12 from bup.helpers import bquote, merge_dict, unlink
13 from bup.io import byte_stream
14 from buptest import ex, exo
15 from wvpytest import wvcheck, wvfail, wvmsg, wvpass, wvpasseq, wvpassne, wvstart
16 import bup.path
17
18
19 sys.stdout.flush()
20 stdout = byte_stream(sys.stdout)
21
22 # FIXME: per-test function
23 environ[b'GIT_AUTHOR_NAME'] = b'bup test-get'
24 environ[b'GIT_COMMITTER_NAME'] = b'bup test-get'
25 environ[b'GIT_AUTHOR_EMAIL'] = b'bup@85430dcca2b611e4b2c3-8f5691723476'
26 environ[b'GIT_COMMITTER_EMAIL'] = b'bup@85430dcca2b611e4b2c3-8f5691723476'
27
28 # The clean-repo test can probably be applied more broadly.  It was
29 # initially just applied to test-pick to catch a bug.
30
31 top = getcwd()
32 bup_cmd = bup.path.exe()
33
34 def rmrf(path):
35     err = []  # because python's scoping mess...
36     def onerror(function, path, excinfo):
37         err.append((function, path, excinfo))
38     rmtree(path, onerror=onerror)
39     if err:
40         function, path, excinfo = err[0]
41         ex_type, ex, traceback = excinfo
42         if (not isinstance(ex, OSError)) or ex.errno != ENOENT:
43             raise ex
44
45 def verify_trees_match(path1, path2):
46     global top
47     exr = exo((top + b'/dev/compare-trees', b'-c', path1, path2), check=False)
48     stdout.write(exr.out)
49     sys.stdout.flush()
50     wvcheck(exr.rc == 0, 'process exit %d == 0' % exr.rc)
51
52 def verify_rcz(cmd, **kwargs):
53     assert not kwargs.get('check')
54     kwargs['check'] = False
55     result = exo(cmd, **kwargs)
56     stdout.write(result.out)
57     rc = result.proc.returncode
58     wvcheck(rc == 0, 'process exit %d == 0' % rc)
59     return result
60
61 # FIXME: multline, or allow opts generally?
62
63 def verify_rx(rx, string):
64     wvcheck(re.search(rx, string), 'rx %r matches %r' % (rx, string))
65
66 def verify_nrx(rx, string):
67     wvcheck(not re.search(rx, string), "rx %r doesn't match %r" % (rx, string))
68
69 def validate_clean_repo():
70     out = verify_rcz((b'git', b'--git-dir', b'get-dest', b'fsck')).out
71     verify_nrx(br'dangling|mismatch|missing|unreachable', out)
72     
73 def validate_blob(src_id, dest_id):
74     global top
75     rmrf(b'restore-src')
76     rmrf(b'restore-dest')
77     cat_tree = top + b'/dev/git-cat-tree'
78     src_blob = verify_rcz((cat_tree, b'--git-dir', b'get-src', src_id)).out
79     dest_blob = verify_rcz((cat_tree, b'--git-dir', b'get-src', src_id)).out
80     wvpasseq(src_blob, dest_blob)
81
82 def validate_tree(src_id, dest_id):
83
84     rmrf(b'restore-src')
85     rmrf(b'restore-dest')
86     mkdir(b'restore-src')
87     mkdir(b'restore-dest')
88     
89     commit_env = merge_dict(environ, {b'GIT_COMMITTER_DATE': b'2014-01-01 01:01'})
90
91     # Create a commit so the archive contents will have matching timestamps.
92     src_c = exo((b'git', b'--git-dir', b'get-src',
93                  b'commit-tree', b'-m', b'foo', src_id),
94                 env=commit_env).out.strip()
95     dest_c = exo((b'git', b'--git-dir', b'get-dest',
96                   b'commit-tree', b'-m', b'foo', dest_id),
97                  env=commit_env).out.strip()
98     exr = verify_rcz(b'git --git-dir get-src archive %s | tar xvf - -C restore-src'
99                      % bquote(src_c),
100                      shell=True)
101     if exr.rc != 0: return False
102     exr = verify_rcz(b'git --git-dir get-dest archive %s | tar xvf - -C restore-dest'
103                      % bquote(dest_c),
104                      shell=True)
105     if exr.rc != 0: return False
106     
107     # git archive doesn't include an entry for ./.
108     unlink(b'restore-src/pax_global_header')
109     unlink(b'restore-dest/pax_global_header')
110     ex((b'touch', b'-r', b'restore-src', b'restore-dest'))
111     verify_trees_match(b'restore-src/', b'restore-dest/')
112     rmrf(b'restore-src')
113     rmrf(b'restore-dest')
114
115 def validate_commit(src_id, dest_id):
116     exr = verify_rcz((b'git', b'--git-dir', b'get-src', b'cat-file', b'commit', src_id))
117     if exr.rc != 0: return False
118     src_cat = exr.out
119     exr = verify_rcz((b'git', b'--git-dir', b'get-dest', b'cat-file', b'commit', dest_id))
120     if exr.rc != 0: return False
121     dest_cat = exr.out
122     wvpasseq(src_cat, dest_cat)
123     if src_cat != dest_cat: return False
124     
125     rmrf(b'restore-src')
126     rmrf(b'restore-dest')
127     mkdir(b'restore-src')
128     mkdir(b'restore-dest')
129     qsrc = bquote(src_id)
130     qdest = bquote(dest_id)
131     exr = verify_rcz((b'git --git-dir get-src archive ' + qsrc
132                       + b' | tar xf - -C restore-src'),
133                      shell=True)
134     if exr.rc != 0: return False
135     exr = verify_rcz((b'git --git-dir get-dest archive ' + qdest +
136                       b' | tar xf - -C restore-dest'),
137                      shell=True)
138     if exr.rc != 0: return False
139     
140     # git archive doesn't include an entry for ./.
141     ex((b'touch', b'-r', b'restore-src', b'restore-dest'))
142     verify_trees_match(b'restore-src/', b'restore-dest/')
143     rmrf(b'restore-src')
144     rmrf(b'restore-dest')
145
146 def _validate_save(orig_dir, save_path, commit_id, tree_id):
147     global bup_cmd
148     rmrf(b'restore')
149     exr = verify_rcz((bup_cmd, b'-d', b'get-dest',
150                       b'restore', b'-C', b'restore', save_path + b'/.'))
151     if exr.rc: return False
152     verify_trees_match(orig_dir + b'/', b'restore/')
153     if tree_id:
154         # FIXME: double check that get-dest is correct
155         exr = verify_rcz((b'git', b'--git-dir', b'get-dest', b'ls-tree', tree_id))
156         if exr.rc: return False
157         cat = verify_rcz((b'git', b'--git-dir', b'get-dest',
158                           b'cat-file', b'commit', commit_id))
159         if cat.rc: return False
160         wvpasseq(b'tree ' + tree_id, cat.out.splitlines()[0])
161
162 # FIXME: re-merge save and new_save?
163         
164 def validate_save(dest_name, restore_subpath, commit_id, tree_id, orig_value,
165                   get_out):
166     out = get_out.splitlines()
167     print('blarg: out', repr(out), file=sys.stderr)
168     wvpasseq(2, len(out))
169     get_tree_id = out[0]
170     get_commit_id = out[1]
171     wvpasseq(tree_id, get_tree_id)
172     wvpasseq(commit_id, get_commit_id)
173     _validate_save(orig_value, dest_name + restore_subpath, commit_id, tree_id)
174
175 def validate_new_save(dest_name, restore_subpath, commit_id, tree_id, orig_value,
176                       get_out):
177     out = get_out.splitlines()
178     wvpasseq(2, len(out))
179     get_tree_id = out[0]
180     get_commit_id = out[1]
181     wvpasseq(tree_id, get_tree_id)
182     wvpassne(commit_id, get_commit_id)
183     _validate_save(orig_value, dest_name + restore_subpath, get_commit_id, tree_id)
184         
185 def validate_tagged_save(tag_name, restore_subpath,
186                          commit_id, tree_id, orig_value, get_out):
187     out = get_out.splitlines()
188     wvpasseq(1, len(out))
189     get_tag_id = out[0]
190     wvpasseq(commit_id, get_tag_id)
191     # Make sure tmp doesn't already exist.
192     exr = exo((b'git', b'--git-dir', b'get-dest', b'show-ref', b'tmp-branch-for-tag'),
193               check=False)
194     wvpasseq(1, exr.rc)
195
196     ex((b'git', b'--git-dir', b'get-dest', b'branch', b'tmp-branch-for-tag',
197         b'refs/tags/' + tag_name))
198     _validate_save(orig_value, b'tmp-branch-for-tag/latest' + restore_subpath,
199                    commit_id, tree_id)
200     ex((b'git', b'--git-dir', b'get-dest', b'branch', b'-D', b'tmp-branch-for-tag'))
201
202 def validate_new_tagged_commit(tag_name, commit_id, tree_id, get_out):
203     out = get_out.splitlines()
204     wvpasseq(1, len(out))
205     get_tag_id = out[0]
206     wvpassne(commit_id, get_tag_id)
207     validate_tree(tree_id, tag_name + b':')
208
209
210 def _run_get(disposition, method, what):
211     print('run_get:', repr((disposition, method, what)), file=sys.stderr)
212     global bup_cmd
213
214     if disposition == 'get':
215         get_cmd = (bup_cmd, b'-d', b'get-dest',
216                    b'get', b'-vvct', b'--print-tags', b'-s', b'get-src')
217     elif disposition == 'get-on':
218         get_cmd = (bup_cmd, b'-d', b'get-dest',
219                    b'on', b'-', b'get', b'-vvct', b'--print-tags', b'-s', b'get-src')
220     elif disposition == 'get-to':
221         get_cmd = (bup_cmd, b'-d', b'get-dest',
222                    b'get', b'-vvct', b'--print-tags', b'-s', b'get-src',
223                    b'-r', b'-:' + getcwd() + b'/get-dest')
224     else:
225         raise Exception('error: unexpected get disposition ' + repr(disposition))
226     
227     if isinstance(what, bytes):
228         cmd = get_cmd + (method, what)
229     else:
230         assert not isinstance(what, str)  # python 3 sanity check
231         if method in (b'--ff', b'--append', b'--pick', b'--force-pick', b'--new-tag',
232                       b'--replace'):
233             method += b':'
234         src, dest = what
235         cmd = get_cmd + (method, src, dest)
236     result = exo(cmd, check=False, stderr=PIPE)
237     fsck = ex((bup_cmd, b'-d', b'get-dest', b'fsck'), check=False)
238     wvpasseq(0, fsck.rc)
239     return result
240
241 def run_get(disposition, method, what=None, given=None):
242     global bup_cmd
243     rmrf(b'get-dest')
244     ex((bup_cmd, b'-d', b'get-dest', b'init'))
245
246     if given:
247         # FIXME: replace bup-get with independent commands as is feasible
248         exr = _run_get(disposition, b'--replace', given)
249         assert not exr.rc
250     return _run_get(disposition, method, what)
251
252 def _test_universal(get_disposition, src_info):
253     methods = (b'--ff', b'--append', b'--pick', b'--force-pick', b'--new-tag',
254                b'--replace', b'--unnamed')
255     for method in methods:
256         mmsg = method.decode('ascii')
257         wvstart(get_disposition + ' ' + mmsg + ', missing source, fails')
258         exr = run_get(get_disposition, method, b'not-there')
259         wvpassne(0, exr.rc)
260         verify_rx(br'cannot find source', exr.err)
261     for method in methods:
262         mmsg = method.decode('ascii')
263         wvstart(get_disposition + ' ' + mmsg + ' / fails')
264         exr = run_get(get_disposition, method, b'/')
265         wvpassne(0, exr.rc)
266         verify_rx(b'cannot fetch entire repository', exr.err)
267
268 def verify_only_refs(**kwargs):
269     for kind, refs in items(kwargs):
270         if kind == 'heads':
271             abs_refs = [b'refs/heads/' + ref for ref in refs]
272             karg = b'--heads'
273         elif kind == 'tags':
274             abs_refs = [b'refs/tags/' + ref for ref in refs]
275             karg = b'--tags'
276         else:
277             raise TypeError('unexpected keyword argument %r' % kind)
278         if abs_refs:
279             verify_rcz([b'git', b'--git-dir', b'get-dest',
280                         b'show-ref', b'--verify', karg] + abs_refs)
281             exr = exo((b'git', b'--git-dir', b'get-dest', b'show-ref', karg),
282                       check=False)
283             wvpasseq(0, exr.rc)
284             expected_refs = sorted(abs_refs)
285             repo_refs = sorted([x.split()[1] for x in exr.out.splitlines()])
286             wvpasseq(expected_refs, repo_refs)
287         else:
288             # FIXME: can we just check "git show-ref --heads == ''"?
289             exr = exo((b'git', b'--git-dir', b'get-dest', b'show-ref', karg),
290                       check=False)
291             wvpasseq(1, exr.rc)
292             wvpasseq(b'', exr.out.strip())
293
294 def _test_replace(get_disposition, src_info):
295     print('blarg:', repr(src_info), file=sys.stderr)
296
297     wvstart(get_disposition + ' --replace to root fails')
298     for item in (b'.tag/tinyfile',
299                  b'src/latest' + src_info['tinyfile-path'],
300                  b'.tag/subtree',
301                  b'src/latest' + src_info['subtree-vfs-path'],
302                  b'.tag/commit-1',
303                  b'src/latest',
304                  b'src'):
305         exr = run_get(get_disposition, b'--replace', (item, b'/'))
306         wvpassne(0, exr.rc)
307         verify_rx(br'impossible; can only overwrite branch or tag', exr.err)
308
309     tinyfile_id = src_info['tinyfile-id']
310     tinyfile_path = src_info['tinyfile-path']
311     subtree_vfs_path = src_info['subtree-vfs-path']
312     subtree_id = src_info['subtree-id']
313     commit_2_id = src_info['commit-2-id']
314     tree_2_id = src_info['tree-2-id']
315
316     # Anything to tag
317     existing_items = {'nothing' : None,
318                       'blob' : (b'.tag/tinyfile', b'.tag/obj'),
319                       'tree' : (b'.tag/tree-1', b'.tag/obj'),
320                       'commit': (b'.tag/commit-1', b'.tag/obj')}
321     for ex_type, ex_ref in items(existing_items):
322         wvstart(get_disposition + ' --replace ' + ex_type + ' with blob tag')
323         for item in (b'.tag/tinyfile', b'src/latest' + tinyfile_path):
324             exr = run_get(get_disposition, b'--replace', (item ,b'.tag/obj'),
325                           given=ex_ref)
326             wvpasseq(0, exr.rc)        
327             validate_blob(tinyfile_id, tinyfile_id)
328             verify_only_refs(heads=[], tags=(b'obj',))
329         wvstart(get_disposition + ' --replace ' + ex_type + ' with tree tag')
330         for item in (b'.tag/subtree',  b'src/latest' + subtree_vfs_path):
331             exr = run_get(get_disposition, b'--replace', (item, b'.tag/obj'),
332                           given=ex_ref)
333             validate_tree(subtree_id, subtree_id)
334             verify_only_refs(heads=[], tags=(b'obj',))
335         wvstart(get_disposition + ' --replace ' + ex_type + ' with commitish tag')
336         for item in (b'.tag/commit-2', b'src/latest', b'src'):
337             exr = run_get(get_disposition, b'--replace', (item, b'.tag/obj'),
338                           given=ex_ref)
339             validate_tagged_save(b'obj', getcwd() + b'/src',
340                                  commit_2_id, tree_2_id, b'src-2', exr.out)
341             verify_only_refs(heads=[], tags=(b'obj',))
342
343         # Committish to branch.
344         existing_items = (('nothing', None),
345                           ('branch', (b'.tag/commit-1', b'obj')))
346         for ex_type, ex_ref in existing_items:
347             for item_type, item in (('commit', b'.tag/commit-2'),
348                                     ('save', b'src/latest'),
349                                     ('branch', b'src')):
350                 wvstart(get_disposition + ' --replace '
351                         + ex_type + ' with ' + item_type)
352                 exr = run_get(get_disposition, b'--replace', (item, b'obj'),
353                               given=ex_ref)
354                 validate_save(b'obj/latest', getcwd() + b'/src',
355                               commit_2_id, tree_2_id, b'src-2', exr.out)
356                 verify_only_refs(heads=(b'obj',), tags=[])
357
358         # Not committish to branch
359         existing_items = (('nothing', None),
360                           ('branch', (b'.tag/commit-1', b'obj')))
361         for ex_type, ex_ref in existing_items:
362             for item_type, item in (('blob', b'.tag/tinyfile'),
363                                     ('blob', b'src/latest' + tinyfile_path),
364                                     ('tree', b'.tag/subtree'),
365                                     ('tree', b'src/latest' + subtree_vfs_path)):
366                 wvstart(get_disposition + ' --replace branch with '
367                         + item_type + ' given ' + ex_type + ' fails')
368
369                 exr = run_get(get_disposition, b'--replace', (item, b'obj'),
370                               given=ex_ref)
371                 wvpassne(0, exr.rc)
372                 verify_rx(br'cannot overwrite branch with .+ for', exr.err)
373
374         wvstart(get_disposition + ' --replace, implicit destinations')
375
376         exr = run_get(get_disposition, b'--replace', b'src')
377         validate_save(b'src/latest', getcwd() + b'/src',
378                       commit_2_id, tree_2_id, b'src-2', exr.out)
379         verify_only_refs(heads=(b'src',), tags=[])
380
381         exr = run_get(get_disposition, b'--replace', b'.tag/commit-2')
382         validate_tagged_save(b'commit-2', getcwd() + b'/src',
383                              commit_2_id, tree_2_id, b'src-2', exr.out)
384         verify_only_refs(heads=[], tags=(b'commit-2',))
385
386 def _test_ff(get_disposition, src_info):
387
388     wvstart(get_disposition + ' --ff to root fails')
389     tinyfile_path = src_info['tinyfile-path']
390     for item in (b'.tag/tinyfile', b'src/latest' + tinyfile_path):
391         exr = run_get(get_disposition, b'--ff', (item, b'/'))
392         wvpassne(0, exr.rc)
393         verify_rx(br'source for .+ must be a branch, save, or commit', exr.err)
394     subtree_vfs_path = src_info['subtree-vfs-path']
395     for item in (b'.tag/subtree', b'src/latest' + subtree_vfs_path):
396         exr = run_get(get_disposition, b'--ff', (item, b'/'))
397         wvpassne(0, exr.rc)
398         verify_rx(br'is impossible; can only --append a tree to a branch',
399                   exr.err)    
400     for item in (b'.tag/commit-1', b'src/latest', b'src'):
401         exr = run_get(get_disposition, b'--ff', (item, b'/'))
402         wvpassne(0, exr.rc)
403         verify_rx(br'destination for .+ is a root, not a branch', exr.err)
404
405     wvstart(get_disposition + ' --ff of not-committish fails')
406     for src in (b'.tag/tinyfile', b'src/latest' + tinyfile_path):
407         # FIXME: use get_item elsewhere?
408         for given, get_item in ((None, (src, b'obj')),
409                                 (None, (src, b'.tag/obj')),
410                                 ((b'.tag/tinyfile', b'.tag/obj'), (src, b'.tag/obj')),
411                                 ((b'.tag/tree-1', b'.tag/obj'), (src, b'.tag/obj')),
412                                 ((b'.tag/commit-1', b'.tag/obj'), (src, b'.tag/obj')),
413                                 ((b'.tag/commit-1', b'obj'), (src, b'obj'))):
414             exr = run_get(get_disposition, b'--ff', get_item, given=given)
415             wvpassne(0, exr.rc)
416             verify_rx(br'must be a branch, save, or commit', exr.err)
417     for src in (b'.tag/subtree', b'src/latest' + subtree_vfs_path):
418         for given, get_item in ((None, (src, b'obj')),
419                                 (None, (src, b'.tag/obj')),
420                                 ((b'.tag/tinyfile', b'.tag/obj'), (src, b'.tag/obj')),
421                                 ((b'.tag/tree-1', b'.tag/obj'), (src, b'.tag/obj')),
422                                 ((b'.tag/commit-1', b'.tag/obj'), (src, b'.tag/obj')),
423                                 ((b'.tag/commit-1', b'obj'), (src, b'obj'))):
424             exr = run_get(get_disposition, b'--ff', get_item, given=given)
425             wvpassne(0, exr.rc)
426             verify_rx(br'can only --append a tree to a branch', exr.err)
427
428     wvstart(get_disposition + ' --ff committish, ff possible')
429     save_2 = src_info['save-2']
430     for src in (b'.tag/commit-2', b'src/' + save_2, b'src'):
431         for given, get_item, complaint in \
432             ((None, (src, b'.tag/obj'),
433               br'destination .+ must be a valid branch name'),
434              ((b'.tag/tinyfile', b'.tag/obj'), (src, b'.tag/obj'),
435               br'destination .+ is a blob, not a branch'),
436              ((b'.tag/tree-1', b'.tag/obj'), (src, b'.tag/obj'),
437               br'destination .+ is a tree, not a branch'),
438              ((b'.tag/commit-1', b'.tag/obj'), (src, b'.tag/obj'),
439               br'destination .+ is a tagged commit, not a branch'),
440              ((b'.tag/commit-2', b'.tag/obj'), (src, b'.tag/obj'),
441               br'destination .+ is a tagged commit, not a branch')):
442             exr = run_get(get_disposition, b'--ff', get_item, given=given)
443             wvpassne(0, exr.rc)
444             verify_rx(complaint, exr.err)
445     # FIXME: use src or item and given or existing consistently in loops...
446     commit_2_id = src_info['commit-2-id']
447     tree_2_id = src_info['tree-2-id']
448     for src in (b'.tag/commit-2', b'src/' + save_2, b'src'):
449         for given in (None, (b'.tag/commit-1', b'obj'), (b'.tag/commit-2', b'obj')):
450             exr = run_get(get_disposition, b'--ff', (src, b'obj'), given=given)
451             wvpasseq(0, exr.rc)
452             validate_save(b'obj/latest', getcwd() + b'/src',
453                           commit_2_id, tree_2_id, b'src-2', exr.out)
454             verify_only_refs(heads=(b'obj',), tags=[])
455             
456     wvstart(get_disposition + ' --ff, implicit destinations')
457     for item in (b'src', b'src/latest'):
458         exr = run_get(get_disposition, b'--ff', item)
459         wvpasseq(0, exr.rc)
460
461         ex((b'find', b'get-dest/refs'))
462         ex((bup_cmd, b'-d', b'get-dest', b'ls'))
463
464         validate_save(b'src/latest', getcwd() + b'/src',
465                      commit_2_id, tree_2_id, b'src-2', exr.out)
466         #verify_only_refs(heads=('src',), tags=[])
467
468     wvstart(get_disposition + ' --ff, ff impossible')
469     for given, get_item in (((b'unrelated-branch', b'src'), b'src'),
470                             ((b'.tag/commit-2', b'src'), (b'.tag/commit-1', b'src'))):
471         exr = run_get(get_disposition, b'--ff', get_item, given=given)
472         wvpassne(0, exr.rc)
473         verify_rx(br'destination is not an ancestor of source', exr.err)
474
475 def _test_append(get_disposition, src_info):
476     tinyfile_path = src_info['tinyfile-path']
477     subtree_vfs_path = src_info['subtree-vfs-path']
478
479     wvstart(get_disposition + ' --append to root fails')
480     for item in (b'.tag/tinyfile', b'src/latest' + tinyfile_path):
481         exr = run_get(get_disposition, b'--append', (item, b'/'))
482         wvpassne(0, exr.rc)
483         verify_rx(br'source for .+ must be a branch, save, commit, or tree',
484                   exr.err)
485     for item in (b'.tag/subtree', b'src/latest' + subtree_vfs_path,
486                  b'.tag/commit-1', b'src/latest', b'src'):
487         exr = run_get(get_disposition, b'--append', (item, b'/'))
488         wvpassne(0, exr.rc)
489         verify_rx(br'destination for .+ is a root, not a branch', exr.err)
490
491     wvstart(get_disposition + ' --append of not-treeish fails')
492     for src in (b'.tag/tinyfile', b'src/latest' + tinyfile_path):
493         for given, item in ((None, (src, b'obj')),
494                             (None, (src, b'.tag/obj')),
495                             ((b'.tag/tinyfile', b'.tag/obj'), (src, b'.tag/obj')),
496                             ((b'.tag/tree-1', b'.tag/obj'), (src, b'.tag/obj')),
497                             ((b'.tag/commit-1', b'.tag/obj'), (src, b'.tag/obj')),
498                             ((b'.tag/commit-1', b'obj'), (src, b'obj'))):
499             exr = run_get(get_disposition, b'--append', item, given=given)
500             wvpassne(0, exr.rc)
501             verify_rx(br'must be a branch, save, commit, or tree', exr.err)
502
503     wvstart(get_disposition + ' --append committish failure cases')
504     save_2 = src_info['save-2']
505     for src in (b'.tag/subtree', b'src/latest' + subtree_vfs_path,
506                 b'.tag/commit-2', b'src/' + save_2, b'src'):
507         for given, item, complaint in \
508             ((None, (src, b'.tag/obj'),
509               br'destination .+ must be a valid branch name'),
510              ((b'.tag/tinyfile', b'.tag/obj'), (src, b'.tag/obj'),
511               br'destination .+ is a blob, not a branch'),
512              ((b'.tag/tree-1', b'.tag/obj'), (src, b'.tag/obj'),
513               br'destination .+ is a tree, not a branch'),
514              ((b'.tag/commit-1', b'.tag/obj'), (src, b'.tag/obj'),
515               br'destination .+ is a tagged commit, not a branch'),
516              ((b'.tag/commit-2', b'.tag/obj'), (src, b'.tag/obj'),
517               br'destination .+ is a tagged commit, not a branch')):
518             exr = run_get(get_disposition, b'--append', item, given=given)
519             wvpassne(0, exr.rc)
520             verify_rx(complaint, exr.err)
521
522     wvstart(get_disposition + ' --append committish')
523     commit_2_id = src_info['commit-2-id']
524     tree_2_id = src_info['tree-2-id']
525     for item in (b'.tag/commit-2', b'src/' + save_2, b'src'):
526         for existing in (None, (b'.tag/commit-1', b'obj'),
527                          (b'.tag/commit-2', b'obj'),
528                          (b'unrelated-branch', b'obj')):
529             exr = run_get(get_disposition, b'--append', (item, b'obj'),
530                           given=existing)
531             wvpasseq(0, exr.rc)
532             validate_new_save(b'obj/latest', getcwd() + b'/src',
533                               commit_2_id, tree_2_id, b'src-2', exr.out)
534             verify_only_refs(heads=(b'obj',), tags=[])
535     # Append ancestor
536     save_1 = src_info['save-1']
537     commit_1_id = src_info['commit-1-id']
538     tree_1_id = src_info['tree-1-id']
539     for item in (b'.tag/commit-1',  b'src/' + save_1, b'src-1'):
540         exr = run_get(get_disposition, b'--append', (item, b'obj'),
541                       given=(b'.tag/commit-2', b'obj'))
542         wvpasseq(0, exr.rc)
543         validate_new_save(b'obj/latest', getcwd() + b'/src',
544                           commit_1_id, tree_1_id, b'src-1', exr.out)
545         verify_only_refs(heads=(b'obj',), tags=[])
546
547     wvstart(get_disposition + ' --append tree')
548     subtree_path = src_info['subtree-path']
549     subtree_id = src_info['subtree-id']
550     for item in (b'.tag/subtree', b'src/latest' + subtree_vfs_path):
551         for existing in (None,
552                          (b'.tag/commit-1', b'obj'),
553                          (b'.tag/commit-2', b'obj')):
554             exr = run_get(get_disposition, b'--append', (item, b'obj'),
555                           given=existing)
556             wvpasseq(0, exr.rc)
557             validate_new_save(b'obj/latest', b'/', None, subtree_id, subtree_path,
558                               exr.out)
559             verify_only_refs(heads=(b'obj',), tags=[])
560
561     wvstart(get_disposition + ' --append, implicit destinations')
562
563     for item in (b'src', b'src/latest'):
564         exr = run_get(get_disposition, b'--append', item)
565         wvpasseq(0, exr.rc)
566         validate_new_save(b'src/latest', getcwd() + b'/src', commit_2_id, tree_2_id,
567                           b'src-2', exr.out)
568         verify_only_refs(heads=(b'src',), tags=[])
569
570 def _test_pick_common(get_disposition, src_info, force=False):
571     flavor = b'--force-pick' if force else b'--pick'
572     flavormsg = flavor.decode('ascii')
573     tinyfile_path = src_info['tinyfile-path']
574     subtree_vfs_path = src_info['subtree-vfs-path']
575     
576     wvstart(get_disposition + ' ' + flavormsg + ' to root fails')
577     for item in (b'.tag/tinyfile', b'src/latest' + tinyfile_path, b'src'):
578         exr = run_get(get_disposition, flavor, (item, b'/'))
579         wvpassne(0, exr.rc)
580         verify_rx(br'can only pick a commit or save', exr.err)
581     for item in (b'.tag/commit-1', b'src/latest'):
582         exr = run_get(get_disposition, flavor, (item, b'/'))
583         wvpassne(0, exr.rc)
584         verify_rx(br'destination is not a tag or branch', exr.err)
585     for item in (b'.tag/subtree', b'src/latest' + subtree_vfs_path):
586         exr = run_get(get_disposition, flavor, (item, b'/'))
587         wvpassne(0, exr.rc)
588         verify_rx(br'is impossible; can only --append a tree', exr.err)
589
590     wvstart(get_disposition + ' ' + flavormsg + ' of blob or branch fails')
591     for item in (b'.tag/tinyfile', b'src/latest' + tinyfile_path, b'src'):
592         for given, get_item in ((None, (item, b'obj')),
593                                 (None, (item, b'.tag/obj')),
594                                 ((b'.tag/tinyfile', b'.tag/obj'), (item, b'.tag/obj')),
595                                 ((b'.tag/tree-1', b'.tag/obj'), (item, b'.tag/obj')),
596                                 ((b'.tag/commit-1', b'.tag/obj'), (item, b'.tag/obj')),
597                                 ((b'.tag/commit-1', b'obj'), (item, b'obj'))):
598             exr = run_get(get_disposition, flavor, get_item, given=given)
599             wvpassne(0, exr.rc)
600             verify_rx(br'impossible; can only pick a commit or save', exr.err)
601
602     wvstart(get_disposition + ' ' + flavormsg + ' of tree fails')
603     for item in (b'.tag/subtree', b'src/latest' + subtree_vfs_path):
604         for given, get_item in ((None, (item, b'obj')),
605                                 (None, (item, b'.tag/obj')),
606                                 ((b'.tag/tinyfile', b'.tag/obj'), (item, b'.tag/obj')),
607                                 ((b'.tag/tree-1', b'.tag/obj'), (item, b'.tag/obj')),
608                                 ((b'.tag/commit-1', b'.tag/obj'), (item, b'.tag/obj')),
609                                 ((b'.tag/commit-1', b'obj'), (item, b'obj'))):
610             exr = run_get(get_disposition, flavor, get_item, given=given)
611             wvpassne(0, exr.rc)
612             verify_rx(br'impossible; can only --append a tree', exr.err)
613
614     save_2 = src_info['save-2']
615     commit_2_id = src_info['commit-2-id']
616     tree_2_id = src_info['tree-2-id']
617     # FIXME: these two wvstart texts?
618     if force:
619         wvstart(get_disposition + ' ' + flavormsg + ' commit/save to existing tag')
620         for item in (b'.tag/commit-2', b'src/' + save_2):
621             for given in ((b'.tag/tinyfile', b'.tag/obj'),
622                           (b'.tag/tree-1', b'.tag/obj'),
623                           (b'.tag/commit-1', b'.tag/obj')):
624                 exr = run_get(get_disposition, flavor, (item, b'.tag/obj'),
625                               given=given)
626                 wvpasseq(0, exr.rc)
627                 validate_new_tagged_commit(b'obj', commit_2_id, tree_2_id,
628                                            exr.out)
629                 verify_only_refs(heads=[], tags=(b'obj',))
630     else: # --pick
631         wvstart(get_disposition + ' ' + flavormsg
632                 + ' commit/save to existing tag fails')
633         for item in (b'.tag/commit-2', b'src/' + save_2):
634             for given in ((b'.tag/tinyfile', b'.tag/obj'),
635                           (b'.tag/tree-1', b'.tag/obj'),
636                           (b'.tag/commit-1', b'.tag/obj')):
637                 exr = run_get(get_disposition, flavor, (item, b'.tag/obj'), given=given)
638                 wvpassne(0, exr.rc)
639                 verify_rx(br'cannot overwrite existing tag', exr.err)
640             
641     wvstart(get_disposition + ' ' + flavormsg + ' commit/save to tag')
642     for item in (b'.tag/commit-2', b'src/' + save_2):
643         exr = run_get(get_disposition, flavor, (item, b'.tag/obj'))
644         wvpasseq(0, exr.rc)
645         validate_clean_repo()
646         validate_new_tagged_commit(b'obj', commit_2_id, tree_2_id, exr.out)
647         verify_only_refs(heads=[], tags=(b'obj',))
648          
649     wvstart(get_disposition + ' ' + flavormsg + ' commit/save to branch')
650     for item in (b'.tag/commit-2', b'src/' + save_2):
651         for given in (None, (b'.tag/commit-1', b'obj'), (b'.tag/commit-2', b'obj')):
652             exr = run_get(get_disposition, flavor, (item, b'obj'), given=given)
653             wvpasseq(0, exr.rc)
654             validate_clean_repo()
655             validate_new_save(b'obj/latest', getcwd() + b'/src',
656                               commit_2_id, tree_2_id, b'src-2', exr.out)
657             verify_only_refs(heads=(b'obj',), tags=[])
658
659     wvstart(get_disposition + ' ' + flavormsg
660             + ' commit/save unrelated commit to branch')
661     for item in(b'.tag/commit-2', b'src/' + save_2):
662         exr = run_get(get_disposition, flavor, (item, b'obj'),
663                       given=(b'unrelated-branch', b'obj'))
664         wvpasseq(0, exr.rc)
665         validate_clean_repo()
666         validate_new_save(b'obj/latest', getcwd() + b'/src',
667                           commit_2_id, tree_2_id, b'src-2', exr.out)
668         verify_only_refs(heads=(b'obj',), tags=[])
669
670     wvstart(get_disposition + ' ' + flavormsg + ' commit/save ancestor to branch')
671     save_1 = src_info['save-1']
672     commit_1_id = src_info['commit-1-id']
673     tree_1_id = src_info['tree-1-id']
674     for item in (b'.tag/commit-1', b'src/' + save_1):
675         exr = run_get(get_disposition, flavor, (item, b'obj'),
676                       given=(b'.tag/commit-2', b'obj'))
677         wvpasseq(0, exr.rc)
678         validate_clean_repo()
679         validate_new_save(b'obj/latest', getcwd() + b'/src',
680                           commit_1_id, tree_1_id, b'src-1', exr.out)
681         verify_only_refs(heads=(b'obj',), tags=[])
682
683
684     wvstart(get_disposition + ' ' + flavormsg + ', implicit destinations')
685     exr = run_get(get_disposition, flavor, b'.tag/commit-2')
686     wvpasseq(0, exr.rc)
687     validate_clean_repo()
688     validate_new_tagged_commit(b'commit-2', commit_2_id, tree_2_id, exr.out)
689     verify_only_refs(heads=[], tags=(b'commit-2',))
690
691     exr = run_get(get_disposition, flavor, b'src/latest')
692     wvpasseq(0, exr.rc)
693     validate_clean_repo()
694     validate_new_save(b'src/latest', getcwd() + b'/src',
695                       commit_2_id, tree_2_id, b'src-2', exr.out)
696     verify_only_refs(heads=(b'src',), tags=[])
697
698 def _test_pick_force(get_disposition, src_info):
699     _test_pick_common(get_disposition, src_info, force=True)
700
701 def _test_pick_noforce(get_disposition, src_info):
702     _test_pick_common(get_disposition, src_info, force=False)
703
704 def _test_new_tag(get_disposition, src_info):
705     tinyfile_id = src_info['tinyfile-id']
706     tinyfile_path = src_info['tinyfile-path']
707     commit_2_id = src_info['commit-2-id']
708     tree_2_id = src_info['tree-2-id']
709     subtree_id = src_info['subtree-id']
710     subtree_vfs_path = src_info['subtree-vfs-path']
711
712     wvstart(get_disposition + ' --new-tag to root fails')
713     for item in (b'.tag/tinyfile',
714                  b'src/latest' + tinyfile_path,
715                  b'.tag/subtree',
716                  b'src/latest' + subtree_vfs_path,
717                  b'.tag/commit-1',
718                  b'src/latest',
719                  b'src'):
720         exr = run_get(get_disposition, b'--new-tag', (item, b'/'))
721         wvpassne(0, exr.rc)
722         verify_rx(br'destination for .+ must be a VFS tag', exr.err)
723
724     # Anything to new tag.
725     wvstart(get_disposition + ' --new-tag, blob tag')
726     for item in (b'.tag/tinyfile', b'src/latest' + tinyfile_path):
727         exr = run_get(get_disposition, b'--new-tag', (item, b'.tag/obj'))
728         wvpasseq(0, exr.rc)        
729         validate_blob(tinyfile_id, tinyfile_id)
730         verify_only_refs(heads=[], tags=(b'obj',))
731
732     wvstart(get_disposition + ' --new-tag, tree tag')
733     for item in (b'.tag/subtree', b'src/latest' + subtree_vfs_path):
734         exr = run_get(get_disposition, b'--new-tag', (item, b'.tag/obj'))
735         wvpasseq(0, exr.rc)        
736         validate_tree(subtree_id, subtree_id)
737         verify_only_refs(heads=[], tags=(b'obj',))
738         
739     wvstart(get_disposition + ' --new-tag, committish tag')
740     for item in (b'.tag/commit-2', b'src/latest', b'src'):
741         exr = run_get(get_disposition, b'--new-tag', (item, b'.tag/obj'))
742         wvpasseq(0, exr.rc)        
743         validate_tagged_save(b'obj', getcwd() + b'/src/', commit_2_id, tree_2_id,
744                              b'src-2', exr.out)
745         verify_only_refs(heads=[], tags=(b'obj',))
746
747     # Anything to existing tag (fails).
748     for ex_type, ex_tag in (('blob', (b'.tag/tinyfile', b'.tag/obj')),
749                             ('tree', (b'.tag/tree-1', b'.tag/obj')),
750                             ('commit', (b'.tag/commit-1', b'.tag/obj'))):
751         for item_type, item in (('blob tag', b'.tag/tinyfile'),
752                                 ('blob path', b'src/latest' + tinyfile_path),
753                                 ('tree tag', b'.tag/subtree'),
754                                 ('tree path', b'src/latest' + subtree_vfs_path),
755                                 ('commit tag', b'.tag/commit-2'),
756                                 ('save', b'src/latest'),
757                                 ('branch', b'src')):
758             wvstart(get_disposition + ' --new-tag of ' + item_type
759                     + ', given existing ' + ex_type + ' tag, fails')
760             exr = run_get(get_disposition, b'--new-tag', (item, b'.tag/obj'),
761                           given=ex_tag)
762             wvpassne(0, exr.rc)
763             verify_rx(br'cannot overwrite existing tag .* \(requires --replace\)',
764                       exr.err)
765
766     # Anything to branch (fails).
767     for ex_type, ex_tag in (('nothing', None),
768                             ('blob', (b'.tag/tinyfile', b'.tag/obj')),
769                             ('tree', (b'.tag/tree-1', b'.tag/obj')),
770                             ('commit', (b'.tag/commit-1', b'.tag/obj'))):
771         for item_type, item in (('blob tag', b'.tag/tinyfile'),
772                 ('blob path', b'src/latest' + tinyfile_path),
773                 ('tree tag', b'.tag/subtree'),
774                 ('tree path', b'src/latest' + subtree_vfs_path),
775                 ('commit tag', b'.tag/commit-2'),
776                 ('save', b'src/latest'),
777                 ('branch', b'src')):
778             wvstart(get_disposition + ' --new-tag to branch of ' + item_type
779                     + ', given existing ' + ex_type + ' tag, fails')
780             exr = run_get(get_disposition, b'--new-tag', (item, b'obj'),
781                           given=ex_tag)
782             wvpassne(0, exr.rc)
783             verify_rx(br'destination for .+ must be a VFS tag', exr.err)
784
785     wvstart(get_disposition + ' --new-tag, implicit destinations')
786     exr = run_get(get_disposition, b'--new-tag', b'.tag/commit-2')
787     wvpasseq(0, exr.rc)        
788     validate_tagged_save(b'commit-2', getcwd() + b'/src/', commit_2_id, tree_2_id,
789                          b'src-2', exr.out)
790     verify_only_refs(heads=[], tags=(b'commit-2',))
791
792 def _test_unnamed(get_disposition, src_info):
793     tinyfile_id = src_info['tinyfile-id']
794     tinyfile_path = src_info['tinyfile-path']
795     subtree_vfs_path = src_info['subtree-vfs-path']
796     wvstart(get_disposition + ' --unnamed to root fails')
797     for item in (b'.tag/tinyfile',
798                  b'src/latest' + tinyfile_path,
799                  b'.tag/subtree',
800                  b'src/latest' + subtree_vfs_path,
801                  b'.tag/commit-1',
802                  b'src/latest',
803                  b'src'):
804         for ex_ref in (None, (item, b'.tag/obj')):
805             exr = run_get(get_disposition, b'--unnamed', (item, b'/'),
806                           given=ex_ref)
807             wvpassne(0, exr.rc)
808             verify_rx(br'usage: bup get ', exr.err)
809
810     wvstart(get_disposition + ' --unnamed file')
811     for item in (b'.tag/tinyfile', b'src/latest' + tinyfile_path):
812         exr = run_get(get_disposition, b'--unnamed', item)
813         wvpasseq(0, exr.rc)        
814         validate_blob(tinyfile_id, tinyfile_id)
815         verify_only_refs(heads=[], tags=[])
816
817         exr = run_get(get_disposition, b'--unnamed', item,
818                       given=(item, b'.tag/obj'))
819         wvpasseq(0, exr.rc)        
820         validate_blob(tinyfile_id, tinyfile_id)
821         verify_only_refs(heads=[], tags=(b'obj',))
822
823     wvstart(get_disposition + ' --unnamed tree')
824     subtree_id = src_info['subtree-id']
825     for item in (b'.tag/subtree', b'src/latest' + subtree_vfs_path):
826         exr = run_get(get_disposition, b'--unnamed', item)
827         wvpasseq(0, exr.rc)        
828         validate_tree(subtree_id, subtree_id)
829         verify_only_refs(heads=[], tags=[])
830         
831         exr = run_get(get_disposition, b'--unnamed', item,
832                       given=(item, b'.tag/obj'))
833         wvpasseq(0, exr.rc)        
834         validate_tree(subtree_id, subtree_id)
835         verify_only_refs(heads=[], tags=(b'obj',))
836         
837     wvstart(get_disposition + ' --unnamed committish')
838     save_2 = src_info['save-2']
839     commit_2_id = src_info['commit-2-id']
840     for item in (b'.tag/commit-2', b'src/' + save_2, b'src'):
841         exr = run_get(get_disposition, b'--unnamed', item)
842         wvpasseq(0, exr.rc)        
843         validate_commit(commit_2_id, commit_2_id)
844         verify_only_refs(heads=[], tags=[])
845
846         exr = run_get(get_disposition, b'--unnamed', item,
847                       given=(item, b'.tag/obj'))
848         wvpasseq(0, exr.rc)        
849         validate_commit(commit_2_id, commit_2_id)
850         verify_only_refs(heads=[], tags=(b'obj',))
851
852 def create_get_src():
853     global bup_cmd, src_info
854     wvstart('preparing')
855     ex((bup_cmd, b'-d', b'get-src', b'init'))
856
857     mkdir(b'src')
858     open(b'src/unrelated', 'a').close()
859     ex((bup_cmd, b'-d', b'get-src', b'index', b'src'))
860     ex((bup_cmd, b'-d', b'get-src', b'save', b'-tcn', b'unrelated-branch', b'src'))
861
862     ex((bup_cmd, b'-d', b'get-src', b'index', b'--clear'))
863     rmrf(b'src')
864     mkdir(b'src')
865     open(b'src/zero', 'a').close()
866     ex((bup_cmd, b'-d', b'get-src', b'index', b'src'))
867     exr = exo((bup_cmd, b'-d', b'get-src', b'save', b'-tcn', b'src', b'src'))
868     out = exr.out.splitlines()
869     tree_0_id = out[0]
870     commit_0_id = out[-1]
871     exr = exo((bup_cmd, b'-d', b'get-src', b'ls', b'src'))
872     save_0 = exr.out.splitlines()[0]
873     ex((b'git', b'--git-dir', b'get-src', b'branch', b'src-0', b'src'))
874     ex((b'cp', b'-RPp', b'src', b'src-0'))
875     
876     rmrf(b'src')
877     mkdir(b'src')
878     mkdir(b'src/x')
879     mkdir(b'src/x/y')
880     ex((bup_cmd + b' -d get-src random 1k > src/1'), shell=True)
881     ex((bup_cmd + b' -d get-src random 1k > src/x/2'), shell=True)
882     ex((bup_cmd, b'-d', b'get-src', b'index', b'src'))
883     exr = exo((bup_cmd, b'-d', b'get-src', b'save', b'-tcn', b'src', b'src'))
884     out = exr.out.splitlines()
885     tree_1_id = out[0]
886     commit_1_id = out[-1]
887     exr = exo((bup_cmd, b'-d', b'get-src', b'ls', b'src'))
888     save_1 = exr.out.splitlines()[1]
889     ex((b'git', b'--git-dir', b'get-src', b'branch', b'src-1', b'src'))
890     ex((b'cp', b'-RPp', b'src', b'src-1'))
891     
892     # Make a copy the current state of src so we'll have an ancestor.
893     ex((b'cp', b'-RPp',
894          b'get-src/refs/heads/src', b'get-src/refs/heads/src-ancestor'))
895
896     with open(b'src/tiny-file', 'ab') as f: f.write(b'xyzzy')
897     ex((bup_cmd, b'-d', b'get-src', b'index', b'src'))
898     ex((bup_cmd, b'-d', b'get-src', b'tick'))  # Ensure the save names differ
899     exr = exo((bup_cmd, b'-d', b'get-src', b'save', b'-tcn', b'src', b'src'))
900     out = exr.out.splitlines()
901     tree_2_id = out[0]
902     commit_2_id = out[-1]
903     exr = exo((bup_cmd, b'-d', b'get-src', b'ls', b'src'))
904     save_2 = exr.out.splitlines()[2]
905     rename(b'src', b'src-2')
906
907     src_root = getcwd() + b'/src'
908
909     subtree_path = b'src-2/x'
910     subtree_vfs_path = src_root + b'/x'
911
912     # No support for "ls -d", so grep...
913     exr = exo((bup_cmd, b'-d', b'get-src', b'ls', b'-s', b'src/latest' + src_root))
914     out = exr.out.splitlines()
915     subtree_id = None
916     for line in out:
917         if b'x' in line:
918             subtree_id = line.split()[0]
919     assert(subtree_id)
920
921     # With a tiny file, we'll get a single blob, not a chunked tree
922     tinyfile_path = src_root + b'/tiny-file'
923     exr = exo((bup_cmd, b'-d', b'get-src', b'ls', b'-s', b'src/latest' + tinyfile_path))
924     tinyfile_id = exr.out.splitlines()[0].split()[0]
925
926     ex((bup_cmd, b'-d', b'get-src', b'tag', b'tinyfile', tinyfile_id))
927     ex((bup_cmd, b'-d', b'get-src', b'tag', b'subtree', subtree_id))
928     ex((bup_cmd, b'-d', b'get-src', b'tag', b'tree-0', tree_0_id))
929     ex((bup_cmd, b'-d', b'get-src', b'tag', b'tree-1', tree_1_id))
930     ex((bup_cmd, b'-d', b'get-src', b'tag', b'tree-2', tree_2_id))
931     ex((bup_cmd, b'-d', b'get-src', b'tag', b'commit-0', commit_0_id))
932     ex((bup_cmd, b'-d', b'get-src', b'tag', b'commit-1', commit_1_id))
933     ex((bup_cmd, b'-d', b'get-src', b'tag', b'commit-2', commit_2_id))
934     ex((b'git', b'--git-dir', b'get-src', b'branch', b'commit-1', commit_1_id))
935     ex((b'git', b'--git-dir', b'get-src', b'branch', b'commit-2', commit_2_id))
936
937     return {'tinyfile-path' : tinyfile_path,
938             'tinyfile-id' : tinyfile_id,
939             'subtree-id' : subtree_id,
940             'tree-0-id' : tree_0_id,
941             'tree-1-id' : tree_1_id,
942             'tree-2-id' : tree_2_id,
943             'commit-0-id' : commit_0_id,
944             'commit-1-id' : commit_1_id,
945             'commit-2-id' : commit_2_id,
946             'save-1' : save_1,
947             'save-2' : save_2,
948             'subtree-path' : subtree_path,
949             'subtree-vfs-path' : subtree_vfs_path}
950     
951 # FIXME: this fails in a strange way:
952 #   WVPASS given nothing get --ff not-there
953
954 dispositions_to_test = ('get',)
955
956 if int(environ.get(b'BUP_TEST_LEVEL', b'0')) >= 11:
957     dispositions_to_test += ('get-on', 'get-to')
958
959 categories = ('replace', 'universal', 'ff', 'append', 'pick_force', 'pick_noforce', 'new_tag', 'unnamed')
960
961 @pytest.mark.parametrize("disposition,category", product(dispositions_to_test, categories))
962 def test_get(tmpdir, disposition, category):
963     chdir(tmpdir)
964     try:
965         src_info = create_get_src()
966         globals().get('_test_' + category)(disposition, src_info)
967     finally:
968         chdir(top)