summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAndreas Rumpf <rumpf_a@web.de>2023-06-10 17:32:11 +0200
committerGitHub <noreply@github.com>2023-06-10 17:32:11 +0200
commit21d941c16a84daa855870717f064802b052d6792 (patch)
tree39302063935e97ae32cb6236ecd5ef5c9794fbc2
parent8bba04078cc93556e027bd43c0c87c34d88ee528 (diff)
downloadNim-21d941c16a84daa855870717f064802b052d6792.tar.gz
Atlas: now in its own repository (#22066)
* Atlas: now in its own repository

* progress
-rw-r--r--atlas/atlas.nim1345
-rw-r--r--atlas/atlas.nim.cfg1
-rw-r--r--atlas/compiledpatterns.nim246
-rw-r--r--atlas/osutils.nim51
-rw-r--r--atlas/packagesjson.nim161
-rw-r--r--atlas/sat.nim313
-rw-r--r--atlas/testdata.nim63
-rw-r--r--atlas/tester.nim46
-rw-r--r--atlas/tests/balls.nimble32
-rw-r--r--atlas/tests/grok.nimble5
-rw-r--r--atlas/tests/nim-bytes2human.nimble7
-rw-r--r--atlas/tests/nim.cfg10
-rw-r--r--atlas/tests/npeg.nimble48
-rw-r--r--atlas/tests/packages/packages.json36
-rw-r--r--atlas/tests/sync.nimble10
-rw-r--r--atlas/tests/testes.nimble23
-rw-r--r--atlas/tests/ups.nimble13
-rw-r--r--atlas/tests/ws_conflict/atlas.workspace2
-rw-r--r--atlas/tests/ws_conflict/expected/atlas.lock20
-rw-r--r--atlas/tests/ws_conflict/expected/deps.dot11
-rw-r--r--atlas/tests/ws_conflict/expected/myproject.nimble1
-rw-r--r--atlas/tests/ws_conflict/expected/nim.cfg7
-rw-r--r--atlas/tests/ws_conflict/source/apkg/apkg.nimble4
-rw-r--r--atlas/tests/ws_conflict/source/bpkg@1.0/bpkg.nimble1
-rw-r--r--atlas/tests/ws_conflict/source/cpkg@1.0/cpkg.nimble1
-rw-r--r--atlas/tests/ws_conflict/source/cpkg@2.0/cpkg.nimble1
-rw-r--r--atlas/tests/ws_conflict/source/dpkg/dpkg.nimble1
-rw-r--r--atlas/tests/ws_conflict/url.rules1
-rw-r--r--atlas/versions.nim348
-rw-r--r--compiler/installer.ini1
-rw-r--r--doc/atlas.md188
-rw-r--r--koch.nim26
-rw-r--r--tools/atlas/parse_requires.nim (renamed from atlas/parse_requires.nim)2
33 files changed, 15 insertions, 3010 deletions
diff --git a/atlas/atlas.nim b/atlas/atlas.nim
deleted file mode 100644
index 2af1a3959..000000000
--- a/atlas/atlas.nim
+++ /dev/null
@@ -1,1345 +0,0 @@
-#
-#           Atlas Package Cloner
-#        (c) Copyright 2021 Andreas Rumpf
-#
-#    See the file "copying.txt", included in this
-#    distribution, for details about the copyright.
-#
-
-## Simple tool to automate frequent workflows: Can "clone"
-## a Nimble dependency and its dependencies recursively.
-
-import std / [parseopt, strutils, os, osproc, tables, sets, json, jsonutils,
-  parsecfg, streams, terminal, strscans, hashes]
-import parse_requires, osutils, packagesjson, compiledpatterns, versions, sat
-
-from unicode import nil
-
-const
-  AtlasVersion = "0.4"
-  LockFileName = "atlas.lock"
-  AtlasWorkspace = "atlas.workspace"
-  Usage = "atlas - Nim Package Cloner Version " & AtlasVersion & """
-
-  (c) 2021 Andreas Rumpf
-Usage:
-  atlas [options] [command] [arguments]
-Command:
-  init                  initializes the current directory as a workspace
-    --deps=DIR          use DIR as the directory for dependencies
-                        (default: store directly in the workspace)
-
-  use url|pkgname       clone a package and all of its dependencies and make
-                        it importable for the current project
-  clone url|pkgname     clone a package and all of its dependencies
-  update url|pkgname    update a package and all of its dependencies
-  install proj.nimble   use the .nimble file to setup the project's dependencies
-  search keyw keywB...  search for package that contains the given keywords
-  extract file.nimble   extract the requirements and custom commands from
-                        the given Nimble file
-  updateProjects [filter]
-                        update every project that has a remote
-                        URL that matches `filter` if a filter is given
-  updateDeps [filter]
-                        update every dependency that has a remote
-                        URL that matches `filter` if a filter is given
-  tag [major|minor|patch]
-                        add and push a new tag, input must be one of:
-                        ['major'|'minor'|'patch'] or a SemVer tag like ['1.0.3']
-                        or a letter ['a'..'z']: a.b.c.d.e.f.g
-  outdated              list the packages that are outdated
-  build|test|doc|tasks  currently delegates to `nimble build|test|doc`
-  task <taskname>       currently delegates to `nimble <taskname>`
-  env <nimversion>      setup a Nim virtual environment
-    --keep              keep the c_code subdirectory
-
-Options:
-  --keepCommits         do not perform any `git checkouts`
-  --cfgHere             also create/maintain a nim.cfg in the current
-                        working directory
-  --workspace=DIR       use DIR as workspace
-  --project=DIR         use DIR as the current project
-  --genlock             generate a lock file (use with `clone` and `update`)
-  --uselock             use the lock file for the build
-  --autoenv             detect the minimal Nim $version and setup a
-                        corresponding Nim virtual environment
-  --autoinit            auto initialize a workspace
-  --colors=on|off       turn on|off colored output
-  --resolver=minver|semver|maxver
-                        which resolution algorithm to use, default is minver
-  --showGraph           show the dependency graph
-  --version             show the version
-  --help                show this help
-"""
-
-proc writeHelp() =
-  stdout.write(Usage)
-  stdout.flushFile()
-  quit(0)
-
-proc writeVersion() =
-  stdout.write(AtlasVersion & "\n")
-  stdout.flushFile()
-  quit(0)
-
-const
-  MockupRun = defined(atlasTests)
-  TestsDir = "atlas/tests"
-
-type
-  LockMode = enum
-    noLock, genLock, useLock
-
-  LockFileEntry = object
-    url, commit: string
-
-  PackageName = distinct string
-  CfgPath = distinct string # put into a config `--path:"../x"`
-  DepRelation = enum
-    normal, strictlyLess, strictlyGreater
-
-  SemVerField = enum
-    major, minor, patch
-
-  ResolutionAlgorithm = enum
-    MinVer, SemVer, MaxVer
-
-  Dependency = object
-    name: PackageName
-    url, commit: string
-    query: VersionInterval
-    self: int # position in the graph
-    parents: seq[int] # why we need this dependency
-    active: bool
-    algo: ResolutionAlgorithm
-  DepGraph = object
-    nodes: seq[Dependency]
-    processed: Table[string, int] # the key is (url / commit)
-    byName: Table[PackageName, seq[int]]
-    availableVersions: Table[PackageName, seq[(string, Version)]] # sorted, latest version comes first
-    bestNimVersion: Version # Nim is a special snowflake
-
-  LockFile = object # serialized as JSON so an object for extensibility
-    items: OrderedTable[string, LockFileEntry]
-
-  Flag = enum
-    KeepCommits
-    CfgHere
-    UsesOverrides
-    Keep
-    NoColors
-    ShowGraph
-    AutoEnv
-
-  AtlasContext = object
-    projectDir, workspace, depsDir, currentDir: string
-    hasPackageList: bool
-    flags: set[Flag]
-    p: Table[string, string] # name -> url mapping
-    errors, warnings: int
-    overrides: Patterns
-    lockMode: LockMode
-    lockFile: LockFile
-    defaultAlgo: ResolutionAlgorithm
-    when MockupRun:
-      step: int
-      mockupSuccess: bool
-
-proc `==`*(a, b: CfgPath): bool {.borrow.}
-
-proc `==`*(a, b: PackageName): bool {.borrow.}
-proc hash*(a: PackageName): Hash {.borrow.}
-
-const
-  InvalidCommit = "#head" #"<invalid commit>"
-  ProduceTest = false
-
-type
-  Command = enum
-    GitDiff = "git diff",
-    GitTag = "git tag",
-    GitTags = "git show-ref --tags",
-    GitLastTaggedRef = "git rev-list --tags --max-count=1",
-    GitDescribe = "git describe",
-    GitRevParse = "git rev-parse",
-    GitCheckout = "git checkout",
-    GitPush = "git push origin",
-    GitPull = "git pull",
-    GitCurrentCommit = "git log -n 1 --format=%H"
-    GitMergeBase = "git merge-base"
-
-include testdata
-
-proc silentExec(cmd: string; args: openArray[string]): (string, int) =
-  var cmdLine = cmd
-  for i in 0..<args.len:
-    cmdLine.add ' '
-    cmdLine.add quoteShell(args[i])
-  result = osproc.execCmdEx(cmdLine)
-
-proc nimbleExec(cmd: string; args: openArray[string]) =
-  var cmdLine = "nimble " & cmd
-  for i in 0..<args.len:
-    cmdLine.add ' '
-    cmdLine.add quoteShell(args[i])
-  discard os.execShellCmd(cmdLine)
-
-proc exec(c: var AtlasContext; cmd: Command; args: openArray[string]): (string, int) =
-  when MockupRun:
-    assert TestLog[c.step].cmd == cmd, $(TestLog[c.step].cmd, cmd, c.step)
-    case cmd
-    of GitDiff, GitTag, GitTags, GitLastTaggedRef, GitDescribe, GitRevParse, GitPush, GitPull, GitCurrentCommit:
-      result = (TestLog[c.step].output, TestLog[c.step].exitCode)
-    of GitCheckout:
-      assert args[0] == TestLog[c.step].output
-    of GitMergeBase:
-      let tmp = TestLog[c.step].output.splitLines()
-      assert tmp.len == 4, $tmp.len
-      assert tmp[0] == args[0]
-      assert tmp[1] == args[1]
-      assert tmp[3] == ""
-      result[0] = tmp[2]
-      result[1] = TestLog[c.step].exitCode
-    inc c.step
-  else:
-    result = silentExec($cmd, args)
-    when ProduceTest:
-      echo "cmd ", cmd, " args ", args, " --> ", result
-
-proc cloneUrl(c: var AtlasContext; url, dest: string; cloneUsingHttps: bool): string =
-  when MockupRun:
-    result = ""
-  else:
-    result = osutils.cloneUrl(url, dest, cloneUsingHttps)
-    when ProduceTest:
-      echo "cloned ", url, " into ", dest
-
-template withDir*(c: var AtlasContext; dir: string; body: untyped) =
-  when MockupRun:
-    body
-  else:
-    let oldDir = getCurrentDir()
-    try:
-      when ProduceTest:
-        echo "Current directory is now ", dir
-      setCurrentDir(dir)
-      body
-    finally:
-      setCurrentDir(oldDir)
-
-proc extractRequiresInfo(c: var AtlasContext; nimbleFile: string): NimbleFileInfo =
-  result = extractRequiresInfo(nimbleFile)
-  when ProduceTest:
-    echo "nimble ", nimbleFile, " info ", result
-
-proc isCleanGit(c: var AtlasContext): string =
-  result = ""
-  let (outp, status) = exec(c, GitDiff, [])
-  if outp.len != 0:
-    result = "'git diff' not empty"
-  elif status != 0:
-    result = "'git diff' returned non-zero"
-
-proc message(c: var AtlasContext; category: string; p: PackageName; arg: string) =
-  var msg = category & "(" & p.string & ") " & arg
-  stdout.writeLine msg
-
-proc warn(c: var AtlasContext; p: PackageName; arg: string) =
-  if NoColors in c.flags:
-    message(c, "[Warning] ", p, arg)
-  else:
-    stdout.styledWriteLine(fgYellow, styleBright, "[Warning] ", resetStyle, fgCyan, "(", p.string, ")", resetStyle, " ", arg)
-  inc c.warnings
-
-proc error(c: var AtlasContext; p: PackageName; arg: string) =
-  if NoColors in c.flags:
-    message(c, "[Error] ", p, arg)
-  else:
-    stdout.styledWriteLine(fgRed, styleBright, "[Error] ", resetStyle, fgCyan, "(", p.string, ")", resetStyle, " ", arg)
-  inc c.errors
-
-proc info(c: var AtlasContext; p: PackageName; arg: string) =
-  if NoColors in c.flags:
-    message(c, "[Info] ", p, arg)
-  else:
-    stdout.styledWriteLine(fgGreen, styleBright, "[Info] ", resetStyle, fgCyan, "(", p.string, ")", resetStyle, " ", arg)
-
-template projectFromCurrentDir(): PackageName = PackageName(c.currentDir.splitPath.tail)
-
-proc readableFile(s: string): string = relativePath(s, getCurrentDir())
-
-proc sameVersionAs(tag, ver: string): bool =
-  const VersionChars = {'0'..'9', '.'}
-
-  proc safeCharAt(s: string; i: int): char {.inline.} =
-    if i >= 0 and i < s.len: s[i] else: '\0'
-
-  let idx = find(tag, ver)
-  if idx >= 0:
-    # we found the version as a substring inside the `tag`. But we
-    # need to watch out the the boundaries are not part of a
-    # larger/different version number:
-    result = safeCharAt(tag, idx-1) notin VersionChars and
-      safeCharAt(tag, idx+ver.len) notin VersionChars
-
-proc gitDescribeRefTag(c: var AtlasContext; commit: string): string =
-  let (lt, status) = exec(c, GitDescribe, ["--tags", commit])
-  result = if status == 0: strutils.strip(lt) else: ""
-
-proc getLastTaggedCommit(c: var AtlasContext): string =
-  let (ltr, status) = exec(c, GitLastTaggedRef, [])
-  if status == 0:
-    let lastTaggedRef = ltr.strip()
-    let lastTag = gitDescribeRefTag(c, lastTaggedRef)
-    if lastTag.len != 0:
-      result = lastTag
-
-proc collectTaggedVersions(c: var AtlasContext): seq[(string, Version)] =
-  let (outp, status) = exec(c, GitTags, [])
-  if status == 0:
-    result = parseTaggedVersions(outp)
-  else:
-    result = @[]
-
-proc versionToCommit(c: var AtlasContext; d: Dependency): string =
-  let allVersions = collectTaggedVersions(c)
-  case d.algo
-  of MinVer:
-    result = selectBestCommitMinVer(allVersions, d.query)
-  of SemVer:
-    result = selectBestCommitSemVer(allVersions, d.query)
-  of MaxVer:
-    result = selectBestCommitMaxVer(allVersions, d.query)
-
-proc shortToCommit(c: var AtlasContext; short: string): string =
-  let (cc, status) = exec(c, GitRevParse, [short])
-  result = if status == 0: strutils.strip(cc) else: ""
-
-proc checkoutGitCommit(c: var AtlasContext; p: PackageName; commit: string) =
-  let (_, status) = exec(c, GitCheckout, [commit])
-  if status != 0:
-    error(c, p, "could not checkout commit " & commit)
-
-proc gitPull(c: var AtlasContext; p: PackageName) =
-  let (_, status) = exec(c, GitPull, [])
-  if status != 0:
-    error(c, p, "could not 'git pull'")
-
-proc gitTag(c: var AtlasContext; tag: string) =
-  let (_, status) = exec(c, GitTag, [tag])
-  if status != 0:
-    error(c, c.projectDir.PackageName, "could not 'git tag " & tag & "'")
-
-proc pushTag(c: var AtlasContext; tag: string) =
-  let (outp, status) = exec(c, GitPush, [tag])
-  if status != 0:
-    error(c, c.projectDir.PackageName, "could not 'git push " & tag & "'")
-  elif outp.strip() == "Everything up-to-date":
-    info(c, c.projectDir.PackageName, "is up-to-date")
-  else:
-    info(c, c.projectDir.PackageName, "successfully pushed tag: " & tag)
-
-proc incrementTag(c: var AtlasContext; lastTag: string; field: Natural): string =
-  var startPos =
-    if lastTag[0] in {'0'..'9'}: 0
-    else: 1
-  var endPos = lastTag.find('.', startPos)
-  if field >= 1:
-    for i in 1 .. field:
-      if endPos == -1:
-        error c, projectFromCurrentDir(), "the last tag '" & lastTag & "' is missing . periods"
-        return ""
-      startPos = endPos + 1
-      endPos = lastTag.find('.', startPos)
-  if endPos == -1:
-    endPos = len(lastTag)
-  let patchNumber = parseInt(lastTag[startPos..<endPos])
-  lastTag[0..<startPos] & $(patchNumber + 1) & lastTag[endPos..^1]
-
-proc incrementLastTag(c: var AtlasContext; field: Natural): string =
-  let (ltr, status) = exec(c, GitLastTaggedRef, [])
-  if status == 0:
-    let
-      lastTaggedRef = ltr.strip()
-      lastTag = gitDescribeRefTag(c, lastTaggedRef)
-      currentCommit = exec(c, GitCurrentCommit, [])[0].strip()
-
-    if lastTaggedRef == currentCommit:
-      info c, c.projectDir.PackageName, "the current commit '" & currentCommit & "' is already tagged '" & lastTag & "'"
-      lastTag
-    else:
-      incrementTag(c, lastTag, field)
-  else: "v0.0.1" # assuming no tags have been made yet
-
-proc tag(c: var AtlasContext; tag: string) =
-  gitTag(c, tag)
-  pushTag(c, tag)
-
-proc tag(c: var AtlasContext; field: Natural) =
-  let oldErrors = c.errors
-  let newTag = incrementLastTag(c, field)
-  if c.errors == oldErrors:
-    tag(c, newTag)
-
-proc updatePackages(c: var AtlasContext) =
-  if dirExists(c.workspace / PackagesDir):
-    withDir(c, c.workspace / PackagesDir):
-      gitPull(c, PackageName PackagesDir)
-  else:
-    withDir c, c.workspace:
-      let err = cloneUrl(c, "https://github.com/nim-lang/packages", PackagesDir, false)
-      if err != "":
-        error c, PackageName(PackagesDir), err
-
-proc fillPackageLookupTable(c: var AtlasContext) =
-  if not c.hasPackageList:
-    c.hasPackageList = true
-    when not MockupRun:
-      updatePackages(c)
-    let plist = getPackages(when MockupRun: TestsDir else: c.workspace)
-    for entry in plist:
-      c.p[unicode.toLower entry.name] = entry.url
-
-proc toUrl(c: var AtlasContext; p: string): string =
-  if p.isUrl:
-    if UsesOverrides in c.flags:
-      result = c.overrides.substitute(p)
-      if result.len > 0: return result
-    result = p
-  else:
-    # either the project name or the URL can be overwritten!
-    if UsesOverrides in c.flags:
-      result = c.overrides.substitute(p)
-      if result.len > 0: return result
-
-    fillPackageLookupTable(c)
-    result = c.p.getOrDefault(unicode.toLower p)
-
-    if result.len == 0:
-      result = getUrlFromGithub(p)
-      if result.len == 0:
-        inc c.errors
-
-    if UsesOverrides in c.flags:
-      let newUrl = c.overrides.substitute(result)
-      if newUrl.len > 0: return newUrl
-
-proc toName(p: string): PackageName =
-  if p.isUrl:
-    result = PackageName splitFile(p).name
-  else:
-    result = PackageName p
-
-proc generateDepGraph(c: var AtlasContext; g: DepGraph) =
-  proc repr(w: Dependency): string =
-    if w.url.endsWith("/"): w.url & w.commit
-    else: w.url & "/" & w.commit
-
-  var dotGraph = ""
-  for i in 0 ..< g.nodes.len:
-    dotGraph.addf("\"$1\" [label=\"$2\"];\n", [g.nodes[i].repr, if g.nodes[i].active: "" else: "unused"])
-  for i in 0 ..< g.nodes.len:
-    for p in items g.nodes[i].parents:
-      if p >= 0:
-        dotGraph.addf("\"$1\" -> \"$2\";\n", [g.nodes[p].repr, g.nodes[i].repr])
-  let dotFile = c.currentDir / "deps.dot"
-  writeFile(dotFile, "digraph deps {\n$1}\n" % dotGraph)
-  let graphvizDotPath = findExe("dot")
-  if graphvizDotPath.len == 0:
-    #echo("gendepend: Graphviz's tool dot is required, " &
-    #  "see https://graphviz.org/download for downloading")
-    discard
-  else:
-    discard execShellCmd("dot -Tpng -odeps.png " & quoteShell(dotFile))
-
-proc setupNimEnv(c: var AtlasContext; nimVersion: string)
-
-proc afterGraphActions(c: var AtlasContext; g: DepGraph) =
-  if ShowGraph in c.flags:
-    generateDepGraph c, g
-  if AutoEnv in c.flags and g.bestNimVersion != Version"":
-    setupNimEnv c, g.bestNimVersion.string
-
-proc needsCommitLookup(commit: string): bool {.inline.} =
-  '.' in commit or commit == InvalidCommit
-
-proc isShortCommitHash(commit: string): bool {.inline.} =
-  commit.len >= 4 and commit.len < 40
-
-proc getRequiredCommit(c: var AtlasContext; w: Dependency): string =
-  if needsCommitLookup(w.commit): versionToCommit(c, w)
-  elif isShortCommitHash(w.commit): shortToCommit(c, w.commit)
-  else: w.commit
-
-proc getRemoteUrl(): string =
-  execProcess("git config --get remote.origin.url").strip()
-
-proc genLockEntry(c: var AtlasContext; w: Dependency) =
-  let url = getRemoteUrl()
-  var commit = getRequiredCommit(c, w)
-  if commit.len == 0 or needsCommitLookup(commit):
-    commit = execProcess("git log -1 --pretty=format:%H").strip()
-  c.lockFile.items[w.name.string] = LockFileEntry(url: url, commit: commit)
-
-proc commitFromLockFile(c: var AtlasContext; w: Dependency): string =
-  let url = getRemoteUrl()
-  let entry = c.lockFile.items.getOrDefault(w.name.string)
-  if entry.commit.len > 0:
-    result = entry.commit
-    if entry.url != url:
-      error c, w.name, "remote URL has been compromised: got: " &
-          url & " but wanted: " & entry.url
-  else:
-    error c, w.name, "package is not listed in the lock file"
-
-proc dependencyDir(c: AtlasContext; w: Dependency): string =
-  result = c.workspace / w.name.string
-  if not dirExists(result):
-    result = c.depsDir / w.name.string
-
-const
-  FileProtocol = "file://"
-  ThisVersion = "current_version.atlas"
-
-proc selectNode(c: var AtlasContext; g: var DepGraph; w: Dependency) =
-  # all other nodes of the same project name are not active
-  for e in items g.byName[w.name]:
-    g.nodes[e].active = e == w.self
-  if c.lockMode == genLock:
-    if w.url.startsWith(FileProtocol):
-      c.lockFile.items[w.name.string] = LockFileEntry(url: w.url, commit: w.commit)
-    else:
-      genLockEntry(c, w)
-
-proc checkoutCommit(c: var AtlasContext; g: var DepGraph; w: Dependency) =
-  let dir = dependencyDir(c, w)
-  withDir c, dir:
-    if c.lockMode == useLock:
-      checkoutGitCommit(c, w.name, commitFromLockFile(c, w))
-    elif w.commit.len == 0 or cmpIgnoreCase(w.commit, "head") == 0:
-      gitPull(c, w.name)
-    else:
-      let err = isCleanGit(c)
-      if err != "":
-        warn c, w.name, err
-      else:
-        let requiredCommit = getRequiredCommit(c, w)
-        let (cc, status) = exec(c, GitCurrentCommit, [])
-        let currentCommit = strutils.strip(cc)
-        if requiredCommit == "" or status != 0:
-          if requiredCommit == "" and w.commit == InvalidCommit:
-            warn c, w.name, "package has no tagged releases"
-          else:
-            warn c, w.name, "cannot find specified version/commit " & w.commit
-        else:
-          if currentCommit != requiredCommit:
-            # checkout the later commit:
-            # git merge-base --is-ancestor <commit> <commit>
-            let (cc, status) = exec(c, GitMergeBase, [currentCommit, requiredCommit])
-            let mergeBase = strutils.strip(cc)
-            if status == 0 and (mergeBase == currentCommit or mergeBase == requiredCommit):
-              # conflict resolution: pick the later commit:
-              if mergeBase == currentCommit:
-                checkoutGitCommit(c, w.name, requiredCommit)
-                selectNode c, g, w
-            else:
-              checkoutGitCommit(c, w.name, requiredCommit)
-              selectNode c, g, w
-              when false:
-                warn c, w.name, "do not know which commit is more recent:",
-                  currentCommit, "(current) or", w.commit, " =", requiredCommit, "(required)"
-
-proc findNimbleFile(c: AtlasContext; dep: Dependency): string =
-  when MockupRun:
-    result = TestsDir / dep.name.string & ".nimble"
-    doAssert fileExists(result), "file does not exist " & result
-  else:
-    let dir = dependencyDir(c, dep)
-    result = dir / (dep.name.string & ".nimble")
-    if not fileExists(result):
-      result = ""
-      for x in walkFiles(dir / "*.nimble"):
-        if result.len == 0:
-          result = x
-        else:
-          # ambiguous .nimble file
-          return ""
-
-proc addUnique[T](s: var seq[T]; elem: sink T) =
-  if not s.contains(elem): s.add elem
-
-proc addUniqueDep(c: var AtlasContext; g: var DepGraph; parent: int;
-                  pkg: string; query: VersionInterval) =
-  let commit = versionKey(query)
-  let oldErrors = c.errors
-  let url = toUrl(c, pkg)
-  if oldErrors != c.errors:
-    warn c, toName(pkg), "cannot resolve package name"
-  else:
-    let key = url / commit
-    if g.processed.hasKey(key):
-      g.nodes[g.processed[key]].parents.addUnique parent
-    else:
-      let self = g.nodes.len
-      g.byName.mgetOrPut(toName(pkg), @[]).add self
-      g.processed[key] = self
-      if c.lockMode == useLock:
-        if c.lockfile.items.contains(pkg):
-          g.nodes.add Dependency(name: toName(pkg),
-                                 url: c.lockfile.items[pkg].url,
-                                 commit: c.lockfile.items[pkg].commit,
-                                 self: self,
-                                 parents: @[parent],
-                                 algo: c.defaultAlgo)
-        else:
-          error c, toName(pkg), "package is not listed in the lock file"
-      else:
-        g.nodes.add Dependency(name: toName(pkg), url: url, commit: commit,
-                               self: self,
-                               query: query,
-                               parents: @[parent],
-                               algo: c.defaultAlgo)
-
-template toDestDir(p: PackageName): string = p.string
-
-proc readLockFile(filename: string): LockFile =
-  let jsonAsStr = readFile(filename)
-  let jsonTree = parseJson(jsonAsStr)
-  result = to(jsonTree, LockFile)
-
-proc rememberNimVersion(g: var DepGraph; q: VersionInterval) =
-  let v = extractGeQuery(q)
-  if v != Version"" and v > g.bestNimVersion: g.bestNimVersion = v
-
-proc collectDeps(c: var AtlasContext; g: var DepGraph; parent: int;
-                 dep: Dependency; nimbleFile: string): CfgPath =
-  # If there is a .nimble file, return the dependency path & srcDir
-  # else return "".
-  assert nimbleFile != ""
-  let nimbleInfo = extractRequiresInfo(c, nimbleFile)
-  for r in nimbleInfo.requires:
-    var i = 0
-    while i < r.len and r[i] notin {'#', '<', '=', '>'} + Whitespace: inc i
-    let pkgName = r.substr(0, i-1)
-    var err = pkgName.len == 0
-    let query = parseVersionInterval(r, i, err)
-    if err:
-      error c, toName(nimbleFile), "invalid 'requires' syntax: " & r
-    else:
-      if cmpIgnoreCase(pkgName, "nim") != 0:
-        c.addUniqueDep g, parent, pkgName, query
-      else:
-        rememberNimVersion g, query
-  result = CfgPath(toDestDir(dep.name) / nimbleInfo.srcDir)
-
-proc collectNewDeps(c: var AtlasContext; g: var DepGraph; parent: int;
-                    dep: Dependency): CfgPath =
-  let nimbleFile = findNimbleFile(c, dep)
-  if nimbleFile != "":
-    result = collectDeps(c, g, parent, dep, nimbleFile)
-  else:
-    result = CfgPath toDestDir(dep.name)
-
-proc selectDir(a, b: string): string = (if dirExists(a): a else: b)
-
-proc copyFromDisk(c: var AtlasContext; w: Dependency) =
-  let destDir = toDestDir(w.name)
-  var u = w.url.substr(FileProtocol.len)
-  if u.startsWith("./"): u = c.workspace / u.substr(2)
-  copyDir(selectDir(u & "@" & w.commit, u), destDir)
-  writeFile destDir / ThisVersion, w.commit
-  #echo "WRITTEN ", destDir / ThisVersion
-
-proc isLaterCommit(destDir, version: string): bool =
-  let oldVersion = try: readFile(destDir / ThisVersion).strip except: "0.0"
-  if isValidVersion(oldVersion) and isValidVersion(version):
-    result = Version(oldVersion) < Version(version)
-  else:
-    result = true
-
-proc collectAvailableVersions(c: var AtlasContext; g: var DepGraph; w: Dependency) =
-  when MockupRun:
-    # don't cache when doing the MockupRun:
-    g.availableVersions[w.name] = collectTaggedVersions(c)
-  else:
-    if not g.availableVersions.hasKey(w.name):
-      g.availableVersions[w.name] = collectTaggedVersions(c)
-
-proc resolve(c: var AtlasContext; g: var DepGraph) =
-  var b = sat.Builder()
-  b.openOpr(AndForm)
-  # Root must true:
-  b.add newVar(VarId 0)
-
-  assert g.nodes.len > 0
-  assert g.nodes[0].active
-  # Implications:
-  for i in 0..<g.nodes.len:
-    if g.nodes[i].active:
-      for j in g.nodes[i].parents:
-        # "parent has a dependency on x" is translated to:
-        # "parent implies x" which is "not parent or x"
-        if j >= 0:
-          b.openOpr(OrForm)
-          b.openOpr(NotForm)
-          b.add newVar(VarId j)
-          b.closeOpr
-          b.add newVar(VarId i)
-          b.closeOpr
-  var idgen = 0
-  var mapping: seq[(string, string, Version)] = @[]
-  # Version selection:
-  for i in 0..<g.nodes.len:
-    if g.nodes[i].active:
-      # A -> (exactly one of: A1, A2, A3)
-      b.openOpr(OrForm)
-      b.openOpr(NotForm)
-      b.add newVar(VarId i)
-      b.closeOpr
-      b.openOpr(ExactlyOneOfForm)
-
-      let av {.cursor.} = g.availableVersions[g.nodes[i].name]
-      var q = g.nodes[i].query
-      if g.nodes[i].algo == SemVer: q = toSemVer(q)
-      if g.nodes[i].algo == MinVer:
-        for j in countup(0, av.len-1):
-          if q.matches(av[j][1]):
-            mapping.add (g.nodes[i].name.string, av[j][0], av[j][1])
-            b.add newVar(VarId(idgen + g.nodes.len))
-            inc idgen
-      else:
-        for j in countdown(av.len-1, 0):
-          if q.matches(av[j][1]):
-            mapping.add (g.nodes[i].name.string, av[j][0], av[j][1])
-            b.add newVar(VarId(idgen + g.nodes.len))
-            inc idgen
-
-      b.closeOpr # ExactlyOneOfForm
-      b.closeOpr # OrForm
-  b.closeOpr()
-  let f = toForm(b)
-  var s = newSeq[BindingKind](idgen)
-  if satisfiable(f, s):
-    for i in g.nodes.len..<s.len:
-      if s[i] == setToTrue:
-        let destDir = mapping[i - g.nodes.len][0]
-        let dir = selectDir(c.workspace / destDir, c.depsDir / destDir)
-        withDir c, dir:
-          checkoutGitCommit(c, toName(destDir), mapping[i - g.nodes.len][1])
-    when false:
-      echo "selecting: "
-      for i in g.nodes.len..<s.len:
-        if s[i] == setToTrue:
-          echo "[x] ", mapping[i - g.nodes.len]
-        else:
-          echo "[ ] ", mapping[i - g.nodes.len]
-      echo f
-  else:
-    error c, toName(c.workspace), "version conflict; for more information use --showGraph"
-    var usedVersions = initCountTable[string]()
-    for i in g.nodes.len..<s.len:
-      if s[i] == setToTrue:
-        usedVersions.inc mapping[i - g.nodes.len][0]
-    for i in g.nodes.len..<s.len:
-      if s[i] == setToTrue:
-        let counter = usedVersions.getOrDefault(mapping[i - g.nodes.len][0])
-        if counter > 0:
-          error c, toName(mapping[i - g.nodes.len][0]), $mapping[i - g.nodes.len][2] & " required"
-
-proc traverseLoop(c: var AtlasContext; g: var DepGraph; startIsDep: bool): seq[CfgPath] =
-  if c.lockMode == useLock:
-    let lockFilePath = dependencyDir(c, g.nodes[0]) / LockFileName
-    c.lockFile = readLockFile(lockFilePath)
-
-  result = @[]
-  var i = 0
-  while i < g.nodes.len:
-    let w = g.nodes[i]
-    let destDir = toDestDir(w.name)
-    let oldErrors = c.errors
-
-    let dir = selectDir(c.workspace / destDir, c.depsDir / destDir)
-    if not dirExists(dir):
-      withDir c, (if i != 0 or startIsDep: c.depsDir else: c.workspace):
-        if w.url.startsWith(FileProtocol):
-          copyFromDisk c, w
-        else:
-          let err = cloneUrl(c, w.url, destDir, false)
-          if err != "":
-            error c, w.name, err
-          elif w.algo != MinVer:
-            collectAvailableVersions c, g, w
-    elif w.algo != MinVer:
-      withDir c, dir:
-        collectAvailableVersions c, g, w
-
-    # assume this is the selected version, it might get overwritten later:
-    selectNode c, g, w
-    if oldErrors == c.errors:
-      if KeepCommits notin c.flags and w.algo == MinVer:
-        if not w.url.startsWith(FileProtocol):
-          checkoutCommit(c, g, w)
-        else:
-          withDir c, (if i != 0 or startIsDep: c.depsDir else: c.workspace):
-            if isLaterCommit(destDir, w.commit):
-              copyFromDisk c, w
-              selectNode c, g, w
-      # even if the checkout fails, we can make use of the somewhat
-      # outdated .nimble file to clone more of the most likely still relevant
-      # dependencies:
-      result.addUnique collectNewDeps(c, g, i, w)
-    inc i
-
-  if g.availableVersions.len > 0:
-    resolve c, g
-  if c.lockMode == genLock:
-    writeFile c.currentDir / LockFileName, toJson(c.lockFile).pretty
-
-proc createGraph(c: var AtlasContext; start, url: string): DepGraph =
-  result = DepGraph(nodes: @[Dependency(name: toName(start), url: url, commit: "", self: 0,
-                                       algo: c.defaultAlgo)])
-  result.byName.mgetOrPut(toName(start), @[]).add 0
-
-proc traverse(c: var AtlasContext; start: string; startIsDep: bool): seq[CfgPath] =
-  # returns the list of paths for the nim.cfg file.
-  let url = toUrl(c, start)
-  var g = createGraph(c, start, url)
-
-  if url == "":
-    error c, toName(start), "cannot resolve package name"
-    return
-
-  c.projectDir = c.workspace / toDestDir(g.nodes[0].name)
-
-  result = traverseLoop(c, g, startIsDep)
-  afterGraphActions c, g
-
-const
-  configPatternBegin = "############# begin Atlas config section ##########\n"
-  configPatternEnd =   "############# end Atlas config section   ##########\n"
-
-proc patchNimCfg(c: var AtlasContext; deps: seq[CfgPath]; cfgPath: string) =
-  var paths = "--noNimblePath\n"
-  for d in deps:
-    let pkgname = toDestDir d.string.PackageName
-    let pkgdir = if dirExists(c.workspace / pkgname): c.workspace / pkgname
-                 else: c.depsDir / pkgName
-    let x = relativePath(pkgdir, cfgPath, '/')
-    paths.add "--path:\"" & x & "\"\n"
-  var cfgContent = configPatternBegin & paths & configPatternEnd
-
-  when MockupRun:
-    assert readFile(TestsDir / "nim.cfg") == cfgContent
-    c.mockupSuccess = true
-  else:
-    let cfg = cfgPath / "nim.cfg"
-    assert cfgPath.len > 0
-    if cfgPath.len > 0 and not dirExists(cfgPath):
-      error(c, c.projectDir.PackageName, "could not write the nim.cfg")
-    elif not fileExists(cfg):
-      writeFile(cfg, cfgContent)
-      info(c, projectFromCurrentDir(), "created: " & cfg.readableFile)
-    else:
-      let content = readFile(cfg)
-      let start = content.find(configPatternBegin)
-      if start >= 0:
-        cfgContent = content.substr(0, start-1) & cfgContent
-        let theEnd = content.find(configPatternEnd, start)
-        if theEnd >= 0:
-          cfgContent.add content.substr(theEnd+len(configPatternEnd))
-      else:
-        cfgContent = content & "\n" & cfgContent
-      if cfgContent != content:
-        # do not touch the file if nothing changed
-        # (preserves the file date information):
-        writeFile(cfg, cfgContent)
-        info(c, projectFromCurrentDir(), "updated: " & cfg.readableFile)
-
-proc fatal*(msg: string) =
-  when defined(debug):
-    writeStackTrace()
-  quit "[Error] " & msg
-
-proc findSrcDir(c: var AtlasContext): string =
-  for nimbleFile in walkPattern(c.currentDir / "*.nimble"):
-    let nimbleInfo = extractRequiresInfo(c, nimbleFile)
-    return c.currentDir / nimbleInfo.srcDir
-  return c.currentDir
-
-proc installDependencies(c: var AtlasContext; nimbleFile: string; startIsDep: bool) =
-  # 1. find .nimble file in CWD
-  # 2. install deps from .nimble
-  var g = DepGraph(nodes: @[])
-  let (_, pkgname, _) = splitFile(nimbleFile)
-  let dep = Dependency(name: toName(pkgname), url: "", commit: "", self: 0,
-                       algo: c.defaultAlgo)
-  discard collectDeps(c, g, -1, dep, nimbleFile)
-  let paths = traverseLoop(c, g, startIsDep)
-  patchNimCfg(c, paths, if CfgHere in c.flags: c.currentDir else: findSrcDir(c))
-  afterGraphActions c, g
-
-proc updateDir(c: var AtlasContext; dir, filter: string) =
-  for kind, file in walkDir(dir):
-    if kind == pcDir and dirExists(file / ".git"):
-      c.withDir file:
-        let pkg = PackageName(file)
-        let (remote, _) = osproc.execCmdEx("git remote -v")
-        if filter.len == 0 or filter in remote:
-          let diff = isCleanGit(c)
-          if diff != "":
-            warn(c, pkg, "has uncommitted changes; skipped")
-          else:
-            let (branch, _) = osproc.execCmdEx("git rev-parse --abbrev-ref HEAD")
-            if branch.strip.len > 0:
-              let (output, exitCode) = osproc.execCmdEx("git pull origin " & branch.strip)
-              if exitCode != 0:
-                error c, pkg, output
-              else:
-                info(c, pkg, "successfully updated")
-            else:
-              error c, pkg, "could not fetch current branch name"
-
-proc patchNimbleFile(c: var AtlasContext; dep: string): string =
-  let thisProject = c.currentDir.splitPath.tail
-  let oldErrors = c.errors
-  let url = toUrl(c, dep)
-  result = ""
-  if oldErrors != c.errors:
-    warn c, toName(dep), "cannot resolve package name"
-  else:
-    for x in walkFiles(c.currentDir / "*.nimble"):
-      if result.len == 0:
-        result = x
-      else:
-        # ambiguous .nimble file
-        warn c, toName(dep), "cannot determine `.nimble` file; there are multiple to choose from"
-        return ""
-    # see if we have this requirement already listed. If so, do nothing:
-    var found = false
-    if result.len > 0:
-      let nimbleInfo = extractRequiresInfo(c, result)
-      for r in nimbleInfo.requires:
-        var tokens: seq[string] = @[]
-        for token in tokenizeRequires(r):
-          tokens.add token
-        if tokens.len > 0:
-          let oldErrors = c.errors
-          let urlB = toUrl(c, tokens[0])
-          if oldErrors != c.errors:
-            warn c, toName(tokens[0]), "cannot resolve package name; found in: " & result
-          if url == urlB:
-            found = true
-            break
-
-    if not found:
-      let line = "requires \"$1\"\n" % dep.escape("", "")
-      if result.len > 0:
-        let oldContent = readFile(result)
-        writeFile result, oldContent & "\n" & line
-        info(c, toName(thisProject), "updated: " & result.readableFile)
-      else:
-        result = c.currentDir / thisProject & ".nimble"
-        writeFile result, line
-        info(c, toName(thisProject), "created: " & result.readableFile)
-    else:
-      info(c, toName(thisProject), "up to date: " & result.readableFile)
-
-proc detectWorkspace(currentDir: string): string =
-  result = currentDir
-  while result.len > 0:
-    if fileExists(result / AtlasWorkspace):
-      return result
-    result = result.parentDir()
-
-proc absoluteDepsDir(workspace, value: string): string =
-  if value == ".":
-    result = workspace
-  elif isAbsolute(value):
-    result = value
-  else:
-    result = workspace / value
-
-proc autoWorkspace(currentDir: string): string =
-  result = currentDir
-  while result.len > 0 and dirExists(result / ".git"):
-    result = result.parentDir()
-
-proc createWorkspaceIn(workspace, depsDir: string) =
-  if not fileExists(workspace / AtlasWorkspace):
-    writeFile workspace / AtlasWorkspace, "deps=\"$#\"" % escape(depsDir, "", "")
-  createDir absoluteDepsDir(workspace, depsDir)
-
-proc parseOverridesFile(c: var AtlasContext; filename: string) =
-  const Separator = " -> "
-  let path = c.workspace / filename
-  var f: File
-  if open(f, path):
-    c.flags.incl UsesOverrides
-    try:
-      var lineCount = 1
-      for line in lines(path):
-        let splitPos = line.find(Separator)
-        if splitPos >= 0 and line[0] != '#':
-          let key = line.substr(0, splitPos-1)
-          let val = line.substr(splitPos+len(Separator))
-          if key.len == 0 or val.len == 0:
-            error c, toName(path), "key/value must not be empty"
-          let err = c.overrides.addPattern(key, val)
-          if err.len > 0:
-            error c, toName(path), "(" & $lineCount & "): " & err
-        else:
-          discard "ignore the line"
-        inc lineCount
-    finally:
-      close f
-  else:
-    error c, toName(path), "cannot open: " & path
-
-proc readConfig(c: var AtlasContext) =
-  let configFile = c.workspace / AtlasWorkspace
-  var f = newFileStream(configFile, fmRead)
-  if f == nil:
-    error c, toName(configFile), "cannot open: " & configFile
-    return
-  var p: CfgParser
-  open(p, f, configFile)
-  while true:
-    var e = next(p)
-    case e.kind
-    of cfgEof: break
-    of cfgSectionStart:
-      discard "who cares about sections"
-    of cfgKeyValuePair:
-      case e.key.normalize
-      of "deps":
-        c.depsDir = absoluteDepsDir(c.workspace, e.value)
-      of "overrides":
-        parseOverridesFile(c, e.value)
-      of "resolver":
-        try:
-          c.defaultAlgo = parseEnum[ResolutionAlgorithm](e.value)
-        except ValueError:
-          warn c, toName(configFile), "ignored unknown resolver: " & e.key
-      else:
-        warn c, toName(configFile), "ignored unknown setting: " & e.key
-    of cfgOption:
-      discard "who cares about options"
-    of cfgError:
-      error c, toName(configFile), e.msg
-  close(p)
-
-const
-  BatchFile = """
-@echo off
-set PATH="$1";%PATH%
-"""
-  ShellFile = "export PATH=$1:$$PATH\n"
-
-const
-  ActivationFile = when defined(windows): "activate.bat" else: "activate.sh"
-
-proc infoAboutActivation(c: var AtlasContext; nimDest, nimVersion: string) =
-  when defined(windows):
-    info c, toName(nimDest), "RUN\nnim-" & nimVersion & "\\activate.bat"
-  else:
-    info c, toName(nimDest), "RUN\nsource nim-" & nimVersion & "/activate.sh"
-
-proc setupNimEnv(c: var AtlasContext; nimVersion: string) =
-  template isDevel(nimVersion: string): bool = nimVersion == "devel"
-
-  template exec(c: var AtlasContext; command: string) =
-    let cmd = command # eval once
-    if os.execShellCmd(cmd) != 0:
-      error c, toName("nim-" & nimVersion), "failed: " & cmd
-      return
-
-  let nimDest = "nim-" & nimVersion
-  if dirExists(c.workspace / nimDest):
-    if not fileExists(c.workspace / nimDest / ActivationFile):
-      info c, toName(nimDest), "already exists; remove or rename and try again"
-    else:
-      infoAboutActivation c, nimDest, nimVersion
-    return
-
-  var major, minor, patch: int
-  if nimVersion != "devel":
-    if not scanf(nimVersion, "$i.$i.$i", major, minor, patch):
-      error c, toName("nim"), "cannot parse version requirement"
-      return
-  let csourcesVersion =
-    if nimVersion.isDevel or (major == 1 and minor >= 9) or major >= 2:
-      # already uses csources_v2
-      "csources_v2"
-    elif major == 0:
-      "csources" # has some chance of working
-    else:
-      "csources_v1"
-  withDir c, c.workspace:
-    if not dirExists(csourcesVersion):
-      exec c, "git clone https://github.com/nim-lang/" & csourcesVersion
-    exec c, "git clone https://github.com/nim-lang/nim " & nimDest
-  withDir c, c.workspace / csourcesVersion:
-    when defined(windows):
-      exec c, "build.bat"
-    else:
-      let makeExe = findExe("make")
-      if makeExe.len == 0:
-        exec c, "sh build.sh"
-      else:
-        exec c, "make"
-  let nimExe0 = ".." / csourcesVersion / "bin" / "nim".addFileExt(ExeExt)
-  withDir c, c.workspace / nimDest:
-    let nimExe = "bin" / "nim".addFileExt(ExeExt)
-    copyFileWithPermissions nimExe0, nimExe
-    let dep = Dependency(name: toName(nimDest), commit: nimVersion, self: 0,
-                         algo: c.defaultAlgo,
-                         query: createQueryEq(if nimVersion.isDevel: Version"#head" else: Version(nimVersion)))
-    if not nimVersion.isDevel:
-      let commit = versionToCommit(c, dep)
-      if commit.len == 0:
-        error c, toName(nimDest), "cannot resolve version to a commit"
-        return
-      checkoutGitCommit(c, dep.name, commit)
-    exec c, nimExe & " c --noNimblePath --skipUserCfg --skipParentCfg --hints:off koch"
-    let kochExe = when defined(windows): "koch.exe" else: "./koch"
-    exec c, kochExe & " boot -d:release --skipUserCfg --skipParentCfg --hints:off"
-    exec c, kochExe & " tools --skipUserCfg --skipParentCfg --hints:off"
-    # remove any old atlas binary that we now would end up using:
-    if cmpPaths(getAppDir(), c.workspace / nimDest / "bin") != 0:
-      removeFile "bin" / "atlas".addFileExt(ExeExt)
-    # unless --keep is used delete the csources because it takes up about 2GB and
-    # is not necessary afterwards:
-    if Keep notin c.flags:
-      removeDir c.workspace / csourcesVersion / "c_code"
-    let pathEntry = (c.workspace / nimDest / "bin")
-    when defined(windows):
-      writeFile "activate.bat", BatchFile % pathEntry.replace('/', '\\')
-    else:
-      writeFile "activate.sh", ShellFile % pathEntry
-    infoAboutActivation c, nimDest, nimVersion
-
-proc extractVersion(s: string): string =
-  var i = 0
-  while i < s.len and s[i] notin {'0'..'9'}: inc i
-  result = s.substr(i)
-
-proc listOutdated(c: var AtlasContext; dir: string) =
-  var updateable = 0
-  for k, f in walkDir(dir, relative=true):
-    if k in {pcDir, pcLinkToDir} and dirExists(dir / f / ".git"):
-      withDir c, dir / f:
-        let (outp, status) = silentExec("git fetch", [])
-        if status == 0:
-          let (cc, status) = exec(c, GitLastTaggedRef, [])
-          let latestVersion = strutils.strip(cc)
-          if status == 0 and latestVersion.len > 0:
-            # see if we're past that commit:
-            let (cc, status) = exec(c, GitCurrentCommit, [])
-            if status == 0:
-              let currentCommit = strutils.strip(cc)
-              if currentCommit != latestVersion:
-                # checkout the later commit:
-                # git merge-base --is-ancestor <commit> <commit>
-                let (cc, status) = exec(c, GitMergeBase, [currentCommit, latestVersion])
-                let mergeBase = strutils.strip(cc)
-                #if mergeBase != latestVersion:
-                #  echo f, " I'm at ", currentCommit, " release is at ", latestVersion, " merge base is ", mergeBase
-                if status == 0 and mergeBase == currentCommit:
-                  let v = extractVersion gitDescribeRefTag(c, latestVersion)
-                  if v.len > 0:
-                    info c, toName(f), "new version available: " & v
-                    inc updateable
-        else:
-          warn c, toName(f), "`git fetch` failed: " & outp
-  if updateable == 0:
-    info c, toName(c.workspace), "all packages are up to date"
-
-proc listOutdated(c: var AtlasContext) =
-  if c.depsDir.len > 0 and c.depsDir != c.workspace:
-    listOutdated c, c.depsDir
-  listOutdated c, c.workspace
-
-proc main =
-  var action = ""
-  var args: seq[string] = @[]
-  template singleArg() =
-    if args.len != 1:
-      fatal action & " command takes a single package name"
-
-  template noArgs() =
-    if args.len != 0:
-      fatal action & " command takes no arguments"
-
-  template projectCmd() =
-    if c.projectDir == c.workspace or c.projectDir == c.depsDir:
-      fatal action & " command must be executed in a project, not in the workspace"
-
-  var c = AtlasContext(projectDir: getCurrentDir(), currentDir: getCurrentDir(), workspace: "")
-  var autoinit = false
-  for kind, key, val in getopt():
-    case kind
-    of cmdArgument:
-      if action.len == 0:
-        action = key.normalize
-      else:
-        args.add key
-    of cmdLongOption, cmdShortOption:
-      case normalize(key)
-      of "help", "h": writeHelp()
-      of "version", "v": writeVersion()
-      of "keepcommits": c.flags.incl KeepCommits
-      of "workspace":
-        if val == ".":
-          c.workspace = getCurrentDir()
-          createWorkspaceIn c.workspace, c.depsDir
-        elif val.len > 0:
-          c.workspace = val
-          createDir(val)
-          createWorkspaceIn c.workspace, c.depsDir
-        else:
-          writeHelp()
-      of "project":
-        if isAbsolute(val):
-          c.currentDir = val
-        else:
-          c.currentDir = getCurrentDir() / val
-      of "deps":
-        if val.len > 0:
-          c.depsDir = val
-        else:
-          writeHelp()
-      of "cfghere": c.flags.incl CfgHere
-      of "autoinit": autoinit = true
-      of "showgraph": c.flags.incl ShowGraph
-      of "keep": c.flags.incl Keep
-      of "autoenv": c.flags.incl AutoEnv
-      of "genlock":
-        if c.lockMode != useLock:
-          c.lockMode = genLock
-        else:
-          writeHelp()
-      of "uselock":
-        if c.lockMode != genLock:
-          c.lockMode = useLock
-        else:
-          writeHelp()
-      of "colors":
-        case val.normalize
-        of "off": c.flags.incl NoColors
-        of "on": c.flags.excl NoColors
-        else: writeHelp()
-      of "resolver":
-        try:
-          c.defaultAlgo = parseEnum[ResolutionAlgorithm](val)
-        except ValueError:
-          quit "unknown resolver: " & val
-      else: writeHelp()
-    of cmdEnd: assert false, "cannot happen"
-
-  if c.workspace.len > 0:
-    if not dirExists(c.workspace): fatal "Workspace directory '" & c.workspace & "' not found."
-  elif action != "init":
-    when MockupRun:
-      c.workspace = autoWorkspace(c.currentDir)
-    else:
-      c.workspace = detectWorkspace(c.currentDir)
-      if c.workspace.len > 0:
-        readConfig c
-        info c, toName(c.workspace.readableFile), "is the current workspace"
-      elif autoinit:
-        c.workspace = autoWorkspace(c.currentDir)
-        createWorkspaceIn c.workspace, c.depsDir
-      elif action notin ["search", "list"]:
-        fatal "No workspace found. Run `atlas init` if you want this current directory to be your workspace."
-
-  when MockupRun:
-    c.depsDir = c.workspace
-
-  case action
-  of "":
-    fatal "No action."
-  of "init":
-    c.workspace = getCurrentDir()
-    createWorkspaceIn c.workspace, c.depsDir
-  of "clone", "update":
-    singleArg()
-    let deps = traverse(c, args[0], startIsDep = false)
-    patchNimCfg c, deps, if CfgHere in c.flags: c.currentDir else: findSrcDir(c)
-    when MockupRun:
-      if not c.mockupSuccess:
-        fatal "There were problems."
-    else:
-      if c.errors > 0:
-        fatal "There were problems."
-  of "use":
-    projectCmd()
-    singleArg()
-    let nimbleFile = patchNimbleFile(c, args[0])
-    if nimbleFile.len > 0:
-      installDependencies(c, nimbleFile, startIsDep = false)
-  of "install":
-    projectCmd()
-    if args.len > 1:
-      fatal "install command takes a single argument"
-    var nimbleFile = ""
-    if args.len == 1:
-      nimbleFile = args[0]
-    else:
-      for x in walkPattern("*.nimble"):
-        nimbleFile = x
-        break
-    if nimbleFile.len == 0:
-      fatal "could not find a .nimble file"
-    else:
-      installDependencies(c, nimbleFile, startIsDep = true)
-  of "refresh":
-    noArgs()
-    updatePackages(c)
-  of "search", "list":
-    if c.workspace.len != 0:
-      updatePackages(c)
-      search getPackages(c.workspace), args
-    else: search @[], args
-  of "updateprojects":
-    updateDir(c, c.workspace, if args.len == 0: "" else: args[0])
-  of "updatedeps":
-    updateDir(c, c.depsDir, if args.len == 0: "" else: args[0])
-  of "extract":
-    singleArg()
-    if fileExists(args[0]):
-      echo toJson(extractRequiresInfo(args[0]))
-    else:
-      fatal "File does not exist: " & args[0]
-  of "tag":
-    projectCmd()
-    if args.len == 0:
-      tag(c, ord(patch))
-    elif args[0].len == 1 and args[0][0] in {'a'..'z'}:
-      let field = ord(args[0][0]) - ord('a')
-      tag(c, field)
-    elif args[0].len == 1 and args[0][0] in {'A'..'Z'}:
-      let field = ord(args[0][0]) - ord('A')
-      tag(c, field)
-    elif '.' in args[0]:
-      tag(c, args[0])
-    else:
-      var field: SemVerField
-      try: field = parseEnum[SemVerField](args[0])
-      except: fatal "tag command takes one of 'patch' 'minor' 'major', a SemVer tag, or a letter from 'a' to 'z'"
-      tag(c, ord(field))
-  of "build", "test", "doc", "tasks":
-    projectCmd()
-    nimbleExec(action, args)
-  of "task":
-    projectCmd()
-    nimbleExec("", args)
-  of "env":
-    singleArg()
-    setupNimEnv c, args[0]
-  of "outdated":
-    listOutdated(c)
-  else:
-    fatal "Invalid action: " & action
-
-when isMainModule:
-  main()
diff --git a/atlas/atlas.nim.cfg b/atlas/atlas.nim.cfg
deleted file mode 100644
index fcace0579..000000000
--- a/atlas/atlas.nim.cfg
+++ /dev/null
@@ -1 +0,0 @@
---define:ssl

diff --git a/atlas/compiledpatterns.nim b/atlas/compiledpatterns.nim
deleted file mode 100644
index 69751d82b..000000000
--- a/atlas/compiledpatterns.nim
+++ /dev/null
@@ -1,246 +0,0 @@
-#
-#           Atlas Package Cloner
-#        (c) Copyright 2021 Andreas Rumpf
-#
-#    See the file "copying.txt", included in this
-#    distribution, for details about the copyright.
-#
-
-##[
-
-Syntax taken from strscans.nim:
-
-=================   ========================================================
-``$$``              Matches a single dollar sign.
-``$*``              Matches until the token following the ``$*`` was found.
-                    The match is allowed to be of 0 length.
-``$+``              Matches until the token following the ``$+`` was found.
-                    The match must consist of at least one char.
-``$s``              Skips optional whitespace.
-=================   ========================================================
-
-]##
-
-import tables
-from strutils import continuesWith, Whitespace
-
-type
-  Opcode = enum
-    MatchVerbatim # needs verbatim match
-    Capture0Until
-    Capture1Until
-    Capture0UntilEnd
-    Capture1UntilEnd
-    SkipWhitespace
-
-  Instr = object
-    opc: Opcode
-    arg1: uint8
-    arg2: uint16
-
-  Pattern* = object
-    code: seq[Instr]
-    usedMatches: int
-    error: string
-
-# A rewrite rule looks like:
-#
-# foo$*bar -> https://gitlab.cross.de/$1
-
-proc compile*(pattern: string; strings: var seq[string]): Pattern =
-  proc parseSuffix(s: string; start: int): int =
-    result = start
-    while result < s.len and s[result] != '$':
-      inc result
-
-  result = Pattern(code: @[], usedMatches: 0, error: "")
-  var p = 0
-  while p < pattern.len:
-    if pattern[p] == '$' and p+1 < pattern.len:
-      case pattern[p+1]
-      of '$':
-        if result.code.len > 0 and result.code[^1].opc in {
-              MatchVerbatim, Capture0Until, Capture1Until, Capture0UntilEnd, Capture1UntilEnd}:
-          # merge with previous opcode
-          let key = strings[result.code[^1].arg2] & "$"
-          var idx = find(strings, key)
-          if idx < 0:
-            idx = strings.len
-            strings.add key
-          result.code[^1].arg2 = uint16(idx)
-        else:
-          var idx = find(strings, "$")
-          if idx < 0:
-            idx = strings.len
-            strings.add "$"
-          result.code.add Instr(opc: MatchVerbatim,
-                                arg1: uint8(0), arg2: uint16(idx))
-        inc p, 2
-      of '+', '*':
-        let isPlus = pattern[p+1] == '+'
-
-        let pEnd = parseSuffix(pattern, p+2)
-        let suffix = pattern.substr(p+2, pEnd-1)
-        p = pEnd
-        if suffix.len == 0:
-          result.code.add Instr(opc: if isPlus: Capture1UntilEnd else: Capture0UntilEnd,
-                                arg1: uint8(result.usedMatches), arg2: uint16(0))
-        else:
-          var idx = find(strings, suffix)
-          if idx < 0:
-            idx = strings.len
-            strings.add suffix
-          result.code.add Instr(opc: if isPlus: Capture1Until else: Capture0Until,
-                                arg1: uint8(result.usedMatches), arg2: uint16(idx))
-        inc result.usedMatches
-
-      of 's':
-        result.code.add Instr(opc: SkipWhitespace)
-        inc p, 2
-      else:
-        result.error = "unknown syntax '$" & pattern[p+1] & "'"
-        break
-    elif pattern[p] == '$':
-      result.error = "unescaped '$'"
-      break
-    else:
-      let pEnd = parseSuffix(pattern, p)
-      let suffix = pattern.substr(p, pEnd-1)
-      var idx = find(strings, suffix)
-      if idx < 0:
-        idx = strings.len
-        strings.add suffix
-      result.code.add Instr(opc: MatchVerbatim,
-                            arg1: uint8(0), arg2: uint16(idx))
-      p = pEnd
-
-type
-  MatchObj = object
-    m: int
-    a: array[20, (int, int)]
-
-proc matches(s: Pattern; strings: seq[string]; input: string): MatchObj =
-  template failed =
-    result.m = -1
-    return result
-
-  var i = 0
-  for instr in s.code:
-    case instr.opc
-    of MatchVerbatim:
-      if continuesWith(input, strings[instr.arg2], i):
-        inc i, strings[instr.arg2].len
-      else:
-        failed()
-    of Capture0Until, Capture1Until:
-      block searchLoop:
-        let start = i
-        while i < input.len:
-          if continuesWith(input, strings[instr.arg2], i):
-            if instr.opc == Capture1Until and i == start:
-              failed()
-            result.a[result.m] = (start, i-1)
-            inc result.m
-            inc i, strings[instr.arg2].len
-            break searchLoop
-          inc i
-        failed()
-
-    of Capture0UntilEnd, Capture1UntilEnd:
-      if instr.opc == Capture1UntilEnd and i >= input.len:
-        failed()
-      result.a[result.m] = (i, input.len-1)
-      inc result.m
-      i = input.len
-    of SkipWhitespace:
-      while i < input.len and input[i] in Whitespace: inc i
-  if i < input.len:
-    # still unmatched stuff was left:
-    failed()
-
-proc translate(m: MatchObj; outputPattern, input: string): string =
-  result = newStringOfCap(outputPattern.len)
-  var i = 0
-  var patternCount = 0
-  while i < outputPattern.len:
-    if i+1 < outputPattern.len and outputPattern[i] == '$':
-      if outputPattern[i+1] == '#':
-        inc i, 2
-        if patternCount < m.a.len:
-          let (a, b) = m.a[patternCount]
-          for j in a..b: result.add input[j]
-        inc patternCount
-      elif outputPattern[i+1] in {'1'..'9'}:
-        var n = ord(outputPattern[i+1]) - ord('0')
-        inc i, 2
-        while i < outputPattern.len and outputPattern[i] in {'0'..'9'}:
-          n = n * 10 + (ord(outputPattern[i]) - ord('0'))
-          inc i
-        patternCount = n
-        if n-1 < m.a.len:
-          let (a, b) = m.a[n-1]
-          for j in a..b: result.add input[j]
-      else:
-        # just ignore the wrong pattern:
-        inc i
-    else:
-      result.add outputPattern[i]
-      inc i
-
-proc replace*(s: Pattern; outputPattern, input: string): string =
-  var strings: seq[string] = @[]
-  let m = s.matches(strings, input)
-  if m.m < 0:
-    result = ""
-  else:
-    result = translate(m, outputPattern, input)
-
-
-type
-  Patterns* = object
-    s: seq[(Pattern, string)]
-    t: Table[string, string]
-    strings: seq[string]
-
-proc initPatterns*(): Patterns =
-  Patterns(s: @[], t: initTable[string, string](), strings: @[])
-
-proc addPattern*(p: var Patterns; inputPattern, outputPattern: string): string =
-  if '$' notin inputPattern and '$' notin outputPattern:
-    p.t[inputPattern] = outputPattern
-    result = ""
-  else:
-    let code = compile(inputPattern, p.strings)
-    if code.error.len > 0:
-      result = code.error
-    else:
-      p.s.add (code, outputPattern)
-      result = ""
-
-proc substitute*(p: Patterns; input: string): string =
-  result = p.t.getOrDefault(input)
-  if result.len == 0:
-    for i in 0..<p.s.len:
-      let m = p.s[i][0].matches(p.strings, input)
-      if m.m >= 0:
-        return translate(m, p.s[i][1], input)
-
-proc replacePattern*(inputPattern, outputPattern, input: string): string =
-  var strings: seq[string] = @[]
-  let code = compile(inputPattern, strings)
-  result = replace(code, outputPattern, input)
-
-when isMainModule:
-  # foo$*bar -> https://gitlab.cross.de/$1
-  const realInput = "$fooXXbar$z00end"
-  var strings: seq[string] = @[]
-  let code = compile("$$foo$*bar$$$*z00$*", strings)
-  echo code
-
-  let m = code.matches(strings, realInput)
-  echo m.m
-
-  echo translate(m, "$1--$#-$#-", realInput)
-
-  echo translate(m, "https://gitlab.cross.de/$1", realInput)
-
diff --git a/atlas/osutils.nim b/atlas/osutils.nim
deleted file mode 100644
index 66cd29be5..000000000
--- a/atlas/osutils.nim
+++ /dev/null
@@ -1,51 +0,0 @@
-## OS utilities like 'withDir'.
-## (c) 2021 Andreas Rumpf
-
-import os, strutils, osproc
-
-proc isUrl*(x: string): bool =
-  x.startsWith("git://") or x.startsWith("https://") or x.startsWith("http://")
-
-proc cloneUrl*(url, dest: string; cloneUsingHttps: bool): string =
-  ## Returns an error message on error or else "".
-  result = ""
-  var modUrl =
-    if url.startsWith("git://") and cloneUsingHttps:
-      "https://" & url[6 .. ^1]
-    else: url
-
-  # github + https + trailing url slash causes a
-  # checkout/ls-remote to fail with Repository not found
-  var isGithub = false
-  if modUrl.contains("github.com") and modUrl.endsWith("/"):
-    modUrl = modUrl[0 .. ^2]
-    isGithub = true
-
-  let (_, exitCode) = execCmdEx("git ls-remote --quiet --tags " & modUrl)
-  var xcode = exitCode
-  if isGithub and exitCode != QuitSuccess:
-    # retry multiple times to avoid annoying github timeouts:
-    for i in 0..4:
-      os.sleep(4000)
-      xcode = execCmdEx("git ls-remote --quiet --tags " & modUrl)[1]
-      if xcode == QuitSuccess: break
-
-  if xcode == QuitSuccess:
-    # retry multiple times to avoid annoying github timeouts:
-    let cmd = "git clone --recursive " & modUrl & " " & dest
-    for i in 0..4:
-      if execShellCmd(cmd) == 0: return ""
-      os.sleep(4000)
-    result = "exernal program failed: " & cmd
-  elif not isGithub:
-    let (_, exitCode) = execCmdEx("hg identify " & modUrl)
-    if exitCode == QuitSuccess:
-      let cmd = "hg clone " & modUrl & " " & dest
-      for i in 0..4:
-        if execShellCmd(cmd) == 0: return ""
-        os.sleep(4000)
-      result = "exernal program failed: " & cmd
-    else:
-      result = "Unable to identify url: " & modUrl
-  else:
-    result = "Unable to identify url: " & modUrl
diff --git a/atlas/packagesjson.nim b/atlas/packagesjson.nim
deleted file mode 100644
index 7e25c6934..000000000
--- a/atlas/packagesjson.nim
+++ /dev/null
@@ -1,161 +0,0 @@
-
-import std / [json, os, sets, strutils, httpclient, uri]
-
-type
-  Package* = ref object
-    # Required fields in a package.
-    name*: string
-    url*: string # Download location.
-    license*: string
-    downloadMethod*: string
-    description*: string
-    tags*: seq[string] # \
-    # From here on, optional fields set to the empty string if not available.
-    version*: string
-    dvcsTag*: string
-    web*: string # Info url for humans.
-
-proc optionalField(obj: JsonNode, name: string, default = ""): string =
-  if hasKey(obj, name) and obj[name].kind == JString:
-    result = obj[name].str
-  else:
-    result = default
-
-proc requiredField(obj: JsonNode, name: string): string =
-  result = optionalField(obj, name, "")
-
-proc fromJson*(obj: JSonNode): Package =
-  result = Package()
-  result.name = obj.requiredField("name")
-  if result.name.len == 0: return nil
-  result.version = obj.optionalField("version")
-  result.url = obj.requiredField("url")
-  if result.url.len == 0: return nil
-  result.downloadMethod = obj.requiredField("method")
-  if result.downloadMethod.len == 0: return nil
-  result.dvcsTag = obj.optionalField("dvcs-tag")
-  result.license = obj.optionalField("license")
-  result.tags = @[]
-  for t in obj["tags"]:
-    result.tags.add(t.str)
-  result.description = obj.requiredField("description")
-  result.web = obj.optionalField("web")
-
-const PackagesDir* = "packages"
-
-proc getPackages*(workspaceDir: string): seq[Package] =
-  result = @[]
-  var uniqueNames = initHashSet[string]()
-  var jsonFiles = 0
-  for kind, path in walkDir(workspaceDir / PackagesDir):
-    if kind == pcFile and path.endsWith(".json"):
-      inc jsonFiles
-      let packages = json.parseFile(path)
-      for p in packages:
-        let pkg = p.fromJson()
-        if pkg != nil and not uniqueNames.containsOrIncl(pkg.name):
-          result.add(pkg)
-
-proc `$`*(pkg: Package): string =
-  result = pkg.name & ":\n"
-  result &= "  url:         " & pkg.url & " (" & pkg.downloadMethod & ")\n"
-  result &= "  tags:        " & pkg.tags.join(", ") & "\n"
-  result &= "  description: " & pkg.description & "\n"
-  result &= "  license:     " & pkg.license & "\n"
-  if pkg.web.len > 0:
-    result &= "  website:     " & pkg.web & "\n"
-
-proc toTags(j: JsonNode): seq[string] =
-  result = @[]
-  if j.kind == JArray:
-    for elem in items j:
-      result.add elem.getStr("")
-
-proc singleGithubSearch(term: string): JsonNode =
-  # For example:
-  # https://api.github.com/search/repositories?q=weave+language:nim
-  var client = newHttpClient()
-  try:
-    let x = client.getContent("https://api.github.com/search/repositories?q=" & encodeUrl(term) & "+language:nim")
-    result = parseJson(x)
-  except:
-    result = parseJson("{\"items\": []}")
-  finally:
-    client.close()
-
-proc githubSearch(seen: var HashSet[string]; terms: seq[string]) =
-  for term in terms:
-    let results = singleGithubSearch(term)
-    for j in items(results.getOrDefault("items")):
-      let p = Package(
-        name: j.getOrDefault("name").getStr,
-        url: j.getOrDefault("html_url").getStr,
-        downloadMethod: "git",
-        tags: toTags(j.getOrDefault("topics")),
-        description: j.getOrDefault("description").getStr,
-        license: j.getOrDefault("license").getOrDefault("spdx_id").getStr,
-        web: j.getOrDefault("html_url").getStr
-      )
-      if not seen.containsOrIncl(p.url):
-        echo p
-
-proc getUrlFromGithub*(term: string): string =
-  let results = singleGithubSearch(term)
-  var matches = 0
-  result = ""
-  for j in items(results.getOrDefault("items")):
-    if cmpIgnoreCase(j.getOrDefault("name").getStr, term) == 0:
-      if matches == 0:
-        result = j.getOrDefault("html_url").getStr
-      inc matches
-  if matches != 1:
-    # ambiguous, not ok!
-    result = ""
-
-proc search*(pkgList: seq[Package]; terms: seq[string]) =
-  var seen = initHashSet[string]()
-  template onFound =
-    echo pkg
-    seen.incl pkg.url
-    break forPackage
-
-  for pkg in pkgList:
-    if terms.len > 0:
-      block forPackage:
-        for term in terms:
-          let word = term.toLower
-          # Search by name.
-          if word in pkg.name.toLower:
-            onFound()
-          # Search by tag.
-          for tag in pkg.tags:
-            if word in tag.toLower:
-              onFound()
-    else:
-      echo(pkg)
-  githubSearch seen, terms
-  if seen.len == 0 and terms.len > 0:
-    echo("No package found.")
-
-type PkgCandidates* = array[3, seq[Package]]
-
-proc determineCandidates*(pkgList: seq[Package];
-                         terms: seq[string]): PkgCandidates =
-  result[0] = @[]
-  result[1] = @[]
-  result[2] = @[]
-  for pkg in pkgList:
-    block termLoop:
-      for term in terms:
-        let word = term.toLower
-        if word == pkg.name.toLower:
-          result[0].add pkg
-          break termLoop
-        elif word in pkg.name.toLower:
-          result[1].add pkg
-          break termLoop
-        else:
-          for tag in pkg.tags:
-            if word in tag.toLower:
-              result[2].add pkg
-              break termLoop
diff --git a/atlas/sat.nim b/atlas/sat.nim
deleted file mode 100644
index 12ad07803..000000000
--- a/atlas/sat.nim
+++ /dev/null
@@ -1,313 +0,0 @@
-## SAT solver
-## (c) 2021 Andreas Rumpf
-## Based on explanations and Haskell code from
-## https://andrew.gibiansky.com/blog/verification/writing-a-sat-solver/
-
-## Formulars as packed ASTs, no pointers no cry. Solves formulars with many
-## thousands of variables in no time.
-
-type
-  FormKind* = enum
-    FalseForm, TrueForm, VarForm, NotForm, AndForm, OrForm, ExactlyOneOfForm # roughly 8 so the last 3 bits
-  BaseType = int32
-  Atom = distinct BaseType
-  VarId* = distinct BaseType
-  Formular* = seq[Atom] # linear storage
-
-proc `==`*(a, b: VarId): bool {.borrow.}
-
-const
-  KindBits = 3
-  KindMask = 0b111
-
-template kind(a: Atom): FormKind = FormKind(BaseType(a) and KindMask)
-template intVal(a: Atom): BaseType = BaseType(a) shr KindBits
-
-proc newVar*(val: VarId): Atom {.inline.} =
-  Atom((BaseType(val) shl KindBits) or BaseType(VarForm))
-
-proc newOperation(k: FormKind; val: BaseType): Atom {.inline.} =
-  Atom((val shl KindBits) or BaseType(k))
-
-proc trueLit(): Atom {.inline.} = Atom(TrueForm)
-proc falseLit(): Atom {.inline.} = Atom(FalseForm)
-
-proc lit(k: FormKind): Atom {.inline.} = Atom(k)
-
-when false:
-  proc isTrueLit(a: Atom): bool {.inline.} = a.kind == TrueForm
-  proc isFalseLit(a: Atom): bool {.inline.} = a.kind == FalseForm
-
-proc varId(a: Atom): VarId =
-  assert a.kind == VarForm
-  result = VarId(BaseType(a) shr KindBits)
-
-type
-  PatchPos = distinct int
-  FormPos = distinct int
-
-proc prepare(dest: var Formular; source: Formular; sourcePos: FormPos): PatchPos =
-  result = PatchPos dest.len
-  dest.add source[sourcePos.int]
-
-proc patch(f: var Formular; pos: PatchPos) =
-  let pos = pos.int
-  let k = f[pos].kind
-  assert k > VarForm
-  let distance = int32(f.len - pos)
-  f[pos] = newOperation(k, distance)
-
-proc nextChild(f: Formular; pos: var int) {.inline.} =
-  let x = f[int pos]
-  pos += (if x.kind <= VarForm: 1 else: int(intVal(x)))
-
-iterator sonsReadonly(f: Formular; n: FormPos): FormPos =
-  var pos = n.int
-  assert f[pos].kind > VarForm
-  let last = pos + f[pos].intVal
-  inc pos
-  while pos < last:
-    yield FormPos pos
-    nextChild f, pos
-
-iterator sons(dest: var Formular; source: Formular; n: FormPos): FormPos =
-  let patchPos = prepare(dest, source, n)
-  for x in sonsReadonly(source, n): yield x
-  patch dest, patchPos
-
-# String representation
-
-proc toString(dest: var string; f: Formular; n: FormPos) =
-  assert n.int >= 0
-  assert n.int < f.len
-  case f[n.int].kind
-  of FalseForm: dest.add 'F'
-  of TrueForm: dest.add 'T'
-  of VarForm:
-    dest.add 'v'
-    dest.addInt varId(f[n.int]).int
-  else:
-    case f[n.int].kind
-    of AndForm:
-      dest.add "(&"
-    of OrForm:
-      dest.add "(|"
-    of ExactlyOneOfForm:
-      dest.add "(1=="
-    of NotForm:
-      dest.add "(~"
-    else: assert false, "cannot happen"
-    for child in sonsReadonly(f, n):
-      toString(dest, f, child)
-      dest.add ' '
-    dest[^1] = ')'
-
-proc `$`*(f: Formular): string =
-  assert f.len > 0
-  toString(result, f, FormPos 0)
-
-type
-  Builder* = object
-    f: Formular
-    toPatch: seq[PatchPos]
-
-proc isEmpty*(b: Builder): bool {.inline.} =
-  b.f.len == 0 or b.f.len == 1 and b.f[0].kind in {NotForm, AndForm, OrForm, ExactlyOneOfForm}
-
-proc openOpr*(b: var Builder; k: FormKind) =
-  b.toPatch.add PatchPos b.f.len
-  b.f.add newOperation(k, 0)
-
-proc add*(b: var Builder; a: Atom) =
-  b.f.add a
-
-proc closeOpr*(b: var Builder) =
-  patch(b.f, b.toPatch.pop())
-
-proc toForm*(b: var Builder): Formular =
-  assert b.toPatch.len == 0, "missing `closeOpr` calls"
-  result = move b.f
-
-# Code from the blog translated into Nim and into our representation
-
-const
-  NoVar = VarId(-1)
-
-proc freeVariable(f: Formular): VarId =
-  ## returns NoVar if there is no free variable.
-  for i in 0..<f.len:
-    if f[i].kind == VarForm: return varId(f[i])
-  return NoVar
-
-type
-  BindingKind* = enum
-    dontCare,
-    setToFalse,
-    setToTrue
-  Solution* = seq[BindingKind]
-
-proc simplify(dest: var Formular; source: Formular; n: FormPos; sol: Solution): FormKind =
-  ## Returns either a Const constructor or a simplified expression;
-  ## if the result is not a Const constructor, it guarantees that there
-  ## are no Const constructors in the source tree further down.
-  let s = source[n.int]
-  result = s.kind
-  case result
-  of FalseForm, TrueForm:
-    # nothing interesting to do:
-    dest.add s
-  of VarForm:
-    let v = varId(s).int
-    if v < sol.len:
-      case sol[v]
-      of dontCare:
-        dest.add s
-      of setToFalse:
-        dest.add falseLit()
-        result = FalseForm
-      of setToTrue:
-        dest.add trueLit()
-        result = TrueForm
-    else:
-      dest.add s
-  of NotForm:
-    let oldLen = dest.len
-    var inner: FormKind
-    for child in sons(dest, source, n):
-      inner = simplify(dest, source, child, sol)
-    if inner in {FalseForm, TrueForm}:
-      setLen dest, oldLen
-      result = (if inner == FalseForm: TrueForm else: FalseForm)
-      dest.add lit(result)
-  of AndForm, OrForm:
-    let (tForm, fForm) = if result == AndForm: (TrueForm, FalseForm)
-                         else:                 (FalseForm, TrueForm)
-
-    let initialLen = dest.len
-    var childCount = 0
-    for child in sons(dest, source, n):
-      let oldLen = dest.len
-
-      let inner = simplify(dest, source, child, sol)
-      # ignore 'and T' or 'or F' subexpressions:
-      if inner == tForm:
-        setLen dest, oldLen
-      elif inner == fForm:
-        # 'and F' is always false and 'or T' is always true:
-        result = fForm
-        break
-      else:
-        inc childCount
-
-    if result == fForm:
-      setLen dest, initialLen
-      dest.add lit(result)
-    elif childCount == 1:
-      for i in initialLen..<dest.len-1:
-        dest[i] = dest[i+1]
-      setLen dest, dest.len-1
-      result = dest[initialLen].kind
-    elif childCount == 0:
-      # that means all subexpressions where ignored:
-      setLen dest, initialLen
-      result = tForm
-      dest.add lit(result)
-  of ExactlyOneOfForm:
-    let initialLen = dest.len
-    var childCount = 0
-    var couldEval = 0
-    for child in sons(dest, source, n):
-      let oldLen = dest.len
-
-      let inner = simplify(dest, source, child, sol)
-      # ignore 'exactlyOneOf F' subexpressions:
-      if inner == FalseForm:
-        setLen dest, oldLen
-      else:
-        if inner == TrueForm:
-          inc couldEval
-        inc childCount
-
-    if couldEval == childCount:
-      setLen dest, initialLen
-      if couldEval != 1:
-        dest.add lit FalseForm
-      else:
-        dest.add lit TrueForm
-    elif childCount == 1:
-      for i in initialLen..<dest.len-1:
-        dest[i] = dest[i+1]
-      setLen dest, dest.len-1
-      result = dest[initialLen].kind
-
-proc satisfiable*(f: Formular; s: var Solution): bool =
-  let v = freeVariable(f)
-  if v == NoVar:
-    result = f[0].kind == TrueForm
-  else:
-    result = false
-    # We have a variable to guess.
-    # Construct the two guesses.
-    # Return whether either one of them works.
-    if v.int >= s.len: s.setLen v.int+1
-    # try `setToFalse` first so that we don't end up with unnecessary dependencies:
-    s[v.int] = setToFalse
-
-    var falseGuess: Formular
-    let res = simplify(falseGuess, f, FormPos 0, s)
-
-    if res == TrueForm:
-      result = true
-    else:
-      result = satisfiable(falseGuess, s)
-      if not result:
-        s[v.int] = setToTrue
-
-        var trueGuess: Formular
-        let res = simplify(trueGuess, f, FormPos 0, s)
-
-        if res == TrueForm:
-          result = true
-        else:
-          result = satisfiable(trueGuess, s)
-          if not result:
-            # heuristic that provides a solution that comes closest to the "real" conflict:
-            s[v.int] = if trueGuess.len <= falseGuess.len: setToFalse else: setToTrue
-
-when isMainModule:
-  proc main =
-    var b: Builder
-    b.openOpr(AndForm)
-
-    b.openOpr(OrForm)
-    b.add newVar(VarId 1)
-    b.add newVar(VarId 2)
-    b.add newVar(VarId 3)
-    b.add newVar(VarId 4)
-    b.closeOpr
-
-    b.openOpr(ExactlyOneOfForm)
-    b.add newVar(VarId 5)
-    b.add newVar(VarId 6)
-    b.add newVar(VarId 7)
-
-    #b.openOpr(NotForm)
-    b.add newVar(VarId 8)
-    #b.closeOpr
-    b.closeOpr
-
-    b.add newVar(VarId 5)
-    b.add newVar(VarId 6)
-    b.closeOpr
-
-    let f = toForm(b)
-    echo "original: "
-    echo f
-
-    var s: Solution
-    echo satisfiable(f, s)
-    echo "solution"
-    for i in 0..<s.len:
-      echo "v", i, " ", s[i]
-
-  main()
diff --git a/atlas/testdata.nim b/atlas/testdata.nim
deleted file mode 100644
index aefaeacd2..000000000
--- a/atlas/testdata.nim
+++ /dev/null
@@ -1,63 +0,0 @@
-
-type
-  PerDirData = object
-    dirname: string
-    cmd: Command
-    exitCode: int
-    output: string
-
-template toData(a, b, c, d): untyped =
-  PerDirData(dirname: a, cmd: b, exitCode: c, output: d)
-
-const
-  TestLog = [
-    toData("balls", GitPull, 0, "Already up to date.\n"),
-    toData("grok", GitDiff, 0, ""),
-    toData("grok", GitTags, 0, "2ca193c31fa2377c1e991a080d60ca3215ff6cf0 refs/tags/0.0.1\n48007554b21ba2f65c726ae2fdda88d621865b4a refs/tags/0.0.2\n7092a0286421c7818cd335cca9ebc72d03d866c2 refs/tags/0.0.3\n62707b8ac684efac35d301dbde57dc750880268e refs/tags/0.0.4\n876f2504e0c2f785ffd2cf65a78e2aea474fa8aa refs/tags/0.0.5\nb7eb1f2501aa2382cb3a38353664a13af62a9888 refs/tags/0.0.6\nf5d818bfd6038884b3d8b531c58484ded20a58a4 refs/tags/0.1.0\n961eaddea49c3144d130d105195583d3f11fb6c6 refs/tags/0.2.0\n15ab8ed8d4f896232a976a9008548bd53af72a66 refs/tags/0.2.1\n426a7d7d4603f77ced658e73ad7f3f582413f6cd refs/tags/0.3.0\n83cf7a39b2fe897786fb0fe01a7a5933c3add286 refs/tags/0.3.1\n8d2e3c900edbc95fa0c036fd76f8e4f814aef2c1 refs/tags/0.3.2\n48b43372f49a3bb4dc0969d82a0fca183fb94662 refs/tags/0.3.3\n9ca947a3009ea6ba17814b20eb953272064eb2e6 refs/tags/0.4.0\n1b5643d04fba6d996a16d1ffc13d034a40003f8f refs/tags/0.5.0\n486b0eb580b1c465453d264ac758cc490c19c33e refs/tags/0.5.1\naedb0d9497390e20b9d2541cef2bb05a5cda7a71 refs/tags/0.5.2\n"),
-    toData("grok", GitCurrentCommit, 0, "349c15fd1e03f1fcdd81a1edefba3fa6116ab911\n"),
-    toData("grok", GitMergeBase, 0, "349c15fd1e03f1fcdd81a1edefba3fa6116ab911\n1b5643d04fba6d996a16d1ffc13d034a40003f8f\n349c15fd1e03f1fcdd81a1edefba3fa6116ab911\n"),
-    toData("grok", GitCheckout, 0, "1b5643d04fba6d996a16d1ffc13d034a40003f8f"), # watch out!
-
-    toData("ups", GitDiff, 0, ""),
-    toData("ups", GitTags, 0, "4008f9339cd22b30e180bc87a6cca7270fd28ac1 refs/tags/0.0.2\n19bc490c22b4f5b0628c31cdedead1375b279356 refs/tags/0.0.3\nff34602aaea824cb46d6588cd5fe1178132e9702 refs/tags/0.0.4\n09de599138f20b745133b6e4fe563e204415a7e8 refs/tags/0.0.5\n85fee3b74798311108a105635df31f892150f5d0 refs/tags/0.0.6\nfd303913b22b121dc42f332109e9c44950b9acd4 refs/tags/0.0.7\n"),
-    toData("ups", GitCurrentCommit, 0, "74c31af8030112dac758440aa51ef175992f71f3\n"),
-    toData("ups", GitMergeBase, 0, "74c31af8030112dac758440aa51ef175992f71f3\n4008f9339cd22b30e180bc87a6cca7270fd28ac1\n74c31af8030112dac758440aa51ef175992f71f3\n"),
-    toData("ups", GitCheckout, 0, "4008f9339cd22b30e180bc87a6cca7270fd28ac1"),
-
-    toData("sync", GitDiff, 0, ""),
-    toData("sync", GitRevParse, 0, "810bd2d75e9f6e182534ae2488670b51a9f13fc3\n"),
-    toData("sync", GitCurrentCommit, 0, "de5c7337ebc22422190e8aeca37d05651735f440\n"),
-    toData("sync", GitMergeBase, 0, "de5c7337ebc22422190e8aeca37d05651735f440\n810bd2d75e9f6e182534ae2488670b51a9f13fc3\n810bd2d75e9f6e182534ae2488670b51a9f13fc3\n"),
-
-    toData("npeg", GitDiff, 0, ""),
-    toData("npeg", GitTags, 0, "8df2f0c9391995fd086b8aab00e8ab7aded1e8f0 refs/tags/0.1.0\n4c959a72db5283b55eeef491076eefb5e02316f1 refs/tags/0.10.0\n802f47c0f7f4318a4f0858ba5a6a6ed2333bde71 refs/tags/0.11.0\n82c8d92837108dce225358ace2c416bf9a3f30ce refs/tags/0.12.0\n87d2f2c4f6ef7da350d45beb5a336611bde7f518 refs/tags/0.13.0\n39964f0d220bfaade47a568bf03c1cf28aa2bc37 refs/tags/0.14.0\nbe9f03f92304cbeab70572944a8563db9b23b2fb refs/tags/0.14.1\na933fb9832566fc95273e417597bfb4faf564ca6 refs/tags/0.15.0\n6aad2e438c52ff0636c7bfb64338e444ac3e83ba refs/tags/0.16.0\nf4ddffb5848c42c6151743dd9c7eddcaaabc56cc refs/tags/0.17.0\n30b446b39442cdbc53a97018ab8a54149aa7c3b7 refs/tags/0.17.1\n1a9d36aa3b34a6169d4530463f1c17a3fe1e075e refs/tags/0.18.0\ndd34f903a9a63b876cb2db19b7a4ce0bcc252134 refs/tags/0.19.0\nd93d49c81fc8722d7929ac463b435c0f2e10c53b refs/tags/0.2.0\neeae7746c9b1118bcf27744ab2aee26969051256 refs/tags/0.20.0\n8c3471a548129f3bf62df15cd0fd8cca1787d852 refs/tags/0.21.0\nc0e873a17bc713c80e74fec3c30cb62dcd5d194a refs/tags/0.21.1\nbae84c47a1bb259b209b6f6be1582327b784539d refs/tags/0.21.2\nbfcb4bcae76a917c3c88736ca773e4cb67dbb2d8 refs/tags/0.21.3\n0eabb7c462d30932049f0b7e6a030c1562cf9fee refs/tags/0.22.0\n2e75367095f54d4351005078bad98041a55b14c1 refs/tags/0.22.1\n814ea235dd398108d7b18f966694c3d951575701 refs/tags/0.22.2\na812064587d983c129737f8500bf74990e6b8dab refs/tags/0.23.0\nbd969ad3745db0d66022564cac76cf9424651104 refs/tags/0.23.1\na037c646a47623b92718efadc2bb74d03664b360 refs/tags/0.23.2\n078475ccceeaca0fac947492acdd24514da8d863 refs/tags/0.24.0\ne7bd87dc992512fd5825a557a56907647e03c979 refs/tags/0.24.1\n45ea601e1c7f64fb857bc99df984b86673621d2c refs/tags/0.3.0\n1ea9868a3fee3aa487ab7ec9129208a4dd483d0d refs/tags/0.4.0\n39afdb5733d3245386d29d08c5ff61c89268f499 refs/tags/0.5.0\n458c7b5910fcb157af3fc51bc3b3e663fdb3ed4a refs/tags/0.6.0\n06c38bd8563d822455bc237c2a98c153d938ed1b refs/tags/0.7.0\nf446b6056eef6d8dc9d8b47a79aca93d17dc8230 refs/tags/0.8.0\nbb25a195133f9f7af06386d0809793923cc5e8ab refs/tags/0.9.0\n"),
-    toData("npeg", GitCurrentCommit, 0, "5d80f93aa720898936668b3bc47d0fff101ec414\n"),
-    toData("npeg", GitMergeBase, 0, "5d80f93aa720898936668b3bc47d0fff101ec414\na037c646a47623b92718efadc2bb74d03664b360\na037c646a47623b92718efadc2bb74d03664b360\n"),
-
-    toData("testes", GitDiff, 0, ""),
-    toData("testes", GitTags, 0, "3ce9b2968b5f644755a0ced1baa3eece88c2f12e refs/tags/0.1.0\nf73af8318b54737678fab8b54bdcd8a451015e0d refs/tags/0.1.1\nd21d84d37b161a123a43318bae353108755916de refs/tags/0.1.2\n5c36b6095353ed03b08ac939d00aff2d73f79a35 refs/tags/0.1.3\na1220d11237ee8f135f772ff9731c11b2d91ba31 refs/tags/0.1.4\n574f741b90d04a7ce8c9b990e6077708d7ad076e refs/tags/0.1.5\nced0a9e58234b680def6931578e09165a32e6291 refs/tags/0.1.6\nbb248952e8742a6011eb1a45a9d2059aeb0341d7 refs/tags/0.1.7\nabb7d7c552da0a8e0ddc586c15ccf7e74b0d068b refs/tags/0.10.0\n6e42a768a90d6442196b344bcdcb6f834b76e7b7 refs/tags/0.2.0\n9d136c3a0851ca2c021f5fb4f7b63f0a0ef77232 refs/tags/0.2.1\ndcb282b2da863fd2939e1969cec7a99788feb456 refs/tags/0.2.2\nf708a632afaa40a322a1a61c1c13722edac8e8c5 refs/tags/0.3.0\n3213f59e3f9ba052452c59f01d1418360d856af6 refs/tags/0.3.1\nf7bb1743dffd327958dfcebae4cfb6f61cc1cb8c refs/tags/0.3.2\n6b64569ebecad6bc60cc8697713701e7659204f4 refs/tags/0.3.3\nb51c25a4367bd17f419f78cb5a27f319e9d820f5 refs/tags/0.3.4\nb265612710cbd5ddb1b173c94ece8ec5c7ceccac refs/tags/0.3.5\ne404bcfe42e92d7509717a2dfa115cacb4964c5d refs/tags/0.3.6\n5e4d0d5b7e7f314dde701c546c4365c59782d3dc refs/tags/0.3.7\ne13f91c9c913d2b81c59adeaad687efa2b35293a refs/tags/0.3.8\n17599625f09af0ae4b525e63ab726a3002540702 refs/tags/0.3.9\n13e907f70571dd146d8dc29ddec4599b40ba4e85 refs/tags/0.4.0\n155a74cf676495df1e0674dd07b5e4a0291a9a4a refs/tags/0.4.1\nf37abccdc148cb02ca637a6f0bc8821491cce358 refs/tags/0.4.2\n0250d29ebdd02f28f9020445adb5a4e51fd1902c refs/tags/0.5.0\n2fb87db6d9f34109a70205876030c53f815739b7 refs/tags/0.5.1\n629d17ba8d6a1a4eca8145eb089ed5bca4473dfc refs/tags/0.6.0\ne926130f5f1b7903f68be49cc1563225bd9d948d refs/tags/0.7.0\n7365303897e6185796c274425c079916047e3f14 refs/tags/0.7.1\na735c4adabeba637409f41c4325dd8fc5fb91e2d refs/tags/0.7.10\nfe023fd27404889c5122f902456cbba14b767405 refs/tags/0.7.11\n4430e72972c77a5e9c1555d59bba11d840682691 refs/tags/0.7.12\nf0e53eb490a9558c7f594d2e095b70665e36ca88 refs/tags/0.7.13\nf6520e25e7c329c2957cda447f149fc6a930db0d refs/tags/0.7.2\nd509762f7191757c240d3c79c9ecda53f8c0cfe3 refs/tags/0.7.3\nc02e7a783d1c42fd1f91bca7142f7c3733950c05 refs/tags/0.7.4\n8c8a9e496e9b86ba7602709438980ca31e6989d9 refs/tags/0.7.5\n29839c18b4ac83c0111a178322b57ebb8a8d402c refs/tags/0.7.6\n3b62973cf74fafd8ea906644d89ac34d29a8a6cf refs/tags/0.7.7\ne67ff99dc43c391e89a37f97a9d298c3428bbde2 refs/tags/0.7.8\n4b72ecda0d40ed8e5ab8ad4095a0691d30ec6cd0 refs/tags/0.7.9\n2512b8cc3d7f001d277e89978da2049a5feee5c4 refs/tags/0.8.0\n86c47029690bd2731d204245f3f54462227bba0d refs/tags/0.9.0\n9a7f94f78588e9b5ba7ca077e1f7eae0607c6cf6 refs/tags/0.9.1\n08c915dc016d16c1dfa9a77d0b045ec29c9f2074 refs/tags/0.9.2\n3fb658b1ce1e1efa37d6f9f14322bdac8def02a5 refs/tags/0.9.3\n738fda0add962379ffe6aa6ca5f01a6943a98a2e refs/tags/0.9.4\n48d821add361f7ad768ecb35a0b19c38f90c919e refs/tags/0.9.5\nff9ae890f597dac301b2ac6e6805eb9ac5afd49a refs/tags/0.9.6\n483c78f06e60b0ec5e79fc3476df075ee7286890 refs/tags/0.9.7\n416eec87a5ae39a1a6035552e9e9a47d76b13026 refs/tags/1.0.0\na935cfe9445cc5218fbdd7e0afb35aa1587fff61 refs/tags/1.0.1\n4b83863a9181f054bb695b11b5d663406dfd85d2 refs/tags/1.0.2\n295145fddaa4fe29c1e71a5044d968a84f9dbf69 refs/tags/1.1.0\n8f74ea4e5718436c47305b4488842e6458a13dac refs/tags/1.1.1\n4135bb291e53d615a976e997c44fb2bd9e1ad343 refs/tags/1.1.10\n8c09dbcd16612f5989065db02ea2e7a752dd2656 refs/tags/1.1.11\naedfebdb6c016431d84b0c07cf181b957a900640 refs/tags/1.1.12\n2c2e958366ef6998115740bdf110588d730e5738 refs/tags/1.1.2\nbecc77258321e6ec40d89efdddf37bafd0d07fc3 refs/tags/1.1.3\ne070d7c9853bf94c35b81cf0c0a8980c2449bb22 refs/tags/1.1.4\n12c986cbbf65e8571a486e9230808bf887e5f04f refs/tags/1.1.5\n63df8986f5b56913b02d26954fa033eeaf43714c refs/tags/1.1.6\n38e02c9c6bd728b043036fe0d1894d774cab3108 refs/tags/1.1.7\n3c3879fff16450d28ade79a6b08982bf5cefc061 refs/tags/1.1.8\ne32b811b3b2e70a1d189d7a663bc2583e9c18f96 refs/tags/1.1.9\n0c1b4277c08197ce7e7e0aa2bad91d909fcd96ac refs/tags/2.0.0\n"),
-    toData("testes", GitCurrentCommit, 0, "d9db2ad09aa38fc26625341e1b666602959e144f\n"),
-    toData("testes", GitMergeBase, 0, "d9db2ad09aa38fc26625341e1b666602959e144f\n416eec87a5ae39a1a6035552e9e9a47d76b13026\nd9db2ad09aa38fc26625341e1b666602959e144f\n"),
-    toData("testes", GitCheckout, 0, "416eec87a5ae39a1a6035552e9e9a47d76b13026"),
-
-    toData("grok", GitDiff, 0, ""),
-    toData("grok", GitTags, 0, "2ca193c31fa2377c1e991a080d60ca3215ff6cf0 refs/tags/0.0.1\n48007554b21ba2f65c726ae2fdda88d621865b4a refs/tags/0.0.2\n7092a0286421c7818cd335cca9ebc72d03d866c2 refs/tags/0.0.3\n62707b8ac684efac35d301dbde57dc750880268e refs/tags/0.0.4\n876f2504e0c2f785ffd2cf65a78e2aea474fa8aa refs/tags/0.0.5\nb7eb1f2501aa2382cb3a38353664a13af62a9888 refs/tags/0.0.6\nf5d818bfd6038884b3d8b531c58484ded20a58a4 refs/tags/0.1.0\n961eaddea49c3144d130d105195583d3f11fb6c6 refs/tags/0.2.0\n15ab8ed8d4f896232a976a9008548bd53af72a66 refs/tags/0.2.1\n426a7d7d4603f77ced658e73ad7f3f582413f6cd refs/tags/0.3.0\n83cf7a39b2fe897786fb0fe01a7a5933c3add286 refs/tags/0.3.1\n8d2e3c900edbc95fa0c036fd76f8e4f814aef2c1 refs/tags/0.3.2\n48b43372f49a3bb4dc0969d82a0fca183fb94662 refs/tags/0.3.3\n9ca947a3009ea6ba17814b20eb953272064eb2e6 refs/tags/0.4.0\n1b5643d04fba6d996a16d1ffc13d034a40003f8f refs/tags/0.5.0\n486b0eb580b1c465453d264ac758cc490c19c33e refs/tags/0.5.1\naedb0d9497390e20b9d2541cef2bb05a5cda7a71 refs/tags/0.5.2\n"),
-    toData("grok", GitCurrentCommit, 0, "4e6526a91a23eaec778184e16ce9a34d25d48bdc\n"),
-    toData("grok", GitMergeBase, 0, "4e6526a91a23eaec778184e16ce9a34d25d48bdc\n62707b8ac684efac35d301dbde57dc750880268e\n349c15fd1e03f1fcdd81a1edefba3fa6116ab911\n"),
-    toData("grok", GitCheckout, 0, "62707b8ac684efac35d301dbde57dc750880268e"),
-
-    toData("nim-bytes2human", GitDiff, 0, ""),
-    toData("nim-bytes2human", GitTags, 0, ""),
-    toData("nim-bytes2human", GitCurrentcommit, 0, "ec2c1a758cabdd4751a06c8ebf2b923f19e32731\n")
-  ]
-
-#[
-Current directory is now E:\atlastest\nim-bytes2human
-cmd git diff args [] --> ("", 0)
-cmd git show-ref --tags args [] --> ("", 1)
-cmd git log -n 1 --format=%H args [] --> (, 0)
-[Warning] (nim-bytes2human) package has no tagged releases
-nimble E:\atlastest\nim-bytes2human\bytes2human.nimble info (requires: @["nim >= 1.0.0"], srcDir: "src", tasks: @[])
-[Error] There were problems.
-Error: execution of an external program failed: 'E:\nim\tools\atlas\atlas.exe clone https://github.com/disruptek/balls'
-]#
diff --git a/atlas/tester.nim b/atlas/tester.nim
deleted file mode 100644
index ccc84f5ab..000000000
--- a/atlas/tester.nim
+++ /dev/null
@@ -1,46 +0,0 @@
-# Small program that runs the test cases
-
-import std / [strutils, os, sequtils]
-from std/private/gitutils import diffFiles
-
-if execShellCmd("nim c -r atlas/versions.nim") != 0:
-  quit("FAILURE: unit tests in atlas/versions.nim failed")
-
-var failures = 0
-
-when defined(develop):
-  const atlasExe = "bin" / "atlas".addFileExt(ExeExt)
-  if execShellCmd("nim c -o:$# atlas/atlas.nim" % [atlasExe]) != 0:
-    quit("FAILURE: compilation of atlas failed")
-else:
-  const atlasExe = "atlas".addFileExt(ExeExt)
-
-proc exec(cmd: string) =
-  if execShellCmd(cmd) != 0:
-    quit "FAILURE: " & cmd
-
-proc sameDirContents(expected, given: string) =
-  for _, e in walkDir(expected):
-    let g = given / splitPath(e).tail
-    if fileExists(g):
-      if readFile(e) != readFile(g):
-        echo "FAILURE: files differ: ", e
-        echo diffFiles(e, g).output
-        inc failures
-    else:
-      echo "FAILURE: file does not exist: ", g
-      inc failures
-
-proc testWsConflict() =
-  const myproject = "atlas/tests/ws_conflict/myproject"
-  createDir(myproject)
-  exec atlasExe & " --project=" & myproject & " --showGraph --genLock use https://github.com/apkg"
-  sameDirContents("atlas/tests/ws_conflict/expected", myproject)
-  removeDir("atlas/tests/ws_conflict/apkg")
-  removeDir("atlas/tests/ws_conflict/bpkg")
-  removeDir("atlas/tests/ws_conflict/cpkg")
-  removeDir("atlas/tests/ws_conflict/dpkg")
-  removeDir(myproject)
-
-testWsConflict()
-if failures > 0: quit($failures & " failures occurred.")
diff --git a/atlas/tests/balls.nimble b/atlas/tests/balls.nimble
deleted file mode 100644
index 143e757e9..000000000
--- a/atlas/tests/balls.nimble
+++ /dev/null
@@ -1,32 +0,0 @@
-version = "3.4.1"
-author = "disruptek"
-description = "a unittest framework with balls 🔴🟡🟢"
-license = "MIT"
-
-# requires newTreeFrom
-requires "https://github.com/disruptek/grok >= 0.5.0 & < 1.0.0"
-requires "https://github.com/disruptek/ups < 1.0.0"
-requires "https://github.com/planetis-m/sync#810bd2d"
-#requires "https://github.com/c-blake/cligen < 2.0.0"
-
-bin = @["balls"]            # build the binary for basic test running
-installExt = @["nim"]       # we need to install balls.nim also
-skipDirs = @["tests"]       # so stupid...  who doesn't want tests?
-#installFiles = @["balls.nim"] # https://github.com/nim-lang/Nim/issues/16661
-
-task test, "run tests for ci":
-  when defined(windows):
-    exec "balls.cmd"
-  else:
-    exec "balls"
-
-task demo, "produce a demo":
-  exec "nim c --define:release balls.nim"
-  when (NimMajor, NimMinor) != (1, 0):
-    echo "due to nim bug #16307, use nim-1.0"
-    quit 1
-  exec """demo docs/demo.svg "nim c --out=\$1 examples/fails.nim""""
-  exec """demo docs/clean.svg "nim c --define:danger -f --out=\$1 tests/test.nim""""
-  exec "nim c --define:release --define:ballsDry balls.nim"
-  exec """demo docs/runner.svg "balls""""
-
diff --git a/atlas/tests/grok.nimble b/atlas/tests/grok.nimble
deleted file mode 100644
index 1b6d77c08..000000000
--- a/atlas/tests/grok.nimble
+++ /dev/null
@@ -1,5 +0,0 @@
-version = "0.0.4"
-author = "disruptek"
-description = "don't read too much into it"
-license = "MIT"
-requires "nim >= 1.0.0"
diff --git a/atlas/tests/nim-bytes2human.nimble b/atlas/tests/nim-bytes2human.nimble
deleted file mode 100644
index 9f3ae2479..000000000
--- a/atlas/tests/nim-bytes2human.nimble
+++ /dev/null
@@ -1,7 +0,0 @@
-version     = "0.2.2"
-author      = "Juan Carlos"
-description = "Convert bytes to kilobytes, megabytes, gigabytes, etc."
-license     = "MIT"
-srcDir      = "src"
-
-requires "nim >= 1.0.0"  # https://github.com/juancarlospaco/nim-bytes2human/issues/2#issue-714338524
diff --git a/atlas/tests/nim.cfg b/atlas/tests/nim.cfg
deleted file mode 100644
index 3982b12bb..000000000
--- a/atlas/tests/nim.cfg
+++ /dev/null
@@ -1,10 +0,0 @@
-############# begin Atlas config section ##########
---noNimblePath
---path:"../balls"
---path:"../grok"
---path:"../ups"
---path:"../sync"
---path:"../npeg/src"
---path:"../testes"
---path:"../nim-bytes2human/src"
-############# end Atlas config section   ##########
diff --git a/atlas/tests/npeg.nimble b/atlas/tests/npeg.nimble
deleted file mode 100644
index e71fc5aa5..000000000
--- a/atlas/tests/npeg.nimble
+++ /dev/null
@@ -1,48 +0,0 @@
-# Package
-
-version       = "0.24.1"
-author        = "Ico Doornekamp"
-description   = "a PEG library"
-license       = "MIT"
-srcDir        = "src"
-installExt    = @["nim"]
-
-# Dependencies
-
-requires "nim >= 0.19.0"
-
-# Test
-
-task test, "Runs the test suite":
-  exec "nimble testc && nimble testcpp && nimble testarc && nimble testjs"
-
-task testc, "C tests":
-  exec "nim c -r tests/tests.nim"
-
-task testcpp, "CPP tests":
-  exec "nim cpp -r tests/tests.nim"
-
-task testjs, "JS tests":
-  exec "nim js -r tests/tests.nim"
-
-task testdanger, "Runs the test suite in danger mode":
-  exec "nim c -d:danger -r tests/tests.nim"
-
-task testwin, "Mingw tests":
-  exec "nim c -d:mingw tests/tests.nim && wine tests/tests.exe"
-
-task test32, "32 bit tests":
-  exec "nim c --cpu:i386 --passC:-m32 --passL:-m32 tests/tests.nim && tests/tests"
-
-task testall, "Test all":
-  exec "nimble test && nimble testcpp && nimble testdanger && nimble testjs && nimble testwin"
-
-when (NimMajor, NimMinor) >= (1, 1):
-  task testarc, "--gc:arc tests":
-    exec "nim c --gc:arc -r tests/tests.nim"
-else:
-  task testarc, "--gc:arc tests":
-    exec "true"
-
-task perf, "Test performance":
-  exec "nim cpp -r -d:danger tests/performance.nim"
diff --git a/atlas/tests/packages/packages.json b/atlas/tests/packages/packages.json
deleted file mode 100644
index d054a201b..000000000
--- a/atlas/tests/packages/packages.json
+++ /dev/null
@@ -1,36 +0,0 @@
-[
-  {
-    "name": "bytes2human",
-    "url": "https://github.com/juancarlospaco/nim-bytes2human",
-    "method": "git",
-    "tags": [
-      "bytes",
-      "human",
-      "minimalism",
-      "size"
-    ],
-    "description": "Convert bytes to kilobytes, megabytes, gigabytes, etc.",
-    "license": "LGPLv3",
-    "web": "https://github.com/juancarlospaco/nim-bytes2human"
-  },
-  {
-    "name": "npeg",
-    "url": "https://github.com/zevv/npeg",
-    "method": "git",
-    "tags": [
-      "PEG",
-      "parser",
-      "parsing",
-      "regexp",
-      "regular",
-      "grammar",
-      "lexer",
-      "lexing",
-      "pattern",
-      "matching"
-    ],
-    "description": "PEG (Parsing Expression Grammars) string matching library for Nim",
-    "license": "MIT",
-    "web": "https://github.com/zevv/npeg"
-  }
-]
diff --git a/atlas/tests/sync.nimble b/atlas/tests/sync.nimble
deleted file mode 100644
index a07ae8925..000000000
--- a/atlas/tests/sync.nimble
+++ /dev/null
@@ -1,10 +0,0 @@
-# Package
-
-version     = "1.4.0"
-author      = "Antonis Geralis"
-description = "Useful synchronization primitives."
-license     = "MIT"
-
-# Deps
-
-requires "nim >= 1.0.0"
diff --git a/atlas/tests/testes.nimble b/atlas/tests/testes.nimble
deleted file mode 100644
index 60fe1d508..000000000
--- a/atlas/tests/testes.nimble
+++ /dev/null
@@ -1,23 +0,0 @@
-version = "1.0.0"
-author = "disruptek"
-description = "a cure for salty testes"
-license = "MIT"
-
-#requires "cligen >= 0.9.41 & <= 0.9.45"
-#requires "bump >= 1.8.18 & < 2.0.0"
-requires "https://github.com/disruptek/grok >= 0.0.4 & < 1.0.0"
-requires "https://github.com/juancarlospaco/nim-bytes2human"
-
-bin = @["testes"]           # build the binary for basic test running
-installExt = @["nim"]       # we need to install testes.nim also
-skipDirs = @["tests"]       # so stupid...  who doesn't want tests?
-
-task test, "run tests for ci":
-  exec "nim c --run testes.nim"
-
-task demo, "produce a demo":
-  when (NimMajor, NimMinor) != (1, 0):
-    echo "due to nim bug #16307, use nim-1.0"
-    quit 1
-  exec """demo docs/demo.svg "nim c --out=\$1 examples/balls.nim""""
-  exec """demo docs/clean.svg "nim c --define:danger --out=\$1 tests/testicles.nim""""
diff --git a/atlas/tests/ups.nimble b/atlas/tests/ups.nimble
deleted file mode 100644
index d91abbe60..000000000
--- a/atlas/tests/ups.nimble
+++ /dev/null
@@ -1,13 +0,0 @@
-version = "0.0.2"
-author = "disruptek"
-description = "a package handler"
-license = "MIT"
-
-requires "npeg >= 0.23.2 & < 1.0.0"
-requires "https://github.com/disruptek/testes >= 1.0.0 & < 2.0.0"
-
-task test, "run tests":
-  when defined(windows):
-    exec "testes.cmd"
-  else:
-    exec findExe"testes"
diff --git a/atlas/tests/ws_conflict/atlas.workspace b/atlas/tests/ws_conflict/atlas.workspace
deleted file mode 100644
index 21954fea1..000000000
--- a/atlas/tests/ws_conflict/atlas.workspace
+++ /dev/null
@@ -1,2 +0,0 @@
-deps=""
-overrides="url.rules"
diff --git a/atlas/tests/ws_conflict/expected/atlas.lock b/atlas/tests/ws_conflict/expected/atlas.lock
deleted file mode 100644
index 2f809ace3..000000000
--- a/atlas/tests/ws_conflict/expected/atlas.lock
+++ /dev/null
@@ -1,20 +0,0 @@
-{
-  "items": {
-    "apkg": {
-      "url": "file://./source/apkg",
-      "commit": "#head"
-    },
-    "bpkg": {
-      "url": "file://./source/bpkg",
-      "commit": "1.0"
-    },
-    "cpkg": {
-      "url": "file://./source/cpkg",
-      "commit": "2.0"
-    },
-    "dpkg": {
-      "url": "file://./source/dpkg",
-      "commit": "1.0"
-    }
-  }
-}
\ No newline at end of file
diff --git a/atlas/tests/ws_conflict/expected/deps.dot b/atlas/tests/ws_conflict/expected/deps.dot
deleted file mode 100644
index dd2e452f1..000000000
--- a/atlas/tests/ws_conflict/expected/deps.dot
+++ /dev/null
@@ -1,11 +0,0 @@
-digraph deps {
-"file://./source/apkg/#head" [label=""];
-"file://./source/bpkg/1.0" [label=""];
-"file://./source/cpkg/1.0" [label="unused"];
-"file://./source/cpkg/2.0" [label=""];
-"file://./source/dpkg/1.0" [label=""];
-"file://./source/apkg/#head" -> "file://./source/bpkg/1.0";
-"file://./source/apkg/#head" -> "file://./source/cpkg/1.0";
-"file://./source/bpkg/1.0" -> "file://./source/cpkg/2.0";
-"file://./source/cpkg/2.0" -> "file://./source/dpkg/1.0";
-}
diff --git a/atlas/tests/ws_conflict/expected/myproject.nimble b/atlas/tests/ws_conflict/expected/myproject.nimble
deleted file mode 100644
index 863e7c171..000000000
--- a/atlas/tests/ws_conflict/expected/myproject.nimble
+++ /dev/null
@@ -1 +0,0 @@
-requires "https://github.com/apkg"
diff --git a/atlas/tests/ws_conflict/expected/nim.cfg b/atlas/tests/ws_conflict/expected/nim.cfg
deleted file mode 100644
index 6f7c73055..000000000
--- a/atlas/tests/ws_conflict/expected/nim.cfg
+++ /dev/null
@@ -1,7 +0,0 @@
-############# begin Atlas config section ##########
---noNimblePath
---path:"../apkg"
---path:"../bpkg"
---path:"../cpkg"
---path:"../dpkg"
-############# end Atlas config section   ##########
diff --git a/atlas/tests/ws_conflict/source/apkg/apkg.nimble b/atlas/tests/ws_conflict/source/apkg/apkg.nimble
deleted file mode 100644
index 11065ec15..000000000
--- a/atlas/tests/ws_conflict/source/apkg/apkg.nimble
+++ /dev/null
@@ -1,4 +0,0 @@
-# require first b and then c
-
-requires "https://github.com/bpkg >= 1.0"
-requires "https://github.com/cpkg >= 1.0"
diff --git a/atlas/tests/ws_conflict/source/bpkg@1.0/bpkg.nimble b/atlas/tests/ws_conflict/source/bpkg@1.0/bpkg.nimble
deleted file mode 100644
index f70ad45cb..000000000
--- a/atlas/tests/ws_conflict/source/bpkg@1.0/bpkg.nimble
+++ /dev/null
@@ -1 +0,0 @@
-requires "https://github.com/cpkg >= 2.0"
diff --git a/atlas/tests/ws_conflict/source/cpkg@1.0/cpkg.nimble b/atlas/tests/ws_conflict/source/cpkg@1.0/cpkg.nimble
deleted file mode 100644
index 0fbd587aa..000000000
--- a/atlas/tests/ws_conflict/source/cpkg@1.0/cpkg.nimble
+++ /dev/null
@@ -1 +0,0 @@
-# No dependency here!
diff --git a/atlas/tests/ws_conflict/source/cpkg@2.0/cpkg.nimble b/atlas/tests/ws_conflict/source/cpkg@2.0/cpkg.nimble
deleted file mode 100644
index 381fae111..000000000
--- a/atlas/tests/ws_conflict/source/cpkg@2.0/cpkg.nimble
+++ /dev/null
@@ -1 +0,0 @@
-requires "https://github.com/dpkg >= 1.0"
diff --git a/atlas/tests/ws_conflict/source/dpkg/dpkg.nimble b/atlas/tests/ws_conflict/source/dpkg/dpkg.nimble
deleted file mode 100644
index 9ed72bb49..000000000
--- a/atlas/tests/ws_conflict/source/dpkg/dpkg.nimble
+++ /dev/null
@@ -1 +0,0 @@
-# empty for now
diff --git a/atlas/tests/ws_conflict/url.rules b/atlas/tests/ws_conflict/url.rules
deleted file mode 100644
index c641cf990..000000000
--- a/atlas/tests/ws_conflict/url.rules
+++ /dev/null
@@ -1 +0,0 @@
-https://github.com/$+ -> file://./source/$#
diff --git a/atlas/versions.nim b/atlas/versions.nim
deleted file mode 100644
index f955846c2..000000000
--- a/atlas/versions.nim
+++ /dev/null
@@ -1,348 +0,0 @@
-#
-#           Atlas Package Cloner
-#        (c) Copyright 2021 Andreas Rumpf
-#
-#    See the file "copying.txt", included in this
-#    distribution, for details about the copyright.
-#
-
-import std / [strutils, parseutils, algorithm]
-
-type
-  Version* = distinct string
-
-  VersionRelation* = enum
-    verGe, # >= V -- Equal or later
-    verGt, # > V
-    verLe, # <= V -- Equal or earlier
-    verLt, # < V
-    verEq, # V
-    verAny, # *
-    verSpecial # #head
-
-  VersionReq* = object
-    r: VersionRelation
-    v: Version
-
-  VersionInterval* = object
-    a: VersionReq
-    b: VersionReq
-    isInterval: bool
-
-template versionKey*(i: VersionInterval): string = i.a.v.string
-
-proc createQueryEq*(v: Version): VersionInterval =
-  VersionInterval(a: VersionReq(r: verEq, v: v))
-
-proc extractGeQuery*(i: VersionInterval): Version =
-  if i.a.r in {verGe, verGt, verEq}:
-    result = i.a.v
-  else:
-    result = Version""
-
-proc `$`*(v: Version): string {.borrow.}
-
-proc isSpecial(v: Version): bool =
-  result = v.string.len > 0 and v.string[0] == '#'
-
-proc isValidVersion*(v: string): bool {.inline.} =
-  result = v.len > 0 and v[0] in {'#'} + Digits
-
-proc isHead(v: Version): bool {.inline.} = cmpIgnoreCase(v.string, "#head") == 0
-
-template next(l, p, s: untyped) =
-  if l > 0:
-    inc p, l
-    if p < s.len and s[p] == '.':
-      inc p
-    else:
-      p = s.len
-  else:
-    p = s.len
-
-proc lt(a, b: string): bool {.inline.} =
-  var i = 0
-  var j = 0
-  while i < a.len or j < b.len:
-    var x = 0
-    let l1 = parseSaturatedNatural(a, x, i)
-
-    var y = 0
-    let l2 = parseSaturatedNatural(b, y, j)
-
-    if x < y:
-      return true
-    elif x == y:
-      discard "continue"
-    else:
-      return false
-    next l1, i, a
-    next l2, j, b
-
-  result = false
-
-proc `<`*(a, b: Version): bool =
-  # Handling for special versions such as "#head" or "#branch".
-  if a.isSpecial or b.isSpecial:
-    if a.isHead: return false
-    if b.isHead: return true
-    # any order will do as long as the "sort" operation moves #thing
-    # to the bottom:
-    if a.isSpecial and b.isSpecial:
-      return a.string < b.string
-  return lt(a.string, b.string)
-
-proc eq(a, b: string): bool {.inline.} =
-  var i = 0
-  var j = 0
-  while i < a.len or j < b.len:
-    var x = 0
-    let l1 = parseSaturatedNatural(a, x, i)
-
-    var y = 0
-    let l2 = parseSaturatedNatural(b, y, j)
-
-    if x == y:
-      discard "continue"
-    else:
-      return false
-    next l1, i, a
-    next l2, j, b
-
-  result = true
-
-proc `==`*(a, b: Version): bool =
-  if a.isSpecial or b.isSpecial:
-    result = a.string == b.string
-  else:
-    result = eq(a.string, b.string)
-
-proc parseVer(s: string; start: var int): Version =
-  if start < s.len and s[start] == '#':
-    var i = start
-    while i < s.len and s[i] notin Whitespace: inc i
-    result = Version s.substr(start, i-1)
-    start = i
-  elif start < s.len and s[start] in Digits:
-    var i = start
-    while i < s.len and s[i] in Digits+{'.'}: inc i
-    result = Version s.substr(start, i-1)
-    start = i
-  else:
-    result = Version""
-
-proc parseVersion*(s: string; start: int): Version =
-  var i = start
-  result = parseVer(s, i)
-
-proc parseSuffix(s: string; start: int; result: var VersionInterval; err: var bool) =
-  # >= 1.5 & <= 1.8
-  #        ^ we are here
-  var i = start
-  while i < s.len and s[i] in Whitespace: inc i
-  # Nimble doesn't use the syntax `>= 1.5, < 1.6` but we do:
-  if i < s.len and s[i] in {'&', ','}:
-    inc i
-    while i < s.len and s[i] in Whitespace: inc i
-    if s[i] == '<':
-      inc i
-      var r = verLt
-      if s[i] == '=':
-        inc i
-        r = verLe
-      while i < s.len and s[i] in Whitespace: inc i
-      result.b = VersionReq(r: r, v: parseVer(s, i))
-      result.isInterval = true
-      while i < s.len and s[i] in Whitespace: inc i
-      # we must have parsed everything:
-      if i < s.len:
-        err = true
-
-proc parseVersionInterval*(s: string; start: int; err: var bool): VersionInterval =
-  var i = start
-  while i < s.len and s[i] in Whitespace: inc i
-  result = VersionInterval(a: VersionReq(r: verAny, v: Version""))
-  if i < s.len:
-    case s[i]
-    of '*': result = VersionInterval(a: VersionReq(r: verAny, v: Version""))
-    of '#', '0'..'9':
-      result = VersionInterval(a: VersionReq(r: verEq, v: parseVer(s, i)))
-      if result.a.v.isHead: result.a.r = verAny
-      err = i < s.len
-    of '=':
-      inc i
-      if i < s.len and s[i] == '=': inc i
-      while i < s.len and s[i] in Whitespace: inc i
-      result = VersionInterval(a: VersionReq(r: verEq, v: parseVer(s, i)))
-      err = i < s.len
-    of '<':
-      inc i
-      var r = verLt
-      if i < s.len and s[i] == '=':
-        r = verLe
-        inc i
-      while i < s.len and s[i] in Whitespace: inc i
-      result = VersionInterval(a: VersionReq(r: r, v: parseVer(s, i)))
-      parseSuffix(s, i, result, err)
-    of '>':
-      inc i
-      var r = verGt
-      if i < s.len and s[i] == '=':
-        r = verGe
-        inc i
-      while i < s.len and s[i] in Whitespace: inc i
-      result = VersionInterval(a: VersionReq(r: r, v: parseVer(s, i)))
-      parseSuffix(s, i, result, err)
-    else:
-      err = true
-  else:
-    result = VersionInterval(a: VersionReq(r: verAny, v: Version"#head"))
-
-proc parseTaggedVersions*(outp: string): seq[(string, Version)] =
-  result = @[]
-  for line in splitLines(outp):
-    if not line.endsWith("^{}"):
-      var i = 0
-      while i < line.len and line[i] notin Whitespace: inc i
-      let commitEnd = i
-      while i < line.len and line[i] in Whitespace: inc i
-      while i < line.len and line[i] notin Digits: inc i
-      let v = parseVersion(line, i)
-      if v != Version(""):
-        result.add (line.substr(0, commitEnd-1), v)
-  result.sort proc (a, b: (string, Version)): int =
-    (if a[1] < b[1]: 1
-    elif a[1] == b[1]: 0
-    else: -1)
-
-proc matches(pattern: VersionReq; v: Version): bool =
-  case pattern.r
-  of verGe:
-    result = pattern.v < v or pattern.v == v
-  of verGt:
-    result = pattern.v < v
-  of verLe:
-    result = v < pattern.v or pattern.v == v
-  of verLt:
-    result = v < pattern.v
-  of verEq, verSpecial:
-    result = pattern.v == v
-  of verAny:
-    result = true
-
-proc matches*(pattern: VersionInterval; v: Version): bool =
-  if pattern.isInterval:
-    result = matches(pattern.a, v) and matches(pattern.b, v)
-  else:
-    result = matches(pattern.a, v)
-
-proc selectBestCommitMinVer*(data: openArray[(string, Version)]; elem: VersionInterval): string =
-  for i in countdown(data.len-1, 0):
-    if elem.matches(data[i][1]):
-      return data[i][0]
-  return ""
-
-proc selectBestCommitMaxVer*(data: openArray[(string, Version)]; elem: VersionInterval): string =
-  for i in countup(0, data.len-1):
-    if elem.matches(data[i][1]): return data[i][0]
-  return ""
-
-proc toSemVer*(i: VersionInterval): VersionInterval =
-  result = i
-  if not result.isInterval and result.a.r in {verGe, verGt}:
-    var major = 0
-    let l1 = parseSaturatedNatural(result.a.v.string, major, 0)
-    if l1 > 0:
-      result.isInterval = true
-      result.b = VersionReq(r: verLt, v: Version($(major+1)))
-
-proc selectBestCommitSemVer*(data: openArray[(string, Version)]; elem: VersionInterval): string =
-  result = selectBestCommitMaxVer(data, elem.toSemVer)
-
-when isMainModule:
-  template v(x): untyped = Version(x)
-
-  assert v"1.0" < v"1.0.1"
-  assert v"1.0" < v"1.1"
-  assert v"1.2.3" < v"1.2.4"
-  assert v"2.0.0" < v"2.0.0.1"
-  assert v"2.0.0" < v"20.0"
-  assert not (v"1.10.0" < v"1.2.0")
-  assert v"1.0" < v"#head"
-  assert v"#branch" < v"#head"
-  assert v"#branch" < v"1.0"
-  assert not (v"#head" < v"#head")
-  assert not (v"#head" < v"10.0")
-
-  const lines = """
-24870f48c40da2146ce12ff1e675e6e7b9748355 1.6.12
-b54236aaee2fc90200cb3a4e7070820ced9ce605 1.6.10
-f06dc8ee3baf8f64cce67a28a6e6e8a8cd9bf04b 1.6.8
-2f85924354af35278a801079b7ff3f8805ff1f5a 1.6.6
-007bf1cb52eac412bc88b3ca2283127ad578ec04 1.6.4
-ee18eda86eef2db0a49788bf0fc8e35996ba7f0d 1.6.2
-1a2a82e94269741b0d8ba012994dd85a53f36f2d 1.6.0
-074f7159752b0da5306bdedb3a4e0470af1f85c0 1.4.8
-4eb05ebab2b4d8b0cd00b19a72af35a2d767819a 1.4.6
-944c8e6d04a044611ed723391272f3c86781eadd 1.4.4
-cd090a6151b452b99d65c5173400d4685916f970 1.4.2
-01dd8c7a959adac4aa4d73abdf62cbc53ffed11b 1.4.0
-1420d508dc4a3e51137647926d4db2f3fa62f43c 1.2.18
-726e3bb1ffc6bacfaab0a0abf0209640acbac807 1.2.16
-80d2206e68cd74020f61e23065c7a22376af8de5 1.2.14
-ddfe3905964fe3db33d7798c6c6c4a493cbda6a3 1.2.12
-6d914b7e6edc29c3b8ab8c0e255bd3622bc58bba 1.2.10
-0d1a9f5933eab686ab3b527b36d0cebd3949a503 1.2.8
-a5a0a9e3cb14e79d572ba377b6116053fc621f6d 1.2.6
-f9829089b36806ac0129c421bf601cbb30c2842c 1.2.4
-8b03d39fd387f6a59c97c2acdec2518f0b18a230 1.2.2
-a8a4725850c443158f9cab38eae3e54a78a523fb 1.2.0
-8b5888e0545ee3d931b7dd45d15a1d8f3d6426ef 1.0.10
-7282e53cad6664d09e8c9efd0d7f263521eda238 1.0.8
-283a4137b6868f1c5bbf0dd9c36d850d086fa007 1.0.6
-e826ff9b48af376fdc65ba22f7aa1c56dc169b94 1.0.4
-4c33037ef9d01905130b22a37ddb13748e27bb7c 1.0.2
-0b6866c0dc48b5ba06a4ce57758932fbc71fe4c2 1.0.0
-a202715d182ce6c47e19b3202e0c4011bece65d8 0.20.2
-8ea451196bd8d77b3592b8b34e7a2c49eed784c9 0.20.0
-1b512cc259b262d06143c4b34d20ebe220d7fb5c 0.19.6
-be22a1f4e04b0fec14f7a668cbaf4e6d0be313cb 0.19.4
-5cbc7f6322de8460cc4d395ed0df6486ae68004e 0.19.2
-79934561e8dde609332239fbc8b410633e490c61 0.19.0
-9c53787087e36b1c38ffd670a077903640d988a8 0.18.0
-a713ffd346c376cc30f8cc13efaee7be1b8dfab9 0.17.2
-2084650f7bf6f0db6003920f085e1a86f1ea2d11 0.17.0
-f7f68de78e9f286b704304836ed8f20d65acc906 0.16.0
-48bd4d72c45f0f0202a0ab5ad9d851b05d363988 0.15.2
-dbee7d55bc107b2624ecb6acde7cabe4cb3f5de4 0.15.0
-0a4854a0b7bcef184f060632f756f83454e9f9de 0.14.2
-5333f2e4cb073f9102f30aacc7b894c279393318 0.14.0
-7e50c5b56d5b5b7b96e56b6c7ab5e104124ae81b 0.13.0
-49bce0ebe941aafe19314438fb724c081ae891aa 0.12.0
-70789ef9c8c4a0541ba24927f2d99e106a6fe6cc 0.11.2
-79cc0cc6e501c8984aeb5b217a274877ec5726bc 0.11.0
-46d829f65086b487c08d522b8d0d3ad36f9a2197 0.10.2
-9354d3de2e1ecc1747d6c42fbfa209fb824837c0 0.9.6
-6bf5b3d78c97ce4212e2dd4cf827d40800650c48 0.9.4
-220d35d9e19b0eae9e7cd1f1cac6e77e798dbc72 0.9.2
-7a70058005c6c76c92addd5fc21b9706717c75e3 0.9.0
-32b4192b3f0771af11e9d850046e5f3dd42a9a5f 0.8.14
-"""
-
-  proc p(s: string): VersionInterval =
-    var err = false
-    result = parseVersionInterval(s, 0, err)
-    assert not err
-
-  let tags = parseTaggedVersions(lines)
-  let query = p">= 1.2 & < 1.4"
-  assert selectBestCommitMinVer(tags, query) == "a8a4725850c443158f9cab38eae3e54a78a523fb"
-
-  let query2 = p">= 1.2 & < 1.4"
-  assert selectBestCommitMaxVer(tags, query2) == "1420d508dc4a3e51137647926d4db2f3fa62f43c"
-
-  let query3 = p">= 0.20.0"
-  assert selectBestCommitSemVer(tags, query3) == "a202715d182ce6c47e19b3202e0c4011bece65d8"
-
-  let query4 = p"#head"
-  assert selectBestCommitSemVer(tags, query4) == "24870f48c40da2146ce12ff1e675e6e7b9748355"
diff --git a/compiler/installer.ini b/compiler/installer.ini
index 5f7747fc5..49ee59b0d 100644
--- a/compiler/installer.ini
+++ b/compiler/installer.ini
@@ -66,7 +66,6 @@ Files: "doc"
 Files: "doc/html"
 Files: "tools"
 Files: "tools/debug/nim-gdb.py"
-Files: "atlas"
 Files: "nimpretty"
 Files: "testament"
 Files: "nimsuggest"
diff --git a/doc/atlas.md b/doc/atlas.md
deleted file mode 100644
index 9438546ba..000000000
--- a/doc/atlas.md
+++ /dev/null
@@ -1,188 +0,0 @@
-# Atlas Package Cloner
-
-Atlas is a simple package cloner tool. It manages an isolated workspace that
-contains projects and dependencies.
-
-Atlas is compatible with Nimble in the sense that it supports the Nimble
-file format.
-
-
-## Concepts
-
-Atlas uses three concepts:
-
-1. Workspaces
-2. Projects
-3. Dependencies
-
-### Workspaces
-
-Every workspace is isolated, nothing is shared between workspaces.
-A workspace is a directory that has a file `atlas.workspace` inside it. Use `atlas init`
-to create a workspace out of the current working directory.
-
-Projects plus their dependencies are stored in a workspace:
-
-```
-  $workspace / main project
-  $workspace / other project
-  $workspace / _deps / dependency A
-  $workspace / _deps / dependency B
-```
-
-The deps directory can be set via `--deps:DIR` during `atlas init`.
-
-
-### Projects
-
-A workspace contains one or multiple "projects". These projects can use each other and it
-is easy to develop multiple projects at the same time.
-
-### Dependencies
-
-Inside a workspace there can be a `_deps` directory where your dependencies are kept. It is
-easy to move a dependency one level up and out the `_deps` directory, turning it into a project.
-Likewise, you can move a project to the `_deps` directory, turning it into a dependency.
-
-The only distinction between a project and a dependency is its location. For dependency resolution
-a project always has a higher priority than a dependency.
-
-
-## No magic
-
-Atlas works by managing two files for you, the `project.nimble` file and the `nim.cfg` file. You can
-edit these manually too, Atlas doesn't touch what should be left untouched.
-
-
-## How it works
-
-Atlas uses git commits internally; version requirements are translated
-to git commits via `git show-ref --tags`.
-
-Atlas uses URLs internally; Nimble package names are translated to URLs
-via Nimble's  `packages.json` file.
-
-Atlas does not call the Nim compiler for a build, instead it creates/patches
-a `nim.cfg` file for the compiler. For example:
-
-```
-############# begin Atlas config section ##########
---noNimblePath
---path:"../nimx"
---path:"../sdl2/src"
---path:"../opengl/src"
-############# end Atlas config section   ##########
-```
-
-The version selection is deterministic, it picks up the *minimum* required
-version. Thanks to this design, lock files are much less important.
-
-
-
-## Commands
-
-Atlas supports the following commands:
-
-
-## Use <url> / <package name>
-
-Clone the package behind `url` or `package name` and its dependencies into
-the `_deps` directory and make it available for your current project which
-should be in the current working directory. Atlas will create or patch
-the files `$project.nimble` and `nim.cfg` for you so that you can simply
-import the required modules.
-
-For example:
-
-```
-  mkdir newproject
-  cd newproject
-  git init
-  atlas use lexim
-  # add `import lexim` to your example.nim file
-  nim c example.nim
-
-```
-
-
-### Clone/Update <url>/<package name>
-
-Clones a URL and all of its dependencies (recursively) into the workspace.
-Creates or patches a `nim.cfg` file with the required `--path` entries.
-
-**Note**: Due to the used algorithms an `update` is the same as a `clone`.
-
-
-If a `<package name>` is given instead the name is first translated into an URL
-via `packages.json` or via a github search.
-
-
-### Search <term term2 term3 ...>
-
-Search the package index `packages.json` for a package that the given terms
-in its description (or name or list of tags).
-
-
-### Install <proj.nimble>
-
-Use the .nimble file to setup the project's dependencies.
-
-### UpdateProjects / updateDeps [filter]
-
-Update every project / dependency in the workspace that has a remote URL that
-matches `filter` if a filter is given. The project / dependency is only updated
-if there are no uncommitted changes.
-
-### Others
-
-Run `atlas --help` for more features.
-
-
-### Overrides
-
-You can override how Atlas resolves a package name or a URL. The overrides use
-a simple pattern matching language and are flexible enough to integrate private
-gitlab repositories.
-
-To setup an override file, edit the `$workspace/atlas.workspace` file to contain
-a line like `overrides="urls.rules"`. Then create a file `urls.rules` that can
-contain lines like:
-
-```
-customProject -> https://gitlab.company.com/customProject
-https://github.com/araq/ormin -> https://github.com/useMyForkInstead/ormin
-```
-
-The `$` has a special meaning in a pattern:
-
-=================   ========================================================
-``$$``              Matches a single dollar sign.
-``$*``              Matches until the token following the ``$*`` was found.
-                    The match is allowed to be of 0 length.
-``$+``              Matches until the token following the ``$+`` was found.
-                    The match must consist of at least one char.
-``$s``              Skips optional whitespace.
-=================   ========================================================
-
-For example, here is how to override any github link:
-
-```
-https://github.com/$+ -> https://utopia.forall/$#
-```
-
-You can use `$1` or `$#` to refer to captures.
-
-
-### Virtual Nim environments
-
-Atlas supports setting up a virtual Nim environment via the `env` command. You can
-even install multiple different Nim versions into the same workspace.
-
-For example:
-
-```
-atlas env 1.6.12
-atlas env devel
-```
-
-When completed, run `source nim-1.6.12/activate.sh` on UNIX and `nim-1.6.12/activate.bat` on Windows.
diff --git a/koch.nim b/koch.nim
index e15215df0..b42987cad 100644
--- a/koch.nim
+++ b/koch.nim
@@ -11,6 +11,7 @@
 
 const
   NimbleStableCommit = "168416290e49023894fc26106799d6f1fc964a2d" # master
+  AtlasStableCommit = "#head"
   # examples of possible values: #head, #ea82b54, 1.2.3
   FusionStableHash = "#372ee4313827ef9f2ea388840f7d6b46c2b1b014"
   ChecksumsStableCommit = "b4c73320253f78e3a265aec6d9e8feb83f97c77b"
@@ -72,6 +73,7 @@ Possible Commands:
                            e.g. nimble)
                            doesn't require network connectivity
   nimble                   builds the Nimble tool
+  atlas                    builds the Atlas tool
   fusion                   installs fusion via Nimble
 
 Boot options:
@@ -156,6 +158,14 @@ proc bundleNimbleExe(latest: bool, args: string) =
   nimCompile("dist/nimble/src/nimble.nim",
              options = "-d:release --mm:refc --noNimblePath " & args)
 
+proc bundleAtlasExe(latest: bool, args: string) =
+  let commit = if latest: "HEAD" else: AtlasStableCommit
+  cloneDependency(distDir, "https://github.com/nim-lang/atlas.git",
+                  commit = commit, allowBundled = true)
+  # installer.ini expects it under $nim/bin
+  nimCompile("dist/atlas/src/atlas.nim",
+             options = "-d:release --noNimblePath " & args)
+
 proc bundleNimsuggest(args: string) =
   nimCompileFold("Compile nimsuggest", "nimsuggest/nimsuggest.nim",
                  options = "-d:danger " & args)
@@ -179,7 +189,6 @@ proc bundleWinTools(args: string) =
   buildVccTool(args)
   nimCompile("tools/nimgrab.nim", options = "-d:ssl " & args)
   nimCompile("tools/nimgrep.nim", options = args)
-  nimCompile("atlas/atlas.nim", options = args)
   nimCompile("testament/testament.nim", options = args)
   when false:
     # not yet a tool worth including
@@ -193,6 +202,7 @@ proc bundleChecksums(latest: bool) =
 proc zip(latest: bool; args: string) =
   bundleChecksums(latest)
   bundleNimbleExe(latest, args)
+  bundleAtlasExe(latest, args)
   bundleNimsuggest(args)
   bundleNimpretty(args)
   bundleWinTools(args)
@@ -236,21 +246,17 @@ proc buildTools(args: string = "") =
       "--opt:speed --stacktrace -d:debug --stacktraceMsgs -d:nimCompilerStacktraceHints " & args,
       outputName = "nim_dbg")
 
-  nimCompileFold("Compile atlas", "atlas/atlas.nim", options = "-d:release " & args,
-      outputName = "atlas")
-
 proc testTools(args: string = "") =
   nimCompileFold("Compile nimgrep", "tools/nimgrep.nim",
                  options = "-d:release " & args)
   when defined(windows): buildVccTool(args)
   bundleNimpretty(args)
   nimCompileFold("Compile testament", "testament/testament.nim", options = "-d:release " & args)
-  nimCompileFold("Compile atlas", "atlas/atlas.nim", options = "-d:release " & args,
-      outputName = "atlas")
 
 proc nsis(latest: bool; args: string) =
   bundleChecksums(latest)
   bundleNimbleExe(latest, args)
+  bundleAtlasExe(latest, args)
   bundleNimsuggest(args)
   bundleWinTools(args)
   # make sure we have generated the niminst executables:
@@ -612,12 +618,6 @@ proc runCI(cmd: string) =
       execFold("build nimsuggest_testing", "nim c -o:bin/nimsuggest_testing -d:release nimsuggest/nimsuggest")
       execFold("Run nimsuggest tests", "nim r nimsuggest/tester")
 
-    execFold("Run atlas tests", "nim c -r -d:atlasTests atlas/atlas.nim clone https://github.com/disruptek/balls")
-    # compile it again to get rid of `-d:atlasTests`:
-    nimCompileFold("Compile atlas", "atlas/atlas.nim", options = "-d:release ",
-        outputName = "atlas")
-    execFold("Run more atlas tests", "nim c -r atlas/tester.nim")
-
     kochExecFold("Testing booting in refc", "boot -d:release --mm:refc -d:nimStrictMode --lib:lib")
 
 
@@ -731,6 +731,7 @@ when isMainModule:
       of "xtemp": xtemp(op.cmdLineRest)
       of "wintools": bundleWinTools(op.cmdLineRest)
       of "nimble": bundleNimbleExe(latest, op.cmdLineRest)
+      of "atlas": bundleAtlasExe(latest, op.cmdLineRest)
       of "nimsuggest": bundleNimsuggest(op.cmdLineRest)
       # toolsNoNimble is kept for backward compatibility with build scripts
       of "toolsnonimble", "toolsnoexternal":
@@ -738,6 +739,7 @@ when isMainModule:
       of "tools":
         buildTools(op.cmdLineRest)
         bundleNimbleExe(latest, op.cmdLineRest)
+        bundleAtlasExe(latest, op.cmdLineRest)
       of "checksums":
         bundleChecksums(latest)
       of "pushcsource":
diff --git a/atlas/parse_requires.nim b/tools/atlas/parse_requires.nim
index 7e26a1656..66879d04f 100644
--- a/atlas/parse_requires.nim
+++ b/tools/atlas/parse_requires.nim
@@ -2,7 +2,7 @@
 ## (c) 2021 Andreas Rumpf
 
 import std / strutils
-import ".." / compiler / [ast, idents, msgs, syntaxes, options, pathutils]
+import ".." / ".." / compiler / [ast, idents, msgs, syntaxes, options, pathutils]
 
 type
   NimbleFileInfo* = object