Patchwork [2,of,3] cat: support cat with explicit paths in subrepos

login
register
mail settings
Submitter Matt Harbison
Date March 22, 2014, 5:08 p.m.
Message ID <35686f3835c908e3e681.1395508097@Envy>
Download mbox | patch
Permalink /patch/4038/
State Accepted
Commit a2cc3c08c3ac39f7d2481760fe96522419dced6d
Headers show

Comments

Matt Harbison - March 22, 2014, 5:08 p.m.
# HG changeset patch
# User Matt Harbison <matt_harbison@yahoo.com>
# Date 1394847125 14400
# Node ID 35686f3835c908e3e681a0abd0c805476ccdfddc
# Parent  9c5c56f97c4df13462ad4e5684ead8b24466bcd1
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.
Matt Harbison - March 24, 2014, 2:34 a.m.
On Sat, 22 Mar 2014 13:08:17 -0400, Matt Harbison wrote:

> # HG changeset patch # User Matt Harbison <matt_harbison@yahoo.com>
> # Date 1394847125 14400 # Node ID
> 35686f3835c908e3e681a0abd0c805476ccdfddc # Parent 
> 9c5c56f97c4df13462ad4e5684ead8b24466bcd1 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.
> 
> diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
> --- a/mercurial/cmdutil.py
> +++ b/mercurial/cmdutil.py
> @@ -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):

Side note: this catches RepoLookupError because if it catches LookupError like
other places that recurse into subrepos, the test below where the subrepo isn't
present crashes.  I even rollbacked back the subrepo thinking that a LookupError
would be generated when the revision that parent wants isn't present- but it
still caught a RepoLookupError.  So the other places catching LookupError (like
cmdutil.add(), cmdutil.forget(), etc) are just wrong?

For the case of a parent looking for a subrepo revision that isn't present, it
would probably be useful to indicate that a revision in the subrepo is missing
(and what it is), not the whole repo itself.


> diff --git a/tests/test-subrepo.t b/tests/test-subrepo.t
> --- a/tests/test-subrepo.t
> +++ b/tests/test-subrepo.t
> @@ -755,6 +755,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
Siddharth Agarwal - April 15, 2014, 5:14 p.m.
On 03/22/2014 10:08 AM, Matt Harbison wrote:
> # HG changeset patch
> # User Matt Harbison <matt_harbison@yahoo.com>
> # Date 1394847125 14400
> # Node ID 35686f3835c908e3e681a0abd0c805476ccdfddc
> # Parent  9c5c56f97c4df13462ad4e5684ead8b24466bcd1
> cat: support cat with explicit paths in subrepos

The first one and this one are queued for default, thanks.


>
> 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.
>
> diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
> --- a/mercurial/cmdutil.py
> +++ b/mercurial/cmdutil.py
> @@ -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):
> diff --git a/mercurial/commands.py b/mercurial/commands.py
> --- a/mercurial/commands.py
> +++ b/mercurial/commands.py
> @@ -1171,7 +1171,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,
> diff --git a/mercurial/help/subrepos.txt b/mercurial/help/subrepos.txt
> --- a/mercurial/help/subrepos.txt
> +++ b/mercurial/help/subrepos.txt
> @@ -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
> diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py
> --- a/mercurial/subrepo.py
> +++ b/mercurial/subrepo.py
> @@ -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]
> diff --git a/tests/test-subrepo.t b/tests/test-subrepo.t
> --- a/tests/test-subrepo.t
> +++ b/tests/test-subrepo.t
> @@ -755,6 +755,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
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@selenic.com
> http://selenic.com/mailman/listinfo/mercurial-devel

Patch

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -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):
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -1171,7 +1171,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,
diff --git a/mercurial/help/subrepos.txt b/mercurial/help/subrepos.txt
--- a/mercurial/help/subrepos.txt
+++ b/mercurial/help/subrepos.txt
@@ -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
diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py
--- a/mercurial/subrepo.py
+++ b/mercurial/subrepo.py
@@ -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]
diff --git a/tests/test-subrepo.t b/tests/test-subrepo.t
--- a/tests/test-subrepo.t
+++ b/tests/test-subrepo.t
@@ -755,6 +755,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