{"thread":{"id":"15132","subject":"[PATCH] add a pre-merge hook","startedAt":"2008-08-21T12:09:42Z","lastAt":"2008-08-27T20:05:45Z","messageCount":4,"participants":["Paolo Bonzini","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"87975","messageId":"E1KW9NC-0003mM-6F@fencepost.gnu.org","threadId":"15132","inReplyTo":null,"subject":"[PATCH] add a pre-merge hook","fromName":"Paolo Bonzini","fromEmail":"bonzini@gnu.org","sentAt":"2008-08-21T12:09:42Z","receivedAt":"2008-08-21T12:09:42Z","isPatch":true,"sender":{"key":"bonzini@gnu.org","avatar":"https://avatars.githubusercontent.com/u/42082?v=4"},"body":"This hook provides a way to look at what kind of merge is invoked,\nand stop it.  The type of merge is passed to the hook.\n\nThe patch provides a sample implementation that implements\na `branch.<branch-name>.allowmerges` key.  It can be useful for\nproject whose policy is \"only fast forwards on the integrator's\nrepository\".\n\nSigned-off-by: Paolo Bonzini <bonzini@gnu.org>\n---\n Documentation/githooks.txt        |   18 +++++\n builtin-merge.c                   |   65 +++++++++++++++----\n t/t5407-pre-merge-hook.sh         |  130 +++++++++++++++++++++++++++++++++++++\n templates/hooks--pre-merge.sample |   49 ++++++++++++++\n 4 files changed, 250 insertions(+), 12 deletions(-)\n create mode 100755 t/t5407-pre-merge-hook.sh\n create mode 100755 templates/hooks--pre-merge.sample\n\n\tThe included testcases demonstrate that trivial merges are\n\tcurrently broken.  The failing test is equivalent to:\n\n\t  git init\n\t  echo a > a\n\t  git add a\n\t  git commit -ma \n\t  git checkout -b branch\n\t  echo b > b\n\t  git add b\n\t  git commit -mb\n\t  git checkout master\n\t  git merge --no-ff -s resolve branch\n\n\tRunning this in 1.5.5 shows: \n\n\t  Trying really trivial in-index merge...\n\t  Wonderful.\n\t  In-index merge\n\n\twhile `next' gives\n\n\t  Trying really trivial in-index merge...\n\t  error: Untracked working tree file 'a' would be overwritten by merge.\n\t  Nope.\n\t  Trying simple merge.\n\t  Merge made by resolve.\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 046a2a7..c45602c 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -187,6 +187,24 @@ Both standard output and standard error output are forwarded to\n 'git-send-pack' on the other end, so you can simply `echo` messages\n for the user.\n \n+pre-merge\n+---------\n+\n+This hook is invoked before a merge is attempted.  The command\n+line passed to the hook can have multiple parameters.  The\n+first parameter is the type of merge, which can be one of\n+\"squash\" (if --squash was given on the command line), \"ff\"\n+(fast-forward), \"trivial\" (trivial in-index merge), \"merge\"\n+(non-trivial 2-head merge), \"octopus\" (any other merge).  After this\n+there is a \"--\" argument, followed by the commit SHA1 values for the\n+heads being merged.\n+\n+The hook can interrupt the merge by returning a non-zero\n+status.  The default hook checks for a boolean configuration\n+key `branch.<branch-name>.allowmerges`, where `<branch-name>`\n+is the current branch.  If the key is false, only squash or\n+fast-forward merges are allowed.\n+\n [[update]]\n update\n ------\ndiff --git a/builtin-merge.c b/builtin-merge.c\nindex a3b9b10..e7f3ece 100644\n--- a/builtin-merge.c\n+++ b/builtin-merge.c\n@@ -298,12 +298,16 @@ static void squash_message(void)\n \tstrbuf_release(&out);\n }\n \n-static int run_hook(const char *name)\n+static int run_hook(const char *name, struct commit_list *heads, ...)\n {\n \tstruct child_process hook;\n-\tconst char *argv[3], *env[2];\n+\tchar **argv, *env[2];\n \tchar index[PATH_MAX];\n+\tva_list args;\n+\tint first_remote_head, i, rc;\n \n+\tva_start(args, heads);\n+\targv = xmalloc((10 + commit_list_count(remoteheads)) * sizeof(char *));\n \targv[0] = git_path(\"hooks/%s\", name);\n \tif (access(argv[0], X_OK) < 0)\n \t\treturn 0;\n@@ -312,19 +316,43 @@ static int run_hook(const char *name)\n \tenv[0] = index;\n \tenv[1] = NULL;\n \n-\tif (squash)\n-\t\targv[1] = \"1\";\n-\telse\n-\t\targv[1] = \"0\";\n-\targv[2] = NULL;\n+\ti = 0;\n+\tdo { \n+\t\t/* \"--\" ovewrites the last string returned by va_arg, but we\n+\t\t   still have to reserve a slot for the final NULL in argv.  */\n+\t        if (++i >= 9)\n+\t                die(\"run_hook(): too many arguments\");\n+\t        argv[i] = va_arg(args, char *);\n+\t} while (argv[i]);\n+\tva_end(args);\n+\n+\tif (heads) {\n+\t\targv[i] = \"--\";\n+\t\tfirst_remote_head = i + 1;\n+\t\tfor (; heads; heads = heads->next)\n+\t\t\targv[++i] = xstrdup(sha1_to_hex(heads->item->object.sha1));\n+\t\targv[++i] = NULL;\n+\t} else\n+\t\tfirst_remote_head = i;\n \n \tmemset(&hook, 0, sizeof(hook));\n-\thook.argv = argv;\n+\thook.argv = (const char **) argv;\n \thook.no_stdin = 1;\n \thook.stdout_to_stderr = 1;\n-\thook.env = env;\n+\thook.env = (const char **) env;\n+\n+\trc = run_command(&hook);\n \n-\treturn run_command(&hook);\n+\tfor (i = first_remote_head; argv[i]; i++)\n+\t\tfree (argv[i]);\n+\tfree (argv);\n+\treturn rc;\n+}\n+\n+static int run_pre_merge_hook(const char *kind)\n+{\n+\treturn run_hook(\"pre-merge\", remoteheads,\n+\t\t\tsquash ? \"squash\" : kind, \"--\", NULL);\n }\n \n static void finish(const unsigned char *new_head, const char *msg)\n@@ -372,7 +400,7 @@ static void finish(const unsigned char *new_head, const char *msg)\n \t}\n \n \t/* Run a post-merge hook */\n-\trun_hook(\"post-merge\");\n+\trun_hook(\"post-merge\", NULL, squash ? \"1\" : \"0\", NULL);\n \n \tstrbuf_release(&reflog_message);\n }\n@@ -877,6 +905,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\tremote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);\n \t\tif (!remote_head)\n \t\t\tdie(\"%s - not something we can merge\", argv[0]);\n+\t\tif (run_hook(\"pre-merge\", NULL, \"ff\", \"--\",\n+\t\t\t     remote_head->sha1, NULL))\n+\t\t\treturn 1;\n+\n \t\tupdate_ref(\"initial pull\", \"HEAD\", remote_head->sha1, NULL, 0,\n \t\t\t\tDIE_ON_ERR);\n \t\treset_hard(remote_head->sha1, 0);\n@@ -974,6 +1006,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \n \t\tstrcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));\n \n+\t\tif (run_pre_merge_hook(\"ff\"))\n+\t\t\treturn 1;\n+\n \t\tprintf(\"Updating %s..%s\\n\",\n \t\t\thex,\n \t\t\tfind_unique_abbrev(remoteheads->item->object.sha1,\n@@ -1011,8 +1046,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\t\tgit_committer_info(IDENT_ERROR_ON_NO_NAME);\n \t\t\tprintf(\"Trying really trivial in-index merge...\\n\");\n \t\t\tif (!read_tree_trivial(common->item->object.sha1,\n-\t\t\t\t\thead, remoteheads->item->object.sha1))\n+\t\t\t\t\thead, remoteheads->item->object.sha1)) {\n+\t\t\t\tif (run_pre_merge_hook(\"trivial\"))\n+\t\t\t\t\treturn 1;\n \t\t\t\treturn merge_trivial();\n+\t\t\t}\n \t\t\tprintf(\"Nope.\\n\");\n \t\t}\n \t} else {\n@@ -1045,6 +1083,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n+\tif (run_pre_merge_hook(remoteheads->next ? \"octopus\" : \"merge\"))\n+\t\treturn 1;\n+\n \t/* We are going to make a new commit. */\n \tgit_committer_info(IDENT_ERROR_ON_NO_NAME);\n \ndiff --git a/t/t5407-pre-merge-hook.sh b/t/t5407-pre-merge-hook.sh\nnew file mode 100755\nindex 0000000..b7029ce\n--- /dev/null\n+++ b/t/t5407-pre-merge-hook.sh\n@@ -0,0 +1,130 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2008 Paolo Bonzini\n+#\n+\n+test_description='Test the pre-merge hook.'\n+. ./test-lib.sh\n+\n+test_expect_success setup '\n+\techo Data for commit0. >a &&\n+\techo a >>a &&\n+\techo b >>a &&\n+\techo c >>a &&\n+\tgit add a &&\n+\tgit commit -m\"setup\" &&\n+\tcommit0=$(git rev-parse HEAD) &&\n+\n+\tgit checkout -b branch1 &&\n+\techo Data for branch1. >b &&\n+\tgit add b &&\n+\tgit commit -m\"setup branch1\" &&\n+\tgit checkout master &&\n+\n+\tgit checkout -b branch2 &&\n+\techo Data for branch2. >c &&\n+\tgit add c &&\n+\tgit commit -m\"setup branch2\" &&\n+\tgit checkout master &&\n+\n+\tgit checkout -b branch3 &&\n+\techo Changed data for branch3. >a &&\n+\techo a >>a &&\n+\techo b >>a &&\n+\techo c >>a &&\n+\tgit commit -m\"setup branch3\" a &&\n+\tgit checkout master &&\n+\n+\tgit checkout -b branch4 &&\n+\techo Data for commit0. >a &&\n+\techo a >>a &&\n+\techo b >>a &&\n+\techo c for branch4 >>a &&\n+\tgit commit -m\"setup branch4\" a &&\n+\tgit checkout master\n+'\n+\n+mkdir .git/hooks\n+cat >.git/hooks/pre-merge <<'EOF'\n+#!/bin/sh\n+echo $# $1 $2 >> $GIT_DIR/pre-merge.args\n+EOF\n+chmod u+x .git/hooks/pre-merge\n+\n+test_expect_success 'pre-merge runs as expected ' '\n+\trm -f .git/pre-merge.args &&\n+\tgit reset --hard $commit0 &&\n+        git merge branch1 &&\n+\ttest -e .git/pre-merge.args\n+'\n+\n+test_expect_success 'pre-merge from fast-forward merge receives the right argument ' '\n+        test \"`cat .git/pre-merge.args`\" = \"4 ff --\"\n+'\n+\n+test_expect_success 'pre-merge from squash merge receives the right argument ' '\n+\trm -f .git/pre-merge.args &&\n+\tgit reset --hard $commit0 &&\n+        git merge --squash branch1 &&\n+        test \"`cat .git/pre-merge.args`\" = \"4 squash --\"\n+'\n+\n+test_expect_failure 'pre-merge from trivial merge receives the right argument ' '\n+\trm -f .git/pre-merge.args &&\n+\tgit checkout $(git rev-parse branch1) &&\n+        git merge --no-ff -s resolve branch1 &&\n+        test \"`cat .git/pre-merge.args`\" = \"4 trivial --\"\n+'\n+\n+test_expect_success 'pre-merge from real merge receives the right argument ' '\n+\trm -f .git/pre-merge.args &&\n+\tgit reset --hard branch3 &&\n+        git merge --no-ff branch4 &&\n+        test \"`cat .git/pre-merge.args`\" = \"4 merge --\"\n+'\n+\n+test_expect_success 'pre-merge from octopus merge receives the right argument ' '\n+\trm -f .git/pre-merge.args &&\n+\tgit reset --hard $commit0 &&\n+        git merge branch1 branch2 branch3 &&\n+        test \"`cat .git/pre-merge.args`\" = \"6 octopus --\"\n+'\n+\n+test_expect_success 'pre-merge into empty head receives the right argument ' '\n+\t# Here we use a subshell so that the next tests have the right\n+\t# cwd if this test fails.\n+\tmkdir second &&\n+\t(cd second &&\n+\tgit init &&\n+\tcp ../.git/hooks/pre-merge .git/hooks/pre-merge &&\n+\tchmod u+x .git/hooks/pre-merge &&\n+\tgit fetch .. &&\n+\tgit merge $commit0 &&\n+        test \"`cat .git/pre-merge.args`\" = \"3 ff --\" &&\n+\tcd .. &&\n+\trm -rf second)\n+'\n+\n+test_expect_success 'pre-merge does not run for up-to-date ' '\n+\trm -f .git/pre-merge.args &&\n+        git merge $commit0 &&\n+\t! test -f .git/pre-merge.args\n+'\n+\n+test_expect_success 'pre-merge does not run for up-to-date octopus ' '\n+\trm -f .git/pre-merge.args &&\n+        git merge branch1 branch2 branch3 &&\n+\t! test -f .git/pre-merge.args\n+'\n+\n+cat >.git/hooks/pre-merge <<'EOF'\n+#!/bin/sh\n+exit 1\n+EOF\n+chmod u+x .git/hooks/pre-merge\n+\n+test_expect_success 'pre-merge can stop merge ' '\n+        ! git merge --no-ff branch4\n+'\n+\n+test_done\ndiff --git a/templates/hooks--pre-merge.sample b/templates/hooks--pre-merge.sample\nnew file mode 100755\nindex 0000000..bf2e82c\n--- /dev/null\n+++ b/templates/hooks--pre-merge.sample\n@@ -0,0 +1,49 @@\n+#!/bin/sh\n+#\n+# An example hook script to block merges on same branches.\n+# Called by git-merge with arguments: type -- head remote1 remote2...\n+#\n+# To enable this hook, rename this file to \"pre-merge\".\n+#\n+# Config\n+# ------\n+# branch.<branch-name>.allowmerges\n+#   This boolean sets whether merges will be allowed for the\n+#   given branch in the repository.  By default they will be.\n+\n+# --- Command line\n+type=\"$1\"\n+\n+# --- Safety check\n+if [ -z \"$GIT_DIR\" ]; then\n+\techo \"Don't run this script from the command line.\" >&2\n+\techo \" (if you want, you could supply GIT_DIR then run\" >&2\n+\techo \"  $0 <type> -- <head>...)\" >&2\n+\texit 1\n+fi\n+\n+if [ -z \"$type\" ]; then\n+\techo \"Usage: $0 <type> -- <head>...\" >&2\n+\texit 1\n+fi\n+\n+# --- Examine repository state\n+head=`git symbolic-ref HEAD 2>/dev/null || echo detached`\n+\n+# --- Do the actual check\n+case \"$head,$type\" in\n+\tdetached,* | *,squash | *,ff)\n+\t\t;;\n+\trefs/heads/*)\n+\t\tallowmerges=$(git config --bool branch.${head#refs/heads/}.allowmerges)\n+\t\ttest \"$allowmerges\" = false && {\n+\t\t\techo Merge commits not allowed on this branch.\n+\t\t\texit 1\n+\t\t}\n+\t\t;;\n+\t*)\n+\t\t;;\n+esac\n+\n+# --- Finished\n+exit 0\n-- \n1.5.5\n"},{"id":"88720","messageId":"E1KYK3t-0000ZX-3b@fencepost.gnu.org","threadId":"15132","inReplyTo":"E1KW9NC-0003mM-6F@fencepost.gnu.org","subject":"[PATCH] add a pre-merge hook","fromName":"Paolo Bonzini","fromEmail":"bonzini@gnu.org","sentAt":"2008-08-27T12:19:42Z","receivedAt":"2008-08-27T12:19:42Z","isPatch":true,"sender":{"key":"bonzini@gnu.org","avatar":"https://avatars.githubusercontent.com/u/42082?v=4"},"body":"This hook provides a way to look at what kind of merge is invoked,\nand stop it.  The type of merge is passed to the hook.\n\nThe patch provides a sample implementation that implements\na `branch.<branch-name>.allowmerges` key.  It can be useful for\nproject whose policy is \"only fast forwards on the integrator's\nrepository\".\n\nSigned-off-by: Paolo Bonzini <bonzini@gnu.org>\n---\n Documentation/githooks.txt        |   18 +++++\n builtin-merge.c                   |   65 +++++++++++++++----\n t/t5407-pre-merge-hook.sh         |  130 +++++++++++++++++++++++++++++++++++++\n templates/hooks--pre-merge.sample |   49 ++++++++++++++\n 4 files changed, 250 insertions(+), 12 deletions(-)\n create mode 100755 t/t5407-pre-merge-hook.sh\n create mode 100755 templates/hooks--pre-merge.sample\n\n\tThe only difference with respect to the previous version is\n\tthat the trivial merge testcase passes.\n\n\tThe follow-up at\n\thttp://permalink.gmane.org/gmane.comp.version-control.git/93147\n\tis unchanged.\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 046a2a7..c45602c 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -187,6 +187,24 @@ Both standard output and standard error output are forwarded to\n 'git-send-pack' on the other end, so you can simply `echo` messages\n for the user.\n \n+pre-merge\n+---------\n+\n+This hook is invoked before a merge is attempted.  The command\n+line passed to the hook can have multiple parameters.  The\n+first parameter is the type of merge, which can be one of\n+\"squash\" (if --squash was given on the command line), \"ff\"\n+(fast-forward), \"trivial\" (trivial in-index merge), \"merge\"\n+(non-trivial 2-head merge), \"octopus\" (any other merge).  After this\n+there is a \"--\" argument, followed by the commit SHA1 values for the\n+heads being merged.\n+\n+The hook can interrupt the merge by returning a non-zero\n+status.  The default hook checks for a boolean configuration\n+key `branch.<branch-name>.allowmerges`, where `<branch-name>`\n+is the current branch.  If the key is false, only squash or\n+fast-forward merges are allowed.\n+\n [[update]]\n update\n ------\ndiff --git a/builtin-merge.c b/builtin-merge.c\nindex a3b9b10..e7f3ece 100644\n--- a/builtin-merge.c\n+++ b/builtin-merge.c\n@@ -298,12 +298,16 @@ static void squash_message(void)\n \tstrbuf_release(&out);\n }\n \n-static int run_hook(const char *name)\n+static int run_hook(const char *name, struct commit_list *heads, ...)\n {\n \tstruct child_process hook;\n-\tconst char *argv[3], *env[2];\n+\tchar **argv, *env[2];\n \tchar index[PATH_MAX];\n+\tva_list args;\n+\tint first_remote_head, i, rc;\n \n+\tva_start(args, heads);\n+\targv = xmalloc((10 + commit_list_count(remoteheads)) * sizeof(char *));\n \targv[0] = git_path(\"hooks/%s\", name);\n \tif (access(argv[0], X_OK) < 0)\n \t\treturn 0;\n@@ -312,19 +316,43 @@ static int run_hook(const char *name)\n \tenv[0] = index;\n \tenv[1] = NULL;\n \n-\tif (squash)\n-\t\targv[1] = \"1\";\n-\telse\n-\t\targv[1] = \"0\";\n-\targv[2] = NULL;\n+\ti = 0;\n+\tdo { \n+\t\t/* \"--\" ovewrites the last string returned by va_arg, but we\n+\t\t   still have to reserve a slot for the final NULL in argv.  */\n+\t        if (++i >= 9)\n+\t                die(\"run_hook(): too many arguments\");\n+\t        argv[i] = va_arg(args, char *);\n+\t} while (argv[i]);\n+\tva_end(args);\n+\n+\tif (heads) {\n+\t\targv[i] = \"--\";\n+\t\tfirst_remote_head = i + 1;\n+\t\tfor (; heads; heads = heads->next)\n+\t\t\targv[++i] = xstrdup(sha1_to_hex(heads->item->object.sha1));\n+\t\targv[++i] = NULL;\n+\t} else\n+\t\tfirst_remote_head = i;\n \n \tmemset(&hook, 0, sizeof(hook));\n-\thook.argv = argv;\n+\thook.argv = (const char **) argv;\n \thook.no_stdin = 1;\n \thook.stdout_to_stderr = 1;\n-\thook.env = env;\n+\thook.env = (const char **) env;\n+\n+\trc = run_command(&hook);\n \n-\treturn run_command(&hook);\n+\tfor (i = first_remote_head; argv[i]; i++)\n+\t\tfree (argv[i]);\n+\tfree (argv);\n+\treturn rc;\n+}\n+\n+static int run_pre_merge_hook(const char *kind)\n+{\n+\treturn run_hook(\"pre-merge\", remoteheads,\n+\t\t\tsquash ? \"squash\" : kind, \"--\", NULL);\n }\n \n static void finish(const unsigned char *new_head, const char *msg)\n@@ -372,7 +400,7 @@ static void finish(const unsigned char *new_head, const char *msg)\n \t}\n \n \t/* Run a post-merge hook */\n-\trun_hook(\"post-merge\");\n+\trun_hook(\"post-merge\", NULL, squash ? \"1\" : \"0\", NULL);\n \n \tstrbuf_release(&reflog_message);\n }\n@@ -877,6 +905,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\tremote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);\n \t\tif (!remote_head)\n \t\t\tdie(\"%s - not something we can merge\", argv[0]);\n+\t\tif (run_hook(\"pre-merge\", NULL, \"ff\", \"--\",\n+\t\t\t     remote_head->sha1, NULL))\n+\t\t\treturn 1;\n+\n \t\tupdate_ref(\"initial pull\", \"HEAD\", remote_head->sha1, NULL, 0,\n \t\t\t\tDIE_ON_ERR);\n \t\treset_hard(remote_head->sha1, 0);\n@@ -974,6 +1006,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \n \t\tstrcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));\n \n+\t\tif (run_pre_merge_hook(\"ff\"))\n+\t\t\treturn 1;\n+\n \t\tprintf(\"Updating %s..%s\\n\",\n \t\t\thex,\n \t\t\tfind_unique_abbrev(remoteheads->item->object.sha1,\n@@ -1011,8 +1046,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\t\tgit_committer_info(IDENT_ERROR_ON_NO_NAME);\n \t\t\tprintf(\"Trying really trivial in-index merge...\\n\");\n \t\t\tif (!read_tree_trivial(common->item->object.sha1,\n-\t\t\t\t\thead, remoteheads->item->object.sha1))\n+\t\t\t\t\thead, remoteheads->item->object.sha1)) {\n+\t\t\t\tif (run_pre_merge_hook(\"trivial\"))\n+\t\t\t\t\treturn 1;\n \t\t\t\treturn merge_trivial();\n+\t\t\t}\n \t\t\tprintf(\"Nope.\\n\");\n \t\t}\n \t} else {\n@@ -1045,6 +1083,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n+\tif (run_pre_merge_hook(remoteheads->next ? \"octopus\" : \"merge\"))\n+\t\treturn 1;\n+\n \t/* We are going to make a new commit. */\n \tgit_committer_info(IDENT_ERROR_ON_NO_NAME);\n \ndiff --git a/t/t5407-pre-merge-hook.sh b/t/t5407-pre-merge-hook.sh\nnew file mode 100755\nindex 0000000..b7029ce\n--- /dev/null\n+++ b/t/t5407-pre-merge-hook.sh\n@@ -0,0 +1,130 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2008 Paolo Bonzini\n+#\n+\n+test_description='Test the pre-merge hook.'\n+. ./test-lib.sh\n+\n+test_expect_success setup '\n+\techo Data for commit0. >a &&\n+\techo a >>a &&\n+\techo b >>a &&\n+\techo c >>a &&\n+\tgit add a &&\n+\tgit commit -m\"setup\" &&\n+\tcommit0=$(git rev-parse HEAD) &&\n+\n+\tgit checkout -b branch1 &&\n+\techo Data for branch1. >b &&\n+\tgit add b &&\n+\tgit commit -m\"setup branch1\" &&\n+\tgit checkout master &&\n+\n+\tgit checkout -b branch2 &&\n+\techo Data for branch2. >c &&\n+\tgit add c &&\n+\tgit commit -m\"setup branch2\" &&\n+\tgit checkout master &&\n+\n+\tgit checkout -b branch3 &&\n+\techo Changed data for branch3. >a &&\n+\techo a >>a &&\n+\techo b >>a &&\n+\techo c >>a &&\n+\tgit commit -m\"setup branch3\" a &&\n+\tgit checkout master &&\n+\n+\tgit checkout -b branch4 &&\n+\techo Data for commit0. >a &&\n+\techo a >>a &&\n+\techo b >>a &&\n+\techo c for branch4 >>a &&\n+\tgit commit -m\"setup branch4\" a &&\n+\tgit checkout master\n+'\n+\n+mkdir .git/hooks\n+cat >.git/hooks/pre-merge <<'EOF'\n+#!/bin/sh\n+echo $# $1 $2 >> $GIT_DIR/pre-merge.args\n+EOF\n+chmod u+x .git/hooks/pre-merge\n+\n+test_expect_success 'pre-merge runs as expected ' '\n+\trm -f .git/pre-merge.args &&\n+\tgit reset --hard $commit0 &&\n+\tgit merge branch1 &&\n+\ttest -e .git/pre-merge.args\n+'\n+\n+test_expect_success 'pre-merge from fast-forward merge receives the right argument ' '\n+\ttest \"`cat .git/pre-merge.args`\" = \"4 ff --\"\n+'\n+\n+test_expect_success 'pre-merge from squash merge receives the right argument ' '\n+\trm -f .git/pre-merge.args &&\n+\tgit reset --hard $commit0 &&\n+\tgit merge --squash branch1 &&\n+\ttest \"`cat .git/pre-merge.args`\" = \"4 squash --\"\n+'\n+\n+test_expect_success 'pre-merge from trivial merge receives the right argument ' '\n+\trm -f .git/pre-merge.args &&\n+\tgit checkout $(git rev-parse branch1) &&\n+\tgit merge --no-ff -s resolve branch1 &&\n+\ttest \"`cat .git/pre-merge.args`\" = \"4 trivial --\"\n+'\n+\n+test_expect_success 'pre-merge from real merge receives the right argument ' '\n+\trm -f .git/pre-merge.args &&\n+\tgit reset --hard branch3 &&\n+\tgit merge --no-ff branch4 &&\n+\ttest \"`cat .git/pre-merge.args`\" = \"4 merge --\"\n+'\n+\n+test_expect_success 'pre-merge from octopus merge receives the right argument ' '\n+\trm -f .git/pre-merge.args &&\n+\tgit reset --hard $commit0 &&\n+\tgit merge branch1 branch2 branch3 &&\n+\ttest \"`cat .git/pre-merge.args`\" = \"6 octopus --\"\n+'\n+\n+test_expect_success 'pre-merge into empty head receives the right argument ' '\n+\t# Here we use a subshell so that the next tests have the right\n+\t# cwd if this test fails.\n+\tmkdir second &&\n+\t(cd second &&\n+\tgit init &&\n+\tcp ../.git/hooks/pre-merge .git/hooks/pre-merge &&\n+\tchmod u+x .git/hooks/pre-merge &&\n+\tgit fetch .. &&\n+\tgit merge $commit0 &&\n+\ttest \"`cat .git/pre-merge.args`\" = \"3 ff --\" &&\n+\tcd .. &&\n+\trm -rf second)\n+'\n+\n+test_expect_success 'pre-merge does not run for up-to-date ' '\n+\trm -f .git/pre-merge.args &&\n+\tgit merge $commit0 &&\n+\t! test -f .git/pre-merge.args\n+'\n+\n+test_expect_success 'pre-merge does not run for up-to-date octopus ' '\n+\trm -f .git/pre-merge.args &&\n+\tgit merge branch1 branch2 branch3 &&\n+\t! test -f .git/pre-merge.args\n+'\n+\n+cat >.git/hooks/pre-merge <<'EOF'\n+#!/bin/sh\n+exit 1\n+EOF\n+chmod u+x .git/hooks/pre-merge\n+\n+test_expect_success 'pre-merge can stop merge ' '\n+\t! git merge --no-ff branch4\n+'\n+\n+test_done\ndiff --git a/templates/hooks--pre-merge.sample b/templates/hooks--pre-merge.sample\nnew file mode 100755\nindex 0000000..bf2e82c\n--- /dev/null\n+++ b/templates/hooks--pre-merge.sample\n@@ -0,0 +1,49 @@\n+#!/bin/sh\n+#\n+# An example hook script to block merges on same branches.\n+# Called by git-merge with arguments: type -- head remote1 remote2...\n+#\n+# To enable this hook, rename this file to \"pre-merge\".\n+#\n+# Config\n+# ------\n+# branch.<branch-name>.allowmerges\n+#   This boolean sets whether merges will be allowed for the\n+#   given branch in the repository.  By default they will be.\n+\n+# --- Command line\n+type=\"$1\"\n+\n+# --- Safety check\n+if [ -z \"$GIT_DIR\" ]; then\n+\techo \"Don't run this script from the command line.\" >&2\n+\techo \" (if you want, you could supply GIT_DIR then run\" >&2\n+\techo \"  $0 <type> -- <head>...)\" >&2\n+\texit 1\n+fi\n+\n+if [ -z \"$type\" ]; then\n+\techo \"Usage: $0 <type> -- <head>...\" >&2\n+\texit 1\n+fi\n+\n+# --- Examine repository state\n+head=`git symbolic-ref HEAD 2>/dev/null || echo detached`\n+\n+# --- Do the actual check\n+case \"$head,$type\" in\n+\tdetached,* | *,squash | *,ff)\n+\t\t;;\n+\trefs/heads/*)\n+\t\tallowmerges=$(git config --bool branch.${head#refs/heads/}.allowmerges)\n+\t\ttest \"$allowmerges\" = false && {\n+\t\t\techo Merge commits not allowed on this branch.\n+\t\t\texit 1\n+\t\t}\n+\t\t;;\n+\t*)\n+\t\t;;\n+esac\n+\n+# --- Finished\n+exit 0\n-- \n1.5.5\n"},{"id":"88742","messageId":"7v63pmqqtx.fsf@gitster.siamese.dyndns.org","threadId":"15132","inReplyTo":"E1KYK3t-0000ZX-3b@fencepost.gnu.org","subject":"Re: [PATCH] add a pre-merge hook","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-08-27T18:02:18Z","receivedAt":"2008-08-27T18:02:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Paolo Bonzini <bonzini@gnu.org> writes:\n\n> +pre-merge\n> +---------\n> +\n> +This hook is invoked before a merge is attempted.  The command\n> +line passed to the hook can have multiple parameters.  The\n> +first parameter is the type of merge, which can be one of\n> +\"squash\" (if --squash was given on the command line), \"ff\"\n> +(fast-forward), \"trivial\" (trivial in-index merge), \"merge\"\n> +(non-trivial 2-head merge), \"octopus\" (any other merge).  After this\n> +there is a \"--\" argument, followed by the commit SHA1 values for the\n> +heads being merged.\n\nI think we should have done something like this for post_merge, not just\n\"1/0 to show if it is a squash\" which was an ill-thought-out hack.  Is\nthere a clean way to fix the interface without breaking existing\npost_merge hooks in people's repositories?\n\n> +The hook can interrupt the merge by returning a non-zero\n> +status.  The default hook checks for a boolean configuration\n> +key `branch.<branch-name>.allowmerges`, where `<branch-name>`\n> +is the current branch.  If the key is false, only squash or\n> +fast-forward merges are allowed.\n\nPlease call whatever you install from template/hooks--* a \"sample\", not\n\"default\".\n\n> +static int run_hook(const char *name, struct commit_list *heads, ...)\n>  {\n> + ...\n> +\tif (heads) {\n> +\t\targv[i] = \"--\";\n> +\t\tfirst_remote_head = i + 1;\n> +\t\tfor (; heads; heads = heads->next)\n> +\t\t\targv[++i] = xstrdup(sha1_to_hex(heads->item->object.sha1));\n> +\t\targv[++i] = NULL;\n> +\t} else\n> +\t\tfirst_remote_head = i;\n\nMaking this function varargs is fine, but this heads==NULL condition needs\nsome explanation.\n\nIf it is primarily a hack to support calling post-merge with an non-object\nname \"0\" or \"1\", probably it will be _much_ cleaner to make this a\nseparate function that does not know anything about post-merge calling\nconvention (it can share the boilerplate parts such as \"hook.no_stdin = 1\"\nwith the function that calls post-merge, of course).\n\n> +static int run_pre_merge_hook(const char *kind)\n> +{\n> +\treturn run_hook(\"pre-merge\", remoteheads,\n> +\t\t\tsquash ? \"squash\" : kind, \"--\", NULL);\n>  }\n\nremoteheads is a commit_list, you have the name of the hook and kind, so\nthis extra \"--\" is only for run_hook() hackery to distinguish between the\ncallers to post-merge and the callers to new pre-merge?\n\nThe general idea to add \"pre-merge\" hook may be sound, but the run_hook()\nimplementation looks quite unclean.\n"},{"id":"88769","messageId":"48B5B399.6090407@gnu.org","threadId":"15132","inReplyTo":"7v63pmqqtx.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH] add a pre-merge hook","fromName":"Paolo Bonzini","fromEmail":"bonzini@gnu.org","sentAt":"2008-08-27T20:05:45Z","receivedAt":"2008-08-27T20:05:45Z","isPatch":true,"sender":{"key":"bonzini@gnu.org","avatar":"https://avatars.githubusercontent.com/u/42082?v=4"},"body":"\n> I think we should have done something like this for post_merge, not just\n> \"1/0 to show if it is a squash\" which was an ill-thought-out hack.  Is\n> there a clean way to fix the interface without breaking existing\n> post_merge hooks in people's repositories?\n\nWell, you can add this argument as a second argument to post-merge.\nShall I do it?\n\n> Please call whatever you install from template/hooks--* a \"sample\", not\n> \"default\".\n\nOk.\n\n> If it is primarily a hack to support calling post-merge with an non-object\n> name \"0\" or \"1\"\n\nIt is to support not appending the commit list to the arguments of\npost-merge.\n\n>> +static int run_pre_merge_hook(const char *kind)\n>> +{\n>> +\treturn run_hook(\"pre-merge\", remoteheads,\n>> +\t\t\tsquash ? \"squash\" : kind, \"--\", NULL);\n>>  }\n> \n> remoteheads is a commit_list, you have the name of the hook and kind, so\n> this extra \"--\" is only for run_hook() hackery to distinguish between the\n> callers to post-merge and the callers to new pre-merge?\n\nNo, absolutely.  The \"--\" separator is for future extension, in case we\nwant to add other arguments before the head list.\n\nrun_hook does not care about what it receives in its varargs part.  It\nis copied verbatim until the NULL.  Then it adds a \"--\" and the heads\nargument if requested (which is not the case for post-merge).  The fact\nthat I had to specify a \"--\" here though is fishy.  Maybe I have a\noff-by-one somewhere that, if fixed, can simplify the code somehow.\n\nI'll take a look tomorrow, thanks for the review.\n\nPaolo\n"}]}