Merge branch 'mh/ref-transaction'
Update "update-ref --stdin [-z]" and then introduce a transactional support for (multi-)reference updates. * mh/ref-transaction: (27 commits) ref_transaction_commit(): work with transaction->updates in place struct ref_update: add a type field struct ref_update: add a lock field ref_transaction_commit(): simplify code using temporary variables struct ref_update: store refname as a FLEX_ARRAY struct ref_update: rename field "ref_name" to "refname" refs: remove API function update_refs() update-ref --stdin: reimplement using reference transactions refs: add a concept of a reference transaction update-ref --stdin: harmonize error messages update-ref --stdin: improve the error message for unexpected EOF t1400: test one mistake at a time update-ref --stdin -z: deprecate interpreting the empty string as zeros update-ref.c: extract a new function, parse_next_sha1() t1400: test that stdin -z update treats empty <newvalue> as zeros update-ref --stdin: simplify error messages for missing oldvalues update-ref --stdin: make error messages more consistent update-ref --stdin: improve error messages for invalid values update-ref.c: extract a new function, parse_refname() parse_cmd_verify(): copy old_sha1 instead of evaluating <oldvalue> twice ...maint
						commit
						2cc70cefdd
					
				|  | @ -68,7 +68,12 @@ performs all modifications together.  Specify commands of the form: | ||||||
| 	option SP <opt> LF | 	option SP <opt> LF | ||||||
|  |  | ||||||
| Quote fields containing whitespace as if they were strings in C source | Quote fields containing whitespace as if they were strings in C source | ||||||
| code.  Alternatively, use `-z` to specify commands without quoting: | code; i.e., surrounded by double-quotes and with backslash escapes. | ||||||
|  | Use 40 "0" characters or the empty string to specify a zero value.  To | ||||||
|  | specify a missing value, omit the value and its preceding SP entirely. | ||||||
|  |  | ||||||
|  | Alternatively, use `-z` to specify in NUL-terminated format, without | ||||||
|  | quoting: | ||||||
|  |  | ||||||
| 	update SP <ref> NUL <newvalue> NUL [<oldvalue>] NUL | 	update SP <ref> NUL <newvalue> NUL [<oldvalue>] NUL | ||||||
| 	create SP <ref> NUL <newvalue> NUL | 	create SP <ref> NUL <newvalue> NUL | ||||||
|  | @ -76,8 +81,12 @@ code.  Alternatively, use `-z` to specify commands without quoting: | ||||||
| 	verify SP <ref> NUL [<oldvalue>] NUL | 	verify SP <ref> NUL [<oldvalue>] NUL | ||||||
| 	option SP <opt> NUL | 	option SP <opt> NUL | ||||||
|  |  | ||||||
| Lines of any other format or a repeated <ref> produce an error. | In this format, use 40 "0" to specify a zero value, and use the empty | ||||||
| Command meanings are: | string to specify a missing value. | ||||||
|  |  | ||||||
|  | In either format, values can be specified in any form that Git | ||||||
|  | recognizes as an object name.  Commands in any other format or a | ||||||
|  | repeated <ref> produce an error.  Command meanings are: | ||||||
|  |  | ||||||
| update:: | update:: | ||||||
| 	Set <ref> to <newvalue> after verifying <oldvalue>, if given. | 	Set <ref> to <newvalue> after verifying <oldvalue>, if given. | ||||||
|  | @ -102,9 +111,6 @@ option:: | ||||||
| 	The only valid option is `no-deref` to avoid dereferencing | 	The only valid option is `no-deref` to avoid dereferencing | ||||||
| 	a symbolic ref. | 	a symbolic ref. | ||||||
|  |  | ||||||
| Use 40 "0" or the empty string to specify a zero value, except that |  | ||||||
| with `-z` an empty <oldvalue> is considered missing. |  | ||||||
|  |  | ||||||
| If all <ref>s can be locked with matching <oldvalue>s | If all <ref>s can be locked with matching <oldvalue>s | ||||||
| simultaneously, all modifications are performed.  Otherwise, no | simultaneously, all modifications are performed.  Otherwise, no | ||||||
| modifications are performed.  Note that while each individual | modifications are performed.  Note that while each individual | ||||||
|  |  | ||||||
|  | @ -624,7 +624,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts, | ||||||
| 		/* Nothing to do. */ | 		/* Nothing to do. */ | ||||||
| 	} else if (opts->force_detach || !new->path) {	/* No longer on any branch. */ | 	} else if (opts->force_detach || !new->path) {	/* No longer on any branch. */ | ||||||
| 		update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL, | 		update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL, | ||||||
| 			   REF_NODEREF, DIE_ON_ERR); | 			   REF_NODEREF, UPDATE_REFS_DIE_ON_ERR); | ||||||
| 		if (!opts->quiet) { | 		if (!opts->quiet) { | ||||||
| 			if (old->path && advice_detached_head) | 			if (old->path && advice_detached_head) | ||||||
| 				detach_advice(new->name); | 				detach_advice(new->name); | ||||||
|  |  | ||||||
|  | @ -521,7 +521,7 @@ static void write_followtags(const struct ref *refs, const char *msg) | ||||||
| 		if (!has_sha1_file(ref->old_sha1)) | 		if (!has_sha1_file(ref->old_sha1)) | ||||||
| 			continue; | 			continue; | ||||||
| 		update_ref(msg, ref->name, ref->old_sha1, | 		update_ref(msg, ref->name, ref->old_sha1, | ||||||
| 			   NULL, 0, DIE_ON_ERR); | 			   NULL, 0, UPDATE_REFS_DIE_ON_ERR); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -589,14 +589,15 @@ static void update_head(const struct ref *our, const struct ref *remote, | ||||||
| 		create_symref("HEAD", our->name, NULL); | 		create_symref("HEAD", our->name, NULL); | ||||||
| 		if (!option_bare) { | 		if (!option_bare) { | ||||||
| 			const char *head = skip_prefix(our->name, "refs/heads/"); | 			const char *head = skip_prefix(our->name, "refs/heads/"); | ||||||
| 			update_ref(msg, "HEAD", our->old_sha1, NULL, 0, DIE_ON_ERR); | 			update_ref(msg, "HEAD", our->old_sha1, NULL, 0, | ||||||
|  | 				   UPDATE_REFS_DIE_ON_ERR); | ||||||
| 			install_branch_config(0, head, option_origin, our->name); | 			install_branch_config(0, head, option_origin, our->name); | ||||||
| 		} | 		} | ||||||
| 	} else if (our) { | 	} else if (our) { | ||||||
| 		struct commit *c = lookup_commit_reference(our->old_sha1); | 		struct commit *c = lookup_commit_reference(our->old_sha1); | ||||||
| 		/* --branch specifies a non-branch (i.e. tags), detach HEAD */ | 		/* --branch specifies a non-branch (i.e. tags), detach HEAD */ | ||||||
| 		update_ref(msg, "HEAD", c->object.sha1, | 		update_ref(msg, "HEAD", c->object.sha1, | ||||||
| 			   NULL, REF_NODEREF, DIE_ON_ERR); | 			   NULL, REF_NODEREF, UPDATE_REFS_DIE_ON_ERR); | ||||||
| 	} else if (remote) { | 	} else if (remote) { | ||||||
| 		/* | 		/* | ||||||
| 		 * We know remote HEAD points to a non-branch, or | 		 * We know remote HEAD points to a non-branch, or | ||||||
|  | @ -604,7 +605,7 @@ static void update_head(const struct ref *our, const struct ref *remote, | ||||||
| 		 * Detach HEAD in all these cases. | 		 * Detach HEAD in all these cases. | ||||||
| 		 */ | 		 */ | ||||||
| 		update_ref(msg, "HEAD", remote->old_sha1, | 		update_ref(msg, "HEAD", remote->old_sha1, | ||||||
| 			   NULL, REF_NODEREF, DIE_ON_ERR); | 			   NULL, REF_NODEREF, UPDATE_REFS_DIE_ON_ERR); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @ -398,7 +398,7 @@ static void finish(struct commit *head_commit, | ||||||
| 			const char *argv_gc_auto[] = { "gc", "--auto", NULL }; | 			const char *argv_gc_auto[] = { "gc", "--auto", NULL }; | ||||||
| 			update_ref(reflog_message.buf, "HEAD", | 			update_ref(reflog_message.buf, "HEAD", | ||||||
| 				new_head, head, 0, | 				new_head, head, 0, | ||||||
| 				DIE_ON_ERR); | 				UPDATE_REFS_DIE_ON_ERR); | ||||||
| 			/* | 			/* | ||||||
| 			 * We ignore errors in 'gc --auto', since the | 			 * We ignore errors in 'gc --auto', since the | ||||||
| 			 * user should see them. | 			 * user should see them. | ||||||
|  | @ -1222,7 +1222,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) | ||||||
| 			die(_("%s - not something we can merge"), argv[0]); | 			die(_("%s - not something we can merge"), argv[0]); | ||||||
| 		read_empty(remote_head->object.sha1, 0); | 		read_empty(remote_head->object.sha1, 0); | ||||||
| 		update_ref("initial pull", "HEAD", remote_head->object.sha1, | 		update_ref("initial pull", "HEAD", remote_head->object.sha1, | ||||||
| 			   NULL, 0, DIE_ON_ERR); | 			   NULL, 0, UPDATE_REFS_DIE_ON_ERR); | ||||||
| 		goto done; | 		goto done; | ||||||
| 	} else { | 	} else { | ||||||
| 		struct strbuf merge_names = STRBUF_INIT; | 		struct strbuf merge_names = STRBUF_INIT; | ||||||
|  | @ -1339,7 +1339,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.sha1, | 	update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.sha1, | ||||||
| 		   NULL, 0, DIE_ON_ERR); | 		   NULL, 0, UPDATE_REFS_DIE_ON_ERR); | ||||||
|  |  | ||||||
| 	if (remoteheads && !common) | 	if (remoteheads && !common) | ||||||
| 		; /* No common ancestors found. We need a real merge. */ | 		; /* No common ancestors found. We need a real merge. */ | ||||||
|  |  | ||||||
|  | @ -717,7 +717,7 @@ static int merge_commit(struct notes_merge_options *o) | ||||||
| 	strbuf_insert(&msg, 0, "notes: ", 7); | 	strbuf_insert(&msg, 0, "notes: ", 7); | ||||||
| 	update_ref(msg.buf, o->local_ref, sha1, | 	update_ref(msg.buf, o->local_ref, sha1, | ||||||
| 		   is_null_sha1(parent_sha1) ? NULL : parent_sha1, | 		   is_null_sha1(parent_sha1) ? NULL : parent_sha1, | ||||||
| 		   0, DIE_ON_ERR); | 		   0, UPDATE_REFS_DIE_ON_ERR); | ||||||
|  |  | ||||||
| 	free_notes(t); | 	free_notes(t); | ||||||
| 	strbuf_release(&msg); | 	strbuf_release(&msg); | ||||||
|  | @ -812,11 +812,11 @@ static int merge(int argc, const char **argv, const char *prefix) | ||||||
| 	if (result >= 0) /* Merge resulted (trivially) in result_sha1 */ | 	if (result >= 0) /* Merge resulted (trivially) in result_sha1 */ | ||||||
| 		/* Update default notes ref with new commit */ | 		/* Update default notes ref with new commit */ | ||||||
| 		update_ref(msg.buf, default_notes_ref(), result_sha1, NULL, | 		update_ref(msg.buf, default_notes_ref(), result_sha1, NULL, | ||||||
| 			   0, DIE_ON_ERR); | 			   0, UPDATE_REFS_DIE_ON_ERR); | ||||||
| 	else { /* Merge has unresolved conflicts */ | 	else { /* Merge has unresolved conflicts */ | ||||||
| 		/* Update .git/NOTES_MERGE_PARTIAL with partial merge result */ | 		/* Update .git/NOTES_MERGE_PARTIAL with partial merge result */ | ||||||
| 		update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL, | 		update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL, | ||||||
| 			   0, DIE_ON_ERR); | 			   0, UPDATE_REFS_DIE_ON_ERR); | ||||||
| 		/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */ | 		/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */ | ||||||
| 		if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL)) | 		if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL)) | ||||||
| 			die("Failed to store link to current notes ref (%s)", | 			die("Failed to store link to current notes ref (%s)", | ||||||
|  |  | ||||||
|  | @ -252,11 +252,13 @@ static int reset_refs(const char *rev, const unsigned char *sha1) | ||||||
| 	if (!get_sha1("HEAD", sha1_orig)) { | 	if (!get_sha1("HEAD", sha1_orig)) { | ||||||
| 		orig = sha1_orig; | 		orig = sha1_orig; | ||||||
| 		set_reflog_message(&msg, "updating ORIG_HEAD", NULL); | 		set_reflog_message(&msg, "updating ORIG_HEAD", NULL); | ||||||
| 		update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR); | 		update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, | ||||||
|  | 			   UPDATE_REFS_MSG_ON_ERR); | ||||||
| 	} else if (old_orig) | 	} else if (old_orig) | ||||||
| 		delete_ref("ORIG_HEAD", old_orig, 0); | 		delete_ref("ORIG_HEAD", old_orig, 0); | ||||||
| 	set_reflog_message(&msg, "updating HEAD", rev); | 	set_reflog_message(&msg, "updating HEAD", rev); | ||||||
| 	update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0, MSG_ON_ERR); | 	update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0, | ||||||
|  | 				       UPDATE_REFS_MSG_ON_ERR); | ||||||
| 	strbuf_release(&msg); | 	strbuf_release(&msg); | ||||||
| 	return update_ref_status; | 	return update_ref_status; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -12,238 +12,329 @@ static const char * const git_update_ref_usage[] = { | ||||||
| 	NULL | 	NULL | ||||||
| }; | }; | ||||||
|  |  | ||||||
| static int updates_alloc; | static struct ref_transaction *transaction; | ||||||
| static int updates_count; |  | ||||||
| static const struct ref_update **updates; |  | ||||||
|  |  | ||||||
| static char line_termination = '\n'; | static char line_termination = '\n'; | ||||||
| static int update_flags; | static int update_flags; | ||||||
|  |  | ||||||
| static struct ref_update *update_alloc(void) | /* | ||||||
| { |  * Parse one whitespace- or NUL-terminated, possibly C-quoted argument | ||||||
| 	struct ref_update *update; |  * and append the result to arg.  Return a pointer to the terminator. | ||||||
|  |  * Die if there is an error in how the argument is C-quoted.  This | ||||||
| 	/* Allocate and zero-init a struct ref_update */ |  * function is only used if not -z. | ||||||
| 	update = xcalloc(1, sizeof(*update)); |  */ | ||||||
| 	ALLOC_GROW(updates, updates_count + 1, updates_alloc); |  | ||||||
| 	updates[updates_count++] = update; |  | ||||||
|  |  | ||||||
| 	/* Store and reset accumulated options */ |  | ||||||
| 	update->flags = update_flags; |  | ||||||
| 	update_flags = 0; |  | ||||||
|  |  | ||||||
| 	return update; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| static void update_store_ref_name(struct ref_update *update, |  | ||||||
| 				  const char *ref_name) |  | ||||||
| { |  | ||||||
| 	if (check_refname_format(ref_name, REFNAME_ALLOW_ONELEVEL)) |  | ||||||
| 		die("invalid ref format: %s", ref_name); |  | ||||||
| 	update->ref_name = xstrdup(ref_name); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| static void update_store_new_sha1(struct ref_update *update, |  | ||||||
| 				  const char *newvalue) |  | ||||||
| { |  | ||||||
| 	if (*newvalue && get_sha1(newvalue, update->new_sha1)) |  | ||||||
| 		die("invalid new value for ref %s: %s", |  | ||||||
| 		    update->ref_name, newvalue); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| static void update_store_old_sha1(struct ref_update *update, |  | ||||||
| 				  const char *oldvalue) |  | ||||||
| { |  | ||||||
| 	if (*oldvalue && get_sha1(oldvalue, update->old_sha1)) |  | ||||||
| 		die("invalid old value for ref %s: %s", |  | ||||||
| 		    update->ref_name, oldvalue); |  | ||||||
|  |  | ||||||
| 	/* We have an old value if non-empty, or if empty without -z */ |  | ||||||
| 	update->have_old = *oldvalue || line_termination; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| static const char *parse_arg(const char *next, struct strbuf *arg) | static const char *parse_arg(const char *next, struct strbuf *arg) | ||||||
| { | { | ||||||
| 	/* Parse SP-terminated, possibly C-quoted argument */ | 	if (*next == '"') { | ||||||
| 	if (*next != '"') | 		const char *orig = next; | ||||||
|  |  | ||||||
|  | 		if (unquote_c_style(arg, next, &next)) | ||||||
|  | 			die("badly quoted argument: %s", orig); | ||||||
|  | 		if (*next && !isspace(*next)) | ||||||
|  | 			die("unexpected character after quoted argument: %s", orig); | ||||||
|  | 	} else { | ||||||
| 		while (*next && !isspace(*next)) | 		while (*next && !isspace(*next)) | ||||||
| 			strbuf_addch(arg, *next++); | 			strbuf_addch(arg, *next++); | ||||||
| 	else if (unquote_c_style(arg, next, &next)) | 	} | ||||||
| 		die("badly quoted argument: %s", next); |  | ||||||
|  |  | ||||||
| 	/* Return position after the argument */ |  | ||||||
| 	return next; | 	return next; | ||||||
| } | } | ||||||
|  |  | ||||||
| static const char *parse_first_arg(const char *next, struct strbuf *arg) | /* | ||||||
|  |  * Parse the reference name immediately after "command SP".  If not | ||||||
|  |  * -z, then handle C-quoting.  Return a pointer to a newly allocated | ||||||
|  |  * string containing the name of the reference, or NULL if there was | ||||||
|  |  * an error.  Update *next to point at the character that terminates | ||||||
|  |  * the argument.  Die if C-quoting is malformed or the reference name | ||||||
|  |  * is invalid. | ||||||
|  |  */ | ||||||
|  | static char *parse_refname(struct strbuf *input, const char **next) | ||||||
| { | { | ||||||
| 	/* Parse argument immediately after "command SP" */ | 	struct strbuf ref = STRBUF_INIT; | ||||||
| 	strbuf_reset(arg); |  | ||||||
| 	if (line_termination) { | 	if (line_termination) { | ||||||
| 		/* Without -z, use the next argument */ | 		/* Without -z, use the next argument */ | ||||||
| 		next = parse_arg(next, arg); | 		*next = parse_arg(*next, &ref); | ||||||
| 	} else { | 	} else { | ||||||
| 		/* With -z, use rest of first NUL-terminated line */ | 		/* With -z, use everything up to the next NUL */ | ||||||
| 		strbuf_addstr(arg, next); | 		strbuf_addstr(&ref, *next); | ||||||
| 		next = next + arg->len; | 		*next += ref.len; | ||||||
| 	} | 	} | ||||||
| 	return next; |  | ||||||
|  | 	if (!ref.len) { | ||||||
|  | 		strbuf_release(&ref); | ||||||
|  | 		return NULL; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (check_refname_format(ref.buf, REFNAME_ALLOW_ONELEVEL)) | ||||||
|  | 		die("invalid ref format: %s", ref.buf); | ||||||
|  |  | ||||||
|  | 	return strbuf_detach(&ref, NULL); | ||||||
| } | } | ||||||
|  |  | ||||||
| static const char *parse_next_arg(const char *next, struct strbuf *arg) | /* | ||||||
|  |  * The value being parsed is <oldvalue> (as opposed to <newvalue>; the | ||||||
|  |  * difference affects which error messages are generated): | ||||||
|  |  */ | ||||||
|  | #define PARSE_SHA1_OLD 0x01 | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * For backwards compatibility, accept an empty string for update's | ||||||
|  |  * <newvalue> in binary mode to be equivalent to specifying zeros. | ||||||
|  |  */ | ||||||
|  | #define PARSE_SHA1_ALLOW_EMPTY 0x02 | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Parse an argument separator followed by the next argument, if any. | ||||||
|  |  * If there is an argument, convert it to a SHA-1, write it to sha1, | ||||||
|  |  * set *next to point at the character terminating the argument, and | ||||||
|  |  * return 0.  If there is no argument at all (not even the empty | ||||||
|  |  * string), return 1 and leave *next unchanged.  If the value is | ||||||
|  |  * provided but cannot be converted to a SHA-1, die.  flags can | ||||||
|  |  * include PARSE_SHA1_OLD and/or PARSE_SHA1_ALLOW_EMPTY. | ||||||
|  |  */ | ||||||
|  | static int parse_next_sha1(struct strbuf *input, const char **next, | ||||||
|  | 			   unsigned char *sha1, | ||||||
|  | 			   const char *command, const char *refname, | ||||||
|  | 			   int flags) | ||||||
| { | { | ||||||
| 	/* Parse next SP-terminated or NUL-terminated argument, if any */ | 	struct strbuf arg = STRBUF_INIT; | ||||||
| 	strbuf_reset(arg); | 	int ret = 0; | ||||||
|  |  | ||||||
|  | 	if (*next == input->buf + input->len) | ||||||
|  | 		goto eof; | ||||||
|  |  | ||||||
| 	if (line_termination) { | 	if (line_termination) { | ||||||
| 		/* Without -z, consume SP and use next argument */ | 		/* Without -z, consume SP and use next argument */ | ||||||
| 		if (!*next) | 		if (!**next || **next == line_termination) | ||||||
| 			return NULL; | 			return 1; | ||||||
| 		if (*next != ' ') | 		if (**next != ' ') | ||||||
| 			die("expected SP but got: %s", next); | 			die("%s %s: expected SP but got: %s", | ||||||
| 		next = parse_arg(next + 1, arg); | 			    command, refname, *next); | ||||||
|  | 		(*next)++; | ||||||
|  | 		*next = parse_arg(*next, &arg); | ||||||
|  | 		if (arg.len) { | ||||||
|  | 			if (get_sha1(arg.buf, sha1)) | ||||||
|  | 				goto invalid; | ||||||
|  | 		} else { | ||||||
|  | 			/* Without -z, an empty value means all zeros: */ | ||||||
|  | 			hashclr(sha1); | ||||||
|  | 		} | ||||||
| 	} else { | 	} else { | ||||||
| 		/* With -z, read the next NUL-terminated line */ | 		/* With -z, read the next NUL-terminated line */ | ||||||
| 		if (*next) | 		if (**next) | ||||||
| 			die("expected NUL but got: %s", next); | 			die("%s %s: expected NUL but got: %s", | ||||||
| 		if (strbuf_getline(arg, stdin, '\0') == EOF) | 			    command, refname, *next); | ||||||
| 			return NULL; | 		(*next)++; | ||||||
| 		next = arg->buf + arg->len; | 		if (*next == input->buf + input->len) | ||||||
|  | 			goto eof; | ||||||
|  | 		strbuf_addstr(&arg, *next); | ||||||
|  | 		*next += arg.len; | ||||||
|  |  | ||||||
|  | 		if (arg.len) { | ||||||
|  | 			if (get_sha1(arg.buf, sha1)) | ||||||
|  | 				goto invalid; | ||||||
|  | 		} else if (flags & PARSE_SHA1_ALLOW_EMPTY) { | ||||||
|  | 			/* With -z, treat an empty value as all zeros: */ | ||||||
|  | 			warning("%s %s: missing <newvalue>, treating as zero", | ||||||
|  | 				command, refname); | ||||||
|  | 			hashclr(sha1); | ||||||
|  | 		} else { | ||||||
|  | 			/* | ||||||
|  | 			 * With -z, an empty non-required value means | ||||||
|  | 			 * unspecified: | ||||||
|  | 			 */ | ||||||
|  | 			ret = 1; | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	strbuf_release(&arg); | ||||||
|  |  | ||||||
|  | 	return ret; | ||||||
|  |  | ||||||
|  |  invalid: | ||||||
|  | 	die(flags & PARSE_SHA1_OLD ? | ||||||
|  | 	    "%s %s: invalid <oldvalue>: %s" : | ||||||
|  | 	    "%s %s: invalid <newvalue>: %s", | ||||||
|  | 	    command, refname, arg.buf); | ||||||
|  |  | ||||||
|  |  eof: | ||||||
|  | 	die(flags & PARSE_SHA1_OLD ? | ||||||
|  | 	    "%s %s: unexpected end of input when reading <oldvalue>" : | ||||||
|  | 	    "%s %s: unexpected end of input when reading <newvalue>", | ||||||
|  | 	    command, refname); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * The following five parse_cmd_*() functions parse the corresponding | ||||||
|  |  * command.  In each case, next points at the character following the | ||||||
|  |  * command name and the following space.  They each return a pointer | ||||||
|  |  * to the character terminating the command, and die with an | ||||||
|  |  * explanatory message if there are any parsing problems.  All of | ||||||
|  |  * these functions handle either text or binary format input, | ||||||
|  |  * depending on how line_termination is set. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | static const char *parse_cmd_update(struct strbuf *input, const char *next) | ||||||
|  | { | ||||||
|  | 	char *refname; | ||||||
|  | 	unsigned char new_sha1[20]; | ||||||
|  | 	unsigned char old_sha1[20]; | ||||||
|  | 	int have_old; | ||||||
|  |  | ||||||
|  | 	refname = parse_refname(input, &next); | ||||||
|  | 	if (!refname) | ||||||
|  | 		die("update: missing <ref>"); | ||||||
|  |  | ||||||
|  | 	if (parse_next_sha1(input, &next, new_sha1, "update", refname, | ||||||
|  | 			    PARSE_SHA1_ALLOW_EMPTY)) | ||||||
|  | 		die("update %s: missing <newvalue>", refname); | ||||||
|  |  | ||||||
|  | 	have_old = !parse_next_sha1(input, &next, old_sha1, "update", refname, | ||||||
|  | 				    PARSE_SHA1_OLD); | ||||||
|  |  | ||||||
|  | 	if (*next != line_termination) | ||||||
|  | 		die("update %s: extra input: %s", refname, next); | ||||||
|  |  | ||||||
|  | 	ref_transaction_update(transaction, refname, new_sha1, old_sha1, | ||||||
|  | 			       update_flags, have_old); | ||||||
|  |  | ||||||
|  | 	update_flags = 0; | ||||||
|  | 	free(refname); | ||||||
|  |  | ||||||
| 	return next; | 	return next; | ||||||
| } | } | ||||||
|  |  | ||||||
| static void parse_cmd_update(const char *next) | static const char *parse_cmd_create(struct strbuf *input, const char *next) | ||||||
| { | { | ||||||
| 	struct strbuf ref = STRBUF_INIT; | 	char *refname; | ||||||
| 	struct strbuf newvalue = STRBUF_INIT; | 	unsigned char new_sha1[20]; | ||||||
| 	struct strbuf oldvalue = STRBUF_INIT; |  | ||||||
| 	struct ref_update *update; |  | ||||||
|  |  | ||||||
| 	update = update_alloc(); | 	refname = parse_refname(input, &next); | ||||||
|  | 	if (!refname) | ||||||
|  | 		die("create: missing <ref>"); | ||||||
|  |  | ||||||
| 	if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) | 	if (parse_next_sha1(input, &next, new_sha1, "create", refname, 0)) | ||||||
| 		update_store_ref_name(update, ref.buf); | 		die("create %s: missing <newvalue>", refname); | ||||||
| 	else |  | ||||||
| 		die("update line missing <ref>"); |  | ||||||
|  |  | ||||||
| 	if ((next = parse_next_arg(next, &newvalue)) != NULL) | 	if (is_null_sha1(new_sha1)) | ||||||
| 		update_store_new_sha1(update, newvalue.buf); | 		die("create %s: zero <newvalue>", refname); | ||||||
| 	else |  | ||||||
| 		die("update %s missing <newvalue>", ref.buf); |  | ||||||
|  |  | ||||||
| 	if ((next = parse_next_arg(next, &oldvalue)) != NULL) | 	if (*next != line_termination) | ||||||
| 		update_store_old_sha1(update, oldvalue.buf); | 		die("create %s: extra input: %s", refname, next); | ||||||
| 	else if(!line_termination) |  | ||||||
| 		die("update %s missing [<oldvalue>] NUL", ref.buf); |  | ||||||
|  |  | ||||||
| 	if (next && *next) | 	ref_transaction_create(transaction, refname, new_sha1, update_flags); | ||||||
| 		die("update %s has extra input: %s", ref.buf, next); |  | ||||||
|  | 	update_flags = 0; | ||||||
|  | 	free(refname); | ||||||
|  |  | ||||||
|  | 	return next; | ||||||
| } | } | ||||||
|  |  | ||||||
| static void parse_cmd_create(const char *next) | static const char *parse_cmd_delete(struct strbuf *input, const char *next) | ||||||
| { | { | ||||||
| 	struct strbuf ref = STRBUF_INIT; | 	char *refname; | ||||||
| 	struct strbuf newvalue = STRBUF_INIT; | 	unsigned char old_sha1[20]; | ||||||
| 	struct ref_update *update; | 	int have_old; | ||||||
|  |  | ||||||
| 	update = update_alloc(); | 	refname = parse_refname(input, &next); | ||||||
| 	update->have_old = 1; | 	if (!refname) | ||||||
|  | 		die("delete: missing <ref>"); | ||||||
|  |  | ||||||
| 	if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) | 	if (parse_next_sha1(input, &next, old_sha1, "delete", refname, | ||||||
| 		update_store_ref_name(update, ref.buf); | 			    PARSE_SHA1_OLD)) { | ||||||
| 	else | 		have_old = 0; | ||||||
| 		die("create line missing <ref>"); | 	} else { | ||||||
|  | 		if (is_null_sha1(old_sha1)) | ||||||
|  | 			die("delete %s: zero <oldvalue>", refname); | ||||||
|  | 		have_old = 1; | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if ((next = parse_next_arg(next, &newvalue)) != NULL) | 	if (*next != line_termination) | ||||||
| 		update_store_new_sha1(update, newvalue.buf); | 		die("delete %s: extra input: %s", refname, next); | ||||||
| 	else |  | ||||||
| 		die("create %s missing <newvalue>", ref.buf); |  | ||||||
| 	if (is_null_sha1(update->new_sha1)) |  | ||||||
| 		die("create %s given zero new value", ref.buf); |  | ||||||
|  |  | ||||||
| 	if (next && *next) | 	ref_transaction_delete(transaction, refname, old_sha1, | ||||||
| 		die("create %s has extra input: %s", ref.buf, next); | 			       update_flags, have_old); | ||||||
|  |  | ||||||
|  | 	update_flags = 0; | ||||||
|  | 	free(refname); | ||||||
|  |  | ||||||
|  | 	return next; | ||||||
| } | } | ||||||
|  |  | ||||||
| static void parse_cmd_delete(const char *next) | static const char *parse_cmd_verify(struct strbuf *input, const char *next) | ||||||
| { | { | ||||||
| 	struct strbuf ref = STRBUF_INIT; | 	char *refname; | ||||||
| 	struct strbuf oldvalue = STRBUF_INIT; | 	unsigned char new_sha1[20]; | ||||||
| 	struct ref_update *update; | 	unsigned char old_sha1[20]; | ||||||
|  | 	int have_old; | ||||||
|  |  | ||||||
| 	update = update_alloc(); | 	refname = parse_refname(input, &next); | ||||||
|  | 	if (!refname) | ||||||
|  | 		die("verify: missing <ref>"); | ||||||
|  |  | ||||||
| 	if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) | 	if (parse_next_sha1(input, &next, old_sha1, "verify", refname, | ||||||
| 		update_store_ref_name(update, ref.buf); | 			    PARSE_SHA1_OLD)) { | ||||||
| 	else | 		hashclr(new_sha1); | ||||||
| 		die("delete line missing <ref>"); | 		have_old = 0; | ||||||
|  | 	} else { | ||||||
|  | 		hashcpy(new_sha1, old_sha1); | ||||||
|  | 		have_old = 1; | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if ((next = parse_next_arg(next, &oldvalue)) != NULL) | 	if (*next != line_termination) | ||||||
| 		update_store_old_sha1(update, oldvalue.buf); | 		die("verify %s: extra input: %s", refname, next); | ||||||
| 	else if(!line_termination) |  | ||||||
| 		die("delete %s missing [<oldvalue>] NUL", ref.buf); |  | ||||||
| 	if (update->have_old && is_null_sha1(update->old_sha1)) |  | ||||||
| 		die("delete %s given zero old value", ref.buf); |  | ||||||
|  |  | ||||||
| 	if (next && *next) | 	ref_transaction_update(transaction, refname, new_sha1, old_sha1, | ||||||
| 		die("delete %s has extra input: %s", ref.buf, next); | 			       update_flags, have_old); | ||||||
|  |  | ||||||
|  | 	update_flags = 0; | ||||||
|  | 	free(refname); | ||||||
|  |  | ||||||
|  | 	return next; | ||||||
| } | } | ||||||
|  |  | ||||||
| static void parse_cmd_verify(const char *next) | static const char *parse_cmd_option(struct strbuf *input, const char *next) | ||||||
| { | { | ||||||
| 	struct strbuf ref = STRBUF_INIT; | 	if (!strncmp(next, "no-deref", 8) && next[8] == line_termination) | ||||||
| 	struct strbuf value = STRBUF_INIT; |  | ||||||
| 	struct ref_update *update; |  | ||||||
|  |  | ||||||
| 	update = update_alloc(); |  | ||||||
|  |  | ||||||
| 	if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) |  | ||||||
| 		update_store_ref_name(update, ref.buf); |  | ||||||
| 	else |  | ||||||
| 		die("verify line missing <ref>"); |  | ||||||
|  |  | ||||||
| 	if ((next = parse_next_arg(next, &value)) != NULL) { |  | ||||||
| 		update_store_old_sha1(update, value.buf); |  | ||||||
| 		update_store_new_sha1(update, value.buf); |  | ||||||
| 	} else if(!line_termination) |  | ||||||
| 		die("verify %s missing [<oldvalue>] NUL", ref.buf); |  | ||||||
|  |  | ||||||
| 	if (next && *next) |  | ||||||
| 		die("verify %s has extra input: %s", ref.buf, next); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| static void parse_cmd_option(const char *next) |  | ||||||
| { |  | ||||||
| 	if (!strcmp(next, "no-deref")) |  | ||||||
| 		update_flags |= REF_NODEREF; | 		update_flags |= REF_NODEREF; | ||||||
| 	else | 	else | ||||||
| 		die("option unknown: %s", next); | 		die("option unknown: %s", next); | ||||||
|  | 	return next + 8; | ||||||
| } | } | ||||||
|  |  | ||||||
| static void update_refs_stdin(void) | static void update_refs_stdin(void) | ||||||
| { | { | ||||||
| 	struct strbuf cmd = STRBUF_INIT; | 	struct strbuf input = STRBUF_INIT; | ||||||
|  | 	const char *next; | ||||||
|  |  | ||||||
|  | 	if (strbuf_read(&input, 0, 1000) < 0) | ||||||
|  | 		die_errno("could not read from stdin"); | ||||||
|  | 	next = input.buf; | ||||||
| 	/* Read each line dispatch its command */ | 	/* Read each line dispatch its command */ | ||||||
| 	while (strbuf_getline(&cmd, stdin, line_termination) != EOF) | 	while (next < input.buf + input.len) { | ||||||
| 		if (!cmd.buf[0]) | 		if (*next == line_termination) | ||||||
| 			die("empty command in input"); | 			die("empty command in input"); | ||||||
| 		else if (isspace(*cmd.buf)) | 		else if (isspace(*next)) | ||||||
| 			die("whitespace before command: %s", cmd.buf); | 			die("whitespace before command: %s", next); | ||||||
| 		else if (starts_with(cmd.buf, "update ")) | 		else if (starts_with(next, "update ")) | ||||||
| 			parse_cmd_update(cmd.buf + 7); | 			next = parse_cmd_update(&input, next + 7); | ||||||
| 		else if (starts_with(cmd.buf, "create ")) | 		else if (starts_with(next, "create ")) | ||||||
| 			parse_cmd_create(cmd.buf + 7); | 			next = parse_cmd_create(&input, next + 7); | ||||||
| 		else if (starts_with(cmd.buf, "delete ")) | 		else if (starts_with(next, "delete ")) | ||||||
| 			parse_cmd_delete(cmd.buf + 7); | 			next = parse_cmd_delete(&input, next + 7); | ||||||
| 		else if (starts_with(cmd.buf, "verify ")) | 		else if (starts_with(next, "verify ")) | ||||||
| 			parse_cmd_verify(cmd.buf + 7); | 			next = parse_cmd_verify(&input, next + 7); | ||||||
| 		else if (starts_with(cmd.buf, "option ")) | 		else if (starts_with(next, "option ")) | ||||||
| 			parse_cmd_option(cmd.buf + 7); | 			next = parse_cmd_option(&input, next + 7); | ||||||
| 		else | 		else | ||||||
| 			die("unknown command: %s", cmd.buf); | 			die("unknown command: %s", next); | ||||||
|  |  | ||||||
| 	strbuf_release(&cmd); | 		next++; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	strbuf_release(&input); | ||||||
| } | } | ||||||
|  |  | ||||||
| int cmd_update_ref(int argc, const char **argv, const char *prefix) | int cmd_update_ref(int argc, const char **argv, const char *prefix) | ||||||
|  | @ -268,12 +359,17 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) | ||||||
| 		die("Refusing to perform update with empty message."); | 		die("Refusing to perform update with empty message."); | ||||||
|  |  | ||||||
| 	if (read_stdin) { | 	if (read_stdin) { | ||||||
|  | 		int ret; | ||||||
|  | 		transaction = ref_transaction_begin(); | ||||||
|  |  | ||||||
| 		if (delete || no_deref || argc > 0) | 		if (delete || no_deref || argc > 0) | ||||||
| 			usage_with_options(git_update_ref_usage, options); | 			usage_with_options(git_update_ref_usage, options); | ||||||
| 		if (end_null) | 		if (end_null) | ||||||
| 			line_termination = '\0'; | 			line_termination = '\0'; | ||||||
| 		update_refs_stdin(); | 		update_refs_stdin(); | ||||||
| 		return update_refs(msg, updates, updates_count, DIE_ON_ERR); | 		ret = ref_transaction_commit(transaction, msg, | ||||||
|  | 					     UPDATE_REFS_DIE_ON_ERR); | ||||||
|  | 		return ret; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if (end_null) | 	if (end_null) | ||||||
|  | @ -305,5 +401,5 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) | ||||||
| 		return delete_ref(refname, oldval ? oldsha1 : NULL, flags); | 		return delete_ref(refname, oldval ? oldsha1 : NULL, flags); | ||||||
| 	else | 	else | ||||||
| 		return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL, | 		return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL, | ||||||
| 				  flags, DIE_ON_ERR); | 				  flags, UPDATE_REFS_DIE_ON_ERR); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -31,7 +31,8 @@ static int update_ref_env(const char *action, | ||||||
| 		rla = "(reflog update)"; | 		rla = "(reflog update)"; | ||||||
| 	if (snprintf(msg, sizeof(msg), "%s: %s", rla, action) >= sizeof(msg)) | 	if (snprintf(msg, sizeof(msg), "%s: %s", rla, action) >= sizeof(msg)) | ||||||
| 		warning("reflog message too long: %.*s...", 50, msg); | 		warning("reflog message too long: %.*s...", 50, msg); | ||||||
| 	return update_ref(msg, refname, sha1, oldval, 0, QUIET_ON_ERR); | 	return update_ref(msg, refname, sha1, oldval, 0, | ||||||
|  | 			  UPDATE_REFS_QUIET_ON_ERR); | ||||||
| } | } | ||||||
|  |  | ||||||
| static int update_local_ref(const char *name, | static int update_local_ref(const char *name, | ||||||
|  |  | ||||||
|  | @ -62,7 +62,7 @@ int notes_cache_write(struct notes_cache *c) | ||||||
| 	if (commit_tree(&msg, tree_sha1, NULL, commit_sha1, NULL, NULL) < 0) | 	if (commit_tree(&msg, tree_sha1, NULL, commit_sha1, NULL, NULL) < 0) | ||||||
| 		return -1; | 		return -1; | ||||||
| 	if (update_ref("update notes cache", c->tree.ref, commit_sha1, NULL, | 	if (update_ref("update notes cache", c->tree.ref, commit_sha1, NULL, | ||||||
| 		       0, QUIET_ON_ERR) < 0) | 		       0, UPDATE_REFS_QUIET_ON_ERR) < 0) | ||||||
| 		return -1; | 		return -1; | ||||||
|  |  | ||||||
| 	return 0; | 	return 0; | ||||||
|  |  | ||||||
|  | @ -48,7 +48,8 @@ void commit_notes(struct notes_tree *t, const char *msg) | ||||||
|  |  | ||||||
| 	create_notes_commit(t, NULL, &buf, commit_sha1); | 	create_notes_commit(t, NULL, &buf, commit_sha1); | ||||||
| 	strbuf_insert(&buf, 0, "notes: ", 7); /* commit message starts at index 7 */ | 	strbuf_insert(&buf, 0, "notes: ", 7); /* commit message starts at index 7 */ | ||||||
| 	update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, DIE_ON_ERR); | 	update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, | ||||||
|  | 		   UPDATE_REFS_DIE_ON_ERR); | ||||||
|  |  | ||||||
| 	strbuf_release(&buf); | 	strbuf_release(&buf); | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										195
									
								
								refs.c
								
								
								
								
							
							
						
						
									
										195
									
								
								refs.c
								
								
								
								
							|  | @ -3243,9 +3243,9 @@ static struct ref_lock *update_ref_lock(const char *refname, | ||||||
| 	if (!lock) { | 	if (!lock) { | ||||||
| 		const char *str = "Cannot lock the ref '%s'."; | 		const char *str = "Cannot lock the ref '%s'."; | ||||||
| 		switch (onerr) { | 		switch (onerr) { | ||||||
| 		case MSG_ON_ERR: error(str, refname); break; | 		case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break; | ||||||
| 		case DIE_ON_ERR: die(str, refname); break; | 		case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break; | ||||||
| 		case QUIET_ON_ERR: break; | 		case UPDATE_REFS_QUIET_ON_ERR: break; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return lock; | 	return lock; | ||||||
|  | @ -3258,15 +3258,118 @@ static int update_ref_write(const char *action, const char *refname, | ||||||
| 	if (write_ref_sha1(lock, sha1, action) < 0) { | 	if (write_ref_sha1(lock, sha1, action) < 0) { | ||||||
| 		const char *str = "Cannot update the ref '%s'."; | 		const char *str = "Cannot update the ref '%s'."; | ||||||
| 		switch (onerr) { | 		switch (onerr) { | ||||||
| 		case MSG_ON_ERR: error(str, refname); break; | 		case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break; | ||||||
| 		case DIE_ON_ERR: die(str, refname); break; | 		case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break; | ||||||
| 		case QUIET_ON_ERR: break; | 		case UPDATE_REFS_QUIET_ON_ERR: break; | ||||||
| 		} | 		} | ||||||
| 		return 1; | 		return 1; | ||||||
| 	} | 	} | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Information needed for a single ref update.  Set new_sha1 to the | ||||||
|  |  * new value or to zero to delete the ref.  To check the old value | ||||||
|  |  * while locking the ref, set have_old to 1 and set old_sha1 to the | ||||||
|  |  * value or to zero to ensure the ref does not exist before update. | ||||||
|  |  */ | ||||||
|  | struct ref_update { | ||||||
|  | 	unsigned char new_sha1[20]; | ||||||
|  | 	unsigned char old_sha1[20]; | ||||||
|  | 	int flags; /* REF_NODEREF? */ | ||||||
|  | 	int have_old; /* 1 if old_sha1 is valid, 0 otherwise */ | ||||||
|  | 	struct ref_lock *lock; | ||||||
|  | 	int type; | ||||||
|  | 	const char refname[FLEX_ARRAY]; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Data structure for holding a reference transaction, which can | ||||||
|  |  * consist of checks and updates to multiple references, carried out | ||||||
|  |  * as atomically as possible.  This structure is opaque to callers. | ||||||
|  |  */ | ||||||
|  | struct ref_transaction { | ||||||
|  | 	struct ref_update **updates; | ||||||
|  | 	size_t alloc; | ||||||
|  | 	size_t nr; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct ref_transaction *ref_transaction_begin(void) | ||||||
|  | { | ||||||
|  | 	return xcalloc(1, sizeof(struct ref_transaction)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void ref_transaction_free(struct ref_transaction *transaction) | ||||||
|  | { | ||||||
|  | 	int i; | ||||||
|  |  | ||||||
|  | 	for (i = 0; i < transaction->nr; i++) | ||||||
|  | 		free(transaction->updates[i]); | ||||||
|  |  | ||||||
|  | 	free(transaction->updates); | ||||||
|  | 	free(transaction); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ref_transaction_rollback(struct ref_transaction *transaction) | ||||||
|  | { | ||||||
|  | 	ref_transaction_free(transaction); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static struct ref_update *add_update(struct ref_transaction *transaction, | ||||||
|  | 				     const char *refname) | ||||||
|  | { | ||||||
|  | 	size_t len = strlen(refname); | ||||||
|  | 	struct ref_update *update = xcalloc(1, sizeof(*update) + len + 1); | ||||||
|  |  | ||||||
|  | 	strcpy((char *)update->refname, refname); | ||||||
|  | 	ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc); | ||||||
|  | 	transaction->updates[transaction->nr++] = update; | ||||||
|  | 	return update; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ref_transaction_update(struct ref_transaction *transaction, | ||||||
|  | 			    const char *refname, | ||||||
|  | 			    unsigned char *new_sha1, unsigned char *old_sha1, | ||||||
|  | 			    int flags, int have_old) | ||||||
|  | { | ||||||
|  | 	struct ref_update *update = add_update(transaction, refname); | ||||||
|  |  | ||||||
|  | 	hashcpy(update->new_sha1, new_sha1); | ||||||
|  | 	update->flags = flags; | ||||||
|  | 	update->have_old = have_old; | ||||||
|  | 	if (have_old) | ||||||
|  | 		hashcpy(update->old_sha1, old_sha1); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ref_transaction_create(struct ref_transaction *transaction, | ||||||
|  | 			    const char *refname, | ||||||
|  | 			    unsigned char *new_sha1, | ||||||
|  | 			    int flags) | ||||||
|  | { | ||||||
|  | 	struct ref_update *update = add_update(transaction, refname); | ||||||
|  |  | ||||||
|  | 	assert(!is_null_sha1(new_sha1)); | ||||||
|  | 	hashcpy(update->new_sha1, new_sha1); | ||||||
|  | 	hashclr(update->old_sha1); | ||||||
|  | 	update->flags = flags; | ||||||
|  | 	update->have_old = 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ref_transaction_delete(struct ref_transaction *transaction, | ||||||
|  | 			    const char *refname, | ||||||
|  | 			    unsigned char *old_sha1, | ||||||
|  | 			    int flags, int have_old) | ||||||
|  | { | ||||||
|  | 	struct ref_update *update = add_update(transaction, refname); | ||||||
|  |  | ||||||
|  | 	update->flags = flags; | ||||||
|  | 	update->have_old = have_old; | ||||||
|  | 	if (have_old) { | ||||||
|  | 		assert(!is_null_sha1(old_sha1)); | ||||||
|  | 		hashcpy(update->old_sha1, old_sha1); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| int update_ref(const char *action, const char *refname, | int update_ref(const char *action, const char *refname, | ||||||
| 	       const unsigned char *sha1, const unsigned char *oldval, | 	       const unsigned char *sha1, const unsigned char *oldval, | ||||||
| 	       int flags, enum action_on_err onerr) | 	       int flags, enum action_on_err onerr) | ||||||
|  | @ -3282,7 +3385,7 @@ static int ref_update_compare(const void *r1, const void *r2) | ||||||
| { | { | ||||||
| 	const struct ref_update * const *u1 = r1; | 	const struct ref_update * const *u1 = r1; | ||||||
| 	const struct ref_update * const *u2 = r2; | 	const struct ref_update * const *u2 = r2; | ||||||
| 	return strcmp((*u1)->ref_name, (*u2)->ref_name); | 	return strcmp((*u1)->refname, (*u2)->refname); | ||||||
| } | } | ||||||
|  |  | ||||||
| static int ref_update_reject_duplicates(struct ref_update **updates, int n, | static int ref_update_reject_duplicates(struct ref_update **updates, int n, | ||||||
|  | @ -3290,15 +3393,15 @@ static int ref_update_reject_duplicates(struct ref_update **updates, int n, | ||||||
| { | { | ||||||
| 	int i; | 	int i; | ||||||
| 	for (i = 1; i < n; i++) | 	for (i = 1; i < n; i++) | ||||||
| 		if (!strcmp(updates[i - 1]->ref_name, updates[i]->ref_name)) { | 		if (!strcmp(updates[i - 1]->refname, updates[i]->refname)) { | ||||||
| 			const char *str = | 			const char *str = | ||||||
| 				"Multiple updates for ref '%s' not allowed."; | 				"Multiple updates for ref '%s' not allowed."; | ||||||
| 			switch (onerr) { | 			switch (onerr) { | ||||||
| 			case MSG_ON_ERR: | 			case UPDATE_REFS_MSG_ON_ERR: | ||||||
| 				error(str, updates[i]->ref_name); break; | 				error(str, updates[i]->refname); break; | ||||||
| 			case DIE_ON_ERR: | 			case UPDATE_REFS_DIE_ON_ERR: | ||||||
| 				die(str, updates[i]->ref_name); break; | 				die(str, updates[i]->refname); break; | ||||||
| 			case QUIET_ON_ERR: | 			case UPDATE_REFS_QUIET_ON_ERR: | ||||||
| 				break; | 				break; | ||||||
| 			} | 			} | ||||||
| 			return 1; | 			return 1; | ||||||
|  | @ -3306,26 +3409,21 @@ static int ref_update_reject_duplicates(struct ref_update **updates, int n, | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| int update_refs(const char *action, const struct ref_update **updates_orig, | int ref_transaction_commit(struct ref_transaction *transaction, | ||||||
| 		int n, enum action_on_err onerr) | 			   const char *msg, enum action_on_err onerr) | ||||||
| { | { | ||||||
| 	int ret = 0, delnum = 0, i; | 	int ret = 0, delnum = 0, i; | ||||||
| 	struct ref_update **updates; |  | ||||||
| 	int *types; |  | ||||||
| 	struct ref_lock **locks; |  | ||||||
| 	const char **delnames; | 	const char **delnames; | ||||||
|  | 	int n = transaction->nr; | ||||||
|  | 	struct ref_update **updates = transaction->updates; | ||||||
|  |  | ||||||
| 	if (!updates_orig || !n) | 	if (!n) | ||||||
| 		return 0; | 		return 0; | ||||||
|  |  | ||||||
| 	/* Allocate work space */ | 	/* Allocate work space */ | ||||||
| 	updates = xmalloc(sizeof(*updates) * n); |  | ||||||
| 	types = xmalloc(sizeof(*types) * n); |  | ||||||
| 	locks = xcalloc(n, sizeof(*locks)); |  | ||||||
| 	delnames = xmalloc(sizeof(*delnames) * n); | 	delnames = xmalloc(sizeof(*delnames) * n); | ||||||
|  |  | ||||||
| 	/* Copy, sort, and reject duplicate refs */ | 	/* Copy, sort, and reject duplicate refs */ | ||||||
| 	memcpy(updates, updates_orig, sizeof(*updates) * n); |  | ||||||
| 	qsort(updates, n, sizeof(*updates), ref_update_compare); | 	qsort(updates, n, sizeof(*updates), ref_update_compare); | ||||||
| 	ret = ref_update_reject_duplicates(updates, n, onerr); | 	ret = ref_update_reject_duplicates(updates, n, onerr); | ||||||
| 	if (ret) | 	if (ret) | ||||||
|  | @ -3333,35 +3431,44 @@ int update_refs(const char *action, const struct ref_update **updates_orig, | ||||||
|  |  | ||||||
| 	/* Acquire all locks while verifying old values */ | 	/* Acquire all locks while verifying old values */ | ||||||
| 	for (i = 0; i < n; i++) { | 	for (i = 0; i < n; i++) { | ||||||
| 		locks[i] = update_ref_lock(updates[i]->ref_name, | 		struct ref_update *update = updates[i]; | ||||||
| 					   (updates[i]->have_old ? |  | ||||||
| 					    updates[i]->old_sha1 : NULL), | 		update->lock = update_ref_lock(update->refname, | ||||||
| 					   updates[i]->flags, | 					       (update->have_old ? | ||||||
| 					   &types[i], onerr); | 						update->old_sha1 : NULL), | ||||||
| 		if (!locks[i]) { | 					       update->flags, | ||||||
|  | 					       &update->type, onerr); | ||||||
|  | 		if (!update->lock) { | ||||||
| 			ret = 1; | 			ret = 1; | ||||||
| 			goto cleanup; | 			goto cleanup; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	/* Perform updates first so live commits remain referenced */ | 	/* Perform updates first so live commits remain referenced */ | ||||||
| 	for (i = 0; i < n; i++) | 	for (i = 0; i < n; i++) { | ||||||
| 		if (!is_null_sha1(updates[i]->new_sha1)) { | 		struct ref_update *update = updates[i]; | ||||||
| 			ret = update_ref_write(action, |  | ||||||
| 					       updates[i]->ref_name, | 		if (!is_null_sha1(update->new_sha1)) { | ||||||
| 					       updates[i]->new_sha1, | 			ret = update_ref_write(msg, | ||||||
| 					       locks[i], onerr); | 					       update->refname, | ||||||
| 			locks[i] = NULL; /* freed by update_ref_write */ | 					       update->new_sha1, | ||||||
|  | 					       update->lock, onerr); | ||||||
|  | 			update->lock = NULL; /* freed by update_ref_write */ | ||||||
| 			if (ret) | 			if (ret) | ||||||
| 				goto cleanup; | 				goto cleanup; | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	/* Perform deletes now that updates are safely completed */ | 	/* Perform deletes now that updates are safely completed */ | ||||||
| 	for (i = 0; i < n; i++) | 	for (i = 0; i < n; i++) { | ||||||
| 		if (locks[i]) { | 		struct ref_update *update = updates[i]; | ||||||
| 			delnames[delnum++] = locks[i]->ref_name; |  | ||||||
| 			ret |= delete_ref_loose(locks[i], types[i]); | 		if (update->lock) { | ||||||
|  | 			delnames[delnum++] = update->lock->ref_name; | ||||||
|  | 			ret |= delete_ref_loose(update->lock, update->type); | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	ret |= repack_without_refs(delnames, delnum); | 	ret |= repack_without_refs(delnames, delnum); | ||||||
| 	for (i = 0; i < delnum; i++) | 	for (i = 0; i < delnum; i++) | ||||||
| 		unlink_or_warn(git_path("logs/%s", delnames[i])); | 		unlink_or_warn(git_path("logs/%s", delnames[i])); | ||||||
|  | @ -3369,12 +3476,10 @@ int update_refs(const char *action, const struct ref_update **updates_orig, | ||||||
|  |  | ||||||
| cleanup: | cleanup: | ||||||
| 	for (i = 0; i < n; i++) | 	for (i = 0; i < n; i++) | ||||||
| 		if (locks[i]) | 		if (updates[i]->lock) | ||||||
| 			unlock_ref(locks[i]); | 			unlock_ref(updates[i]->lock); | ||||||
| 	free(updates); |  | ||||||
| 	free(types); |  | ||||||
| 	free(locks); |  | ||||||
| 	free(delnames); | 	free(delnames); | ||||||
|  | 	ref_transaction_free(transaction); | ||||||
| 	return ret; | 	return ret; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										94
									
								
								refs.h
								
								
								
								
							
							
						
						
									
										94
									
								
								refs.h
								
								
								
								
							|  | @ -10,19 +10,7 @@ struct ref_lock { | ||||||
| 	int force_write; | 	int force_write; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /** | struct ref_transaction; | ||||||
|  * Information needed for a single ref update.  Set new_sha1 to the |  | ||||||
|  * new value or to zero to delete the ref.  To check the old value |  | ||||||
|  * while locking the ref, set have_old to 1 and set old_sha1 to the |  | ||||||
|  * value or to zero to ensure the ref does not exist before update. |  | ||||||
|  */ |  | ||||||
| struct ref_update { |  | ||||||
| 	const char *ref_name; |  | ||||||
| 	unsigned char new_sha1[20]; |  | ||||||
| 	unsigned char old_sha1[20]; |  | ||||||
| 	int flags; /* REF_NODEREF? */ |  | ||||||
| 	int have_old; /* 1 if old_sha1 is valid, 0 otherwise */ |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Bit values set in the flags argument passed to each_ref_fn(): |  * Bit values set in the flags argument passed to each_ref_fn(): | ||||||
|  | @ -166,7 +154,7 @@ extern void unlock_ref(struct ref_lock *lock); | ||||||
| extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg); | extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg); | ||||||
|  |  | ||||||
| /** Setup reflog before using. **/ | /** Setup reflog before using. **/ | ||||||
| int log_ref_setup(const char *ref_name, char *logfile, int bufsize); | int log_ref_setup(const char *refname, char *logfile, int bufsize); | ||||||
|  |  | ||||||
| /** Reads log for the value of ref during at_time. **/ | /** Reads log for the value of ref during at_time. **/ | ||||||
| extern int read_ref_at(const char *refname, unsigned long at_time, int cnt, | extern int read_ref_at(const char *refname, unsigned long at_time, int cnt, | ||||||
|  | @ -214,18 +202,80 @@ extern int rename_ref(const char *oldref, const char *newref, const char *logmsg | ||||||
|  */ |  */ | ||||||
| extern int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1); | extern int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1); | ||||||
|  |  | ||||||
| /** lock a ref and then write its file */ | enum action_on_err { | ||||||
| enum action_on_err { MSG_ON_ERR, DIE_ON_ERR, QUIET_ON_ERR }; | 	UPDATE_REFS_MSG_ON_ERR, | ||||||
|  | 	UPDATE_REFS_DIE_ON_ERR, | ||||||
|  | 	UPDATE_REFS_QUIET_ON_ERR | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Begin a reference transaction.  The reference transaction must | ||||||
|  |  * eventually be commited using ref_transaction_commit() or rolled | ||||||
|  |  * back using ref_transaction_rollback(). | ||||||
|  |  */ | ||||||
|  | struct ref_transaction *ref_transaction_begin(void); | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Roll back a ref_transaction and free all associated data. | ||||||
|  |  */ | ||||||
|  | void ref_transaction_rollback(struct ref_transaction *transaction); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * The following functions add a reference check or update to a | ||||||
|  |  * ref_transaction.  In all of them, refname is the name of the | ||||||
|  |  * reference to be affected.  The functions make internal copies of | ||||||
|  |  * refname, so the caller retains ownership of the parameter.  flags | ||||||
|  |  * can be REF_NODEREF; it is passed to update_ref_lock(). | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Add a reference update to transaction.  new_sha1 is the value that | ||||||
|  |  * the reference should have after the update, or zeros if it should | ||||||
|  |  * be deleted.  If have_old is true, then old_sha1 holds the value | ||||||
|  |  * that the reference should have had before the update, or zeros if | ||||||
|  |  * it must not have existed beforehand. | ||||||
|  |  */ | ||||||
|  | void ref_transaction_update(struct ref_transaction *transaction, | ||||||
|  | 			    const char *refname, | ||||||
|  | 			    unsigned char *new_sha1, unsigned char *old_sha1, | ||||||
|  | 			    int flags, int have_old); | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Add a reference creation to transaction.  new_sha1 is the value | ||||||
|  |  * that the reference should have after the update; it must not be the | ||||||
|  |  * null SHA-1.  It is verified that the reference does not exist | ||||||
|  |  * already. | ||||||
|  |  */ | ||||||
|  | void ref_transaction_create(struct ref_transaction *transaction, | ||||||
|  | 			    const char *refname, | ||||||
|  | 			    unsigned char *new_sha1, | ||||||
|  | 			    int flags); | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Add a reference deletion to transaction.  If have_old is true, then | ||||||
|  |  * old_sha1 holds the value that the reference should have had before | ||||||
|  |  * the update (which must not be the null SHA-1). | ||||||
|  |  */ | ||||||
|  | void ref_transaction_delete(struct ref_transaction *transaction, | ||||||
|  | 			    const char *refname, | ||||||
|  | 			    unsigned char *old_sha1, | ||||||
|  | 			    int flags, int have_old); | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Commit all of the changes that have been queued in transaction, as | ||||||
|  |  * atomically as possible.  Return a nonzero value if there is a | ||||||
|  |  * problem.  The ref_transaction is freed by this function. | ||||||
|  |  */ | ||||||
|  | int ref_transaction_commit(struct ref_transaction *transaction, | ||||||
|  | 			   const char *msg, enum action_on_err onerr); | ||||||
|  |  | ||||||
|  | /** Lock a ref and then write its file */ | ||||||
| int update_ref(const char *action, const char *refname, | int update_ref(const char *action, const char *refname, | ||||||
| 		const unsigned char *sha1, const unsigned char *oldval, | 		const unsigned char *sha1, const unsigned char *oldval, | ||||||
| 		int flags, enum action_on_err onerr); | 		int flags, enum action_on_err onerr); | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Lock all refs and then perform all modifications. |  | ||||||
|  */ |  | ||||||
| int update_refs(const char *action, const struct ref_update **updates, |  | ||||||
| 		int n, enum action_on_err onerr); |  | ||||||
|  |  | ||||||
| extern int parse_hide_refs_config(const char *var, const char *value, const char *); | extern int parse_hide_refs_config(const char *var, const char *value, const char *); | ||||||
| extern int ref_is_hidden(const char *); | extern int ref_is_hidden(const char *); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @ -350,22 +350,28 @@ test_expect_success 'stdin fails on unknown command' ' | ||||||
| 	grep "fatal: unknown command: unknown $a" err | 	grep "fatal: unknown command: unknown $a" err | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'stdin fails on badly quoted input' ' | test_expect_success 'stdin fails on unbalanced quotes' ' | ||||||
| 	echo "create $a \"master" >stdin && | 	echo "create $a \"master" >stdin && | ||||||
| 	test_must_fail git update-ref --stdin <stdin 2>err && | 	test_must_fail git update-ref --stdin <stdin 2>err && | ||||||
| 	grep "fatal: badly quoted argument: \\\"master" err | 	grep "fatal: badly quoted argument: \\\"master" err | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'stdin fails on arguments not separated by space' ' | test_expect_success 'stdin fails on invalid escape' ' | ||||||
|  | 	echo "create $a \"ma\zter\"" >stdin && | ||||||
|  | 	test_must_fail git update-ref --stdin <stdin 2>err && | ||||||
|  | 	grep "fatal: badly quoted argument: \\\"ma\\\\zter\\\"" err | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'stdin fails on junk after quoted argument' ' | ||||||
| 	echo "create \"$a\"master" >stdin && | 	echo "create \"$a\"master" >stdin && | ||||||
| 	test_must_fail git update-ref --stdin <stdin 2>err && | 	test_must_fail git update-ref --stdin <stdin 2>err && | ||||||
| 	grep "fatal: expected SP but got: master" err | 	grep "fatal: unexpected character after quoted argument: \\\"$a\\\"master" err | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'stdin fails create with no ref' ' | test_expect_success 'stdin fails create with no ref' ' | ||||||
| 	echo "create " >stdin && | 	echo "create " >stdin && | ||||||
| 	test_must_fail git update-ref --stdin <stdin 2>err && | 	test_must_fail git update-ref --stdin <stdin 2>err && | ||||||
| 	grep "fatal: create line missing <ref>" err | 	grep "fatal: create: missing <ref>" err | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'stdin fails create with bad ref name' ' | test_expect_success 'stdin fails create with bad ref name' ' | ||||||
|  | @ -377,19 +383,19 @@ test_expect_success 'stdin fails create with bad ref name' ' | ||||||
| test_expect_success 'stdin fails create with no new value' ' | test_expect_success 'stdin fails create with no new value' ' | ||||||
| 	echo "create $a" >stdin && | 	echo "create $a" >stdin && | ||||||
| 	test_must_fail git update-ref --stdin <stdin 2>err && | 	test_must_fail git update-ref --stdin <stdin 2>err && | ||||||
| 	grep "fatal: create $a missing <newvalue>" err | 	grep "fatal: create $a: missing <newvalue>" err | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'stdin fails create with too many arguments' ' | test_expect_success 'stdin fails create with too many arguments' ' | ||||||
| 	echo "create $a $m $m" >stdin && | 	echo "create $a $m $m" >stdin && | ||||||
| 	test_must_fail git update-ref --stdin <stdin 2>err && | 	test_must_fail git update-ref --stdin <stdin 2>err && | ||||||
| 	grep "fatal: create $a has extra input:  $m" err | 	grep "fatal: create $a: extra input:  $m" err | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'stdin fails update with no ref' ' | test_expect_success 'stdin fails update with no ref' ' | ||||||
| 	echo "update " >stdin && | 	echo "update " >stdin && | ||||||
| 	test_must_fail git update-ref --stdin <stdin 2>err && | 	test_must_fail git update-ref --stdin <stdin 2>err && | ||||||
| 	grep "fatal: update line missing <ref>" err | 	grep "fatal: update: missing <ref>" err | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'stdin fails update with bad ref name' ' | test_expect_success 'stdin fails update with bad ref name' ' | ||||||
|  | @ -401,19 +407,19 @@ test_expect_success 'stdin fails update with bad ref name' ' | ||||||
| test_expect_success 'stdin fails update with no new value' ' | test_expect_success 'stdin fails update with no new value' ' | ||||||
| 	echo "update $a" >stdin && | 	echo "update $a" >stdin && | ||||||
| 	test_must_fail git update-ref --stdin <stdin 2>err && | 	test_must_fail git update-ref --stdin <stdin 2>err && | ||||||
| 	grep "fatal: update $a missing <newvalue>" err | 	grep "fatal: update $a: missing <newvalue>" err | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'stdin fails update with too many arguments' ' | test_expect_success 'stdin fails update with too many arguments' ' | ||||||
| 	echo "update $a $m $m $m" >stdin && | 	echo "update $a $m $m $m" >stdin && | ||||||
| 	test_must_fail git update-ref --stdin <stdin 2>err && | 	test_must_fail git update-ref --stdin <stdin 2>err && | ||||||
| 	grep "fatal: update $a has extra input:  $m" err | 	grep "fatal: update $a: extra input:  $m" err | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'stdin fails delete with no ref' ' | test_expect_success 'stdin fails delete with no ref' ' | ||||||
| 	echo "delete " >stdin && | 	echo "delete " >stdin && | ||||||
| 	test_must_fail git update-ref --stdin <stdin 2>err && | 	test_must_fail git update-ref --stdin <stdin 2>err && | ||||||
| 	grep "fatal: delete line missing <ref>" err | 	grep "fatal: delete: missing <ref>" err | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'stdin fails delete with bad ref name' ' | test_expect_success 'stdin fails delete with bad ref name' ' | ||||||
|  | @ -425,13 +431,13 @@ test_expect_success 'stdin fails delete with bad ref name' ' | ||||||
| test_expect_success 'stdin fails delete with too many arguments' ' | test_expect_success 'stdin fails delete with too many arguments' ' | ||||||
| 	echo "delete $a $m $m" >stdin && | 	echo "delete $a $m $m" >stdin && | ||||||
| 	test_must_fail git update-ref --stdin <stdin 2>err && | 	test_must_fail git update-ref --stdin <stdin 2>err && | ||||||
| 	grep "fatal: delete $a has extra input:  $m" err | 	grep "fatal: delete $a: extra input:  $m" err | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'stdin fails verify with too many arguments' ' | test_expect_success 'stdin fails verify with too many arguments' ' | ||||||
| 	echo "verify $a $m $m" >stdin && | 	echo "verify $a $m $m" >stdin && | ||||||
| 	test_must_fail git update-ref --stdin <stdin 2>err && | 	test_must_fail git update-ref --stdin <stdin 2>err && | ||||||
| 	grep "fatal: verify $a has extra input:  $m" err | 	grep "fatal: verify $a: extra input:  $m" err | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'stdin fails option with unknown name' ' | test_expect_success 'stdin fails option with unknown name' ' | ||||||
|  | @ -458,6 +464,24 @@ test_expect_success 'stdin create ref works' ' | ||||||
| 	test_cmp expect actual | 	test_cmp expect actual | ||||||
| ' | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'stdin succeeds with quoted argument' ' | ||||||
|  | 	git update-ref -d $a && | ||||||
|  | 	echo "create $a \"$m\"" >stdin && | ||||||
|  | 	git update-ref --stdin <stdin && | ||||||
|  | 	git rev-parse $m >expect && | ||||||
|  | 	git rev-parse $a >actual && | ||||||
|  | 	test_cmp expect actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'stdin succeeds with escaped character' ' | ||||||
|  | 	git update-ref -d $a && | ||||||
|  | 	echo "create $a \"ma\\163ter\"" >stdin && | ||||||
|  | 	git update-ref --stdin <stdin && | ||||||
|  | 	git rev-parse $m >expect && | ||||||
|  | 	git rev-parse $a >actual && | ||||||
|  | 	test_cmp expect actual | ||||||
|  | ' | ||||||
|  |  | ||||||
| test_expect_success 'stdin update ref creates with zero old value' ' | test_expect_success 'stdin update ref creates with zero old value' ' | ||||||
| 	echo "update $b $m $Z" >stdin && | 	echo "update $b $m $Z" >stdin && | ||||||
| 	git update-ref --stdin <stdin && | 	git update-ref --stdin <stdin && | ||||||
|  | @ -494,21 +518,21 @@ test_expect_success 'stdin update ref fails with wrong old value' ' | ||||||
| test_expect_success 'stdin update ref fails with bad old value' ' | test_expect_success 'stdin update ref fails with bad old value' ' | ||||||
| 	echo "update $c $m does-not-exist" >stdin && | 	echo "update $c $m does-not-exist" >stdin && | ||||||
| 	test_must_fail git update-ref --stdin <stdin 2>err && | 	test_must_fail git update-ref --stdin <stdin 2>err && | ||||||
| 	grep "fatal: invalid old value for ref $c: does-not-exist" err && | 	grep "fatal: update $c: invalid <oldvalue>: does-not-exist" err && | ||||||
| 	test_must_fail git rev-parse --verify -q $c | 	test_must_fail git rev-parse --verify -q $c | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'stdin create ref fails with bad new value' ' | test_expect_success 'stdin create ref fails with bad new value' ' | ||||||
| 	echo "create $c does-not-exist" >stdin && | 	echo "create $c does-not-exist" >stdin && | ||||||
| 	test_must_fail git update-ref --stdin <stdin 2>err && | 	test_must_fail git update-ref --stdin <stdin 2>err && | ||||||
| 	grep "fatal: invalid new value for ref $c: does-not-exist" err && | 	grep "fatal: create $c: invalid <newvalue>: does-not-exist" err && | ||||||
| 	test_must_fail git rev-parse --verify -q $c | 	test_must_fail git rev-parse --verify -q $c | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'stdin create ref fails with zero new value' ' | test_expect_success 'stdin create ref fails with zero new value' ' | ||||||
| 	echo "create $c " >stdin && | 	echo "create $c " >stdin && | ||||||
| 	test_must_fail git update-ref --stdin <stdin 2>err && | 	test_must_fail git update-ref --stdin <stdin 2>err && | ||||||
| 	grep "fatal: create $c given zero new value" err && | 	grep "fatal: create $c: zero <newvalue>" err && | ||||||
| 	test_must_fail git rev-parse --verify -q $c | 	test_must_fail git rev-parse --verify -q $c | ||||||
| ' | ' | ||||||
|  |  | ||||||
|  | @ -532,7 +556,7 @@ test_expect_success 'stdin delete ref fails with wrong old value' ' | ||||||
| test_expect_success 'stdin delete ref fails with zero old value' ' | test_expect_success 'stdin delete ref fails with zero old value' ' | ||||||
| 	echo "delete $a " >stdin && | 	echo "delete $a " >stdin && | ||||||
| 	test_must_fail git update-ref --stdin <stdin 2>err && | 	test_must_fail git update-ref --stdin <stdin 2>err && | ||||||
| 	grep "fatal: delete $a given zero old value" err && | 	grep "fatal: delete $a: zero <oldvalue>" err && | ||||||
| 	git rev-parse $m >expect && | 	git rev-parse $m >expect && | ||||||
| 	git rev-parse $a >actual && | 	git rev-parse $a >actual && | ||||||
| 	test_cmp expect actual | 	test_cmp expect actual | ||||||
|  | @ -673,7 +697,7 @@ test_expect_success 'stdin -z fails on unknown command' ' | ||||||
| test_expect_success 'stdin -z fails create with no ref' ' | test_expect_success 'stdin -z fails create with no ref' ' | ||||||
| 	printf $F "create " >stdin && | 	printf $F "create " >stdin && | ||||||
| 	test_must_fail git update-ref -z --stdin <stdin 2>err && | 	test_must_fail git update-ref -z --stdin <stdin 2>err && | ||||||
| 	grep "fatal: create line missing <ref>" err | 	grep "fatal: create: missing <ref>" err | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'stdin -z fails create with bad ref name' ' | test_expect_success 'stdin -z fails create with bad ref name' ' | ||||||
|  | @ -685,7 +709,7 @@ test_expect_success 'stdin -z fails create with bad ref name' ' | ||||||
| test_expect_success 'stdin -z fails create with no new value' ' | test_expect_success 'stdin -z fails create with no new value' ' | ||||||
| 	printf $F "create $a" >stdin && | 	printf $F "create $a" >stdin && | ||||||
| 	test_must_fail git update-ref -z --stdin <stdin 2>err && | 	test_must_fail git update-ref -z --stdin <stdin 2>err && | ||||||
| 	grep "fatal: create $a missing <newvalue>" err | 	grep "fatal: create $a: unexpected end of input when reading <newvalue>" err | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'stdin -z fails create with too many arguments' ' | test_expect_success 'stdin -z fails create with too many arguments' ' | ||||||
|  | @ -697,25 +721,39 @@ test_expect_success 'stdin -z fails create with too many arguments' ' | ||||||
| test_expect_success 'stdin -z fails update with no ref' ' | test_expect_success 'stdin -z fails update with no ref' ' | ||||||
| 	printf $F "update " >stdin && | 	printf $F "update " >stdin && | ||||||
| 	test_must_fail git update-ref -z --stdin <stdin 2>err && | 	test_must_fail git update-ref -z --stdin <stdin 2>err && | ||||||
| 	grep "fatal: update line missing <ref>" err | 	grep "fatal: update: missing <ref>" err | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'stdin -z fails update with too few args' ' | ||||||
|  | 	printf $F "update $a" "$m" >stdin && | ||||||
|  | 	test_must_fail git update-ref -z --stdin <stdin 2>err && | ||||||
|  | 	grep "fatal: update $a: unexpected end of input when reading <oldvalue>" err | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'stdin -z fails update with bad ref name' ' | test_expect_success 'stdin -z fails update with bad ref name' ' | ||||||
| 	printf $F "update ~a" "$m" >stdin && | 	printf $F "update ~a" "$m" "" >stdin && | ||||||
| 	test_must_fail git update-ref -z --stdin <stdin 2>err && | 	test_must_fail git update-ref -z --stdin <stdin 2>err && | ||||||
| 	grep "fatal: invalid ref format: ~a" err | 	grep "fatal: invalid ref format: ~a" err | ||||||
| ' | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'stdin -z emits warning with empty new value' ' | ||||||
|  | 	git update-ref $a $m && | ||||||
|  | 	printf $F "update $a" "" "" >stdin && | ||||||
|  | 	git update-ref -z --stdin <stdin 2>err && | ||||||
|  | 	grep "warning: update $a: missing <newvalue>, treating as zero" err && | ||||||
|  | 	test_must_fail git rev-parse --verify -q $a | ||||||
|  | ' | ||||||
|  |  | ||||||
| test_expect_success 'stdin -z fails update with no new value' ' | test_expect_success 'stdin -z fails update with no new value' ' | ||||||
| 	printf $F "update $a" >stdin && | 	printf $F "update $a" >stdin && | ||||||
| 	test_must_fail git update-ref -z --stdin <stdin 2>err && | 	test_must_fail git update-ref -z --stdin <stdin 2>err && | ||||||
| 	grep "fatal: update $a missing <newvalue>" err | 	grep "fatal: update $a: unexpected end of input when reading <newvalue>" err | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'stdin -z fails update with no old value' ' | test_expect_success 'stdin -z fails update with no old value' ' | ||||||
| 	printf $F "update $a" "$m" >stdin && | 	printf $F "update $a" "$m" >stdin && | ||||||
| 	test_must_fail git update-ref -z --stdin <stdin 2>err && | 	test_must_fail git update-ref -z --stdin <stdin 2>err && | ||||||
| 	grep "fatal: update $a missing \\[<oldvalue>\\] NUL" err | 	grep "fatal: update $a: unexpected end of input when reading <oldvalue>" err | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'stdin -z fails update with too many arguments' ' | test_expect_success 'stdin -z fails update with too many arguments' ' | ||||||
|  | @ -727,7 +765,7 @@ test_expect_success 'stdin -z fails update with too many arguments' ' | ||||||
| test_expect_success 'stdin -z fails delete with no ref' ' | test_expect_success 'stdin -z fails delete with no ref' ' | ||||||
| 	printf $F "delete " >stdin && | 	printf $F "delete " >stdin && | ||||||
| 	test_must_fail git update-ref -z --stdin <stdin 2>err && | 	test_must_fail git update-ref -z --stdin <stdin 2>err && | ||||||
| 	grep "fatal: delete line missing <ref>" err | 	grep "fatal: delete: missing <ref>" err | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'stdin -z fails delete with bad ref name' ' | test_expect_success 'stdin -z fails delete with bad ref name' ' | ||||||
|  | @ -739,7 +777,7 @@ test_expect_success 'stdin -z fails delete with bad ref name' ' | ||||||
| test_expect_success 'stdin -z fails delete with no old value' ' | test_expect_success 'stdin -z fails delete with no old value' ' | ||||||
| 	printf $F "delete $a" >stdin && | 	printf $F "delete $a" >stdin && | ||||||
| 	test_must_fail git update-ref -z --stdin <stdin 2>err && | 	test_must_fail git update-ref -z --stdin <stdin 2>err && | ||||||
| 	grep "fatal: delete $a missing \\[<oldvalue>\\] NUL" err | 	grep "fatal: delete $a: unexpected end of input when reading <oldvalue>" err | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'stdin -z fails delete with too many arguments' ' | test_expect_success 'stdin -z fails delete with too many arguments' ' | ||||||
|  | @ -757,7 +795,7 @@ test_expect_success 'stdin -z fails verify with too many arguments' ' | ||||||
| test_expect_success 'stdin -z fails verify with no old value' ' | test_expect_success 'stdin -z fails verify with no old value' ' | ||||||
| 	printf $F "verify $a" >stdin && | 	printf $F "verify $a" >stdin && | ||||||
| 	test_must_fail git update-ref -z --stdin <stdin 2>err && | 	test_must_fail git update-ref -z --stdin <stdin 2>err && | ||||||
| 	grep "fatal: verify $a missing \\[<oldvalue>\\] NUL" err | 	grep "fatal: verify $a: unexpected end of input when reading <oldvalue>" err | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'stdin -z fails option with unknown name' ' | test_expect_success 'stdin -z fails option with unknown name' ' | ||||||
|  | @ -816,7 +854,7 @@ test_expect_success 'stdin -z update ref fails with wrong old value' ' | ||||||
| test_expect_success 'stdin -z update ref fails with bad old value' ' | test_expect_success 'stdin -z update ref fails with bad old value' ' | ||||||
| 	printf $F "update $c" "$m" "does-not-exist" >stdin && | 	printf $F "update $c" "$m" "does-not-exist" >stdin && | ||||||
| 	test_must_fail git update-ref -z --stdin <stdin 2>err && | 	test_must_fail git update-ref -z --stdin <stdin 2>err && | ||||||
| 	grep "fatal: invalid old value for ref $c: does-not-exist" err && | 	grep "fatal: update $c: invalid <oldvalue>: does-not-exist" err && | ||||||
| 	test_must_fail git rev-parse --verify -q $c | 	test_must_fail git rev-parse --verify -q $c | ||||||
| ' | ' | ||||||
|  |  | ||||||
|  | @ -834,14 +872,14 @@ test_expect_success 'stdin -z create ref fails with bad new value' ' | ||||||
| 	git update-ref -d "$c" && | 	git update-ref -d "$c" && | ||||||
| 	printf $F "create $c" "does-not-exist" >stdin && | 	printf $F "create $c" "does-not-exist" >stdin && | ||||||
| 	test_must_fail git update-ref -z --stdin <stdin 2>err && | 	test_must_fail git update-ref -z --stdin <stdin 2>err && | ||||||
| 	grep "fatal: invalid new value for ref $c: does-not-exist" err && | 	grep "fatal: create $c: invalid <newvalue>: does-not-exist" err && | ||||||
| 	test_must_fail git rev-parse --verify -q $c | 	test_must_fail git rev-parse --verify -q $c | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'stdin -z create ref fails with zero new value' ' | test_expect_success 'stdin -z create ref fails with empty new value' ' | ||||||
| 	printf $F "create $c" "" >stdin && | 	printf $F "create $c" "" >stdin && | ||||||
| 	test_must_fail git update-ref -z --stdin <stdin 2>err && | 	test_must_fail git update-ref -z --stdin <stdin 2>err && | ||||||
| 	grep "fatal: create $c given zero new value" err && | 	grep "fatal: create $c: missing <newvalue>" err && | ||||||
| 	test_must_fail git rev-parse --verify -q $c | 	test_must_fail git rev-parse --verify -q $c | ||||||
| ' | ' | ||||||
|  |  | ||||||
|  | @ -865,7 +903,7 @@ test_expect_success 'stdin -z delete ref fails with wrong old value' ' | ||||||
| test_expect_success 'stdin -z delete ref fails with zero old value' ' | test_expect_success 'stdin -z delete ref fails with zero old value' ' | ||||||
| 	printf $F "delete $a" "$Z" >stdin && | 	printf $F "delete $a" "$Z" >stdin && | ||||||
| 	test_must_fail git update-ref -z --stdin <stdin 2>err && | 	test_must_fail git update-ref -z --stdin <stdin 2>err && | ||||||
| 	grep "fatal: delete $a given zero old value" err && | 	grep "fatal: delete $a: zero <oldvalue>" err && | ||||||
| 	git rev-parse $m >expect && | 	git rev-parse $m >expect && | ||||||
| 	git rev-parse $a >actual && | 	git rev-parse $a >actual && | ||||||
| 	test_cmp expect actual | 	test_cmp expect actual | ||||||
|  | @ -923,7 +961,7 @@ test_expect_success 'stdin -z update refs works with identity updates' ' | ||||||
|  |  | ||||||
| test_expect_success 'stdin -z update refs fails with wrong old value' ' | test_expect_success 'stdin -z update refs fails with wrong old value' ' | ||||||
| 	git update-ref $c $m && | 	git update-ref $c $m && | ||||||
| 	printf $F "update $a" "$m" "$m" "update $b" "$m" "$m" "update $c" "" "$Z" >stdin && | 	printf $F "update $a" "$m" "$m" "update $b" "$m" "$m" "update $c" "$m" "$Z" >stdin && | ||||||
| 	test_must_fail git update-ref -z --stdin <stdin 2>err && | 	test_must_fail git update-ref -z --stdin <stdin 2>err && | ||||||
| 	grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err && | 	grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err && | ||||||
| 	git rev-parse $m >expect && | 	git rev-parse $m >expect && | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 Junio C Hamano
						Junio C Hamano