From 3d781c92a7588d31d428697297b731f66851a646 Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Fri, 27 Sep 2024 04:09:17 +1000 Subject: [PATCH] update bootsupport and _vfscommon --- .../modules/punk/mix/commandset/repo-0.1.0.tm | 1 + src/bootsupport/modules/punk/repo-0.1.1.tm | 72 +++++++++++++++++-- .../modules/punk/mix/commandset/repo-0.1.0.tm | 1 + .../bootsupport/modules/punk/repo-0.1.1.tm | 72 +++++++++++++++++-- .../modules/punk/mix/commandset/repo-0.1.0.tm | 1 + .../bootsupport/modules/punk/repo-0.1.1.tm | 72 +++++++++++++++++-- .../modules/punk/mix/commandset/repo-0.1.0.tm | 1 + src/vfs/_vfscommon/modules/punk/repl-0.1.tm | 55 +++++++++++--- src/vfs/_vfscommon/modules/punk/repo-0.1.1.tm | 72 +++++++++++++++++-- 9 files changed, 311 insertions(+), 36 deletions(-) diff --git a/src/bootsupport/modules/punk/mix/commandset/repo-0.1.0.tm b/src/bootsupport/modules/punk/mix/commandset/repo-0.1.0.tm index 2b3ca282..73b54874 100644 --- a/src/bootsupport/modules/punk/mix/commandset/repo-0.1.0.tm +++ b/src/bootsupport/modules/punk/mix/commandset/repo-0.1.0.tm @@ -25,6 +25,7 @@ namespace eval punk::mix::commandset::repo { namespace export * proc tickets {{project ""}} { + #todo set result "" if {[string length $project]} { puts stderr "project status unimplemented" diff --git a/src/bootsupport/modules/punk/repo-0.1.1.tm b/src/bootsupport/modules/punk/repo-0.1.1.tm index e056b14a..4e0217b0 100644 --- a/src/bootsupport/modules/punk/repo-0.1.1.tm +++ b/src/bootsupport/modules/punk/repo-0.1.1.tm @@ -346,6 +346,11 @@ namespace eval punk::repo { #does a dual git/fossil repo make sense if both are committing?? # see: https://fossil-scm.org/home/doc/trunk/www/inout.wiki for bidirectional sync info proc workingdir_state {{abspath {}} args} { + + #we should try to minimize executable calls + #an extra git/fossil executable call required for tags + #git seems to require more executable calls + set defaults [list\ -repotypes [list fossil git]\ -repopaths ""\ @@ -408,6 +413,8 @@ namespace eval punk::repo { set revision "" set revision_iso8601 "" set pathdict [dict create] + set branch "" + set tags "" if {![llength $repotypes_to_query]} { error "No tracking information available for project at $repodir with the chosen repotypes '$opt_repotypes'. Ensure project workingdir is a fossil (or git) checkout" @@ -437,6 +444,38 @@ namespace eval punk::repo { } set revision_iso8601 "${revision_ymd}T${revision_hms}${revision_tz}" + #REVIEW! what are the semantic difference between tags in fossil v git? + #fossil has tagtypes such as propagated and singleton(onetime) + #if we get all tag info for the revision - we can get the current branch (branch=somename tag) at the same time + #by retrieving with --raw - we have to process some prefixes such as sym- but probably best not done here + #we will return all tags that apply to the current revision and let the caller decide the meanings + if {![catch {punk::mix::util::do_in_path $repodir [list exec {*}$fossil_cmd tag ls --raw $revision]} cmdresult]} { + set branchinfo [lindex [grep {branch=*} $cmdresult] 0] ;#first line match - should only be one + set branch [lindex [split $branchinfo =] 1] + set tags [list] + foreach ln [split $cmdresult \n] { + if {[string trim $ln] eq ""} { + continue + } + lappend tags [string trim $ln] + } + } + + #set tags_info [lindex [grep {tags:*} $fossilstate 0] ;#first line match - should only be one + #we get lines like: + #tags: trunk, main + #tags: trunk + #set rawtags [lrange $tags_info 1 end] ;#REVIEW + #set tags [list] + #foreach t $rawtags { + # lappend tags [string trimright $t ,] + #} + + + #if {![catch {punk::mix::util::do_in_path $repodir [list exec {*}$fossil_cmd branch current]} cmdresult]} { + # set branch $cmdresult ;#command result doesn't include newline etc + #} + dict set resultdict ahead "" dict set resultdict behind "" @@ -490,6 +529,7 @@ namespace eval punk::repo { set git_cmd [auto_execok git] # -uno = suppress ? lines. # -b = show ranch and tracking info + #our basic parsing/grepping assumes --porcelain=2 if {[catch {punk::mix::util::do_in_path $repodir [list exec {*}$git_cmd status --porcelain=2 -b -- $abspath]} gitstate]} { error "workingdir_state error: Unable to retrieve workingdir state using git. Errormsg: $gitstate" } @@ -499,6 +539,13 @@ namespace eval punk::repo { puts stderr "workingdir_state: git revision is (initial) - no file state to gather" break } + # line: # branch.head somebranchname + set branch [lindex [grep {# branch.head *} $gitstate] 0 2] + + if {![catch {punk::mix::util::do_in_path $repodir [list exec {*}$git_cmd describe --exact-match --tags]} cmdresult]} { + set tags $cmdresult ;#review - we have short tags vs longer.. e.g v0.1a vs v0.1a-184-g856fab4 - which is returned? Also how are multiple separated? + } + #often there will be no tag - so the common case is actually an error "fatal: not ag exactly matchs 'xxxx...'" # -- --- --- --- --- #could use %ci for ISO8601 data - see git-show manpage, but this will be in timezone of developer's machine - we need it in UTC for comparison to fossil outputs and other devs @@ -600,9 +647,11 @@ namespace eval punk::repo { puts stderr "workingdir_state - repotype $rt not supported" } } - dict set resultdict revision $revision - dict set resultdict revision_iso8601 $revision_iso8601 - dict set resultdict paths $pathdict + dict set resultdict branch $branch + dict set resultdict tags $tags + dict set resultdict revision $revision + dict set resultdict revision_iso8601 $revision_iso8601 + dict set resultdict paths $pathdict return $resultdict } proc workingdir_state_summary {repostate args} { @@ -610,8 +659,13 @@ namespace eval punk::repo { error "workingdir_state_summary error repostate doesn't appear to be a repostate dict. (use workingdir_state to create)" } package require overtype + + #the revision branch and tags are highly relevant to the file state - and workingdir_state currently retrieves them anyway + # - so we'll include them in the defaults + # - when we are including working dir state as part of other output - we could be duplicating branch/tag retrievals + # - todo - flags to stop duplicating effort ?? set defaults [dict create\ - -fields {ahead behind unchanged changed new missing extra}\ + -fields {revision branch tags ahead behind unchanged changed new missing extra}\ ] set opts [dict merge $defaults $args] # -- --- --- --- --- --- --- --- --- --- @@ -625,6 +679,8 @@ namespace eval punk::repo { subpath subpath\ revision revision\ revision_iso8601 revision_iso8601\ + branch branch\ + tags tags\ ahead ahead\ behind behind\ repotype repotype\ @@ -662,13 +718,15 @@ namespace eval punk::repo { set result [string trimright $result \n] return $result } + + #todo - describe purpose and possibly rename proc workingdir_state_summary_dict {repostate} { if {![dict exists $repostate repotype] || ![dict exists $repostate paths]} { error "workingdir_state_summary_dict error repostate doesn't appear to be a repostate dict. (use workingdir_state to create)" } set filestates [dict values [dict get $repostate paths]] set path_count_fields [list unchanged changed new missing extra] - set state_fields [list ahead behind repodir subpath repotype revision revision_iso8601] + set state_fields [list ahead behind repodir subpath repotype revision revision_iso8601 branch tags] set dresult [dict create] if {[dict exists $repostate error]} { foreach f $state_fields { @@ -1182,7 +1240,7 @@ namespace eval punk::repo { #------------------------------------ #limit to exec so full punk shell not required in scripts - proc git_revision {{path {}}} { + proc git_revision {{path ""}} { if {$path eq {}} { set path [pwd] } # ::kettle::path::revision.git do_in_path $path { @@ -1196,7 +1254,7 @@ namespace eval punk::repo { } return [string trim $v] } - proc git_remote {{path {{}}}} { + proc git_remote {{path ""}} { if {$path eq {}} { set path [pwd] } do_in_path $path { try { diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/repo-0.1.0.tm b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/repo-0.1.0.tm index 2b3ca282..73b54874 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/repo-0.1.0.tm +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/repo-0.1.0.tm @@ -25,6 +25,7 @@ namespace eval punk::mix::commandset::repo { namespace export * proc tickets {{project ""}} { + #todo set result "" if {[string length $project]} { puts stderr "project status unimplemented" diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm index e056b14a..4e0217b0 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm @@ -346,6 +346,11 @@ namespace eval punk::repo { #does a dual git/fossil repo make sense if both are committing?? # see: https://fossil-scm.org/home/doc/trunk/www/inout.wiki for bidirectional sync info proc workingdir_state {{abspath {}} args} { + + #we should try to minimize executable calls + #an extra git/fossil executable call required for tags + #git seems to require more executable calls + set defaults [list\ -repotypes [list fossil git]\ -repopaths ""\ @@ -408,6 +413,8 @@ namespace eval punk::repo { set revision "" set revision_iso8601 "" set pathdict [dict create] + set branch "" + set tags "" if {![llength $repotypes_to_query]} { error "No tracking information available for project at $repodir with the chosen repotypes '$opt_repotypes'. Ensure project workingdir is a fossil (or git) checkout" @@ -437,6 +444,38 @@ namespace eval punk::repo { } set revision_iso8601 "${revision_ymd}T${revision_hms}${revision_tz}" + #REVIEW! what are the semantic difference between tags in fossil v git? + #fossil has tagtypes such as propagated and singleton(onetime) + #if we get all tag info for the revision - we can get the current branch (branch=somename tag) at the same time + #by retrieving with --raw - we have to process some prefixes such as sym- but probably best not done here + #we will return all tags that apply to the current revision and let the caller decide the meanings + if {![catch {punk::mix::util::do_in_path $repodir [list exec {*}$fossil_cmd tag ls --raw $revision]} cmdresult]} { + set branchinfo [lindex [grep {branch=*} $cmdresult] 0] ;#first line match - should only be one + set branch [lindex [split $branchinfo =] 1] + set tags [list] + foreach ln [split $cmdresult \n] { + if {[string trim $ln] eq ""} { + continue + } + lappend tags [string trim $ln] + } + } + + #set tags_info [lindex [grep {tags:*} $fossilstate 0] ;#first line match - should only be one + #we get lines like: + #tags: trunk, main + #tags: trunk + #set rawtags [lrange $tags_info 1 end] ;#REVIEW + #set tags [list] + #foreach t $rawtags { + # lappend tags [string trimright $t ,] + #} + + + #if {![catch {punk::mix::util::do_in_path $repodir [list exec {*}$fossil_cmd branch current]} cmdresult]} { + # set branch $cmdresult ;#command result doesn't include newline etc + #} + dict set resultdict ahead "" dict set resultdict behind "" @@ -490,6 +529,7 @@ namespace eval punk::repo { set git_cmd [auto_execok git] # -uno = suppress ? lines. # -b = show ranch and tracking info + #our basic parsing/grepping assumes --porcelain=2 if {[catch {punk::mix::util::do_in_path $repodir [list exec {*}$git_cmd status --porcelain=2 -b -- $abspath]} gitstate]} { error "workingdir_state error: Unable to retrieve workingdir state using git. Errormsg: $gitstate" } @@ -499,6 +539,13 @@ namespace eval punk::repo { puts stderr "workingdir_state: git revision is (initial) - no file state to gather" break } + # line: # branch.head somebranchname + set branch [lindex [grep {# branch.head *} $gitstate] 0 2] + + if {![catch {punk::mix::util::do_in_path $repodir [list exec {*}$git_cmd describe --exact-match --tags]} cmdresult]} { + set tags $cmdresult ;#review - we have short tags vs longer.. e.g v0.1a vs v0.1a-184-g856fab4 - which is returned? Also how are multiple separated? + } + #often there will be no tag - so the common case is actually an error "fatal: not ag exactly matchs 'xxxx...'" # -- --- --- --- --- #could use %ci for ISO8601 data - see git-show manpage, but this will be in timezone of developer's machine - we need it in UTC for comparison to fossil outputs and other devs @@ -600,9 +647,11 @@ namespace eval punk::repo { puts stderr "workingdir_state - repotype $rt not supported" } } - dict set resultdict revision $revision - dict set resultdict revision_iso8601 $revision_iso8601 - dict set resultdict paths $pathdict + dict set resultdict branch $branch + dict set resultdict tags $tags + dict set resultdict revision $revision + dict set resultdict revision_iso8601 $revision_iso8601 + dict set resultdict paths $pathdict return $resultdict } proc workingdir_state_summary {repostate args} { @@ -610,8 +659,13 @@ namespace eval punk::repo { error "workingdir_state_summary error repostate doesn't appear to be a repostate dict. (use workingdir_state to create)" } package require overtype + + #the revision branch and tags are highly relevant to the file state - and workingdir_state currently retrieves them anyway + # - so we'll include them in the defaults + # - when we are including working dir state as part of other output - we could be duplicating branch/tag retrievals + # - todo - flags to stop duplicating effort ?? set defaults [dict create\ - -fields {ahead behind unchanged changed new missing extra}\ + -fields {revision branch tags ahead behind unchanged changed new missing extra}\ ] set opts [dict merge $defaults $args] # -- --- --- --- --- --- --- --- --- --- @@ -625,6 +679,8 @@ namespace eval punk::repo { subpath subpath\ revision revision\ revision_iso8601 revision_iso8601\ + branch branch\ + tags tags\ ahead ahead\ behind behind\ repotype repotype\ @@ -662,13 +718,15 @@ namespace eval punk::repo { set result [string trimright $result \n] return $result } + + #todo - describe purpose and possibly rename proc workingdir_state_summary_dict {repostate} { if {![dict exists $repostate repotype] || ![dict exists $repostate paths]} { error "workingdir_state_summary_dict error repostate doesn't appear to be a repostate dict. (use workingdir_state to create)" } set filestates [dict values [dict get $repostate paths]] set path_count_fields [list unchanged changed new missing extra] - set state_fields [list ahead behind repodir subpath repotype revision revision_iso8601] + set state_fields [list ahead behind repodir subpath repotype revision revision_iso8601 branch tags] set dresult [dict create] if {[dict exists $repostate error]} { foreach f $state_fields { @@ -1182,7 +1240,7 @@ namespace eval punk::repo { #------------------------------------ #limit to exec so full punk shell not required in scripts - proc git_revision {{path {}}} { + proc git_revision {{path ""}} { if {$path eq {}} { set path [pwd] } # ::kettle::path::revision.git do_in_path $path { @@ -1196,7 +1254,7 @@ namespace eval punk::repo { } return [string trim $v] } - proc git_remote {{path {{}}}} { + proc git_remote {{path ""}} { if {$path eq {}} { set path [pwd] } do_in_path $path { try { diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/repo-0.1.0.tm b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/repo-0.1.0.tm index 2b3ca282..73b54874 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/repo-0.1.0.tm +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/repo-0.1.0.tm @@ -25,6 +25,7 @@ namespace eval punk::mix::commandset::repo { namespace export * proc tickets {{project ""}} { + #todo set result "" if {[string length $project]} { puts stderr "project status unimplemented" diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm index e056b14a..4e0217b0 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm @@ -346,6 +346,11 @@ namespace eval punk::repo { #does a dual git/fossil repo make sense if both are committing?? # see: https://fossil-scm.org/home/doc/trunk/www/inout.wiki for bidirectional sync info proc workingdir_state {{abspath {}} args} { + + #we should try to minimize executable calls + #an extra git/fossil executable call required for tags + #git seems to require more executable calls + set defaults [list\ -repotypes [list fossil git]\ -repopaths ""\ @@ -408,6 +413,8 @@ namespace eval punk::repo { set revision "" set revision_iso8601 "" set pathdict [dict create] + set branch "" + set tags "" if {![llength $repotypes_to_query]} { error "No tracking information available for project at $repodir with the chosen repotypes '$opt_repotypes'. Ensure project workingdir is a fossil (or git) checkout" @@ -437,6 +444,38 @@ namespace eval punk::repo { } set revision_iso8601 "${revision_ymd}T${revision_hms}${revision_tz}" + #REVIEW! what are the semantic difference between tags in fossil v git? + #fossil has tagtypes such as propagated and singleton(onetime) + #if we get all tag info for the revision - we can get the current branch (branch=somename tag) at the same time + #by retrieving with --raw - we have to process some prefixes such as sym- but probably best not done here + #we will return all tags that apply to the current revision and let the caller decide the meanings + if {![catch {punk::mix::util::do_in_path $repodir [list exec {*}$fossil_cmd tag ls --raw $revision]} cmdresult]} { + set branchinfo [lindex [grep {branch=*} $cmdresult] 0] ;#first line match - should only be one + set branch [lindex [split $branchinfo =] 1] + set tags [list] + foreach ln [split $cmdresult \n] { + if {[string trim $ln] eq ""} { + continue + } + lappend tags [string trim $ln] + } + } + + #set tags_info [lindex [grep {tags:*} $fossilstate 0] ;#first line match - should only be one + #we get lines like: + #tags: trunk, main + #tags: trunk + #set rawtags [lrange $tags_info 1 end] ;#REVIEW + #set tags [list] + #foreach t $rawtags { + # lappend tags [string trimright $t ,] + #} + + + #if {![catch {punk::mix::util::do_in_path $repodir [list exec {*}$fossil_cmd branch current]} cmdresult]} { + # set branch $cmdresult ;#command result doesn't include newline etc + #} + dict set resultdict ahead "" dict set resultdict behind "" @@ -490,6 +529,7 @@ namespace eval punk::repo { set git_cmd [auto_execok git] # -uno = suppress ? lines. # -b = show ranch and tracking info + #our basic parsing/grepping assumes --porcelain=2 if {[catch {punk::mix::util::do_in_path $repodir [list exec {*}$git_cmd status --porcelain=2 -b -- $abspath]} gitstate]} { error "workingdir_state error: Unable to retrieve workingdir state using git. Errormsg: $gitstate" } @@ -499,6 +539,13 @@ namespace eval punk::repo { puts stderr "workingdir_state: git revision is (initial) - no file state to gather" break } + # line: # branch.head somebranchname + set branch [lindex [grep {# branch.head *} $gitstate] 0 2] + + if {![catch {punk::mix::util::do_in_path $repodir [list exec {*}$git_cmd describe --exact-match --tags]} cmdresult]} { + set tags $cmdresult ;#review - we have short tags vs longer.. e.g v0.1a vs v0.1a-184-g856fab4 - which is returned? Also how are multiple separated? + } + #often there will be no tag - so the common case is actually an error "fatal: not ag exactly matchs 'xxxx...'" # -- --- --- --- --- #could use %ci for ISO8601 data - see git-show manpage, but this will be in timezone of developer's machine - we need it in UTC for comparison to fossil outputs and other devs @@ -600,9 +647,11 @@ namespace eval punk::repo { puts stderr "workingdir_state - repotype $rt not supported" } } - dict set resultdict revision $revision - dict set resultdict revision_iso8601 $revision_iso8601 - dict set resultdict paths $pathdict + dict set resultdict branch $branch + dict set resultdict tags $tags + dict set resultdict revision $revision + dict set resultdict revision_iso8601 $revision_iso8601 + dict set resultdict paths $pathdict return $resultdict } proc workingdir_state_summary {repostate args} { @@ -610,8 +659,13 @@ namespace eval punk::repo { error "workingdir_state_summary error repostate doesn't appear to be a repostate dict. (use workingdir_state to create)" } package require overtype + + #the revision branch and tags are highly relevant to the file state - and workingdir_state currently retrieves them anyway + # - so we'll include them in the defaults + # - when we are including working dir state as part of other output - we could be duplicating branch/tag retrievals + # - todo - flags to stop duplicating effort ?? set defaults [dict create\ - -fields {ahead behind unchanged changed new missing extra}\ + -fields {revision branch tags ahead behind unchanged changed new missing extra}\ ] set opts [dict merge $defaults $args] # -- --- --- --- --- --- --- --- --- --- @@ -625,6 +679,8 @@ namespace eval punk::repo { subpath subpath\ revision revision\ revision_iso8601 revision_iso8601\ + branch branch\ + tags tags\ ahead ahead\ behind behind\ repotype repotype\ @@ -662,13 +718,15 @@ namespace eval punk::repo { set result [string trimright $result \n] return $result } + + #todo - describe purpose and possibly rename proc workingdir_state_summary_dict {repostate} { if {![dict exists $repostate repotype] || ![dict exists $repostate paths]} { error "workingdir_state_summary_dict error repostate doesn't appear to be a repostate dict. (use workingdir_state to create)" } set filestates [dict values [dict get $repostate paths]] set path_count_fields [list unchanged changed new missing extra] - set state_fields [list ahead behind repodir subpath repotype revision revision_iso8601] + set state_fields [list ahead behind repodir subpath repotype revision revision_iso8601 branch tags] set dresult [dict create] if {[dict exists $repostate error]} { foreach f $state_fields { @@ -1182,7 +1240,7 @@ namespace eval punk::repo { #------------------------------------ #limit to exec so full punk shell not required in scripts - proc git_revision {{path {}}} { + proc git_revision {{path ""}} { if {$path eq {}} { set path [pwd] } # ::kettle::path::revision.git do_in_path $path { @@ -1196,7 +1254,7 @@ namespace eval punk::repo { } return [string trim $v] } - proc git_remote {{path {{}}}} { + proc git_remote {{path ""}} { if {$path eq {}} { set path [pwd] } do_in_path $path { try { diff --git a/src/vfs/_vfscommon/modules/punk/mix/commandset/repo-0.1.0.tm b/src/vfs/_vfscommon/modules/punk/mix/commandset/repo-0.1.0.tm index 2b3ca282..73b54874 100644 --- a/src/vfs/_vfscommon/modules/punk/mix/commandset/repo-0.1.0.tm +++ b/src/vfs/_vfscommon/modules/punk/mix/commandset/repo-0.1.0.tm @@ -25,6 +25,7 @@ namespace eval punk::mix::commandset::repo { namespace export * proc tickets {{project ""}} { + #todo set result "" if {[string length $project]} { puts stderr "project status unimplemented" diff --git a/src/vfs/_vfscommon/modules/punk/repl-0.1.tm b/src/vfs/_vfscommon/modules/punk/repl-0.1.tm index 48f10610..eef8799d 100644 --- a/src/vfs/_vfscommon/modules/punk/repl-0.1.tm +++ b/src/vfs/_vfscommon/modules/punk/repl-0.1.tm @@ -609,9 +609,19 @@ proc repl::newout2 {} { proc repl::doprompt {prompt {col {green bold}}} { #prompt to stderr. #We can pipe commands into repl's stdin without the prompt interfering with the output. - #Although all command output for each line goes to stdout - not just what is emmited with puts + #Although all command output for each line goes to stdout - not just what is emitted with puts if {$::tcl_interactive} { + flush stdout; #we are writing this prompt on stderr, but stdout could still be writing to screen + #our first char on stderr is based on the 'lastchar' of stdout which we have recorded but may not have arrived on screen. + #The issue we're trying to avoid is the (stderr)prompt arriving midway through a large stdout chunk + #REVIEW - this basic attempt to get stderr/stdout to cooperate is experimental and unlikely to achieve the desired effect + #note that our 'flush stdout' tcl call does not wait if stdout is non-blocking + #todo - investigate if the overhead is reasonable for a special channel that accepts stdout and stderr records with a reader to send to console in chunk-sizes we know will be emitted correctly + # - reader of such channel could be ok to be blocking (on read? on write to real channels?)... except everything still needs to be interruptable by things like signals? + #? - we want ordinary puts to stderr to be prioritized? to arrive on-screen - just not at arbitrary locations within stdout, and still must be correctly ordered wrt all other stderr + # - in our repl and code threads we don't want to put stderr/stdout writes in blocking mode and have code waiting on it + set last_char_info [screen_last_char_getinfo] if {![llength $last_char_info]} { set needs_clearance 1 @@ -1364,24 +1374,53 @@ proc repl::repl_handler {inputchan prompt_config} { lassign [dict get $outconf -winsize] cols rows } else { #fallback - try external executable. Which is a bit ugly - #tput can work on windows too if it's installed e.g from msys2 + #tput can't seem to get dimensions (on FreeBSD at least) when not run interactively - ie via exec. (always returns 80x24 no matter if run with <@stdin) + + #bizarrely - tput can work with exec on windows if it's installed e.g from msys2 #but can be *slow* compared to unix e.g 400ms+ vs <2ms on FreeBSD ! - set tputcmd [auto_execok tput] - if {$tputcmd ne ""} { - if {![catch {exec {*}$tputcmd cols lines} values} { - lassign $values cols rows - } + #stty -a is 400ms+ vs 500us+ on FreeBSD + + if {"windows" eq $::tcl_platform(platform)} { + set tputcmd [auto_execok tput] + if {$tputcmd ne ""} { + if {![catch {exec {*}$tputcmd cols lines} values]} { + lassign $values cols rows + } + } + } + + if {![string is integer -strict $cols] || ![string is integer -strict $rows]} { + #same for all platforms? tested on windows, wsl, FreeBSD + #exec stty -a gives a result on the first line like: + #speed xxxx baud; rows rr; columns cc; + #review - more robust parsing - do we know it's first line? + set sttycmd [auto_execok stty] + if {$sttycmd ne ""} { + #the more parseable: stty -g doesn't give rows/columns + if {![catch {exec {*}$sttycmd -a} result]} { + lassign [split $result \n] firstline + set lineparts [split $firstline {;}] ;#we seem to get segments that look well behaved enough to be treated as tcl lists - review - regex? + set rowinfo [lsearch -index end -inline $lineparts rows] + if {[llength $rowinfo] == 2} { + set rows [lindex $rowinfo 0] + } + set colinfo [lsearch -index end -inline $lineparts columns] + if {[llength $colinfo] == 2} { + set cols [lindex $colinfo 0] + } + } + } } } if {[string is integer -strict $cols] && [string is integer -strict $rows]} { #got_dimensions - todo - try spinner? #puts -nonewline stdout [punk::ansi::move $rows 4]$msg #use cursorsave_ version which avoids get_cursor_pos_list call + set msglen [ansistring length $msg] punk::console::cursorsave_move_emitblock_return $rows [expr {$cols - $msglen -1}] $msg } else { #no mechanism to get console dimensions #we are reduced to continuously spewing lines. - set msglen [ansistring length $msg] puts stderr $msg } diff --git a/src/vfs/_vfscommon/modules/punk/repo-0.1.1.tm b/src/vfs/_vfscommon/modules/punk/repo-0.1.1.tm index e056b14a..4e0217b0 100644 --- a/src/vfs/_vfscommon/modules/punk/repo-0.1.1.tm +++ b/src/vfs/_vfscommon/modules/punk/repo-0.1.1.tm @@ -346,6 +346,11 @@ namespace eval punk::repo { #does a dual git/fossil repo make sense if both are committing?? # see: https://fossil-scm.org/home/doc/trunk/www/inout.wiki for bidirectional sync info proc workingdir_state {{abspath {}} args} { + + #we should try to minimize executable calls + #an extra git/fossil executable call required for tags + #git seems to require more executable calls + set defaults [list\ -repotypes [list fossil git]\ -repopaths ""\ @@ -408,6 +413,8 @@ namespace eval punk::repo { set revision "" set revision_iso8601 "" set pathdict [dict create] + set branch "" + set tags "" if {![llength $repotypes_to_query]} { error "No tracking information available for project at $repodir with the chosen repotypes '$opt_repotypes'. Ensure project workingdir is a fossil (or git) checkout" @@ -437,6 +444,38 @@ namespace eval punk::repo { } set revision_iso8601 "${revision_ymd}T${revision_hms}${revision_tz}" + #REVIEW! what are the semantic difference between tags in fossil v git? + #fossil has tagtypes such as propagated and singleton(onetime) + #if we get all tag info for the revision - we can get the current branch (branch=somename tag) at the same time + #by retrieving with --raw - we have to process some prefixes such as sym- but probably best not done here + #we will return all tags that apply to the current revision and let the caller decide the meanings + if {![catch {punk::mix::util::do_in_path $repodir [list exec {*}$fossil_cmd tag ls --raw $revision]} cmdresult]} { + set branchinfo [lindex [grep {branch=*} $cmdresult] 0] ;#first line match - should only be one + set branch [lindex [split $branchinfo =] 1] + set tags [list] + foreach ln [split $cmdresult \n] { + if {[string trim $ln] eq ""} { + continue + } + lappend tags [string trim $ln] + } + } + + #set tags_info [lindex [grep {tags:*} $fossilstate 0] ;#first line match - should only be one + #we get lines like: + #tags: trunk, main + #tags: trunk + #set rawtags [lrange $tags_info 1 end] ;#REVIEW + #set tags [list] + #foreach t $rawtags { + # lappend tags [string trimright $t ,] + #} + + + #if {![catch {punk::mix::util::do_in_path $repodir [list exec {*}$fossil_cmd branch current]} cmdresult]} { + # set branch $cmdresult ;#command result doesn't include newline etc + #} + dict set resultdict ahead "" dict set resultdict behind "" @@ -490,6 +529,7 @@ namespace eval punk::repo { set git_cmd [auto_execok git] # -uno = suppress ? lines. # -b = show ranch and tracking info + #our basic parsing/grepping assumes --porcelain=2 if {[catch {punk::mix::util::do_in_path $repodir [list exec {*}$git_cmd status --porcelain=2 -b -- $abspath]} gitstate]} { error "workingdir_state error: Unable to retrieve workingdir state using git. Errormsg: $gitstate" } @@ -499,6 +539,13 @@ namespace eval punk::repo { puts stderr "workingdir_state: git revision is (initial) - no file state to gather" break } + # line: # branch.head somebranchname + set branch [lindex [grep {# branch.head *} $gitstate] 0 2] + + if {![catch {punk::mix::util::do_in_path $repodir [list exec {*}$git_cmd describe --exact-match --tags]} cmdresult]} { + set tags $cmdresult ;#review - we have short tags vs longer.. e.g v0.1a vs v0.1a-184-g856fab4 - which is returned? Also how are multiple separated? + } + #often there will be no tag - so the common case is actually an error "fatal: not ag exactly matchs 'xxxx...'" # -- --- --- --- --- #could use %ci for ISO8601 data - see git-show manpage, but this will be in timezone of developer's machine - we need it in UTC for comparison to fossil outputs and other devs @@ -600,9 +647,11 @@ namespace eval punk::repo { puts stderr "workingdir_state - repotype $rt not supported" } } - dict set resultdict revision $revision - dict set resultdict revision_iso8601 $revision_iso8601 - dict set resultdict paths $pathdict + dict set resultdict branch $branch + dict set resultdict tags $tags + dict set resultdict revision $revision + dict set resultdict revision_iso8601 $revision_iso8601 + dict set resultdict paths $pathdict return $resultdict } proc workingdir_state_summary {repostate args} { @@ -610,8 +659,13 @@ namespace eval punk::repo { error "workingdir_state_summary error repostate doesn't appear to be a repostate dict. (use workingdir_state to create)" } package require overtype + + #the revision branch and tags are highly relevant to the file state - and workingdir_state currently retrieves them anyway + # - so we'll include them in the defaults + # - when we are including working dir state as part of other output - we could be duplicating branch/tag retrievals + # - todo - flags to stop duplicating effort ?? set defaults [dict create\ - -fields {ahead behind unchanged changed new missing extra}\ + -fields {revision branch tags ahead behind unchanged changed new missing extra}\ ] set opts [dict merge $defaults $args] # -- --- --- --- --- --- --- --- --- --- @@ -625,6 +679,8 @@ namespace eval punk::repo { subpath subpath\ revision revision\ revision_iso8601 revision_iso8601\ + branch branch\ + tags tags\ ahead ahead\ behind behind\ repotype repotype\ @@ -662,13 +718,15 @@ namespace eval punk::repo { set result [string trimright $result \n] return $result } + + #todo - describe purpose and possibly rename proc workingdir_state_summary_dict {repostate} { if {![dict exists $repostate repotype] || ![dict exists $repostate paths]} { error "workingdir_state_summary_dict error repostate doesn't appear to be a repostate dict. (use workingdir_state to create)" } set filestates [dict values [dict get $repostate paths]] set path_count_fields [list unchanged changed new missing extra] - set state_fields [list ahead behind repodir subpath repotype revision revision_iso8601] + set state_fields [list ahead behind repodir subpath repotype revision revision_iso8601 branch tags] set dresult [dict create] if {[dict exists $repostate error]} { foreach f $state_fields { @@ -1182,7 +1240,7 @@ namespace eval punk::repo { #------------------------------------ #limit to exec so full punk shell not required in scripts - proc git_revision {{path {}}} { + proc git_revision {{path ""}} { if {$path eq {}} { set path [pwd] } # ::kettle::path::revision.git do_in_path $path { @@ -1196,7 +1254,7 @@ namespace eval punk::repo { } return [string trim $v] } - proc git_remote {{path {{}}}} { + proc git_remote {{path ""}} { if {$path eq {}} { set path [pwd] } do_in_path $path { try {