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