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