The unified diff between revisions [1e32aef4..] and [95c3202d..] is displayed below. It can also be downloaded as a raw diff.

#
#
# patch "cmd_netsync.cc"
#  from [814327c3c6fcc1982fc34d000a1410f3d86f3267]
#    to [6b39feccad350c30a4d3900619e180c47a01d0fc]
#
# patch "key_store.cc"
#  from [510eb02eab491fd9e79c49fcfcf5e6efa145872f]
#    to [5c423e17442795e5cd6ca41ddc2c041e5ac5e553]
#
# patch "key_store.hh"
#  from [a2cba22c7534941db07838f8f7b9e3abede66cce]
#    to [86b0cfb3002761e38f91aa1b2b696d604a6bc674]
#
# patch "monotone.texi"
#  from [c3c35da89e98ebf1c7c0386dfb83336c64f95422]
#    to [c6f295ff19bdc6a6fc1fe1d4ec1a43d500f782dc]
#
# patch "netsync.cc"
#  from [f46b86a575c13f5454ac1df71a28f03d977e282a]
#    to [22eda2f383261b7bb48b44fa3f77bee10a4e2f3b]
#
# patch "options_list.hh"
#  from [06fc91493585539a0397d9020253aeda717ccb90]
#    to [a2b3cbf33d9686a72ab80da09489e9ea3bf5ee33]
#
============================================================
--- cmd_netsync.cc	814327c3c6fcc1982fc34d000a1410f3d86f3267
+++ cmd_netsync.cc	6b39feccad350c30a4d3900619e180c47a01d0fc
@@ -237,6 +237,39 @@ CMD(push, "push", "", CMD_REF(network),
                        client_voice, source_role, info);
 }

+CMD_AUTOMATE(push, N_("[ADDRESS[:PORTNUMBER] [PATTERN ...]]"),
+             N_("Pushes branches to a netsync server"),
+             "",
+             options::opts::set_default | options::opts::exclude |
+             options::opts::key_to_push | options::opts::do_not_enforce_ssh_agent)
+{
+  database db(app);
+  key_store keys(app);
+  project_t project(db);
+
+  netsync_connection_info info;
+  extract_client_connection_info(app.opts, app.lua, db, keys, args, info);
+
+  if (app.opts.signing_key() != "" && !app.opts.do_not_enforce_ssh_agent)
+    {
+      // enforce that any command which needs to encrypt a private key later on
+      // uses the ssh_agent to do the task, because we cannot handle password
+      // prompts over stdio. basically this is the same as giving --ssh-sign=only
+      // as option to stdio, but it is more explicit. however, this also leaves
+      // the opportunity of giving a possible passphrase via the get_passphrase
+      // lua hook, because the ssh agent is _always_ used, but its a reasonable
+      // trade-off
+      keys.enforce_ssh_agent();
+
+      N(keys.agent_knows_key(app.opts.signing_key),
+        F("key '%s' is unknown to the running ssh-agent instance")
+            % app.opts.signing_key);
+    }
+
+  run_netsync_protocol(app.opts, app.lua, project, keys,
+                       client_voice, source_role, info);
+}
+
 CMD(pull, "pull", "", CMD_REF(network),
     N_("[ADDRESS[:PORTNUMBER] [PATTERN ...]]"),
     N_("Pulls branches from a netsync server"),
@@ -262,7 +295,8 @@ CMD_AUTOMATE(pull, N_("[ADDRESS[:PORTNUM
 CMD_AUTOMATE(pull, N_("[ADDRESS[:PORTNUMBER] [PATTERN ...]]"),
              N_("Pulls branches from a netsync server"),
              "",
-             options::opts::set_default | options::opts::exclude)
+             options::opts::set_default | options::opts::exclude |
+             options::opts::do_not_enforce_ssh_agent)
 {
   database db(app);
   key_store keys(app);
@@ -272,9 +306,22 @@ CMD_AUTOMATE(pull, N_("[ADDRESS[:PORTNUM
   extract_client_connection_info(app.opts, app.lua, db, keys,
                                  args, info, false);

-  if (app.opts.signing_key() == "")
-    P(F("doing anonymous pull; use -kKEYNAME if you need authentication"));
+  if (app.opts.signing_key() != "" && !app.opts.do_not_enforce_ssh_agent)
+    {
+      // enforce that any command which needs to encrypt a private key later on
+      // uses the ssh_agent to do the task, because we cannot handle password
+      // prompts over stdio. basically this is the same as giving --ssh-sign=only
+      // as option to stdio, but it is more explicit. however, this also leaves
+      // the opportunity of giving a possible passphrase via the get_passphrase
+      // lua hook, because the ssh agent is _always_ used, but its a reasonable
+      // trade-off
+      keys.enforce_ssh_agent();

+      N(keys.agent_knows_key(app.opts.signing_key),
+        F("key '%s' is unknown to the running ssh-agent instance")
+            % app.opts.signing_key);
+    }
+
   run_netsync_protocol(app.opts, app.lua, project, keys,
                        client_voice, sink_role, info);
 }
@@ -285,7 +332,7 @@ CMD(sync, "sync", "", CMD_REF(network),
     N_("This synchronizes branches that match the pattern given in PATTERN "
        "with the netsync server at the address ADDRESS."),
     options::opts::set_default | options::opts::exclude |
-    options::opts::key_to_push)
+    options::opts::key_to_push | options::opts::do_not_enforce_ssh_agent)
 {
   database db(app);
   key_store keys(app);
@@ -305,6 +352,39 @@ CMD(sync, "sync", "", CMD_REF(network),
                        client_voice, source_and_sink_role, info);
 }

+CMD_AUTOMATE(sync, N_("[ADDRESS[:PORTNUMBER] [PATTERN ...]]"),
+             N_("Synchronizes branches with a netsync server"),
+             "",
+             options::opts::set_default | options::opts::exclude |
+             options::opts::key_to_push)
+{
+  database db(app);
+  key_store keys(app);
+  project_t project(db);
+
+  netsync_connection_info info;
+  extract_client_connection_info(app.opts, app.lua, db, keys, args, info);
+
+  if (app.opts.signing_key() != "" && !app.opts.do_not_enforce_ssh_agent)
+    {
+      // enforce that any command which needs to encrypt a private key later on
+      // uses the ssh_agent to do the task, because we cannot handle password
+      // prompts over stdio. basically this is the same as giving --ssh-sign=only
+      // as option to stdio, but it is more explicit. however, this also leaves
+      // the opportunity of giving a possible passphrase via the get_passphrase
+      // lua hook, because the ssh agent is _always_ used, but its a reasonable
+      // trade-off
+      keys.enforce_ssh_agent();
+
+      N(keys.agent_knows_key(app.opts.signing_key),
+        F("key '%s' is unknown to the running ssh-agent instance")
+            % app.opts.signing_key);
+    }
+
+  run_netsync_protocol(app.opts, app.lua, project, keys,
+                       client_voice, source_and_sink_role, info);
+}
+
 class dir_cleanup_helper
 {
 public:
============================================================
--- key_store.cc	510eb02eab491fd9e79c49fcfcf5e6efa145872f
+++ key_store.cc	5c423e17442795e5cd6ca41ddc2c041e5ac5e553
@@ -710,7 +710,31 @@ key_store::export_key_for_agent(rsa_keyp
   p.end_msg();
 }

+void
+key_store::enforce_ssh_agent()
+{
+  ssh_agent & agent = s->get_agent();
+  N(agent.connected(),
+    F("this command enforces the only usage of ssh_agent, "
+      "but no ssh-agent is available."));
+  if (s->ssh_sign_mode != "only")
+    {
+      W(F("enforcing the only usage of ssh_agent"));
+      const_cast<std::string &>(s->ssh_sign_mode) = "only";
+    }
+}

+bool
+key_store::agent_knows_key(rsa_keypair_id const & ident)
+{
+  keypair keyp;
+  N(maybe_get_key_pair(ident, keyp),
+    F("no keypair for '%s' in keyring") % ident);
+
+  ssh_agent & agent = s->get_agent();
+  return agent.has_key(keyp);
+}
+
 //
 // Migration from old databases
 //
============================================================
--- key_store.hh	a2cba22c7534941db07838f8f7b9e3abede66cce
+++ key_store.hh	86b0cfb3002761e38f91aa1b2b696d604a6bc674
@@ -82,6 +82,8 @@ public:
   void add_key_to_agent(rsa_keypair_id const & id);
   void export_key_for_agent(rsa_keypair_id const & id,
                             std::ostream & os);
+  void enforce_ssh_agent();
+  bool agent_knows_key(rsa_keypair_id const & ident);

   // Migration from old databases

============================================================
--- monotone.texi	c3c35da89e98ebf1c7c0386dfb83336c64f95422
+++ monotone.texi	c6f295ff19bdc6a6fc1fe1d4ec1a43d500f782dc
@@ -7573,6 +7573,103 @@ @section Automation

 @end table

+
+@item mtn automate pull [--set-default] [@var{uri-or-address}] [@var{glob} [...] [--exclude=@var{exclude-glob}]]] [--do-not-enforce-ssh-agent]
+@itemx mtn automate push [--set-default] [@var{uri-or-address}] [@var{glob} [...] [--exclude=@var{exclude-glob}]]] [--do-not-enforce-ssh-agent] [--key-to-push=@var{key-id}]
+@itemx mtn automate sync [--set-default] [@var{uri-or-address}] [@var{glob} [...] [--exclude=@var{exclude-glob}]]] [--do-not-enforce-ssh-agent] [--key-to-push=@var{key-id}]
+
+@table @strong
+@item Arguments:
+
+The automate versions of pull, push and sync have a superset of arguments and
+options compared to the non-automate versions. For an explanation of these
+common parts please check this manual in the appropriate section.
+
+Furthermore, you can define @option{--do-not-enforce-ssh-agent} which make
+these commands not using a running ssh agent instance explicitely. The setting
+of this option is highly discouraged if you run the command from inside stdio,
+because it might break your output in case a password is needed to decrypt a
+user key and no get_passphrase() lua hook has been set up.
+
+@item Added in:
+
+8.0
+
+@item Purpose:
+
+Pushes, pulls or syncs (push & pull) revisions, certificates and keys from
+the given database to / from / with to another (foreign) one. A running
+ssh-agent instance is required and the key which is used for signing /
+authentication has to be loaded into ssh-agent beforehand.
+
+@item Sample output (stdio):
+
+The following output shows an example of a pull which transferred two revisions
+and eight certs.
+
+@emph{Note:} The linebreaks are not part of the actual format.
+
+@verbatim
+0:0:p:62:doing anonymous pull; use -kKEYNAME if you need authentication
+0:0:p:25:connecting to monotone.ca0:0:p:29:finding items to synchronize:
+0:0:t:34:c:certificates;k:keys;r:revisions;
+0:0:t:12:c=0;k=0;r=0;
+0:0:t:13:c#0;k#0;r#64;
+0:0:t:14:c#0;k#0;r#128;
+[...]
+0:0:t:6:c;k;r;
+0:0:t:44:>:bytes in;<:bytes out;c:certs in;r:revs in;
+0:0:t:16:>=0;<=0;c=0;r=0;
+0:0:t:21:>#420;<#1344;c#0;r#0;
+0:0:t:22:>#1165;<#1741;c#0;r#0;
+[...]
+0:0:t:24:>#20839;<#14882;c#0;r#1;
+0:0:t:24:>#20839;<#14882;c#3;r#1;
+0:0:t:24:>#20839;<#14882;c#4;r#2;
+0:0:t:24:>#20839;<#14882;c#6;r#2;
+0:0:t:24:>#20863;<#14930;c#8;r#2;
+0:0:t:8:<;>;c;r;
+0:0:p:36:successful exchange with monotone.ca
+0:0:l:0:
+@end verbatim
+
+@item Output format:
+
+Netsync commands output info and ticker messages just like the normal netsync
+commands do.
+
+If these commands are run over stdio, the stdio ticker format is used
+(for a description of this format, check @command{automate stdio}).
+
+The following ticker types are printed out during the refinement phase:
+
+@itemize
+@item 'c': The amount of certs found for an upcoming synchronization
+@item 'k': The amount of keys found for an upcoming synchronization
+@item 'r': The amount of revisions found for an upcoming synchronization
+@end itemize
+
+After refinement the actual synchronization between the two nodes start. The
+ticker stanzas in this phase are the following:
+
+@itemize
+@item '>': number of incoming bytes
+@item '<': number of outgoing bytes
+@item 'c': number of incoming certs  (only pull and sync)
+@item 'C': number of outgoing certs  (only push and sync)
+@item 'r': number of incoming revisions  (only pull and sync)
+@item 'R': number of outgoing revisions  (only push and sync)
+@end itemize
+
+@item Error conditions:
+
+If no ssh-agent instance is running, the signing key hasn't been loaded into
+a running ssh-agent instance or a netsync error occurs, the command outputs
+an error and exits with status 1.
+
+@end table
+
+
 @item mtn automate get_current_revision [--exclude @var{excl}] [--depth=@var{depth}] [@var{path} ...]

 @table @strong
============================================================
--- netsync.cc	f46b86a575c13f5454ac1df71a28f03d977e282a
+++ netsync.cc	22eda2f383261b7bb48b44fa3f77bee10a4e2f3b
@@ -2498,6 +2498,9 @@ call_server(options & opts,
           // Commit whatever work we managed to accomplish anyways.
           guard.commit();

+          // ensure that the tickers have finished and write any last ticks
+          ui.ensure_clean_line();
+
           // We had an I/O error. We must decide if this represents a
           // user-reported error or a clean disconnect. See protocol
           // state diagram in session::process_bye_cmd.
============================================================
--- options_list.hh	06fc91493585539a0397d9020253aeda717ccb90
+++ options_list.hh	a2b3cbf33d9686a72ab80da09489e9ea3bf5ee33
@@ -80,6 +80,17 @@ OPT(automate_stdio_size, "automate-stdio
 }
 #endif

+OPT(do_not_enforce_ssh_agent, "do-not-enforce-ssh-agent", bool, false,
+     gettext_noop("do not enforce the usage of ssh-agent. enabling this is "
+                  "highly discouraged if you run this command over stdio "
+                  "as it may prompt for a key password which cannot be "
+                  "handled."))
+#ifdef option_bodies
+{
+  do_not_enforce_ssh_agent = true;
+}
+#endif
+
 OPTSET(bind_opts)
 OPTVAR(bind_opts, std::list<utf8>, bind_uris, )
 OPTVAR(bind_opts, bool, bind_stdio, false)