#
# TCL Library for TkCVS
#

#
# $Id: cvs.tcl,v 1.127 2004/03/18 06:36:29 dorothyr Exp $
# 
# Contains procedures used in interaction with CVS.
#

proc cvs_notincvs {} {
  cvsfail "This directory is not in CVS."
}

proc cvs_incvs {} {
  cvsfail "You can\'t do that here because this directory is already in CVS."
}

#
#  Create a temporary directory
#  cd to that directory
#  run the CVS command in that directory
#
#  returns: the current wd (ERROR) or the sandbox directory (OK)
#
proc cvs_sandbox_runcmd {cmd output_var} {
  global cvscfg
  global cwd

  upvar $output_var view_this

  # Big note: the temp directory fed to a remote servers's command line
  # needs to be seen by the server.  It can't cd to an absolute path.
  # In addition it's fussy about where you are when you do a checkout -d.
  # Best avoid that altogether.
  gen_log:log T "ENTER ($cmd $output_var)"
  set pid [pid]
  
  cd $cvscfg(tmpdir)
  gen_log:log F "CD [pwd]"
  if {! [file isdirectory cvstmpdir.$pid]} {
    gen_log:log F "MKDIR cvstmpdir.$pid"
    file mkdir cvstmpdir.$pid
  }

  cd cvstmpdir.$pid
  gen_log:log F "CD [pwd]"

  gen_log:log C "$cmd"
  set ret [catch {eval "exec $cmd"} view_this]
  gen_log:log T "RETURN $cvscfg(tmpdir)/cvstmpdir.$pid"
  return $cvscfg(tmpdir)/cvstmpdir.$pid
}

#
#  cvs_sandbox_filetags
#   assume that the sandbox contains the checked out files
#   return a list of all the tags in the files
#
proc cvs_sandbox_filetags {mcode filenames} {
  global cvscfg
  global cvs

  set pid [pid]
  set cwd [pwd]
  gen_log:log T "ENTER ($mcode $filenames)"
  
  cd [file join $cvscfg(tmpdir) cvstmpdir.$pid $mcode]
  set commandline "$cvs -n log -l $filenames"
  gen_log:log C "$commandline"
  set ret [catch {eval "exec $commandline"} view_this]
  if {$ret} {
    cd $cwd
    cvsfail $view_this
    gen_log:log T "LEAVE ERROR"
    return $keepers
  }
  set view_lines [split $view_this "\n"]
  foreach line $view_lines {
    if {[string index $line 0] == "\t" } {
      regsub -all {[\t ]*} $line "" tag
      append keepers "$tag "
    }
  }
  cd $cwd
  gen_log:log T "LEAVE"
  return $keepers
}

proc cvs_remove {args} {
#
# This deletes a file from the directory and the repository,
# asking for confirmation first.
#
  global cvs
  global incvs
  global cvscfg

  gen_log:log T "ENTER ($args)"
  if {! $incvs} {
    cvs_notincvs
    return 1
  }
  set filelist [join $args]

  set success 1
  set faillist ""
  foreach file $filelist {
    file delete -force -- $file
    gen_log:log F "DELETE $file"
    if {[file exists $file]} {
      set success 0
      append faillist $file
    }
  }
  if {$success == 0} {
    cvsfail "Remove $file failed"
    return
  }

  set cmd [exec::new "$cvs remove $filelist"]
  if {$cvscfg(auto_status)} {
    $cmd\::wait
    setup_dir
  }

  gen_log:log T "LEAVE"
}

proc cvs_remove_dir {args} {
# This removes files recursively.
  global cvs
  global incvs
  global cvscfg

  gen_log:log T "ENTER ($args)"
  if {! $incvs} {
    cvs_notincvs
    return 1
  }
  set filelist [join $args]
  if {$filelist == ""} {
    cvsfail "Please select a directory!"
    return 
  } else {
    set mess "This will remove the contents of these directories:\n\n"
    foreach file $filelist {
      append mess "   $file\n"
    }  
  }
  
  set v [viewer::new "CVS Remove directory"]

  set awd [pwd]
  foreach file $filelist {
    if {[file isdirectory $file]} {
      set awd [pwd]
      cd $file
      gen_log:log F "CD [pwd]"
      rem_subdirs $v
      cd $awd
      gen_log:log F "CD [pwd]"

      set commandline "$cvs remove \"$file\""
      #gen_log:log C "$commandline"
      $v\::do "$commandline" 1 status_colortags
      $v\::wait
    }
  }

  if {$cvscfg(auto_status)} {
    setup_dir
  }
  gen_log:log T "LEAVE"
}

proc cvs_edit {args} {
#
# This sets the edit flag for a file
# asking for confirmation first.
#
  global cvs
  global incvs
  global cvscfg

  gen_log:log T "ENTER ($args)"

  if {! $incvs} {
    cvs_notincvs
    return 1
  }

  foreach file [join $args] {
    regsub -all {\$} $file {\$} file
    set commandline "$cvs edit \"$file\""
    gen_log:log C "$commandline"
    set ret [catch {eval "exec $commandline"} view_this]
    if {$ret != 0} {
      view_output::new "CVS Edit" $view_this
    }
  }
  if {$cvscfg(auto_status)} {
    setup_dir
  }
  gen_log:log T "LEAVE"
}

proc cvs_unedit {args} {
#
# This resets the edit flag for a file.
# Needs stdin as there is sometimes a dialog if file is modified
# (defaults to no)
#
  global cvs
  global incvs
  global cvscfg

  gen_log:log T "ENTER ($args)"

  if {! $incvs} {
    cvs_notincvs
    return 1
  }

  foreach file [join $args] {
    # Unedit may hang asking for confirmation if file is not up-to-date
    regsub -all {\$} $file {\$} file
    set commandline "cvs -n update \"$file\""
    gen_log:log C "$commandline"
    catch {eval "exec $commandline"} view_this
    # Its OK if its locally added
    if {([llength $view_this] > 0) && ![string match "A*" $view_this] } {
      gen_log:log D "$view_this"
      cvsfail "File $file is not up-to-date"
      gen_log:log T "LEAVE -- cvs unedit failed"
      return
    }

    set commandline "$cvs unedit \"$file\""
    gen_log:log C "$commandline"
    set ret [catch {eval "exec $commandline"} view_this]
    if {$ret != 0} {
      view_output::new "CVS Edit" $view_this
    }
  }
  if {$cvscfg(auto_status)} {
    setup_dir
  }
  gen_log:log T "LEAVE"
}

proc cvs_history {allflag mcode} {
  global cvs
  global cvscfg

  set all ""
  gen_log:log T "ENTER ($allflag $mcode)"
  if {$allflag == "all"} {
    set all "-a"
  }
  if {$mcode == ""} {
    set commandline "$cvs -d $cvscfg(cvsroot) history $all"
  } else {
    set commandline "$cvs -d $cvscfg(cvsroot) history $all -n $mcode"
  }
  # FIXME: If $all, it would be nice to process the output
  set v [viewer::new "CVS History"]
  $v\::do "$commandline"
  gen_log:log T "LEAVE"
}

proc cvs_add {binflag args} {
#
# This adds a file to the repository.
#
  global cvs
  global cvscfg
  global incvs

  gen_log:log T "ENTER ($binflag $args)"
  if {! $incvs} {
    cvs_notincvs
    return 1
  }
  set filelist [join $args]
  if {$filelist == ""} {
    set mess "This will add all new files"
  } else {
    set mess "This will add these files:\n\n"
    foreach file $filelist {
      append mess "   $file\n"
    }  
  }

  if {$filelist == ""} {
    append filelist [glob -nocomplain $cvscfg(aster) .??*]
  }
  set cmd [exec::new "$cvs add $binflag $filelist"]
  if {$cvscfg(auto_status)} {
    $cmd\::wait
    setup_dir
  }

  gen_log:log T "LEAVE"
}

proc cvs_add_dir {binflag args} {
# This starts adding recursively at the directory level
  global cvs
  global cvscfg
  global incvs

  gen_log:log T "ENTER ($binflag $args)"
  if {! $incvs} {
    cvs_notincvs
    return 1
  }
  set filelist [join $args]
  if {$filelist == ""} {
    cvsfail "Please select a directory!"
    return 1
  } else {
    set mess "This will recursively add these directories:\n\n"
    foreach file $filelist {
      append mess "   $file\n"
    }  
  }
  
  set v [viewer::new "CVS Add directory"]

  set awd [pwd]
  foreach file $filelist {
    if {[file isdirectory $file]} {
      set commandline "$cvs add \"$file\""
      #gen_log:log C "$commandline"
      $v\::do "$commandline"
      $v\::wait

      cd $file
      gen_log:log F "CD [pwd]"
      add_subdirs $binflag $v
    }
  }

  cd $awd
  gen_log:log F "[pwd]"
  if {$cvscfg(auto_status)} {
    setup_dir
  }
  gen_log:log T "LEAVE"
}

proc add_subdirs {binflag v} {
  global cvs
  global cvsglb
  global cvscfg

  gen_log:log T "ENTER ($binflag $v)"
  set plainfiles {}
  foreach child  [glob -nocomplain $cvscfg(aster) .??*] {
    if [file isdirectory $child] {
      if {[regexp -nocase {^CVS$} [file tail $child]]} {
        gen_log:log D "Skipping $child"
        continue
      }
      set commandline "$cvs add \"$child\""
      #gen_log:log C "$commandline"
      $v\::do "$commandline"
      $v\::wait
      set awd [pwd]
      cd $child
      gen_log:log F "CD [pwd]"
      add_subdirs $binflag $v
      cd $awd
      gen_log:log F "CD [pwd]"
    } else {
      lappend plainfiles $child
    }
  }
  if {[llength $plainfiles] > 0} {
    # LJZ: get local ignore file filter list
    set ignore_file_filter $cvsglb(default_ignore_filter)
    if { [ file exists ".cvsignore" ] } {
      set fileId [ open ".cvsignore" "r" ]
      while { [ eof $fileId ] == 0 } {
        gets $fileId line
        append ignore_file_filter " $line"
      }
      close $fileId
    }

    # LJZ: ignore files if requested in recursive add
    if { $ignore_file_filter != "" } {
      foreach item $ignore_file_filter {
        # for each pattern
        if { $item != "*" } {
          # if not "*"
          while { [set idx [lsearch $plainfiles $item]] != -1 } {
            # for each occurence, delete
            catch { set plainfiles [ lreplace $plainfiles $idx $idx ] }
          }
        }
      }
    }

    # LJZ: any files left after filtering?
    if {[llength $plainfiles] > 0} {
      set commandline "$cvs add $binflag $plainfiles"
      #gen_log:log C "$commandline"
      $v\::do "$commandline"
      $v\::wait
    }
  }

  gen_log:log T "LEAVE"
}

proc rem_subdirs { v } {
  global cvs
  global incvs
  global cvscfg

  gen_log:log T "ENTER ($v)"
  set plainfiles {}
  foreach child  [glob -nocomplain $cvscfg(aster) .??*] {
    if [file isdirectory $child] {
      if {[regexp -nocase {^CVS$} [file tail $child]]} {
        gen_log:log D "Skipping $child"
        continue
      }
      set awd [pwd]
      cd $child
      gen_log:log F "CD [pwd]"
      rem_subdirs $v
      cd $awd
      gen_log:log F "CD [pwd]"
    } else {
      lappend plainfiles $child
    }
  }
  if {[llength $plainfiles] > 0} {
    foreach file $plainfiles {
      gen_log:log F "DELETE $file"    
      file delete -force -- $file
      if {[file exists $file]} {cvsfail "Remove $file failed"}
    }
    set commandline "$cvs remove $plainfiles"
    #gen_log:log C "$commandline"
    $v\::do "$commandline"
    $v\::wait
  }

  gen_log:log T "LEAVE"
}

proc cvs_diff {args} {
#
# This diffs a file with the repository.
#
  global cvs
  global cvscfg
  global incvs
  global inrcs

  gen_log:log T "ENTER ($args)"
  if {! ($incvs || $inrcs)} {
    cvs_notincvs
    return 1
  }

  set filelist [join $args]
  if {$filelist == ""} {
    cvsfail "Please select one or more files to compare!"
  } else {
    foreach file $filelist {
      regsub -all {\$} $file {\$} file
      gen_log:log C "$cvscfg(tkdiff) \"$file\""
      catch {eval "exec $cvscfg(tkdiff) \"$file\" &"} view_this
    }
  }
  gen_log:log T "LEAVE"
}

proc cvs_diff_r {rev1 rev2 dir args} {
#
# This diffs a file with the repository, using two revisions or tags.
#
  global cvs
  global cvscfg
 
  gen_log:log T "ENTER ($rev1 $rev2 $dir $args)"

  if {$rev1 == {} && $rev2 == {}} {
    cvsfail "Must have at least one revision number or tag for this function!"
    return 1
  }

  if {$rev1 != {}} { set rev1 "-r \"$rev1\"" }
  if {$rev2 != {}} { set rev2 "-r \"$rev2\"" }
 
  # dont join args because we dont get them from workdir_list_files
  foreach file $args {
    set cwd [pwd]
    if {[catch {cd $dir}]} {
        cvsfail "unable to access $dir"
        gen_log:log T "LEAVE unable to access $dir"
        return
    }

    #this should already be done when we get here
    #regsub -all {\$} $file {\$} file
    set commandline "$cvscfg(tkdiff) $rev1 $rev2 \"$file\""
    gen_log:log C "$commandline"
    catch {eval "exec $commandline &"} view_this
  }
  gen_log:log T "LEAVE"

  if {[catch {cd $cwd}]} {
      # FIXME: WTF do we do now?!?
      gen_log:log T "LEAVE unable to return to $cwd"
      return
  }
  
}

proc cvs_fileview_update {revision filename} {
#
# This views a specific revision of a file in the repository.
# For files checked out in the current sandbox.
#
  global cvs
  global cvscfg

  gen_log:log T "ENTER ($filename $revision)"
  if {$revision == {}} {
    set commandline "$cvs -d $cvscfg(cvsroot) update -p \"$filename\""
    #gen_log:log C "$commandline"
    set v [viewer::new "$filename"]
    $v\::do "$commandline"
  } else {
    set commandline "$cvs -d $cvscfg(cvsroot) update -p -r $revision \"$filename\""
    #gen_log:log C "$commandline"
    set v [viewer::new "$filename Revision $revision"]
    $v\::do "$commandline"
  }
  gen_log:log T "LEAVE"
}

proc cvs_fileview_checkout {revision filename} {
#
# This looks at a revision of a file from the repository.
# Called from Module Browser -> File Browse -> View
# For files not currently checked out
#
  global cvs
  global cvscfg

  gen_log:log T "ENTER ($filename $revision)"
  if {$revision == {}} {
    set commandline "$cvs -d $cvscfg(cvsroot) checkout -p \"$filename\""
    #gen_log:log C "$commandline"
    set v [viewer::new "$filename"]
    $v\::do "$commandline"
  } else {
    set commandline "$cvs -d $cvscfg(cvsroot) checkout -p -r $revision \"$filename\""
    #gen_log:log C "$commandline"
    set v [viewer::new "$filename Revision $revision"]
    $v\::do "$commandline"
  }
  gen_log:log T "LEAVE"
}

proc cvs_logcanvas {directory files} {
#
# This looks at the revision log of a file.  It's is called from workdir.tcl,
# when we are in a CVS-controlled directory.  Merges are enabled.
#
  global cvs
  global cvscfg

  gen_log:log T "ENTER ($directory $files)"

  if {$files == {}} {
    cvsfail "Please select one or more files!"
    return
  }

  foreach file $files {
    ::logcanvas::new $directory $file "diff ok" "$cvs -n log -l \"$file\""
  }

  gen_log:log T "LEAVE"
}

proc cvs_log {args} {
#
# This looks at a log from the repository.
# Called by Workdir menu Reports->"CVS log ..."
#
  global cvs
  global incvs
  global cvscfg
  global current_tagname

  gen_log:log T "ENTER ($args)"
  if {! $incvs} {
    cvs_notincvs
    return 1
  }

  set filelist [join $args]

  set commandline "$cvs log "
  if {$cvscfg(ldetail) == "latest"} {
    if {[llength $current_tagname] == 1} {
      # We have a branch here
      append commandline "-r$current_tagname "
    }
    append commandline "-N "
  }
  append commandline "$filelist"

  
  # If verbose, just output the whole thing
  if {$cvscfg(ldetail) == "verbose"} {
    set logcmd [viewer::new "CVS log ($cvscfg(ldetail))"]
    $logcmd\::do "$commandline"
    busy_done .workdir.main
    gen_log:log T "LEAVE"
    return
  }


  # Else we have to take out some of it
  busy_start .workdir.main
  set cooked_log ""
  set logcmd [exec::new "$commandline"]
  set log_lines [split [$logcmd\::output] "\n"]
  if {$cvscfg(ldetail) == "summary"} {
    set n -9999
    foreach logline $log_lines {
      # Beginning of a file's record
      #gen_log:log D "$logline"
      if {[string match "Working file:*" $logline]} {
        append cooked_log "$logline\n"
        # Zingggg - reset!
        set n -9999
      }
      # Beginning of a revision
      if {[string match "----------------------------" $logline]} {
        append cooked_log "$logline\n"
        set n 0
      }
      if {$n >= 1} {
        append cooked_log "$logline\n"
      }
      incr n
    }
  } elseif {$cvscfg(ldetail) == "latest"} {
    set br 0
    while {[llength $log_lines] > 0} {
      set logline [join [lrange $log_lines 0 0]]
      set log_lines [lrange $log_lines 1 end]
      #gen_log:log D "$logline"

      # Beginning of a file's record
      if {[string match "Working file:*" $logline]} {
        append cooked_log "$logline\n"
        while {[llength $log_lines] > 0} {
          set log_lines [lrange $log_lines 1 end]
          set logline [join [lrange $log_lines 0 0]]
          #gen_log:log D " ! $logline !"

          # Reason to skip
          if {[string match "*selected revisions: 0" $logline]} {
            append cooked_log "No revisions on branch\n"
            append cooked_log "======================================="
            append cooked_log "=======================================\n"
            #set br 0
            break
          }
          # Beginning of a revision
          if {[string match "----------------------------" $logline]} {
            #gen_log:log D "  !! $logline !!"
            append cooked_log "$logline\n"
            while {[llength $log_lines] > 0} {
              set log_lines [lrange $log_lines 1 end]
              set logline [join [lrange $log_lines 0 0]]
              #gen_log:log D "        $logline"
              if { [string match "========================*" $logline] ||
                  [string match "--------------*" $logline]} {
                append cooked_log "======================================="
                append cooked_log "=======================================\n"
                set br 1
                break
              } else {
                append cooked_log "$logline\n"
              }
            }
          }
          # If we broke out of the inside loop, break out of this one too
          if {$br == 1} {set br 0; break}
        }
      }
    }
  } else {
    cvsfail "Unknown log option \"$cvscfg(ldetail)\""
  }

  busy_done .workdir.main
  view_output::new "CVS Log ($cvscfg(ldetail))" $cooked_log
  gen_log:log T "LEAVE"
}

proc cvs_annotate {revision args} {
#
# This looks at a log from the repository.
# Called by Workdir menu Reports->"CVS log ..."
#
  global cvs
  global cvscfg

  gen_log:log T "ENTER ($revision $args)"

  if {$revision != ""} {
    # We were given a revision
    set revflag "-r$revision"
  } else {
    set revflag ""
  }

  set filelist [join $args]
  if {$filelist == ""} {
    cvsfail "Annotate:\nPlease select one or more files !"
    gen_log:log T "LEAVE (Unselected files)"
    return
  }
  foreach file $filelist {
    annotate::new $revflag $file 1
  }
  gen_log:log T "LEAVE"
}

proc cvs_annotate_r {revision args} {
#
# This looks at a log from the repository.
# Called by Logcanvas when not in a CVS directory
#
  global cvs
  global cvscfg

  gen_log:log T "ENTER ($revision $args)"

  if {$revision != ""} {
    # We were given a revision
    set revflag "-r$revision"
  } else {
    set revflag ""
  }

  set filelist [join $args]

  annotate::new $revflag $filelist 0
  gen_log:log T "LEAVE"
}

proc cvs_commit {revision comment args} {
#
# This commits changes to the repository.
#
# The parameters work differently here -- args is a list.  The first
# element of args is a list of file names.  This is because I can't
# use eval on the parameters, because comment contains spaces.
#
  global cvs
  global cvscfg
  global incvs

  gen_log:log T "ENTER ($revision $comment $args)"
  if {! $incvs} {
    cvs_notincvs
    return 1
  }

  set filelist [lindex $args 0]

  # changed the message to be a little more explicit.  -sj
  set commit_output ""
  if {$filelist == ""} {
    set mess "This will commit your changes to ** ALL ** files in"
    append mess " and under this directory."
  } else {
    foreach file $filelist {
      append commit_output "\n$file"
    }
    set mess "This will commit your changes to:$commit_output"
  }
  append mess "\n\nAre you sure?"
  set commit_output ""

  if {[cvsconfirm $mess] == 1} {
    return 1
  }

  set revflag ""
  if {$revision != ""} {
    set revflag "-r $revision"
  }

  if {$cvscfg(use_cvseditor)} {
    # Starts text editor of your choice to enter the log message.
    # This way a template in CVSROOT can be used.
    update idletasks
    set commandline \
      "$cvscfg(terminal) $cvs commit -R $revflag $filelist"
    gen_log:log C "$commandline"
    set ret [catch {eval "exec $commandline"} view_this]
    if {$ret} {
      cvsfail $view_this
      gen_log:log T "LEAVE ERROR ($view_this)"
      return
    }
  } else {
    if {$comment == ""} {
      cvsfail "You must enter a comment!"
      return 1
    }
    set v [viewer::new "CVS Commit"]
    regsub -all "\"" $comment "\\\"" comment
    # Lets not show stderr as it does a lot of "examining"
    $v\::do "$cvs commit -R $revflag -m \"$comment\" $filelist" 0
    $v\::wait
  }

  if {$cvscfg(auto_status)} {
    setup_dir
  }
  gen_log:log T "LEAVE"
}

proc cvs_tag {tagname force branch args} {
#
# This tags a file in a directory.
#
  global cvs
  global cvscfg
  global incvs

  gen_log:log T "ENTER ($tagname $force $branch $args)"

  if {! $incvs} {
    cvs_notincvs
    return 1
  }

  if {$tagname == ""} {
    cvsfail "You must enter a tag name!"
    return 1
  }

  set filelist [join $args]

  set command "$cvs tag"
  if {$branch == "yes"} {
   append command " -b"
  }
  if {$force == "yes"} {
    append command " -F"
  }
  append command " $tagname $filelist"
  #gen_log:log C "$command"

  set v [viewer::new "CVS Tag"]
  $v\::do "$command"
  $v\::wait

  if {$branch == "yes"} {
    # update so we're on the branch
    set command "$cvs update -r $tagname $filelist"
    #gen_log:log C "$command"
    $v\::do "$command" 0 status_colortags
    $v\::wait
  }

  if {$cvscfg(auto_status)} {
    setup_dir
  }
  gen_log:log T "LEAVE"
}

proc cvs_update {tagname normal_binary action_if_no_tag get_all_dirs dir args} {
#
# This updates the files in the current directory.
#
  global cvs
  global cvscfg
  global incvs

  gen_log:log T "ENTER ($tagname $normal_binary $action_if_no_tag $get_all_dirs $dir $args)"

  if { $normal_binary == "Normal" } {
      set mess "Using normal (text) mode.\n"
  } elseif { $normal_binary == "Binary" } {
      set mess "Using binary mode.\n"
  } else {
      set mess "Unknown mode:  $normal_binary\n"
  }

  if { $tagname != "BASE"  && $tagname != "HEAD" } {
      append mess "\nIf a file does not have tag $tagname"
      if { $action_if_no_tag == "Remove" } {
          append mess " it will be removed from your local directory.\n"
      } elseif { $action_if_no_tag == "Get_head" } {
          append mess " the head revision will be retrieved.\n"
      } elseif { $action_if_no_tag == "Skip" } {
          append mess " it will be skipped.\n"
      }
  }

  if { $tagname == "HEAD" } {
    append mess "\nYour local files will be updated to the"
    append mess " latest main trunk (head) revision."
    append mess " CVS will try to preserve any local, un-committed changes.\n"
  }

  append mess "\nIf there is a directory in the repository"
  append mess " that is not in your local, working directory,"
  if { $get_all_dirs == "Yes" } {
    append mess " it will be checked out at this time.\n"
  } else {
    append mess " it will not be checked out.\n"
  }

  set filelist [join $args]
  if {$filelist == ""} {
    append mess "\nYou are about to download from"
    append mess " the repository to your local"
    append mess " filespace ** ALL ** files which"
    append mess " have changed in it."
  } else {
    append mess "\nYou are about to download from"
    append mess " the repository to your local"
    append mess " filespace these files which"
    append mess " have changed:\n"
  
    foreach file $filelist {
      append mess "\n\t$file"
    }
  }
  append mess "\n\nAre you sure?"
  if {[cvsconfirm $mess] == 0} {
    # modified by jo to build the commandline incrementally
    set commandline "$cvs update -P"
    if { $normal_binary == "Binary" } {
      append commandline " -kb"
    }
    if { $get_all_dirs == "Yes" } {
      append commandline " -d $dir"
    }
    if { $tagname != "BASE" && $tagname != "HEAD" } {
      if { $action_if_no_tag == "Remove" } {
          append commandline " -r $tagname"
      } elseif { $action_if_no_tag == "Get_head" } {
          append commandline " -f -r $tagname"
      } elseif { $action_if_no_tag == "Skip" } {
          append commandline " -s -r $tagname"
      }
    }
    if { $tagname == "HEAD" } {
      append commandline " -A"
    }
    foreach file $filelist {
      append commandline " \"$file\""
    }

    #gen_log:log C $commandline
    set check_cmd [viewer::new "CVS Update"]
    $check_cmd\::do $commandline 0 status_colortags
    
    if {$cvscfg(auto_status)} {
      $check_cmd\::wait
      setup_dir
    }
  }
  gen_log:log T "LEAVE"
}

proc cvs_join {from since fromtag totag args} {
#
# This does a join (merge) of a chosen revision of localfile to the
# current revision.
#
  global cvs
  global cvscfg
  global current_tagname

  gen_log:log T "ENTER ($from $since $fromtag $totag $args)"

  set filelist $args
  set v [viewer::new "CVS Join"]

  set commandline "$cvs update -d -j$from $filelist"
  if {$since == ""} {
    set commandline "$cvs update -d -j$from $filelist"
  } else {
    set commandline "$cvs update -d -j$since -j$from $filelist"
  }
    
  $v\::do "$commandline" 0 status_colortags
  $v\::wait

  if {$cvscfg(auto_tag)} {
    set comandline "$cvs tag -F -r $from $fromtag $filelist"
    $v\::do "$cvs tag -F -r $from $fromtag $filelist"
    toplevel .reminder
    message .reminder.m1 -aspect 600 -text \
      "When you are finished checking in your merges, \
      you should apply the tag"
    entry .reminder.ent -width 32 -relief raised -bd 1
    .reminder.ent insert end $totag 
    message .reminder.m2 -aspect 600 -text \
      "using the \"Tag the selected files\" button"
    frame .reminder.bottom -relief raised -bd 2
    button .reminder.bottom.close -text "Dismiss" \
      -command {destroy .reminder}
    pack .reminder.bottom -side bottom -fill x
    pack .reminder.bottom.close -side bottom -expand yes
    pack .reminder.m1 -side top
    pack .reminder.ent -side top -padx 2
    pack .reminder.m2 -side top
  }

  if {$cvscfg(auto_status)} {
    setup_dir
  }
  gen_log:log T "LEAVE"
}

proc cvs_status {args} {
#
# This does a status report on the files in the current directory.
#
  global cvs
  global cvscfg

  gen_log:log T "ENTER ($args)"

  if {$args == "."} {
    set args ""
  }
  # if there are selected files, I want verbose output for those files
  # so I'm going to save the current setting here
  # - added by Jo
  set verbosity_setting ""

  busy_start .workdir.main
  set filelist [join $args]
  # if recurse option is true or there are no selected files, recurse
  set cmd_options ""
  if {! [info exists cvscfg(recurse)]} {
    set cmd_options "-l"
  }

  # if there are selected files, use verbose output
  # but save the current setting so it can be reset
  # - added by Jo
  if {[llength $filelist] > 0 || \
      ([llength $filelist] == 1  && ! [file isdir $filelist])} {
    set verbosity_setting $cvscfg(rdetail)
    set cvscfg(rdetail) "verbose"
  }

  # support verious levels of verboseness. Ideas derived from GIC
  set statcmd [exec::new "$cvs -Q status $cmd_options $filelist"]
  set raw_status [$statcmd\::output]

  if {$cvscfg(rdetail) == "verbose"} {
    view_output::new "CVS Status ($cvscfg(rdetail))" $raw_status
  } else {
    set cooked_status ""
    set stat_lines [split $raw_status "\n"]
    foreach statline $stat_lines {
      if {[string match "*Status:*" $statline]} {
        gen_log:log D "$statline"
        if {$cvscfg(rdetail) == "terse" &&\
            [string match "*Up-to-date*" $statline]} {
          continue
        } else {
          regsub {^File: } $statline {} statline
          regsub {Status:} $statline " " line
          append cooked_status $line
          append cooked_status "\n"
        }
      }
    }
    view_output::new "CVS Status ($cvscfg(rdetail))" $cooked_status
  }

  # reset the verbosity setting if necessary
  if { $verbosity_setting != "" } {
    set cvscfg(rdetail) $verbosity_setting
  }
  busy_done .workdir.main
  gen_log:log T "LEAVE"
}


proc cvs_tag_status {args} {
#
# This processes the output of 'cvs status' to provide a simple
# report of the current sticky tags
#
  global cvs
  global cvscfg

  gen_log:log T "ENTER ($args)"

  busy_start .workdir.main
  set filelist [join $args]

  set statcmd [exec::new "$cvs -Q status -l $filelist"]
  set raw_status [$statcmd\::output]

  set cooked_status ""
  set stat_lines [split $raw_status "\n"]
  foreach statline $stat_lines {
    if {[string match "*Status:*" $statline]} {
      gen_log:log D "$statline"
      regsub {^File: } $statline {} statline
      regsub {Status:} $statline " " line
      append cooked_status $line
      append cooked_status "\n"
    }
    if {[string match "*Sticky Tag:*" $statline]} {
      regsub -all {[ \t]+} $statline " " statline
      set line [split $statline]
      gen_log:log D "$line"
      append cooked_status "   [lrange $line 4 end]"
      append cooked_status "\n"
    }

  }
  busy_done .workdir.main
  view_output::new "CVS Sticky Status" $cooked_status

  gen_log:log T "LEAVE"
}

proc cvs_check {directory} {
#
# This does a cvscheck on the files in the current directory.
#
  global cvs
  global cvscfg

  gen_log:log T "ENTER ($directory)"

  busy_start .workdir.main

  # The current directory doesn't have to be in CVS for cvs update to work.

  # Sometimes, cvs update doesn't work with ".", only with "" or an argument
  if {$directory == "."} {
    set directory ""
  }

  set commandline "$cvs -n -q update $cvscfg(checkrecursive) $directory"
  #gen_log:log C "$commandline"
  set check_cmd [viewer::new "Directory Status Check"]
  $check_cmd\::do $commandline 0 status_colortags

  busy_done .workdir.main
  gen_log:log T "LEAVE"
}

proc cvs_checkout { dir cvsroot prune kflag revtag date target mtag1 mtag2 module } {
  #
  # This checks out a new module into the current directory.
  #
  global cvs
  global cvscfg

  gen_log:log T "ENTER ($dir $cvsroot $prune $kflag $revtag $date $target $mtag1 $mtag2 $module)"

  foreach {incvs inrcs module_dir} [cvsroot_check $dir] { break }
  if {$incvs} {
    set mess "This is already a CVS controlled directory.  Are you\
              sure that you want to check out another module in\
              to this directory?"
    if {[cvsconfirm $mess] == 1} {
      return
    }
  }

  set mess "This will check out $module from CVS.\nAre you sure?"
  if {[cvsconfirm $mess] == 0} {
    if {$revtag != {}} {
      set revtag "-r \"$revtag\""
    }
    if {$date != {}} {
      set date "-D \"$date\""
    }
    if {$target != {}} {
      set target "-d \"$target\""
    }
    if {$mtag1 != {}} {
      set mtag1 "-j \"$mtag1\""
    }
    if {$mtag2 != {}} {
      set mtag2 "-j \"$mtag2\""
    }
    set v [::viewer::new "CVS Checkout"]
    set cwd [pwd]
    cd $dir
    $v\::do "$cvs -d \"$cvsroot\" checkout $prune\
             $revtag $date $target\
             $mtag1 $mtag2\
             $kflag \"$module\""
    cd $cwd
  }
  gen_log:log T "LEAVE"
  return
}

proc cvs_filelog {filename} {
#
# This looks at the revision log of a file.  It's called from filebrowse.tcl, 
# so we can't do operations such as merges.
#
  global cvs
  global cvscfg
  global cwd
  
  gen_log:log T "ENTER ($filename)"
  set pid [pid]
  set filetail [file tail $filename]
  
  set commandline "$cvs -d $cvscfg(cvsroot) checkout \"$filename\""
  gen_log:log C "$commandline"
  set ret [cvs_sandbox_runcmd "$commandline" cmd_output]
  if {$ret == $cwd} {
    cvsfail $cmd_output
    cd $cwd
    gen_log:log T "LEAVE -- cvs checkout failed"
    return
  }

  set commandline "$cvs -d $cvscfg(cvsroot) -n log -l \"$filename\""

  # Log canvas viewer
  logcanvas::new [pwd] $filename "no file" $commandline
  cd $cwd
  gen_log:log T "LEAVE"
}

proc rcs_filediff {filename ver1 ver2} {
#
# This does a diff of an RCS file within the repository.
#
  global cvscfg

  gen_log:log T "ENTER ($dir $cvsroot $kflag $revtag $date $target $module)"

  if {$ver1 == {} || $ver2 == {}} {
    cvsfail "Must have two revision numbers for this function!"
    return 1
  }
  set command "$cvscfg(tkdiff) -r$ver1 -r$ver2 \"$filename\" &"
  exec::new $commandline
  
  #catch {eval "exec $cvscfg(tkdiff) -r$ver1 -r$ver2 \"$filename\" &"}
  gen_log:log T "LEAVE"
}

proc cvs_export { dir cvsroot kflag revtag date target module } {
#
# This exports a new module (see man cvs and read about export) into
# the current directory.
#
  global cvs
  global cvscfg 

  gen_log:log T "ENTER ($dir $cvsroot $kflag $revtag $date $target $module)"
    
  foreach {incvs inrcs module_dir} [cvsroot_check $dir] { break }
  if {$incvs} { 
    set mess "This is already a CVS controlled directory.  Are you\
              sure that you want to export a module in to this directory?"
    if {[cvsconfirm $mess] == 1} {
      return
    }
  }

  set mess "This will export $module from CVS.\nAre you sure?"
  if {[cvsconfirm $mess] == 0} {
    if {$revtag != {}} {
      set revtag "-r \"$revtag\""
    }
    if {$date != {}} {
      set date "-D \"$date\""
    }
    if {$target != {}} {
      set target "-d \"$target\""
    }

    set v [::viewer::new "CVS Export"]
    set cwd [pwd]
    cd $dir
    $v\::do "$cvs -d \"$cvsroot\" export\
             $revtag $date $target $kflag \"$module\""
    cd $cwd
  }
  gen_log:log T "LEAVE"
  return
}

proc cvs_patch { cvsroot module difffmt revtagA dateA revtagB dateB outmode outfile } {
#
# This creates a patch file between two revisions of a module.  If the
# second revision is null, it creates a patch to the head revision.
# If both are null the top two revisions of the file are diffed.
#
  global cvs
  global cvscfg
 
  gen_log:log T "ENTER ($cvsroot $module $difffmt $revtagA $dateA $revtagB $dateB $outmode $outfile)"

  foreach {rev1 rev2} {{} {}} { break }
  if {$revtagA != {}} {
    set rev1 "-r \"$revtagA\""
  } elseif {$dateA != {}} {
    set rev1 "-D \"$dateA\""
  }
  if {$revtagB != {}} {
    set rev2 "-r \"$revtagB\""
  } elseif {$dateA != {}} {
    set rev2 "-D \"$dateB\""
  }
  if {$rev1 == {} && $rev2 == {}} {
    set rev1 "-t"
  }

  set commandline "$cvs -d \"$cvsroot\" patch $difffmt $rev1 $rev2 \"$module\""

  if {$outmode == 0} {
    set v [viewer::new "CVS Patch"]
    $v\::do "$commandline" 0 patch_colortags
  } else {
    set e [exec::new "$commandline"]
    set patch [$e\::output]
    gen_log:log F "OPEN $outfile"
    if {[catch {set fo [open $outfile w]}]} {
      cvsfail "Cannot open $outfile for writing"
      return
    }
    puts $fo $patch
    close $fo
    gen_log:log F "CLOSE $outfile"
  }
  gen_log:log T "LEAVE"
  return
}

proc cvs_version {} {
#
# This shows CVS banner.
#
  global cvs
  global cvscfg

  gen_log:log T "ENTER"
  set commandline "$cvs -v"
  #gen_log:log C "$commandline"
  set v [viewer::new "CVS version"]
  $v\::do "$commandline"
  gen_log:log T "LEAVE"
}

proc cvs_version_number {} {
#
# This finds the current CVS version number.
#
  global cvs
  global cvscfg

  gen_log:log T "ENTER"
  set commandline "$cvs -v"
  #gen_log:log C "$commandline"
  set e [exec::new "$commandline" {} 0 parse_version]
  set number [$e\::output]
  regsub -all {\s*} $number {} number
  
  gen_log:log T "LEAVE ($number)"
  return $number
}

proc cvs_merge_conflict {args} {
  global cvscfg
  global cvs

  gen_log:log T "ENTER ($args)"

  set filelist [join $args]
  if {$filelist == ""} {
    cvsfail "Please select some files to merge first!"
    return
  }

  foreach file $filelist {
    # Make sure its really a conflict - tkdiff will bomb otherwise
    regsub -all {\$} $file {\$} filename
    set commandline "$cvs -n -q update \"$filename\""
    gen_log:log C "$commandline"
    catch {eval "exec $commandline"} status
    gen_log:log C "$status"

    gen_log:log F "OPEN $file"
    set f [open $file]
    set match 0
    while { [eof $f] == 0 } {
      gets $f line
      if { [string match "<<<<<<< *" $line] } {
        set match 1
        break
      }
    }
    gen_log:log F "CLOSE $file"
    close $f
   
    if { [string match "C *" $status] } {
      # If its marked "Needs Merge", we have to update before
      # we can resolve the conflict
      gen_log:log C "$commandline"
      set commandline "$cvs update \"$file\""
      gen_log:log C "$status"
      catch {eval "exec $commandline"} status
    } elseif { $match == 1 } { 
      # There are conflict markers already, dont update
      ;
    } else {
      cvsfail "This file does not appear to have a conflict."
      return
    }
    # Invoke tkdiff with the proper option for a conflict file
    # and have it write to the original file
    set commandline "$cvscfg(tkdiff) -conflict -o \"$filename\" \"$filename\""
    gen_log:log C "$commandline"
    catch {eval "exec $commandline"} view_this
  }
  
  if {$cvscfg(auto_status)} {
    setup_dir
  }
  gen_log:log T "LEAVE"
}

proc cvs_gettaglist {filename} {
  global cvs
  global cvscfg
  global cwd

  set keepers ""
  set pid [pid]
  gen_log:log T "ENTER ($filename)"
  set filetail [file tail $filename]
  
  set commandline "$cvs -d $cvscfg(cvsroot) checkout \"$filename\"" 
  # run a command, possibly creating the sandbox to play in
  set ret [cvs_sandbox_runcmd $commandline cmd_output]
  if {$cwd == $ret} {
    cvsfail $cmd_output
    cd $cwd
    gen_log:log T "LEAVE ERROR ($cmd_output)"
    return $keepers
  }

  set commandline "$cvs -d $cvscfg(cvsroot) -n log -l \"$filename\""
  gen_log:log C "$commandline"
  set ret [catch {eval "exec $commandline"} view_this]
  if {$ret} {
    cvsfail $view_this
    cd $cwd
    gen_log:log T "LEAVE ERROR"
    return $keepers
  }
  set view_lines [split $view_this "\n"]
  foreach line $view_lines {
    if {[string index $line 0] == "\t" } {
      set line [string trimleft $line]
      gen_log:log D "$line"
      append keepers "$line\n"
    }
  }
  if {$keepers == ""} {
    set keepers "No Tags"
  }

  cd $cwd
  gen_log:log T "LEAVE"
  return "$keepers"
}

proc cvs_release {delflag directory} {
  global cvs
  global cvscfg

  gen_log:log T "ENTER ($directory)"
  if {! [file isdirectory $directory]} {
    cvsfail "$directory is not a directory"
    return
  }

  set commandline "$cvs -n -q update $directory"
  gen_log:log C "$commandline"
  set ret [catch {eval "exec $commandline"} view_this]
  if {$view_this != ""} {
    view_output::new "CVS Check" $view_this
    set mess "$directory is not up-to-date."
    append mess "\nRelease anyway?"
    if {[cvsconfirm $mess] == 1} {
      return
    }
  }
  set commandline "$cvs -Q release $delflag $directory"
  set ret [catch {eval "exec $commandline"} view_this]
  gen_log:log C "$commandline"
  if {$ret != 0} {
    view_output::new "CVS Release" $view_this
  }

  if {$cvscfg(auto_status)} {
    setup_dir
  }
  gen_log:log T "LEAVE"
}

proc rcs_filelog {filename} {
#
# This looks at the revision log of a file with RCS.
# We can't do operations such as merges.
#
  global cvscfg
  global cwd
  
  gen_log:log T "ENTER ($filename)"
  set pid [pid]
  set filetail [file tail $filename]
  
  set commandline "rlog \"$filename\""

  # Log canvas viewer
  logcanvas::new $cwd $filename "no file" $commandline
  gen_log:log T "LEAVE"
}

proc cvs_rtag { cvsroot mcode branch force oldtag newtag } {
#
# This tags a module in the repository.
# Called by the tag commands in the Module Browser
#
  global cvs
  global cvscfg
  
  gen_log:log T "ENTER ($cvsroot $mcode $branch $force $oldtag $newtag)"
  if {$newtag == ""} {
    cvsfail "You must enter a tag name!"
    return 1
  }

  set command "$cvs -d \"$cvsroot\" rtag"
  if {$branch == "yes"} {
    append command " -b"
  } 
  if {$force == "yes"} {
    append command " -F" 
  }   
  if {$oldtag != ""} {
    append command " -r \"$oldtag\""
  }
  append command " \"$newtag\" \"$mcode\""

  set v [::viewer::new "CVS Rtag"]
  $v\::do "$command"

  gen_log:log T "LEAVE"
}

