| author | paulson <lp15@cam.ac.uk> | 
| Tue, 19 Sep 2017 16:37:19 +0100 | |
| changeset 66660 | bc3584f7ac0c | 
| parent 66570 | 9af879e222cc | 
| child 66895 | e378e0468ef2 | 
| permissions | -rw-r--r-- | 
| 63997 
e11ccb5aa82f
more formal Mercurial support (with the potential to upgrade to command server);
 wenzelm parents: diff
changeset | 1 | /* Title: Pure/General/mercurial.scala | 
| 
e11ccb5aa82f
more formal Mercurial support (with the potential to upgrade to command server);
 wenzelm parents: diff
changeset | 2 | Author: Makarius | 
| 
e11ccb5aa82f
more formal Mercurial support (with the potential to upgrade to command server);
 wenzelm parents: diff
changeset | 3 | |
| 64280 | 4 | Support for Mercurial repositories, with local or remote repository clone | 
| 5 | and working directory (via ssh connection). | |
| 63997 
e11ccb5aa82f
more formal Mercurial support (with the potential to upgrade to command server);
 wenzelm parents: diff
changeset | 6 | */ | 
| 
e11ccb5aa82f
more formal Mercurial support (with the potential to upgrade to command server);
 wenzelm parents: diff
changeset | 7 | |
| 
e11ccb5aa82f
more formal Mercurial support (with the potential to upgrade to command server);
 wenzelm parents: diff
changeset | 8 | package isabelle | 
| 
e11ccb5aa82f
more formal Mercurial support (with the potential to upgrade to command server);
 wenzelm parents: diff
changeset | 9 | |
| 
e11ccb5aa82f
more formal Mercurial support (with the potential to upgrade to command server);
 wenzelm parents: diff
changeset | 10 | |
| 
e11ccb5aa82f
more formal Mercurial support (with the potential to upgrade to command server);
 wenzelm parents: diff
changeset | 11 | import java.io.{File => JFile}
 | 
| 
e11ccb5aa82f
more formal Mercurial support (with the potential to upgrade to command server);
 wenzelm parents: diff
changeset | 12 | |
| 65832 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 13 | import scala.annotation.tailrec | 
| 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 14 | import scala.collection.mutable | 
| 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 15 | |
| 63997 
e11ccb5aa82f
more formal Mercurial support (with the potential to upgrade to command server);
 wenzelm parents: diff
changeset | 16 | |
| 
e11ccb5aa82f
more formal Mercurial support (with the potential to upgrade to command server);
 wenzelm parents: diff
changeset | 17 | object Mercurial | 
| 
e11ccb5aa82f
more formal Mercurial support (with the potential to upgrade to command server);
 wenzelm parents: diff
changeset | 18 | {
 | 
| 
e11ccb5aa82f
more formal Mercurial support (with the potential to upgrade to command server);
 wenzelm parents: diff
changeset | 19 | /* command-line syntax */ | 
| 
e11ccb5aa82f
more formal Mercurial support (with the potential to upgrade to command server);
 wenzelm parents: diff
changeset | 20 | |
| 63999 | 21 | def optional(s: String, prefix: String = ""): String = | 
| 64304 | 22 | if (s == "") "" else " " + prefix + " " + Bash.string(s) | 
| 63999 | 23 | |
| 24 | def opt_flag(flag: String, b: Boolean): String = if (b) " " + flag else "" | |
| 25 | def opt_rev(s: String): String = optional(s, "--rev") | |
| 26 | def opt_template(s: String): String = optional(s, "--template") | |
| 63997 
e11ccb5aa82f
more formal Mercurial support (with the potential to upgrade to command server);
 wenzelm parents: diff
changeset | 27 | |
| 
e11ccb5aa82f
more formal Mercurial support (with the potential to upgrade to command server);
 wenzelm parents: diff
changeset | 28 | |
| 
e11ccb5aa82f
more formal Mercurial support (with the potential to upgrade to command server);
 wenzelm parents: diff
changeset | 29 | /* repository access */ | 
| 
e11ccb5aa82f
more formal Mercurial support (with the potential to upgrade to command server);
 wenzelm parents: diff
changeset | 30 | |
| 66570 | 31 | def is_repository(root: Path, ssh: SSH.System = SSH.Local): Boolean = | 
| 32 |     ssh.is_dir(root + Path.explode(".hg")) &&
 | |
| 33 |     new Repository(root, ssh).command("root").ok
 | |
| 64330 | 34 | |
| 66570 | 35 | def repository(root: Path, ssh: SSH.System = SSH.Local): Repository = | 
| 64168 | 36 |   {
 | 
| 37 | val hg = new Repository(root, ssh) | |
| 38 |     hg.command("root").check
 | |
| 39 | hg | |
| 40 | } | |
| 41 | ||
| 66570 | 42 | def find_repository(start: Path, ssh: SSH.System = SSH.Local): Option[Repository] = | 
| 65559 | 43 |   {
 | 
| 44 | def find(root: Path): Option[Repository] = | |
| 45 | if (is_repository(root, ssh)) Some(repository(root, ssh = ssh)) | |
| 46 | else if (root.is_root) None | |
| 47 | else find(root + Path.parent) | |
| 48 | ||
| 66570 | 49 | find(ssh.expand_path(start)) | 
| 65559 | 50 | } | 
| 51 | ||
| 66104 | 52 | def clone_repository(source: String, root: Path, rev: String = "", options: String = "", | 
| 66570 | 53 | ssh: SSH.System = SSH.Local): Repository = | 
| 64168 | 54 |   {
 | 
| 64230 | 55 | val hg = new Repository(root, ssh) | 
| 66570 | 56 | ssh.mkdirs(hg.root.dir) | 
| 66104 | 57 |     hg.command("clone", Bash.string(source) + " " + File.bash_path(hg.root) + opt_rev(rev), options)
 | 
| 58 | .check | |
| 64168 | 59 | hg | 
| 60 | } | |
| 63997 
e11ccb5aa82f
more formal Mercurial support (with the potential to upgrade to command server);
 wenzelm parents: diff
changeset | 61 | |
| 66570 | 62 | def setup_repository(source: String, root: Path, ssh: SSH.System = SSH.Local): Repository = | 
| 64252 
e84cba30d7ff
clarified setup_repository: more uniform pull vs. clone, without update;
 wenzelm parents: 
64233diff
changeset | 63 |   {
 | 
| 66570 | 64 |     if (ssh.is_dir(root)) { val hg = repository(root, ssh = ssh); hg.pull(remote = source); hg }
 | 
| 66105 
8889aad1ff92
reverted 94cad7590015: does not help much on Windows;
 wenzelm parents: 
66104diff
changeset | 65 | else clone_repository(source, root, options = "--noupdate", ssh = ssh) | 
| 64252 
e84cba30d7ff
clarified setup_repository: more uniform pull vs. clone, without update;
 wenzelm parents: 
64233diff
changeset | 66 | } | 
| 64230 | 67 | |
| 66570 | 68 | class Repository private[Mercurial](root_path: Path, ssh: SSH.System = SSH.Local) | 
| 63997 
e11ccb5aa82f
more formal Mercurial support (with the potential to upgrade to command server);
 wenzelm parents: diff
changeset | 69 |   {
 | 
| 64167 
097d122222f6
support remote repositories via ssh command execution;
 wenzelm parents: 
64162diff
changeset | 70 | hg => | 
| 
097d122222f6
support remote repositories via ssh command execution;
 wenzelm parents: 
64162diff
changeset | 71 | |
| 66570 | 72 | val root = ssh.expand_path(root_path) | 
| 73 | def root_url: String = ssh.hg_url + root.implode | |
| 64230 | 74 | |
| 66570 | 75 | override def toString: String = ssh.prefix + root.implode | 
| 63997 
e11ccb5aa82f
more formal Mercurial support (with the potential to upgrade to command server);
 wenzelm parents: diff
changeset | 76 | |
| 64168 | 77 | def command(name: String, args: String = "", options: String = ""): Process_Result = | 
| 64167 
097d122222f6
support remote repositories via ssh command execution;
 wenzelm parents: 
64162diff
changeset | 78 |     {
 | 
| 
097d122222f6
support remote repositories via ssh command execution;
 wenzelm parents: 
64162diff
changeset | 79 | val cmdline = | 
| 65825 
11f87ab51ddb
more robust command invocation, without defaults from hgrc;
 wenzelm parents: 
65822diff
changeset | 80 |         "\"${HG:-hg}\" --config " + Bash.string("defaults." + name + "=") +
 | 
| 64230 | 81 | (if (name == "clone") "" else " --repository " + File.bash_path(root)) + | 
| 64168 | 82 | " --noninteractive " + name + " " + options + " " + args | 
| 66570 | 83 | ssh.execute(cmdline) | 
| 64167 
097d122222f6
support remote repositories via ssh command execution;
 wenzelm parents: 
64162diff
changeset | 84 | } | 
| 63999 | 85 | |
| 64505 | 86 | def archive(target: String, rev: String = "", options: String = ""): Unit = | 
| 87 |       hg.command("archive", opt_rev(rev) + " " + Bash.string(target), options).check
 | |
| 88 | ||
| 63999 | 89 |     def heads(template: String = "{node|short}\n", options: String = ""): List[String] =
 | 
| 64168 | 90 |       hg.command("heads", opt_template(template), options).check.out_lines
 | 
| 63999 | 91 | |
| 64232 
367d83d6030e
clarified hg.id operation, with explicit tip as default;
 wenzelm parents: 
64230diff
changeset | 92 | def identify(rev: String = "tip", options: String = ""): String = | 
| 64168 | 93 |       hg.command("id", opt_rev(rev), options).check.out_lines.headOption getOrElse ""
 | 
| 63998 | 94 | |
| 64232 
367d83d6030e
clarified hg.id operation, with explicit tip as default;
 wenzelm parents: 
64230diff
changeset | 95 | def id(rev: String = "tip"): String = identify(rev, options = "-i") | 
| 
367d83d6030e
clarified hg.id operation, with explicit tip as default;
 wenzelm parents: 
64230diff
changeset | 96 | |
| 63998 | 97 | def manifest(rev: String = "", options: String = ""): List[String] = | 
| 64168 | 98 |       hg.command("manifest", opt_rev(rev), options).check.out_lines
 | 
| 63997 
e11ccb5aa82f
more formal Mercurial support (with the potential to upgrade to command server);
 wenzelm parents: diff
changeset | 99 | |
| 64033 | 100 | def log(rev: String = "", template: String = "", options: String = ""): String = | 
| 64168 | 101 |       hg.command("log", opt_rev(rev) + opt_template(template), options).check.out
 | 
| 64027 | 102 | |
| 64348 
4c253e84ae62
clarified push/pull chain: current ISABELLE_HOME may server as source for changes that are not published on isabelle_repos_source yet (e.g. isabelle-release branch);
 wenzelm parents: 
64347diff
changeset | 103 | def push(remote: String = "", rev: String = "", force: Boolean = false, options: String = "") | 
| 
4c253e84ae62
clarified push/pull chain: current ISABELLE_HOME may server as source for changes that are not published on isabelle_repos_source yet (e.g. isabelle-release branch);
 wenzelm parents: 
64347diff
changeset | 104 |     {
 | 
| 
4c253e84ae62
clarified push/pull chain: current ISABELLE_HOME may server as source for changes that are not published on isabelle_repos_source yet (e.g. isabelle-release branch);
 wenzelm parents: 
64347diff
changeset | 105 |       hg.command("push", opt_rev(rev) + opt_flag("--force", force) + optional(remote), options).
 | 
| 64408 
50bcf976f276
clarified hg push return code: 1 means "nothing to push";
 wenzelm parents: 
64348diff
changeset | 106 | check_rc(rc => rc == 0 | rc == 1) | 
| 64348 
4c253e84ae62
clarified push/pull chain: current ISABELLE_HOME may server as source for changes that are not published on isabelle_repos_source yet (e.g. isabelle-release branch);
 wenzelm parents: 
64347diff
changeset | 107 | } | 
| 
4c253e84ae62
clarified push/pull chain: current ISABELLE_HOME may server as source for changes that are not published on isabelle_repos_source yet (e.g. isabelle-release branch);
 wenzelm parents: 
64347diff
changeset | 108 | |
| 63999 | 109 | def pull(remote: String = "", rev: String = "", options: String = ""): Unit = | 
| 64168 | 110 |       hg.command("pull", opt_rev(rev) + optional(remote), options).check
 | 
| 63999 | 111 | |
| 64168 | 112 | def update( | 
| 113 | rev: String = "", clean: Boolean = false, check: Boolean = false, options: String = "") | |
| 63999 | 114 |     {
 | 
| 64168 | 115 |       hg.command("update",
 | 
| 116 |         opt_rev(rev) + opt_flag("--clean", clean) + opt_flag("--check", check), options).check
 | |
| 63999 | 117 | } | 
| 65819 
ff3dc9325eaa
explore repository structure, with minimal assumptions about "hg log" output;
 wenzelm parents: 
65818diff
changeset | 118 | |
| 65822 
17b8528c2f53
clarified notion of known files (before actual commit);
 wenzelm parents: 
65819diff
changeset | 119 | def known_files(): List[String] = | 
| 65825 
11f87ab51ddb
more robust command invocation, without defaults from hgrc;
 wenzelm parents: 
65822diff
changeset | 120 |       hg.command("status", options = "--modified --added --clean --no-status").check.out_lines
 | 
| 65819 
ff3dc9325eaa
explore repository structure, with minimal assumptions about "hg log" output;
 wenzelm parents: 
65818diff
changeset | 121 | |
| 
ff3dc9325eaa
explore repository structure, with minimal assumptions about "hg log" output;
 wenzelm parents: 
65818diff
changeset | 122 | def graph(): Graph[String, Unit] = | 
| 
ff3dc9325eaa
explore repository structure, with minimal assumptions about "hg log" output;
 wenzelm parents: 
65818diff
changeset | 123 |     {
 | 
| 
ff3dc9325eaa
explore repository structure, with minimal assumptions about "hg log" output;
 wenzelm parents: 
65818diff
changeset | 124 |       val Node = """^node: (\w{12}) (\w{12}) (\w{12})""".r
 | 
| 
ff3dc9325eaa
explore repository structure, with minimal assumptions about "hg log" output;
 wenzelm parents: 
65818diff
changeset | 125 | val log_result = | 
| 65825 
11f87ab51ddb
more robust command invocation, without defaults from hgrc;
 wenzelm parents: 
65822diff
changeset | 126 |         log(template = """node: {node|short} {p1node|short} {p2node|short}\n""")
 | 
| 65819 
ff3dc9325eaa
explore repository structure, with minimal assumptions about "hg log" output;
 wenzelm parents: 
65818diff
changeset | 127 |       (Graph.string[Unit] /: split_lines(log_result)) {
 | 
| 
ff3dc9325eaa
explore repository structure, with minimal assumptions about "hg log" output;
 wenzelm parents: 
65818diff
changeset | 128 | case (graph, Node(x, y, z)) => | 
| 
ff3dc9325eaa
explore repository structure, with minimal assumptions about "hg log" output;
 wenzelm parents: 
65818diff
changeset | 129 | val deps = List(y, z).filterNot(s => s.forall(_ == '0')) | 
| 
ff3dc9325eaa
explore repository structure, with minimal assumptions about "hg log" output;
 wenzelm parents: 
65818diff
changeset | 130 | val graph1 = (graph /: (x :: deps))(_.default_node(_, ())) | 
| 
ff3dc9325eaa
explore repository structure, with minimal assumptions about "hg log" output;
 wenzelm parents: 
65818diff
changeset | 131 |           (graph1 /: deps)({ case (g, dep) => g.add_edge(dep, x) })
 | 
| 
ff3dc9325eaa
explore repository structure, with minimal assumptions about "hg log" output;
 wenzelm parents: 
65818diff
changeset | 132 | case (graph, _) => graph | 
| 
ff3dc9325eaa
explore repository structure, with minimal assumptions about "hg log" output;
 wenzelm parents: 
65818diff
changeset | 133 | } | 
| 
ff3dc9325eaa
explore repository structure, with minimal assumptions about "hg log" output;
 wenzelm parents: 
65818diff
changeset | 134 | } | 
| 63997 
e11ccb5aa82f
more formal Mercurial support (with the potential to upgrade to command server);
 wenzelm parents: diff
changeset | 135 | } | 
| 65832 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 136 | |
| 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 137 | |
| 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 138 | /* unknown files */ | 
| 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 139 | |
| 66570 | 140 | def unknown_files(files: List[Path], ssh: SSH.System = SSH.Local): List[Path] = | 
| 65832 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 141 |   {
 | 
| 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 142 | val unknown = new mutable.ListBuffer[Path] | 
| 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 143 | |
| 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 144 | @tailrec def check(paths: List[Path]) | 
| 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 145 |     {
 | 
| 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 146 |       paths match {
 | 
| 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 147 | case path :: rest => | 
| 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 148 |           find_repository(path, ssh) match {
 | 
| 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 149 | case None => unknown += path; check(rest) | 
| 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 150 | case Some(hg) => | 
| 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 151 | val known = | 
| 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 152 | hg.known_files().iterator.map(name => | 
| 65833 | 153 | (hg.root + Path.explode(name)).canonical_file).toSet | 
| 154 | if (!known(path.canonical_file)) unknown += path | |
| 155 | check(rest.filterNot(p => known(p.canonical_file))) | |
| 65832 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 156 | } | 
| 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 157 | case Nil => | 
| 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 158 | } | 
| 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 159 | } | 
| 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 160 | |
| 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 161 | check(files) | 
| 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 162 | unknown.toList | 
| 
2fb85623c386
implicitly check for unknown files (not part of a Mercurial repository);
 wenzelm parents: 
65825diff
changeset | 163 | } | 
| 63997 
e11ccb5aa82f
more formal Mercurial support (with the potential to upgrade to command server);
 wenzelm parents: diff
changeset | 164 | } |