From be300ec31e640b35a19d8ccb6b43dd3471054354 Mon Sep 17 00:00:00 2001 From: Rob Browning Date: Tue, 24 Dec 2013 15:34:44 -0600 Subject: [PATCH] Add -n, -A, -F, --file-type, --numeric-ids and detailed -l to "bup ls". With the addition of -A, change -a to behave more conventionally, i.e. to show "." and "..". Signed-off-by: Rob Browning Tested-by: Alexander Barton Reviewed-By: Zoran Zaric [rlb@defaultvalue.org: fall back to n.mode when n.metadata() isn't available in node_info() so that CommitDirs ("ls -AF /"), etc. will still work; thanks to Gabriel Filion for the report; fix the rdiff-backup test to use -A.] Signed-off-by: Rob Browning --- Documentation/bup-ls.md | 31 +++++--- cmd/ls-cmd.py | 1 - lib/bup/ls.py | 117 +++++++++++++++++++--------- lib/bup/metadata.py | 12 +-- t/test-rm-between-index-and-save.sh | 8 +- t/test.sh | 10 +-- 6 files changed, 118 insertions(+), 61 deletions(-) diff --git a/Documentation/bup-ls.md b/Documentation/bup-ls.md index 359d914..cc9b774 100644 --- a/Documentation/bup-ls.md +++ b/Documentation/bup-ls.md @@ -8,7 +8,7 @@ bup-ls - list the contents of a bup repository # SYNOPSIS -bup ls [-s] [-a] \ +bup ls [OPTION...] \ # DESCRIPTION @@ -21,12 +21,13 @@ the `-n` option in `bup save`), the next level is the date 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 @@ -44,11 +45,23 @@ you can view its contents using `bup join` or `git show`. -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 diff --git a/cmd/ls-cmd.py b/cmd/ls-cmd.py index 581d6e1..94a7131 100755 --- a/cmd/ls-cmd.py +++ b/cmd/ls-cmd.py @@ -9,5 +9,4 @@ top = vfs.RefList(None) # 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) diff --git a/lib/bup/ls.py b/lib/bup/ls.py index a86a904..002e15d 100644 --- a/lib/bup/ls.py +++ b/lib/bup/ls.py @@ -1,28 +1,47 @@ """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 = """ @@ -30,16 +49,22 @@ 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) @@ -47,7 +72,32 @@ def do_ls(args, pwd, default='.', onabort=None, spec_prefix=''): 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]): @@ -55,28 +105,21 @@ def do_ls(args, pwd, default='.', onabort=None, spec_prefix=''): 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 diff --git a/lib/bup/metadata.py b/lib/bup/metadata.py index d8c0a88..8c20694 100644 --- a/lib/bup/metadata.py +++ b/lib/bup/metadata.py @@ -8,7 +8,7 @@ import errno, os, sys, stat, time, pwd, grp, socket 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 @@ -865,19 +865,21 @@ all_fields = frozenset(['path', '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 '' diff --git a/t/test-rm-between-index-and-save.sh b/t/test-rm-between-index-and-save.sh index 31b6f3b..ee0cd0a 100755 --- a/t/test-rm-between-index-and-save.sh +++ b/t/test-rm-between-index-and-save.sh @@ -29,7 +29,7 @@ WVPASS rm "$D"/foo 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 @@ -37,7 +37,7 @@ WVPASS bup tick 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" @@ -62,7 +62,7 @@ WVPASS rmdir "$D"/foo # (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 @@ -70,7 +70,7 @@ WVPASS bup index -ux "$D" # 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" diff --git a/t/test.sh b/t/test.sh index 2413bed..be0d549 100755 --- a/t/test.sh +++ b/t/test.sh @@ -345,7 +345,7 @@ WVPASS mkdir $D/d $D/d/e 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" @@ -597,7 +597,7 @@ WVPASS touch $D/b 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 @@ -618,7 +618,7 @@ WVPASS mkdir -p $D/hourly.0/buptest/c/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/" @@ -635,7 +635,7 @@ if [ "$(type -p rdiff-backup)" != "" ]; then 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 @@ -699,7 +699,7 @@ WVSTART "save disjoint top-level directories" 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 $? -- 2.39.2