1 """Common code for listing files from a bup repository."""
3 from __future__ import absolute_import
4 from binascii import hexlify
5 from itertools import chain
6 from stat import S_ISDIR
10 from bup import metadata, vfs, xstat
11 from bup.compat import argv_bytes
12 from bup.io import path_msg
13 from bup.options import Options
14 from bup.repo import LocalRepo, RemoteRepo
15 from bup.helpers import columnate, istty1, log
17 def item_hash(item, tree_for_commit):
18 """If the item is a Commit, return its commit oid, otherwise return
19 the item's oid, if it has one.
22 if tree_for_commit and isinstance(item, vfs.Commit):
24 return getattr(item, 'oid', None)
26 def item_info(item, name,
30 classification = None,
32 human_readable = False):
33 """Return bytes containing the information to display for the VFS
34 item. Classification may be "all", "type", or None.
39 oid = item_hash(item, commit_hash)
40 result += b'%s ' % (hexlify(oid) if oid
41 else b'0000000000000000000000000000000000000000')
43 meta = item.meta.copy()
45 # FIXME: need some way to track fake vs real meta items?
46 result += metadata.summary_bytes(meta,
47 numeric_ids=numeric_ids,
48 classification=classification,
49 human_readable=human_readable)
53 cls = xstat.classification_str(vfs.item_mode(item),
54 classification == 'all')
55 result += cls.encode('ascii')
60 bup ls [-r host:path] [-l] [-d] [-F] [-a] [-A] [-s] [-n] [path...]
62 r,remote= remote repository path
63 s,hash show hash for each file
64 commit-hash show commit hash instead of tree for commits (implies -s)
65 a,all show hidden files
66 A,almost-all show hidden files except . and ..
67 l use a detailed, long listing format
68 d,directory show directories, not contents; don't follow symlinks
69 F,classify append type indicator: dir/ sym@ fifo| sock= exec*
70 file-type append type indicator: dir/ sym@ fifo| sock=
71 human-readable print human readable file sizes (i.e. 3.9K, 4.7M)
72 n,numeric-ids list numeric IDs (user, group, etc.) rather than names
75 def opts_from_cmdline(args, onabort=None, pwd=b'/'):
76 """Parse ls command line arguments and return a dictionary of ls
77 options, agumented with "classification", "long_listing",
78 "paths", and "show_hidden".
82 opt, flags, extra = Options(optspec, onabort=onabort).parse_bytes(args)
84 opt, flags, extra = Options(optspec).parse_bytes(args)
86 opt.paths = [argv_bytes(x) for x in extra] or (pwd,)
87 opt.long_listing = opt.l
88 opt.classification = None
89 opt.show_hidden = None
91 option, parameter = flag
92 if option in ('-F', '--classify'):
93 opt.classification = 'all'
94 elif option == '--file-type':
95 opt.classification = 'type'
96 elif option in ('-a', '--all'):
97 opt.show_hidden = 'all'
98 elif option in ('-A', '--almost-all'):
99 opt.show_hidden = 'almost'
102 def within_repo(repo, opt, out, pwd=b''):
107 def item_line(item, name):
108 return item_info(item, name,
110 commit_hash=opt.commit_hash,
111 long_fmt=opt.long_listing,
112 classification=opt.classification,
113 numeric_ids=opt.numeric_ids,
114 human_readable=opt.human_readable)
117 want_meta = bool(opt.long_listing or opt.classification)
119 last_n = len(opt.paths) - 1
120 for n, printpath in enumerate(opt.paths):
121 path = posixpath.join(pwd, printpath)
124 out.write(b'%s:\n' % printpath)
127 resolved = vfs.resolve(repo, path, follow=False)
129 resolved = vfs.try_resolve(repo, path, want_meta=want_meta)
131 leaf_name, leaf_item = resolved[-1]
133 log('error: cannot access %r in %r\n'
134 % ('/'.join(path_msg(name) for name, item in resolved),
138 if not opt.directory and S_ISDIR(vfs.item_mode(leaf_item)):
139 items = vfs.contents(repo, leaf_item, want_meta=want_meta)
140 if opt.show_hidden == 'all':
141 # Match non-bup "ls -a ... /".
142 parent = resolved[-2] if len(resolved) > 1 else resolved[0]
143 items = chain(items, ((b'..', parent[1]),))
144 for sub_name, sub_item in sorted(items, key=lambda x: x[0]):
145 if opt.show_hidden != 'all' and sub_name == b'.':
147 if sub_name.startswith(b'.') and \
148 opt.show_hidden not in ('almost', 'all'):
151 sub_item = vfs.ensure_item_has_metadata(repo, sub_item,
154 sub_item = vfs.augment_item_meta(repo, sub_item,
156 line = item_line(sub_item, sub_name)
157 if not opt.long_listing and istty1:
164 leaf_item = vfs.augment_item_meta(repo, leaf_item,
166 line = item_line(leaf_item, os.path.normpath(path))
167 if not opt.long_listing and istty1:
172 except vfs.IOError as ex:
173 log('bup: %s\n' % ex)
177 out.write(columnate(pending, b''))
185 def via_cmdline(args, out=None, onabort=None):
186 """Write a listing of a file or directory in the bup repository to out.
188 When a long listing is not requested and stdout is attached to a
189 tty, the output is formatted in columns. When not attached to tty
190 (for example when the output is piped to another command), one
191 file is listed per line.
195 opt = opts_from_cmdline(args, onabort=onabort)
196 repo = RemoteRepo(argv_bytes(opt.remote)) if opt.remote else LocalRepo()
197 return within_repo(repo, opt, out)