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