largefiles: synchronize lfdirstate with dirstate after automated committing
Before this patch, after successful "hg rebase" of the revision
removing largefiles, "hg status" may still show ""R" for such
largefiles unexpectedly.
"lfilesrepo.commit" executes the special code path for automated
committing while rebase/transplant, and lfdirstate entries for removed
files aren't updated in this code path, even after successful
committing.
Then, "R" entries still existing in lfdirstate cause unexpected "hg
status" output.
This patch synchronizes lfdirstate with dirstate after automated
committing.
This patch passes False as "normallookup" to "synclfdirstate", because
modified files in "files()" of the recent (= just committed) context
should be "normal"-ed.
This is a temporary way to fix with less changes. For fundamental
resolution of this kind of problems in the future, lfdirstate should
be updated with dirstate simultaneously. Hooking "markcommitted" of
ctx in "localrepository.commitctx" may achieve this.
This problem occurs, only when (1) the parent of the working directory
is rebased and (2) it removes largefiles, because:
- if the parent of the working directory isn't rebased, returning to
the initial revision (= update) after rebase hides this problem
- files added on "other" branch (= rebase target) are treated not as
"added" but as "modified" (= "normal" status and "unset"
timestamp) at merging
This patch tests also the status of added largefile, but it is only
for avoiding regression.
In addition to conditions above, "hg status" must not take existing
files to reproduce this problem, because existing files make
"match._files" not empty in "lfilesrepo.status" code path below:
def sfindirstate(f):
sf = lfutil.standin(f)
dirstate = self.dirstate
return sf in dirstate or sf in dirstate.dirs()
match._files = [f for f in match._files
if sfindirstate(f)]
Not empty "match._files" prevents "status" on lfdirstate from
returning the result containing problematic "R" files.
This is reason why "large1" (removed) and "largeX" (added) are checked
separately in this patch.
Problematic code path in "lfilesrepo.commit" is used also by "hg
transplant", but this problem doesn't occur at "hg transplant",
because invocation of "updatelfiles" after transplant-ing in
"overridetransplant" causes cleaning lfdirstate up.
This patch tests also "hg transplant" as same as "hg rebase", but it
is only for avoiding regression.
# fancyopts.py - better command line parsing
#
# Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
import getopt
import util
from i18n import _
def gnugetopt(args, options, longoptions):
"""Parse options mostly like getopt.gnu_getopt.
This is different from getopt.gnu_getopt in that an argument of - will
become an argument of - instead of vanishing completely.
"""
extraargs = []
if '--' in args:
stopindex = args.index('--')
extraargs = args[stopindex + 1:]
args = args[:stopindex]
opts, parseargs = getopt.getopt(args, options, longoptions)
args = []
while parseargs:
arg = parseargs.pop(0)
if arg and arg[0] == '-' and len(arg) > 1:
parseargs.insert(0, arg)
topts, newparseargs = getopt.getopt(parseargs, options, longoptions)
opts = opts + topts
parseargs = newparseargs
else:
args.append(arg)
args.extend(extraargs)
return opts, args
def fancyopts(args, options, state, gnu=False):
"""
read args, parse options, and store options in state
each option is a tuple of:
short option or ''
long option
default value
description
option value label(optional)
option types include:
boolean or none - option sets variable in state to true
string - parameter string is stored in state
list - parameter string is added to a list
integer - parameter strings is stored as int
function - call function with parameter
non-option args are returned
"""
namelist = []
shortlist = ''
argmap = {}
defmap = {}
for option in options:
if len(option) == 5:
short, name, default, comment, dummy = option
else:
short, name, default, comment = option
# convert opts to getopt format
oname = name
name = name.replace('-', '_')
argmap['-' + short] = argmap['--' + oname] = name
defmap[name] = default
# copy defaults to state
if isinstance(default, list):
state[name] = default[:]
elif callable(default):
state[name] = None
else:
state[name] = default
# does it take a parameter?
if not (default is None or default is True or default is False):
if short:
short += ':'
if oname:
oname += '='
if short:
shortlist += short
if name:
namelist.append(oname)
# parse arguments
if gnu:
parse = gnugetopt
else:
parse = getopt.getopt
opts, args = parse(args, shortlist, namelist)
# transfer result to state
for opt, val in opts:
name = argmap[opt]
t = type(defmap[name])
if t is type(fancyopts):
state[name] = defmap[name](val)
elif t is type(1):
try:
state[name] = int(val)
except ValueError:
raise util.Abort(_('invalid value %r for option %s, '
'expected int') % (val, opt))
elif t is type(''):
state[name] = val
elif t is type([]):
state[name].append(val)
elif t is type(None) or t is type(False):
state[name] = True
# return unparsed args
return args