{"thread":{"id":"64163","subject":"[PATCH 0/2] doc: git-push: clarify section","startedAt":"2025-09-17T21:33:38Z","lastAt":"2025-09-25T15:59:55Z","messageCount":15,"participants":["Julia Evans via GitGitGadget","Junio C Hamano","Julia Evans","brian m. carlson","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"526633","messageId":"pull.1973.git.1758144815.gitgitgadget@gmail.com","threadId":"64163","inReplyTo":null,"subject":"[PATCH 0/2] doc: git-push: clarify section","fromName":"Julia Evans via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-09-17T21:33:33Z","receivedAt":"2025-09-17T21:33:38Z","isPatch":true,"sender":{"key":"julia@jvns.ca","avatar":"https://avatars.githubusercontent.com/u/817739?v=4"},"body":"This is a continuation of the changes to git push, from\nhttps://lore.kernel.org/git/pull.1964.git.1756240823.gitgitgadget@gmail.com/\n. These changes to the refspec section got kind of big so I'm moving them\ninto a separate topic.\n\nSince the last review, the main change is to move the rules for pushing out\nof the section and into their own section (\"PUSH RULES\") so that it can be\neasily referenced from other places in the man page.\n\nI don't love the nested list in PUSH RULES but the sentence starting with\n\"If the source is a tag or commit object...\" is really a tough one to read,\nit's not going to be relevant to the vast majority of people, and I think\nkeeping it contained inside a bullet point will make it much easier to skip\nover to get to later information which is more likely to be relevant to\nfolks.\n\nOther changes:\n\n * removed \"+:<dst> is optional.\", from Junio's review\n * kept \"+ is optional and does the same thing as --force\", since now the\n   push rules are in their own section.\n * fixed the fully expanded refspec form (main:refs/heads/main =>\n   refs/heads/main:refs/heads/main)\n * switched from a numbered list to an unordered list, from Junio's review.\n   I think the numbered list looks a lot nicer in the terminal output, but\n   it's true that there isn't any order. I briefly attempted to understand\n   how AsciiDoc's nroff (?) generation works to see if it's possible to make\n   unordered lists indent with fewer spaces (2 instead of 4) but I was left\n   feeling that nroff/troff/etc are not for mere mortals like me to\n   understand.\n * made it clear that \"tag v1.0\" is not really a refspec, from Junio's\n   review\n\nJulia Evans (2):\n  doc: git-push: create PUSH RULES section\n  doc: git-push: rewrite refspec specification\n\n Documentation/git-push.adoc | 198 +++++++++++++++++++-----------------\n 1 file changed, 102 insertions(+), 96 deletions(-)\n\n\nbase-commit: c44beea485f0f2feaf460e2ac87fdd5608d63cf0\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1973%2Fjvns%2Fclarify-refspec-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1973/jvns/clarify-refspec-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/1973\n-- \ngitgitgadget\n"},{"id":"526634","messageId":"2f2dc22c47530445bce50f1bdef9630b046677bd.1758144815.git.gitgitgadget@gmail.com","threadId":"64163","inReplyTo":"pull.1973.git.1758144815.gitgitgadget@gmail.com","subject":"[PATCH 1/2] doc: git-push: create PUSH RULES section","fromName":"Julia Evans via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-09-17T21:33:34Z","receivedAt":"2025-09-17T21:33:41Z","isPatch":true,"sender":{"key":"julia@jvns.ca","avatar":"https://avatars.githubusercontent.com/u/817739?v=4"},"body":"From: Julia Evans <julia@jvns.ca>\n\nRight now the rules for when a `git push` is allowed are buried at the\nbottom of the description of `<refspec>`. Put them in their own section\nso that we can reference them from `--force` and give some context for\nwhy they exist.\n\nHaving the \"PUSH RULES\" section also lets us be a little bit more\nspecific with the rule in `--force`: we can just focus on the rule\nfor pushing for a branch (which is likely the one that's most relevant)\nand leave the details about what happens when you push to a tag or a ref\nthat isn't a branch to the later section.\n\nSigned-off-by: Julia Evans <julia@jvns.ca>\n---\n Documentation/git-push.adoc | 93 ++++++++++++++++++-------------------\n 1 file changed, 44 insertions(+), 49 deletions(-)\n\ndiff --git a/Documentation/git-push.adoc b/Documentation/git-push.adoc\nindex d1978650d6..193016e291 100644\n--- a/Documentation/git-push.adoc\n+++ b/Documentation/git-push.adoc\n@@ -91,48 +91,6 @@ is ambiguous.\n   configuration (see linkgit:git-config[1]) suggest what refs/\n   namespace you may have wanted to push to.\n \n---\n-+\n-The object referenced by <src> is used to update the <dst> reference\n-on the remote side. Whether this is allowed depends on where in\n-`refs/*` the <dst> reference lives as described in detail below, in\n-those sections \"update\" means any modifications except deletes, which\n-as noted after the next few sections are treated differently.\n-+\n-The `refs/heads/*` namespace will only accept commit objects, and\n-updates only if they can be fast-forwarded.\n-+\n-The `refs/tags/*` namespace will accept any kind of object (as\n-commits, trees and blobs can be tagged), and any updates to them will\n-be rejected.\n-+\n-It's possible to push any type of object to any namespace outside of\n-`refs/{tags,heads}/*`. In the case of tags and commits, these will be\n-treated as if they were the commits inside `refs/heads/*` for the\n-purposes of whether the update is allowed.\n-+\n-I.e. a fast-forward of commits and tags outside `refs/{tags,heads}/*`\n-is allowed, even in cases where what's being fast-forwarded is not a\n-commit, but a tag object which happens to point to a new commit which\n-is a fast-forward of the commit the last tag (or commit) it's\n-replacing. Replacing a tag with an entirely different tag is also\n-allowed, if it points to the same commit, as well as pushing a peeled\n-tag, i.e. pushing the commit that existing tag object points to, or a\n-new tag object which an existing commit points to.\n-+\n-Tree and blob objects outside of `refs/{tags,heads}/*` will be treated\n-the same way as if they were inside `refs/tags/*`, any update of them\n-will be rejected.\n-+\n-All of the rules described above about what's not allowed as an update\n-can be overridden by adding an the optional leading `+` to a refspec\n-(or using `--force` command line option). The only exception to this\n-is that no amount of forcing will make the `refs/heads/*` namespace\n-accept a non-commit object. Hooks and configuration can also override\n-or amend these rules, see e.g. `receive.denyNonFastForwards` in\n-linkgit:git-config[1] and `pre-receive` and `update` in\n-linkgit:githooks[5].\n-+\n Pushing an empty <src> allows you to delete the <dst> ref from the\n remote repository. Deletions are always accepted without a leading `+`\n in the refspec (or `--force`), except when forbidden by configuration\n@@ -145,6 +103,7 @@ the local side, the remote side is updated if a branch of the same name\n already exists on the remote side.\n +\n `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.\n+Not all updates are allowed: see PUSH RULES below for the details.\n \n --all::\n --branches::\n@@ -332,14 +291,12 @@ allowing a forced update.\n \n -f::\n --force::\n-\tUsually, the command refuses to update a remote ref that is\n-\tnot an ancestor of the local ref used to overwrite it.\n-\tAlso, when `--force-with-lease` option is used, the command refuses\n-\tto update a remote ref whose current value does not match\n-\twhat is expected.\n+\tUsually, `git push` will refuse to update a branch that is not an\n+\tancestor of the local branch or commit being pushed.\n +\n-This flag disables these checks, and can cause the remote repository\n-to lose commits; use it with care.\n+This flag disables that check, the other safety checks in PUSH RULES\n+below, and the checks in --force-with-lease. It can cause the remote\n+repository to lose commits; use it with care.\n +\n Note that `--force` applies to all the refs that are pushed, hence\n using it with `push.default` set to `matching` or with multiple push\n@@ -508,6 +465,44 @@ reason::\n \trefs, no explanation is needed. For a failed ref, the reason for\n \tfailure is described.\n \n+PUSH RULES\n+----------\n+\n+As a safety feature, the `git push` command only allows certain kinds of\n+updates to prevent you from accidentally losing data on the remote.\n+\n+Because branches and tags are intended to be used differently, the\n+safety rules for pushing to a branch are different from the rules\n+for pushing to a tag. In the following rules \"update\" means any\n+modifications except deletes. Deletions are always allowed, except when\n+forbidden by configuration or hooks.\n+\n+1. If the push destination is a **branch** (`refs/heads/*`): only\n+   fast-forward updates are allowed: the destination must be an ancestor\n+   of the source commit. The source must be a commit.\n+2. If the push destination is a **tag** (`refs/tags/*`): all updates will\n+   be rejected. The source can be any object\n+   (since commits, trees and blobs can be tagged).\n+3. If the push destination is not a branch or tag:\n+   * If the source is a tree or blob object, any updates will be rejected\n+   * If the source is a tag or commit object, any fast-forward update\n+     is allowed, even in cases where what's being fast-forwarded is not a\n+     commit, but a tag object which happens to point to a new commit which\n+     is a fast-forward of the commit the last tag (or commit) it's\n+     replacing. Replacing a tag with an entirely different tag is also\n+     allowed, if it points to the same commit, as well as pushing a peeled\n+     tag, i.e. pushing the commit that existing tag object points to, or a\n+     new tag object which an existing commit points to.\n+\n+You can override these rules by passing `--force` or by adding the\n+optional leading `+` to a refspec. The only exception to this is that no\n+amount of forcing will make a branch accept a non-commit object.\n+\n+Hooks and configuration can also override or amend these rules,\n+see e.g. `receive.denyNonFastForwards` and `receive.denyDeletes`\n+in linkgit:git-config[1] and `pre-receive` and `update` in\n+linkgit:githooks[5].\n+\n NOTE ABOUT FAST-FORWARDS\n ------------------------\n \n-- \ngitgitgadget\n\n"},{"id":"526635","messageId":"e1b667f64597cf6702f133f88a5ba913e2af3167.1758144815.git.gitgitgadget@gmail.com","threadId":"64163","inReplyTo":"pull.1973.git.1758144815.gitgitgadget@gmail.com","subject":"[PATCH 2/2] doc: git-push: rewrite refspec specification","fromName":"Julia Evans via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-09-17T21:33:35Z","receivedAt":"2025-09-17T21:33:42Z","isPatch":true,"sender":{"key":"julia@jvns.ca","avatar":"https://avatars.githubusercontent.com/u/817739?v=4"},"body":"From: Julia Evans <julia@jvns.ca>\n\nFrom user feedback, there was a request for examples, as well as a\ncomment that one person found \"If git push [<repository>] without\nany <refspec> argument is set to update some ref at the destination\nwith <src> with remote.<repository>.push configuration variable...\"\nimpossible to understand.\n\nTo make the section easier to navigate, create a list of every possible\nrefspec form, with examples for each form as well as 2 forms which were\npreviously missing (patterns and negative refspecs).\n\nMade a few changes to use more familiar language, but ultimately\nrefspecs are a pretty advanced feature so I've mostly left the\nterminology alone.\n\nSigned-off-by: Julia Evans <julia@jvns.ca>\n---\n Documentation/git-push.adoc | 105 ++++++++++++++++++++----------------\n 1 file changed, 58 insertions(+), 47 deletions(-)\n\ndiff --git a/Documentation/git-push.adoc b/Documentation/git-push.adoc\nindex 193016e291..068441d09d 100644\n--- a/Documentation/git-push.adoc\n+++ b/Documentation/git-push.adoc\n@@ -55,54 +55,65 @@ OPTIONS[[OPTIONS]]\n \n <refspec>...::\n \tSpecify what destination ref to update with what source object.\n-\tThe format of a <refspec> parameter is an optional plus\n-\t`+`, followed by the source object <src>, followed\n-\tby a colon `:`, followed by the destination ref <dst>.\n-+\n-The <src> is often the name of the branch you would want to push, but\n-it can be any arbitrary \"SHA-1 expression\", such as `master~4` or\n-`HEAD` (see linkgit:gitrevisions[7]).\n-+\n-The <dst> tells which ref on the remote side is updated with this\n-push. Arbitrary expressions cannot be used here, an actual ref must\n-be named.\n-If `git push [<repository>]` without any `<refspec>` argument is set to\n-update some ref at the destination with `<src>` with\n-`remote.<repository>.push` configuration variable, `:<dst>` part can\n-be omitted--such a push will update a ref that `<src>` normally updates\n-without any `<refspec>` on the command line.  Otherwise, missing\n-`:<dst>` means to update the same ref as the `<src>`.\n-+\n-If <dst> doesn't start with `refs/` (e.g. `refs/heads/master`) we will\n-try to infer where in `refs/*` on the destination <repository> it\n-belongs based on the type of <src> being pushed and whether <dst>\n-is ambiguous.\n +\n---\n-* If <dst> unambiguously refers to a ref on the <repository> remote,\n-  then push to that ref.\n-\n-* If <src> resolves to a ref starting with refs/heads/ or refs/tags/,\n-  then prepend that to <dst>.\n-\n-* Other ambiguity resolutions might be added in the future, but for\n-  now any other cases will error out with an error indicating what we\n-  tried, and depending on the `advice.pushUnqualifiedRefname`\n-  configuration (see linkgit:git-config[1]) suggest what refs/\n-  namespace you may have wanted to push to.\n-\n-Pushing an empty <src> allows you to delete the <dst> ref from the\n-remote repository. Deletions are always accepted without a leading `+`\n-in the refspec (or `--force`), except when forbidden by configuration\n-or hooks. See `receive.denyDeletes` in linkgit:git-config[1] and\n-`pre-receive` and `update` in linkgit:githooks[5].\n-+\n-The special refspec `:` (or `+:` to allow non-fast-forward updates)\n-directs Git to push \"matching\" branches: for every branch that exists on\n-the local side, the remote side is updated if a branch of the same name\n-already exists on the remote side.\n-+\n-`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.\n+The format for a refspec is [+]<src>[:<dst>], for example `main`,\n+`main:other`, or `HEAD^:refs/heads/main`.\n++\n+The `<src>` is often the name of the local branch to push, but it can be\n+any arbitrary \"SHA-1 expression\" (see linkgit:gitrevisions[7]).\n++\n+The `<dst>` determines what ref to update on the remote side. It must be the\n+name of a branch, tag, or other ref, not an arbitrary expression.\n++\n+The `+` is optional and does the same thing as `--force`.\n++\n+You can write a refspec using the fully expanded form (for\n+example `refs/heads/main:refs/heads/main`) which specifies the exact source\n+and destination, or with a shorter form (for example `main` or\n+`main:other`). Here are the rules for how refspecs are expanded,\n+as well as various other special refspec forms:\n++\n+ *  `<src>` without a `:<dst>` means to update the same ref as the\n+    `<src>`, unless the `remote.<repository>.push` configuration specifies a\n+    different <dst>. For example, if `main` is a branch, then the refspec\n+    `main` expands to `main:refs/heads/main`.\n+ *  If `<dst>` unambiguously refers to a ref on the <repository> remote,\n+    then expand it to that ref. For example, if `v1.0` is a tag on the\n+    remote, then `HEAD:v1.0` expands to `HEAD:refs/tags/v1.0`.\n+ *  If `<src>` resolves to a ref starting with `refs/heads/` or `refs/tags/`,\n+    then prepend that to <dst>. For example, if `main` is a branch, then\n+    `main:other` expands to `main:refs/heads/other`\n+ *  The special refspec `:` (or `+:` to allow non-fast-forward updates)\n+    directs Git to push \"matching\" branches: for every branch that exists on\n+    the local side, the remote side is updated if a branch of the same name\n+    already exists on the remote side.\n+ *  <src> may contain a * to indicate a simple pattern match.\n+    This works like a glob that matches any ref matching the pattern.\n+    There must be only one * in both the `<src>` and `<dst>`.\n+    It will map refs to the destination by replacing the * with the\n+    contents matched from the source. For example, `refs/heads/*:refs/heads/*`\n+    will push all branches.\n+ *  A refspec starting with `^` is a negative refspec.\n+    This specifies refs to exclude. A ref will be considered to\n+    match if it matches at least one positive refspec, and does not\n+    match any negative refspec. Negative refspecs can be pattern refspecs.\n+    They must only contain a `<src>`.\n+    Fully spelled out hex object names are also not supported.\n+    For example, `git push origin 'refs/heads/*' '^refs/heads/dev-*'`\n+    will push all branches except for those starting with `dev-`\n+ *  If `<src>` is empty, it deletes the `<dst>` ref from the remote\n+    repository. For example, `git push origin :dev` will\n+    delete the `dev` branch.\n+ *  `tag <tag>` expands to `refs/tags/<tag>:refs/tags/<tag>`.\n+\tThis is technically a special syntax for `git push` and not a refspec,\n+\tsince in `git push origin tag v1.0` the arguments `tag` and `v1.0`\n+\tare separate.\n+ *  If the refspec can't be expanded unambiguously, error out\n+    with an error indicating what was tried, and depending\n+    on the `advice.pushUnqualifiedRefname` configuration (see\n+    linkgit:git-config[1]) suggest what refs/ namespace you may have\n+    wanted to push to.\n+\n Not all updates are allowed: see PUSH RULES below for the details.\n \n --all::\n-- \ngitgitgadget\n"},{"id":"526642","messageId":"xmqq348kbtbz.fsf@gitster.g","threadId":"64163","inReplyTo":"2f2dc22c47530445bce50f1bdef9630b046677bd.1758144815.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 1/2] doc: git-push: create PUSH RULES section","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-17T22:35:44Z","receivedAt":"2025-09-17T22:35:47Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Julia Evans via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n>  --force::\n> -\tUsually, the command refuses to update a remote ref that is\n> -\tnot an ancestor of the local ref used to overwrite it.\n> -\tAlso, when `--force-with-lease` option is used, the command refuses\n> -\tto update a remote ref whose current value does not match\n> -\twhat is expected.\n> +\tUsually, `git push` will refuse to update a branch that is not an\n> +\tancestor of the local branch or commit being pushed.\n\nI read this as \"there are two conditions, and satisifying only one of\nthem is sufficient for the push to be allowed.  (1) the local branch\nis a decendant of the remote branch being updated, or (2) the commit\nwe push to update the remote branch is a descendant of the remote\nbranch being updated\".\n\nBut of course that is not what you wanted to say.  (1) would mean\n\n    $ git reset origin/foo && git push origin anything:foo\n\nwould allow us to push literally anything to overwrite origin's foo\nbranch.\n\nI think\n\n    \"... not an ancestor of the commit being pushed to update it.\"\n\nwould be a way to avoid such confusion.\n\nThe problem the original description has is the phrase \"the local\nref used to overwrite it\".  It wasn't as commonly done to push a\nspecific commit that may not necessarily at the tip of the branch\nback when this paragraph was written.\n\n> -This flag disables these checks, and can cause the remote repository\n> -to lose commits; use it with care.\n> +This flag disables that check, the other safety checks in PUSH RULES\n> +below, and the checks in --force-with-lease. It can cause the remote\n> +repository to lose commits; use it with care.\n\nOK.\n\n> +PUSH RULES\n> +----------\n> +\n> +As a safety feature, the `git push` command only allows certain kinds of\n> +updates to prevent you from accidentally losing data on the remote.\n> +\n> +Because branches and tags are intended to be used differently, the\n> +safety rules for pushing to a branch are different from the rules\n> +for pushing to a tag. In the following rules \"update\" means any\n> +modifications except deletes. Deletions are always allowed, except when\n> +forbidden by configuration or hooks.\n\nOne important operation is omitted.  \"update\" does not include\n\"create\" in the following, no?  Obviously since refs/tags/ would\nnever take any \"update\" (unless forced), if \"create\" were thrown\ninto the same category as \"update\", you cannot push a new tag out.\n\nSo, next to \"Deletions are always allowed\", shouldn't we describe\nwhat rules apply to creations?  I presume that they are also always\nallowed?\n\n> +1. If the push destination is a **branch** (`refs/heads/*`): only\n> +   fast-forward updates are allowed: the destination must be an ancestor\n> +   of the source commit. The source must be a commit.\n\nReads very well and also correct.\n\nThe first colon might be acceptable (I find it a bit odd, though).\nThe second colon is very weird.  \": only\" -> \", only\" & \"allowed:\nthe\" -> \"allowed. The\", perhaps?\n\n> +2. If the push destination is a **tag** (`refs/tags/*`): all updates will\n> +   be rejected. The source can be any object\n> +   (since commits, trees and blobs can be tagged).\n\nAgain, I might prefer \":\" -> \",\".  I cannot decide which I prefer\nbetween \"all updates will be rejected\" and \"by default no updates\nare allowed\".  Either should be OK, so let's take what has already\nbeen written.\n\nThe second sentence is not wrong per-se, and I can see that this was\ninherited from the original, but gives me a strange aftertaste. When\nyou list object types in the context of \"tag\" and have only commit,\ntree, and blob, a little voice in the back of my head asks \"oh, what\nhappend to tags?\".  It is made a bit worse with the phrase \"can be\ntagged\", as it typically means either (1) to create an annotated or\nsigned tag object, or (2) to create a ref in refs/tags/ hierarchy\nlocally, but usually you do not think of pushing to refs/tags/\nhierarchy as \"tagging that object remotely\".\n\nI think the untold assumption here is that refs/tags/foo at the\nremote is being updated most of the time from refs/tags/foo we have\nlocally, and \"any kind of object can be tagged\" is trying to say\nthat refs/tags/foo we have locally can be an object of any type, as\nthe act of creating a ref \"refs/tags/foo\" and pointing it directly\nat an object is \"to create a light-weight tag\" for the object.\nSince we can have not just tags but any kind of object locally\n(because any object \"can be tagged\"), a push can ask object of any\nkind to be pushed to refs/tags/* hierarchy.  But it is an awkward\nconcept to explain.\n\nWould side-stepping what exactly \"tagging a thing\" means, and\nphrasing it like this\n\n    The source is not limited to an annotated or signed tag object,\n    but can be a commit, a tree or even a blob.\n\nwork better, I wonder?\n\n> +3. If the push destination is not a branch or tag:\n\nHere, I do understand and support the colon, so I'd equally support\nthe first colon of the previous 2 sections for consistency.\n\n> +   * If the source is a tree or blob object, any updates will be rejected\n\nOK, so this is the same rule as the refs/tags/ hierarchy.\n\n> +   * If the source is a tag or commit object, any fast-forward update\n> +     is allowed, even in cases where what's being fast-forwarded is not a\n> +     commit, but a tag object which happens to point to a new commit which\n> +     is a fast-forward of the commit the last tag (or commit) it's\n> +     replacing. Replacing a tag with an entirely different tag is also\n> +     allowed, if it points to the same commit, as well as pushing a peeled\n> +     tag, i.e. pushing the commit that existing tag object points to, or a\n> +     new tag object which an existing commit points to.\n> +\n> +You can override these rules by passing `--force` or by adding the\n> +optional leading `+` to a refspec. The only exception to this is that no\n> +amount of forcing will make a branch accept a non-commit object.\n> +\n> +Hooks and configuration can also override or amend these rules,\n> +see e.g. `receive.denyNonFastForwards` and `receive.denyDeletes`\n> +in linkgit:git-config[1] and `pre-receive` and `update` in\n> +linkgit:githooks[5].\n\nVery well written.  refs/heads/ not taking any non-commit, and\nreceiver side hooks that may reject a push, are not something\n\"--force\" can override.  The mention to \"the only exception\" above\nsounded as if it forgot to mention the latter.\n\nLooking mostly good.  Thanks.\n"},{"id":"526727","messageId":"311f6e20-b12c-454a-991f-bcbd66b318bd@app.fastmail.com","threadId":"64163","inReplyTo":"xmqq348kbtbz.fsf@gitster.g","subject":"Re: [PATCH 1/2] doc: git-push: create PUSH RULES section","fromName":"Julia Evans","fromEmail":"julia@jvns.ca","sentAt":"2025-09-18T20:48:46Z","receivedAt":"2025-09-18T20:49:08Z","isPatch":true,"sender":{"key":"julia@jvns.ca","avatar":"https://avatars.githubusercontent.com/u/817739?v=4"},"body":">>  --force::\n>> -\tUsually, the command refuses to update a remote ref that is\n>> -\tnot an ancestor of the local ref used to overwrite it.\n>> -\tAlso, when `--force-with-lease` option is used, the command refuses\n>> -\tto update a remote ref whose current value does not match\n>> -\twhat is expected.\n>> +\tUsually, `git push` will refuse to update a branch that is not an\n>> +\tancestor of the local branch or commit being pushed.\n>\n> I read this as \"there are two conditions, and satisifying only one of\n> them is sufficient for the push to be allowed.  (1) the local branch\n> is a decendant of the remote branch being updated, or (2) the commit\n> we push to update the remote branch is a descendant of the remote\n> branch being updated\".\n>\n> But of course that is not what you wanted to say.  (1) would mean\n>\n>     $ git reset origin/foo && git push origin anything:foo\n>\n> would allow us to push literally anything to overwrite origin's foo\n> branch.\n>\n> I think\n>\n>     \"... not an ancestor of the commit being pushed to update it.\"\n>\n> would be a way to avoid such confusion.\n\nI like this, will use that phrasing.\n\n>\n>> +PUSH RULES\n>> +----------\n>> +\n>> +As a safety feature, the `git push` command only allows certain kinds of\n>> +updates to prevent you from accidentally losing data on the remote.\n>> +\n>> +Because branches and tags are intended to be used differently, the\n>> +safety rules for pushing to a branch are different from the rules\n>> +for pushing to a tag. In the following rules \"update\" means any\n>> +modifications except deletes. Deletions are always allowed, except when\n>> +forbidden by configuration or hooks.\n>\n> One important operation is omitted.  \"update\" does not include\n> \"create\" in the following, no?  Obviously since refs/tags/ would\n> never take any \"update\" (unless forced), if \"create\" were thrown\n> into the same category as \"update\", you cannot push a new tag out.\n>\n> So, next to \"Deletions are always allowed\", shouldn't we describe\n> what rules apply to creations?  I presume that they are also always\n> allowed?\n\n> The first colon might be acceptable (I find it a bit odd, though).\n> The second colon is very weird.  \": only\" -> \", only\" & \"allowed:\n> the\" -> \"allowed. The\", perhaps?\n\nI agree the second colon is weird, will fix it. (probably \", which means the\ndestination must be an ancestor of the source commit\")\n\n>\n>> +2. If the push destination is a **tag** (`refs/tags/*`): all updates will\n>> +   be rejected. The source can be any object\n>> +   (since commits, trees and blobs can be tagged).\n>\n> Again, I might prefer \":\" -> \",\".  I cannot decide which I prefer\n> between \"all updates will be rejected\" and \"by default no updates\n> are allowed\".  Either should be OK, so let's take what has already\n> been written.\n>\n> The second sentence is not wrong per-se, and I can see that this was\n> inherited from the original, but gives me a strange aftertaste. When\n> you list object types in the context of \"tag\" and have only commit,\n> tree, and blob, a little voice in the back of my head asks \"oh, what\n> happend to tags?\".  It is made a bit worse with the phrase \"can be\n> tagged\", as it typically means either (1) to create an annotated or\n> signed tag object, or (2) to create a ref in refs/tags/ hierarchy\n> locally, but usually you do not think of pushing to refs/tags/\n> hierarchy as \"tagging that object remotely\".\n>\n> I think the untold assumption here is that refs/tags/foo at the\n> remote is being updated most of the time from refs/tags/foo we have\n> locally, and \"any kind of object can be tagged\" is trying to say\n> that refs/tags/foo we have locally can be an object of any type, as\n> the act of creating a ref \"refs/tags/foo\" and pointing it directly\n> at an object is \"to create a light-weight tag\" for the object.\n> Since we can have not just tags but any kind of object locally\n> (because any object \"can be tagged\"), a push can ask object of any\n> kind to be pushed to refs/tags/* hierarchy.  But it is an awkward\n> concept to explain.\n>\n> Would side-stepping what exactly \"tagging a thing\" means, and\n> phrasing it like this\n>\n>     The source is not limited to an annotated or signed tag object,\n>     but can be a commit, a tree or even a blob.\n>\n> work better, I wonder?\n\nI think it depends on whether the reader is more familiar with lightweight\ntags or annotated tags. The only kind of tag I've ever used personally is a\nlightweight tag, so the sentence\n\"The source is not limited to an annotated or signed tag object,\"\nfeels confusing to me (\"why would it be limited to an annotated tag?\nI'm not even totally sure what an annotated tag is!\")\n\nBut I can see that it would be different if the reader is more familiar\nwith annotated tags. Will think about this. \n\nMaybe we can just say \"the source can be any object\" to just be clear that there\nare no restrictions without trying to educate the reader about the nature of\nGit tags.\n\n>> +3. If the push destination is not a branch or tag:\n>\n> Here, I do understand and support the colon, so I'd equally support\n> the first colon of the previous 2 sections for consistency.\n>\n>> +   * If the source is a tree or blob object, any updates will be rejected\n>\n> OK, so this is the same rule as the refs/tags/ hierarchy.\n>\n>> +   * If the source is a tag or commit object, any fast-forward update\n>> +     is allowed, even in cases where what's being fast-forwarded is not a\n>> +     commit, but a tag object which happens to point to a new commit which\n>> +     is a fast-forward of the commit the last tag (or commit) it's\n>> +     replacing. Replacing a tag with an entirely different tag is also\n>> +     allowed, if it points to the same commit, as well as pushing a peeled\n>> +     tag, i.e. pushing the commit that existing tag object points to, or a\n>> +     new tag object which an existing commit points to.\n>> +\n>> +You can override these rules by passing `--force` or by adding the\n>> +optional leading `+` to a refspec. The only exception to this is that no\n>> +amount of forcing will make a branch accept a non-commit object.\n>> +\n>> +Hooks and configuration can also override or amend these rules,\n>> +see e.g. `receive.denyNonFastForwards` and `receive.denyDeletes`\n>> +in linkgit:git-config[1] and `pre-receive` and `update` in\n>> +linkgit:githooks[5].\n>\n> Very well written.  refs/heads/ not taking any non-commit, and\n> receiver side hooks that may reject a push, are not something\n> \"--force\" can override.  The mention to \"the only exception\" above\n> sounded as if it forgot to mention the latter.\n\nWill mention the receiver side hooks there, I didn't think of that.\n\n> Looking mostly good.  Thanks.\n"},{"id":"526745","messageId":"aMymMLkJg7PkmxL7@fruit.crustytoothpaste.net","threadId":"64163","inReplyTo":"pull.1973.git.1758144815.gitgitgadget@gmail.com","subject":"Re: [PATCH 0/2] doc: git-push: clarify section","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2025-09-19T00:39:12Z","receivedAt":"2025-09-19T00:39:14Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2025-09-17 at 21:33:33, Julia Evans via GitGitGadget wrote:\n> This is a continuation of the changes to git push, from\n> https://lore.kernel.org/git/pull.1964.git.1756240823.gitgitgadget@gmail.com/\n> . These changes to the refspec section got kind of big so I'm moving them\n> into a separate topic.\n> \n> Since the last review, the main change is to move the rules for pushing out\n> of the section and into their own section (\"PUSH RULES\") so that it can be\n> easily referenced from other places in the man page.\n> \n> I don't love the nested list in PUSH RULES but the sentence starting with\n> \"If the source is a tag or commit object...\" is really a tough one to read,\n> it's not going to be relevant to the vast majority of people, and I think\n> keeping it contained inside a bullet point will make it much easier to skip\n> over to get to later information which is more likely to be relevant to\n> folks.\n> \n> Other changes:\n> \n>  * removed \"+:<dst> is optional.\", from Junio's review\n>  * kept \"+ is optional and does the same thing as --force\", since now the\n>    push rules are in their own section.\n>  * fixed the fully expanded refspec form (main:refs/heads/main =>\n>    refs/heads/main:refs/heads/main)\n>  * switched from a numbered list to an unordered list, from Junio's review.\n>    I think the numbered list looks a lot nicer in the terminal output, but\n>    it's true that there isn't any order. I briefly attempted to understand\n>    how AsciiDoc's nroff (?) generation works to see if it's possible to make\n>    unordered lists indent with fewer spaces (2 instead of 4) but I was left\n>    feeling that nroff/troff/etc are not for mere mortals like me to\n>    understand.\n\nI have used groff for many years to write letters and address envelopes\n(as well as write a few manual pages and design some awards) and I still\ndon't understand much of it, so I understand how you feel.\n\nIn this case we have two possible implementations, AsciiDoc and\nAsciidoctor, but both use the man macros.  I think if there were some\nway to control the value generated as the argument to the `.RS` macro,\nthen that would be what you want, but I have no idea how to adjust that\nin either one.  Personally, I would just leave it as it is, since I\nbelieve 4 is the traditional value used with the man macros, even if\ntypographically or aesthetically it might be suboptimal.\n\nHaving said all that, I think this documentation is much improved and\neasier to understand, so thank you for that.  I don't have specific\ncomments on the patches (although I see that Junio has suggested some\nclarifying comments), but I do deeply appreciate your efforts to improve\nthe documentation (and, outside of the list, to clarify difficult\ntechnical topics in general).\n-- \nbrian m. carlson (they/them)\nToronto, Ontario, CA\n"},{"id":"526757","messageId":"20250919042545.GA3979@coredump.intra.peff.net","threadId":"64163","inReplyTo":"aMymMLkJg7PkmxL7@fruit.crustytoothpaste.net","subject":"Re: [PATCH 0/2] doc: git-push: clarify section","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-09-19T04:25:45Z","receivedAt":"2025-09-19T04:25:52Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Sep 19, 2025 at 12:39:12AM +0000, brian m. carlson wrote:\n\n> In this case we have two possible implementations, AsciiDoc and\n> Asciidoctor, but both use the man macros.  I think if there were some\n> way to control the value generated as the argument to the `.RS` macro,\n> then that would be what you want, but I have no idea how to adjust that\n> in either one.  Personally, I would just leave it as it is, since I\n> believe 4 is the traditional value used with the man macros, even if\n> typographically or aesthetically it might be suboptimal.\n\nI think both implementations will just generate XML via our Makefile,\nand ultimately it is DocBook which will convert the <orderedlist> into\nactual roff. So something like:\n\ndiff --git a/Documentation/manpage-normal.xsl b/Documentation/manpage-normal.xsl\nindex beb5ff8ec2..b494fbb5df 100644\n--- a/Documentation/manpage-normal.xsl\n+++ b/Documentation/manpage-normal.xsl\n@@ -11,4 +11,6 @@\n <!-- unset maximum length of title -->\n <xsl:param name=\"man.th.title.max.length\"/>\n \n+<xsl:param name=\"man.indent.width\" select=\"2\"/>\n+\n </xsl:stylesheet>\n\nwould affect that process. And I think there might even be a specific\nlist-indent variable, but I didn't dig very far. I agree it's probably\nnot worth going too far into the rabbit hole of manpage styling. The\nparameter docs are here:\n\n  https://docbook.sourceforge.net/release/xsl/1.78.1/doc/manpages/indent.html\n\nI believe Asciidocttor _can_ generate roff directly, but we don't use it\nthat way. I don't think it would make sense to do so unless we are ready\nto drop AsciiDoc support entirely (since keeping them as close together\nas possible reduces the maintenance burden).\n\n-Peff\n"},{"id":"527121","messageId":"955752f2-5695-433b-95d8-a7a40cb41d65@app.fastmail.com","threadId":"64163","inReplyTo":"20250919042545.GA3979@coredump.intra.peff.net","subject":"Re: [PATCH 0/2] doc: git-push: clarify section","fromName":"Julia Evans","fromEmail":"julia@jvns.ca","sentAt":"2025-09-23T18:08:37Z","receivedAt":"2025-09-23T18:08:58Z","isPatch":true,"sender":{"key":"julia@jvns.ca","avatar":"https://avatars.githubusercontent.com/u/817739?v=4"},"body":"\n\nOn Fri, Sep 19, 2025, at 12:25 AM, Jeff King wrote:\n> On Fri, Sep 19, 2025 at 12:39:12AM +0000, brian m. carlson wrote:\n>\n>> In this case we have two possible implementations, AsciiDoc and\n>> Asciidoctor, but both use the man macros.  I think if there were some\n>> way to control the value generated as the argument to the `.RS` macro,\n>> then that would be what you want, but I have no idea how to adjust that\n>> in either one.  Personally, I would just leave it as it is, since I\n>> believe 4 is the traditional value used with the man macros, even if\n>> typographically or aesthetically it might be suboptimal.\n>\n> I think both implementations will just generate XML via our Makefile,\n> and ultimately it is DocBook which will convert the <orderedlist> into\n> actual roff. So something like:\n>\n> diff --git a/Documentation/manpage-normal.xsl b/Documentation/manpage-normal.xsl\n> index beb5ff8ec2..b494fbb5df 100644\n> --- a/Documentation/manpage-normal.xsl\n> +++ b/Documentation/manpage-normal.xsl\n> @@ -11,4 +11,6 @@\n>  <!-- unset maximum length of title -->\n>  <xsl:param name=\"man.th.title.max.length\"/>\n> \n> +<xsl:param name=\"man.indent.width\" select=\"2\"/>\n> +\n>  </xsl:stylesheet>\n> would affect that process.\n\nThat's good to know, thanks. We'd need a way to indent unordered lists\nby 2 and ordered lists by 4 anyway (to make space for the numbers in the\nordered lists) and at that point I'm not sure if it would even\nlook good. I think I'll leave this rabbit hole as is :)\n\n> And I think there might even be a specific\n> list-indent variable, but I didn't dig very far. I agree it's probably\n> not worth going too far into the rabbit hole of manpage styling. The\n> parameter docs are here:\n>\n>   https://docbook.sourceforge.net/release/xsl/1.78.1/doc/manpages/indent.html\n>\n> I believe Asciidocttor _can_ generate roff directly, but we don't use it\n> that way. I don't think it would make sense to do so unless we are ready\n> to drop AsciiDoc support entirely (since keeping them as close together\n> as possible reduces the maintenance burden).\n>\n> -Peff\n"},{"id":"527122","messageId":"pull.1973.v2.git.1758651049.gitgitgadget@gmail.com","threadId":"64163","inReplyTo":"pull.1973.git.1758144815.gitgitgadget@gmail.com","subject":"[PATCH v2 0/2] doc: git-push: clarify section","fromName":"Julia Evans via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-09-23T18:10:47Z","receivedAt":"2025-09-23T18:10:53Z","isPatch":true,"sender":{"key":"julia@jvns.ca","avatar":"https://avatars.githubusercontent.com/u/817739?v=4"},"body":"This is a continuation of the changes to git push, from\nhttps://lore.kernel.org/git/pull.1964.git.1756240823.gitgitgadget@gmail.com/\n. These changes to the refspec section got kind of big so I'm moving them\ninto a separate topic.\n\nI don't love the nested list in PUSH RULES but the sentence starting with\n\"If the source is a tag or commit object...\" is really a tough one to read,\nit's not going to be relevant to the vast majority of people, and I think\nkeeping it contained inside a bullet point will make it much easier to skip\nover to get to later information which is more likely to be relevant to\nfolks.\n\nChanges since the original thread:\n\n * The main change is to move the rules for pushing out of the section and\n   into their own section (\"PUSH RULES\") so that it can be easily referenced\n   from other places in the man page.\n * removed \"+:<dst> is optional.\", from Junio's review\n * kept \"+ is optional and does the same thing as --force\", since now the\n   push rules are in their own section.\n * fixed the fully expanded refspec form (main:refs/heads/main =>\n   refs/heads/main:refs/heads/main)\n * switched from a numbered list to an unordered list, from Junio's review.\n   I think the numbered list looks a lot nicer in the terminal output, but\n   it's true that there isn't any order. I briefly attempted to understand\n   how AsciiDoc's nroff (?) generation works to see if it's possible to make\n   unordered lists indent with fewer spaces (2 instead of 4) but I was left\n   feeling that nroff/troff/etc are not for mere mortals like me to\n   understand.\n * made it clear that \"tag v1.0\" is not really a refspec, from Junio's\n   review\n\nChanges in v2:\n\n * Say just \"The source can be any object.\" and don't try to educate folks\n   further about tags, from Junio's review\n * Mention both exceptions to --force working, from Junio's review\n * Change \"local branch or commit being pushed\" => \"commit being pushed\" to\n   make it clear that it's just 1 condition, from Junio's review\n * Remove an awkward double colon, from Junio's review\n * Be explicit that creations are always allowed, from Junio's revew\n\nJulia Evans (2):\n  doc: git-push: create PUSH RULES section\n  doc: git-push: rewrite refspec specification\n\n Documentation/git-push.adoc | 199 +++++++++++++++++++-----------------\n 1 file changed, 103 insertions(+), 96 deletions(-)\n\n\nbase-commit: c44beea485f0f2feaf460e2ac87fdd5608d63cf0\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1973%2Fjvns%2Fclarify-refspec-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1973/jvns/clarify-refspec-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/1973\n\nRange-diff vs v1:\n\n 1:  2f2dc22c47 ! 1:  8be0554d02 doc: git-push: create PUSH RULES section\n     @@ Documentation/git-push.adoc: allowing a forced update.\n      -\tto update a remote ref whose current value does not match\n      -\twhat is expected.\n      +\tUsually, `git push` will refuse to update a branch that is not an\n     -+\tancestor of the local branch or commit being pushed.\n     ++\tancestor of the commit being pushed.\n       +\n      -This flag disables these checks, and can cause the remote repository\n      -to lose commits; use it with care.\n     @@ Documentation/git-push.adoc: reason::\n      +Because branches and tags are intended to be used differently, the\n      +safety rules for pushing to a branch are different from the rules\n      +for pushing to a tag. In the following rules \"update\" means any\n     -+modifications except deletes. Deletions are always allowed, except when\n     -+forbidden by configuration or hooks.\n     ++modifications except deletions and creations. Deletions and creations\n     ++are always allowed, except when forbidden by configuration or hooks.\n      +\n      +1. If the push destination is a **branch** (`refs/heads/*`): only\n     -+   fast-forward updates are allowed: the destination must be an ancestor\n     -+   of the source commit. The source must be a commit.\n     ++   fast-forward updates are allowed, which means the destination must be\n     ++   an ancestor of the source commit. The source must be a commit.\n      +2. If the push destination is a **tag** (`refs/tags/*`): all updates will\n     -+   be rejected. The source can be any object\n     -+   (since commits, trees and blobs can be tagged).\n     ++   be rejected. The source can be any object.\n      +3. If the push destination is not a branch or tag:\n      +   * If the source is a tree or blob object, any updates will be rejected\n      +   * If the source is a tag or commit object, any fast-forward update\n     @@ Documentation/git-push.adoc: reason::\n      +     new tag object which an existing commit points to.\n      +\n      +You can override these rules by passing `--force` or by adding the\n     -+optional leading `+` to a refspec. The only exception to this is that no\n     -+amount of forcing will make a branch accept a non-commit object.\n     ++optional leading `+` to a refspec. The only exceptions are that no\n     ++amount of forcing will make a branch accept a non-commit object,\n     ++and forcing won't make the remote repository accept a push that it's\n     ++configured to deny.\n      +\n      +Hooks and configuration can also override or amend these rules,\n      +see e.g. `receive.denyNonFastForwards` and `receive.denyDeletes`\n 2:  e1b667f645 = 2:  11ad190c3e doc: git-push: rewrite refspec specification\n\n-- \ngitgitgadget\n"},{"id":"527123","messageId":"8be0554d02e147ddf8a033e8bb7b30417bf4d501.1758651049.git.gitgitgadget@gmail.com","threadId":"64163","inReplyTo":"pull.1973.v2.git.1758651049.gitgitgadget@gmail.com","subject":"[PATCH v2 1/2] doc: git-push: create PUSH RULES section","fromName":"Julia Evans via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-09-23T18:10:48Z","receivedAt":"2025-09-23T18:10:56Z","isPatch":true,"sender":{"key":"julia@jvns.ca","avatar":"https://avatars.githubusercontent.com/u/817739?v=4"},"body":"From: Julia Evans <julia@jvns.ca>\n\nRight now the rules for when a `git push` is allowed are buried at the\nbottom of the description of `<refspec>`. Put them in their own section\nso that we can reference them from `--force` and give some context for\nwhy they exist.\n\nHaving the \"PUSH RULES\" section also lets us be a little bit more\nspecific with the rule in `--force`: we can just focus on the rule\nfor pushing for a branch (which is likely the one that's most relevant)\nand leave the details about what happens when you push to a tag or a ref\nthat isn't a branch to the later section.\n\nSigned-off-by: Julia Evans <julia@jvns.ca>\n---\n Documentation/git-push.adoc | 94 ++++++++++++++++++-------------------\n 1 file changed, 45 insertions(+), 49 deletions(-)\n\ndiff --git a/Documentation/git-push.adoc b/Documentation/git-push.adoc\nindex d1978650d6..4faf915f94 100644\n--- a/Documentation/git-push.adoc\n+++ b/Documentation/git-push.adoc\n@@ -91,48 +91,6 @@ is ambiguous.\n   configuration (see linkgit:git-config[1]) suggest what refs/\n   namespace you may have wanted to push to.\n \n---\n-+\n-The object referenced by <src> is used to update the <dst> reference\n-on the remote side. Whether this is allowed depends on where in\n-`refs/*` the <dst> reference lives as described in detail below, in\n-those sections \"update\" means any modifications except deletes, which\n-as noted after the next few sections are treated differently.\n-+\n-The `refs/heads/*` namespace will only accept commit objects, and\n-updates only if they can be fast-forwarded.\n-+\n-The `refs/tags/*` namespace will accept any kind of object (as\n-commits, trees and blobs can be tagged), and any updates to them will\n-be rejected.\n-+\n-It's possible to push any type of object to any namespace outside of\n-`refs/{tags,heads}/*`. In the case of tags and commits, these will be\n-treated as if they were the commits inside `refs/heads/*` for the\n-purposes of whether the update is allowed.\n-+\n-I.e. a fast-forward of commits and tags outside `refs/{tags,heads}/*`\n-is allowed, even in cases where what's being fast-forwarded is not a\n-commit, but a tag object which happens to point to a new commit which\n-is a fast-forward of the commit the last tag (or commit) it's\n-replacing. Replacing a tag with an entirely different tag is also\n-allowed, if it points to the same commit, as well as pushing a peeled\n-tag, i.e. pushing the commit that existing tag object points to, or a\n-new tag object which an existing commit points to.\n-+\n-Tree and blob objects outside of `refs/{tags,heads}/*` will be treated\n-the same way as if they were inside `refs/tags/*`, any update of them\n-will be rejected.\n-+\n-All of the rules described above about what's not allowed as an update\n-can be overridden by adding an the optional leading `+` to a refspec\n-(or using `--force` command line option). The only exception to this\n-is that no amount of forcing will make the `refs/heads/*` namespace\n-accept a non-commit object. Hooks and configuration can also override\n-or amend these rules, see e.g. `receive.denyNonFastForwards` in\n-linkgit:git-config[1] and `pre-receive` and `update` in\n-linkgit:githooks[5].\n-+\n Pushing an empty <src> allows you to delete the <dst> ref from the\n remote repository. Deletions are always accepted without a leading `+`\n in the refspec (or `--force`), except when forbidden by configuration\n@@ -145,6 +103,7 @@ the local side, the remote side is updated if a branch of the same name\n already exists on the remote side.\n +\n `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.\n+Not all updates are allowed: see PUSH RULES below for the details.\n \n --all::\n --branches::\n@@ -332,14 +291,12 @@ allowing a forced update.\n \n -f::\n --force::\n-\tUsually, the command refuses to update a remote ref that is\n-\tnot an ancestor of the local ref used to overwrite it.\n-\tAlso, when `--force-with-lease` option is used, the command refuses\n-\tto update a remote ref whose current value does not match\n-\twhat is expected.\n+\tUsually, `git push` will refuse to update a branch that is not an\n+\tancestor of the commit being pushed.\n +\n-This flag disables these checks, and can cause the remote repository\n-to lose commits; use it with care.\n+This flag disables that check, the other safety checks in PUSH RULES\n+below, and the checks in --force-with-lease. It can cause the remote\n+repository to lose commits; use it with care.\n +\n Note that `--force` applies to all the refs that are pushed, hence\n using it with `push.default` set to `matching` or with multiple push\n@@ -508,6 +465,45 @@ reason::\n \trefs, no explanation is needed. For a failed ref, the reason for\n \tfailure is described.\n \n+PUSH RULES\n+----------\n+\n+As a safety feature, the `git push` command only allows certain kinds of\n+updates to prevent you from accidentally losing data on the remote.\n+\n+Because branches and tags are intended to be used differently, the\n+safety rules for pushing to a branch are different from the rules\n+for pushing to a tag. In the following rules \"update\" means any\n+modifications except deletions and creations. Deletions and creations\n+are always allowed, except when forbidden by configuration or hooks.\n+\n+1. If the push destination is a **branch** (`refs/heads/*`): only\n+   fast-forward updates are allowed, which means the destination must be\n+   an ancestor of the source commit. The source must be a commit.\n+2. If the push destination is a **tag** (`refs/tags/*`): all updates will\n+   be rejected. The source can be any object.\n+3. If the push destination is not a branch or tag:\n+   * If the source is a tree or blob object, any updates will be rejected\n+   * If the source is a tag or commit object, any fast-forward update\n+     is allowed, even in cases where what's being fast-forwarded is not a\n+     commit, but a tag object which happens to point to a new commit which\n+     is a fast-forward of the commit the last tag (or commit) it's\n+     replacing. Replacing a tag with an entirely different tag is also\n+     allowed, if it points to the same commit, as well as pushing a peeled\n+     tag, i.e. pushing the commit that existing tag object points to, or a\n+     new tag object which an existing commit points to.\n+\n+You can override these rules by passing `--force` or by adding the\n+optional leading `+` to a refspec. The only exceptions are that no\n+amount of forcing will make a branch accept a non-commit object,\n+and forcing won't make the remote repository accept a push that it's\n+configured to deny.\n+\n+Hooks and configuration can also override or amend these rules,\n+see e.g. `receive.denyNonFastForwards` and `receive.denyDeletes`\n+in linkgit:git-config[1] and `pre-receive` and `update` in\n+linkgit:githooks[5].\n+\n NOTE ABOUT FAST-FORWARDS\n ------------------------\n \n-- \ngitgitgadget\n\n"},{"id":"527124","messageId":"11ad190c3ea457352a7f3a589bebba97d20f7ee8.1758651049.git.gitgitgadget@gmail.com","threadId":"64163","inReplyTo":"pull.1973.v2.git.1758651049.gitgitgadget@gmail.com","subject":"[PATCH v2 2/2] doc: git-push: rewrite refspec specification","fromName":"Julia Evans via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-09-23T18:10:49Z","receivedAt":"2025-09-23T18:10:57Z","isPatch":true,"sender":{"key":"julia@jvns.ca","avatar":"https://avatars.githubusercontent.com/u/817739?v=4"},"body":"From: Julia Evans <julia@jvns.ca>\n\nFrom user feedback, there was a request for examples, as well as a\ncomment that one person found \"If git push [<repository>] without\nany <refspec> argument is set to update some ref at the destination\nwith <src> with remote.<repository>.push configuration variable...\"\nimpossible to understand.\n\nTo make the section easier to navigate, create a list of every possible\nrefspec form, with examples for each form as well as 2 forms which were\npreviously missing (patterns and negative refspecs).\n\nMade a few changes to use more familiar language, but ultimately\nrefspecs are a pretty advanced feature so I've mostly left the\nterminology alone.\n\nSigned-off-by: Julia Evans <julia@jvns.ca>\n---\n Documentation/git-push.adoc | 105 ++++++++++++++++++++----------------\n 1 file changed, 58 insertions(+), 47 deletions(-)\n\ndiff --git a/Documentation/git-push.adoc b/Documentation/git-push.adoc\nindex 4faf915f94..ff125297b0 100644\n--- a/Documentation/git-push.adoc\n+++ b/Documentation/git-push.adoc\n@@ -55,54 +55,65 @@ OPTIONS[[OPTIONS]]\n \n <refspec>...::\n \tSpecify what destination ref to update with what source object.\n-\tThe format of a <refspec> parameter is an optional plus\n-\t`+`, followed by the source object <src>, followed\n-\tby a colon `:`, followed by the destination ref <dst>.\n-+\n-The <src> is often the name of the branch you would want to push, but\n-it can be any arbitrary \"SHA-1 expression\", such as `master~4` or\n-`HEAD` (see linkgit:gitrevisions[7]).\n-+\n-The <dst> tells which ref on the remote side is updated with this\n-push. Arbitrary expressions cannot be used here, an actual ref must\n-be named.\n-If `git push [<repository>]` without any `<refspec>` argument is set to\n-update some ref at the destination with `<src>` with\n-`remote.<repository>.push` configuration variable, `:<dst>` part can\n-be omitted--such a push will update a ref that `<src>` normally updates\n-without any `<refspec>` on the command line.  Otherwise, missing\n-`:<dst>` means to update the same ref as the `<src>`.\n-+\n-If <dst> doesn't start with `refs/` (e.g. `refs/heads/master`) we will\n-try to infer where in `refs/*` on the destination <repository> it\n-belongs based on the type of <src> being pushed and whether <dst>\n-is ambiguous.\n +\n---\n-* If <dst> unambiguously refers to a ref on the <repository> remote,\n-  then push to that ref.\n-\n-* If <src> resolves to a ref starting with refs/heads/ or refs/tags/,\n-  then prepend that to <dst>.\n-\n-* Other ambiguity resolutions might be added in the future, but for\n-  now any other cases will error out with an error indicating what we\n-  tried, and depending on the `advice.pushUnqualifiedRefname`\n-  configuration (see linkgit:git-config[1]) suggest what refs/\n-  namespace you may have wanted to push to.\n-\n-Pushing an empty <src> allows you to delete the <dst> ref from the\n-remote repository. Deletions are always accepted without a leading `+`\n-in the refspec (or `--force`), except when forbidden by configuration\n-or hooks. See `receive.denyDeletes` in linkgit:git-config[1] and\n-`pre-receive` and `update` in linkgit:githooks[5].\n-+\n-The special refspec `:` (or `+:` to allow non-fast-forward updates)\n-directs Git to push \"matching\" branches: for every branch that exists on\n-the local side, the remote side is updated if a branch of the same name\n-already exists on the remote side.\n-+\n-`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.\n+The format for a refspec is [+]<src>[:<dst>], for example `main`,\n+`main:other`, or `HEAD^:refs/heads/main`.\n++\n+The `<src>` is often the name of the local branch to push, but it can be\n+any arbitrary \"SHA-1 expression\" (see linkgit:gitrevisions[7]).\n++\n+The `<dst>` determines what ref to update on the remote side. It must be the\n+name of a branch, tag, or other ref, not an arbitrary expression.\n++\n+The `+` is optional and does the same thing as `--force`.\n++\n+You can write a refspec using the fully expanded form (for\n+example `refs/heads/main:refs/heads/main`) which specifies the exact source\n+and destination, or with a shorter form (for example `main` or\n+`main:other`). Here are the rules for how refspecs are expanded,\n+as well as various other special refspec forms:\n++\n+ *  `<src>` without a `:<dst>` means to update the same ref as the\n+    `<src>`, unless the `remote.<repository>.push` configuration specifies a\n+    different <dst>. For example, if `main` is a branch, then the refspec\n+    `main` expands to `main:refs/heads/main`.\n+ *  If `<dst>` unambiguously refers to a ref on the <repository> remote,\n+    then expand it to that ref. For example, if `v1.0` is a tag on the\n+    remote, then `HEAD:v1.0` expands to `HEAD:refs/tags/v1.0`.\n+ *  If `<src>` resolves to a ref starting with `refs/heads/` or `refs/tags/`,\n+    then prepend that to <dst>. For example, if `main` is a branch, then\n+    `main:other` expands to `main:refs/heads/other`\n+ *  The special refspec `:` (or `+:` to allow non-fast-forward updates)\n+    directs Git to push \"matching\" branches: for every branch that exists on\n+    the local side, the remote side is updated if a branch of the same name\n+    already exists on the remote side.\n+ *  <src> may contain a * to indicate a simple pattern match.\n+    This works like a glob that matches any ref matching the pattern.\n+    There must be only one * in both the `<src>` and `<dst>`.\n+    It will map refs to the destination by replacing the * with the\n+    contents matched from the source. For example, `refs/heads/*:refs/heads/*`\n+    will push all branches.\n+ *  A refspec starting with `^` is a negative refspec.\n+    This specifies refs to exclude. A ref will be considered to\n+    match if it matches at least one positive refspec, and does not\n+    match any negative refspec. Negative refspecs can be pattern refspecs.\n+    They must only contain a `<src>`.\n+    Fully spelled out hex object names are also not supported.\n+    For example, `git push origin 'refs/heads/*' '^refs/heads/dev-*'`\n+    will push all branches except for those starting with `dev-`\n+ *  If `<src>` is empty, it deletes the `<dst>` ref from the remote\n+    repository. For example, `git push origin :dev` will\n+    delete the `dev` branch.\n+ *  `tag <tag>` expands to `refs/tags/<tag>:refs/tags/<tag>`.\n+\tThis is technically a special syntax for `git push` and not a refspec,\n+\tsince in `git push origin tag v1.0` the arguments `tag` and `v1.0`\n+\tare separate.\n+ *  If the refspec can't be expanded unambiguously, error out\n+    with an error indicating what was tried, and depending\n+    on the `advice.pushUnqualifiedRefname` configuration (see\n+    linkgit:git-config[1]) suggest what refs/ namespace you may have\n+    wanted to push to.\n+\n Not all updates are allowed: see PUSH RULES below for the details.\n \n --all::\n-- \ngitgitgadget\n"},{"id":"527159","messageId":"xmqqa52komwf.fsf@gitster.g","threadId":"64163","inReplyTo":"pull.1973.v2.git.1758651049.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 0/2] doc: git-push: clarify section","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-23T21:54:24Z","receivedAt":"2025-09-23T21:54:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Julia Evans via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> This is a continuation of the changes to git push, from\n> https://lore.kernel.org/git/pull.1964.git.1756240823.gitgitgadget@gmail.com/\n> . These changes to the refspec section got kind of big so I'm moving them\n> into a separate topic.\n\nIt is somewhat awkward to have two outstanding sets of patches to\nthe same file under the same theme from the same author.  How would\nthis relate to the other \"git push documentation\" topic?\n\nThanks.\n"},{"id":"527160","messageId":"2ddab74c-6d42-4ef4-b7b1-783f1631bdba@app.fastmail.com","threadId":"64163","inReplyTo":"xmqqa52komwf.fsf@gitster.g","subject":"Re: [PATCH v2 0/2] doc: git-push: clarify section","fromName":"Julia Evans","fromEmail":"julia@jvns.ca","sentAt":"2025-09-23T22:10:09Z","receivedAt":"2025-09-23T22:10:31Z","isPatch":true,"sender":{"key":"julia@jvns.ca","avatar":"https://avatars.githubusercontent.com/u/817739?v=4"},"body":"> It is somewhat awkward to have two outstanding sets of patches to\n> the same file under the same theme from the same author.  How would\n> this relate to the other \"git push documentation\" topic?\n\nI split this one out because they were logically separate and I was concerned that\ntogether they were becoming unmanageable. \n\nHappy to re-combine them if you'd prefer.\n\n> Thanks.\n"},{"id":"527162","messageId":"xmqq5xd8ojfx.fsf@gitster.g","threadId":"64163","inReplyTo":"2ddab74c-6d42-4ef4-b7b1-783f1631bdba@app.fastmail.com","subject":"Re: [PATCH v2 0/2] doc: git-push: clarify section","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-23T23:09:06Z","receivedAt":"2025-09-23T23:09:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Julia Evans\" <julia@jvns.ca> writes:\n\n>> It is somewhat awkward to have two outstanding sets of patches to\n>> the same file under the same theme from the same author.  How would\n>> this relate to the other \"git push documentation\" topic?\n>\n> I split this one out because they were logically separate and I was concerned that\n> together they were becoming unmanageable. \n\nOh as long as they can independently advance and join cleanly, it is\nfine.  It would be easier on the reviewers to keep each of them\nsmall.\n\nThanks.\n"},{"id":"527332","messageId":"xmqq1pnuikuf.fsf@gitster.g","threadId":"64163","inReplyTo":"pull.1973.v2.git.1758651049.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 0/2] doc: git-push: clarify section","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-25T15:59:52Z","receivedAt":"2025-09-25T15:59:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Julia Evans via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> I don't love the nested list in PUSH RULES but the sentence starting with\n> \"If the source is a tag or commit object...\" is really a tough one to read,\n> it's not going to be relevant to the vast majority of people, and I think\n> keeping it contained inside a bullet point will make it much easier to skip\n> over to get to later information which is more likely to be relevant to\n> folks.\n> ...\n> Changes in v2:\n>\n>  * Say just \"The source can be any object.\" and don't try to educate folks\n>    further about tags, from Junio's review\n>  * Mention both exceptions to --force working, from Junio's review\n>  * Change \"local branch or commit being pushed\" => \"commit being pushed\" to\n>    make it clear that it's just 1 condition, from Junio's review\n>  * Remove an awkward double colon, from Junio's review\n>  * Be explicit that creations are always allowed, from Junio's revew\n\nNothing questionable stood out during my re-read of these two\npatches.\n\nFurther comments from other reviewers are always welcome, but we\nfind needs for immediate improvements, let's mark the topic for\n'next'.  Thanks.\n"}]}