diff --git a/hgext3rd/tweakdefaults.py b/hgext3rd/tweakdefaults.py
--- a/hgext3rd/tweakdefaults.py
+++ b/hgext3rd/tweakdefaults.py
@@ -228,6 +228,8 @@
 
 def reposetup(ui, repo):
     _fixpager(ui)
+    # Allow uncommit on dirty working directory
+    repo.ui.setconfig('experimental', 'uncommitondirtywdir', True)
 
 def tweakorder():
     """
diff --git a/hgext3rd/uncommit.py b/hgext3rd/uncommit.py
deleted file mode 100644
--- a/hgext3rd/uncommit.py
+++ /dev/null
@@ -1,176 +0,0 @@
-# uncommit - undo the actions of a commit
-#
-# Copyright 2011 Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
-#                Logilab SA        <contact@logilab.fr>
-#                Pierre-Yves David <pierre-yves.david@ens-lyon.org>
-#                Patrick Mezard <patrick@mezard.eu>
-# Copyright 2016 Facebook, Inc.
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2 or any later version.
-
-"""uncommit some or all of a local changeset
-
-This command undoes the effect of a local commit, returning the affected
-files to their uncommitted state. This means that files modified or
-deleted in the changeset will be left unchanged, and so will remain modified in
-the working directory.
-"""
-
-from mercurial import (
-    phases,
-    obsolete,
-    commands,
-    error,
-    scmutil,
-    copies,
-    context,
-    node,
-    registrar,
-)
-from mercurial.i18n import _
-
-cmdtable = {}
-command = registrar.command(cmdtable)
-
-testedwith = 'ships-with-fb-hgext'
-
-def _updatebookmarks(repo, oldid, newid, tr):
-    oldbookmarks = repo.nodebookmarks(oldid)
-    if oldbookmarks:
-        changes = []
-        for b in oldbookmarks:
-            changes.append((b, newid))
-        repo._bookmarks.applychanges(repo, tr, changes)
-
-def _commitfiltered(repo, ctx, match):
-    """Recommit ctx with changed files not in match. Return the new
-    node identifier, or None if nothing changed.
-    """
-    base = ctx.p1()
-    # ctx
-    initialfiles = set(ctx.files())
-    exclude = set(f for f in initialfiles if match(f))
-
-    # No files matched commit, so nothing excluded
-    if not exclude:
-        return None
-
-    files = (initialfiles - exclude)
-    if not files and not repo.ui.configbool('ui', 'allowemptycommit'):
-        return ctx.parents()[0].node()
-
-    # Filter copies
-    copied = copies.pathcopies(base, ctx)
-    copied = dict((dst, src) for dst, src in copied.iteritems()
-                  if dst in files)
-    def filectxfn(repo, memctx, path, contentctx=ctx, redirect=()):
-        if path not in contentctx:
-            return None
-        fctx = contentctx[path]
-        mctx = context.memfilectx(repo, fctx.path(), fctx.data(),
-                                  fctx.islink(),
-                                  fctx.isexec(),
-                                  copied=copied.get(path))
-        return mctx
-
-    new = context.memctx(repo,
-                         parents=[base.node(), node.nullid],
-                         text=ctx.description(),
-                         files=files,
-                         filectxfn=filectxfn,
-                         user=ctx.user(),
-                         date=ctx.date(),
-                         extra=ctx.extra())
-    newid = repo.commitctx(new)
-    return newid
-
-def _uncommitdirstate(repo, oldctx, match):
-    """Fix the dirstate after switching the working directory from
-    oldctx to a copy of oldctx not containing changed files matched by
-    match.
-    """
-    ctx = repo['.']
-    ds = repo.dirstate
-    copies = dict(ds.copies())
-    m, a, r = repo.status(oldctx.p1(), oldctx, match=match)[:3]
-    for f in m:
-        if ds[f] == 'r':
-            # modified + removed -> removed
-            continue
-        ds.normallookup(f)
-
-    for f in a:
-        if ds[f] == 'r':
-            # added + removed -> unknown
-            ds.drop(f)
-        elif ds[f] != 'a':
-            ds.add(f)
-
-    for f in r:
-        if ds[f] == 'a':
-            # removed + added -> normal
-            ds.normallookup(f)
-        elif ds[f] != 'r':
-            ds.remove(f)
-
-    # Merge old parent and old working dir copies
-    oldcopies = {}
-    for f in (m + a):
-        src = oldctx[f].renamed()
-        if src:
-            oldcopies[f] = src[0]
-    oldcopies.update(copies)
-    copies = dict((dst, oldcopies.get(src, src))
-                  for dst, src in oldcopies.iteritems())
-    # Adjust the dirstate copies
-    for dst, src in copies.iteritems():
-        if (src not in ctx or dst in ctx or ds[dst] != 'a'):
-            src = None
-        ds.copy(src, dst)
-
-@command('uncommit',
-    commands.walkopts,
-    _('[OPTION]... [FILE]...'))
-def uncommit(ui, repo, *pats, **opts):
-    """uncommit some or all of a local changeset
-
-    This command undoes the effect of a local commit, returning the affected
-    files to their uncommitted state. This means that files modified or
-    deleted in the changeset will be left unchanged, and so will remain
-    modified in the working directory.
-    """
-
-    with repo.wlock(), repo.lock():
-        wctx = repo[None]
-
-        if len(wctx.parents()) <= 0 or not wctx.parents()[0]:
-            raise error.Abort(_("cannot uncommit null changeset"))
-        if len(wctx.parents()) > 1:
-            raise error.Abort(_("cannot uncommit while merging"))
-        old = repo['.']
-        oldphase = old.phase()
-        if oldphase == phases.public:
-            raise error.Abort(_("cannot rewrite immutable changeset"))
-        if len(old.parents()) > 1:
-            raise error.Abort(_("cannot uncommit merge changeset"))
-
-        with repo.transaction('uncommit') as tr:
-            match = scmutil.match(old, pats, opts)
-            newid = _commitfiltered(repo, old, match)
-            if newid is None:
-                raise error.Abort(_('nothing to uncommit'))
-
-            if newid != old.p1().node():
-                # Move local changes on filtered changeset
-                obsolete.createmarkers(repo, [(old, (repo[newid],))])
-                phases.retractboundary(repo, tr, oldphase, [newid])
-            else:
-                # Fully removed the old commit
-                obsolete.createmarkers(repo, [(old, ())])
-
-            with repo.dirstate.parentchange():
-                repo.dirstate.setparents(newid, node.nullid)
-                _uncommitdirstate(repo, old, match)
-
-            _updatebookmarks(repo, old.node(), newid, tr)
diff --git a/tests/test-uncommit.t b/tests/test-uncommit.t
deleted file mode 100644
--- a/tests/test-uncommit.t
+++ /dev/null
@@ -1,225 +0,0 @@
-Test uncommit - set up the config
-
-  $ cat >> $HGRCPATH <<EOF
-  > [experimental]
-  > evolution=createmarkers
-  > [extensions]
-  > uncommit = $TESTDIR/../hgext3rd/uncommit.py
-  > drawdag=$RUNTESTDIR/drawdag.py
-  > EOF
-
-Build up a repo
-
-  $ hg init repo
-  $ cd repo
-
-Uncommit with no commits should fail
-
-  $ hg uncommit
-  abort: cannot uncommit null changeset
-  [255]
-
-Create some commits
-
-  $ touch files
-  $ hg add files
-  $ for i in a ab abc abcd abcde; do echo $i > files; echo $i > file-$i; hg add file-$i; hg commit -m "added file-$i"; done
-  $ ls
-  file-a
-  file-ab
-  file-abc
-  file-abcd
-  file-abcde
-  files
-  $ hg log -G -T '{rev}:{node} {desc}' --hidden
-  @  4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
-  |
-  o  3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
-  |
-  o  2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
-  |
-  o  1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
-  |
-  o  0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
-  
-Simple uncommit off the top
-
-  $ hg uncommit
-  $ hg status
-  M files
-  A file-abcde
-  $ hg heads -T '{rev}:{node} {desc}'
-  3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd (no-eol)
-
-Recommit
-
-  $ hg commit -m 'new change abcde'
-  $ hg status
-  $ hg heads -T '{rev}:{node} {desc}'
-  5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde (no-eol)
-
-Uncommit of non-existent and unchanged files has no effect
-  $ hg uncommit nothinghere
-  abort: nothing to uncommit
-  [255]
-  $ hg status
-  $ hg uncommit file-abc
-  abort: nothing to uncommit
-  [255]
-  $ hg status
-
-Try partial uncommit
-
-  $ hg uncommit files
-  $ hg status
-  M files
-  $ hg heads -T '{rev}:{node} {desc}'
-  6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde (no-eol)
-  $ hg log -r . -p -T '{rev}:{node} {desc}'
-  6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcdediff -r 6db330d65db4 -r 3727deee06f7 file-abcde
-  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-  +++ b/file-abcde	Thu Jan 01 00:00:00 1970 +0000
-  @@ -0,0 +1,1 @@
-  +abcde
-  
-  $ hg commit -m 'update files for abcde'
-
-Uncommit with dirty state
-
-  $ echo "foo" >> files
-  $ cat files
-  abcde
-  foo
-  $ hg status
-  M files
-  $ hg uncommit files
-  $ cat files
-  abcde
-  foo
-  $ hg commit -m "files abcde + foo"
-
-Uncommit in the middle of a stack
-
-  $ hg checkout '.^^^'
-  1 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  $ hg log -r . -p -T '{rev}:{node} {desc}'
-  2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abcdiff -r 69a232e754b0 -r abf2df566fc1 file-abc
-  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-  +++ b/file-abc	Thu Jan 01 00:00:00 1970 +0000
-  @@ -0,0 +1,1 @@
-  +abc
-  diff -r 69a232e754b0 -r abf2df566fc1 files
-  --- a/files	Thu Jan 01 00:00:00 1970 +0000
-  +++ b/files	Thu Jan 01 00:00:00 1970 +0000
-  @@ -1,1 +1,1 @@
-  -ab
-  +abc
-  
-  $ hg uncommit
-  $ hg status
-  M files
-  A file-abc
-  $ hg heads -T '{rev}:{node} {desc}'
-  8:83815831694b1271e9f207cb1b79b2b19275edcb files abcde + foo (no-eol)
-  $ hg commit -m 'new abc'
-  created new head
-
-Partial uncommit in the middle
-
-  $ hg checkout '.^'
-  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ hg log -r . -p -T '{rev}:{node} {desc}'
-  1:69a232e754b08d568c4899475faf2eb44b857802 added file-abdiff -r 3004d2d9b508 -r 69a232e754b0 file-ab
-  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-  +++ b/file-ab	Thu Jan 01 00:00:00 1970 +0000
-  @@ -0,0 +1,1 @@
-  +ab
-  diff -r 3004d2d9b508 -r 69a232e754b0 files
-  --- a/files	Thu Jan 01 00:00:00 1970 +0000
-  +++ b/files	Thu Jan 01 00:00:00 1970 +0000
-  @@ -1,1 +1,1 @@
-  -a
-  +ab
-  
-  $ hg uncommit file-ab
-  $ hg status
-  A file-ab
-  $ hg heads -T '{rev}:{node} {desc}'
-  10:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab9:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc8:83815831694b1271e9f207cb1b79b2b19275edcb files abcde + foo (no-eol)
-  $ hg commit -m 'update ab'
-  $ hg status
-  $ hg heads -T '{rev}:{node} {desc}'
-  11:f21039c59242b085491bb58f591afc4ed1c04c09 update ab9:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc8:83815831694b1271e9f207cb1b79b2b19275edcb files abcde + foo (no-eol)
-  $ hg log -G -T '{rev}:{node} {desc}' --hidden
-  @  11:f21039c59242b085491bb58f591afc4ed1c04c09 update ab
-  |
-  o  10:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab
-  |
-  | o  9:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
-  | |
-  | | o  8:83815831694b1271e9f207cb1b79b2b19275edcb files abcde + foo
-  | | |
-  | | | x  7:0977fa602c2fd7d8427ed4e7ee15ea13b84c9173 update files for abcde
-  | | |/
-  | | o  6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde
-  | | |
-  | | | x  5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde
-  | | |/
-  | | | x  4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
-  | | |/
-  | | o  3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
-  | | |
-  | | x  2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
-  | |/
-  | x  1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
-  |/
-  o  0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
-  
-Uncommit with draft parent
-
-  $ hg uncommit
-  $ hg phase -r .
-  10: draft
-  $ hg commit -m 'update ab again'
-
-Uncommit with public parent
-
-  $ hg phase -p "::.^"
-  $ hg uncommit
-  $ hg phase -r .
-  10: public
-
-Partial uncommit with public parent
-
-  $ echo xyz > xyz
-  $ hg add xyz
-  $ hg commit -m "update ab and add xyz"
-  $ hg uncommit xyz
-  $ hg status
-  A xyz
-  $ hg phase -r .
-  14: draft
-  $ hg phase -r ".^"
-  10: public
-
-Uncommit leaving an empty changeset
-
-  $ cd $TESTTMP
-  $ hg init repo1
-  $ cd repo1
-  $ hg debugdrawdag <<'EOS'
-  > Q
-  > |
-  > P
-  > EOS
-  $ hg up Q -q
-  $ hg uncommit --config ui.allowemptycommit=1
-  $ hg log -G -T '{desc} FILES: {files}'
-  @  Q FILES:
-  |
-  | x  Q FILES: Q
-  |/
-  o  P FILES: P
-  
-  $ hg status
-  A Q