2 * Copyright (C) the libgit2 contributors. All rights reserved.
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.
9 #include "repository.h"
14 #include "git2/types.h"
15 #include "git2/merge.h"
16 #include "git2/cherrypick.h"
17 #include "git2/commit.h"
18 #include "git2/sys/commit.h"
20 #define GIT_CHERRY_PICK_FILE_MODE 0666
22 static int write_cherry_pick_head(
24 const char *commit_oidstr
)
26 git_filebuf file
= GIT_FILEBUF_INIT
;
27 git_buf file_path
= GIT_BUF_INIT
;
30 if ((error
= git_buf_joinpath(&file_path
, repo
->path_repository
, GIT_CHERRY_PICK_HEAD_FILE
)) >= 0 &&
31 (error
= git_filebuf_open(&file
, file_path
.ptr
, GIT_FILEBUF_FORCE
, GIT_CHERRY_PICK_FILE_MODE
)) >= 0 &&
32 (error
= git_filebuf_printf(&file
, "%s\n", commit_oidstr
)) >= 0)
33 error
= git_filebuf_commit(&file
);
36 git_filebuf_cleanup(&file
);
38 git_buf_free(&file_path
);
43 static int write_merge_msg(
45 const char *commit_msg
)
47 git_filebuf file
= GIT_FILEBUF_INIT
;
48 git_buf file_path
= GIT_BUF_INIT
;
51 if ((error
= git_buf_joinpath(&file_path
, repo
->path_repository
, GIT_MERGE_MSG_FILE
)) < 0 ||
52 (error
= git_filebuf_open(&file
, file_path
.ptr
, GIT_FILEBUF_FORCE
, GIT_CHERRY_PICK_FILE_MODE
)) < 0 ||
53 (error
= git_filebuf_printf(&file
, "%s", commit_msg
)) < 0)
56 error
= git_filebuf_commit(&file
);
60 git_filebuf_cleanup(&file
);
62 git_buf_free(&file_path
);
67 static int cherry_pick_normalize_opts(
69 git_cherry_pick_options
*opts
,
70 const git_cherry_pick_options
*given
,
71 const char *their_label
)
74 unsigned int default_checkout_strategy
= GIT_CHECKOUT_SAFE_CREATE
|
75 GIT_CHECKOUT_ALLOW_CONFLICTS
;
80 memcpy(opts
, given
, sizeof(git_cherry_pick_options
));
82 git_cherry_pick_options default_opts
= GIT_CHERRY_PICK_OPTIONS_INIT
;
83 memcpy(opts
, &default_opts
, sizeof(git_cherry_pick_options
));
86 if (!opts
->checkout_opts
.checkout_strategy
)
87 opts
->checkout_opts
.checkout_strategy
= default_checkout_strategy
;
89 if (!opts
->checkout_opts
.our_label
)
90 opts
->checkout_opts
.our_label
= "HEAD";
92 if (!opts
->checkout_opts
.their_label
)
93 opts
->checkout_opts
.their_label
= their_label
;
98 static int cherry_pick_state_cleanup(git_repository
*repo
)
100 const char *state_files
[] = { GIT_CHERRY_PICK_HEAD_FILE
, GIT_MERGE_MSG_FILE
};
102 return git_repository__cleanup_files(repo
, state_files
, ARRAY_SIZE(state_files
));
105 static int cherry_pick_seterr(git_commit
*commit
, const char *fmt
)
107 char commit_oidstr
[GIT_OID_HEXSZ
+ 1];
109 giterr_set(GITERR_CHERRYPICK
, fmt
,
110 git_oid_tostr(commit_oidstr
, GIT_OID_HEXSZ
+ 1, git_commit_id(commit
)));
115 int git_cherry_pick_commit(
117 git_repository
*repo
,
118 git_commit
*cherry_pick_commit
,
119 git_commit
*our_commit
,
120 unsigned int mainline
,
121 const git_merge_options
*merge_opts
)
123 git_commit
*parent_commit
= NULL
;
124 git_tree
*parent_tree
= NULL
, *our_tree
= NULL
, *cherry_pick_tree
= NULL
;
125 int parent
= 0, error
= 0;
127 assert(out
&& repo
&& cherry_pick_commit
&& our_commit
);
129 if (git_commit_parentcount(cherry_pick_commit
) > 1) {
131 return cherry_pick_seterr(cherry_pick_commit
,
132 "Mainline branch is not specified but %s is a merge commit");
137 return cherry_pick_seterr(cherry_pick_commit
,
138 "Mainline branch specified but %s is not a merge commit");
140 parent
= git_commit_parentcount(cherry_pick_commit
);
144 ((error
= git_commit_parent(&parent_commit
, cherry_pick_commit
, (parent
- 1))) < 0 ||
145 (error
= git_commit_tree(&parent_tree
, parent_commit
)) < 0))
148 if ((error
= git_commit_tree(&cherry_pick_tree
, cherry_pick_commit
)) < 0 ||
149 (error
= git_commit_tree(&our_tree
, our_commit
)) < 0)
152 error
= git_merge_trees(out
, repo
, parent_tree
, our_tree
, cherry_pick_tree
, merge_opts
);
155 git_tree_free(parent_tree
);
156 git_tree_free(our_tree
);
157 git_tree_free(cherry_pick_tree
);
158 git_commit_free(parent_commit
);
164 git_repository
*repo
,
166 const git_cherry_pick_options
*given_opts
)
168 git_cherry_pick_options opts
;
169 git_reference
*our_ref
= NULL
;
170 git_commit
*our_commit
= NULL
;
171 char commit_oidstr
[GIT_OID_HEXSZ
+ 1];
172 const char *commit_msg
, *commit_summary
;
173 git_buf their_label
= GIT_BUF_INIT
;
174 git_index
*index_new
= NULL
, *index_repo
= NULL
;
177 assert(repo
&& commit
);
179 GITERR_CHECK_VERSION(given_opts
, GIT_CHERRY_PICK_OPTIONS_VERSION
, "git_cherry_pick_options");
181 if ((error
= git_repository__ensure_not_bare(repo
, "cherry-pick")) < 0)
184 if ((commit_msg
= git_commit_message(commit
)) == NULL
||
185 (commit_summary
= git_commit_summary(commit
)) == NULL
) {
190 git_oid_nfmt(commit_oidstr
, sizeof(commit_oidstr
), git_commit_id(commit
));
192 if ((error
= write_merge_msg(repo
, commit_msg
)) < 0 ||
193 (error
= git_buf_printf(&their_label
, "%.7s... %s", commit_oidstr
, commit_summary
)) < 0 ||
194 (error
= cherry_pick_normalize_opts(repo
, &opts
, given_opts
, git_buf_cstr(&their_label
))) < 0 ||
195 (error
= write_cherry_pick_head(repo
, commit_oidstr
)) < 0 ||
196 (error
= git_repository_head(&our_ref
, repo
)) < 0 ||
197 (error
= git_reference_peel((git_object
**)&our_commit
, our_ref
, GIT_OBJ_COMMIT
)) < 0 ||
198 (error
= git_cherry_pick_commit(&index_new
, repo
, commit
, our_commit
, opts
.mainline
, &opts
.merge_opts
)) < 0 ||
199 (error
= git_merge__indexes(repo
, index_new
)) < 0 ||
200 (error
= git_repository_index(&index_repo
, repo
)) < 0 ||
201 (error
= git_merge__append_conflicts_to_merge_msg(repo
, index_repo
)) < 0 ||
202 (error
= git_checkout_index(repo
, index_repo
, &opts
.checkout_opts
)) < 0)
208 cherry_pick_state_cleanup(repo
);
211 git_index_free(index_new
);
212 git_index_free(index_repo
);
213 git_commit_free(our_commit
);
214 git_reference_free(our_ref
);
215 git_buf_free(&their_label
);
220 int git_cherry_pick_init_options(
221 git_cherry_pick_options
*opts
, unsigned int version
)
223 GIT_INIT_STRUCTURE_FROM_TEMPLATE(
224 opts
, version
, git_cherry_pick_options
, GIT_CHERRY_PICK_OPTIONS_INIT
);