mercurial/HGChangesetId.st
author Jan Vrany <jan.vrany@fit.cvut.cz>
Thu, 23 Aug 2018 10:37:24 +0100
changeset 858 dcfd093c3257
parent 807 ef00a1303f73
child 919 db2ebeb3fa00
permissions -rw-r--r--
Fix bad error messages in `HGChangesetId` parser The error was signalled correctly but its message was wrong / misleading (copy-paste error)

"
stx:libscm - a new source code management library for Smalltalk/X
Copyright (C) 2012-2015 Jan Vrany

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License. 

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
"
"{ Package: 'stx:libscm/mercurial' }"

"{ NameSpace: Smalltalk }"

ByteArray variableByteSubclass:#HGChangesetId
	instanceVariableNames:'revno'
	classVariableNames:'NullId'
	poolDictionaries:''
	category:'SCM-Mercurial-Core'
!

!HGChangesetId class methodsFor:'documentation'!

copyright
"
stx:libscm - a new source code management library for Smalltalk/X
Copyright (C) 2012-2015 Jan Vrany

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License. 

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
"
!

documentation
"
    Represent a changeset id in Mercurial repository.

    !!!!!! WARNING !!!!!!

    Due to a stupid design here, DO NOT USE put it into hashed collections!!
    I have to change it later...

    [author:]
        Jan Vrany <jan.vrany@fit.cvut.cz>

    [instance variables:]

    [class variables:]

    [see also:]

"
! !

!HGChangesetId class methodsFor:'instance creation'!

decodeFromLiteralArray:anArray
    anArray size == 2 ifTrue:[
        "/ HGChangesetId 'hash-string'
        ^ self readFrom:(anArray at:2).
    ].

    ^ super decodeFromLiteralArray:anArray

    "
    #(HGChangesetId 'f6f68d32de73') decodeAsLiteralArray literalArrayEncoding
    "

    "Created: / 07-09-2015 / 17:43:33 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

fromBytes: aByteArrayOrString

    | sz |

    sz := aByteArrayOrString size.
    (sz ~~ 20 and:[sz ~~ 6]) ifTrue:[
        self error:'Node ID has either 20 or 6 bytes (short form)'.
        ^nil.
    ].
    ^(self new: sz) replaceBytesFrom: 1 to: sz with: aByteArrayOrString startingAt: 1

    "Created: / 25-09-2012 / 21:00:06 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 13-11-2012 / 16:47:40 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

fromString: aString
    ^self readFrom: aString readStream

    "Created: / 10-09-2012 / 10:49:43 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 13-11-2012 / 16:49:39 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

new
    ^self new: 20

    "Created: / 10-09-2012 / 10:42:39 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 19-10-2012 / 15:51:45 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

new: size
    (size ~~ 0 and:[size ~~ 20 and:[size ~~ 6]]) ifTrue:[
        self error: 'Size of HGNodeId must be either 20 bytes or 6 bytes (short form) or 0 (revno only)'.
        ^nil.
    ].
    ^super new: size

    "Created: / 10-09-2012 / 10:44:00 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 16-11-2012 / 21:24:52 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

readFrom: aStringOrStream 
    ^self readFrom: aStringOrStream onError:[:msg|self error:msg].

    "Created: / 13-11-2012 / 16:56:58 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

readFrom: aStringOrStream onError: aBlock
    "Parses node id from stream and returns it. Support both,
     short and full node ids"

    | stream c c1 c2 sign revno hash hashPos charPos short |

    hash := ByteArray new: 20.
    hashPos := 1.
    charPos := 0.
    short := true.
    revno := 0.

    stream := aStringOrStream readStream.
    stream peek == $- ifTrue:[        
        stream next.
        sign := -1.
    ] ifFalse:[
        sign := 1.
    ].

    "/ Read revno...

    [ stream atEnd not and:[(c := stream peek) isDigit] ] whileTrue:[
        "/ Update revno
        revno := (revno * 10) + c digitValue.
        "/ Update hash for case we're reading hash instead..."
        c1 isNil ifTrue:[
            c1 := c.
        ] ifFalse:[
            c2 := c.
            hash at:hashPos put: (c1 digitValue << 4) | c2 digitValue.
            hashPos := hashPos + 1.
            c1 := c2 := nil.
        ].
        stream next.
        charPos := charPos + 1.
    ].
    revno := revno * sign.
    stream atEnd ifTrue:[
        "/ We might have read revno or a short hash that by chance
        "/ consist of only digits and not leading with 0. In this case
        "/ we actually cannot distinguish. 
        "/ In case we have read less than 12 characters (size of a short id), 
        "/ treat it as revno. A conservative guess...
        charPos < 12 ifTrue:[ 
            ^(HGChangesetId new: 0)
                revno: revno;
                yourself
        ]
    ].
    (stream atEnd not and:[stream peek isSeparator]) ifTrue:[
        ^(HGChangesetId new: 0)
            revno: revno;
            yourself
    ].
    stream peek == $: ifTrue:[
        "/OK. we have read revno"
        hashPos := 1.
        c1 := c2 := nil.
        stream next. "/eat :
    ] ifFalse:[
        revno := nil.
    ].
    "/ Read hash
    hashPos <= 6 ifTrue:[
        hashPos to: 6 do:[:i|
            stream atEnd ifTrue:[
                ^ aBlock valueWithOptionalArgument:'Unexpected end of stream, hex digit expected'.
            ].
            c1 isNil ifTrue:[
                c1 := stream peek.
                c1 isHexDigit ifFalse:[
                    ^ aBlock valueWithOptionalArgument:'Hex digit ([0-9a-z]) expected but ', c1 , ' found'.
                ].
                stream next.
            ].
            stream atEnd ifTrue:[
                ^ aBlock valueWithOptionalArgument:'Unexpected end of stream, hex digit expected'.
            ].
            c2 := stream peek.
            c2 isHexDigit ifFalse:[
                ^ aBlock valueWithOptionalArgument:'Hex digit ([0-9a-z]) expected but ', c2 , ' found'.
            ].
            hash at:i put: (c1 digitValue << 4) | c2 digitValue.
            hashPos := i + 1.
            c1 := c2 := nil.
            stream next.
        ].
    ].
    (stream atEnd not and:[stream peek isHexDigit]) ifTrue:[
        "/OK, full 40-char node id
        short := false.
        hashPos to: 20 do:[:i|
            stream atEnd ifTrue:[
                ^ aBlock valueWithOptionalArgument:'Unexpected end of stream, hex digit expected'.
            ].
                        c1 := stream peek.
            c1 isHexDigit ifFalse:[
                ^ aBlock valueWithOptionalArgument:'Hex digit ([0-9a-z]) expected but ', c1 , ' found'.
            ].
            stream next.
            stream atEnd ifTrue:[
                ^ aBlock valueWithOptionalArgument:'Unexpected end of stream, hex digit expected'.
            ].
            c2 := stream peek.
            c2 isHexDigit ifFalse:[
                ^ aBlock valueWithOptionalArgument:'Hex digit ([0-9a-z]) expected but ', c2 , ' found'.
            ].
            hash at:i put: (c1 digitValue << 4) + c2 digitValue.
            stream next.
        ].
    ].
    (revno == -1) ifTrue:[
        (hash = #[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] 
            or:[hash = #[0 0 0 0 0 0]]) ifTrue:[
            ^self null
        ].
    ].
    short ifTrue:[
        ^(HGChangesetId new: 6)
            revno: revno;
            replaceBytesFrom: 1 to: 6 with: hash startingAt: 1;
            yourself
    ] ifFalse:[
        ^(HGChangesetId fromBytes: hash) revno: revno.
    ]

    "
    HGChangesetId fromString:'4:6f88e1f44d9eb86e0b56ca15e30e5d786acd83c7'
    HGChangesetId readFrom: '96DB65258808720D8D5EA6CB7A6A4D4F4E467325''!!' readStream

    Bad ones:

    HGChangesetId fromString:'4:6f88e1f44d9eb86e0b56ca15e30e5d786acd' 
    HGChangesetId fromString:'4:6f88Z1f44d9eb86e0b56ca15e30e5d786acd83c7' 

    "

    "Created: / 13-11-2012 / 16:49:19 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 23-08-2018 / 10:30:25 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!HGChangesetId class methodsFor:'accessing'!

null
    NullId isNil ifTrue:[
        NullId := self new.
        NullId revno: -1
    ].
    ^NullId

    "
        HGChangesetId null
    "

    "Created: / 19-10-2012 / 15:51:25 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified (comment): / 30-11-2012 / 22:00:52 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!HGChangesetId methodsFor:'accessing'!

revno
    ^ revno

    "Created: / 13-11-2012 / 09:52:02 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 30-11-2012 / 22:05:47 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

revno:anInteger
    revno := anInteger.
! !

!HGChangesetId methodsFor:'comparing'!

= anotherId

    self class == anotherId class ifFalse:[ ^ false].
    (self size == 0 or:[anotherId size == 0]) ifTrue:[
        ^(self revno == anotherId revno and:[ self revno ~~ -2 ])
    ].
    self size == anotherId size ifTrue:[
        ^super = anotherId
    ].
    "One of them must be short, another long"
    1 to: 6 do:[:i|
        (self at:i) ~~ (anotherId at:i) ifTrue:[ ^ false ].
    ].
    ^true

    "Created: / 13-11-2012 / 17:37:48 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 16-11-2012 / 21:39:17 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

hash
    ^self size > 0 
        ifTrue:[self computeXorHashFrom:1 to:6]
        ifFalse:[revno hash].

    "Created: / 16-11-2012 / 21:45:16 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!HGChangesetId methodsFor:'converting'!

asHGChangesetId
    ^ self

    "Created: / 16-11-2012 / 21:22:20 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

asHGRevset
    ^ self asString asHGRevset

    "Created: / 07-02-2014 / 13:03:44 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

asString
    self hasRevnoOnly ifTrue:[ 
        ^ revno printString
    ] ifFalse:[ 
        ^ self hexPrintString asLowercase
    ].

    "Created: / 17-11-2012 / 01:08:05 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 30-10-2017 / 20:48:17 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

literalArrayEncoding
    ^ Array 
        with:(self class name)
        with:(self printStringWithoutNumber).
    "
    #(HGChangesetId 'f6f68d32de73') decodeAsLiteralArray literalArrayEncoding
    "

    "Created: / 07-09-2015 / 17:43:55 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!HGChangesetId methodsFor:'printing & storing'!

displayOn:aStream

    ^self printOn: aStream

    "Created: / 13-11-2012 / 09:55:00 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 30-11-2012 / 22:01:12 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

printOn:aStream
    "append a printed representation if the receiver to the argument, aStream"

    | rn |

    rn := self revno.
    rn notNil ifTrue:[
        rn printOn: aStream.
        self isEmpty ifTrue:[ ^ self ].
        aStream nextPut: $:.
    ].

    aStream nextPutAll: (self copyTo: 6) hexPrintString asLowercase

    "Modified: / 01-12-2012 / 00:55:49 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

printStringWithoutNumber
    ^(self copyTo: 6) hexPrintString asLowercase

    "Created: / 27-03-2013 / 11:52:25 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!HGChangesetId methodsFor:'queries'!

hasHashOnly
    ^revno isNil

    "Created: / 16-11-2012 / 21:54:38 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

hasRevno
    ^revno notNil

    "Created: / 22-01-2013 / 22:30:05 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

hasRevnoOnly
    ^self size == 0

    "Created: / 16-11-2012 / 21:54:18 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

isNull
    ^(revno isNil or:[revno == -1]) 
        and:[self allSatisfy:[:e|e == 0]]

    "Created: / 01-04-2013 / 13:03:14 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!HGChangesetId methodsFor:'testing'!

isFull
    "Return true, if given changeset id is shortened"

    ^self size = 20

    "Created: / 22-01-2013 / 22:28:59 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

isShort
    "Return true, if given changeset id is shortened"

    ^self size < 20

    "Created: / 16-12-2012 / 00:54:19 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!HGChangesetId class methodsFor:'documentation'!

version_HG

    ^ '$Changeset: <not expanded> $'
!

version_SVN
    ^ 'Id::                                                                                                                        '
! !