import: use dirstateguard instead of dirstate.invalidate
Before this patch, "commands.import()" uses "dirstate.invalidate()" as
a kind of "restore .hg/dirstate to the original status" during a failure.
But it just discards changes in memory, and doesn't actually restore
".hg/dirstate". Then, it can't work as expected, if "dirstate.write()"
is executed while processing.
This patch uses "dirstateguard" instead of "dirstate.invalidate()" to
restore ".hg/dirstate" at failure even if "dirstate.write()" is
executed before failure.
This patch also removes "beginparentchage()" and "endparentchange()",
because "dirstateguard" makes them useless, too.
This is a part of preparations to fix the issue that the recent (in
memory) dirstate isn't visible to external process (e.g. "precommit"
hook).
# Helper script used for generating history and working copy files and content.
# The file's name corresponds to its history. The number of changesets can
# be specified on the command line. With 2 changesets, files with names like
# content1_content2_content1-untracked are generated. The first two filename
# segments describe the contents in the two changesets. The third segment
# ("content1-untracked") describes the state in the working copy, i.e.
# the file has content "content1" and is untracked (since it was previously
# tracked, it has been forgotten).
#
# This script generates the filenames and their content, but it's up to the
# caller to tell hg about the state.
#
# There are two subcommands:
# filelist <numchangesets>
# state <numchangesets> (<changeset>|wc)
#
# Typical usage:
#
# $ python $TESTDIR/generate-working-copy-states.py state 2 1
# $ hg addremove --similarity 0
# $ hg commit -m 'first'
#
# $ python $TESTDIR/generate-working-copy-states.py state 2 1
# $ hg addremove --similarity 0
# $ hg commit -m 'second'
#
# $ python $TESTDIR/generate-working-copy-states.py state 2 wc
# $ hg addremove --similarity 0
# $ hg forget *_*_*-untracked
# $ rm *_*_missing-*
import sys
import os
# Generates pairs of (filename, contents), where 'contents' is a list
# describing the file's content at each revision (or in the working copy).
# At each revision, it is either None or the file's actual content. When not
# None, it may be either new content or the same content as an earlier
# revisions, so all of (modified,clean,added,removed) can be tested.
def generatestates(maxchangesets, parentcontents):
depth = len(parentcontents)
if depth == maxchangesets + 1:
for tracked in ('untracked', 'tracked'):
filename = "_".join([(content is None and 'missing' or content) for
content in parentcontents]) + "-" + tracked
yield (filename, parentcontents)
else:
for content in (set([None, 'content' + str(depth + 1)]) |
set(parentcontents)):
for combination in generatestates(maxchangesets,
parentcontents + [content]):
yield combination
# retrieve the command line arguments
target = sys.argv[1]
maxchangesets = int(sys.argv[2])
if target == 'state':
depth = sys.argv[3]
# sort to make sure we have stable output
combinations = sorted(generatestates(maxchangesets, []))
# compute file content
content = []
for filename, states in combinations:
if target == 'filelist':
print filename
elif target == 'state':
if depth == 'wc':
# Make sure there is content so the file gets written and can be
# tracked. It will be deleted outside of this script.
content.append((filename, states[maxchangesets] or 'TOBEDELETED'))
else:
content.append((filename, states[int(depth) - 1]))
else:
print >> sys.stderr, "unknown target:", target
sys.exit(1)
# write actual content
for filename, data in content:
if data is not None:
f = open(filename, 'wb')
f.write(data + '\n')
f.close()
elif os.path.exists(filename):
os.remove(filename)