emacs: initial add of repo-transient module

Copied out of my dotfiles.  Terminal mode stuff is a little WIP right
now.

BUG=none
TEST=use menus, most commands sorta working

Change-Id: I48fd8eee4a84e9370fe9a445fb71cf66866516ba
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/dev-util/+/2324820
Tested-by: Jack Rosenthal <jrosenth@chromium.org>
Reviewed-by: Sean McAllister <smcallis@google.com>
Commit-Queue: Jack Rosenthal <jrosenth@chromium.org>
diff --git a/contrib/emacs/repo-transient.el b/contrib/emacs/repo-transient.el
new file mode 100644
index 0000000..aa4aa8f
--- /dev/null
+++ b/contrib/emacs/repo-transient.el
@@ -0,0 +1,187 @@
+;; -*- lexical-binding: t -*-
+;; Copyright 2020 The Chromium OS Authors. All Rights Reserved.
+;; Use of this source code is governed by a BSD-style license that can be
+;; found in the LICENSE file.
+
+;; repo-transient.el --- Transient menus to use some repo commands within magit
+;;
+
+;; This file is not part of GNU Emacs.
+
+(require 'term)
+(require 'transient)
+(require 'magit-process)
+
+(define-infix-argument repo:current-project ()
+  :description "Current Project"
+  :class 'transient-switch
+  :key "-c"
+  :argument ".")
+
+(define-infix-argument repo:all-projects ()
+  :description "All Projects"
+  :class 'transient-switch
+  :key "-A"
+  :argument "--all")
+
+;; TODO(jrosenth): the below keybindings don't work ... might be
+;; something with evil mode
+(defun repo--term-insert-yes ()
+  (interactive)
+  (term-send-string (get-buffer-process (current-buffer))
+                    "yes\n"))
+
+(defun repo--term-insert-no ()
+  (interactive)
+  (term-send-string (get-buffer-process (current-buffer))
+                    "no\n"))
+
+(defvar repo-term-mode-map
+  (let ((map (copy-keymap magit-mode-map)))
+    (define-key map (kbd "y") #'repo--term-insert-yes)
+    (define-key map (kbd "n") #'repo--term-insert-no)
+    (set-keymap-parent map magit-mode-map)
+    map))
+
+(define-derived-mode repo-term-mode term-mode "Repo Output"
+  "Derived terminal mode for repo output.")
+
+(defun repo--run-interactively (&rest args)
+  (let ((buf (apply #'make-term "repo-output" "repo" nil args)))
+    (with-current-buffer buf
+      (repo-term-mode))
+    (magit-display-buffer buf)))
+
+(defun repo-sync (args)
+  "Run a repo sync command."
+  (interactive (list (transient-args 'repo-sync-menu)))
+  (apply #'repo--run-interactively "sync" args))
+
+(defun repo-rebase (args)
+  "Run a repo rebase command."
+  (interactive (list (transient-args 'repo-rebase-menu)))
+  (apply #'repo--run-interactively "rebase" args))
+
+(define-transient-command repo-sync-menu ()
+  "Transient menu for repo sync."
+  ["Project"
+   (repo:current-project)]
+  ["Commands"
+   ("y" "Sync" repo-sync)])
+
+(define-transient-command repo-rebase-menu
+  "Transient menu for repo rebase."
+  ["Project"
+   (repo:current-project)]
+  ["Commands"
+   ("r" "Rebase" repo-rebase)])
+
+(defun repo-start (branch-name args)
+  "Run a repo start command."
+  (interactive (list "sBranch name: ")
+               (transient-args 'repo-start-menu))
+  (apply #'magit-call-process "repo" "start" args))
+
+(defun repo-start-temp (args)
+  "Run a repo start command with an auto-generated branch name."
+  (interactive (transient-args 'repo-start-menu))
+  (repo-start (format-time-string "temp-%Y-%m-%dT%H:%M:%S")
+              args))
+
+(define-transient-command repo-start-menu
+  "Transient menu for repo start."
+  ["Project"
+   (repo:all-projects)]
+  ["Commands"
+   ("s" "Start new development branch" repo-start)
+   ("t" "Start temporary development branch" repo-start-temp)])
+
+(defun repo-upload (args)
+  "Run a repo upload command."
+  (cond
+   ((and (not (member "--label=Verified+1" args))
+         (not (member "--label=Verified-1" args)))
+    (repo-upload `(,@args "--label=Verified+1")))
+   ((and (not (member "--cbr" args))
+         (not (seq-some (lambda (arg)
+                          (string-prefix-p "--br=" arg))
+                        args)))
+    (repo-upload `(,@args "--cbr")))
+   (t (apply #'repo--run-interactively "upload" args))))
+
+(defun repo-upload-current (args)
+  "Run a repo upload command in the current project."
+  (interactive (list (transient-args 'repo-upload-menu)))
+  (repo-upload (cons "." args)))
+
+(defun repo-upload-all (args)
+  "Run a repo upload command for all projects."
+  (interactive (list (transient-args 'repo-upload-menu)))
+  (repo-upload args))
+
+(define-infix-argument repo:--re ()
+  :description "Set reviewers"
+  :class 'transient-option
+  :key "-r"
+  :argument "--re=")
+
+(define-infix-argument repo:--cc ()
+  :description "Set reviewers"
+  :class 'transient-option
+  :key "-c"
+  :argument "--cc=")
+
+(define-infix-argument repo:--br ()
+  :description "Local branch to upload"
+  :class 'transient-option
+  :key "-b"
+  :argument "--br="
+  :reader 'magit-transient-read-revision)
+
+(define-infix-argument repo:--dest ()
+  :description "Remote destination branch"
+  :class 'transient-option
+  :key "-D"
+  :argument "--dest=")
+
+(define-infix-argument repo:--hashtag ()
+  :description "Hashtags"
+  :class 'transient-option
+  :key "-h"
+  :argument "--hashtag=")
+
+(define-transient-command repo-upload-menu ()
+  "Transient menu for repo upload."
+  ["People"
+   (repo:--re)
+   (repo:--cc)
+   ("-E" "Don't send emails" "--no-emails")]
+  ["Upload Hooks"
+   ("-n" "Skip upload hooks" "--no-verify")
+   ("-i" "Ignore failures in upload hooks" "--ignore-hooks")]
+  ["Labels"
+   ("-a" "Label Auto-Submit+1" "--label=Auto-Submit+1")
+   ("-d" "Label Commit-Queue+1 (dry run)" "--label=Commit-Queue+1")
+   ("-Q" "Label Commit-Queue+2" "--label=Commit-Queue+2")
+   ("-B" "Label Verified-1 (BAD)" "--label=Verified-1")
+   ("-S" "Sticky CQ+2" "--hashtag=stickycq")]
+  ["CL Options"
+   ("-w" "Work in Progress" "--wip")
+   ("-p" "Private" "--private")
+   (repo:--hashtag)]
+  ["Branches"
+   (repo:--br)
+   (repo:--dest)]
+  ["Upload"
+   ("u" "Upload current project" repo-upload-current)
+   ("U" "Upload all projects" repo-upload-all)])
+
+(define-transient-command repo-main-menu ()
+  "Transient menu for repo commands."
+  ["Subcommands"
+   ("y" "sync" repo-sync-menu)
+   ("r" "rebase" repo-rebase-menu)
+   ("s" "start" repo-start-menu)
+   ("u" "upload" repo-upload-menu)])
+
+(provide 'repo-transient)