blob: a29e911d3099be9d327ecc030d68c56fe046a04a [file] [log] [blame]
Christian Couder54b0c1e2009-02-02 06:12:44 +01001/*
2 * Builtin "git replace"
3 *
4 * Copyright (c) 2008 Christian Couder <chriscool@tuxfamily.org>
5 *
Phil Hord09b7e222013-06-18 13:44:58 -04006 * Based on builtin/tag.c by Kristian Høgsberg <krh@redhat.com>
Christian Couder54b0c1e2009-02-02 06:12:44 +01007 * and Carlos Rica <jasampler@gmail.com> that was itself based on
8 * git-tag.sh and mktag.c by Linus Torvalds.
9 */
10
11#include "cache.h"
Brandon Williamsb2141fc2017-06-14 11:07:36 -070012#include "config.h"
Christian Couder54b0c1e2009-02-02 06:12:44 +010013#include "builtin.h"
14#include "refs.h"
15#include "parse-options.h"
Jeff Kingb892bb42014-04-26 22:00:57 +020016#include "run-command.h"
Stefan Beller60ce76d2018-04-11 17:21:10 -070017#include "object-store.h"
18#include "repository.h"
Christian Couder25a05a82014-07-19 17:01:14 +020019#include "tag.h"
Christian Couder54b0c1e2009-02-02 06:12:44 +010020
21static const char * const git_replace_usage[] = {
Nguyễn Thái Ngọc Duy2477beb2012-08-20 19:32:36 +070022 N_("git replace [-f] <object> <replacement>"),
Christian Couderab77c302014-05-17 14:16:38 +020023 N_("git replace [-f] --edit <object>"),
Christian Couder4228e8b2014-07-19 17:01:08 +020024 N_("git replace [-f] --graft <commit> [<parent>...]"),
Jean-Noël Avila959d6702022-01-31 22:07:48 +000025 "git replace [-f] --convert-graft-file",
Nguyễn Thái Ngọc Duy2477beb2012-08-20 19:32:36 +070026 N_("git replace -d <object>..."),
Christian Couder44f9f852013-12-11 08:46:10 +010027 N_("git replace [--format=<format>] [-l [<pattern>]]"),
Christian Couder54b0c1e2009-02-02 06:12:44 +010028 NULL
29};
30
Christian Couder663a8562013-12-28 12:00:05 +010031enum replace_format {
Jeff King3cc9d872014-06-24 05:43:07 -040032 REPLACE_FORMAT_SHORT,
33 REPLACE_FORMAT_MEDIUM,
34 REPLACE_FORMAT_LONG
Christian Couder663a8562013-12-28 12:00:05 +010035};
Christian Couder44f9f852013-12-11 08:46:10 +010036
37struct show_data {
38 const char *pattern;
Christian Couder663a8562013-12-28 12:00:05 +010039 enum replace_format format;
Christian Couder44f9f852013-12-11 08:46:10 +010040};
41
Stefan Beller212e0f72018-08-20 18:24:19 +000042static int show_reference(struct repository *r, const char *refname,
43 const struct object_id *oid,
Christian Couder54b0c1e2009-02-02 06:12:44 +010044 int flag, void *cb_data)
45{
Christian Couder44f9f852013-12-11 08:46:10 +010046 struct show_data *data = cb_data;
Christian Couder54b0c1e2009-02-02 06:12:44 +010047
Ævar Arnfjörð Bjarmason55d34262017-06-22 21:38:08 +000048 if (!wildmatch(data->pattern, refname, 0)) {
Christian Couder663a8562013-12-28 12:00:05 +010049 if (data->format == REPLACE_FORMAT_SHORT)
Christian Couder44f9f852013-12-11 08:46:10 +010050 printf("%s\n", refname);
Christian Couder663a8562013-12-28 12:00:05 +010051 else if (data->format == REPLACE_FORMAT_MEDIUM)
Michael Haggertyd70d7a82015-05-25 18:38:44 +000052 printf("%s -> %s\n", refname, oid_to_hex(oid));
Christian Couder663a8562013-12-28 12:00:05 +010053 else { /* data->format == REPLACE_FORMAT_LONG */
Michael Haggertyd70d7a82015-05-25 18:38:44 +000054 struct object_id object;
Christian Couder44f9f852013-12-11 08:46:10 +010055 enum object_type obj_type, repl_type;
56
brian m. carlsone82caf32017-07-13 23:49:28 +000057 if (get_oid(refname, &object))
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +020058 return error(_("failed to resolve '%s' as a valid ref"), refname);
Christian Couder44f9f852013-12-11 08:46:10 +010059
Stefan Beller212e0f72018-08-20 18:24:19 +000060 obj_type = oid_object_info(r, &object, NULL);
61 repl_type = oid_object_info(r, oid, NULL);
Christian Couder44f9f852013-12-11 08:46:10 +010062
Brandon Williamsdebca9d2018-02-14 10:59:24 -080063 printf("%s (%s) -> %s (%s)\n", refname, type_name(obj_type),
64 oid_to_hex(oid), type_name(repl_type));
Christian Couder44f9f852013-12-11 08:46:10 +010065 }
66 }
Christian Couder54b0c1e2009-02-02 06:12:44 +010067
68 return 0;
69}
70
Christian Couder44f9f852013-12-11 08:46:10 +010071static int list_replace_refs(const char *pattern, const char *format)
Christian Couder54b0c1e2009-02-02 06:12:44 +010072{
Christian Couder44f9f852013-12-11 08:46:10 +010073 struct show_data data;
74
Junio C Hamanoafe8a902022-05-02 09:50:37 -070075 if (!pattern)
Christian Couder54b0c1e2009-02-02 06:12:44 +010076 pattern = "*";
Christian Couder44f9f852013-12-11 08:46:10 +010077 data.pattern = pattern;
Christian Couder54b0c1e2009-02-02 06:12:44 +010078
Christian Couder44f9f852013-12-11 08:46:10 +010079 if (format == NULL || *format == '\0' || !strcmp(format, "short"))
Christian Couder663a8562013-12-28 12:00:05 +010080 data.format = REPLACE_FORMAT_SHORT;
Christian Couder44f9f852013-12-11 08:46:10 +010081 else if (!strcmp(format, "medium"))
Christian Couder663a8562013-12-28 12:00:05 +010082 data.format = REPLACE_FORMAT_MEDIUM;
83 else if (!strcmp(format, "long"))
84 data.format = REPLACE_FORMAT_LONG;
Nguyễn Thái Ngọc Duy5a59a232019-02-16 18:24:41 +070085 /*
86 * Please update _git_replace() in git-completion.bash when
87 * you add new format
88 */
Christian Couder44f9f852013-12-11 08:46:10 +010089 else
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +020090 return error(_("invalid replace format '%s'\n"
91 "valid formats are 'short', 'medium' and 'long'"),
Johannes Schindeline24e8712018-04-29 00:44:26 +020092 format);
Christian Couder44f9f852013-12-11 08:46:10 +010093
Stefan Beller60ce76d2018-04-11 17:21:10 -070094 for_each_replace_ref(the_repository, show_reference, (void *)&data);
Christian Couder54b0c1e2009-02-02 06:12:44 +010095
96 return 0;
97}
98
99typedef int (*each_replace_name_fn)(const char *name, const char *ref,
brian m. carlsoncea43322017-02-21 23:47:30 +0000100 const struct object_id *oid);
Christian Couder54b0c1e2009-02-02 06:12:44 +0100101
102static int for_each_replace_name(const char **argv, each_replace_name_fn fn)
103{
Michael J Gruber9dfc3682012-11-13 11:34:11 +0100104 const char **p, *full_hex;
Jeff King7f897b62017-03-28 15:46:30 -0400105 struct strbuf ref = STRBUF_INIT;
106 size_t base_len;
Christian Couder54b0c1e2009-02-02 06:12:44 +0100107 int had_error = 0;
brian m. carlsoncea43322017-02-21 23:47:30 +0000108 struct object_id oid;
Derrick Stolee97e61e02022-08-05 17:58:37 +0000109 const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;
Christian Couder54b0c1e2009-02-02 06:12:44 +0100110
Jeff King7f897b62017-03-28 15:46:30 -0400111 strbuf_addstr(&ref, git_replace_ref_base);
112 base_len = ref.len;
113
Christian Couder54b0c1e2009-02-02 06:12:44 +0100114 for (p = argv; *p; p++) {
brian m. carlsoncea43322017-02-21 23:47:30 +0000115 if (get_oid(*p, &oid)) {
Nguyễn Thái Ngọc Duy1a07e592018-07-21 09:49:19 +0200116 error("failed to resolve '%s' as a valid ref", *p);
Christian Couder54b0c1e2009-02-02 06:12:44 +0100117 had_error = 1;
118 continue;
119 }
Jeff King7f897b62017-03-28 15:46:30 -0400120
121 strbuf_setlen(&ref, base_len);
122 strbuf_addstr(&ref, oid_to_hex(&oid));
123 full_hex = ref.buf + base_len;
124
brian m. carlson34c290a2017-10-15 22:06:56 +0000125 if (read_ref(ref.buf, &oid)) {
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200126 error(_("replace ref '%s' not found"), full_hex);
Christian Couder54b0c1e2009-02-02 06:12:44 +0100127 had_error = 1;
128 continue;
129 }
Jeff King7f897b62017-03-28 15:46:30 -0400130 if (fn(full_hex, ref.buf, &oid))
Christian Couder54b0c1e2009-02-02 06:12:44 +0100131 had_error = 1;
132 }
Junio C Hamano372b0502017-04-17 21:56:54 -0700133 strbuf_release(&ref);
Christian Couder54b0c1e2009-02-02 06:12:44 +0100134 return had_error;
135}
136
137static int delete_replace_ref(const char *name, const char *ref,
brian m. carlsoncea43322017-02-21 23:47:30 +0000138 const struct object_id *oid)
Christian Couder54b0c1e2009-02-02 06:12:44 +0100139{
brian m. carlson2616a5e2017-10-15 22:06:50 +0000140 if (delete_ref(NULL, ref, oid, 0))
Christian Couder54b0c1e2009-02-02 06:12:44 +0100141 return 1;
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200142 printf_ln(_("Deleted replace ref '%s'"), name);
Christian Couder54b0c1e2009-02-02 06:12:44 +0100143 return 0;
144}
145
Johannes Schindeline24e8712018-04-29 00:44:26 +0200146static int check_ref_valid(struct object_id *object,
brian m. carlsoncea43322017-02-21 23:47:30 +0000147 struct object_id *prev,
Jeff King7f897b62017-03-28 15:46:30 -0400148 struct strbuf *ref,
Christian Couderb6e38842014-05-17 14:16:35 +0200149 int force)
150{
Derrick Stolee97e61e02022-08-05 17:58:37 +0000151 const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;
152
Jeff King7f897b62017-03-28 15:46:30 -0400153 strbuf_reset(ref);
154 strbuf_addf(ref, "%s%s", git_replace_ref_base, oid_to_hex(object));
155 if (check_refname_format(ref->buf, 0))
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200156 return error(_("'%s' is not a valid ref name"), ref->buf);
Christian Couderb6e38842014-05-17 14:16:35 +0200157
brian m. carlson34c290a2017-10-15 22:06:56 +0000158 if (read_ref(ref->buf, prev))
brian m. carlsoncea43322017-02-21 23:47:30 +0000159 oidclr(prev);
Christian Couderb6e38842014-05-17 14:16:35 +0200160 else if (!force)
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200161 return error(_("replace ref '%s' already exists"), ref->buf);
Johannes Schindeline24e8712018-04-29 00:44:26 +0200162 return 0;
Christian Couderb6e38842014-05-17 14:16:35 +0200163}
164
brian m. carlsoncea43322017-02-21 23:47:30 +0000165static int replace_object_oid(const char *object_ref,
166 struct object_id *object,
Jeff King479bd752014-04-26 22:00:56 +0200167 const char *replace_ref,
brian m. carlsoncea43322017-02-21 23:47:30 +0000168 struct object_id *repl,
Jeff King479bd752014-04-26 22:00:56 +0200169 int force)
Christian Couderbebdd272009-02-02 06:12:53 +0100170{
brian m. carlsoncea43322017-02-21 23:47:30 +0000171 struct object_id prev;
Christian Couder277336a2013-09-06 07:10:53 +0200172 enum object_type obj_type, repl_type;
Jeff King7f897b62017-03-28 15:46:30 -0400173 struct strbuf ref = STRBUF_INIT;
Ronnie Sahlberg867c2fa2014-04-16 15:32:29 -0700174 struct ref_transaction *transaction;
175 struct strbuf err = STRBUF_INIT;
Johannes Schindeline24e8712018-04-29 00:44:26 +0200176 int res = 0;
Christian Couderbebdd272009-02-02 06:12:53 +0100177
Stefan Beller0df8e962018-04-25 11:20:59 -0700178 obj_type = oid_object_info(the_repository, object, NULL);
179 repl_type = oid_object_info(the_repository, repl, NULL);
Christian Couder277336a2013-09-06 07:10:53 +0200180 if (!force && obj_type != repl_type)
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200181 return error(_("Objects must be of the same type.\n"
182 "'%s' points to a replaced object of type '%s'\n"
183 "while '%s' points to a replacement object of "
184 "type '%s'."),
Johannes Schindeline24e8712018-04-29 00:44:26 +0200185 object_ref, type_name(obj_type),
186 replace_ref, type_name(repl_type));
Christian Couder277336a2013-09-06 07:10:53 +0200187
Johannes Schindeline24e8712018-04-29 00:44:26 +0200188 if (check_ref_valid(object, &prev, &ref, force)) {
189 strbuf_release(&ref);
190 return -1;
191 }
Christian Couderbebdd272009-02-02 06:12:53 +0100192
Ronnie Sahlberg867c2fa2014-04-16 15:32:29 -0700193 transaction = ref_transaction_begin(&err);
194 if (!transaction ||
brian m. carlson89f3bbd2017-10-15 22:06:53 +0000195 ref_transaction_update(transaction, ref.buf, repl, &prev,
Michael Haggerty1d147bd2015-02-17 18:00:15 +0100196 0, NULL, &err) ||
Ronnie Sahlbergdb7516a2014-04-30 12:22:42 -0700197 ref_transaction_commit(transaction, &err))
Johannes Schindeline24e8712018-04-29 00:44:26 +0200198 res = error("%s", err.buf);
Christian Couderbebdd272009-02-02 06:12:53 +0100199
Ronnie Sahlberg867c2fa2014-04-16 15:32:29 -0700200 ref_transaction_free(transaction);
Jeff King7f897b62017-03-28 15:46:30 -0400201 strbuf_release(&ref);
Johannes Schindeline24e8712018-04-29 00:44:26 +0200202 return res;
Christian Couderbebdd272009-02-02 06:12:53 +0100203}
204
Jeff King479bd752014-04-26 22:00:56 +0200205static int replace_object(const char *object_ref, const char *replace_ref, int force)
206{
brian m. carlsoncea43322017-02-21 23:47:30 +0000207 struct object_id object, repl;
Jeff King479bd752014-04-26 22:00:56 +0200208
brian m. carlsoncea43322017-02-21 23:47:30 +0000209 if (get_oid(object_ref, &object))
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200210 return error(_("failed to resolve '%s' as a valid ref"),
Johannes Schindeline24e8712018-04-29 00:44:26 +0200211 object_ref);
brian m. carlsoncea43322017-02-21 23:47:30 +0000212 if (get_oid(replace_ref, &repl))
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200213 return error(_("failed to resolve '%s' as a valid ref"),
Johannes Schindeline24e8712018-04-29 00:44:26 +0200214 replace_ref);
Jeff King479bd752014-04-26 22:00:56 +0200215
brian m. carlsoncea43322017-02-21 23:47:30 +0000216 return replace_object_oid(object_ref, &object, replace_ref, &repl, force);
Jeff King479bd752014-04-26 22:00:56 +0200217}
218
Jeff Kingb892bb42014-04-26 22:00:57 +0200219/*
Jeff King2deda622014-06-24 05:46:31 -0400220 * Write the contents of the object named by "sha1" to the file "filename".
221 * If "raw" is true, then the object's raw contents are printed according to
222 * "type". Otherwise, we pretty-print the contents for human editing.
Jeff Kingb892bb42014-04-26 22:00:57 +0200223 */
Johannes Schindeline24e8712018-04-29 00:44:26 +0200224static int export_object(const struct object_id *oid, enum object_type type,
Jeff King2deda622014-06-24 05:46:31 -0400225 int raw, const char *filename)
Jeff Kingb892bb42014-04-26 22:00:57 +0200226{
René Scharfed3180272014-08-19 21:09:35 +0200227 struct child_process cmd = CHILD_PROCESS_INIT;
Jeff Kingb892bb42014-04-26 22:00:57 +0200228 int fd;
229
230 fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
231 if (fd < 0)
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200232 return error_errno(_("unable to open %s for writing"), filename);
Jeff Kingb892bb42014-04-26 22:00:57 +0200233
Jeff King22f9b7f2020-07-28 16:24:27 -0400234 strvec_push(&cmd.args, "--no-replace-objects");
235 strvec_push(&cmd.args, "cat-file");
Jeff King2deda622014-06-24 05:46:31 -0400236 if (raw)
Jeff King22f9b7f2020-07-28 16:24:27 -0400237 strvec_push(&cmd.args, type_name(type));
Jeff King2deda622014-06-24 05:46:31 -0400238 else
Jeff King22f9b7f2020-07-28 16:24:27 -0400239 strvec_push(&cmd.args, "-p");
240 strvec_push(&cmd.args, oid_to_hex(oid));
Jeff Kingb892bb42014-04-26 22:00:57 +0200241 cmd.git_cmd = 1;
242 cmd.out = fd;
243
244 if (run_command(&cmd))
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200245 return error(_("cat-file reported failure"));
Johannes Schindeline24e8712018-04-29 00:44:26 +0200246 return 0;
Jeff Kingb892bb42014-04-26 22:00:57 +0200247}
248
249/*
250 * Read a previously-exported (and possibly edited) object back from "filename",
251 * interpreting it as "type", and writing the result to the object database.
252 * The sha1 of the written object is returned via sha1.
253 */
Johannes Schindeline24e8712018-04-29 00:44:26 +0200254static int import_object(struct object_id *oid, enum object_type type,
Jeff King2deda622014-06-24 05:46:31 -0400255 int raw, const char *filename)
Jeff Kingb892bb42014-04-26 22:00:57 +0200256{
257 int fd;
258
259 fd = open(filename, O_RDONLY);
260 if (fd < 0)
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200261 return error_errno(_("unable to open %s for reading"), filename);
Jeff Kingb892bb42014-04-26 22:00:57 +0200262
Jeff King2deda622014-06-24 05:46:31 -0400263 if (!raw && type == OBJ_TREE) {
René Scharfed3180272014-08-19 21:09:35 +0200264 struct child_process cmd = CHILD_PROCESS_INIT;
Jeff Kingb892bb42014-04-26 22:00:57 +0200265 struct strbuf result = STRBUF_INIT;
266
Ævar Arnfjörð Bjarmason2b709892021-11-25 23:52:20 +0100267 strvec_push(&cmd.args, "mktree");
Jeff Kingb892bb42014-04-26 22:00:57 +0200268 cmd.git_cmd = 1;
269 cmd.in = fd;
270 cmd.out = -1;
271
Johannes Schindeline24e8712018-04-29 00:44:26 +0200272 if (start_command(&cmd)) {
273 close(fd);
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200274 return error(_("unable to spawn mktree"));
Johannes Schindeline24e8712018-04-29 00:44:26 +0200275 }
Jeff Kingb892bb42014-04-26 22:00:57 +0200276
brian m. carlson28ba1832019-08-18 20:04:02 +0000277 if (strbuf_read(&result, cmd.out, the_hash_algo->hexsz + 1) < 0) {
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200278 error_errno(_("unable to read from mktree"));
Johannes Schindeline24e8712018-04-29 00:44:26 +0200279 close(fd);
280 close(cmd.out);
281 return -1;
282 }
Jeff Kingb892bb42014-04-26 22:00:57 +0200283 close(cmd.out);
284
Johannes Schindeline24e8712018-04-29 00:44:26 +0200285 if (finish_command(&cmd)) {
286 strbuf_release(&result);
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200287 return error(_("mktree reported failure"));
Johannes Schindeline24e8712018-04-29 00:44:26 +0200288 }
289 if (get_oid_hex(result.buf, oid) < 0) {
290 strbuf_release(&result);
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200291 return error(_("mktree did not return an object name"));
Johannes Schindeline24e8712018-04-29 00:44:26 +0200292 }
Jeff Kingb892bb42014-04-26 22:00:57 +0200293
294 strbuf_release(&result);
295 } else {
296 struct stat st;
297 int flags = HASH_FORMAT_CHECK | HASH_WRITE_OBJECT;
298
Johannes Schindeline24e8712018-04-29 00:44:26 +0200299 if (fstat(fd, &st) < 0) {
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200300 error_errno(_("unable to fstat %s"), filename);
Johannes Schindeline24e8712018-04-29 00:44:26 +0200301 close(fd);
302 return -1;
303 }
Nguyễn Thái Ngọc Duyf8adbec2019-01-24 15:29:12 +0700304 if (index_fd(the_repository->index, oid, fd, &st, type, NULL, flags) < 0)
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200305 return error(_("unable to write object to database"));
Jeff Kingb892bb42014-04-26 22:00:57 +0200306 /* index_fd close()s fd for us */
307 }
308
309 /*
310 * No need to close(fd) here; both run-command and index-fd
311 * will have done it for us.
312 */
Johannes Schindeline24e8712018-04-29 00:44:26 +0200313 return 0;
Jeff Kingb892bb42014-04-26 22:00:57 +0200314}
315
Jeff King2deda622014-06-24 05:46:31 -0400316static int edit_and_replace(const char *object_ref, int force, int raw)
Jeff Kingb892bb42014-04-26 22:00:57 +0200317{
Johannes Schindeline24e8712018-04-29 00:44:26 +0200318 char *tmpfile;
Jeff Kingb892bb42014-04-26 22:00:57 +0200319 enum object_type type;
Brandon Williamsefdfe112018-02-14 10:59:59 -0800320 struct object_id old_oid, new_oid, prev;
Jeff King7f897b62017-03-28 15:46:30 -0400321 struct strbuf ref = STRBUF_INIT;
Jeff Kingb892bb42014-04-26 22:00:57 +0200322
Brandon Williamsefdfe112018-02-14 10:59:59 -0800323 if (get_oid(object_ref, &old_oid) < 0)
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200324 return error(_("not a valid object name: '%s'"), object_ref);
Jeff Kingb892bb42014-04-26 22:00:57 +0200325
Stefan Beller0df8e962018-04-25 11:20:59 -0700326 type = oid_object_info(the_repository, &old_oid, NULL);
Jeff Kingb892bb42014-04-26 22:00:57 +0200327 if (type < 0)
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200328 return error(_("unable to get object type for %s"),
Johannes Schindeline24e8712018-04-29 00:44:26 +0200329 oid_to_hex(&old_oid));
Jeff Kingb892bb42014-04-26 22:00:57 +0200330
Johannes Schindeline24e8712018-04-29 00:44:26 +0200331 if (check_ref_valid(&old_oid, &prev, &ref, force)) {
332 strbuf_release(&ref);
333 return -1;
334 }
Jeff King7f897b62017-03-28 15:46:30 -0400335 strbuf_release(&ref);
Christian Couder24790832014-05-17 14:16:36 +0200336
Johannes Schindeline24e8712018-04-29 00:44:26 +0200337 tmpfile = git_pathdup("REPLACE_EDITOBJ");
338 if (export_object(&old_oid, type, raw, tmpfile)) {
339 free(tmpfile);
340 return -1;
341 }
342 if (launch_editor(tmpfile, NULL, NULL) < 0) {
343 free(tmpfile);
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200344 return error(_("editing object file failed"));
Johannes Schindeline24e8712018-04-29 00:44:26 +0200345 }
346 if (import_object(&new_oid, type, raw, tmpfile)) {
347 free(tmpfile);
348 return -1;
349 }
Jeff Kingb892bb42014-04-26 22:00:57 +0200350 free(tmpfile);
351
Jeff King4a7e27e2018-08-28 17:22:40 -0400352 if (oideq(&old_oid, &new_oid))
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200353 return error(_("new object is the same as the old one: '%s'"), oid_to_hex(&old_oid));
Christian Couderf22166b2014-05-17 14:16:34 +0200354
Brandon Williamsefdfe112018-02-14 10:59:59 -0800355 return replace_object_oid(object_ref, &old_oid, "replacement", &new_oid, force);
Jeff Kingb892bb42014-04-26 22:00:57 +0200356}
357
Johannes Schindeline24e8712018-04-29 00:44:26 +0200358static int replace_parents(struct strbuf *buf, int argc, const char **argv)
Christian Couder4228e8b2014-07-19 17:01:08 +0200359{
360 struct strbuf new_parents = STRBUF_INIT;
361 const char *parent_start, *parent_end;
362 int i;
brian m. carlson28ba1832019-08-18 20:04:02 +0000363 const unsigned hexsz = the_hash_algo->hexsz;
Christian Couder4228e8b2014-07-19 17:01:08 +0200364
365 /* find existing parents */
366 parent_start = buf->buf;
brian m. carlson28ba1832019-08-18 20:04:02 +0000367 parent_start += hexsz + 6; /* "tree " + "hex sha1" + "\n" */
Christian Couder4228e8b2014-07-19 17:01:08 +0200368 parent_end = parent_start;
369
370 while (starts_with(parent_end, "parent "))
brian m. carlson28ba1832019-08-18 20:04:02 +0000371 parent_end += hexsz + 8; /* "parent " + "hex sha1" + "\n" */
Christian Couder4228e8b2014-07-19 17:01:08 +0200372
373 /* prepare new parents */
374 for (i = 0; i < argc; i++) {
brian m. carlsoncea43322017-02-21 23:47:30 +0000375 struct object_id oid;
Christian Couderf8e44a82019-03-31 15:46:58 +0200376 struct commit *commit;
377
Johannes Schindeline24e8712018-04-29 00:44:26 +0200378 if (get_oid(argv[i], &oid) < 0) {
379 strbuf_release(&new_parents);
Nguyễn Thái Ngọc Duy1a07e592018-07-21 09:49:19 +0200380 return error(_("not a valid object name: '%s'"),
Johannes Schindeline24e8712018-04-29 00:44:26 +0200381 argv[i]);
382 }
Christian Couderf8e44a82019-03-31 15:46:58 +0200383 commit = lookup_commit_reference(the_repository, &oid);
384 if (!commit) {
Johannes Schindeline24e8712018-04-29 00:44:26 +0200385 strbuf_release(&new_parents);
Christian Couderf8e44a82019-03-31 15:46:58 +0200386 return error(_("could not parse %s as a commit"), argv[i]);
Johannes Schindeline24e8712018-04-29 00:44:26 +0200387 }
Christian Couderf8e44a82019-03-31 15:46:58 +0200388 strbuf_addf(&new_parents, "parent %s\n", oid_to_hex(&commit->object.oid));
Christian Couder4228e8b2014-07-19 17:01:08 +0200389 }
390
391 /* replace existing parents with new ones */
392 strbuf_splice(buf, parent_start - buf->buf, parent_end - parent_start,
393 new_parents.buf, new_parents.len);
394
395 strbuf_release(&new_parents);
Johannes Schindeline24e8712018-04-29 00:44:26 +0200396 return 0;
Christian Couder4228e8b2014-07-19 17:01:08 +0200397}
398
Christian Couder25a05a82014-07-19 17:01:14 +0200399struct check_mergetag_data {
400 int argc;
401 const char **argv;
402};
403
Johannes Schindelinfef461e2018-04-25 11:54:04 +0200404static int check_one_mergetag(struct commit *commit,
Christian Couder25a05a82014-07-19 17:01:14 +0200405 struct commit_extra_header *extra,
406 void *data)
407{
408 struct check_mergetag_data *mergetag_data = (struct check_mergetag_data *)data;
409 const char *ref = mergetag_data->argv[0];
brian m. carlsoncea43322017-02-21 23:47:30 +0000410 struct object_id tag_oid;
Christian Couder25a05a82014-07-19 17:01:14 +0200411 struct tag *tag;
412 int i;
413
Matheus Tavares2dcde202020-01-30 17:32:22 -0300414 hash_object_file(the_hash_algo, extra->value, extra->len,
Ævar Arnfjörð Bjarmason44439c12022-02-05 00:48:32 +0100415 OBJ_TAG, &tag_oid);
Stefan Bellerce71efb2018-06-28 18:22:03 -0700416 tag = lookup_tag(the_repository, &tag_oid);
Christian Couder25a05a82014-07-19 17:01:14 +0200417 if (!tag)
Johannes Schindeline24e8712018-04-29 00:44:26 +0200418 return error(_("bad mergetag in commit '%s'"), ref);
Stefan Beller0e740fe2018-06-28 18:22:04 -0700419 if (parse_tag_buffer(the_repository, tag, extra->value, extra->len))
Johannes Schindeline24e8712018-04-29 00:44:26 +0200420 return error(_("malformed mergetag in commit '%s'"), ref);
Christian Couder25a05a82014-07-19 17:01:14 +0200421
422 /* iterate over new parents */
423 for (i = 1; i < mergetag_data->argc; i++) {
brian m. carlsonf2fd0762015-11-10 02:22:28 +0000424 struct object_id oid;
brian m. carlsone82caf32017-07-13 23:49:28 +0000425 if (get_oid(mergetag_data->argv[i], &oid) < 0)
Nguyễn Thái Ngọc Duy1a07e592018-07-21 09:49:19 +0200426 return error(_("not a valid object name: '%s'"),
Johannes Schindeline24e8712018-04-29 00:44:26 +0200427 mergetag_data->argv[i]);
René Scharfec77722b2019-09-05 21:59:42 +0200428 if (oideq(get_tagged_oid(tag), &oid))
Johannes Schindelinfef461e2018-04-25 11:54:04 +0200429 return 0; /* found */
Christian Couder25a05a82014-07-19 17:01:14 +0200430 }
431
Johannes Schindeline24e8712018-04-29 00:44:26 +0200432 return error(_("original commit '%s' contains mergetag '%s' that is "
433 "discarded; use --edit instead of --graft"), ref,
434 oid_to_hex(&tag_oid));
Christian Couder25a05a82014-07-19 17:01:14 +0200435}
436
Johannes Schindelinfef461e2018-04-25 11:54:04 +0200437static int check_mergetags(struct commit *commit, int argc, const char **argv)
Christian Couder25a05a82014-07-19 17:01:14 +0200438{
439 struct check_mergetag_data mergetag_data;
440
441 mergetag_data.argc = argc;
442 mergetag_data.argv = argv;
Johannes Schindelinfef461e2018-04-25 11:54:04 +0200443 return for_each_mergetag(check_one_mergetag, commit, &mergetag_data);
Christian Couder25a05a82014-07-19 17:01:14 +0200444}
445
Johannes Schindelin041c98e2018-04-29 00:44:32 +0200446static int create_graft(int argc, const char **argv, int force, int gentle)
Christian Couder4228e8b2014-07-19 17:01:08 +0200447{
Brandon Williamsefdfe112018-02-14 10:59:59 -0800448 struct object_id old_oid, new_oid;
Christian Couder4228e8b2014-07-19 17:01:08 +0200449 const char *old_ref = argv[0];
450 struct commit *commit;
451 struct strbuf buf = STRBUF_INIT;
452 const char *buffer;
453 unsigned long size;
454
Brandon Williamsefdfe112018-02-14 10:59:59 -0800455 if (get_oid(old_ref, &old_oid) < 0)
Nguyễn Thái Ngọc Duy1a07e592018-07-21 09:49:19 +0200456 return error(_("not a valid object name: '%s'"), old_ref);
Stefan Beller2122f672018-06-28 18:21:58 -0700457 commit = lookup_commit_reference(the_repository, &old_oid);
Johannes Schindeline24e8712018-04-29 00:44:26 +0200458 if (!commit)
459 return error(_("could not parse %s"), old_ref);
Christian Couder4228e8b2014-07-19 17:01:08 +0200460
461 buffer = get_commit_buffer(commit, &size);
462 strbuf_add(&buf, buffer, size);
463 unuse_commit_buffer(commit, buffer);
464
Johannes Schindeline24e8712018-04-29 00:44:26 +0200465 if (replace_parents(&buf, argc - 1, &argv[1]) < 0) {
466 strbuf_release(&buf);
467 return -1;
468 }
Christian Couder4228e8b2014-07-19 17:01:08 +0200469
Christian Couder0b05ab62014-07-19 17:01:12 +0200470 if (remove_signature(&buf)) {
Nguyễn Thái Ngọc Duy1a07e592018-07-21 09:49:19 +0200471 warning(_("the original commit '%s' has a gpg signature"), old_ref);
Christian Couder0b05ab62014-07-19 17:01:12 +0200472 warning(_("the signature will be removed in the replacement commit!"));
473 }
474
Johannes Schindeline24e8712018-04-29 00:44:26 +0200475 if (check_mergetags(commit, argc, argv)) {
476 strbuf_release(&buf);
477 return -1;
478 }
Christian Couder25a05a82014-07-19 17:01:14 +0200479
Ævar Arnfjörð Bjarmasonc80d2262022-02-05 00:48:26 +0100480 if (write_object_file(buf.buf, buf.len, OBJ_COMMIT, &new_oid)) {
Johannes Schindeline24e8712018-04-29 00:44:26 +0200481 strbuf_release(&buf);
482 return error(_("could not write replacement commit for: '%s'"),
483 old_ref);
484 }
Christian Couder4228e8b2014-07-19 17:01:08 +0200485
486 strbuf_release(&buf);
487
Christian Couderee521ec2019-03-31 15:46:59 +0200488 if (oideq(&commit->object.oid, &new_oid)) {
Johannes Schindelin041c98e2018-04-29 00:44:32 +0200489 if (gentle) {
Christian Couderee521ec2019-03-31 15:46:59 +0200490 warning(_("graft for '%s' unnecessary"),
491 oid_to_hex(&commit->object.oid));
Johannes Schindelin041c98e2018-04-29 00:44:32 +0200492 return 0;
493 }
Christian Couderee521ec2019-03-31 15:46:59 +0200494 return error(_("new commit is the same as the old one: '%s'"),
495 oid_to_hex(&commit->object.oid));
Johannes Schindelin041c98e2018-04-29 00:44:32 +0200496 }
Christian Couder4228e8b2014-07-19 17:01:08 +0200497
Christian Couderee521ec2019-03-31 15:46:59 +0200498 return replace_object_oid(old_ref, &commit->object.oid,
499 "replacement", &new_oid, force);
Christian Couder4228e8b2014-07-19 17:01:08 +0200500}
501
Johannes Schindelinfb404292018-04-29 00:44:35 +0200502static int convert_graft_file(int force)
503{
Junio C Hamanob16b60f2018-06-29 10:24:33 -0700504 const char *graft_file = get_graft_file(the_repository);
Johannes Schindelinfb404292018-04-29 00:44:35 +0200505 FILE *fp = fopen_or_warn(graft_file, "r");
506 struct strbuf buf = STRBUF_INIT, err = STRBUF_INIT;
Jeff King22f9b7f2020-07-28 16:24:27 -0400507 struct strvec args = STRVEC_INIT;
Johannes Schindelinfb404292018-04-29 00:44:35 +0200508
509 if (!fp)
510 return -1;
511
Ævar Arnfjörð Bjarmasonab628582021-08-23 12:44:02 +0200512 no_graft_file_deprecated_advice = 1;
Johannes Schindelinfb404292018-04-29 00:44:35 +0200513 while (strbuf_getline(&buf, fp) != EOF) {
514 if (*buf.buf == '#')
515 continue;
516
Jeff King22f9b7f2020-07-28 16:24:27 -0400517 strvec_split(&args, buf.buf);
Jeff Kingd70a9eb2020-07-28 20:37:20 -0400518 if (args.nr && create_graft(args.nr, args.v, force, 1))
Johannes Schindelinfb404292018-04-29 00:44:35 +0200519 strbuf_addf(&err, "\n\t%s", buf.buf);
Jeff King22f9b7f2020-07-28 16:24:27 -0400520 strvec_clear(&args);
Johannes Schindelinfb404292018-04-29 00:44:35 +0200521 }
522 fclose(fp);
523
524 strbuf_release(&buf);
525
526 if (!err.len)
527 return unlink_or_warn(graft_file);
528
529 warning(_("could not convert the following graft(s):\n%s"), err.buf);
530 strbuf_release(&err);
531
532 return -1;
533}
534
Christian Couder54b0c1e2009-02-02 06:12:44 +0100535int cmd_replace(int argc, const char **argv, const char *prefix)
536{
Jeff King70c7bd62014-04-26 22:00:55 +0200537 int force = 0;
Jeff King2deda622014-06-24 05:46:31 -0400538 int raw = 0;
Christian Couder44f9f852013-12-11 08:46:10 +0100539 const char *format = NULL;
Jeff King70c7bd62014-04-26 22:00:55 +0200540 enum {
541 MODE_UNSPECIFIED = 0,
542 MODE_LIST,
543 MODE_DELETE,
Jeff Kingb892bb42014-04-26 22:00:57 +0200544 MODE_EDIT,
Christian Couder4228e8b2014-07-19 17:01:08 +0200545 MODE_GRAFT,
Johannes Schindelinfb404292018-04-29 00:44:35 +0200546 MODE_CONVERT_GRAFT_FILE,
Jeff King70c7bd62014-04-26 22:00:55 +0200547 MODE_REPLACE
548 } cmdmode = MODE_UNSPECIFIED;
Christian Couder54b0c1e2009-02-02 06:12:44 +0100549 struct option options[] = {
Jeff King70c7bd62014-04-26 22:00:55 +0200550 OPT_CMDMODE('l', "list", &cmdmode, N_("list replace refs"), MODE_LIST),
551 OPT_CMDMODE('d', "delete", &cmdmode, N_("delete replace refs"), MODE_DELETE),
Jeff Kingb892bb42014-04-26 22:00:57 +0200552 OPT_CMDMODE('e', "edit", &cmdmode, N_("edit existing object"), MODE_EDIT),
Christian Couder4228e8b2014-07-19 17:01:08 +0200553 OPT_CMDMODE('g', "graft", &cmdmode, N_("change a commit's parents"), MODE_GRAFT),
Johannes Schindelinfb404292018-04-29 00:44:35 +0200554 OPT_CMDMODE(0, "convert-graft-file", &cmdmode, N_("convert existing graft file"), MODE_CONVERT_GRAFT_FILE),
Nguyễn Thái Ngọc Duy1b354752018-02-09 18:02:13 +0700555 OPT_BOOL_F('f', "force", &force, N_("replace the ref if it exists"),
556 PARSE_OPT_NOCOMPLETE),
Jeff King2deda622014-06-24 05:46:31 -0400557 OPT_BOOL(0, "raw", &raw, N_("do not pretty-print contents for --edit")),
Christian Couder44f9f852013-12-11 08:46:10 +0100558 OPT_STRING(0, "format", &format, N_("format"), N_("use this format")),
Christian Couder54b0c1e2009-02-02 06:12:44 +0100559 OPT_END()
560 };
561
Jeff King6ebd1ca2018-07-18 16:45:20 -0400562 read_replace_refs = 0;
Johannes Schindelin36b14372016-04-20 08:38:03 +0200563 git_config(git_default_config, NULL);
Christian Couder769a4fa2013-12-11 08:46:12 +0100564
Christian Couder451bb212009-02-02 06:12:58 +0100565 argc = parse_options(argc, argv, prefix, options, git_replace_usage, 0);
Christian Couder54b0c1e2009-02-02 06:12:44 +0100566
Jeff King70c7bd62014-04-26 22:00:55 +0200567 if (!cmdmode)
568 cmdmode = argc ? MODE_REPLACE : MODE_LIST;
Jeff King3f495f62014-04-26 22:00:54 +0200569
Jeff King70c7bd62014-04-26 22:00:55 +0200570 if (format && cmdmode != MODE_LIST)
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200571 usage_msg_opt(_("--format cannot be used when not listing"),
Christian Couder44f9f852013-12-11 08:46:10 +0100572 git_replace_usage, options);
573
Christian Couder4228e8b2014-07-19 17:01:08 +0200574 if (force &&
575 cmdmode != MODE_REPLACE &&
576 cmdmode != MODE_EDIT &&
Johannes Schindelinfb404292018-04-29 00:44:35 +0200577 cmdmode != MODE_GRAFT &&
578 cmdmode != MODE_CONVERT_GRAFT_FILE)
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200579 usage_msg_opt(_("-f only makes sense when writing a replacement"),
Christian Couder86af2ca2009-02-02 06:13:06 +0100580 git_replace_usage, options);
Christian Couderbebdd272009-02-02 06:12:53 +0100581
Jeff King2deda622014-06-24 05:46:31 -0400582 if (raw && cmdmode != MODE_EDIT)
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200583 usage_msg_opt(_("--raw only makes sense with --edit"),
Jeff King2deda622014-06-24 05:46:31 -0400584 git_replace_usage, options);
585
Jeff King70c7bd62014-04-26 22:00:55 +0200586 switch (cmdmode) {
587 case MODE_DELETE:
Christian Couder54b0c1e2009-02-02 06:12:44 +0100588 if (argc < 1)
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200589 usage_msg_opt(_("-d needs at least one argument"),
Christian Couder86af2ca2009-02-02 06:13:06 +0100590 git_replace_usage, options);
Christian Couder54b0c1e2009-02-02 06:12:44 +0100591 return for_each_replace_name(argv, delete_replace_ref);
Christian Couder54b0c1e2009-02-02 06:12:44 +0100592
Jeff King70c7bd62014-04-26 22:00:55 +0200593 case MODE_REPLACE:
Christian Couderbebdd272009-02-02 06:12:53 +0100594 if (argc != 2)
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200595 usage_msg_opt(_("bad number of arguments"),
Christian Couder86af2ca2009-02-02 06:13:06 +0100596 git_replace_usage, options);
Christian Couderbebdd272009-02-02 06:12:53 +0100597 return replace_object(argv[0], argv[1], force);
Jeff King70c7bd62014-04-26 22:00:55 +0200598
Jeff Kingb892bb42014-04-26 22:00:57 +0200599 case MODE_EDIT:
600 if (argc != 1)
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200601 usage_msg_opt(_("-e needs exactly one argument"),
Jeff Kingb892bb42014-04-26 22:00:57 +0200602 git_replace_usage, options);
Jeff King2deda622014-06-24 05:46:31 -0400603 return edit_and_replace(argv[0], force, raw);
Jeff Kingb892bb42014-04-26 22:00:57 +0200604
Christian Couder4228e8b2014-07-19 17:01:08 +0200605 case MODE_GRAFT:
606 if (argc < 1)
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200607 usage_msg_opt(_("-g needs at least one argument"),
Christian Couder4228e8b2014-07-19 17:01:08 +0200608 git_replace_usage, options);
Johannes Schindelin041c98e2018-04-29 00:44:32 +0200609 return create_graft(argc, argv, force, 0);
Christian Couder4228e8b2014-07-19 17:01:08 +0200610
Johannes Schindelinfb404292018-04-29 00:44:35 +0200611 case MODE_CONVERT_GRAFT_FILE:
612 if (argc != 0)
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200613 usage_msg_opt(_("--convert-graft-file takes no argument"),
Johannes Schindelinfb404292018-04-29 00:44:35 +0200614 git_replace_usage, options);
615 return !!convert_graft_file(force);
Christian Couder54b0c1e2009-02-02 06:12:44 +0100616
617 case MODE_LIST:
618 if (argc > 1)
Nguyễn Thái Ngọc Duy225c62e2018-07-21 09:49:25 +0200619 usage_msg_opt(_("only one pattern can be given with -l"),
Christian Couder54b0c1e2009-02-02 06:12:44 +0100620 git_replace_usage, options);
621 return list_replace_refs(argv[0], format);
622
623 default:
Johannes Schindelind398f2e2018-04-25 11:54:06 +0200624 BUG("invalid cmdmode %d", (int)cmdmode);
Christian Couder54b0c1e2009-02-02 06:12:44 +0100625 }
626}