From 292687003abcfb68d296c57d7e812b0469f74647 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Fri, 23 Jan 2009 10:06:38 +0100 Subject: refs: add a "for_each_replace_ref" function This is some preparation work for the following patches that are using the "refs/replace/" ref namespace. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano diff --git a/refs.c b/refs.c index 24438c6..6a13651 100644 --- a/refs.c +++ b/refs.c @@ -667,6 +667,11 @@ int for_each_remote_ref(each_ref_fn fn, void *cb_data) return for_each_ref_in("refs/remotes/", fn, cb_data); } +int for_each_replace_ref(each_ref_fn fn, void *cb_data) +{ + return do_for_each_ref("refs/replace/", fn, 13, 0, cb_data); +} + int for_each_rawref(each_ref_fn fn, void *cb_data) { return do_for_each_ref("refs/", fn, 0, diff --git a/refs.h b/refs.h index c11f6a6..777b5b7 100644 --- a/refs.h +++ b/refs.h @@ -24,6 +24,7 @@ extern int for_each_ref_in(const char *, each_ref_fn, void *); extern int for_each_tag_ref(each_ref_fn, void *); extern int for_each_branch_ref(each_ref_fn, void *); extern int for_each_remote_ref(each_ref_fn, void *); +extern int for_each_replace_ref(each_ref_fn, void *); /* can be used to learn about broken ref and symref */ extern int for_each_rawref(each_ref_fn, void *); -- cgit v0.10.2-6-g49f6 From 680955702990c1d4bfb3c6feed6ae9c6cb5c3c07 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Fri, 23 Jan 2009 10:06:53 +0100 Subject: replace_object: add mechanism to replace objects found in "refs/replace/" The code implementing this mechanism has been copied more-or-less from the commit graft code. This mechanism is used in "read_sha1_file". sha1 passed to this function that match a ref name in "refs/replace/" are replaced by the sha1 that has been read in the ref. We "die" if the replacement recursion depth is too high or if we can't read the replacement object. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 0ab1cff..eaea65c 100644 --- a/Makefile +++ b/Makefile @@ -512,6 +512,7 @@ LIB_OBJS += read-cache.o LIB_OBJS += reflog-walk.o LIB_OBJS += refs.o LIB_OBJS += remote.o +LIB_OBJS += replace_object.o LIB_OBJS += rerere.o LIB_OBJS += revision.o LIB_OBJS += run-command.o diff --git a/commit.h b/commit.h index f3eaf1d..a8bdd09 100644 --- a/commit.h +++ b/commit.h @@ -124,6 +124,8 @@ struct commit_graft *read_graft_line(char *buf, int len); int register_commit_graft(struct commit_graft *, int); struct commit_graft *lookup_commit_graft(const unsigned char *sha1); +const unsigned char *lookup_replace_object(const unsigned char *sha1); + extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, int cleanup); extern struct commit_list *get_merge_bases_many(struct commit *one, int n, struct commit **twos, int cleanup); extern struct commit_list *get_octopus_merge_bases(struct commit_list *in); diff --git a/replace_object.c b/replace_object.c new file mode 100644 index 0000000..b23e1cd --- /dev/null +++ b/replace_object.c @@ -0,0 +1,111 @@ +#include "cache.h" +#include "sha1-lookup.h" +#include "refs.h" + +static struct replace_object { + unsigned char sha1[2][20]; +} **replace_object; + +static int replace_object_alloc, replace_object_nr; + +static const unsigned char *replace_sha1_access(size_t index, void *table) +{ + struct replace_object **replace = table; + return replace[index]->sha1[0]; +} + +static int replace_object_pos(const unsigned char *sha1) +{ + return sha1_pos(sha1, replace_object, replace_object_nr, + replace_sha1_access); +} + +static int register_replace_object(struct replace_object *replace, + int ignore_dups) +{ + int pos = replace_object_pos(replace->sha1[0]); + + if (0 <= pos) { + if (ignore_dups) + free(replace); + else { + free(replace_object[pos]); + replace_object[pos] = replace; + } + return 1; + } + pos = -pos - 1; + if (replace_object_alloc <= ++replace_object_nr) { + replace_object_alloc = alloc_nr(replace_object_alloc); + replace_object = xrealloc(replace_object, + sizeof(*replace_object) * + replace_object_alloc); + } + if (pos < replace_object_nr) + memmove(replace_object + pos + 1, + replace_object + pos, + (replace_object_nr - pos - 1) * + sizeof(*replace_object)); + replace_object[pos] = replace; + return 0; +} + +static int register_replace_ref(const char *refname, + const unsigned char *sha1, + int flag, void *cb_data) +{ + /* Get sha1 from refname */ + const char *slash = strrchr(refname, '/'); + const char *hash = slash ? slash + 1 : refname; + struct replace_object *repl_obj = xmalloc(sizeof(*repl_obj)); + + if (strlen(hash) != 40 || get_sha1_hex(hash, repl_obj->sha1[0])) { + free(repl_obj); + warning("bad replace ref name: %s", refname); + return 0; + } + + /* Copy sha1 from the read ref */ + hashcpy(repl_obj->sha1[1], sha1); + + /* Register new object */ + if (register_replace_object(repl_obj, 1)) + die("duplicate replace ref: %s", refname); + + return 0; +} + +static void prepare_replace_object(void) +{ + static int replace_object_prepared; + + if (replace_object_prepared) + return; + + for_each_replace_ref(register_replace_ref, NULL); + replace_object_prepared = 1; +} + +/* We allow "recursive" replacement. Only within reason, though */ +#define MAXREPLACEDEPTH 5 + +const unsigned char *lookup_replace_object(const unsigned char *sha1) +{ + int pos, depth = MAXREPLACEDEPTH; + const unsigned char *cur = sha1; + + prepare_replace_object(); + + /* Try to recursively replace the object */ + do { + if (--depth < 0) + die("replace depth too high for object %s", + sha1_to_hex(sha1)); + + pos = replace_object_pos(cur); + if (0 <= pos) + cur = replace_object[pos]->sha1[1]; + } while (0 <= pos); + + return cur; +} diff --git a/sha1_file.c b/sha1_file.c index e73cd4f..4bf24ff 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -2148,10 +2148,18 @@ static void *read_object(const unsigned char *sha1, enum object_type *type, void *read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size) { - void *data = read_object(sha1, type, size); + const unsigned char *repl = lookup_replace_object(sha1); + void *data = read_object(repl, type, size); + + /* die if we replaced an object with one that does not exist */ + if (!data && repl != sha1) + die("replacement %s not found for %s", + sha1_to_hex(repl), sha1_to_hex(sha1)); + /* legacy behavior is to die on corrupted objects */ - if (!data && (has_loose_object(sha1) || has_packed_and_bad(sha1))) - die("object %s is corrupted", sha1_to_hex(sha1)); + if (!data && (has_loose_object(repl) || has_packed_and_bad(repl))) + die("object %s is corrupted", sha1_to_hex(repl)); + return data; } -- cgit v0.10.2-6-g49f6 From f5552aee39d664bb8774c205341abd6db74b38d3 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Fri, 23 Jan 2009 10:07:01 +0100 Subject: sha1_file: add a "read_sha1_file_repl" function This new function will replace "read_sha1_file". This latter function becoming just a stub to call the former will a NULL "replacement" argument. This new function is needed because sometimes we need to use the replacement sha1. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano diff --git a/cache.h b/cache.h index b8503ad..94b1228 100644 --- a/cache.h +++ b/cache.h @@ -649,7 +649,11 @@ char *strip_path_suffix(const char *path, const char *suffix); /* Read and unpack a sha1 file into memory, write memory to a sha1 file */ extern int sha1_object_info(const unsigned char *, unsigned long *); -extern void * read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size); +extern void *read_sha1_file_repl(const unsigned char *sha1, enum object_type *type, unsigned long *size, const unsigned char **replacement); +static inline void *read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size) +{ + return read_sha1_file_repl(sha1, type, size, NULL); +} extern int hash_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *sha1); extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *return_sha1); extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *); diff --git a/sha1_file.c b/sha1_file.c index 4bf24ff..9119b79 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -2145,8 +2145,10 @@ static void *read_object(const unsigned char *sha1, enum object_type *type, return read_packed_sha1(sha1, type, size); } -void *read_sha1_file(const unsigned char *sha1, enum object_type *type, - unsigned long *size) +void *read_sha1_file_repl(const unsigned char *sha1, + enum object_type *type, + unsigned long *size, + const unsigned char **replacement) { const unsigned char *repl = lookup_replace_object(sha1); void *data = read_object(repl, type, size); @@ -2160,6 +2162,9 @@ void *read_sha1_file(const unsigned char *sha1, enum object_type *type, if (!data && (has_loose_object(repl) || has_packed_and_bad(repl))) die("object %s is corrupted", sha1_to_hex(repl)); + if (replacement) + *replacement = repl; + return data; } -- cgit v0.10.2-6-g49f6 From 0e87c36763a384d5bf6fae4bda44fd035c0b75ff Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Fri, 23 Jan 2009 10:07:10 +0100 Subject: object: call "check_sha1_signature" with the replacement sha1 Otherwise we get a "sha1 mismatch" error for replaced objects. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano diff --git a/object.c b/object.c index a6ef439..fe8eaaf 100644 --- a/object.c +++ b/object.c @@ -188,17 +188,18 @@ struct object *parse_object(const unsigned char *sha1) unsigned long size; enum object_type type; int eaten; - void *buffer = read_sha1_file(sha1, &type, &size); + const unsigned char *repl; + void *buffer = read_sha1_file_repl(sha1, &type, &size, &repl); if (buffer) { struct object *obj; - if (check_sha1_signature(sha1, buffer, size, typename(type)) < 0) { + if (check_sha1_signature(repl, buffer, size, typename(type)) < 0) { free(buffer); - error("sha1 mismatch %s\n", sha1_to_hex(sha1)); + error("sha1 mismatch %s\n", sha1_to_hex(repl)); return NULL; } - obj = parse_object_buffer(sha1, type, size, buffer, &eaten); + obj = parse_object_buffer(repl, type, size, buffer, &eaten); if (!eaten) free(buffer); return obj; -- cgit v0.10.2-6-g49f6 From a3e826722515ec8abed8ccf29d958eb52d4be3f8 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Fri, 23 Jan 2009 10:07:18 +0100 Subject: replace_object: add a test case In this patch the setup code is very big, but this will be used in test cases that will be added later. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh new file mode 100755 index 0000000..0a585ec --- /dev/null +++ b/t/t6050-replace.sh @@ -0,0 +1,75 @@ +#!/bin/sh +# +# Copyright (c) 2008 Christian Couder +# +test_description='Tests replace refs functionality' + +exec > hello && + echo "line 2" >> hello && + echo "line 3" >> hello && + echo "line 4" >> hello && + add_and_commit_file hello "4 lines" && + HASH1=$(git rev-parse --verify HEAD) && + echo "line BUG" >> hello && + echo "line 6" >> hello && + echo "line 7" >> hello && + echo "line 8" >> hello && + add_and_commit_file hello "4 more lines with a BUG" && + HASH2=$(git rev-parse --verify HEAD) && + echo "line 9" >> hello && + echo "line 10" >> hello && + add_and_commit_file hello "2 more lines" && + HASH3=$(git rev-parse --verify HEAD) && + echo "line 11" >> hello && + add_and_commit_file hello "1 more line" && + HASH4=$(git rev-parse --verify HEAD) && + sed -e "s/BUG/5/" hello > hello.new && + mv hello.new hello && + add_and_commit_file hello "BUG fixed" && + HASH5=$(git rev-parse --verify HEAD) && + echo "line 12" >> hello && + echo "line 13" >> hello && + add_and_commit_file hello "2 more lines" && + HASH6=$(git rev-parse --verify HEAD) + echo "line 14" >> hello && + echo "line 15" >> hello && + echo "line 16" >> hello && + add_and_commit_file hello "again 3 more lines" && + HASH7=$(git rev-parse --verify HEAD) +' + +test_expect_success 'replace the author' ' + git cat-file commit $HASH2 | grep "author A U Thor" && + R=$(git cat-file commit $HASH2 | sed -e "s/A U/O/" | git hash-object -t commit --stdin -w) && + git cat-file commit $R | grep "author O Thor" && + git update-ref refs/replace/$HASH2 $R && + git show HEAD~5 | grep "O Thor" && + git show $HASH2 | grep "O Thor" +' + +# +# +test_done -- cgit v0.10.2-6-g49f6 From cc400f50112a58471b992a54b1a05d99a8a82457 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Fri, 23 Jan 2009 10:07:26 +0100 Subject: mktag: call "check_sha1_signature" with the replacement sha1 Otherwise we get a "sha1 mismatch" error for replaced objects. Note that I am not sure at all that this is a good change. It may be that we should just refuse to tag a replaced object. But in this case we should probably give a meaningfull error message instead of "sha1 mismatch". Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano diff --git a/mktag.c b/mktag.c index 99a356e..6e0b5fe 100644 --- a/mktag.c +++ b/mktag.c @@ -19,16 +19,17 @@ /* * We refuse to tag something we can't verify. Just because. */ -static int verify_object(unsigned char *sha1, const char *expected_type) +static int verify_object(const unsigned char *sha1, const char *expected_type) { int ret = -1; enum object_type type; unsigned long size; - void *buffer = read_sha1_file(sha1, &type, &size); + const unsigned char *repl; + void *buffer = read_sha1_file_repl(sha1, &type, &size, &repl); if (buffer) { if (type == type_from_string(expected_type)) - ret = check_sha1_signature(sha1, buffer, size, expected_type); + ret = check_sha1_signature(repl, buffer, size, expected_type); free(buffer); } return ret; diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh index 0a585ec..334aed6 100755 --- a/t/t6050-replace.sh +++ b/t/t6050-replace.sh @@ -70,6 +70,18 @@ test_expect_success 'replace the author' ' git show $HASH2 | grep "O Thor" ' +cat >tag.sig < 0 +0000 + +EOF + +test_expect_success 'tag replaced commit' ' + git mktag .git/refs/tags/mytag 2>message +' + # # test_done -- cgit v0.10.2-6-g49f6 From dae556bdb1e2ad6fb5eafe82e975bde01029fca9 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Fri, 23 Jan 2009 10:07:46 +0100 Subject: environment: add global variable to disable replacement This new "read_replace_refs" global variable is set to 1 by default, so that replace refs are used by default. But reachability traversal and packing commands ("cmd_fsck", "cmd_prune", "cmd_pack_objects", "upload_pack", "cmd_unpack_objects") set it to 0, as they must work with the original DAG. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano diff --git a/builtin-fsck.c b/builtin-fsck.c index 7da706c..bc05de6 100644 --- a/builtin-fsck.c +++ b/builtin-fsck.c @@ -589,6 +589,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) struct alternate_object_database *alt; errors_found = 0; + read_replace_refs = 0; argc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0); if (write_lost_and_found) { diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 9742b45..b2c93d3 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -2103,6 +2103,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) int rp_ac_alloc = 64; int rp_ac; + read_replace_refs = 0; + rp_av = xcalloc(rp_ac_alloc, sizeof(*rp_av)); rp_av[0] = "pack-objects"; diff --git a/builtin-prune.c b/builtin-prune.c index 0ed9cce..8459aec 100644 --- a/builtin-prune.c +++ b/builtin-prune.c @@ -140,6 +140,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix) char *s; save_commit_buffer = 0; + read_replace_refs = 0; init_revisions(&revs, prefix); argc = parse_options(argc, argv, prefix, options, prune_usage, 0); diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c index 9a77323..c9f5ac0 100644 --- a/builtin-unpack-objects.c +++ b/builtin-unpack-objects.c @@ -495,6 +495,8 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix) int i; unsigned char sha1[20]; + read_replace_refs = 0; + git_config(git_default_config, NULL); quiet = !isatty(2); diff --git a/cache.h b/cache.h index 94b1228..91ef340 100644 --- a/cache.h +++ b/cache.h @@ -516,6 +516,7 @@ extern size_t packed_git_window_size; extern size_t packed_git_limit; extern size_t delta_base_cache_limit; extern int auto_crlf; +extern int read_replace_refs; extern int fsync_object_files; extern int core_preload_index; diff --git a/environment.c b/environment.c index 801a005..6d90074 100644 --- a/environment.c +++ b/environment.c @@ -38,6 +38,7 @@ int pager_use_color = 1; const char *editor_program; const char *excludes_file; int auto_crlf = 0; /* 1: both ways, -1: only when adding git objects */ +int read_replace_refs = 1; enum safe_crlf safe_crlf = SAFE_CRLF_WARN; unsigned whitespace_rule_cfg = WS_DEFAULT_RULE; enum branch_track git_branch_track = BRANCH_TRACK_REMOTE; diff --git a/replace_object.c b/replace_object.c index b23e1cd..eb59604 100644 --- a/replace_object.c +++ b/replace_object.c @@ -94,6 +94,9 @@ const unsigned char *lookup_replace_object(const unsigned char *sha1) int pos, depth = MAXREPLACEDEPTH; const unsigned char *cur = sha1; + if (!read_replace_refs) + return sha1; + prepare_replace_object(); /* Try to recursively replace the object */ diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh index 334aed6..17f6063 100755 --- a/t/t6050-replace.sh +++ b/t/t6050-replace.sh @@ -82,6 +82,29 @@ test_expect_success 'tag replaced commit' ' git mktag .git/refs/tags/mytag 2>message ' +test_expect_success '"git fsck" works' ' + git fsck master > fsck_master.out && + grep "dangling commit $R" fsck_master.out && + grep "dangling tag $(cat .git/refs/tags/mytag)" fsck_master.out && + test -z "$(git fsck)" +' + +test_expect_success 'repack, clone and fetch work' ' + git repack -a -d && + git clone --no-hardlinks . clone_dir && + cd clone_dir && + git show HEAD~5 | grep "A U Thor" && + git show $HASH2 | grep "A U Thor" && + git cat-file commit $R && + git repack -a -d && + test_must_fail git cat-file commit $R && + git fetch ../ "refs/replace/*:refs/replace/*" && + git show HEAD~5 | grep "O Thor" && + git show $HASH2 | grep "O Thor" && + git cat-file commit $R && + cd .. +' + # # test_done diff --git a/upload-pack.c b/upload-pack.c index edc7861..e6c4f34 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -618,6 +618,7 @@ int main(int argc, char **argv) int strict = 0; git_extract_argv0_path(argv[0]); + read_replace_refs = 0; for (i = 1; i < argc; i++) { char *arg = argv[i]; -- cgit v0.10.2-6-g49f6 From 54b0c1e041e50cc08b1520b7d557770916d0b7ab Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Mon, 2 Feb 2009 06:12:44 +0100 Subject: Add new "git replace" command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This command can only be used now to list replace refs in "refs/replace/" and to delete them. The option to list replace refs is "-l". The option to delete replace refs is "-d". The behavior should be consistent with how "git tag" and "git branch" are working. The code has been copied from "builtin-tag.c" by Kristian Høgsberg and Carlos Rica that was itself based on git-tag.sh and mktag.c by Linus Torvalds. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index eaea65c..6c46629 100644 --- a/Makefile +++ b/Makefile @@ -602,6 +602,7 @@ BUILTIN_OBJS += builtin-read-tree.o BUILTIN_OBJS += builtin-receive-pack.o BUILTIN_OBJS += builtin-reflog.o BUILTIN_OBJS += builtin-remote.o +BUILTIN_OBJS += builtin-replace.o BUILTIN_OBJS += builtin-rerere.o BUILTIN_OBJS += builtin-reset.o BUILTIN_OBJS += builtin-rev-list.o diff --git a/builtin-replace.c b/builtin-replace.c new file mode 100644 index 0000000..b5c40aa --- /dev/null +++ b/builtin-replace.c @@ -0,0 +1,105 @@ +/* + * Builtin "git replace" + * + * Copyright (c) 2008 Christian Couder + * + * Based on builtin-tag.c by Kristian Høgsberg + * and Carlos Rica that was itself based on + * git-tag.sh and mktag.c by Linus Torvalds. + */ + +#include "cache.h" +#include "builtin.h" +#include "refs.h" +#include "parse-options.h" + +static const char * const git_replace_usage[] = { + "git replace -d ...", + "git replace -l []", + NULL +}; + +static int show_reference(const char *refname, const unsigned char *sha1, + int flag, void *cb_data) +{ + const char *pattern = cb_data; + + if (!fnmatch(pattern, refname, 0)) + printf("%s\n", refname); + + return 0; +} + +static int list_replace_refs(const char *pattern) +{ + if (pattern == NULL) + pattern = "*"; + + for_each_replace_ref(show_reference, (void *) pattern); + + return 0; +} + +typedef int (*each_replace_name_fn)(const char *name, const char *ref, + const unsigned char *sha1); + +static int for_each_replace_name(const char **argv, each_replace_name_fn fn) +{ + const char **p; + char ref[PATH_MAX]; + int had_error = 0; + unsigned char sha1[20]; + + for (p = argv; *p; p++) { + if (snprintf(ref, sizeof(ref), "refs/replace/%s", *p) + >= sizeof(ref)) { + error("replace ref name too long: %.*s...", 50, *p); + had_error = 1; + continue; + } + if (!resolve_ref(ref, sha1, 1, NULL)) { + error("replace ref '%s' not found.", *p); + had_error = 1; + continue; + } + if (fn(*p, ref, sha1)) + had_error = 1; + } + return had_error; +} + +static int delete_replace_ref(const char *name, const char *ref, + const unsigned char *sha1) +{ + if (delete_ref(ref, sha1, 0)) + return 1; + printf("Deleted replace ref '%s'\n", name); + return 0; +} + +int cmd_replace(int argc, const char **argv, const char *prefix) +{ + int list = 0, delete = 0; + struct option options[] = { + OPT_BOOLEAN('l', NULL, &list, "list replace refs"), + OPT_BOOLEAN('d', NULL, &delete, "delete replace refs"), + OPT_END() + }; + + argc = parse_options(argc, argv, options, git_replace_usage, 0); + + if (list && delete) + usage_with_options(git_replace_usage, options); + + if (delete) { + if (argc < 1) + usage_with_options(git_replace_usage, options); + return for_each_replace_name(argv, delete_replace_ref); + } + + /* List refs, even if "list" is not set */ + if (argc > 1) + usage_with_options(git_replace_usage, options); + + return list_replace_refs(argv[0]); +} diff --git a/builtin.h b/builtin.h index 20427d2..38ceddc 100644 --- a/builtin.h +++ b/builtin.h @@ -112,5 +112,6 @@ extern int cmd_write_tree(int argc, const char **argv, const char *prefix); extern int cmd_verify_pack(int argc, const char **argv, const char *prefix); extern int cmd_show_ref(int argc, const char **argv, const char *prefix); extern int cmd_pack_refs(int argc, const char **argv, const char *prefix); +extern int cmd_replace(int argc, const char **argv, const char *prefix); #endif diff --git a/git.c b/git.c index 7d7f949..8695d67 100644 --- a/git.c +++ b/git.c @@ -340,6 +340,7 @@ static void handle_internal_command(int argc, const char **argv) { "receive-pack", cmd_receive_pack }, { "reflog", cmd_reflog, RUN_SETUP }, { "remote", cmd_remote, RUN_SETUP }, + { "replace", cmd_replace, RUN_SETUP }, { "repo-config", cmd_config }, { "rerere", cmd_rerere, RUN_SETUP }, { "reset", cmd_reset, RUN_SETUP }, diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh index 17f6063..bf4c93f 100755 --- a/t/t6050-replace.sh +++ b/t/t6050-replace.sh @@ -105,6 +105,18 @@ test_expect_success 'repack, clone and fetch work' ' cd .. ' +test_expect_success '"git replace" listing and deleting' ' + test "$HASH2" = "$(git replace -l)" && + test "$HASH2" = "$(git replace)" && + aa=${HASH2%??????????????????????????????????????} && + test "$HASH2" = "$(git replace -l "$aa*")" && + test_must_fail git replace -d $R && + test_must_fail git replace -d && + test_must_fail git replace -l -d $HASH2 && + git replace -d $HASH2 && + test -z "$(git replace -l)" +' + # # test_done -- cgit v0.10.2-6-g49f6 From bebdd271ff660d603ad75fef346ad1ff19fca0cb Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Mon, 2 Feb 2009 06:12:53 +0100 Subject: builtin-replace: teach "git replace" to actually replace Teach the syntax: "git replace ", so that "git replace" can now create replace refs. These replace refs will be used by read_sha1_file to substitute with for most of the commands. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano diff --git a/builtin-replace.c b/builtin-replace.c index b5c40aa..e3767b9 100644 --- a/builtin-replace.c +++ b/builtin-replace.c @@ -14,6 +14,7 @@ #include "parse-options.h" static const char * const git_replace_usage[] = { + "git replace [-f] ", "git replace -d ...", "git replace -l []", NULL @@ -77,12 +78,46 @@ static int delete_replace_ref(const char *name, const char *ref, return 0; } +static int replace_object(const char *object_ref, const char *replace_ref, + int force) +{ + unsigned char object[20], prev[20], repl[20]; + char ref[PATH_MAX]; + struct ref_lock *lock; + + if (get_sha1(object_ref, object)) + die("Failed to resolve '%s' as a valid ref.", object_ref); + if (get_sha1(replace_ref, repl)) + die("Failed to resolve '%s' as a valid ref.", replace_ref); + + if (snprintf(ref, sizeof(ref), + "refs/replace/%s", + sha1_to_hex(object)) > sizeof(ref) - 1) + die("replace ref name too long: %.*s...", 50, ref); + if (check_ref_format(ref)) + die("'%s' is not a valid ref name.", ref); + + if (!resolve_ref(ref, prev, 1, NULL)) + hashclr(prev); + else if (!force) + die("replace ref '%s' already exists", ref); + + lock = lock_any_ref_for_update(ref, prev, 0); + if (!lock) + die("%s: cannot lock the ref", ref); + if (write_ref_sha1(lock, repl, NULL) < 0) + die("%s: cannot update the ref", ref); + + return 0; +} + int cmd_replace(int argc, const char **argv, const char *prefix) { - int list = 0, delete = 0; + int list = 0, delete = 0, force = 0; struct option options[] = { OPT_BOOLEAN('l', NULL, &list, "list replace refs"), OPT_BOOLEAN('d', NULL, &delete, "delete replace refs"), + OPT_BOOLEAN('f', NULL, &force, "replace the ref if it exists"), OPT_END() }; @@ -91,15 +126,28 @@ int cmd_replace(int argc, const char **argv, const char *prefix) if (list && delete) usage_with_options(git_replace_usage, options); + if (force && (list || delete)) + usage_with_options(git_replace_usage, options); + + /* Delete refs */ if (delete) { if (argc < 1) usage_with_options(git_replace_usage, options); return for_each_replace_name(argv, delete_replace_ref); } + /* Replace object */ + if (!list && argc) { + if (argc != 2) + usage_with_options(git_replace_usage, options); + return replace_object(argv[0], argv[1], force); + } + /* List refs, even if "list" is not set */ if (argc > 1) usage_with_options(git_replace_usage, options); + if (force) + usage_with_options(git_replace_usage, options); return list_replace_refs(argv[0]); } diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh index bf4c93f..448a19a 100755 --- a/t/t6050-replace.sh +++ b/t/t6050-replace.sh @@ -114,9 +114,19 @@ test_expect_success '"git replace" listing and deleting' ' test_must_fail git replace -d && test_must_fail git replace -l -d $HASH2 && git replace -d $HASH2 && + git show $HASH2 | grep "A U Thor" && test -z "$(git replace -l)" ' +test_expect_success '"git replace" replacing' ' + git replace $HASH2 $R && + git show $HASH2 | grep "O Thor" && + test_must_fail git replace $HASH2 $R && + git replace -f $HASH2 $R && + test_must_fail git replace -f && + test "$HASH2" = "$(git replace)" +' + # # test_done -- cgit v0.10.2-6-g49f6 From 451bb210f81c10b60bf90403c9183be26beaaabf Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Mon, 2 Feb 2009 06:12:58 +0100 Subject: parse-options: add new function "usage_msg_opt" This function can be used instead of "usage_with_options" when you want to print an error message before the usage string. It may be useful because: if (condition) usage_msg_opt("condition is false", usage, opts); is shorter than: if (condition) { fprintf(stderr, "condition is false\n\n"); usage_with_options(usage, opts); } and may be more consistent. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano diff --git a/builtin-replace.c b/builtin-replace.c index e3767b9..8220d6e 100644 --- a/builtin-replace.c +++ b/builtin-replace.c @@ -121,7 +121,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix) OPT_END() }; - argc = parse_options(argc, argv, options, git_replace_usage, 0); + argc = parse_options(argc, argv, prefix, options, git_replace_usage, 0); if (list && delete) usage_with_options(git_replace_usage, options); diff --git a/parse-options.c b/parse-options.c index b85cab2..743f5ba 100644 --- a/parse-options.c +++ b/parse-options.c @@ -555,6 +555,14 @@ void usage_with_options(const char * const *usagestr, exit(129); } +void usage_msg_opt(const char *msg, + const char * const *usagestr, + const struct option *options) +{ + fprintf(stderr, "%s\n\n", msg); + usage_with_options(usagestr, options); +} + int parse_options_usage(const char * const *usagestr, const struct option *opts) { diff --git a/parse-options.h b/parse-options.h index b374ade..f0f885c 100644 --- a/parse-options.h +++ b/parse-options.h @@ -132,6 +132,10 @@ extern int parse_options(int argc, const char **argv, const char *prefix, extern NORETURN void usage_with_options(const char * const *usagestr, const struct option *options); +extern NORETURN void usage_msg_opt(const char *msg, + const char * const *usagestr, + const struct option *options); + /*----- incremental advanced APIs -----*/ enum { -- cgit v0.10.2-6-g49f6 From 86af2caa5ac322b1ebda8b4f0aceb9dfafe3e5ba Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Mon, 2 Feb 2009 06:13:06 +0100 Subject: builtin-replace: use "usage_msg_opt" to give better error messages Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano diff --git a/builtin-replace.c b/builtin-replace.c index 8220d6e..fe3a647 100644 --- a/builtin-replace.c +++ b/builtin-replace.c @@ -124,30 +124,36 @@ int cmd_replace(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, git_replace_usage, 0); if (list && delete) - usage_with_options(git_replace_usage, options); + usage_msg_opt("-l and -d cannot be used together", + git_replace_usage, options); if (force && (list || delete)) - usage_with_options(git_replace_usage, options); + usage_msg_opt("-f cannot be used with -d or -l", + git_replace_usage, options); /* Delete refs */ if (delete) { if (argc < 1) - usage_with_options(git_replace_usage, options); + usage_msg_opt("-d needs at least one argument", + git_replace_usage, options); return for_each_replace_name(argv, delete_replace_ref); } /* Replace object */ if (!list && argc) { if (argc != 2) - usage_with_options(git_replace_usage, options); + usage_msg_opt("bad number of arguments", + git_replace_usage, options); return replace_object(argv[0], argv[1], force); } /* List refs, even if "list" is not set */ if (argc > 1) - usage_with_options(git_replace_usage, options); + usage_msg_opt("only one pattern can be given with -l", + git_replace_usage, options); if (force) - usage_with_options(git_replace_usage, options); + usage_msg_opt("-f needs some arguments", + git_replace_usage, options); return list_replace_refs(argv[0]); } -- cgit v0.10.2-6-g49f6 From 2aaa84567e37f3c90087b56f9440a1608cac2282 Mon Sep 17 00:00:00 2001 From: David Aguilar Date: Mon, 13 Apr 2009 19:01:27 -0700 Subject: Add git-replace to .gitignore Signed-off-by: David Aguilar Signed-off-by: Junio C Hamano diff --git a/.gitignore b/.gitignore index 41c0b20..10808e3 100644 --- a/.gitignore +++ b/.gitignore @@ -105,6 +105,7 @@ git-reflog git-relink git-remote git-repack +git-replace git-repo-config git-request-pull git-rerere -- cgit v0.10.2-6-g49f6 From 0f3a5bfd34bfc9f710d6d7d259ad0a38b37a2334 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Tue, 14 Apr 2009 00:36:59 +0200 Subject: Documentation: add documentation for "git replace" Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano diff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt new file mode 100644 index 0000000..915cb77 --- /dev/null +++ b/Documentation/git-replace.txt @@ -0,0 +1,71 @@ +git-replace(1) +============== + +NAME +---- +git-replace - Create, list, delete refs to replace objects + +SYNOPSIS +-------- +[verse] +'git replace' [-f] +'git replace' -d ... +'git replace' -l [] + +DESCRIPTION +----------- +Adds a 'replace' reference in `.git/refs/replace/` + +The name of the 'replace' reference is the SHA1 of the object that is +replaced. The content of the replace reference is the SHA1 of the +replacement object. + +Unless `-f` is given, the replace reference must not yet exist in +`.git/refs/replace/` directory. + +OPTIONS +------- +-f:: + If an existing replace ref for the same object exists, it will + be overwritten (instead of failing). + +-d:: + Delete existing replace refs for the given objects. + +-l :: + List replace refs for objects that match the given pattern (or + all if no pattern is given). + Typing "git replace" without arguments, also lists all replace + refs. + +BUGS +---- +Comparing blobs or trees that have been replaced with those that +replace them will not work properly. And using 'git reset --hard' to +go back to a replaced commit will move the branch to the replacement +commit instead of the replaced commit. + +There may be other problems when using 'git rev-list' related to +pending objects. And of course things may break if an object of one +type is replaced by an object of another type (for example a blob +replaced by a commit). + +SEE ALSO +-------- +linkgit:git-tag[1] +linkgit:git-branch[1] + +Author +------ +Written by Christian Couder and Junio C +Hamano , based on 'git tag' by Kristian Hogsberg + and Carlos Rica . + +Documentation +-------------- +Documentation by Christian Couder and the +git-list , based on 'git tag' documentation. + +GIT +--- +Part of the linkgit:git[1] suite -- cgit v0.10.2-6-g49f6 From 4e65b538acc97dd853e19a1692893f5fd47043e6 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Wed, 27 May 2009 07:14:09 +0200 Subject: t6050: check pushing something based on a replaced commit When using something like: $ git push $there 04a8c^2:master we need to parse 04a8c to find its second parent and then start discussing what object to send with the other end. "04a8c^2" is a direct user input and should mean the same commit as git show "04a8c^2" would give the user, so it obviously needs to obey the replace rules (making 04a8c parsed), but the object transfer should not look at replace at all. This patch adds some tests to check that the above is working well. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh index 448a19a..8b8bd81 100755 --- a/t/t6050-replace.sh +++ b/t/t6050-replace.sh @@ -127,6 +127,74 @@ test_expect_success '"git replace" replacing' ' test "$HASH2" = "$(git replace)" ' +# This creates a side branch where the bug in H2 +# does not appear because P2 is created by applying +# H2 and squashing H5 into it. +# P3, P4 and P6 are created by cherry-picking H3, H4 +# and H6 respectively. +# +# At this point, we should have the following: +# +# P2--P3--P4--P6 +# / +# H1-H2-H3-H4-H5-H6-H7 +# +# Then we replace H6 with P6. +# +test_expect_success 'create parallel branch without the bug' ' + git replace -d $HASH2 && + git show $HASH2 | grep "A U Thor" && + git checkout $HASH1 && + git cherry-pick $HASH2 && + git show $HASH5 | git apply && + git commit --amend -m "hello: 4 more lines WITHOUT the bug" hello && + PARA2=$(git rev-parse --verify HEAD) && + git cherry-pick $HASH3 && + PARA3=$(git rev-parse --verify HEAD) && + git cherry-pick $HASH4 && + PARA4=$(git rev-parse --verify HEAD) && + git cherry-pick $HASH6 && + PARA6=$(git rev-parse --verify HEAD) && + git replace $HASH6 $PARA6 && + git checkout master && + cur=$(git rev-parse --verify HEAD) && + test "$cur" = "$HASH7" && + git log --pretty=oneline | grep $PARA2 && + git remote add cloned ./clone_dir +' + +test_expect_success 'push to cloned repo' ' + git push cloned $HASH6^:refs/heads/parallel && + cd clone_dir && + git checkout parallel && + git log --pretty=oneline | grep $PARA2 && + cd .. +' + +test_expect_success 'push branch with replacement' ' + git cat-file commit $PARA3 | grep "author A U Thor" && + S=$(git cat-file commit $PARA3 | sed -e "s/A U/O/" | git hash-object -t commit --stdin -w) && + git cat-file commit $S | grep "author O Thor" && + git replace $PARA3 $S && + git show $HASH6~2 | grep "O Thor" && + git show $PARA3 | grep "O Thor" && + git push cloned $HASH6^:refs/heads/parallel2 && + cd clone_dir && + git checkout parallel2 && + git log --pretty=oneline | grep $PARA3 && + git show $PARA3 | grep "A U Thor" && + cd .. +' + +test_expect_success 'fetch branch with replacement' ' + git branch tofetch $HASH6 && + cd clone_dir && + git fetch origin refs/heads/tofetch:refs/heads/parallel3 + git log --pretty=oneline parallel3 | grep $PARA3 + git show $PARA3 | grep "A U Thor" + cd .. +' + # # test_done -- cgit v0.10.2-6-g49f6