@@ -400,3 +400,73 @@
searching for changes
no changes found
[1]
+
+
+Test `push.on-dirty-working-copy`
+---------------------------------
+
+ $ cat >> $HGRCPATH << EOF
+ > [extensions]
+ > drawdag=$TESTDIR/drawdag.py
+ > EOF
+ $ cd $TESTTMP
+ $ mkdir dirty-working-copy
+ $ cd dirty-working-copy
+ $ hg init source
+ $ cat >> source/.hg/hgrc << EOF
+ > [phases]
+ > publish=false
+ > EOF
+ $ hg clone source dest
+ updating to branch default
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd dest
+ $ hg debugdrawdag << 'EOF'
+ > D
+ > |
+ > F C
+ > | |
+ > E B
+ > |/
+ > A
+ > EOF
+ $ cat >> .hg/hgrc << EOF
+ > [push]
+ > on-dirty-working-copy=abort
+ > EOF
+# Push all commits just to make the output from further pushes consistently
+# "no changes found".
+ $ hg push -q -r 'head()'
+ $ hg co C
+ 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ echo modified >> A
+# Cannot push the parent commit with a dirty working copy
+ $ hg push -q -r .
+ abort: won't push with dirty working directory
+ (maybe you meant to commit or amend the changes; if you want to push
+ anyway, use --allow-dirty, or set on-dirty-working-copy=ignore in
+ the [push] section of your ~/.hgrc)
+ [20]
+# Cannot push a descendant either
+ $ hg push -q -r D
+ abort: won't push with dirty working directory
+ (maybe you meant to commit or amend the changes; if you want to push
+ anyway, use --allow-dirty, or set on-dirty-working-copy=ignore in
+ the [push] section of your ~/.hgrc)
+ [20]
+# Typo in config value results in default behavior (which is to allow push)
+ $ hg push --config push.on-dirty-working-copy=abirt -q -r .
+ [1]
+# Can override config
+ $ hg push -q -r . --allow-dirty
+ [1]
+# Can push an ancestor
+ $ hg push -q -r B
+ [1]
+# Can push a sibling
+ $ hg push -q -r F
+ [1]
+# Can push descendant if the working copy parent is public
+ $ hg phase -p
+ $ hg push -q -r D
+ [1]
@@ -362,7 +362,7 @@
phase: public, draft, secret, force, rev
pull: update, force, confirm, rev, bookmark, branch, ssh, remotecmd, insecure
purge: abort-on-err, all, ignored, dirs, files, print, print0, confirm, include, exclude
- push: force, rev, bookmark, all-bookmarks, branch, new-branch, pushvars, publish, ssh, remotecmd, insecure
+ push: force, rev, bookmark, all-bookmarks, branch, new-branch, pushvars, publish, allow-dirty, ssh, remotecmd, insecure
recover: verify
remove: after, force, subrepos, include, exclude, dry-run
rename: forget, after, at-rev, force, include, exclude, dry-run
@@ -1869,6 +1869,12 @@
default=False,
)
coreconfigitem(
+ b'push',
+ b'on-dirty-working-copy',
+ default=b"ignore",
+ experimental=True,
+)
+coreconfigitem(
b'rewrite',
b'backup-bundle',
default=True,
@@ -5633,6 +5633,15 @@
False,
_(b'push the changeset as public (EXPERIMENTAL)'),
),
+ (
+ b'',
+ b'allow-dirty',
+ False,
+ _(
+ b'allow pushing with a dirty working copy, overriding '
+ b'push.on-dirty-working-copy=abort (EXPERIMENTAL)'
+ ),
+ ),
]
+ remoteopts,
_(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]...'),
@@ -5735,8 +5744,10 @@
try:
if revs:
- revs = [repo[r].node() for r in logcmdutil.revrange(repo, revs)]
- if not revs:
+ nodes = [
+ repo[r].node() for r in logcmdutil.revrange(repo, revs)
+ ]
+ if not nodes:
raise error.InputError(
_(b"specified revisions evaluate to an empty set"),
hint=_(b"use different revision arguments"),
@@ -5746,8 +5757,8 @@
# to DAG heads to make discovery simpler.
expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
revs = scmutil.revrange(repo, [expr])
- revs = [repo[rev].node() for rev in revs]
- if not revs:
+ nodes = [repo[rev].node() for rev in revs]
+ if not nodes:
raise error.InputError(
_(
b'default push revset for path evaluates to an empty set'
@@ -5758,6 +5769,10 @@
_(b'no revisions specified to push'),
hint=_(b'did you mean "hg push -r ."?'),
)
+ else:
+ nodes = None
+ if nodes and not opts.get(b'allow_dirty'):
+ cmdutil.check_push_dirty_wc(repo, nodes)
repo._subtoppath = dest
try:
@@ -5780,7 +5795,7 @@
repo,
other,
opts.get(b'force'),
- revs=revs,
+ revs=nodes,
newbranch=opts.get(b'new_branch'),
bookmarks=opts.get(b'bookmark', ()),
publish=opts.get(b'publish'),
@@ -1112,6 +1112,26 @@
ctx.sub(s).bailifchanged(hint=hint)
+def check_push_dirty_wc(repo, nodes):
+ """Checks that `.` is not being pushed if the working copy is dirty."""
+ if repo.ui.config(b'push', b'on-dirty-working-copy') == b'abort':
+ # We assume that commits back to the public ancestors will be pushed.
+ if repo.revs('only(%ln, public()) & parents()', nodes):
+ # Covers both pending working directory state and merges.
+ try:
+ bailifchanged(repo)
+ except error.Abort:
+ hint = (
+ b'maybe you meant to commit or amend the changes; if '
+ b'you want to push\nanyway, use --allow-dirty, or set '
+ b'on-dirty-working-copy=ignore in\nthe [push] section '
+ b'of your ~/.hgrc'
+ )
+ raise error.StateError(
+ b"won't push with dirty working directory", hint=_(hint)
+ )
+
+
def logmessage(ui, opts):
"""get the log message according to -m and -l option"""