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