changeset 13186:fda7ae939344

merge with crew
author Matt Mackall <mpm@selenic.com>
date Wed, 22 Dec 2010 13:16:00 -0600
parents f16b3b1a2234 (diff) c6e00dfcdcb8 (current diff)
children e3b87fb34d00
files
diffstat 16 files changed, 819 insertions(+), 356 deletions(-) [+]
line wrap: on
line diff
--- a/hgext/mq.py	Tue Dec 21 19:47:36 2010 +0900
+++ b/hgext/mq.py	Wed Dec 22 13:16:00 2010 -0600
@@ -793,6 +793,19 @@
             return top, patch
         return None, None
 
+    def check_substate(self, repo):
+        '''return list of subrepos at a different revision than substate.
+        Abort if any subrepos have uncommitted changes.'''
+        inclsubs = []
+        wctx = repo[None]
+        for s in wctx.substate:
+            if wctx.sub(s).dirty(True):
+                raise util.Abort(
+                    _("uncommitted changes in subrepository %s") % s)
+            elif wctx.sub(s).dirty():
+                inclsubs.append(s)
+        return inclsubs
+
     def check_localchanges(self, repo, force=False, refresh=True):
         m, a, r, d = repo.status()[:4]
         if (m or a or r or d) and not force:
@@ -826,16 +839,23 @@
                                  % patchfn)
             else:
                 raise util.Abort(_('patch "%s" already exists') % patchfn)
+
+        inclsubs = self.check_substate(repo)
+        if inclsubs:
+            inclsubs.append('.hgsubstate')
         if opts.get('include') or opts.get('exclude') or pats:
+            if inclsubs:
+                pats = list(pats or []) + inclsubs
             match = cmdutil.match(repo, pats, opts)
             # detect missing files in pats
             def badfn(f, msg):
-                raise util.Abort('%s: %s' % (f, msg))
+                if f != '.hgsubstate': # .hgsubstate is auto-created
+                    raise util.Abort('%s: %s' % (f, msg))
             match.bad = badfn
             m, a, r, d = repo.status(match=match)[:4]
         else:
             m, a, r, d = self.check_localchanges(repo, force=True)
-            match = cmdutil.matchfiles(repo, m + a + r)
+            match = cmdutil.matchfiles(repo, m + a + r + inclsubs)
         if len(repo[None].parents()) > 1:
             raise util.Abort(_('cannot manage merge changesets'))
         commitfiles = m + a + r
@@ -1259,6 +1279,8 @@
             if repo.changelog.heads(top) != [top]:
                 raise util.Abort(_("cannot refresh a revision with children"))
 
+            inclsubs = self.check_substate(repo)
+
             cparents = repo.changelog.parents(top)
             patchparent = self.qparents(repo, top)
             ph = patchheader(self.join(patchfn), self.plainmode)
@@ -1310,18 +1332,12 @@
             # local dirstate. in this case, we want them to only
             # show up in the added section
             for x in m:
-                if x == '.hgsub' or x == '.hgsubstate':
-                    self.ui.warn(_('warning: not refreshing %s\n') % x)
-                    continue
                 if x not in aa:
                     mm.add(x)
             # we might end up with files added by the local dirstate that
             # were deleted by the patch.  In this case, they should only
             # show up in the changed section.
             for x in a:
-                if x == '.hgsub' or x == '.hgsubstate':
-                    self.ui.warn(_('warning: not adding %s\n') % x)
-                    continue
                 if x in dd:
                     dd.remove(x)
                     mm.add(x)
@@ -1331,9 +1347,6 @@
             # are not in the add or change column of the patch
             forget = []
             for x in d + r:
-                if x == '.hgsub' or x == '.hgsubstate':
-                    self.ui.warn(_('warning: not removing %s\n') % x)
-                    continue
                 if x in aa:
                     aa.remove(x)
                     forget.append(x)
@@ -1346,7 +1359,7 @@
             r = list(dd)
             a = list(aa)
             c = [filter(matchfn, l) for l in (m, a, r)]
-            match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
+            match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs))
             chunks = patch.diff(repo, patchparent, match=match,
                                 changes=c, opts=diffopts)
             for chunk in chunks:
--- a/i18n/da.po	Tue Dec 21 19:47:36 2010 +0900
+++ b/i18n/da.po	Wed Dec 22 13:16:00 2010 -0600
@@ -17,14 +17,14 @@
 msgstr ""
 "Project-Id-Version: Mercurial\n"
 "Report-Msgid-Bugs-To: <mercurial-devel@selenic.com>\n"
-"POT-Creation-Date: 2010-11-01 11:03+0100\n"
-"PO-Revision-Date: 2010-11-01 11:11+0100\n"
+"POT-Creation-Date: 2010-12-10 12:44+0100\n"
+"PO-Revision-Date: 2010-12-10 12:46+0100\n"
 "Last-Translator: <mg@lazybytes.net>\n"
 "Language-Team: Danish\n"
+"Language: Danish\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Language: Danish\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 
 #, python-format
@@ -2070,6 +2070,13 @@
 "  antagelse af at de har miksede linieskift med vilje."
 
 msgid ""
+"The ``win32text.forbid*`` hooks provided by the win32text extension\n"
+"have been unified into a single hook named ``eol.hook``. The hook will\n"
+"lookup the expected line endings from the ``.hgeol`` file, which means\n"
+"you must migrate to a ``.hgeol`` file first before using the hook."
+msgstr ""
+
+msgid ""
 "See :hg:`help patterns` for more information about the glob patterns\n"
 "used.\n"
 msgstr ""
@@ -2767,8 +2774,8 @@
 #, python-format
 msgid "*** the current per-user limit on the number of inotify watches is %s\n"
 msgstr ""
-"*** den nuværende grænse pr bruger for antallet af inotify overvågninger er %"
-"s\n"
+"*** den nuværende grænse pr bruger for antallet af inotify overvågninger er "
+"%s\n"
 
 msgid "*** this limit is too low to watch every directory in this repository\n"
 msgstr ""
@@ -2975,7 +2982,7 @@
 msgid ""
 "The default template mappings (view with :hg:`kwdemo -d`) can be\n"
 "replaced with customized keywords and templates. Again, run\n"
-":hg:`kwdemo` to control the results of your config changes."
+":hg:`kwdemo` to control the results of your configuration changes."
 msgstr ""
 
 msgid ""
@@ -3408,7 +3415,7 @@
 
 #, python-format
 msgid "cannot write patch \"%s\": %s"
-msgstr "kan ikke skrive patch \"%s\": %s"
+msgstr "kan ikke skrive rettelse \"%s\": %s"
 
 #, python-format
 msgid "error unlinking %s\n"
@@ -3501,6 +3508,18 @@
 msgid "cannot refresh a revision with children"
 msgstr "kan ikke genopfriske en revision som har børn"
 
+#, python-format
+msgid "warning: not refreshing %s\n"
+msgstr "advarsel: genopfrisker ikke %s\n"
+
+#, python-format
+msgid "warning: not adding %s\n"
+msgstr "advarsel: tilføjer ikke %s\n"
+
+#, python-format
+msgid "warning: not removing %s\n"
+msgstr "advarsel: fjerner ikke %s\n"
+
 msgid ""
 "refresh interrupted while patch was popped! (revert --all, qpush to "
 "recover)\n"
@@ -5362,8 +5381,8 @@
 msgid "changesets"
 msgstr "ændringer"
 
-msgid "fix unresolved conflicts with hg resolve then run hg rebase --continue"
-msgstr "ret uløste konflikter med hg resolve og kør så hg rebase --continue"
+msgid "unresolved conflicts (see hg resolve, then hg rebase --continue)"
+msgstr "uløste konflikter (se først hg resolve og dernæst hg rebase --continue)"
 
 #, python-format
 msgid "no changes, revision %d skipped\n"
@@ -5967,11 +5986,13 @@
 msgid "Note that there are some limitations on using this extension:"
 msgstr ""
 
-msgid "- You should use single encoding in one repository."
-msgstr ""
-
-msgid ""
-"\n"
+msgid ""
+"- You should use single encoding in one repository.\n"
+"- If the repository path ends with 0x5c, .hg/hgrc cannot be read.\n"
+"- win32mbcs is not compatible with fixutf8 extention."
+msgstr ""
+
+msgid ""
 "By default, win32mbcs uses encoding.encoding decided by Mercurial.\n"
 "You can specify the encoding by config option::"
 msgstr ""
@@ -6223,8 +6244,8 @@
 #, python-format
 msgid "%s has not been committed yet, so no copy data will be stored for %s.\n"
 msgstr ""
-"%s er endnu ikke comitted, så der vil ikke blive gemt kopieringsdata for %"
-"s.\n"
+"%s er endnu ikke comitted, så der vil ikke blive gemt kopieringsdata for "
+"%s.\n"
 
 #, python-format
 msgid "%s: not copying - file is not managed\n"
@@ -8561,7 +8582,7 @@
 
 msgid ""
 "    Start a local HTTP repository browser and pull server. You can use\n"
-"    this for ad-hoc sharing and browing of repositories. It is\n"
+"    this for ad-hoc sharing and browsing of repositories. It is\n"
 "    recommended to use a real web server to serve a repository for\n"
 "    longer periods of time."
 msgstr ""
@@ -11704,11 +11725,11 @@
 msgstr ""
 
 msgid ""
-"    hg log -r \"(keyword(bug) or keyword(issue)) and not ancestors(tagged())"
-"\"\n"
-msgstr ""
-"    hg log -r \"(keyword(bug) or keyword(issue)) and not ancestors(tagged())"
-"\"\n"
+"    hg log -r \"(keyword(bug) or keyword(issue)) and not ancestors(tagged"
+"())\"\n"
+msgstr ""
+"    hg log -r \"(keyword(bug) or keyword(issue)) and not ancestors(tagged"
+"())\"\n"
 
 msgid ""
 "Subrepositories let you nest external repositories or projects into a\n"
@@ -12167,7 +12188,7 @@
 msgid ""
 "Paths in the local filesystem can either point to Mercurial\n"
 "repositories or to bundle files (as created by :hg:`bundle` or :hg:`\n"
-"incoming --bundle`)."
+"incoming --bundle`). See also :hg:`help paths`."
 msgstr ""
 
 msgid ""
@@ -12527,6 +12548,10 @@
 msgid "working directory of %s"
 msgstr "arbejdskatalog for %s"
 
+#, python-format
+msgid "warning: can't find ancestor for '%s' copied from '%s'!\n"
+msgstr ""
+
 msgid "cannot partially commit a merge (do not specify files or patterns)"
 msgstr ""
 "kan ikke deponere en sammenføjning partielt (undgå at specificere filer "
@@ -13369,6 +13394,10 @@
 msgstr ""
 
 #, python-format
+msgid "warning: subrepo spec file %s not found\n"
+msgstr "advarsel: underdepot spec-fil %s blev ikke fundet\n"
+
+#, python-format
 msgid "subrepo spec file %s not found"
 msgstr "underdepot spec-fil %s blev ikke fundet"
 
@@ -13591,6 +13620,13 @@
 msgid "could not symlink to %r: %s"
 msgstr "kunne ikke lave et symbolsk link til %r: %s"
 
+msgid "check your clock"
+msgstr ""
+
+#, python-format
+msgid "negative timestamp: %d"
+msgstr ""
+
 #, python-format
 msgid "invalid date: %r"
 msgstr "ugyldig dato: %r"
@@ -13600,6 +13636,10 @@
 msgstr "dato overskrider 32 bit: %d"
 
 #, python-format
+msgid "negative date value: %d"
+msgstr ""
+
+#, python-format
 msgid "impossible time zone offset: %d"
 msgstr "umuligt tidszone: %d"
 
--- a/i18n/de.po	Tue Dec 21 19:47:36 2010 +0900
+++ b/i18n/de.po	Wed Dec 22 13:16:00 2010 -0600
@@ -38,7 +38,7 @@
 "Project-Id-Version: Mercurial\n"
 "Report-Msgid-Bugs-To: <mercurial-devel@selenic.com>\n"
 "POT-Creation-Date: 2010-09-22 13:26+0200\n"
-"PO-Revision-Date: 2010-09-24 02:15+0200\n"
+"PO-Revision-Date: 2010-12-04 19:28+0100\n"
 "Last-Translator: Martin Roppelt <m.p.roppelt@web.de>\n"
 "Language-Team: German (http://transifex.net/projects/p/mercurial/team/de/) "
 "<>\n"
@@ -5864,7 +5864,7 @@
 msgstr "Emailadressen von CC-Empfängern"
 
 msgid "ask for confirmation before sending"
-msgstr ""
+msgstr "Vor dem Abschicken bestätigen"
 
 msgid "add diffstat output to messages"
 msgstr "Fügt Ausgabe von diffstat hinzu"
@@ -5895,7 +5895,7 @@
 msgstr "Antwortadresse (reply-to)"
 
 msgid "flags to add in subject prefixes"
-msgstr ""
+msgstr "Diese Stichwörter zu Betreffs-Präfixen hinzufügen"
 
 msgid "email addresses of recipients"
 msgstr "Emailadressen der Empfänger"
@@ -5929,7 +5929,7 @@
 msgstr "hg email [OPTION]... [ZIEL]..."
 
 msgid "show progress bars for some actions"
-msgstr ""
+msgstr "Bei einigen Befehlen Fortschrittsbalken zeigen"
 
 msgid ""
 "This extension uses the progress information logged by hg commands\n"
@@ -5965,35 +5965,46 @@
 msgstr "Löscht nicht versionierte Dateien aus dem Arbeitsverzeichnis"
 
 msgid "removes files not tracked by Mercurial"
-msgstr ""
+msgstr "Entfernt nicht von Mercurial versionierte Dateien"
 
 msgid ""
 "    Delete files not known to Mercurial. This is useful to test local\n"
 "    and uncommitted changes in an otherwise-clean source tree."
 msgstr ""
+"    Entferne Dateien, die Mercurial nicht bekannt sind. Nützlich, um\n"
+"    lokale und nicht versionierte Dateien in einem ansonsten\n"
+"    unveränderten Projektarchiv zu testen."
 
 msgid "    This means that purge will delete:"
-msgstr ""
+msgstr "    Das heißt, purge wird das folgende löschen:"
 
 msgid ""
 "    - Unknown files: files marked with \"?\" by :hg:`status`\n"
 "    - Empty directories: in fact Mercurial ignores directories unless\n"
 "      they contain files under source control management"
 msgstr ""
+"    - Unbekannte Dateien: Dateien, die :hg:`status` mit \"?\\\" markiert\n"
+"    - Leere Verzeichnisse: Mercurial ignoriert Verzeichnisse, solange\n"
+"      sie keine Dateien unter Versionsverwaltung enthalten."
 
 msgid "    But it will leave untouched:"
-msgstr ""
+msgstr "    Aber das folgende unberührt lassen:"
 
 msgid ""
 "    - Modified and unmodified tracked files\n"
 "    - Ignored files (unless --all is specified)\n"
 "    - New files added to the repository (with :hg:`add`)"
 msgstr ""
+"    - Veränderte und unveränderte Dateien unter Versionsverwaltung\n"
+"    - Ignorierte Dateien (es sei denn --all wurde angegeben)\n"
+"    - Neu hinzugefügte Dateien (mit :hg:`add`)"
 
 msgid ""
 "    If directories are given on the command line, only files in these\n"
 "    directories are considered."
 msgstr ""
+"    Wenn auf der Befehlzeile Verzeichnisse angegeben wurden, werden\n"
+"    nur Dateien in diesen Verzeichnissen einbezogen."
 
 msgid ""
 "    Be careful with purge, as you could irreversibly delete some files\n"
@@ -6002,28 +6013,33 @@
 "    option.\n"
 "    "
 msgstr ""
+"    Seien Sie mit purge vorsichtig, da Sie Dateien unwiderbringlich\n"
+"    löschen könnten, die Sie nicht zum Projektarchiv hinzugefügt\n"
+"    haben. Wenn Sie nur die Liste der Dateien sehen wollen, die dieses\n"
+"    Programm entfernen würde, nutzen Sie die Option --print.\n"
+"    "
 
 #, python-format
 msgid "%s cannot be removed"
-msgstr ""
+msgstr "%s kann nicht entfernt werden"
 
 #, python-format
 msgid "warning: %s\n"
-msgstr ""
+msgstr "Warnung: %s\n"
 
 #, python-format
 msgid "Removing file %s\n"
-msgstr ""
+msgstr "Entferne Datei %s\n"
 
 #, python-format
 msgid "Removing directory %s\n"
-msgstr ""
+msgstr "Entferne Verzeichnis %s\n"
 
 msgid "abort if an error occurs"
-msgstr ""
+msgstr "Bei Fehler abbrechen"
 
 msgid "purge ignored files too"
-msgstr ""
+msgstr "Auch ignorierte Dateien entfernen"
 
 msgid "print filenames instead of deleting them"
 msgstr "Zeigt Dateinamen an, statt sie zu entfernen"
@@ -6033,7 +6049,7 @@
 "Beendet Dateinamen mit NUL zur Nutzung mit xargs (implizert -p/--print)"
 
 msgid "hg purge [OPTION]... [DIR]..."
-msgstr ""
+msgstr "hg purge [OPTION]... [DIR]..."
 
 msgid "command to move sets of revisions to a different ancestor"
 msgstr "Verknüpft Änderungssätze mit einem anderen Vorgänger"
--- a/mercurial/localrepo.py	Tue Dec 21 19:47:36 2010 +0900
+++ b/mercurial/localrepo.py	Wed Dec 22 13:16:00 2010 -0600
@@ -949,7 +949,6 @@
 
             # commit subs
             if subs or removedsubs:
-                pstate = subrepo.substate(self['.'])
                 state = wctx.substate.copy()
                 for s in sorted(subs):
                     sub = wctx.sub(s)
@@ -957,19 +956,7 @@
                         subrepo.subrelpath(sub))
                     sr = sub.commit(cctx._text, user, date)
                     state[s] = (state[s][0], sr)
-
-                changed = False
-                if len(pstate) != len(state):
-                    changed = True
-                if not changed:
-                    for newstate in state:
-                        if state[newstate][1] != pstate[newstate]:
-                            changed = True
-                if changed:
-                    subrepo.writestate(self, state)
-                elif (changes[0] == ['.hgsubstate'] and changes[1] == [] and
-                     changes[2] == []):
-                    return None
+                subrepo.writestate(self, state)
 
             # Save commit message in case this transaction gets rolled back
             # (e.g. by a pretxncommit hook).  Leave the content alone on
--- a/mercurial/parser.py	Tue Dec 21 19:47:36 2010 +0900
+++ b/mercurial/parser.py	Wed Dec 22 13:16:00 2010 -0600
@@ -22,6 +22,7 @@
         self._tokenizer = tokenizer
         self._elements = elements
         self._methods = methods
+        self.current = None
     def _advance(self):
         'advance the tokenizer'
         t = self.current
@@ -76,7 +77,7 @@
     def parse(self, message):
         'generate a parse tree from a message'
         self._iter = self._tokenizer(message)
-        self.current = self._iter.next()
+        self._advance()
         return self._parse()
     def eval(self, tree):
         'recursively evaluate a parse tree using node methods'
--- a/mercurial/store.py	Tue Dec 21 19:47:36 2010 +0900
+++ b/mercurial/store.py	Wed Dec 22 13:16:00 2010 -0600
@@ -323,7 +323,8 @@
             self.fncache.rewrite(existing)
 
     def copylist(self):
-        d = _data + ' dh fncache'
+        d = ('data dh fncache'
+             ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
         return (['requires', '00changelog.i'] +
                 [self.pathjoiner('store', f) for f in d.split()])
 
--- a/mercurial/subrepo.py	Tue Dec 21 19:47:36 2010 +0900
+++ b/mercurial/subrepo.py	Wed Dec 22 13:16:00 2010 -0600
@@ -13,19 +13,6 @@
 
 nullstate = ('', '', 'empty')
 
-
-def substate(ctx):
-    rev = {}
-    if '.hgsubstate' in ctx:
-        try:
-            for l in ctx['.hgsubstate'].data().splitlines():
-                revision, path = l.split(" ", 1)
-                rev[path] = revision
-        except IOError, err:
-            if err.errno != errno.ENOENT:
-                raise
-    return rev
-
 def state(ctx, ui):
     """return a state dict, mapping subrepo paths configured in .hgsub
     to tuple: (source from .hgsub, revision from .hgsubstate, kind
@@ -52,7 +39,15 @@
     for path, src in ui.configitems('subpaths'):
         p.set('subpaths', path, src, ui.configsource('subpaths', path))
 
-    rev = substate(ctx)
+    rev = {}
+    if '.hgsubstate' in ctx:
+        try:
+            for l in ctx['.hgsubstate'].data().splitlines():
+                revision, path = l.split(" ", 1)
+                rev[path] = revision
+        except IOError, err:
+            if err.errno != errno.ENOENT:
+                raise
 
     state = {}
     for path, src in p[''].items():
@@ -177,6 +172,8 @@
 
 def subrelpath(sub):
     """return path to this subrepo as seen from outermost repo"""
+    if hasattr(sub, '_relpath'):
+        return sub._relpath
     if not hasattr(sub, '_repo'):
         return sub._path
     return reporelpath(sub._repo)
@@ -241,9 +238,10 @@
 
 class abstractsubrepo(object):
 
-    def dirty(self):
-        """returns true if the dirstate of the subrepo does not match
-        current stored state
+    def dirty(self, ignoreupdate=False):
+        """returns true if the dirstate of the subrepo is dirty or does not
+        match current stored state. If ignoreupdate is true, only check
+        whether the subrepo has uncommitted changes in its dirstate.
         """
         raise NotImplementedError
 
@@ -395,12 +393,13 @@
             s = subrepo(ctx, subpath)
             s.archive(ui, archiver, os.path.join(prefix, self._path))
 
-    def dirty(self):
+    def dirty(self, ignoreupdate=False):
         r = self._state[1]
-        if r == '':
+        if r == '' and not ignoreupdate: # no state recorded
             return True
         w = self._repo[None]
-        if w.p1() != self._repo[r]: # version checked out change
+        # version checked out changed?
+        if w.p1() != self._repo[r] and not ignoreupdate:
             return True
         return w.dirty() # working directory changed
 
@@ -543,9 +542,10 @@
                     return True, True
         return bool(changes), False
 
-    def dirty(self):
-        if self._wcrev() == self._state[1] and not self._wcchanged()[0]:
-            return False
+    def dirty(self, ignoreupdate=False):
+        if not self._wcchanged()[0]:
+            if self._wcrev() == self._state[1] and not ignoreupdate:
+                return False
         return True
 
     def commit(self, text, user, date):
@@ -619,15 +619,17 @@
         # TODO add git version check.
         self._state = state
         self._ctx = ctx
-        self._relpath = path
-        self._path = ctx._repo.wjoin(path)
+        self._path = path
+        self._relpath = os.path.join(reporelpath(ctx._repo), path)
+        self._abspath = ctx._repo.wjoin(path)
         self._ui = ctx._repo.ui
 
     def _gitcommand(self, commands, env=None, stream=False):
         return self._gitdir(commands, env=env, stream=stream)[0]
 
     def _gitdir(self, commands, env=None, stream=False):
-        return self._gitnodir(commands, env=env, stream=stream, cwd=self._path)
+        return self._gitnodir(commands, env=env, stream=stream,
+                              cwd=self._abspath)
 
     def _gitnodir(self, commands, env=None, stream=False, cwd=None):
         """Calls the git command
@@ -680,32 +682,42 @@
         return base == r1
 
     def _gitbranchmap(self):
-        '''returns 3 things:
+        '''returns 2 things:
         a map from git branch to revision
-        a map from revision to branches
-        a map from remote branch to local tracking branch'''
+        a map from revision to branches'''
         branch2rev = {}
         rev2branch = {}
-        tracking = {}
+
         out = self._gitcommand(['for-each-ref', '--format',
-                                '%(objectname) %(refname) %(upstream) end'])
+                                '%(objectname) %(refname)'])
         for line in out.split('\n'):
-            revision, ref, upstream = line.split(' ')[:3]
+            revision, ref = line.split(' ')
             if ref.startswith('refs/tags/'):
                 continue
             if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
                 continue # ignore remote/HEAD redirects
             branch2rev[ref] = revision
             rev2branch.setdefault(revision, []).append(ref)
-            if upstream:
-                # assumes no more than one local tracking branch for a remote
-                tracking[upstream] = ref
-        return branch2rev, rev2branch, tracking
+        return branch2rev, rev2branch
+
+    def _gittracking(self, branches):
+        'return map of remote branch to local tracking branch'
+        # assumes no more than one local tracking branch for each remote
+        tracking = {}
+        for b in branches:
+            if b.startswith('refs/remotes/'):
+                continue
+            remote = self._gitcommand(['config', 'branch.%s.remote' % b])
+            if remote:
+                ref = self._gitcommand(['config', 'branch.%s.merge' % b])
+                tracking['refs/remotes/%s/%s' %
+                         (remote, ref.split('/', 2)[2])] = b
+        return tracking
 
     def _fetch(self, source, revision):
-        if not os.path.exists('%s/.git' % self._path):
+        if not os.path.exists(os.path.join(self._abspath, '.git')):
             self._ui.status(_('cloning subrepo %s\n') % self._relpath)
-            self._gitnodir(['clone', source, self._path])
+            self._gitnodir(['clone', source, self._abspath])
         if self._githavelocally(revision):
             return
         self._ui.status(_('pulling subrepo %s\n') % self._relpath)
@@ -717,10 +729,11 @@
         self._gitcommand(['fetch', source])
         if not self._githavelocally(revision):
             raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
-                               (revision, self._path))
+                               (revision, self._relpath))
 
-    def dirty(self):
-        if self._state[1] != self._gitstate(): # version checked out changed?
+    def dirty(self, ignoreupdate=False):
+        # version checked out changed?
+        if not ignoreupdate and self._state[1] != self._gitstate():
             return True
         # check for staged changes or modified files; ignore untracked files
         out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
@@ -737,7 +750,7 @@
                 return
         elif self._gitstate() == revision:
             return
-        branch2rev, rev2branch, tracking = self._gitbranchmap()
+        branch2rev, rev2branch = self._gitbranchmap()
 
         def rawcheckout():
             # no branch to checkout, check it out with no branch
@@ -763,6 +776,7 @@
             self._gitcommand(['checkout', firstlocalbranch])
             return
 
+        tracking = self._gittracking(branch2rev.keys())
         # choose a remote branch already tracked if possible
         remote = branches[0]
         if remote not in tracking:
@@ -815,7 +829,7 @@
 
     def push(self, force):
         # if a branch in origin contains the revision, nothing to do
-        branch2rev, rev2branch, tracking = self._gitbranchmap()
+        branch2rev, rev2branch = self._gitbranchmap()
         if self._state[1] in rev2branch:
             for b in rev2branch[self._state[1]]:
                 if b.startswith('refs/remotes/origin/'):
@@ -849,16 +863,16 @@
     def remove(self):
         if self.dirty():
             self._ui.warn(_('not removing repo %s because '
-                            'it has changes.\n') % self._path)
+                            'it has changes.\n') % self._relpath)
             return
         # we can't fully delete the repository as it may contain
         # local-only history
-        self._ui.note(_('removing subrepo %s\n') % self._path)
+        self._ui.note(_('removing subrepo %s\n') % self._relpath)
         self._gitcommand(['config', 'core.bare', 'true'])
-        for f in os.listdir(self._path):
+        for f in os.listdir(self._abspath):
             if f == '.git':
                 continue
-            path = os.path.join(self._path, f)
+            path = os.path.join(self._abspath, f)
             if os.path.isdir(path) and not os.path.islink(path):
                 shutil.rmtree(path)
             else:
@@ -876,14 +890,42 @@
         relpath = subrelpath(self)
         ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
         for i, info in enumerate(tar):
-            archiver.addfile(os.path.join(prefix, self._relpath, info.name),
-                             info.mode, info.issym(),
-                             tar.extractfile(info).read())
+            if info.isdir():
+                continue
+            if info.issym():
+                data = info.linkname
+            else:
+                data = tar.extractfile(info).read()
+            archiver.addfile(os.path.join(prefix, self._path, info.name),
+                             info.mode, info.issym(), data)
             ui.progress(_('archiving (%s)') % relpath, i + 1,
                         unit=_('files'))
         ui.progress(_('archiving (%s)') % relpath, None)
 
 
+    def status(self, rev2, **opts):
+        rev1 = self._state[1]
+        modified, added, removed = [], [], []
+        if rev2:
+            command = ['diff-tree', rev1, rev2]
+        else:
+            command = ['diff-index', rev1]
+        out = self._gitcommand(command)
+        for line in out.split('\n'):
+            tab = line.find('\t')
+            if tab == -1:
+                continue
+            status, f = line[tab - 1], line[tab + 1:]
+            if status == 'M':
+                modified.append(f)
+            elif status == 'A':
+                added.append(f)
+            elif status == 'D':
+                removed.append(f)
+
+        deleted = unknown = ignored = clean = []
+        return modified, added, removed, deleted, unknown, ignored, clean
+
 types = {
     'hg': hgsubrepo,
     'svn': svnsubrepo,
--- a/mercurial/templater.py	Tue Dec 21 19:47:36 2010 +0900
+++ b/mercurial/templater.py	Wed Dec 22 13:16:00 2010 -0600
@@ -7,7 +7,192 @@
 
 from i18n import _
 import sys, os
-import util, config, templatefilters
+import util, config, templatefilters, parser, error
+
+# template parsing
+
+elements = {
+    "(": (20, ("group", 1, ")"), ("func", 1, ")")),
+    ",": (2, None, ("list", 2)),
+    "|": (5, None, ("|", 5)),
+    "%": (6, None, ("%", 6)),
+    ")": (0, None, None),
+    "symbol": (0, ("symbol",), None),
+    "string": (0, ("string",), None),
+    "end": (0, None, None),
+}
+
+def tokenizer(data):
+    program, start, end = data
+    pos = start
+    while pos < end:
+        c = program[pos]
+        if c.isspace(): # skip inter-token whitespace
+            pass
+        elif c in "(,)%|": # handle simple operators
+            yield (c, None, pos)
+        elif (c in '"\'' or c == 'r' and
+              program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
+            if c == 'r':
+                pos += 1
+                c = program[pos]
+                decode = lambda x: x
+            else:
+                decode = lambda x: x.decode('string-escape')
+            pos += 1
+            s = pos
+            while pos < end: # find closing quote
+                d = program[pos]
+                if d == '\\': # skip over escaped characters
+                    pos += 2
+                    continue
+                if d == c:
+                    yield ('string', decode(program[s:pos]), s)
+                    break
+                pos += 1
+            else:
+                raise error.ParseError(_("unterminated string"), s)
+        elif c.isalnum() or c in '_':
+            s = pos
+            pos += 1
+            while pos < end: # find end of symbol
+                d = program[pos]
+                if not (d.isalnum() or d == "_"):
+                    break
+                pos += 1
+            sym = program[s:pos]
+            yield ('symbol', sym, s)
+            pos -= 1
+        elif c == '}':
+            pos += 1
+            break
+        else:
+            raise error.ParseError(_("syntax error"), pos)
+        pos += 1
+    data[2] = pos
+    yield ('end', None, pos)
+
+def compiletemplate(tmpl, context):
+    parsed = []
+    pos, stop = 0, len(tmpl)
+    p = parser.parser(tokenizer, elements)
+
+    while pos < stop:
+        n = tmpl.find('{', pos)
+        if n < 0:
+            parsed.append(("string", tmpl[pos:]))
+            break
+        if n > 0 and tmpl[n - 1] == '\\':
+            # escaped
+            parsed.append(("string", tmpl[pos:n - 1] + "{"))
+            pos = n + 1
+            continue
+        if n > pos:
+            parsed.append(("string", tmpl[pos:n]))
+
+        pd = [tmpl, n + 1, stop]
+        parsed.append(p.parse(pd))
+        pos = pd[2]
+
+    return [compileexp(e, context) for e in parsed]
+
+def compileexp(exp, context):
+    t = exp[0]
+    if t in methods:
+        return methods[t](exp, context)
+    raise error.ParseError(_("unknown method '%s'") % t)
+
+# template evaluation
+
+def getsymbol(exp):
+    if exp[0] == 'symbol':
+        return exp[1]
+    raise error.ParseError(_("expected a symbol"))
+
+def getlist(x):
+    if not x:
+        return []
+    if x[0] == 'list':
+        return getlist(x[1]) + [x[2]]
+    return [x]
+
+def getfilter(exp, context):
+    f = getsymbol(exp)
+    if f not in context._filters:
+        raise error.ParseError(_("unknown function '%s'") % f)
+    return context._filters[f]
+
+def gettemplate(exp, context):
+    if exp[0] == 'string':
+        return compiletemplate(exp[1], context)
+    if exp[0] == 'symbol':
+        return context._load(exp[1])
+    raise error.ParseError(_("expected template specifier"))
+
+def runstring(context, mapping, data):
+    return data
+
+def runsymbol(context, mapping, key):
+    v = mapping.get(key)
+    if v is None:
+        v = context._defaults.get(key, '')
+    if hasattr(v, '__call__'):
+        return v(**mapping)
+    return v
+
+def buildfilter(exp, context):
+    func, data = compileexp(exp[1], context)
+    filt = getfilter(exp[2], context)
+    return (runfilter, (func, data, filt))
+
+def runfilter(context, mapping, data):
+    func, data, filt = data
+    return filt(func(context, mapping, data))
+
+def buildmap(exp, context):
+    func, data = compileexp(exp[1], context)
+    ctmpl = gettemplate(exp[2], context)
+    return (runmap, (func, data, ctmpl))
+
+def runmap(context, mapping, data):
+    func, data, ctmpl = data
+    d = func(context, mapping, data)
+    lm = mapping.copy()
+
+    for i in d:
+        if isinstance(i, dict):
+            lm.update(i)
+            for f, d in ctmpl:
+                yield f(context, lm, d)
+        else:
+            # v is not an iterable of dicts, this happen when 'key'
+            # has been fully expanded already and format is useless.
+            # If so, return the expanded value.
+            yield i
+
+def buildfunc(exp, context):
+    n = getsymbol(exp[1])
+    args = [compileexp(x, context) for x in getlist(exp[2])]
+    if n in context._filters:
+        if len(args) != 1:
+            raise error.ParseError(_("filter %s expects one argument") % n)
+        f = context._filters[n]
+        return (runfilter, (args[0][0], args[0][1], f))
+    elif n in context._funcs:
+        f = context._funcs[n]
+        return (f, args)
+
+methods = {
+    "string": lambda e, c: (runstring, e[1]),
+    "symbol": lambda e, c: (runsymbol, e[1]),
+    "group": lambda e, c: compileexp(e[1], c),
+#    ".": buildmember,
+    "|": buildfilter,
+    "%": buildmap,
+    "func": buildfunc,
+    }
+
+# template engine
 
 path = ['templates', '../templates']
 stringify = templatefilters.stringify
@@ -66,104 +251,18 @@
         self._defaults = defaults
         self._cache = {}
 
+    def _load(self, t):
+        '''load, parse, and cache a template'''
+        if t not in self._cache:
+            self._cache[t] = compiletemplate(self._loader(t), self)
+        return self._cache[t]
+
     def process(self, t, mapping):
         '''Perform expansion. t is name of map element to expand.
         mapping contains added elements for use during expansion. Is a
         generator.'''
-        return _flatten(self._process(self._load(t), mapping))
-
-    def _load(self, t):
-        '''load, parse, and cache a template'''
-        if t not in self._cache:
-            self._cache[t] = self._parse(self._loader(t))
-        return self._cache[t]
-
-    def _get(self, mapping, key):
-        v = mapping.get(key)
-        if v is None:
-            v = self._defaults.get(key, '')
-        if hasattr(v, '__call__'):
-            v = v(**mapping)
-        return v
-
-    def _filter(self, mapping, parts):
-        filters, val = parts
-        x = self._get(mapping, val)
-        for f in filters:
-            x = f(x)
-        return x
-
-    def _format(self, mapping, args):
-        key, parsed = args
-        v = self._get(mapping, key)
-        if not hasattr(v, '__iter__'):
-            raise SyntaxError(_("error expanding '%s%%%s'")
-                              % (key, parsed))
-        lm = mapping.copy()
-        for i in v:
-            if isinstance(i, dict):
-                lm.update(i)
-                yield self._process(parsed, lm)
-            else:
-                # v is not an iterable of dicts, this happen when 'key'
-                # has been fully expanded already and format is useless.
-                # If so, return the expanded value.
-                yield i
-
-    def _parse(self, tmpl):
-        '''preparse a template'''
-        parsed = []
-        pos, stop = 0, len(tmpl)
-        while pos < stop:
-            n = tmpl.find('{', pos)
-            if n < 0:
-                parsed.append((None, tmpl[pos:stop]))
-                break
-            if n > 0 and tmpl[n - 1] == '\\':
-                # escaped
-                parsed.append((None, tmpl[pos:n - 1] + "{"))
-                pos = n + 1
-                continue
-            if n > pos:
-                parsed.append((None, tmpl[pos:n]))
-
-            pos = n
-            n = tmpl.find('}', pos)
-            if n < 0:
-                # no closing
-                parsed.append((None, tmpl[pos:stop]))
-                break
-
-            expr = tmpl[pos + 1:n]
-            pos = n + 1
-
-            if '%' in expr:
-                # the keyword should be formatted with a template
-                key, t = expr.split('%')
-                parsed.append((self._format, (key.strip(),
-                                              self._load(t.strip()))))
-            elif '|' in expr:
-                # process the keyword value with one or more filters
-                parts = expr.split('|')
-                val = parts[0].strip()
-                try:
-                    filters = [self._filters[f.strip()] for f in parts[1:]]
-                except KeyError, i:
-                    raise SyntaxError(_("unknown filter '%s'") % i[0])
-                parsed.append((self._filter, (filters, val)))
-            else:
-                # just get the keyword
-                parsed.append((self._get, expr.strip()))
-
-        return parsed
-
-    def _process(self, parsed, mapping):
-        '''Render a template. Returns a generator.'''
-        for f, e in parsed:
-            if f:
-                yield f(mapping, e)
-            else:
-                yield e
+        return _flatten(func(self, mapping, data) for func, data in
+                         self._load(t))
 
 engines = {'default': engine}
 
@@ -214,6 +313,8 @@
         if not t in self.cache:
             try:
                 self.cache[t] = open(self.map[t][1]).read()
+            except KeyError, inst:
+                raise util.Abort(_('"%s" not in template map') % inst.args[0])
             except IOError, inst:
                 raise IOError(inst.args[0], _('template file %s: %s') %
                               (self.map[t][1], inst.args[1]))
--- a/mercurial/templates/paper/branches.tmpl	Tue Dec 21 19:47:36 2010 +0900
+++ b/mercurial/templates/paper/branches.tmpl	Wed Dec 22 13:16:00 2010 -0600
@@ -40,7 +40,18 @@
  <th>branch</th>
  <th>node</th>
 </tr>
-{entries%branchentry}
+{entries %
+' <tr class="tagEntry parity{parity}">
+    <td>
+      <a href="{url}shortlog/{node|short}{sessionvars%urlparameter}" class="{status}">
+        {branch|escape}
+      </a>
+    </td>
+    <td class="node">
+      {node|short}
+    </td>
+  </tr>'
+}
 </table>
 </div>
 </div>
--- a/mercurial/templates/paper/shortlogentry.tmpl	Tue Dec 21 19:47:36 2010 +0900
+++ b/mercurial/templates/paper/shortlogentry.tmpl	Wed Dec 22 13:16:00 2010 -0600
@@ -1,5 +1,5 @@
  <tr class="parity{parity}">
-  <td class="age">{date|age}</td>
+  <td class="age">{age(date)}</td>
   <td class="author">{author|person}</td>
-  <td class="description"><a href="{url}rev/{node|short}{sessionvars%urlparameter}">{desc|strip|firstline|escape|nonempty}</a>{inbranch%changelogbranchname}{branches%changelogbranchhead}{tags%changelogtag}</td>
+  <td class="description"><a href="{url}rev/{node|short}{sessionvars%urlparameter}">{desc|strip|firstline|escape|nonempty}</a>{inbranch%changelogbranchname}{branches%changelogbranchhead}{tags % '<span class="tag">{name|escape}</span> '}</td>
  </tr>
--- a/mercurial/url.py	Tue Dec 21 19:47:36 2010 +0900
+++ b/mercurial/url.py	Wed Dec 22 13:16:00 2010 -0600
@@ -546,7 +546,7 @@
                               self.host)
             else:
                 self.ui.warn(_("warning: %s certificate not verified "
-                               "(check web.cacerts config setting)\n") % 
+                               "(check web.cacerts config setting)\n") %
                              self.host)
                 httplib.HTTPSConnection.connect(self)
 
--- a/tests/test-command-template.t	Tue Dec 21 19:47:36 2010 +0900
+++ b/tests/test-command-template.t	Wed Dec 22 13:16:00 2010 -0600
@@ -449,7 +449,7 @@
 
   $ echo 'q = q' > t
   $ hg log --style ./t
-  abort: ./t: no key named 'changeset'
+  abort: "changeset" not in template map
   [255]
 
 Error if include fails:
--- a/tests/test-mq-qrefresh.t	Tue Dec 21 19:47:36 2010 +0900
+++ b/tests/test-mq-qrefresh.t	Wed Dec 22 13:16:00 2010 -0600
@@ -487,74 +487,3 @@
 
   $ cd ..
 
-
-Issue2499: refuse to add .hgsub{,state} to a patch
-
-  $ hg init repo-2499
-  $ cd repo-2499
-  $ hg qinit
-  $ hg qnew -m 0 0.diff
-  $ echo a > a
-  $ hg init sub
-  $ cd sub
-  $ echo b > b
-  $ hg ci -Am 0sub
-  adding b
-  $ cd ..
-
-test when adding
-  $ echo sub = sub > .hgsub
-  $ echo `hg id -i --debug sub` sub > .hgsubstate
-  $ hg add
-  adding .hgsub
-  adding .hgsubstate
-  adding a
-  $ hg qrefresh
-  warning: not adding .hgsub
-  warning: not adding .hgsubstate
-  $ hg qfinish -a
-  $ hg status
-  A .hgsub
-  A .hgsubstate
-  $ hg forget .hgsubstate
-  $ rm .hgsubstate
-
-add subrepo with a real commit
-  $ hg ci -m 1
-  committing subrepository sub
-  $ hg qnew -m 2 2.diff
-
-test when modifying
-  $ echo sub2 = sub2 >> .hgsub
-  $ hg qrefresh
-  warning: not refreshing .hgsub
-  $ echo 0000000000000000000000000000000000000000 sub2 >> .hgsubstate
-  $ hg qrefresh
-  warning: not refreshing .hgsub
-  warning: not refreshing .hgsubstate
-  $ hg revert --no-backup .hgsub .hgsubstate
-
-test when removing
-  $ hg rm .hgsub
-  $ hg rm .hgsubstate
-  $ hg qrefresh
-  warning: not removing .hgsub
-  warning: not removing .hgsubstate
-  $ hg status
-  R .hgsub
-  R .hgsubstate
-  $ hg revert --no-backup .hgsub .hgsubstate
-
-test when deleting
-  $ rm .hgsub .hgsubstate
-  $ hg qrefresh
-  warning: not removing .hgsub
-  warning: not removing .hgsubstate
-  warning: subrepo spec file .hgsub not found
-  $ hg status
-  ! .hgsub
-  ! .hgsubstate
-  $ hg cat -r1 .hgsub > .hgsub
-  $ hg revert --no-backup .hgsubstate
-
-  $ cd ..
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-mq-subrepo.t	Wed Dec 22 13:16:00 2010 -0600
@@ -0,0 +1,342 @@
+  $ echo "[extensions]" >> $HGRCPATH
+  $ echo "mq=" >> $HGRCPATH
+  $ echo "record=" >> $HGRCPATH
+  $ echo "[diff]" >> $HGRCPATH
+  $ echo "nodates=1" >> $HGRCPATH
+
+fn to create new repository w/dirty subrepo, and cd into it
+  $ mkrepo() {
+  >     hg init $1
+  >     cd $1
+  >     hg qinit
+  > }
+
+fn to create dirty subrepo
+  $ mksubrepo() {
+  >     hg init $1
+  >     cd $1
+  >     echo a > a
+  >     hg add
+  >     cd ..
+  > }
+
+  $ testadd() {
+  >     local stdin=`cat`
+  >     mksubrepo sub
+  >     echo sub = sub >> .hgsub
+  >     hg add .hgsub
+  >     echo % abort when adding .hgsub w/dirty subrepo
+  >     hg status -S
+  >     echo '%' $*
+  >     echo "$stdin" | hg $*
+  >     echo [$?]
+  >     hg -R sub ci -m0sub
+  >     echo % update substate when adding .hgsub w/clean updated subrepo
+  >     hg status -S
+  >     echo '%' $*
+  >     echo "$stdin" | hg $*
+  >     hg debugsub
+  > }
+
+  $ testmod() {
+  >     local stdin=`cat`
+  >     mksubrepo sub2
+  >     echo sub2 = sub2 >> .hgsub
+  >     echo % abort when modifying .hgsub w/dirty subrepo
+  >     hg status -S
+  >     echo '%' $*
+  >     echo "$stdin" | hg $*
+  >     echo [$?]
+  >     hg -R sub2 ci -m0sub2
+  >     echo % update substate when modifying .hgsub w/clean updated subrepo
+  >     hg status -S
+  >     echo '%' $*
+  >     echo "$stdin" | hg $*
+  >     hg debugsub
+  > }
+
+  $ testrm1() {
+  >     mksubrepo sub3
+  >     echo sub3 = sub3 >> .hgsub
+  >     hg ci -Aqmsub3
+  >     $EXTRA
+  >     echo b >> sub3/a
+  >     hg rm .hgsub
+  >     echo % update substate when removing .hgsub w/dirty subrepo
+  >     hg status -S
+  >     echo '%' $*
+  >     echo "$stdin" | hg $*
+  >     echo % debugsub should be empty
+  >     hg debugsub
+  > }
+  $ testrm2() {
+  >     mksubrepo sub4
+  >     echo sub4 = sub4 >> .hgsub
+  >     hg ci -Aqmsub4
+  >     $EXTRA
+  >     hg rm .hgsub
+  >     echo % update substate when removing .hgsub w/clean updated subrepo
+  >     hg status -S
+  >     echo '%' $*
+  >     echo "$stdin" | hg $*
+  >     echo % debugsub should be empty
+  >     hg debugsub
+  > }
+
+
+handle subrepos safely on qnew
+
+  $ mkrepo repo-2499-qnew
+  $ testadd qnew -m0 0.diff
+  adding a
+  % abort when adding .hgsub w/dirty subrepo
+  A .hgsub
+  A sub/a
+  % qnew -m0 0.diff
+  abort: uncommitted changes in subrepository sub
+  [255]
+  % update substate when adding .hgsub w/clean updated subrepo
+  A .hgsub
+  % qnew -m0 0.diff
+  committing subrepository sub
+  path sub
+   source   sub
+   revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
+
+  $ testmod qnew -m1 1.diff
+  adding a
+  % abort when modifying .hgsub w/dirty subrepo
+  M .hgsub
+  A sub2/a
+  % qnew -m1 1.diff
+  abort: uncommitted changes in subrepository sub2
+  [255]
+  % update substate when modifying .hgsub w/clean updated subrepo
+  M .hgsub
+  % qnew -m1 1.diff
+  committing subrepository sub2
+  path sub
+   source   sub
+   revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
+  path sub2
+   source   sub2
+   revision 1f94c7611cc6b74f5a17b16121a1170d44776845
+
+  $ hg qpop -qa
+  patch queue now empty
+  $ testrm1 qnew -m2 2.diff
+  adding a
+  % update substate when removing .hgsub w/dirty subrepo
+  M sub3/a
+  R .hgsub
+  % qnew -m2 2.diff
+  % debugsub should be empty
+
+  $ hg qpop -qa
+  patch queue now empty
+  $ testrm2 qnew -m3 3.diff
+  adding a
+  % update substate when removing .hgsub w/clean updated subrepo
+  R .hgsub
+  % qnew -m3 3.diff
+  % debugsub should be empty
+
+  $ cd ..
+
+
+handle subrepos safely on qrefresh
+
+  $ mkrepo repo-2499-qrefresh
+  $ hg qnew -m0 0.diff
+  $ testadd qrefresh
+  adding a
+  % abort when adding .hgsub w/dirty subrepo
+  A .hgsub
+  A sub/a
+  % qrefresh
+  abort: uncommitted changes in subrepository sub
+  [255]
+  % update substate when adding .hgsub w/clean updated subrepo
+  A .hgsub
+  % qrefresh
+  committing subrepository sub
+  path sub
+   source   sub
+   revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
+
+  $ hg qnew -m1 1.diff
+  $ testmod qrefresh
+  adding a
+  % abort when modifying .hgsub w/dirty subrepo
+  M .hgsub
+  A sub2/a
+  % qrefresh
+  abort: uncommitted changes in subrepository sub2
+  [255]
+  % update substate when modifying .hgsub w/clean updated subrepo
+  M .hgsub
+  % qrefresh
+  committing subrepository sub2
+  path sub
+   source   sub
+   revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
+  path sub2
+   source   sub2
+   revision 1f94c7611cc6b74f5a17b16121a1170d44776845
+
+  $ hg qpop -qa
+  patch queue now empty
+  $ EXTRA='hg qnew -m2 2.diff' testrm1 qrefresh
+  adding a
+  % update substate when removing .hgsub w/dirty subrepo
+  M sub3/a
+  R .hgsub
+  % qrefresh
+  % debugsub should be empty
+
+  $ hg qpop -qa
+  patch queue now empty
+  $ EXTRA='hg qnew -m3 3.diff' testrm2 qrefresh
+  adding a
+  % update substate when removing .hgsub w/clean updated subrepo
+  R .hgsub
+  % qrefresh
+  % debugsub should be empty
+
+  $ cd ..
+
+
+handle subrepos safely on qpush/qpop
+
+  $ mkrepo repo-2499-qpush
+  $ mksubrepo sub
+  adding a
+  $ hg -R sub ci -m0sub
+  $ echo sub = sub > .hgsub
+  $ hg add .hgsub
+  $ hg qnew -m0 0.diff
+  committing subrepository sub
+  $ hg debugsub
+  path sub
+   source   sub
+   revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
+
+qpop
+  $ hg qpop
+  popping 0.diff
+  patch queue now empty
+  $ hg status -AS
+  $ hg debugsub
+
+qpush
+  $ hg qpush
+  applying 0.diff
+  now at: 0.diff
+  $ hg status -AS
+  C .hgsub
+  C .hgsubstate
+  C sub/a
+  $ hg debugsub
+  path sub
+   source   sub
+   revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
+
+  $ cd ..
+
+
+handle subrepos safely on qrecord
+
+  $ mkrepo repo-2499-qrecord
+  $ testadd qrecord --config ui.interactive=1 -m0 0.diff <<EOF
+  > y
+  > y
+  > EOF
+  adding a
+  % abort when adding .hgsub w/dirty subrepo
+  A .hgsub
+  A sub/a
+  % qrecord --config ui.interactive=1 -m0 0.diff
+  diff --git a/.hgsub b/.hgsub
+  new file mode 100644
+  examine changes to '.hgsub'? [Ynsfdaq?] 
+  abort: uncommitted changes in subrepository sub
+  [255]
+  % update substate when adding .hgsub w/clean updated subrepo
+  A .hgsub
+  % qrecord --config ui.interactive=1 -m0 0.diff
+  diff --git a/.hgsub b/.hgsub
+  new file mode 100644
+  examine changes to '.hgsub'? [Ynsfdaq?] 
+  committing subrepository sub
+  path sub
+   source   sub
+   revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
+
+  $ testmod qrecord --config ui.interactive=1 -m1 1.diff <<EOF
+  > y
+  > y
+  > EOF
+  adding a
+  % abort when modifying .hgsub w/dirty subrepo
+  M .hgsub
+  A sub2/a
+  % qrecord --config ui.interactive=1 -m1 1.diff
+  diff --git a/.hgsub b/.hgsub
+  1 hunks, 1 lines changed
+  examine changes to '.hgsub'? [Ynsfdaq?] 
+  @@ -1,1 +1,2 @@
+   sub = sub
+  +sub2 = sub2
+  record this change to '.hgsub'? [Ynsfdaq?] 
+  abort: uncommitted changes in subrepository sub2
+  [255]
+  % update substate when modifying .hgsub w/clean updated subrepo
+  M .hgsub
+  % qrecord --config ui.interactive=1 -m1 1.diff
+  diff --git a/.hgsub b/.hgsub
+  1 hunks, 1 lines changed
+  examine changes to '.hgsub'? [Ynsfdaq?] 
+  @@ -1,1 +1,2 @@
+   sub = sub
+  +sub2 = sub2
+  record this change to '.hgsub'? [Ynsfdaq?] 
+  committing subrepository sub2
+  path sub
+   source   sub
+   revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
+  path sub2
+   source   sub2
+   revision 1f94c7611cc6b74f5a17b16121a1170d44776845
+
+  $ hg qpop -qa
+  patch queue now empty
+  $ EXTRA= testrm1 qrecord --config ui.interactive=1 -m2 2.diff <<EOF
+  > y
+  > y
+  > EOF
+  adding a
+  % update substate when removing .hgsub w/dirty subrepo
+  M sub3/a
+  R .hgsub
+  % qrecord --config ui.interactive=1 -m2 2.diff
+  diff --git a/.hgsub b/.hgsub
+  deleted file mode 100644
+  examine changes to '.hgsub'? [Ynsfdaq?] 
+  % debugsub should be empty
+
+  $ hg qpop -qa
+  patch queue now empty
+  $ EXTRA= testrm2 qrecord --config ui.interactive=1 -m3 3.diff <<EOF
+  > y
+  > y
+  > EOF
+  adding a
+  % update substate when removing .hgsub w/clean updated subrepo
+  R .hgsub
+  % qrecord --config ui.interactive=1 -m3 3.diff
+  diff --git a/.hgsub b/.hgsub
+  deleted file mode 100644
+  examine changes to '.hgsub'? [Ynsfdaq?] 
+  % debugsub should be empty
+
+  $ cd ..
--- a/tests/test-subrepo-empty-commit.t	Tue Dec 21 19:47:36 2010 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-  $ hg init
-  $ hg init sub
-  $ echo 'sub = sub' > .hgsub
-  $ hg add .hgsub
-  $ echo c1 > f1
-  $ echo c2 > sub/f2
-  $ hg add -S
-  adding f1
-  adding sub/f2
-  $ hg commit -m0
-  committing subrepository sub
-
-Make .hgsubstate dirty:
-
-  $ echo '0000000000000000000000000000000000000000 sub' > .hgsubstate
-  $ hg diff --nodates
-  diff -r 853ea21970bb .hgsubstate
-  --- a/.hgsubstate
-  +++ b/.hgsubstate
-  @@ -1,1 +1,1 @@
-  -5bbc614a5b06ad7f3bf7c2463d74b005324f34c1 sub
-  +0000000000000000000000000000000000000000 sub
-
-trying to do an empty commit:
-
-  $ hg commit -m1
-  committing subrepository sub
-  nothing changed
-  [1]
-
-an okay update of .hgsubstate
-  $ cd sub
-  $ echo c3 > f2
-  $ hg commit -m "Sub commit"
-  $ cd ..
-  $ hg commit -m "Updated sub"
-  committing subrepository sub
-
-deleting again:
-  $ echo '' > .hgsub
-  $ hg commit -m2
-  $ cat .hgsub
-  
-  $ cat .hgsubstate
-
-an okay commit, but with a dirty .hgsubstate
-  $ echo 'sub = sub' > .hgsub
-  $ hg commit -m3
-  committing subrepository sub
-  $ echo '0000000000000000000000000000000000000000 sub' > .hgsubstate
-  $ hg diff --nodates
-  diff -r 41e1dee3d5d9 .hgsubstate
-  --- a/.hgsubstate
-  +++ b/.hgsubstate
-  @@ -1,1 +1,1 @@
-  -fe0229ee9a0a38b43163c756bb51b94228b118e7 sub
-  +0000000000000000000000000000000000000000 sub
-  $ echo c4 > f3
-  $ hg add f3
-  $ hg status 
-  M .hgsubstate
-  A f3
-  $ hg commit -m4
-  committing subrepository sub
--- a/tests/test-subrepo-git.t	Tue Dec 21 19:47:36 2010 +0900
+++ b/tests/test-subrepo-git.t	Wed Dec 22 13:16:00 2010 -0600
@@ -34,7 +34,7 @@
   $ git clone -q ../gitroot s
   $ hg add .hgsub
   $ hg commit -m 'new git subrepo'
-  committing subrepository $TESTTMP/t/s
+  committing subrepository s
   $ hg debugsub
   path s
    source   ../gitroot
@@ -52,8 +52,10 @@
   $ git checkout -q -b testing origin/testing >/dev/null
 
   $ cd ..
+  $ hg status --subrepos
+  M s/g
   $ hg commit -m 'update git subrepo'
-  committing subrepository $TESTTMP/t/s
+  committing subrepository s
   $ hg debugsub
   path s
    source   ../gitroot
@@ -99,8 +101,10 @@
 
   $ cd ../ta
   $ echo ggg >> s/g
+  $ hg status --subrepos
+  M s/g
   $ hg commit -m ggg
-  committing subrepository $TESTTMP/ta/s
+  committing subrepository s
   $ hg debugsub
   path s
    source   ../gitroot
@@ -119,8 +123,10 @@
   $ git add f
   $ cd ..
 
+  $ hg status --subrepos
+  A s/f
   $ hg commit -m f
-  committing subrepository $TESTTMP/tb/s
+  committing subrepository s
   $ hg debugsub
   path s
    source   ../gitroot
@@ -159,7 +165,11 @@
   gg
   ggg
   $ hg commit -m 'merge'
-  committing subrepository $TESTTMP/ta/s
+  committing subrepository s
+  $ hg status --subrepos --rev 1:5
+  M .hgsubstate
+  M s/g
+  A s/f
   $ hg debugsub
   path s
    source   ../gitroot
@@ -212,7 +222,7 @@
   $ git pull -q >/dev/null 2>/dev/null
   $ cd ..
   $ hg commit -m 'git upstream sync'
-  committing subrepository $TESTTMP/ta/s
+  committing subrepository s
   $ hg debugsub
   path s
    source   ../gitroot
@@ -260,3 +270,37 @@
   g
   gg
   ggg
+
+create nested repo
+
+  $ cd ..
+  $ hg init outer
+  $ cd outer
+  $ echo b>b
+  $ hg add b
+  $ hg commit -m b
+
+  $ hg clone ../t inner
+  updating to branch default
+  cloning subrepo s
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo inner = inner > .hgsub
+  $ hg add .hgsub
+  $ hg commit -m 'nested sub'
+  committing subrepository inner
+
+nested commit
+
+  $ echo ffff >> inner/s/f
+  $ hg status --subrepos
+  M inner/s/f
+  $ hg commit -m nested
+  committing subrepository inner
+  committing subrepository inner/s
+
+nested archive
+
+  $ hg archive --subrepos ../narchive
+  $ ls ../narchive/inner/s
+  f
+  g