From 47a59185369b8905ad3a4012688cba92fd2ac1ff Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 8 Jul 2013 13:56:53 -0700 Subject: [PATCH 1/9] cache.h: move remote/connect API out of it The definition of "struct ref" in "cache.h", a header file so central to the system, always confused me. This structure is not about the local ref used by sha1-name API to name local objects. It is what refspecs are expanded into, after finding out what refs the other side has, to define what refs are updated after object transfer succeeds to what values. It belongs to "remote.h" together with "struct refspec". While we are at it, also move the types and functions related to the Git transport connection to a new header file connect.h Signed-off-by: Junio C Hamano --- builtin/fetch-pack.c | 2 ++ builtin/receive-pack.c | 1 + builtin/send-pack.c | 1 + cache.h | 62 ------------------------------------------ connect.c | 1 + connect.h | 13 +++++++++ fetch-pack.c | 1 + fetch-pack.h | 1 + refs.c | 8 ------ remote.c | 8 ++++++ remote.h | 54 ++++++++++++++++++++++++++++++++++++ send-pack.c | 1 + transport.c | 2 ++ transport.h | 1 + upload-pack.c | 1 + 15 files changed, 87 insertions(+), 70 deletions(-) create mode 100644 connect.h diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index aba4465552..c6888c66ce 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -1,6 +1,8 @@ #include "builtin.h" #include "pkt-line.h" #include "fetch-pack.h" +#include "remote.h" +#include "connect.h" static const char fetch_pack_usage[] = "git fetch-pack [--all] [--stdin] [--quiet|-q] [--keep|-k] [--thin] " diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index e3eb5fc058..7434d9b4a2 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -8,6 +8,7 @@ #include "commit.h" #include "object.h" #include "remote.h" +#include "connect.h" #include "transport.h" #include "string-list.h" #include "sha1-array.h" diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 152c4ea092..e86d3b5d67 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -5,6 +5,7 @@ #include "sideband.h" #include "run-command.h" #include "remote.h" +#include "connect.h" #include "send-pack.h" #include "quote.h" #include "transport.h" diff --git a/cache.h b/cache.h index dd0fb33a15..cb2891d8fa 100644 --- a/cache.h +++ b/cache.h @@ -1035,68 +1035,6 @@ struct pack_entry { struct packed_git *p; }; -struct ref { - struct ref *next; - unsigned char old_sha1[20]; - unsigned char new_sha1[20]; - char *symref; - unsigned int - force:1, - forced_update:1, - deletion:1, - matched:1; - - /* - * Order is important here, as we write to FETCH_HEAD - * in numeric order. And the default NOT_FOR_MERGE - * should be 0, so that xcalloc'd structures get it - * by default. - */ - enum { - FETCH_HEAD_MERGE = -1, - FETCH_HEAD_NOT_FOR_MERGE = 0, - FETCH_HEAD_IGNORE = 1 - } fetch_head_status; - - enum { - REF_STATUS_NONE = 0, - REF_STATUS_OK, - REF_STATUS_REJECT_NONFASTFORWARD, - REF_STATUS_REJECT_ALREADY_EXISTS, - REF_STATUS_REJECT_NODELETE, - REF_STATUS_REJECT_FETCH_FIRST, - REF_STATUS_REJECT_NEEDS_FORCE, - REF_STATUS_UPTODATE, - REF_STATUS_REMOTE_REJECT, - REF_STATUS_EXPECTING_REPORT - } status; - char *remote_status; - struct ref *peer_ref; /* when renaming */ - char name[FLEX_ARRAY]; /* more */ -}; - -#define REF_NORMAL (1u << 0) -#define REF_HEADS (1u << 1) -#define REF_TAGS (1u << 2) - -extern struct ref *find_ref_by_name(const struct ref *list, const char *name); - -#define CONNECT_VERBOSE (1u << 0) -extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags); -extern int finish_connect(struct child_process *conn); -extern int git_connection_is_socket(struct child_process *conn); -struct extra_have_objects { - int nr, alloc; - unsigned char (*array)[20]; -}; -extern struct ref **get_remote_heads(int in, char *src_buf, size_t src_len, - struct ref **list, unsigned int flags, - struct extra_have_objects *); -extern int server_supports(const char *feature); -extern int parse_feature_request(const char *features, const char *feature); -extern const char *server_feature_value(const char *feature, int *len_ret); -extern const char *parse_feature_value(const char *feature_list, const char *feature, int *len_ret); - extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path); /* A hook for count-objects to report invalid files in pack directory */ diff --git a/connect.c b/connect.c index a0783d4867..a80ebd316c 100644 --- a/connect.c +++ b/connect.c @@ -5,6 +5,7 @@ #include "refs.h" #include "run-command.h" #include "remote.h" +#include "connect.h" #include "url.h" static char *server_capabilities; diff --git a/connect.h b/connect.h new file mode 100644 index 0000000000..9dff25cad4 --- /dev/null +++ b/connect.h @@ -0,0 +1,13 @@ +#ifndef CONNECT_H +#define CONNECT_H + +#define CONNECT_VERBOSE (1u << 0) +extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags); +extern int finish_connect(struct child_process *conn); +extern int git_connection_is_socket(struct child_process *conn); +extern int server_supports(const char *feature); +extern int parse_feature_request(const char *features, const char *feature); +extern const char *server_feature_value(const char *feature, int *len_ret); +extern const char *parse_feature_value(const char *feature_list, const char *feature, int *len_ret); + +#endif diff --git a/fetch-pack.c b/fetch-pack.c index abe5ffbba5..c2bab42e6d 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -9,6 +9,7 @@ #include "fetch-pack.h" #include "remote.h" #include "run-command.h" +#include "connect.h" #include "transport.h" #include "version.h" diff --git a/fetch-pack.h b/fetch-pack.h index 40f08bab24..461cbf39b2 100644 --- a/fetch-pack.h +++ b/fetch-pack.h @@ -2,6 +2,7 @@ #define FETCH_PACK_H #include "string-list.h" +#include "run-command.h" struct fetch_pack_args { const char *uploadpack; diff --git a/refs.c b/refs.c index 4302206649..330060c866 100644 --- a/refs.c +++ b/refs.c @@ -3193,14 +3193,6 @@ int update_ref(const char *action, const char *refname, return 0; } -struct ref *find_ref_by_name(const struct ref *list, const char *name) -{ - for ( ; list; list = list->next) - if (!strcmp(list->name, name)) - return (struct ref *)list; - return NULL; -} - /* * generate a format suitable for scanf from a ref_rev_parse_rules * rule, that is replace the "%.*s" spec with a "%s" spec diff --git a/remote.c b/remote.c index 6f57830b64..b1ff7a208f 100644 --- a/remote.c +++ b/remote.c @@ -1302,6 +1302,14 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds free(sent_tips.tip); } +struct ref *find_ref_by_name(const struct ref *list, const char *name) +{ + for ( ; list; list = list->next) + if (!strcmp(list->name, name)) + return (struct ref *)list; + return NULL; +} + /* * Given the set of refs the local repository has, the set of refs the * remote repository has, and the refspec used for push, determine diff --git a/remote.h b/remote.h index cf56724661..a850059804 100644 --- a/remote.h +++ b/remote.h @@ -71,6 +71,52 @@ struct refspec { extern const struct refspec *tag_refspec; +struct ref { + struct ref *next; + unsigned char old_sha1[20]; + unsigned char new_sha1[20]; + char *symref; + unsigned int + force:1, + forced_update:1, + deletion:1, + matched:1; + + /* + * Order is important here, as we write to FETCH_HEAD + * in numeric order. And the default NOT_FOR_MERGE + * should be 0, so that xcalloc'd structures get it + * by default. + */ + enum { + FETCH_HEAD_MERGE = -1, + FETCH_HEAD_NOT_FOR_MERGE = 0, + FETCH_HEAD_IGNORE = 1 + } fetch_head_status; + + enum { + REF_STATUS_NONE = 0, + REF_STATUS_OK, + REF_STATUS_REJECT_NONFASTFORWARD, + REF_STATUS_REJECT_ALREADY_EXISTS, + REF_STATUS_REJECT_NODELETE, + REF_STATUS_REJECT_FETCH_FIRST, + REF_STATUS_REJECT_NEEDS_FORCE, + REF_STATUS_UPTODATE, + REF_STATUS_REMOTE_REJECT, + REF_STATUS_EXPECTING_REPORT + } status; + char *remote_status; + struct ref *peer_ref; /* when renaming */ + char name[FLEX_ARRAY]; /* more */ +}; + +#define REF_NORMAL (1u << 0) +#define REF_HEADS (1u << 1) +#define REF_TAGS (1u << 2) + +extern struct ref *find_ref_by_name(const struct ref *list, const char *name); + struct ref *alloc_ref(const char *name); struct ref *copy_ref(const struct ref *ref); struct ref *copy_ref_list(const struct ref *ref); @@ -84,6 +130,14 @@ int check_ref_type(const struct ref *ref, int flags); */ void free_refs(struct ref *ref); +struct extra_have_objects { + int nr, alloc; + unsigned char (*array)[20]; +}; +extern struct ref **get_remote_heads(int in, char *src_buf, size_t src_len, + struct ref **list, unsigned int flags, + struct extra_have_objects *); + int resolve_remote_symref(struct ref *ref, struct ref *list); int ref_newer(const unsigned char *new_sha1, const unsigned char *old_sha1); diff --git a/send-pack.c b/send-pack.c index 7d172ef37f..9a9908c774 100644 --- a/send-pack.c +++ b/send-pack.c @@ -5,6 +5,7 @@ #include "sideband.h" #include "run-command.h" #include "remote.h" +#include "connect.h" #include "send-pack.h" #include "quote.h" #include "transport.h" diff --git a/transport.c b/transport.c index e15db9808c..b84dbf086c 100644 --- a/transport.c +++ b/transport.c @@ -3,6 +3,8 @@ #include "run-command.h" #include "pkt-line.h" #include "fetch-pack.h" +#include "remote.h" +#include "connect.h" #include "send-pack.h" #include "walker.h" #include "bundle.h" diff --git a/transport.h b/transport.h index ea70ea7e4a..b551f99110 100644 --- a/transport.h +++ b/transport.h @@ -2,6 +2,7 @@ #define TRANSPORT_H #include "cache.h" +#include "run-command.h" #include "remote.h" struct git_transport_options { diff --git a/upload-pack.c b/upload-pack.c index 127e59a603..b03492e664 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -10,6 +10,7 @@ #include "revision.h" #include "list-objects.h" #include "run-command.h" +#include "connect.h" #include "sigchain.h" #include "version.h" #include "string-list.h" From ab22d2eb83c1810043bfa07dd142594580e093be Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 8 Jul 2013 14:50:27 -0700 Subject: [PATCH 2/9] builtin/push.c: use OPT_BOOL, not OPT_BOOLEAN The command line parser of "git push" for "--tags", "--delete", and "--thin" options still used outdated OPT_BOOLEAN. Because these options do not give escalating levels when given multiple times, they should use OPT_BOOL. Signed-off-by: Junio C Hamano --- builtin/push.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/builtin/push.c b/builtin/push.c index 2d84d10720..342d792f93 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -427,15 +427,15 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BIT( 0 , "all", &flags, N_("push all refs"), TRANSPORT_PUSH_ALL), OPT_BIT( 0 , "mirror", &flags, N_("mirror all refs"), (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)), - OPT_BOOLEAN( 0, "delete", &deleterefs, N_("delete refs")), - OPT_BOOLEAN( 0 , "tags", &tags, N_("push tags (can't be used with --all or --mirror)")), + OPT_BOOL( 0, "delete", &deleterefs, N_("delete refs")), + OPT_BOOL( 0 , "tags", &tags, N_("push tags (can't be used with --all or --mirror)")), OPT_BIT('n' , "dry-run", &flags, N_("dry run"), TRANSPORT_PUSH_DRY_RUN), OPT_BIT( 0, "porcelain", &flags, N_("machine-readable output"), TRANSPORT_PUSH_PORCELAIN), OPT_BIT('f', "force", &flags, N_("force updates"), TRANSPORT_PUSH_FORCE), { OPTION_CALLBACK, 0, "recurse-submodules", &flags, N_("check"), N_("control recursive pushing of submodules"), PARSE_OPT_OPTARG, option_parse_recurse_submodules }, - OPT_BOOLEAN( 0 , "thin", &thin, N_("use thin pack")), + OPT_BOOL( 0 , "thin", &thin, N_("use thin pack")), OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", N_("receive pack program")), OPT_STRING( 0 , "exec", &receivepack, "receive-pack", N_("receive pack program")), OPT_BIT('u', "set-upstream", &flags, N_("set upstream for git pull/status"), From 28f5d176110d2ed768a0a49159993c7a02d8cb15 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 8 Jul 2013 15:34:36 -0700 Subject: [PATCH 3/9] remote.c: add command line option parser for "--force-with-lease" Update "git push" and "git send-pack" to parse this commnd line option. The intended sematics is: * "--force-with-lease" alone, without specifying the details, will protect _all_ remote refs that are going to be updated by requiring their current value to be the same as some reasonable default, unless otherwise specified; * "--force-with-lease=refname", without specifying the expected value, will protect that refname, if it is going to be updated, by requiring its current value to be the same as some reasonable default. * "--force-with-lease=refname:value" will protect that refname, if it is going to be updated, by requiring its current value to be the same as the specified value; and * "--no-force-with-lease" will cancel all the previous --force-with-lease on the command line. For now, "some reasonable default" is tentatively defined as "the value of the remote-tracking branch we have for the ref of the remote being updated", and it is an error if we do not have such a remote-tracking branch. But this is known to be fragile, its use is not yet recommended, and hopefully we will find more reasonable default as we gain experience with this feature. The manual marks the feature as experimental unless the expected value is specified explicitly for this reason. Because the command line options are parsed _before_ we know which remote we are pushing to, there needs further processing to the parsed data after we instantiate the transport object to: * expand "refname" given by the user to a full refname to be matched with the list of "struct ref" used in match_push_refs() and set_ref_status_for_push(); and * learning the actual local ref that is the remote-tracking branch for the specified remote ref. Further, some processing need to be deferred until we find the set of remote refs and match_push_refs() returns in order to find the ones that need to be checked after explicit ones have been processed for "--force-with-lease" (no specific details). These post-processing will be the topic of the next patch. This option was originally called "cas" (for "compare and swap"), the name which nobody liked because it was too technical. The second attempt called it "lockref" (because it is conceptually like pushing after taking a lock) but the word "lock" was hated because it implied that it may reject push by others, which is not the way this option works. This round calls it "force-with-lease". You assume you took the lease on the ref when you fetched to decide what the rebased history should be, and you can push back only if the lease has not been broken. Signed-off-by: Junio C Hamano --- Documentation/git-push.txt | 77 ++++++++++++++++++++++++++++++++------ builtin/push.c | 6 +++ builtin/send-pack.c | 17 +++++++++ remote.c | 57 ++++++++++++++++++++++++++++ remote.h | 22 +++++++++++ 5 files changed, 168 insertions(+), 11 deletions(-) diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index f7dfe48d28..e2992f17a0 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -11,6 +11,7 @@ SYNOPSIS [verse] 'git push' [--all | --mirror | --tags] [--follow-tags] [-n | --dry-run] [--receive-pack=] [--repo=] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream] + [--force-with-lease[=[:]]] [--no-verify] [ [...]] DESCRIPTION @@ -130,21 +131,75 @@ already exists on the remote side. repository over ssh, and you do not have the program in a directory on the default $PATH. +--[no-]force-with-lease:: +--force-with-lease=:: +--force-with-lease=::: + Usually, "git push" refuses to update a remote ref that is + not an ancestor of the local ref used to overwrite it. ++ +This option bypasses the check, but instead requires that the +current value of the ref to be the expected value. "git push" +fails otherwise. ++ +Imagine that you have to rebase what you have already published. +You will have to bypass the "must fast-forward" rule in order to +replace the history you originally published with the rebased history. +If somebody else built on top of your original history while you are +rebasing, the tip of the branch at the remote may advance with her +commit, and blindly pushing with `--force` will lose her work. ++ +This option allows you to say that you expect the history you are +updating is what you rebased and want to replace. If the remote ref +still points at the commit you specified, you can be sure that no +other people did anything to the ref (it is like taking a "lease" on +the ref without explicitly locking it, and you update the ref while +making sure that your earlier "lease" is still valid). ++ +`--force-with-lease` alone, without specifying the details, will protect +all remote refs that are going to be updated by requiring their +current value to be the same as the remote-tracking branch we have +for them, unless specified with a `--force-with-lease=:` +option that explicitly states what the expected value is. ++ +`--force-with-lease=`, without specifying the expected value, will +protect the named ref (alone), if it is going to be updated, by +requiring its current value to be the same as the remote-tracking +branch we have for it. ++ +`--force-with-lease=:` will protect the named ref (alone), +if it is going to be updated, by requiring its current value to be +the same as the specified value (which is allowed to be +different from the remote-tracking branch we have for the refname, +or we do not even have to have such a remote-tracking branch when +this form is used). ++ +Note that all forms other than `--force-with-lease=:` +that specifies the expected current value of the ref explicitly are +still experimental and their semantics may change as we gain experience +with this feature. ++ +"--no-force-with-lease" will cancel all the previous --force-with-lease on the +command line. + -f:: --force:: Usually, the command refuses to update a remote ref that is not an ancestor of the local ref used to overwrite it. - This flag disables the check. This can cause the - remote repository to lose commits; use it with care. - Note that `--force` applies to all the refs that are pushed, - hence using it with `push.default` set to `matching` or with - multiple push destinations configured with `remote.*.push` - may overwrite refs other than the current branch (including - local refs that are strictly behind their remote counterpart). - To force a push to only one branch, use a `+` in front of the - refspec to push (e.g `git push origin +master` to force a push - to the `master` branch). See the `...` section above - for details. + Also, when `--force-with-lease` option is used, the command refuses + to update a remote ref whose current value does not match + what is expected. ++ +This flag disables these checks, and can cause the remote repository +to lose commits; use it with care. ++ +Note that `--force` applies to all the refs that are pushed, hence +using it with `push.default` set to `matching` or with multiple push +destinations configured with `remote.*.push` may overwrite refs +other than the current branch (including local refs that are +strictly behind their remote counterpart). To force a push to only +one branch, use a `+` in front of the refspec to push (e.g `git push +origin +master` to force a push to the `master` branch). See the +`...` section above for details. --repo=:: This option is only relevant if no argument is diff --git a/builtin/push.c b/builtin/push.c index 342d792f93..31a5ba085d 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -21,6 +21,8 @@ static const char *receivepack; static int verbosity; static int progress = -1; +static struct push_cas_option cas; + static const char **refspec; static int refspec_nr; static int refspec_alloc; @@ -432,6 +434,10 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BIT('n' , "dry-run", &flags, N_("dry run"), TRANSPORT_PUSH_DRY_RUN), OPT_BIT( 0, "porcelain", &flags, N_("machine-readable output"), TRANSPORT_PUSH_PORCELAIN), OPT_BIT('f', "force", &flags, N_("force updates"), TRANSPORT_PUSH_FORCE), + { OPTION_CALLBACK, + 0, CAS_OPT_NAME, &cas, N_("refname>:" */ + clear_cas_option(cas); + return 0; + } + + if (!arg) { + /* just "--