{"thread":{"id":"39929","subject":"[PATCH 1/9] refs: Introduce pseudoref and per-worktree ref concepts","startedAt":"2015-07-24T04:45:20Z","lastAt":"2015-08-02T22:48:05Z","messageCount":20,"participants":["David Turner","Eric Sunshine","Junio C Hamano","Philip Oakley","Michael Haggerty"],"isPatch":true,"patchVersion":1,"patchTotal":9},"messages":[{"id":"266704","messageId":"1437713129-19373-1-git-send-email-dturner@twopensource.com","threadId":"39929","inReplyTo":null,"subject":"[PATCH/RFC 0/9] Pseudorefs","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-07-24T04:45:20Z","receivedAt":"2015-07-24T04:45:20Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"This series is another chunk of the pluggable refs backend work.  The\nmajor reason it is listed as \"PATCH/RFC\" is beacuse it breaks\nt9300-fast-import.sh, because fast-import wants to create a ref called\nTEMP_TAG, which would now be a pseudoref.  The commit that introduces\nthis test says that cvs2svn creates a tag called TAG_FIXUP \"as a branch\nname for temporary work needed to cleanup the tree prior to creating\nan annotated tag object.\"\n\nIt appears that cvs2svn still does this.  So I'm not quite sure what to\ndo about this particular case.\n\nAs we discussed earlier, the motivation for this series is that refs\nbackends other than the files-based backend need to treat per-worktree\nrefs (HEAD) and pseudorefs (FETCH_HEAD) differently from other refs;\nother refs are per-repo rather than per-worktree.\n"},{"id":"266703","messageId":"1437713129-19373-2-git-send-email-dturner@twopensource.com","threadId":"39929","inReplyTo":"1437713129-19373-1-git-send-email-dturner@twopensource.com","subject":"[PATCH 1/9] refs: Introduce pseudoref and per-worktree ref concepts","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-07-24T04:45:21Z","receivedAt":"2015-07-24T04:45:21Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Add glossary entries for both concepts.\n\nPseudorefs and per-worktree refs do not yet have special handling,\nbecause the files refs backend already handles them correctly.  Later,\nwe will make the LMDB backend call out to the files backend to handle\nper-worktree refs.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n Documentation/glossary-content.txt | 17 +++++++++++++++++\n refs.c                             | 23 +++++++++++++++++++++++\n refs.h                             |  2 ++\n 3 files changed, 42 insertions(+)\n\ndiff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt\nindex ab18f4b..d04819e 100644\n--- a/Documentation/glossary-content.txt\n+++ b/Documentation/glossary-content.txt\n@@ -411,6 +411,23 @@ exclude;;\n \tcore Git. Porcelains expose more of a <<def_SCM,SCM>>\n \tinterface than the <<def_plumbing,plumbing>>.\n \n+[[def_per_worktree_ref]]per-worktree ref::\n+\tRefs that are per-<<def_worktree,worktree>>, rather than\n+\tglobal.  This is presently only <<def_HEAD,HEAD>>, but might\n+\tlater include other unsuual refs.\n+\n+[[def_pseudoref]]pseudoref::\n+\tFiles under `$GIT_DIR` whose names are all-caps, and that\n+\tcontain a line consisting of a <<def_sha1,SHA-1>> followed by\n+\ta newline, and optionally some additional data.  `MERGE_HEAD`\n+\tand `CHERRY_PICK_HEAD` are examples.  Unlike\n+\t<<def_per_worktree_ref,per-worktree refs>>, these files cannot\n+\tbe symbolic refs, and never not have reflogs.  They also\n+\tcannot be updated through the normal ref update machinery.\n+\tInstead, they are updated by directly writing to the files.\n+\tHowever, they can be read as if they were refs, so `git\n+\trev-parse MERGE_HEAD` will work.\n+\n [[def_pull]]pull::\n \tPulling a <<def_branch,branch>> means to <<def_fetch,fetch>> it and\n \t<<def_merge,merge>> it.  See also linkgit:git-pull[1].\ndiff --git a/refs.c b/refs.c\nindex 0b96ece..d31ca42 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3857,6 +3857,29 @@ void ref_transaction_free(struct ref_transaction *transaction)\n \tfree(transaction);\n }\n \n+int is_per_worktree_ref(const char *refname)\n+{\n+\treturn !strcmp(refname, \"HEAD\");\n+}\n+\n+static int is_pseudoref(const char *refname)\n+{\n+\tconst char *c;\n+\n+\tif (strchr(refname, '/'))\n+\t\treturn 0;\n+\n+\tif (is_per_worktree_ref(refname))\n+\t\treturn 0;\n+\n+\tfor (c = refname; *c; ++c) {\n+\t\tif (!isupper(*c) && *c != '-' && *c != '_')\n+\t\t\treturn 0;\n+\t}\n+\n+\treturn 1;\n+}\n+\n static struct ref_update *add_update(struct ref_transaction *transaction,\n \t\t\t\t     const char *refname)\n {\ndiff --git a/refs.h b/refs.h\nindex e4e46c3..bd5526e 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -445,6 +445,8 @@ extern int parse_hide_refs_config(const char *var, const char *value, const char\n \n extern int ref_is_hidden(const char *);\n \n+int is_per_worktree_ref(const char *refname);\n+\n enum expire_reflog_flags {\n \tEXPIRE_REFLOGS_DRY_RUN = 1 << 0,\n \tEXPIRE_REFLOGS_UPDATE_REF = 1 << 1,\n-- \n2.0.4.315.gad8727a-twtrsrc\n"},{"id":"266709","messageId":"1437713129-19373-3-git-send-email-dturner@twopensource.com","threadId":"39929","inReplyTo":"1437713129-19373-1-git-send-email-dturner@twopensource.com","subject":"[PATCH 2/9] notes: replace pseudorefs with real refs","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-07-24T04:45:22Z","receivedAt":"2015-07-24T04:45:22Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"All-caps files like NOTES_MERGE_REF are pseudorefs, and thus are\nper-worktree.  We don't want multiple notes merges happening at once\n(in different worktrees), so we want to make these refs true refs.\n\nSo, we lowercase NOTES_MERGE_REF and friends.  That way, backends\nthat distinguish between pseudorefs and real refs can correctly\nhandle notes merges.\n\nThis will also enable us to prevent pseudorefs from being updated by\nthe ref update machinery e.g. git update-ref.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n Documentation/git-notes.txt           | 12 ++---\n builtin/notes.c                       | 38 ++++++++--------\n notes-merge.c                         | 10 ++--\n notes-merge.h                         |  8 ++--\n t/t3310-notes-merge-manual-resolve.sh | 86 +++++++++++++++++------------------\n t/t3311-notes-merge-fanout.sh         |  6 +--\n 6 files changed, 80 insertions(+), 80 deletions(-)\n\ndiff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt\nindex 851518d..82fa3fd 100644\n--- a/Documentation/git-notes.txt\n+++ b/Documentation/git-notes.txt\n@@ -103,7 +103,7 @@ merge::\n If conflicts arise and a strategy for automatically resolving\n conflicting notes (see the -s/--strategy option) is not given,\n the \"manual\" resolver is used. This resolver checks out the\n-conflicting notes in a special worktree (`.git/NOTES_MERGE_WORKTREE`),\n+conflicting notes in a special worktree (`.git/notes_merge_worktree`),\n and instructs the user to manually resolve the conflicts there.\n When done, the user can either finalize the merge with\n 'git notes merge --commit', or abort the merge with\n@@ -189,11 +189,11 @@ OPTIONS\n --commit::\n \tFinalize an in-progress 'git notes merge'. Use this option\n \twhen you have resolved the conflicts that 'git notes merge'\n-\tstored in .git/NOTES_MERGE_WORKTREE. This amends the partial\n+\tstored in .git/notes_merge_worktree. This amends the partial\n \tmerge commit created by 'git notes merge' (stored in\n-\t.git/NOTES_MERGE_PARTIAL) by adding the notes in\n-\t.git/NOTES_MERGE_WORKTREE. The notes ref stored in the\n-\t.git/NOTES_MERGE_REF symref is updated to the resulting commit.\n+\t.git/notes_merge_partial) by adding the notes in\n+\t.git/notes_merge_worktree. The notes ref stored in the\n+\t.git/notes_merge_ref symref is updated to the resulting commit.\n \n --abort::\n \tAbort/reset a in-progress 'git notes merge', i.e. a notes merge\n@@ -241,7 +241,7 @@ NOTES MERGE STRATEGIES\n \n The default notes merge strategy is \"manual\", which checks out\n conflicting notes in a special work tree for resolving notes conflicts\n-(`.git/NOTES_MERGE_WORKTREE`), and instructs the user to resolve the\n+(`.git/notes_merge_worktree`), and instructs the user to resolve the\n conflicts in that work tree.\n When done, the user can either finalize the merge with\n 'git notes merge --commit', or abort the merge with\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 63f95fc..e0b8a02 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -670,14 +670,14 @@ static int merge_abort(struct notes_merge_options *o)\n \tint ret = 0;\n \n \t/*\n-\t * Remove .git/NOTES_MERGE_PARTIAL and .git/NOTES_MERGE_REF, and call\n-\t * notes_merge_abort() to remove .git/NOTES_MERGE_WORKTREE.\n+\t * Remove .git/notes_merge_partial and .git/notes_merge_ref, and call\n+\t * notes_merge_abort() to remove .git/notes_merge_worktree.\n \t */\n \n-\tif (delete_ref(\"NOTES_MERGE_PARTIAL\", NULL, 0))\n-\t\tret += error(\"Failed to delete ref NOTES_MERGE_PARTIAL\");\n-\tif (delete_ref(\"NOTES_MERGE_REF\", NULL, REF_NODEREF))\n-\t\tret += error(\"Failed to delete ref NOTES_MERGE_REF\");\n+\tif (delete_ref(\"notes_merge_partial\", NULL, 0))\n+\t\tret += error(\"Failed to delete ref notes_merge_partial\");\n+\tif (delete_ref(\"notes_merge_ref\", NULL, REF_NODEREF))\n+\t\tret += error(\"Failed to delete ref notes_merge_ref\");\n \tif (notes_merge_abort(o))\n \t\tret += error(\"Failed to remove 'git notes merge' worktree\");\n \treturn ret;\n@@ -694,16 +694,16 @@ static int merge_commit(struct notes_merge_options *o)\n \tint ret;\n \n \t/*\n-\t * Read partial merge result from .git/NOTES_MERGE_PARTIAL,\n-\t * and target notes ref from .git/NOTES_MERGE_REF.\n+\t * Read partial merge result from .git/notes_merge_partial,\n+\t * and target notes ref from .git/notes_merge_ref.\n \t */\n \n-\tif (get_sha1(\"NOTES_MERGE_PARTIAL\", sha1))\n-\t\tdie(\"Failed to read ref NOTES_MERGE_PARTIAL\");\n+\tif (get_sha1(\"notes_merge_partial\", sha1))\n+\t\tdie(\"Failed to read ref notes_merge_partial\");\n \telse if (!(partial = lookup_commit_reference(sha1)))\n-\t\tdie(\"Could not find commit from NOTES_MERGE_PARTIAL.\");\n+\t\tdie(\"Could not find commit from notes_merge_partial.\");\n \telse if (parse_commit(partial))\n-\t\tdie(\"Could not parse commit from NOTES_MERGE_PARTIAL.\");\n+\t\tdie(\"Could not parse commit from notes_merge_partial.\");\n \n \tif (partial->parents)\n \t\thashcpy(parent_sha1, partial->parents->item->object.sha1);\n@@ -711,12 +711,12 @@ static int merge_commit(struct notes_merge_options *o)\n \t\thashclr(parent_sha1);\n \n \tt = xcalloc(1, sizeof(struct notes_tree));\n-\tinit_notes(t, \"NOTES_MERGE_PARTIAL\", combine_notes_overwrite, 0);\n+\tinit_notes(t, \"notes_merge_partial\", combine_notes_overwrite, 0);\n \n \to->local_ref = local_ref_to_free =\n-\t\tresolve_refdup(\"NOTES_MERGE_REF\", 0, sha1, NULL);\n+\t\tresolve_refdup(\"notes_merge_ref\", 0, sha1, NULL);\n \tif (!o->local_ref)\n-\t\tdie(\"Failed to resolve NOTES_MERGE_REF\");\n+\t\tdie(\"Failed to resolve notes_merge_ref\");\n \n \tif (notes_merge_commit(o, t, partial, sha1))\n \t\tdie(\"Failed to finalize notes merge\");\n@@ -825,11 +825,11 @@ static int merge(int argc, const char **argv, const char *prefix)\n \t\tupdate_ref(msg.buf, default_notes_ref(), result_sha1, NULL,\n \t\t\t   0, UPDATE_REFS_DIE_ON_ERR);\n \telse { /* Merge has unresolved conflicts */\n-\t\t/* Update .git/NOTES_MERGE_PARTIAL with partial merge result */\n-\t\tupdate_ref(msg.buf, \"NOTES_MERGE_PARTIAL\", result_sha1, NULL,\n+\t\t/* Update .git/notes_merge_partial with partial merge result */\n+\t\tupdate_ref(msg.buf, \"notes_merge_partial\", result_sha1, NULL,\n \t\t\t   0, UPDATE_REFS_DIE_ON_ERR);\n-\t\t/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */\n-\t\tif (create_symref(\"NOTES_MERGE_REF\", default_notes_ref(), NULL))\n+\t\t/* Store ref-to-be-updated into .git/notes_merge_ref */\n+\t\tif (create_symref(\"notes_merge_ref\", default_notes_ref(), NULL))\n \t\t\tdie(\"Failed to store link to current notes ref (%s)\",\n \t\t\t    default_notes_ref());\n \t\tprintf(\"Automatic notes merge failed. Fix conflicts in %s and \"\ndiff --git a/notes-merge.c b/notes-merge.c\nindex 0b2b82c..58a8637 100644\n--- a/notes-merge.c\n+++ b/notes-merge.c\n@@ -274,10 +274,10 @@ static void check_notes_merge_worktree(struct notes_merge_options *o)\n \t\t\t\t    \"'git notes merge --commit' or 'git notes \"\n \t\t\t\t    \"merge --abort' to commit/abort the \"\n \t\t\t\t    \"previous merge before you start a new \"\n-\t\t\t\t    \"notes merge.\", git_path(\"NOTES_MERGE_*\"));\n+\t\t\t\t    \"notes merge.\", git_path(\"notes_merge_*\"));\n \t\t\telse\n \t\t\t\tdie(\"You have not concluded your notes merge \"\n-\t\t\t\t    \"(%s exists).\", git_path(\"NOTES_MERGE_*\"));\n+\t\t\t\t    \"(%s exists).\", git_path(\"notes_merge_*\"));\n \t\t}\n \n \t\tif (safe_create_leading_directories_const(git_path(\n@@ -663,7 +663,7 @@ int notes_merge_commit(struct notes_merge_options *o,\n \t\t       unsigned char *result_sha1)\n {\n \t/*\n-\t * Iterate through files in .git/NOTES_MERGE_WORKTREE and add all\n+\t * Iterate through files in .git/notes_merge_workree and add all\n \t * found notes to 'partial_tree'. Write the updated notes tree to\n \t * the DB, and commit the resulting tree object while reusing the\n \t * commit message and parents from 'partial_commit'.\n@@ -734,8 +734,8 @@ int notes_merge_commit(struct notes_merge_options *o,\n int notes_merge_abort(struct notes_merge_options *o)\n {\n \t/*\n-\t * Remove all files within .git/NOTES_MERGE_WORKTREE. We do not remove\n-\t * the .git/NOTES_MERGE_WORKTREE directory itself, since it might be\n+\t * Remove all files within .git/notes_merge_workree. We do not remove\n+\t * the .git/notes_merge_workree directory itself, since it might be\n \t * the current working directory of the user.\n \t */\n \tstruct strbuf buf = STRBUF_INIT;\ndiff --git a/notes-merge.h b/notes-merge.h\nindex 1d01f6a..18705d6 100644\n--- a/notes-merge.h\n+++ b/notes-merge.h\n@@ -1,7 +1,7 @@\n #ifndef NOTES_MERGE_H\n #define NOTES_MERGE_H\n \n-#define NOTES_MERGE_WORKTREE \"NOTES_MERGE_WORKTREE\"\n+#define NOTES_MERGE_WORKTREE \"notes_merge_worktree\"\n \n enum notes_merge_verbosity {\n \tNOTES_MERGE_VERBOSITY_DEFAULT = 2,\n@@ -46,7 +46,7 @@ void init_notes_merge_options(struct notes_merge_options *o);\n  *    are stored in 'local_tree', and the SHA1 or the resulting commit\n  *    (to be amended when the conflicts have been resolved) is written into\n  *    'result_sha1'. The unmerged entries are written into the\n- *    .git/NOTES_MERGE_WORKTREE directory with conflict markers.\n+ *    .git/notes_merge_worktree directory with conflict markers.\n  *    -1 is returned.\n  *\n  * Both o->local_ref and o->remote_ref must be given (non-NULL), but either ref\n@@ -65,7 +65,7 @@ int notes_merge(struct notes_merge_options *o,\n  * the given 'partial_commit', the partial result commit created by a previous\n  * call to notes_merge().\n  *\n- * This function will add the (now resolved) notes in .git/NOTES_MERGE_WORKTREE\n+ * This function will add the (now resolved) notes in .git/notes_merge_worktree\n  * to 'partial_tree', and create a final notes merge commit, the SHA1 of which\n  * will be stored in 'result_sha1'.\n  */\n@@ -77,7 +77,7 @@ int notes_merge_commit(struct notes_merge_options *o,\n /*\n  * Abort conflict resolution from an earlier notes_merge()\n  *\n- * Removes the notes merge worktree in .git/NOTES_MERGE_WORKTREE.\n+ * Removes the notes merge worktree in .git/notes_merge_worktree.\n  */\n int notes_merge_abort(struct notes_merge_options *o);\n \ndiff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh\nindex 195bb97..dc9ecd5 100755\n--- a/t/t3310-notes-merge-manual-resolve.sh\n+++ b/t/t3310-notes-merge-manual-resolve.sh\n@@ -178,12 +178,12 @@ test_expect_success 'merge z into m (== y) with default (\"manual\") resolver => C\n \tgit config core.notesRef refs/notes/m &&\n \ttest_must_fail git notes merge z >output &&\n \t# Output should point to where to resolve conflicts\n-\tgrep -q \"\\\\.git/NOTES_MERGE_WORKTREE\" output &&\n+\tgrep -q \"\\\\.git/notes_merge_worktree\" output &&\n \t# Inspect merge conflicts\n-\tls .git/NOTES_MERGE_WORKTREE >output_conflicts &&\n+\tls .git/notes_merge_worktree >output_conflicts &&\n \ttest_cmp expect_conflicts output_conflicts &&\n \t( for f in $(cat expect_conflicts); do\n-\t\ttest_cmp \"expect_conflict_$f\" \".git/NOTES_MERGE_WORKTREE/$f\" ||\n+\t\ttest_cmp \"expect_conflict_$f\" \".git/notes_merge_worktree/$f\" ||\n \t\texit 1\n \tdone ) &&\n \t# Verify that current notes tree (pre-merge) has not changed (m == y)\n@@ -222,10 +222,10 @@ test_expect_success 'change notes in z' '\n '\n \n test_expect_success 'cannot do merge w/conflicts when previous merge is unfinished' '\n-\ttest -d .git/NOTES_MERGE_WORKTREE &&\n+\ttest -d .git/notes_merge_worktree &&\n \ttest_must_fail git notes merge z >output 2>&1 &&\n \t# Output should indicate what is wrong\n-\tgrep -q \"\\\\.git/NOTES_MERGE_\\\\* exists\" output\n+\tgrep -q \"\\\\.git/notes_merge_\\\\* exists\" output\n '\n \n # Setup non-conflicting merge between x and new notes ref w\n@@ -282,7 +282,7 @@ w notes on 1st commit\n EOF\n \n test_expect_success 'can do merge without conflicts even if previous merge is unfinished (x => w)' '\n-\ttest -d .git/NOTES_MERGE_WORKTREE &&\n+\ttest -d .git/notes_merge_worktree &&\n \tgit notes merge x &&\n \tverify_notes w &&\n \t# Verify that other notes refs has not changed (x and y)\n@@ -316,15 +316,15 @@ EOF\n \n test_expect_success 'finalize conflicting merge (z => m)' '\n \t# Resolve conflicts and finalize merge\n-\tcat >.git/NOTES_MERGE_WORKTREE/$commit_sha1 <<EOF &&\n+\tcat >.git/notes_merge_worktree/$commit_sha1 <<EOF &&\n y and z notes on 1st commit\n EOF\n-\tcat >.git/NOTES_MERGE_WORKTREE/$commit_sha4 <<EOF &&\n+\tcat >.git/notes_merge_worktree/$commit_sha4 <<EOF &&\n y and z notes on 4th commit\n EOF\n \tgit notes merge --commit &&\n-\t# No .git/NOTES_MERGE_* files left\n-\ttest_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&\n+\t# No .git/notes_merge_* files left\n+\ttest_might_fail ls .git/notes_merge_* >output 2>/dev/null &&\n \ttest_cmp /dev/null output &&\n \t# Merge commit has pre-merge y and pre-merge z as parents\n \ttest \"$(git rev-parse refs/notes/m^1)\" = \"$(cat pre_merge_y)\" &&\n@@ -369,12 +369,12 @@ test_expect_success 'redo merge of z into m (== y) with default (\"manual\") resol\n \tgit config core.notesRef refs/notes/m &&\n \ttest_must_fail git notes merge z >output &&\n \t# Output should point to where to resolve conflicts\n-\tgrep -q \"\\\\.git/NOTES_MERGE_WORKTREE\" output &&\n+\tgrep -q \"\\\\.git/notes_merge_worktree\" output &&\n \t# Inspect merge conflicts\n-\tls .git/NOTES_MERGE_WORKTREE >output_conflicts &&\n+\tls .git/notes_merge_worktree >output_conflicts &&\n \ttest_cmp expect_conflicts output_conflicts &&\n \t( for f in $(cat expect_conflicts); do\n-\t\ttest_cmp \"expect_conflict_$f\" \".git/NOTES_MERGE_WORKTREE/$f\" ||\n+\t\ttest_cmp \"expect_conflict_$f\" \".git/notes_merge_worktree/$f\" ||\n \t\texit 1\n \tdone ) &&\n \t# Verify that current notes tree (pre-merge) has not changed (m == y)\n@@ -385,8 +385,8 @@ test_expect_success 'redo merge of z into m (== y) with default (\"manual\") resol\n \n test_expect_success 'abort notes merge' '\n \tgit notes merge --abort &&\n-\t# No .git/NOTES_MERGE_* files left\n-\ttest_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&\n+\t# No .git/notes_merge_* files left\n+\ttest_might_fail ls .git/notes_merge_* >output 2>/dev/null &&\n \ttest_cmp /dev/null output &&\n \t# m has not moved (still == y)\n \ttest \"$(git rev-parse refs/notes/m)\" = \"$(cat pre_merge_y)\" &&\n@@ -403,12 +403,12 @@ git rev-parse refs/notes/z > pre_merge_z\n test_expect_success 'redo merge of z into m (== y) with default (\"manual\") resolver => Conflicting 3-way merge' '\n \ttest_must_fail git notes merge z >output &&\n \t# Output should point to where to resolve conflicts\n-\tgrep -q \"\\\\.git/NOTES_MERGE_WORKTREE\" output &&\n+\tgrep -q \"\\\\.git/notes_merge_worktree\" output &&\n \t# Inspect merge conflicts\n-\tls .git/NOTES_MERGE_WORKTREE >output_conflicts &&\n+\tls .git/notes_merge_worktree >output_conflicts &&\n \ttest_cmp expect_conflicts output_conflicts &&\n \t( for f in $(cat expect_conflicts); do\n-\t\ttest_cmp \"expect_conflict_$f\" \".git/NOTES_MERGE_WORKTREE/$f\" ||\n+\t\ttest_cmp \"expect_conflict_$f\" \".git/notes_merge_worktree/$f\" ||\n \t\texit 1\n \tdone ) &&\n \t# Verify that current notes tree (pre-merge) has not changed (m == y)\n@@ -441,19 +441,19 @@ EOF\n \n test_expect_success 'add + remove notes in finalized merge (z => m)' '\n \t# Resolve one conflict\n-\tcat >.git/NOTES_MERGE_WORKTREE/$commit_sha1 <<EOF &&\n+\tcat >.git/notes_merge_worktree/$commit_sha1 <<EOF &&\n y and z notes on 1st commit\n EOF\n \t# Remove another conflict\n-\trm .git/NOTES_MERGE_WORKTREE/$commit_sha4 &&\n+\trm .git/notes_merge_worktree/$commit_sha4 &&\n \t# Remove a D/F conflict\n-\trm .git/NOTES_MERGE_WORKTREE/$commit_sha3 &&\n+\trm .git/notes_merge_worktree/$commit_sha3 &&\n \t# Add a new note\n-\techo \"new note on 5th commit\" > .git/NOTES_MERGE_WORKTREE/$commit_sha5 &&\n+\techo \"new note on 5th commit\" > .git/notes_merge_worktree/$commit_sha5 &&\n \t# Finalize merge\n \tgit notes merge --commit &&\n-\t# No .git/NOTES_MERGE_* files left\n-\ttest_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&\n+\t# No .git/notes_merge_* files left\n+\ttest_might_fail ls .git/notes_merge_* >output 2>/dev/null &&\n \ttest_cmp /dev/null output &&\n \t# Merge commit has pre-merge y and pre-merge z as parents\n \ttest \"$(git rev-parse refs/notes/m^1)\" = \"$(cat pre_merge_y)\" &&\n@@ -484,12 +484,12 @@ test_expect_success 'redo merge of z into m (== y) with default (\"manual\") resol\n \tgit update-ref refs/notes/m refs/notes/y &&\n \ttest_must_fail git notes merge z >output &&\n \t# Output should point to where to resolve conflicts\n-\tgrep -q \"\\\\.git/NOTES_MERGE_WORKTREE\" output &&\n+\tgrep -q \"\\\\.git/notes_merge_worktree\" output &&\n \t# Inspect merge conflicts\n-\tls .git/NOTES_MERGE_WORKTREE >output_conflicts &&\n+\tls .git/notes_merge_worktree >output_conflicts &&\n \ttest_cmp expect_conflicts output_conflicts &&\n \t( for f in $(cat expect_conflicts); do\n-\t\ttest_cmp \"expect_conflict_$f\" \".git/NOTES_MERGE_WORKTREE/$f\" ||\n+\t\ttest_cmp \"expect_conflict_$f\" \".git/notes_merge_worktree/$f\" ||\n \t\texit 1\n \tdone ) &&\n \t# Verify that current notes tree (pre-merge) has not changed (m == y)\n@@ -507,31 +507,31 @@ test_expect_success 'reset notes ref m to somewhere else (w)' '\n \ttest \"$(git rev-parse refs/notes/m)\" = \"$(git rev-parse refs/notes/w)\"\n '\n \n-test_expect_success 'fail to finalize conflicting merge if underlying ref has moved in the meantime (m != NOTES_MERGE_PARTIAL^1)' '\n+test_expect_success 'fail to finalize conflicting merge if underlying ref has moved in the meantime (m != notes_merge_partial^1)' '\n \t# Resolve conflicts\n-\tcat >.git/NOTES_MERGE_WORKTREE/$commit_sha1 <<EOF &&\n+\tcat >.git/notes_merge_worktree/$commit_sha1 <<EOF &&\n y and z notes on 1st commit\n EOF\n-\tcat >.git/NOTES_MERGE_WORKTREE/$commit_sha4 <<EOF &&\n+\tcat >.git/notes_merge_worktree/$commit_sha4 <<EOF &&\n y and z notes on 4th commit\n EOF\n \t# Fail to finalize merge\n \ttest_must_fail git notes merge --commit >output 2>&1 &&\n-\t# .git/NOTES_MERGE_* must remain\n-\ttest -f .git/NOTES_MERGE_PARTIAL &&\n-\ttest -f .git/NOTES_MERGE_REF &&\n-\ttest -f .git/NOTES_MERGE_WORKTREE/$commit_sha1 &&\n-\ttest -f .git/NOTES_MERGE_WORKTREE/$commit_sha2 &&\n-\ttest -f .git/NOTES_MERGE_WORKTREE/$commit_sha3 &&\n-\ttest -f .git/NOTES_MERGE_WORKTREE/$commit_sha4 &&\n+\t# .git/notes_merge_* must remain\n+\tgit rev-parse --verify notes_merge_partial &&\n+\tgit rev-parse --verify notes_merge_ref &&\n+\ttest -f .git/notes_merge_worktree/$commit_sha1 &&\n+\ttest -f .git/notes_merge_worktree/$commit_sha2 &&\n+\ttest -f .git/notes_merge_worktree/$commit_sha3 &&\n+\ttest -f .git/notes_merge_worktree/$commit_sha4 &&\n \t# Refs are unchanged\n \ttest \"$(git rev-parse refs/notes/m)\" = \"$(git rev-parse refs/notes/w)\" &&\n-\ttest \"$(git rev-parse refs/notes/y)\" = \"$(git rev-parse NOTES_MERGE_PARTIAL^1)\" &&\n-\ttest \"$(git rev-parse refs/notes/m)\" != \"$(git rev-parse NOTES_MERGE_PARTIAL^1)\" &&\n+\ttest \"$(git rev-parse refs/notes/y)\" = \"$(git rev-parse notes_merge_partial^1)\" &&\n+\ttest \"$(git rev-parse refs/notes/m)\" != \"$(git rev-parse notes_merge_partial^1)\" &&\n \t# Mention refs/notes/m, and its current and expected value in output\n \tgrep -q \"refs/notes/m\" output &&\n \tgrep -q \"$(git rev-parse refs/notes/m)\" output &&\n-\tgrep -q \"$(git rev-parse NOTES_MERGE_PARTIAL^1)\" output &&\n+\tgrep -q \"$(git rev-parse notes_merge_partial^1)\" output &&\n \t# Verify that other notes refs has not changed (w, x, y and z)\n \tverify_notes w &&\n \tverify_notes x &&\n@@ -541,8 +541,8 @@ EOF\n \n test_expect_success 'resolve situation by aborting the notes merge' '\n \tgit notes merge --abort &&\n-\t# No .git/NOTES_MERGE_* files left\n-\ttest_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&\n+\t# No .git/notes_merge_* files left\n+\ttest_might_fail ls .git/notes_merge_* >output 2>/dev/null &&\n \ttest_cmp /dev/null output &&\n \t# m has not moved (still == w)\n \ttest \"$(git rev-parse refs/notes/m)\" = \"$(git rev-parse refs/notes/w)\" &&\n@@ -563,7 +563,7 @@ test_expect_success 'switch cwd before committing notes merge' '\n \tgit notes --ref=other add -m bar HEAD &&\n \ttest_must_fail git notes merge refs/notes/other &&\n \t(\n-\t\tcd .git/NOTES_MERGE_WORKTREE &&\n+\t\tcd .git/notes_merge_worktree &&\n \t\techo \"foo\" > $(git rev-parse HEAD) &&\n \t\techo \"bar\" >> $(git rev-parse HEAD) &&\n \t\tgit notes merge --commit\ndiff --git a/t/t3311-notes-merge-fanout.sh b/t/t3311-notes-merge-fanout.sh\nindex 93516ef..eeaea62 100755\n--- a/t/t3311-notes-merge-fanout.sh\n+++ b/t/t3311-notes-merge-fanout.sh\n@@ -387,10 +387,10 @@ other notes for commit4\n EOF\n \n test_expect_success 'verify conflict entries (with no fanout)' '\n-\tls .git/NOTES_MERGE_WORKTREE >output_conflicts &&\n+\tls .git/notes_merge_worktree >output_conflicts &&\n \ttest_cmp expect_conflicts output_conflicts &&\n \t( for f in $(cat expect_conflicts); do\n-\t\ttest_cmp \"expect_conflict_$f\" \".git/NOTES_MERGE_WORKTREE/$f\" ||\n+\t\ttest_cmp \"expect_conflict_$f\" \".git/notes_merge_worktree/$f\" ||\n \t\texit 1\n \tdone ) &&\n \t# Verify that current notes tree (pre-merge) has not changed (m == w)\n@@ -417,7 +417,7 @@ other notes for commit1\n EOF\n \n test_expect_success 'resolve and finalize merge (z => w)' '\n-\tcat >.git/NOTES_MERGE_WORKTREE/$commit_sha3 <<EOF &&\n+\tcat >.git/notes_merge_worktree/$commit_sha3 <<EOF &&\n other notes for commit3\n \n appended notes for commit3\n-- \n2.0.4.315.gad8727a-twtrsrc\n"},{"id":"266705","messageId":"1437713129-19373-4-git-send-email-dturner@twopensource.com","threadId":"39929","inReplyTo":"1437713129-19373-1-git-send-email-dturner@twopensource.com","subject":"[PATCH 3/9] bisect: do not use update-ref for BISECT_HEAD","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-07-24T04:45:23Z","receivedAt":"2015-07-24T04:45:23Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Because BISECT_HEAD is a pseudoref, we shouldn't use update-ref to\nupdate it.  Instead, we simply write to and delete the file.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n git-bisect.sh | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/git-bisect.sh b/git-bisect.sh\nindex ea63223..8263555 100755\n--- a/git-bisect.sh\n+++ b/git-bisect.sh\n@@ -181,7 +181,7 @@ bisect_start() {\n \t#\n \techo \"$start_head\" >\"$GIT_DIR/BISECT_START\" && {\n \t\ttest \"z$mode\" != \"z--no-checkout\" ||\n-\t\tgit update-ref --no-deref BISECT_HEAD \"$start_head\"\n+\t\tgit rev-parse \"$start_head\" > \"$GIT_DIR/BISECT_HEAD\"\n \t} &&\n \tgit rev-parse --sq-quote \"$@\" >\"$GIT_DIR/BISECT_NAMES\" &&\n \teval \"$eval true\" &&\n@@ -425,7 +425,7 @@ bisect_clean_state() {\n \trm -f \"$GIT_DIR/BISECT_TERMS\" &&\n \t# Cleanup head-name if it got left by an old version of git-bisect\n \trm -f \"$GIT_DIR/head-name\" &&\n-\tgit update-ref -d --no-deref BISECT_HEAD &&\n+\trm -f \"$GIT_DIR/BISECT_HEAD\" &&\n \t# clean up BISECT_START last\n \trm -f \"$GIT_DIR/BISECT_START\"\n }\n-- \n2.0.4.315.gad8727a-twtrsrc\n"},{"id":"266710","messageId":"1437713129-19373-5-git-send-email-dturner@twopensource.com","threadId":"39929","inReplyTo":"1437713129-19373-1-git-send-email-dturner@twopensource.com","subject":"[PATCH 4/9] tests: treat FETCH_HEAD as a pseudoref","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-07-24T04:45:24Z","receivedAt":"2015-07-24T04:45:24Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Signed-off-by: David Turner <dturner@twopensource.com>\n---\n t/t5510-fetch.sh | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 0ba9db0..467cade 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -362,7 +362,7 @@ test_expect_success 'fetch with a non-applying branch.<name>.merge' '\n test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge [1]' '\n \tone_head=$(cd one && git rev-parse HEAD) &&\n \tthis_head=$(git rev-parse HEAD) &&\n-\tgit update-ref -d FETCH_HEAD &&\n+\trm .git/FETCH_HEAD &&\n \tgit fetch one &&\n \ttest $one_head = \"$(git rev-parse --verify FETCH_HEAD)\" &&\n \ttest $this_head = \"$(git rev-parse --verify HEAD)\"\n@@ -374,7 +374,7 @@ test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge\n \tone_ref=$(cd one && git symbolic-ref HEAD) &&\n \tgit config branch.master.remote blub &&\n \tgit config branch.master.merge \"$one_ref\" &&\n-\tgit update-ref -d FETCH_HEAD &&\n+\trm .git/FETCH_HEAD &&\n \tgit fetch one &&\n \ttest $one_head = \"$(git rev-parse --verify FETCH_HEAD)\" &&\n \ttest $this_head = \"$(git rev-parse --verify HEAD)\"\n@@ -384,7 +384,7 @@ test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge\n # the merge spec does not match the branch the remote HEAD points to\n test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge [3]' '\n \tgit config branch.master.merge \"${one_ref}_not\" &&\n-\tgit update-ref -d FETCH_HEAD &&\n+\trm .git/FETCH_HEAD &&\n \tgit fetch one &&\n \ttest $one_head = \"$(git rev-parse --verify FETCH_HEAD)\" &&\n \ttest $this_head = \"$(git rev-parse --verify HEAD)\"\n-- \n2.0.4.315.gad8727a-twtrsrc\n"},{"id":"266712","messageId":"1437713129-19373-6-git-send-email-dturner@twopensource.com","threadId":"39929","inReplyTo":"1437713129-19373-1-git-send-email-dturner@twopensource.com","subject":"[PATCH 5/9] tests: use --create-reflog option to git update-ref","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-07-24T04:45:25Z","receivedAt":"2015-07-24T04:45:25Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Instead of manually precreating reflogs by writing files, use\nupdate-ref --create-reflog to create the reflogs at the same time\nwe first create the ref.\n\nIn one case, we need an entirely empty reflog, so we do a no-op\nupdate-ref with --create-reflog and then expire the reflog.\n\nThis makes it slighly easier to test alternate ref backends.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n t/t1400-update-ref.sh       | 5 ++---\n t/t1411-reflog-show.sh      | 3 ++-\n t/t1503-rev-parse-verify.sh | 9 +++------\n 3 files changed, 7 insertions(+), 10 deletions(-)\n\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex 9d21c19..0038f28 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -174,12 +174,11 @@ test_expect_success \"(not) changed .git/$m\" \"\n '\n rm -f .git/$m\n \n-: a repository with working tree always has reflog these days...\n-: >.git/logs/refs/heads/master\n+rm .git/logs/refs/heads/master\n test_expect_success \\\n \t\"create $m (logged by touch)\" \\\n \t'GIT_COMMITTER_DATE=\"2005-05-26 23:30\" \\\n-\t git update-ref HEAD '\"$A\"' -m \"Initial Creation\" &&\n+\t git update-ref --create-reflog HEAD '\"$A\"' -m \"Initial Creation\" &&\n \t test '\"$A\"' = $(cat .git/'\"$m\"')'\n test_expect_success \\\n \t\"update $m (logged by touch)\" \\\ndiff --git a/t/t1411-reflog-show.sh b/t/t1411-reflog-show.sh\nindex 3eb4f10..ee069ac 100755\n--- a/t/t1411-reflog-show.sh\n+++ b/t/t1411-reflog-show.sh\n@@ -138,7 +138,8 @@ test_expect_success '--date magic does not override explicit @{0} syntax' '\n : >expect\n test_expect_success 'empty reflog file' '\n \tgit branch empty &&\n-\t: >.git/logs/refs/heads/empty &&\n+\tgit update-ref --create-reflog refs/heads/empty empty &&\n+\tgit reflog expire --expire=all refs/heads/empty &&\n \n \tgit log -g empty >actual &&\n \ttest_cmp expect actual\ndiff --git a/t/t1503-rev-parse-verify.sh b/t/t1503-rev-parse-verify.sh\nindex 823fe1d..ab27d0d 100755\n--- a/t/t1503-rev-parse-verify.sh\n+++ b/t/t1503-rev-parse-verify.sh\n@@ -85,8 +85,7 @@ test_expect_success 'fails silently when using -q' '\n \n test_expect_success 'fails silently when using -q with deleted reflogs' '\n \tref=$(git rev-parse HEAD) &&\n-\t: >.git/logs/refs/test &&\n-\tgit update-ref -m \"message for refs/test\" refs/test \"$ref\" &&\n+\tgit update-ref --create-reflog -m \"message for refs/test\" refs/test \"$ref\" &&\n \tgit reflog delete --updateref --rewrite refs/test@{0} &&\n \ttest_must_fail git rev-parse -q --verify refs/test@{0} >error 2>&1 &&\n \ttest_must_be_empty error\n@@ -94,16 +93,14 @@ test_expect_success 'fails silently when using -q with deleted reflogs' '\n \n test_expect_success 'fails silently when using -q with not enough reflogs' '\n \tref=$(git rev-parse HEAD) &&\n-\t: >.git/logs/refs/test2 &&\n-\tgit update-ref -m \"message for refs/test2\" refs/test2 \"$ref\" &&\n+\tgit update-ref --create-reflog -m \"message for refs/test2\" refs/test2 \"$ref\" &&\n \ttest_must_fail git rev-parse -q --verify refs/test2@{999} >error 2>&1 &&\n \ttest_must_be_empty error\n '\n \n test_expect_success 'succeeds silently with -q and reflogs that do not go far back enough in time' '\n \tref=$(git rev-parse HEAD) &&\n-\t: >.git/logs/refs/test3 &&\n-\tgit update-ref -m \"message for refs/test3\" refs/test3 \"$ref\" &&\n+\tgit update-ref --create-reflog -m \"message for refs/test3\" refs/test3 \"$ref\" &&\n \tgit rev-parse -q --verify refs/test3@{1.year.ago} >actual 2>error &&\n \ttest_must_be_empty error &&\n \techo \"$ref\" >expect &&\n-- \n2.0.4.315.gad8727a-twtrsrc\n"},{"id":"266706","messageId":"1437713129-19373-7-git-send-email-dturner@twopensource.com","threadId":"39929","inReplyTo":"1437713129-19373-1-git-send-email-dturner@twopensource.com","subject":"[PATCH 6/9] pseudorefs: create and use pseudoref update and delete functions","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-07-24T04:45:26Z","receivedAt":"2015-07-24T04:45:26Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Pseudorefs should not be updated through the ref API, because the ref\nAPI is for real refs.  Instead, use a dedicated pseudoref API.\n\nThis patch changes writes to CHERRY_PICK_HEAD, REVERT_HEAD, and ORIG_HEAD.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n builtin/merge.c |  3 +--\n builtin/reset.c |  6 ++----\n refs.c          | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-\n refs.h          |  5 +++++\n sequencer.c     | 21 ++-------------------\n 5 files changed, 64 insertions(+), 26 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 7e2541a..cea57a0 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1354,8 +1354,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\tfree(list);\n \t}\n \n-\tupdate_ref(\"updating ORIG_HEAD\", \"ORIG_HEAD\", head_commit->object.sha1,\n-\t\t   NULL, 0, UPDATE_REFS_DIE_ON_ERR);\n+\twrite_pseudoref(\"ORIG_HEAD\", head_commit->object.sha1, NULL);\n \n \tif (remoteheads && !common)\n \t\t; /* No common ancestors found. We need a real merge. */\ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex 4c08ddc..0282e78 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -252,11 +252,9 @@ static int reset_refs(const char *rev, const unsigned char *sha1)\n \t\told_orig = sha1_old_orig;\n \tif (!get_sha1(\"HEAD\", sha1_orig)) {\n \t\torig = sha1_orig;\n-\t\tset_reflog_message(&msg, \"updating ORIG_HEAD\", NULL);\n-\t\tupdate_ref(msg.buf, \"ORIG_HEAD\", orig, old_orig, 0,\n-\t\t\t   UPDATE_REFS_MSG_ON_ERR);\n+\t\twrite_pseudoref(\"ORIG_HEAD\", orig, old_orig);\n \t} else if (old_orig)\n-\t\tdelete_ref(\"ORIG_HEAD\", old_orig, 0);\n+\t\tdelete_pseudoref(\"ORIG_HEAD\", old_orig);\n \tset_reflog_message(&msg, \"updating HEAD\", rev);\n \tupdate_ref_status = update_ref(msg.buf, \"HEAD\", sha1, orig, 0,\n \t\t\t\t       UPDATE_REFS_MSG_ON_ERR);\ndiff --git a/refs.c b/refs.c\nindex d31ca42..efa84e3 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3862,7 +3862,7 @@ int is_per_worktree_ref(const char *refname)\n \treturn !strcmp(refname, \"HEAD\");\n }\n \n-static int is_pseudoref(const char *refname)\n+int is_pseudoref(const char *refname)\n {\n \tconst char *c;\n \n@@ -4544,3 +4544,56 @@ int reflog_expire(const char *refname, const unsigned char *sha1,\n \tunlock_ref(lock);\n \treturn -1;\n }\n+\n+void write_pseudoref(const char *pseudoref, const unsigned char *sha1,\n+\t\t     const unsigned char *old_sha1)\n+{\n+\tconst char *filename;\n+\tint fd;\n+\tstatic struct lock_file lock;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf, \"%s\\n\", sha1_to_hex(sha1));\n+\n+\tfilename = git_path(\"%s\", pseudoref);\n+\tfd = hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR);\n+\tif (fd < 0)\n+\t\tdie_errno(_(\"Could not open '%s' for writing\"), filename);\n+\n+\tif (old_sha1) {\n+\t\tunsigned char actual_old_sha1[20];\n+\t\tread_ref(pseudoref, actual_old_sha1);\n+\t\tif (hashcmp(actual_old_sha1, old_sha1))\n+\t\t\tdie(\"Unexpected sha1 when writing %s\", pseudoref);\n+\t}\n+\tif (write_in_full(fd, buf.buf, buf.len) != buf.len)\n+\t\tdie_errno(_(\"Could not write to '%s'\"), filename);\n+\tstrbuf_release(&buf);\n+\tcommit_lock_file(&lock);\n+}\n+\n+void delete_pseudoref(const char *pseudoref, const unsigned char *old_sha1)\n+{\n+\tstatic struct lock_file lock;\n+\tconst char *filename;\n+\n+\tfilename = git_path(\"%s\", pseudoref);\n+\n+\tif (old_sha1) {\n+\t\tint fd;\n+\t\tunsigned char actual_old_sha1[20];\n+\n+\t\tfd = hold_lock_file_for_update(&lock, filename,\n+\t\t\t\t\t       LOCK_DIE_ON_ERROR);\n+\t\tif (fd < 0)\n+\t\t\tdie_errno(_(\"Could not open '%s' for writing\"), filename);\n+\t\tread_ref(pseudoref, actual_old_sha1);\n+\t\tif (hashcmp(actual_old_sha1, old_sha1))\n+\t\t\tdie(\"Unexpected sha1 when writing %s\", pseudoref);\n+\n+\t\tunlink(pseudoref);\n+\t\trollback_lock_file(&lock);\n+\t} else {\n+\t\tunlink(pseudoref);\n+\t}\n+}\ndiff --git a/refs.h b/refs.h\nindex bd5526e..450860b 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -441,6 +441,11 @@ int update_ref(const char *msg, const char *refname,\n \t       const unsigned char *new_sha1, const unsigned char *old_sha1,\n \t       unsigned int flags, enum action_on_err onerr);\n \n+void write_pseudoref(const char *pseudoref, const unsigned char *sha1,\n+\t\t     const unsigned char *old_sha1);\n+\n+void delete_pseudoref(const char *pseudoref, const unsigned char *old_sha1);\n+\n extern int parse_hide_refs_config(const char *var, const char *value, const char *);\n \n extern int ref_is_hidden(const char *);\ndiff --git a/sequencer.c b/sequencer.c\nindex c4f4b7d..c9fc9a7 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -158,23 +158,6 @@ static void free_message(struct commit *commit, struct commit_message *msg)\n \tunuse_commit_buffer(commit, msg->message);\n }\n \n-static void write_cherry_pick_head(struct commit *commit, const char *pseudoref)\n-{\n-\tconst char *filename;\n-\tint fd;\n-\tstruct strbuf buf = STRBUF_INIT;\n-\n-\tstrbuf_addf(&buf, \"%s\\n\", sha1_to_hex(commit->object.sha1));\n-\n-\tfilename = git_path(\"%s\", pseudoref);\n-\tfd = open(filename, O_WRONLY | O_CREAT, 0666);\n-\tif (fd < 0)\n-\t\tdie_errno(_(\"Could not open '%s' for writing\"), filename);\n-\tif (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd))\n-\t\tdie_errno(_(\"Could not write to '%s'\"), filename);\n-\tstrbuf_release(&buf);\n-}\n-\n static void print_advice(int show_hint, struct replay_opts *opts)\n {\n \tchar *msg = getenv(\"GIT_CHERRY_PICK_HELP\");\n@@ -607,9 +590,9 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)\n \t * write it at all.\n \t */\n \tif (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1))\n-\t\twrite_cherry_pick_head(commit, \"CHERRY_PICK_HEAD\");\n+\t\twrite_pseudoref(\"CHERRY_PICK_HEAD\", commit->object.sha1, NULL);\n \tif (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1))\n-\t\twrite_cherry_pick_head(commit, \"REVERT_HEAD\");\n+\t\twrite_pseudoref(\"REVERT_HEAD\", commit->object.sha1, NULL);\n \n \tif (res) {\n \t\terror(opts->action == REPLAY_REVERT\n-- \n2.0.4.315.gad8727a-twtrsrc\n"},{"id":"266711","messageId":"1437713129-19373-8-git-send-email-dturner@twopensource.com","threadId":"39929","inReplyTo":"1437713129-19373-1-git-send-email-dturner@twopensource.com","subject":"[PATCH 7/9] am/rebase: update pseudorefs by writing files","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-07-24T04:45:27Z","receivedAt":"2015-07-24T04:45:27Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"git update-ref can no longer be used to update pseudorefs, so in\ngit-am and git-rebase, we just directly edit the files.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n builtin/am.c               | 6 +++---\n contrib/examples/git-am.sh | 4 ++--\n git-rebase--interactive.sh | 2 +-\n git-rebase.sh              | 2 +-\n 4 files changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 3f0fc75..ff3eeee 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1049,12 +1049,12 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,\n \tif (!get_sha1(\"HEAD\", curr_head)) {\n \t\twrite_file(am_path(state, \"abort-safety\"), 1, \"%s\", sha1_to_hex(curr_head));\n \t\tif (!state->rebasing)\n-\t\t\tupdate_ref(\"am\", \"ORIG_HEAD\", curr_head, NULL, 0,\n-\t\t\t\t\tUPDATE_REFS_DIE_ON_ERR);\n+\t\t\twrite_pseudoref(\"ORIG_HEAD\", curr_head, NULL);\n \t} else {\n \t\twrite_file(am_path(state, \"abort-safety\"), 1, \"%s\", \"\");\n \t\tif (!state->rebasing)\n-\t\t\tdelete_ref(\"ORIG_HEAD\", NULL, 0);\n+\t\t\tif (unlink(git_path(\"ORIG_HEAD\")) && errno != ENOENT)\n+\t\t\t\tdie(\"failed to unlink ORIG_HEAD\");\n \t}\n \n \t/*\ndiff --git a/contrib/examples/git-am.sh b/contrib/examples/git-am.sh\nindex a3d0c84..a01a06e 100755\n--- a/contrib/examples/git-am.sh\n+++ b/contrib/examples/git-am.sh\n@@ -614,9 +614,9 @@ Use \\\"git am --abort\\\" to remove it.\")\"\n \t\t: >\"$dotest/applying\"\n \t\tif test -n \"$HAS_HEAD\"\n \t\tthen\n-\t\t\tgit update-ref ORIG_HEAD HEAD\n+\t\t\tgit rev-parse HEAD >\"$GIT_DIR/ORIG_HEAD\"\n \t\telse\n-\t\t\tgit update-ref -d ORIG_HEAD >/dev/null 2>&1\n+\t\t\trm -f \"$GIT_DIR/ORIG_HEAD\"\n \t\tfi\n \tfi\n fi\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex f01637b..ae116a6 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -948,7 +948,7 @@ warn_lines () {\n checkout_onto () {\n \tGIT_REFLOG_ACTION=\"$GIT_REFLOG_ACTION: checkout $onto_name\"\n \toutput git checkout $onto || die_abort \"could not detach HEAD\"\n-\tgit update-ref ORIG_HEAD $orig_head\n+\techo $orig_head >\"$GIT_DIR/ORIG_HEAD\"\n }\n \n get_missing_commit_check_level () {\ndiff --git a/git-rebase.sh b/git-rebase.sh\nindex 1757404..23e2e64 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -611,7 +611,7 @@ say \"$(gettext \"First, rewinding head to replay your work on top of it...\")\"\n \n GIT_REFLOG_ACTION=\"$GIT_REFLOG_ACTION: checkout $onto_name\" \\\n \tgit checkout -q \"$onto^0\" || die \"could not detach HEAD\"\n-git update-ref ORIG_HEAD $orig_head\n+echo $orig_head >\"$GIT_DIR/ORIG_HEAD\"\n \n # If the $onto is a proper descendant of the tip of the branch, then\n # we just fast-forwarded.\n-- \n2.0.4.315.gad8727a-twtrsrc\n"},{"id":"266707","messageId":"1437713129-19373-9-git-send-email-dturner@twopensource.com","threadId":"39929","inReplyTo":"1437713129-19373-1-git-send-email-dturner@twopensource.com","subject":"[PATCH 8/9] rebase: use write_pseudoref","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-07-24T04:45:28Z","receivedAt":"2015-07-24T04:45:28Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Instead of manually writing a pseudoref (in one case) and using git\nupdate-ref (in another), use the new write_pseudoref function.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n bisect.c | 35 +++++++----------------------------\n 1 file changed, 7 insertions(+), 28 deletions(-)\n\ndiff --git a/bisect.c b/bisect.c\nindex 857cf59..3a9fd63 100644\n--- a/bisect.c\n+++ b/bisect.c\n@@ -19,7 +19,6 @@ static struct object_id *current_bad_oid;\n \n static const char *argv_checkout[] = {\"checkout\", \"-q\", NULL, \"--\", NULL};\n static const char *argv_show_branch[] = {\"show-branch\", NULL, NULL};\n-static const char *argv_update_ref[] = {\"update-ref\", \"--no-deref\", \"BISECT_HEAD\", NULL, NULL};\n \n static const char *term_bad;\n static const char *term_good;\n@@ -675,34 +674,16 @@ static int is_expected_rev(const struct object_id *oid)\n \treturn res;\n }\n \n-static void mark_expected_rev(char *bisect_rev_hex)\n-{\n-\tint len = strlen(bisect_rev_hex);\n-\tconst char *filename = git_path(\"BISECT_EXPECTED_REV\");\n-\tint fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);\n-\n-\tif (fd < 0)\n-\t\tdie_errno(\"could not create file '%s'\", filename);\n-\n-\tbisect_rev_hex[len] = '\\n';\n-\twrite_or_die(fd, bisect_rev_hex, len + 1);\n-\tbisect_rev_hex[len] = '\\0';\n-\n-\tif (close(fd) < 0)\n-\t\tdie(\"closing file %s: %s\", filename, strerror(errno));\n-}\n-\n-static int bisect_checkout(char *bisect_rev_hex, int no_checkout)\n+static int bisect_checkout(const unsigned char *bisect_rev, int no_checkout)\n {\n+\tchar bisect_rev_hex[GIT_SHA1_HEXSZ + 1];\n \n-\tmark_expected_rev(bisect_rev_hex);\n+\tmemcpy(bisect_rev_hex, sha1_to_hex(bisect_rev), GIT_SHA1_HEXSZ + 1);\n+\twrite_pseudoref(\"BISECT_EXPECTED_REV\", bisect_rev, NULL);\n \n \targv_checkout[2] = bisect_rev_hex;\n \tif (no_checkout) {\n-\t\targv_update_ref[3] = bisect_rev_hex;\n-\t\tif (run_command_v_opt(argv_update_ref, RUN_GIT_CMD))\n-\t\t\tdie(\"update-ref --no-deref HEAD failed on %s\",\n-\t\t\t    bisect_rev_hex);\n+\t\twrite_pseudoref(\"BISECT_HEAD\", bisect_rev, NULL);\n \t} else {\n \t\tint res;\n \t\tres = run_command_v_opt(argv_checkout, RUN_GIT_CMD);\n@@ -804,7 +785,7 @@ static void check_merge_bases(int no_checkout)\n \t\t\thandle_skipped_merge_base(mb);\n \t\t} else {\n \t\t\tprintf(\"Bisecting: a merge base must be tested\\n\");\n-\t\t\texit(bisect_checkout(sha1_to_hex(mb), no_checkout));\n+\t\t\texit(bisect_checkout(mb, no_checkout));\n \t\t}\n \t}\n \n@@ -948,7 +929,6 @@ int bisect_next_all(const char *prefix, int no_checkout)\n \tstruct commit_list *tried;\n \tint reaches = 0, all = 0, nr, steps;\n \tconst unsigned char *bisect_rev;\n-\tchar bisect_rev_hex[GIT_SHA1_HEXSZ + 1];\n \n \tread_bisect_terms(&term_bad, &term_good);\n \tif (read_bisect_refs())\n@@ -986,7 +966,6 @@ int bisect_next_all(const char *prefix, int no_checkout)\n \t}\n \n \tbisect_rev = revs.commits->item->object.sha1;\n-\tmemcpy(bisect_rev_hex, sha1_to_hex(bisect_rev), GIT_SHA1_HEXSZ + 1);\n \n \tif (!hashcmp(bisect_rev, current_bad_oid->hash)) {\n \t\texit_if_skipped_commits(tried, current_bad_oid);\n@@ -1003,7 +982,7 @@ int bisect_next_all(const char *prefix, int no_checkout)\n \t       \"(roughly %d step%s)\\n\", nr, (nr == 1 ? \"\" : \"s\"),\n \t       steps, (steps == 1 ? \"\" : \"s\"));\n \n-\treturn bisect_checkout(bisect_rev_hex, no_checkout);\n+\treturn bisect_checkout(bisect_rev, no_checkout);\n }\n \n static inline int log2i(int n)\n-- \n2.0.4.315.gad8727a-twtrsrc\n"},{"id":"266708","messageId":"1437713129-19373-10-git-send-email-dturner@twopensource.com","threadId":"39929","inReplyTo":"1437713129-19373-1-git-send-email-dturner@twopensource.com","subject":"[PATCH 9/9] refs: forbid pseudoref updates through ref update API","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-07-24T04:45:29Z","receivedAt":"2015-07-24T04:45:29Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"It is now forbidden to update pseudorefs through the ref update\nframework.\n\nBecause of the pseudoref update rules change, test cases that used git\nupdate-ref on a pseudoref now operate on plain refs.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n bisect.c              |  2 +-\n refs.c                |  2 ++\n t/t1400-update-ref.sh | 24 ++++++++++++------------\n 3 files changed, 15 insertions(+), 13 deletions(-)\n\ndiff --git a/bisect.c b/bisect.c\nindex 3a9fd63..47cc778 100644\n--- a/bisect.c\n+++ b/bisect.c\n@@ -969,7 +969,7 @@ int bisect_next_all(const char *prefix, int no_checkout)\n \n \tif (!hashcmp(bisect_rev, current_bad_oid->hash)) {\n \t\texit_if_skipped_commits(tried, current_bad_oid);\n-\t\tprintf(\"%s is the first %s commit\\n\", bisect_rev_hex,\n+\t\tprintf(\"%s is the first %s commit\\n\", sha1_to_hex(bisect_rev),\n \t\t\tterm_bad);\n \t\tshow_diff_tree(prefix, revs.commits->item);\n \t\t/* This means the bisection process succeeded. */\ndiff --git a/refs.c b/refs.c\nindex efa84e3..f8dc877 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3902,6 +3902,8 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \tstruct ref_update *update;\n \n \tassert(err);\n+\tif (is_pseudoref(refname))\n+\t\treturn error(_(\"BUG: Cannot update pseudorefs (all-caps files in .git) through the refs API\"));\n \n \tif (transaction->state != REF_TRANSACTION_OPEN)\n \t\tdie(\"BUG: update called for transaction that is not open\");\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex 0038f28..b05657c 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -608,13 +608,13 @@ test_expect_success 'stdin delete ref fails with zero old value' '\n '\n \n test_expect_success 'stdin update symref works option no-deref' '\n-\tgit symbolic-ref TESTSYMREF $b &&\n+\tgit symbolic-ref refs/TESTSYMREF $b &&\n \tcat >stdin <<-EOF &&\n \toption no-deref\n-\tupdate TESTSYMREF $a $b\n+\tupdate refs/TESTSYMREF $a $b\n \tEOF\n \tgit update-ref --stdin <stdin &&\n-\tgit rev-parse TESTSYMREF >expect &&\n+\tgit rev-parse refs/TESTSYMREF >expect &&\n \tgit rev-parse $a >actual &&\n \ttest_cmp expect actual &&\n \tgit rev-parse $m~1 >expect &&\n@@ -623,13 +623,13 @@ test_expect_success 'stdin update symref works option no-deref' '\n '\n \n test_expect_success 'stdin delete symref works option no-deref' '\n-\tgit symbolic-ref TESTSYMREF $b &&\n+\tgit symbolic-ref refs/TESTSYMREF $b &&\n \tcat >stdin <<-EOF &&\n \toption no-deref\n-\tdelete TESTSYMREF $b\n+\tdelete refs/TESTSYMREF $b\n \tEOF\n \tgit update-ref --stdin <stdin &&\n-\ttest_must_fail git rev-parse --verify -q TESTSYMREF &&\n+\ttest_must_fail git rev-parse --verify -q refs/TESTSYMREF &&\n \tgit rev-parse $m~1 >expect &&\n \tgit rev-parse $b >actual &&\n \ttest_cmp expect actual\n@@ -983,10 +983,10 @@ test_expect_success 'stdin -z delete ref fails with zero old value' '\n '\n \n test_expect_success 'stdin -z update symref works option no-deref' '\n-\tgit symbolic-ref TESTSYMREF $b &&\n-\tprintf $F \"option no-deref\" \"update TESTSYMREF\" \"$a\" \"$b\" >stdin &&\n+\tgit symbolic-ref refs/TESTSYMREF $b &&\n+\tprintf $F \"option no-deref\" \"update refs/TESTSYMREF\" \"$a\" \"$b\" >stdin &&\n \tgit update-ref -z --stdin <stdin &&\n-\tgit rev-parse TESTSYMREF >expect &&\n+\tgit rev-parse refs/TESTSYMREF >expect &&\n \tgit rev-parse $a >actual &&\n \ttest_cmp expect actual &&\n \tgit rev-parse $m~1 >expect &&\n@@ -995,10 +995,10 @@ test_expect_success 'stdin -z update symref works option no-deref' '\n '\n \n test_expect_success 'stdin -z delete symref works option no-deref' '\n-\tgit symbolic-ref TESTSYMREF $b &&\n-\tprintf $F \"option no-deref\" \"delete TESTSYMREF\" \"$b\" >stdin &&\n+\tgit symbolic-ref refs/TESTSYMREF $b &&\n+\tprintf $F \"option no-deref\" \"delete refs/TESTSYMREF\" \"$b\" >stdin &&\n \tgit update-ref -z --stdin <stdin &&\n-\ttest_must_fail git rev-parse --verify -q TESTSYMREF &&\n+\ttest_must_fail git rev-parse --verify -q refs/TESTSYMREF &&\n \tgit rev-parse $m~1 >expect &&\n \tgit rev-parse $b >actual &&\n \ttest_cmp expect actual\n-- \n2.0.4.315.gad8727a-twtrsrc\n"},{"id":"266718","messageId":"CAPig+cRkY3uQJ4MyjwHRFb3WtN5JKiO5HHF6B8T=nWfmUSW3ng@mail.gmail.com","threadId":"39929","inReplyTo":"1437713129-19373-2-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH 1/9] refs: Introduce pseudoref and per-worktree ref concepts","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2015-07-24T07:34:46Z","receivedAt":"2015-07-24T07:34:46Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, Jul 24, 2015 at 12:45 AM, David Turner <dturner@twopensource.com> wrote:\n> Add glossary entries for both concepts.\n>\n> Pseudorefs and per-worktree refs do not yet have special handling,\n> because the files refs backend already handles them correctly.  Later,\n> we will make the LMDB backend call out to the files backend to handle\n> per-worktree refs.\n>\n> Signed-off-by: David Turner <dturner@twopensource.com>\n> ---\n> diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt\n> index ab18f4b..d04819e 100644\n> --- a/Documentation/glossary-content.txt\n> +++ b/Documentation/glossary-content.txt\n> @@ -411,6 +411,23 @@ exclude;;\n>         core Git. Porcelains expose more of a <<def_SCM,SCM>>\n>         interface than the <<def_plumbing,plumbing>>.\n>\n> +[[def_per_worktree_ref]]per-worktree ref::\n> +       Refs that are per-<<def_worktree,worktree>>, rather than\n> +       global.  This is presently only <<def_HEAD,HEAD>>, but might\n> +       later include other unsuual refs.\n> +\n> +[[def_pseudoref]]pseudoref::\n> +       Files under `$GIT_DIR` whose names are all-caps, and that\n> +       contain a line consisting of a <<def_sha1,SHA-1>> followed by\n> +       a newline, and optionally some additional data.  `MERGE_HEAD`\n> +       and `CHERRY_PICK_HEAD` are examples.  Unlike\n> +       <<def_per_worktree_ref,per-worktree refs>>, these files cannot\n> +       be symbolic refs, and never not have reflogs.  They also\n\ns/never not/never/\n\n> +       cannot be updated through the normal ref update machinery.\n> +       Instead, they are updated by directly writing to the files.\n> +       However, they can be read as if they were refs, so `git\n> +       rev-parse MERGE_HEAD` will work.\n> +\n>  [[def_pull]]pull::\n>         Pulling a <<def_branch,branch>> means to <<def_fetch,fetch>> it and\n>         <<def_merge,merge>> it.  See also linkgit:git-pull[1].\n"},{"id":"266748","messageId":"xmqqio99mkfp.fsf@gitster.dls.corp.google.com","threadId":"39929","inReplyTo":"1437713129-19373-2-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH 1/9] refs: Introduce pseudoref and per-worktree ref concepts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-07-24T19:20:42Z","receivedAt":"2015-07-24T19:20:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"David Turner <dturner@twopensource.com> writes:\n\n> +[[def_pseudoref]]pseudoref::\n> +\tFiles under `$GIT_DIR` whose names are all-caps, and that\n> +\tcontain a line consisting of a <<def_sha1,SHA-1>> followed by\n> +\ta newline, and optionally some additional data.  `MERGE_HEAD`\n> +\tand `CHERRY_PICK_HEAD` are examples.  Unlike...\n\nI wonder if you meant to include FETCH_HEAD in this category (I am\nnot complaining about not having it listed as an example).  If you\ndid mean to include FETCH_HEAD, then \"followed-by a newline\" must\nbe rethought.\n\nDocumentation pedant might say that the above definition would throw\nHEAD into this category.  s/all-caps,/all-caps (except \"HEAD\"),/ or\nsomething like that may be needed to prevent them from making useless\nnoise.\n\n> diff --git a/refs.c b/refs.c\n> index 0b96ece..d31ca42 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -3857,6 +3857,29 @@ void ref_transaction_free(struct ref_transaction *transaction)\n>  \tfree(transaction);\n>  }\n>  \n> +int is_per_worktree_ref(const char *refname)\n> +{\n> +\treturn !strcmp(refname, \"HEAD\");\n> +}\n> +\n> +static int is_pseudoref(const char *refname)\n> +{\n> +\tconst char *c;\n> +\n> +\tif (strchr(refname, '/'))\n> +\t\treturn 0;\n> +\n> +\tif (is_per_worktree_ref(refname))\n> +\t\treturn 0;\n> +\n> +\tfor (c = refname; *c; ++c) {\n> +\t\tif (!isupper(*c) && *c != '-' && *c != '_')\n> +\t\t\treturn 0;\n> +\t}\n> +\n> +\treturn 1;\n> +}\n> +\n>  static struct ref_update *add_update(struct ref_transaction *transaction,\n>  \t\t\t\t     const char *refname)\n>  {\n> diff --git a/refs.h b/refs.h\n> index e4e46c3..bd5526e 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -445,6 +445,8 @@ extern int parse_hide_refs_config(const char *var, const char *value, const char\n>  \n>  extern int ref_is_hidden(const char *);\n>  \n> +int is_per_worktree_ref(const char *refname);\n> +\n>  enum expire_reflog_flags {\n>  \tEXPIRE_REFLOGS_DRY_RUN = 1 << 0,\n>  \tEXPIRE_REFLOGS_UPDATE_REF = 1 << 1,\n"},{"id":"266749","messageId":"xmqqbnf1mk72.fsf@gitster.dls.corp.google.com","threadId":"39929","inReplyTo":"1437713129-19373-7-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH 6/9] pseudorefs: create and use pseudoref update and delete functions","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-07-24T19:25:53Z","receivedAt":"2015-07-24T19:25:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"David Turner <dturner@twopensource.com> writes:\n\n> Pseudorefs should not be updated through the ref API, because the ref\n> API is for real refs.  Instead, use a dedicated pseudoref API.\n>\n> This patch changes writes to CHERRY_PICK_HEAD, REVERT_HEAD, and ORIG_HEAD.\n\nThis feels somewhat backwards, and it makes me wonder if it is a\nbetter approach to teach update_ref() about the naming rules, so\nthat the callers do not have to say the same thing twice \"This is\nnot a ref and I am giving all-caps name, by the way I am also not\ncalling update_ref() because that is only for real refs\".\n"},{"id":"266752","messageId":"344748CA1CA54D55A9F245FE2DBD23DC@PhilipOakley","threadId":"39929","inReplyTo":"1437713129-19373-2-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH 1/9] refs: Introduce pseudoref and per-worktree ref concepts","fromName":"Philip Oakley","fromEmail":"philipoakley@iee.org","sentAt":"2015-07-24T19:52:49Z","receivedAt":"2015-07-24T19:52:49Z","isPatch":true,"sender":{"key":"philipoakley@iee.email","avatar":"https://avatars.githubusercontent.com/u/914343?v=4"},"body":"From: \"David Turner\" <dturner@twopensource.com>\n> Add glossary entries for both concepts.\n>\n> Pseudorefs and per-worktree refs do not yet have special handling,\n> because the files refs backend already handles them correctly.  Later,\n> we will make the LMDB backend call out to the files backend to handle\n> per-worktree refs.\n>\n> Signed-off-by: David Turner <dturner@twopensource.com>\n> ---\n> Documentation/glossary-content.txt | 17 +++++++++++++++++\n> refs.c                             | 23 +++++++++++++++++++++++\n> refs.h                             |  2 ++\n> 3 files changed, 42 insertions(+)\n>\n> diff --git a/Documentation/glossary-content.txt \n> b/Documentation/glossary-content.txt\n> index ab18f4b..d04819e 100644\n> --- a/Documentation/glossary-content.txt\n> +++ b/Documentation/glossary-content.txt\n> @@ -411,6 +411,23 @@ exclude;;\n>  core Git. Porcelains expose more of a <<def_SCM,SCM>>\n>  interface than the <<def_plumbing,plumbing>>.\n>\n> +[[def_per_worktree_ref]]per-worktree ref::\n> + Refs that are per-<<def_worktree,worktree>>, rather than\n> + global.  This is presently only <<def_HEAD,HEAD>>, but might\n> + later include other unsuual refs.\ns/unsuual/unusual/\n\n> +\n> +[[def_pseudoref]]pseudoref::\n> + Files under `$GIT_DIR` whose names are all-caps, and that\n\ns/and that/excluding 'HEAD', which is special./ ?\n\n> + contain a line consisting of a <<def_sha1,SHA-1>> followed by\n\ns/contain/Pseudorefs typically contain/ perhaps?\n\n> + a newline, and optionally some additional data.  `MERGE_HEAD`\n> + and `CHERRY_PICK_HEAD` are examples.  Unlike\n> + <<def_per_worktree_ref,per-worktree refs>>, these files cannot\n> + be symbolic refs, and never not have reflogs.  They also\n> + cannot be updated through the normal ref update machinery.\n> + Instead, they are updated by directly writing to the files.\n> + However, they can be read as if they were refs, so `git\n> + rev-parse MERGE_HEAD` will work.\n> +\n> [[def_pull]]pull::\n>  Pulling a <<def_branch,branch>> means to <<def_fetch,fetch>> it and\n>  <<def_merge,merge>> it.  See also linkgit:git-pull[1].\n[...]\n\nThe key points I'd picked out were:\n\n*    ALL_CAPS names\n\n*    acts like a ref <<>>, but separate from refs\n\n*    HEAD is special and not a pseudoref (i.e. see <<HEAD>>).\n\n*    CHERRY_PICK_HEAD and REVERT_HEAD are examples.\n\n*    three way split: refs/; pseudorefs; HEAD.\n\n*    one-level ALL_CAPS names are per worktree\n\n*    is a file in $GIT_DIR/ that always contains at least a SHA1.\n\n--\nPhilip \n"},{"id":"266754","messageId":"1437768818.1141.14.camel@twopensource.com","threadId":"39929","inReplyTo":"xmqqio99mkfp.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH 1/9] refs: Introduce pseudoref and per-worktree ref concepts","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-07-24T20:13:38Z","receivedAt":"2015-07-24T20:13:38Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"On Fri, 2015-07-24 at 12:20 -0700, Junio C Hamano wrote:\n> David Turner <dturner@twopensource.com> writes:\n> \n> > +[[def_pseudoref]]pseudoref::\n> > +\tFiles under `$GIT_DIR` whose names are all-caps, and that\n> > +\tcontain a line consisting of a <<def_sha1,SHA-1>> followed by\n> > +\ta newline, and optionally some additional data.  `MERGE_HEAD`\n> > +\tand `CHERRY_PICK_HEAD` are examples.  Unlike...\n> \n> I wonder if you meant to include FETCH_HEAD in this category (I am\n> not complaining about not having it listed as an example).  If you\n> did mean to include FETCH_HEAD, then \"followed-by a newline\" must\n> be rethought.\n\nI guess \"followed by whitespace\"?\n\n> Documentation pedant might say that the above definition would throw\n> HEAD into this category.  s/all-caps,/all-caps (except \"HEAD\"),/ or\n> something like that may be needed to prevent them from making useless\n> noise.\n\nI meant \"and that contain a line consisting of a SHA\" to exclude HEAD,\nbut I need to add a \"always\" in there, since detached HEAD would\notherwise apply.  That is, it is an additional condition on what a\npseudoref is.\n\nSomething like the following?\n\n\tPseudorefs are a class of files under `$GIT_DIR` which behave\n\tlike refs for the purposes of rev-parse, but which are treated\n\tspecially by git.  Psuedorefs both have names are all-caps,\n\tand always start with a line consisting of a\n\t<<def_sha1,SHA-1>> followed by whitespace.  So, HEAD is not a\n\tpseudoref, because it is sometimes a symbolic ref.  They might\n\toptionally some additional data.  `MERGE_HEAD` and\n\t`CHERRY_PICK_HEAD` are examples.  Unlike\n\t<<def_per_worktree_ref,per-worktree refs>>, these files cannot\n\tbe symbolic refs, and never have reflogs.  They also cannot be\n\tupdated through the normal ref update machinery.  Instead,\n\tthey are updated by directly writing to the files.  However,\n\tthey can be read as if they were refs, so `git rev-parse\n\tMERGE_HEAD` will work.\n"},{"id":"266755","messageId":"1437768881.1141.15.camel@twopensource.com","threadId":"39929","inReplyTo":"344748CA1CA54D55A9F245FE2DBD23DC@PhilipOakley","subject":"Re: [PATCH 1/9] refs: Introduce pseudoref and per-worktree ref concepts","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-07-24T20:14:41Z","receivedAt":"2015-07-24T20:14:41Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"On Fri, 2015-07-24 at 20:52 +0100, Philip Oakley wrote:\n> From: \"David Turner\" <dturner@twopensource.com>\n> > Add glossary entries for both concepts.\n> >\n> > Pseudorefs and per-worktree refs do not yet have special handling,\n> > because the files refs backend already handles them correctly.  Later,\n> > we will make the LMDB backend call out to the files backend to handle\n> > per-worktree refs.\n> >\n> > Signed-off-by: David Turner <dturner@twopensource.com>\n> > ---\n> > Documentation/glossary-content.txt | 17 +++++++++++++++++\n> > refs.c                             | 23 +++++++++++++++++++++++\n> > refs.h                             |  2 ++\n> > 3 files changed, 42 insertions(+)\n> >\n> > diff --git a/Documentation/glossary-content.txt \n> > b/Documentation/glossary-content.txt\n> > index ab18f4b..d04819e 100644\n> > --- a/Documentation/glossary-content.txt\n> > +++ b/Documentation/glossary-content.txt\n> > @@ -411,6 +411,23 @@ exclude;;\n> >  core Git. Porcelains expose more of a <<def_SCM,SCM>>\n> >  interface than the <<def_plumbing,plumbing>>.\n> >\n> > +[[def_per_worktree_ref]]per-worktree ref::\n> > + Refs that are per-<<def_worktree,worktree>>, rather than\n> > + global.  This is presently only <<def_HEAD,HEAD>>, but might\n> > + later include other unsuual refs.\n> s/unsuual/unusual/\n\nThanks.\n\n> > +\n> > +[[def_pseudoref]]pseudoref::\n> > + Files under `$GIT_DIR` whose names are all-caps, and that\n> \n> s/and that/excluding 'HEAD', which is special./ ?\n> \n> > + contain a line consisting of a <<def_sha1,SHA-1>> followed by\n> \n> s/contain/Pseudorefs typically contain/ perhaps?\n\nSee my reply to Junio on these; that \"contain a line\" thing was intended\nto be an additional condition on what constitutes a pseudoref.\n"},{"id":"266756","messageId":"1437769035.1141.16.camel@twopensource.com","threadId":"39929","inReplyTo":"xmqqbnf1mk72.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH 6/9] pseudorefs: create and use pseudoref update and delete functions","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-07-24T20:17:15Z","receivedAt":"2015-07-24T20:17:15Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"On Fri, 2015-07-24 at 12:25 -0700, Junio C Hamano wrote:\n> David Turner <dturner@twopensource.com> writes:\n> \n> > Pseudorefs should not be updated through the ref API, because the ref\n> > API is for real refs.  Instead, use a dedicated pseudoref API.\n> >\n> > This patch changes writes to CHERRY_PICK_HEAD, REVERT_HEAD, and ORIG_HEAD.\n> \n> This feels somewhat backwards, and it makes me wonder if it is a\n> better approach to teach update_ref() about the naming rules, so\n> that the callers do not have to say the same thing twice \"This is\n> not a ref and I am giving all-caps name, by the way I am also not\n> calling update_ref() because that is only for real refs\".\n\nDo you mean teach update_ref to call write_pseudoref for pseudorefs and\ncall the normal codepath for regular refs?  I considered this, but I was\nworried about ref transactions.  It would be odd if\nref_transaction_update worked differently than update_ref.  And I didn't\nwant to deal with making pseudoref updates transactional, because then\nalternate backends would have to maintain multiple transactions, which\nturned out to be a giant hassle.\n\nBut maybe it's OK if update_ref operates differently than\nref_transaction_update?  (and same for\ndelete_ref/ref_transaction_delete).  After all, people who call\nupdate_ref don't really care about the transactional semantics.\n"},{"id":"266758","messageId":"xmqqegjxl1zl.fsf@gitster.dls.corp.google.com","threadId":"39929","inReplyTo":"1437768818.1141.14.camel@twopensource.com","subject":"Re: [PATCH 1/9] refs: Introduce pseudoref and per-worktree ref concepts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-07-24T20:44:30Z","receivedAt":"2015-07-24T20:44:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"David Turner <dturner@twopensource.com> writes:\n\n> I meant \"and that contain a line consisting of a SHA\" to exclude HEAD,\n> but I need to add a \"always\" in there, since detached HEAD would\n> otherwise apply.\n\nI do not think \"always\" is as a good escape hatch as an explicit\nexclusion of \"HEAD\".  The latter is more direct and to the point.\n"},{"id":"266759","messageId":"xmqqa8ull1ki.fsf@gitster.dls.corp.google.com","threadId":"39929","inReplyTo":"1437769035.1141.16.camel@twopensource.com","subject":"Re: [PATCH 6/9] pseudorefs: create and use pseudoref update and delete functions","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-07-24T20:53:33Z","receivedAt":"2015-07-24T20:53:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"David Turner <dturner@twopensource.com> writes:\n\n> On Fri, 2015-07-24 at 12:25 -0700, Junio C Hamano wrote:\n>> David Turner <dturner@twopensource.com> writes:\n>> \n>> > Pseudorefs should not be updated through the ref API, because the ref\n>> > API is for real refs.  Instead, use a dedicated pseudoref API.\n>> >\n>> > This patch changes writes to CHERRY_PICK_HEAD, REVERT_HEAD, and ORIG_HEAD.\n>> \n>> This feels somewhat backwards, and it makes me wonder if it is a\n>> better approach to teach update_ref() about the naming rules, so\n>> that the callers do not have to say the same thing twice \"This is\n>> not a ref and I am giving all-caps name, by the way I am also not\n>> calling update_ref() because that is only for real refs\".\n>\n> Do you mean teach update_ref to call write_pseudoref for pseudorefs and\n> call the normal codepath for regular refs?\n\nWhile reviewing this series, I mostly am viewing them from the point\nof view of a user of the ref API.\n\nYou may name that \"always delegate to filesystem backend\" helper\nfunction \"write_pseudoref()\" or whatever name we (i.e. the ref API\nimplementors) find easy to remember and understand, and as long as\nit is static within refs.c (or refs-be-file.c), a user of the ref\nAPI does not have to know.  He does not have to care (ideally he\nshouldn't have to be able to call it directly, but that is merely a\nsafety against bugs).\n\n> I considered this, but I was worried about ref transactions.\n\nConsidering the nature of these FOO_HEADs, I think it is perfectly\nfine to declare that handling of them _must_ happen within a single\ntransaction that does not involve regular refs, or that handling of\nthem _must_ happen outside transactions.  I do not think any of the\nexisting creation, update, or deletion of these FOO_HEADs happen\ninside any transaction anyway.\n"},{"id":"267297","messageId":"55BE9E25.6050902@alum.mit.edu","threadId":"39929","inReplyTo":"1437713129-19373-1-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH/RFC 0/9] Pseudorefs","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-08-02T22:48:05Z","receivedAt":"2015-08-02T22:48:05Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 07/24/2015 06:45 AM, David Turner wrote:\n> This series is another chunk of the pluggable refs backend work.  The\n> major reason it is listed as \"PATCH/RFC\" is beacuse it breaks\n> t9300-fast-import.sh, because fast-import wants to create a ref called\n> TEMP_TAG, which would now be a pseudoref.  The commit that introduces\n> this test says that cvs2svn creates a tag called TAG_FIXUP \"as a branch\n> name for temporary work needed to cleanup the tree prior to creating\n> an annotated tag object.\"\n> \n> It appears that cvs2svn still does this.  So I'm not quite sure what to\n> do about this particular case.\n> \n> As we discussed earlier, the motivation for this series is that refs\n> backends other than the files-based backend need to treat per-worktree\n> refs (HEAD) and pseudorefs (FETCH_HEAD) differently from other refs;\n> other refs are per-repo rather than per-worktree.\n\nSorry, I missed this email while on vacation.\n\ncvs2git needs to create a temporary reference for cobbling together\ntagged commits. Originally I was going to use the name `TAG_FIXUP`, as\nsuggested by git-fast-import(1) [1]. But that functionality turned out\nto be buggy in `git fast-import` at the time. So I reported the bug but\nchanged cvs2git to use `refs/heads/TAG.FIXUP` [2]. The bug in `git\nfast-import` was fixed, including the test case that you found, but I\nnever changed cvs2git back to using the originally-planned name. The\nfact that the test case uses a name different than `TAG_FIXUP` probably\nmeans that Shawn thought that any references named similarly should be\nallowed.\n\nSummary: support for a reference named `TAG_FIXUP` is not needed by\ncvs2git. However, its use is a documented recommendation of `git\nfast-import`, so it is quite possible that other importers are using\nthis name.\n\nI expect that such importers would slightly prefer that this reference\nbe worktree-specific, but I doubt that anybody really cares that much.\nIt seems unlikely that people will run `git fast-import` in multiple\nworktrees simultaneously.\n\nMichael\n\n[1] http://git-scm.com/docs/git-fast-import#_use_tag_fixup_branches\n[2]\nhttps://github.com/mhagger/cvs2svn/blob/master/cvs2svn_lib/git_output_option.py#L502-L509\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\n"}]}