push --force-with-lease by default
Asked Answered
E

7

183

I just learned about git push --force-with-lease. It's pretty awesome. But, of course, I don't use force that often, and so I'm worried that I might forget about this nifty feature the next time I need it.

Is there a way to configure git so git push -f will automatically use --force-with-lease unless I intentionally override it with --no-force-with-lease?

(I can't imagine ever wanting to use force without lease!)

Erasmoerasmus answered 30/5, 2015 at 5:9 Comment(0)
C
187

There currently is no way to configure git to always use force-with-lease instead of force. As such the next best available option is, as so often, to create an alias which serves this purpose.

EDIT This is still true in May 2022. But git 2.30 added an additional option force-if-includes which makes force-with-lease even safer; checkout this in-depth answer if you want to understand the reasoning.

Create an alias

To create an alias one would use git config --global alias.<alias-name> <command>, in our case I would suggest something similar to this.

git config --global alias.pushfwl "push --force-with-lease"

This will create an entry in your global .gitconfig file (which you can usually find in your home directory). After this you can use git pushfwl to force-with-lease.

Get your hands dirty

Alternatively you could decide to implement this feature yourself! If you aren't sure where to start, you might want to take a look at the documentation directory in the git repository. Here you can find the coding guidelines and information on how to submit patches.

You can find all these links and more on the official community page.

Cletacleti answered 1/6, 2015 at 6:54 Comment(2)
A note on this not being a feature: the common argument against rewriting standard commands ("push --force") is that you get used to them, forget their origin, and one day accidentally use them in that way on a new system. Much like aliasing rm to rm -i in your .bashrc; you'll forget and delete an important file on a server some day. Going with your own alias doesn't have that problem :)Clarindaclarine
Personal anecdote/word of caution: I tried aliasing it to pushf but always double-checked that I was not doing a push -f, due to it looking similar to the alias. Some team members were using push -f anyways, thinking that the alias was just a cosmetical shorthand for it. Ultimately, we aliased the safer form to pushfl instead and stopped worrying about it.Buchmanism
C
53

My solution was to create a wrapper script, and use an alias so that I always use it in place of the real git.

Whenever I try to git push -f, I see the following:

⚡ git push -f
use this instead so you don't cause race conditions in the 
repo: git push --force-with-lease

Some advantages of this script are:

  • it trains me to habitually use --force-with-lease, so i don't get nagged when i get it wrong
  • if, for some reason, we really do need to force push, git push --force will work.

How to implement it:

  1. create a custom script that will pass through any params to git, except for -f
  2. alias that script so we use it instead of git

These instructions assume Linux or Mac, running bash. I haven't tried this with zsh or Windows, but I assume it'll work there too.

~/.bash_profile:

alias git=~/.git_wrapper.sh

~./git_wrapper.sh:

#!/bin/bash
for arg in "$@"; do
    if [ "$arg" = "push" ]; then
        ispush=1
    elif [ "$ispush" = 1 -a "$arg" = '-f' ]; then
        echo "use this instead so you don't cause race conflicts in the repo: git push --force-with-lease"
        exit 1
    fi
done

git "$@"

With those changes, restart your terminal and git should now get uppity when you try to force push.

Chlorophyll answered 29/11, 2017 at 1:20 Comment(2)
That seems convenient. +1. Maybe replace "hey idiot" by "hey, you gentle but simple soul" or somehing like that ;)Calamite
there should never be any case where you really need to use git push --force because as long as you do a git fetch before, you will always be able to do git push --force-with-lease .Horsefly
C
37

2020: I mention in "Is git push --force-with-lease always safe?" the new option

git push --force-if-includes

Add a check to verify if the remote-tracking ref of the local branch is reachable from one of its "reflog" entries.

Alexey Romanov refers in the comments to:
"When should I use "git push --force-if-includes", which adds more details.


2017:

I'm worried that I might forget about this nifty feature the next time I need it.

Git 2.13 (Q2 2017) explains why there is no "protection" against this push option being forgotten, because even if you do not forget it at the git push level, it might still be ignored.

See commit f17d642 (19 Apr 2017) by Ævar Arnfjörð Bjarmason (avar).
(Merged by Junio C Hamano -- gitster -- in commit 46bdfa3, 26 Apr 2017)

push: document & test --force-with-lease with multiple remotes

Document & test for cases where there are two remotes pointing to the same URL, and a background fetch & subsequent git push --force-with-lease shouldn't clobber un-updated references we haven't fetched.

Some editors like Microsoft's VSC have a feature to auto-fetch in the background, this bypasses the protections offered by --force-with-lease & --force-with-lease=<refname>, as noted in the documentation being added here.

So the documentation for git push now includes:

general note on safety: supplying this option without an expected value, i.e. as --force-with-lease or --force-with-lease=<refname> interacts very badly with anything that implicitly runs git fetch on the remote to be pushed to in the background, e.g. git fetch origin on your repository in a cronjob.

The protection it offers over --force is ensuring that subsequent changes your work wasn't based on aren't clobbered, but this is trivially defeated if some background process is updating refs in the background. We don't have anything except the remote tracking info to go by as a heuristic for refs you're expected to have seen & are willing to clobber.

If your editor or some other system is running git fetch in the background for you, a way to mitigate this is to simply set up another remote:

git remote add origin-push $(git config remote.origin.url)
git fetch origin-push

Now when the background process runs git fetch origin the references on origin-push won't be updated, and thus commands like:

git push --force-with-lease origin-push

Will fail unless you manually run git fetch origin-push.
This method is of course entirely defeated by something that runs git fetch --all, in that case you'd need to either disable it or do something more tedious like:

git fetch              # update 'master' from remote
git tag base master    # mark our base point
git rebase -i master   # rewrite some commits
git push --force-with-lease=master:base master:master

I.e. create a base tag for versions of the upstream code that you've seen and are willing to overwrite, then rewrite history, and finally force push changes to master if the remote version is still at base, regardless of what your local remotes/origin/master has been updated to in the background.

Calamite answered 1/5, 2017 at 20:35 Comment(2)
In case someone else runs into this answer: since Git 2.30 there is --force-if-includes to help with this problem of --force-with-lease stackoverflow.com/questions/65837109/…Singlefoot
@AlexeyRomanov Thank you for your feedback. I (finally) managed to included your comment in the answer for more visibility.Calamite
R
8

I want to be reminded that I shouldn't use -f, but I don't want to be fooled into believing that -f means --force-with-lease. So this is my take:

git() {
  if [[ $@ == 'push -f'* || $@ == 'push --force '*  ]]; then
    echo Hey stupid, use --force-with-lease instead
  else
    command git "$@"
  fi
}

Add to your .bash_profile, .bashrc or .zshrc.

Rozamond answered 11/12, 2019 at 10:42 Comment(0)
A
6

For people using OMYZSH you can simply use ggfl.

Avertin answered 13/12, 2018 at 13:30 Comment(1)
or gpf (note: you need to ensure the git plugin is enabled in ~/.zshrc)Microbicide
O
6

You can create a bash function that replaces git and use --force-with-lease instead of --force

# replaces `git push --force` with `git push --force-with-lease`
git() {
  if [[ $@ == 'push -f'* || $@ == 'push --force'* ]]; then
    command git push --force-with-lease
  else
    command git "$@"
  fi
}

or, in one line:

git() { if [[ $@ == 'push -f'* || $@ == 'push --force'* ]]; then command git push --force-with-lease; else command git "$@"; fi; }

Just add it to your ~/.bashrc or ~/.zshrc.

Oreopithecus answered 5/12, 2019 at 14:23 Comment(3)
This one is dangerous: It will drop all arguments to git push -fRozamond
One could imagine a safer version of this that iterates through the arguments, swapping "-f" or "--force" for "--force-with-lease", and propagating all other args.Coston
I managed to make a version that replaces the -f|--force argument anywhere and keep the others (fn() { if [[ $1 == 'push' ]]; then command echo fn "${@/(-f|--force)/--force-with-lease}"; else command echo fn "$@"; fi; }) but I couldn't make it replace the whole word, so it replaces --follow-tags with ---force-with-leaseollow-tags. Any suggestions?Oreopithecus
S
1

Since there is still no option to use force-with-lease by default, I combined paulodiovani's suggestion of redefining git as a shell function, neu242's improvement of training me into a good habit, and Jessica Knight's better job of detecting the force switch, to come up with the following for my ~/.bash_aliases file:

function git () {
  local ispush
  ispush=0
  local isforce
  isforce=0
  for arg in "$@"; do
    if [[ "$arg" == "push" ]]; then
      ispush=1
    fi
    if [ "$ispush" -eq 1 ]; then
      if [[ "$arg" == '--force' ]] || [[ "$arg" =~ ^\-[^\-]*f.*$ ]]; then
        isforce=1
      fi
    fi
    if [ $isforce -eq 1 ]; then
      >&2 echo "Please use '--force-with-lease' instead of '--force'."
      return 1
    fi
  done
  command git "$@"
}

This works in either bash or zsh, passes shellcheck muster, and even detects force when -f is combined with other short switches (e.g. -vf).

Shillyshally answered 1/3 at 23:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.