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 "commit.h"
11 #include "tag.h"
12 #include "merge.h"
13 #include "diff.h"
14 #include "annotated_commit.h"
15 #include "git2/reset.h"
16 #include "git2/checkout.h"
17 #include "git2/merge.h"
18 #include "git2/refs.h"
19
20 #define ERROR_MSG "Cannot perform reset"
21
git_reset_default(git_repository * repo,const git_object * target,const git_strarray * pathspecs)22 int git_reset_default(
23 git_repository *repo,
24 const git_object *target,
25 const git_strarray *pathspecs)
26 {
27 git_object *commit = NULL;
28 git_tree *tree = NULL;
29 git_diff *diff = NULL;
30 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
31 size_t i, max_i;
32 git_index_entry entry;
33 int error;
34 git_index *index = NULL;
35
36 GIT_ASSERT_ARG(pathspecs && pathspecs->count > 0);
37
38 memset(&entry, 0, sizeof(git_index_entry));
39
40 if ((error = git_repository_index(&index, repo)) < 0)
41 goto cleanup;
42
43 if (target) {
44 if (git_object_owner(target) != repo) {
45 git_error_set(GIT_ERROR_OBJECT,
46 "%s_default - The given target does not belong to this repository.", ERROR_MSG);
47 return -1;
48 }
49
50 if ((error = git_object_peel(&commit, target, GIT_OBJECT_COMMIT)) < 0 ||
51 (error = git_commit_tree(&tree, (git_commit *)commit)) < 0)
52 goto cleanup;
53 }
54
55 opts.pathspec = *pathspecs;
56 opts.flags = GIT_DIFF_REVERSE;
57
58 if ((error = git_diff_tree_to_index(
59 &diff, repo, tree, index, &opts)) < 0)
60 goto cleanup;
61
62 for (i = 0, max_i = git_diff_num_deltas(diff); i < max_i; ++i) {
63 const git_diff_delta *delta = git_diff_get_delta(diff, i);
64
65 GIT_ASSERT(delta->status == GIT_DELTA_ADDED ||
66 delta->status == GIT_DELTA_MODIFIED ||
67 delta->status == GIT_DELTA_CONFLICTED ||
68 delta->status == GIT_DELTA_DELETED);
69
70 error = git_index_conflict_remove(index, delta->old_file.path);
71 if (error < 0) {
72 if (delta->status == GIT_DELTA_ADDED && error == GIT_ENOTFOUND)
73 git_error_clear();
74 else
75 goto cleanup;
76 }
77
78 if (delta->status == GIT_DELTA_DELETED) {
79 if ((error = git_index_remove(index, delta->old_file.path, 0)) < 0)
80 goto cleanup;
81 } else {
82 entry.mode = delta->new_file.mode;
83 git_oid_cpy(&entry.id, &delta->new_file.id);
84 entry.path = (char *)delta->new_file.path;
85
86 if ((error = git_index_add(index, &entry)) < 0)
87 goto cleanup;
88 }
89 }
90
91 error = git_index_write(index);
92
93 cleanup:
94 git_object_free(commit);
95 git_tree_free(tree);
96 git_index_free(index);
97 git_diff_free(diff);
98
99 return error;
100 }
101
reset(git_repository * repo,const git_object * target,const char * to,git_reset_t reset_type,const git_checkout_options * checkout_opts)102 static int reset(
103 git_repository *repo,
104 const git_object *target,
105 const char *to,
106 git_reset_t reset_type,
107 const git_checkout_options *checkout_opts)
108 {
109 git_object *commit = NULL;
110 git_index *index = NULL;
111 git_tree *tree = NULL;
112 int error = 0;
113 git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
114 git_buf log_message = GIT_BUF_INIT;
115
116 GIT_ASSERT_ARG(repo);
117 GIT_ASSERT_ARG(target);
118
119 if (checkout_opts)
120 opts = *checkout_opts;
121
122 if (git_object_owner(target) != repo) {
123 git_error_set(GIT_ERROR_OBJECT,
124 "%s - The given target does not belong to this repository.", ERROR_MSG);
125 return -1;
126 }
127
128 if (reset_type != GIT_RESET_SOFT &&
129 (error = git_repository__ensure_not_bare(repo,
130 reset_type == GIT_RESET_MIXED ? "reset mixed" : "reset hard")) < 0)
131 return error;
132
133 if ((error = git_object_peel(&commit, target, GIT_OBJECT_COMMIT)) < 0 ||
134 (error = git_repository_index(&index, repo)) < 0 ||
135 (error = git_commit_tree(&tree, (git_commit *)commit)) < 0)
136 goto cleanup;
137
138 if (reset_type == GIT_RESET_SOFT &&
139 (git_repository_state(repo) == GIT_REPOSITORY_STATE_MERGE ||
140 git_index_has_conflicts(index)))
141 {
142 git_error_set(GIT_ERROR_OBJECT, "%s (soft) in the middle of a merge", ERROR_MSG);
143 error = GIT_EUNMERGED;
144 goto cleanup;
145 }
146
147 if ((error = git_buf_printf(&log_message, "reset: moving to %s", to)) < 0)
148 return error;
149
150 if (reset_type == GIT_RESET_HARD) {
151 /* overwrite working directory with the new tree */
152 opts.checkout_strategy = GIT_CHECKOUT_FORCE;
153
154 if ((error = git_checkout_tree(repo, (git_object *)tree, &opts)) < 0)
155 goto cleanup;
156 }
157
158 /* move HEAD to the new target */
159 if ((error = git_reference__update_terminal(repo, GIT_HEAD_FILE,
160 git_object_id(commit), NULL, git_buf_cstr(&log_message))) < 0)
161 goto cleanup;
162
163 if (reset_type > GIT_RESET_SOFT) {
164 /* reset index to the target content */
165
166 if ((error = git_index_read_tree(index, tree)) < 0 ||
167 (error = git_index_write(index)) < 0)
168 goto cleanup;
169
170 if ((error = git_repository_state_cleanup(repo)) < 0) {
171 git_error_set(GIT_ERROR_INDEX, "%s - failed to clean up merge data", ERROR_MSG);
172 goto cleanup;
173 }
174 }
175
176 cleanup:
177 git_object_free(commit);
178 git_index_free(index);
179 git_tree_free(tree);
180 git_buf_dispose(&log_message);
181
182 return error;
183 }
184
git_reset(git_repository * repo,const git_object * target,git_reset_t reset_type,const git_checkout_options * checkout_opts)185 int git_reset(
186 git_repository *repo,
187 const git_object *target,
188 git_reset_t reset_type,
189 const git_checkout_options *checkout_opts)
190 {
191 return reset(repo, target, git_oid_tostr_s(git_object_id(target)), reset_type, checkout_opts);
192 }
193
git_reset_from_annotated(git_repository * repo,const git_annotated_commit * commit,git_reset_t reset_type,const git_checkout_options * checkout_opts)194 int git_reset_from_annotated(
195 git_repository *repo,
196 const git_annotated_commit *commit,
197 git_reset_t reset_type,
198 const git_checkout_options *checkout_opts)
199 {
200 return reset(repo, (git_object *) commit->commit, commit->description, reset_type, checkout_opts);
201 }
202