view hgext/graphlog.py @ 25759:ff11c1565c04

cmdutil: apply dirstate.normallookup on (maybe partially) committed files To detect change of a file without redundant comparison of file content, dirstate recognizes a file as certainly clean, if: (1) it is already known as "normal", (2) dirstate entry for it has valid (= not "-1") timestamp, and (3) mode, size and timestamp of it on the filesystem are as same as ones expected in dirstate This works as expected in many cases, but doesn't in the corner case that changing a file keeps mode, size and timestamp of it on the filesystem. The timetable below shows steps in one of typical such situations: ---- ----------------------------------- ---------------- timestamp of "f" ---------------- dirstate file- time action mem file system ---- ----------------------------------- ---- ----- ----- N *** *** - change "f" N - execute 'hg commit -i' - backup "f" with timestamp N - revert "f" by 'merge.update()' N with 'partially' - apply selected hunks N by 'patch.patch()' - 'repo.commit()' - 'dirstate.normal("f")' N N+1 - 'dirstate.write()' N N - restore "f" N+1 - restore timestamp of "f" N - 'hg status' shows "f" as "clean" N N N ---- ----------------------------------- ---- ----- ----- The most important point is that 'dirstate.write()' is executed at N+1 or later. This causes writing dirstate timestamp N of "f" out successfully. If it is executed at N, 'parsers.pack_dirstate()' replaces timestamp N with "-1" before actual writing dirstate out. This issue can occur when 'hg commit -i' satisfies conditions below: - the file is committed partially, and - mode and size of the file aren't changed before and after committing The root cause of this issue is that (maybe partially changed) files are restored with original timestamp but dirstate isn't updated for them. To detect changes of files correctly, this patch applies 'dirstate.normallookup()' on restored files. Status check is needed before 'dirstate.normallookup()', because status other than "n(ormal)" should be kept at failure of committing. This patch doesn't examine whether each files are committed fully or partially, because interactive hunk selection makes it difficult. After this change, timetable is changed as below: ---- ----------------------------------- ---------------- timestamp of "f" ---------------- dirstate file- time action mem file system ---- ----------------------------------- ---- ----- ----- N *** *** - change "f" N - execute 'hg commit -i' - backup "f" with timestamp N - revert "f" by 'merge.update()' N with 'partially' - apply selected hunks N by 'patch.internalpatch()' - 'repo.commit()' - 'dirstate.normal("f")' N N+1 - 'dirstate.write()' N N - restore "f" N+1 - restore timestamp of "f" N ----------------------------------- ---- ----- ----- - normallookup("f") -1 - release wlock - 'dirstate.write()' -1 -1 N ----------------------------------- ---- ----- ----- - 'hg status' shows "f" as "clean" -1 -1 N ---- ----------------------------------- ---- ----- ----- To reproduce this issue in tests certainly, this patch emulates some timing critical actions as below: - change "f" at N 'touch -t 200001010000' before command invocation changes mtime of "f" to "2000-01-01 00:00" (= N). - apply selected hunks at N 'patch.internalpatch()' with 'fakepatchtime.py' explicitly changes mtime of patched files to "2000-01-01 00:00" (= N). - 'dirstate.write()' at N+1 (or "not at N") 'pack_dirstate()' uses actual timestamp at runtime as "now", and it should be different from the "2000-01-01 00:00" of "f". BTW, in 'test-commit-interactive.t', files are sometimes treated as modified , even though they are just committed fully via 'hg commit -i' and 'hg diff' shows nothing for them. Enabling win32text causes EOL style mismatching below: - files are changed in LF style EOL => files restored after committing uses LF style EOL (1) - 'merge.update()' reverts files in CRLF style EOL - 'patch.internalpatch()' changes files in CRLF style EOL => 'dirstate.normal()' via 'repo.commit()' uses the size of files in CRLF style EOL (2) Therefore, fully committed files are treated as "modified", because 'lstat()' returns size of (1) restored files in LF style EOL, but dirstate expects size of (2) committed files in CRLF style EOL. After this patch, 'dirstate.normallookup()' on committed files forces subsequent 'hg status' to examine changes exactly, and fully committed files are treated as clean as expected. This is reason why this patch also does: - add some 'hg status' checking status of fully committed files - clear win32text configuration before size/timestamp sensitive examination
author FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
date Wed, 08 Jul 2015 17:07:45 +0900
parents 80c5b2666a96
children 2f804a38351e
line wrap: on
line source

# ASCII graph log extension for Mercurial
#
# Copyright 2007 Joel Rosdahl <joel@rosdahl.net>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.

'''command to view revision graphs from a shell (DEPRECATED)

The functionality of this extension has been include in core Mercurial
since version 2.3.

This extension adds a --graph option to the incoming, outgoing and log
commands. When this options is given, an ASCII representation of the
revision graph is also shown.
'''

from mercurial.i18n import _
from mercurial import cmdutil, commands

cmdtable = {}
command = cmdutil.command(cmdtable)
# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'

@command('glog',
    [('f', 'follow', None,
     _('follow changeset history, or file history across copies and renames')),
    ('', 'follow-first', None,
     _('only follow the first parent of merge changesets (DEPRECATED)')),
    ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
    ('C', 'copies', None, _('show copied files')),
    ('k', 'keyword', [],
     _('do case-insensitive search for a given text'), _('TEXT')),
    ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
    ('', 'removed', None, _('include revisions where files were removed')),
    ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
    ('u', 'user', [], _('revisions committed by user'), _('USER')),
    ('', 'only-branch', [],
     _('show only changesets within the given named branch (DEPRECATED)'),
     _('BRANCH')),
    ('b', 'branch', [],
     _('show changesets within the given named branch'), _('BRANCH')),
    ('P', 'prune', [],
     _('do not display revision or any of its ancestors'), _('REV')),
    ] + commands.logopts + commands.walkopts,
    _('[OPTION]... [FILE]'),
    inferrepo=True)
def graphlog(ui, repo, *pats, **opts):
    """show revision history alongside an ASCII revision graph

    Print a revision history alongside a revision graph drawn with
    ASCII characters.

    Nodes printed as an @ character are parents of the working
    directory.
    """
    opts['graph'] = True
    return commands.log(ui, repo, *pats, **opts)