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 10 #include "repository.h" 11 #include "filebuf.h" 12 #include "merge.h" 13 #include "vector.h" 14 #include "index.h" 15 16 #include "git2/types.h" 17 #include "git2/merge.h" 18 #include "git2/cherrypick.h" 19 #include "git2/commit.h" 20 #include "git2/sys/commit.h" 21 22 #define GIT_CHERRYPICK_FILE_MODE 0666 23 24 static int write_cherrypick_head( 25 git_repository *repo, 26 const char *commit_oidstr) 27 { 28 git_filebuf file = GIT_FILEBUF_INIT; 29 git_buf file_path = GIT_BUF_INIT; 30 int error = 0; 31 32 if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_CHERRYPICK_HEAD_FILE)) >= 0 && 33 (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_CHERRYPICK_FILE_MODE)) >= 0 && 34 (error = git_filebuf_printf(&file, "%s\n", commit_oidstr)) >= 0) 35 error = git_filebuf_commit(&file); 36 37 if (error < 0) 38 git_filebuf_cleanup(&file); 39 40 git_buf_dispose(&file_path); 41 42 return error; 43 } 44 45 static int write_merge_msg( 46 git_repository *repo, 47 const char *commit_msg) 48 { 49 git_filebuf file = GIT_FILEBUF_INIT; 50 git_buf file_path = GIT_BUF_INIT; 51 int error = 0; 52 53 if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 || 54 (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_CHERRYPICK_FILE_MODE)) < 0 || 55 (error = git_filebuf_printf(&file, "%s", commit_msg)) < 0) 56 goto cleanup; 57 58 error = git_filebuf_commit(&file); 59 60 cleanup: 61 if (error < 0) 62 git_filebuf_cleanup(&file); 63 64 git_buf_dispose(&file_path); 65 66 return error; 67 } 68 69 static int cherrypick_normalize_opts( 70 git_repository *repo, 71 git_cherrypick_options *opts, 72 const git_cherrypick_options *given, 73 const char *their_label) 74 { 75 int error = 0; 76 unsigned int default_checkout_strategy = GIT_CHECKOUT_SAFE | 77 GIT_CHECKOUT_ALLOW_CONFLICTS; 78 79 GIT_UNUSED(repo); 80 81 if (given != NULL) 82 memcpy(opts, given, sizeof(git_cherrypick_options)); 83 else { 84 git_cherrypick_options default_opts = GIT_CHERRYPICK_OPTIONS_INIT; 85 memcpy(opts, &default_opts, sizeof(git_cherrypick_options)); 86 } 87 88 if (!opts->checkout_opts.checkout_strategy) 89 opts->checkout_opts.checkout_strategy = default_checkout_strategy; 90 checkout_notify(checkout_data * data,git_checkout_notify_t why,const git_diff_delta * delta,const git_index_entry * wditem)91 if (!opts->checkout_opts.our_label) 92 opts->checkout_opts.our_label = "HEAD"; 93 94 if (!opts->checkout_opts.their_label) 95 opts->checkout_opts.their_label = their_label; 96 97 return error; 98 } 99 100 static int cherrypick_state_cleanup(git_repository *repo) 101 { 102 const char *state_files[] = { GIT_CHERRYPICK_HEAD_FILE, GIT_MERGE_MSG_FILE }; 103 104 return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files)); 105 } 106 107 static int cherrypick_seterr(git_commit *commit, const char *fmt) 108 { 109 char commit_oidstr[GIT_OID_HEXSZ + 1]; 110 111 git_error_set(GIT_ERROR_CHERRYPICK, fmt, 112 git_oid_tostr(commit_oidstr, GIT_OID_HEXSZ + 1, git_commit_id(commit))); 113 114 return -1; 115 } 116 117 int git_cherrypick_commit( 118 git_index **out, 119 git_repository *repo, 120 git_commit *cherrypick_commit, 121 git_commit *our_commit, 122 unsigned int mainline, 123 const git_merge_options *merge_opts) 124 { 125 git_commit *parent_commit = NULL; 126 git_tree *parent_tree = NULL, *our_tree = NULL, *cherrypick_tree = NULL; 127 int parent = 0, error = 0; 128 129 GIT_ASSERT_ARG(out); 130 GIT_ASSERT_ARG(repo); 131 GIT_ASSERT_ARG(cherrypick_commit); 132 GIT_ASSERT_ARG(our_commit); 133 134 if (git_commit_parentcount(cherrypick_commit) > 1) { 135 if (!mainline) 136 return cherrypick_seterr(cherrypick_commit, 137 "mainline branch is not specified but %s is a merge commit"); 138 139 parent = mainline; 140 } else { 141 if (mainline) 142 return cherrypick_seterr(cherrypick_commit, 143 "mainline branch specified but %s is not a merge commit"); 144 145 parent = git_commit_parentcount(cherrypick_commit); 146 } 147 148 if (parent && 149 ((error = git_commit_parent(&parent_commit, cherrypick_commit, (parent - 1))) < 0 || 150 (error = git_commit_tree(&parent_tree, parent_commit)) < 0)) is_workdir_base_or_new(const git_oid * workdir_id,const git_diff_file * baseitem,const git_diff_file * newitem)151 goto done; 152 153 if ((error = git_commit_tree(&cherrypick_tree, cherrypick_commit)) < 0 || 154 (error = git_commit_tree(&our_tree, our_commit)) < 0) 155 goto done; 156 157 error = git_merge_trees(out, repo, parent_tree, our_tree, cherrypick_tree, merge_opts); 158 159 done: is_filemode_changed(git_filemode_t a,git_filemode_t b,int respect_filemode)160 git_tree_free(parent_tree); 161 git_tree_free(our_tree); 162 git_tree_free(cherrypick_tree); 163 git_commit_free(parent_commit); 164 165 return error; 166 } 167 168 int git_cherrypick( 169 git_repository *repo, 170 git_commit *commit, 171 const git_cherrypick_options *given_opts) 172 { 173 git_cherrypick_options opts; 174 git_reference *our_ref = NULL; 175 git_commit *our_commit = NULL; checkout_is_workdir_modified(checkout_data * data,const git_diff_file * baseitem,const git_diff_file * newitem,const git_index_entry * wditem)176 char commit_oidstr[GIT_OID_HEXSZ + 1]; 177 const char *commit_msg, *commit_summary; 178 git_buf their_label = GIT_BUF_INIT; 179 git_index *index = NULL; 180 git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; 181 int error = 0; 182 183 GIT_ASSERT_ARG(repo); 184 GIT_ASSERT_ARG(commit); 185 186 GIT_ERROR_CHECK_VERSION(given_opts, GIT_CHERRYPICK_OPTIONS_VERSION, "git_cherrypick_options"); 187 188 if ((error = git_repository__ensure_not_bare(repo, "cherry-pick")) < 0) 189 return error; 190 191 if ((commit_msg = git_commit_message(commit)) == NULL || 192 (commit_summary = git_commit_summary(commit)) == NULL) { 193 error = -1; 194 goto on_error; 195 } 196 197 git_oid_nfmt(commit_oidstr, sizeof(commit_oidstr), git_commit_id(commit)); 198 199 if ((error = write_merge_msg(repo, commit_msg)) < 0 || 200 (error = git_buf_printf(&their_label, "%.7s... %s", commit_oidstr, commit_summary)) < 0 || 201 (error = cherrypick_normalize_opts(repo, &opts, given_opts, git_buf_cstr(&their_label))) < 0 || 202 (error = git_indexwriter_init_for_operation(&indexwriter, repo, &opts.checkout_opts.checkout_strategy)) < 0 || 203 (error = write_cherrypick_head(repo, commit_oidstr)) < 0 || 204 (error = git_repository_head(&our_ref, repo)) < 0 || 205 (error = git_reference_peel((git_object **)&our_commit, our_ref, GIT_OBJECT_COMMIT)) < 0 || 206 (error = git_cherrypick_commit(&index, repo, commit, our_commit, opts.mainline, &opts.merge_opts)) < 0 || 207 (error = git_merge__check_result(repo, index)) < 0 || 208 (error = git_merge__append_conflicts_to_merge_msg(repo, index)) < 0 || 209 (error = git_checkout_index(repo, index, &opts.checkout_opts)) < 0 || 210 (error = git_indexwriter_commit(&indexwriter)) < 0) 211 goto on_error; 212 213 goto done; 214 215 on_error: 216 cherrypick_state_cleanup(repo); 217 218 done: 219 git_indexwriter_cleanup(&indexwriter); 220 git_index_free(index); 221 git_commit_free(our_commit); 222 git_reference_free(our_ref); 223 git_buf_dispose(&their_label); 224 225 return error; 226 } 227 228 int git_cherrypick_options_init( 229 git_cherrypick_options *opts, unsigned int version) 230 { 231 GIT_INIT_STRUCTURE_FROM_TEMPLATE( 232 opts, version, git_cherrypick_options, GIT_CHERRYPICK_OPTIONS_INIT); 233 return 0; 234 } 235 236 #ifndef GIT_DEPRECATE_HARD 237 int git_cherrypick_init_options( 238 git_cherrypick_options *opts, unsigned int version) 239 { 240 return git_cherrypick_options_init(opts, version); 241 } 242 #endif 243