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