ADDED fslrc Index: fslrc ================================================================== --- fslrc +++ fslrc @@ -0,0 +1,324 @@ +# -*-tcl-*- + +# -- Aliases: + +alias . changes +alias d diff +alias , ui +alias log timeline +alias heads leaves; # for hg refugees + +# -- Filters: + +filter status {changes status timeline add rm addremove} { + lassign [split [string trim $line]] status + switch $status { + MERGED_WITH { coloured purple $line } + MISSING { coloured yellow $line } + ADDED { coloured green $line } + EDITED { coloured cyan $line } + DELETED { coloured red $line } + default { set line } + } +} + +filter log_entry {leaves timeline} { + if {[regexp "^=== .* ===" $line]} { + coloured blue $line + } else { + regsub -all {\[[A-Fa-f0-9]+\]} $line [coloured yellow &] + } +} + +# Filter on alias `d' instead of `diff' so that output can be +# redirected to create patch files. +filter diff {d} { + switch -regexp $line { + {^-} { coloured red $line } + {^\+} { coloured green $line } + {^@@} { coloured yellow $line } + default { set line } + } +} + +filter highlight_branch {branch} { + expr {[regexp {^\* } $line] ? [coloured yellow $line] : $line} +} + +# record: interactive commits +# +# We add two commands which provide interactive commits: +# +# - rec, record: interactive version of 'commit' +# - rst, rstash: interactive version of 'stash' when used with 'save' +# are 'snapshot' subcommands +# - stash: accepts a --record option which works like 'rstash' + + +# askrecord question ask +# - ask == yes ... accept without question +# - ask == no ... decline without question +# - otherwise ... ask +# +# Return values +# - y ... do record +# - n ... do not record +# - s ... skip remaining changes of this file +# - d ... skip all remaining changes +# - f ... record remaining changes of this file +# - q ... abort, do not record anything +proc askrecord {question ask} { + if {$ask == yes} { return f } + if {$ask == no} { return n } + while yes { + puts -nonewline [coloured yellow "$question \[Ynsfdaq?\] "] + flush stdout + gets stdin answer + if {$answer == {}} { set answer y } + switch $answer { + n { return n } + s { return s } + f { return f } + d { return d } + q { return q } + y { return y } + a { return a } + ? { + puts "y - yes, record this change" + puts "n - no, do not record this change" + puts "s - skip remaining changes to this file" + puts "f - record remaining changes to this file" + puts "d - done, skip remaining changes and files" + puts "a - record all remaining changes to all remaining files" + puts "q - quit, record no changes" + puts "? - help, show this message" + } + default { puts "ERROR: invalid command" } + } + } +} + +# colourizehunk hunk +# - hunk: a hunk of a diff to be colourized +# +# Returns the colourized hunk +# - removals in red +# - additions in green +# - info lines in magenta +proc colourizehunk {hunk} { + set result {} + foreach line [split $hunk "\n"] { + lappend result \ + [switch -regexp $line { + {^-} { coloured red $line } + {^\+} { coloured green $line } + {^@@} { coloured magenta $line } + default { set line } + }] + } + return [join $result "\n"] +} + +# recordhunks files params +# - files is a list of filenames to be recorded +# - params are additional parameters to the underlying command (e.g. +# 'commit') +# + +# The function generates a unified diff of the specified files. The +# diff is split into files and hunks. Then the function asks +# interactive questions for each file and hunk to select those hunks +# that should be committed. The not selected hunks are use to build a +# patch. The inverse of this patch is applied to the working directory +# leaving only the selected changes. Then the underlying commit +# command is executed and afterwards the patch is used to restore the +# non-selected changes. +proc recordhunks {files params} { + set rgxsplit {^(?:Index: .*$\n=+$\n)?^--- ([^\n]*)$\n^\+\+\+ ([^\n]*)$\n((^.+$\n)*)} + set rgxhunk {^@@.*@@$\n((^[^@I].*$\n)*)} + + # get repo root + set workdir [pwd] + if {[llength $files] == 0} { + catch {exec fossil info} inf + regexp -line {local-root:\s*(\S.*)} $inf m workdir + } + + # get diff and split into files and hunks + catch {exec fossil diff -i -c 1 {*}$files} diff + + set files {} + set start 0 + set nhunks 0 + while {[regexp -start $start -indices -line $rgxsplit $diff all inf outf hunk] == 1} { + set htxt [string range $diff {*}$hunk] + set hunks {} + set hstart 0 + while {[regexp -start $hstart -indices -line $rgxhunk $htxt hall] == 1} { + lappend hunks [string range $htxt {*}$hall] + set hstart [lindex $hall 1] + } + lappend files [list \ + [string range $diff {*}$inf] \ + [string range $diff {*}$outf] \ + $hunks] + incr nhunks [llength $hunks] + set start [lindex $all 1] + } + + # iterate through files/hunks and ask some questions + set recfiles {} + set norecfiles {} + set askfile ask + set ifile 0 + set nfiles [llength $files] + set ihunk 0 + foreach file $files { + lassign $file inf outf hunks + + incr ifile + set answer [askrecord "examine file '$inf' \[$ifile/$nfiles\]?" $askfile] + set askhunk ask + switch $answer { + n { set askhunk no } + s { set askhunk no } + f { set askhunk yes } + d { + set askfile no + set askhunk no + } + a { + set askfile yes + set askhunk yes + } + q { return {} } + } + + set rechunks {} + set norechunks {} + foreach hunk $hunks { + incr ihunk + if {$askhunk == "ask"} { + puts [colourizehunk $hunk] + } + set answer [askrecord "record hunk \[$ihunk/$nhunks\]?" $askhunk] + switch $answer { + y { set dorec yes } + n { set dorec no } + s { + set dorec no + set askhunk no + } + f { + set dorec yes + set askhunk yes + } + d { + set dorec no + set askfile no + set askhunk no + } + a { + set dorec yes + set askfile yes + set askhunk yes + } + q { return {} } + } + if $dorec { + lappend rechunks $hunk + } else { + lappend norechunks $hunk + } + } + + if {[llength $rechunks] > 0} { + lappend recfiles [list $inf $outf $rechunks] + } + if {[llength $norechunks] > 0} { + lappend norecfiles [list $inf $outf $norechunks] + } + } + + set fh [open fsl-record.patch w 0600] + puts $fh $diff + close $fh + + # build patch of changed *not* selected + set nopatch {} + foreach file $norecfiles { + lassign $file inf outf hunks + lappend nopatch "--- $inf\n" + lappend nopatch "+++ $outf\n" + foreach hunk $hunks { + lappend nopatch $hunk + } + lappend nopatch "\n" + } + set nopatch [join $nopatch {}] + set fh [open fsl-norecord.patch w 0600] + puts $fh $nopatch + close $fh + + set dir [pwd] + cd $workdir + exec patch -p0 -R << $nopatch + catch {fossil {*}$params} + exec patch -p0 << $nopatch + cd $dir + + file delete fsl-record.patch + file delete fsl-norecord.patch + + return {} +} + +# record: interactive version of 'commit' +interceptor rec:record { + lset params 0 commit + set files {} + for {set i 1} {$i < [llength $params]} {incr i} { + set p [lindex $params $i] + switch -regexp $p { + {^(-m|--message|-M|--message-file|--mimetype|--bgcolor|--branch|--branchcolor|--tag)$} { incr i } + {^-} {} + default { lappend files $p } + } + } + return [recordhunks $files $params] +} + +# rstash: interactive version of 'stash save' and 'stash snapshot' +interceptor rst:rstash { + lset params 0 stash + if {[llength $params] <= 1} { + return [recordhunks {} $params] + } elseif {[regexp {^(-.*|save|snapshot)$} [lindex $params 1]] == 1} { + set files {} + for {set i 2} {$i < [llength $params]} {incr i} { + set p [lindex $params $i] + switch -regexp $p { + {^(-m|--comment)$} { incr i } + {^-} {} + default { lappend files $p } + } + } + return [recordhunks $files $params] + } else { + return $params + } +} + +# stash --record: additional argument for stash for using interactive commits +interceptor stash { + if {[regexp -indices -- {[[:blank:]]+(--record)} $params {} rec] == 1} { + set params [string replace $params {*}$rec] + lset params 0 rstash + fossil {*}$params + return {} + } else { + return $params + } +} + +# vim: ft=tcl