hgext/closehead.py
author Raphaël Gomès <rgomes@octobus.net>
Wed, 19 Oct 2022 12:38:06 +0200
changeset 49568 da48f170d203
parent 48875 6000f5b25c9b
child 50873 259213382862
permissions -rw-r--r--
rust-status: query fs traversal metadata lazily Currently, any time the status algorithm needs to read a directory from the filesystem (because the stat-only optimization is not available), it also stats each directory entry eagerly. Stat'ing the entries is only needed in a few cases (like when checking the mtime of a directory for caching): this patch creates a wrapper struct `DirEntry` that only stats the directory entry it represents when needed. Excerpt of an `strace` before this change on Mozilla Central: ``` openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3 newfstatat(3, "", {st_mode=S_IFDIR|0755, st_size=3540, ...}, AT_EMPTY_PATH) = 0 getdents64(3, 0x55dc970bd440 /* 139 entries */, 32768) = 5072 statx(3, ".hg", AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFDIR|0755, stx_size=772, ...}) = 0 [... 135 other successful `statx` calls] getdents64(3, 0x55dc970bd440 /* 0 entries */, 32768) = 0 close(3) = 0 ``` After this change: ``` openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3 newfstatat(3, "", {st_mode=S_IFDIR|0755, st_size=3540, ...}, AT_EMPTY_PATH) = 0 getdents64(3, 0x561567c10190 /* 139 entries */, 32768) = 5072 getdents64(3, 0x561567c10190 /* 0 entries */, 32768) = 0 close(3) = 0 ```

# closehead.py - Close arbitrary heads without checking them out first
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.

'''close arbitrary heads without checking them out first'''


from mercurial.i18n import _
from mercurial import (
    bookmarks,
    cmdutil,
    context,
    error,
    logcmdutil,
    pycompat,
    registrar,
)

cmdtable = {}
command = registrar.command(cmdtable)
# Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
# be specifying the version(s) of Mercurial they are tested with, or
# leave the attribute unspecified.
testedwith = b'ships-with-hg-core'

commitopts = cmdutil.commitopts
commitopts2 = cmdutil.commitopts2
commitopts3 = [(b'r', b'rev', [], _(b'revision to check'), _(b'REV'))]


@command(
    b'close-head|close-heads',
    commitopts + commitopts2 + commitopts3,
    _(b'[OPTION]... [REV]...'),
    helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
    inferrepo=True,
)
def close_branch(ui, repo, *revs, **opts):
    """close the given head revisions

    This is equivalent to checking out each revision in a clean tree and running
    ``hg commit --close-branch``, except that it doesn't change the working
    directory.

    The commit message must be specified with -l or -m.
    """

    def docommit(rev):
        cctx = context.memctx(
            repo,
            parents=[rev, None],
            text=message,
            files=[],
            filectxfn=None,
            user=opts.get(b'user'),
            date=opts.get(b'date'),
            extra=extra,
        )
        tr = repo.transaction(b'commit')
        ret = repo.commitctx(cctx, True)
        bookmarks.update(repo, [rev, None], ret)
        cctx.markcommitted(ret)
        tr.close()

    opts = pycompat.byteskwargs(opts)

    revs += tuple(opts.get(b'rev', []))
    revs = logcmdutil.revrange(repo, revs)

    if not revs:
        raise error.Abort(_(b'no revisions specified'))

    heads = []
    for branch in repo.branchmap():
        heads.extend(repo.branchheads(branch))
    heads = {repo[h].rev() for h in heads}
    for rev in revs:
        if rev not in heads:
            raise error.Abort(_(b'revision is not an open head: %d') % rev)

    message = cmdutil.logmessage(ui, opts)
    if not message:
        raise error.Abort(_(b"no commit message specified with -l or -m"))
    extra = {b'close': b'1'}

    with repo.wlock(), repo.lock():
        for rev in revs:
            r = repo[rev]
            branch = r.branch()
            extra[b'branch'] = branch
            docommit(r)
    return 0