Version 19 of Wiki History Diff

Updated 2004-01-23 15:15:57

Keith Vetter 2003-11-07 : There's been a recent hue and cry for "a good diffs module" (see There is a huge need for a diffs module!). Here's something I've been using for half a year or so.

You enter a wiki page number and it displays the revision history for that page. You then select any two entries, click a button and it will fetch those two revisions and run tkdiff on them (or if you prefer you can use ExamDiff [L1 ]).

This works reasonably well but with two flaws: first, the information in the wiki history seems to be a day behind the wiki itself, and second, wiki pages often contain very long lines which don't have good diff behavior. WikiDiff handle this second problem in a very clever way by doing a word level diff.

KPV It seems these two flaws have been addressed. The first via an undocumented interface which lets you access the latest version; the second by the new version of tkdiff (4.0b1) that does inline comparisons.


Keith, FYI, on Mac OS X the above needs font "courier 10" to be readable (with X11). Also note that this works fine with the tkdiff starkit on sdarchive. Great utility! -jcw


This is probably a better place to write this than on the Diffs Code Module in progress page. --PS

If anyone is interested, I have a port of wikidiff into wikit at [L2 ]. I have had it running for over a year now, sorry for not contributing sooner ;-).

It still uses diff/patch from the filesystem, either the standard unix ones or cygwin versions, it should be easily changed to tcllib/diff (does it have patch too?).

There are some bugs in the display code, but it *does* store the history in a chain of diffs either in wikit.tkd or a separate history.tkd flawlessly.

 if {0} {
 Usage:

 tclkit wikit-hist.kit my-wikit.tkd -history internal

 or 

 tclkit wikit-hist.kit my-wikit.tkd -history /somewhere/history.tkd
 }

To access the revision history, you need to run it as a CGI (no internal httpd in this version), you will find that the 'Updated on somedate' at the bottom of each wiki page is now augmented with a revision X link, click on that and you can get at the diffs. And you also need to set WIKIT_CSS=http://pascal.scheffers.net/pascal.css in your wiki.cgi file to have the colours in the diff show.

-- PS


Yes, I'm interested (as you know!). Will have a look, thx. Another comment on Keith's code: it currently diffs against pages in history but not the last one, if it was changed very recently. There is an undocumented feature of this wiki: you can access raw pages as "NNN.txt" (e.g. [L3 ]). - jcw

KPV 2003-11-10: that's just the feature this tool needs. I've upgraded the code to always list the latest version as one of the available version. This will be a (harmless) redundancy for most pages but quite useful for the critical ones.


KPV 2004-01-23: Added Recents window, an Tkhtml widget showing Wiki page 4--Recent changes. Clicking on any link will put it's Wiki page number into the Wiki Page entry. Double clicking any link will fetch that page's history.


 ##+##########################################################################
 #
 # Wiki History Diff.tcl -- diffs back versions of the tcl Wiki pages
 # by Keith Vetter
 #
 # Revisions:
 # KPV Mar 11, 2003 - initial revision
 # KPV Nov 07, 2003 - some touch up work
 # KPV Nov 10, 2003 - added JCW's undocumented way of fetching latest version
 # KPV Jan 23, 2004 - added Recents window showing Wiki page 4
 #

 package require Tk
 package require http
 set S(tkhtml) [expr {! [catch {package require Tkhtml}]}]

 set S(diffprog) tkdiff                          ;# Examdiff.exe works well too
 set S(title) "Wiki History Diff"
 set S(bg) "#9977cc"
 set S(pnum) "xx"                                ;# Page we want to compare
 set S(pnum2) ""                                 ;# Entry widget version of pnum
 set S(busy) 0
 set S(font) {courier 8}
 if {$tcl_platform(machine) == "Power Macintosh"} {
    set S(font) {courier 10}
 } 

 # Stupid temp directory
 set S(tmp) [pwd]
 if {[file isdirectory "c:/temp"]} { set S(tmp) "c:/temp"}
 if {[file isdirectory "/tmp"]} { set S(tmp) "/tmp"}
 catch {set S(tmp) $env(TRASH_FOLDER)}           ;# Macintosh(?)
 catch {set S(tmp) $env(TMPDIR)}
 catch {set S(tmp) $env(TMP)}
 catch {set S(tmp) $env(TEMP)}

 proc INFO {msg} {puts [set ::S(msg) $msg]}
 proc DoDisplay {} {
    global S

    wm title . "Wiki History Diff"
    wm protocol . WM_DELETE_WINDOW {Cleanup 1}
    frame .bottom
    pack .bottom -side bottom -fill x

    button .recent -text "Show Recent Changes" -command Recents
    if {! $S(tkhtml)} { .recent config -state disabled}
    label .msg -textvariable S(msg)
    button .about -text About -command About
    pack .recent -in .bottom -side left -padx .1i
    pack .about -in .bottom -side right -padx .1i
    pack .msg -in .bottom -side bottom -fill x

    option add *Label.background $S(bg)
    option add *Label.foreground white
    option add *Button.background $S(bg)
    option add *Button.activeBackground $S(bg)
    option add *Button.foreground white
    option add *Button.activeForeground white

    frame .top -bg $S(bg) -relief groove -bd 2
    frame .topl -bg $S(bg)
    button .topl.wiki -text "Wiki Page " -command DoWikiPage -relief flat
    entry .topl.ewiki -textvariable S(pnum2) -relief sunken -width 6
    bind .topl.ewiki <Key-Return> [list .topl.wiki invoke]
    label .top.title -textvariable S(title) -fg magenta
    trace variable S(pnum2) w tracer

    set font [font actual [.top.title cget -font]]
    .top.title config -font "$font -weight bold -size 18"

    frame .topr -bg $S(bg)
    label .topr.l0 -text "Version "
    label .topr.e0 -textvar S(0) -width 5 -relief sunken
    label .topr.v -text " vs. "
    label .topr.l1 -text "Version "
    label .topr.e1 -textvar S(1) -width 5 -relief sunken
    label .topr.= -text " => "
    button .topr.diff -text "Run $S(diffprog)" -command RunTKDiff -bd 5

    pack .top -side top -fill x
    pack .topr -in .top -side right
    pack .topl -in .top -side left
    pack .top.title -side top -expand 1
    eval pack [winfo child .topl] -side left
    eval pack [winfo child .topr] -side left
    pack config .topl.wiki -padx {.1i .05i}
    pack config .topr.diff -padx {0 .1i}

    pack [frame .mid -bd 4 -relief ridge] -side top -fill both -expand 1
    foreach w {0 1} {
        listbox .l$w -exportselection 0 -width 63 -activestyle none -bd 0 \
            -font {courier 8} -yscrollcommand [list .sb$w set] -height 20 
        bind .l$w <<ListboxSelect>> [list ListboxSelect %W $w]
        bind .l$w <1> [list focus %W]
        scrollbar .sb$w -orient v -command [list .l$w yview]
    }
    grid .l0 .sb0 .l1 .sb1 -in .mid -sticky news
    grid rowconfigure .mid 0 -weight 1
    grid columnconfigure .mid {0 2} -weight 1
    option clear
    bind all <Key-F2> {console show}

    focus .topl.ewiki
 }
 proc ListboxSelect {W who} {
    global S
    set row [$W curselection]
    set data [$W get $row]
    set S($who) [lindex $data 1]
    if {! [string is integer -strict $S($who)]} {
        set S($who) "Latest"
    }
 }
 proc tracer {var1 var2 op} {
    global S

    if {$S(pnum2) == $S(pnum)} {
        .topl.ewiki config -bg [lindex [.topl.ewiki config -bg] 3]
        .topl.wiki config -relief flat
    } else {
        .topl.ewiki config -bg red
        .topl.wiki config -relief raised
    }
 }
 proc ReInit {} {
    global S
    set S(cnt) 0
 }
 proc GetVersionInfo {} {
    global S

    .l0 delete 0 end
    .l1 delete 0 end
    update

    GetTitle
    INFO "Getting history for page $S(pnum)"
    set url "http://mini.net/tclhist/$S(pnum)*"
    set token [::http::geturl $url]
    set data [::http::data $token]
    ::http::cleanup $token

    set S(cnt) 1
    .l0 insert end " Latest Version"
    .l1 insert end " Latest Version"
    foreach {version tstamp who c1 c2} $data {
        set when [clock format $tstamp -gmt 1 -format "%e %b %Y %T %Z"]
        set txt [format " version %2s  %16s  %s $c1 $c2" $version $who $when]
        .l0 insert end $txt
        .l1 insert end $txt
        incr S(cnt)
    }
    set S(0) [set S(1) ""]
    if {$S(cnt) > 0} {
        foreach i {0 1} {
            set el [expr {$S(cnt) > 1 ? 1 - $i : 0}]
            .l$i selection clear 0 end
            .l$i selection set $el
            .l$i selection anchor $el
            event generate .l$i <<ListboxSelect>>
        }
    } else {
        .l0 insert end <empty>
        .l1 insert end <empty>
    }
    set msg "Wiki page $S(pnum) has $S(cnt) version"
    if {$S(cnt) != 1} {append msg s}
    INFO $msg
 }
 proc GetTitle {} {
    global S
    INFO "Getting title for page $S(pnum)"
    set url "http://mini.net/tclhist/$S(pnum)"
    set token [::http::geturl $url]
    set data [::http::data $token]
    ::http::cleanup $token

    set S(title) "No Wiki History"
    regexp -line {Title:\s*(.*)} $data => S(title)
    set S(title) "\"$S(title)\""
 }
 proc GetVersion {pnum ver} {
    set fname [file join $::S(tmp) "wiki.$pnum.$ver"]
    set fout [open $fname "w"]

    if {[string is integer -strict $ver]} {
        set url "http://mini.net/tclhist/$pnum.$ver"
    } else {
        set url "http://mini.net/tcl/$pnum.txt" ;# Hidden feature
    }
    set token [::http::geturl $url -channel $fout]
    close $fout
    ::http::cleanup $token

    return $fname
 }
 proc RunTKDiff {} {
    global S

    Cleanup
    if {$S(0) == "" || $S(1) == ""} return
    set f1 [GetVersion $S(pnum) $S(0)]
    set f2 [GetVersion $S(pnum) $S(1)]

    exec $S(diffprog) $f1 $f2 &
    set ::TMPFILES($f1) 1
    set ::TMPFILES($f2) 1
    after 2000                                  ;# Pause to let tkdiff start
 }
 proc DoWikiPage {} {
    global S

    set S(busy) 1
    while {1} {
        after 10 Cleanup
        .topl.ewiki icursor end
        if {! [string is integer -strict $S(pnum2)]} break
        if {$S(pnum) == $S(pnum2)} break
        set S(pnum) $S(pnum2)
        GetVersionInfo
        set S(pnum2) $S(pnum2)
    }
    set S(busy) 0
 }
 proc Cleanup {{exit 0}} {
    global TMPFILES

    foreach fname [array names TMPFILES] {
        set n [catch {file delete $fname}]
        if {! $n} { unset TMPFILES($fname) }
    }
    if {$exit} exit
 }
 proc About {} {
    set msg "WikiDiff\nby Keith Vetter\nMarch 2003\n\n"
    append msg "Compares back revisions of a\nTcl'ers Wiki "
    append msg "page using tkdiff."
    tk_messageBox -title "About" -message $msg
 }
 proc Recents {} {
    if {! $::S(tkhtml)} return
    destroy .r
    toplevel .r
    wm title .r "WIKI Recent Changes"
    wm transient .r .

    scrollbar .r.v -o v -command {.r.h yv}
    html .r.h -yscrollcommand {.r.v set} -background white
    bind .r.h.x <1> [list Click 1 %x %y]    
    bind .r.h.x <Double-Button-1> [list Click 2 %x %y]    
    bind .r.h <MouseWheel> [bind Text <MouseWheel>]

    pack .r.v -side right -fill y
    pack .r.h -side left -fill both -expand 1

    set url "http://mini.net/tcl/4.html"
    set token [::http::geturl $url]
    set data [::http::data $token]
    ::http::cleanup $token

    .r.h clear
    .r.h config -base $url
    .r.h parse $data
    focus .r.h
 }
 proc Click {cnt x y} {
    if {$::S(busy)} return
    set href [.r.h href $x $y]
    if {$href == ""} return
    set n [regexp {/(\d+)\}?$} $href => pnum]
    if {! $n} return

    set ::S(pnum2) $pnum
    if {$cnt == 2} {
        .topl.wiki invoke
    }
 }
 DoDisplay
 set S(pnum2) [lindex $argv 0]
 .l0 insert end "Enter the number of a Wiki page to run diff on"
 return

Category Wikit