]> arthur.barton.de Git - bup.git/commitdiff
Add -n, -A, -F, --file-type, --numeric-ids and detailed -l to "bup ls".
authorRob Browning <rlb@defaultvalue.org>
Tue, 24 Dec 2013 21:34:44 +0000 (15:34 -0600)
committerRob Browning <rlb@defaultvalue.org>
Tue, 24 Dec 2013 21:34:44 +0000 (15:34 -0600)
With the addition of -A, change -a to behave more conventionally,
i.e. to show "." and "..".

Signed-off-by: Rob Browning <rlb@defaultvalue.org>
Tested-by: Alexander Barton <alex@barton.de>
Reviewed-By: Zoran Zaric <zz@zoranzaric.de>
[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 <gabster@lelutin.ca> for the
 report; fix the rdiff-backup test to use -A.]
Signed-off-by: Rob Browning <rlb@defaultvalue.org>
Documentation/bup-ls.md
cmd/ls-cmd.py
lib/bup/ls.py
lib/bup/metadata.py
t/test-rm-between-index-and-save.sh
t/test.sh

index 359d91486a3a088e3f8b8dc79f9c9f3ac57932b3..cc9b77409543fd93268d3efb39afbef65868f57d 100644 (file)
@@ -8,7 +8,7 @@ bup-ls - list the contents of a bup repository
 
 # SYNOPSIS
 
-bup ls [-s] [-a] \<paths...\>
+bup ls [OPTION...] \<paths...\>
 
 # 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
 
index 581d6e116891387659dec7ee1d04fd6098bd37d0..94a7131f61c242f196d393d6a0934ab6d363df9b 100755 (executable)
@@ -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)
index a86a904a0a83d28dc7a7f155c2e40896b71bfc22..002e15d60f824f97c474deb96fa50b7c31d5352a 100644 (file)
@@ -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
index d8c0a8896b2578ae5ac7bcd310f5cbfac55acd67..8c206946c3fb1a6a6a9b8254a95e1f4cf0b366b4 100644 (file)
@@ -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 ''
index 31b6f3b43fa396a9776708689189bb8818641568..ee0cd0ac31a2f0c868fd3db5584cc238592b3c72 100755 (executable)
@@ -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"
index 2413bed4d36fbee87a35782a326221b54298c42d..be0d54996d128c0c34c99862e5f52b7e34a856b6 100755 (executable)
--- 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 $?