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