]> arthur.barton.de Git - bup.git/blob - lib/bup/ls.py
755c9981878385a2222b2223c8d53d9006ff2a83
[bup.git] / lib / bup / ls.py
1 """Common code for listing files from a bup repository."""
2
3 from __future__ import absolute_import
4 from binascii import hexlify
5 from itertools import chain
6 from stat import S_ISDIR
7 import os.path
8 import posixpath
9
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
16
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.
20
21     """
22     if tree_for_commit and isinstance(item, vfs.Commit):
23         return item.coid
24     return getattr(item, 'oid', None)
25
26 def item_info(item, name,
27               show_hash = False,
28               commit_hash=False,
29               long_fmt = False,
30               classification = None,
31               numeric_ids = False,
32               human_readable = False):
33     """Return bytes containing the information to display for the VFS
34     item.  Classification may be "all", "type", or None.
35
36     """
37     result = b''
38     if show_hash:
39         oid = item_hash(item, commit_hash)
40         result += b'%s ' % (hexlify(oid) if oid
41                             else b'0000000000000000000000000000000000000000')
42     if long_fmt:
43         meta = item.meta.copy()
44         meta.path = name
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)
50     else:
51         result += name
52         if classification:
53             cls = xstat.classification_str(vfs.item_mode(item),
54                                            classification == 'all')
55             result += cls.encode('ascii')
56     return result
57
58
59 optspec = """
60 bup ls [-r host:path] [-l] [-d] [-F] [-a] [-A] [-s] [-n] [path...]
61 --
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
73 """
74
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".
79
80     """
81     if onabort:
82         opt, flags, extra = Options(optspec, onabort=onabort).parse_bytes(args)
83     else:
84         opt, flags, extra = Options(optspec).parse_bytes(args)
85
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
90     for flag in flags:
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'
100     return opt
101
102 def within_repo(repo, opt, out, pwd=b''):
103
104     if opt.commit_hash:
105         opt.hash = True
106
107     def item_line(item, name):
108         return item_info(item, name,
109                          show_hash=opt.hash,
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)
115
116     ret = 0
117     want_meta = bool(opt.long_listing or opt.classification)
118     pending = []
119     last_n = len(opt.paths) - 1
120     for n, printpath in enumerate(opt.paths):
121         path = posixpath.join(pwd, printpath)
122         try:
123             if last_n > 0:
124                 out.write(b'%s:\n' % printpath)
125
126             if opt.directory:
127                 resolved = vfs.resolve(repo, path, follow=False)
128             else:
129                 resolved = vfs.try_resolve(repo, path, want_meta=want_meta)
130
131             leaf_name, leaf_item = resolved[-1]
132             if not leaf_item:
133                 log('error: cannot access %r in %r\n'
134                     % ('/'.join(path_msg(name) for name, item in resolved),
135                        path_msg(path)))
136                 ret = 1
137                 continue
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'.':
146                         continue
147                     if sub_name.startswith(b'.') and \
148                        opt.show_hidden not in ('almost', 'all'):
149                         continue
150                     if opt.l:
151                         sub_item = vfs.ensure_item_has_metadata(repo, sub_item,
152                                                                 include_size=True)
153                     elif want_meta:
154                         sub_item = vfs.augment_item_meta(repo, sub_item,
155                                                          include_size=True)
156                     line = item_line(sub_item, sub_name)
157                     if not opt.long_listing and istty1:
158                         pending.append(line)
159                     else:
160                         out.write(line)
161                         out.write(b'\n')
162             else:
163                 if opt.long_listing:
164                     leaf_item = vfs.augment_item_meta(repo, leaf_item,
165                                                       include_size=True)
166                 line = item_line(leaf_item, os.path.normpath(path))
167                 if not opt.long_listing and istty1:
168                     pending.append(line)
169                 else:
170                     out.write(line)
171                     out.write(b'\n')
172         except vfs.IOError as ex:
173             log('bup: %s\n' % ex)
174             ret = 1
175
176         if pending:
177             out.write(columnate(pending, b''))
178             pending = []
179
180         if n < last_n:
181             out.write(b'\n')
182
183     return ret
184
185 def via_cmdline(args, out=None, onabort=None):
186     """Write a listing of a file or directory in the bup repository to out.
187
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.
192
193     """
194     assert out
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)