From 8be0554d02e147ddf8a033e8bb7b30417bf4d501 Mon Sep 17 00:00:00 2001 From: Julia Evans Date: Wed, 3 Sep 2025 16:41:19 -0400 Subject: [PATCH 1/2] doc: git-push: create PUSH RULES section Right now the rules for when a `git push` is allowed are buried at the bottom of the description of ``. Put them in their own section so that we can reference them from `--force` and give some context for why they exist. Having the "PUSH RULES" section also lets us be a little bit more specific with the rule in `--force`: we can just focus on the rule for pushing for a branch (which is likely the one that's most relevant) and leave the details about what happens when you push to a tag or a ref that isn't a branch to the later section. Signed-off-by: Julia Evans --- Documentation/git-push.adoc | 94 ++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 49 deletions(-) diff --git a/Documentation/git-push.adoc b/Documentation/git-push.adoc index d1978650d60a7c..4faf915f94dd14 100644 --- a/Documentation/git-push.adoc +++ b/Documentation/git-push.adoc @@ -91,48 +91,6 @@ is ambiguous. configuration (see linkgit:git-config[1]) suggest what refs/ namespace you may have wanted to push to. --- -+ -The object referenced by is used to update the reference -on the remote side. Whether this is allowed depends on where in -`refs/*` the reference lives as described in detail below, in -those sections "update" means any modifications except deletes, which -as noted after the next few sections are treated differently. -+ -The `refs/heads/*` namespace will only accept commit objects, and -updates only if they can be fast-forwarded. -+ -The `refs/tags/*` namespace will accept any kind of object (as -commits, trees and blobs can be tagged), and any updates to them will -be rejected. -+ -It's possible to push any type of object to any namespace outside of -`refs/{tags,heads}/*`. In the case of tags and commits, these will be -treated as if they were the commits inside `refs/heads/*` for the -purposes of whether the update is allowed. -+ -I.e. a fast-forward of commits and tags outside `refs/{tags,heads}/*` -is allowed, even in cases where what's being fast-forwarded is not a -commit, but a tag object which happens to point to a new commit which -is a fast-forward of the commit the last tag (or commit) it's -replacing. Replacing a tag with an entirely different tag is also -allowed, if it points to the same commit, as well as pushing a peeled -tag, i.e. pushing the commit that existing tag object points to, or a -new tag object which an existing commit points to. -+ -Tree and blob objects outside of `refs/{tags,heads}/*` will be treated -the same way as if they were inside `refs/tags/*`, any update of them -will be rejected. -+ -All of the rules described above about what's not allowed as an update -can be overridden by adding an the optional leading `+` to a refspec -(or using `--force` command line option). The only exception to this -is that no amount of forcing will make the `refs/heads/*` namespace -accept a non-commit object. Hooks and configuration can also override -or amend these rules, see e.g. `receive.denyNonFastForwards` in -linkgit:git-config[1] and `pre-receive` and `update` in -linkgit:githooks[5]. -+ Pushing an empty allows you to delete the ref from the remote repository. Deletions are always accepted without a leading `+` in the refspec (or `--force`), except when forbidden by configuration @@ -145,6 +103,7 @@ the local side, the remote side is updated if a branch of the same name already exists on the remote side. + `tag ` means the same as `refs/tags/:refs/tags/`. +Not all updates are allowed: see PUSH RULES below for the details. --all:: --branches:: @@ -332,14 +291,12 @@ allowing a forced update. -f:: --force:: - Usually, the command refuses to update a remote ref that is - not an ancestor of the local ref used to overwrite it. - Also, when `--force-with-lease` option is used, the command refuses - to update a remote ref whose current value does not match - what is expected. + Usually, `git push` will refuse to update a branch that is not an + ancestor of the commit being pushed. + -This flag disables these checks, and can cause the remote repository -to lose commits; use it with care. +This flag disables that check, the other safety checks in PUSH RULES +below, and the checks in --force-with-lease. It can cause the remote +repository to lose commits; use it with care. + Note that `--force` applies to all the refs that are pushed, hence using it with `push.default` set to `matching` or with multiple push @@ -508,6 +465,45 @@ reason:: refs, no explanation is needed. For a failed ref, the reason for failure is described. +PUSH RULES +---------- + +As a safety feature, the `git push` command only allows certain kinds of +updates to prevent you from accidentally losing data on the remote. + +Because branches and tags are intended to be used differently, the +safety rules for pushing to a branch are different from the rules +for pushing to a tag. In the following rules "update" means any +modifications except deletions and creations. Deletions and creations +are always allowed, except when forbidden by configuration or hooks. + +1. If the push destination is a **branch** (`refs/heads/*`): only + fast-forward updates are allowed, which means the destination must be + an ancestor of the source commit. The source must be a commit. +2. If the push destination is a **tag** (`refs/tags/*`): all updates will + be rejected. The source can be any object. +3. If the push destination is not a branch or tag: + * If the source is a tree or blob object, any updates will be rejected + * If the source is a tag or commit object, any fast-forward update + is allowed, even in cases where what's being fast-forwarded is not a + commit, but a tag object which happens to point to a new commit which + is a fast-forward of the commit the last tag (or commit) it's + replacing. Replacing a tag with an entirely different tag is also + allowed, if it points to the same commit, as well as pushing a peeled + tag, i.e. pushing the commit that existing tag object points to, or a + new tag object which an existing commit points to. + +You can override these rules by passing `--force` or by adding the +optional leading `+` to a refspec. The only exceptions are that no +amount of forcing will make a branch accept a non-commit object, +and forcing won't make the remote repository accept a push that it's +configured to deny. + +Hooks and configuration can also override or amend these rules, +see e.g. `receive.denyNonFastForwards` and `receive.denyDeletes` +in linkgit:git-config[1] and `pre-receive` and `update` in +linkgit:githooks[5]. + NOTE ABOUT FAST-FORWARDS ------------------------ From 11ad190c3ea457352a7f3a589bebba97d20f7ee8 Mon Sep 17 00:00:00 2001 From: Julia Evans Date: Mon, 25 Aug 2025 16:22:54 -0400 Subject: [PATCH 2/2] doc: git-push: rewrite refspec specification From user feedback, there was a request for examples, as well as a comment that one person found "If git push [] without any argument is set to update some ref at the destination with with remote..push configuration variable..." impossible to understand. To make the section easier to navigate, create a list of every possible refspec form, with examples for each form as well as 2 forms which were previously missing (patterns and negative refspecs). Made a few changes to use more familiar language, but ultimately refspecs are a pretty advanced feature so I've mostly left the terminology alone. Signed-off-by: Julia Evans --- Documentation/git-push.adoc | 105 ++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 47 deletions(-) diff --git a/Documentation/git-push.adoc b/Documentation/git-push.adoc index 4faf915f94dd14..ff125297b01316 100644 --- a/Documentation/git-push.adoc +++ b/Documentation/git-push.adoc @@ -55,54 +55,65 @@ OPTIONS[[OPTIONS]] ...:: Specify what destination ref to update with what source object. - The format of a parameter is an optional plus - `+`, followed by the source object , followed - by a colon `:`, followed by the destination ref . -+ -The is often the name of the branch you would want to push, but -it can be any arbitrary "SHA-1 expression", such as `master~4` or -`HEAD` (see linkgit:gitrevisions[7]). -+ -The tells which ref on the remote side is updated with this -push. Arbitrary expressions cannot be used here, an actual ref must -be named. -If `git push []` without any `` argument is set to -update some ref at the destination with `` with -`remote..push` configuration variable, `:` part can -be omitted--such a push will update a ref that `` normally updates -without any `` on the command line. Otherwise, missing -`:` means to update the same ref as the ``. -+ -If doesn't start with `refs/` (e.g. `refs/heads/master`) we will -try to infer where in `refs/*` on the destination it -belongs based on the type of being pushed and whether -is ambiguous. + --- -* If unambiguously refers to a ref on the remote, - then push to that ref. - -* If resolves to a ref starting with refs/heads/ or refs/tags/, - then prepend that to . - -* Other ambiguity resolutions might be added in the future, but for - now any other cases will error out with an error indicating what we - tried, and depending on the `advice.pushUnqualifiedRefname` - configuration (see linkgit:git-config[1]) suggest what refs/ - namespace you may have wanted to push to. - -Pushing an empty allows you to delete the ref from the -remote repository. Deletions are always accepted without a leading `+` -in the refspec (or `--force`), except when forbidden by configuration -or hooks. See `receive.denyDeletes` in linkgit:git-config[1] and -`pre-receive` and `update` in linkgit:githooks[5]. -+ -The special refspec `:` (or `+:` to allow non-fast-forward updates) -directs Git to push "matching" branches: for every branch that exists on -the local side, the remote side is updated if a branch of the same name -already exists on the remote side. -+ -`tag ` means the same as `refs/tags/:refs/tags/`. +The format for a refspec is [+][:], for example `main`, +`main:other`, or `HEAD^:refs/heads/main`. ++ +The `` is often the name of the local branch to push, but it can be +any arbitrary "SHA-1 expression" (see linkgit:gitrevisions[7]). ++ +The `` determines what ref to update on the remote side. It must be the +name of a branch, tag, or other ref, not an arbitrary expression. ++ +The `+` is optional and does the same thing as `--force`. ++ +You can write a refspec using the fully expanded form (for +example `refs/heads/main:refs/heads/main`) which specifies the exact source +and destination, or with a shorter form (for example `main` or +`main:other`). Here are the rules for how refspecs are expanded, +as well as various other special refspec forms: ++ + * `` without a `:` means to update the same ref as the + ``, unless the `remote..push` configuration specifies a + different . For example, if `main` is a branch, then the refspec + `main` expands to `main:refs/heads/main`. + * If `` unambiguously refers to a ref on the remote, + then expand it to that ref. For example, if `v1.0` is a tag on the + remote, then `HEAD:v1.0` expands to `HEAD:refs/tags/v1.0`. + * If `` resolves to a ref starting with `refs/heads/` or `refs/tags/`, + then prepend that to . For example, if `main` is a branch, then + `main:other` expands to `main:refs/heads/other` + * The special refspec `:` (or `+:` to allow non-fast-forward updates) + directs Git to push "matching" branches: for every branch that exists on + the local side, the remote side is updated if a branch of the same name + already exists on the remote side. + * may contain a * to indicate a simple pattern match. + This works like a glob that matches any ref matching the pattern. + There must be only one * in both the `` and ``. + It will map refs to the destination by replacing the * with the + contents matched from the source. For example, `refs/heads/*:refs/heads/*` + will push all branches. + * A refspec starting with `^` is a negative refspec. + This specifies refs to exclude. A ref will be considered to + match if it matches at least one positive refspec, and does not + match any negative refspec. Negative refspecs can be pattern refspecs. + They must only contain a ``. + Fully spelled out hex object names are also not supported. + For example, `git push origin 'refs/heads/*' '^refs/heads/dev-*'` + will push all branches except for those starting with `dev-` + * If `` is empty, it deletes the `` ref from the remote + repository. For example, `git push origin :dev` will + delete the `dev` branch. + * `tag ` expands to `refs/tags/:refs/tags/`. + This is technically a special syntax for `git push` and not a refspec, + since in `git push origin tag v1.0` the arguments `tag` and `v1.0` + are separate. + * If the refspec can't be expanded unambiguously, error out + with an error indicating what was tried, and depending + on the `advice.pushUnqualifiedRefname` configuration (see + linkgit:git-config[1]) suggest what refs/ namespace you may have + wanted to push to. + Not all updates are allowed: see PUSH RULES below for the details. --all::