{"thread":{"id":"7471","subject":"[PATCH 3/3] Use BASE index extension in git-commit and git-merge.","startedAt":"2007-03-29T08:23:12Z","lastAt":"2007-03-29T08:23:12Z","messageCount":1,"participants":["Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"38295","messageId":"7vbqic4fwv.fsf@assigned-by-dhcp.cox.net","threadId":"7471","inReplyTo":null,"subject":"[PATCH 3/3] Use BASE index extension in git-commit and git-merge.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2007-03-29T08:23:12Z","receivedAt":"2007-03-29T08:23:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This makes git-commit and git-merge check the HEAD commit the\nindex is based on, and make sure the HEAD still points at that\ncommit, so that the user will notice the situation where\nsomebody else runs \"git push\" into the repository to update the\nhead of the current branch.  Committing blindly on such a head\ncan cause lossage of what was pushed into.\n\nWhen you are working on your changes, somebody (maybe yourself\nfrom a different machine) could push into your current branch by\nmistake.  When this happens, what is happening is that you start\nfrom this graph:\n\n ---o---A branch tip\n\nYou are working on commit A to create the next commit X.\n\n ---o---A............X (you were working towards creating this commit)\n\nHowever, somebody else mucked with your branch head in the meantime.\n\n          x---x---B updated branch tip\n         /\n ---o---A............X\n\nAt this point, your working tree and the index are still based\non A, so you would hopefully notice something very fishy is\nhappening behind your back if you run \"git diff --cached\" or\n\"git status\", and you would see many paths that are touched by\n'x' and 'B' commits as \"updated\" in the comments in \"git commit\"\nlog message template. But if you are not careful, you may not\nnotice and make a commit on top of whereever the HEAD is.\n\nIf we allowed a commit to be created in such a case, your next\ncommit will have B as the parent, with the tree state you wanted\nto have in X.  The graph becomes like this:\n\n          x---x---B\n         /          \\\n ---o---A            X (New HEAD)\n\nThe commit essentially reverts what happened in 'x' and 'B',\nwhich is quite bad.\n\nWhat you want to happen in this case is to make a graph like\nthis:\n\n          x---x---B branch tip\n         /\n ---o---A-------X (your work is still based on A)\n\nand then perhaps merge B's work, after making sure B is a\nfast-forward of A and doing other sanity checks:\n\n          x---x---B\n         /         \\\n ---o---A-------X---M the final branch tip\n\nAn example transcript with this patch when you try to commit in\nthe above scenario looks like this:\n\n    : start working based on A\n    ... in the meantime, somebody does 'git push' into the\n    ... repository to update the branch you are on.\n    $ edit; git commit -a -m 'X'; echo $?\n    * The index is based on '1d5c138... A', however, the HEAD\n      points at different commit 'a093461... B'\n    1\n\nAt this point, you would recover by detaching your HEAD to point\nat A, make a commit on top of it (which would be X whose parent\nis A and does not lose any of 'x' and 'B'), and then merge in\n'B'.  There is no command to do these yet.  This commit\nimplements only the detection of the above problematic case, and\ndoes not deal with the recovery.  The outline for the recovery\nprocedure would be:\n\n    $ ref=`git symbolic-ref HEAD`  ;# find out the original branch\n    $ branch=${ref#refs/heads/}\n    $ rm -f .git/HEAD              ;# detach the HEAD by hand\n    $ git update-index --get-base >.git/HEAD\n    $ git commit -a -m 'X' ;# the commit you wanted to make\n    $ git merge $ref               ;# create M\n    $ newhead=`git rev-parse HEAD` ;# ... and remember where it is\n    $ git checkout $branch\n    $ git merge $newhead           ;# and fast forward the branch\n\nThe last merge would fast-forward the original branch to M,\nresulting in the above picture that has the final branch tip at\ncommit M.\n\nNOTE!\n\nThere are other index-altering commands that can change HEAD\n(e.g. git-checkout <branch>) that need to be taught about\nchecking and updating BASE.\n\nCurrently they just invalidate the BASE commit object recorded\nin the index (see the change to builtin-read-tree.c in this\ncommit), so later commit/merge safety check won't trigger.\n\nSigned-off-by: Junio C Hamano <junkio@cox.net>\n---\n builtin-read-tree.c |    1 +\n git-commit.sh       |   32 +++++++++++++++++++++++++++++++-\n git-merge.sh        |   15 +++++++++++++++\n merge-recursive.c   |    3 +++\n 4 files changed, 50 insertions(+), 1 deletions(-)\n\ndiff --git a/builtin-read-tree.c b/builtin-read-tree.c\nindex 793eae0..06c2912 100644\n--- a/builtin-read-tree.c\n+++ b/builtin-read-tree.c\n@@ -31,6 +31,7 @@ static int read_cache_unmerged(void)\n \tstruct cache_entry *last = NULL;\n \n \tread_cache();\n+\tactive_cache_base_valid = 0;\n \tdst = active_cache;\n \tfor (i = 0; i < active_nr; i++) {\n \t\tstruct cache_entry *ce = active_cache[i];\ndiff --git a/git-commit.sh b/git-commit.sh\nindex 292cf96..42f1c93 100755\n--- a/git-commit.sh\n+++ b/git-commit.sh\n@@ -25,6 +25,22 @@ refuse_partial () {\n \texit 1\n }\n \n+check_base () {\n+\tquiet=\"$1\"\n+\tif HEAD=`git rev-parse --verify HEAD 2>/dev/null` &&\n+\t   BASE=`git update-index --get-base` &&\n+\t   test -n \"$BASE\" &&\n+\t   test \"$BASE\" != \"$HEAD\"\n+\tthen\n+\t\ttest -z \"$quiet\" || exit 1\n+\t\tours=`git show -s --pretty=oneline --abbrev-commit $BASE`\n+\t\ttheirs=`git show -s --pretty=oneline --abbrev-commit $HEAD`\n+\t\techo >&2 \"* The index is based on '$ours', however, the HEAD\"\n+\t\techo >&2 \"  points at different commit '$theirs'\"\n+\t\texit 1\n+\tfi\n+}\n+\n THIS_INDEX=\"$GIT_DIR/index\"\n NEXT_INDEX=\"$GIT_DIR/next-index$$\"\n rm -f \"$NEXT_INDEX\"\n@@ -39,6 +55,10 @@ run_status () {\n \t# NEXT_INDEX exists, that is the index file used to\n \t# make the commit.  Otherwise we are using as-is commit\n \t# so the regular index file is what we use to compare.\n+\n+\t# We want a subshell, as we do not want to affect others with\n+\t# GIT_INDEX_FILE environment.\n+\t(\n \tif test '' != \"$TMP_INDEX\"\n \tthen\n \t\tGIT_INDEX_FILE=\"$TMP_INDEX\"\n@@ -57,6 +77,7 @@ run_status () {\n \t\t${verbose:+--verbose} \\\n \t\t${amend:+--amend} \\\n \t\t${untracked_files:+--untracked}\n+\t)\n }\n \n trap '\n@@ -322,6 +343,10 @@ t,,[1-9]*)\n esac\n \n ################################################################\n+# Check for a case where HEAD was updated underneath us.\n+check_base || exit\n+\n+################################################################\n # Prepare index to have a tree to be committed\n \n case \"$all,$also\" in\n@@ -610,6 +635,10 @@ if cnt=`grep -v -i '^Signed-off-by' \"$GIT_DIR\"/COMMIT_MSG |\n \twc -l` &&\n    test 0 -lt $cnt\n then\n+\t# Check for a case where HEAD was updated underneath us\n+\t# while we were editing the message.\n+\tcheck_base || exit\n+\n \tif test -z \"$TMP_INDEX\"\n \tthen\n \t\ttree=$(GIT_INDEX_FILE=\"$USE_INDEX\" git-write-tree)\n@@ -626,7 +655,8 @@ then\n \t\tmv \"$NEXT_INDEX\" \"$THIS_INDEX\"\n \telse\n \t\t: ;# happy\n-\tfi\n+\tfi &&\n+\tgit update-index --set-base $commit\n else\n \techo >&2 \"* no commit message?  aborting commit.\"\n \tfalse\ndiff --git a/git-merge.sh b/git-merge.sh\nindex fa45891..df4d3c9 100755\n--- a/git-merge.sh\n+++ b/git-merge.sh\n@@ -80,6 +80,7 @@ finish () {\n \t\t\t;;\n \t\t*)\n \t\t\tgit-update-ref -m \"$rlogm\" HEAD \"$1\" \"$head\" || exit 1\n+\t\t\tgit-update-index --set-base \"$1\"\n \t\t\t;;\n \t\tesac\n \t\t;;\n@@ -167,6 +168,8 @@ do\n \tshift\n done\n \n+BASE=`git update-index --get-base`\n+\n # This could be traditional \"merge <msg> HEAD <commit>...\"  and the\n # way we can tell it is to see if the second token is HEAD, but some\n # people might have misused the interface and used a committish that\n@@ -196,8 +199,16 @@ then\n \trh=$(git rev-parse --verify \"$1^0\") ||\n \t\tdie \"$1 - not something we can merge\"\n \n+\t# If somebody pushed :refs/heads/$current_branch in the meantime\n+\t# then we are in trouble.\n+\tif test -n \"$BASE\"\n+\tthen\n+\t\tdie \"Your index is based on '$BASE' commit, but the branch you were on disappeared.\"\n+\tfi\n+\n \tgit-update-ref -m \"initial pull\" HEAD \"$rh\" \"\" &&\n \tgit-read-tree --reset -u HEAD\n+\tgit update-index --set-base $rh\n \texit\n \n else\n@@ -217,6 +228,10 @@ else\n \tmerge_msg=\"${merge_msg:+$merge_msg$LF$LF}$merge_name\"\n fi\n head=$(git-rev-parse --verify \"$head_arg\"^0) || usage\n+if test -n \"$BASE\" && test \"$head\" != \"$BASE\"\n+then\n+\tdie \"Your index is based on '$BASE' commit, but the branch tip you are on is at '$head'\"\n+fi\n \n # All the rest are remote heads\n test \"$#\" = 0 && usage ;# we need at least one remote head.\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex c96e1a7..4b86351 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1361,6 +1361,9 @@ int main(int argc, char *argv[])\n \t}\n \tclean = merge(h1, h2, branch1, branch2, ca, &result);\n \n+\thashcpy(active_cache_base, h1->object.sha1);\n+\tactive_cache_base_valid = 1;\n+\n \tif (active_cache_changed &&\n \t    (write_cache(index_fd, active_cache, active_nr) ||\n \t     close(index_fd) || commit_lock_file(lock)))\n-- \n1.5.1.rc3.1.ga429d\n"}]}