[PATCH 2 of 3 RFC] flags: introduce a command for manipulating file flags (issue2020)

Matt Harbison mharbison72 at gmail.com
Wed Jul 6 22:35:15 EDT 2016


On Wed, 06 Jul 2016 21:46:48 -0400, timeless <timeless at gmail.com> wrote:

> so, we could do:
>
> hg chmod -flag +executable paths
>
> It could also be done using a command other than `chmod` once you
> switch to using `-flag` (or similar) as the flag.
>
> In this model, you could also support octal as the parser would come
> from `-flag` instead of having to explain to the command parser that
> you have arbitrary arguments that are either files or attributes.

Interesting idea.  I wish it were less verbose, but I don't have a better  
idea.

The octal idea is interesting too, but since Mercurial really only tracks  
a single x-bit and then turns them on for each r-bit (see  
posix.setflags()), there will be a bunch of combinations that make little  
or no sense.  Should they be rejected?  Just the u+x bit tested?

> On Sun, Jul 3, 2016 at 6:00 PM, Matt Harbison <mharbison72 at gmail.com>  
> wrote:
>> # HG changeset patch
>> # User Matt Harbison <matt_harbison at yahoo.com>
>> # Date 1467573637 14400
>> #      Sun Jul 03 15:20:37 2016 -0400
>> # Node ID e6da62c6d4b29f29a1cad36e72d98d9208186e51
>> # Parent  e9fce4275ce6b26b941f47044744015e90367e7b
>> flags: introduce a command for manipulating file flags (issue2020)
>>
>> The need here is for tweaking the executable bit on Windows, but rather  
>> than
>> making this a platform specific command, Unix is supported with a simple
>> `chmod`, allowing the existing flag handling to continue unchanged.  I  
>> didn't
>> think a debug command was appropriate, because we probably don't want  
>> most users
>> to know that is a thing.
>>
>> I suppose we could move the chmod into the flagstate class, but there's  
>> also the
>> issue of needing to setup the transaction here, so I'm not sure that it  
>> is worth
>> it.
>>
>> In the workingctx class, the flagstate file is consulted first.  Only  
>> if there
>> is not an entry for a file does it fall back to the previous manifest  
>> based
>> code to fabricate the flags.  This is simpler than only keeping an  
>> entry if it
>> is the opposite of the manifest.  The file is discarded for both `hg  
>> update -C`
>> and `hg commit`.  This ignores revert for now, because there are some  
>> issues
>> with that.  This can also probably be used by record, since
>> test-commit-interactive-record.t is basically broken on Windows without  
>> it.
>>
>> I hate this UI.  I was originally thinking `hg chmod [+|-]x` without  
>> thinking
>> that the args need to start with '-', so it ends up being '-x' sets the  
>> bit
>> whereas `chmod -x` clears it.  Yuck.  And the name isn't very intuitive  
>> for a
>> user.  So bikeshed away, I'm sure someone has a better idea.  As  
>> mentioned, I
>> was also trying to not block a '-l' and '-f' or some such thing to  
>> manipulate
>> symlinks in the future, though I don't currently have an interest in  
>> that.
>>
>> I'll integrate the tests with something existing once this starts  
>> winding down.
>>
>> diff --git a/mercurial/commands.py b/mercurial/commands.py
>> --- a/mercurial/commands.py
>> +++ b/mercurial/commands.py
>> @@ -3970,6 +3970,50 @@
>>
>>      return ret
>>
>> + at command('^flags',
>> +    [('n', 'normal', None, _('clear the executable bit')),
>> +    ('x', 'executable', None, _('set the executable bit'))
>> +    ] + walkopts,
>> +    _('FILE...'),
>> +    inferrepo=True)
>> +def flags(ui, repo, *pats, **opts):
>> +    """modify the flags on the given files
>> +
>> +    Set or clear the executable bit for the named files.  On a  
>> filesystem that
>> +    tracks the executable bit, this is equivalent to ``chmod +x FILE``.
>> +
>> +    Returns 0 on success.
>> +    """
>> +    isexec = opts.get('executable')
>> +    isnorm = opts.get('normal')
>> +
>> +    if (isexec and isnorm) or (not isexec and not isnorm):
>> +        raise error.Abort(_("exactly one of -x and -n must be  
>> specified"))
>> +
>> +    wctx = repo[None]
>> +    m = scmutil.match(wctx, pats, opts)
>> +
>> +    if util.checkexec(repo.root):
>> +        for f in m.files():
>> +            repo.wvfs.chmod(f, isexec and 0o755 or 0o644 )
>> +    else:
>> +        wlock = lock = tr = None
>> +        try:
>> +            wlock = repo.wlock()
>> +            lock = repo.lock()
>> +            fs = wctx.flagstate
>> +
>> +            tr = repo.transaction('flags')
>> +            for f in m.files():
>> +                fs[f] = isexec and 'x' or ''
>> +
>> +            fs.write(tr)
>> +            tr.close()
>> +        finally:
>> +            lockmod.release(wlock, lock, tr)
>> +
>> +    return 0
>> +
>>  @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
>>  def forget(ui, repo, *pats, **opts):
>>      """forget the specified files on the next commit
>> diff --git a/mercurial/context.py b/mercurial/context.py
>> --- a/mercurial/context.py
>> +++ b/mercurial/context.py
>> @@ -1188,6 +1188,7 @@
>>          # Create a fallback function for getting file flags when the
>>          # filesystem doesn't support them
>>
>> +        fs = scmutil.flagstate(self._repo)
>>          copiesget = self._repo.dirstate.copies().get
>>          parents = self.parents()
>>          if len(parents) < 2:
>> @@ -1195,6 +1196,8 @@
>>              man = parents[0].manifest()
>>              def func(f):
>>                  f = copiesget(f, f)
>> +                if f in fs:
>> +                    return fs[f]
>>                  return man.flags(f)
>>          else:
>>              # merges are tricky: we try to reconstruct the unstored
>> @@ -1205,6 +1208,8 @@
>>
>>              def func(f):
>>                  f = copiesget(f, f) # may be wrong for merges with  
>> copies
>> +                if f in fs:
>> +                    return fs[f]
>>                  fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
>>                  if fl1 == fl2:
>>                      return fl1
>> @@ -1408,6 +1413,10 @@
>>              p = p[:-1]
>>          return [changectx(self._repo, x) for x in p]
>>
>> +    @propertycache
>> +    def flagstate(self):
>> +        return scmutil.flagstate(self._repo)
>> +
>>      def filectx(self, path, filelog=None):
>>          """get a file context from the working directory"""
>>          return workingfilectx(self._repo, path, workingctx=self,
>> diff --git a/mercurial/hg.py b/mercurial/hg.py
>> --- a/mercurial/hg.py
>> +++ b/mercurial/hg.py
>> @@ -691,6 +691,7 @@
>>      """forcibly switch the working directory to node, clobbering  
>> changes"""
>>      stats = updaterepo(repo, node, True)
>>      util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
>> +    repo[None].flagstate.discard()
>>      if show_stats:
>>          _showstats(repo, stats, quietempty)
>>      return stats[3] > 0
>> diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
>> --- a/mercurial/localrepo.py
>> +++ b/mercurial/localrepo.py
>> @@ -1638,6 +1638,7 @@
>>              # update bookmarks, dirstate and mergestate
>>              bookmarks.update(self, [p1, p2], ret)
>>              cctx.markcommitted(ret)
>> +            wctx.flagstate.discard()
>>              ms.reset()
>>              tr.close()
>>
>> diff --git a/tests/test-flags2.t b/tests/test-flags2.t
>> new file mode 100644
>> --- /dev/null
>> +++ b/tests/test-flags2.t
>> @@ -0,0 +1,66 @@
>> +  $ hg init foo
>> +  $ cd foo
>> +  $ echo noexec > noexec.txt
>> +  $ echo noexec > noexec2.txt
>> +  $ hg ci -Aqm "no exec"
>> +  $ hg files -v
>> +           7   noexec.txt
>> +           7   noexec2.txt
>> +
>> +
>> +chmod +x, test and commit
>> +
>> +  $ hg flags -x noexec.txt
>> +  $ hg files -v
>> +           7 x noexec.txt
>> +           7   noexec2.txt
>> +
>> +  $ hg diff --git
>> +  diff --git a/noexec.txt b/noexec.txt
>> +  old mode 100644
>> +  new mode 100755
>> +
>> +  $ hg ci -m "set exec"
>> +  $ hg files -v
>> +           7 x noexec.txt
>> +           7   noexec2.txt
>> +  $ hg diff --git
>> +  $ hg diff -r '.^' --git
>> +  diff --git a/noexec.txt b/noexec.txt
>> +  old mode 100644
>> +  new mode 100755
>> +  $ hg manifest --debug
>> +  5bb662b3917ab12c167093eb2fa379a1b63142c3 755 * noexec.txt
>> +  5bb662b3917ab12c167093eb2fa379a1b63142c3 644   noexec2.txt
>> +
>> +
>> +chmod -x, test and commit
>> +
>> +#  $ sleep 2
>> +#  $ touch noexec.txt
>> +
>> +  $ hg flags -n noexec.txt
>> +  $ hg files -v
>> +           7   noexec.txt
>> +           7   noexec2.txt
>> +  $ hg diff --git
>> +  diff --git a/noexec.txt b/noexec.txt
>> +  old mode 100755
>> +  new mode 100644
>> +  $ hg ci -m "clear exec"
>> +  $ hg files -v
>> +           7   noexec.txt
>> +           7   noexec2.txt
>> +
>> +  $ hg flags -x noexec.txt
>> +  $ hg files -v
>> +           7 x noexec.txt
>> +           7   noexec2.txt
>> +
>> +Update -C to nuke it
>> +  $ hg update -C
>> +  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
>> +  $ hg files -v
>> +           7   noexec.txt
>> +           7   noexec2.txt
>> +
>> _______________________________________________
>> Mercurial-devel mailing list
>> Mercurial-devel at mercurial-scm.org
>> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


More information about the Mercurial-devel mailing list