[PATCH 1/4] push: add --confirm option to ask before sending updates

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Owen W. Taylor <otaylor@xxxxxxxxxxxx>

When --confirm is specified, the refs being updated are displayed
to the user first, and the user is prompted whether to proceed
or not.

Signed-off-by: Owen W. Taylor <otaylor@xxxxxxxxxxxx>
---
 Documentation/git-push.txt |    9 ++++-
 builtin-push.c             |    8 +++--
 builtin-send-pack.c        |    4 ++-
 send-pack.h                |    3 +-
 transport.c                |   87 ++++++++++++++++++++++++++++++++++++++++----
 transport.h                |    3 +-
 6 files changed, 98 insertions(+), 16 deletions(-)

diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 58d2bd5..c0bbf16 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -9,8 +9,9 @@ git-push - Update remote refs along with associated objects
 SYNOPSIS
 --------
 [verse]
-'git push' [--all | --mirror | --tags] [--dry-run] [--receive-pack=<git-receive-pack>]
-	   [--repo=<repository>] [-f | --force] [-v | --verbose]
+'git push' [--all | --mirror | --tags] [--dry-run] [--confirm]
+	   [--receive-pack=<git-receive-pack>]  [--repo=<repository>]
+	   [-f | --force] [-v | --verbose]
 	   [<repository> <refspec>...]
 
 DESCRIPTION
@@ -85,6 +86,10 @@ nor in any Push line of the corresponding remotes file---see below).
 --dry-run::
 	Do everything except actually send the updates.
 
+--confirm::
+	Print a summary of what will be done, and then ask the user
+	interactively before actually sending the updates.
+
 --porcelain::
 	Produce machine-readable output.  The output status line for each ref
 	will be tab-separated and sent to stdout instead of stderr.  The full
diff --git a/builtin-push.c b/builtin-push.c
index 6eda372..231be5d 100644
--- a/builtin-push.c
+++ b/builtin-push.c
@@ -10,7 +10,7 @@
 #include "parse-options.h"
 
 static const char * const push_usage[] = {
-	"git push [--all | --mirror] [--dry-run] [--porcelain] [--tags] [--receive-pack=<git-receive-pack>] [--repo=<repository>] [-f | --force] [-v] [<repository> <refspec>...]",
+	"git push [--all | --mirror] [--dry-run] [--confirm] [--porcelain] [--tags] [--receive-pack=<git-receive-pack>] [--repo=<repository>] [-f | --force] [-v] [<repository> <refspec>...]",
 	NULL,
 };
 
@@ -141,6 +141,7 @@ static int do_push(const char *repo, int flags)
 			transport_get(remote, url[i]);
 		int err;
 		int nonfastforward;
+		int disconfirmed;
 		if (receivepack)
 			transport_set_option(transport,
 					     TRANS_OPT_RECEIVEPACK, receivepack);
@@ -150,10 +151,10 @@ static int do_push(const char *repo, int flags)
 		if (flags & TRANSPORT_PUSH_VERBOSE)
 			fprintf(stderr, "Pushing to %s\n", url[i]);
 		err = transport_push(transport, refspec_nr, refspec, flags,
-				     &nonfastforward);
+				     &nonfastforward, &disconfirmed);
 		err |= transport_disconnect(transport);
 
-		if (!err)
+		if (!err || disconfirmed)
 			continue;
 
 		error("failed to push some refs to '%s'", url[i]);
@@ -182,6 +183,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
 		OPT_BIT( 0 , "mirror", &flags, "mirror all refs",
 			    (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)),
 		OPT_BOOLEAN( 0 , "tags", &tags, "push tags"),
+		OPT_BIT( 0 , "confirm", &flags, "ask before pushing", TRANSPORT_PUSH_CONFIRM),
 		OPT_BIT( 0 , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN),
 		OPT_BIT( 0,  "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN),
 		OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE),
diff --git a/builtin-send-pack.c b/builtin-send-pack.c
index 37e528e..0264180 100644
--- a/builtin-send-pack.c
+++ b/builtin-send-pack.c
@@ -406,7 +406,9 @@ int send_pack(struct send_pack_args *args,
 			REF_STATUS_OK;
 	}
 
-	packet_flush(out);
+	/* Don't flush until the second pass of 'git push --confirm' */
+	if (!(args->dry_run && args->confirm))
+		packet_flush(out);
 	if (new_refs && !args->dry_run) {
 		if (pack_objects(out, remote_refs, extra_have, args) < 0) {
 			for (ref = remote_refs; ref; ref = ref->next)
diff --git a/send-pack.h b/send-pack.h
index 8b3cf02..f2b9292 100644
--- a/send-pack.h
+++ b/send-pack.h
@@ -8,7 +8,8 @@ struct send_pack_args {
 		force_update:1,
 		use_thin_pack:1,
 		use_ofs_delta:1,
-		dry_run:1;
+		dry_run:1,
+		confirm:1;
 };
 
 int send_pack(struct send_pack_args *args,
diff --git a/transport.c b/transport.c
index 4cb8077..aa1852d 100644
--- a/transport.c
+++ b/transport.c
@@ -766,14 +766,18 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
 	args.verbose = !!(flags & TRANSPORT_PUSH_VERBOSE);
 	args.quiet = !!(flags & TRANSPORT_PUSH_QUIET);
 	args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
+	args.confirm = !!(flags & TRANSPORT_PUSH_CONFIRM);
 
 	ret = send_pack(&args, data->fd, data->conn, remote_refs,
 			&data->extra_have);
 
-	close(data->fd[1]);
-	close(data->fd[0]);
-	ret |= finish_connect(data->conn);
-	data->conn = NULL;
+	/* On the first dry-run pass of --confirm, we need to leave the connection open */
+	if (!((flags & TRANSPORT_PUSH_CONFIRM) && (flags & TRANSPORT_PUSH_DRY_RUN))) {
+		close(data->fd[1]);
+		close(data->fd[0]);
+		ret |= finish_connect(data->conn);
+		data->conn = NULL;
+	}
 
 	return ret;
 }
@@ -867,14 +871,37 @@ int transport_set_option(struct transport *transport,
 	return 1;
 }
 
+static int prompt_yesno(const char *prompt)
+{
+	while (1) {
+		char buf[128];
+
+		fprintf(stderr, prompt);
+		if (!fgets(buf, sizeof(buf), stdin))
+			return 0;
+		if (buf[0] == 'y' || buf[0] == 'Y')
+			return 1;
+		else if (buf[0] == 'n' || buf[0] == 'N')
+			return 0;
+	}
+}
+
 int transport_push(struct transport *transport,
 		   int refspec_nr, const char **refspec, int flags,
-		   int * nonfastforward)
+		   int * nonfastforward, int * disconfirmed)
 {
+	*disconfirmed = 0;
+
 	verify_remote_names(refspec_nr, refspec);
 
-	if (transport->push)
+	if (transport->push) {
+		if (flags & TRANSPORT_PUSH_CONFIRM) {
+			fprintf(stderr, "--confirm cannot be used with remote URL %s\n", transport->url);
+			return -1;
+		}
+
 		return transport->push(transport, refspec_nr, refspec, flags);
+	}
 	if (transport->push_refs) {
 		struct ref *remote_refs =
 			transport->get_refs_list(transport, 1);
@@ -883,6 +910,8 @@ int transport_push(struct transport *transport,
 		int verbose = flags & TRANSPORT_PUSH_VERBOSE;
 		int quiet = flags & TRANSPORT_PUSH_QUIET;
 		int porcelain = flags & TRANSPORT_PUSH_PORCELAIN;
+		int dry_run = flags & TRANSPORT_PUSH_DRY_RUN;
+		int confirm = flags & TRANSPORT_PUSH_CONFIRM;
 		int ret;
 
 		if (flags & TRANSPORT_PUSH_ALL)
@@ -895,14 +924,56 @@ int transport_push(struct transport *transport,
 			return -1;
 		}
 
+		/* --confirm is a no-op when --dry-run is also specified */
+		if (confirm && (dry_run || !isatty(0))) {
+			confirm = 0;
+			flags &= ~TRANSPORT_PUSH_CONFIRM;
+		}
+
+		if (confirm) {
+			struct ref *ref;
+			int proceed = 0;
+
+			ret = transport->push_refs(transport, remote_refs,
+						   flags | TRANSPORT_PUSH_DRY_RUN);
+
+			/* Interaction with --porcelain: we do the first pass interactively
+			 * to stderr with normal formatting, and then once the user has
+			 * confirmed, send the porcelain-formatted output to stdout.
+			 */
+			print_push_status(transport->url, remote_refs,
+					  verbose, 0,
+					  nonfastforward);
+
+			if (ret)
+				return ret;
+
+			if (!refs_pushed(remote_refs)) {
+				fprintf(stderr, "Everything up-to-date\n");
+				return 0;
+			}
+
+			proceed = prompt_yesno("Proceed [y/n]? ");
+			if (!proceed) {
+				*disconfirmed = 1;
+				return -1;
+			}
+
+			for (ref = remote_refs; ref; ref = ref->next)
+				ref->status = REF_STATUS_NONE;
+		}
+
 		ret = transport->push_refs(transport, remote_refs, flags);
 
-		if (!quiet || push_had_errors(remote_refs))
+		/* For --confirm, we don't need to print the details again unless
+		 * something unexpected happened (denyNonFastforwards=true perhaps)
+		 */
+		if (!(quiet || (confirm && !porcelain)) || push_had_errors(remote_refs))
 			print_push_status(transport->url, remote_refs,
 					verbose | porcelain, porcelain,
 					nonfastforward);
 
-		if (!(flags & TRANSPORT_PUSH_DRY_RUN)) {
+		if (!dry_run) {
 			struct ref *ref;
 			for (ref = remote_refs; ref; ref = ref->next)
 				update_tracking_ref(transport->remote, ref, verbose);
diff --git a/transport.h b/transport.h
index c14da6f..1d691d7 100644
--- a/transport.h
+++ b/transport.h
@@ -37,6 +37,7 @@ struct transport {
 #define TRANSPORT_PUSH_VERBOSE 16
 #define TRANSPORT_PUSH_PORCELAIN 32
 #define TRANSPORT_PUSH_QUIET 64
+#define TRANSPORT_PUSH_CONFIRM 128
 
 /* Returns a transport suitable for the url */
 struct transport *transport_get(struct remote *, const char *);
@@ -70,7 +71,7 @@ int transport_set_option(struct transport *transport, const char *name,
 
 int transport_push(struct transport *connection,
 		   int refspec_nr, const char **refspec, int flags,
-		   int * nonfastforward);
+		   int * nonfastforward, int * disconfirmed);
 
 const struct ref *transport_get_remote_refs(struct transport *transport);
 
-- 
1.6.2.5

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]