sequencer (rebase -i): implement the 'edit' command
This patch is a straight-forward reimplementation of the `edit` operation of the interactive rebase command. Well, not *quite* straight-forward: when stopping, the `edit` command wants to write the `patch` file (which is not only the patch, but includes the commit message and author information). To that end, this patch requires the earlier work that taught the log-tree machinery to respect the `file` setting of rev_info->diffopt to write to a file stream different than stdout. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>maint
							parent
							
								
									25c4366782
								
							
						
					
					
						commit
						56dc3ab04b
					
				
							
								
								
									
										117
									
								
								sequencer.c
								
								
								
								
							
							
						
						
									
										117
									
								
								sequencer.c
								
								
								
								
							|  | @ -17,6 +17,7 @@ | |||
| #include "argv-array.h" | ||||
| #include "quote.h" | ||||
| #include "trailer.h" | ||||
| #include "log-tree.h" | ||||
|  | ||||
| #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" | ||||
|  | ||||
|  | @ -44,6 +45,20 @@ static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") | |||
|  * being rebased. | ||||
|  */ | ||||
| static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script") | ||||
| /* | ||||
|  * When an "edit" rebase command is being processed, the SHA1 of the | ||||
|  * commit to be edited is recorded in this file.  When "git rebase | ||||
|  * --continue" is executed, if there are any staged changes then they | ||||
|  * will be amended to the HEAD commit, but only provided the HEAD | ||||
|  * commit is still the commit to be edited.  When any other rebase | ||||
|  * command is processed, this file is deleted. | ||||
|  */ | ||||
| static GIT_PATH_FUNC(rebase_path_amend, "rebase-merge/amend") | ||||
| /* | ||||
|  * When we stop at a given patch via the "edit" command, this file contains | ||||
|  * the abbreviated commit name of the corresponding patch. | ||||
|  */ | ||||
| static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha") | ||||
| /* | ||||
|  * The following files are written by git-rebase just after parsing the | ||||
|  * command-line (and are only consumed, not modified, by the sequencer). | ||||
|  | @ -616,6 +631,7 @@ enum todo_command { | |||
| 	/* commands that handle commits */ | ||||
| 	TODO_PICK = 0, | ||||
| 	TODO_REVERT, | ||||
| 	TODO_EDIT, | ||||
| 	/* commands that do nothing but are counted for reporting progress */ | ||||
| 	TODO_NOOP | ||||
| }; | ||||
|  | @ -623,6 +639,7 @@ enum todo_command { | |||
| static const char *todo_command_strings[] = { | ||||
| 	"pick", | ||||
| 	"revert", | ||||
| 	"edit", | ||||
| 	"noop" | ||||
| }; | ||||
|  | ||||
|  | @ -1302,9 +1319,87 @@ static int save_opts(struct replay_opts *opts) | |||
| 	return res; | ||||
| } | ||||
|  | ||||
| static int make_patch(struct commit *commit, struct replay_opts *opts) | ||||
| { | ||||
| 	struct strbuf buf = STRBUF_INIT; | ||||
| 	struct rev_info log_tree_opt; | ||||
| 	const char *subject, *p; | ||||
| 	int res = 0; | ||||
|  | ||||
| 	p = short_commit_name(commit); | ||||
| 	if (write_message(p, strlen(p), rebase_path_stopped_sha(), 1) < 0) | ||||
| 		return -1; | ||||
|  | ||||
| 	strbuf_addf(&buf, "%s/patch", get_dir(opts)); | ||||
| 	memset(&log_tree_opt, 0, sizeof(log_tree_opt)); | ||||
| 	init_revisions(&log_tree_opt, NULL); | ||||
| 	log_tree_opt.abbrev = 0; | ||||
| 	log_tree_opt.diff = 1; | ||||
| 	log_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH; | ||||
| 	log_tree_opt.disable_stdin = 1; | ||||
| 	log_tree_opt.no_commit_id = 1; | ||||
| 	log_tree_opt.diffopt.file = fopen(buf.buf, "w"); | ||||
| 	log_tree_opt.diffopt.use_color = GIT_COLOR_NEVER; | ||||
| 	if (!log_tree_opt.diffopt.file) | ||||
| 		res |= error_errno(_("could not open '%s'"), buf.buf); | ||||
| 	else { | ||||
| 		res |= log_tree_commit(&log_tree_opt, commit); | ||||
| 		fclose(log_tree_opt.diffopt.file); | ||||
| 	} | ||||
| 	strbuf_reset(&buf); | ||||
|  | ||||
| 	strbuf_addf(&buf, "%s/message", get_dir(opts)); | ||||
| 	if (!file_exists(buf.buf)) { | ||||
| 		const char *commit_buffer = get_commit_buffer(commit, NULL); | ||||
| 		find_commit_subject(commit_buffer, &subject); | ||||
| 		res |= write_message(subject, strlen(subject), buf.buf, 1); | ||||
| 		unuse_commit_buffer(commit, commit_buffer); | ||||
| 	} | ||||
| 	strbuf_release(&buf); | ||||
|  | ||||
| 	return res; | ||||
| } | ||||
|  | ||||
| static int intend_to_amend(void) | ||||
| { | ||||
| 	unsigned char head[20]; | ||||
| 	char *p; | ||||
|  | ||||
| 	if (get_sha1("HEAD", head)) | ||||
| 		return error(_("cannot read HEAD")); | ||||
|  | ||||
| 	p = sha1_to_hex(head); | ||||
| 	return write_message(p, strlen(p), rebase_path_amend(), 1); | ||||
| } | ||||
|  | ||||
| static int error_with_patch(struct commit *commit, | ||||
| 	const char *subject, int subject_len, | ||||
| 	struct replay_opts *opts, int exit_code, int to_amend) | ||||
| { | ||||
| 	if (make_patch(commit, opts)) | ||||
| 		return -1; | ||||
|  | ||||
| 	if (to_amend) { | ||||
| 		if (intend_to_amend()) | ||||
| 			return -1; | ||||
|  | ||||
| 		fprintf(stderr, "You can amend the commit now, with\n" | ||||
| 			"\n" | ||||
| 			"  git commit --amend %s\n" | ||||
| 			"\n" | ||||
| 			"Once you are satisfied with your changes, run\n" | ||||
| 			"\n" | ||||
| 			"  git rebase --continue\n", gpg_sign_opt_quoted(opts)); | ||||
| 	} else if (exit_code) | ||||
| 		fprintf(stderr, "Could not apply %s... %.*s\n", | ||||
| 			short_commit_name(commit), subject_len, subject); | ||||
|  | ||||
| 	return exit_code; | ||||
| } | ||||
|  | ||||
| static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) | ||||
| { | ||||
| 	int res; | ||||
| 	int res = 0; | ||||
|  | ||||
| 	setenv(GIT_REFLOG_ACTION, action_name(opts), 0); | ||||
| 	if (opts->allow_ff) | ||||
|  | @ -1317,10 +1412,20 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) | |||
| 		struct todo_item *item = todo_list->items + todo_list->current; | ||||
| 		if (save_todo(todo_list, opts)) | ||||
| 			return -1; | ||||
| 		if (item->command <= TODO_REVERT) | ||||
| 		if (item->command <= TODO_EDIT) { | ||||
| 			res = do_pick_commit(item->command, item->commit, | ||||
| 					opts); | ||||
| 		else if (!is_noop(item->command)) | ||||
| 			if (item->command == TODO_EDIT) { | ||||
| 				struct commit *commit = item->commit; | ||||
| 				if (!res) | ||||
| 					warning(_("stopped at %s... %.*s"), | ||||
| 						short_commit_name(commit), | ||||
| 						item->arg_len, item->arg); | ||||
| 				return error_with_patch(commit, | ||||
| 					item->arg, item->arg_len, opts, res, | ||||
| 					!res); | ||||
| 			} | ||||
| 		} else if (!is_noop(item->command)) | ||||
| 			return error(_("unknown command %d"), item->command); | ||||
|  | ||||
| 		todo_list->current++; | ||||
|  | @ -1328,6 +1433,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) | |||
| 			return res; | ||||
| 	} | ||||
|  | ||||
| 	if (is_rebase_i(opts)) { | ||||
| 		/* Stopped in the middle, as planned? */ | ||||
| 		if (todo_list->current < todo_list->nr) | ||||
| 			return 0; | ||||
| 	} | ||||
|  | ||||
| 	/* | ||||
| 	 * Sequence of picks finished successfully; cleanup by | ||||
| 	 * removing the .git/sequencer directory | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Johannes Schindelin
						Johannes Schindelin