Skip to content

Commit

Permalink
Merge branch 'db/push-sign-if-asked'
Browse files Browse the repository at this point in the history
The client side codepaths in "git push" have been cleaned up
and the user can request to perform an optional "signed push",
i.e. sign only when the other end accepts signed push.

* db/push-sign-if-asked:
  push: add a config option push.gpgSign for default signed pushes
  push: support signing pushes iff the server supports it
  builtin/send-pack.c: use parse_options API
  config.c: rename git_config_maybe_bool_text and export it as git_parse_maybe_bool
  transport: remove git_transport_options.push_cert
  gitremote-helpers.txt: document pushcert option
  Documentation/git-send-pack.txt: document --signed
  Documentation/git-send-pack.txt: wrap long synopsis line
  Documentation/git-push.txt: document when --signed may fail
  • Loading branch information
Junio C Hamano committed Aug 31, 2015
2 parents 5b6211a + 68c757f commit b21089d
Show file tree
Hide file tree
Showing 14 changed files with 253 additions and 152 deletions.
8 changes: 8 additions & 0 deletions Documentation/config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2213,6 +2213,14 @@ push.followTags::
may override this configuration at time of push by specifying
'--no-follow-tags'.

push.gpgSign::
May be set to a boolean value, or the string 'if-asked'. A true
value causes all pushes to be GPG signed, as if '--signed' is
passed to linkgit:git-push[1]. The string 'if-asked' causes
pushes to be signed if the server supports it, as if
'--signed=if-asked' is passed to 'git push'. A false value may
override a value from a lower-priority config file. An explicit
command-line flag always overrides this config option.

rebase.stat::
Whether to show a diffstat of what changed upstream since the last
Expand Down
15 changes: 10 additions & 5 deletions Documentation/git-push.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ SYNOPSIS
[verse]
'git push' [--all | --mirror | --tags] [--follow-tags] [--atomic] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
[--repo=<repository>] [-f | --force] [--prune] [-v | --verbose]
[-u | --set-upstream] [--signed]
[-u | --set-upstream]
[--[no-]signed|--sign=(true|false|if-asked)]
[--force-with-lease[=<refname>[:<expect>]]]
[--no-verify] [<repository> [<refspec>...]]

Expand Down Expand Up @@ -132,12 +133,16 @@ already exists on the remote side.
with configuration variable 'push.followTags'. For more
information, see 'push.followTags' in linkgit:git-config[1].


--signed::
--[no-]signed::
--sign=(true|false|if-asked)::
GPG-sign the push request to update refs on the receiving
side, to allow it to be checked by the hooks and/or be
logged. See linkgit:git-receive-pack[1] for the details
on the receiving end.
logged. If `false` or `--no-signed`, no signing will be
attempted. If `true` or `--signed`, the push will fail if the
server does not support signed pushes. If set to `if-asked`,
sign if and only if the server supports signed pushes. The push
will also fail if the actual call to `gpg --sign` fails. See
linkgit:git-receive-pack[1] for the details on the receiving end.

--[no-]atomic::
Use an atomic transaction on the remote side if available.
Expand Down
16 changes: 15 additions & 1 deletion Documentation/git-send-pack.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ git-send-pack - Push objects over Git protocol to another repository
SYNOPSIS
--------
[verse]
'git send-pack' [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] [<host>:]<directory> [<ref>...]
'git send-pack' [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>]
[--verbose] [--thin] [--atomic]
[--[no-]signed|--sign=(true|false|if-asked)]
[<host>:]<directory> [<ref>...]

DESCRIPTION
-----------
Expand Down Expand Up @@ -67,6 +70,17 @@ be in a separate packet, and the list must end with a flush packet.
fails to update then the entire push will fail without changing any
refs.

--[no-]signed::
--sign=(true|false|if-asked)::
GPG-sign the push request to update refs on the receiving
side, to allow it to be checked by the hooks and/or be
logged. If `false` or `--no-signed`, no signing will be
attempted. If `true` or `--signed`, the push will fail if the
server does not support signed pushes. If set to `if-asked`,
sign if and only if the server supports signed pushes. The push
will also fail if the actual call to `gpg --sign` fails. See
linkgit:git-receive-pack[1] for the details on the receiving end.

<host>::
A remote host to house the repository. When this
part is specified, 'git-receive-pack' is invoked via
Expand Down
3 changes: 3 additions & 0 deletions Documentation/gitremote-helpers.txt
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,9 @@ set by Git if the remote helper has the 'option' capability.
'option update-shallow {'true'|'false'}::
Allow to extend .git/shallow if the new refs require it.

'option pushcert {'true'|'false'}::
GPG sign pushes.

SEE ALSO
--------
linkgit:git-remote[1]
Expand Down
42 changes: 41 additions & 1 deletion builtin/push.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "transport.h"
#include "parse-options.h"
#include "submodule.h"
#include "send-pack.h"

static const char * const push_usage[] = {
N_("git push [<options>] [<repository> [<refspec>...]]"),
Expand Down Expand Up @@ -471,6 +472,24 @@ static int option_parse_recurse_submodules(const struct option *opt,
return 0;
}

static void set_push_cert_flags(int *flags, int v)
{
switch (v) {
case SEND_PACK_PUSH_CERT_NEVER:
*flags &= ~(TRANSPORT_PUSH_CERT_ALWAYS | TRANSPORT_PUSH_CERT_IF_ASKED);
break;
case SEND_PACK_PUSH_CERT_ALWAYS:
*flags |= TRANSPORT_PUSH_CERT_ALWAYS;
*flags &= ~TRANSPORT_PUSH_CERT_IF_ASKED;
break;
case SEND_PACK_PUSH_CERT_IF_ASKED:
*flags |= TRANSPORT_PUSH_CERT_IF_ASKED;
*flags &= ~TRANSPORT_PUSH_CERT_ALWAYS;
break;
}
}


static int git_push_config(const char *k, const char *v, void *cb)
{
int *flags = cb;
Expand All @@ -486,6 +505,23 @@ static int git_push_config(const char *k, const char *v, void *cb)
else
*flags &= ~TRANSPORT_PUSH_FOLLOW_TAGS;
return 0;
} else if (!strcmp(k, "push.gpgsign")) {
const char *value;
if (!git_config_get_value("push.gpgsign", &value)) {
switch (git_config_maybe_bool("push.gpgsign", value)) {
case 0:
set_push_cert_flags(flags, SEND_PACK_PUSH_CERT_NEVER);
break;
case 1:
set_push_cert_flags(flags, SEND_PACK_PUSH_CERT_ALWAYS);
break;
default:
if (value && !strcasecmp(value, "if-asked"))
set_push_cert_flags(flags, SEND_PACK_PUSH_CERT_IF_ASKED);
else
return error("Invalid value for '%s'", k);
}
}
}

return git_default_config(k, v, NULL);
Expand All @@ -495,6 +531,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
{
int flags = 0;
int tags = 0;
int push_cert = -1;
int rc;
const char *repo = NULL; /* default repository */
struct option options[] = {
Expand Down Expand Up @@ -526,14 +563,17 @@ int cmd_push(int argc, const char **argv, const char *prefix)
OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), TRANSPORT_PUSH_NO_HOOK),
OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"),
TRANSPORT_PUSH_FOLLOW_TAGS),
OPT_BIT(0, "signed", &flags, N_("GPG sign the push"), TRANSPORT_PUSH_CERT),
{ OPTION_CALLBACK,
0, "signed", &push_cert, "yes|no|if-asked", N_("GPG sign the push"),
PARSE_OPT_OPTARG, option_parse_push_signed },
OPT_BIT(0, "atomic", &flags, N_("request atomic transaction on remote side"), TRANSPORT_PUSH_ATOMIC),
OPT_END()
};

packet_trace_identity("push");
git_config(git_push_config, &flags);
argc = parse_options(argc, argv, prefix, options, push_usage, 0);
set_push_cert_flags(&flags, push_cert);

if (deleterefs && (tags || (flags & (TRANSPORT_PUSH_ALL | TRANSPORT_PUSH_MIRROR))))
die(_("--delete is incompatible with --all, --mirror and --tags"));
Expand Down
192 changes: 87 additions & 105 deletions builtin/send-pack.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,15 @@
#include "version.h"
#include "sha1-array.h"
#include "gpg-interface.h"
#include "gettext.h"

static const char send_pack_usage[] =
"git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] [<host>:]<directory> [<ref>...]\n"
" --all and explicit <ref> specification are mutually exclusive.";
static const char * const send_pack_usage[] = {
N_("git send-pack [--all | --mirror] [--dry-run] [--force] "
"[--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] "
"[<host>:]<directory> [<ref>...]\n"
" --all and explicit <ref> specification are mutually exclusive."),
NULL,
};

static struct send_pack_args args;

Expand Down Expand Up @@ -92,6 +97,31 @@ static void print_helper_status(struct ref *ref)
strbuf_release(&buf);
}

static int send_pack_config(const char *k, const char *v, void *cb)
{
git_gpg_config(k, v, NULL);

if (!strcmp(k, "push.gpgsign")) {
const char *value;
if (!git_config_get_value("push.gpgsign", &value)) {
switch (git_config_maybe_bool("push.gpgsign", value)) {
case 0:
args.push_cert = SEND_PACK_PUSH_CERT_NEVER;
break;
case 1:
args.push_cert = SEND_PACK_PUSH_CERT_ALWAYS;
break;
default:
if (value && !strcasecmp(value, "if-asked"))
args.push_cert = SEND_PACK_PUSH_CERT_IF_ASKED;
else
return error("Invalid value for '%s'", k);
}
}
}
return 0;
}

int cmd_send_pack(int argc, const char **argv, const char *prefix)
{
int i, nr_refspecs = 0;
Expand All @@ -107,116 +137,68 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
int ret;
int helper_status = 0;
int send_all = 0;
int verbose = 0;
const char *receivepack = "git-receive-pack";
unsigned dry_run = 0;
unsigned send_mirror = 0;
unsigned force_update = 0;
unsigned quiet = 0;
int push_cert = 0;
unsigned use_thin_pack = 0;
unsigned atomic = 0;
unsigned stateless_rpc = 0;
int flags;
unsigned int reject_reasons;
int progress = -1;
int from_stdin = 0;
struct push_cas_option cas = {0};

git_config(git_gpg_config, NULL);

argv++;
for (i = 1; i < argc; i++, argv++) {
const char *arg = *argv;

if (*arg == '-') {
if (starts_with(arg, "--receive-pack=")) {
receivepack = arg + 15;
continue;
}
if (starts_with(arg, "--exec=")) {
receivepack = arg + 7;
continue;
}
if (starts_with(arg, "--remote=")) {
remote_name = arg + 9;
continue;
}
if (!strcmp(arg, "--all")) {
send_all = 1;
continue;
}
if (!strcmp(arg, "--dry-run")) {
args.dry_run = 1;
continue;
}
if (!strcmp(arg, "--mirror")) {
args.send_mirror = 1;
continue;
}
if (!strcmp(arg, "--force")) {
args.force_update = 1;
continue;
}
if (!strcmp(arg, "--quiet")) {
args.quiet = 1;
continue;
}
if (!strcmp(arg, "--verbose")) {
args.verbose = 1;
continue;
}
if (!strcmp(arg, "--signed")) {
args.push_cert = 1;
continue;
}
if (!strcmp(arg, "--progress")) {
progress = 1;
continue;
}
if (!strcmp(arg, "--no-progress")) {
progress = 0;
continue;
}
if (!strcmp(arg, "--thin")) {
args.use_thin_pack = 1;
continue;
}
if (!strcmp(arg, "--atomic")) {
args.atomic = 1;
continue;
}
if (!strcmp(arg, "--stateless-rpc")) {
args.stateless_rpc = 1;
continue;
}
if (!strcmp(arg, "--stdin")) {
from_stdin = 1;
continue;
}
if (!strcmp(arg, "--helper-status")) {
helper_status = 1;
continue;
}
if (!strcmp(arg, "--" CAS_OPT_NAME)) {
if (parse_push_cas_option(&cas, NULL, 0) < 0)
exit(1);
continue;
}
if (!strcmp(arg, "--no-" CAS_OPT_NAME)) {
if (parse_push_cas_option(&cas, NULL, 1) < 0)
exit(1);
continue;
}
if (starts_with(arg, "--" CAS_OPT_NAME "=")) {
if (parse_push_cas_option(&cas,
strchr(arg, '=') + 1, 0) < 0)
exit(1);
continue;
}
usage(send_pack_usage);
}
if (!dest) {
dest = arg;
continue;
}
refspecs = (const char **) argv;
nr_refspecs = argc - i;
break;
struct option options[] = {
OPT__VERBOSITY(&verbose),
OPT_STRING(0, "receive-pack", &receivepack, "receive-pack", N_("receive pack program")),
OPT_STRING(0, "exec", &receivepack, "receive-pack", N_("receive pack program")),
OPT_STRING(0, "remote", &remote_name, "remote", N_("remote name")),
OPT_BOOL(0, "all", &send_all, N_("push all refs")),
OPT_BOOL('n' , "dry-run", &dry_run, N_("dry run")),
OPT_BOOL(0, "mirror", &send_mirror, N_("mirror all refs")),
OPT_BOOL('f', "force", &force_update, N_("force updates")),
{ OPTION_CALLBACK,
0, "signed", &push_cert, "yes|no|if-asked", N_("GPG sign the push"),
PARSE_OPT_OPTARG, option_parse_push_signed },
OPT_BOOL(0, "progress", &progress, N_("force progress reporting")),
OPT_BOOL(0, "thin", &use_thin_pack, N_("use thin pack")),
OPT_BOOL(0, "atomic", &atomic, N_("request atomic transaction on remote side")),
OPT_BOOL(0, "stateless-rpc", &stateless_rpc, N_("use stateless RPC protocol")),
OPT_BOOL(0, "stdin", &from_stdin, N_("read refs from stdin")),
OPT_BOOL(0, "helper-status", &helper_status, N_("print status from remote helper")),
{ OPTION_CALLBACK,
0, CAS_OPT_NAME, &cas, N_("refname>:<expect"),
N_("require old value of ref to be at this value"),
PARSE_OPT_OPTARG, parseopt_push_cas_option },
OPT_END()
};

git_config(send_pack_config, NULL);
argc = parse_options(argc, argv, prefix, options, send_pack_usage, 0);
if (argc > 0) {
dest = argv[0];
refspecs = (const char **)(argv + 1);
nr_refspecs = argc - 1;
}

if (!dest)
usage(send_pack_usage);
usage_with_options(send_pack_usage, options);

args.verbose = verbose;
args.dry_run = dry_run;
args.send_mirror = send_mirror;
args.force_update = force_update;
args.quiet = quiet;
args.push_cert = push_cert;
args.progress = progress;
args.use_thin_pack = use_thin_pack;
args.atomic = atomic;
args.stateless_rpc = stateless_rpc;

if (from_stdin) {
struct argv_array all_refspecs = ARGV_ARRAY_INIT;
Expand Down Expand Up @@ -245,7 +227,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
*/
if ((refspecs && (send_all || args.send_mirror)) ||
(send_all && args.send_mirror))
usage(send_pack_usage);
usage_with_options(send_pack_usage, options);

if (remote_name) {
remote = remote_get(remote_name);
Expand Down
Loading

0 comments on commit b21089d

Please sign in to comment.