Julian Noble
1 year ago
7 changed files with 470 additions and 948 deletions
@ -0,0 +1,9 @@ |
|||||||
|
INSTALLER -tsiso 2023-10-05T01:17:29 -ts 1696429049063279 -name manual -keep_events 5 { |
||||||
|
EVENT -tsiso 2023-10-05T01:17:29 -ts 1696429049063338 -type install -id 600bfdac-284e-488f-b8bb-62984640f12b -source ../../modules -target . -config {-glob repo-0.1.1.tm -antiglob_file_core {*.swp *999999.0a1.0* *-buildversion.txt .punkcheck} -antiglob_file {} -antiglob_dir_core {{#*} _aside .git .fossil*} -antiglob_dir {}} |
||||||
|
EVENT -tsiso 2023-10-05T01:19:05 -ts 1696429145153107 -type install -id cb25e965-965f-4ebb-bd3d-cac901273bf0 -source ../../../modules -target . -config {-glob repo-0.1.1.tm -antiglob_file_core {*.swp *999999.0a1.0* *-buildversion.txt .punkcheck} -antiglob_file {} -antiglob_dir_core {{#*} _aside .git .fossil*} -antiglob_dir {}} |
||||||
|
} |
||||||
|
FILEINFO -target punk/repo-0.1.1.tm -keep_installrecords 2 -keep_skipped 1 -keep_installing 2 { |
||||||
|
INSTALLRECORD -tsiso 2023-10-05T01:19:05 -ts 1696429145155747 -installer manual -eventid cb25e965-965f-4ebb-bd3d-cac901273bf0 -elapsed_us 16897 { |
||||||
|
SOURCE -type file -path ../../../modules/punk/repo-0.1.1.tm -cksum eaf21ef8df7355cfe077b1da8aeca3b4a7a9d7f7 -cksum_all_opts {-cksum_content 1 -cksum_meta 0 -cksum_acls 0 -cksum_usetar 0 -cksum_algorithm sha1} -changed 1 -metadata_us 10894 |
||||||
|
} |
||||||
|
} |
@ -1,814 +0,0 @@ |
|||||||
# -*- tcl -*- |
|
||||||
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt |
|
||||||
# |
|
||||||
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. |
|
||||||
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. |
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
#Copyright (c) 2023 Julian Noble |
|
||||||
#Copyright (c) 2012-2018 Andreas Kupries |
|
||||||
# - code from A.K's 'kettle' project used in this module |
|
||||||
# |
|
||||||
# @@ Meta Begin |
|
||||||
# Application punk::repo 0.1.0 |
|
||||||
# Meta platform tcl |
|
||||||
# Meta license BSD |
|
||||||
# @@ Meta End |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
## Requirements |
|
||||||
##e.g package require frobz |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
# |
|
||||||
# path/repo functions |
|
||||||
# |
|
||||||
if {$::tcl_platform(platform) eq "windows"} { |
|
||||||
package require punk::winpath |
|
||||||
} else { |
|
||||||
catch {package require punk::winpath} |
|
||||||
} |
|
||||||
package require cksum ;#tcllib |
|
||||||
package require fileutil; #tcllib |
|
||||||
|
|
||||||
|
|
||||||
# -- --- --- --- --- --- --- --- --- --- --- |
|
||||||
# For performance/efficiency reasons - use file functions on paths in preference to string operations |
|
||||||
# e.g use file join |
|
||||||
# branch to avoid unnecessary calls to 'pwd' or 'file normalize' - which can be surprisingly expensive operations (as at tcl 8.7 2023) |
|
||||||
# pwd is only expensive if we treat it as a string instead of a list/path |
|
||||||
# e.g |
|
||||||
# > time {set x [pwd]} |
|
||||||
# 5 microsoeconds.. no problem |
|
||||||
# > time {set x [pwd]} |
|
||||||
# 4 microsoeconds.. still no problem |
|
||||||
# > string length $x |
|
||||||
# 45 |
|
||||||
# > time {set x [pwd]} |
|
||||||
# 1372 microseconds per iteration ;#!! values above 0.5ms common.. and that's a potential problem in loops that trawl filesystem |
|
||||||
# The same sorts of timings occur with file normalize |
|
||||||
# also.. even if we build up a path with file join from a base value that has already been normalized - the subsequent normalize will be expensive |
|
||||||
# -- --- --- --- --- --- --- --- --- --- --- |
|
||||||
|
|
||||||
namespace eval punk::repo { |
|
||||||
variable tmpfile_counter 0 ;#additional tmpfile collision avoidance |
|
||||||
|
|
||||||
proc is_fossil {{path {}}} { |
|
||||||
if {$path eq {}} { set path [pwd] } |
|
||||||
return [expr {[find_fossil $path] ne {}}] |
|
||||||
} |
|
||||||
proc is_git {{path {}}} { |
|
||||||
if {$path eq {}} { set path [pwd] } |
|
||||||
return [expr {[find_git $path] ne {}}] |
|
||||||
} |
|
||||||
#tracked repo - but may not be a project |
|
||||||
proc is_repo {{path {}}} { |
|
||||||
if {$path eq {}} { set path [pwd] } |
|
||||||
return [expr {[isfossil] || [is_git]}] |
|
||||||
} |
|
||||||
proc is_candidate {{path {}}} { |
|
||||||
if {$path eq {}} { set path [pwd] } |
|
||||||
return [expr {[find_candidate $path] ne {}}] |
|
||||||
} |
|
||||||
proc is_project {{path {}}} { |
|
||||||
if {$path eq {}} { set path [pwd] } |
|
||||||
return [expr {[find_project $path] ne {}}] |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
proc find_fossil {{path {}}} { |
|
||||||
if {$path eq {}} { set path [pwd] } |
|
||||||
scanup $path is_fossil_root |
|
||||||
} |
|
||||||
proc find_git {{path {}}} { |
|
||||||
if {$path eq {}} { set path [pwd] } |
|
||||||
scanup $path is_git_root |
|
||||||
} |
|
||||||
proc find_candidate {{path {}}} { |
|
||||||
if {$path eq {}} { set path [pwd] } |
|
||||||
scanup $path is_candidate_root |
|
||||||
} |
|
||||||
proc find_repo {{path {}}} { |
|
||||||
if {$path eq {}} { set path [pwd] } |
|
||||||
#find the closest (lowest in dirtree) repository |
|
||||||
set f_root [find_fossil $path] |
|
||||||
set g_root [find_git $path] |
|
||||||
if {[string length $f_root]} { |
|
||||||
if {[string length $g_root]} { |
|
||||||
if {[path_a_below_b $f_root $g_root]} { |
|
||||||
return $f_root |
|
||||||
} else { |
|
||||||
return $g_root |
|
||||||
} |
|
||||||
} else { |
|
||||||
return $f_root |
|
||||||
} |
|
||||||
} else { |
|
||||||
if {[string length $g_root]} { |
|
||||||
return $g_root |
|
||||||
} else { |
|
||||||
return "" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
proc find_project {{path {}}} { |
|
||||||
if {$path eq {}} { set path [pwd] } |
|
||||||
scanup $path is_project_root |
|
||||||
} |
|
||||||
|
|
||||||
proc is_fossil_root {{path {}}} { |
|
||||||
if {$path eq {}} { set path [pwd] } |
|
||||||
#from kettle::path::is.fossil |
|
||||||
foreach control { |
|
||||||
_FOSSIL_ |
|
||||||
.fslckout |
|
||||||
.fos |
|
||||||
} { |
|
||||||
set control $path/$control |
|
||||||
if {[file exists $control] && [file isfile $control]} {return 1} |
|
||||||
} |
|
||||||
return 0 |
|
||||||
} |
|
||||||
proc is_git_root {{path {}}} { |
|
||||||
if {$path eq {}} { set path [pwd] } |
|
||||||
set control [file join $path .git] |
|
||||||
expr {[file exists $control] && [file isdirectory $control]} |
|
||||||
} |
|
||||||
proc is_repo_root {{path {}}} { |
|
||||||
if {$path eq {}} { set path [pwd] } |
|
||||||
expr {[is_fossil_root $path] || [is_git_root $path]} |
|
||||||
} |
|
||||||
#require a minimum of /src and /modules|lib|scriptapps|*.vfs - and that it's otherwise sensible |
|
||||||
proc is_candidate_root {{path {}}} { |
|
||||||
if {$path eq {}} { set path [pwd] } |
|
||||||
if {[file pathtype $path] eq "relative"} { |
|
||||||
if {$::tcl_platform(platform) eq "windows"} { |
|
||||||
set normpath [punk::repo::norm [punk::winpath::winpath $path]] |
|
||||||
} else { |
|
||||||
set normpath [punk::repo::norm $path] |
|
||||||
} |
|
||||||
} else { |
|
||||||
set normpath $path |
|
||||||
} |
|
||||||
set unwise_paths [list "/" "/usr/local" "/usr/local/bin" "/usr/local/lib" "c:/windows"] |
|
||||||
if {[string tolower $normpath] in $unwise_paths} { |
|
||||||
return 0 |
|
||||||
} |
|
||||||
if {[file pathtype [string trimright $normpath /]] eq "volumerelative"} { |
|
||||||
#tcl 8.6/8.7 cd command doesn't preserve the windows "ProviderPath" (per drive current working directory) |
|
||||||
return 0 |
|
||||||
} |
|
||||||
|
|
||||||
#review - adjust to allow symlinks to folders? |
|
||||||
foreach required { |
|
||||||
src |
|
||||||
} { |
|
||||||
set req $path/$required |
|
||||||
if {(![file exists $req]) || ([file type $req] ne "directory") } {return 0} |
|
||||||
} |
|
||||||
|
|
||||||
set src_subs [glob -nocomplain -dir $path/src -types d -tail *] |
|
||||||
if {"modules" in $src_subs || "lib" in $src_subs || "scriptapps" in $src_subs} { |
|
||||||
return 1 |
|
||||||
} |
|
||||||
foreach sub $src_subs { |
|
||||||
if {[string match *.vfs $sub]} { |
|
||||||
return 1 |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#todo - breadth first search with depth limit (say depth 3?) for *.tm or *.tcl as another positive qualifier for this dir to be a project-root |
|
||||||
#we probably don't want to deep search a src folder in case the user is accidentally in some other type of project's tree |
|
||||||
#such a src tree could be very large, so if we don't find tcl indicators near the root it's a good bet this isn't a candidate |
|
||||||
|
|
||||||
return 0 |
|
||||||
} |
|
||||||
#keep this message in sync with the programmed requirements of is_candidate_root |
|
||||||
#message is not titled - it is intended to be output along with more contextual information from the calling site. |
|
||||||
proc is_candidate_root_requirements_msg {} { |
|
||||||
set msg "" |
|
||||||
append msg "./src directory must exist." \n |
|
||||||
append msg "At least one of ./src/lib ./src/modules ./src/scriptapps or a ./src/<something>.vfs folder should exist." \n |
|
||||||
#append msg "Alternatively - the presence of any .tm or .tcl files within the top few levels of ./src will suffice." \n |
|
||||||
return $msg |
|
||||||
} |
|
||||||
|
|
||||||
proc is_project_root {path} { |
|
||||||
#review - find a reliable simple mechanism. Noting we have projects based on different templates. |
|
||||||
#Should there be a specific required 'project' file of some sort? |
|
||||||
|
|
||||||
#test for file/folder items indicating fossil or git workdir base |
|
||||||
if {(![punk::repo::is_fossil_root $path]) && (![punk::repo::is_git_root $path])} { |
|
||||||
return 0 |
|
||||||
} |
|
||||||
#exclude some known places we wouldn't want to put a project |
|
||||||
if {![is_candidate_root $path]} { |
|
||||||
return 0 |
|
||||||
} |
|
||||||
return 1 |
|
||||||
} |
|
||||||
|
|
||||||
proc find_roots_and_warnings_dict {path} { |
|
||||||
set start_dir $path |
|
||||||
|
|
||||||
#root is a 'project' if it it meets the candidate requrements and is under repo control |
|
||||||
#therefore if project is in the closest_types list - candidate will always be there too - and at least one of git or fossil |
|
||||||
set root_dict [list closest {} closest_types {} fossil {} git {} candidate {} project {} warnings {}] |
|
||||||
set msg "" |
|
||||||
|
|
||||||
#we're only searching in a straight path up the tree looking for a few specific marker files/folder |
|
||||||
set fosroot [punk::repo::find_fossil $start_dir] |
|
||||||
dict set root_dict fossil $fosroot |
|
||||||
set gitroot [punk::repo::find_git $start_dir] |
|
||||||
dict set root_dict git $gitroot |
|
||||||
set candroot [punk::repo::find_candidate $start_dir] |
|
||||||
dict set root_dict candidate $candroot |
|
||||||
|
|
||||||
|
|
||||||
if {[string length $fosroot]} { |
|
||||||
if {([string length $candroot]) && ([string tolower $fosroot] ne [string tolower $candroot])} { |
|
||||||
|
|
||||||
#todo - only warn if this candidate is *within* the found repo root? |
|
||||||
append msg "**" \n |
|
||||||
append msg "** found folder with /src at or above starting folder - that isn't the fossil root" \n |
|
||||||
append msg "** starting folder : $start_dir" \n |
|
||||||
append msg "** unexpected : $candroot" \n |
|
||||||
append msg "** fossil root : $fosroot ([punk::repo::path_relative $start_dir $fosroot])" \n |
|
||||||
append msg "** reporting based on the fossil root found." |
|
||||||
append msg "**" \n |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} else { |
|
||||||
if {[string length $gitroot]} { |
|
||||||
|
|
||||||
if {([string length $candroot]) && ([string tolower $gitroot] ne [string tolower $candroot])} { |
|
||||||
|
|
||||||
append msg "**" \n |
|
||||||
append msg "** found folder with /src at or above current folder - that isn't the git root" \n |
|
||||||
append msg "** starting folder : $start_dir" \n |
|
||||||
append msg "** unexpected : $candroot ([punk::repo::path_relative $start_dir $candroot])" \n |
|
||||||
append msg "** git root : $gitroot ([punk::repo::path_relative $start_dir $gitroot])" \n |
|
||||||
append msg "** reporting based on the git root found." |
|
||||||
append msg "**" \n |
|
||||||
|
|
||||||
} |
|
||||||
} else { |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if {(![string length [dict get $root_dict fossil]])} { |
|
||||||
append msg "Not a punk fossil project" \n |
|
||||||
} |
|
||||||
#don't warn if not git - unless also not fossil |
|
||||||
if {(![string length [dict get $root_dict fossil]]) && (![string length [dict get $root_dict git]])} { |
|
||||||
append msg "No repository located at or above starting folder $start_dir" \n |
|
||||||
if {![string length [dict get $root_dict candidate]]} { |
|
||||||
append msg "No candidate project root found. " \n |
|
||||||
append msg "Searched upwards from '$start_dir' expecting a folder with the following requirements: " \n |
|
||||||
append msg [punk::repo::is_candidate_root_requirements_msg] \n |
|
||||||
} else { |
|
||||||
append msg "Candidate project root found at : $candidate" \n |
|
||||||
append msg " - consider putting this folder under fossil control (and/or git)" \n |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
set pathinfo [list];#exclude not found |
|
||||||
foreach repotype [list fossil git candidate] { |
|
||||||
set path [dict get $root_dict $repotype] |
|
||||||
if {[string length $path]} { |
|
||||||
set plen [llength [file split $path]] |
|
||||||
lappend pathinfo [list $repotype $path $plen] |
|
||||||
} |
|
||||||
} |
|
||||||
#these root are all inline towards root of drive - so anything of same length should be same path - shorter path must be above another |
|
||||||
#we will check equal depth paths are equal strings and raise an error just in case there are problems with the coding for the various path functions used here |
|
||||||
#longest path is 'closest' to start_dir |
|
||||||
set longest_first [lsort -index 2 $pathinfo] |
|
||||||
if {![llength $longest_first]} { |
|
||||||
#no repos or candidate - we have already created msg above |
|
||||||
} else { |
|
||||||
dict set root_dict closest [lindex $longest_first 0 1] ;#the *path* of the closest to start_dir - now we need to find all the types of this len |
|
||||||
#see if others same len |
|
||||||
set longestlen [lindex $longest_first 0 2] |
|
||||||
set equal_longest [lsearch -all -inline -index 2 $longest_first $longestlen] |
|
||||||
set ctypes [list] |
|
||||||
foreach pinfo $equal_longest { |
|
||||||
lappend ctypes [lindex $pinfo 0] |
|
||||||
} |
|
||||||
dict set root_dict closest_types $ctypes |
|
||||||
} |
|
||||||
|
|
||||||
if {[string length [set fosroot [dict get $root_dict fossil]]] && [string length [set gitroot [dict get $root_dict git]]]} { |
|
||||||
if {$fosroot ne $gitroot} { |
|
||||||
if {[path_a_above_b $fosroot $gitroot]} { |
|
||||||
append msg "Found git repo nested within fossil repo - be careful" \n |
|
||||||
append msg "** fos root : $fosroot ([punk::repo::path_relative $start_dir $fosroot])" \n |
|
||||||
append msg " * git root : $gitroot ([punk::repo::path_relative $start_dir $gitroot])" \n |
|
||||||
} else { |
|
||||||
append msg "Found fossil repo nested within git repo - be careful" \n |
|
||||||
append msg "** git root : $gitroot ([punk::repo::path_relative $start_dir $gitroot])" \n |
|
||||||
append msg " * fos root : $fosroot ([punk::repo::path_relative $start_dir $fosroot])" \n |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
dict set root_dict warnings $msg |
|
||||||
#some quick sanity checks.. |
|
||||||
set ctypes [dict get $root_dict closest_types] |
|
||||||
if {"project" in $ctypes} { |
|
||||||
if {"candidate" ni $ctypes} { |
|
||||||
set errmsg "find_roots_and_warnings_dict logic error: have project but not also classified as candidate (coding error in punk::repo) - inform developer\n" |
|
||||||
append errmsg " warnings gathered before error:\n $msg" |
|
||||||
error $errmsg |
|
||||||
} |
|
||||||
if {("git" ni $ctypes) && ("fossil" ni $ctypes)} { |
|
||||||
set errmsg "find_roots_and_warnings_dict logic error: have project but not also at least one of 'git', 'fossil' (coding error in punk::repo) - inform developer\n" |
|
||||||
append errmsg " warnings gathered before error:\n $msg" |
|
||||||
error $errmsg |
|
||||||
} |
|
||||||
} |
|
||||||
set ctype_paths [list] |
|
||||||
foreach ctype [dict get $root_dict closest_types] { |
|
||||||
lappend ctype_paths [lindex [dict get $root_dict $ctype] 1] ;# type, path, len |
|
||||||
} |
|
||||||
set unique [lsort -unique $ctype_paths] |
|
||||||
if {[llength $unique] > 1} { |
|
||||||
# this may be a filesystem path representation issue? case? normalisation? |
|
||||||
set errmsg "find_roots_and_warnings_dict logic error: different paths for closest folders found (error in punk::repo) - inform developer\n" |
|
||||||
append errmsg " warnings gathered before error:\n $msg" |
|
||||||
error $errmsg |
|
||||||
} |
|
||||||
|
|
||||||
return $root_dict |
|
||||||
} |
|
||||||
|
|
||||||
#------------------------------------ |
|
||||||
#limit to exec so full punk shell not required in scripts |
|
||||||
proc git_revision {{path {}}} { |
|
||||||
if {$path eq {}} { set path [pwd] } |
|
||||||
# ::kettle::path::revision.git |
|
||||||
do_in_path $path { |
|
||||||
try { |
|
||||||
#git describe will error with 'No names found' if repo has no tags |
|
||||||
#set v [::exec {*}[auto_execok git] describe] |
|
||||||
set v [::exec {*}[auto_execok git] rev-parse HEAD] ;# consider 'git rev-parse --short HEAD' |
|
||||||
} on error {e o} { |
|
||||||
set v [lindex [split [dict get $o -errorinfo] \n] 0] |
|
||||||
} |
|
||||||
} |
|
||||||
return [string trim $v] |
|
||||||
} |
|
||||||
proc git_remote {{path {{}}}} { |
|
||||||
if {$path eq {}} { set path [pwd] } |
|
||||||
do_in_path $path { |
|
||||||
try { |
|
||||||
#git describe will error with 'No names found' if repo has no tags |
|
||||||
#set v [::exec {*}[auto_execok git] describe] |
|
||||||
set v [::exec {*}[auto_execok git] -remote -v] ;# consider 'git rev-parse --short HEAD' |
|
||||||
} on error {e o} { |
|
||||||
set v [lindex [split [dict get $o -errorinfo] \n] 0] |
|
||||||
} |
|
||||||
} |
|
||||||
return [string trim $v] |
|
||||||
} |
|
||||||
|
|
||||||
proc fossil_revision {{path {}}} { |
|
||||||
if {$path eq {}} { set path [pwd] } |
|
||||||
# ::kettle::path::revision.fossil |
|
||||||
set fossilcmd [auto_execok fossil] |
|
||||||
if {[llength $fossilcmd]} { |
|
||||||
do_in_path $path { |
|
||||||
set info [::exec {*}$fossilcmd info] |
|
||||||
} |
|
||||||
return [lindex [grep {checkout:*} $info] 0 1] |
|
||||||
} else { |
|
||||||
return Unknown |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
proc fossil_remote {{path {}}} { |
|
||||||
if {$path eq {}} { set path [pwd] } |
|
||||||
# ::kettle::path::revision.fossil |
|
||||||
set fossilcmd [auto_execok fossil] |
|
||||||
if {[llength $fossilcmd]} { |
|
||||||
do_in_path $path { |
|
||||||
set info [::exec {*}$fossilcmd remote ls] |
|
||||||
} |
|
||||||
return [string trim $v] |
|
||||||
} else { |
|
||||||
return Unknown |
|
||||||
} |
|
||||||
} |
|
||||||
#------------------------------------ |
|
||||||
|
|
||||||
proc cksum_path_content {path args} { |
|
||||||
dict set args -cksum_content 1 |
|
||||||
dict set args -cksum_meta 0 |
|
||||||
tailcall cksum_path $path {*}args |
|
||||||
} |
|
||||||
#for full cksum - using tar could reduce number of hashes to be made.. |
|
||||||
#but as it stores metadata such as permission - we don't know if/how the archive will vary based on platform/filesystem |
|
||||||
#-noperms only available on extraction - so that doesn't help |
|
||||||
#Needs to operate on non-existant paths and return empty string in cksum field |
|
||||||
proc cksum_path {path args} { |
|
||||||
if {$path eq {}} { set path [pwd] } |
|
||||||
if {[file pathtype $path] eq "relative"} { |
|
||||||
set path [file normalize $path] |
|
||||||
} |
|
||||||
set base [file dirname $path] |
|
||||||
set startdir [pwd] |
|
||||||
|
|
||||||
set defaults [list -cksum_content 1 -cksum_meta 1 -cksum_acls 0 -use_tar 1] |
|
||||||
set opts [dict merge $defaults $args] |
|
||||||
if {![file exists $path]} { |
|
||||||
return [list cksum "" opts $opts] |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
set opt_cksum_acls [dict get $opts -cksum_acls] |
|
||||||
if {$opt_cksum_acls} { |
|
||||||
puts stderr "cksum_path is not yet able to cksum ACLs" |
|
||||||
return |
|
||||||
} |
|
||||||
set opt_cksum_meta [dict get $opts -cksum_meta] |
|
||||||
if {$opt_cksum_meta} { |
|
||||||
|
|
||||||
} else { |
|
||||||
if {[file type $path] ne "file"} { |
|
||||||
puts stderr "cksum_path doesn't yet support a content-only cksum of a folder structure. Currently only files supported without metadata. For folders use cksum_path -cksum_meta 1" |
|
||||||
return [list error unsupported opts $opts] |
|
||||||
} |
|
||||||
} |
|
||||||
set opt_use_tar [dict get $opts -use_tar] |
|
||||||
if {$opt_use_tar} { |
|
||||||
package require tar ;#from tcllib |
|
||||||
} else { |
|
||||||
if {[file type $path] eq "directory"} { |
|
||||||
puts stderr "cksum_path doesn't yet support -use_tar 0 for folders" |
|
||||||
return [list error unsupported opts $opts] |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if {$path eq $base} { |
|
||||||
#attempting to cksum at root/volume level of a filesystem.. extra work |
|
||||||
#This needs fixing for general use.. not necessarily just for project repos |
|
||||||
puts stderr "cksum_path doesn't yet support cksum of entire volume. (todo)" |
|
||||||
return [list error unsupported opts $opts] |
|
||||||
} |
|
||||||
set cksum "" |
|
||||||
if {$opt_use_tar} { |
|
||||||
set target [file tail $path] |
|
||||||
set tmplocation [tmpdir] |
|
||||||
set archivename $tmplocation/[tmpfile].tar |
|
||||||
|
|
||||||
cd $base ;#cd is process-wide.. keep cd in effect for as small a scope as possible. (review for thread issues) |
|
||||||
|
|
||||||
#temp emission to stdout.. todo - repl telemetry channel |
|
||||||
puts stdout "cksum_path: creating temporary tar archive at: $archivename .." |
|
||||||
tar::create $archivename $target |
|
||||||
puts stdout "cksum_path: calculating cksum for $target (size [file size $target])..." |
|
||||||
set cksum [crc::cksum -format 0x%X -file $archivename] |
|
||||||
puts stdout "cksum_path: cleaning up.. " |
|
||||||
file delete -force $archivename |
|
||||||
cd $startdir |
|
||||||
|
|
||||||
} else { |
|
||||||
#todo |
|
||||||
if {[file type $path] eq "file"} { |
|
||||||
if {$opt_cksum_meta} { |
|
||||||
return [list error unsupported opts $opts] |
|
||||||
} else { |
|
||||||
set cksum [crc::cksum -format 0x%X -file $path] |
|
||||||
} |
|
||||||
} else { |
|
||||||
error "cksum_path unsupported $opts for path type [file type $path]" |
|
||||||
} |
|
||||||
} |
|
||||||
set result [dict create] |
|
||||||
dict set result cksum $cksum |
|
||||||
dict set result opts $opts |
|
||||||
return $result |
|
||||||
} |
|
||||||
#temporarily cd to workpath to run script - return to correct path even on failure |
|
||||||
proc do_in_path {path script} { |
|
||||||
#from ::kettle::path::in |
|
||||||
set here [pwd] |
|
||||||
try { |
|
||||||
cd $path |
|
||||||
uplevel 1 $script |
|
||||||
} finally { |
|
||||||
cd $here |
|
||||||
} |
|
||||||
} |
|
||||||
proc scanup {path cmd} { |
|
||||||
if {$path eq {}} { set path [pwd] } |
|
||||||
#based on kettle::path::scanup |
|
||||||
if {[file pathtype $path] eq "relative"} { |
|
||||||
set path [file normalize $path] |
|
||||||
} |
|
||||||
while {1} { |
|
||||||
# Found the proper directory, per the predicate. |
|
||||||
if {[{*}$cmd $path]} { return $path } |
|
||||||
|
|
||||||
# Not found, walk to parent |
|
||||||
set new [file dirname $path] |
|
||||||
|
|
||||||
# Stop when reaching the root. |
|
||||||
if {$new eq $path} { return {} } |
|
||||||
if {$new eq {}} { return {} } |
|
||||||
|
|
||||||
# Ok, truly walk up. |
|
||||||
set path $new |
|
||||||
} |
|
||||||
return {} |
|
||||||
} |
|
||||||
#get content part of content/zip delimited by special \x1a (ctrl-z) char as used in tarjr and kettle::path::c/z |
|
||||||
proc c/z {content} { |
|
||||||
return [lindex [split $content \x1A] 0] |
|
||||||
} |
|
||||||
proc grep {pattern data} { |
|
||||||
set data [string map [list \r\n \n] $data] |
|
||||||
return [lsearch -all -inline -glob [split $data \n] $pattern] |
|
||||||
} |
|
||||||
|
|
||||||
proc rgrep {pattern data} { |
|
||||||
set data [string map [list \r\n \n] $data] |
|
||||||
return [lsearch -all -inline -regexp [split $data \n] $pattern] |
|
||||||
} |
|
||||||
|
|
||||||
proc tmpfile {{prefix tmp_}} { |
|
||||||
#note risk of collision if pregenerating a list of tmpfile names |
|
||||||
#we will maintain an icrementing id so the caller doesn't have to bear that in mind |
|
||||||
variable tmpfile_counter |
|
||||||
global tcl_platform |
|
||||||
return .punkrepo_$prefix[pid]_[clock microseconds]_[incr tmpfile_counter]_[info hostname]_$tcl_platform(user) |
|
||||||
} |
|
||||||
|
|
||||||
proc tmpdir {} { |
|
||||||
# Taken from tcllib fileutil. |
|
||||||
global tcl_platform env |
|
||||||
|
|
||||||
set attempdirs [list] |
|
||||||
set problems {} |
|
||||||
|
|
||||||
foreach tmp {TMPDIR TEMP TMP} { |
|
||||||
if { [info exists env($tmp)] } { |
|
||||||
lappend attempdirs $env($tmp) |
|
||||||
} else { |
|
||||||
lappend problems "No environment variable $tmp" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
switch $tcl_platform(platform) { |
|
||||||
windows { |
|
||||||
lappend attempdirs "C:\\TEMP" "C:\\TMP" "\\TEMP" "\\TMP" |
|
||||||
} |
|
||||||
macintosh { |
|
||||||
lappend attempdirs $env(TRASH_FOLDER) ;# a better place? |
|
||||||
} |
|
||||||
default { |
|
||||||
lappend attempdirs \ |
|
||||||
[file join / tmp] \ |
|
||||||
[file join / var tmp] \ |
|
||||||
[file join / usr tmp] |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
lappend attempdirs [pwd] |
|
||||||
|
|
||||||
foreach tmp $attempdirs { |
|
||||||
if { [file isdirectory $tmp] && |
|
||||||
[file writable $tmp] } { |
|
||||||
return [file normalize $tmp] |
|
||||||
} elseif { ![file isdirectory $tmp] } { |
|
||||||
lappend problems "Not a directory: $tmp" |
|
||||||
} else { |
|
||||||
lappend problems "Not writable: $tmp" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
# Fail if nothing worked. |
|
||||||
return -code error "Unable to determine a proper directory for temporary files\n[join $problems \n]" |
|
||||||
} |
|
||||||
|
|
||||||
#todo - review |
|
||||||
proc ensure-cleanup {path} { |
|
||||||
#::atexit [lambda {path} { |
|
||||||
#file delete -force $path |
|
||||||
#} [norm $path]] |
|
||||||
|
|
||||||
file delete -force $path |
|
||||||
} |
|
||||||
|
|
||||||
proc path_relative {base dst} { |
|
||||||
#see also kettle |
|
||||||
# Modified copy of ::fileutil::relative (tcllib) |
|
||||||
# Adapted to 8.5 ({*}). |
|
||||||
# |
|
||||||
# Taking two _directory_ paths, a base and a destination, computes the path |
|
||||||
# of the destination relative to the base. |
|
||||||
# |
|
||||||
# Arguments: |
|
||||||
# base The path to make the destination relative to. |
|
||||||
# dst The destination path |
|
||||||
# |
|
||||||
# Results: |
|
||||||
# The path of the destination, relative to the base. |
|
||||||
|
|
||||||
# Ensure that the link to directory 'dst' is properly done relative to |
|
||||||
# the directory 'base'. |
|
||||||
|
|
||||||
if {[file pathtype $base] ne [file pathtype $dst]} { |
|
||||||
return -code error "Unable to compute relation for paths of different pathtypes: [file pathtype $base] vs. [file pathtype $dst], ($base vs. $dst)" |
|
||||||
} |
|
||||||
|
|
||||||
set base [norm $base] |
|
||||||
set dst [norm $dst] |
|
||||||
|
|
||||||
set save $dst |
|
||||||
set base [file split $base] |
|
||||||
set dst [file split $dst] |
|
||||||
|
|
||||||
while {[lindex $dst 0] eq [lindex $base 0]} { |
|
||||||
set dst [lrange $dst 1 end] |
|
||||||
set base [lrange $base 1 end] |
|
||||||
if {![llength $dst]} {break} |
|
||||||
} |
|
||||||
|
|
||||||
set dstlen [llength $dst] |
|
||||||
set baselen [llength $base] |
|
||||||
|
|
||||||
if {($dstlen == 0) && ($baselen == 0)} { |
|
||||||
# Cases: |
|
||||||
# (a) base == dst |
|
||||||
|
|
||||||
set dst . |
|
||||||
} else { |
|
||||||
# Cases: |
|
||||||
# (b) base is: base/sub = sub |
|
||||||
# dst is: base = {} |
|
||||||
|
|
||||||
# (c) base is: base = {} |
|
||||||
# dst is: base/sub = sub |
|
||||||
|
|
||||||
while {$baselen > 0} { |
|
||||||
set dst [linsert $dst 0 ..] |
|
||||||
incr baselen -1 |
|
||||||
} |
|
||||||
set dst [file join {*}$dst] |
|
||||||
} |
|
||||||
|
|
||||||
return $dst |
|
||||||
} |
|
||||||
|
|
||||||
#literate-programming style naming for some path tests |
|
||||||
#Note the naming of the operator portion of a_op_b is consistent in that it is the higher side of the filesystem tree first. |
|
||||||
#hence aboveorat vs atorbelow |
|
||||||
#These names also sort in the logical order of higher to lower in the filesystem (when considering the root as 'higher' in the filesystem) |
|
||||||
proc path_a_above_b {path_a path_b} { |
|
||||||
#stripPath prefix path |
|
||||||
return [expr {[fileutil::stripPath $path_a $path_b] ni [list . $path_b]}] |
|
||||||
} |
|
||||||
proc path_a_aboveorat_b {path_a path_b} { |
|
||||||
return [expr {[fileutil::stripPath $path_a $path_b] ne $path_b}] |
|
||||||
} |
|
||||||
proc path_a_at_b {path_a path_b} { |
|
||||||
return [expr {[fileutil::stripPath $path_a $path_b] eq "." }] |
|
||||||
} |
|
||||||
proc path_a_atorbelow_b {path_a path_b} { |
|
||||||
return [expr {[fileutil::stripPath $path_b $path_a] ne $path_a}] |
|
||||||
} |
|
||||||
proc path_a_below_b {path_a path_b} { |
|
||||||
return [expr {[fileutil::stripPath $path_b $path_a] ni [list . $path_a]}] |
|
||||||
} |
|
||||||
proc path_a_inlinewith_b {path_a path_b} { |
|
||||||
return [expr {[path_a_aboveorat_b $path_a $path_b] || [path_a_below_b $path_a $path_b]}] |
|
||||||
} |
|
||||||
|
|
||||||
#whether path is at and/or below one of the vfs mount points |
|
||||||
#The design should facilitate nested vfs mountpoints |
|
||||||
proc path_vfs_info {filepath} { |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
#file normalize is expensive so this is too |
|
||||||
proc norm {path {platform env}} { |
|
||||||
#kettle::path::norm |
|
||||||
#see also wiki |
|
||||||
#full path normalization |
|
||||||
|
|
||||||
set platform [string tolower $platform] |
|
||||||
if {$platform eq "env"} { |
|
||||||
set platform $::tcl_platform(platform) |
|
||||||
} |
|
||||||
if {$platform eq "windows"} { |
|
||||||
return [file dirname [file normalize [punk::winpath::winpath $path]/__]] |
|
||||||
} else { |
|
||||||
return [file dirname [file normalize $path/__]] |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#This taken from kettle::path::strip |
|
||||||
#It doesn't compare the prefix contents presumably for speed when used in kettle::path::scan |
|
||||||
#renamed to better indicate its behaviour |
|
||||||
|
|
||||||
proc path_strip_prefixdepth {path prefix} { |
|
||||||
return [file join \ |
|
||||||
{*}[lrange \ |
|
||||||
[file split [norm $path]] \ |
|
||||||
[llength [file split [norm $prefix]]] \ |
|
||||||
end]] |
|
||||||
} |
|
||||||
#MUCH faster version for absolute path prefix (pre-normalized) |
|
||||||
proc path_strip_alreadynormalized_prefixdepth {path prefix} { |
|
||||||
return [file join \ |
|
||||||
{*}[lrange \ |
|
||||||
[file split $path] \ |
|
||||||
[llength [file split $prefix]] \ |
|
||||||
end]] |
|
||||||
} |
|
||||||
|
|
||||||
proc fcat {args} { |
|
||||||
if {$::tcl_platform(platform) ne "windows"} { |
|
||||||
return [fileutil::cat {*}$args] |
|
||||||
} |
|
||||||
|
|
||||||
set knownopts [list -eofchar -translation -encoding --] |
|
||||||
set last_opt 0 |
|
||||||
for {set i 0} {$i < [llength $args]} {incr i} { |
|
||||||
set ival [lindex $args $i] |
|
||||||
#puts stdout "i:$i a: $ival known: [expr {$ival in $knownopts}]" |
|
||||||
if {$ival eq "--"} { |
|
||||||
set last_opt $i |
|
||||||
break |
|
||||||
} else { |
|
||||||
if {$ival in $knownopts} { |
|
||||||
#puts ">known at $i : [lindex $args $i]" |
|
||||||
if {($i % 2) != 0} { |
|
||||||
error "unexpected option at index $i. known options: $knownopts must come in -opt val pairs." |
|
||||||
} |
|
||||||
incr i |
|
||||||
set last_opt $i |
|
||||||
} else { |
|
||||||
set last_opt [expr {$i - 1}] |
|
||||||
break |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
set first_non_opt [expr {$last_opt + 1}] |
|
||||||
|
|
||||||
#puts stderr "first_non_opt: $first_non_opt" |
|
||||||
set opts [lrange $args -1 $first_non_opt-1] |
|
||||||
set paths [lrange $args $first_non_opt end] |
|
||||||
if {![llength $paths]} { |
|
||||||
error "Unable to find file in the supplied arguments: $args. Ensure options are all -opt val pairs and that file name(s) follow" |
|
||||||
} |
|
||||||
#puts stderr "opts: $opts paths: $paths" |
|
||||||
set finalpaths [list] |
|
||||||
foreach p $paths { |
|
||||||
if {[punk::winpath::illegalname_test $p]} { |
|
||||||
lappend finalpaths [punk::winpath::illegalname_fix $p] |
|
||||||
} else { |
|
||||||
lappend finalpaths $p |
|
||||||
} |
|
||||||
} |
|
||||||
fileutil::cat {*}$opts {*}$finalpaths |
|
||||||
} |
|
||||||
|
|
||||||
interp alias {} is_fossil {} ::punk::repo::is_fossil |
|
||||||
interp alias {} is_fossil_root {} ::punk::repo::is_fossil_root |
|
||||||
interp alias {} find_fossil {} ::punk::repo::find_fossil |
|
||||||
interp alias {} fossil_revision {} ::punk::repo::fossil_revision |
|
||||||
interp alias {} is_git {} ::punk::repo::is_git |
|
||||||
interp alias {} is_git_root {} ::punk::repo::is_git_root |
|
||||||
interp alias {} find_git {} ::punk::repo::find_git |
|
||||||
interp alias {} git_revision {} ::punk::repo::git_revision |
|
||||||
|
|
||||||
|
|
||||||
interp alias {} gs {} git status -sb |
|
||||||
interp alias {} gr {} ::punk::repo::git_revision |
|
||||||
interp alias {} gl {} git log --oneline --decorate ;#decorate so stdout consistent with what we see on console |
|
||||||
interp alias {} glast {} git log -1 HEAD --stat |
|
||||||
interp alias {} gconf {} git config --global -l |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
## Ready |
|
||||||
package provide punk::repo [namespace eval punk::repo { |
|
||||||
variable version |
|
||||||
set version 0.1.0 |
|
||||||
}] |
|
||||||
return |
|
Loading…
Reference in new issue