git: support simple push

Summary:
Support pushing to a git repo using `git push` command.
This is implemented in remotenames' `push`, since that's
what used in production. The vanilla `push` is unchanged.

The `%include builtin:git.rc` in hgrc should have remotenames
enabled.

Reviewed By: DurhamG

Differential Revision: D33351380

fbshipit-source-id: f1e2dfd64168b83d1cf608c0490e56634688da15
This commit is contained in:
Jun Wu 2022-01-20 10:19:33 -08:00 committed by Facebook GitHub Bot
parent 2209a1c032
commit 471b5a49a4
4 changed files with 155 additions and 1 deletions

View File

@ -109,6 +109,10 @@ def extsetup(ui):
def _push(orig, ui, repo, dest=None, *args, **opts): def _push(orig, ui, repo, dest=None, *args, **opts):
# use the original push logic to handle "no default path" case.
if "default" not in repo.ui.paths:
return orig(ui, repo, dest, *args, **opts)
bookmark = opts.get("to") or "" bookmark = opts.get("to") or ""
create = opts.get("create") or False create = opts.get("create") or False

View File

@ -28,6 +28,7 @@ import re
import shutil import shutil
import typing import typing
from edenscm import tracing
from edenscm.mercurial import ( from edenscm.mercurial import (
bookmarks, bookmarks,
commands, commands,
@ -36,6 +37,7 @@ from edenscm.mercurial import (
error, error,
exchange, exchange,
extensions, extensions,
git,
hg, hg,
localrepo, localrepo,
mutation, mutation,
@ -784,7 +786,53 @@ def _getrebasedest(repo, opts):
return tracking.get(active) return tracking.get(active)
def _guesspushtobookmark(repo, pushnode, remotename):
"""try to guess the "push --to" bookmark name
Find the remote name that starts with "{remotename}/" that does not have
another remotename descandant, and can fast-forward to pushnode (aka. is
an ancestor of node) with the least distance.
Return the name that can be used as "push --to", or None if there are no
unique choice.
"""
prefix = "%s/" % remotename
remotenamenodes = [
nodes[0] for nodes in repo._remotenames["bookmarks"].values() if len(nodes) == 1
]
candidatenodes = set(
repo.dageval(lambda: parents(only([pushnode], remotenamenodes)))
)
candidates = [
(name[len(prefix) :], nodes[0])
for name, nodes in repo._remotenames["bookmarks"].items()
if name.startswith(prefix) and len(nodes) == 1 and nodes[0] in candidatenodes
]
names = [item[0] for item in candidates]
if len(names) == 1:
return names[0]
tracing.debug("candidates of --to: %r" % (names,))
return None
def expushcmd(orig, ui, repo, dest=None, **opts): def expushcmd(orig, ui, repo, dest=None, **opts):
if git.isgit(repo):
force = opts.get("force")
delete = opts.get("delete")
if dest == "default":
dest = None
dest = dest or "origin"
if delete:
pushnode = None
to = delete
else:
revspec = (["."] + opts.get("bookmark", []) + opts.get("rev", []))[-1]
pushnode = scmutil.revsingle(repo, revspec).node()
to = opts.get("to") or _guesspushtobookmark(repo, pushnode, dest)
if not to:
raise error.Abort(_("use '--to' to specify destination bookmark"))
return git.push(repo, dest, pushnode, to, force=force)
# during the upgrade from old to new remotenames, tooling that uses --force # during the upgrade from old to new remotenames, tooling that uses --force
# will continue working if remotenames.forcecompat is enabled # will continue working if remotenames.forcecompat is enabled
forcecompat = ui.configbool("remotenames", "forcecompat") forcecompat = ui.configbool("remotenames", "forcecompat")

View File

@ -17,6 +17,7 @@ from edenscm import tracing
from . import error, util from . import error, util
from .i18n import _ from .i18n import _
from .node import hex
GIT_DIR_FILE = "gitdir" GIT_DIR_FILE = "gitdir"
GIT_REQUIREMENT = "git" GIT_REQUIREMENT = "git"
@ -195,6 +196,24 @@ def postpullupdate(repo, node=None):
return hg.updatetotally(repo.ui, repo, node, None) return hg.updatetotally(repo.ui, repo, node, None)
def push(repo, dest, pushnode, to, force=False):
"""Push "pushnode" to remote "dest" bookmark "to"
If force is True, enable non-fast-forward moves.
If pushnode is None, delete the remote bookmark.
"""
if pushnode is None:
fromspec = ""
elif force:
fromspec = "+%s" % hex(pushnode)
else:
fromspec = "%s" % hex(pushnode)
refspec = "%s:refs/heads/%s" % (fromspec, to)
ret = rungit(repo, ["push", "-u", dest, refspec])
repo.invalidatechangelog()
return ret
def callgit(repo, args): def callgit(repo, args):
"""Run git command in the backing git repo, return its output""" """Run git command in the backing git repo, return its output"""
gitdir = readgitdir(repo) gitdir = readgitdir(repo)

View File

@ -1,4 +1,5 @@
#chg-compatible #chg-compatible
#require git no-windows
$ export GIT_AUTHOR_NAME='test' $ export GIT_AUTHOR_NAME='test'
$ export GIT_AUTHOR_EMAIL='test@example.org' $ export GIT_AUTHOR_EMAIL='test@example.org'
@ -6,7 +7,7 @@
$ export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME" $ export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"
$ export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL" $ export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
$ export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" $ export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
$ setconfig diff.git=true $ setconfig diff.git=true ui.allowemptycommit=true
$ unset GIT_DIR $ unset GIT_DIR
Prepare a git repo: Prepare a git repo:
@ -206,3 +207,85 @@ Test clone with flags (--noupdate, --updaterev):
5c9a5ee451a8 origin/foo alpha3 5c9a5ee451a8 origin/foo alpha3
$ cd .. $ cd ..
Test push:
$ cd "$TESTTMP/clonetest/cloned1"
$ echo 3 > beta
$ hg commit -m 'beta.change'
- --to without -r
$ hg push -q --to book_change_beta
- --to with -r
$ hg push -r '.^' --to parent_change_beta
To file:/*/$TESTTMP/gitrepo (glob)
* [new branch] 5c9a5ee451a8051f0d16433dee8a2c2259d5fed8 -> parent_change_beta
$ hg log -r '.^+.' -T '{desc} {remotenames}\n'
alpha3 origin/foo origin/parent_change_beta
beta.change origin/book_change_beta
- delete bookmark
$ hg push --delete book_change_beta
To file:/*/$TESTTMP/gitrepo (glob)
- [deleted] book_change_beta
$ hg log -r '.^+.' -T '{desc} {remotenames}\n'
alpha3 origin/foo origin/parent_change_beta
beta.change
- infinitepush compatibility
$ hg push -q -r '.^' --to push_with_infinitepush --config extensions.infinitepush=
- push with --force
$ cd "$TESTTMP"
$ git init -qb main --bare "pushforce.git"
$ hg clone "git+file://$TESTTMP/pushforce.git"
$ cd pushforce
$ git --git-dir=.hg/store/git config advice.pushUpdateRejected false
$ drawdag << 'EOS'
> B C
> |/
> A
> EOS
$ hg push -qr $B --to foo
$ hg push -qr $C --to foo
To file:/*/$TESTTMP/pushforce.git (glob)
! [rejected] 5d38a953d58b0c80a4416ba62e62d3f2985a3726 -> foo (non-fast-forward)
error: failed to push some refs to 'file:/*/$TESTTMP/pushforce.git' (glob)
[1]
$ hg push -qr $C --to foo --force
- push without --to
$ cd "$TESTTMP"
$ git init -qb main --bare "pushto.git"
$ hg clone "git+file://$TESTTMP/pushto.git"
$ cd pushto
$ drawdag << 'EOS'
> B
> |
> A
> EOS
$ hg push -qr $A --to stable
$ hg push -qr $B --to main
$ hg up -q $B
$ hg commit -m C
(pick "main" automatically)
$ hg push
To file:/*/$TESTTMP/pushto.git (glob)
0de3093..a9d5bd6 a9d5bd6ac8bcf89de9cd99fd215cca243e8aeed9 -> main
$ hg push -q --to stable
(cannot pick with multiple candidates)
$ hg commit -m D
$ hg push
abort: use '--to' to specify destination bookmark
[255]