cat: support cat with explicit paths in subrepos
authorMatt Harbison <matt_harbison@yahoo.com>
Fri, 14 Mar 2014 21:32:05 -0400
changeset 21041 a2cc3c08c3ac
parent 21040 bdf5ed5246d2
child 21042 32b3331f18eb
cat: support cat with explicit paths in subrepos The cat command with an explicit path into a subrepo is now handled by invoking cat on the file, from that subrepo. The previous behavior was to complain that the file didn't exist in the revision (of the top most repo). Now when the file is actually missing, the revision of the subrepo is named instead (though it is probably desirable to continue naming the top level repo). The documented output formatters %d and %p reflect the path from the top level repo, since the purpose of this is to give the illusion of a unified repository. Support for the undocumented (for cat) formatters %H, %R, %h, %m and %r was added long ago (I tested back as far as 0.5), but unfortunately these will reflect the subrepo node instead of the parent context. The previous implementation was a bit loose with the return value, i.e. it would return 0 if _any_ file requested was cat'd successfully. This maintains that behavior.
mercurial/cmdutil.py
mercurial/commands.py
mercurial/help/subrepos.txt
mercurial/subrepo.py
tests/test-subrepo.t
--- a/mercurial/cmdutil.py	Thu Mar 13 23:45:18 2014 -0400
+++ b/mercurial/cmdutil.py	Fri Mar 14 21:32:05 2014 -0400
@@ -1812,11 +1812,12 @@
     forgot.extend(forget)
     return bad, forgot
 
-def cat(ui, repo, ctx, matcher, **opts):
+def cat(ui, repo, ctx, matcher, prefix, **opts):
     err = 1
 
     def write(path):
-        fp = makefileobj(repo, opts.get('output'), ctx.node(), pathname=path)
+        fp = makefileobj(repo, opts.get('output'), ctx.node(),
+                         pathname=os.path.join(prefix, path))
         data = ctx[path].data()
         if opts.get('decode'):
             data = repo.wwritedata(path, data)
@@ -1833,9 +1834,35 @@
             write(file)
             return 0
 
+    # Don't warn about "missing" files that are really in subrepos
+    bad = matcher.bad
+
+    def badfn(path, msg):
+        for subpath in ctx.substate:
+            if path.startswith(subpath):
+                return
+        bad(path, msg)
+
+    matcher.bad = badfn
+
     for abs in ctx.walk(matcher):
         write(abs)
         err = 0
+
+    matcher.bad = bad
+
+    for subpath in sorted(ctx.substate):
+        sub = ctx.sub(subpath)
+        try:
+            submatch = matchmod.narrowmatcher(subpath, matcher)
+
+            if not sub.cat(ui, submatch, os.path.join(prefix, sub._path),
+                           **opts):
+                err = 0
+        except error.RepoLookupError:
+            ui.status(_("skipping missing subrepository: %s\n")
+                           % os.path.join(prefix, subpath))
+
     return err
 
 def duplicatecopies(repo, rev, fromrev):
--- a/mercurial/commands.py	Thu Mar 13 23:45:18 2014 -0400
+++ b/mercurial/commands.py	Fri Mar 14 21:32:05 2014 -0400
@@ -1174,7 +1174,7 @@
     ctx = scmutil.revsingle(repo, opts.get('rev'))
     m = scmutil.match(ctx, (file1,) + pats, opts)
 
-    return cmdutil.cat(ui, repo, ctx, m, **opts)
+    return cmdutil.cat(ui, repo, ctx, m, '', **opts)
 
 @command('^clone',
     [('U', 'noupdate', None,
--- a/mercurial/help/subrepos.txt	Thu Mar 13 23:45:18 2014 -0400
+++ b/mercurial/help/subrepos.txt	Fri Mar 14 21:32:05 2014 -0400
@@ -84,6 +84,9 @@
 :archive: archive does not recurse in subrepositories unless
     -S/--subrepos is specified.
 
+:cat: cat currently only handles exact file matches in subrepos.
+    Git and Subversion subrepositories are currently ignored.
+
 :commit: commit creates a consistent snapshot of the state of the
     entire project and its subrepositories. If any subrepositories
     have been modified, Mercurial will abort.  Mercurial can be made
--- a/mercurial/subrepo.py	Thu Mar 13 23:45:18 2014 -0400
+++ b/mercurial/subrepo.py	Fri Mar 14 21:32:05 2014 -0400
@@ -439,6 +439,9 @@
     def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
         return []
 
+    def cat(self, ui, match, prefix, **opts):
+        return 1
+
     def status(self, rev2, **opts):
         return [], [], [], [], [], [], []
 
@@ -609,6 +612,12 @@
                            os.path.join(prefix, self._path), explicitonly)
 
     @annotatesubrepoerror
+    def cat(self, ui, match, prefix, **opts):
+        rev = self._state[1]
+        ctx = self._repo[rev]
+        return cmdutil.cat(ui, self._repo, ctx, match, prefix, **opts)
+
+    @annotatesubrepoerror
     def status(self, rev2, **opts):
         try:
             rev1 = self._state[1]
--- a/tests/test-subrepo.t	Thu Mar 13 23:45:18 2014 -0400
+++ b/tests/test-subrepo.t	Fri Mar 14 21:32:05 2014 -0400
@@ -792,6 +792,19 @@
   $ echo test >> sub/repo/foo
   $ hg ci -mtest
   committing subrepository sub/repo (glob)
+  $ hg cat sub/repo/foo
+  test
+  test
+  $ mkdir -p tmp/sub/repo
+  $ hg cat -r 0 --output tmp/%p_p sub/repo/foo
+  $ cat tmp/sub/repo/foo_p
+  test
+  $ mv sub/repo sub_
+  $ hg cat sub/repo/baz
+  skipping missing subrepository: sub/repo
+  [1]
+  $ rm -rf sub/repo
+  $ mv sub_ sub/repo
   $ cd ..
 
 Create repo without default path, pull top repo, and see what happens on update