From 31899c1e29f0f38f156386329d45face10a89162 Mon Sep 17 00:00:00 2001 From: ArneBachmann Date: Sat, 3 Mar 2018 15:29:39 +0100 Subject: [PATCH] Fixes #102. --- README.md | 2 +- setup.py | 2 +- sos/sos.coco | 59 ++++++++++++++++++++++++++++++-------------------- sos/tests.coco | 18 ++++++++++----- sos/usage.coco | 10 ++++++--- 5 files changed, 56 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 11ec6b8..40bb7f1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Subversion Offline Solution (SOS 1.4.7) # +# Subversion Offline Solution (SOS 1.5.0) # [![Travis badge](https://travis-ci.org/ArneBachmann/sos.svg?branch=master)](https://travis-ci.org/ArneBachmann/sos) [![Build status](https://ci.appveyor.com/api/projects/status/fe915rtx02buqe4r?svg=true)](https://ci.appveyor.com/project/ArneBachmann/sos) diff --git a/setup.py b/setup.py index 172bec4..714fc8a 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ import os, shutil, subprocess, sys, time, unittest from setuptools import setup, find_packages -RELEASE = "1.4.7" +RELEASE = "1.5.0" print("sys.argv is %r" % sys.argv) readmeFile = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'README.md') diff --git a/sos/sos.coco b/sos/sos.coco index 0580be1..747d25c 100644 --- a/sos/sos.coco +++ b/sos/sos.coco @@ -804,35 +804,46 @@ def remove(relPath:str, pattern:str, negative:bool = False): def ls(folder:str? = None, options:str[] = []): ''' List specified directory, augmenting with repository metadata. ''' - folder = folder ?? os.getcwd() m:Metadata = Metadata() + folder = folder ?? os.getcwd() + if '--all' in options: folder = m.root # always start at SOS repo root with --all + recursive:bool = '--recursive' in options or '-r' in options or '--all' in options + patterns:bool = '--patterns' in options or '-p' in options + DOT:str = (DOT_SYMBOL if m.c.useUnicodeFont else " ") * 3 debug(MARKER + "Repository is in %s mode" % ("tracking" if m.track else ("picky" if m.picky else "simple"))) relPath:str = relativize(m.root, os.path.join(folder, "-"))[0] if relPath.startswith(os.pardir): Exit("Cannot list contents of folder outside offline repository") trackingPatterns:FrozenSet[str]? = m.getTrackingPatterns() if m.track or m.picky else f{} # for current branch untrackingPatterns:FrozenSet[str]? = m.getTrackingPatterns(negative = True) if m.track or m.picky else f{} # for current branch - if '--tags' in options: - printo(ajoin("TAG ", sorted(m.tags), nl = "\n")) - return - if '--patterns' in options: - out:str = ajoin("TRK ", [os.path.basename(p) for p in trackingPatterns if os.path.dirname(p).replace(os.sep, SLASH) == relPath], nl = "\n") - if out: printo(out) + if '--tags' in options: # TODO this has nothing to do with "ls" - it's an entirely different command. Move if something like "sos tag" has been implemented + if len(m.tags) > 0: printo(ajoin("TAG ", sorted(m.tags), nl = "\n")) return - files:List[str] = list(sorted(entry for entry in os.listdir(folder) if os.path.isfile(os.path.join(folder, entry)))) - printo("DIR %s" % relPath) - for file in files: # for each file list all tracking patterns that match, or none (e.g. in picky mode after commit) - ignore:str? = None - for ig in m.c.ignores: - if fnmatch.fnmatch(file, ig): ignore = ig; break # remember first match - if ig: - for wl in m.c.ignoresWhitelist: - if fnmatch.fnmatch(file, wl): ignore = None; break # found a white list entry for ignored file, undo ignoring it - matches:List[str] = [] - if not ignore: - for pattern in (p for p in trackingPatterns if os.path.dirname(p).replace(os.sep, SLASH) == relPath): # only patterns matching current folder - if fnmatch.fnmatch(file, os.path.basename(pattern)): matches.append(os.path.basename(pattern)) - matches.sort(key = (element) -> len(element)) # sort in-place - printo("%s %s%s" % ("IGN" if ignore is not None else ("TRK" if len(matches) > 0 else DOT_SYMBOL * 3), file, " (%s)" % ignore if ignore is not None else (" (%s)" % ("; ".join(matches)) if len(matches) > 0 else ""))) + for dirpath, dirnames, _filenames in os.walk(folder): + if not recursive: dirnames.clear() # avoid recursion + dirnames[:] = sorted([decode(d) for d in dirnames]) + dirnames[:] = [d for d in dirnames if len([n for n in m.c.ignoreDirs if fnmatch.fnmatch(d, n)]) == 0 or len([p for p in m.c.ignoreDirsWhitelist if fnmatch.fnmatch(d, p)]) > 0] # global ignores + + folder = decode(dirpath) + relPath:str = relativize(m.root, os.path.join(folder, "-"))[0] + if patterns: + out:str = ajoin("TRK ", [os.path.basename(p) for p in trackingPatterns if os.path.dirname(p).replace(os.sep, SLASH) == relPath], nl = "\n") + if out: printo("DIR %s\n" % relPath + out) + continue # with next folder + files:List[str] = list(sorted(entry for entry in os.listdir(folder) if os.path.isfile(os.path.join(folder, entry)))) + if len(files) > 0: printo("DIR %s" % relPath) + for file in files: # for each file list all tracking patterns that match, or none (e.g. in picky mode after commit) + ignore:str? = None + for ig in m.c.ignores: + if fnmatch.fnmatch(file, ig): ignore = ig; break # remember first match + if ig: + for wl in m.c.ignoresWhitelist: + if fnmatch.fnmatch(file, wl): ignore = None; break # found a white list entry for ignored file, undo ignoring it + matches:List[str] = [] + if not ignore: + for pattern in (p for p in trackingPatterns if os.path.dirname(p).replace(os.sep, SLASH) == relPath): # only patterns matching current folder + if fnmatch.fnmatch(file, os.path.basename(pattern)): matches.append(os.path.basename(pattern)) + matches.sort(key = (element) -> len(element)) # sort in-place + printo("%s %s%s" % ("IGN" if ignore is not None else ("TRK" if len(matches) > 0 else DOT), file, " (%s)" % ignore if ignore is not None else (" (%s)" % ("; ".join(matches)) if len(matches) > 0 else ""))) def log(options:str[] = []): ''' List previous commits on current branch. ''' @@ -1009,7 +1020,7 @@ def move(relPath:str, pattern:str, newRelPath:str, newPattern:str, options:List[ m.saveBranches() def parse(root:str, cwd:str, cmd:str): - ''' Main operation. Main has already chdir into VCS root folder, cwd is original working directory for add, rm, mv. ''' + ''' Main operation. Main has already chdir into SOS root folder, cwd is original working directory for add, rm, mv. ''' debug("Parsing command-line arguments...") try: onlys, excps = parseOnlyOptions(cwd, sys.argv) # extracts folder-relative information for changes, commit, diff, switch, update @@ -1057,7 +1068,7 @@ def main(): if '--help' in sys.argv or len(sys.argv) < 2: usage(APPNAME, version.__version__) command:str? = sys.argv[1] if len(sys.argv) > 1 else None root, vcs, cmd = findSosVcsBase() # root is None if no .sos folder exists up the folder tree (still working online); vcs is checkout/repo root folder; cmd is the VCS base command - debug("Found root folders for SOS|VCS: %s|%s" % (root ?? "-", vcs ?? "-")) + debug("Found root folders for SOS | VCS: %s | %s" % (root ?? "-", vcs ?? "-")) defaults["defaultbranch"] = vcsBranches.get(cmd, "trunk") ?? "default" # sets dynamic default with SVN fallback defaults["useChangesCommand"] = cmd == "fossil" # sets dynamic default with SVN fallback if force_sos or root is not None or (command ?? "")[:2] == "of" or (command ?? "")[:1] in "hv": # in offline mode or just going offline TODO what about git config? diff --git a/sos/tests.coco b/sos/tests.coco index c4f26dd..ad14cc2 100644 --- a/sos/tests.coco +++ b/sos/tests.coco @@ -721,7 +721,7 @@ class Tests(unittest.TestCase): out = sos.safeSplit(wrapChannels(() -> sos.ls()).replace("\r", ""), "\n") _.assertInAny("TRK file1 (file*)", out) _.assertNotInAny("... file1 (file*)", out) - _.assertInAny("··· foo", out) + _.assertInAny(" foo", out) out = sos.safeSplit(wrapChannels(() -> sos.ls(options = ["--patterns"])).replace("\r", ""), "\n") _.assertInAny("TRK file*", out) _.createFile("a", prefix = "sub") @@ -817,6 +817,7 @@ class Tests(unittest.TestCase): _.createFile("foo") _.createFile("ign1") _.createFile("ign2") + _.createFile("bar", prefix = "sub") sos.offline("test") # set up repo in tracking mode (SVN- or gitless-style) try: sos.config(["set", "ignores", "ign1"]) # define an ignore pattern except SystemExit as E: _.assertEqual(0, E.code) @@ -827,9 +828,14 @@ class Tests(unittest.TestCase): out = wrapChannels(() -> sos.config(["show"])).replace("\r", "") _.assertIn(" ignores [global] ['ign1', 'ign2']", out) out = sos.safeSplit(wrapChannels(() -> sos.ls()).replace("\r", ""), "\n") - _.assertInAny('··· file1', out) - _.assertInAny('··· ign1', out) - _.assertInAny('··· ign2', out) + _.assertInAny(' file1', out) + _.assertInAny(' ign1', out) + _.assertInAny(' ign2', out) + _.assertNotIn('DIR sub', out) + _.assertNotIn(' bar', out) + out = wrapChannels(() -> sos.ls(options = ["--recursive"])).replace("\r", "") + _.assertIn('DIR sub', out) + _.assertIn(' bar', out) try: sos.config(["rm", "foo", "bar"]); _.fail() except SystemExit as E: _.assertEqual(1, E.code) try: sos.config(["rm", "ignores", "foo"]); _.fail() @@ -839,9 +845,9 @@ class Tests(unittest.TestCase): try: sos.config(["unset", "ignoresWhitelist"]) # remove ignore pattern except SystemExit as E: _.assertEqual(0, E.code) out = sos.safeSplit(wrapChannels(() -> sos.ls()).replace("\r", ""), "\n") - _.assertInAny('··· ign1', out) + _.assertInAny(' ign1', out) _.assertInAny('IGN ign2', out) - _.assertNotInAny('··· ign2', out) + _.assertNotInAny(' ign2', out) def testWhitelist(_): # TODO test same for simple mode diff --git a/sos/usage.coco b/sos/usage.coco index 1a6a66d..f72187e 100644 --- a/sos/usage.coco +++ b/sos/usage.coco @@ -21,8 +21,8 @@ Usage: {cmd} [] [, ...] When operating in of --strict Always compare entire file contents online Finish working offline dump [/] Perform (differential) repository dump - [--full] Export the entire repository, don't attempt differential backup - [--skip-nackup] Don't create an archive copy before commencing backup + [--full] Export the entire repository, don't attempt differential backup, if file already exists + [--skip-nackup] Don't create an archive copy before backup Working with branches: branch [ []] Create a new branch from current file tree and switch to it @@ -45,7 +45,11 @@ Usage: {cmd} [] [, ...] When operating in of --to=branch/revision Take "to" revision as target to compare against (instead of current file tree state) --ignore-whitespace | --iw Ignore white spaces during comparison --wrap Wrap text around terminal size instead of shortening - ls [] [--patterns|--tags] List file tree and mark changes and tracking status + ls [] List file tree and mark changes and tracking status + --patterns Only show tracking patterns + --tags List all repository tags (has nothing to do with file or filepattern listing) + --recursive | -r Recursively list also sub-folders + --all | -a Recursively list all starting from repository root Defining file patterns: add[not] Add a tracking pattern to current branch (file pattern). Using addnot adds to tracking blacklist