Issue 256: fix parsing branch list when branch name(s) contains spaces
authorJan Vrany <jan.vrany@fit.cvut.cz>
Thu, 07 Mar 2019 12:22:12 +0000
changeset 867 7527dc6bc38e
parent 866 8a885a75daa9
child 870 bc22f9a991e8
child 889 ecb49bf29646
Issue 256: fix parsing branch list when branch name(s) contains spaces Since branch names may contain space, we must use custom template to get a a robust machine readable output. However, this is supported since Mercurial 3.5 (including) so for older Mercurials, we still use old code as a courtesy to users who may have old Mercurials. Las lomng as branch has no spaces, it should just work.
mercurial/HGCommand.st
mercurial/HGCommandParser.st
mercurial/HGCommandParserTests.st
mercurial/HGTests.st
--- a/mercurial/HGCommand.st	Tue Jan 08 09:35:11 2019 +0000
+++ b/mercurial/HGCommand.st	Thu Mar 07 12:22:12 2019 +0000
@@ -27,6 +27,7 @@
 		HGVersionIsGreaterOrEqualThan_2_4
 		HGVersionIsGreaterOrEqualThan_2_5
 		HGVersionIsGreaterOrEqualThan_3_3
+		HGVersionIsGreaterOrEqualThan_3_5
 		HGVersionIsGreaterOrEqualThan_4_1
 		HGVersionIsGreaterOrEqualThan_4_8'
 	poolDictionaries:'HGDebugFlags'
@@ -294,8 +295,8 @@
     HGCommandString := command notNil ifTrue:[ command asString ] ifFalse:[ nil ].
     HGExecutable := HGExecutableArguments := HGVersion := nil.
     HGVersionIsGreaterOrEqualThan_2_4 := HGVersionIsGreaterOrEqualThan_2_5 := 
-        HGVersionIsGreaterOrEqualThan_3_3 := HGVersionIsGreaterOrEqualThan_4_1 := 
-        HGVersionIsGreaterOrEqualThan_4_8 := nil.
+        HGVersionIsGreaterOrEqualThan_3_3 := HGVersionIsGreaterOrEqualThan_3_5 := 
+        HGVersionIsGreaterOrEqualThan_4_1 := HGVersionIsGreaterOrEqualThan_4_8 := nil.
 
 
     "
@@ -306,7 +307,7 @@
     "
 
     "Created: / 19-11-2012 / 21:49:02 / Jan Vrany <jan.vrany@fit.cvut.cz>"
-    "Modified: / 10-01-2019 / 14:43:02 / Jan Vrany <jan.vrany@fit.cvut.cz>"
+    "Modified: / 09-03-2019 / 08:13:56 / Jan Vrany <jan.vrany@fit.cvut.cz>"
 !
 
 hgCommandValidate: command
@@ -457,6 +458,16 @@
     "Modified: / 25-03-2016 / 17:23:13 / Jan Vrany <jan.vrany@fit.cvut.cz>"
 !
 
+hgVersionIsGreaterOrEqualThan_3_5
+    "/ 3.5 released 2015-07-31
+    HGVersionIsGreaterOrEqualThan_3_5 isNil ifTrue:[
+        HGVersionIsGreaterOrEqualThan_3_5 := self hgVersionIsGreaterOrEqualThan:#( 3 5 ).
+    ].
+    ^ HGVersionIsGreaterOrEqualThan_3_5
+
+    "Created: / 09-03-2019 / 08:13:28 / Jan Vrany <jan.vrany@fit.cvut.cz>"
+!
+
 hgVersionIsGreaterOrEqualThan_4_1
     HGVersionIsGreaterOrEqualThan_4_1 isNil ifTrue:[
         HGVersionIsGreaterOrEqualThan_4_1 := self hgVersionIsGreaterOrEqualThan:#( 4 1 ).
@@ -1438,7 +1449,15 @@
         stream nextPut:'--closed'
     ].
 
+    self class hgVersionIsGreaterOrEqualThan_3_5 ifTrue:[
+        stream
+            nextPut:'--template';
+            nextPut:HGCommandParser templateBranches.
+    ]
+
     "Created: / 27-11-2012 / 19:54:08 / Jan Vrany <jan.vrany@fit.cvut.cz>"
+    "Modified: / 09-03-2019 / 08:16:17 / Jan Vrany <jan.vrany@fit.cvut.cz>"
+    "Modified (format): / 12-03-2019 / 11:58:40 / Jan Vrany <jan.vrany@fit.cvut.cz>"
 !
 
 parseError:stream
@@ -1451,9 +1470,14 @@
     "Parses output of 'hg' command, i.e. commit, log, update, checkout,
      etc."
 
-    ^ (self parserOn:stream) parseCommandBranches
+     self class hgVersionIsGreaterOrEqualThan_3_5 ifTrue:[ 
+        ^ (self parserOn:stream) parseCommandBranches
+     ] ifFalse:[ 
+        ^ (self parserOn:stream) parseCommandBranchesOld
+    ]
 
     "Created: / 27-11-2012 / 19:55:00 / Jan Vrany <jan.vrany@fit.cvut.cz>"
+    "Modified: / 09-03-2019 / 08:18:55 / Jan Vrany <jan.vrany@fit.cvut.cz>"
 ! !
 
 !HGCommand::branches methodsFor:'queries'!
--- a/mercurial/HGCommandParser.st	Tue Jan 08 09:35:11 2019 +0000
+++ b/mercurial/HGCommandParser.st	Thu Mar 07 12:22:12 2019 +0000
@@ -76,6 +76,12 @@
 
 !HGCommandParser class methodsFor:'templates'!
 
+templateBranches
+    ^ '{branch}\0{rev}:{node}\0{ifeq(closed,''True'',''C'',ifeq(active,''False'',''I'',''A''))}\n'
+
+    "Created: / 07-03-2019 / 11:02:46 / Jan Vrany <jan.vrany@fit.cvut.cz>"
+!
+
 templateHeads
     ^'{rev}:{node}\n'
 
@@ -229,63 +235,127 @@
 parseBranches
     "Parse output of 'hg branches' command. Return collection
      of orphaned HGBranch"
-
-    | branches branch |
+    
+    | branches  branch |
 
     branches := OrderedCollection new.
-    stream atEnd ifFalse:[ 
-        branch := self parseBranchesEntryAllowForInvalidBranchheadsMessage: true.
-        branch notNil ifTrue:[ 
-            branches add: branch.
+    stream atEnd ifFalse:[
+        branch := self 
+                        parseBranchesEntryAllowForInvalidBranchheadsMessageEntryAllowForInvalidBranchheadsMessage:true.
+        branch notNil ifTrue:[
+            branches add:branch.
         ].
     ].
     [ stream atEnd ] whileFalse:[
-        branch := self parseBranchesEntryAllowForInvalidBranchheadsMessage: false.  
-        branches add:  branch.
+        branch := self 
+                        parseBranchesEntryAllowForInvalidBranchheadsMessageEntryAllowForInvalidBranchheadsMessage:false.
+        branches add:branch.
+    ].
+    ^ branches
+
+    "Created: / 09-03-2019 / 08:25:55 / Jan Vrany <jan.vrany@fit.cvut.cz>"
+!
+
+parseBranchesEntryAllowForInvalidBranchheadsMessageEntryAllowForInvalidBranchheadsMessage:allowForInvalidBrancheadsMessage 
+    | name  branch  state |
+
+    name := self parseNameUpToNullOrNewLine.
+    allowForInvalidBrancheadsMessage ifTrue:[
+        stream peek == Character null ifFalse:[
+            | message |
+
+            message := name , ' ' , stream nextLine.
+            self notify:message.
+            name := self parseNameUpToNullOrNewLine.
+        ].
     ].
-    ^branches
+    branch := HGBranch new.
+    branch setName:name.
+    self expect:Character null.
+    self parseNodeId.
+    self expect:Character null.
+    state := stream next.
+    state == $A ifTrue:[
+        branch setActive:true
+    ] ifFalse:[
+        state == $C ifTrue:[
+            branch setClosed:true
+        ] ifFalse:[
+            state == $I ifTrue:[
+                branch setActive:false
+            ] ifFalse:[
+                self error:'Invalid branch state: ' , state
+            ]
+        ]
+    ].
+    self expectLineEnd.
+    ^ branch
+
+    "Created: / 09-03-2019 / 08:26:01 / Jan Vrany <jan.vrany@fit.cvut.cz>"
+!
+
+parseBranchesOld
+    "Parse output of 'hg branches' command. Return collection
+     of orphaned HGBranch"
+    
+    | branches  branch |
+
+    branches := OrderedCollection new.
+    stream atEnd ifFalse:[
+        branch := self parseBranchesOldEntryAllowForInvalidBranchheadsMessage:true.
+        branch notNil ifTrue:[
+            branches add:branch.
+        ].
+    ].
+    [ stream atEnd ] whileFalse:[
+        branch := self parseBranchesOldEntryAllowForInvalidBranchheadsMessage:false.
+        branches add:branch.
+    ].
+    ^ branches
 
     "Created: / 27-11-2012 / 20:20:56 / Jan Vrany <jan.vrany@fit.cvut.cz>"
     "Modified: / 08-10-2014 / 20:56:29 / Jan Vrany <jan.vrany@fit.cvut.cz>"
 !
 
-parseBranchesEntryAllowForInvalidBranchheadsMessage: allowForInvalidBrancheadsMessage
-    | name branch |
+parseBranchesOldEntryAllowForInvalidBranchheadsMessage:allowForInvalidBrancheadsMessage 
+    | name  branch |
 
     name := self parseName.
     stream skipSeparators.
-    allowForInvalidBrancheadsMessage ifTrue:[ 
-        stream peek isDigit ifFalse:[ 
+    allowForInvalidBrancheadsMessage ifTrue:[
+        stream peek isDigit ifFalse:[
             | message |
 
-            message := name , ' ', stream nextLine.
-            self notify: message.  
+            message := name , ' ' , stream nextLine.
+            self notify:message.
             name := self parseName.
-            stream skipSeparators.   
+            stream skipSeparators.
         ].
-    ].                
+    ].
     branch := HGBranch new.
-    branch setName: name.
- 
+    branch setName:name.
     self parseNodeId.
     stream peek == Character space ifTrue:[
         stream next.
-        stream peek == $( ifFalse:[self error:'''('' expected but ''' , stream peek , ''' found'].
+        stream peek == $( ifFalse:[
+            self error:'''('' expected but ''' , stream peek , ''' found'
+        ].
         stream next.
         stream peek == $i ifTrue:[
             self expect:'inactive)'.
-            branch setActive: false.
+            branch setActive:false.
         ] ifFalse:[
             stream peek == $c ifTrue:[
                 self expect:'closed)'.
-                branch setClosed: true.
+                branch setClosed:true.
             ] ifFalse:[
-                self error:'Unexpected branch attribute (only ''closed'' and ''inactive'' supported)'''
+                self 
+                    error:'Unexpected branch attribute (only ''closed'' and ''inactive'' supported)'''
             ]
         ].
     ].
     self expectLineEnd.
-    ^branch
+    ^ branch
 
     "Created: / 08-10-2014 / 20:54:46 / Jan Vrany <jan.vrany@fit.cvut.cz>"
 !
@@ -509,6 +579,7 @@
     ].
 
     "Created: / 27-11-2012 / 20:21:27 / Jan Vrany <jan.vrany@fit.cvut.cz>"
+    "Modified: / 07-03-2019 / 11:17:52 / Jan Vrany <jan.vrany@fit.cvut.cz>"
 !
 
 parseNameList
@@ -527,6 +598,33 @@
     "Created: / 27-11-2012 / 20:30:02 / Jan Vrany <jan.vrany@fit.cvut.cz>"
 !
 
+parseNameUpTo: terminator1 or: terminator2
+    ^String streamContents:[:out|
+        | c |    
+        [ c := stream peek. c == terminator1 or:[ c == terminator2] ] whileFalse:[
+            out nextPut:stream next
+        ]
+    ].
+
+    "Created: / 07-03-2019 / 12:16:26 / Jan Vrany <jan.vrany@fit.cvut.cz>"
+!
+
+parseNameUpToNullOrNewLine
+    ^ self parseNameUpTo: Character null or: Character cr.
+
+    "Created: / 07-03-2019 / 12:16:53 / Jan Vrany <jan.vrany@fit.cvut.cz>"
+!
+
+parseNameZ    
+    ^String streamContents:[:out|
+        [ stream peek == Character null ] whileFalse:[
+            out nextPut:stream next
+        ]
+    ].
+
+    "Created: / 07-03-2019 / 11:18:24 / Jan Vrany <jan.vrany@fit.cvut.cz>"
+!
+
 parseNodeId
     "Parses node id from stream and returns it. Support both,
      short and full node ids"
@@ -737,8 +835,17 @@
 parseCommandBranches
     "Parse output of 'hg branches' command. Return collection
      of orphaned HGBranch"
+    
+    ^ self parseBranches
 
-    ^self parseBranches
+    "Created: / 09-03-2019 / 08:19:26 / Jan Vrany <jan.vrany@fit.cvut.cz>"
+!
+
+parseCommandBranchesOld
+    "Parse output of 'hg branches' command. Return collection
+     of orphaned HGBranch"
+    
+    ^ self parseBranchesOld
 
     "Created: / 27-11-2012 / 19:16:12 / Jan Vrany <jan.vrany@fit.cvut.cz>"
     "Modified: / 27-11-2012 / 20:21:15 / Jan Vrany <jan.vrany@fit.cvut.cz>"
--- a/mercurial/HGCommandParserTests.st	Tue Jan 08 09:35:11 2019 +0000
+++ b/mercurial/HGCommandParserTests.st	Thu Mar 07 12:22:12 2019 +0000
@@ -110,10 +110,10 @@
 
     | branches |
 
-    branches := (HGCommandParser on: 'default                        5:f22945219f9be25a1fe436d81afece07b89330be
-branch1                        4:5bd21fb5eea8a7cb4adf45bccfea76cda11df84a (inactive)
-branch2                        3:32d32dee719fb422a69cfa6f7f8c1d8e299de2df (closed)
-') parseCommandBranches.
+    branches := (HGCommandParser on: (('default|5:f22945219f9be25a1fe436d81afece07b89330be|A
+branch1|4:5bd21fb5eea8a7cb4adf45bccfea76cda11df84a|I
+branch2|3:32d32dee719fb422a69cfa6f7f8c1d8e299de2df|C
+' copyReplaceAll: $| with: Character null))) parseCommandBranches.
 
     self assert: branches size == 3.
 
@@ -130,16 +130,17 @@
     self assert: branches third isClosed.
 
     "Created: / 27-11-2012 / 19:00:30 / Jan Vrany <jan.vrany@fit.cvut.cz>"
+    "Modified: / 07-03-2019 / 12:12:20 / Jan Vrany <jan.vrany@fit.cvut.cz>"
 !
 
 test_cmd_branches_02
 
     | branches |
 
-    branches := (HGCommandParser on: 'invalid branchheads cache (visible): tip differs
-default                     5694:756610fa329d48cd8b225524016713485aefbb95
-jv                          5684:2c32b6c5d3543cd0381f9b346d62bfeabb95e6c6
-') parseCommandBranches.
+    branches := (HGCommandParser on: (('invalid branchheads cache (visible): tip differs
+default|5694:756610fa329d48cd8b225524016713485aefbb95|A
+jv|5684:2c32b6c5d3543cd0381f9b346d62bfeabb95e6c6|A
+')copyReplaceAll: $| with: Character null)) parseCommandBranches.
 
     self assert: branches size == 2.
 
@@ -152,6 +153,29 @@
     self assert: branches second isClosed not.
 
     "Created: / 08-10-2014 / 20:39:33 / Jan Vrany <jan.vrany@fit.cvut.cz>"
+    "Modified: / 07-03-2019 / 12:08:26 / Jan Vrany <jan.vrany@fit.cvut.cz>"
+!
+
+test_cmd_branches_issue256b
+
+    | branches |
+
+    branches := (HGCommandParser on: (('default|6:6b8bef68bf9c6b8bef686b8bef68bf9c6b8bef68|A
+test branches issue256b  |5:9db0e56bb86e6b8bef686b8bef68bf9c6b8bef68|I
+') copyReplaceAll: $| with: Character null)) parseCommandBranches.
+
+    self assert: branches size == 2.
+
+    self assert: branches first name = 'default'.
+    self assert: branches first isActive.
+    self assert: branches first isClosed not.
+
+    self assert: branches second name = 'test branches issue256b  '.
+    self assert: branches second isActive not.
+    self assert: branches second isClosed not.
+
+    "Created: / 07-03-2019 / 10:21:57 / Jan Vrany <jan.vrany@fit.cvut.cz>"
+    "Modified: / 07-03-2019 / 12:07:31 / Jan Vrany <jan.vrany@fit.cvut.cz>"
 !
 
 test_cmd_err_branches_01
--- a/mercurial/HGTests.st	Tue Jan 08 09:35:11 2019 +0000
+++ b/mercurial/HGTests.st	Thu Mar 07 12:22:12 2019 +0000
@@ -479,6 +479,45 @@
     self assert:(repo branches contains: [:b|b name = 'test branches issue256a']).
 
     "Created: / 07-01-2019 / 22:42:24 / Jan Vrany <jan.vrany@fit.cvut.cz>"
+!
+
+test_branches_issue256b
+    "
+    Change branch with spaces & commit. Check whether commited changeset
+    has the branch set.
+    "
+
+    | repo wc |
+
+    "/ Older Mercurials do not support templates so we cannot reliably support
+    "/ branches with spaces in there. Hence we skip the test.
+    self skipIf: HGCommand hgVersionIsGreaterOrEqualThan_3_5 not description: 'Not supported on hg < 3.5'.
+
+    repo := self repositoryNamed:'test_repo_01'.
+    wc := repo workingCopy.
+    self assert: repo branches size == 1.
+    wc branch: 'test branches issue256b'.
+
+    "Modify some file"
+    (wc / 'f1.txt') writingFileDo:[:s | s nextPutAll:'modified...' ].
+    wc commit: testSelector , ' - commit 01'.
+
+    self assert: wc changeset branches size == 1.
+    self assert: wc changeset branches anElement name = 'test branches issue256b'.
+    self assert: repo branches size == 2.       
+    self assert:(repo branches contains: [:b|b name = 'default']).
+    self assert:(repo branches contains: [:b|b name = 'test branches issue256b']).
+    
+    wc branch: 'default'.    
+    "Modify some file"
+    (wc / 'f1.txt') writingFileDo:[:s | s nextPutAll:'modified_default...' ].
+    wc commit: testSelector , ' - commit default branch 01'.
+    
+    self assert: wc changeset branches size == 1.
+    self assert: wc changeset branches anElement name = 'default'.
+
+    "Created: / 06-03-2019 / 10:32:50 / svestkap"
+    "Modified (format): / 12-03-2019 / 11:58:14 / Jan Vrany <jan.vrany@fit.cvut.cz>"
 ! !
 
 !HGTests methodsFor:'tests - changesets'!