1 #include <string.h>
2
3 #include "git2.h"
4
5 #include "egit.h"
6 #include "egit-options.h"
7 #include "interface.h"
8 #include "egit-merge.h"
9
10
11 EGIT_DOC(merge, "REPO HEADS &optional MERGE-OPTIONS CHECKOUT-OPTIONS",
12 "Merge HEADS (a list of annotated commits) into the HEAD of REPO.\n"
13 "For CHECKOUT-OPTIONS, see `libgit-checkout-head'.\n"
14 "MERGE-OPTIONS is an alist with the following keys:\n"
15 "- `find-renames': if non-nil, detect renames, enabling the ability\n"
16 " to merge between modified and renamed files\n"
17 "- `fail-on-conflict': if non-nil, exit immediately on conflict without\n"
18 " continuing with resolution\n"
19 "- `skip-reuc': if non-nil, do not write the REUC extension on the\n"
20 " generated index\n"
21 "- `no-recursive': if non-nil, do not build a recursive merge base\n"
22 " in case of multiple merge bases, and instead simply use the first\n"
23 " base"
24 "- `rename-threshold': similarity above which to consider a file to be\n"
25 " renamed (default 50)\n"
26 "- `target-limit': maximum similarity sources to examine for renames\n"
27 " (default 200); this setting overrides the merge.renameLimit setting\n"
28 "- `recursion-limit': maximum number of times to merge common ancestors\n"
29 " to build a virtual merge base when faced with criss-cross merges.\n"
30 " When the limit is reached, the next ancestor will be used instead of\n"
31 " attempting to merge it. Default unlimited.\n"
32 "- `default-driver': string denoting the merge driver (default \"text\")\n"
33 "- `file-favor': can be any of the symbols `normal' (default): produce a\n"
34 " conflict in case a region of a file is changed in both branches,\n"
35 " `ours': choose our side, don't produce a conflict, `theirs': choose\n"
36 " their side, don't produce a conflict, `union': pick each unique line\n"
37 " from each side, don't produce a conflict\n"
38 "- `file-flags': an alist of flags (boolean options) with the following\n"
39 " keys:\n"
40 " - `style-merge': create standard conflicted merge files\n"
41 " - `style-diff3': create diff3-style files\n"
42 " - `simplify-alnum': condense non-alphanumeric regions\n"
43 " - `ignore-whitespace': ignore all whitespace\n"
44 " - `ignore-whitespace-change': ignore changes in whitespace\n"
45 " - `ignore-whitespace-eol': ignore whitespace at end of line\n"
46 " - `patience': use the patience diff algorithm\n"
47 " - `minimal': take extra time to find a minimal diff");
egit_merge(emacs_env * env,emacs_value _repo,emacs_value _heads,emacs_value _merge_opts,emacs_value _checkout_opts)48 emacs_value egit_merge(
49 emacs_env *env, emacs_value _repo, emacs_value _heads,
50 emacs_value _merge_opts, emacs_value _checkout_opts)
51 {
52 EGIT_ASSERT_REPOSITORY(_repo);
53 git_repository *repo = EGIT_EXTRACT(_repo);
54
55 ptrdiff_t i = 0;
56 ptrdiff_t nheads = egit_assert_list(env, EGIT_ANNOTATED_COMMIT, esym_libgit_annotated_commit_p, _heads);
57 if (nheads < 0)
58 return esym_nil;
59 const git_annotated_commit *heads[nheads];
60 {
61 EM_DOLIST(h, _heads, get_heads);
62 heads[i++] = EGIT_EXTRACT(h);
63 EM_DOLIST_END(get_heads);
64 }
65
66 git_merge_options merge_opts;
67 egit_merge_options_parse(env, _merge_opts, &merge_opts);
68 EM_RETURN_NIL_IF_NLE();
69
70 git_checkout_options checkout_opts;
71 egit_checkout_options_parse(env, _checkout_opts, &checkout_opts);
72 EM_RETURN_NIL_IF_NLE();
73
74 int retval = git_merge(repo, heads, nheads, &merge_opts, &checkout_opts);
75 egit_checkout_options_release(&checkout_opts);
76 EGIT_CHECK_ERROR(retval);
77
78 return esym_nil;
79 }
80
81 EGIT_DOC(merge_analysis, "REPO HEADS",
82 "Analyze the effects of merging HEADS into the current HEAD of repo.\n"
83 "HEADS should be a list of annotated commits.\n"
84 "The return value is a cons (ANALYSIS . PREFERENCE) where ANALYSIS is\n"
85 "a list with the following symbols:\n"
86 "- `normal': a normal merge is possible\n"
87 "- `up-to-date': inputs are reachable from HEAD, no merge necessary\n"
88 "- `fastforward': a fast-forward merge is possible\n"
89 "- `unborn': HEAD is unborn, no merge possible\n\n"
90 "PREFERENCE indicates the merge.ff setting, and can be either `nil',\n"
91 "`no-fastforward' or `fastforward-only'.");
egit_merge_analysis(emacs_env * env,emacs_value _repo,emacs_value _heads)92 emacs_value egit_merge_analysis(emacs_env *env, emacs_value _repo, emacs_value _heads)
93 {
94 EGIT_ASSERT_REPOSITORY(_repo);
95 git_repository *repo = EGIT_EXTRACT(_repo);
96
97 ptrdiff_t i = 0;
98 ptrdiff_t nheads = egit_assert_list(env, EGIT_ANNOTATED_COMMIT, esym_libgit_annotated_commit_p, _heads);
99 if (nheads < 0)
100 return esym_nil;
101 const git_annotated_commit *heads[nheads];
102 {
103 EM_DOLIST(h, _heads, get_heads);
104 heads[i++] = EGIT_EXTRACT(h);
105 EM_DOLIST_END(get_heads);
106 }
107
108 git_merge_analysis_t analysis;
109 git_merge_preference_t preference;
110 int retval = git_merge_analysis(&analysis, &preference, repo, heads, nheads);
111 EGIT_CHECK_ERROR(retval);
112
113 emacs_value _analysis = em_getlist_merge_analysis(env, analysis);
114
115 // These are bit flags but only one can be set
116 emacs_value _preference = em_findenum_merge_preference(preference);
117
118 return em_cons(env, _analysis, _preference);
119 }
120
121 EGIT_DOC(merge_base, "REPO IDS",
122 "Find the best merge base between commits given by the list IDS.\n"
123 "Returns a commit ID.");
egit_merge_base(emacs_env * env,emacs_value _repo,emacs_value _ids)124 emacs_value egit_merge_base(emacs_env *env, emacs_value _repo, emacs_value _ids)
125 {
126 EGIT_ASSERT_REPOSITORY(_repo);
127 git_repository *repo = EGIT_EXTRACT(_repo);
128
129 ptrdiff_t i = 0, nids = em_assert_list(env, esym_stringp, _ids);
130 git_oid ids[nids];
131 {
132 EM_DOLIST(id, _ids, get_ids);
133 EGIT_EXTRACT_OID(id, ids[i]);
134 i++;
135 EM_DOLIST_END(get_ids);
136 }
137
138 git_oid out;
139 int retval;
140 if (nids == 2)
141 retval = git_merge_base(&out, repo, &ids[0], &ids[1]);
142 else
143 retval = git_merge_base_many(&out, repo, nids, ids);
144 EGIT_CHECK_ERROR(retval);
145
146 const char *oid_s = git_oid_tostr_s(&out);
147 return EM_STRING(oid_s);
148 }
149
150 EGIT_DOC(merge_base_octopus, "REPO IDS",
151 "Find a merge base in preparation for an octopus merge.\n"
152 "Returns a commit ID.");
egit_merge_base_octopus(emacs_env * env,emacs_value _repo,emacs_value _ids)153 emacs_value egit_merge_base_octopus(emacs_env *env, emacs_value _repo, emacs_value _ids)
154 {
155 EGIT_ASSERT_REPOSITORY(_repo);
156 git_repository *repo = EGIT_EXTRACT(_repo);
157
158 ptrdiff_t i = 0, nids = em_assert_list(env, esym_stringp, _ids);
159 git_oid ids[nids];
160 {
161 EM_DOLIST(id, _ids, get_ids);
162 EGIT_EXTRACT_OID(id, ids[i]);
163 i++;
164 EM_DOLIST_END(get_ids);
165 }
166
167 git_oid out;
168 int retval = git_merge_base_octopus(&out, repo, nids, ids);
169 EGIT_CHECK_ERROR(retval);
170
171 const char *oid_s = git_oid_tostr_s(&out);
172 return EM_STRING(oid_s);
173 }
174
175 EGIT_DOC(merge_bases, "REPO IDS",
176 "Find all merge bases between commits given by the list IDS.\n"
177 "Returns a list of commit IDs.");
egit_merge_bases(emacs_env * env,emacs_value _repo,emacs_value _ids)178 emacs_value egit_merge_bases(emacs_env *env, emacs_value _repo, emacs_value _ids)
179 {
180 EGIT_ASSERT_REPOSITORY(_repo);
181 git_repository *repo = EGIT_EXTRACT(_repo);
182
183 ptrdiff_t i = 0, nids = em_assert_list(env, esym_stringp, _ids);
184 git_oid ids[nids];
185 {
186 EM_DOLIST(id, _ids, get_ids);
187 EGIT_EXTRACT_OID(id, ids[i]);
188 i++;
189 EM_DOLIST_END(get_ids);
190 }
191
192 git_oidarray out;
193 int retval;
194 if (nids == 2)
195 retval = git_merge_bases(&out, repo, &ids[0], &ids[1]);
196 else
197 retval = git_merge_bases_many(&out, repo, nids, ids);
198 EGIT_CHECK_ERROR(retval);
199
200 emacs_value ret = esym_nil;
201 for (size_t i = out.count; i > 0; i--) {
202 const char *oid_s = git_oid_tostr_s(&out.ids[i-1]);
203 ret = em_cons(env, EM_STRING(oid_s), ret);
204 }
205 git_oidarray_free(&out);
206
207 return ret;
208 }
209