]> arthur.barton.de Git - bup.git/blob - lib/bup/ls.py
get: adjust for python 3 and test there
[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, S_ISLNK
7 import copy, locale, os.path, stat, sys
8
9 from bup import metadata, options, vfs, xstat
10 from bup.compat import argv_bytes
11 from bup.io import path_msg
12 from bup.options import Options
13 from bup.repo import LocalRepo, RemoteRepo
14 from bup.helpers import columnate, istty1, last, log
15
16 def item_hash(item, tree_for_commit):
17     """If the item is a Commit, return its commit oid, otherwise return
18     the item's oid, if it has one.
19
20     """
21     if tree_for_commit and isinstance(item, vfs.Commit):
22         return item.coid
23     return getattr(item, 'oid', None)
24
25 def item_info(item, name,
26               show_hash = False,
27               commit_hash=False,
28               long_fmt = False,
29               classification = None,
30               numeric_ids = False,
31               human_readable = False):
32     """Return bytes containing the information to display for the VFS
33     item.  Classification may be "all", "type", or None.
34
35     """
36     result = b''
37     if show_hash:
38         oid = item_hash(item, commit_hash)
39         result += b'%s ' % (hexlify(oid) if oid
40                             else b'0000000000000000000000000000000000000000')
41     if long_fmt:
42         meta = item.meta.copy()
43         meta.path = name
44         # FIXME: need some way to track fake vs real meta items?
45         result += metadata.summary_bytes(meta,
46                                          numeric_ids=numeric_ids,
47                                          classification=classification,
48                                          human_readable=human_readable)
49     else:
50         result += name
51         if classification:
52             cls = xstat.classification_str(vfs.item_mode(item),
53                                            classification == 'all')
54             result += cls.encode('iso-8859-1')
55     return result
56
57
58 optspec = """
59 bup ls [-r host:path] [-l] [-d] [-F] [-a] [-A] [-s] [-n] [path...]
60 --
61 r,remote=   remote repository path
62 s,hash   show hash for each file
63 commit-hash show commit hash instead of tree for commits (implies -s)
64 a,all    show hidden files
65 A,almost-all    show hidden files except . and ..
66 l        use a detailed, long listing format
67 d,directory show directories, not contents; don't follow symlinks
68 F,classify append type indicator: dir/ sym@ fifo| sock= exec*
69 file-type append type indicator: dir/ sym@ fifo| sock=
70 human-readable    print human readable file sizes (i.e. 3.9K, 4.7M)
71 n,numeric-ids list numeric IDs (user, group, etc.) rather than names
72 """
73
74 def opts_from_cmdline(args, onabort=None):
75     """Parse ls command line arguments and return a dictionary of ls
76     options, agumented with "classification", "long_listing",
77     "paths", and "show_hidden".
78
79     """
80     if onabort:
81         opt, flags, extra = Options(optspec, onabort=onabort).parse(args)
82     else:
83         opt, flags, extra = Options(optspec).parse(args)
84
85     opt.paths = [argv_bytes(x) for x in extra] or (b'/',)
86     opt.long_listing = opt.l
87     opt.classification = None
88     opt.show_hidden = None
89     for flag in flags:
90         option, parameter = flag
91         if option in ('-F', '--classify'):
92             opt.classification = 'all'
93         elif option == '--file-type':
94             opt.classification = 'type'
95         elif option in ('-a', '--all'):
96             opt.show_hidden = 'all'
97         elif option in ('-A', '--almost-all'):
98             opt.show_hidden = 'almost'
99     return opt
100
101 def within_repo(repo, opt, out):
102
103     if opt.commit_hash:
104         opt.hash = True
105
106     def item_line(item, name):
107         return item_info(item, name,
108                          show_hash=opt.hash,
109                          commit_hash=opt.commit_hash,
110                          long_fmt=opt.long_listing,
111                          classification=opt.classification,
112                          numeric_ids=opt.numeric_ids,
113                          human_readable=opt.human_readable)
114
115     ret = 0
116     pending = []
117     for path in opt.paths:
118         try:
119             if opt.directory:
120                 resolved = vfs.resolve(repo, path, follow=False)
121             else:
122                 resolved = vfs.try_resolve(repo, path)
123
124             leaf_name, leaf_item = resolved[-1]
125             if not leaf_item:
126                 log('error: cannot access %r in %r\n'
127                     % ('/'.join(path_msg(name) for name, item in resolved),
128                        path_msg(path)))
129                 ret = 1
130                 continue
131             if not opt.directory and S_ISDIR(vfs.item_mode(leaf_item)):
132                 items = vfs.contents(repo, leaf_item)
133                 if opt.show_hidden == 'all':
134                     # Match non-bup "ls -a ... /".
135                     parent = resolved[-2] if len(resolved) > 1 else resolved[0]
136                     items = chain(items, ((b'..', parent[1]),))
137                 for sub_name, sub_item in sorted(items, key=lambda x: x[0]):
138                     if opt.show_hidden != 'all' and sub_name == b'.':
139                         continue
140                     if sub_name.startswith(b'.') and \
141                        opt.show_hidden not in ('almost', 'all'):
142                         continue
143                     if opt.l:
144                         sub_item = vfs.ensure_item_has_metadata(repo, sub_item,
145                                                                 include_size=True)
146                     else:
147                         sub_item = vfs.augment_item_meta(repo, sub_item,
148                                                          include_size=True)
149                     line = item_line(sub_item, sub_name)
150                     if not opt.long_listing and istty1:
151                         pending.append(line)
152                     else:
153                         out.write(line)
154                         out.write(b'\n')
155             else:
156                 leaf_item = vfs.augment_item_meta(repo, leaf_item,
157                                                   include_size=True)
158                 line = item_line(leaf_item, os.path.normpath(path))
159                 if not opt.long_listing and istty1:
160                     pending.append(line)
161                 else:
162                     out.write(line)
163                     out.write(b'\n')
164         except vfs.IOError as ex:
165             log('bup: %s\n' % ex)
166             ret = 1
167
168     if pending:
169         out.write(columnate(pending, b''))
170
171     return ret
172
173 def via_cmdline(args, out=None, onabort=None):
174     """Write a listing of a file or directory in the bup repository to out.
175
176     When a long listing is not requested and stdout is attached to a
177     tty, the output is formatted in columns. When not attached to tty
178     (for example when the output is piped to another command), one
179     file is listed per line.
180
181     """
182     assert out
183     opt = opts_from_cmdline(args, onabort=onabort)
184     repo = RemoteRepo(argv_bytes(opt.remote)) if opt.remote else LocalRepo()
185     return within_repo(repo, opt, out)