{"thread":{"id":"6494","subject":"[PATCH 4/4] make git-merge-one-file a builtin","startedAt":"2007-01-24T01:17:55Z","lastAt":"2007-01-24T01:17:55Z","messageCount":1,"participants":["Johannes Schindelin"],"isPatch":true,"patchVersion":1,"patchTotal":4},"messages":[{"id":"32469","messageId":"Pine.LNX.4.63.0701240214210.22628@wbgn013.biozentrum.uni-wuerzburg.de","threadId":"6494","inReplyTo":null,"subject":"[PATCH 4/4] make git-merge-one-file a builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-01-24T01:17:55Z","receivedAt":"2007-01-24T01:17:55Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"\nWhile at it, fix the bug that made git-merge-one-file fail when\nthere is no 'base', but the two-way merge succeeds.\n\nSigned-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n---\n\n\tThe plan is, of course, to get rid of the fork() altogether,\n\tcalling merge_one_file() from merge-index directly.\n\n\tBut not today. I'll go to bed now.\n\n Makefile                  |    3 +-\n builtin-merge-one-file.c  |   49 +++++++++++++++\n builtin.h                 |    1 +\n cache.h                   |    3 +\n git-merge-one-file.sh     |  134 ----------------------------------------\n git.c                     |    1 +\n merge-file.c              |  149 ++++++++++++++++++++++++++++++++++++++++++++-\n merge-tree.c              |    4 +-\n t/t6025-merge-one-file.sh |    4 +-\n 9 files changed, 207 insertions(+), 141 deletions(-)\n create mode 100644 builtin-merge-one-file.c\n delete mode 100755 git-merge-one-file.sh\n\ndiff --git a/Makefile b/Makefile\nindex b5c7336..759564f 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -173,7 +173,7 @@ SCRIPT_SH = \\\n \tgit-clean.sh git-clone.sh git-commit.sh \\\n \tgit-fetch.sh git-gc.sh \\\n \tgit-ls-remote.sh \\\n-\tgit-merge-one-file.sh git-parse-remote.sh \\\n+\tgit-parse-remote.sh \\\n \tgit-pull.sh git-rebase.sh \\\n \tgit-repack.sh git-request-pull.sh git-reset.sh \\\n \tgit-resolve.sh git-revert.sh git-sh-setup.sh \\\n@@ -295,6 +295,7 @@ BUILTIN_OBJS = \\\n \tbuiltin-mailsplit.o \\\n \tbuiltin-merge-base.o \\\n \tbuiltin-merge-file.o \\\n+\tbuiltin-merge-one-file.o \\\n \tbuiltin-mv.o \\\n \tbuiltin-name-rev.o \\\n \tbuiltin-pack-objects.o \\\ndiff --git a/builtin-merge-one-file.c b/builtin-merge-one-file.c\nnew file mode 100644\nindex 0000000..5f74c6b\n--- /dev/null\n+++ b/builtin-merge-one-file.c\n@@ -0,0 +1,49 @@\n+#include \"cache.h\"\n+\n+static const char merge_one_file_usage[] =\n+\"git-merge-one-file <orig-sha1> <our-sha1> <their-sha1> <path> <orig-mode> <our-mode> <their-mode>\";\n+\n+int cmd_merge_one_file(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct lock_file *lock_file;\n+\tunsigned char buffers[3][20], *hashes[3];\n+\tunsigned int modes[3];\n+\tint i, newfd;\n+\n+\tif (argc != 8)\n+\t\tusage(merge_one_file_usage);\n+\n+\n+\t/* We can't free this memory, it becomes part of a linked list parsed atexit() */\n+\tlock_file = xcalloc(1, sizeof(struct lock_file));\n+\n+\tnewfd = hold_lock_file_for_update(lock_file, get_index_file(), 1);\n+\n+\tif (read_cache() < 0)\n+\t\tdie(\"cache corrupted\");\n+\n+\tfor (i = 0; i < 3; i++) {\n+\t\tchar *p;\n+\t\thashes[i] = buffers[i];\n+\t\tif (!*argv[i + 1] || *argv[i + 1])\n+\t\t\thashes[i] = NULL;\n+\t\telse if (get_sha1_hex(argv[i + 1], buffers[i]))\n+\t\t\treturn error(\"invalid hash: %s\\n\", argv[i + 1]);\n+\t\tmodes[i] = strtoul(argv[i + 5], &p, 0);\n+\t\tif (p && *p)\n+\t\t\treturn error(\"invalid mode: %s\\n\", argv[i + 5]);\n+\t}\n+\n+\ti = merge_one_file(hashes[0], hashes[1], hashes[2], argv[4],\n+\t\tmodes[0], modes[1], modes[2]);\n+\n+\tif (i >= 0 && active_cache_changed) {\n+\t\tif (write_cache(newfd, active_cache, active_nr) ||\n+\t\t    close(newfd) || commit_lock_file(lock_file))\n+\t\t\tdie(\"Unable to write new index file\");\n+\t}\n+\n+\trollback_lock_file(lock_file);\n+\n+\treturn i ? 1 : 0;\n+}\ndiff --git a/builtin.h b/builtin.h\nindex 818c7bf..4be6846 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -45,6 +45,7 @@ extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);\n extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);\n extern int cmd_merge_base(int argc, const char **argv, const char *prefix);\n extern int cmd_merge_file(int argc, const char **argv, const char *prefix);\n+extern int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\n extern int cmd_mv(int argc, const char **argv, const char *prefix);\n extern int cmd_name_rev(int argc, const char **argv, const char *prefix);\n extern int cmd_pack_objects(int argc, const char **argv, const char *prefix);\ndiff --git a/cache.h b/cache.h\nindex 64ea1dc..da5ba8c 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -469,4 +469,7 @@ extern int nfvasprintf(char **str, const char *fmt, va_list va);\n extern void trace_printf(const char *format, ...);\n extern void trace_argv_printf(const char **argv, int count, const char *format, ...);\n \n+/* merge-file.c */\n+extern int merge_one_file(const unsigned char *, const unsigned char *, const unsigned char *, const char *, unsigned int, unsigned int, unsigned int);\n+\n #endif /* CACHE_H */\ndiff --git a/git-merge-one-file.sh b/git-merge-one-file.sh\ndeleted file mode 100755\nindex bc7c5ef..0000000\n--- a/git-merge-one-file.sh\n+++ /dev/null\n@@ -1,134 +0,0 @@\n-#!/bin/sh\n-#\n-# Copyright (c) Linus Torvalds, 2005\n-#\n-# This is the git per-file merge script, called with\n-#\n-#   $1 - original file SHA1 (or empty)\n-#   $2 - file in branch1 SHA1 (or empty)\n-#   $3 - file in branch2 SHA1 (or empty)\n-#   $4 - pathname in repository\n-#   $5 - original file mode (or empty)\n-#   $6 - file in branch1 mode (or empty)\n-#   $7 - file in branch2 mode (or empty)\n-#\n-# Handle some trivial cases.. The _really_ trivial cases have\n-# been handled already by git-read-tree, but that one doesn't\n-# do any merges that might change the tree layout.\n-\n-case \"${1:-.}${2:-.}${3:-.}\" in\n-#\n-# Deleted in both or deleted in one and unchanged in the other\n-#\n-\"$1..\" | \"$1.$1\" | \"$1$1.\")\n-\tif [ \"$2\" ]; then\n-\t\techo \"Removing $4\"\n-\telse\n-\t\t# read-tree checked that index matches HEAD already,\n-\t\t# so we know we do not have this path tracked.\n-\t\t# there may be an unrelated working tree file here,\n-\t\t# which we should just leave unmolested.\n-\t\texit 0\n-\tfi\n-\tif test -f \"$4\"; then\n-\t\trm -f -- \"$4\" &&\n-\t\trmdir -p \"$(expr \"z$4\" : 'z\\(.*\\)/')\" 2>/dev/null || :\n-\tfi &&\n-\t\texec git-update-index --remove -- \"$4\"\n-\t;;\n-\n-#\n-# Added in one.\n-#\n-\".$2.\")\n-\t# the other side did not add and we added so there is nothing\n-\t# to be done.\n-\t;;\n-\"..$3\")\n-\techo \"Adding $4\"\n-\ttest -f \"$4\" || {\n-\t\techo \"ERROR: untracked $4 is overwritten by the merge.\"\n-\t\texit 1\n-\t}\n-\tgit-update-index --add --cacheinfo \"$6$7\" \"$2$3\" \"$4\" &&\n-\t\texec git-checkout-index -u -f -- \"$4\"\n-\t;;\n-\n-#\n-# Added in both, identically (check for same permissions).\n-#\n-\".$3$2\")\n-\tif [ \"$6\" != \"$7\" ]; then\n-\t\techo \"ERROR: File $4 added identically in both branches,\"\n-\t\techo \"ERROR: but permissions conflict $6->$7.\"\n-\t\texit 1\n-\tfi\n-\techo \"Adding $4\"\n-\tgit-update-index --add --cacheinfo \"$6\" \"$2\" \"$4\" &&\n-\t\texec git-checkout-index -u -f -- \"$4\"\n-\t;;\n-\n-#\n-# Modified in both, but differently.\n-#\n-\"$1$2$3\" | \".$2$3\")\n-\n-\tcase \",$6,$7,\" in\n-\t*,120000,*)\n-\t\techo \"ERROR: $4: Not merging symbolic link changes.\"\n-\t\texit 1\n-\t\t;;\n-\tesac\n-\n-\tsrc2=`git-unpack-file $3`\n-\tcase \"$1\" in\n-\t'')\n-\t\techo \"Added $4 in both, but differently.\"\n-\t\t# This extracts OUR file in $orig, and uses git-apply to\n-\t\t# remove lines that are unique to ours.\n-\t\torig=`git-unpack-file $2`\n-\t\tsz0=`wc -c <\"$orig\" | tr -dc 0-9`\n-\t\tdiff -u -La/$orig -Lb/$orig $orig $src2 | git-apply --no-add \n-\t\tsz1=`wc -c <\"$orig\" | tr -dc 0-9`\n-\n-\t\t# If we do not have enough common material, it is not\n-\t\t# worth trying two-file merge using common subsections.\n-\t\texpr \"$sz0\" \\< \"$sz1\" \\* 2 >/dev/null || : >$orig\n-\t\t;;\n-\t*)\n-\t\techo \"Auto-merging $4\"\n-\t\torig=`git-unpack-file $1`\n-\t\t;;\n-\tesac\n-\n-\t# Be careful for funny filename such as \"-L\" in \"$4\", which\n-\t# would confuse \"merge\" greatly.\n-\tsrc1=`git-unpack-file $2`\n-\tgit-merge-file \"$src1\" \"$orig\" \"$src2\"\n-\tret=$?\n-\n-\t# Create the working tree file, using \"our tree\" version from the\n-\t# index, and then store the result of the merge.\n-\tgit-checkout-index -f --stage=2 -- \"$4\" && cat \"$src1\" >\"$4\"\n-\trm -f -- \"$orig\" \"$src1\" \"$src2\"\n-\n-\tif [ \"$6\" != \"$7\" ]; then\n-\t\techo \"ERROR: Permissions conflict: $5->$6,$7.\"\n-\t\tret=1\n-\tfi\n-\tif [ \"$1\" = '' ]; then\n-\t\tret=1\n-\tfi\n-\n-\tif [ $ret -ne 0 ]; then\n-\t\techo \"ERROR: Merge conflict in $4\"\n-\t\texit 1\n-\tfi\n-\texec git-update-index -- \"$4\"\n-\t;;\n-\n-*)\n-\techo \"ERROR: $4: Not handling case $1 -> $2 -> $3\"\n-\t;;\n-esac\n-exit 1\ndiff --git a/git.c b/git.c\nindex 5133a07..1ea2c5b 100644\n--- a/git.c\n+++ b/git.c\n@@ -251,6 +251,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)\n \t\t{ \"mailsplit\", cmd_mailsplit },\n \t\t{ \"merge-base\", cmd_merge_base, RUN_SETUP },\n \t\t{ \"merge-file\", cmd_merge_file },\n+\t\t{ \"merge-one-file\", cmd_merge_one_file },\n \t\t{ \"mv\", cmd_mv, RUN_SETUP | NOT_BARE },\n \t\t{ \"name-rev\", cmd_name_rev, RUN_SETUP },\n \t\t{ \"pack-objects\", cmd_pack_objects, RUN_SETUP },\ndiff --git a/merge-file.c b/merge-file.c\nindex 69dc1eb..ba54c96 100644\n--- a/merge-file.c\n+++ b/merge-file.c\n@@ -24,7 +24,7 @@ static void free_mmfile(mmfile_t *f)\n \tfree(f->ptr);\n }\n \n-static void *three_way_filemerge(mmfile_t *base, mmfile_t *our, mmfile_t *their, unsigned long *size)\n+static void *three_way_filemerge(mmfile_t *base, mmfile_t *our, mmfile_t *their, unsigned long *size, int *result)\n {\n \tmmbuffer_t res;\n \txpparam_t xpp;\n@@ -34,6 +34,9 @@ static void *three_way_filemerge(mmfile_t *base, mmfile_t *our, mmfile_t *their,\n \tmerge_status = xdl_merge(base, our, \".our\", their, \".their\",\n \t\t&xpp, XDL_MERGE_ZEALOUS, &res);\n \n+\tif (result)\n+\t\t*result = merge_status;\n+\n \tif (merge_status < 0)\n \t\treturn NULL;\n \n@@ -73,7 +76,7 @@ static int generate_common_file(mmfile_t *res, mmfile_t *f1, mmfile_t *f2)\n \treturn xdl_diff(f1, f2, &xpp, &xecfg, &ecb);\n }\n \n-void *merge_file(struct blob *base, struct blob *our, struct blob *their, unsigned long *size)\n+void *merge_file(struct blob *base, struct blob *our, struct blob *their, unsigned long *size, int *return_code)\n {\n \tvoid *res = NULL;\n \tmmfile_t f1, f2, common;\n@@ -106,7 +109,7 @@ void *merge_file(struct blob *base, struct blob *our, struct blob *their, unsign\n \t\tif (generate_common_file(&common, &f1, &f2) < 0)\n \t\t\tgoto out_free_f2_f1;\n \t}\n-\tres = three_way_filemerge(&common, &f1, &f2, size);\n+\tres = three_way_filemerge(&common, &f1, &f2, size, return_code);\n \tfree_mmfile(&common);\n out_free_f2_f1:\n \tfree_mmfile(&f2);\n@@ -115,3 +118,143 @@ out_free_f1:\n out_no_mmfile:\n \treturn res;\n }\n+\n+int remove_from_index(const char *path)\n+{\n+\tint pos = cache_name_pos(path, strlen(path));\n+\tif (pos < 0) {\n+\t\tpos = -1 - pos;\n+\t\tif (pos >= active_nr || strcmp(path, active_cache[pos]->name))\n+\t\t\treturn error(\"%s not in index\", path);\n+\t}\n+\twhile (pos < active_nr) {\n+\t\tif (strcmp(active_cache[pos]->name, path))\n+\t\t\tbreak;\n+\t\tremove_cache_entry_at(pos);\n+\t}\n+\treturn 0;\n+}\n+\n+int update_file(const char *path, void *buffer, unsigned long size,\n+\t\tint mode, int update_index)\n+{\n+\tif (update_index && remove_from_index(path))\n+\t\treturn -1;\n+\n+\tif (S_ISLNK(mode)) {\n+\t\tchar *target = buffer;\n+\t\ttarget[size] = '\\0';\n+\t\tif (symlink(target, path))\n+\t\t\treturn error(\"Could not create symlink %s\", path);\n+\t} else {\n+\t\tint fd = open(path, O_WRONLY | O_CREAT, mode);\n+\t\tif (!fd)\n+\t\t\treturn error(\"Could not open %s\\n\", path);\n+\t\tif (size != write_in_full(fd, buffer, size))\n+\t\t\treturn error(\"Could not write %s\\n\", path);\n+\t\tif (close(fd))\n+\t\t\treturn error(\"Could not close %s\\n\", path);\n+\t}\n+\tif (update_index)\n+\t\treturn add_file_to_index(path, 0);\n+\treturn 0;\n+}\n+\n+int write_file_from_sha1(const char *path, const unsigned char *sha1, int mode)\n+{\n+\tunsigned long size;\n+\tunsigned char type[20];\n+\tvoid *buffer = read_sha1_file(sha1, type, &size);\n+\tint ret = buffer ? update_file(path, buffer, size, mode, 1) : 0;\n+\n+\tfree(buffer);\n+\treturn ret;\n+}\n+\n+/*\n+ * Handle some trivial cases.. The _really_ trivial cases have\n+ * been handled already by git-read-tree, but that one doesn't\n+ * do any merges that might change the tree layout.\n+ */\n+int merge_one_file(const unsigned char *orig_sha1,\n+\t\tconst unsigned char *our_sha1,\n+\t\tconst unsigned char *their_sha1,\n+\t\tconst char *path,\n+\t\tunsigned int orig_mode,\n+\t\tunsigned int our_mode,\n+\t\tunsigned int their_mode)\n+{\n+\tint ret = -1;\n+\tstruct stat st;\n+\n+\t/* Deleted in both or deleted in one and unchanged in the other */\n+\tif (orig_sha1 && (!our_sha1 || !hashcmp(orig_sha1, our_sha1)) &&\n+\t\t\t(!their_sha1 || !hashcmp(orig_sha1, their_sha1))) {\n+\t\tif (our_sha1)\n+\t\t\tprintf(\"Removing %s\\n\", path);\n+\t\telse\n+\t\t\t/*\n+\t\t\t * read-tree checked that index matches HEAD already,\n+\t\t\t * so we know we do not have this path tracked.\n+\t\t\t * there may be an unrelated working tree file here,\n+\t\t\t * which we should just leave unmolested.\n+\t\t\t */\n+\t\t\treturn 0;\n+\t\tif (!lstat(path, &st)) {\n+\t\t\tif (remove_from_index(path))\n+\t\t\t\treturn -1;\n+\t\t\tif (remove_path_recursively(path) < 0)\n+\t\t\t\tdie(\"Error removing %s: %s\\n\",\n+\t\t\t\t\t\tpath, strerror(errno));\n+\t\t}\n+\t\treturn 0;\n+\t}\n+\tif (!orig_sha1) {\n+\t\t/* Added in one */\n+\t\tif (our_sha1 && !their_sha1)\n+\t\t\treturn 0;\n+\t\telse if (their_sha1 && (!our_sha1 ||\n+\t\t\t\t\t!hashcmp(our_sha1, their_sha1))) {\n+\t\t\tif (!our_sha1 && !lstat(path, &st))\n+\t\t\t\treturn error(\"Untracked %s is overwritten by \"\n+\t\t\t\t\t\t\"the merge\", path);\n+\t\t\tif (our_sha1 && our_mode != their_mode)\n+\t\t\t\treturn error(\"File %s added in both branches,\\n\"\n+\t\t\t\t\t\"but permissions conflict 0%o->0%o\",\n+\t\t\t\t\tpath, our_mode, their_mode);\n+\t\t\treturn write_file_from_sha1(path,\n+\t\t\t\t\ttheir_sha1, their_mode);\n+\t\t}\n+\t}\n+\tif (our_sha1 && their_sha1) {\n+\t\tstruct object *base = NULL, *our, *their;\n+\t\tvoid *result;\n+\t\tunsigned long size;\n+\n+\t\tif (S_ISLNK(our_mode) || S_ISLNK(their_mode))\n+\t\t\treturn error(\"%s: not merging symbolic link changes.\",\n+\t\t\t\tpath);\n+\t\tif (!(our = parse_object(our_sha1)))\n+\t\t\treturn error(\"Could not read %s\\n\",\n+\t\t\t\t\tsha1_to_hex(our_sha1));\n+\t\tif (!(their = parse_object(their_sha1)))\n+\t\t\treturn error(\"Could not read %s\\n\",\n+\t\t\t\t\tsha1_to_hex(their_sha1));\n+\t\tif (orig_sha1 && !(base = parse_object(orig_sha1)))\n+\t\t\treturn error(\"Could not read %s\\n\",\n+\t\t\t\t\tsha1_to_hex(orig_sha1));\n+\n+\t\tresult = merge_file((struct blob *)base, (struct blob *)our,\n+\t\t\t\t(struct blob *)their, &size, &ret);\n+\t\tif (result)\n+\t\t\tret |= update_file(path, result, size, our_mode, !ret);\n+\t\tif (ret)\n+\t\t\treturn error(\"%s: merge failure\", path);\n+\t\treturn 0;\n+\t}\n+\n+\treturn error(\"Unhandled case: %s->%s,%s\\n\",\n+\t\t\tsha1_to_hex(orig_sha1 ? orig_sha1 : null_sha1),\n+\t\t\tsha1_to_hex(our_sha1 ? our_sha1 : null_sha1),\n+\t\t\tsha1_to_hex(their_sha1 ? their_sha1 : null_sha1));\n+}\ndiff --git a/merge-tree.c b/merge-tree.c\nindex 692ede0..2a52a56 100644\n--- a/merge-tree.c\n+++ b/merge-tree.c\n@@ -53,7 +53,7 @@ static const char *explanation(struct merge_list *entry)\n \treturn \"removed in remote\";\n }\n \n-extern void *merge_file(struct blob *, struct blob *, struct blob *, unsigned long *);\n+extern void *merge_file(struct blob *, struct blob *, struct blob *, unsigned long *, int *);\n \n static void *result(struct merge_list *entry, unsigned long *size)\n {\n@@ -75,7 +75,7 @@ static void *result(struct merge_list *entry, unsigned long *size)\n \ttheir = NULL;\n \tif (entry)\n \t\ttheir = entry->blob;\n-\treturn merge_file(base, our, their, size);\n+\treturn merge_file(base, our, their, size, NULL);\n }\n \n static void *origin(struct merge_list *entry, unsigned long *size)\ndiff --git a/t/t6025-merge-one-file.sh b/t/t6025-merge-one-file.sh\nindex 2eadcb3..6b79202 100644\n--- a/t/t6025-merge-one-file.sh\n+++ b/t/t6025-merge-one-file.sh\n@@ -67,7 +67,9 @@ git-update-index --index-info << EOF\n 10644 $hash_13 3\ttwo\n EOF\n \n-test_expect_failure \"created non-identically, but mergeable\" \\\n+# the old git-merge-one-file script used to fail here, too, even\n+# if the merge succeeded.\n+test_expect_success \"created non-identically, but mergeable\" \\\n \t\"git-merge-one-file '' $hash_one $hash_13 two '' 0644 0644\"\n \n test_expect_success \"-> correct file\" \"diff -u thirteen two\"\n-- \n1.5.0.rc2.gc5868-dirty\n"}]}