Version 1 of A tiny version control

Updated 2006-01-30 09:10:15 by suchenwi

Richard Suchenwirth 2006-01-30 - This Sunday fun project was created because I had no fun on Saturday - coding merrily for Sepp, I suddenly reached a point when the NoteBook widget did not react, and I wasn't exactly clear what changes since the "last know good" state had caused that. At work, I use CVS for version control, so it's easy to compare the current with earlier versions - but on the cellphone? So I decided I need some version control.

At currently 61 lines of code, this is of course a far cry from the "real things" like CVS. It's also less efficient in memory - previous versions are stored fully, not as diff. (But this is not so much of a concern, when pocket source files tend to be short anyway, not much more than a K or two.) For a version-controlled file foo.bar, there are in the same directory copies named foo#0.bar, foo#1.bar etc. (somehow like on old VAX/VMS), the highest version number being the HEAD. If the "top" instance (the one without #) is equal to HEAD, it is considered up-to-date.

Usage:

 ver commit filename 

Copies the top to new head, one higher than the current, if not up-to-date. (Also does cvs add-like: starts at 0 when the file wasn't versioned yet)

 ver diff filename ?n?

Compares the top with the given numbered version (HEAD if no n given) line by line, displays the first three differences. (This is still crude, but already helpful on little changes). Returns "identical" if so, else {}.

 ver status filename

Returns "up-to-date with #x", "locally modified - last: #x", or "nothing known" when there are no numbered versions.

 ver tell filename

Returns the sorted list of available version numbers for the given top file, HEAD first.


 proc ver {cmd {filename ""} {n ""}} {
   set c [info procs ver::$cmd*]
   if {[llength $c] != 1} {error "unknown $cmd - use one of: [ver::_cmds]"}
   $c $filename $n
 }

 namespace eval ver { }

 proc ver::commit {fn n} {
   set last [_head $fn]
   if {[_equal $fn "" $last] && $last ne ""} {
       return up-to-date
   }
   if {$last eq ""} {set last -1}
   file copy $fn [_fmt $fn [incr last]]
   set last
 }

 proc ver::diff {fn n} {
   set f1 [open $fn]
   if {$n eq ""} {set n  [_head $fn]}
   if {$n eq ""} {return "nothing to diff"}
   set f2 [open [_fmt $fn $n]]
   set diff 0
   while {[gets $f1 a]>=0 \
        && [gets $f2 b]>=0} {
      if {$a ne $b} {
          puts "< $a\n> $b"; incr diff
          if {$diff>3} break
      }
   }
   close $f1; close $f2
   if !$diff {return identical}
 }

 proc ver::status {fn {n ""}} {
   set last [lindex [tell $fn] 0]
   if {$last eq ""} {return "nothing known"}
   if [_equal $fn "" $last] {
       return "up-to-date with #$last"
   } else {return "locally modified - #$last"}
 }

 proc ver::tell {fn {n ""}} {
   set res {}
   foreach f [glob -noc [_fmt $fn *]] {
       regexp {#(.+)\.} $f -> no
       lappend res $no
   }
   lsort -integer -dec $res
 }

#-- Internal functions start with _

 proc ver::_cmds {} {
   lsort [string map {::ver:: ""} \
       [info procs {::ver::[a-z]*}]]
 }
 proc ver::_equal {fn v1 v2} {
   expr {[file mtime [_fmt $fn $v1]]\
        ==[file mtime [_fmt $fn $v2]]}
 }
 proc ver::_fmt {fn n} {
   if {$n eq ""} {return $fn}
   return [file root $fn]#$n[file ext $fn]
 }
 proc ver::_head fn {lindex [tell $fn] 0}

Category Development - Arts and crafts of Tcl-Tk programming