#!/bin/sh
# the next line restarts using tclsh \
exec tclsh "$0" -- ${1+"$@"}


proc cleanup_old {root} {
  if {[ file isdirectory $root ]} {
    puts "Deleting $root"
    file delete -force $root
  }
  set oldirs [glob -nocomplain -- git_test**]
  foreach od $oldirs {
    puts "Deleting $od"
    file delete -force $od
  }
}

proc clone {Root Clone} {
  puts "==============================="
  puts "CLONING"
  set exec_cmd "git clone $Root $Clone"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  # For some reason catch returns 1 even though it succeeded
  puts $out
  if {! [file exists $Clone/.git] && ! [file exists $Clone/config]} {
    puts "COULD NOT CLONE REPOSITORY $Root to $Clone"
    exit 1
  }
}

proc worktree {Root Branch} {
  puts "==============================="
  puts "MAKING WORKTREE"
  cd $Root
  set exec_cmd "git worktree add --track -b branch$Branch ../git_test_branch$Branch"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  puts $out
  cd ..
}

proc repository {Root} {
  puts "==============================="
  puts "MAKING REPOSITORY $Root"

  # Create the repository
  #file mkdir $Root
  set exec_cmd "git init --bare $Root"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  puts $out
  if {$ret} {
    puts "COULD NOT CREATE REPOSITORY $Root"
    exit 1
  }
  puts "CREATED $Root"
}

proc populate {clone} {
  global WD

  mkfiles $clone
  # Git needs to know our email or else it won't commit
  cd $clone
  set exec_cmd "whoami"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} myname]
  if {$ret} {
    puts $myname
  }
  set exec_cmd "hostname"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} host]
  if {$ret} {
    puts $host
  }
  set mymail "$myname@$host"
  set exec_cmd "git config user.name $myname"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  if {$ret} {
    puts $out
  }
  set exec_cmd "git config user.email $mymail"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  if {$ret} {
    puts $out
  }
  puts "==============================="
  puts "IMPORTING FILETREE"
  set exec_cmd "git add --verbose ."
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  puts $out
  # See what we did
  set exec_cmd "git status"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  puts $out
  puts "IMPORT FINISHED"
  cd $WD
}

proc newbranch {oldtag newtag} {
  global WD

  puts "==============================="
  puts "Creating new $newtag"
  puts "In [pwd]"
  cd git_test_$oldtag
  puts "In [pwd]"
  set exec_cmd "git branch --track $newtag"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  if {$ret} {
    puts $out
    exit
  }
  set exec_cmd "git checkout $newtag"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  puts $out

  cd $WD
  puts "\nIn [pwd]"
  puts "Cloning $oldtag to a new directory for $newtag"
  set exec_cmd "git clone git_test_$oldtag git_test_$newtag"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  puts $out

  puts "Restoring git_test_$oldtag to $oldtag"
  cd git_test_$oldtag
  puts "In [pwd]"
  set exec_cmd "git checkout $oldtag"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  puts $out

  cd $WD
}

proc merge {fromtag totag} {
  global WD

  cd git_test_$totag
  set exec_cmd "git merge --no-ff $fromtag"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  puts $out

  cd $WD
}

proc tag {tag msg} {
  if {$msg eq ""} {
    set exec_cmd "git tag $tag"
  } else {
    set exec_cmd "git tag -a $tag -m \"$msg\""
  }
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  if {$ret} {
    puts $out
    exit 1
  }
}

proc writefile {filename string {encoding {}} } {
  puts " append \"$string\" to $filename"
  set fp [open "$filename" a]
  if {$encoding != ""} {
    chan configure $fp -encoding $encoding
  }
  puts $fp $string
  chan close $fp
}

proc addfile {filename branch} {
  puts "Add $filename on $branch"
  set exec_cmd "git add --verbose $filename"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  puts $out
}

proc stage {} {
  puts "Stage [pwd]"
  set exec_cmd "git add --verbose *"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  puts $out
}

proc delfile {filename branch} {
  puts "Delete $filename on $branch"
  file delete $filename
  set exec_cmd "git rm -r $filename"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  puts $out
}

proc movefile {oldname newname} {
  # rename a file
  puts "Rename $oldname to $newname"
  set exec_cmd "git mv $oldname $newname"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  puts $out
}

proc push {origin} {
  puts "==============================="
  set exec_cmd "git push $origin"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  puts $out
  if {$ret} {
    if {[string match {fatal*} $out]} {
      exit 1
    }
  }
}

proc fetch {{origin {}}} {
  puts "Fetching from $origin"
  set exec_cmd "git fetch "
  if {$origin != ""} {
    append exec_cmd " $origin"
  }
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
}

proc commit {comment} {
  # It seems to need the email all over again in a cloned directory
  set exec_cmd "whoami"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} myname]
  if {$ret} {
    puts $myname
  }
  set exec_cmd "hostname"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} host]
  if {$ret} {
    puts $host
  }
  set mymail "$myname@$host"
  set exec_cmd "git config user.name $myname"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  if {$ret} {
    puts $out
  }
  set exec_cmd "git config user.email $mymail"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  if {$ret} {
    puts $out
  }

  # Finally, do it
  set exec_cmd "git commit -m \"$comment\""
  # This is so the timestamp is different from the last commit
  after 1000
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  puts $out
}

proc mkfiles {topdir} {
  global WD

  puts "MAKING FILETREE"
  # Make some files to put in the repository
  file mkdir "$topdir"
  cd $topdir

  # Make some files each containing a random word
  foreach n {1 2 3 4} {
    writefile "File$n.txt" "Initial"
  }
  writefile "File Spc.txt" "Initial"
  writefile "FTags.txt" "Initial"
  writefile "F-utf-8.txt" "\u20AC20" "utf-8"
  writefile "F-iso8859-1.txt" "Copyright \xA9 2025" "iso8859-1"
  foreach D {Dir1 "Dir 2"} {
    puts $D
    file mkdir $D
    foreach n {1 2 " 3" 4} {
      set subf [file join $D "F$n.txt"]
      writefile $subf "Initial"
    }
  }
  cd $WD
}

proc modfiles {string} {
  global tcl_platform

  set tmpfile "list.tmp"
  file delete -force $tmpfile
  if {$tcl_platform(platform) eq "windows"} {
    puts "Must be a PC"
    set ret [catch {eval "exec [auto_execok dir] /b F*.txt /s > $tmpfile"} out]
  } else {
    set ret [catch {eval "exec find . -name F*.txt -a -type f > $tmpfile"} out]
  }
  if {$ret} {
    puts "Find failed"
    puts $out
    exit 1
  }
  set fl [open $tmpfile r]
  while { [gets $fl item] >= 0} {
    writefile $item $string
  }
  close $fl
  file delete -force $tmpfile
}

proc conflict {filename} {
  # Create a conflict. In Git, this is done with a temporary branch.
 
  # Check out a new branch
  set exec_cmd "git checkout -b clash"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  puts $out
  writefile $filename "Conflict A"
  set exec_cmd "git commit -m \"change on clash\" $filename"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  puts $out
  # Check out head, which now conflicts with our change
  set exec_cmd "git checkout master"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  puts $out
  # Make a different change to same line
  writefile $filename "Conflict B"
  set exec_cmd "git commit -m \"change on master\" $filename"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  puts $out
  set exec_cmd "git merge clash"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  puts $out
}

##############################################
set branching_desired 1
set leave_a_mess 1

for {set i 1} {$i < [llength $argv]} {incr i} {
  set arg [lindex $argv $i]

  switch -regexp $arg {
    {^--*nobranch} {
      set branching_desired 0
    }
    {^--*nomess} {
      set leave_a_mess 0
    }
  }
}

if [file exists .git] {
  puts "Please don't do that here.  There's already a .git directory."
  exit 1
}

set WD [pwd]
set Root [file join $WD "GIT_REPOSITORY.git"]
set Master "git_test_master"

cleanup_old $Root

# Create the bare "server" repo
repository $Root
# Clone it to one we can work in
clone $Root $Master
# Import some files
populate $Master
cd $Master
commit "Commit the imported files"
tag "init" "the starting point"
push ""
cd $WD

# Make some changes
puts "==============================="
puts "First revision on trunk"
cd $Master
writefile .gitignore ".DS_Store"
addfile .gitignnore trunk

modfiles "Main 1"
writefile Ftrunk.txt "Main 1"
addfile Ftrunk.txt master
stage
commit "First revision on trunk"
tag "tagA" ""
tag "tagC" ""
push ""
cd $WD

if {$branching_desired} {
  puts "==============================="
  puts "MAKING BRANCH A"
  newbranch master branchA
  cd $WD/git_test_branchA
  writefile FbranchA.txt "BranchA 1"
  addfile FbranchA.txt branchA
  stage
  commit "Add file FbranchA.txt on branch A"

  puts "==============================="
  puts "First revision on Branch A"
  modfiles "BranchA 1"
  stage
  commit "First revision on branch A"

  puts "==============================="
  puts "Second revision on Branch A"
  modfiles "BranchA 2"
  stage
  commit "Second revision on branch A"
  push ""
  cd $WD

  # Branch C
  puts "==============================="
  puts "MAKING BRANCH C FROM SAME ROOT"
  worktree git_test_master C
  cd $WD/git_test_branchC
  modfiles "BranchC 1"
  writefile FbranchC.txt "BranchC 1"
  addfile FbranchC.txt branchC
  stage
  commit "First changes on Branch C"
  # Make two identical branches (OK in Git)
  set exec_cmd "git branch -c branchC branchD"
  puts "$exec_cmd"
  set ret [catch {eval "exec $exec_cmd"} out]
  cd $WD

  puts "==============================="
  puts "Merging BranchA to trunk"
  merge branchA master
  cd $WD
}

# Make more modifications on trunk
puts "==============================="
puts "Second revision on trunk"
cd $WD/$Master
fetch {--all}

modfiles "Main 2"
stage
commit "Second revision on trunk"
foreach t {one ten one_hundred one_thousand ten_thousand one_hundred_thousand} {
  tag "tag_$t" FTags.txt
}

puts "==============================="
puts "Third revision on trunk"
modfiles "Main 3"
stage
commit "Third revision on trunk"
tag "tagB" ""
tag "one" "Dir1/F1.txt"
push ". HEAD"
cd $WD

if {$branching_desired} {

  # Branch off of the branch
  puts "==============================="
  puts "MAKING BRANCH AA"
  cd $WD/git_test_branchA
  tag "tagAA" ""
  cd $WD
  worktree git_test_branchA AA
  cd $WD/git_test_branchAA
  modfiles "BranchAA 1"
  writefile FbranchAA.txt "BranchAA 1"
  addfile FbranchAA.txt branchAA
  delfile Ftrunk.txt branchAA
  stage
  commit "First changes on Branch AA"

  puts "==============================="
  puts "Revision on Branch AA"
  modfiles "BranchAA 2"
  stage
  commit "Second changes on Branch AA"
  cd $WD

  # Branch Y
  puts "==============================="
  puts "MAKING BRANCH Y"
  worktree git_test_branchAA Y
  cd $WD/git_test_branchY
  modfiles "BranchY 1"
  stage
  commit "First changes on Branch Y"
  cd $WD

  # Make another revision on branchA after
  # branchAA has branched off
  puts "==============================="
  puts "Third revision on Branch A"
  cd $WD/git_test_branchA
  modfiles "BranchA 3"
  stage
  commit "Third revision on Branch A"
  cd $WD

  # Branch B
  puts "==============================="
  puts "MAKING BRANCH B"
  worktree git_test_master B
  cd $WD/git_test_branchB
  modfiles "BranchB 1"
  writefile FbranchB.txt "BranchB 1"
  addfile FbranchB.txt branchB
  stage
  commit "First changes on Branch B"

  puts "==============================="
  puts "Revision on Branch B"
  modfiles "BranchB 2"
  stage
  commit "Second changes on Branch B"
  cd $WD

  # Update the clones
  #foreach branch {branchA branchAA branchB branchC master} {
    #cd $WD/git_test_$branch
    #push {--all}
    #fetch {--all}
    #cd $WD
  #}
}

if {$leave_a_mess} {
  # Leave the trunk with uncommitted changes
  puts "==============================="
  puts "Making Uncommitted changes on trunk"
  cd $WD/$Master
  # Local only
  writefile FileLocal.txt "Pending"
  # Conflict. Have to do this before the add and delete,
  # or the merge will fail before you get to the conflicted file
  #conflict Ftrunk.txt
  # Newly added
  writefile FileAdd.txt "Pending"
  addfile FileAdd.txt trunk
  # Rename a file. git "R " status
  # git status reports this wrong! as "R Dir1/F4.txt -> FileMoved.txt"
  #movefile ./File4.txt ./FileMoved.txt
  # Plain, empty directory
  file mkdir Dir3
  file mkdir "Dir1/New dir"
  # Missing
  file delete -- File3.txt trunk
  # Modify
  writefile File2.txt "Pending"
  writefile "F-utf-8.txt" "\xA53378" "utf-8"
  writefile "F-iso8859-1.txt" "\xA9 2022-2024" "iso8859-1"
  writefile "Dir1/F 3.txt" "Pending"
  delfile "Dir1/F2.txt" trunk
  writefile "Dir 2/F1.txt" "Pending"
  movefile "Dir1/F4.txt" "Dir1/FMoved.txt"
  # Change a file that's been moved. Only git detects this
  writefile "Dir1/FMoved.txt" "Post-move change"
  cd $WD
}
