anchor.diff:      | [] -> None
       | l -> Some (List.max l)
   
  +let shell program cmd =
  +    Printf.kprintf (fun cmd ->
  +        shellExec {
  +            Program = program
  +            WorkingDirectory = "."
  +            CommandLine = cmd
  +            Args = []
  +        }
  +        |> function
  +            | 0 -> ()
  +            | n -> failwithf "%s %s failed with code %i" program cmd n
  +    ) cmd
  +
  +let shellOut program cmd =
  +    Printf.kprintf (fun cmd ->
  +        let psi =
  +            System.Diagnostics.ProcessStartInfo(
  +                FileName = program,
  +                Arguments = cmd,
  +                RedirectStandardOutput = true,
  +                UseShellExecute = false
  +            )
  +        let proc = System.Diagnostics.Process.Start(psi)
  +        let out = proc.StandardOutput.ReadToEnd()
  +        proc.WaitForExit()
  +        match proc.ExitCode with
  +        | 0 -> out
  +        | n -> failwithf "%s %s failed with code %i" program cmd n
  +    ) cmd
  +
  +let git cmd =
  +    Printf.kprintf (fun s ->
  +        logfn "> git %s" s
  +        Git.CommandHelper.directRunGitCommandAndFail "." s
  +    ) cmd
  +
  +let hg cmd = shell "hg" cmd
  +let hg' cmd = shellOut "hg" cmd
  +
  +let private splitLines (s: string) =
  +    s.Split([| "\r\n"; "\n" |], StringSplitOptions.None)
  +
  +module Hg =
  +    let getBookmarks() =
  +        hg' """bookmark -T "{bookmark} {node|short}\n" """
  +        |> splitLines
  +        |> Array.map (fun s -> s.Split(' ').[0])
  +
  +    let getCurrentCommitId() =
  +        (hg' "id -i").Trim()
  +
  +    let commitIdForBookmark (b: string) =
  +        hg' """bookmark -T "{bookmark} {node|short}\n" """
  +        |> splitLines
  +        |> Array.pick (fun s ->
  +            let a = s.Split(' ')
  +            if a.[0] = b then Some a.[1] else None
  +        )
  +
  +    let branchExists (b: string) =
  +        hg' "branches -T {branch}"
  +        |> splitLines
  +        |> Array.contains b
  +
  +    let isAncestorOfCurrent (ref: string) =
  +        let currentRef = getCurrentCommitId()
  +        let o = hg' """log --rev "ancestors(.) and %s" """ ref
  +        not (String.IsNullOrWhiteSpace o)
  +
  +module VC =
  +    let isGit = Directory.Exists ".git"
  +
  +    let getTags() =
  +        if isGit then
  +            let ok, out, err = Git.CommandHelper.runGitCommand "." "tag --merged"
  +            out.ToArray()
  +        else
  +            Hg.getBookmarks()
  +
  +    let getCurrentCommitId() =
  +        if isGit then
  +            Git.Information.getCurrentSHA1 "."
  +        else
  +            Hg.getCurrentCommitId()
  +
  +    let commitIdForTag tag =
  +        if isGit then
  +            Git.Branches.getSHA1 "." ("refs/tags/" + tag)
  +        else Hg.commitIdForBookmark tag
  +
   let ComputeVersion (baseVersion: option<Paket.SemVerInfo>) =
       let lastVersion, tag =
           try
  -            let ok, out, err = Git.CommandHelper.runGitCommand "." "tag --merged"
  +            let tags = VC.getTags()
               let re = Regex("""^(?:[a-zA-Z]+[-.]?)?([0-9]+(?:\.[0-9]+){0,3})(?:-.*)?$""")
  -            match out.ToArray()
  -                |> Array.choose (fun tag ->
  -                    try
  -                        let m = re.Match(tag)
  -                        if m.Success then
  -                            let v = m.Groups.[1].Value |> Paket.SemVer.Parse
  -                            Some (v, Some tag)
  -                        else None
  -                    with _ -> None) with
  +            match tags |> Array.choose (fun tag ->
  +                try
  +                    let m = re.Match(tag)
  +                    if m.Success then
  +                        let v = m.Groups.[1].Value |> Paket.SemVer.Parse
  +                        Some (v, Some tag)
  +                    else None
  +                with _ -> None) with
               | [||] ->
                   traceImportant "Warning: no latest tag found"
                   Paket.SemVer.Zero, None
  
positive.code:         match args.WorkBranch with
          | None -> ()
          | Some branch ->
              try git "checkout -f %s" branch
              with e ->
                  try git "checkout -f -b %s" branch
                  with _ -> raise e
              if args.MergeMaster then
                  git "merge -Xtheirs --no-ff --no-commit %s" args.BaseRef
  
      Target "WS-Commit" <| fun () ->
          let tag = "v" + args.Version.AsString
          git "add ."
          git "commit --allow-empty -m \"[CI] %s\"" tag
          git "tag %s" tag
          git "push %s %s" args.PushRemote (Git.Information.getBranchName ".")
          git "push %s %s" args.PushRemote tag
  
      Target "WS-Publish" <| fun () ->
          match environVarOrNone "NugetPublishUrl", environVarOrNone "NugetApiKey" with
  
negative.code: let dotnetArgs = if verbose then [ "-v"; "n" ] else []
  MSBuildDefaults <- { MSBuildDefaults with Verbosity = Some msbuildVerbosity }
  
  let MakeTargets (args: Args) =
  
      let dirtyDirs =
  