{"action":"created","comment":{"url":"https://api.github.com/repos/gemini-hlsw/ocs/pulls/comments/25687680","id":25687680,"diff_hunk":"@@ -0,0 +1,160 @@\n+package edu.gemini.sp.vcs.diff.osgi\n+\n+import edu.gemini.pot.sp.version._\n+import edu.gemini.pot.spdb.IDBDatabaseService\n+import edu.gemini.sp.vcs.diff.ProgramLocation.{Both, RemoteOnly, LocalOnly, Neither}\n+import edu.gemini.sp.vcs.diff._\n+import edu.gemini.sp.vcs.diff.VcsAction._\n+import edu.gemini.sp.vcs.reg.VcsRegistrar\n+import edu.gemini.spModel.core.{Peer, SPProgramID}\n+import edu.gemini.util.security.auth.keychain.KeyChain\n+\n+import scalaz._\n+import Scalaz._\n+\n+trait Commands {\n+  def vcs2(cmd: String, args: Array[String]): String\n+}\n+\n+object Commands {\n+  def parseId(s: String): String \\/ SPProgramID =\n+    \\/.fromTryCatch {\n+      SPProgramID.toProgramID(s)\n+    }.leftMap(_ => s\"Sorry, '$s' isn't a valid program id\")\n+\n+  def parseLoc(s: String): String \\/ Peer =\n+    Option(Peer.tryParse(s)).toRightDisjunction(s\"Sorry, expecting host:port(:site) not '$s'\")\n+\n+  def usage(cmd: String): String =\n+    s\"Usage: vcs2 $cmd [[host:port]] program_id]\"\n+\n+  case class CmdArgs(loc: Peer, id: SPProgramID)\n+\n+  def apply(odb: IDBDatabaseService, auth: KeyChain, vcs: Vcs, reg: VcsRegistrar): Commands = new Commands {\n+    var defaultLocation = new Peer(\"localhost\", 8443, null)\n+\n+    var lastProgId: Option[SPProgramID] = None\n+\n+    def host(): String =\n+      s\"default vcs host set to ${defaultLocation.host} ${defaultLocation.port}\"\n+\n+    def host(loc: Peer): String = {\n+      defaultLocation = loc\n+      host()\n+    }\n+\n+    def locationFor(id: SPProgramID): Peer =\n+      reg.registration(id).getOrElse(defaultLocation)\n+\n+    def parseArgs(cmd: String, args: Array[String]): String \\/ CmdArgs =\n+      args.length match {\n+        case 2 =>\n+          for {\n+            loc <- parseLoc(args(0))\n+            id  <- parseId(args(1))\n+          } yield CmdArgs(loc, id)\n+\n+        case 1 => parseId(args(0)).map { id =>\n+          CmdArgs(locationFor(id), id)\n+        }\n+        case 0 => lastProgId.toRightDisjunction(usage(cmd)).map { id =>\n+          CmdArgs(locationFor(id), id)\n+        }\n+        case _ => usage(cmd).left\n+      }\n+\n+    def remember(args: CmdArgs) {\n+      lastProgId = Some(args.id)\n+      reg.register(args.id, args.loc)\n+    }\n+\n+    type VcsOp = (SPProgramID, Peer) => TryVcs[String]\n+\n+    def doVcsOp(cmd: String, args: Array[String])(op: VcsOp): String =\n+      (for {\n+        ca  <- parseArgs(cmd, args)\n+        res <- op(ca.id, ca.loc).leftMap { f =>\n+                 VcsFailure.explain(f, ca.id, cmd, Some(ca.loc))\n+               }\n+      } yield {\n+        remember(ca)\n+        res\n+      }).toEither.merge\n+\n+    def timed[A](body: => A): (A, Long) = {\n+      val start = System.currentTimeMillis()\n+      (body, System.currentTimeMillis() - start)\n+    }\n+\n+    def runAndFormat[A](id: SPProgramID, peer: Peer, f: (SPProgramID, Peer) => VcsAction[A])(showA: A => String): TryVcs[String] = {\n+      val (out, time) = timed { f(id, peer).unsafeRun }\n+      out.map { a => s\"${showA(a)}\\n$time ms\"}\n+    }\n+\n+    val add: VcsOp = (id, peer) =>\n+      runAndFormat(id, peer, vcs.add) { _ => s\"Added $id to $peer\" }\n+\n+    val checkout: VcsOp = (id, peer) =>\n+      runAndFormat(id, peer, vcs.checkout) { _ => s\"Checked out $id from $peer\" }\n+\n+    val pull: VcsOp = (id, peer) =>\n+      runAndFormat(id, peer, vcs.pull) { updated =>\n+        if (updated) \"Updated local program.\" else \"Already up to date.\"\n+      }\n+\n+    val push: VcsOp = (id, peer) =>\n+      runAndFormat(id, peer, vcs.push) { updated =>\n+        if (updated) \"Updated remote program.\" else \"Already up to date.\"\n+      }\n+\n+    val sync: VcsOp = (id, peer) =>\n+      runAndFormat(id, peer, vcs.sync) {\n+        case Neither    => \"Already up to date.\"\n+        case LocalOnly  => \"Updated local program.\"\n+        case RemoteOnly => \"Updated remote program.\"\n+        case Both       => \"Synchronized.\"\n+      }\n+\n+    val vcsOps: Map[String, VcsOp] = Map(\n+      \"add\"      -> add,\n+      \"checkout\" -> checkout,\n+      \"co\"       -> checkout,\n+      \"pull\"     -> pull,\n+      \"push\"     -> push,\n+      \"sync\"     -> sync\n+    )\n+\n+    type VcsCmdHandler = (String, Array[String]) => Option[String]\n+\n+    val vcsCmdHandler: VcsCmdHandler = (cmd, args) =>\n+      vcsOps.get(cmd).map { op =>\n+        doVcsOp(cmd, args)(op)\n+      }\n+\n+    val hostHandler: VcsCmdHandler = (cmd, args) =>\n+      if (\"host\".equals(cmd))\n+        args.length match {\n+          case 0 => Some(host())\n+          case 1 => Some(parseLoc(args(0)).map(host).toEither.merge)\n+          case _ => None\n+        }\n+      else None\n+\n+    val handlers = List(vcsCmdHandler, hostHandler)\n+\n+    def vcs2(cmd: String, args: Array[String]): String =\n+      try {\n+        exec(cmd, args, handlers) | \"Usage: vcs2 host|sync\"\n+      } catch {\n+        case e: Exception =>\n+          e.printStackTrace()\n+          \"???\"","path":"bundle/edu.gemini.sp.vcs/src/main/scala/edu/gemini/sp/vcs/diff/osgi/Commands.scala","position":151,"original_position":151,"commit_id":"85c05ef470be7ff1cefde49c25095f2585b4a2a7","original_commit_id":"85c05ef470be7ff1cefde49c25095f2585b4a2a7","user":{"login":"swalker2m","id":4906023,"avatar_url":"https://avatars.githubusercontent.com/u/4906023?v=3","gravatar_id":"","url":"https://api.github.com/users/swalker2m","html_url":"https://github.com/swalker2m","followers_url":"https://api.github.com/users/swalker2m/followers","following_url":"https://api.github.com/users/swalker2m/following{/other_user}","gists_url":"https://api.github.com/users/swalker2m/gists{/gist_id}","starred_url":"https://api.github.com/users/swalker2m/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/swalker2m/subscriptions","organizations_url":"https://api.github.com/users/swalker2m/orgs","repos_url":"https://api.github.com/users/swalker2m/repos","events_url":"https://api.github.com/users/swalker2m/events{/privacy}","received_events_url":"https://api.github.com/users/swalker2m/received_events","type":"User","site_admin":false},"body":"Thanks, I need to clean that up a bit and add the other commands to the usage line.  Also, the \"vcs2\" command will be renamed to \"vcs\" once I get rid of the old version.","created_at":"2015-03-03T13:45:49Z","updated_at":"2015-03-03T13:45:49Z","html_url":"https://github.com/gemini-hlsw/ocs/pull/277#discussion_r25687680","pull_request_url":"https://api.github.com/repos/gemini-hlsw/ocs/pulls/277","_links":{"self":{"href":"https://api.github.com/repos/gemini-hlsw/ocs/pulls/comments/25687680"},"html":{"href":"https://github.com/gemini-hlsw/ocs/pull/277#discussion_r25687680"},"pull_request":{"href":"https://api.github.com/repos/gemini-hlsw/ocs/pulls/277"}}},"pull_request":{"url":"https://api.github.com/repos/gemini-hlsw/ocs/pulls/277","id":30295537,"html_url":"https://github.com/gemini-hlsw/ocs/pull/277","diff_url":"https://github.com/gemini-hlsw/ocs/pull/277.diff","patch_url":"https://github.com/gemini-hlsw/ocs/pull/277.patch","issue_url":"https://api.github.com/repos/gemini-hlsw/ocs/issues/277","number":277,"state":"open","locked":false,"title":"OCSADV-200: client/server + shell commands","user":{"login":"swalker2m","id":4906023,"avatar_url":"https://avatars.githubusercontent.com/u/4906023?v=3","gravatar_id":"","url":"https://api.github.com/users/swalker2m","html_url":"https://github.com/swalker2m","followers_url":"https://api.github.com/users/swalker2m/followers","following_url":"https://api.github.com/users/swalker2m/following{/other_user}","gists_url":"https://api.github.com/users/swalker2m/gists{/gist_id}","starred_url":"https://api.github.com/users/swalker2m/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/swalker2m/subscriptions","organizations_url":"https://api.github.com/users/swalker2m/orgs","repos_url":"https://api.github.com/users/swalker2m/repos","events_url":"https://api.github.com/users/swalker2m/events{/privacy}","received_events_url":"https://api.github.com/users/swalker2m/received_events","type":"User","site_admin":false},"body":"This is a preliminary PR (so, :-1: for now).  It contains an initial implementation for VCS client/server interactions and shell commands.  So far, push, pull, and sync commands are implemented but there are still some missing features that need be added in subsequent commits.  In particular:\r\n\r\n* checkout a remote program\r\n* add a program to a remote database\r\n* fetch/store log updates\r\n\r\nI have updated the [documentation](https://github.com/gemini-hlsw/ocs/wiki/Science-Program-Synchronization-Notes#4-application-clientserver) to describe the main type aliases and classes and how they relate.  This is the key picture:\r\n\r\n![](https://github.com/gemini-hlsw/ocs/wiki/sync/ClientServer.png)\r\n\r\nYou'll notice there are no test cases.  Since this code is about implementing client/server interactions and requires keys, etc. I'm not sure how to go about that.\r\n\r\n__Note:__ as this reimplementation grows, the amount of overlap with the old implementation increases.  When this is all over there will be a mega cleanup step to remove the existing contents of `edu.gemini.sp.vcs`, the entire `edu.gemini.sp.vcs.tui` bundle.","created_at":"2015-03-02T16:09:33Z","updated_at":"2015-03-03T13:45:49Z","closed_at":null,"merged_at":null,"merge_commit_sha":"28256ef1a7109c3585aada8a169d1174730ac76a","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/gemini-hlsw/ocs/pulls/277/commits","review_comments_url":"https://api.github.com/repos/gemini-hlsw/ocs/pulls/277/comments","review_comment_url":"https://api.github.com/repos/gemini-hlsw/ocs/pulls/comments{/number}","comments_url":"https://api.github.com/repos/gemini-hlsw/ocs/issues/277/comments","statuses_url":"https://api.github.com/repos/gemini-hlsw/ocs/statuses/85c05ef470be7ff1cefde49c25095f2585b4a2a7","head":{"label":"swalker2m:feature/OCSADV-200-G","ref":"feature/OCSADV-200-G","sha":"85c05ef470be7ff1cefde49c25095f2585b4a2a7","user":{"login":"swalker2m","id":4906023,"avatar_url":"https://avatars.githubusercontent.com/u/4906023?v=3","gravatar_id":"","url":"https://api.github.com/users/swalker2m","html_url":"https://github.com/swalker2m","followers_url":"https://api.github.com/users/swalker2m/followers","following_url":"https://api.github.com/users/swalker2m/following{/other_user}","gists_url":"https://api.github.com/users/swalker2m/gists{/gist_id}","starred_url":"https://api.github.com/users/swalker2m/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/swalker2m/subscriptions","organizations_url":"https://api.github.com/users/swalker2m/orgs","repos_url":"https://api.github.com/users/swalker2m/repos","events_url":"https://api.github.com/users/swalker2m/events{/privacy}","received_events_url":"https://api.github.com/users/swalker2m/received_events","type":"User","site_admin":false},"repo":{"id":23966397,"name":"ocs","full_name":"swalker2m/ocs","owner":{"login":"swalker2m","id":4906023,"avatar_url":"https://avatars.githubusercontent.com/u/4906023?v=3","gravatar_id":"","url":"https://api.github.com/users/swalker2m","html_url":"https://github.com/swalker2m","followers_url":"https://api.github.com/users/swalker2m/followers","following_url":"https://api.github.com/users/swalker2m/following{/other_user}","gists_url":"https://api.github.com/users/swalker2m/gists{/gist_id}","starred_url":"https://api.github.com/users/swalker2m/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/swalker2m/subscriptions","organizations_url":"https://api.github.com/users/swalker2m/orgs","repos_url":"https://api.github.com/users/swalker2m/repos","events_url":"https://api.github.com/users/swalker2m/events{/privacy}","received_events_url":"https://api.github.com/users/swalker2m/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/swalker2m/ocs","description":"","fork":true,"url":"https://api.github.com/repos/swalker2m/ocs","forks_url":"https://api.github.com/repos/swalker2m/ocs/forks","keys_url":"https://api.github.com/repos/swalker2m/ocs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/swalker2m/ocs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/swalker2m/ocs/teams","hooks_url":"https://api.github.com/repos/swalker2m/ocs/hooks","issue_events_url":"https://api.github.com/repos/swalker2m/ocs/issues/events{/number}","events_url":"https://api.github.com/repos/swalker2m/ocs/events","assignees_url":"https://api.github.com/repos/swalker2m/ocs/assignees{/user}","branches_url":"https://api.github.com/repos/swalker2m/ocs/branches{/branch}","tags_url":"https://api.github.com/repos/swalker2m/ocs/tags","blobs_url":"https://api.github.com/repos/swalker2m/ocs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/swalker2m/ocs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/swalker2m/ocs/git/refs{/sha}","trees_url":"https://api.github.com/repos/swalker2m/ocs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/swalker2m/ocs/statuses/{sha}","languages_url":"https://api.github.com/repos/swalker2m/ocs/languages","stargazers_url":"https://api.github.com/repos/swalker2m/ocs/stargazers","contributors_url":"https://api.github.com/repos/swalker2m/ocs/contributors","subscribers_url":"https://api.github.com/repos/swalker2m/ocs/subscribers","subscription_url":"https://api.github.com/repos/swalker2m/ocs/subscription","commits_url":"https://api.github.com/repos/swalker2m/ocs/commits{/sha}","git_commits_url":"https://api.github.com/repos/swalker2m/ocs/git/commits{/sha}","comments_url":"https://api.github.com/repos/swalker2m/ocs/comments{/number}","issue_comment_url":"https://api.github.com/repos/swalker2m/ocs/issues/comments{/number}","contents_url":"https://api.github.com/repos/swalker2m/ocs/contents/{+path}","compare_url":"https://api.github.com/repos/swalker2m/ocs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/swalker2m/ocs/merges","archive_url":"https://api.github.com/repos/swalker2m/ocs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/swalker2m/ocs/downloads","issues_url":"https://api.github.com/repos/swalker2m/ocs/issues{/number}","pulls_url":"https://api.github.com/repos/swalker2m/ocs/pulls{/number}","milestones_url":"https://api.github.com/repos/swalker2m/ocs/milestones{/number}","notifications_url":"https://api.github.com/repos/swalker2m/ocs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/swalker2m/ocs/labels{/name}","releases_url":"https://api.github.com/repos/swalker2m/ocs/releases{/id}","created_at":"2014-09-12T15:41:27Z","updated_at":"2015-02-25T15:56:14Z","pushed_at":"2015-03-02T20:51:39Z","git_url":"git://github.com/swalker2m/ocs.git","ssh_url":"git@github.com:swalker2m/ocs.git","clone_url":"https://github.com/swalker2m/ocs.git","svn_url":"https://github.com/swalker2m/ocs","homepage":null,"size":257695,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"develop"}},"base":{"label":"gemini-hlsw:develop","ref":"develop","sha":"809f0bcac8e18118673e3763f43a16bcea332a9d","user":{"login":"gemini-hlsw","id":8593735,"avatar_url":"https://avatars.githubusercontent.com/u/8593735?v=3","gravatar_id":"","url":"https://api.github.com/users/gemini-hlsw","html_url":"https://github.com/gemini-hlsw","followers_url":"https://api.github.com/users/gemini-hlsw/followers","following_url":"https://api.github.com/users/gemini-hlsw/following{/other_user}","gists_url":"https://api.github.com/users/gemini-hlsw/gists{/gist_id}","starred_url":"https://api.github.com/users/gemini-hlsw/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gemini-hlsw/subscriptions","organizations_url":"https://api.github.com/users/gemini-hlsw/orgs","repos_url":"https://api.github.com/users/gemini-hlsw/repos","events_url":"https://api.github.com/users/gemini-hlsw/events{/privacy}","received_events_url":"https://api.github.com/users/gemini-hlsw/received_events","type":"Organization","site_admin":false},"repo":{"id":23963494,"name":"ocs","full_name":"gemini-hlsw/ocs","owner":{"login":"gemini-hlsw","id":8593735,"avatar_url":"https://avatars.githubusercontent.com/u/8593735?v=3","gravatar_id":"","url":"https://api.github.com/users/gemini-hlsw","html_url":"https://github.com/gemini-hlsw","followers_url":"https://api.github.com/users/gemini-hlsw/followers","following_url":"https://api.github.com/users/gemini-hlsw/following{/other_user}","gists_url":"https://api.github.com/users/gemini-hlsw/gists{/gist_id}","starred_url":"https://api.github.com/users/gemini-hlsw/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gemini-hlsw/subscriptions","organizations_url":"https://api.github.com/users/gemini-hlsw/orgs","repos_url":"https://api.github.com/users/gemini-hlsw/repos","events_url":"https://api.github.com/users/gemini-hlsw/events{/privacy}","received_events_url":"https://api.github.com/users/gemini-hlsw/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/gemini-hlsw/ocs","description":"Observatory Control Software","fork":false,"url":"https://api.github.com/repos/gemini-hlsw/ocs","forks_url":"https://api.github.com/repos/gemini-hlsw/ocs/forks","keys_url":"https://api.github.com/repos/gemini-hlsw/ocs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/gemini-hlsw/ocs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/gemini-hlsw/ocs/teams","hooks_url":"https://api.github.com/repos/gemini-hlsw/ocs/hooks","issue_events_url":"https://api.github.com/repos/gemini-hlsw/ocs/issues/events{/number}","events_url":"https://api.github.com/repos/gemini-hlsw/ocs/events","assignees_url":"https://api.github.com/repos/gemini-hlsw/ocs/assignees{/user}","branches_url":"https://api.github.com/repos/gemini-hlsw/ocs/branches{/branch}","tags_url":"https://api.github.com/repos/gemini-hlsw/ocs/tags","blobs_url":"https://api.github.com/repos/gemini-hlsw/ocs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/gemini-hlsw/ocs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/gemini-hlsw/ocs/git/refs{/sha}","trees_url":"https://api.github.com/repos/gemini-hlsw/ocs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/gemini-hlsw/ocs/statuses/{sha}","languages_url":"https://api.github.com/repos/gemini-hlsw/ocs/languages","stargazers_url":"https://api.github.com/repos/gemini-hlsw/ocs/stargazers","contributors_url":"https://api.github.com/repos/gemini-hlsw/ocs/contributors","subscribers_url":"https://api.github.com/repos/gemini-hlsw/ocs/subscribers","subscription_url":"https://api.github.com/repos/gemini-hlsw/ocs/subscription","commits_url":"https://api.github.com/repos/gemini-hlsw/ocs/commits{/sha}","git_commits_url":"https://api.github.com/repos/gemini-hlsw/ocs/git/commits{/sha}","comments_url":"https://api.github.com/repos/gemini-hlsw/ocs/comments{/number}","issue_comment_url":"https://api.github.com/repos/gemini-hlsw/ocs/issues/comments{/number}","contents_url":"https://api.github.com/repos/gemini-hlsw/ocs/contents/{+path}","compare_url":"https://api.github.com/repos/gemini-hlsw/ocs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/gemini-hlsw/ocs/merges","archive_url":"https://api.github.com/repos/gemini-hlsw/ocs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/gemini-hlsw/ocs/downloads","issues_url":"https://api.github.com/repos/gemini-hlsw/ocs/issues{/number}","pulls_url":"https://api.github.com/repos/gemini-hlsw/ocs/pulls{/number}","milestones_url":"https://api.github.com/repos/gemini-hlsw/ocs/milestones{/number}","notifications_url":"https://api.github.com/repos/gemini-hlsw/ocs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/gemini-hlsw/ocs/labels{/name}","releases_url":"https://api.github.com/repos/gemini-hlsw/ocs/releases{/id}","created_at":"2014-09-12T14:20:23Z","updated_at":"2015-02-27T20:11:41Z","pushed_at":"2015-02-27T20:11:40Z","git_url":"git://github.com/gemini-hlsw/ocs.git","ssh_url":"git@github.com:gemini-hlsw/ocs.git","clone_url":"https://github.com/gemini-hlsw/ocs.git","svn_url":"https://github.com/gemini-hlsw/ocs","homepage":"","size":264479,"stargazers_count":2,"watchers_count":2,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":8,"mirror_url":null,"open_issues_count":3,"forks":8,"open_issues":3,"watchers":2,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/gemini-hlsw/ocs/pulls/277"},"html":{"href":"https://github.com/gemini-hlsw/ocs/pull/277"},"issue":{"href":"https://api.github.com/repos/gemini-hlsw/ocs/issues/277"},"comments":{"href":"https://api.github.com/repos/gemini-hlsw/ocs/issues/277/comments"},"review_comments":{"href":"https://api.github.com/repos/gemini-hlsw/ocs/pulls/277/comments"},"review_comment":{"href":"https://api.github.com/repos/gemini-hlsw/ocs/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/gemini-hlsw/ocs/pulls/277/commits"},"statuses":{"href":"https://api.github.com/repos/gemini-hlsw/ocs/statuses/85c05ef470be7ff1cefde49c25095f2585b4a2a7"}}}}