# SYNOPSIS
-bup ls [-s] [-a] \<paths...\>
+bup ls [OPTION...] \<paths...\>
# DESCRIPTION
of the backup, and subsequent levels correspond to files in
the backup.
-When `bup ls` is asked to output on a tty, it formats its output
-in columns so that it can list as much as possible in as few lines
-as possible. However, when `bup ls` is asked to output to something
-other than a tty (say you pipe the output to another command, or you
-redirect it to a file), it will output one file name per line. This
-makes the listing easier to parse with external tools.
+When `bup ls` is asked to output on a tty, and `-l` is not specified,
+it formats the output in columns so it can list as much as possible in
+as few lines as possible. However, when `-l` is specified or bup is
+asked to output to something other than a tty (say you pipe the output
+to another command, or you redirect it to a file), it will print one
+file name per line. This makes the listing easier to parse with
+external tools.
Note that `bup ls` doesn't show hidden files by default and one needs to use
the `-a` option to show them. Files are hidden when their name begins with a
-a, \--all
: show hidden files.
+-A, \--almost-all
+: show hidden files, except "." and "..".
+
-l
-: show file sizes.
+: provide a detailed, long listing for each item.
+
+-F, \--classify
+: append type indicator: dir/, symlink@, fifo|, socket=, and executable*.
+
+\--file-type
+: append type indicator: dir/, symlink@, fifo|, socket=.
\--human-readable
-: print human readable file sizes (i.e. 3.9K, 4.7M)
+: print human readable file sizes (i.e. 3.9K, 4.7M).
+
+\--numeric-ids
+: display numeric IDs (user, group, etc.) rather than names.
# EXAMPLE
# Check out lib/bup/ls.py for the opt spec
ret = ls.do_ls(sys.argv[1:], top, default='/', spec_prefix='bup ')
-
sys.exit(ret)
"""Common code for listing files from a bup repository."""
-import stat
-from bup import options, vfs
+import copy, stat
+from bup import metadata, options, vfs
from helpers import *
-def node_name(text, n, show_hash = False,
- show_filesize = False,
- filesize = None,
+def node_info(n, name,
+ show_hash = False,
+ long_fmt = False,
+ classification = None,
+ numeric_ids = False,
human_readable = False):
- """Add symbols to a node's name to differentiate file types."""
- prefix = ''
+ """Return a string containing the information to display for the node
+ n. Classification may be "all", "type", or None.
+
+ """
+ result = ''
if show_hash:
- prefix += "%s " % n.hash.encode('hex')
- if show_filesize:
- if human_readable:
- prefix += "%10s " % format_filesize(filesize)
+ result += "%s " % n.hash.encode('hex')
+ if long_fmt:
+ meta = copy.copy(n.metadata())
+ meta.size = n.size()
+ result += metadata.summary_str(meta,
+ numeric_ids = numeric_ids,
+ human_readable = human_readable) + ' '
+ result += name
+ if classification:
+ if n.metadata():
+ mode = n.metadata().mode
else:
- prefix += "%14d " % filesize
- if stat.S_ISDIR(n.mode):
- return '%s%s/' % (prefix, text)
- elif stat.S_ISLNK(n.mode):
- return '%s%s@' % (prefix, text)
- else:
- return '%s%s' % (prefix, text)
+ mode = n.mode
+ if stat.S_ISREG(mode):
+ if classification == 'all' \
+ and stat.S_IMODE(mode) & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH):
+ result += '*'
+ elif stat.S_ISDIR(mode):
+ result += '/'
+ elif stat.S_ISLNK(mode):
+ result += '@'
+ elif stat.S_ISFIFO(mode):
+ result += '|'
+ elif stat.S_ISSOCK(mode):
+ result += '='
+ return result
optspec = """
--
s,hash show hash for each file
a,all show hidden files
-l show file sizes
+A,almost-all show hidden files except . and ..
+l use a detailed, long listing format
+F,classify append type indicator: dir/ sym@ fifo| sock= exec*
+file-type append type indicator: dir/ sym@ fifo| sock=
human-readable print human readable file sizes (i.e. 3.9K, 4.7M)
+n,numeric-ids list numeric IDs (user, group, etc.) rather than names
"""
def do_ls(args, pwd, default='.', onabort=None, spec_prefix=''):
"""Output a listing of a file or directory in the bup repository.
- When stdout is attached to a tty, the output is formatted in columns. When
- not attached to tty (for example when the output is piped to another
- command), one file is listed per line.
+ When a long listing is not requested and stdout is attached to a
+ tty, the output is formatted in columns. When not attached to tty
+ (for example when the output is piped to another command), one
+ file is listed per line.
+
"""
if onabort:
o = options.Options(optspec % spec_prefix, onabort=onabort)
o = options.Options(optspec % spec_prefix)
(opt, flags, extra) = o.parse(args)
+ # Handle order-sensitive options.
+ classification = None
+ show_hidden = None
+ for flag in flags:
+ (option, parameter) = flag
+ if option in ('-F', '--classify'):
+ classification = 'all'
+ elif option == '--file-type':
+ classification = 'type'
+ elif option in ('-a', '--all'):
+ show_hidden = 'all'
+ elif option in ('-A', '--almost-all'):
+ show_hidden = 'almost'
+
L = []
+ def output_node_info(node, name):
+ info = node_info(node, name,
+ show_hash = opt.hash,
+ long_fmt = opt.l,
+ classification = classification,
+ numeric_ids = opt.numeric_ids,
+ human_readable = opt.human_readable)
+ if not opt.l and istty1:
+ L.append(info)
+ else:
+ print info
ret = 0
for path in (extra or [default]):
n = pwd.try_resolve(path)
if stat.S_ISDIR(n.mode):
+ if show_hidden == 'all':
+ for implied, name in ((n, '.'), (n.parent, '..')):
+ output_node_info(implied, name)
for sub in n:
name = sub.name
- fsize = sub.size() if opt.l else None
- nname = node_name(name, sub, opt.hash, opt.l, fsize,
- opt.human_readable)
- if opt.all or not len(name)>1 or not name.startswith('.'):
- if istty1:
- L.append(nname)
- else:
- print nname
+ if show_hidden in ('almost', 'all') \
+ or not len(name)>1 or not name.startswith('.'):
+ output_node_info(sub, name)
else:
- nname = node_name(path, n, opt.hash, opt.l, None,
- opt.human_readable)
- if istty1:
- L.append(nname)
- else:
- print nname
+ output_node_info(n, path)
except vfs.NodeError, e:
log('error: %s\n' % e)
ret = 1
- if istty1:
+ if L:
sys.stdout.write(columnate(L, ''))
return ret
from cStringIO import StringIO
from bup import vint, xstat
from bup.drecurse import recursive_dirlist
-from bup.helpers import add_error, mkdirp, log, is_superuser
+from bup.helpers import add_error, mkdirp, log, is_superuser, format_filesize
from bup.helpers import pwd_from_uid, pwd_from_name, grp_from_gid, grp_from_name
from bup.xstat import utime, lutime
'posix1e-acl'])
-def summary_str(meta):
+def summary_str(meta, numeric_ids = False, human_readable = False):
mode_val = xstat.mode_str(meta.mode)
user_val = meta.user
- if not user_val:
+ if numeric_ids or not user_val:
user_val = str(meta.uid)
group_val = meta.group
- if not group_val:
+ if numeric_ids or not group_val:
group_val = str(meta.gid)
size_or_dev_val = '-'
if stat.S_ISCHR(meta.mode) or stat.S_ISBLK(meta.mode):
size_or_dev_val = '%d,%d' % (os.major(meta.rdev), os.minor(meta.rdev))
- elif meta.size:
+ elif meta.size != None:
size_or_dev_val = meta.size
+ if human_readable:
+ size_or_dev_val = format_filesize(meta.size)
mtime_secs = xstat.fstime_floor_secs(meta.mtime)
time_val = time.strftime('%Y-%m-%d %H:%M', time.localtime(mtime_secs))
path_val = meta.path or ''
WVPASS bup save -n save-fail-missing "$D"
# when the save-call failed, foo is missing from output, since only
# then bup notices, that it was removed:
-WVPASSEQ "$(bup ls -a save-fail-missing/latest/$TOP/$D/)" "bar
+WVPASSEQ "$(bup ls -A save-fail-missing/latest/$TOP/$D/)" "bar
baz
foo"
# index/save again
WVPASS bup index -ux "$D"
WVPASS bup save -n save-fail-missing "$D"
# now foo is gone:
-WVPASSEQ "$(bup ls -a save-fail-missing/latest/$TOP/$D/)" "bar
+WVPASSEQ "$(bup ls -A save-fail-missing/latest/$TOP/$D/)" "bar
baz"
# (complete with delayed error)
WVFAIL bup save -n save-fail-missing "$D"
# ... so "foo" is absent from "bup ls"
-WVPASSEQ "$(bup ls -a save-fail-missing/latest/$TOP/$D/)" "bar/
+WVPASSEQ "$(bup ls -AF save-fail-missing/latest/$TOP/$D/)" "bar/
baz/"
# Index again:
WVPASS bup tick
# no non-zero-exitcode anymore:
WVPASS bup save -n save-fail-missing "$D"
# foo is (still...) missing, of course:
-WVPASSEQ "$(bup ls -a save-fail-missing/latest/$TOP/$D/)" "bar/
+WVPASSEQ "$(bup ls -AF save-fail-missing/latest/$TOP/$D/)" "bar/
baz/"
WVPASS rm -rf "$tmpdir"
WVPASS bup random 512 >$D/f
WVPASS bup index -ux $D
WVPASS bup save -n exclude-bupdir $D
-WVPASSEQ "$(bup ls -a exclude-bupdir/latest/$TOP/$D/)" "a
+WVPASSEQ "$(bup ls -AF exclude-bupdir/latest/$TOP/$D/)" "a
b
d/
f"
WVPASS mkdir $D/c
WVPASS bup index -ux $D
WVPASS bup save --strip -n bupdir $D
-WVPASSEQ "$(bup ls bupdir/latest/)" "a
+WVPASSEQ "$(bup ls -F bupdir/latest/)" "a
b
c/"
WVPASS bup index -f $INDEXFILE --exclude=$D/c -ux $D
WVPASS touch $D/hourly.0/buptest/c/d/e
WVPASS true
WVPASS bup import-rsnapshot $D/
-WVPASSEQ "$(bup ls buptest/latest/)" "a/
+WVPASSEQ "$(bup ls -F buptest/latest/)" "a/
c/"
WVPASS rdiff-backup $TOP/Documentation $D/rdiff-backup
WVPASS bup import-rdiff-backup $D/rdiff-backup import-rdiff-backup
WVPASSEQ $(bup ls import-rdiff-backup/ | wc -l) 3
- WVPASSEQ "$(bup ls -a import-rdiff-backup/latest/ | sort)" \
+ WVPASSEQ "$(bup ls -A import-rdiff-backup/latest/ | sort)" \
"$(ls -A $TOP/Documentation | sort)"
fi
WVPASS bup save -t -n src $(pwd)/$D/x "$tmpdir"
# For now, assume that "ls -a" and "sort" use the same order.
- actual="$(WVPASS bup ls -a src/latest)" || exit $?
+ actual="$(WVPASS bup ls -AF src/latest)" || exit $?
expected="$(echo -e "$pwd_top/\n$tmp_top/" | WVPASS sort)" || exit $?
WVPASSEQ "$actual" "$expected"
) || exit $?