replace: add --edit option

This allows you to run: git replace --edit SHA1 to get dumped in an editor with the contents of the object for SHA1. The result is then read back in and used as a "replace" object for SHA1. The writing/reading is type-aware, so you get to edit "ls-tree" output rather than the binary tree format. Missing documentation and tests. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed Apr 26, 2014 at 22:00 UTC b892bb45eacb484be281a992bef66ea723210717
1 file changed +111 -1
builtin/replace.c
+111 -1
@@ -12,6 +12,7 @@
12 #include "builtin.h"
13 #include "refs.h"
14 #include "parse-options.h"
15 +#include "run-command.h"
16
17 static const char * const git_replace_usage[] = {
18 N_("git replace [-f] <object> <replacement>"),
@@ -176,6 +177,107 @@ static int replace_object(const char *object_ref, const char *replace_ref, int f
177 return replace_object_sha1(object_ref, object, replace_ref, repl, force);
178 }
179
180 +/*
181 + * Write the contents of the object named by "sha1" to the file "filename",
182 + * pretty-printed for human editing based on its type.
183 + */
184 +static void export_object(const unsigned char *sha1, const char *filename)
185 +{
186 + const char *argv[] = { "--no-replace-objects", "cat-file", "-p", NULL, NULL };
187 + struct child_process cmd = { argv };
188 + int fd;
189 +
190 + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
191 + if (fd < 0)
192 + die_errno("unable to open %s for writing", filename);
193 +
194 + argv[3] = sha1_to_hex(sha1);
195 + cmd.git_cmd = 1;
196 + cmd.out = fd;
197 +
198 + if (run_command(&cmd))
199 + die("cat-file reported failure");
200 +
201 + close(fd);
202 +}
203 +
204 +/*
205 + * Read a previously-exported (and possibly edited) object back from "filename",
206 + * interpreting it as "type", and writing the result to the object database.
207 + * The sha1 of the written object is returned via sha1.
208 + */
209 +static void import_object(unsigned char *sha1, enum object_type type,
210 + const char *filename)
211 +{
212 + int fd;
213 +
214 + fd = open(filename, O_RDONLY);
215 + if (fd < 0)
216 + die_errno("unable to open %s for reading", filename);
217 +
218 + if (type == OBJ_TREE) {
219 + const char *argv[] = { "mktree", NULL };
220 + struct child_process cmd = { argv };
221 + struct strbuf result = STRBUF_INIT;
222 +
223 + cmd.argv = argv;
224 + cmd.git_cmd = 1;
225 + cmd.in = fd;
226 + cmd.out = -1;
227 +
228 + if (start_command(&cmd))
229 + die("unable to spawn mktree");
230 +
231 + if (strbuf_read(&result, cmd.out, 41) < 0)
232 + die_errno("unable to read from mktree");
233 + close(cmd.out);
234 +
235 + if (finish_command(&cmd))
236 + die("mktree reported failure");
237 + if (get_sha1_hex(result.buf, sha1) < 0)
238 + die("mktree did not return an object name");
239 +
240 + strbuf_release(&result);
241 + } else {
242 + struct stat st;
243 + int flags = HASH_FORMAT_CHECK | HASH_WRITE_OBJECT;
244 +
245 + if (fstat(fd, &st) < 0)
246 + die_errno("unable to fstat %s", filename);
247 + if (index_fd(sha1, fd, &st, type, NULL, flags) < 0)
248 + die("unable to write object to database");
249 + /* index_fd close()s fd for us */
250 + }
251 +
252 + /*
253 + * No need to close(fd) here; both run-command and index-fd
254 + * will have done it for us.
255 + */
256 +}
257 +
258 +static int edit_and_replace(const char *object_ref, int force)
259 +{
260 + char *tmpfile = git_pathdup("REPLACE_EDITOBJ");
261 + enum object_type type;
262 + unsigned char old[20], new[20];
263 +
264 + if (get_sha1(object_ref, old) < 0)
265 + die("Not a valid object name: '%s'", object_ref);
266 +
267 + type = sha1_object_info(old, NULL);
268 + if (type < 0)
269 + die("unable to get object type for %s", sha1_to_hex(old));
270 +
271 + export_object(old, tmpfile);
272 + if (launch_editor(tmpfile, NULL, NULL) < 0)
273 + die("editing object file failed");
274 + import_object(new, type, tmpfile);
275 +
276 + free(tmpfile);
277 +
278 + return replace_object_sha1(object_ref, old, "replacement", new, force);
279 +}
280 +
281 int cmd_replace(int argc, const char **argv, const char *prefix)
282 {
283 int force = 0;
@@ -184,11 +286,13 @@ int cmd_replace(int argc, const char **argv, const char *prefix)
286 MODE_UNSPECIFIED = 0,
287 MODE_LIST,
288 MODE_DELETE,
289 + MODE_EDIT,
290 MODE_REPLACE
291 } cmdmode = MODE_UNSPECIFIED;
292 struct option options[] = {
293 OPT_CMDMODE('l', "list", &cmdmode, N_("list replace refs"), MODE_LIST),
294 OPT_CMDMODE('d', "delete", &cmdmode, N_("delete replace refs"), MODE_DELETE),
295 + OPT_CMDMODE('e', "edit", &cmdmode, N_("edit existing object"), MODE_EDIT),
296 OPT_BOOL('f', "force", &force, N_("replace the ref if it exists")),
297 OPT_STRING(0, "format", &format, N_("format"), N_("use this format")),
298 OPT_END()
@@ -205,7 +309,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix)
309 usage_msg_opt("--format cannot be used when not listing",
310 git_replace_usage, options);
311
208 - if (force && cmdmode != MODE_REPLACE)
312 + if (force && cmdmode != MODE_REPLACE && cmdmode != MODE_EDIT)
313 usage_msg_opt("-f only makes sense when writing a replacement",
314 git_replace_usage, options);
315
@@ -222,6 +326,12 @@ int cmd_replace(int argc, const char **argv, const char *prefix)
326 git_replace_usage, options);
327 return replace_object(argv[0], argv[1], force);
328
329 + case MODE_EDIT:
330 + if (argc != 1)
331 + usage_msg_opt("-e needs exactly one argument",
332 + git_replace_usage, options);
333 + return edit_and_replace(argv[0], force);
334 +
335 case MODE_LIST:
336 if (argc > 1)
337 usage_msg_opt("only one pattern can be given with -l",