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