]>
Commit | Line | Data |
---|---|---|
8a0fc8d1 JT |
1 | #include "builtin.h" |
2 | #include "config.h" | |
3 | #include "parse-options.h" | |
4 | #include "refs.h" | |
5 | #include "lockfile.h" | |
6 | #include "cache-tree.h" | |
7 | #include "unpack-trees.h" | |
8 | #include "merge-recursive.h" | |
9 | #include "argv-array.h" | |
10 | #include "run-command.h" | |
11 | #include "dir.h" | |
12 | #include "rerere.h" | |
13 | ||
14 | static const char * const git_stash_helper_usage[] = { | |
15 | N_("git stash--helper apply [--index] [-q|--quiet] [<stash>]"), | |
16 | NULL | |
17 | }; | |
18 | ||
19 | static const char * const git_stash_helper_apply_usage[] = { | |
20 | N_("git stash--helper apply [--index] [-q|--quiet] [<stash>]"), | |
21 | NULL | |
22 | }; | |
23 | ||
24 | static const char *ref_stash = "refs/stash"; | |
25 | static struct strbuf stash_index_path = STRBUF_INIT; | |
26 | ||
27 | /* | |
28 | * w_commit is set to the commit containing the working tree | |
29 | * b_commit is set to the base commit | |
30 | * i_commit is set to the commit containing the index tree | |
31 | * u_commit is set to the commit containing the untracked files tree | |
32 | * w_tree is set to the working tree | |
33 | * b_tree is set to the base tree | |
34 | * i_tree is set to the index tree | |
35 | * u_tree is set to the untracked files tree | |
36 | */ | |
37 | struct stash_info { | |
38 | struct object_id w_commit; | |
39 | struct object_id b_commit; | |
40 | struct object_id i_commit; | |
41 | struct object_id u_commit; | |
42 | struct object_id w_tree; | |
43 | struct object_id b_tree; | |
44 | struct object_id i_tree; | |
45 | struct object_id u_tree; | |
46 | struct strbuf revision; | |
47 | int is_stash_ref; | |
48 | int has_u; | |
49 | }; | |
50 | ||
51 | static void free_stash_info(struct stash_info *info) | |
52 | { | |
53 | strbuf_release(&info->revision); | |
54 | } | |
55 | ||
56 | static void assert_stash_like(struct stash_info *info, const char *revision) | |
57 | { | |
58 | if (get_oidf(&info->b_commit, "%s^1", revision) || | |
59 | get_oidf(&info->w_tree, "%s:", revision) || | |
60 | get_oidf(&info->b_tree, "%s^1:", revision) || | |
61 | get_oidf(&info->i_tree, "%s^2:", revision)) | |
62 | die(_("'%s' is not a stash-like commit"), revision); | |
63 | } | |
64 | ||
65 | static int get_stash_info(struct stash_info *info, int argc, const char **argv) | |
66 | { | |
67 | int ret; | |
68 | char *end_of_rev; | |
69 | char *expanded_ref; | |
70 | const char *revision; | |
71 | const char *commit = NULL; | |
72 | struct object_id dummy; | |
73 | struct strbuf symbolic = STRBUF_INIT; | |
74 | ||
75 | if (argc > 1) { | |
76 | int i; | |
77 | struct strbuf refs_msg = STRBUF_INIT; | |
78 | ||
79 | for (i = 0; i < argc; i++) | |
80 | strbuf_addf(&refs_msg, " '%s'", argv[i]); | |
81 | ||
82 | fprintf_ln(stderr, _("Too many revisions specified:%s"), | |
83 | refs_msg.buf); | |
84 | strbuf_release(&refs_msg); | |
85 | ||
86 | return -1; | |
87 | } | |
88 | ||
89 | if (argc == 1) | |
90 | commit = argv[0]; | |
91 | ||
92 | strbuf_init(&info->revision, 0); | |
93 | if (!commit) { | |
94 | if (!ref_exists(ref_stash)) { | |
95 | free_stash_info(info); | |
96 | fprintf_ln(stderr, _("No stash entries found.")); | |
97 | return -1; | |
98 | } | |
99 | ||
100 | strbuf_addf(&info->revision, "%s@{0}", ref_stash); | |
101 | } else if (strspn(commit, "0123456789") == strlen(commit)) { | |
102 | strbuf_addf(&info->revision, "%s@{%s}", ref_stash, commit); | |
103 | } else { | |
104 | strbuf_addstr(&info->revision, commit); | |
105 | } | |
106 | ||
107 | revision = info->revision.buf; | |
108 | ||
109 | if (get_oid(revision, &info->w_commit)) { | |
110 | error(_("%s is not a valid reference"), revision); | |
111 | free_stash_info(info); | |
112 | return -1; | |
113 | } | |
114 | ||
115 | assert_stash_like(info, revision); | |
116 | ||
117 | info->has_u = !get_oidf(&info->u_tree, "%s^3:", revision); | |
118 | ||
119 | end_of_rev = strchrnul(revision, '@'); | |
120 | strbuf_add(&symbolic, revision, end_of_rev - revision); | |
121 | ||
122 | ret = dwim_ref(symbolic.buf, symbolic.len, &dummy, &expanded_ref); | |
123 | strbuf_release(&symbolic); | |
124 | switch (ret) { | |
125 | case 0: /* Not found, but valid ref */ | |
126 | info->is_stash_ref = 0; | |
127 | break; | |
128 | case 1: | |
129 | info->is_stash_ref = !strcmp(expanded_ref, ref_stash); | |
130 | break; | |
131 | default: /* Invalid or ambiguous */ | |
132 | free_stash_info(info); | |
133 | } | |
134 | ||
135 | free(expanded_ref); | |
136 | return !(ret == 0 || ret == 1); | |
137 | } | |
138 | ||
139 | static int reset_tree(struct object_id *i_tree, int update, int reset) | |
140 | { | |
141 | int nr_trees = 1; | |
142 | struct unpack_trees_options opts; | |
143 | struct tree_desc t[MAX_UNPACK_TREES]; | |
144 | struct tree *tree; | |
145 | struct lock_file lock_file = LOCK_INIT; | |
146 | ||
147 | read_cache_preload(NULL); | |
148 | if (refresh_cache(REFRESH_QUIET)) | |
149 | return -1; | |
150 | ||
151 | hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); | |
152 | ||
153 | memset(&opts, 0, sizeof(opts)); | |
154 | ||
155 | tree = parse_tree_indirect(i_tree); | |
156 | if (parse_tree(tree)) | |
157 | return -1; | |
158 | ||
159 | init_tree_desc(t, tree->buffer, tree->size); | |
160 | ||
161 | opts.head_idx = 1; | |
162 | opts.src_index = &the_index; | |
163 | opts.dst_index = &the_index; | |
164 | opts.merge = 1; | |
165 | opts.reset = reset; | |
166 | opts.update = update; | |
167 | opts.fn = oneway_merge; | |
168 | ||
169 | if (unpack_trees(nr_trees, t, &opts)) | |
170 | return -1; | |
171 | ||
172 | if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) | |
173 | return error(_("unable to write new index file")); | |
174 | ||
175 | return 0; | |
176 | } | |
177 | ||
178 | static int diff_tree_binary(struct strbuf *out, struct object_id *w_commit) | |
179 | { | |
180 | struct child_process cp = CHILD_PROCESS_INIT; | |
181 | const char *w_commit_hex = oid_to_hex(w_commit); | |
182 | ||
183 | /* | |
184 | * Diff-tree would not be very hard to replace with a native function, | |
185 | * however it should be done together with apply_cached. | |
186 | */ | |
187 | cp.git_cmd = 1; | |
188 | argv_array_pushl(&cp.args, "diff-tree", "--binary", NULL); | |
189 | argv_array_pushf(&cp.args, "%s^2^..%s^2", w_commit_hex, w_commit_hex); | |
190 | ||
191 | return pipe_command(&cp, NULL, 0, out, 0, NULL, 0); | |
192 | } | |
193 | ||
194 | static int apply_cached(struct strbuf *out) | |
195 | { | |
196 | struct child_process cp = CHILD_PROCESS_INIT; | |
197 | ||
198 | /* | |
199 | * Apply currently only reads either from stdin or a file, thus | |
200 | * apply_all_patches would have to be updated to optionally take a | |
201 | * buffer. | |
202 | */ | |
203 | cp.git_cmd = 1; | |
204 | argv_array_pushl(&cp.args, "apply", "--cached", NULL); | |
205 | return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0); | |
206 | } | |
207 | ||
208 | static int reset_head(void) | |
209 | { | |
210 | struct child_process cp = CHILD_PROCESS_INIT; | |
211 | ||
212 | /* | |
213 | * Reset is overall quite simple, however there is no current public | |
214 | * API for resetting. | |
215 | */ | |
216 | cp.git_cmd = 1; | |
217 | argv_array_push(&cp.args, "reset"); | |
218 | ||
219 | return run_command(&cp); | |
220 | } | |
221 | ||
222 | static int get_newly_staged(struct strbuf *out, struct object_id *c_tree) | |
223 | { | |
224 | struct child_process cp = CHILD_PROCESS_INIT; | |
225 | const char *c_tree_hex = oid_to_hex(c_tree); | |
226 | ||
227 | /* | |
228 | * diff-index is very similar to diff-tree above, and should be | |
229 | * converted together with update_index. | |
230 | */ | |
231 | cp.git_cmd = 1; | |
232 | argv_array_pushl(&cp.args, "diff-index", "--cached", "--name-only", | |
233 | "--diff-filter=A", NULL); | |
234 | argv_array_push(&cp.args, c_tree_hex); | |
235 | return pipe_command(&cp, NULL, 0, out, 0, NULL, 0); | |
236 | } | |
237 | ||
238 | static int update_index(struct strbuf *out) | |
239 | { | |
240 | struct child_process cp = CHILD_PROCESS_INIT; | |
241 | ||
242 | /* | |
243 | * Update-index is very complicated and may need to have a public | |
244 | * function exposed in order to remove this forking. | |
245 | */ | |
246 | cp.git_cmd = 1; | |
247 | argv_array_pushl(&cp.args, "update-index", "--add", "--stdin", NULL); | |
248 | return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0); | |
249 | } | |
250 | ||
251 | static int restore_untracked(struct object_id *u_tree) | |
252 | { | |
253 | int res; | |
254 | struct child_process cp = CHILD_PROCESS_INIT; | |
255 | ||
256 | /* | |
257 | * We need to run restore files from a given index, but without | |
258 | * affecting the current index, so we use GIT_INDEX_FILE with | |
259 | * run_command to fork processes that will not interfere. | |
260 | */ | |
261 | cp.git_cmd = 1; | |
262 | argv_array_push(&cp.args, "read-tree"); | |
263 | argv_array_push(&cp.args, oid_to_hex(u_tree)); | |
264 | argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", | |
265 | stash_index_path.buf); | |
266 | if (run_command(&cp)) { | |
267 | remove_path(stash_index_path.buf); | |
268 | return -1; | |
269 | } | |
270 | ||
271 | child_process_init(&cp); | |
272 | cp.git_cmd = 1; | |
273 | argv_array_pushl(&cp.args, "checkout-index", "--all", NULL); | |
274 | argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", | |
275 | stash_index_path.buf); | |
276 | ||
277 | res = run_command(&cp); | |
278 | remove_path(stash_index_path.buf); | |
279 | return res; | |
280 | } | |
281 | ||
282 | static int do_apply_stash(const char *prefix, struct stash_info *info, | |
283 | int index, int quiet) | |
284 | { | |
285 | int ret; | |
286 | int has_index = index; | |
287 | struct merge_options o; | |
288 | struct object_id c_tree; | |
289 | struct object_id index_tree; | |
290 | struct commit *result; | |
291 | const struct object_id *bases[1]; | |
292 | ||
293 | read_cache_preload(NULL); | |
294 | if (refresh_cache(REFRESH_QUIET)) | |
295 | return -1; | |
296 | ||
297 | if (write_cache_as_tree(&c_tree, 0, NULL)) | |
298 | return error(_("cannot apply a stash in the middle of a merge")); | |
299 | ||
300 | if (index) { | |
301 | if (oideq(&info->b_tree, &info->i_tree) || | |
302 | oideq(&c_tree, &info->i_tree)) { | |
303 | has_index = 0; | |
304 | } else { | |
305 | struct strbuf out = STRBUF_INIT; | |
306 | ||
307 | if (diff_tree_binary(&out, &info->w_commit)) { | |
308 | strbuf_release(&out); | |
309 | return error(_("could not generate diff %s^!."), | |
310 | oid_to_hex(&info->w_commit)); | |
311 | } | |
312 | ||
313 | ret = apply_cached(&out); | |
314 | strbuf_release(&out); | |
315 | if (ret) | |
316 | return error(_("conflicts in index." | |
317 | "Try without --index.")); | |
318 | ||
319 | discard_cache(); | |
320 | read_cache(); | |
321 | if (write_cache_as_tree(&index_tree, 0, NULL)) | |
322 | return error(_("could not save index tree")); | |
323 | ||
324 | reset_head(); | |
325 | } | |
326 | } | |
327 | ||
328 | if (info->has_u && restore_untracked(&info->u_tree)) | |
329 | return error(_("could not restore untracked files from stash")); | |
330 | ||
331 | init_merge_options(&o); | |
332 | ||
333 | o.branch1 = "Updated upstream"; | |
334 | o.branch2 = "Stashed changes"; | |
335 | ||
336 | if (oideq(&info->b_tree, &c_tree)) | |
337 | o.branch1 = "Version stash was based on"; | |
338 | ||
339 | if (quiet) | |
340 | o.verbosity = 0; | |
341 | ||
342 | if (o.verbosity >= 3) | |
343 | printf_ln(_("Merging %s with %s"), o.branch1, o.branch2); | |
344 | ||
345 | bases[0] = &info->b_tree; | |
346 | ||
347 | ret = merge_recursive_generic(&o, &c_tree, &info->w_tree, 1, bases, | |
348 | &result); | |
349 | if (ret) { | |
350 | rerere(0); | |
351 | ||
352 | if (index) | |
353 | fprintf_ln(stderr, _("Index was not unstashed.")); | |
354 | ||
355 | return ret; | |
356 | } | |
357 | ||
358 | if (has_index) { | |
359 | if (reset_tree(&index_tree, 0, 0)) | |
360 | return -1; | |
361 | } else { | |
362 | struct strbuf out = STRBUF_INIT; | |
363 | ||
364 | if (get_newly_staged(&out, &c_tree)) { | |
365 | strbuf_release(&out); | |
366 | return -1; | |
367 | } | |
368 | ||
369 | if (reset_tree(&c_tree, 0, 1)) { | |
370 | strbuf_release(&out); | |
371 | return -1; | |
372 | } | |
373 | ||
374 | ret = update_index(&out); | |
375 | strbuf_release(&out); | |
376 | if (ret) | |
377 | return -1; | |
378 | ||
379 | discard_cache(); | |
380 | } | |
381 | ||
382 | if (quiet) { | |
383 | if (refresh_cache(REFRESH_QUIET)) | |
384 | warning("could not refresh index"); | |
385 | } else { | |
386 | struct child_process cp = CHILD_PROCESS_INIT; | |
387 | ||
388 | /* | |
389 | * Status is quite simple and could be replaced with calls to | |
390 | * wt_status in the future, but it adds complexities which may | |
391 | * require more tests. | |
392 | */ | |
393 | cp.git_cmd = 1; | |
394 | cp.dir = prefix; | |
395 | argv_array_push(&cp.args, "status"); | |
396 | run_command(&cp); | |
397 | } | |
398 | ||
399 | return 0; | |
400 | } | |
401 | ||
402 | static int apply_stash(int argc, const char **argv, const char *prefix) | |
403 | { | |
404 | int ret; | |
405 | int quiet = 0; | |
406 | int index = 0; | |
407 | struct stash_info info; | |
408 | struct option options[] = { | |
409 | OPT__QUIET(&quiet, N_("be quiet, only report errors")), | |
410 | OPT_BOOL(0, "index", &index, | |
411 | N_("attempt to recreate the index")), | |
412 | OPT_END() | |
413 | }; | |
414 | ||
415 | argc = parse_options(argc, argv, prefix, options, | |
416 | git_stash_helper_apply_usage, 0); | |
417 | ||
418 | if (get_stash_info(&info, argc, argv)) | |
419 | return -1; | |
420 | ||
421 | ret = do_apply_stash(prefix, &info, index, quiet); | |
422 | free_stash_info(&info); | |
423 | return ret; | |
424 | } | |
425 | ||
426 | int cmd_stash__helper(int argc, const char **argv, const char *prefix) | |
427 | { | |
428 | pid_t pid = getpid(); | |
429 | const char *index_file; | |
430 | ||
431 | struct option options[] = { | |
432 | OPT_END() | |
433 | }; | |
434 | ||
435 | git_config(git_default_config, NULL); | |
436 | ||
437 | argc = parse_options(argc, argv, prefix, options, git_stash_helper_usage, | |
438 | PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH); | |
439 | ||
440 | index_file = get_index_file(); | |
441 | strbuf_addf(&stash_index_path, "%s.stash.%" PRIuMAX, index_file, | |
442 | (uintmax_t)pid); | |
443 | ||
444 | if (argc < 1) | |
445 | usage_with_options(git_stash_helper_usage, options); | |
446 | if (!strcmp(argv[0], "apply")) | |
447 | return !!apply_stash(argc, argv, prefix); | |
448 | ||
449 | usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]), | |
450 | git_stash_helper_usage, options); | |
451 | } |