Patchwork [6,of,8] dirstate: create class for status lists

login
register
mail settings
Submitter Martin von Zweigbergk
Date Oct. 2, 2014, 12:01 a.m.
Message ID <048f3810ef38a85db4b2.1412208118@handduk2.mtv.corp.google.com>
Download mbox | patch
Permalink /patch/6078/
State Accepted
Headers show

Comments

Martin von Zweigbergk - Oct. 2, 2014, 12:01 a.m.
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@gmail.com>
# Date 1412204534 25200
#      Wed Oct 01 16:02:14 2014 -0700
# Node ID 048f3810ef38a85db4b265f020fe94fe6a12d103
# Parent  31cbb21f1283847c65e385bd94b81e2c78fd5121
dirstate: create class for status lists

Callers of various status() methods (on dirstate, context, repo) get a
tuple of 7 or 8 elements, where each element is a list of files. This
results in lots of uses of indexes where names would be much more
readable. For example, "status.ignored" seems clearer than "status[4]"
[1]. So, let's introduce a simple class containing the 7 status
fields: modified, added, removed, deleted, unknown, ignored, clean. In
the cases of dirstate.status(), which has an 8th element ('lookup'),
return a 2-tuple of the new status class and the lookup list.

Having a class for the status also lets us add methods to it. For
example, a status.haschanges() method turns out to be useful.


 [1] Did you even notice that it should have been "status[5]"?
Sean Farley - Oct. 2, 2014, 12:31 a.m.
Martin von Zweigbergk writes:

> # HG changeset patch
> # User Martin von Zweigbergk <martinvonz@gmail.com>
> # Date 1412204534 25200
> #      Wed Oct 01 16:02:14 2014 -0700
> # Node ID 048f3810ef38a85db4b265f020fe94fe6a12d103
> # Parent  31cbb21f1283847c65e385bd94b81e2c78fd5121
> dirstate: create class for status lists
>
> Callers of various status() methods (on dirstate, context, repo) get a
> tuple of 7 or 8 elements, where each element is a list of files. This
> results in lots of uses of indexes where names would be much more
> readable. For example, "status.ignored" seems clearer than "status[4]"
> [1]. So, let's introduce a simple class containing the 7 status
> fields: modified, added, removed, deleted, unknown, ignored, clean. In
> the cases of dirstate.status(), which has an 8th element ('lookup'),
> return a 2-tuple of the new status class and the lookup list.
>
> Having a class for the status also lets us add methods to it. For
> example, a status.haschanges() method turns out to be useful.

Interesting. Sid and I talked about this at the mini-sprint earlier this
year when I was ripping out status from localrepo.

It would be nice to simplify the status logic done in context.py because
it is confusing. For those that don't know, there is a (cached)
ctx.status() method which is different from ctx.status(rev). It's pretty
hairy.
Mads Kiilerich - Oct. 2, 2014, 1:40 a.m.
Comments added all over. I choose to send it now instead of polishing it 
and perhaps sending it tomorrow ;-)


On 10/02/2014 02:01 AM, Martin von Zweigbergk wrote:
> # HG changeset patch
> # User Martin von Zweigbergk <martinvonz@gmail.com>
> # Date 1412204534 25200
> #      Wed Oct 01 16:02:14 2014 -0700
> # Node ID 048f3810ef38a85db4b265f020fe94fe6a12d103
> # Parent  31cbb21f1283847c65e385bd94b81e2c78fd5121
> dirstate: create class for status lists

I'm spoiled by using Skype - that influence my vocabulary. I just want 
to say: (heart)

> Callers of various status() methods (on dirstate, context, repo) get a
> tuple of 7 or 8 elements, where each element is a list of files. This
> results in lots of uses of indexes where names would be much more
> readable. For example, "status.ignored" seems clearer than "status[4]"
> [1]. So, let's introduce a simple class containing the 7 status
> fields: modified, added, removed, deleted, unknown, ignored, clean. In
> the cases of dirstate.status(), which has an 8th element ('lookup'),
> return a 2-tuple of the new status class and the lookup list.
>
> Having a class for the status also lets us add methods to it. For
> example, a status.haschanges() method turns out to be useful.
>
>
>   [1] Did you even notice that it should have been "status[5]"?

;-)


A general comment: A "." lookup is relatively expensive. It seems like 
you introduce a lot of them. We should take care to not introduce more 
of them in tight loops (that might already be the case - I did not check 
and not see any statement on whether that was the case). I also think 
you should consider make the diff smaller by starting out with assigning 
the values to the old local variables (you already do that in some 
places but it could perhaps be done in more).

> diff --git a/hgext/convert/hg.py b/hgext/convert/hg.py
> --- a/hgext/convert/hg.py
> +++ b/hgext/convert/hg.py
> @@ -371,7 +371,8 @@
>               if self._changescache[0] == rev:
>                   m, a, r = self._changescache[1]
>               else:
> -                m, a, r = self.repo.status(parents[0].node(), ctx.node())[:3]
> +                s = self.repo.status(parents[0].node(), ctx.node())
> +                m, a, r = s.modified, s.added, s.removed
>               if not full:
>                   files = m + a + r
>               copyfiles = m + a
> @@ -437,7 +438,8 @@
>               changes = [], ctx.manifest().keys(), []
>           else:
>               i = i or 0
> -            changes = self.repo.status(parents[i].node(), ctx.node())[:3]
> +            s = self.repo.status(parents[i].node(), ctx.node())
> +            changes = s.modified, s.added, s.removed
>           changes = [[f for f in l if f not in self.ignored] for l in changes]
>   
>           if i == 0:
> diff --git a/hgext/extdiff.py b/hgext/extdiff.py
> --- a/hgext/extdiff.py
> +++ b/hgext/extdiff.py
> @@ -143,9 +143,12 @@
>               do3way = False
>   
>       matcher = scmutil.match(repo[node2], pats, opts)
> -    mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher)[:3])
> +    st_a = repo.status(node1a, node2, matcher)
> +    mod_a, add_a, rem_a = map(set, (st_a.modified, st_a.added, st_a.removed))

This map do no longer make the code shorter or more readable.

>       if do3way:
> -        mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher)[:3])
> +        st_b = repo.status(node1b, node2, matcher)
> +        mod_b, add_b, rem_b = map(set,
> +                                  (st_b.modified, st_b.added, st_b.removed))
>       else:
>           mod_b, add_b, rem_b = set(), set(), set()
>       modadd = mod_a | add_a | mod_b | add_b
> diff --git a/hgext/gpg.py b/hgext/gpg.py
> --- a/hgext/gpg.py
> +++ b/hgext/gpg.py
> @@ -255,7 +255,7 @@
>   
>       msigs = match.exact(repo.root, '', ['.hgsigs'])
>       s = repo.status(match=msigs, unknown=True, ignored=True)
> -    if util.any(s) and not opts["force"]:
> +    if util.any(s.all()) and not opts["force"]:
>           raise util.Abort(_("working copy of .hgsigs is changed "
>                              "(please commit .hgsigs manually "
>                              "or use --force)"))
> diff --git a/hgext/hgcia.py b/hgext/hgcia.py
> --- a/hgext/hgcia.py
> +++ b/hgext/hgcia.py
> @@ -82,14 +82,14 @@
>           if url and url[-1] == '/':
>               url = url[:-1]
>           elems = []
> -        for path in f[0]:
> +        for path in f.modified:
>               uri = '%s/diff/%s/%s' % (url, short(n), path)
>               elems.append(self.fileelem(path, url and uri, 'modify'))
> -        for path in f[1]:
> +        for path in f.added:
>               # TODO: copy/rename ?
>               uri = '%s/file/%s/%s' % (url, short(n), path)
>               elems.append(self.fileelem(path, url and uri, 'add'))
> -        for path in f[2]:
> +        for path in f.removed:
>               elems.append(self.fileelem(path, '', 'remove'))
>   
>           return '\n'.join(elems)
> diff --git a/hgext/histedit.py b/hgext/histedit.py
> --- a/hgext/histedit.py
> +++ b/hgext/histedit.py
> @@ -688,8 +688,7 @@
>   
>       # Commit dirty working directory if necessary
>       new = None
> -    m, a, r, d = repo.status()[:4]
> -    if m or a or r or d:
> +    if repo.status().haschanges():
>           # prepare the message for the commit to comes
>           if action in ('f', 'fold', 'r', 'roll'):
>               message = 'fold-temp-revision %s' % currentnode
> diff --git a/hgext/keyword.py b/hgext/keyword.py
> --- a/hgext/keyword.py
> +++ b/hgext/keyword.py
> @@ -171,9 +171,8 @@
>       '''Retrieves modified and added files from a working directory state
>       and returns the subset of each contained in given changed files
>       retrieved from a change context.'''
> -    modified, added = wstatus[:2]
> -    modified = [f for f in modified if f in changed]
> -    added = [f for f in added if f in changed]
> +    modified = [f for f in wstatus.modified if f in changed]
> +    added = [f for f in wstatus.added if f in changed]
>       return modified, added
>   
>   
> @@ -349,10 +348,9 @@
>       wlock = repo.wlock()
>       try:
>           status = _status(ui, repo, wctx, kwt, *pats, **opts)
> -        modified, added, removed, deleted, unknown, ignored, clean = status
> -        if modified or added or removed or deleted:
> +        if status.haschanges():
>               raise util.Abort(_('outstanding uncommitted changes'))
> -        kwt.overwrite(wctx, clean, True, expand)
> +        kwt.overwrite(wctx, status.clean, True, expand)
>       finally:
>           wlock.release()
>   
> @@ -503,20 +501,19 @@
>       wctx = repo[None]
>       status = _status(ui, repo, wctx, kwt, *pats, **opts)
>       cwd = pats and repo.getcwd() or ''
> -    modified, added, removed, deleted, unknown, ignored, clean = status
>       files = []
>       if not opts.get('unknown') or opts.get('all'):
> -        files = sorted(modified + added + clean)
> +        files = sorted(status.modified + status.added + status.clean)
>       kwfiles = kwt.iskwfile(files, wctx)
> -    kwdeleted = kwt.iskwfile(deleted, wctx)
> -    kwunknown = kwt.iskwfile(unknown, wctx)
> +    kwdeleted = kwt.iskwfile(status.deleted, wctx)
> +    kwunknown = kwt.iskwfile(status.unknown, wctx)
>       if not opts.get('ignore') or opts.get('all'):
>           showfiles = kwfiles, kwdeleted, kwunknown
>       else:
>           showfiles = [], [], []
>       if opts.get('all') or opts.get('ignore'):
>           showfiles += ([f for f in files if f not in kwfiles],
> -                      [f for f in unknown if f not in kwunknown])
> +                      [f for f in status.unknown if f not in kwunknown])
>       kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split()
>       kwstates = zip(kwlabels, 'K!kIi', showfiles)
>       fm = ui.formatter('kwfiles', opts)
> diff --git a/hgext/largefiles/lfutil.py b/hgext/largefiles/lfutil.py
> --- a/hgext/largefiles/lfutil.py
> +++ b/hgext/largefiles/lfutil.py
> @@ -136,19 +136,18 @@
>   
>   def lfdirstatestatus(lfdirstate, repo, rev):
>       match = match_.always(repo.root, repo.getcwd())
> -    s = lfdirstate.status(match, [], False, False, False)
> -    unsure, modified, added, removed, missing, unknown, ignored, clean = s
> -    for lfile in unsure:
> +    (s, lookup) = lfdirstate.status(match, [], False, False, False)
> +    for lfile in lookup:

I don't think I like the renaming from unsure to lookup. Unsure 
describes the content of the list. Lookup is what we might want to do 
with it because of that.

Anyway, it seems like an unrelated change that could be done (and 
discussed) later.

I also wonder if it would make sense to make "unsure" an optional member 
of the status struct. I don't know.

>           try:
>               fctx = repo[rev][standin(lfile)]
>           except LookupError:
>               fctx = None
>           if not fctx or fctx.data().strip() != hashfile(repo.wjoin(lfile)):
> -            modified.append(lfile)
> +            s.modified.append(lfile)
>           else:
> -            clean.append(lfile)
> +            s.clean.append(lfile)
>               lfdirstate.normal(lfile)
> -    return (modified, added, removed, missing, unknown, ignored, clean)
> +    return s
>   
>   def listlfiles(repo, rev=None, matcher=None):
>       '''return a list of largefiles in the working copy or the
> diff --git a/hgext/largefiles/overrides.py b/hgext/largefiles/overrides.py
> --- a/hgext/largefiles/overrides.py
> +++ b/hgext/largefiles/overrides.py
> @@ -153,7 +153,8 @@
>       manifest = repo[None].manifest()
>       modified, added, deleted, clean = [[f for f in list
>                                           if lfutil.standin(f) in manifest]
> -                                       for list in [s[0], s[1], s[3], s[6]]]
> +                                       for list in (s.modified, s.added,
> +                                                    s.deleted, s.clean)]
>   
>       def warn(files, msg):
>           for f in files:
> @@ -353,13 +354,11 @@
>       wlock = repo.wlock()
>       try:
>           lfdirstate = lfutil.openlfdirstate(ui, repo)
> -        s = lfdirstate.status(match_.always(repo.root, repo.getcwd()),
> -            [], False, False, False)
> -        (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
> -
> +        (s, lookup) = lfdirstate.status(match_.always(repo.root, repo.getcwd()),
> +                                        [], False, False, False)
>           if opts['check']:
> -            mod = len(modified) > 0
> -            for lfile in unsure:
> +            mod = len(s.modified) > 0
> +            for lfile in lookup:
>                   standin = lfutil.standin(lfile)
>                   if repo['.'][standin].data().strip() != \
>                           lfutil.hashfile(repo.wjoin(lfile)):
> @@ -662,12 +661,11 @@
>       wlock = repo.wlock()
>       try:
>           lfdirstate = lfutil.openlfdirstate(ui, repo)
> -        (modified, added, removed, missing, unknown, ignored, clean) = \
> -            lfutil.lfdirstatestatus(lfdirstate, repo, repo['.'].rev())
> +        s = lfutil.lfdirstatestatus(lfdirstate, repo, repo['.'].rev())
>           lfdirstate.write()
> -        for lfile in modified:
> +        for lfile in s.modified:
>               lfutil.updatestandin(repo, lfutil.standin(lfile))
> -        for lfile in missing:
> +        for lfile in s.deleted:

The change would be easier to review if missing was renamed to deleted 
in a separate changeset. It might however not worth the trouble ...

>               if (os.path.exists(repo.wjoin(lfutil.standin(lfile)))):
>                   os.unlink(repo.wjoin(lfutil.standin(lfile)))
>   
> @@ -956,17 +954,15 @@
>   def overridebailifchanged(orig, repo):
>       orig(repo)
>       repo.lfstatus = True
> -    modified, added, removed, deleted = repo.status()[:4]
>       repo.lfstatus = False
> -    if modified or added or removed or deleted:
> +    if repo.status().haschanges():
>           raise util.Abort(_('uncommitted changes'))
>   
>   # Fetch doesn't use cmdutil.bailifchanged so override it to add the check
>   def overridefetch(orig, ui, repo, *pats, **opts):
>       repo.lfstatus = True
> -    modified, added, removed, deleted = repo.status()[:4]
>       repo.lfstatus = False
> -    if modified or added or removed or deleted:
> +    if repo.status().haschanges():
>           raise util.Abort(_('uncommitted changes'))
>       return orig(ui, repo, *pats, **opts)
>   
> @@ -981,7 +977,7 @@
>           s = repo.status(match=m, clean=True)
>       finally:
>           repo.lfstatus = False
> -    forget = sorted(s[0] + s[1] + s[3] + s[6])
> +    forget = sorted(s.modified + s.added + s.deleted + s.clean)
>       forget = [f for f in forget if lfutil.standin(f) in repo[None].manifest()]
>   
>       for f in forget:
> @@ -1112,16 +1108,15 @@
>           return orig(repo, pats, opts, dry_run, similarity)
>       # Get the list of missing largefiles so we can remove them
>       lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
> -    s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
> -        False, False)
> -    (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
> +    (s, lookup) = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [],
> +                                    False, False, False)
>   
>       # Call into the normal remove code, but the removing of the standin, we want
>       # to have handled by original addremove.  Monkey patching here makes sure
>       # we don't remove the standin in the largefiles code, preventing a very
>       # confused state later.
> -    if missing:
> -        m = [repo.wjoin(f) for f in missing]
> +    if s.deleted:
> +        m = [repo.wjoin(f) for f in s.deleted]
>           repo._isaddremove = True
>           removelargefiles(repo.ui, repo, *m, **opts)
>           repo._isaddremove = False
> @@ -1148,10 +1143,9 @@
>           r = oldstatus(node1, node2, match, ignored, clean, unknown,
>                         listsubrepos)
>           lfdirstate = lfutil.openlfdirstate(ui, repo)
> -        modified, added, removed, deleted, unknown, ignored, clean = r
> -        unknown = [f for f in unknown if lfdirstate[f] == '?']
> -        ignored = [f for f in ignored if lfdirstate[f] == '?']
> -        return modified, added, removed, deleted, unknown, ignored, clean
> +        r.unknown = [f for f in r.unknown if lfdirstate[f] == '?']
> +        r.ignored = [f for f in r.ignored if lfdirstate[f] == '?']
> +        return r
>       repo.status = overridestatus
>       orig(ui, repo, *dirs, **opts)
>       repo.status = oldstatus
> @@ -1290,10 +1284,10 @@
>               # update standins for linear-merge or force-branch-merge,
>               # because largefiles in the working directory may be modified
>               lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
> -            s = lfdirstate.status(match_.always(repo.root, repo.getcwd()),
> -                                  [], False, False, False)
> -            unsure, modified, added = s[:3]
> -            for lfile in unsure + modified + added:
> +            (s, lookup) = lfdirstate.status(match_.always(repo.root,
> +                                                          repo.getcwd()),
> +                                            [], False, False, False)
> +            for lfile in lookup + s.modified + s.added:
>                   lfutil.updatestandin(repo, lfutil.standin(lfile))
>   
>           if linearmerge:
> diff --git a/hgext/largefiles/reposetup.py b/hgext/largefiles/reposetup.py
> --- a/hgext/largefiles/reposetup.py
> +++ b/hgext/largefiles/reposetup.py
> @@ -5,6 +5,7 @@
>   #
>   # This software may be used and distributed according to the terms of the
>   # GNU General Public License version 2 or any later version.
> +from mercurial.dirstate import status

It is better to use   from mercurial import dirstate   and then use 
dirstate.status later on. Both to preserve the namespacing but also to 
allow demandimport to do its job.

>   '''setup for largefiles repositories: reposetup'''
>   import copy
> @@ -159,12 +160,13 @@
>                                       if sfindirstate(f)]
>                       # Don't waste time getting the ignored and unknown
>                       # files from lfdirstate
> -                    s = lfdirstate.status(match, [], False,
> -                            listclean, False)
> -                    (unsure, modified, added, removed, missing, _unknown,
> -                            _ignored, clean) = s
> +                    (s, lookup) = lfdirstate.status(match, [], False,
> +                                                    listclean, False)
> +                    modified = s.modified
> +                    added = s.added
> +                    clean = s.clean
>                       if parentworking:
> -                        for lfile in unsure:
> +                        for lfile in lookup:
>                               standin = lfutil.standin(lfile)
>                               if standin not in ctx1:
>                                   # from second parent
> @@ -177,7 +179,7 @@
>                                       clean.append(lfile)
>                                   lfdirstate.normal(lfile)
>                       else:
> -                        tocheck = unsure + modified + added + clean
> +                        tocheck = lookup + s.modified + s.added + s.clean
>                           modified, added, clean = [], [], []
>   
>                           for lfile in tocheck:
> @@ -191,6 +193,7 @@
>                               else:
>                                   added.append(lfile)
>   
> +                    removed = s.removed
>                       # Standins no longer found in lfdirstate has been
>                       # removed
>                       for standin in ctx1.walk(lfutil.getstandinmatcher(self)):
> @@ -200,9 +203,6 @@
>                           if lfile not in lfdirstate:
>                               removed.append(lfile)
>   
> -                    # Filter result lists
> -                    result = list(result)
> -
>                       # Largefiles are not really removed when they're
>                       # still in the normal dirstate. Likewise, normal
>                       # files are not really removed if they are still in
> @@ -210,19 +210,19 @@
>                       # change type.
>                       removed = [f for f in removed
>                                  if f not in self.dirstate]
> -                    result[2] = [f for f in result[2]
> +                    result.removed = [f for f in result.removed
>                                    if f not in lfdirstate]
>   
>                       lfiles = set(lfdirstate._map)
>                       # Unknown files
> -                    result[4] = set(result[4]).difference(lfiles)
> +                    result.unknown = set(result.unknown).difference(lfiles)
>                       # Ignored files
> -                    result[5] = set(result[5]).difference(lfiles)
> +                    result.ignored = set(result.ignored).difference(lfiles)
>                       # combine normal files and largefiles
>                       normals = [[fn for fn in filelist
>                                   if not lfutil.isstandin(fn)]
> -                               for filelist in result]
> -                    lfstatus = (modified, added, removed, missing, [], [],
> +                               for filelist in result.all()]
> +                    lfstatus = (modified, added, removed, s.deleted, [], [],
>                                   clean)
>                       result = [sorted(list1 + list2)
>                                 for (list1, list2) in zip(normals, lfstatus)]
> @@ -234,6 +234,8 @@
>                       result = [[toname(f) for f in items]
>                                 for items in result]
>   
> +                result = status(result[0], result[1], result[2], result[3],
> +                                result[4], result[5], result[6])
>                   if wlock:
>                       lfdirstate.write()
>   
> @@ -296,10 +298,9 @@
>                       # large.
>                       lfdirstate = lfutil.openlfdirstate(ui, self)
>                       dirtymatch = match_.always(self.root, self.getcwd())
> -                    s = lfdirstate.status(dirtymatch, [], False, False, False)
> -                    (unsure, modified, added, removed, _missing, _unknown,
> -                            _ignored, _clean) = s
> -                    modifiedfiles = unsure + modified + added + removed
> +                    (s, lookup) = lfdirstate.status(dirtymatch, [], False,
> +                                                    False, False)
> +                    modifiedfiles = lookup + s.modified + s.added + s.removed
>                       lfiles = lfutil.listlfiles(self)
>                       # this only loops through largefiles that exist (not
>                       # removed/renamed)
> diff --git a/hgext/mq.py b/hgext/mq.py
> --- a/hgext/mq.py
> +++ b/hgext/mq.py
> @@ -1021,7 +1021,7 @@
>           return None, None
>   
>       def putsubstate2changes(self, substatestate, changes):
> -        for files in changes[:3]:
> +        for files in changes:

This imply a change from a list of lists to a single list. Could that be 
done in a separate changeset?

>               if '.hgsubstate' in files:
>                   return # already listed up
>           # not yet listed up
> @@ -1092,11 +1092,11 @@
>                   if f != '.hgsubstate': # .hgsubstate is auto-created
>                       raise util.Abort('%s: %s' % (f, msg))
>               match.bad = badfn
> -            changes = repo.status(match=match)
> +            changes = repo.status(match=match).committable()
>           else:
> -            changes = self.checklocalchanges(repo, force=True)
> +            changes = self.checklocalchanges(repo, force=True).committable()
>           commitfiles = list(inclsubs)
> -        for files in changes[:3]:
> +        for files in changes:
>               commitfiles.extend(files)
>           match = scmutil.matchfiles(repo, commitfiles)
>           if len(repo[None].parents()) > 1:
> @@ -1360,11 +1360,12 @@
>   
>               tobackup = set()
>               if (not nobackup and force) or keepchanges:
> -                m, a, r, d = self.checklocalchanges(repo, force=True)
> +                changes = self.checklocalchanges(repo, force=True)
>                   if keepchanges:
> -                    tobackup.update(m + a + r + d)
> +                    tobackup.update(changes.modified + changes.added +
> +                                    changes.removed + changes.deleted)
>                   else:
> -                    tobackup.update(m + a)
> +                    tobackup.update(changes.modified + changes.added)
>   
>               s = self.series[start:end]
>               all_files = set()
> @@ -1448,13 +1449,14 @@
>   
>               tobackup = set()
>               if update:
> -                m, a, r, d = self.checklocalchanges(
> +                changes = self.checklocalchanges(
>                       repo, force=force or keepchanges)
>                   if force:
>                       if not nobackup:
> -                        tobackup.update(m + a)
> +                        tobackup.update(changes.modified + changes.added)
>                   elif keepchanges:
> -                    tobackup.update(m + a + r + d)
> +                    tobackup.update(changes.modified + changes.added +
> +                                    changes.removed + changes.deleted)
>   
>               self.applieddirty = True
>               end = len(self.applied)
> @@ -1479,19 +1481,20 @@
>               if update:
>                   qp = self.qparents(repo, rev)
>                   ctx = repo[qp]
> -                m, a, r, d = repo.status(qp, '.')[:4]
> -                if d:
> +                status = repo.status(qp, '.')
> +                if status.deleted:
>                       raise util.Abort(_("deletions found between repo revs"))
>   
> -                tobackup = set(a + m + r) & tobackup
> +                tobackup = set(status.added + status.modified +
> +                               status.removed) & tobackup
>                   if keepchanges and tobackup:
>                       raise util.Abort(_("local changes found, refresh first"))
>                   self.backup(repo, tobackup)
>                   repo.dirstate.beginparentchange()
> -                for f in a:
> +                for f in status.added:
>                       util.unlinkpath(repo.wjoin(f), ignoremissing=True)
>                       repo.dirstate.drop(f)
> -                for f in m + r:
> +                for f in status.modified + status.removed:
>                       fctx = ctx[f]
>                       repo.wwrite(f, fctx.data(), fctx.flags())
>                       repo.dirstate.normal(f)
> @@ -1570,7 +1573,8 @@
>               #   mm, dd, aa = repo.status(top, patchparent)[:3]
>               # but we do it backwards to take advantage of manifest/changelog
>               # caching against the next repo.status call
> -            mm, aa, dd = repo.status(patchparent, top)[:3]
> +            s = repo.status(patchparent, top)
> +            mm, aa, dd = s.modified, s.added, s.removed
>               changes = repo.changelog.read(top)
>               man = repo.manifest.read(changes[0])
>               aaa = aa[:]
> @@ -1585,7 +1589,8 @@
>                   matchfn = scmutil.match(repo[None], opts=opts)
>               else:
>                   match = scmutil.matchall(repo)
> -            m, a, r, d = repo.status(match=match)[:4]
> +            s = repo.status(match=match)
> +            m, a, r, d = s.modified, s.added, s.removed, s.deleted
>               mm = set(mm)
>               aa = set(aa)
>               dd = set(dd)
> @@ -1624,8 +1629,9 @@
>   
>               # create 'match' that includes the files to be recommitted.
>               # apply matchfn via repo.status to ensure correct case handling.
> -            cm, ca, cr, cd = repo.status(patchparent, match=matchfn)[:4]
> -            allmatches = set(cm + ca + cr + cd)
> +            status = repo.status(patchparent, match=matchfn)
> +            allmatches = set(status.modified + status.added + status.removed +
> +                             status.deleted)
>               refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
>   
>               files = set(inclsubs)
> diff --git a/hgext/purge.py b/hgext/purge.py
> --- a/hgext/purge.py
> +++ b/hgext/purge.py
> @@ -102,7 +102,7 @@
>       status = repo.status(match=match, ignored=opts['all'], unknown=True)
>   
>       if removefiles:
> -        for f in sorted(status[4] + status[5]):
> +        for f in sorted(status.unknown + status.ignored):
>               if act:
>                   ui.note(_('removing file %s\n') % f)
>               remove(util.unlink, f)
> diff --git a/hgext/record.py b/hgext/record.py
> --- a/hgext/record.py
> +++ b/hgext/record.py
> @@ -519,12 +519,12 @@
>               raise util.Abort(_('cannot partially commit a merge '
>                                  '(use "hg commit" instead)'))
>   
> -        changes = repo.status(match=match)[:3]
> +        status = repo.status(match=match)
>           diffopts = opts.copy()
>           diffopts['nodates'] = True
>           diffopts['git'] = True
>           diffopts = patch.diffopts(ui, opts=diffopts)
> -        chunks = patch.diff(repo, changes=changes, opts=diffopts)
> +        chunks = patch.diff(repo, changes=status.committable(), opts=diffopts)
>           fp = cStringIO.StringIO()
>           fp.write(''.join(chunks))
>           fp.seek(0)
> @@ -544,13 +544,13 @@
>               except AttributeError:
>                   pass
>   
> -        changed = changes[0] + changes[1] + changes[2]
> +        changed = status.modified + status.added + status.removed
>           newfiles = [f for f in changed if f in contenders]
>           if not newfiles:
>               ui.status(_('no changes to record\n'))
>               return 0
>   
> -        modified = set(changes[0])
> +        modified = set(status.modified)
>   
>           # 2. backup changed files, so we can restore them in the end
>           if backupall:
> diff --git a/hgext/shelve.py b/hgext/shelve.py
> --- a/hgext/shelve.py
> +++ b/hgext/shelve.py
> @@ -227,9 +227,9 @@
>   
>           if not node:
>               stat = repo.status(match=scmutil.match(repo[None], pats, opts))
> -            if stat[3]:
> +            if stat.deleted:
>                   ui.status(_("nothing changed (%d missing files, see "
> -                            "'hg status')\n") % len(stat[3]))
> +                            "'hg status')\n") % len(stat.deleted))
>               else:
>                   ui.status(_("nothing changed\n"))
>               return 1
> @@ -401,7 +401,7 @@
>           files.extend(shelvectx.parents()[0].files())
>   
>           # revert will overwrite unknown files, so move them out of the way
> -        for file in repo.status(unknown=True)[4]:
> +        for file in repo.status(unknown=True).unknown:
>               if file in files:
>                   util.rename(file, file + ".orig")
>           ui.pushbuffer(True)
> @@ -545,8 +545,7 @@
>           # to the original pctx.
>   
>           # Store pending changes in a commit
> -        m, a, r, d = repo.status()[:4]
> -        if m or a or r or d:
> +        if repo.status().haschanges():
>               ui.status(_("temporarily committing pending changes "
>                           "(restore with 'hg unshelve --abort')\n"))
>               def commitfunc(ui, repo, message, match, opts):
> diff --git a/hgext/strip.py b/hgext/strip.py
> --- a/hgext/strip.py
> +++ b/hgext/strip.py
> @@ -32,15 +32,15 @@
>   
>   def checklocalchanges(repo, force=False, excsuffix=''):
>       cmdutil.checkunfinished(repo)
> -    m, a, r, d = repo.status()[:4]
> +    status = repo.status()
>       if not force:
> -        if (m or a or r or d):
> +        if (status.haschanges()):
>               _("local changes found") # i18n tool detection
>               raise util.Abort(_("local changes found" + excsuffix))
>           if checksubstate(repo):
>               _("local changed subrepos found") # i18n tool detection
>               raise util.Abort(_("local changed subrepos found" + excsuffix))
> -    return m, a, r, d
> +    return status
>   
>   def strip(ui, repo, revs, update=True, backup=True, force=None, bookmark=None):
>       wlock = lock = None
> diff --git a/hgext/transplant.py b/hgext/transplant.py
> --- a/hgext/transplant.py
> +++ b/hgext/transplant.py
> @@ -611,8 +611,7 @@
>       if not opts.get('continue'):
>           if p2 != revlog.nullid:
>               raise util.Abort(_('outstanding uncommitted merges'))
> -        m, a, r, d = repo.status()[:4]
> -        if m or a or r or d:
> +        if repo.status().haschanges():
>               raise util.Abort(_('outstanding local changes'))
>   
>       sourcerepo = opts.get('source')
> diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
> --- a/mercurial/cmdutil.py
> +++ b/mercurial/cmdutil.py
> @@ -83,8 +83,7 @@
>   def bailifchanged(repo):
>       if repo.dirstate.p2() != nullid:
>           raise util.Abort(_('outstanding uncommitted merge'))
> -    modified, added, removed, deleted = repo.status()[:4]
> -    if modified or added or removed or deleted:
> +    if repo.status().haschanges():
>           raise util.Abort(_('uncommitted changes'))
>       ctx = repo[None]
>       for s in sorted(ctx.substate):
> @@ -940,7 +939,8 @@
>                         label='log.date')
>   
>           if self.ui.debugflag:
> -            files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
> +            status = self.repo.status(log.parents(changenode)[0], changenode)
> +            files = (status.modified, status.added, status.removed)
>               for key, value in zip([# i18n: column positioning for "hg log"
>                                      _("files:"),
>                                      # i18n: column positioning for "hg log"
> @@ -1074,11 +1074,11 @@
>   
>               files = ctx.status(ctx.p1())
>               self.ui.write(',\n  "modified": [%s]' %
> -                          ", ".join('"%s"' % j(f) for f in files[0]))
> +                          ", ".join('"%s"' % j(f) for f in files.modified))
>               self.ui.write(',\n  "added": [%s]' %
> -                          ", ".join('"%s"' % j(f) for f in files[1]))
> +                          ", ".join('"%s"' % j(f) for f in files.added))
>               self.ui.write(',\n  "removed": [%s]' %
> -                          ", ".join('"%s"' % j(f) for f in files[2]))
> +                          ", ".join('"%s"' % j(f) for f in files.removed))
>   
>           elif self.ui.verbose:
>               self.ui.write(',\n  "files": [%s]' %
> @@ -2016,7 +2016,7 @@
>       wctx = repo[None]
>       forgot = []
>       s = repo.status(match=match, clean=True)
> -    forget = sorted(s[0] + s[1] + s[3] + s[6])
> +    forget = sorted(s.modified + s.added + s.deleted + s.clean)
>       if explicitonly:
>           forget = [f for f in forget if match.exact(f)]
>   
> @@ -2187,8 +2187,8 @@
>               if len(old.parents()) > 1:
>                   # ctx.files() isn't reliable for merges, so fall back to the
>                   # slower repo.status() method
> -                files = set([fn for st in repo.status(base, old)[:3]
> -                             for fn in st])
> +                status = repo.status(base, old)
> +                files = set([fn for st in status.committable() for fn in st])
>               else:
>                   files = set(old.files())
>   
> @@ -2511,19 +2511,19 @@
>                                     unknown=True, ignored=True, clean=True)
>           else:
>               changes = repo.status(match=m)
> -            for kind in changes:
> +            for kind in changes.all():
>                   for abs in kind:
>                       names[abs] = m.rel(abs), m.exact(abs)
>   
>               m = scmutil.matchfiles(repo, names)
>   
> -        modified = set(changes[0])
> -        added    = set(changes[1])
> -        removed  = set(changes[2])
> -        _deleted = set(changes[3])
> -        unknown  = set(changes[4])
> -        unknown.update(changes[5])
> -        clean    = set(changes[6])
> +        modified = set(changes.modified)
> +        added    = set(changes.added)
> +        removed  = set(changes.removed)
> +        _deleted = set(changes.deleted)

Instead of refactoring an unused line, it would be nice to just remove 
it in an earlier changeset.

> +        unknown  = set(changes.unknown)
> +        unknown.update(changes.ignored)
> +        clean    = set(changes.clean)
>   
>           # split between files known in target manifest and the others
>           smf = set(mf)
> @@ -2544,9 +2544,9 @@
>               modified, added, removed = set(), set(), set()
>           else:
>               changes = repo.status(node1=parent, match=m)
> -            dsmodified = set(changes[0])
> -            dsadded    = set(changes[1])
> -            dsremoved  = set(changes[2])
> +            dsmodified = set(changes.modified)
> +            dsadded    = set(changes.added)
> +            dsremoved  = set(changes.removed)
>   
>               # only take into account for removes between wc and target
>               clean |= dsremoved - removed
> diff --git a/mercurial/commands.py b/mercurial/commands.py
> --- a/mercurial/commands.py
> +++ b/mercurial/commands.py
> @@ -1491,9 +1491,9 @@
>   
>           if not node:
>               stat = repo.status(match=scmutil.match(repo[None], pats, opts))
> -            if stat[3]:
> +            if stat.deleted:
>                   ui.status(_("nothing changed (%d missing files, see "
> -                            "'hg status')\n") % len(stat[3]))
> +                            "'hg status')\n") % len(stat.deleted))
>               else:
>                   ui.status(_("nothing changed\n"))
>               return 1
> @@ -3951,7 +3951,7 @@
>               parents = ctx.parents()
>               changed = ""
>               if default or id or num:
> -                if (util.any(repo.status())
> +                if (util.any(repo.status().all())
>                       or util.any(ctx.sub(s).dirty() for s in ctx.substate)):
>                       changed = '+'
>               if default or id:
> @@ -5149,7 +5149,6 @@
>   
>       m = scmutil.match(repo[None], pats, opts)
>       s = repo.status(match=m, clean=True)
> -    modified, added, deleted, clean = s[0], s[1], s[3], s[6]
>   
>       # warn about failure to delete explicit files/dirs
>       wctx = repo[None]
> @@ -5165,19 +5164,19 @@
>           ret = 1
>   
>       if force:
> -        list = modified + deleted + clean + added
> +        list = s.modified + s.deleted + s.clean + s.added
>       elif after:
> -        list = deleted
> -        for f in modified + added + clean:
> +        list = s.deleted
> +        for f in s.modified + s.added + s.clean:
>               ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
>               ret = 1
>       else:
> -        list = deleted + clean
> -        for f in modified:
> +        list = s.deleted + s.clean
> +        for f in s.modified:
>               ui.warn(_('not removing %s: file is modified (use -f'
>                         ' to force removal)\n') % m.rel(f))
>               ret = 1
> -        for f in added:
> +        for f in s.added:
>               ui.warn(_('not removing %s: file has been marked for add'
>                         ' (use forget to undo)\n') % m.rel(f))
>               ret = 1
> @@ -5190,7 +5189,7 @@
>       try:
>           if not after:
>               for f in list:
> -                if f in added:
> +                if f in s.added:
>                       continue # we never unlink added files on remove
>                   util.unlinkpath(repo.wjoin(f), ignoremissing=True)
>           repo[None].forget(list)
> @@ -5406,7 +5405,7 @@
>               hint = _("uncommitted merge, use --all to discard all changes,"
>                        " or 'hg update -C .' to abort the merge")
>               raise util.Abort(msg, hint=hint)
> -        dirty = util.any(repo.status())
> +        dirty = util.any(repo.status().all())
>           node = ctx.node()
>           if node != parent:
>               if dirty:
> @@ -5711,7 +5710,14 @@
>       stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
>                          'ignored' in show, 'clean' in show, 'unknown' in show,
>                          opts.get('subrepos'))
> -    changestates = zip(states, 'MAR!?IC', stat)
> +    changestates = zip(states, 'MAR!?IC',
> +                       [stat.modified,
> +                        stat.added,
> +                        stat.removed,
> +                        stat.deleted,
> +                        stat.unknown,
> +                        stat.ignored,
> +                        stat.clean])
>   
>       if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
>           copy = copies.pathcopies(repo[node1], repo[node2])
> @@ -5793,7 +5799,7 @@
>               ui.write(' ' + m, label='log.bookmark')
>           ui.write('\n', label='log.bookmark')
>   
> -    st = list(repo.status(unknown=True))[:5]
> +    st = list(repo.status(unknown=True).all())[:5]
>   
>       c = repo.dirstate.copies()
>       copied, renamed = [], []
> diff --git a/mercurial/context.py b/mercurial/context.py
> --- a/mercurial/context.py
> +++ b/mercurial/context.py
> @@ -14,6 +14,7 @@
>   import repoview
>   import fileset
>   import revlog
> +from mercurial.dirstate import status
>   
>   propertycache = util.propertycache
>   
> @@ -120,7 +121,7 @@
>           mf2 = self._manifestmatches(match, s)
>   
>           modified, added, clean = [], [], []
> -        deleted, unknown, ignored = s[3], s[4], s[5]
> +        deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
>           withflags = mf1.withflags() | mf2.withflags()
>           for fn, mf2node in mf2.iteritems():
>               if fn in mf1:
> @@ -140,7 +141,8 @@
>               unknown = [fn for fn in unknown if fn not in mf1]
>               ignored = [fn for fn in ignored if fn not in mf1]
>   
> -        return [modified, added, removed, deleted, unknown, ignored, clean]
> +        return status(modified, added, removed, deleted, unknown, ignored,
> +                      clean)
>   
>       @propertycache
>       def substate(self):
> @@ -310,7 +312,7 @@
>               reversed = True
>               ctx1, ctx2 = ctx2, ctx1
>   
> -        r = [[], [], [], [], [], [], []]
> +        r = status([], [], [], [], [], [], [])

Shouldn't "empty list" be the default for status.__ini__?

>           match = ctx2._matchstatus(ctx1, r, match, listignored, listclean,
>                                     listunknown)
>           r = ctx2._prestatus(ctx1, r, match, listignored, listclean, listunknown)
> @@ -321,7 +323,7 @@
>   
>           if reversed:
>               # reverse added and removed
> -            r[1], r[2] = r[2], r[1]
> +            r.added, r.removed = r.removed, r.added
>   
>           if listsubrepos:
>               for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
> @@ -331,17 +333,16 @@
>                       s = sub.status(rev2, match=submatch, ignored=listignored,
>                                      clean=listclean, unknown=listunknown,
>                                      listsubrepos=True)
> -                    for rfiles, sfiles in zip(r, s):
> +                    for rfiles, sfiles in zip(r.all(), s.all()):
>                           rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
>                   except error.LookupError:
>                       self._repo.ui.status(_("skipping missing "
>                                              "subrepository: %s\n") % subpath)
>   
> -        for l in r:
> +        for l in r.all():
>               l.sort()
>   
> -        # we return a tuple to signify that this list isn't changing
> -        return tuple(r)
> +        return r
>   
>   
>   def makememctx(repo, parents, text, user, date, branch, files, store,
> @@ -1050,8 +1051,7 @@
>   
>           copied = self._repo.dirstate.copies()
>           ff = self._flagfunc
> -        modified, added, removed, deleted = self._status[:4]
> -        for i, l in (("a", added), ("m", modified)):
> +        for i, l in (("a", self._status.added), ("m", self._status.modified)):
>               for f in l:
>                   orig = copied.get(f, f)
>                   man[f] = getman(orig).get(orig, nullid) + i
> @@ -1060,7 +1060,7 @@
>                   except OSError:
>                       pass
>   
> -        for f in deleted + removed:
> +        for f in self._status.deleted + self._status.removed:
>               if f in man:
>                   del man[f]
>   
> @@ -1088,22 +1088,23 @@
>       def description(self):
>           return self._text
>       def files(self):
> -        return sorted(self._status[0] + self._status[1] + self._status[2])
> +        return sorted(self._status.modified + self._status.added + \
> +                      self._status.removed)
>   
>       def modified(self):
> -        return self._status[0]
> +        return self._status.modified
>       def added(self):
> -        return self._status[1]
> +        return self._status.added
>       def removed(self):
> -        return self._status[2]
> +        return self._status.removed
>       def deleted(self):
> -        return self._status[3]
> +        return self._status.deleted
>       def unknown(self):
> -        return self._status[4]
> +        return self._status.unknown
>       def ignored(self):
> -        return self._status[5]
> +        return self._status.ignored
>       def clean(self):
> -        return self._status[6]
> +        return self._status.clean
>       def branch(self):
>           return encoding.tolocal(self._extra['branch'])
>       def closesbranch(self):
> @@ -1378,11 +1379,10 @@
>           need to build a manifest and return what matches.
>           """
>           mf = self._repo['.']._manifestmatches(match, s)
> -        modified, added, removed = s[0:3]
> -        for f in modified + added:
> +        for f in s.modified + s.added:
>               mf[f] = None
>               mf.set(f, self.flags(f))
> -        for f in removed:
> +        for f in s.removed:
>               if f in mf:
>                   del mf[f]
>           return mf
> @@ -1405,8 +1405,8 @@
>           accidentally ended up with the entire contents of the file they are
>           susposed to be linking to.
>           """
> -        s[0] = self._filtersuspectsymlink(s[0])
> -        self._status = s[:]
> +        s.modified = self._filtersuspectsymlink(s.modified)
> +        self._status = s
>           return s
>   
>       def _dirstatestatus(self, match=None, ignored=False, clean=False,
> @@ -1417,20 +1417,19 @@
>           subrepos = []
>           if '.hgsub' in self:
>               subrepos = sorted(self.substate)
> -        s = self._repo.dirstate.status(match, subrepos, listignored,
> -                                       listclean, listunknown)
> -        cmp, modified, added, removed, deleted, unknown, ignored, clean = s
> +        (s, lookup) = self._repo.dirstate.status(match, subrepos, listignored,
> +                                                 listclean, listunknown)
>   
>           # check for any possibly clean files
> -        if cmp:
> -            modified2, fixup = self._checklookup(cmp)
> -            modified += modified2
> +        if lookup:
> +            modified2, fixup = self._checklookup(lookup)
> +            s.modified += modified2
>   
> -            # update dirstate for files that are actually clean
> +            # update status for files that are actually clean
>               if fixup and listclean:
> -                clean += fixup
> +                s.clean += fixup
>   
> -        return [modified, added, removed, deleted, unknown, ignored, clean]
> +        return s
>   
>       def _buildstatus(self, other, s, match, listignored, listclean,
>                        listunknown):
> @@ -1478,10 +1477,8 @@
>           s = super(workingctx, self).status(other, match, listignored, listclean,
>                                              listunknown, listsubrepos)
>           # calling 'super' subtly reveresed the contexts, so we flip the results
> -        # (s[1] is 'added' and s[2] is 'removed')
> -        s = list(s)
> -        s[1], s[2] = s[2], s[1]
> -        return tuple(s)
> +        s.added, s.removed = s.removed, s.added
> +        return s
>   
>   class committablefilectx(basefilectx):
>       """A committablefilectx provides common functionality for a file context
> @@ -1612,7 +1609,7 @@
>           p1, p2 = parents
>           self._parents = [changectx(self._repo, p) for p in (p1, p2)]
>           files = sorted(set(files))
> -        self._status = [files, [], [], [], []]
> +        self._status = status(files, [], [], [], [], [], [])
>           self._filectxfn = filectxfn
>           self.substate = {}
>   
> diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
> --- a/mercurial/dirstate.py
> +++ b/mercurial/dirstate.py
> @@ -26,6 +26,40 @@
>       def join(self, obj, fname):
>           return obj._join(fname)
>   
> +class status(object):

Please describe the intention with this class.

> +    def __init__(self, modified, added, removed, deleted, unknown,
> +                ignored, clean):
> +        self.modified = modified
> +        self.added = added
> +        self.removed = removed
> +        self.deleted = deleted
> +        self.unknown = unknown
> +        self.ignored = ignored
> +        self.clean = clean

http://mercurial.selenic.com/wiki/CodingStyle#Classes ... but I agree it 
is nicer than the 7-tuples.

It is also very similar to a named tuple. Could we use that instead ... 
or inherit from it? (That would require some kludge as long as we 
support Python < 2.6.)

> +
> +    def all(self):
> +        return (self.modified,
> +                self.added,
> +                self.removed,
> +                self.deleted,
> +                self.unknown,
> +                self.ignored,
> +                self.clean)

The name 'all' is not completely obvious. If that is the best possible 
name, it deserves a docstring.

But I think a more Pythonic way (whatever that is) would be to define 
__iter__ to make it possible to say   s = tuple(st)

> +    def changes(self):
> +        return (self.modified,
> +                self.added,
> +                self.removed,
> +                self.deleted)

It seems like this (and committable) are convenience functions that 
could be added later.

It do not return the changes but the files that have changed. I suggest 
calling it "changed".

> +    def haschanges(self):
> +        return util.any(self.changes())

Comes for free with __iter__.

> +    def committable(self):
> +        return (self.modified,
> +                self.added,
> +                self.removed)

Actually, this might be more like what I would call "changed".

It is hard to come up with good names (although docstrings would help). 
Putting them in separate patches might make it more obvious if they are 
worth it.

> @@ -900,8 +934,8 @@
>               elif state == 'r':
>                   radd(fn)
>   
> -        return (lookup, modified, added, removed, deleted, unknown, ignored,
> -                clean)
> +        return (status(modified, added, removed, deleted, unknown,
> +                      ignored, clean), lookup)
>   
>       def matches(self, match):
>           '''
> diff --git a/mercurial/fileset.py b/mercurial/fileset.py
> --- a/mercurial/fileset.py
> +++ b/mercurial/fileset.py
> @@ -124,7 +124,7 @@
>       """
>       # i18n: "modified" is a keyword
>       getargs(x, 0, 0, _("modified takes no arguments"))
> -    s = mctx.status()[0]
> +    s = mctx.status().modified
>       return [f for f in mctx.subset if f in s]
>   
>   def added(mctx, x):
> @@ -133,7 +133,7 @@
>       """
>       # i18n: "added" is a keyword
>       getargs(x, 0, 0, _("added takes no arguments"))
> -    s = mctx.status()[1]
> +    s = mctx.status().added
>       return [f for f in mctx.subset if f in s]
>   
>   def removed(mctx, x):
> @@ -142,7 +142,7 @@
>       """
>       # i18n: "removed" is a keyword
>       getargs(x, 0, 0, _("removed takes no arguments"))
> -    s = mctx.status()[2]
> +    s = mctx.status().removed
>       return [f for f in mctx.subset if f in s]
>   
>   def deleted(mctx, x):
> @@ -151,7 +151,7 @@
>       """
>       # i18n: "deleted" is a keyword
>       getargs(x, 0, 0, _("deleted takes no arguments"))
> -    s = mctx.status()[3]
> +    s = mctx.status().deleted
>       return [f for f in mctx.subset if f in s]
>   
>   def unknown(mctx, x):
> @@ -161,7 +161,7 @@
>       """
>       # i18n: "unknown" is a keyword
>       getargs(x, 0, 0, _("unknown takes no arguments"))
> -    s = mctx.status()[4]
> +    s = mctx.status().unknown
>       return [f for f in mctx.subset if f in s]
>   
>   def ignored(mctx, x):
> @@ -171,7 +171,7 @@
>       """
>       # i18n: "ignored" is a keyword
>       getargs(x, 0, 0, _("ignored takes no arguments"))
> -    s = mctx.status()[5]
> +    s = mctx.status().ignored
>       return [f for f in mctx.subset if f in s]
>   
>   def clean(mctx, x):
> @@ -180,7 +180,7 @@
>       """
>       # i18n: "clean" is a keyword
>       getargs(x, 0, 0, _("clean takes no arguments"))
> -    s = mctx.status()[6]
> +    s = mctx.status().clean
>       return [f for f in mctx.subset if f in s]
>   
>   def func(mctx, a, b):
> @@ -448,8 +448,8 @@
>           return [f for f in files if f in self.subset]
>       def existing(self):
>           if self._status is not None:
> -            removed = set(self._status[3])
> -            unknown = set(self._status[4] + self._status[5])
> +            removed = set(self._status.deleted)
> +            unknown = set(self._status.unknown + self._status.ignored)
>           else:
>               removed = set()
>               unknown = set()
> @@ -495,7 +495,7 @@
>           status = r.status(ctx.p1(), ctx,
>                             unknown=unknown, ignored=ignored, clean=True)
>           subset = []
> -        for c in status:
> +        for c in status.all():
>               subset.extend(c)
>       else:
>           status = None
> diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
> --- a/mercurial/localrepo.py
> +++ b/mercurial/localrepo.py
> @@ -574,7 +574,7 @@
>           date: date tuple to use if committing'''
>   
>           if not local:
> -            for x in self.status()[:4]:
> +            for x in self.status().changes():
>                   if '.hgtags' in x:
>                       raise util.Abort(_('working copy of .hgtags is changed '
>                                          '(please commit .hgtags manually)'))
> @@ -1229,9 +1229,10 @@
>                   raise util.Abort(_('cannot partially commit a merge '
>                                      '(do not specify files or patterns)'))
>   
> -            changes = self.status(match=match, clean=force)
> +            status = self.status(match=match, clean=force)
>               if force:
> -                changes[0].extend(changes[6]) # mq may commit unchanged files
> +                # mq may commit unchanged files
> +                status.modified.extend(status.clean)
>   
>               # check subrepos
>               subs = []
> @@ -1240,7 +1241,7 @@
>               # only manage subrepos and .hgsubstate if .hgsub is present
>               if '.hgsub' in wctx:
>                   # we'll decide whether to track this ourselves, thanks
> -                for c in changes[:3]:
> +                for c in [status.modified, status.added, status.removed]:

A place where commitable could be used? Except I don't think it is that 
bad to have it explicit ;-)

>                       if '.hgsubstate' in c:
>                           c.remove('.hgsubstate')
>   
> @@ -1278,23 +1279,24 @@
>                           '.hgsub' in (wctx.modified() + wctx.added())):
>                           raise util.Abort(
>                               _("can't commit subrepos without .hgsub"))
> -                    changes[0].insert(0, '.hgsubstate')
> +                    status.modified.insert(0, '.hgsubstate')
>   
> -            elif '.hgsub' in changes[2]:
> +            elif '.hgsub' in status.removed:
>                   # clean up .hgsubstate when .hgsub is removed
>                   if ('.hgsubstate' in wctx and
> -                    '.hgsubstate' not in changes[0] + changes[1] + changes[2]):
> -                    changes[2].insert(0, '.hgsubstate')
> +                    '.hgsubstate' not in (status.modified + status.added +
> +                                          status.removed)):
> +                    status.removed.insert(0, '.hgsubstate')
>   
>               # make sure all explicit patterns are matched
>               if not force and match.files():
> -                matched = set(changes[0] + changes[1] + changes[2])
> +                matched = set(status.modified + status.added + status.removed)
>   
>                   for f in match.files():
>                       f = self.dirstate.normalize(f)
>                       if f == '.' or f in matched or f in wctx.substate:
>                           continue
> -                    if f in changes[3]: # missing
> +                    if f in status.deleted:
>                           fail(f, _('file not found!'))
>                       if f in vdirs: # visited directory
>                           d = f + '/'
> @@ -1306,7 +1308,7 @@
>                       elif f not in self.dirstate:
>                           fail(f, _("file not tracked!"))
>   
> -            cctx = context.workingctx(self, text, user, date, extra, changes)
> +            cctx = context.workingctx(self, text, user, date, extra, status)
>   
>               if (not force and not extra.get("close") and not merge
>                   and not cctx.files()
> @@ -1317,7 +1319,7 @@
>                   raise util.Abort(_("cannot commit merge with missing files"))
>   
>               ms = mergemod.mergestate(self)
> -            for f in changes[0]:
> +            for f in status.modified:
>                   if f in ms and ms[f] == 'u':
>                       raise util.Abort(_("unresolved merge conflicts "
>                                          "(see hg help resolve)"))
> diff --git a/mercurial/patch.py b/mercurial/patch.py
> --- a/mercurial/patch.py
> +++ b/mercurial/patch.py
> @@ -1619,8 +1619,8 @@
>       ctx2 = repo[node2]
>   
>       if not changes:
> -        changes = repo.status(ctx1, ctx2, match=match)
> -    modified, added, removed = changes[:3]
> +        changes = repo.status(ctx1, ctx2, match=match).committable()
> +    modified, added, removed = changes[0], changes[1], changes[2]

That seems convoluted.

>       if not modified and not added and not removed:
>           return []
> diff --git a/mercurial/revset.py b/mercurial/revset.py
> --- a/mercurial/revset.py
> +++ b/mercurial/revset.py
> @@ -316,7 +316,7 @@
>       """
>       # i18n: "adds" is a keyword
>       pat = getstring(x, _("adds requires a pattern"))
> -    return checkstatus(repo, subset, pat, 1)
> +    return checkstatus(repo, subset, pat, 'added')

Using indices was bad, but member names seems worse. Could it be done in 
a more elegant way?

>   def ancestor(repo, subset, x):
>       """``ancestor(*changeset)``
> @@ -544,7 +544,7 @@
>                       break
>               else:
>                   return False
> -        files = repo.status(c.p1().node(), c.node())[field]
> +        files = repo.status(c.p1().node(), c.node()).__dict__[field]

field -> fieldname ?

>           if fname is not None:
>               if fname in files:
>                   return True
> @@ -1112,7 +1112,7 @@
>       """
>       # i18n: "modifies" is a keyword
>       pat = getstring(x, _("modifies requires a pattern"))
> -    return checkstatus(repo, subset, pat, 0)
> +    return checkstatus(repo, subset, pat, 'modified')
>   
>   def node_(repo, subset, x):
>       """``id(string)``
> @@ -1336,7 +1336,7 @@
>       """
>       # i18n: "removes" is a keyword
>       pat = getstring(x, _("removes requires a pattern"))
> -    return checkstatus(repo, subset, pat, 2)
> +    return checkstatus(repo, subset, pat, 'removed')
>   
>   def rev(repo, subset, x):
>       """``rev(number)``
> diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py
> --- a/mercurial/subrepo.py
> +++ b/mercurial/subrepo.py
> @@ -12,6 +12,7 @@
>   import config, util, node, error, cmdutil, bookmarks, match as matchmod
>   import phases
>   import pathutil
> +from mercurial.dirstate import status as statusmod

status is not a module - calling it statusmod is confusing. But as 
before, just import dirstate and use dirstate.status .

>   hg = None
>   propertycache = util.propertycache
>   
> @@ -447,7 +448,7 @@
>           return 1
>   
>       def status(self, rev2, **opts):
> -        return [], [], [], [], [], [], []
> +        return statusmod([], [], [], [], [], [], [])
>   
>       def diff(self, ui, diffopts, node2, match, prefix, **opts):
>           pass
> @@ -649,7 +650,7 @@
>           except error.RepoLookupError, inst:
>               self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
>                                  % (inst, subrelpath(self)))
> -            return [], [], [], [], [], [], []
> +            return statusmod([], [], [], [], [], [], [])
>   
>       @annotatesubrepoerror
>       def diff(self, ui, diffopts, node2, match, prefix, **opts):
> @@ -1564,7 +1565,7 @@
>           rev1 = self._state[1]
>           if self._gitmissing() or not rev1:
>               # if the repo is missing, return no results
> -            return [], [], [], [], [], [], []
> +            return statusmod([], [], [], [], [], [], [])
>           modified, added, removed = [], [], []
>           self._gitupdatestat()
>           if rev2:
> @@ -1585,7 +1586,8 @@
>                   removed.append(f)
>   
>           deleted = unknown = ignored = clean = []
> -        return modified, added, removed, deleted, unknown, ignored, clean
> +        return statusmod(modified, added, removed, deleted, unknown, ignored,
> +                         clean)
>   
>       def shortid(self, revid):
>           return revid[:7]
> diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
> --- a/mercurial/templatekw.py
> +++ b/mercurial/templatekw.py
> @@ -100,7 +100,7 @@
>   
>   def getfiles(repo, ctx, revcache):
>       if 'files' not in revcache:
> -        revcache['files'] = repo.status(ctx.p1().node(), ctx.node())[:3]
> +        revcache['files'] = repo.status(ctx.p1().node(), ctx.node())
>       return revcache['files']
>   
>   def getlatesttags(repo, ctx, cache):
> @@ -248,7 +248,7 @@
>   def showfileadds(**args):
>       """:file_adds: List of strings. Files added by this changeset."""
>       repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
> -    return showlist('file_add', getfiles(repo, ctx, revcache)[1],
> +    return showlist('file_add', getfiles(repo, ctx, revcache).added,
>                       element='file', **args)
>   
>   def showfilecopies(**args):
> @@ -286,13 +286,13 @@
>   def showfiledels(**args):
>       """:file_dels: List of strings. Files removed by this changeset."""
>       repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
> -    return showlist('file_del', getfiles(repo, ctx, revcache)[2],
> +    return showlist('file_del', getfiles(repo, ctx, revcache).removed,
>                       element='file', **args)
>   
>   def showfilemods(**args):
>       """:file_mods: List of strings. Files modified by this changeset."""
>       repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
> -    return showlist('file_mod', getfiles(repo, ctx, revcache)[0],
> +    return showlist('file_mod', getfiles(repo, ctx, revcache).modified,
>                       element='file', **args)
>   
>   def showfiles(**args):
> diff --git a/tests/test-context.py b/tests/test-context.py
> --- a/tests/test-context.py
> +++ b/tests/test-context.py
> @@ -44,7 +44,7 @@
>   ctxb = context.memctx(repo, [ctxa.node(), None], "test diff", ["foo"],
>                         getfilectx, ctxa.user(), ctxa.date())
>   
> -print ctxb.status(ctxa)
> +print ctxb.status(ctxa).all()
>   
>   # test performing a diff on a memctx
>   
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@selenic.com
> http://selenic.com/mailman/listinfo/mercurial-devel
Martin von Zweigbergk - Oct. 2, 2014, 5 a.m.
Thanks for taking a look! Answers below to some of your questions.
I'll leave the others for later, or they might just get address in v2
of the series.

On Wed, Oct 1, 2014 at 6:40 PM, Mads Kiilerich <mads@kiilerich.com> wrote:
> Comments added all over. I choose to send it now instead of polishing it and
> perhaps sending it tomorrow ;-)
>
>
> On 10/02/2014 02:01 AM, Martin von Zweigbergk wrote:
>>
>> # HG changeset patch
>> # User Martin von Zweigbergk <martinvonz@gmail.com>
>> # Date 1412204534 25200
>> #      Wed Oct 01 16:02:14 2014 -0700
>> # Node ID 048f3810ef38a85db4b265f020fe94fe6a12d103
>> # Parent  31cbb21f1283847c65e385bd94b81e2c78fd5121
>> dirstate: create class for status lists
>
>
> I'm spoiled by using Skype - that influence my vocabulary. I just want to
> say: (heart)
>
>> Callers of various status() methods (on dirstate, context, repo) get a
>> tuple of 7 or 8 elements, where each element is a list of files. This
>> results in lots of uses of indexes where names would be much more
>> readable. For example, "status.ignored" seems clearer than "status[4]"
>> [1]. So, let's introduce a simple class containing the 7 status
>> fields: modified, added, removed, deleted, unknown, ignored, clean. In
>> the cases of dirstate.status(), which has an 8th element ('lookup'),
>> return a 2-tuple of the new status class and the lookup list.
>>
>> Having a class for the status also lets us add methods to it. For
>> example, a status.haschanges() method turns out to be useful.
>>
>>
>>   [1] Did you even notice that it should have been "status[5]"?
>
>
> ;-)
>
>
> A general comment: A "." lookup is relatively expensive. It seems like you
> introduce a lot of them. We should take care to not introduce more of them
> in tight loops (that might already be the case - I did not check and not see
> any statement on whether that was the case).

Hmm, I don't think I even considered that (I've never used python before).

I tried running the test suite:

Before:
real    3m22.026s
user    25m31.715s
sys 5m45.684s

After:
real    3m24.136s
user    25m29.724s
sys     5m46.637s

I also tried this:

$ python -mtimeit -s'a=(1,2,3,4,5,6)' 'a[3]'
10000000 loops, best of 3: 0.0402 usec per loop

$ python -mtimeit -s'class c(object):
  def __init__(self,a,b,c,d,e,f):
    self.a=a
    self.b=b
    self.c=c
    self.d=d
    self.e=e
    self.f=f
a=c(1,2,3,4,5,6)' 'a.d'
10000000 loops, best of 3: 0.0458 usec per loop

So no big difference in either test. I'm running Python 2.7.3. Is that
small difference still significant overall, you think? Or was the
performance penalty just there in older Pythons? (And do we care in
that case?) Or am I not testing it right?

> I also think you should
> consider make the diff smaller by starting out with assigning the values to
> the old local variables (you already do that in some places but it could
> perhaps be done in more).

I think that makes sense. I'll give it a shot.

>
>> diff --git a/hgext/convert/hg.py b/hgext/convert/hg.py
>> --- a/hgext/convert/hg.py
>> +++ b/hgext/convert/hg.py
>> @@ -371,7 +371,8 @@
>>               if self._changescache[0] == rev:
>>                   m, a, r = self._changescache[1]
>>               else:
>> -                m, a, r = self.repo.status(parents[0].node(),
>> ctx.node())[:3]
>> +                s = self.repo.status(parents[0].node(), ctx.node())
>> +                m, a, r = s.modified, s.added, s.removed
>>               if not full:
>>                   files = m + a + r
>>               copyfiles = m + a
>> @@ -437,7 +438,8 @@
>>               changes = [], ctx.manifest().keys(), []
>>           else:
>>               i = i or 0
>> -            changes = self.repo.status(parents[i].node(), ctx.node())[:3]
>> +            s = self.repo.status(parents[i].node(), ctx.node())
>> +            changes = s.modified, s.added, s.removed
>>           changes = [[f for f in l if f not in self.ignored] for l in
>> changes]
>>             if i == 0:
>> diff --git a/hgext/extdiff.py b/hgext/extdiff.py
>> --- a/hgext/extdiff.py
>> +++ b/hgext/extdiff.py
>> @@ -143,9 +143,12 @@
>>               do3way = False
>>         matcher = scmutil.match(repo[node2], pats, opts)
>> -    mod_a, add_a, rem_a = map(set, repo.status(node1a, node2,
>> matcher)[:3])
>> +    st_a = repo.status(node1a, node2, matcher)
>> +    mod_a, add_a, rem_a = map(set, (st_a.modified, st_a.added,
>> st_a.removed))
>
>
> This map do no longer make the code shorter or more readable.
>
>>       if do3way:
>> -        mod_b, add_b, rem_b = map(set, repo.status(node1b, node2,
>> matcher)[:3])
>> +        st_b = repo.status(node1b, node2, matcher)
>> +        mod_b, add_b, rem_b = map(set,
>> +                                  (st_b.modified, st_b.added,
>> st_b.removed))
>>       else:
>>           mod_b, add_b, rem_b = set(), set(), set()
>>       modadd = mod_a | add_a | mod_b | add_b
>> diff --git a/hgext/gpg.py b/hgext/gpg.py
>> --- a/hgext/gpg.py
>> +++ b/hgext/gpg.py
>> @@ -255,7 +255,7 @@
>>         msigs = match.exact(repo.root, '', ['.hgsigs'])
>>       s = repo.status(match=msigs, unknown=True, ignored=True)
>> -    if util.any(s) and not opts["force"]:
>> +    if util.any(s.all()) and not opts["force"]:
>>           raise util.Abort(_("working copy of .hgsigs is changed "
>>                              "(please commit .hgsigs manually "
>>                              "or use --force)"))
>> diff --git a/hgext/hgcia.py b/hgext/hgcia.py
>> --- a/hgext/hgcia.py
>> +++ b/hgext/hgcia.py
>> @@ -82,14 +82,14 @@
>>           if url and url[-1] == '/':
>>               url = url[:-1]
>>           elems = []
>> -        for path in f[0]:
>> +        for path in f.modified:
>>               uri = '%s/diff/%s/%s' % (url, short(n), path)
>>               elems.append(self.fileelem(path, url and uri, 'modify'))
>> -        for path in f[1]:
>> +        for path in f.added:
>>               # TODO: copy/rename ?
>>               uri = '%s/file/%s/%s' % (url, short(n), path)
>>               elems.append(self.fileelem(path, url and uri, 'add'))
>> -        for path in f[2]:
>> +        for path in f.removed:
>>               elems.append(self.fileelem(path, '', 'remove'))
>>             return '\n'.join(elems)
>> diff --git a/hgext/histedit.py b/hgext/histedit.py
>> --- a/hgext/histedit.py
>> +++ b/hgext/histedit.py
>> @@ -688,8 +688,7 @@
>>         # Commit dirty working directory if necessary
>>       new = None
>> -    m, a, r, d = repo.status()[:4]
>> -    if m or a or r or d:
>> +    if repo.status().haschanges():
>>           # prepare the message for the commit to comes
>>           if action in ('f', 'fold', 'r', 'roll'):
>>               message = 'fold-temp-revision %s' % currentnode
>> diff --git a/hgext/keyword.py b/hgext/keyword.py
>> --- a/hgext/keyword.py
>> +++ b/hgext/keyword.py
>> @@ -171,9 +171,8 @@
>>       '''Retrieves modified and added files from a working directory state
>>       and returns the subset of each contained in given changed files
>>       retrieved from a change context.'''
>> -    modified, added = wstatus[:2]
>> -    modified = [f for f in modified if f in changed]
>> -    added = [f for f in added if f in changed]
>> +    modified = [f for f in wstatus.modified if f in changed]
>> +    added = [f for f in wstatus.added if f in changed]
>>       return modified, added
>>     @@ -349,10 +348,9 @@
>>       wlock = repo.wlock()
>>       try:
>>           status = _status(ui, repo, wctx, kwt, *pats, **opts)
>> -        modified, added, removed, deleted, unknown, ignored, clean =
>> status
>> -        if modified or added or removed or deleted:
>> +        if status.haschanges():
>>               raise util.Abort(_('outstanding uncommitted changes'))
>> -        kwt.overwrite(wctx, clean, True, expand)
>> +        kwt.overwrite(wctx, status.clean, True, expand)
>>       finally:
>>           wlock.release()
>>   @@ -503,20 +501,19 @@
>>       wctx = repo[None]
>>       status = _status(ui, repo, wctx, kwt, *pats, **opts)
>>       cwd = pats and repo.getcwd() or ''
>> -    modified, added, removed, deleted, unknown, ignored, clean = status
>>       files = []
>>       if not opts.get('unknown') or opts.get('all'):
>> -        files = sorted(modified + added + clean)
>> +        files = sorted(status.modified + status.added + status.clean)
>>       kwfiles = kwt.iskwfile(files, wctx)
>> -    kwdeleted = kwt.iskwfile(deleted, wctx)
>> -    kwunknown = kwt.iskwfile(unknown, wctx)
>> +    kwdeleted = kwt.iskwfile(status.deleted, wctx)
>> +    kwunknown = kwt.iskwfile(status.unknown, wctx)
>>       if not opts.get('ignore') or opts.get('all'):
>>           showfiles = kwfiles, kwdeleted, kwunknown
>>       else:
>>           showfiles = [], [], []
>>       if opts.get('all') or opts.get('ignore'):
>>           showfiles += ([f for f in files if f not in kwfiles],
>> -                      [f for f in unknown if f not in kwunknown])
>> +                      [f for f in status.unknown if f not in kwunknown])
>>       kwlabels = 'enabled deleted enabledunknown ignored
>> ignoredunknown'.split()
>>       kwstates = zip(kwlabels, 'K!kIi', showfiles)
>>       fm = ui.formatter('kwfiles', opts)
>> diff --git a/hgext/largefiles/lfutil.py b/hgext/largefiles/lfutil.py
>> --- a/hgext/largefiles/lfutil.py
>> +++ b/hgext/largefiles/lfutil.py
>> @@ -136,19 +136,18 @@
>>     def lfdirstatestatus(lfdirstate, repo, rev):
>>       match = match_.always(repo.root, repo.getcwd())
>> -    s = lfdirstate.status(match, [], False, False, False)
>> -    unsure, modified, added, removed, missing, unknown, ignored, clean =
>> s
>> -    for lfile in unsure:
>> +    (s, lookup) = lfdirstate.status(match, [], False, False, False)
>> +    for lfile in lookup:
>
>
> I don't think I like the renaming from unsure to lookup. Unsure describes
> the content of the list. Lookup is what we might want to do with it because
> of that.

I asked on IRC and mpm preferred 'lookup' because it's older, IIRC.
There's also the risk of confusing 'unsure' and 'unknown'. I can see
how you would prefer 'unsure', though. I did, at least initially, but
I picked the other name for some reason.

> Anyway, it seems like an unrelated change that could be done (and discussed)
> later.

True, it's definitely a separate change now (see next answer). Will extract.

> I also wonder if it would make sense to make "unsure" an optional member of
> the status struct. I don't know.

I know, and it doesn't :-). I did that at first, but it just got ugly
with having to assert (or assume) that lookup/unsure was empty where
it should be.

>
>>           try:
>>               fctx = repo[rev][standin(lfile)]
>>           except LookupError:
>>               fctx = None
>>           if not fctx or fctx.data().strip() !=
>> hashfile(repo.wjoin(lfile)):
>> -            modified.append(lfile)
>> +            s.modified.append(lfile)
>>           else:
>> -            clean.append(lfile)
>> +            s.clean.append(lfile)
>>               lfdirstate.normal(lfile)
>> -    return (modified, added, removed, missing, unknown, ignored, clean)
>> +    return s
>>     def listlfiles(repo, rev=None, matcher=None):
>>       '''return a list of largefiles in the working copy or the
>> diff --git a/hgext/largefiles/overrides.py b/hgext/largefiles/overrides.py
>> --- a/hgext/largefiles/overrides.py
>> +++ b/hgext/largefiles/overrides.py
>> @@ -153,7 +153,8 @@
>>       manifest = repo[None].manifest()
>>       modified, added, deleted, clean = [[f for f in list
>>                                           if lfutil.standin(f) in
>> manifest]
>> -                                       for list in [s[0], s[1], s[3],
>> s[6]]]
>> +                                       for list in (s.modified, s.added,
>> +                                                    s.deleted, s.clean)]
>>         def warn(files, msg):
>>           for f in files:
>> @@ -353,13 +354,11 @@
>>       wlock = repo.wlock()
>>       try:
>>           lfdirstate = lfutil.openlfdirstate(ui, repo)
>> -        s = lfdirstate.status(match_.always(repo.root, repo.getcwd()),
>> -            [], False, False, False)
>> -        (unsure, modified, added, removed, missing, unknown, ignored,
>> clean) = s
>> -
>> +        (s, lookup) = lfdirstate.status(match_.always(repo.root,
>> repo.getcwd()),
>> +                                        [], False, False, False)
>>           if opts['check']:
>> -            mod = len(modified) > 0
>> -            for lfile in unsure:
>> +            mod = len(s.modified) > 0
>> +            for lfile in lookup:
>>                   standin = lfutil.standin(lfile)
>>                   if repo['.'][standin].data().strip() != \
>>                           lfutil.hashfile(repo.wjoin(lfile)):
>> @@ -662,12 +661,11 @@
>>       wlock = repo.wlock()
>>       try:
>>           lfdirstate = lfutil.openlfdirstate(ui, repo)
>> -        (modified, added, removed, missing, unknown, ignored, clean) = \
>> -            lfutil.lfdirstatestatus(lfdirstate, repo, repo['.'].rev())
>> +        s = lfutil.lfdirstatestatus(lfdirstate, repo, repo['.'].rev())
>>           lfdirstate.write()
>> -        for lfile in modified:
>> +        for lfile in s.modified:
>>               lfutil.updatestandin(repo, lfutil.standin(lfile))
>> -        for lfile in missing:
>> +        for lfile in s.deleted:
>
>
> The change would be easier to review if missing was renamed to deleted in a
> separate changeset. It might however not worth the trouble ...
>
>>               if (os.path.exists(repo.wjoin(lfutil.standin(lfile)))):
>>                   os.unlink(repo.wjoin(lfutil.standin(lfile)))
>>   @@ -956,17 +954,15 @@
>>   def overridebailifchanged(orig, repo):
>>       orig(repo)
>>       repo.lfstatus = True
>> -    modified, added, removed, deleted = repo.status()[:4]
>>       repo.lfstatus = False
>> -    if modified or added or removed or deleted:
>> +    if repo.status().haschanges():
>>           raise util.Abort(_('uncommitted changes'))
>>     # Fetch doesn't use cmdutil.bailifchanged so override it to add the
>> check
>>   def overridefetch(orig, ui, repo, *pats, **opts):
>>       repo.lfstatus = True
>> -    modified, added, removed, deleted = repo.status()[:4]
>>       repo.lfstatus = False
>> -    if modified or added or removed or deleted:
>> +    if repo.status().haschanges():
>>           raise util.Abort(_('uncommitted changes'))
>>       return orig(ui, repo, *pats, **opts)
>>   @@ -981,7 +977,7 @@
>>           s = repo.status(match=m, clean=True)
>>       finally:
>>           repo.lfstatus = False
>> -    forget = sorted(s[0] + s[1] + s[3] + s[6])
>> +    forget = sorted(s.modified + s.added + s.deleted + s.clean)
>>       forget = [f for f in forget if lfutil.standin(f) in
>> repo[None].manifest()]
>>         for f in forget:
>> @@ -1112,16 +1108,15 @@
>>           return orig(repo, pats, opts, dry_run, similarity)
>>       # Get the list of missing largefiles so we can remove them
>>       lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
>> -    s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [],
>> False,
>> -        False, False)
>> -    (unsure, modified, added, removed, missing, unknown, ignored, clean)
>> = s
>> +    (s, lookup) = lfdirstate.status(match_.always(repo.root,
>> repo.getcwd()), [],
>> +                                    False, False, False)
>>         # Call into the normal remove code, but the removing of the
>> standin, we want
>>       # to have handled by original addremove.  Monkey patching here makes
>> sure
>>       # we don't remove the standin in the largefiles code, preventing a
>> very
>>       # confused state later.
>> -    if missing:
>> -        m = [repo.wjoin(f) for f in missing]
>> +    if s.deleted:
>> +        m = [repo.wjoin(f) for f in s.deleted]
>>           repo._isaddremove = True
>>           removelargefiles(repo.ui, repo, *m, **opts)
>>           repo._isaddremove = False
>> @@ -1148,10 +1143,9 @@
>>           r = oldstatus(node1, node2, match, ignored, clean, unknown,
>>                         listsubrepos)
>>           lfdirstate = lfutil.openlfdirstate(ui, repo)
>> -        modified, added, removed, deleted, unknown, ignored, clean = r
>> -        unknown = [f for f in unknown if lfdirstate[f] == '?']
>> -        ignored = [f for f in ignored if lfdirstate[f] == '?']
>> -        return modified, added, removed, deleted, unknown, ignored, clean
>> +        r.unknown = [f for f in r.unknown if lfdirstate[f] == '?']
>> +        r.ignored = [f for f in r.ignored if lfdirstate[f] == '?']
>> +        return r
>>       repo.status = overridestatus
>>       orig(ui, repo, *dirs, **opts)
>>       repo.status = oldstatus
>> @@ -1290,10 +1284,10 @@
>>               # update standins for linear-merge or force-branch-merge,
>>               # because largefiles in the working directory may be
>> modified
>>               lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
>> -            s = lfdirstate.status(match_.always(repo.root,
>> repo.getcwd()),
>> -                                  [], False, False, False)
>> -            unsure, modified, added = s[:3]
>> -            for lfile in unsure + modified + added:
>> +            (s, lookup) = lfdirstate.status(match_.always(repo.root,
>> +                                                          repo.getcwd()),
>> +                                            [], False, False, False)
>> +            for lfile in lookup + s.modified + s.added:
>>                   lfutil.updatestandin(repo, lfutil.standin(lfile))
>>             if linearmerge:
>> diff --git a/hgext/largefiles/reposetup.py b/hgext/largefiles/reposetup.py
>> --- a/hgext/largefiles/reposetup.py
>> +++ b/hgext/largefiles/reposetup.py
>> @@ -5,6 +5,7 @@
>>   #
>>   # This software may be used and distributed according to the terms of
>> the
>>   # GNU General Public License version 2 or any later version.
>> +from mercurial.dirstate import status
>
>
> It is better to use   from mercurial import dirstate   and then use
> dirstate.status later on. Both to preserve the namespacing but also to allow
> demandimport to do its job.
>
>>   '''setup for largefiles repositories: reposetup'''
>>   import copy
>> @@ -159,12 +160,13 @@
>>                                       if sfindirstate(f)]
>>                       # Don't waste time getting the ignored and unknown
>>                       # files from lfdirstate
>> -                    s = lfdirstate.status(match, [], False,
>> -                            listclean, False)
>> -                    (unsure, modified, added, removed, missing, _unknown,
>> -                            _ignored, clean) = s
>> +                    (s, lookup) = lfdirstate.status(match, [], False,
>> +                                                    listclean, False)
>> +                    modified = s.modified
>> +                    added = s.added
>> +                    clean = s.clean
>>                       if parentworking:
>> -                        for lfile in unsure:
>> +                        for lfile in lookup:
>>                               standin = lfutil.standin(lfile)
>>                               if standin not in ctx1:
>>                                   # from second parent
>> @@ -177,7 +179,7 @@
>>                                       clean.append(lfile)
>>                                   lfdirstate.normal(lfile)
>>                       else:
>> -                        tocheck = unsure + modified + added + clean
>> +                        tocheck = lookup + s.modified + s.added + s.clean
>>                           modified, added, clean = [], [], []
>>                             for lfile in tocheck:
>> @@ -191,6 +193,7 @@
>>                               else:
>>                                   added.append(lfile)
>>   +                    removed = s.removed
>>                       # Standins no longer found in lfdirstate has been
>>                       # removed
>>                       for standin in
>> ctx1.walk(lfutil.getstandinmatcher(self)):
>> @@ -200,9 +203,6 @@
>>                           if lfile not in lfdirstate:
>>                               removed.append(lfile)
>>   -                    # Filter result lists
>> -                    result = list(result)
>> -
>>                       # Largefiles are not really removed when they're
>>                       # still in the normal dirstate. Likewise, normal
>>                       # files are not really removed if they are still in
>> @@ -210,19 +210,19 @@
>>                       # change type.
>>                       removed = [f for f in removed
>>                                  if f not in self.dirstate]
>> -                    result[2] = [f for f in result[2]
>> +                    result.removed = [f for f in result.removed
>>                                    if f not in lfdirstate]
>>                         lfiles = set(lfdirstate._map)
>>                       # Unknown files
>> -                    result[4] = set(result[4]).difference(lfiles)
>> +                    result.unknown =
>> set(result.unknown).difference(lfiles)
>>                       # Ignored files
>> -                    result[5] = set(result[5]).difference(lfiles)
>> +                    result.ignored =
>> set(result.ignored).difference(lfiles)
>>                       # combine normal files and largefiles
>>                       normals = [[fn for fn in filelist
>>                                   if not lfutil.isstandin(fn)]
>> -                               for filelist in result]
>> -                    lfstatus = (modified, added, removed, missing, [],
>> [],
>> +                               for filelist in result.all()]
>> +                    lfstatus = (modified, added, removed, s.deleted, [],
>> [],
>>                                   clean)
>>                       result = [sorted(list1 + list2)
>>                                 for (list1, list2) in zip(normals,
>> lfstatus)]
>> @@ -234,6 +234,8 @@
>>                       result = [[toname(f) for f in items]
>>                                 for items in result]
>>   +                result = status(result[0], result[1], result[2],
>> result[3],
>> +                                result[4], result[5], result[6])
>>                   if wlock:
>>                       lfdirstate.write()
>>   @@ -296,10 +298,9 @@
>>                       # large.
>>                       lfdirstate = lfutil.openlfdirstate(ui, self)
>>                       dirtymatch = match_.always(self.root, self.getcwd())
>> -                    s = lfdirstate.status(dirtymatch, [], False, False,
>> False)
>> -                    (unsure, modified, added, removed, _missing,
>> _unknown,
>> -                            _ignored, _clean) = s
>> -                    modifiedfiles = unsure + modified + added + removed
>> +                    (s, lookup) = lfdirstate.status(dirtymatch, [],
>> False,
>> +                                                    False, False)
>> +                    modifiedfiles = lookup + s.modified + s.added +
>> s.removed
>>                       lfiles = lfutil.listlfiles(self)
>>                       # this only loops through largefiles that exist (not
>>                       # removed/renamed)
>> diff --git a/hgext/mq.py b/hgext/mq.py
>> --- a/hgext/mq.py
>> +++ b/hgext/mq.py
>> @@ -1021,7 +1021,7 @@
>>           return None, None
>>         def putsubstate2changes(self, substatestate, changes):
>> -        for files in changes[:3]:
>> +        for files in changes:
>
>
> This imply a change from a list of lists to a single list. Could that be
> done in a separate changeset?
>
>>               if '.hgsubstate' in files:
>>                   return # already listed up
>>           # not yet listed up
>> @@ -1092,11 +1092,11 @@
>>                   if f != '.hgsubstate': # .hgsubstate is auto-created
>>                       raise util.Abort('%s: %s' % (f, msg))
>>               match.bad = badfn
>> -            changes = repo.status(match=match)
>> +            changes = repo.status(match=match).committable()
>>           else:
>> -            changes = self.checklocalchanges(repo, force=True)
>> +            changes = self.checklocalchanges(repo,
>> force=True).committable()
>>           commitfiles = list(inclsubs)
>> -        for files in changes[:3]:
>> +        for files in changes:
>>               commitfiles.extend(files)
>>           match = scmutil.matchfiles(repo, commitfiles)
>>           if len(repo[None].parents()) > 1:
>> @@ -1360,11 +1360,12 @@
>>                 tobackup = set()
>>               if (not nobackup and force) or keepchanges:
>> -                m, a, r, d = self.checklocalchanges(repo, force=True)
>> +                changes = self.checklocalchanges(repo, force=True)
>>                   if keepchanges:
>> -                    tobackup.update(m + a + r + d)
>> +                    tobackup.update(changes.modified + changes.added +
>> +                                    changes.removed + changes.deleted)
>>                   else:
>> -                    tobackup.update(m + a)
>> +                    tobackup.update(changes.modified + changes.added)
>>                 s = self.series[start:end]
>>               all_files = set()
>> @@ -1448,13 +1449,14 @@
>>                 tobackup = set()
>>               if update:
>> -                m, a, r, d = self.checklocalchanges(
>> +                changes = self.checklocalchanges(
>>                       repo, force=force or keepchanges)
>>                   if force:
>>                       if not nobackup:
>> -                        tobackup.update(m + a)
>> +                        tobackup.update(changes.modified + changes.added)
>>                   elif keepchanges:
>> -                    tobackup.update(m + a + r + d)
>> +                    tobackup.update(changes.modified + changes.added +
>> +                                    changes.removed + changes.deleted)
>>                 self.applieddirty = True
>>               end = len(self.applied)
>> @@ -1479,19 +1481,20 @@
>>               if update:
>>                   qp = self.qparents(repo, rev)
>>                   ctx = repo[qp]
>> -                m, a, r, d = repo.status(qp, '.')[:4]
>> -                if d:
>> +                status = repo.status(qp, '.')
>> +                if status.deleted:
>>                       raise util.Abort(_("deletions found between repo
>> revs"))
>>   -                tobackup = set(a + m + r) & tobackup
>> +                tobackup = set(status.added + status.modified +
>> +                               status.removed) & tobackup
>>                   if keepchanges and tobackup:
>>                       raise util.Abort(_("local changes found, refresh
>> first"))
>>                   self.backup(repo, tobackup)
>>                   repo.dirstate.beginparentchange()
>> -                for f in a:
>> +                for f in status.added:
>>                       util.unlinkpath(repo.wjoin(f), ignoremissing=True)
>>                       repo.dirstate.drop(f)
>> -                for f in m + r:
>> +                for f in status.modified + status.removed:
>>                       fctx = ctx[f]
>>                       repo.wwrite(f, fctx.data(), fctx.flags())
>>                       repo.dirstate.normal(f)
>> @@ -1570,7 +1573,8 @@
>>               #   mm, dd, aa = repo.status(top, patchparent)[:3]
>>               # but we do it backwards to take advantage of
>> manifest/changelog
>>               # caching against the next repo.status call
>> -            mm, aa, dd = repo.status(patchparent, top)[:3]
>> +            s = repo.status(patchparent, top)
>> +            mm, aa, dd = s.modified, s.added, s.removed
>>               changes = repo.changelog.read(top)
>>               man = repo.manifest.read(changes[0])
>>               aaa = aa[:]
>> @@ -1585,7 +1589,8 @@
>>                   matchfn = scmutil.match(repo[None], opts=opts)
>>               else:
>>                   match = scmutil.matchall(repo)
>> -            m, a, r, d = repo.status(match=match)[:4]
>> +            s = repo.status(match=match)
>> +            m, a, r, d = s.modified, s.added, s.removed, s.deleted
>>               mm = set(mm)
>>               aa = set(aa)
>>               dd = set(dd)
>> @@ -1624,8 +1629,9 @@
>>                 # create 'match' that includes the files to be
>> recommitted.
>>               # apply matchfn via repo.status to ensure correct case
>> handling.
>> -            cm, ca, cr, cd = repo.status(patchparent, match=matchfn)[:4]
>> -            allmatches = set(cm + ca + cr + cd)
>> +            status = repo.status(patchparent, match=matchfn)
>> +            allmatches = set(status.modified + status.added +
>> status.removed +
>> +                             status.deleted)
>>               refreshchanges = [x.intersection(allmatches) for x in (mm,
>> aa, dd)]
>>                 files = set(inclsubs)
>> diff --git a/hgext/purge.py b/hgext/purge.py
>> --- a/hgext/purge.py
>> +++ b/hgext/purge.py
>> @@ -102,7 +102,7 @@
>>       status = repo.status(match=match, ignored=opts['all'], unknown=True)
>>         if removefiles:
>> -        for f in sorted(status[4] + status[5]):
>> +        for f in sorted(status.unknown + status.ignored):
>>               if act:
>>                   ui.note(_('removing file %s\n') % f)
>>               remove(util.unlink, f)
>> diff --git a/hgext/record.py b/hgext/record.py
>> --- a/hgext/record.py
>> +++ b/hgext/record.py
>> @@ -519,12 +519,12 @@
>>               raise util.Abort(_('cannot partially commit a merge '
>>                                  '(use "hg commit" instead)'))
>>   -        changes = repo.status(match=match)[:3]
>> +        status = repo.status(match=match)
>>           diffopts = opts.copy()
>>           diffopts['nodates'] = True
>>           diffopts['git'] = True
>>           diffopts = patch.diffopts(ui, opts=diffopts)
>> -        chunks = patch.diff(repo, changes=changes, opts=diffopts)
>> +        chunks = patch.diff(repo, changes=status.committable(),
>> opts=diffopts)
>>           fp = cStringIO.StringIO()
>>           fp.write(''.join(chunks))
>>           fp.seek(0)
>> @@ -544,13 +544,13 @@
>>               except AttributeError:
>>                   pass
>>   -        changed = changes[0] + changes[1] + changes[2]
>> +        changed = status.modified + status.added + status.removed
>>           newfiles = [f for f in changed if f in contenders]
>>           if not newfiles:
>>               ui.status(_('no changes to record\n'))
>>               return 0
>>   -        modified = set(changes[0])
>> +        modified = set(status.modified)
>>             # 2. backup changed files, so we can restore them in the end
>>           if backupall:
>> diff --git a/hgext/shelve.py b/hgext/shelve.py
>> --- a/hgext/shelve.py
>> +++ b/hgext/shelve.py
>> @@ -227,9 +227,9 @@
>>             if not node:
>>               stat = repo.status(match=scmutil.match(repo[None], pats,
>> opts))
>> -            if stat[3]:
>> +            if stat.deleted:
>>                   ui.status(_("nothing changed (%d missing files, see "
>> -                            "'hg status')\n") % len(stat[3]))
>> +                            "'hg status')\n") % len(stat.deleted))
>>               else:
>>                   ui.status(_("nothing changed\n"))
>>               return 1
>> @@ -401,7 +401,7 @@
>>           files.extend(shelvectx.parents()[0].files())
>>             # revert will overwrite unknown files, so move them out of the
>> way
>> -        for file in repo.status(unknown=True)[4]:
>> +        for file in repo.status(unknown=True).unknown:
>>               if file in files:
>>                   util.rename(file, file + ".orig")
>>           ui.pushbuffer(True)
>> @@ -545,8 +545,7 @@
>>           # to the original pctx.
>>             # Store pending changes in a commit
>> -        m, a, r, d = repo.status()[:4]
>> -        if m or a or r or d:
>> +        if repo.status().haschanges():
>>               ui.status(_("temporarily committing pending changes "
>>                           "(restore with 'hg unshelve --abort')\n"))
>>               def commitfunc(ui, repo, message, match, opts):
>> diff --git a/hgext/strip.py b/hgext/strip.py
>> --- a/hgext/strip.py
>> +++ b/hgext/strip.py
>> @@ -32,15 +32,15 @@
>>     def checklocalchanges(repo, force=False, excsuffix=''):
>>       cmdutil.checkunfinished(repo)
>> -    m, a, r, d = repo.status()[:4]
>> +    status = repo.status()
>>       if not force:
>> -        if (m or a or r or d):
>> +        if (status.haschanges()):
>>               _("local changes found") # i18n tool detection
>>               raise util.Abort(_("local changes found" + excsuffix))
>>           if checksubstate(repo):
>>               _("local changed subrepos found") # i18n tool detection
>>               raise util.Abort(_("local changed subrepos found" +
>> excsuffix))
>> -    return m, a, r, d
>> +    return status
>>     def strip(ui, repo, revs, update=True, backup=True, force=None,
>> bookmark=None):
>>       wlock = lock = None
>> diff --git a/hgext/transplant.py b/hgext/transplant.py
>> --- a/hgext/transplant.py
>> +++ b/hgext/transplant.py
>> @@ -611,8 +611,7 @@
>>       if not opts.get('continue'):
>>           if p2 != revlog.nullid:
>>               raise util.Abort(_('outstanding uncommitted merges'))
>> -        m, a, r, d = repo.status()[:4]
>> -        if m or a or r or d:
>> +        if repo.status().haschanges():
>>               raise util.Abort(_('outstanding local changes'))
>>         sourcerepo = opts.get('source')
>> diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
>> --- a/mercurial/cmdutil.py
>> +++ b/mercurial/cmdutil.py
>> @@ -83,8 +83,7 @@
>>   def bailifchanged(repo):
>>       if repo.dirstate.p2() != nullid:
>>           raise util.Abort(_('outstanding uncommitted merge'))
>> -    modified, added, removed, deleted = repo.status()[:4]
>> -    if modified or added or removed or deleted:
>> +    if repo.status().haschanges():
>>           raise util.Abort(_('uncommitted changes'))
>>       ctx = repo[None]
>>       for s in sorted(ctx.substate):
>> @@ -940,7 +939,8 @@
>>                         label='log.date')
>>             if self.ui.debugflag:
>> -            files = self.repo.status(log.parents(changenode)[0],
>> changenode)[:3]
>> +            status = self.repo.status(log.parents(changenode)[0],
>> changenode)
>> +            files = (status.modified, status.added, status.removed)
>>               for key, value in zip([# i18n: column positioning for "hg
>> log"
>>                                      _("files:"),
>>                                      # i18n: column positioning for "hg
>> log"
>> @@ -1074,11 +1074,11 @@
>>                 files = ctx.status(ctx.p1())
>>               self.ui.write(',\n  "modified": [%s]' %
>> -                          ", ".join('"%s"' % j(f) for f in files[0]))
>> +                          ", ".join('"%s"' % j(f) for f in
>> files.modified))
>>               self.ui.write(',\n  "added": [%s]' %
>> -                          ", ".join('"%s"' % j(f) for f in files[1]))
>> +                          ", ".join('"%s"' % j(f) for f in files.added))
>>               self.ui.write(',\n  "removed": [%s]' %
>> -                          ", ".join('"%s"' % j(f) for f in files[2]))
>> +                          ", ".join('"%s"' % j(f) for f in
>> files.removed))
>>             elif self.ui.verbose:
>>               self.ui.write(',\n  "files": [%s]' %
>> @@ -2016,7 +2016,7 @@
>>       wctx = repo[None]
>>       forgot = []
>>       s = repo.status(match=match, clean=True)
>> -    forget = sorted(s[0] + s[1] + s[3] + s[6])
>> +    forget = sorted(s.modified + s.added + s.deleted + s.clean)
>>       if explicitonly:
>>           forget = [f for f in forget if match.exact(f)]
>>   @@ -2187,8 +2187,8 @@
>>               if len(old.parents()) > 1:
>>                   # ctx.files() isn't reliable for merges, so fall back to
>> the
>>                   # slower repo.status() method
>> -                files = set([fn for st in repo.status(base, old)[:3]
>> -                             for fn in st])
>> +                status = repo.status(base, old)
>> +                files = set([fn for st in status.committable() for fn in
>> st])
>>               else:
>>                   files = set(old.files())
>>   @@ -2511,19 +2511,19 @@
>>                                     unknown=True, ignored=True,
>> clean=True)
>>           else:
>>               changes = repo.status(match=m)
>> -            for kind in changes:
>> +            for kind in changes.all():
>>                   for abs in kind:
>>                       names[abs] = m.rel(abs), m.exact(abs)
>>                 m = scmutil.matchfiles(repo, names)
>>   -        modified = set(changes[0])
>> -        added    = set(changes[1])
>> -        removed  = set(changes[2])
>> -        _deleted = set(changes[3])
>> -        unknown  = set(changes[4])
>> -        unknown.update(changes[5])
>> -        clean    = set(changes[6])
>> +        modified = set(changes.modified)
>> +        added    = set(changes.added)
>> +        removed  = set(changes.removed)
>> +        _deleted = set(changes.deleted)
>
>
> Instead of refactoring an unused line, it would be nice to just remove it in
> an earlier changeset.
>
>> +        unknown  = set(changes.unknown)
>> +        unknown.update(changes.ignored)
>> +        clean    = set(changes.clean)
>>             # split between files known in target manifest and the others
>>           smf = set(mf)
>> @@ -2544,9 +2544,9 @@
>>               modified, added, removed = set(), set(), set()
>>           else:
>>               changes = repo.status(node1=parent, match=m)
>> -            dsmodified = set(changes[0])
>> -            dsadded    = set(changes[1])
>> -            dsremoved  = set(changes[2])
>> +            dsmodified = set(changes.modified)
>> +            dsadded    = set(changes.added)
>> +            dsremoved  = set(changes.removed)
>>                 # only take into account for removes between wc and target
>>               clean |= dsremoved - removed
>> diff --git a/mercurial/commands.py b/mercurial/commands.py
>> --- a/mercurial/commands.py
>> +++ b/mercurial/commands.py
>> @@ -1491,9 +1491,9 @@
>>             if not node:
>>               stat = repo.status(match=scmutil.match(repo[None], pats,
>> opts))
>> -            if stat[3]:
>> +            if stat.deleted:
>>                   ui.status(_("nothing changed (%d missing files, see "
>> -                            "'hg status')\n") % len(stat[3]))
>> +                            "'hg status')\n") % len(stat.deleted))
>>               else:
>>                   ui.status(_("nothing changed\n"))
>>               return 1
>> @@ -3951,7 +3951,7 @@
>>               parents = ctx.parents()
>>               changed = ""
>>               if default or id or num:
>> -                if (util.any(repo.status())
>> +                if (util.any(repo.status().all())
>>                       or util.any(ctx.sub(s).dirty() for s in
>> ctx.substate)):
>>                       changed = '+'
>>               if default or id:
>> @@ -5149,7 +5149,6 @@
>>         m = scmutil.match(repo[None], pats, opts)
>>       s = repo.status(match=m, clean=True)
>> -    modified, added, deleted, clean = s[0], s[1], s[3], s[6]
>>         # warn about failure to delete explicit files/dirs
>>       wctx = repo[None]
>> @@ -5165,19 +5164,19 @@
>>           ret = 1
>>         if force:
>> -        list = modified + deleted + clean + added
>> +        list = s.modified + s.deleted + s.clean + s.added
>>       elif after:
>> -        list = deleted
>> -        for f in modified + added + clean:
>> +        list = s.deleted
>> +        for f in s.modified + s.added + s.clean:
>>               ui.warn(_('not removing %s: file still exists\n') %
>> m.rel(f))
>>               ret = 1
>>       else:
>> -        list = deleted + clean
>> -        for f in modified:
>> +        list = s.deleted + s.clean
>> +        for f in s.modified:
>>               ui.warn(_('not removing %s: file is modified (use -f'
>>                         ' to force removal)\n') % m.rel(f))
>>               ret = 1
>> -        for f in added:
>> +        for f in s.added:
>>               ui.warn(_('not removing %s: file has been marked for add'
>>                         ' (use forget to undo)\n') % m.rel(f))
>>               ret = 1
>> @@ -5190,7 +5189,7 @@
>>       try:
>>           if not after:
>>               for f in list:
>> -                if f in added:
>> +                if f in s.added:
>>                       continue # we never unlink added files on remove
>>                   util.unlinkpath(repo.wjoin(f), ignoremissing=True)
>>           repo[None].forget(list)
>> @@ -5406,7 +5405,7 @@
>>               hint = _("uncommitted merge, use --all to discard all
>> changes,"
>>                        " or 'hg update -C .' to abort the merge")
>>               raise util.Abort(msg, hint=hint)
>> -        dirty = util.any(repo.status())
>> +        dirty = util.any(repo.status().all())
>>           node = ctx.node()
>>           if node != parent:
>>               if dirty:
>> @@ -5711,7 +5710,14 @@
>>       stat = repo.status(node1, node2, scmutil.match(repo[node2], pats,
>> opts),
>>                          'ignored' in show, 'clean' in show, 'unknown' in
>> show,
>>                          opts.get('subrepos'))
>> -    changestates = zip(states, 'MAR!?IC', stat)
>> +    changestates = zip(states, 'MAR!?IC',
>> +                       [stat.modified,
>> +                        stat.added,
>> +                        stat.removed,
>> +                        stat.deleted,
>> +                        stat.unknown,
>> +                        stat.ignored,
>> +                        stat.clean])
>>         if (opts.get('all') or opts.get('copies')) and not
>> opts.get('no_status'):
>>           copy = copies.pathcopies(repo[node1], repo[node2])
>> @@ -5793,7 +5799,7 @@
>>               ui.write(' ' + m, label='log.bookmark')
>>           ui.write('\n', label='log.bookmark')
>>   -    st = list(repo.status(unknown=True))[:5]
>> +    st = list(repo.status(unknown=True).all())[:5]
>>         c = repo.dirstate.copies()
>>       copied, renamed = [], []
>> diff --git a/mercurial/context.py b/mercurial/context.py
>> --- a/mercurial/context.py
>> +++ b/mercurial/context.py
>> @@ -14,6 +14,7 @@
>>   import repoview
>>   import fileset
>>   import revlog
>> +from mercurial.dirstate import status
>>     propertycache = util.propertycache
>>   @@ -120,7 +121,7 @@
>>           mf2 = self._manifestmatches(match, s)
>>             modified, added, clean = [], [], []
>> -        deleted, unknown, ignored = s[3], s[4], s[5]
>> +        deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
>>           withflags = mf1.withflags() | mf2.withflags()
>>           for fn, mf2node in mf2.iteritems():
>>               if fn in mf1:
>> @@ -140,7 +141,8 @@
>>               unknown = [fn for fn in unknown if fn not in mf1]
>>               ignored = [fn for fn in ignored if fn not in mf1]
>>   -        return [modified, added, removed, deleted, unknown, ignored,
>> clean]
>> +        return status(modified, added, removed, deleted, unknown,
>> ignored,
>> +                      clean)
>>         @propertycache
>>       def substate(self):
>> @@ -310,7 +312,7 @@
>>               reversed = True
>>               ctx1, ctx2 = ctx2, ctx1
>>   -        r = [[], [], [], [], [], [], []]
>> +        r = status([], [], [], [], [], [], [])
>
>
> Shouldn't "empty list" be the default for status.__ini__?
>
>>           match = ctx2._matchstatus(ctx1, r, match, listignored,
>> listclean,
>>                                     listunknown)
>>           r = ctx2._prestatus(ctx1, r, match, listignored, listclean,
>> listunknown)
>> @@ -321,7 +323,7 @@
>>             if reversed:
>>               # reverse added and removed
>> -            r[1], r[2] = r[2], r[1]
>> +            r.added, r.removed = r.removed, r.added
>>             if listsubrepos:
>>               for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
>> @@ -331,17 +333,16 @@
>>                       s = sub.status(rev2, match=submatch,
>> ignored=listignored,
>>                                      clean=listclean, unknown=listunknown,
>>                                      listsubrepos=True)
>> -                    for rfiles, sfiles in zip(r, s):
>> +                    for rfiles, sfiles in zip(r.all(), s.all()):
>>                           rfiles.extend("%s/%s" % (subpath, f) for f in
>> sfiles)
>>                   except error.LookupError:
>>                       self._repo.ui.status(_("skipping missing "
>>                                              "subrepository: %s\n") %
>> subpath)
>>   -        for l in r:
>> +        for l in r.all():
>>               l.sort()
>>   -        # we return a tuple to signify that this list isn't changing
>> -        return tuple(r)
>> +        return r
>>       def makememctx(repo, parents, text, user, date, branch, files,
>> store,
>> @@ -1050,8 +1051,7 @@
>>             copied = self._repo.dirstate.copies()
>>           ff = self._flagfunc
>> -        modified, added, removed, deleted = self._status[:4]
>> -        for i, l in (("a", added), ("m", modified)):
>> +        for i, l in (("a", self._status.added), ("m",
>> self._status.modified)):
>>               for f in l:
>>                   orig = copied.get(f, f)
>>                   man[f] = getman(orig).get(orig, nullid) + i
>> @@ -1060,7 +1060,7 @@
>>                   except OSError:
>>                       pass
>>   -        for f in deleted + removed:
>> +        for f in self._status.deleted + self._status.removed:
>>               if f in man:
>>                   del man[f]
>>   @@ -1088,22 +1088,23 @@
>>       def description(self):
>>           return self._text
>>       def files(self):
>> -        return sorted(self._status[0] + self._status[1] +
>> self._status[2])
>> +        return sorted(self._status.modified + self._status.added + \
>> +                      self._status.removed)
>>         def modified(self):
>> -        return self._status[0]
>> +        return self._status.modified
>>       def added(self):
>> -        return self._status[1]
>> +        return self._status.added
>>       def removed(self):
>> -        return self._status[2]
>> +        return self._status.removed
>>       def deleted(self):
>> -        return self._status[3]
>> +        return self._status.deleted
>>       def unknown(self):
>> -        return self._status[4]
>> +        return self._status.unknown
>>       def ignored(self):
>> -        return self._status[5]
>> +        return self._status.ignored
>>       def clean(self):
>> -        return self._status[6]
>> +        return self._status.clean
>>       def branch(self):
>>           return encoding.tolocal(self._extra['branch'])
>>       def closesbranch(self):
>> @@ -1378,11 +1379,10 @@
>>           need to build a manifest and return what matches.
>>           """
>>           mf = self._repo['.']._manifestmatches(match, s)
>> -        modified, added, removed = s[0:3]
>> -        for f in modified + added:
>> +        for f in s.modified + s.added:
>>               mf[f] = None
>>               mf.set(f, self.flags(f))
>> -        for f in removed:
>> +        for f in s.removed:
>>               if f in mf:
>>                   del mf[f]
>>           return mf
>> @@ -1405,8 +1405,8 @@
>>           accidentally ended up with the entire contents of the file they
>> are
>>           susposed to be linking to.
>>           """
>> -        s[0] = self._filtersuspectsymlink(s[0])
>> -        self._status = s[:]
>> +        s.modified = self._filtersuspectsymlink(s.modified)
>> +        self._status = s
>>           return s
>>         def _dirstatestatus(self, match=None, ignored=False, clean=False,
>> @@ -1417,20 +1417,19 @@
>>           subrepos = []
>>           if '.hgsub' in self:
>>               subrepos = sorted(self.substate)
>> -        s = self._repo.dirstate.status(match, subrepos, listignored,
>> -                                       listclean, listunknown)
>> -        cmp, modified, added, removed, deleted, unknown, ignored, clean =
>> s
>> +        (s, lookup) = self._repo.dirstate.status(match, subrepos,
>> listignored,
>> +                                                 listclean, listunknown)
>>             # check for any possibly clean files
>> -        if cmp:
>> -            modified2, fixup = self._checklookup(cmp)
>> -            modified += modified2
>> +        if lookup:
>> +            modified2, fixup = self._checklookup(lookup)
>> +            s.modified += modified2
>>   -            # update dirstate for files that are actually clean
>> +            # update status for files that are actually clean
>>               if fixup and listclean:
>> -                clean += fixup
>> +                s.clean += fixup
>>   -        return [modified, added, removed, deleted, unknown, ignored,
>> clean]
>> +        return s
>>         def _buildstatus(self, other, s, match, listignored, listclean,
>>                        listunknown):
>> @@ -1478,10 +1477,8 @@
>>           s = super(workingctx, self).status(other, match, listignored,
>> listclean,
>>                                              listunknown, listsubrepos)
>>           # calling 'super' subtly reveresed the contexts, so we flip the
>> results
>> -        # (s[1] is 'added' and s[2] is 'removed')
>> -        s = list(s)
>> -        s[1], s[2] = s[2], s[1]
>> -        return tuple(s)
>> +        s.added, s.removed = s.removed, s.added
>> +        return s
>>     class committablefilectx(basefilectx):
>>       """A committablefilectx provides common functionality for a file
>> context
>> @@ -1612,7 +1609,7 @@
>>           p1, p2 = parents
>>           self._parents = [changectx(self._repo, p) for p in (p1, p2)]
>>           files = sorted(set(files))
>> -        self._status = [files, [], [], [], []]
>> +        self._status = status(files, [], [], [], [], [], [])
>>           self._filectxfn = filectxfn
>>           self.substate = {}
>>   diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
>> --- a/mercurial/dirstate.py
>> +++ b/mercurial/dirstate.py
>> @@ -26,6 +26,40 @@
>>       def join(self, obj, fname):
>>           return obj._join(fname)
>>   +class status(object):
>
>
> Please describe the intention with this class.
>
>> +    def __init__(self, modified, added, removed, deleted, unknown,
>> +                ignored, clean):
>> +        self.modified = modified
>> +        self.added = added
>> +        self.removed = removed
>> +        self.deleted = deleted
>> +        self.unknown = unknown
>> +        self.ignored = ignored
>> +        self.clean = clean
>
>
> http://mercurial.selenic.com/wiki/CodingStyle#Classes ... but I agree it is
> nicer than the 7-tuples.

Heh, I guess I should have read that first. I guess "recovering Java
programmer" describes me well, but I'm not sure I thought three times.
I did, however, ask on the IRC channel and heard no complaints (and no
encouragement either). I know Augie is for it.

> It is also very similar to a named tuple. Could we use that instead ... or
> inherit from it? (That would require some kludge as long as we support
> Python < 2.6.)

After having read the style guide, I know that extending from it is
not an option :-) I'll let others discuss and decide, since I know
nothing about Python. The ~8 lines of constructor doesn't seem that
bad to me.

>> +
>> +    def all(self):
>> +        return (self.modified,
>> +                self.added,
>> +                self.removed,
>> +                self.deleted,
>> +                self.unknown,
>> +                self.ignored,
>> +                self.clean)
>
>
> The name 'all' is not completely obvious. If that is the best possible name,
> it deserves a docstring.
>
> But I think a more Pythonic way (whatever that is) would be to define
> __iter__ to make it possible to say   s = tuple(st)
>
>> +    def changes(self):
>> +        return (self.modified,
>> +                self.added,
>> +                self.removed,
>> +                self.deleted)
>
>
> It seems like this (and committable) are convenience functions that could be
> added later.

Agreed. Will do.

> It do not return the changes but the files that have changed. I suggest
> calling it "changed".
>
>> +    def haschanges(self):
>> +        return util.any(self.changes())
>
>
> Comes for free with __iter__.
>
>> +    def committable(self):
>> +        return (self.modified,
>> +                self.added,
>> +                self.removed)
>
>
> Actually, this might be more like what I would call "changed".
>
> It is hard to come up with good names (although docstrings would help).
> Putting them in separate patches might make it more obvious if they are
> worth it.
>
>> @@ -900,8 +934,8 @@
>>               elif state == 'r':
>>                   radd(fn)
>>   -        return (lookup, modified, added, removed, deleted, unknown,
>> ignored,
>> -                clean)
>> +        return (status(modified, added, removed, deleted, unknown,
>> +                      ignored, clean), lookup)
>>         def matches(self, match):
>>           '''
>> diff --git a/mercurial/fileset.py b/mercurial/fileset.py
>> --- a/mercurial/fileset.py
>> +++ b/mercurial/fileset.py
>> @@ -124,7 +124,7 @@
>>       """
>>       # i18n: "modified" is a keyword
>>       getargs(x, 0, 0, _("modified takes no arguments"))
>> -    s = mctx.status()[0]
>> +    s = mctx.status().modified
>>       return [f for f in mctx.subset if f in s]
>>     def added(mctx, x):
>> @@ -133,7 +133,7 @@
>>       """
>>       # i18n: "added" is a keyword
>>       getargs(x, 0, 0, _("added takes no arguments"))
>> -    s = mctx.status()[1]
>> +    s = mctx.status().added
>>       return [f for f in mctx.subset if f in s]
>>     def removed(mctx, x):
>> @@ -142,7 +142,7 @@
>>       """
>>       # i18n: "removed" is a keyword
>>       getargs(x, 0, 0, _("removed takes no arguments"))
>> -    s = mctx.status()[2]
>> +    s = mctx.status().removed
>>       return [f for f in mctx.subset if f in s]
>>     def deleted(mctx, x):
>> @@ -151,7 +151,7 @@
>>       """
>>       # i18n: "deleted" is a keyword
>>       getargs(x, 0, 0, _("deleted takes no arguments"))
>> -    s = mctx.status()[3]
>> +    s = mctx.status().deleted
>>       return [f for f in mctx.subset if f in s]
>>     def unknown(mctx, x):
>> @@ -161,7 +161,7 @@
>>       """
>>       # i18n: "unknown" is a keyword
>>       getargs(x, 0, 0, _("unknown takes no arguments"))
>> -    s = mctx.status()[4]
>> +    s = mctx.status().unknown
>>       return [f for f in mctx.subset if f in s]
>>     def ignored(mctx, x):
>> @@ -171,7 +171,7 @@
>>       """
>>       # i18n: "ignored" is a keyword
>>       getargs(x, 0, 0, _("ignored takes no arguments"))
>> -    s = mctx.status()[5]
>> +    s = mctx.status().ignored
>>       return [f for f in mctx.subset if f in s]
>>     def clean(mctx, x):
>> @@ -180,7 +180,7 @@
>>       """
>>       # i18n: "clean" is a keyword
>>       getargs(x, 0, 0, _("clean takes no arguments"))
>> -    s = mctx.status()[6]
>> +    s = mctx.status().clean
>>       return [f for f in mctx.subset if f in s]
>>     def func(mctx, a, b):
>> @@ -448,8 +448,8 @@
>>           return [f for f in files if f in self.subset]
>>       def existing(self):
>>           if self._status is not None:
>> -            removed = set(self._status[3])
>> -            unknown = set(self._status[4] + self._status[5])
>> +            removed = set(self._status.deleted)
>> +            unknown = set(self._status.unknown + self._status.ignored)
>>           else:
>>               removed = set()
>>               unknown = set()
>> @@ -495,7 +495,7 @@
>>           status = r.status(ctx.p1(), ctx,
>>                             unknown=unknown, ignored=ignored, clean=True)
>>           subset = []
>> -        for c in status:
>> +        for c in status.all():
>>               subset.extend(c)
>>       else:
>>           status = None
>> diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
>> --- a/mercurial/localrepo.py
>> +++ b/mercurial/localrepo.py
>> @@ -574,7 +574,7 @@
>>           date: date tuple to use if committing'''
>>             if not local:
>> -            for x in self.status()[:4]:
>> +            for x in self.status().changes():
>>                   if '.hgtags' in x:
>>                       raise util.Abort(_('working copy of .hgtags is
>> changed '
>>                                          '(please commit .hgtags
>> manually)'))
>> @@ -1229,9 +1229,10 @@
>>                   raise util.Abort(_('cannot partially commit a merge '
>>                                      '(do not specify files or
>> patterns)'))
>>   -            changes = self.status(match=match, clean=force)
>> +            status = self.status(match=match, clean=force)
>>               if force:
>> -                changes[0].extend(changes[6]) # mq may commit unchanged
>> files
>> +                # mq may commit unchanged files
>> +                status.modified.extend(status.clean)
>>                 # check subrepos
>>               subs = []
>> @@ -1240,7 +1241,7 @@
>>               # only manage subrepos and .hgsubstate if .hgsub is present
>>               if '.hgsub' in wctx:
>>                   # we'll decide whether to track this ourselves, thanks
>> -                for c in changes[:3]:
>> +                for c in [status.modified, status.added, status.removed]:
>
>
> A place where commitable could be used? Except I don't think it is that bad
> to have it explicit ;-)
>
>>                       if '.hgsubstate' in c:
>>                           c.remove('.hgsubstate')
>>   @@ -1278,23 +1279,24 @@
>>                           '.hgsub' in (wctx.modified() + wctx.added())):
>>                           raise util.Abort(
>>                               _("can't commit subrepos without .hgsub"))
>> -                    changes[0].insert(0, '.hgsubstate')
>> +                    status.modified.insert(0, '.hgsubstate')
>>   -            elif '.hgsub' in changes[2]:
>> +            elif '.hgsub' in status.removed:
>>                   # clean up .hgsubstate when .hgsub is removed
>>                   if ('.hgsubstate' in wctx and
>> -                    '.hgsubstate' not in changes[0] + changes[1] +
>> changes[2]):
>> -                    changes[2].insert(0, '.hgsubstate')
>> +                    '.hgsubstate' not in (status.modified + status.added
>> +
>> +                                          status.removed)):
>> +                    status.removed.insert(0, '.hgsubstate')
>>                 # make sure all explicit patterns are matched
>>               if not force and match.files():
>> -                matched = set(changes[0] + changes[1] + changes[2])
>> +                matched = set(status.modified + status.added +
>> status.removed)
>>                     for f in match.files():
>>                       f = self.dirstate.normalize(f)
>>                       if f == '.' or f in matched or f in wctx.substate:
>>                           continue
>> -                    if f in changes[3]: # missing
>> +                    if f in status.deleted:
>>                           fail(f, _('file not found!'))
>>                       if f in vdirs: # visited directory
>>                           d = f + '/'
>> @@ -1306,7 +1308,7 @@
>>                       elif f not in self.dirstate:
>>                           fail(f, _("file not tracked!"))
>>   -            cctx = context.workingctx(self, text, user, date, extra,
>> changes)
>> +            cctx = context.workingctx(self, text, user, date, extra,
>> status)
>>                 if (not force and not extra.get("close") and not merge
>>                   and not cctx.files()
>> @@ -1317,7 +1319,7 @@
>>                   raise util.Abort(_("cannot commit merge with missing
>> files"))
>>                 ms = mergemod.mergestate(self)
>> -            for f in changes[0]:
>> +            for f in status.modified:
>>                   if f in ms and ms[f] == 'u':
>>                       raise util.Abort(_("unresolved merge conflicts "
>>                                          "(see hg help resolve)"))
>> diff --git a/mercurial/patch.py b/mercurial/patch.py
>> --- a/mercurial/patch.py
>> +++ b/mercurial/patch.py
>> @@ -1619,8 +1619,8 @@
>>       ctx2 = repo[node2]
>>         if not changes:
>> -        changes = repo.status(ctx1, ctx2, match=match)
>> -    modified, added, removed = changes[:3]
>> +        changes = repo.status(ctx1, ctx2, match=match).committable()
>> +    modified, added, removed = changes[0], changes[1], changes[2]
>
>
> That seems convoluted.
>
>>       if not modified and not added and not removed:
>>           return []
>> diff --git a/mercurial/revset.py b/mercurial/revset.py
>> --- a/mercurial/revset.py
>> +++ b/mercurial/revset.py
>> @@ -316,7 +316,7 @@
>>       """
>>       # i18n: "adds" is a keyword
>>       pat = getstring(x, _("adds requires a pattern"))
>> -    return checkstatus(repo, subset, pat, 1)
>> +    return checkstatus(repo, subset, pat, 'added')
>
>
> Using indices was bad, but member names seems worse. Could it be done in a
> more elegant way?
>
>>   def ancestor(repo, subset, x):
>>       """``ancestor(*changeset)``
>> @@ -544,7 +544,7 @@
>>                       break
>>               else:
>>                   return False
>> -        files = repo.status(c.p1().node(), c.node())[field]
>> +        files = repo.status(c.p1().node(), c.node()).__dict__[field]
>
>
> field -> fieldname ?
>
>>           if fname is not None:
>>               if fname in files:
>>                   return True
>> @@ -1112,7 +1112,7 @@
>>       """
>>       # i18n: "modifies" is a keyword
>>       pat = getstring(x, _("modifies requires a pattern"))
>> -    return checkstatus(repo, subset, pat, 0)
>> +    return checkstatus(repo, subset, pat, 'modified')
>>     def node_(repo, subset, x):
>>       """``id(string)``
>> @@ -1336,7 +1336,7 @@
>>       """
>>       # i18n: "removes" is a keyword
>>       pat = getstring(x, _("removes requires a pattern"))
>> -    return checkstatus(repo, subset, pat, 2)
>> +    return checkstatus(repo, subset, pat, 'removed')
>>     def rev(repo, subset, x):
>>       """``rev(number)``
>> diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py
>> --- a/mercurial/subrepo.py
>> +++ b/mercurial/subrepo.py
>> @@ -12,6 +12,7 @@
>>   import config, util, node, error, cmdutil, bookmarks, match as matchmod
>>   import phases
>>   import pathutil
>> +from mercurial.dirstate import status as statusmod
>
>
> status is not a module - calling it statusmod is confusing. But as before,
> just import dirstate and use dirstate.status .

Oh, that's what 'mod' means. I just thought it meant 'modified' and
was used to avoid conflicts. Will fix.

>>   hg = None
>>   propertycache = util.propertycache
>>   @@ -447,7 +448,7 @@
>>           return 1
>>         def status(self, rev2, **opts):
>> -        return [], [], [], [], [], [], []
>> +        return statusmod([], [], [], [], [], [], [])
>>         def diff(self, ui, diffopts, node2, match, prefix, **opts):
>>           pass
>> @@ -649,7 +650,7 @@
>>           except error.RepoLookupError, inst:
>>               self._repo.ui.warn(_('warning: error "%s" in subrepository
>> "%s"\n')
>>                                  % (inst, subrelpath(self)))
>> -            return [], [], [], [], [], [], []
>> +            return statusmod([], [], [], [], [], [], [])
>>         @annotatesubrepoerror
>>       def diff(self, ui, diffopts, node2, match, prefix, **opts):
>> @@ -1564,7 +1565,7 @@
>>           rev1 = self._state[1]
>>           if self._gitmissing() or not rev1:
>>               # if the repo is missing, return no results
>> -            return [], [], [], [], [], [], []
>> +            return statusmod([], [], [], [], [], [], [])
>>           modified, added, removed = [], [], []
>>           self._gitupdatestat()
>>           if rev2:
>> @@ -1585,7 +1586,8 @@
>>                   removed.append(f)
>>             deleted = unknown = ignored = clean = []
>> -        return modified, added, removed, deleted, unknown, ignored, clean
>> +        return statusmod(modified, added, removed, deleted, unknown,
>> ignored,
Martin von Zweigbergk - Oct. 2, 2014, 5:39 a.m.
On Wed, Oct 1, 2014 at 6:40 PM, Mads Kiilerich <mads@kiilerich.com> wrote:
>
> On 10/02/2014 02:01 AM, Martin von Zweigbergk wrote:
>>
>>   @@ -2511,19 +2511,19 @@
>>                                     unknown=True, ignored=True,
>> clean=True)
>>           else:
>>               changes = repo.status(match=m)
>> -            for kind in changes:
>> +            for kind in changes.all():
>>                   for abs in kind:
>>                       names[abs] = m.rel(abs), m.exact(abs)
>>                 m = scmutil.matchfiles(repo, names)
>>   -        modified = set(changes[0])
>> -        added    = set(changes[1])
>> -        removed  = set(changes[2])
>> -        _deleted = set(changes[3])
>> -        unknown  = set(changes[4])
>> -        unknown.update(changes[5])
>> -        clean    = set(changes[6])
>> +        modified = set(changes.modified)
>> +        added    = set(changes.added)
>> +        removed  = set(changes.removed)
>> +        _deleted = set(changes.deleted)
>
>
> Instead of refactoring an unused line,

Are you referring to '_deleted'? Why do you think it's unused? Because
it starts with underscore? I didn't see anything about that in the
style guide, but I think I've seen that convention in some places.
Should I send a patch?

> it would be nice to just remove it in
> an earlier changeset.

Off topic, but that reminds me: I hope someone is working on the
'stop/halt' action for histedit so that's not such a pain. (But please
correct me if there's something better than "hg bookmark -r .^^^ tmp
&& hg update tmp && <edit> && hg commit && hg update feature && hg
rebase -d tmp && hg bookmarks -d tmp". I'd prefer something like "hg
histedit && <edit> && hg commit && hg histedit --continue".)
Yuya Nishihara - Oct. 2, 2014, 12:07 p.m.
On Wed, 1 Oct 2014 22:00:15 -0700, Martin von Zweigbergk wrote:
> >>   +class status(object):
> >
> >
> > Please describe the intention with this class.
> >
> >> +    def __init__(self, modified, added, removed, deleted, unknown,
> >> +                ignored, clean):
> >> +        self.modified = modified
> >> +        self.added = added
> >> +        self.removed = removed
> >> +        self.deleted = deleted
> >> +        self.unknown = unknown
> >> +        self.ignored = ignored
> >> +        self.clean = clean
> >
> >
> > http://mercurial.selenic.com/wiki/CodingStyle#Classes ... but I agree it is
> > nicer than the 7-tuples.
> 
> Heh, I guess I should have read that first. I guess "recovering Java
> programmer" describes me well, but I'm not sure I thought three times.
> I did, however, ask on the IRC channel and heard no complaints (and no
> encouragement either). I know Augie is for it.
> 
> > It is also very similar to a named tuple. Could we use that instead ... or
> > inherit from it? (That would require some kludge as long as we support
> > Python < 2.6.)
> 
> After having read the style guide, I know that extending from it is
> not an option :-) I'll let others discuss and decide, since I know
> nothing about Python. The ~8 lines of constructor doesn't seem that
> bad to me.

It's fairly easy to build namedtuple-like object.

https://hg.python.org/cpython/file/v2.7.8/Lib/collections.py#l234

    class statustuple(tuple):
        __slots__ = ()

        def __new__(cls, modified, added, ...):
            return tuple.__new__(cls, (modified, added, ...))

        @property
        def modified(self):
            return self[0]
        ...

        def __repr__(self):
            ...

It can avoid breaking third-party extensions (and thg) that expects status()
returns a tuple.

Regards,
Mads Kiilerich - Oct. 2, 2014, 12:46 p.m.
On 10/02/2014 07:00 AM, Martin von Zweigbergk wrote:
> Thanks for taking a look! Answers below to some of your questions.
> I'll leave the others for later, or they might just get address in v2
> of the series.
>
> On Wed, Oct 1, 2014 at 6:40 PM, Mads Kiilerich <mads@kiilerich.com> wrote:
>> A general comment: A "." lookup is relatively expensive. It seems like you
>> introduce a lot of them. We should take care to not introduce more of them
>> in tight loops (that might already be the case - I did not check and not see
>> any statement on whether that was the case).
> Hmm, I don't think I even considered that (I've never used python before).
>
> I tried running the test suite:
>
> Before:
> real    3m22.026s
> user    25m31.715s
> sys 5m45.684s
>
> After:
> real    3m24.136s
> user    25m29.724s
> sys     5m46.637s
>
> I also tried this:
>
> $ python -mtimeit -s'a=(1,2,3,4,5,6)' 'a[3]'
> 10000000 loops, best of 3: 0.0402 usec per loop
>
> $ python -mtimeit -s'class c(object):
>    def __init__(self,a,b,c,d,e,f):
>      self.a=a
>      self.b=b
>      self.c=c
>      self.d=d
>      self.e=e
>      self.f=f
> a=c(1,2,3,4,5,6)' 'a.d'
> 10000000 loops, best of 3: 0.0458 usec per loop

Yes, these two are pretty much the same. But:

$ python -mtimeit -s'a=(1,2,3,4,5,6)' -s 'b=a[0]' 'x=b'
100000000 loops, best of 3: 0.0114 usec per loop

$ python -mtimeit -s'class c(object):
   def __init__(self,a,b,c,d,e,f):
     self.a=a
     self.b=b
     self.c=c
     self.d=d
     self.e=e
     self.f=f
a=c(1,2,3,4,5,6)' 'x=a.d'
10000000 loops, best of 3: 0.026 usec per loop

That is why we do crazy stuff like 
http://selenic.com/hg/rev/b6dc3b79bb25 ... but only in tight loops.

The test suite do not have "realistic" repo sizes and will not reveal 
new hot spots.

>> I also wonder if it would make sense to make "unsure" an optional member of
>> the status struct. I don't know.
> I know, and it doesn't :-). I did that at first, but it just got ugly
> with having to assert (or assume) that lookup/unsure was empty where
> it should be.

Would it be that different from how .clean also is "optional"?

/Mads
Martin von Zweigbergk - Oct. 2, 2014, 1:12 p.m.
On Thu, Oct 2, 2014 at 5:07 AM, Yuya Nishihara <yuya@tcha.org> wrote:
> On Wed, 1 Oct 2014 22:00:15 -0700, Martin von Zweigbergk wrote:
>> >>   +class status(object):
>> >
>> >
>> > Please describe the intention with this class.
>> >
>> >> +    def __init__(self, modified, added, removed, deleted, unknown,
>> >> +                ignored, clean):
>> >> +        self.modified = modified
>> >> +        self.added = added
>> >> +        self.removed = removed
>> >> +        self.deleted = deleted
>> >> +        self.unknown = unknown
>> >> +        self.ignored = ignored
>> >> +        self.clean = clean
>> >
>> >
>> > http://mercurial.selenic.com/wiki/CodingStyle#Classes ... but I agree it is
>> > nicer than the 7-tuples.
>>
>> Heh, I guess I should have read that first. I guess "recovering Java
>> programmer" describes me well, but I'm not sure I thought three times.
>> I did, however, ask on the IRC channel and heard no complaints (and no
>> encouragement either). I know Augie is for it.
>>
>> > It is also very similar to a named tuple. Could we use that instead ... or
>> > inherit from it? (That would require some kludge as long as we support
>> > Python < 2.6.)
>>
>> After having read the style guide, I know that extending from it is
>> not an option :-) I'll let others discuss and decide, since I know
>> nothing about Python. The ~8 lines of constructor doesn't seem that
>> bad to me.
>
> It's fairly easy to build namedtuple-like object.
>
> https://hg.python.org/cpython/file/v2.7.8/Lib/collections.py#l234
>
>     class statustuple(tuple):
>         __slots__ = ()
>
>         def __new__(cls, modified, added, ...):
>             return tuple.__new__(cls, (modified, added, ...))
>
>         @property
>         def modified(self):
>             return self[0]
>         ...
>
>         def __repr__(self):
>             ...
>
> It can avoid breaking third-party extensions (and thg) that expects status()
> returns a tuple.
>
> Regards,

Sure, I'm happy to make it extend tuple if others agree. I actually
considered letting the class extend tuple temporarily in the patch
series to make the diffs smaller, but I don't really want it to be a
tuple in the end (I'd prefer if new instances of "status[4]" could not
be added later).

I'll also let the rest of you reach decide whether to use namedtuple.
After reading about them for about a minute, I can't answer that
myself.
Martin von Zweigbergk - Oct. 2, 2014, 2:26 p.m.
On Thu, Oct 2, 2014 at 5:46 AM, Mads Kiilerich <mads@kiilerich.com> wrote:
> On 10/02/2014 07:00 AM, Martin von Zweigbergk wrote:
>>
>> Thanks for taking a look! Answers below to some of your questions.
>> I'll leave the others for later, or they might just get address in v2
>> of the series.
>>
>> On Wed, Oct 1, 2014 at 6:40 PM, Mads Kiilerich <mads@kiilerich.com> wrote:
>>>
>>> A general comment: A "." lookup is relatively expensive. It seems like
>>> you
>>> introduce a lot of them. We should take care to not introduce more of
>>> them
>>> in tight loops (that might already be the case - I did not check and not
>>> see
>>> any statement on whether that was the case).
>>
>> Hmm, I don't think I even considered that (I've never used python before).
>>
>> I tried running the test suite:
>>
>> Before:
>> real    3m22.026s
>> user    25m31.715s
>> sys 5m45.684s
>>
>> After:
>> real    3m24.136s
>> user    25m29.724s
>> sys     5m46.637s
>>
>> I also tried this:
>>
>> $ python -mtimeit -s'a=(1,2,3,4,5,6)' 'a[3]'
>> 10000000 loops, best of 3: 0.0402 usec per loop
>>
>> $ python -mtimeit -s'class c(object):
>>    def __init__(self,a,b,c,d,e,f):
>>      self.a=a
>>      self.b=b
>>      self.c=c
>>      self.d=d
>>      self.e=e
>>      self.f=f
>> a=c(1,2,3,4,5,6)' 'a.d'
>> 10000000 loops, best of 3: 0.0458 usec per loop
>
>
> Yes, these two are pretty much the same. But:
>
> $ python -mtimeit -s'a=(1,2,3,4,5,6)' -s 'b=a[0]' 'x=b'
> 100000000 loops, best of 3: 0.0114 usec per loop
>
> $ python -mtimeit -s'class c(object):
>   def __init__(self,a,b,c,d,e,f):
>     self.a=a
>     self.b=b
>     self.c=c
>     self.d=d
>     self.e=e
>     self.f=f
> a=c(1,2,3,4,5,6)' 'x=a.d'
> 10000000 loops, best of 3: 0.026 usec per loop

That doesn't seem like a fair comparison to me. Assuming that '-s'
means "this is setup, don't include in timing", the only thing your
tuple test case does is to test assignment, right? So it's not
comparing tuple lookup vs field name lookup.

> That is why we do crazy stuff like http://selenic.com/hg/rev/b6dc3b79bb25
> ... but only in tight loops.

That seems to only show that looking up is slower than not looking up;
it's not comparing lookup of tuple items and field names. Still, it's
something I wasn't aware of, so I'll make sure not to add any lookups
in loops in my next revision of the series (i.e, I'll do things like
"m = s.modified" outside of loops).

> The test suite do not have "realistic" repo sizes and will not reveal new
> hot spots.
>
>>> I also wonder if it would make sense to make "unsure" an optional member
>>> of
>>> the status struct. I don't know.
>>
>> I know, and it doesn't :-). I did that at first, but it just got ugly
>> with having to assert (or assume) that lookup/unsure was empty where
>> it should be.
>
>
> Would it be that different from how .clean also is "optional"?

Yes, the problem with lookup is that it's not a real status, it's just
an optimization. Callers who care will have to resolve it to be either
modified or clean. Forgetting to resolve gives an incorrect list of
files for .modified and .clean.

>
> /Mads
Matt Mackall - Oct. 2, 2014, 5:58 p.m.
On Thu, 2014-10-02 at 06:12 -0700, Martin von Zweigbergk wrote:
> On Thu, Oct 2, 2014 at 5:07 AM, Yuya Nishihara <yuya@tcha.org> wrote:
> > On Wed, 1 Oct 2014 22:00:15 -0700, Martin von Zweigbergk wrote:
> >> >>   +class status(object):
> >> >
> >> >
> >> > Please describe the intention with this class.
> >> >
> >> >> +    def __init__(self, modified, added, removed, deleted, unknown,
> >> >> +                ignored, clean):
> >> >> +        self.modified = modified
> >> >> +        self.added = added
> >> >> +        self.removed = removed
> >> >> +        self.deleted = deleted
> >> >> +        self.unknown = unknown
> >> >> +        self.ignored = ignored
> >> >> +        self.clean = clean
> >> >
> >> >
> >> > http://mercurial.selenic.com/wiki/CodingStyle#Classes ... but I agree it is
> >> > nicer than the 7-tuples.
> >>
> >> Heh, I guess I should have read that first. I guess "recovering Java
> >> programmer" describes me well, but I'm not sure I thought three times.
> >> I did, however, ask on the IRC channel and heard no complaints (and no
> >> encouragement either). I know Augie is for it.
> >>
> >> > It is also very similar to a named tuple. Could we use that instead ... or
> >> > inherit from it? (That would require some kludge as long as we support
> >> > Python < 2.6.)
> >>
> >> After having read the style guide, I know that extending from it is
> >> not an option :-) I'll let others discuss and decide, since I know
> >> nothing about Python. The ~8 lines of constructor doesn't seem that
> >> bad to me.
> >
> > It's fairly easy to build namedtuple-like object.
> >
> > https://hg.python.org/cpython/file/v2.7.8/Lib/collections.py#l234
> >
> >     class statustuple(tuple):
> >         __slots__ = ()
> >
> >         def __new__(cls, modified, added, ...):
> >             return tuple.__new__(cls, (modified, added, ...))
> >
> >         @property
> >         def modified(self):
> >             return self[0]
> >         ...
> >
> >         def __repr__(self):
> >             ...
> >
> > It can avoid breaking third-party extensions (and thg) that expects status()
> > returns a tuple.
> >
> > Regards,
> 
> Sure, I'm happy to make it extend tuple if others agree. I actually
> considered letting the class extend tuple temporarily in the patch
> series to make the diffs smaller, but I don't really want it to be a
> tuple in the end (I'd prefer if new instances of "status[4]" could not
> be added later).

A named tuple with slots is the way to go. It also leads to a much
smaller initial patch. I actually wouldn't go to the trouble of changing
all the users right away - a lot of them are implicitly assigning things
to local vars as a micro-optimization.

(The secret to working with the current tuple, by the way is to remember
the string 'marduic' which match the option flags of status and is also
the name of the Babylonian god of status results.)
Mads Kiilerich - Oct. 6, 2014, 4:49 p.m.
On 10/02/2014 04:26 PM, Martin von Zweigbergk wrote:
> On Thu, Oct 2, 2014 at 5:46 AM, Mads Kiilerich <mads@kiilerich.com> wrote:
>> On 10/02/2014 07:00 AM, Martin von Zweigbergk wrote:
>>> Thanks for taking a look! Answers below to some of your questions.
>>> I'll leave the others for later, or they might just get address in v2
>>> of the series.
>>>
>>> On Wed, Oct 1, 2014 at 6:40 PM, Mads Kiilerich <mads@kiilerich.com> wrote:
>>>> A general comment: A "." lookup is relatively expensive. It seems like
>>>> you
>>>> introduce a lot of them. We should take care to not introduce more of
>>>> them
>>>> in tight loops (that might already be the case - I did not check and not
>>>> see
>>>> any statement on whether that was the case).
>>> Hmm, I don't think I even considered that (I've never used python before).
>>>
>>> I tried running the test suite:
>>>
>>> Before:
>>> real    3m22.026s
>>> user    25m31.715s
>>> sys 5m45.684s
>>>
>>> After:
>>> real    3m24.136s
>>> user    25m29.724s
>>> sys     5m46.637s
>>>
>>> I also tried this:
>>>
>>> $ python -mtimeit -s'a=(1,2,3,4,5,6)' 'a[3]'
>>> 10000000 loops, best of 3: 0.0402 usec per loop
>>>
>>> $ python -mtimeit -s'class c(object):
>>>     def __init__(self,a,b,c,d,e,f):
>>>       self.a=a
>>>       self.b=b
>>>       self.c=c
>>>       self.d=d
>>>       self.e=e
>>>       self.f=f
>>> a=c(1,2,3,4,5,6)' 'a.d'
>>> 10000000 loops, best of 3: 0.0458 usec per loop
>>
>> Yes, these two are pretty much the same. But:
>>
>> $ python -mtimeit -s'a=(1,2,3,4,5,6)' -s 'b=a[0]' 'x=b'
>> 100000000 loops, best of 3: 0.0114 usec per loop
>>
>> $ python -mtimeit -s'class c(object):
>>    def __init__(self,a,b,c,d,e,f):
>>      self.a=a
>>      self.b=b
>>      self.c=c
>>      self.d=d
>>      self.e=e
>>      self.f=f
>> a=c(1,2,3,4,5,6)' 'x=a.d'
>> 10000000 loops, best of 3: 0.026 usec per loop
> That doesn't seem like a fair comparison to me. Assuming that '-s'
> means "this is setup, don't include in timing", the only thing your
> tuple test case does is to test assignment, right? So it's not
> comparing tuple lookup vs field name lookup.

Yes, I agree. The cost of a lookup is however relevant if we replace a 
modified.append(x) with status.modified.append(x). (The even better 
microoptimization would be to use modifiedappend=status.modified.append .)

/Mads
Mads Kiilerich - Oct. 6, 2014, 5:09 p.m.
On 10/02/2014 07:39 AM, Martin von Zweigbergk wrote:
> On Wed, Oct 1, 2014 at 6:40 PM, Mads Kiilerich <mads@kiilerich.com> wrote:
>> On 10/02/2014 02:01 AM, Martin von Zweigbergk wrote:
>>>    @@ -2511,19 +2511,19 @@
>>>                                      unknown=True, ignored=True,
>>> clean=True)
>>>            else:
>>>                changes = repo.status(match=m)
>>> -            for kind in changes:
>>> +            for kind in changes.all():
>>>                    for abs in kind:
>>>                        names[abs] = m.rel(abs), m.exact(abs)
>>>                  m = scmutil.matchfiles(repo, names)
>>>    -        modified = set(changes[0])
>>> -        added    = set(changes[1])
>>> -        removed  = set(changes[2])
>>> -        _deleted = set(changes[3])
>>> -        unknown  = set(changes[4])
>>> -        unknown.update(changes[5])
>>> -        clean    = set(changes[6])
>>> +        modified = set(changes.modified)
>>> +        added    = set(changes.added)
>>> +        removed  = set(changes.removed)
>>> +        _deleted = set(changes.deleted)
>>
>> Instead of refactoring an unused line,
> Are you referring to '_deleted'? Why do you think it's unused? Because
> it starts with underscore? I didn't see anything about that in the
> style guide, but I think I've seen that convention in some places.
> Should I send a patch?

Yes, it is in the default for pylint --dummy-variables-rgx and thus a 
somewhat common convention in Python ... but I guess you are right that 
it isn't in the Mercurial style guide.

afead12e724b do however seem to use a very different convention - 
perhaps more inspired by how leading _ in namespaces usually indicate 
"private". It tricked me and is thus a bit dangerous ... either that, or 
it is me who are dangerous.

I would prefer if we consistently could use the 
leading-underscore-is-unused convention.

/Mads
Martin von Zweigbergk - Oct. 6, 2014, 5:23 p.m.
On Mon, Oct 6, 2014 at 10:09 AM, Mads Kiilerich <mads@kiilerich.com> wrote:
> On 10/02/2014 07:39 AM, Martin von Zweigbergk wrote:
>>
>> On Wed, Oct 1, 2014 at 6:40 PM, Mads Kiilerich <mads@kiilerich.com> wrote:
>>>
>>> On 10/02/2014 02:01 AM, Martin von Zweigbergk wrote:
>>>>
>>>>    @@ -2511,19 +2511,19 @@
>>>>                                      unknown=True, ignored=True,
>>>> clean=True)
>>>>            else:
>>>>                changes = repo.status(match=m)
>>>> -            for kind in changes:
>>>> +            for kind in changes.all():
>>>>                    for abs in kind:
>>>>                        names[abs] = m.rel(abs), m.exact(abs)
>>>>                  m = scmutil.matchfiles(repo, names)
>>>>    -        modified = set(changes[0])
>>>> -        added    = set(changes[1])
>>>> -        removed  = set(changes[2])
>>>> -        _deleted = set(changes[3])
>>>> -        unknown  = set(changes[4])
>>>> -        unknown.update(changes[5])
>>>> -        clean    = set(changes[6])
>>>> +        modified = set(changes.modified)
>>>> +        added    = set(changes.added)
>>>> +        removed  = set(changes.removed)
>>>> +        _deleted = set(changes.deleted)
>>>
>>>
>>> Instead of refactoring an unused line,
>>
>> Are you referring to '_deleted'? Why do you think it's unused? Because
>> it starts with underscore? I didn't see anything about that in the
>> style guide, but I think I've seen that convention in some places.
>> Should I send a patch?
>
>
> Yes, it is in the default for pylint --dummy-variables-rgx and thus a
> somewhat common convention in Python ... but I guess you are right that it
> isn't in the Mercurial style guide.
>
> afead12e724b do however seem to use a very different convention - perhaps
> more inspired by how leading _ in namespaces usually indicate "private". It
> tricked me and is thus a bit dangerous ... either that, or it is me who are
> dangerous.

Ah, thanks. I didn't even check.

> I would prefer if we consistently could use the leading-underscore-is-unused
> convention.

I would at least agree that consistency is better. In the languages
I've used before, I would generally create a narrower scope by
extracting a method. If that makes sense in Python too, it seems like
underscore as prefix could be reserved for unused variables. It seems
like we'd have to convince at least marmoute.

Patch

diff --git a/hgext/convert/hg.py b/hgext/convert/hg.py
--- a/hgext/convert/hg.py
+++ b/hgext/convert/hg.py
@@ -371,7 +371,8 @@ 
             if self._changescache[0] == rev:
                 m, a, r = self._changescache[1]
             else:
-                m, a, r = self.repo.status(parents[0].node(), ctx.node())[:3]
+                s = self.repo.status(parents[0].node(), ctx.node())
+                m, a, r = s.modified, s.added, s.removed
             if not full:
                 files = m + a + r
             copyfiles = m + a
@@ -437,7 +438,8 @@ 
             changes = [], ctx.manifest().keys(), []
         else:
             i = i or 0
-            changes = self.repo.status(parents[i].node(), ctx.node())[:3]
+            s = self.repo.status(parents[i].node(), ctx.node())
+            changes = s.modified, s.added, s.removed
         changes = [[f for f in l if f not in self.ignored] for l in changes]
 
         if i == 0:
diff --git a/hgext/extdiff.py b/hgext/extdiff.py
--- a/hgext/extdiff.py
+++ b/hgext/extdiff.py
@@ -143,9 +143,12 @@ 
             do3way = False
 
     matcher = scmutil.match(repo[node2], pats, opts)
-    mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher)[:3])
+    st_a = repo.status(node1a, node2, matcher)
+    mod_a, add_a, rem_a = map(set, (st_a.modified, st_a.added, st_a.removed))
     if do3way:
-        mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher)[:3])
+        st_b = repo.status(node1b, node2, matcher)
+        mod_b, add_b, rem_b = map(set,
+                                  (st_b.modified, st_b.added, st_b.removed))
     else:
         mod_b, add_b, rem_b = set(), set(), set()
     modadd = mod_a | add_a | mod_b | add_b
diff --git a/hgext/gpg.py b/hgext/gpg.py
--- a/hgext/gpg.py
+++ b/hgext/gpg.py
@@ -255,7 +255,7 @@ 
 
     msigs = match.exact(repo.root, '', ['.hgsigs'])
     s = repo.status(match=msigs, unknown=True, ignored=True)
-    if util.any(s) and not opts["force"]:
+    if util.any(s.all()) and not opts["force"]:
         raise util.Abort(_("working copy of .hgsigs is changed "
                            "(please commit .hgsigs manually "
                            "or use --force)"))
diff --git a/hgext/hgcia.py b/hgext/hgcia.py
--- a/hgext/hgcia.py
+++ b/hgext/hgcia.py
@@ -82,14 +82,14 @@ 
         if url and url[-1] == '/':
             url = url[:-1]
         elems = []
-        for path in f[0]:
+        for path in f.modified:
             uri = '%s/diff/%s/%s' % (url, short(n), path)
             elems.append(self.fileelem(path, url and uri, 'modify'))
-        for path in f[1]:
+        for path in f.added:
             # TODO: copy/rename ?
             uri = '%s/file/%s/%s' % (url, short(n), path)
             elems.append(self.fileelem(path, url and uri, 'add'))
-        for path in f[2]:
+        for path in f.removed:
             elems.append(self.fileelem(path, '', 'remove'))
 
         return '\n'.join(elems)
diff --git a/hgext/histedit.py b/hgext/histedit.py
--- a/hgext/histedit.py
+++ b/hgext/histedit.py
@@ -688,8 +688,7 @@ 
 
     # Commit dirty working directory if necessary
     new = None
-    m, a, r, d = repo.status()[:4]
-    if m or a or r or d:
+    if repo.status().haschanges():
         # prepare the message for the commit to comes
         if action in ('f', 'fold', 'r', 'roll'):
             message = 'fold-temp-revision %s' % currentnode
diff --git a/hgext/keyword.py b/hgext/keyword.py
--- a/hgext/keyword.py
+++ b/hgext/keyword.py
@@ -171,9 +171,8 @@ 
     '''Retrieves modified and added files from a working directory state
     and returns the subset of each contained in given changed files
     retrieved from a change context.'''
-    modified, added = wstatus[:2]
-    modified = [f for f in modified if f in changed]
-    added = [f for f in added if f in changed]
+    modified = [f for f in wstatus.modified if f in changed]
+    added = [f for f in wstatus.added if f in changed]
     return modified, added
 
 
@@ -349,10 +348,9 @@ 
     wlock = repo.wlock()
     try:
         status = _status(ui, repo, wctx, kwt, *pats, **opts)
-        modified, added, removed, deleted, unknown, ignored, clean = status
-        if modified or added or removed or deleted:
+        if status.haschanges():
             raise util.Abort(_('outstanding uncommitted changes'))
-        kwt.overwrite(wctx, clean, True, expand)
+        kwt.overwrite(wctx, status.clean, True, expand)
     finally:
         wlock.release()
 
@@ -503,20 +501,19 @@ 
     wctx = repo[None]
     status = _status(ui, repo, wctx, kwt, *pats, **opts)
     cwd = pats and repo.getcwd() or ''
-    modified, added, removed, deleted, unknown, ignored, clean = status
     files = []
     if not opts.get('unknown') or opts.get('all'):
-        files = sorted(modified + added + clean)
+        files = sorted(status.modified + status.added + status.clean)
     kwfiles = kwt.iskwfile(files, wctx)
-    kwdeleted = kwt.iskwfile(deleted, wctx)
-    kwunknown = kwt.iskwfile(unknown, wctx)
+    kwdeleted = kwt.iskwfile(status.deleted, wctx)
+    kwunknown = kwt.iskwfile(status.unknown, wctx)
     if not opts.get('ignore') or opts.get('all'):
         showfiles = kwfiles, kwdeleted, kwunknown
     else:
         showfiles = [], [], []
     if opts.get('all') or opts.get('ignore'):
         showfiles += ([f for f in files if f not in kwfiles],
-                      [f for f in unknown if f not in kwunknown])
+                      [f for f in status.unknown if f not in kwunknown])
     kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split()
     kwstates = zip(kwlabels, 'K!kIi', showfiles)
     fm = ui.formatter('kwfiles', opts)
diff --git a/hgext/largefiles/lfutil.py b/hgext/largefiles/lfutil.py
--- a/hgext/largefiles/lfutil.py
+++ b/hgext/largefiles/lfutil.py
@@ -136,19 +136,18 @@ 
 
 def lfdirstatestatus(lfdirstate, repo, rev):
     match = match_.always(repo.root, repo.getcwd())
-    s = lfdirstate.status(match, [], False, False, False)
-    unsure, modified, added, removed, missing, unknown, ignored, clean = s
-    for lfile in unsure:
+    (s, lookup) = lfdirstate.status(match, [], False, False, False)
+    for lfile in lookup:
         try:
             fctx = repo[rev][standin(lfile)]
         except LookupError:
             fctx = None
         if not fctx or fctx.data().strip() != hashfile(repo.wjoin(lfile)):
-            modified.append(lfile)
+            s.modified.append(lfile)
         else:
-            clean.append(lfile)
+            s.clean.append(lfile)
             lfdirstate.normal(lfile)
-    return (modified, added, removed, missing, unknown, ignored, clean)
+    return s
 
 def listlfiles(repo, rev=None, matcher=None):
     '''return a list of largefiles in the working copy or the
diff --git a/hgext/largefiles/overrides.py b/hgext/largefiles/overrides.py
--- a/hgext/largefiles/overrides.py
+++ b/hgext/largefiles/overrides.py
@@ -153,7 +153,8 @@ 
     manifest = repo[None].manifest()
     modified, added, deleted, clean = [[f for f in list
                                         if lfutil.standin(f) in manifest]
-                                       for list in [s[0], s[1], s[3], s[6]]]
+                                       for list in (s.modified, s.added,
+                                                    s.deleted, s.clean)]
 
     def warn(files, msg):
         for f in files:
@@ -353,13 +354,11 @@ 
     wlock = repo.wlock()
     try:
         lfdirstate = lfutil.openlfdirstate(ui, repo)
-        s = lfdirstate.status(match_.always(repo.root, repo.getcwd()),
-            [], False, False, False)
-        (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
-
+        (s, lookup) = lfdirstate.status(match_.always(repo.root, repo.getcwd()),
+                                        [], False, False, False)
         if opts['check']:
-            mod = len(modified) > 0
-            for lfile in unsure:
+            mod = len(s.modified) > 0
+            for lfile in lookup:
                 standin = lfutil.standin(lfile)
                 if repo['.'][standin].data().strip() != \
                         lfutil.hashfile(repo.wjoin(lfile)):
@@ -662,12 +661,11 @@ 
     wlock = repo.wlock()
     try:
         lfdirstate = lfutil.openlfdirstate(ui, repo)
-        (modified, added, removed, missing, unknown, ignored, clean) = \
-            lfutil.lfdirstatestatus(lfdirstate, repo, repo['.'].rev())
+        s = lfutil.lfdirstatestatus(lfdirstate, repo, repo['.'].rev())
         lfdirstate.write()
-        for lfile in modified:
+        for lfile in s.modified:
             lfutil.updatestandin(repo, lfutil.standin(lfile))
-        for lfile in missing:
+        for lfile in s.deleted:
             if (os.path.exists(repo.wjoin(lfutil.standin(lfile)))):
                 os.unlink(repo.wjoin(lfutil.standin(lfile)))
 
@@ -956,17 +954,15 @@ 
 def overridebailifchanged(orig, repo):
     orig(repo)
     repo.lfstatus = True
-    modified, added, removed, deleted = repo.status()[:4]
     repo.lfstatus = False
-    if modified or added or removed or deleted:
+    if repo.status().haschanges():
         raise util.Abort(_('uncommitted changes'))
 
 # Fetch doesn't use cmdutil.bailifchanged so override it to add the check
 def overridefetch(orig, ui, repo, *pats, **opts):
     repo.lfstatus = True
-    modified, added, removed, deleted = repo.status()[:4]
     repo.lfstatus = False
-    if modified or added or removed or deleted:
+    if repo.status().haschanges():
         raise util.Abort(_('uncommitted changes'))
     return orig(ui, repo, *pats, **opts)
 
@@ -981,7 +977,7 @@ 
         s = repo.status(match=m, clean=True)
     finally:
         repo.lfstatus = False
-    forget = sorted(s[0] + s[1] + s[3] + s[6])
+    forget = sorted(s.modified + s.added + s.deleted + s.clean)
     forget = [f for f in forget if lfutil.standin(f) in repo[None].manifest()]
 
     for f in forget:
@@ -1112,16 +1108,15 @@ 
         return orig(repo, pats, opts, dry_run, similarity)
     # Get the list of missing largefiles so we can remove them
     lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
-    s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
-        False, False)
-    (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
+    (s, lookup) = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [],
+                                    False, False, False)
 
     # Call into the normal remove code, but the removing of the standin, we want
     # to have handled by original addremove.  Monkey patching here makes sure
     # we don't remove the standin in the largefiles code, preventing a very
     # confused state later.
-    if missing:
-        m = [repo.wjoin(f) for f in missing]
+    if s.deleted:
+        m = [repo.wjoin(f) for f in s.deleted]
         repo._isaddremove = True
         removelargefiles(repo.ui, repo, *m, **opts)
         repo._isaddremove = False
@@ -1148,10 +1143,9 @@ 
         r = oldstatus(node1, node2, match, ignored, clean, unknown,
                       listsubrepos)
         lfdirstate = lfutil.openlfdirstate(ui, repo)
-        modified, added, removed, deleted, unknown, ignored, clean = r
-        unknown = [f for f in unknown if lfdirstate[f] == '?']
-        ignored = [f for f in ignored if lfdirstate[f] == '?']
-        return modified, added, removed, deleted, unknown, ignored, clean
+        r.unknown = [f for f in r.unknown if lfdirstate[f] == '?']
+        r.ignored = [f for f in r.ignored if lfdirstate[f] == '?']
+        return r
     repo.status = overridestatus
     orig(ui, repo, *dirs, **opts)
     repo.status = oldstatus
@@ -1290,10 +1284,10 @@ 
             # update standins for linear-merge or force-branch-merge,
             # because largefiles in the working directory may be modified
             lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
-            s = lfdirstate.status(match_.always(repo.root, repo.getcwd()),
-                                  [], False, False, False)
-            unsure, modified, added = s[:3]
-            for lfile in unsure + modified + added:
+            (s, lookup) = lfdirstate.status(match_.always(repo.root,
+                                                          repo.getcwd()),
+                                            [], False, False, False)
+            for lfile in lookup + s.modified + s.added:
                 lfutil.updatestandin(repo, lfutil.standin(lfile))
 
         if linearmerge:
diff --git a/hgext/largefiles/reposetup.py b/hgext/largefiles/reposetup.py
--- a/hgext/largefiles/reposetup.py
+++ b/hgext/largefiles/reposetup.py
@@ -5,6 +5,7 @@ 
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
+from mercurial.dirstate import status
 
 '''setup for largefiles repositories: reposetup'''
 import copy
@@ -159,12 +160,13 @@ 
                                     if sfindirstate(f)]
                     # Don't waste time getting the ignored and unknown
                     # files from lfdirstate
-                    s = lfdirstate.status(match, [], False,
-                            listclean, False)
-                    (unsure, modified, added, removed, missing, _unknown,
-                            _ignored, clean) = s
+                    (s, lookup) = lfdirstate.status(match, [], False,
+                                                    listclean, False)
+                    modified = s.modified
+                    added = s.added
+                    clean = s.clean
                     if parentworking:
-                        for lfile in unsure:
+                        for lfile in lookup:
                             standin = lfutil.standin(lfile)
                             if standin not in ctx1:
                                 # from second parent
@@ -177,7 +179,7 @@ 
                                     clean.append(lfile)
                                 lfdirstate.normal(lfile)
                     else:
-                        tocheck = unsure + modified + added + clean
+                        tocheck = lookup + s.modified + s.added + s.clean
                         modified, added, clean = [], [], []
 
                         for lfile in tocheck:
@@ -191,6 +193,7 @@ 
                             else:
                                 added.append(lfile)
 
+                    removed = s.removed
                     # Standins no longer found in lfdirstate has been
                     # removed
                     for standin in ctx1.walk(lfutil.getstandinmatcher(self)):
@@ -200,9 +203,6 @@ 
                         if lfile not in lfdirstate:
                             removed.append(lfile)
 
-                    # Filter result lists
-                    result = list(result)
-
                     # Largefiles are not really removed when they're
                     # still in the normal dirstate. Likewise, normal
                     # files are not really removed if they are still in
@@ -210,19 +210,19 @@ 
                     # change type.
                     removed = [f for f in removed
                                if f not in self.dirstate]
-                    result[2] = [f for f in result[2]
+                    result.removed = [f for f in result.removed
                                  if f not in lfdirstate]
 
                     lfiles = set(lfdirstate._map)
                     # Unknown files
-                    result[4] = set(result[4]).difference(lfiles)
+                    result.unknown = set(result.unknown).difference(lfiles)
                     # Ignored files
-                    result[5] = set(result[5]).difference(lfiles)
+                    result.ignored = set(result.ignored).difference(lfiles)
                     # combine normal files and largefiles
                     normals = [[fn for fn in filelist
                                 if not lfutil.isstandin(fn)]
-                               for filelist in result]
-                    lfstatus = (modified, added, removed, missing, [], [],
+                               for filelist in result.all()]
+                    lfstatus = (modified, added, removed, s.deleted, [], [],
                                 clean)
                     result = [sorted(list1 + list2)
                               for (list1, list2) in zip(normals, lfstatus)]
@@ -234,6 +234,8 @@ 
                     result = [[toname(f) for f in items]
                               for items in result]
 
+                result = status(result[0], result[1], result[2], result[3],
+                                result[4], result[5], result[6])
                 if wlock:
                     lfdirstate.write()
 
@@ -296,10 +298,9 @@ 
                     # large.
                     lfdirstate = lfutil.openlfdirstate(ui, self)
                     dirtymatch = match_.always(self.root, self.getcwd())
-                    s = lfdirstate.status(dirtymatch, [], False, False, False)
-                    (unsure, modified, added, removed, _missing, _unknown,
-                            _ignored, _clean) = s
-                    modifiedfiles = unsure + modified + added + removed
+                    (s, lookup) = lfdirstate.status(dirtymatch, [], False,
+                                                    False, False)
+                    modifiedfiles = lookup + s.modified + s.added + s.removed
                     lfiles = lfutil.listlfiles(self)
                     # this only loops through largefiles that exist (not
                     # removed/renamed)
diff --git a/hgext/mq.py b/hgext/mq.py
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -1021,7 +1021,7 @@ 
         return None, None
 
     def putsubstate2changes(self, substatestate, changes):
-        for files in changes[:3]:
+        for files in changes:
             if '.hgsubstate' in files:
                 return # already listed up
         # not yet listed up
@@ -1092,11 +1092,11 @@ 
                 if f != '.hgsubstate': # .hgsubstate is auto-created
                     raise util.Abort('%s: %s' % (f, msg))
             match.bad = badfn
-            changes = repo.status(match=match)
+            changes = repo.status(match=match).committable()
         else:
-            changes = self.checklocalchanges(repo, force=True)
+            changes = self.checklocalchanges(repo, force=True).committable()
         commitfiles = list(inclsubs)
-        for files in changes[:3]:
+        for files in changes:
             commitfiles.extend(files)
         match = scmutil.matchfiles(repo, commitfiles)
         if len(repo[None].parents()) > 1:
@@ -1360,11 +1360,12 @@ 
 
             tobackup = set()
             if (not nobackup and force) or keepchanges:
-                m, a, r, d = self.checklocalchanges(repo, force=True)
+                changes = self.checklocalchanges(repo, force=True)
                 if keepchanges:
-                    tobackup.update(m + a + r + d)
+                    tobackup.update(changes.modified + changes.added +
+                                    changes.removed + changes.deleted)
                 else:
-                    tobackup.update(m + a)
+                    tobackup.update(changes.modified + changes.added)
 
             s = self.series[start:end]
             all_files = set()
@@ -1448,13 +1449,14 @@ 
 
             tobackup = set()
             if update:
-                m, a, r, d = self.checklocalchanges(
+                changes = self.checklocalchanges(
                     repo, force=force or keepchanges)
                 if force:
                     if not nobackup:
-                        tobackup.update(m + a)
+                        tobackup.update(changes.modified + changes.added)
                 elif keepchanges:
-                    tobackup.update(m + a + r + d)
+                    tobackup.update(changes.modified + changes.added +
+                                    changes.removed + changes.deleted)
 
             self.applieddirty = True
             end = len(self.applied)
@@ -1479,19 +1481,20 @@ 
             if update:
                 qp = self.qparents(repo, rev)
                 ctx = repo[qp]
-                m, a, r, d = repo.status(qp, '.')[:4]
-                if d:
+                status = repo.status(qp, '.')
+                if status.deleted:
                     raise util.Abort(_("deletions found between repo revs"))
 
-                tobackup = set(a + m + r) & tobackup
+                tobackup = set(status.added + status.modified +
+                               status.removed) & tobackup
                 if keepchanges and tobackup:
                     raise util.Abort(_("local changes found, refresh first"))
                 self.backup(repo, tobackup)
                 repo.dirstate.beginparentchange()
-                for f in a:
+                for f in status.added:
                     util.unlinkpath(repo.wjoin(f), ignoremissing=True)
                     repo.dirstate.drop(f)
-                for f in m + r:
+                for f in status.modified + status.removed:
                     fctx = ctx[f]
                     repo.wwrite(f, fctx.data(), fctx.flags())
                     repo.dirstate.normal(f)
@@ -1570,7 +1573,8 @@ 
             #   mm, dd, aa = repo.status(top, patchparent)[:3]
             # but we do it backwards to take advantage of manifest/changelog
             # caching against the next repo.status call
-            mm, aa, dd = repo.status(patchparent, top)[:3]
+            s = repo.status(patchparent, top)
+            mm, aa, dd = s.modified, s.added, s.removed
             changes = repo.changelog.read(top)
             man = repo.manifest.read(changes[0])
             aaa = aa[:]
@@ -1585,7 +1589,8 @@ 
                 matchfn = scmutil.match(repo[None], opts=opts)
             else:
                 match = scmutil.matchall(repo)
-            m, a, r, d = repo.status(match=match)[:4]
+            s = repo.status(match=match)
+            m, a, r, d = s.modified, s.added, s.removed, s.deleted
             mm = set(mm)
             aa = set(aa)
             dd = set(dd)
@@ -1624,8 +1629,9 @@ 
 
             # create 'match' that includes the files to be recommitted.
             # apply matchfn via repo.status to ensure correct case handling.
-            cm, ca, cr, cd = repo.status(patchparent, match=matchfn)[:4]
-            allmatches = set(cm + ca + cr + cd)
+            status = repo.status(patchparent, match=matchfn)
+            allmatches = set(status.modified + status.added + status.removed +
+                             status.deleted)
             refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
 
             files = set(inclsubs)
diff --git a/hgext/purge.py b/hgext/purge.py
--- a/hgext/purge.py
+++ b/hgext/purge.py
@@ -102,7 +102,7 @@ 
     status = repo.status(match=match, ignored=opts['all'], unknown=True)
 
     if removefiles:
-        for f in sorted(status[4] + status[5]):
+        for f in sorted(status.unknown + status.ignored):
             if act:
                 ui.note(_('removing file %s\n') % f)
             remove(util.unlink, f)
diff --git a/hgext/record.py b/hgext/record.py
--- a/hgext/record.py
+++ b/hgext/record.py
@@ -519,12 +519,12 @@ 
             raise util.Abort(_('cannot partially commit a merge '
                                '(use "hg commit" instead)'))
 
-        changes = repo.status(match=match)[:3]
+        status = repo.status(match=match)
         diffopts = opts.copy()
         diffopts['nodates'] = True
         diffopts['git'] = True
         diffopts = patch.diffopts(ui, opts=diffopts)
-        chunks = patch.diff(repo, changes=changes, opts=diffopts)
+        chunks = patch.diff(repo, changes=status.committable(), opts=diffopts)
         fp = cStringIO.StringIO()
         fp.write(''.join(chunks))
         fp.seek(0)
@@ -544,13 +544,13 @@ 
             except AttributeError:
                 pass
 
-        changed = changes[0] + changes[1] + changes[2]
+        changed = status.modified + status.added + status.removed
         newfiles = [f for f in changed if f in contenders]
         if not newfiles:
             ui.status(_('no changes to record\n'))
             return 0
 
-        modified = set(changes[0])
+        modified = set(status.modified)
 
         # 2. backup changed files, so we can restore them in the end
         if backupall:
diff --git a/hgext/shelve.py b/hgext/shelve.py
--- a/hgext/shelve.py
+++ b/hgext/shelve.py
@@ -227,9 +227,9 @@ 
 
         if not node:
             stat = repo.status(match=scmutil.match(repo[None], pats, opts))
-            if stat[3]:
+            if stat.deleted:
                 ui.status(_("nothing changed (%d missing files, see "
-                            "'hg status')\n") % len(stat[3]))
+                            "'hg status')\n") % len(stat.deleted))
             else:
                 ui.status(_("nothing changed\n"))
             return 1
@@ -401,7 +401,7 @@ 
         files.extend(shelvectx.parents()[0].files())
 
         # revert will overwrite unknown files, so move them out of the way
-        for file in repo.status(unknown=True)[4]:
+        for file in repo.status(unknown=True).unknown:
             if file in files:
                 util.rename(file, file + ".orig")
         ui.pushbuffer(True)
@@ -545,8 +545,7 @@ 
         # to the original pctx.
 
         # Store pending changes in a commit
-        m, a, r, d = repo.status()[:4]
-        if m or a or r or d:
+        if repo.status().haschanges():
             ui.status(_("temporarily committing pending changes "
                         "(restore with 'hg unshelve --abort')\n"))
             def commitfunc(ui, repo, message, match, opts):
diff --git a/hgext/strip.py b/hgext/strip.py
--- a/hgext/strip.py
+++ b/hgext/strip.py
@@ -32,15 +32,15 @@ 
 
 def checklocalchanges(repo, force=False, excsuffix=''):
     cmdutil.checkunfinished(repo)
-    m, a, r, d = repo.status()[:4]
+    status = repo.status()
     if not force:
-        if (m or a or r or d):
+        if (status.haschanges()):
             _("local changes found") # i18n tool detection
             raise util.Abort(_("local changes found" + excsuffix))
         if checksubstate(repo):
             _("local changed subrepos found") # i18n tool detection
             raise util.Abort(_("local changed subrepos found" + excsuffix))
-    return m, a, r, d
+    return status
 
 def strip(ui, repo, revs, update=True, backup=True, force=None, bookmark=None):
     wlock = lock = None
diff --git a/hgext/transplant.py b/hgext/transplant.py
--- a/hgext/transplant.py
+++ b/hgext/transplant.py
@@ -611,8 +611,7 @@ 
     if not opts.get('continue'):
         if p2 != revlog.nullid:
             raise util.Abort(_('outstanding uncommitted merges'))
-        m, a, r, d = repo.status()[:4]
-        if m or a or r or d:
+        if repo.status().haschanges():
             raise util.Abort(_('outstanding local changes'))
 
     sourcerepo = opts.get('source')
diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -83,8 +83,7 @@ 
 def bailifchanged(repo):
     if repo.dirstate.p2() != nullid:
         raise util.Abort(_('outstanding uncommitted merge'))
-    modified, added, removed, deleted = repo.status()[:4]
-    if modified or added or removed or deleted:
+    if repo.status().haschanges():
         raise util.Abort(_('uncommitted changes'))
     ctx = repo[None]
     for s in sorted(ctx.substate):
@@ -940,7 +939,8 @@ 
                       label='log.date')
 
         if self.ui.debugflag:
-            files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
+            status = self.repo.status(log.parents(changenode)[0], changenode)
+            files = (status.modified, status.added, status.removed)
             for key, value in zip([# i18n: column positioning for "hg log"
                                    _("files:"),
                                    # i18n: column positioning for "hg log"
@@ -1074,11 +1074,11 @@ 
 
             files = ctx.status(ctx.p1())
             self.ui.write(',\n  "modified": [%s]' %
-                          ", ".join('"%s"' % j(f) for f in files[0]))
+                          ", ".join('"%s"' % j(f) for f in files.modified))
             self.ui.write(',\n  "added": [%s]' %
-                          ", ".join('"%s"' % j(f) for f in files[1]))
+                          ", ".join('"%s"' % j(f) for f in files.added))
             self.ui.write(',\n  "removed": [%s]' %
-                          ", ".join('"%s"' % j(f) for f in files[2]))
+                          ", ".join('"%s"' % j(f) for f in files.removed))
 
         elif self.ui.verbose:
             self.ui.write(',\n  "files": [%s]' %
@@ -2016,7 +2016,7 @@ 
     wctx = repo[None]
     forgot = []
     s = repo.status(match=match, clean=True)
-    forget = sorted(s[0] + s[1] + s[3] + s[6])
+    forget = sorted(s.modified + s.added + s.deleted + s.clean)
     if explicitonly:
         forget = [f for f in forget if match.exact(f)]
 
@@ -2187,8 +2187,8 @@ 
             if len(old.parents()) > 1:
                 # ctx.files() isn't reliable for merges, so fall back to the
                 # slower repo.status() method
-                files = set([fn for st in repo.status(base, old)[:3]
-                             for fn in st])
+                status = repo.status(base, old)
+                files = set([fn for st in status.committable() for fn in st])
             else:
                 files = set(old.files())
 
@@ -2511,19 +2511,19 @@ 
                                   unknown=True, ignored=True, clean=True)
         else:
             changes = repo.status(match=m)
-            for kind in changes:
+            for kind in changes.all():
                 for abs in kind:
                     names[abs] = m.rel(abs), m.exact(abs)
 
             m = scmutil.matchfiles(repo, names)
 
-        modified = set(changes[0])
-        added    = set(changes[1])
-        removed  = set(changes[2])
-        _deleted = set(changes[3])
-        unknown  = set(changes[4])
-        unknown.update(changes[5])
-        clean    = set(changes[6])
+        modified = set(changes.modified)
+        added    = set(changes.added)
+        removed  = set(changes.removed)
+        _deleted = set(changes.deleted)
+        unknown  = set(changes.unknown)
+        unknown.update(changes.ignored)
+        clean    = set(changes.clean)
 
         # split between files known in target manifest and the others
         smf = set(mf)
@@ -2544,9 +2544,9 @@ 
             modified, added, removed = set(), set(), set()
         else:
             changes = repo.status(node1=parent, match=m)
-            dsmodified = set(changes[0])
-            dsadded    = set(changes[1])
-            dsremoved  = set(changes[2])
+            dsmodified = set(changes.modified)
+            dsadded    = set(changes.added)
+            dsremoved  = set(changes.removed)
 
             # only take into account for removes between wc and target
             clean |= dsremoved - removed
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -1491,9 +1491,9 @@ 
 
         if not node:
             stat = repo.status(match=scmutil.match(repo[None], pats, opts))
-            if stat[3]:
+            if stat.deleted:
                 ui.status(_("nothing changed (%d missing files, see "
-                            "'hg status')\n") % len(stat[3]))
+                            "'hg status')\n") % len(stat.deleted))
             else:
                 ui.status(_("nothing changed\n"))
             return 1
@@ -3951,7 +3951,7 @@ 
             parents = ctx.parents()
             changed = ""
             if default or id or num:
-                if (util.any(repo.status())
+                if (util.any(repo.status().all())
                     or util.any(ctx.sub(s).dirty() for s in ctx.substate)):
                     changed = '+'
             if default or id:
@@ -5149,7 +5149,6 @@ 
 
     m = scmutil.match(repo[None], pats, opts)
     s = repo.status(match=m, clean=True)
-    modified, added, deleted, clean = s[0], s[1], s[3], s[6]
 
     # warn about failure to delete explicit files/dirs
     wctx = repo[None]
@@ -5165,19 +5164,19 @@ 
         ret = 1
 
     if force:
-        list = modified + deleted + clean + added
+        list = s.modified + s.deleted + s.clean + s.added
     elif after:
-        list = deleted
-        for f in modified + added + clean:
+        list = s.deleted
+        for f in s.modified + s.added + s.clean:
             ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
             ret = 1
     else:
-        list = deleted + clean
-        for f in modified:
+        list = s.deleted + s.clean
+        for f in s.modified:
             ui.warn(_('not removing %s: file is modified (use -f'
                       ' to force removal)\n') % m.rel(f))
             ret = 1
-        for f in added:
+        for f in s.added:
             ui.warn(_('not removing %s: file has been marked for add'
                       ' (use forget to undo)\n') % m.rel(f))
             ret = 1
@@ -5190,7 +5189,7 @@ 
     try:
         if not after:
             for f in list:
-                if f in added:
+                if f in s.added:
                     continue # we never unlink added files on remove
                 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
         repo[None].forget(list)
@@ -5406,7 +5405,7 @@ 
             hint = _("uncommitted merge, use --all to discard all changes,"
                      " or 'hg update -C .' to abort the merge")
             raise util.Abort(msg, hint=hint)
-        dirty = util.any(repo.status())
+        dirty = util.any(repo.status().all())
         node = ctx.node()
         if node != parent:
             if dirty:
@@ -5711,7 +5710,14 @@ 
     stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
                        'ignored' in show, 'clean' in show, 'unknown' in show,
                        opts.get('subrepos'))
-    changestates = zip(states, 'MAR!?IC', stat)
+    changestates = zip(states, 'MAR!?IC',
+                       [stat.modified,
+                        stat.added,
+                        stat.removed,
+                        stat.deleted,
+                        stat.unknown,
+                        stat.ignored,
+                        stat.clean])
 
     if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
         copy = copies.pathcopies(repo[node1], repo[node2])
@@ -5793,7 +5799,7 @@ 
             ui.write(' ' + m, label='log.bookmark')
         ui.write('\n', label='log.bookmark')
 
-    st = list(repo.status(unknown=True))[:5]
+    st = list(repo.status(unknown=True).all())[:5]
 
     c = repo.dirstate.copies()
     copied, renamed = [], []
diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -14,6 +14,7 @@ 
 import repoview
 import fileset
 import revlog
+from mercurial.dirstate import status
 
 propertycache = util.propertycache
 
@@ -120,7 +121,7 @@ 
         mf2 = self._manifestmatches(match, s)
 
         modified, added, clean = [], [], []
-        deleted, unknown, ignored = s[3], s[4], s[5]
+        deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
         withflags = mf1.withflags() | mf2.withflags()
         for fn, mf2node in mf2.iteritems():
             if fn in mf1:
@@ -140,7 +141,8 @@ 
             unknown = [fn for fn in unknown if fn not in mf1]
             ignored = [fn for fn in ignored if fn not in mf1]
 
-        return [modified, added, removed, deleted, unknown, ignored, clean]
+        return status(modified, added, removed, deleted, unknown, ignored,
+                      clean)
 
     @propertycache
     def substate(self):
@@ -310,7 +312,7 @@ 
             reversed = True
             ctx1, ctx2 = ctx2, ctx1
 
-        r = [[], [], [], [], [], [], []]
+        r = status([], [], [], [], [], [], [])
         match = ctx2._matchstatus(ctx1, r, match, listignored, listclean,
                                   listunknown)
         r = ctx2._prestatus(ctx1, r, match, listignored, listclean, listunknown)
@@ -321,7 +323,7 @@ 
 
         if reversed:
             # reverse added and removed
-            r[1], r[2] = r[2], r[1]
+            r.added, r.removed = r.removed, r.added
 
         if listsubrepos:
             for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
@@ -331,17 +333,16 @@ 
                     s = sub.status(rev2, match=submatch, ignored=listignored,
                                    clean=listclean, unknown=listunknown,
                                    listsubrepos=True)
-                    for rfiles, sfiles in zip(r, s):
+                    for rfiles, sfiles in zip(r.all(), s.all()):
                         rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
                 except error.LookupError:
                     self._repo.ui.status(_("skipping missing "
                                            "subrepository: %s\n") % subpath)
 
-        for l in r:
+        for l in r.all():
             l.sort()
 
-        # we return a tuple to signify that this list isn't changing
-        return tuple(r)
+        return r
 
 
 def makememctx(repo, parents, text, user, date, branch, files, store,
@@ -1050,8 +1051,7 @@ 
 
         copied = self._repo.dirstate.copies()
         ff = self._flagfunc
-        modified, added, removed, deleted = self._status[:4]
-        for i, l in (("a", added), ("m", modified)):
+        for i, l in (("a", self._status.added), ("m", self._status.modified)):
             for f in l:
                 orig = copied.get(f, f)
                 man[f] = getman(orig).get(orig, nullid) + i
@@ -1060,7 +1060,7 @@ 
                 except OSError:
                     pass
 
-        for f in deleted + removed:
+        for f in self._status.deleted + self._status.removed:
             if f in man:
                 del man[f]
 
@@ -1088,22 +1088,23 @@ 
     def description(self):
         return self._text
     def files(self):
-        return sorted(self._status[0] + self._status[1] + self._status[2])
+        return sorted(self._status.modified + self._status.added + \
+                      self._status.removed)
 
     def modified(self):
-        return self._status[0]
+        return self._status.modified
     def added(self):
-        return self._status[1]
+        return self._status.added
     def removed(self):
-        return self._status[2]
+        return self._status.removed
     def deleted(self):
-        return self._status[3]
+        return self._status.deleted
     def unknown(self):
-        return self._status[4]
+        return self._status.unknown
     def ignored(self):
-        return self._status[5]
+        return self._status.ignored
     def clean(self):
-        return self._status[6]
+        return self._status.clean
     def branch(self):
         return encoding.tolocal(self._extra['branch'])
     def closesbranch(self):
@@ -1378,11 +1379,10 @@ 
         need to build a manifest and return what matches.
         """
         mf = self._repo['.']._manifestmatches(match, s)
-        modified, added, removed = s[0:3]
-        for f in modified + added:
+        for f in s.modified + s.added:
             mf[f] = None
             mf.set(f, self.flags(f))
-        for f in removed:
+        for f in s.removed:
             if f in mf:
                 del mf[f]
         return mf
@@ -1405,8 +1405,8 @@ 
         accidentally ended up with the entire contents of the file they are
         susposed to be linking to.
         """
-        s[0] = self._filtersuspectsymlink(s[0])
-        self._status = s[:]
+        s.modified = self._filtersuspectsymlink(s.modified)
+        self._status = s
         return s
 
     def _dirstatestatus(self, match=None, ignored=False, clean=False,
@@ -1417,20 +1417,19 @@ 
         subrepos = []
         if '.hgsub' in self:
             subrepos = sorted(self.substate)
-        s = self._repo.dirstate.status(match, subrepos, listignored,
-                                       listclean, listunknown)
-        cmp, modified, added, removed, deleted, unknown, ignored, clean = s
+        (s, lookup) = self._repo.dirstate.status(match, subrepos, listignored,
+                                                 listclean, listunknown)
 
         # check for any possibly clean files
-        if cmp:
-            modified2, fixup = self._checklookup(cmp)
-            modified += modified2
+        if lookup:
+            modified2, fixup = self._checklookup(lookup)
+            s.modified += modified2
 
-            # update dirstate for files that are actually clean
+            # update status for files that are actually clean
             if fixup and listclean:
-                clean += fixup
+                s.clean += fixup
 
-        return [modified, added, removed, deleted, unknown, ignored, clean]
+        return s
 
     def _buildstatus(self, other, s, match, listignored, listclean,
                      listunknown):
@@ -1478,10 +1477,8 @@ 
         s = super(workingctx, self).status(other, match, listignored, listclean,
                                            listunknown, listsubrepos)
         # calling 'super' subtly reveresed the contexts, so we flip the results
-        # (s[1] is 'added' and s[2] is 'removed')
-        s = list(s)
-        s[1], s[2] = s[2], s[1]
-        return tuple(s)
+        s.added, s.removed = s.removed, s.added
+        return s
 
 class committablefilectx(basefilectx):
     """A committablefilectx provides common functionality for a file context
@@ -1612,7 +1609,7 @@ 
         p1, p2 = parents
         self._parents = [changectx(self._repo, p) for p in (p1, p2)]
         files = sorted(set(files))
-        self._status = [files, [], [], [], []]
+        self._status = status(files, [], [], [], [], [], [])
         self._filectxfn = filectxfn
         self.substate = {}
 
diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -26,6 +26,40 @@ 
     def join(self, obj, fname):
         return obj._join(fname)
 
+class status(object):
+    def __init__(self, modified, added, removed, deleted, unknown,
+                ignored, clean):
+        self.modified = modified
+        self.added = added
+        self.removed = removed
+        self.deleted = deleted
+        self.unknown = unknown
+        self.ignored = ignored
+        self.clean = clean
+
+    def all(self):
+        return (self.modified,
+                self.added,
+                self.removed,
+                self.deleted,
+                self.unknown,
+                self.ignored,
+                self.clean)
+
+    def changes(self):
+        return (self.modified,
+                self.added,
+                self.removed,
+                self.deleted)
+
+    def haschanges(self):
+        return util.any(self.changes())
+
+    def committable(self):
+        return (self.modified,
+                self.added,
+                self.removed)
+
 class dirstate(object):
 
     def __init__(self, opener, ui, root, validate):
@@ -900,8 +934,8 @@ 
             elif state == 'r':
                 radd(fn)
 
-        return (lookup, modified, added, removed, deleted, unknown, ignored,
-                clean)
+        return (status(modified, added, removed, deleted, unknown,
+                      ignored, clean), lookup)
 
     def matches(self, match):
         '''
diff --git a/mercurial/fileset.py b/mercurial/fileset.py
--- a/mercurial/fileset.py
+++ b/mercurial/fileset.py
@@ -124,7 +124,7 @@ 
     """
     # i18n: "modified" is a keyword
     getargs(x, 0, 0, _("modified takes no arguments"))
-    s = mctx.status()[0]
+    s = mctx.status().modified
     return [f for f in mctx.subset if f in s]
 
 def added(mctx, x):
@@ -133,7 +133,7 @@ 
     """
     # i18n: "added" is a keyword
     getargs(x, 0, 0, _("added takes no arguments"))
-    s = mctx.status()[1]
+    s = mctx.status().added
     return [f for f in mctx.subset if f in s]
 
 def removed(mctx, x):
@@ -142,7 +142,7 @@ 
     """
     # i18n: "removed" is a keyword
     getargs(x, 0, 0, _("removed takes no arguments"))
-    s = mctx.status()[2]
+    s = mctx.status().removed
     return [f for f in mctx.subset if f in s]
 
 def deleted(mctx, x):
@@ -151,7 +151,7 @@ 
     """
     # i18n: "deleted" is a keyword
     getargs(x, 0, 0, _("deleted takes no arguments"))
-    s = mctx.status()[3]
+    s = mctx.status().deleted
     return [f for f in mctx.subset if f in s]
 
 def unknown(mctx, x):
@@ -161,7 +161,7 @@ 
     """
     # i18n: "unknown" is a keyword
     getargs(x, 0, 0, _("unknown takes no arguments"))
-    s = mctx.status()[4]
+    s = mctx.status().unknown
     return [f for f in mctx.subset if f in s]
 
 def ignored(mctx, x):
@@ -171,7 +171,7 @@ 
     """
     # i18n: "ignored" is a keyword
     getargs(x, 0, 0, _("ignored takes no arguments"))
-    s = mctx.status()[5]
+    s = mctx.status().ignored
     return [f for f in mctx.subset if f in s]
 
 def clean(mctx, x):
@@ -180,7 +180,7 @@ 
     """
     # i18n: "clean" is a keyword
     getargs(x, 0, 0, _("clean takes no arguments"))
-    s = mctx.status()[6]
+    s = mctx.status().clean
     return [f for f in mctx.subset if f in s]
 
 def func(mctx, a, b):
@@ -448,8 +448,8 @@ 
         return [f for f in files if f in self.subset]
     def existing(self):
         if self._status is not None:
-            removed = set(self._status[3])
-            unknown = set(self._status[4] + self._status[5])
+            removed = set(self._status.deleted)
+            unknown = set(self._status.unknown + self._status.ignored)
         else:
             removed = set()
             unknown = set()
@@ -495,7 +495,7 @@ 
         status = r.status(ctx.p1(), ctx,
                           unknown=unknown, ignored=ignored, clean=True)
         subset = []
-        for c in status:
+        for c in status.all():
             subset.extend(c)
     else:
         status = None
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -574,7 +574,7 @@ 
         date: date tuple to use if committing'''
 
         if not local:
-            for x in self.status()[:4]:
+            for x in self.status().changes():
                 if '.hgtags' in x:
                     raise util.Abort(_('working copy of .hgtags is changed '
                                        '(please commit .hgtags manually)'))
@@ -1229,9 +1229,10 @@ 
                 raise util.Abort(_('cannot partially commit a merge '
                                    '(do not specify files or patterns)'))
 
-            changes = self.status(match=match, clean=force)
+            status = self.status(match=match, clean=force)
             if force:
-                changes[0].extend(changes[6]) # mq may commit unchanged files
+                # mq may commit unchanged files
+                status.modified.extend(status.clean)
 
             # check subrepos
             subs = []
@@ -1240,7 +1241,7 @@ 
             # only manage subrepos and .hgsubstate if .hgsub is present
             if '.hgsub' in wctx:
                 # we'll decide whether to track this ourselves, thanks
-                for c in changes[:3]:
+                for c in [status.modified, status.added, status.removed]:
                     if '.hgsubstate' in c:
                         c.remove('.hgsubstate')
 
@@ -1278,23 +1279,24 @@ 
                         '.hgsub' in (wctx.modified() + wctx.added())):
                         raise util.Abort(
                             _("can't commit subrepos without .hgsub"))
-                    changes[0].insert(0, '.hgsubstate')
+                    status.modified.insert(0, '.hgsubstate')
 
-            elif '.hgsub' in changes[2]:
+            elif '.hgsub' in status.removed:
                 # clean up .hgsubstate when .hgsub is removed
                 if ('.hgsubstate' in wctx and
-                    '.hgsubstate' not in changes[0] + changes[1] + changes[2]):
-                    changes[2].insert(0, '.hgsubstate')
+                    '.hgsubstate' not in (status.modified + status.added +
+                                          status.removed)):
+                    status.removed.insert(0, '.hgsubstate')
 
             # make sure all explicit patterns are matched
             if not force and match.files():
-                matched = set(changes[0] + changes[1] + changes[2])
+                matched = set(status.modified + status.added + status.removed)
 
                 for f in match.files():
                     f = self.dirstate.normalize(f)
                     if f == '.' or f in matched or f in wctx.substate:
                         continue
-                    if f in changes[3]: # missing
+                    if f in status.deleted:
                         fail(f, _('file not found!'))
                     if f in vdirs: # visited directory
                         d = f + '/'
@@ -1306,7 +1308,7 @@ 
                     elif f not in self.dirstate:
                         fail(f, _("file not tracked!"))
 
-            cctx = context.workingctx(self, text, user, date, extra, changes)
+            cctx = context.workingctx(self, text, user, date, extra, status)
 
             if (not force and not extra.get("close") and not merge
                 and not cctx.files()
@@ -1317,7 +1319,7 @@ 
                 raise util.Abort(_("cannot commit merge with missing files"))
 
             ms = mergemod.mergestate(self)
-            for f in changes[0]:
+            for f in status.modified:
                 if f in ms and ms[f] == 'u':
                     raise util.Abort(_("unresolved merge conflicts "
                                        "(see hg help resolve)"))
diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -1619,8 +1619,8 @@ 
     ctx2 = repo[node2]
 
     if not changes:
-        changes = repo.status(ctx1, ctx2, match=match)
-    modified, added, removed = changes[:3]
+        changes = repo.status(ctx1, ctx2, match=match).committable()
+    modified, added, removed = changes[0], changes[1], changes[2]
 
     if not modified and not added and not removed:
         return []
diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -316,7 +316,7 @@ 
     """
     # i18n: "adds" is a keyword
     pat = getstring(x, _("adds requires a pattern"))
-    return checkstatus(repo, subset, pat, 1)
+    return checkstatus(repo, subset, pat, 'added')
 
 def ancestor(repo, subset, x):
     """``ancestor(*changeset)``
@@ -544,7 +544,7 @@ 
                     break
             else:
                 return False
-        files = repo.status(c.p1().node(), c.node())[field]
+        files = repo.status(c.p1().node(), c.node()).__dict__[field]
         if fname is not None:
             if fname in files:
                 return True
@@ -1112,7 +1112,7 @@ 
     """
     # i18n: "modifies" is a keyword
     pat = getstring(x, _("modifies requires a pattern"))
-    return checkstatus(repo, subset, pat, 0)
+    return checkstatus(repo, subset, pat, 'modified')
 
 def node_(repo, subset, x):
     """``id(string)``
@@ -1336,7 +1336,7 @@ 
     """
     # i18n: "removes" is a keyword
     pat = getstring(x, _("removes requires a pattern"))
-    return checkstatus(repo, subset, pat, 2)
+    return checkstatus(repo, subset, pat, 'removed')
 
 def rev(repo, subset, x):
     """``rev(number)``
diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py
--- a/mercurial/subrepo.py
+++ b/mercurial/subrepo.py
@@ -12,6 +12,7 @@ 
 import config, util, node, error, cmdutil, bookmarks, match as matchmod
 import phases
 import pathutil
+from mercurial.dirstate import status as statusmod
 hg = None
 propertycache = util.propertycache
 
@@ -447,7 +448,7 @@ 
         return 1
 
     def status(self, rev2, **opts):
-        return [], [], [], [], [], [], []
+        return statusmod([], [], [], [], [], [], [])
 
     def diff(self, ui, diffopts, node2, match, prefix, **opts):
         pass
@@ -649,7 +650,7 @@ 
         except error.RepoLookupError, inst:
             self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
                                % (inst, subrelpath(self)))
-            return [], [], [], [], [], [], []
+            return statusmod([], [], [], [], [], [], [])
 
     @annotatesubrepoerror
     def diff(self, ui, diffopts, node2, match, prefix, **opts):
@@ -1564,7 +1565,7 @@ 
         rev1 = self._state[1]
         if self._gitmissing() or not rev1:
             # if the repo is missing, return no results
-            return [], [], [], [], [], [], []
+            return statusmod([], [], [], [], [], [], [])
         modified, added, removed = [], [], []
         self._gitupdatestat()
         if rev2:
@@ -1585,7 +1586,8 @@ 
                 removed.append(f)
 
         deleted = unknown = ignored = clean = []
-        return modified, added, removed, deleted, unknown, ignored, clean
+        return statusmod(modified, added, removed, deleted, unknown, ignored,
+                         clean)
 
     def shortid(self, revid):
         return revid[:7]
diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -100,7 +100,7 @@ 
 
 def getfiles(repo, ctx, revcache):
     if 'files' not in revcache:
-        revcache['files'] = repo.status(ctx.p1().node(), ctx.node())[:3]
+        revcache['files'] = repo.status(ctx.p1().node(), ctx.node())
     return revcache['files']
 
 def getlatesttags(repo, ctx, cache):
@@ -248,7 +248,7 @@ 
 def showfileadds(**args):
     """:file_adds: List of strings. Files added by this changeset."""
     repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
-    return showlist('file_add', getfiles(repo, ctx, revcache)[1],
+    return showlist('file_add', getfiles(repo, ctx, revcache).added,
                     element='file', **args)
 
 def showfilecopies(**args):
@@ -286,13 +286,13 @@ 
 def showfiledels(**args):
     """:file_dels: List of strings. Files removed by this changeset."""
     repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
-    return showlist('file_del', getfiles(repo, ctx, revcache)[2],
+    return showlist('file_del', getfiles(repo, ctx, revcache).removed,
                     element='file', **args)
 
 def showfilemods(**args):
     """:file_mods: List of strings. Files modified by this changeset."""
     repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
-    return showlist('file_mod', getfiles(repo, ctx, revcache)[0],
+    return showlist('file_mod', getfiles(repo, ctx, revcache).modified,
                     element='file', **args)
 
 def showfiles(**args):
diff --git a/tests/test-context.py b/tests/test-context.py
--- a/tests/test-context.py
+++ b/tests/test-context.py
@@ -44,7 +44,7 @@ 
 ctxb = context.memctx(repo, [ctxa.node(), None], "test diff", ["foo"],
                       getfilectx, ctxa.user(), ctxa.date())
 
-print ctxb.status(ctxa)
+print ctxb.status(ctxa).all()
 
 # test performing a diff on a memctx