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",