summaryrefslogtreecommitdiff
path: root/t/helper/test-fast-rebase.c
blob: 373212256a66283ed759de3f19b01e18778f99f6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
/*
 * "git fast-rebase" builtin command
 *
 * FAST: Forking Any Subprocesses (is) Taboo
 *
 * This is meant SOLELY as a demo of what is possible.  sequencer.c and
 * rebase.c should be refactored to use the ideas here, rather than attempting
 * to extend this file to replace those (unless Phillip or Dscho say that
 * refactoring is too hard and we need a clean slate, but I'm guessing that
 * refactoring is the better route).
 */
 
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "test-tool.h"
 
#include "cache-tree.h"
#include "commit.h"
#include "lockfile.h"
#include "merge-ort.h"
#include "refs.h"
#include "revision.h"
#include "sequencer.h"
#include "strvec.h"
#include "tree.h"
 
static const char *short_commit_name(struct commit *commit)
{
	return find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV);
}
 
static struct commit *peel_committish(const char *name)
{
	struct object *obj;
	struct object_id oid;
 
	if (get_oid(name, &oid))
		return NULL;
	obj = parse_object(the_repository, &oid);
	return (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT);
}
 
static char *get_author(const char *message)
{
	size_t len;
	const char *a;
 
	a = find_commit_header(message, "author", &len);
	if (a)
		return xmemdupz(a, len);
 
	return NULL;
}
 
static struct commit *create_commit(struct tree *tree,
				    struct commit *based_on,
				    struct commit *parent)
{
	struct object_id ret;
	struct object *obj;
	struct commit_list *parents = NULL;
	char *author;
	char *sign_commit = NULL;
	struct commit_extra_header *extra;
	struct strbuf msg = STRBUF_INIT;
	const char *out_enc = get_commit_output_encoding();
	const char *message = logmsg_reencode(based_on, NULL, out_enc);
	const char *orig_message = NULL;
	const char *exclude_gpgsig[] = { "gpgsig", NULL };
 
	commit_list_insert(parent, &parents);
	extra = read_commit_extra_headers(based_on, exclude_gpgsig);
	find_commit_subject(message, &orig_message);
	strbuf_addstr(&msg, orig_message);
	author = get_author(message);
	reset_ident_date();
	if (commit_tree_extended(msg.buf, msg.len, &tree->object.oid, parents,
				 &ret, author, NULL, sign_commit, extra)) {
		error(_("failed to write commit object"));
		return NULL;
	}
	free(author);
	strbuf_release(&msg);
 
	obj = parse_object(the_repository, &ret);
	return (struct commit *)obj;
}
 
int cmd__fast_rebase(int argc, const char **argv)
{
	struct commit *onto;
	struct commit *last_commit = NULL, *last_picked_commit = NULL;
	struct object_id head;
	struct lock_file lock = LOCK_INIT;
	int clean = 1;
	struct strvec rev_walk_args = STRVEC_INIT;
	struct rev_info revs;
	struct commit *commit;
	struct merge_options merge_opt;
	struct tree *next_tree, *base_tree, *head_tree;
	struct merge_result result;
	struct strbuf reflog_msg = STRBUF_INIT;
	struct strbuf branch_name = STRBUF_INIT;
 
	/*
	 * test-tool stuff doesn't set up the git directory by default; need to
	 * do that manually.
	 */
	setup_git_directory();
 
	if (argc == 2 && !strcmp(argv[1], "-h")) {
		printf("Sorry, I am not a psychiatrist; I can not give you the help you need.  Oh, you meant usage...\n");
		exit(129);
	}
 
	if (argc != 5 || strcmp(argv[1], "--onto"))
		die("usage: read the code, figure out how to use it, then do so");
 
	onto = peel_committish(argv[2]);
	strbuf_addf(&branch_name, "refs/heads/%s", argv[4]);
 
	/* Sanity check */
	if (get_oid("HEAD", &head))
		die(_("Cannot read HEAD"));
	assert(oideq(&onto->object.oid, &head));
 
	hold_locked_index(&lock, LOCK_DIE_ON_ERROR);
	assert(repo_read_index(the_repository) >= 0);
 
	repo_init_revisions(the_repository, &revs, NULL);
	revs.verbose_header = 1;
	revs.max_parents = 1;
	revs.cherry_mark = 1;
	revs.limited = 1;
	revs.reverse = 1;
	revs.right_only = 1;
	revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
	revs.topo_order = 1;
	strvec_pushl(&rev_walk_args, "", argv[4], "--not", argv[3], NULL);
 
	if (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1)
		return error(_("unhandled options"));
 
	strvec_clear(&rev_walk_args);
 
	if (prepare_revision_walk(&revs) < 0)
		return error(_("error preparing revisions"));
 
	init_merge_options(&merge_opt, the_repository);
	memset(&result, 0, sizeof(result));
	merge_opt.show_rename_progress = 1;
	merge_opt.branch1 = "HEAD";
	head_tree = get_commit_tree(onto);
	result.tree = head_tree;
	last_commit = onto;
	while ((commit = get_revision(&revs))) {
		struct commit *base;
 
		fprintf(stderr, "Rebasing %s...\r",
			oid_to_hex(&commit->object.oid));
		assert(commit->parents && !commit->parents->next);
		base = commit->parents->item;
 
		next_tree = get_commit_tree(commit);
		base_tree = get_commit_tree(base);
 
		merge_opt.branch2 = short_commit_name(commit);
		merge_opt.ancestor = xstrfmt("parent of %s", merge_opt.branch2);
 
		merge_incore_nonrecursive(&merge_opt,
					  base_tree,
					  result.tree,
					  next_tree,
					  &result);
 
		free((char*)merge_opt.ancestor);
		merge_opt.ancestor = NULL;
		if (!result.clean)
			die("Aborting: Hit a conflict and restarting is not implemented.");
		last_picked_commit = commit;
		last_commit = create_commit(result.tree, commit, last_commit);
	}
	fprintf(stderr, "\nDone.\n");
	/* TODO: There should be some kind of rev_info_free(&revs) call... */
	memset(&revs, 0, sizeof(revs));
 
	merge_switch_to_result(&merge_opt, head_tree, &result, 1, !result.clean);
 
	if (result.clean < 0)
		exit(128);
 
	strbuf_addf(&reflog_msg, "finish rebase %s onto %s",
		    oid_to_hex(&last_picked_commit->object.oid),
		    oid_to_hex(&last_commit->object.oid));
	if (update_ref(reflog_msg.buf, branch_name.buf,
		       &last_commit->object.oid,
		       &last_picked_commit->object.oid,
		       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {
		error(_("could not update %s"), argv[4]);
		die("Failed to update %s", argv[4]);
	}
	if (create_symref("HEAD", branch_name.buf, reflog_msg.buf) < 0)
		die(_("unable to update HEAD"));
	strbuf_release(&reflog_msg);
	strbuf_release(&branch_name);
 
	prime_cache_tree(the_repository, the_repository->index, result.tree);
	if (write_locked_index(&the_index, &lock,
			       COMMIT_LOCK | SKIP_IF_UNCHANGED))
		die(_("unable to write %s"), get_index_file());
	return (clean == 0);
}