]> git.proxmox.com Git - libgit2.git/blame - src/rebase.c
Introduce git_rebase_abort
[libgit2.git] / src / rebase.c
CommitLineData
867a36f3
ET
1/*
2 * Copyright (C) the libgit2 contributors. All rights reserved.
3 *
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
6 */
7
8#include "common.h"
9#include "buffer.h"
10#include "repository.h"
11#include "posix.h"
12#include "filebuf.h"
13#include "merge.h"
14#include "array.h"
15
16#include <git2/types.h>
17#include <git2/rebase.h>
18#include <git2/commit.h>
19#include <git2/reset.h>
20#include <git2/revwalk.h>
21
22#define REBASE_APPLY_DIR "rebase-apply"
23#define REBASE_MERGE_DIR "rebase-merge"
24
25#define HEAD_NAME_FILE "head-name"
26#define ORIG_HEAD_FILE "orig-head"
27#define HEAD_FILE "head"
28#define ONTO_FILE "onto"
29#define ONTO_NAME_FILE "onto_name"
30#define QUIET_FILE "quiet"
31
32#define MSGNUM_FILE "msgnum"
33#define END_FILE "end"
34#define CMT_FILE_FMT "cmt.%d"
35
36#define ORIG_DETACHED_HEAD "detached HEAD"
37
38#define REBASE_DIR_MODE 0777
39#define REBASE_FILE_MODE 0666
40
41typedef enum {
42 GIT_REBASE_TYPE_NONE = 0,
43 GIT_REBASE_TYPE_APPLY = 1,
44 GIT_REBASE_TYPE_MERGE = 2,
45} git_rebase_type_t;
46
4fe84d62
ET
47typedef struct {
48 git_rebase_type_t type;
49 char *state_path;
50
51 int head_detached : 1;
52
53 char *orig_head_name;
54 git_oid orig_head_id;
55} git_rebase_state;
56
57#define GIT_REBASE_STATE_INIT {0}
58
867a36f3
ET
59static int rebase_state_type(
60 git_rebase_type_t *type_out,
61 char **path_out,
62 git_repository *repo)
63{
64 git_buf path = GIT_BUF_INIT;
65 git_rebase_type_t type = GIT_REBASE_TYPE_NONE;
66
67 if (git_buf_joinpath(&path, repo->path_repository, REBASE_APPLY_DIR) < 0)
68 return -1;
69
70 if (git_path_isdir(git_buf_cstr(&path))) {
71 type = GIT_REBASE_TYPE_APPLY;
72 goto done;
73 }
74
75 git_buf_clear(&path);
76 if (git_buf_joinpath(&path, repo->path_repository, REBASE_MERGE_DIR) < 0)
77 return -1;
78
79 if (git_path_isdir(git_buf_cstr(&path))) {
80 type = GIT_REBASE_TYPE_MERGE;
81 goto done;
82 }
83
84done:
85 *type_out = type;
86
87 if (type != GIT_REBASE_TYPE_NONE && path_out)
88 *path_out = git_buf_detach(&path);
89
90 git_buf_free(&path);
91
92 return 0;
93}
94
4fe84d62
ET
95static int rebase_state(git_rebase_state *state, git_repository *repo)
96{
97 git_buf path = GIT_BUF_INIT, orig_head_name = GIT_BUF_INIT,
98 orig_head_id = GIT_BUF_INIT;
99 int state_path_len, error;
100
101 memset(state, 0x0, sizeof(git_rebase_state));
102
103 if ((error = rebase_state_type(&state->type, &state->state_path, repo)) < 0)
104 goto done;
105
106 if (state->type == GIT_REBASE_TYPE_NONE) {
107 giterr_set(GITERR_REBASE, "There is no rebase in progress");
108 return GIT_ENOTFOUND;
109 }
110
111 if ((error = git_buf_puts(&path, state->state_path)) < 0)
112 goto done;
113
114 state_path_len = git_buf_len(&path);
115
116 if ((error = git_buf_joinpath(&path, path.ptr, HEAD_NAME_FILE)) < 0 ||
117 (error = git_futils_readbuffer(&orig_head_name, path.ptr)) < 0)
118 goto done;
119
120 git_buf_rtrim(&orig_head_name);
121
122 if (strcmp(ORIG_DETACHED_HEAD, orig_head_name.ptr) == 0)
123 state->head_detached = 1;
124
125 git_buf_truncate(&path, state_path_len);
126
127 if ((error = git_buf_joinpath(&path, path.ptr, ORIG_HEAD_FILE)) < 0)
128 goto done;
129
130 if (!git_path_isfile(path.ptr)) {
131 /* Previous versions of git.git used 'head' here; support that. */
132 git_buf_truncate(&path, state_path_len);
133
134 if ((error = git_buf_joinpath(&path, path.ptr, HEAD_FILE)) < 0)
135 goto done;
136 }
137
138 if ((error = git_futils_readbuffer(&orig_head_id, path.ptr)) < 0)
139 goto done;
140
141 git_buf_rtrim(&orig_head_id);
142
143 if ((error = git_oid_fromstr(&state->orig_head_id, orig_head_id.ptr)) < 0)
144 goto done;
145
146 if (!state->head_detached)
147 state->orig_head_name = git_buf_detach(&orig_head_name);
148
149done:
150 git_buf_free(&path);
151 git_buf_free(&orig_head_name);
152 git_buf_free(&orig_head_id);
153 git_buf_free(&onto_id);
154 return error;
155}
156
157static void rebase_state_free(git_rebase_state *state)
158{
159 if (state == NULL)
160 return;
161
162 git__free(state->orig_head_name);
163 git__free(state->state_path);
164}
165
166static int rebase_finish(git_rebase_state *state)
167{
168 return git_path_isdir(state->state_path) ?
169 git_futils_rmdir_r(state->state_path, NULL, GIT_RMDIR_REMOVE_FILES) :
170 0;
171}
172
867a36f3
ET
173static int rebase_setupfile(git_repository *repo, const char *filename, const char *fmt, ...)
174{
175 git_buf path = GIT_BUF_INIT,
176 contents = GIT_BUF_INIT;
177 va_list ap;
178 int error;
179
180 va_start(ap, fmt);
181 git_buf_vprintf(&contents, fmt, ap);
182 va_end(ap);
183
184 if ((error = git_buf_joinpath(&path, repo->path_repository, REBASE_MERGE_DIR)) == 0 &&
185 (error = git_buf_joinpath(&path, path.ptr, filename)) == 0)
186 error = git_futils_writebuffer(&contents, path.ptr, O_RDWR|O_CREAT, REBASE_FILE_MODE);
187
188 git_buf_free(&path);
189 git_buf_free(&contents);
190
191 return error;
192}
193
194/* TODO: git.git actually uses the literal argv here, this is an attempt
195 * to emulate that.
196 */
197static const char *rebase_onto_name(const git_merge_head *onto)
198{
199 if (onto->ref_name && git__strncmp(onto->ref_name, "refs/heads/", 11) == 0)
200 return onto->ref_name + 11;
201 else if (onto->ref_name)
202 return onto->ref_name;
203 else
204 return onto->oid_str;
205}
206
207static int rebase_setup_merge(
208 git_repository *repo,
209 const git_merge_head *branch,
210 const git_merge_head *upstream,
211 const git_merge_head *onto,
212 const git_rebase_options *opts)
213{
214 git_revwalk *revwalk = NULL;
215 git_commit *commit;
216 git_buf commit_filename = GIT_BUF_INIT;
217 git_oid id;
218 char id_str[GIT_OID_HEXSZ];
219 bool merge;
220 int commit_cnt = 0, error;
221
222 GIT_UNUSED(opts);
223
224 if (!upstream)
225 upstream = onto;
226
227 if ((error = git_revwalk_new(&revwalk, repo)) < 0 ||
228 (error = git_revwalk_push(revwalk, &branch->oid)) < 0 ||
229 (error = git_revwalk_hide(revwalk, &upstream->oid)) < 0)
230 goto done;
231
232 git_revwalk_sorting(revwalk, GIT_SORT_REVERSE | GIT_SORT_TIME);
233
234 while ((error = git_revwalk_next(&id, revwalk)) == 0) {
235 if ((error = git_commit_lookup(&commit, repo, &id)) < 0)
236 goto done;
237
238 merge = (git_commit_parentcount(commit) > 1);
239 git_commit_free(commit);
240
241 if (merge)
242 continue;
243
244 commit_cnt++;
245
246 git_buf_clear(&commit_filename);
247 git_buf_printf(&commit_filename, CMT_FILE_FMT, commit_cnt);
248
249 git_oid_fmt(id_str, &id);
250 if ((error = rebase_setupfile(repo, commit_filename.ptr,
251 "%.*s\n", GIT_OID_HEXSZ, id_str)) < 0)
252 goto done;
253 }
254
255 if (error != GIT_ITEROVER ||
256 (error = rebase_setupfile(repo, END_FILE, "%d\n", commit_cnt)) < 0)
257 goto done;
258
259 error = rebase_setupfile(repo, ONTO_NAME_FILE, "%s\n",
260 rebase_onto_name(onto));
261
262done:
263 git_revwalk_free(revwalk);
264 git_buf_free(&commit_filename);
265
266 return error;
267}
268
269static int rebase_setup(
270 git_repository *repo,
271 const git_merge_head *branch,
272 const git_merge_head *upstream,
273 const git_merge_head *onto,
274 const git_rebase_options *opts)
275{
276 git_buf state_path = GIT_BUF_INIT;
277 const char *orig_head_name;
278 int error;
279
280 if (git_buf_joinpath(&state_path, repo->path_repository, REBASE_MERGE_DIR) < 0)
281 return -1;
282
283 if ((error = p_mkdir(state_path.ptr, REBASE_DIR_MODE)) < 0) {
284 giterr_set(GITERR_OS, "Failed to create rebase directory '%s'",
285 state_path.ptr);
286 goto done;
287 }
288
289 if ((error = git_repository__set_orig_head(repo, &branch->oid)) < 0)
290 goto done;
291
292 orig_head_name = branch->ref_name ? branch->ref_name : ORIG_DETACHED_HEAD;
293
294 if ((error = rebase_setupfile(repo, HEAD_NAME_FILE, "%s\n", orig_head_name)) < 0 ||
295 (error = rebase_setupfile(repo, ONTO_FILE, "%s\n", onto->oid_str)) < 0 ||
296 (error = rebase_setupfile(repo, ORIG_HEAD_FILE, "%s\n", branch->oid_str)) < 0 ||
297 (error = rebase_setupfile(repo, QUIET_FILE, opts->quiet ? "t\n" : "\n")) < 0)
298 goto done;
299
300 error = rebase_setup_merge(repo, branch, upstream, onto, opts);
301
302done:
303 if (error < 0)
304 git_repository__cleanup_files(repo, (const char **)&state_path.ptr, 1);
305
306 git_buf_free(&state_path);
307
308 return error;
309}
310
311int git_rebase_init_options(git_rebase_options *opts, unsigned int version)
312{
313 GIT_INIT_STRUCTURE_FROM_TEMPLATE(
314 opts, version, git_rebase_options, GIT_REBASE_OPTIONS_INIT);
315 return 0;
316}
317
318static void rebase_normalize_options(
319 git_rebase_options *opts,
320 const git_rebase_options *given_opts)
321{
322 if (given_opts)
323 memcpy(&opts, given_opts, sizeof(git_rebase_options));
324}
325
326static int rebase_ensure_not_in_progress(git_repository *repo)
327{
328 int error;
329 git_rebase_type_t type;
330
331 if ((error = rebase_state_type(&type, NULL, repo)) < 0)
332 return error;
333
334 if (type != GIT_REBASE_TYPE_NONE) {
335 giterr_set(GITERR_REBASE, "There is an existing rebase in progress");
336 return -1;
337 }
338
339 return 0;
340}
341
342static int rebase_ensure_not_dirty(git_repository *repo)
343{
344 git_tree *head = NULL;
345 git_index *index = NULL;
346 git_diff *diff = NULL;
347 int error;
348
349 if ((error = git_repository_head_tree(&head, repo)) < 0 ||
350 (error = git_repository_index(&index, repo)) < 0 ||
351 (error = git_diff_tree_to_index(&diff, repo, head, index, NULL)) < 0)
352 goto done;
353
354 if (git_diff_num_deltas(diff) > 0) {
355 giterr_set(GITERR_REBASE, "Uncommitted changes exist in index");
356 error = -1;
357 goto done;
358 }
359
360 git_diff_free(diff);
361 diff = NULL;
362
363 if ((error = git_diff_index_to_workdir(&diff, repo, index, NULL)) < 0)
364 goto done;
365
366 if (git_diff_num_deltas(diff) > 0) {
367 giterr_set(GITERR_REBASE, "Unstaged changes exist in workdir");
368 error = -1;
369 }
370
371done:
372 git_diff_free(diff);
373 git_index_free(index);
374 git_tree_free(head);
375
376 return error;
377}
378
379int git_rebase(
380 git_repository *repo,
381 const git_merge_head *branch,
382 const git_merge_head *upstream,
383 const git_merge_head *onto,
384 const git_signature *signature,
385 const git_rebase_options *given_opts)
386{
387 git_rebase_options opts = GIT_REBASE_OPTIONS_INIT;
388 git_reference *head_ref = NULL;
389 git_buf reflog = GIT_BUF_INIT;
390 git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
391 int error;
392
393 assert(repo && branch && (upstream || onto));
394
395 GITERR_CHECK_VERSION(given_opts, GIT_MERGE_OPTIONS_VERSION, "git_merge_options");
396 rebase_normalize_options(&opts, given_opts);
397
398 if ((error = git_repository__ensure_not_bare(repo, "rebase")) < 0 ||
399 (error = rebase_ensure_not_in_progress(repo)) < 0 ||
400 (error = rebase_ensure_not_dirty(repo)) < 0)
401 goto done;
402
403 if (!onto)
404 onto = upstream;
405
406 if ((error = rebase_setup(repo, branch, upstream, onto, &opts)) < 0)
407 goto done;
408
409 if ((error = git_buf_printf(&reflog,
410 "rebase: checkout %s", rebase_onto_name(onto))) < 0 ||
411 (error = git_reference_create(&head_ref, repo, GIT_HEAD_FILE,
412 &onto->oid, 1, signature, reflog.ptr)) < 0)
413 goto done;
414
415 checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE;
416 error = git_checkout_head(repo, &checkout_opts);
417
418done:
419 git_reference_free(head_ref);
420 git_buf_free(&reflog);
421 return error;
422}
4fe84d62
ET
423
424int git_rebase_abort(git_repository *repo, const git_signature *signature)
425{
426 git_rebase_state state = GIT_REBASE_STATE_INIT;
427 git_reference *orig_head_ref = NULL;
428 git_commit *orig_head_commit = NULL;
429 int error;
430
431 assert(repo && signature);
432
433 if ((error = rebase_state(&state, repo)) < 0)
434 goto done;
435
436 error = state.head_detached ?
437 git_reference_create(&orig_head_ref, repo, GIT_HEAD_FILE,
438 &state.orig_head_id, 1, signature, "rebase: aborting") :
439 git_reference_symbolic_create(
440 &orig_head_ref, repo, GIT_HEAD_FILE, state.orig_head_name, 1,
441 signature, "rebase: aborting");
442
443 if (error < 0)
444 goto done;
445
446 if ((error = git_commit_lookup(
447 &orig_head_commit, repo, &state.orig_head_id)) < 0 ||
448 (error = git_reset(repo, (git_object *)orig_head_commit,
449 GIT_RESET_HARD, NULL, signature, NULL)) < 0)
450 goto done;
451
452 error = rebase_finish(&state);
453
454done:
455 git_commit_free(orig_head_commit);
456 git_reference_free(orig_head_ref);
457 rebase_state_free(&state);
458
459 return error;
460}