[PATCH 1 of 3] dirstate: add begin/endparentchange to dirstate

Durham Goode durham at fb.com
Mon Sep 8 13:42:41 CDT 2014


On 9/8/14, 6:27 PM, "Mads Kiilerich" <mads at kiilerich.com> wrote:

>On 09/05/2014 09:36 PM, Durham Goode wrote:
>> # HG changeset patch
>> # User Durham Goode <durham at fb.com>
>> # Date 1409942069 25200
>> #      Fri Sep 05 11:34:29 2014 -0700
>> # Node ID 8c6d502c92c8ed8dfa79b8fac4c8a2cc9e6c4153
>> # Parent  188b8aa2120b03eead618ba150319074f4e3b42b
>> dirstate: add begin/endparentchange to dirstate
>>
>> It's possible for the dirstate to become incoherent (issue4353) if 
>>there is an
>> exception in the middle of the dirstate parent and entries being 
>>written (like
>> if the user ctrl+c's). This change adds begin/endparentchange which a 
>>future
>> patch will require to be set before changing the dirstate parent.  This 
>>will
>> allow us to prevent writing the dirstate in the event of an exception 
>>while
>> changing the parent.
>>
>> diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
>> --- a/mercurial/dirstate.py
>> +++ b/mercurial/dirstate.py
>> @@ -44,6 +44,17 @@
>>           self._lastnormaltime = 0
>>           self._ui = ui
>>           self._filecache = {}
>> +        self._parentwriters = 0
>> +
>> +    def beginparentchange(self):
>> +        self._parentwriters += 1
>> +
>> +    def endparentchange(self):
>> +        if self._parentwriters > 0:
>> +            self._parentwriters -= 1
>
>The primary use case for this might be parent changes but it seems like 
>it is a general transaction concept that should have a more generic name.

It’s not fully transactional, so I didn’t want to give the wrong 
impression.  For instance, if someone explicitly calls dirstate.write() 
during the middle of this, it will still write to disk.

>
>> +
>> +    def pendingparentchange(self):
>> +        return self._parentwriters > 0
>>   
>>       @propertycache
>>       def _map(self):
>> @@ -300,6 +311,7 @@
>>                   delattr(self, a)
>>           self._lastnormaltime = 0
>>           self._dirty = False
>> +        self._parentwriters = 0
>>   
>>       def copy(self, source, dest):
>>           """Mark dest as a copy of source. Unmark dest if source is 
>>None."""
>> diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
>> --- a/mercurial/localrepo.py
>> +++ b/mercurial/localrepo.py
>> @@ -1108,7 +1108,11 @@
>>               return l
>>   
>>           def unlock():
>> -            self.dirstate.write()
>> +            if self.dirstate.pendingparentchange():
>> +                self.dirstate.invalidate()
>> +            else:
>> +                self.dirstate.write()
>> +
>
>Shouldn't this "don't write with pending parent change" live inside 
>dirstate? Why expose it?

Because certain functionality relies on calling dirstate.write halfway 
through (like rebase). So this allows callers to treat dirstate.write like 
a dirstate.commit type function, then wlock makes a decision on whether to 
auto-commit or not based on if there was an exception or not (I.e. if 
pendingparentchanges == 0).

>
>I also wonder if it would be a good idea to have some built-in 
>verification that the count reaches 0 unless we are in an exceptional 
>case. That would probably require some way of indicating whether it is a 
>normal or exceptional unlock we are doing ... which perhaps could be 
>used by itself instead of this semaphore. Hmm ... I don't know where 
>this is going. I will keep wondering.

I originally wanted more transactional semantics:

Try:
    Dirstate.begin()


    Dirstate.commit()
Finally:
    Dirstate.close()

But that would introduce a lot of duplication (with wlock.begin/release) 
and Matt wanted to still allow the one off dirstate changes (like hg add) 
without the begin/end stuff

>
>/Mads
>





More information about the Mercurial-devel mailing list