diff options
Diffstat (limited to 'git-gui/lib')
-rw-r--r-- | git-gui/lib/blame.tcl | 90 | ||||
-rw-r--r-- | git-gui/lib/branch.tcl | 562 | ||||
-rw-r--r-- | git-gui/lib/branch_checkout.tcl | 89 | ||||
-rw-r--r-- | git-gui/lib/branch_create.tcl | 220 | ||||
-rw-r--r-- | git-gui/lib/branch_delete.tcl | 149 | ||||
-rw-r--r-- | git-gui/lib/branch_rename.tcl | 14 | ||||
-rw-r--r-- | git-gui/lib/browser.tcl | 25 | ||||
-rw-r--r-- | git-gui/lib/checkout_op.tcl | 579 | ||||
-rw-r--r-- | git-gui/lib/choose_rev.tcl | 367 | ||||
-rw-r--r-- | git-gui/lib/class.tcl | 38 | ||||
-rw-r--r-- | git-gui/lib/commit.tcl | 65 | ||||
-rw-r--r-- | git-gui/lib/console.tcl | 73 | ||||
-rw-r--r-- | git-gui/lib/database.tcl | 2 | ||||
-rw-r--r-- | git-gui/lib/diff.tcl | 26 | ||||
-rw-r--r-- | git-gui/lib/index.tcl | 48 | ||||
-rw-r--r-- | git-gui/lib/merge.tcl | 36 | ||||
-rw-r--r-- | git-gui/lib/option.tcl | 2 | ||||
-rw-r--r-- | git-gui/lib/remote.tcl | 89 | ||||
-rw-r--r-- | git-gui/lib/remote_branch_delete.tcl | 6 | ||||
-rw-r--r-- | git-gui/lib/shortcut.tcl | 57 | ||||
-rw-r--r-- | git-gui/lib/status_bar.tcl | 96 | ||||
-rw-r--r-- | git-gui/lib/transport.tcl | 4 |
22 files changed, 1835 insertions, 802 deletions
diff --git a/git-gui/lib/blame.tcl b/git-gui/lib/blame.tcl index b523654815..4bdb9a27a3 100644 --- a/git-gui/lib/blame.tcl +++ b/git-gui/lib/blame.tcl @@ -21,7 +21,7 @@ field w_amov ; # text column: annotations + move tracking field w_asim ; # text column: annotations (simple computation) field w_file ; # text column: actual file data field w_cviewer ; # pane showing commit message -field status ; # text variable bound to status bar +field status ; # status mega-widget instance field old_height ; # last known height of $w.file_pane # Tk UI colors @@ -33,6 +33,13 @@ variable group_colors { #ececec } +# Switches for original location detection +# +variable original_options [list -C -C] +if {[git-version >= 1.5.3]} { + lappend original_options -w ; # ignore indentation changes +} + # Current blame data; cleared/reset on each load # field commit ; # input commit to blame @@ -235,14 +242,7 @@ constructor new {i_commit i_path} { pack $w.file_pane.cm.sbx -side bottom -fill x pack $w_cviewer -expand 1 -fill both - frame $w.status \ - -borderwidth 1 \ - -relief sunken - label $w.status.l \ - -textvariable @status \ - -anchor w \ - -justify left - pack $w.status.l -side left + set status [::status_bar::new $w.status] menu $w.ctxm -tearoff 0 $w.ctxm add command \ @@ -304,8 +304,9 @@ constructor new {i_commit i_path} { set req_w [winfo reqwidth $top] set req_h [winfo reqheight $top] + set scr_h [expr {[winfo screenheight $top] - 100}] if {$req_w < 600} {set req_w 600} - if {$req_h < 400} {set req_h 400} + if {$req_h < $scr_h} {set req_h $scr_h} set g "${req_w}x${req_h}" wm geometry $top $g update @@ -352,19 +353,6 @@ method _load {jump} { set total_lines 0 } - if {[winfo exists $w.status.c]} { - $w.status.c coords bar 0 0 0 20 - } else { - canvas $w.status.c \ - -width 100 \ - -height [expr {int([winfo reqheight $w.status.l] * 0.6)}] \ - -borderwidth 1 \ - -relief groove \ - -highlightt 0 - $w.status.c create rectangle 0 0 0 20 -tags bar -fill navy - pack $w.status.c -side right - } - if {$history eq {}} { $w_back conf -state disabled } else { @@ -378,13 +366,12 @@ method _load {jump} { set amov_data [list [list]] set asim_data [list [list]] - set status "Loading $commit:[escape_path $path]..." + $status show "Reading $commit:[escape_path $path]..." $w_path conf -text [escape_path $path] if {$commit eq {}} { set fd [open $path r] } else { - set cmd [list git cat-file blob "$commit:$path"] - set fd [open "| $cmd" r] + set fd [git_read cat-file blob "$commit:$path"] } fconfigure $fd -blocking 0 -translation lf -encoding binary fileevent $fd readable [cb _read_file $fd $jump] @@ -487,30 +474,28 @@ method _read_file {fd jump} { } ifdeleted { catch {close $fd} } method _exec_blame {cur_w cur_d options cur_s} { - set cmd [list] - if {![is_Windows] || [is_Cygwin]} { - lappend cmd nice - } - lappend cmd git blame - set cmd [concat $cmd $options] - lappend cmd --incremental + lappend options --incremental if {$commit eq {}} { - lappend cmd --contents $path + lappend options --contents $path } else { - lappend cmd $commit + lappend options $commit } - lappend cmd -- $path - set fd [open "| $cmd" r] + lappend options -- $path + set fd [eval git_read --nice blame $options] fconfigure $fd -blocking 0 -translation lf -encoding binary - fileevent $fd readable [cb _read_blame $fd $cur_w $cur_d $cur_s] + fileevent $fd readable [cb _read_blame $fd $cur_w $cur_d] set current_fd $fd set blame_lines 0 - _status $this $cur_s + + $status start \ + "Loading$cur_s annotations..." \ + {lines annotated} } -method _read_blame {fd cur_w cur_d cur_s} { +method _read_blame {fd cur_w cur_d} { upvar #0 $cur_d line_data variable group_colors + variable original_options if {$fd ne $current_fd} { catch {close $fd} @@ -547,6 +532,10 @@ method _read_blame {fd cur_w cur_d cur_s} { set a_name {} catch {set a_name $header($cmit,author)} while {$a_name ne {}} { + if {$author_abbr ne {} + && [string index $a_name 0] eq {'}} { + regsub {^'[^']+'\s+} $a_name {} a_name + } if {![regexp {^([[:upper:]])} $a_name _a]} break append author_abbr $_a unset _a @@ -680,30 +669,17 @@ method _read_blame {fd cur_w cur_d cur_s} { close $fd if {$cur_w eq $w_asim} { _exec_blame $this $w_amov @amov_data \ - [list -M -C -C] \ + $original_options \ { original location} } else { set current_fd {} - set status {Annotation complete.} - destroy $w.status.c + $status stop {Annotation complete.} } } else { - _status $this $cur_s + $status update $blame_lines $total_lines } } ifdeleted { catch {close $fd} } -method _status {cur_s} { - set have $blame_lines - set total $total_lines - set pdone 0 - if {$total} {set pdone [expr {100 * $have / $total}]} - - set status [format \ - "Loading%s annotations... %i of %i lines annotated (%2i%%)" \ - $cur_s $have $total $pdone] - $w.status.c coords bar 0 0 $pdone 20 -} - method _click {cur_w pos} { set lno [lindex [split [$cur_w index $pos] .] 0] _showcommit $this $cur_w $lno @@ -784,7 +760,7 @@ method _showcommit {cur_w lno} { if {[catch {set msg $header($cmit,message)}]} { set msg {} catch { - set fd [open "| git cat-file commit $cmit" r] + set fd [git_read cat-file commit $cmit] fconfigure $fd -encoding binary -translation lf if {[catch {set enc $repo_config(i18n.commitencoding)}]} { set enc utf-8 diff --git a/git-gui/lib/branch.tcl b/git-gui/lib/branch.tcl index 4f648b2bc7..777eeb79c1 100644 --- a/git-gui/lib/branch.tcl +++ b/git-gui/lib/branch.tcl @@ -2,573 +2,37 @@ # Copyright (C) 2006, 2007 Shawn Pearce proc load_all_heads {} { - global all_heads + global some_heads_tracking + set rh refs/heads + set rh_len [expr {[string length $rh] + 1}] set all_heads [list] - set fd [open "| git for-each-ref --format=%(refname) refs/heads" r] + set fd [git_read for-each-ref --format=%(refname) $rh] while {[gets $fd line] > 0} { - if {[is_tracking_branch $line]} continue - if {![regsub ^refs/heads/ $line {} name]} continue - lappend all_heads $name + if {!$some_heads_tracking || ![is_tracking_branch $line]} { + lappend all_heads [string range $line $rh_len end] + } } close $fd - set all_heads [lsort $all_heads] + return [lsort $all_heads] } proc load_all_tags {} { set all_tags [list] - set fd [open "| git for-each-ref --format=%(refname) refs/tags" r] + set fd [git_read for-each-ref \ + --sort=-taggerdate \ + --format=%(refname) \ + refs/tags] while {[gets $fd line] > 0} { if {![regsub ^refs/tags/ $line {} name]} continue lappend all_tags $name } close $fd - - return [lsort $all_tags] -} - -proc populate_branch_menu {} { - global all_heads disable_on_lock - - set m .mbar.branch - set last [$m index last] - for {set i 0} {$i <= $last} {incr i} { - if {[$m type $i] eq {separator}} { - $m delete $i last - set new_dol [list] - foreach a $disable_on_lock { - if {[lindex $a 0] ne $m || [lindex $a 2] < $i} { - lappend new_dol $a - } - } - set disable_on_lock $new_dol - break - } - } - - if {$all_heads ne {}} { - $m add separator - } - foreach b $all_heads { - $m add radiobutton \ - -label $b \ - -command [list switch_branch $b] \ - -variable current_branch \ - -value $b - lappend disable_on_lock \ - [list $m entryconf [$m index last] -state] - } -} - -proc do_create_branch_action {w} { - global all_heads null_sha1 repo_config - global create_branch_checkout create_branch_revtype - global create_branch_head create_branch_trackinghead - global create_branch_name create_branch_revexp - global create_branch_tag - - set newbranch $create_branch_name - if {$newbranch eq {} - || $newbranch eq $repo_config(gui.newbranchtemplate)} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Please supply a branch name." - focus $w.desc.name_t - return - } - if {![catch {git show-ref --verify -- "refs/heads/$newbranch"}]} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Branch '$newbranch' already exists." - focus $w.desc.name_t - return - } - if {[catch {git check-ref-format "heads/$newbranch"}]} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "We do not like '$newbranch' as a branch name." - focus $w.desc.name_t - return - } - - set rev {} - switch -- $create_branch_revtype { - head {set rev $create_branch_head} - tracking {set rev $create_branch_trackinghead} - tag {set rev $create_branch_tag} - expression {set rev $create_branch_revexp} - } - if {[catch {set cmt [git rev-parse --verify "${rev}^0"]}]} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Invalid starting revision: $rev" - return - } - if {[catch { - git update-ref \ - -m "branch: Created from $rev" \ - "refs/heads/$newbranch" \ - $cmt \ - $null_sha1 - } err]} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Failed to create '$newbranch'.\n\n$err" - return - } - - lappend all_heads $newbranch - set all_heads [lsort $all_heads] - populate_branch_menu - destroy $w - if {$create_branch_checkout} { - switch_branch $newbranch - } + return $all_tags } proc radio_selector {varname value args} { upvar #0 $varname var set var $value } - -trace add variable create_branch_head write \ - [list radio_selector create_branch_revtype head] -trace add variable create_branch_trackinghead write \ - [list radio_selector create_branch_revtype tracking] -trace add variable create_branch_tag write \ - [list radio_selector create_branch_revtype tag] - -trace add variable delete_branch_head write \ - [list radio_selector delete_branch_checktype head] -trace add variable delete_branch_trackinghead write \ - [list radio_selector delete_branch_checktype tracking] - -proc do_create_branch {} { - global all_heads current_branch repo_config - global create_branch_checkout create_branch_revtype - global create_branch_head create_branch_trackinghead - global create_branch_name create_branch_revexp - global create_branch_tag - - set w .branch_editor - toplevel $w - wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - - label $w.header -text {Create New Branch} \ - -font font_uibold - pack $w.header -side top -fill x - - frame $w.buttons - button $w.buttons.create -text Create \ - -default active \ - -command [list do_create_branch_action $w] - pack $w.buttons.create -side right - button $w.buttons.cancel -text {Cancel} \ - -command [list destroy $w] - pack $w.buttons.cancel -side right -padx 5 - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - - labelframe $w.desc -text {Branch Description} - label $w.desc.name_l -text {Name:} - entry $w.desc.name_t \ - -borderwidth 1 \ - -relief sunken \ - -width 40 \ - -textvariable create_branch_name \ - -validate key \ - -validatecommand { - if {%d == 1 && [regexp {[~^:?*\[\0- ]} %S]} {return 0} - return 1 - } - grid $w.desc.name_l $w.desc.name_t -sticky we -padx {0 5} - grid columnconfigure $w.desc 1 -weight 1 - pack $w.desc -anchor nw -fill x -pady 5 -padx 5 - - labelframe $w.from -text {Starting Revision} - if {$all_heads ne {}} { - radiobutton $w.from.head_r \ - -text {Local Branch:} \ - -value head \ - -variable create_branch_revtype - eval tk_optionMenu $w.from.head_m create_branch_head $all_heads - grid $w.from.head_r $w.from.head_m -sticky w - } - set all_trackings [all_tracking_branches] - if {$all_trackings ne {}} { - set create_branch_trackinghead [lindex $all_trackings 0] - radiobutton $w.from.tracking_r \ - -text {Tracking Branch:} \ - -value tracking \ - -variable create_branch_revtype - eval tk_optionMenu $w.from.tracking_m \ - create_branch_trackinghead \ - $all_trackings - grid $w.from.tracking_r $w.from.tracking_m -sticky w - } - set all_tags [load_all_tags] - if {$all_tags ne {}} { - set create_branch_tag [lindex $all_tags 0] - radiobutton $w.from.tag_r \ - -text {Tag:} \ - -value tag \ - -variable create_branch_revtype - eval tk_optionMenu $w.from.tag_m create_branch_tag $all_tags - grid $w.from.tag_r $w.from.tag_m -sticky w - } - radiobutton $w.from.exp_r \ - -text {Revision Expression:} \ - -value expression \ - -variable create_branch_revtype - entry $w.from.exp_t \ - -borderwidth 1 \ - -relief sunken \ - -width 50 \ - -textvariable create_branch_revexp \ - -validate key \ - -validatecommand { - if {%d == 1 && [regexp {\s} %S]} {return 0} - if {%d == 1 && [string length %S] > 0} { - set create_branch_revtype expression - } - return 1 - } - grid $w.from.exp_r $w.from.exp_t -sticky we -padx {0 5} - grid columnconfigure $w.from 1 -weight 1 - pack $w.from -anchor nw -fill x -pady 5 -padx 5 - - labelframe $w.postActions -text {Post Creation Actions} - checkbutton $w.postActions.checkout \ - -text {Checkout after creation} \ - -variable create_branch_checkout - pack $w.postActions.checkout -anchor nw - pack $w.postActions -anchor nw -fill x -pady 5 -padx 5 - - set create_branch_checkout 1 - set create_branch_head $current_branch - set create_branch_revtype head - set create_branch_name $repo_config(gui.newbranchtemplate) - set create_branch_revexp {} - - bind $w <Visibility> " - grab $w - $w.desc.name_t icursor end - focus $w.desc.name_t - " - bind $w <Key-Escape> "destroy $w" - bind $w <Key-Return> "do_create_branch_action $w;break" - wm title $w "[appname] ([reponame]): Create Branch" - tkwait window $w -} - -proc do_delete_branch_action {w} { - global all_heads - global delete_branch_checktype delete_branch_head delete_branch_trackinghead - - set check_rev {} - switch -- $delete_branch_checktype { - head {set check_rev $delete_branch_head} - tracking {set check_rev $delete_branch_trackinghead} - always {set check_rev {:none}} - } - if {$check_rev eq {:none}} { - set check_cmt {} - } elseif {[catch {set check_cmt [git rev-parse --verify "${check_rev}^0"]}]} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Invalid check revision: $check_rev" - return - } - - set to_delete [list] - set not_merged [list] - foreach i [$w.list.l curselection] { - set b [$w.list.l get $i] - if {[catch {set o [git rev-parse --verify $b]}]} continue - if {$check_cmt ne {}} { - if {$b eq $check_rev} continue - if {[catch {set m [git merge-base $o $check_cmt]}]} continue - if {$o ne $m} { - lappend not_merged $b - continue - } - } - lappend to_delete [list $b $o] - } - if {$not_merged ne {}} { - set msg "The following branches are not completely merged into $check_rev: - - - [join $not_merged "\n - "]" - tk_messageBox \ - -icon info \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message $msg - } - if {$to_delete eq {}} return - if {$delete_branch_checktype eq {always}} { - set msg {Recovering deleted branches is difficult. - -Delete the selected branches?} - if {[tk_messageBox \ - -icon warning \ - -type yesno \ - -title [wm title $w] \ - -parent $w \ - -message $msg] ne yes} { - return - } - } - - set failed {} - foreach i $to_delete { - set b [lindex $i 0] - set o [lindex $i 1] - if {[catch {git update-ref -d "refs/heads/$b" $o} err]} { - append failed " - $b: $err\n" - } else { - set x [lsearch -sorted -exact $all_heads $b] - if {$x >= 0} { - set all_heads [lreplace $all_heads $x $x] - } - } - } - - if {$failed ne {}} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Failed to delete branches:\n$failed" - } - - set all_heads [lsort $all_heads] - populate_branch_menu - destroy $w -} - -proc do_delete_branch {} { - global all_heads tracking_branches current_branch - global delete_branch_checktype delete_branch_head delete_branch_trackinghead - - set w .branch_editor - toplevel $w - wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - - label $w.header -text {Delete Local Branch} \ - -font font_uibold - pack $w.header -side top -fill x - - frame $w.buttons - button $w.buttons.create -text Delete \ - -command [list do_delete_branch_action $w] - pack $w.buttons.create -side right - button $w.buttons.cancel -text {Cancel} \ - -command [list destroy $w] - pack $w.buttons.cancel -side right -padx 5 - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - - labelframe $w.list -text {Local Branches} - listbox $w.list.l \ - -height 10 \ - -width 70 \ - -selectmode extended \ - -yscrollcommand [list $w.list.sby set] - foreach h $all_heads { - if {$h ne $current_branch} { - $w.list.l insert end $h - } - } - scrollbar $w.list.sby -command [list $w.list.l yview] - pack $w.list.sby -side right -fill y - pack $w.list.l -side left -fill both -expand 1 - pack $w.list -fill both -expand 1 -pady 5 -padx 5 - - labelframe $w.validate -text {Delete Only If} - radiobutton $w.validate.head_r \ - -text {Merged Into Local Branch:} \ - -value head \ - -variable delete_branch_checktype - eval tk_optionMenu $w.validate.head_m delete_branch_head $all_heads - grid $w.validate.head_r $w.validate.head_m -sticky w - set all_trackings [all_tracking_branches] - if {$all_trackings ne {}} { - set delete_branch_trackinghead [lindex $all_trackings 0] - radiobutton $w.validate.tracking_r \ - -text {Merged Into Tracking Branch:} \ - -value tracking \ - -variable delete_branch_checktype - eval tk_optionMenu $w.validate.tracking_m \ - delete_branch_trackinghead \ - $all_trackings - grid $w.validate.tracking_r $w.validate.tracking_m -sticky w - } - radiobutton $w.validate.always_r \ - -text {Always (Do not perform merge checks)} \ - -value always \ - -variable delete_branch_checktype - grid $w.validate.always_r -columnspan 2 -sticky w - grid columnconfigure $w.validate 1 -weight 1 - pack $w.validate -anchor nw -fill x -pady 5 -padx 5 - - set delete_branch_head $current_branch - set delete_branch_checktype head - - bind $w <Visibility> "grab $w; focus $w" - bind $w <Key-Escape> "destroy $w" - wm title $w "[appname] ([reponame]): Delete Branch" - tkwait window $w -} - -proc switch_branch {new_branch} { - global HEAD commit_type current_branch repo_config - - if {![lock_index switch]} return - - # -- Our in memory state should match the repository. - # - repository_state curType curHEAD curMERGE_HEAD - if {[string match amend* $commit_type] - && $curType eq {normal} - && $curHEAD eq $HEAD} { - } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} { - info_popup {Last scanned state does not match repository state. - -Another Git program has modified this repository since the last scan. A rescan must be performed before the current branch can be changed. - -The rescan will be automatically started now. -} - unlock_index - rescan {set ui_status_value {Ready.}} - return - } - - # -- Don't do a pointless switch. - # - if {$current_branch eq $new_branch} { - unlock_index - return - } - - if {$repo_config(gui.trustmtime) eq {true}} { - switch_branch_stage2 {} $new_branch - } else { - set ui_status_value {Refreshing file status...} - set cmd [list git update-index] - lappend cmd -q - lappend cmd --unmerged - lappend cmd --ignore-missing - lappend cmd --refresh - set fd_rf [open "| $cmd" r] - fconfigure $fd_rf -blocking 0 -translation binary - fileevent $fd_rf readable \ - [list switch_branch_stage2 $fd_rf $new_branch] - } -} - -proc switch_branch_stage2 {fd_rf new_branch} { - global ui_status_value HEAD - - if {$fd_rf ne {}} { - read $fd_rf - if {![eof $fd_rf]} return - close $fd_rf - } - - set ui_status_value "Updating working directory to '$new_branch'..." - set cmd [list git read-tree] - lappend cmd -m - lappend cmd -u - lappend cmd --exclude-per-directory=.gitignore - lappend cmd $HEAD - lappend cmd $new_branch - set fd_rt [open "| $cmd" r] - fconfigure $fd_rt -blocking 0 -translation binary - fileevent $fd_rt readable \ - [list switch_branch_readtree_wait $fd_rt $new_branch] -} - -proc switch_branch_readtree_wait {fd_rt new_branch} { - global selected_commit_type commit_type HEAD MERGE_HEAD PARENT - global current_branch - global ui_comm ui_status_value - - # -- We never get interesting output on stdout; only stderr. - # - read $fd_rt - fconfigure $fd_rt -blocking 1 - if {![eof $fd_rt]} { - fconfigure $fd_rt -blocking 0 - return - } - - # -- The working directory wasn't in sync with the index and - # we'd have to overwrite something to make the switch. A - # merge is required. - # - if {[catch {close $fd_rt} err]} { - regsub {^fatal: } $err {} err - warn_popup "File level merge required. - -$err - -Staying on branch '$current_branch'." - set ui_status_value "Aborted checkout of '$new_branch' (file level merging is required)." - unlock_index - return - } - - # -- Update the symbolic ref. Core git doesn't even check for failure - # here, it Just Works(tm). If it doesn't we are in some really ugly - # state that is difficult to recover from within git-gui. - # - if {[catch {git symbolic-ref HEAD "refs/heads/$new_branch"} err]} { - error_popup "Failed to set current branch. - -This working directory is only partially switched. We successfully updated your files, but failed to update an internal Git file. - -This should not have occurred. [appname] will now close and give up. - -$err" - do_quit - return - } - - # -- Update our repository state. If we were previously in amend mode - # we need to toss the current buffer and do a full rescan to update - # our file lists. If we weren't in amend mode our file lists are - # accurate and we can avoid the rescan. - # - unlock_index - set selected_commit_type new - if {[string match amend* $commit_type]} { - $ui_comm delete 0.0 end - $ui_comm edit reset - $ui_comm edit modified false - rescan {set ui_status_value "Checked out branch '$current_branch'."} - } else { - repository_state commit_type HEAD MERGE_HEAD - set PARENT $HEAD - set ui_status_value "Checked out branch '$current_branch'." - } -} diff --git a/git-gui/lib/branch_checkout.tcl b/git-gui/lib/branch_checkout.tcl new file mode 100644 index 0000000000..72c45b4554 --- /dev/null +++ b/git-gui/lib/branch_checkout.tcl @@ -0,0 +1,89 @@ +# git-gui branch checkout support +# Copyright (C) 2007 Shawn Pearce + +class branch_checkout { + +field w ; # widget path +field w_rev ; # mega-widget to pick the initial revision + +field opt_fetch 1; # refetch tracking branch if used? +field opt_detach 0; # force a detached head case? + +constructor dialog {} { + make_toplevel top w + wm title $top "[appname] ([reponame]): Checkout Branch" + if {$top ne {.}} { + wm geometry $top "+[winfo rootx .]+[winfo rooty .]" + } + + label $w.header -text {Checkout Branch} -font font_uibold + pack $w.header -side top -fill x + + frame $w.buttons + button $w.buttons.create -text Checkout \ + -default active \ + -command [cb _checkout] + pack $w.buttons.create -side right + button $w.buttons.cancel -text {Cancel} \ + -command [list destroy $w] + pack $w.buttons.cancel -side right -padx 5 + pack $w.buttons -side bottom -fill x -pady 10 -padx 10 + + set w_rev [::choose_rev::new $w.rev {Revision}] + $w_rev bind_listbox <Double-Button-1> [cb _checkout] + pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5 + + labelframe $w.options -text {Options} + + checkbutton $w.options.fetch \ + -text {Fetch Tracking Branch} \ + -variable @opt_fetch + pack $w.options.fetch -anchor nw + + checkbutton $w.options.detach \ + -text {Detach From Local Branch} \ + -variable @opt_detach + pack $w.options.detach -anchor nw + + pack $w.options -anchor nw -fill x -pady 5 -padx 5 + + bind $w <Visibility> [cb _visible] + bind $w <Key-Escape> [list destroy $w] + bind $w <Key-Return> [cb _checkout]\;break + tkwait window $w +} + +method _checkout {} { + set spec [$w_rev get_tracking_branch] + if {$spec ne {} && $opt_fetch} { + set new {} + } elseif {[catch {set new [$w_rev commit_or_die]}]} { + return + } + + if {$opt_detach} { + set ref {} + } else { + set ref [$w_rev get_local_branch] + } + + set co [::checkout_op::new [$w_rev get] $new $ref] + $co parent $w + $co enable_checkout 1 + if {$spec ne {} && $opt_fetch} { + $co enable_fetch $spec + } + + if {[$co run]} { + destroy $w + } else { + $w_rev focus_filter + } +} + +method _visible {} { + grab $w + $w_rev focus_filter +} + +} diff --git a/git-gui/lib/branch_create.tcl b/git-gui/lib/branch_create.tcl new file mode 100644 index 0000000000..def615d19d --- /dev/null +++ b/git-gui/lib/branch_create.tcl @@ -0,0 +1,220 @@ +# git-gui branch create support +# Copyright (C) 2006, 2007 Shawn Pearce + +class branch_create { + +field w ; # widget path +field w_rev ; # mega-widget to pick the initial revision +field w_name ; # new branch name widget + +field name {}; # name of the branch the user has chosen +field name_type user; # type of branch name to use + +field opt_merge ff; # type of merge to apply to existing branch +field opt_checkout 1; # automatically checkout the new branch? +field opt_fetch 1; # refetch tracking branch if used? +field reset_ok 0; # did the user agree to reset? + +constructor dialog {} { + global repo_config + + make_toplevel top w + wm title $top "[appname] ([reponame]): Create Branch" + if {$top ne {.}} { + wm geometry $top "+[winfo rootx .]+[winfo rooty .]" + } + + label $w.header -text {Create New Branch} -font font_uibold + pack $w.header -side top -fill x + + frame $w.buttons + button $w.buttons.create -text Create \ + -default active \ + -command [cb _create] + pack $w.buttons.create -side right + button $w.buttons.cancel -text {Cancel} \ + -command [list destroy $w] + pack $w.buttons.cancel -side right -padx 5 + pack $w.buttons -side bottom -fill x -pady 10 -padx 10 + + labelframe $w.desc -text {Branch Name} + radiobutton $w.desc.name_r \ + -anchor w \ + -text {Name:} \ + -value user \ + -variable @name_type + set w_name $w.desc.name_t + entry $w_name \ + -borderwidth 1 \ + -relief sunken \ + -width 40 \ + -textvariable @name \ + -validate key \ + -validatecommand [cb _validate %d %S] + grid $w.desc.name_r $w_name -sticky we -padx {0 5} + + radiobutton $w.desc.match_r \ + -anchor w \ + -text {Match Tracking Branch Name} \ + -value match \ + -variable @name_type + grid $w.desc.match_r -sticky we -padx {0 5} -columnspan 2 + + grid columnconfigure $w.desc 1 -weight 1 + pack $w.desc -anchor nw -fill x -pady 5 -padx 5 + + set w_rev [::choose_rev::new $w.rev {Starting Revision}] + pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5 + + labelframe $w.options -text {Options} + + frame $w.options.merge + label $w.options.merge.l -text {Update Existing Branch:} + pack $w.options.merge.l -side left + radiobutton $w.options.merge.no \ + -text No \ + -value none \ + -variable @opt_merge + pack $w.options.merge.no -side left + radiobutton $w.options.merge.ff \ + -text {Fast Forward Only} \ + -value ff \ + -variable @opt_merge + pack $w.options.merge.ff -side left + radiobutton $w.options.merge.reset \ + -text {Reset} \ + -value reset \ + -variable @opt_merge + pack $w.options.merge.reset -side left + pack $w.options.merge -anchor nw + + checkbutton $w.options.fetch \ + -text {Fetch Tracking Branch} \ + -variable @opt_fetch + pack $w.options.fetch -anchor nw + + checkbutton $w.options.checkout \ + -text {Checkout After Creation} \ + -variable @opt_checkout + pack $w.options.checkout -anchor nw + pack $w.options -anchor nw -fill x -pady 5 -padx 5 + + trace add variable @name_type write [cb _select] + + set name $repo_config(gui.newbranchtemplate) + if {[is_config_true gui.matchtrackingbranch]} { + set name_type match + } + + bind $w <Visibility> [cb _visible] + bind $w <Key-Escape> [list destroy $w] + bind $w <Key-Return> [cb _create]\;break + tkwait window $w +} + +method _create {} { + global repo_config + global M1B + + set spec [$w_rev get_tracking_branch] + switch -- $name_type { + user { + set newbranch $name + } + match { + if {$spec eq {}} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Please select a tracking branch." + return + } + if {![regsub ^refs/heads/ [lindex $spec 2] {} newbranch]} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Tracking branch [$w get] is not a branch in the remote repository." + return + } + } + } + + if {$newbranch eq {} + || $newbranch eq $repo_config(gui.newbranchtemplate)} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Please supply a branch name." + focus $w_name + return + } + + if {[catch {git check-ref-format "heads/$newbranch"}]} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "'$newbranch' is not an acceptable branch name." + focus $w_name + return + } + + if {$spec ne {} && $opt_fetch} { + set new {} + } elseif {[catch {set new [$w_rev commit_or_die]}]} { + return + } + + set co [::checkout_op::new \ + [$w_rev get] \ + $new \ + refs/heads/$newbranch] + $co parent $w + $co enable_create 1 + $co enable_merge $opt_merge + $co enable_checkout $opt_checkout + if {$spec ne {} && $opt_fetch} { + $co enable_fetch $spec + } + + if {[$co run]} { + destroy $w + } else { + focus $w_name + } +} + +method _validate {d S} { + if {$d == 1} { + if {[regexp {[~^:?*\[\0- ]} $S]} { + return 0 + } + if {[string length $S] > 0} { + set name_type user + } + } + return 1 +} + +method _select {args} { + if {$name_type eq {match}} { + $w_rev pick_tracking_branch + } +} + +method _visible {} { + grab $w + if {$name_type eq {user}} { + $w_name icursor end + focus $w_name + } +} + +} diff --git a/git-gui/lib/branch_delete.tcl b/git-gui/lib/branch_delete.tcl new file mode 100644 index 0000000000..c7573c6c72 --- /dev/null +++ b/git-gui/lib/branch_delete.tcl @@ -0,0 +1,149 @@ +# git-gui branch delete support +# Copyright (C) 2007 Shawn Pearce + +class branch_delete { + +field w ; # widget path +field w_heads ; # listbox of local head names +field w_check ; # revision picker for merge test +field w_delete ; # delete button + +constructor dialog {} { + global current_branch + + make_toplevel top w + wm title $top "[appname] ([reponame]): Delete Branch" + if {$top ne {.}} { + wm geometry $top "+[winfo rootx .]+[winfo rooty .]" + } + + label $w.header -text {Delete Local Branch} -font font_uibold + pack $w.header -side top -fill x + + frame $w.buttons + set w_delete $w.buttons.delete + button $w_delete \ + -text Delete \ + -default active \ + -state disabled \ + -command [cb _delete] + pack $w_delete -side right + button $w.buttons.cancel \ + -text {Cancel} \ + -command [list destroy $w] + pack $w.buttons.cancel -side right -padx 5 + pack $w.buttons -side bottom -fill x -pady 10 -padx 10 + + labelframe $w.list -text {Local Branches} + set w_heads $w.list.l + listbox $w_heads \ + -height 10 \ + -width 70 \ + -selectmode extended \ + -exportselection false \ + -yscrollcommand [list $w.list.sby set] + scrollbar $w.list.sby -command [list $w.list.l yview] + pack $w.list.sby -side right -fill y + pack $w.list.l -side left -fill both -expand 1 + pack $w.list -fill both -expand 1 -pady 5 -padx 5 + + set w_check [choose_rev::new \ + $w.check \ + {Delete Only If Merged Into} \ + ] + $w_check none {Always (Do not perform merge test.)} + pack $w.check -anchor nw -fill x -pady 5 -padx 5 + + foreach h [load_all_heads] { + if {$h ne $current_branch} { + $w_heads insert end $h + } + } + + bind $w_heads <<ListboxSelect>> [cb _select] + bind $w <Visibility> " + grab $w + focus $w + " + bind $w <Key-Escape> [list destroy $w] + bind $w <Key-Return> [cb _delete]\;break + tkwait window $w +} + +method _select {} { + if {[$w_heads curselection] eq {}} { + $w_delete configure -state disabled + } else { + $w_delete configure -state normal + } +} + +method _delete {} { + if {[catch {set check_cmt [$w_check commit_or_die]}]} { + return + } + + set to_delete [list] + set not_merged [list] + foreach i [$w_heads curselection] { + set b [$w_heads get $i] + if {[catch { + set o [git rev-parse --verify "refs/heads/$b"] + }]} continue + if {$check_cmt ne {}} { + if {[catch {set m [git merge-base $o $check_cmt]}]} continue + if {$o ne $m} { + lappend not_merged $b + continue + } + } + lappend to_delete [list $b $o] + } + if {$not_merged ne {}} { + set msg "The following branches are not completely merged into [$w_check get]: + + - [join $not_merged "\n - "]" + tk_messageBox \ + -icon info \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message $msg + } + if {$to_delete eq {}} return + if {$check_cmt eq {}} { + set msg {Recovering deleted branches is difficult. + +Delete the selected branches?} + if {[tk_messageBox \ + -icon warning \ + -type yesno \ + -title [wm title $w] \ + -parent $w \ + -message $msg] ne yes} { + return + } + } + + set failed {} + foreach i $to_delete { + set b [lindex $i 0] + set o [lindex $i 1] + if {[catch {git update-ref -d "refs/heads/$b" $o} err]} { + append failed " - $b: $err\n" + } + } + + if {$failed ne {}} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Failed to delete branches:\n$failed" + } + + destroy $w +} + +} diff --git a/git-gui/lib/branch_rename.tcl b/git-gui/lib/branch_rename.tcl index 405101637f..1cadc31d20 100644 --- a/git-gui/lib/branch_rename.tcl +++ b/git-gui/lib/branch_rename.tcl @@ -8,7 +8,7 @@ field oldname field newname constructor dialog {} { - global all_heads current_branch + global current_branch make_toplevel top w wm title $top "[appname] ([reponame]): Rename Branch" @@ -34,7 +34,7 @@ constructor dialog {} { frame $w.rename label $w.rename.oldname_l -text {Branch:} - eval tk_optionMenu $w.rename.oldname_m @oldname $all_heads + eval tk_optionMenu $w.rename.oldname_m @oldname [load_all_heads] label $w.rename.newname_l -text {New Name:} entry $w.rename.newname_t \ @@ -64,7 +64,7 @@ constructor dialog {} { } method _rename {} { - global all_heads current_branch + global current_branch if {$oldname eq {}} { tk_messageBox \ @@ -118,14 +118,6 @@ method _rename {} { return } - set oldidx [lsearch -exact -sorted $all_heads $oldname] - if {$oldidx >= 0} { - set all_heads [lreplace $all_heads $oldidx $oldidx] - } - lappend all_heads $newname - set all_heads [lsort $all_heads] - populate_branch_menu - if {$current_branch eq $oldname} { set current_branch $newname } diff --git a/git-gui/lib/browser.tcl b/git-gui/lib/browser.tcl index 3d6341bcc5..911e5af7f4 100644 --- a/git-gui/lib/browser.tcl +++ b/git-gui/lib/browser.tcl @@ -11,6 +11,8 @@ field browser_status {Starting...} field browser_stack {} field browser_busy 1 +field ls_buf {}; # Buffered record output from ls-tree + constructor new {commit} { global cursor_ptr M1B make_toplevel top w @@ -160,7 +162,7 @@ method _click {was_double_click pos} { } method _ls {tree_id {name {}}} { - set browser_buffer {} + set ls_buf {} set browser_files {} set browser_busy 1 @@ -178,24 +180,25 @@ method _ls {tree_id {name {}}} { lappend browser_stack [list $tree_id $name] $w conf -state disabled - set cmd [list git ls-tree -z $tree_id] - set fd [open "| $cmd" r] + set fd [git_read ls-tree -z $tree_id] fconfigure $fd -blocking 0 -translation binary -encoding binary fileevent $fd readable [cb _read $fd] } method _read {fd} { - append browser_buffer [read $fd] - set pck [split $browser_buffer "\0"] - set browser_buffer [lindex $pck end] + append ls_buf [read $fd] + set pck [split $ls_buf "\0"] + set ls_buf [lindex $pck end] set n [llength $browser_files] $w conf -state normal foreach p [lrange $pck 0 end-1] { - set info [split $p "\t"] - set path [lindex $info 1] - set info [split [lindex $info 0] { }] - set type [lindex $info 1] + set tab [string first "\t" $p] + if {$tab == -1} continue + + set info [split [string range $p 0 [expr {$tab - 1}]] { }] + set path [string range $p [expr {$tab + 1}] end] + set type [lindex $info 1] set object [lindex $info 2] switch -- $type { @@ -225,7 +228,7 @@ method _read {fd} { close $fd set browser_status Ready. set browser_busy 0 - unset browser_buffer + set ls_buf {} if {$n > 0} { $w tag add in_sel 1.0 2.0 focus -force $w diff --git a/git-gui/lib/checkout_op.tcl b/git-gui/lib/checkout_op.tcl new file mode 100644 index 0000000000..00a994be12 --- /dev/null +++ b/git-gui/lib/checkout_op.tcl @@ -0,0 +1,579 @@ +# git-gui commit checkout support +# Copyright (C) 2007 Shawn Pearce + +class checkout_op { + +field w {}; # our window (if we have one) +field w_cons {}; # embedded console window object + +field new_expr ; # expression the user saw/thinks this is +field new_hash ; # commit SHA-1 we are switching to +field new_ref ; # ref we are updating/creating + +field parent_w .; # window that started us +field merge_type none; # type of merge to apply to existing branch +field fetch_spec {}; # refetch tracking branch if used? +field checkout 1; # actually checkout the branch? +field create 0; # create the branch if it doesn't exist? + +field reset_ok 0; # did the user agree to reset? +field fetch_ok 0; # did the fetch succeed? + +field readtree_d {}; # buffered output from read-tree +field update_old {}; # was the update-ref call deferred? +field reflog_msg {}; # log message for the update-ref call + +constructor new {expr hash {ref {}}} { + set new_expr $expr + set new_hash $hash + set new_ref $ref + + return $this +} + +method parent {path} { + set parent_w [winfo toplevel $path] +} + +method enable_merge {type} { + set merge_type $type +} + +method enable_fetch {spec} { + set fetch_spec $spec +} + +method enable_checkout {co} { + set checkout $co +} + +method enable_create {co} { + set create $co +} + +method run {} { + if {$fetch_spec ne {}} { + global M1B + + # We were asked to refresh a single tracking branch + # before we get to work. We should do that before we + # consider any ref updating. + # + set fetch_ok 0 + set l_trck [lindex $fetch_spec 0] + set remote [lindex $fetch_spec 1] + set r_head [lindex $fetch_spec 2] + regsub ^refs/heads/ $r_head {} r_name + + _toplevel $this {Refreshing Tracking Branch} + set w_cons [::console::embed \ + $w.console \ + "Fetching $r_name from $remote"] + pack $w.console -fill both -expand 1 + $w_cons exec \ + [list git fetch $remote +$r_head:$l_trck] \ + [cb _finish_fetch] + + bind $w <$M1B-Key-w> break + bind $w <$M1B-Key-W> break + bind $w <Visibility> " + [list grab $w] + [list focus $w] + " + wm protocol $w WM_DELETE_WINDOW [cb _noop] + tkwait window $w + + if {!$fetch_ok} { + delete_this + return 0 + } + } + + if {$new_ref ne {}} { + # If we have a ref we need to update it before we can + # proceed with a checkout (if one was enabled). + # + if {![_update_ref $this]} { + delete_this + return 0 + } + } + + if {$checkout} { + _checkout $this + return 1 + } + + delete_this + return 1 +} + +method _noop {} {} + +method _finish_fetch {ok} { + if {$ok} { + set l_trck [lindex $fetch_spec 0] + if {[catch {set new_hash [git rev-parse --verify "$l_trck^0"]} err]} { + set ok 0 + $w_cons insert "fatal: Cannot resolve $l_trck" + $w_cons insert $err + } + } + + $w_cons done $ok + set w_cons {} + wm protocol $w WM_DELETE_WINDOW {} + + if {$ok} { + destroy $w + set w {} + } else { + button $w.close -text Close -command [list destroy $w] + pack $w.close -side bottom -anchor e -padx 10 -pady 10 + } + + set fetch_ok $ok +} + +method _update_ref {} { + global null_sha1 current_branch + + set ref $new_ref + set new $new_hash + + set is_current 0 + set rh refs/heads/ + set rn [string length $rh] + if {[string equal -length $rn $rh $ref]} { + set newbranch [string range $ref $rn end] + if {$current_branch eq $newbranch} { + set is_current 1 + } + } else { + set newbranch $ref + } + + if {[catch {set cur [git rev-parse --verify "$ref^0"]}]} { + # Assume it does not exist, and that is what the error was. + # + if {!$create} { + _error $this "Branch '$newbranch' does not exist." + return 0 + } + + set reflog_msg "branch: Created from $new_expr" + set cur $null_sha1 + } elseif {$create && $merge_type eq {none}} { + # We were told to create it, but not do a merge. + # Bad. Name shouldn't have existed. + # + _error $this "Branch '$newbranch' already exists." + return 0 + } elseif {!$create && $merge_type eq {none}} { + # We aren't creating, it exists and we don't merge. + # We are probably just a simple branch switch. + # Use whatever value we just read. + # + set new $cur + set new_hash $cur + } elseif {$new eq $cur} { + # No merge would be required, don't compute anything. + # + } else { + set mrb {} + catch {set mrb [git merge-base $new $cur]} + switch -- $merge_type { + ff { + if {$mrb eq $new} { + # The current branch is actually newer. + # + set new $cur + } elseif {$mrb eq $cur} { + # The current branch is older. + # + set reflog_msg "merge $new_expr: Fast-forward" + } else { + _error $this "Branch '$newbranch' already exists.\n\nIt cannot fast-forward to $new_expr.\nA merge is required." + return 0 + } + } + reset { + if {$mrb eq $cur} { + # The current branch is older. + # + set reflog_msg "merge $new_expr: Fast-forward" + } else { + # The current branch will lose things. + # + if {[_confirm_reset $this $cur]} { + set reflog_msg "reset $new_expr" + } else { + return 0 + } + } + } + default { + _error $this "Only 'ff' and 'reset' merge is currently supported." + return 0 + } + } + } + + if {$new ne $cur} { + if {$is_current} { + # No so fast. We should defer this in case + # we cannot update the working directory. + # + set update_old $cur + return 1 + } + + if {[catch { + git update-ref -m $reflog_msg $ref $new $cur + } err]} { + _error $this "Failed to update '$newbranch'.\n\n$err" + return 0 + } + } + + return 1 +} + +method _checkout {} { + if {[lock_index checkout_op]} { + after idle [cb _start_checkout] + } else { + _error $this "Index is already locked." + delete_this + } +} + +method _start_checkout {} { + global HEAD commit_type + + # -- Our in memory state should match the repository. + # + repository_state curType curHEAD curMERGE_HEAD + if {[string match amend* $commit_type] + && $curType eq {normal} + && $curHEAD eq $HEAD} { + } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} { + info_popup {Last scanned state does not match repository state. + +Another Git program has modified this repository since the last scan. A rescan must be performed before the current branch can be changed. + +The rescan will be automatically started now. +} + unlock_index + rescan ui_ready + delete_this + return + } + + if {[is_config_true gui.trustmtime]} { + _readtree $this + } else { + ui_status {Refreshing file status...} + set fd [git_read update-index \ + -q \ + --unmerged \ + --ignore-missing \ + --refresh \ + ] + fconfigure $fd -blocking 0 -translation binary + fileevent $fd readable [cb _refresh_wait $fd] + } +} + +method _refresh_wait {fd} { + read $fd + if {[eof $fd]} { + close $fd + _readtree $this + } +} + +method _name {} { + if {$new_ref eq {}} { + return [string range $new_hash 0 7] + } + + set rh refs/heads/ + set rn [string length $rh] + if {[string equal -length $rn $rh $new_ref]} { + return [string range $new_ref $rn end] + } else { + return $new_ref + } +} + +method _readtree {} { + global HEAD + + set readtree_d {} + $::main_status start \ + "Updating working directory to '[_name $this]'..." \ + {files checked out} + + set fd [git_read --stderr read-tree \ + -m \ + -u \ + -v \ + --exclude-per-directory=.gitignore \ + $HEAD \ + $new_hash \ + ] + fconfigure $fd -blocking 0 -translation binary + fileevent $fd readable [cb _readtree_wait $fd] +} + +method _readtree_wait {fd} { + global current_branch + + set buf [read $fd] + $::main_status update_meter $buf + append readtree_d $buf + + fconfigure $fd -blocking 1 + if {![eof $fd]} { + fconfigure $fd -blocking 0 + return + } + + if {[catch {close $fd}]} { + set err $readtree_d + regsub {^fatal: } $err {} err + $::main_status stop "Aborted checkout of '[_name $this]' (file level merging is required)." + warn_popup "File level merge required. + +$err + +Staying on branch '$current_branch'." + unlock_index + delete_this + return + } + + $::main_status stop + _after_readtree $this +} + +method _after_readtree {} { + global selected_commit_type commit_type HEAD MERGE_HEAD PARENT + global current_branch is_detached + global ui_comm + + set name [_name $this] + set log "checkout: moving" + if {!$is_detached} { + append log " from $current_branch" + } + + # -- Move/create HEAD as a symbolic ref. Core git does not + # even check for failure here, it Just Works(tm). If it + # doesn't we are in some really ugly state that is difficult + # to recover from within git-gui. + # + set rh refs/heads/ + set rn [string length $rh] + if {[string equal -length $rn $rh $new_ref]} { + set new_branch [string range $new_ref $rn end] + append log " to $new_branch" + + if {[catch { + git symbolic-ref -m $log HEAD $new_ref + } err]} { + _fatal $this $err + } + set current_branch $new_branch + set is_detached 0 + } else { + append log " to $new_expr" + + if {[catch { + _detach_HEAD $log $new_hash + } err]} { + _fatal $this $err + } + set current_branch HEAD + set is_detached 1 + } + + # -- We had to defer updating the branch itself until we + # knew the working directory would update. So now we + # need to finish that work. If it fails we're in big + # trouble. + # + if {$update_old ne {}} { + if {[catch { + git update-ref \ + -m $reflog_msg \ + $new_ref \ + $new_hash \ + $update_old + } err]} { + _fatal $this $err + } + } + + if {$is_detached} { + info_popup "You are no longer on a local branch. + +If you wanted to be on a branch, create one now starting from 'This Detached Checkout'." + } + + # -- Update our repository state. If we were previously in + # amend mode we need to toss the current buffer and do a + # full rescan to update our file lists. If we weren't in + # amend mode our file lists are accurate and we can avoid + # the rescan. + # + unlock_index + set selected_commit_type new + if {[string match amend* $commit_type]} { + $ui_comm delete 0.0 end + $ui_comm edit reset + $ui_comm edit modified false + rescan [list ui_status "Checked out '$name'."] + } else { + repository_state commit_type HEAD MERGE_HEAD + set PARENT $HEAD + ui_status "Checked out '$name'." + } + delete_this +} + +git-version proc _detach_HEAD {log new} { + >= 1.5.3 { + git update-ref --no-deref -m $log HEAD $new + } + default { + set p [gitdir HEAD] + file delete $p + set fd [open $p w] + fconfigure $fd -translation lf -encoding utf-8 + puts $fd $new + close $fd + } +} + +method _confirm_reset {cur} { + set reset_ok 0 + set name [_name $this] + set gitk [list do_gitk [list $cur ^$new_hash]] + + _toplevel $this {Confirm Branch Reset} + pack [label $w.msg1 \ + -anchor w \ + -justify left \ + -text "Resetting '$name' to $new_expr will lose the following commits:" \ + ] -anchor w + + set list $w.list.l + frame $w.list + text $list \ + -font font_diff \ + -width 80 \ + -height 10 \ + -wrap none \ + -xscrollcommand [list $w.list.sbx set] \ + -yscrollcommand [list $w.list.sby set] + scrollbar $w.list.sbx -orient h -command [list $list xview] + scrollbar $w.list.sby -orient v -command [list $list yview] + pack $w.list.sbx -fill x -side bottom + pack $w.list.sby -fill y -side right + pack $list -fill both -expand 1 + pack $w.list -fill both -expand 1 -padx 5 -pady 5 + + pack [label $w.msg2 \ + -anchor w \ + -justify left \ + -text {Recovering lost commits may not be easy.} \ + ] + pack [label $w.msg3 \ + -anchor w \ + -justify left \ + -text "Reset '$name'?" \ + ] + + frame $w.buttons + button $w.buttons.visualize \ + -text Visualize \ + -command $gitk + pack $w.buttons.visualize -side left + button $w.buttons.reset \ + -text Reset \ + -command " + set @reset_ok 1 + destroy $w + " + pack $w.buttons.reset -side right + button $w.buttons.cancel \ + -default active \ + -text Cancel \ + -command [list destroy $w] + pack $w.buttons.cancel -side right -padx 5 + pack $w.buttons -side bottom -fill x -pady 10 -padx 10 + + set fd [git_read rev-list --pretty=oneline $cur ^$new_hash] + while {[gets $fd line] > 0} { + set abbr [string range $line 0 7] + set subj [string range $line 41 end] + $list insert end "$abbr $subj\n" + } + close $fd + $list configure -state disabled + + bind $w <Key-v> $gitk + bind $w <Visibility> " + grab $w + focus $w.buttons.cancel + " + bind $w <Key-Return> [list destroy $w] + bind $w <Key-Escape> [list destroy $w] + tkwait window $w + return $reset_ok +} + +method _error {msg} { + if {[winfo ismapped $parent_w]} { + set p $parent_w + } else { + set p . + } + + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $p] \ + -parent $p \ + -message $msg +} + +method _toplevel {title} { + regsub -all {::} $this {__} w + set w .$w + + if {[winfo ismapped $parent_w]} { + set p $parent_w + } else { + set p . + } + + toplevel $w + wm title $w $title + wm geometry $w "+[winfo rootx $p]+[winfo rooty $p]" +} + +method _fatal {err} { + error_popup "Failed to set current branch. + +This working directory is only partially switched. We successfully updated your files, but failed to update an internal Git file. + +This should not have occurred. [appname] will now close and give up. + +$err" + exit 1 +} + +} diff --git a/git-gui/lib/choose_rev.tcl b/git-gui/lib/choose_rev.tcl new file mode 100644 index 0000000000..afd81707ce --- /dev/null +++ b/git-gui/lib/choose_rev.tcl @@ -0,0 +1,367 @@ +# git-gui revision chooser +# Copyright (C) 2006, 2007 Shawn Pearce + +class choose_rev { + +image create photo ::choose_rev::img_find -data {R0lGODlhEAAQAIYAAPwCBCQmJDw+PBQSFAQCBMza3NTm5MTW1HyChOT29Ozq7MTq7Kze5Kzm7Oz6/NTy9Iza5GzGzKzS1Nzy9Nz29Kzq9HTGzHTK1Lza3AwKDLzu9JTi7HTW5GTCzITO1Mzq7Hza5FTK1ESyvHzKzKzW3DQyNDyqtDw6PIzW5HzGzAT+/Dw+RKyurNTOzMTGxMS+tJSGdATCxHRydLSqpLymnLSijBweHERCRNze3Pz69PTy9Oze1OTSxOTGrMSqlLy+vPTu5OzSvMymjNTGvNS+tMy2pMyunMSefAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAe4gACCAAECA4OIiAIEBQYHBAKJgwIICQoLDA0IkZIECQ4PCxARCwSSAxITFA8VEBYXGBmJAQYLGhUbHB0eH7KIGRIMEBAgISIjJKaIJQQLFxERIialkieUGigpKRoIBCqJKyyLBwvJAioEyoICLS4v6QQwMQQyLuqLli8zNDU2BCf1lN3AkUPHDh49fAQAAEnGD1MCCALZEaSHkIUMBQS8wWMIkSJGhBzBmFEGgRsBUqpMiSgdAD+BAAAh/mhDcmVhdGVkIGJ5IEJNUFRvR0lGIFBybyB2ZXJzaW9uIDIuNQ0KqSBEZXZlbENvciAxOTk3LDE5OTguIEFsbCByaWdodHMgcmVzZXJ2ZWQuDQpodHRwOi8vd3d3LmRldmVsY29yLmNvbQA7} + +field w ; # our megawidget path +field w_list ; # list of currently filtered specs +field w_filter ; # filter entry for $w_list + +field c_expr {}; # current revision expression +field filter ; # current filter string +field revtype head; # type of revision chosen +field cur_specs [list]; # list of specs for $revtype +field spec_head ; # list of all head specs +field spec_trck ; # list of all tracking branch specs +field spec_tag ; # list of all tag specs + +constructor new {path {title {}}} { + global current_branch is_detached + + set w $path + + if {$title ne {}} { + labelframe $w -text $title + } else { + frame $w + } + bind $w <Destroy> [cb _delete %W] + + if {$is_detached} { + radiobutton $w.detachedhead_r \ + -anchor w \ + -text {This Detached Checkout} \ + -value HEAD \ + -variable @revtype + grid $w.detachedhead_r -sticky we -padx {0 5} -columnspan 2 + } + + radiobutton $w.expr_r \ + -text {Revision Expression:} \ + -value expr \ + -variable @revtype + entry $w.expr_t \ + -borderwidth 1 \ + -relief sunken \ + -width 50 \ + -textvariable @c_expr \ + -validate key \ + -validatecommand [cb _validate %d %S] + grid $w.expr_r $w.expr_t -sticky we -padx {0 5} + + frame $w.types + radiobutton $w.types.head_r \ + -text {Local Branch} \ + -value head \ + -variable @revtype + pack $w.types.head_r -side left + radiobutton $w.types.trck_r \ + -text {Tracking Branch} \ + -value trck \ + -variable @revtype + pack $w.types.trck_r -side left + radiobutton $w.types.tag_r \ + -text {Tag} \ + -value tag \ + -variable @revtype + pack $w.types.tag_r -side left + set w_filter $w.types.filter + entry $w_filter \ + -borderwidth 1 \ + -relief sunken \ + -width 12 \ + -textvariable @filter \ + -validate key \ + -validatecommand [cb _filter %P] + pack $w_filter -side right + pack [label $w.types.filter_icon \ + -image ::choose_rev::img_find \ + ] -side right + grid $w.types -sticky we -padx {0 5} -columnspan 2 + + frame $w.list + set w_list $w.list.l + listbox $w_list \ + -font font_diff \ + -width 50 \ + -height 5 \ + -selectmode browse \ + -exportselection false \ + -xscrollcommand [cb _sb_set $w.list.sbx h] \ + -yscrollcommand [cb _sb_set $w.list.sby v] + pack $w_list -fill both -expand 1 + grid $w.list -sticky nswe -padx {20 5} -columnspan 2 + + grid columnconfigure $w 1 -weight 1 + if {$is_detached} { + grid rowconfigure $w 3 -weight 1 + } else { + grid rowconfigure $w 2 -weight 1 + } + + trace add variable @revtype write [cb _select] + bind $w_filter <Key-Return> [list focus $w_list]\;break + bind $w_filter <Key-Down> [list focus $w_list] + + set spec_head [list] + foreach name [load_all_heads] { + lappend spec_head [list $name refs/heads/$name] + } + + set spec_trck [list] + foreach spec [all_tracking_branches] { + set name [lindex $spec 0] + regsub ^refs/(heads|remotes)/ $name {} name + lappend spec_trck [concat $name $spec] + } + + set spec_tag [list] + foreach name [load_all_tags] { + lappend spec_tag [list $name refs/tags/$name] + } + + if {$is_detached} { set revtype HEAD + } elseif {[llength $spec_head] > 0} { set revtype head + } elseif {[llength $spec_trck] > 0} { set revtype trck + } elseif {[llength $spec_tag ] > 0} { set revtype tag + } else { set revtype expr + } + + if {$revtype eq {head} && $current_branch ne {}} { + set i 0 + foreach spec $spec_head { + if {[lindex $spec 0] eq $current_branch} { + $w_list selection clear 0 end + $w_list selection set $i + break + } + incr i + } + } + + return $this +} + +method none {text} { + if {![winfo exists $w.none_r]} { + radiobutton $w.none_r \ + -anchor w \ + -value none \ + -variable @revtype + grid $w.none_r -sticky we -padx {0 5} -columnspan 2 + } + $w.none_r configure -text $text +} + +method get {} { + switch -- $revtype { + head - + trck - + tag { + set i [$w_list curselection] + if {$i ne {}} { + return [lindex $cur_specs $i 0] + } else { + return {} + } + } + + HEAD { return HEAD } + expr { return $c_expr } + none { return {} } + default { error "unknown type of revision" } + } +} + +method pick_tracking_branch {} { + set revtype trck +} + +method focus_filter {} { + if {[$w_filter cget -state] eq {normal}} { + focus $w_filter + } +} + +method bind_listbox {event script} { + bind $w_list $event $script +} + +method get_local_branch {} { + if {$revtype eq {head}} { + return [_expr $this] + } else { + return {} + } +} + +method get_tracking_branch {} { + set i [$w_list curselection] + if {$i eq {} || $revtype ne {trck}} { + return {} + } + return [lrange [lindex $cur_specs $i] 1 end] +} + +method get_commit {} { + set e [_expr $this] + if {$e eq {}} { + return {} + } + return [git rev-parse --verify "$e^0"] +} + +method commit_or_die {} { + if {[catch {set new [get_commit $this]} err]} { + + # Cleanup the not-so-friendly error from rev-parse. + # + regsub {^fatal:\s*} $err {} err + if {$err eq {Needed a single revision}} { + set err {} + } + + set top [winfo toplevel $w] + set msg "Invalid revision: [get $this]\n\n$err" + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $top] \ + -parent $top \ + -message $msg + error $msg + } + return $new +} + +method _expr {} { + switch -- $revtype { + head - + trck - + tag { + set i [$w_list curselection] + if {$i ne {}} { + return [lindex $cur_specs $i 1] + } else { + error "No revision selected." + } + } + + expr { + if {$c_expr ne {}} { + return $c_expr + } else { + error "Revision expression is empty." + } + } + HEAD { return HEAD } + none { return {} } + default { error "unknown type of revision" } + } +} + +method _validate {d S} { + if {$d == 1} { + if {[regexp {\s} $S]} { + return 0 + } + if {[string length $S] > 0} { + set revtype expr + } + } + return 1 +} + +method _filter {P} { + if {[regexp {\s} $P]} { + return 0 + } + _rebuild $this $P + return 1 +} + +method _select {args} { + _rebuild $this $filter + focus_filter $this +} + +method _rebuild {pat} { + set ste normal + switch -- $revtype { + head { set new $spec_head } + trck { set new $spec_trck } + tag { set new $spec_tag } + expr - + HEAD - + none { + set new [list] + set ste disabled + } + } + + if {[$w_list cget -state] eq {disabled}} { + $w_list configure -state normal + } + $w_list delete 0 end + + if {$pat ne {}} { + set pat *${pat}* + } + set cur_specs [list] + foreach spec $new { + set txt [lindex $spec 0] + if {$pat eq {} || [string match $pat $txt]} { + lappend cur_specs $spec + $w_list insert end $txt + } + } + if {$cur_specs ne {}} { + $w_list selection clear 0 end + $w_list selection set 0 + } + + if {[$w_filter cget -state] ne $ste} { + $w_list configure -state $ste + $w_filter configure -state $ste + } +} + +method _delete {current} { + if {$current eq $w} { + delete_this + } +} + +method _sb_set {sb orient first last} { + set old_focus [focus -lastfor $w] + + if {$first == 0 && $last == 1} { + if {[winfo exists $sb]} { + destroy $sb + if {$old_focus ne {}} { + update + focus $old_focus + } + } + return + } + + if {![winfo exists $sb]} { + if {$orient eq {h}} { + scrollbar $sb -orient h -command [list $w_list xview] + pack $sb -fill x -side bottom -before $w_list + } else { + scrollbar $sb -orient v -command [list $w_list yview] + pack $sb -fill y -side right -before $w_list + } + if {$old_focus ne {}} { + update + focus $old_focus + } + } + $sb set $first $last +} + +} diff --git a/git-gui/lib/class.tcl b/git-gui/lib/class.tcl index 9d298d0dcc..24e8cecea4 100644 --- a/git-gui/lib/class.tcl +++ b/git-gui/lib/class.tcl @@ -5,7 +5,7 @@ proc class {class body} { if {[namespace exists $class]} { error "class $class already declared" } - namespace eval $class { + namespace eval $class " variable __nextid 0 variable __sealed 0 variable __field_list {} @@ -13,10 +13,9 @@ proc class {class body} { proc cb {name args} { upvar this this - set args [linsert $args 0 $name $this] - return [uplevel [list namespace code $args]] + concat \[list ${class}::\$name \$this\] \$args } - } + " namespace eval $class $body } @@ -51,15 +50,16 @@ proc constructor {name params body} { set mbodyc {} append mbodyc {set this } $class - append mbodyc {::__o[incr } $class {::__nextid]} \; - append mbodyc {namespace eval $this {}} \; + append mbodyc {::__o[incr } $class {::__nextid]::__d} \; + append mbodyc {create_this } $class \; + append mbodyc {set __this [namespace qualifiers $this]} \; if {$__field_list ne {}} { append mbodyc {upvar #0} foreach n $__field_list { set n [lindex $n 0] - append mbodyc { ${this}::} $n { } $n - regsub -all @$n\\M $body "\${this}::$n" body + append mbodyc { ${__this}::} $n { } $n + regsub -all @$n\\M $body "\${__this}::$n" body } append mbodyc \; foreach n $__field_list { @@ -80,10 +80,12 @@ proc method {name params body {deleted {}} {del_body {}}} { set params [linsert $params 0 this] set mbodyc {} + append mbodyc {set __this [namespace qualifiers $this]} \; + switch $deleted { {} {} ifdeleted { - append mbodyc {if {![namespace exists $this]} } + append mbodyc {if {![namespace exists $__this]} } append mbodyc \{ $del_body \; return \} \; } default { @@ -98,10 +100,12 @@ proc method {name params body {deleted {}} {del_body {}}} { if { [regexp -all -- $n\\M $body] == 1 && [regexp -all -- \\\$$n\\M $body] == 1 && [regexp -all -- \\\$$n\\( $body] == 0} { - regsub -all \\\$$n\\M $body "\[set \${this}::$n\]" body + regsub -all \ + \\\$$n\\M $body \ + "\[set \${__this}::$n\]" body } else { - append decl { ${this}::} $n { } $n - regsub -all @$n\\M $body "\${this}::$n" body + append decl { ${__this}::} $n { } $n + regsub -all @$n\\M $body "\${__this}::$n" body } } } @@ -112,11 +116,21 @@ proc method {name params body {deleted {}} {del_body {}}} { namespace eval $class [list proc $name $params $mbodyc] } +proc create_this {class} { + upvar this this + namespace eval [namespace qualifiers $this] [list proc \ + [namespace tail $this] \ + [list name args] \ + "eval \[list ${class}::\$name $this\] \$args" \ + ] +} + proc delete_this {{t {}}} { if {$t eq {}} { upvar this this set t $this } + set t [namespace qualifiers $t] if {[namespace exists $t]} {namespace delete $t} } diff --git a/git-gui/lib/commit.tcl b/git-gui/lib/commit.tcl index f9791f64db..46a78c158f 100644 --- a/git-gui/lib/commit.tcl +++ b/git-gui/lib/commit.tcl @@ -25,7 +25,7 @@ You are currently in the middle of a merge that has not been fully completed. Y set msg {} set parents [list] if {[catch { - set fd [open "| git cat-file commit $curHEAD" r] + set fd [git_read cat-file commit $curHEAD] fconfigure $fd -encoding binary -translation lf if {[catch {set enc $repo_config(i18n.commitencoding)}]} { set enc utf-8 @@ -58,7 +58,7 @@ You are currently in the middle of a merge that has not been fully completed. Y $ui_comm insert end $msg $ui_comm edit reset $ui_comm edit modified false - rescan {set ui_status_value {Ready.}} + rescan ui_ready } set GIT_COMMITTER_IDENT {} @@ -108,12 +108,12 @@ proc create_new_commit {} { $ui_comm delete 0.0 end $ui_comm edit reset $ui_comm edit modified false - rescan {set ui_status_value {Ready.}} + rescan ui_ready } proc commit_tree {} { global HEAD commit_type file_states ui_comm repo_config - global ui_status_value pch_error + global pch_error if {[committer_ident] eq {}} return if {![lock_index update]} return @@ -132,7 +132,7 @@ Another Git program has modified this repository since the last scan. A rescan The rescan will be automatically started now. } unlock_index - rescan {set ui_status_value {Ready.}} + rescan ui_ready return } @@ -206,7 +206,7 @@ A good commit message has the following format: return } - set ui_status_value {Calling pre-commit hook...} + ui_status {Calling pre-commit hook...} set pch_error {} set fd_ph [open "| $pchook" r] fconfigure $fd_ph -blocking 0 -translation binary @@ -215,13 +215,13 @@ A good commit message has the following format: } proc commit_prehook_wait {fd_ph curHEAD msg} { - global pch_error ui_status_value + global pch_error append pch_error [read $fd_ph] fconfigure $fd_ph -blocking 1 if {[eof $fd_ph]} { if {[catch {close $fd_ph}]} { - set ui_status_value {Commit declined by pre-commit hook.} + ui_status {Commit declined by pre-commit hook.} hook_failed_popup pre-commit $pch_error unlock_index } else { @@ -234,25 +234,23 @@ proc commit_prehook_wait {fd_ph curHEAD msg} { } proc commit_writetree {curHEAD msg} { - global ui_status_value - - set ui_status_value {Committing changes...} - set fd_wt [open "| git write-tree" r] + ui_status {Committing changes...} + set fd_wt [git_read write-tree] fileevent $fd_wt readable \ [list commit_committree $fd_wt $curHEAD $msg] } proc commit_committree {fd_wt curHEAD msg} { global HEAD PARENT MERGE_HEAD commit_type - global all_heads current_branch - global ui_status_value ui_comm selected_commit_type + global current_branch + global ui_comm selected_commit_type global file_states selected_paths rescan_active global repo_config gets $fd_wt tree_id if {$tree_id eq {} || [catch {close $fd_wt} err]} { error_popup "write-tree failed:\n\n$err" - set ui_status_value {Commit failed.} + ui_status {Commit failed.} unlock_index return } @@ -260,7 +258,18 @@ proc commit_committree {fd_wt curHEAD msg} { # -- Verify this wasn't an empty change. # if {$commit_type eq {normal}} { - set old_tree [git rev-parse "$PARENT^{tree}"] + set fd_ot [git_read cat-file commit $PARENT] + fconfigure $fd_ot -encoding binary -translation lf + set old_tree [gets $fd_ot] + close $fd_ot + + if {[string equal -length 5 {tree } $old_tree] + && [string length $old_tree] == 45} { + set old_tree [string range $old_tree 5 end] + } else { + error "Commit $PARENT appears to be corrupt" + } + if {$tree_id eq $old_tree} { info_popup {No changes to commit. @@ -269,7 +278,7 @@ No files were modified by this commit and it was not a merge commit. A rescan will be automatically started now. } unlock_index - rescan {set ui_status_value {No changes to commit.}} + rescan {ui_status {No changes to commit.}} return } } @@ -294,7 +303,7 @@ A rescan will be automatically started now. lappend cmd <$msg_p if {[catch {set cmt_id [eval git $cmd]} err]} { error_popup "commit-tree failed:\n\n$err" - set ui_status_value {Commit failed.} + ui_status {Commit failed.} unlock_index return } @@ -316,7 +325,7 @@ A rescan will be automatically started now. git update-ref -m $reflogm HEAD $cmt_id $curHEAD } err]} { error_popup "update-ref failed:\n\n$err" - set ui_status_value {Commit failed.} + ui_status {Commit failed.} unlock_index return } @@ -331,7 +340,12 @@ A rescan will be automatically started now. # -- Let rerere do its thing. # - if {[file isdirectory [gitdir rr-cache]]} { + if {[get_config rerere.enabled] eq {}} { + set rerere [file isdirectory [gitdir rr-cache]] + } else { + set rerere [is_config_true rerere.enabled] + } + if {$rerere} { catch {git rerere} } @@ -356,14 +370,6 @@ A rescan will be automatically started now. if {[is_enabled singlecommit]} do_quit - # -- Make sure our current branch exists. - # - if {$commit_type eq {initial}} { - lappend all_heads $current_branch - set all_heads [lsort -unique $all_heads] - populate_branch_menu - } - # -- Update in memory status # set selected_commit_type new @@ -405,6 +411,5 @@ A rescan will be automatically started now. display_all_files unlock_index reshow_diff - set ui_status_value \ - "Created commit [string range $cmt_id 0 7]: $subject" + ui_status "Created commit [string range $cmt_id 0 7]: $subject" } diff --git a/git-gui/lib/console.tcl b/git-gui/lib/console.tcl index ce25d92cac..6f718fbac3 100644 --- a/git-gui/lib/console.tcl +++ b/git-gui/lib/console.tcl @@ -7,6 +7,7 @@ field t_short field t_long field w field console_cr +field is_toplevel 1; # are we our own window? constructor new {short_title long_title} { set t_short $short_title @@ -15,10 +16,25 @@ constructor new {short_title long_title} { return $this } +constructor embed {path title} { + set t_short {} + set t_long $title + set w $path + set is_toplevel 0 + _init $this + return $this +} + method _init {} { global M1B - make_toplevel top w -autodelete 0 - wm title $top "[appname] ([reponame]): $t_short" + + if {$is_toplevel} { + make_toplevel top w -autodelete 0 + wm title $top "[appname] ([reponame]): $t_short" + } else { + frame $w + } + set console_cr 1.0 frame $w.m @@ -31,16 +47,20 @@ method _init {} { -background white -borderwidth 1 \ -relief sunken \ -width 80 -height 10 \ + -wrap none \ -font font_diff \ -state disabled \ + -xscrollcommand [list $w.m.sbx set] \ -yscrollcommand [list $w.m.sby set] label $w.m.s -text {Working... please wait...} \ -anchor w \ -justify left \ -font font_uibold + scrollbar $w.m.sbx -command [list $w.m.t xview] -orient h scrollbar $w.m.sby -command [list $w.m.t yview] pack $w.m.l1 -side top -fill x pack $w.m.s -side bottom -fill x + pack $w.m.sbx -side bottom -fill x pack $w.m.sby -side right -fill y pack $w.m.t -side left -fill both -expand 1 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10 @@ -57,31 +77,26 @@ method _init {} { $w.m.t tag remove sel 0.0 end " - button $w.ok -text {Close} \ - -state disabled \ - -command "destroy $w" - pack $w.ok -side bottom -anchor e -pady 10 -padx 10 + if {$is_toplevel} { + button $w.ok -text {Close} \ + -state disabled \ + -command [list destroy $w] + pack $w.ok -side bottom -anchor e -pady 10 -padx 10 + bind $w <Visibility> [list focus $w] + } bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y" bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break" bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break" - bind $w <Visibility> "focus $w" } method exec {cmd {after {}}} { - # -- Cygwin's Tcl tosses the enviroment when we exec our child. - # But most users need that so we have to relogin. :-( - # - if {[is_Cygwin]} { - set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"] + if {[lindex $cmd 0] eq {git}} { + set fd_f [eval git_read --stderr [lrange $cmd 1 end]] + } else { + lappend cmd 2>@1 + set fd_f [_open_stdout_stderr $cmd] } - - # -- Tcl won't let us redirect both stdout and stderr to - # the same pipe. So pass it through cat... - # - set cmd [concat | $cmd |& cat] - - set fd_f [open $cmd r] fconfigure $fd_f -blocking 0 -translation binary fileevent $fd_f readable [cb _read $fd_f $after] } @@ -155,20 +170,32 @@ method chain {cmdlist {ok 1}} { } } +method insert {txt} { + if {![winfo exists $w.m.t]} {_init $this} + $w.m.t conf -state normal + $w.m.t insert end "$txt\n" + set console_cr [$w.m.t index {end -1c}] + $w.m.t conf -state disabled +} + method done {ok} { if {$ok} { if {[winfo exists $w.m.s]} { $w.m.s conf -background green -text {Success} - $w.ok conf -state normal - focus $w.ok + if {$is_toplevel} { + $w.ok conf -state normal + focus $w.ok + } } } else { if {![winfo exists $w.m.s]} { _init $this } $w.m.s conf -background red -text {Error: Command Failed} - $w.ok conf -state normal - focus $w.ok + if {$is_toplevel} { + $w.ok conf -state normal + focus $w.ok + } } delete_this } diff --git a/git-gui/lib/database.tcl b/git-gui/lib/database.tcl index 43e4a289bb..87c815d7ac 100644 --- a/git-gui/lib/database.tcl +++ b/git-gui/lib/database.tcl @@ -2,7 +2,7 @@ # Copyright (C) 2006, 2007 Shawn Pearce proc do_stats {} { - set fd [open "| git count-objects -v" r] + set fd [git_read count-objects -v] while {[gets $fd line] > 0} { if {[regexp {^([^:]+): (\d+)$} $line _ name value]} { set stats($name) $value diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl index 29436b50cb..9cb9d0604a 100644 --- a/git-gui/lib/diff.tcl +++ b/git-gui/lib/diff.tcl @@ -17,7 +17,7 @@ proc clear_diff {} { } proc reshow_diff {} { - global ui_status_value file_states file_lists + global file_states file_lists global current_diff_path current_diff_side set p $current_diff_path @@ -49,13 +49,13 @@ A rescan will be automatically started to find other files which may have the sa clear_diff display_file $path __ - rescan {set ui_status_value {Ready.}} 0 + rescan ui_ready 0 } proc show_diff {path w {lno {}}} { global file_states file_lists global is_3way_diff diff_active repo_config - global ui_diff ui_status_value ui_index ui_workdir + global ui_diff ui_index ui_workdir global current_diff_path current_diff_side current_diff_header if {$diff_active || ![lock_index read]} return @@ -78,7 +78,7 @@ proc show_diff {path w {lno {}}} { set current_diff_path $path set current_diff_side $w set current_diff_header {} - set ui_status_value "Loading diff of [escape_path $path]..." + ui_status "Loading diff of [escape_path $path]..." # - Git won't give us the diff, there's nothing to compare to! # @@ -92,7 +92,7 @@ proc show_diff {path w {lno {}}} { } err ]} { set diff_active 0 unlock_index - set ui_status_value "Unable to display [escape_path $path]" + ui_status "Unable to display [escape_path $path]" error_popup "Error loading file:\n\n$err" return } @@ -127,11 +127,11 @@ proc show_diff {path w {lno {}}} { $ui_diff conf -state disabled set diff_active 0 unlock_index - set ui_status_value {Ready.} + ui_ready return } - set cmd [list | git] + set cmd [list] if {$w eq $ui_index} { lappend cmd diff-index lappend cmd --cached @@ -154,10 +154,10 @@ proc show_diff {path w {lno {}}} { lappend cmd -- lappend cmd $path - if {[catch {set fd [open $cmd r]} err]} { + if {[catch {set fd [eval git_read --nice $cmd]} err]} { set diff_active 0 unlock_index - set ui_status_value "Unable to display [escape_path $path]" + ui_status "Unable to display [escape_path $path]" error_popup "Error loading diff:\n\n$err" return } @@ -170,7 +170,7 @@ proc show_diff {path w {lno {}}} { } proc read_diff {fd} { - global ui_diff ui_status_value diff_active + global ui_diff diff_active global is_3way_diff current_diff_header $ui_diff conf -state normal @@ -256,7 +256,7 @@ proc read_diff {fd} { close $fd set diff_active 0 unlock_index - set ui_status_value {Ready.} + ui_ready if {[$ui_diff index end] eq {2.0}} { handle_empty_diff @@ -271,7 +271,7 @@ proc apply_hunk {x y} { if {$current_diff_path eq {} || $current_diff_header eq {}} return if {![lock_index apply_hunk]} return - set apply_cmd {git apply --cached --whitespace=nowarn} + set apply_cmd {apply --cached --whitespace=nowarn} set mi [lindex $file_states($current_diff_path) 0] if {$current_diff_side eq $ui_index} { set mode unstage @@ -301,7 +301,7 @@ proc apply_hunk {x y} { } if {[catch { - set p [open "| $apply_cmd" w] + set p [eval git_write $apply_cmd] fconfigure $p -translation binary -encoding binary puts -nonewline $p $current_diff_header puts -nonewline $p [$ui_diff get $s_lno $e_lno] diff --git a/git-gui/lib/index.tcl b/git-gui/lib/index.tcl index 42742850ee..3ea72e1ec9 100644 --- a/git-gui/lib/index.tcl +++ b/git-gui/lib/index.tcl @@ -2,7 +2,7 @@ # Copyright (C) 2006, 2007 Shawn Pearce proc update_indexinfo {msg pathList after} { - global update_index_cp ui_status_value + global update_index_cp if {![lock_index update]} return @@ -12,12 +12,12 @@ proc update_indexinfo {msg pathList after} { set batch [expr {int($totalCnt * .01) + 1}] if {$batch > 25} {set batch 25} - set ui_status_value [format \ + ui_status [format \ "$msg... %i/%i files (%.2f%%)" \ $update_index_cp \ $totalCnt \ 0.0] - set fd [open "| git update-index -z --index-info" w] + set fd [git_write update-index -z --index-info] fconfigure $fd \ -blocking 0 \ -buffering full \ @@ -36,7 +36,7 @@ proc update_indexinfo {msg pathList after} { } proc write_update_indexinfo {fd pathList totalCnt batch msg after} { - global update_index_cp ui_status_value + global update_index_cp global file_states current_diff_path if {$update_index_cp >= $totalCnt} { @@ -67,7 +67,7 @@ proc write_update_indexinfo {fd pathList totalCnt batch msg after} { display_file $path $new } - set ui_status_value [format \ + ui_status [format \ "$msg... %i/%i files (%.2f%%)" \ $update_index_cp \ $totalCnt \ @@ -75,7 +75,7 @@ proc write_update_indexinfo {fd pathList totalCnt batch msg after} { } proc update_index {msg pathList after} { - global update_index_cp ui_status_value + global update_index_cp if {![lock_index update]} return @@ -85,12 +85,12 @@ proc update_index {msg pathList after} { set batch [expr {int($totalCnt * .01) + 1}] if {$batch > 25} {set batch 25} - set ui_status_value [format \ + ui_status [format \ "$msg... %i/%i files (%.2f%%)" \ $update_index_cp \ $totalCnt \ 0.0] - set fd [open "| git update-index --add --remove -z --stdin" w] + set fd [git_write update-index --add --remove -z --stdin] fconfigure $fd \ -blocking 0 \ -buffering full \ @@ -109,7 +109,7 @@ proc update_index {msg pathList after} { } proc write_update_index {fd pathList totalCnt batch msg after} { - global update_index_cp ui_status_value + global update_index_cp global file_states current_diff_path if {$update_index_cp >= $totalCnt} { @@ -144,7 +144,7 @@ proc write_update_index {fd pathList totalCnt batch msg after} { display_file $path $new } - set ui_status_value [format \ + ui_status [format \ "$msg... %i/%i files (%.2f%%)" \ $update_index_cp \ $totalCnt \ @@ -152,7 +152,7 @@ proc write_update_index {fd pathList totalCnt batch msg after} { } proc checkout_index {msg pathList after} { - global update_index_cp ui_status_value + global update_index_cp if {![lock_index update]} return @@ -162,18 +162,18 @@ proc checkout_index {msg pathList after} { set batch [expr {int($totalCnt * .01) + 1}] if {$batch > 25} {set batch 25} - set ui_status_value [format \ + ui_status [format \ "$msg... %i/%i files (%.2f%%)" \ $update_index_cp \ $totalCnt \ 0.0] - set cmd [list git checkout-index] - lappend cmd --index - lappend cmd --quiet - lappend cmd --force - lappend cmd -z - lappend cmd --stdin - set fd [open "| $cmd " w] + set fd [git_write checkout-index \ + --index \ + --quiet \ + --force \ + -z \ + --stdin \ + ] fconfigure $fd \ -blocking 0 \ -buffering full \ @@ -192,7 +192,7 @@ proc checkout_index {msg pathList after} { } proc write_checkout_index {fd pathList totalCnt batch msg after} { - global update_index_cp ui_status_value + global update_index_cp global file_states current_diff_path if {$update_index_cp >= $totalCnt} { @@ -217,7 +217,7 @@ proc write_checkout_index {fd pathList totalCnt batch msg after} { } } - set ui_status_value [format \ + ui_status [format \ "$msg... %i/%i files (%.2f%%)" \ $update_index_cp \ $totalCnt \ @@ -249,7 +249,7 @@ proc unstage_helper {txt paths} { update_indexinfo \ $txt \ $pathList \ - [concat $after {set ui_status_value {Ready.}}] + [concat $after [list ui_ready]] } } @@ -293,7 +293,7 @@ proc add_helper {txt paths} { update_index \ $txt \ $pathList \ - [concat $after {set ui_status_value {Ready to commit.}}] + [concat $after {ui_status {Ready to commit.}}] } } @@ -370,7 +370,7 @@ Any unadded changes will be permanently lost by the revert." \ checkout_index \ $txt \ $pathList \ - [concat $after {set ui_status_value {Ready.}}] + [concat $after [list ui_ready]] } else { unlock_index } diff --git a/git-gui/lib/merge.tcl b/git-gui/lib/merge.tcl index ae0389df5b..288d7ac889 100644 --- a/git-gui/lib/merge.tcl +++ b/git-gui/lib/merge.tcl @@ -28,7 +28,7 @@ Another Git program has modified this repository since the last scan. A rescan The rescan will be automatically started now. } unlock_index - rescan {set ui_status_value {Ready.}} + rescan ui_ready return 0 } @@ -79,7 +79,7 @@ proc _visualize {w list} { } proc _start {w list} { - global HEAD ui_status_value current_branch + global HEAD current_branch set cmd [list git merge] set names [_refs $w $list] @@ -121,7 +121,7 @@ Please select fewer branches. To merge more than 15 branches, merge the branche } set msg "Merging $current_branch, [join $names {, }]" - set ui_status_value "$msg..." + ui_status "$msg..." set cons [console::new "Merge" $msg] console::exec $cons $cmd \ [namespace code [list _finish $revcnt $cons]] @@ -146,18 +146,18 @@ The working directory will now be reset. You can attempt this merge again by merging only one branch at a time." $w - set fd [open "| git read-tree --reset -u HEAD" r] + set fd [git_read read-tree --reset -u HEAD] fconfigure $fd -blocking 0 -translation binary fileevent $fd readable \ [namespace code [list _reset_wait $fd]] - set ui_status_value {Aborting... please wait...} + ui_status {Aborting... please wait...} return } set msg {Merge failed. Conflict resolution is required.} } unlock_index - rescan [list set ui_status_value $msg] + rescan [list ui_status $msg] } proc dialog {} { @@ -167,11 +167,13 @@ proc dialog {} { if {![_can_merge]} return set fmt {list %(objectname) %(*objectname) %(refname) %(subject)} - set cmd [list git for-each-ref --tcl --format=$fmt] - lappend cmd refs/heads - lappend cmd refs/remotes - lappend cmd refs/tags - set fr_fd [open "| $cmd" r] + set fr_fd [git_read for-each-ref \ + --tcl \ + --format=$fmt \ + refs/heads \ + refs/remotes \ + refs/tags \ + ] fconfigure $fr_fd -translation binary while {[gets $fr_fd line] > 0} { set line [eval $line] @@ -186,7 +188,7 @@ proc dialog {} { close $fr_fd set to_show {} - set fr_fd [open "| git rev-list --all --not HEAD"] + set fr_fd [git_read rev-list --all --not HEAD] while {[gets $fr_fd line] > 0} { if {[catch {set ref $sha1($line)}]} continue foreach n $ref { @@ -213,7 +215,9 @@ proc dialog {} { pack $w.buttons.visualize -side left button $w.buttons.create -text Merge -command $_start pack $w.buttons.create -side right - button $w.buttons.cancel -text {Cancel} -command [list destroy $w] + button $w.buttons.cancel \ + -text {Cancel} \ + -command "unlock_index;destroy $w" pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 @@ -280,10 +284,10 @@ You must finish amending this commit. Aborting the current $op will cause *ALL* uncommitted changes to be lost. Continue with aborting the current $op?"] eq {yes}} { - set fd [open "| git read-tree --reset -u HEAD" r] + set fd [git_read read-tree --reset -u HEAD] fconfigure $fd -blocking 0 -translation binary fileevent $fd readable [namespace code [list _reset_wait $fd]] - set ui_status_value {Aborting... please wait...} + ui_status {Aborting... please wait...} } else { unlock_index } @@ -306,7 +310,7 @@ proc _reset_wait {fd} { catch {file delete [gitdir MERGE_MSG]} catch {file delete [gitdir GITGUI_MSG]} - rescan {set ui_status_value {Abort completed. Ready.}} + rescan {ui_status {Abort completed. Ready.}} } } diff --git a/git-gui/lib/option.tcl b/git-gui/lib/option.tcl index ae19a8f9cf..aa9f783afd 100644 --- a/git-gui/lib/option.tcl +++ b/git-gui/lib/option.tcl @@ -95,6 +95,7 @@ $copyright" \ } set d {} + append d "git wrapper: $::_git\n" append d "git exec dir: [gitexec]\n" append d "git-gui lib: $oguilib" @@ -191,6 +192,7 @@ proc do_options {} { {b gui.trustmtime {Trust File Modification Timestamps}} {b gui.pruneduringfetch {Prune Tracking Branches During Fetch}} + {b gui.matchtrackingbranch {Match Tracking Branches}} {i-0..99 gui.diffcontext {Number of Diff Context Lines}} {t gui.newbranchtemplate {New Branch Name Template}} } { diff --git a/git-gui/lib/remote.tcl b/git-gui/lib/remote.tcl index b54824ab72..e235ca8876 100644 --- a/git-gui/lib/remote.tcl +++ b/git-gui/lib/remote.tcl @@ -1,14 +1,13 @@ # git-gui remote management # Copyright (C) 2006, 2007 Shawn Pearce +set some_heads_tracking 0; # assume not + proc is_tracking_branch {name} { global tracking_branches - - if {![catch {set info $tracking_branches($name)}]} { - return 1 - } - foreach t [array names tracking_branches] { - if {[string match {*/\*} $t] && [string match $t $name]} { + foreach spec $tracking_branches { + set t [lindex $spec 0] + if {$t eq $name || [string match $t $name]} { return 1 } } @@ -18,36 +17,53 @@ proc is_tracking_branch {name} { proc all_tracking_branches {} { global tracking_branches - set all_trackings {} - set cmd {} - foreach name [array names tracking_branches] { - if {[regsub {/\*$} $name {} name]} { - lappend cmd $name + set all [list] + set pat [list] + set cmd [list] + + foreach spec $tracking_branches { + set dst [lindex $spec 0] + if {[string range $dst end-1 end] eq {/*}} { + lappend pat $spec + lappend cmd [string range $dst 0 end-2] } else { - regsub ^refs/(heads|remotes)/ $name {} name - lappend all_trackings $name + lappend all $spec } } - if {$cmd ne {}} { - set fd [open "| git for-each-ref --format=%(refname) $cmd" r] - while {[gets $fd name] > 0} { - regsub ^refs/(heads|remotes)/ $name {} name - lappend all_trackings $name + if {$pat ne {}} { + set fd [eval git_read for-each-ref --format=%(refname) $cmd] + while {[gets $fd n] > 0} { + foreach spec $pat { + set dst [string range [lindex $spec 0] 0 end-2] + set len [string length $dst] + if {[string equal -length $len $dst $n]} { + set src [string range [lindex $spec 2] 0 end-2] + set spec [list \ + $n \ + [lindex $spec 1] \ + $src[string range $n $len end] \ + ] + lappend all $spec + } + } } close $fd } - return [lsort -unique $all_trackings] + return [lsort -index 0 -unique $all] } proc load_all_remotes {} { global repo_config - global all_remotes tracking_branches + global all_remotes tracking_branches some_heads_tracking + set some_heads_tracking 0 set all_remotes [list] - array unset tracking_branches + set trck [list] + set rh_str refs/heads/ + set rh_len [string length $rh_str] set rm_dir [gitdir remotes] if {[file isdirectory $rm_dir]} { set all_remotes [glob \ @@ -62,10 +78,19 @@ proc load_all_remotes {} { while {[gets $fd line] >= 0} { if {![regexp {^Pull:[ ]*([^:]+):(.+)$} \ $line line src dst]} continue - if {![regexp ^refs/ $dst]} { - set dst "refs/heads/$dst" + if {[string index $src 0] eq {+}} { + set src [string range $src 1 end] } - set tracking_branches($dst) [list $name $src] + if {![string equal -length 5 refs/ $src]} { + set src $rh_str$src + } + if {![string equal -length 5 refs/ $dst]} { + set dst $rh_str$dst + } + if {[string equal -length $rh_len $rh_str $dst]} { + set some_heads_tracking 1 + } + lappend trck [list $dst $name $src] } close $fd } @@ -81,13 +106,23 @@ proc load_all_remotes {} { } foreach line $fl { if {![regexp {^([^:]+):(.+)$} $line line src dst]} continue - if {![regexp ^refs/ $dst]} { - set dst "refs/heads/$dst" + if {[string index $src 0] eq {+}} { + set src [string range $src 1 end] + } + if {![string equal -length 5 refs/ $src]} { + set src $rh_str$src + } + if {![string equal -length 5 refs/ $dst]} { + set dst $rh_str$dst + } + if {[string equal -length $rh_len $rh_str $dst]} { + set some_heads_tracking 1 } - set tracking_branches($dst) [list $name $src] + lappend trck [list $dst $name $src] } } + set tracking_branches [lsort -index 0 -unique $trck] set all_remotes [lsort -unique $all_remotes] } diff --git a/git-gui/lib/remote_branch_delete.tcl b/git-gui/lib/remote_branch_delete.tcl index b83e1b6315..c88a360db5 100644 --- a/git-gui/lib/remote_branch_delete.tcl +++ b/git-gui/lib/remote_branch_delete.tcl @@ -98,10 +98,10 @@ constructor dialog {} { button $w.heads.footer.rescan \ -text {Rescan} \ -command [cb _rescan] - pack $w.heads.footer.status -side left -fill x -expand 1 + pack $w.heads.footer.status -side left -fill x pack $w.heads.footer.rescan -side right - pack $w.heads.footer -side bottom -fill x -expand 1 + pack $w.heads.footer -side bottom -fill x pack $w.heads.sby -side right -fill y pack $w.heads.l -side left -fill both -expand 1 pack $w.heads -fill both -expand 1 -pady 5 -padx 5 @@ -296,7 +296,7 @@ method _load {cache uri} { set full_list [list] set head_cache($cache) [list] set full_cache($cache) [list] - set active_ls [open "| [list git ls-remote $uri]" r] + set active_ls [git_read ls-remote $uri] fconfigure $active_ls \ -blocking 0 \ -translation lf \ diff --git a/git-gui/lib/shortcut.tcl b/git-gui/lib/shortcut.tcl index ebf72e4452..c36be2f3cd 100644 --- a/git-gui/lib/shortcut.tcl +++ b/git-gui/lib/shortcut.tcl @@ -9,11 +9,15 @@ proc do_windows_shortcut {} { -title "[appname] ([reponame]): Create Desktop Icon" \ -initialfile "Git [reponame].bat"] if {$fn != {}} { + if {[file extension $fn] ne {.bat}} { + set fn ${fn}.bat + } if {[catch { + set ge [file normalize [file dirname $::_git]] set fd [open $fn w] puts $fd "@ECHO Entering [reponame]" puts $fd "@ECHO Starting git-gui... please wait..." - puts $fd "@SET PATH=[file normalize [gitexec]];%PATH%" + puts $fd "@SET PATH=$ge;%PATH%" puts $fd "@SET GIT_DIR=[file normalize [gitdir]]" puts -nonewline $fd "@\"[info nameofexecutable]\"" puts $fd " \"[file normalize $argv0]\"" @@ -42,12 +46,15 @@ proc do_cygwin_shortcut {} { -initialdir $desktop \ -initialfile "Git [reponame].bat"] if {$fn != {}} { + if {[file extension $fn] ne {.bat}} { + set fn ${fn}.bat + } if {[catch { set fd [open $fn w] set sh [exec cygpath \ --windows \ --absolute \ - /bin/sh] + /bin/sh.exe] set me [exec cygpath \ --unix \ --absolute \ @@ -56,18 +63,12 @@ proc do_cygwin_shortcut {} { --unix \ --absolute \ [gitdir]] - set gw [exec cygpath \ - --windows \ - --absolute \ - [file dirname [gitdir]]] - regsub -all ' $me "'\\''" me - regsub -all ' $gd "'\\''" gd - puts $fd "@ECHO Entering $gw" + puts $fd "@ECHO Entering [reponame]" puts $fd "@ECHO Starting git-gui... please wait..." puts -nonewline $fd "@\"$sh\" --login -c \"" - puts -nonewline $fd "GIT_DIR='$gd'" - puts -nonewline $fd " '$me'" - puts $fd "&\"" + puts -nonewline $fd "GIT_DIR=[sq $gd]" + puts -nonewline $fd " [sq $me]" + puts $fd " &\"" close $fd } err]} { error_popup "Cannot write script:\n\n$err" @@ -84,6 +85,9 @@ proc do_macosx_app {} { -initialdir [file join $env(HOME) Desktop] \ -initialfile "Git [reponame].app"] if {$fn != {}} { + if {[file extension $fn] ne {.app}} { + set fn ${fn}.app + } if {[catch { set Contents [file join $fn Contents] set MacOS [file join $Contents MacOS] @@ -117,20 +121,27 @@ proc do_macosx_app {} { close $fd set fd [open $exe w] - set gd [file normalize [gitdir]] - set ep [file normalize [gitexec]] - regsub -all ' $gd "'\\''" gd - regsub -all ' $ep "'\\''" ep puts $fd "#!/bin/sh" - foreach name [array names env] { - if {[string match GIT_* $name]} { - regsub -all ' $env($name) "'\\''" v - puts $fd "export $name='$v'" + foreach name [lsort [array names env]] { + set value $env($name) + switch -- $name { + GIT_DIR { set value [file normalize [gitdir]] } + } + + switch -glob -- $name { + SSH_* - + GIT_* { + puts $fd "if test \"z\$$name\" = z; then" + puts $fd " export $name=[sq $value]" + puts $fd "fi &&" + } } } - puts $fd "export PATH='$ep':\$PATH" - puts $fd "export GIT_DIR='$gd'" - puts $fd "exec [file normalize $argv0]" + puts $fd "export PATH=[sq [file dirname $::_git]]:\$PATH &&" + puts $fd "cd [sq [file normalize [pwd]]] &&" + puts $fd "exec \\" + puts $fd " [sq [info nameofexecutable]] \\" + puts $fd " [sq [file normalize $argv0]]" close $fd file attributes $exe -permissions u+x,g+x,o+x diff --git a/git-gui/lib/status_bar.tcl b/git-gui/lib/status_bar.tcl new file mode 100644 index 0000000000..72a8fe1fd3 --- /dev/null +++ b/git-gui/lib/status_bar.tcl @@ -0,0 +1,96 @@ +# git-gui status bar mega-widget +# Copyright (C) 2007 Shawn Pearce + +class status_bar { + +field w ; # our own window path +field w_l ; # text widget we draw messages into +field w_c ; # canvas we draw a progress bar into +field status {}; # single line of text we show +field prefix {}; # text we format into status +field units {}; # unit of progress +field meter {}; # current core git progress meter (if active) + +constructor new {path} { + set w $path + set w_l $w.l + set w_c $w.c + + frame $w \ + -borderwidth 1 \ + -relief sunken + label $w_l \ + -textvariable @status \ + -anchor w \ + -justify left + pack $w_l -side left + + bind $w <Destroy> [cb _delete %W] + return $this +} + +method start {msg uds} { + if {[winfo exists $w_c]} { + $w_c coords bar 0 0 0 20 + } else { + canvas $w_c \ + -width 100 \ + -height [expr {int([winfo reqheight $w_l] * 0.6)}] \ + -borderwidth 1 \ + -relief groove \ + -highlightt 0 + $w_c create rectangle 0 0 0 20 -tags bar -fill navy + pack $w_c -side right + } + + set status $msg + set prefix $msg + set units $uds + set meter {} +} + +method update {have total} { + set pdone 0 + if {$total > 0} { + set pdone [expr {100 * $have / $total}] + } + + set status [format "%s ... %i of %i %s (%2i%%)" \ + $prefix $have $total $units $pdone] + $w_c coords bar 0 0 $pdone 20 +} + +method update_meter {buf} { + append meter $buf + set r [string last "\r" $meter] + if {$r == -1} { + return + } + + set prior [string range $meter 0 $r] + set meter [string range $meter [expr {$r + 1}] end] + if {[regexp "\\((\\d+)/(\\d+)\\)\\s+done\r\$" $prior _j a b]} { + update $this $a $b + } +} + +method stop {{msg {}}} { + destroy $w_c + if {$msg ne {}} { + set status $msg + } +} + +method show {msg {test {}}} { + if {$test eq {} || $status eq $test} { + set status $msg + } +} + +method _delete {current} { + if {$current eq $w} { + delete_this + } +} + +} diff --git a/git-gui/lib/transport.tcl b/git-gui/lib/transport.tcl index e8ebc6eda0..3a22bd40d4 100644 --- a/git-gui/lib/transport.tcl +++ b/git-gui/lib/transport.tcl @@ -74,7 +74,7 @@ trace add variable push_remote write \ [list radio_selector push_urltype remote] proc do_push_anywhere {} { - global all_heads all_remotes current_branch + global all_remotes current_branch global push_urltype push_remote push_url push_thin push_tags set w .push_setup @@ -101,7 +101,7 @@ proc do_push_anywhere {} { -width 70 \ -selectmode extended \ -yscrollcommand [list $w.source.sby set] - foreach h $all_heads { + foreach h [load_all_heads] { $w.source.l insert end $h if {$h eq $current_branch} { $w.source.l select set end |