From c1fd897a25d85424a2ee93a8ae2eb247ba7c2558 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 22 Jun 2007 01:29:20 -0400 Subject: git-gui: Start blame windows as tall as possible Most users these days are using a windowing system attached to a monitor that has more than 600 pixels worth of vertical space available for application use. As most files stored by Git are longer than they are wide (have more lines than columns) we want to dedicate as much vertical space as we can to the viewer. Instead of always starting the window at ~600 pixels high we now start the window 100 pixels shorter than the screen claims it has available to it. This -100 rule is used because some popular OSen add menu bars at the top of the monitor, and docks on the bottom (e.g. Mac OS X, CDE, KDE). We want to avoid making our window too big and causing the window's resize control from being out of reach of the user. Signed-off-by: Shawn O. Pearce diff --git a/lib/blame.tcl b/lib/blame.tcl index b523654..77abd82 100644 --- a/lib/blame.tcl +++ b/lib/blame.tcl @@ -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 -- cgit v0.10.2-6-g49f6 From 1eb96a25c9e76b70ca3f335bce7e60d66220eaa9 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 4 Jul 2007 14:06:28 -0400 Subject: git-gui: Correct resizing of remote branch delete dialog The status field of the remote branch delete dialog was marked to expand, which meant that if the user grew the window vertically most of the new vertical height was given to the status field and not to the branch list. Since the status field is just a single line of text there is no reason for it to gain additional height, instead we should make sure all additional height goes to the branch list. Signed-off-by: Shawn O. Pearce diff --git a/lib/remote_branch_delete.tcl b/lib/remote_branch_delete.tcl index b83e1b6..d7e2b8d 100644 --- a/lib/remote_branch_delete.tcl +++ b/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 -- cgit v0.10.2-6-g49f6 From d4c5307701551ec65c10bef0dacc643205313098 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 8 Jul 2007 17:41:24 -0400 Subject: git-gui: Honor rerere.enabled configuration option Recently in git.git change b4372ef136 Johannes Schindelin taught git-commit.sh to invoke (or skip) calling git-rerere based upon the rerere.enabled configuration setting: So, check the config variable "rerere.enabled". If it is set to "false" explicitely, do not activate rerere, even if .git/rr-cache exists. This should help when you want to disable rerere temporarily. If "rerere.enabled" is not set at all, fall back to detection of the directory .git/rr-cache. We now do the same logic in git-gui's own commit implementation. Signed-off-by: Shawn O. Pearce diff --git a/lib/commit.tcl b/lib/commit.tcl index f9791f6..0de2a28 100644 --- a/lib/commit.tcl +++ b/lib/commit.tcl @@ -331,7 +331,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} } -- cgit v0.10.2-6-g49f6 From d696702209dd0d4c20d2b87b5bc10cae3d54e839 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 8 Jul 2007 18:48:08 -0400 Subject: git-gui: New Git version check support routine Some newer features of git-gui want to rely on features that are new to Git 1.5.3. Since they were added as part of the 1.5.3 development series we cannot use those features with versions of Git that are older than 1.5.3, such as from the stable 1.5.2 series. We introduce [git-version >= 1.5.3] to allow the caller to get a response of 0 if the current version of git is < 1.5.3 and 1 if the current version of git is >= 1.5.3. This makes it easy to setup conditional code based upon the version of Git available to us at runtime. Instead of parsing the version text by hand we now use the Tcl [package vcompare] subcommand to compare the two version strings. This works nicely, as Tcl as already done all of the hard work of doing version comparsions. But we do have to remove the Git specific components such as the Git commit SHA-1, commit count and release candidate suffix (rc) as we want only the final release version number. Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index 0096f49..c36a986 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -308,33 +308,76 @@ proc tk_optionMenu {w varName args} { ## ## version check -set req_maj 1 -set req_min 5 - -if {[catch {set v [git --version]} err]} { +if {[catch {set _git_version [git --version]} err]} { catch {wm withdraw .} error_popup "Cannot determine Git version: $err -[appname] requires Git $req_maj.$req_min or later." +[appname] requires Git 1.5.0 or later." + exit 1 +} +if {![regsub {^git version } $_git_version {} _git_version]} { + catch {wm withdraw .} + error_popup "Cannot parse Git version string:\n\n$_git_version" exit 1 } -if {[regexp {^git version (\d+)\.(\d+)} $v _junk act_maj act_min]} { - if {$act_maj < $req_maj - || ($act_maj == $req_maj && $act_min < $req_min)} { - catch {wm withdraw .} - error_popup "[appname] requires Git $req_maj.$req_min or later. +regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version +regsub {\.rc[0-9]+$} $_git_version {} _git_version -You are using $v." - exit 1 +proc git-version {args} { + global _git_version + + switch [llength $args] { + 0 { + return $_git_version } -} else { + + 2 { + set op [lindex $args 0] + set vr [lindex $args 1] + set cm [package vcompare $_git_version $vr] + return [expr $cm $op 0] + } + + 4 { + set type [lindex $args 0] + set name [lindex $args 1] + set parm [lindex $args 2] + set body [lindex $args 3] + + if {($type ne {proc} && $type ne {method})} { + error "Invalid arguments to git-version" + } + if {[llength $body] < 2 || [lindex $body end-1] ne {default}} { + error "Last arm of $type $name must be default" + } + + foreach {op vr cb} [lrange $body 0 end-2] { + if {[git-version $op $vr]} { + return [uplevel [list $type $name $parm $cb]] + } + } + + return [uplevel [list $type $name $parm [lindex $body end]]] + } + + default { + error "git-version >= x" + } + + } +} + +if {[git-version < 1.5]} { catch {wm withdraw .} - error_popup "Cannot parse Git version string:\n\n$v" + error_popup "[appname] requires Git 1.5.0 or later. + +You are using [git-version]: + +[git --version]" exit 1 } -unset -nocomplain v _junk act_maj act_min req_maj req_min ###################################################################### ## -- cgit v0.10.2-6-g49f6 From a840566770c262f76e8fd7fb69239fbedd10b259 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 20 Jun 2007 21:44:16 -0700 Subject: git-gui: use "blame -w -C -C" for "where did it come from, originally?" The blame window shows "who wrote the piece originally" and "who moved it there" in two columns. In order to identify the former more correctly, it helps to use the new -w option. [sp: Minor change to only enable -w if underlying git >= 1.5.3] Signed-off-by: Junio C Hamano Signed-off-by: Shawn O. Pearce diff --git a/lib/blame.tcl b/lib/blame.tcl index 77abd82..dcdb11b 100644 --- a/lib/blame.tcl +++ b/lib/blame.tcl @@ -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 @@ -512,6 +519,7 @@ method _exec_blame {cur_w cur_d options cur_s} { method _read_blame {fd cur_w cur_d cur_s} { upvar #0 $cur_d line_data variable group_colors + variable original_options if {$fd ne $current_fd} { catch {close $fd} @@ -681,7 +689,7 @@ 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 {} -- cgit v0.10.2-6-g49f6 From 6233ab17297684c0049923cb8492393276672b01 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 30 Jun 2007 04:34:59 -0400 Subject: git-gui: Teach class system to support [$this cmd] syntax Its handy to be able to ask an object to do something for you by handing it a subcommand. For example if we want to get the value of an object's private field the object could expose a method that would return that value. Application level code can then invoke "$inst get" to perform the method call. Tk uses this pattern for all of its widgets, so we'd certainly like to use it for our own mega-widgets that we might develop. Up until now we haven't needed such functionality, but I'm working on a new revision picker mega-widget that would benefit from it. To make this work we have to change the definition of $this to actually be a procedure within the namespace. By making $this a procedure any caller that has $this can call subcommands by passing them as the first argument to $this. That subcommand then needs to call the proper subroutine. Placing the dispatch procedure into the object's variable namespace ensures that it will always be deleted when the object is deleted. Signed-off-by: Shawn O. Pearce diff --git a/lib/class.tcl b/lib/class.tcl index 9d298d0..24e8cec 100644 --- a/lib/class.tcl +++ b/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} } -- cgit v0.10.2-6-g49f6 From b1fa2bfff36933f94dada476ba5b3d3cb894b723 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 3 Jul 2007 22:57:18 -0400 Subject: git-gui: Abstract the revision picker into a mega widget This rather large change pulls the "Starting Revision" part of the new branch dialog into a mega widget that we can use anytime we need to select a commit SHA-1. To make use of the mega widget I have also refactored the branch dialog to use the class system, much like the delete remote branch dialog already does. Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index c36a986..e07f4ba 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1496,7 +1496,7 @@ if {[is_enabled branch]} { menu .mbar.branch .mbar.branch add command -label {Create...} \ - -command do_create_branch \ + -command branch_create::dialog \ -accelerator $M1T-N lappend disable_on_lock [list .mbar.branch entryconf \ [.mbar.branch index last] -state] @@ -2221,8 +2221,8 @@ bind $ui_diff {catch {%W yview scroll 1 pages};break} bind $ui_diff {focus %W} if {[is_enabled branch]} { - bind . <$M1B-Key-n> do_create_branch - bind . <$M1B-Key-N> do_create_branch + bind . <$M1B-Key-n> branch_create::dialog + bind . <$M1B-Key-N> branch_create::dialog } if {[is_enabled transport]} { bind . <$M1B-Key-p> do_push_anywhere diff --git a/lib/branch.tcl b/lib/branch.tcl index 4f648b2..e7559f9 100644 --- a/lib/branch.tcl +++ b/lib/branch.tcl @@ -61,221 +61,11 @@ proc populate_branch_menu {} { } } -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 - } -} - 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 " - grab $w - $w.desc.name_t icursor end - focus $w.desc.name_t - " - bind $w "destroy $w" - bind $w "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 diff --git a/lib/branch_create.tcl b/lib/branch_create.tcl new file mode 100644 index 0000000..ef63f81 --- /dev/null +++ b/lib/branch_create.tcl @@ -0,0 +1,157 @@ +# 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 opt_checkout 1; # automatically checkout the new branch? + +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 Description} + label $w.desc.name_r \ + -anchor w \ + -text {Name:} + 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} + + 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 x -pady 5 -padx 5 + + labelframe $w.postActions -text {Post Creation Actions} + checkbutton $w.postActions.checkout \ + -text {Checkout after creation} \ + -variable @opt_checkout + pack $w.postActions.checkout -anchor nw + pack $w.postActions -anchor nw -fill x -pady 5 -padx 5 + + set name $repo_config(gui.newbranchtemplate) + + bind $w " + grab $w + $w_name icursor end + focus $w_name + " + bind $w [list destroy $w] + bind $w [cb _create]\;break + tkwait window $w +} + +method _create {} { + global null_sha1 repo_config + global all_heads + + set newbranch $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_name + 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_name + 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_name + return + } + + if {[catch {set cmt [$w_rev get_commit]}]} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Invalid starting revision: [$w_rev get]" + return + } + if {[catch { + git update-ref \ + -m "branch: Created from [$w_rev get]" \ + "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 {$opt_checkout} { + switch_branch $newbranch + } +} + +method _validate {d S} { + if {$d == 1} { + if {[regexp {[~^:?*\[\0- ]} $S]} { + return 0 + } + if {[string length $S] > 0} { + set name_type user + } + } + return 1 +} + +} diff --git a/lib/choose_rev.tcl b/lib/choose_rev.tcl new file mode 100644 index 0000000..04f77c0 --- /dev/null +++ b/lib/choose_rev.tcl @@ -0,0 +1,127 @@ +# git-gui revision chooser +# Copyright (C) 2006, 2007 Shawn Pearce + +class choose_rev { + +field w ; # our megawidget path +field revtype {}; # type of revision chosen + +field c_head {}; # selected local branch head +field c_trck {}; # selected tracking branch +field c_tag {}; # selected tag +field c_expr {}; # current revision expression + +constructor new {path {title {}}} { + global all_heads current_branch + + set w $path + + if {$title ne {}} { + labelframe $w -text $title + } else { + frame $w + } + bind $w [cb _delete %W] + + if {$all_heads ne {}} { + set c_head $current_branch + radiobutton $w.head_r \ + -text {Local Branch:} \ + -value head \ + -variable @revtype + eval tk_optionMenu $w.head_m @c_head $all_heads + grid $w.head_r $w.head_m -sticky w + if {$revtype eq {}} { + set revtype head + } + trace add variable @c_head write [cb _select head] + } + + set all_trackings [all_tracking_branches] + if {$all_trackings ne {}} { + set c_trck [lindex $all_trackings 0] + radiobutton $w.trck_r \ + -text {Tracking Branch:} \ + -value trck \ + -variable @revtype + eval tk_optionMenu $w.trck_m @c_trck $all_trackings + grid $w.trck_r $w.trck_m -sticky w + if {$revtype eq {}} { + set revtype trck + } + trace add variable @c_trck write [cb _select trck] + } + + set all_tags [load_all_tags] + if {$all_tags ne {}} { + set c_tag [lindex $all_tags 0] + radiobutton $w.tag_r \ + -text {Tag:} \ + -value tag \ + -variable @revtype + eval tk_optionMenu $w.tag_m @c_tag $all_tags + grid $w.tag_r $w.tag_m -sticky w + if {$revtype eq {}} { + set revtype tag + } + trace add variable @c_tag write [cb _select tag] + } + + 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} + if {$revtype eq {}} { + set revtype expr + } + + grid columnconfigure $w 1 -weight 1 + return $this +} + +method get {} { + switch -- $revtype { + head { return $c_head } + trck { return $c_trck } + tag { return $c_tag } + expr { return $c_expr } + default { error "unknown type of revision" } + } +} + +method get_commit {} { + set rev [get $this] + return [git rev-parse --verify "${rev}^0"] +} + +method _validate {d S} { + if {$d == 1} { + if {[regexp {\s} $S]} { + return 0 + } + if {[string length $S] > 0} { + set revtype expr + } + } + return 1 +} + +method _select {value args} { + set revtype $value +} + +method _delete {current} { + if {$current eq $w} { + delete_this + } +} + +} -- cgit v0.10.2-6-g49f6 From 3206c63d0a365b11a430225a31f6a673f7645c25 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 3 Jul 2007 23:33:59 -0400 Subject: git-gui: Refactor the delete branch dialog to use class system A simple refactoring of the delete branch dialog to allow use of the class construct to better organize the code and to reuse the revision selection code of our new choose_rev mega-widget. Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index e07f4ba..63b2045 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1507,7 +1507,7 @@ if {[is_enabled branch]} { [.mbar.branch index last] -state] .mbar.branch add command -label {Delete...} \ - -command do_delete_branch + -command branch_delete::dialog lappend disable_on_lock [list .mbar.branch entryconf \ [.mbar.branch index last] -state] diff --git a/lib/branch.tcl b/lib/branch.tcl index e7559f9..e56f674 100644 --- a/lib/branch.tcl +++ b/lib/branch.tcl @@ -66,170 +66,6 @@ proc radio_selector {varname value args} { set var $value } -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 "grab $w; focus $w" - bind $w "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 diff --git a/lib/branch_delete.tcl b/lib/branch_delete.tcl new file mode 100644 index 0000000..16ca693 --- /dev/null +++ b/lib/branch_delete.tcl @@ -0,0 +1,163 @@ +# 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 all_heads 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 \ + -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 $all_heads { + if {$h ne $current_branch} { + $w_heads insert end $h + } + } + + bind $w_heads <> [cb _select] + bind $w " + grab $w + focus $w + " + bind $w [list destroy $w] + bind $w [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 {} { + global all_heads + + if {[catch {set check_cmt [$w_check get_commit]} err]} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Invalid revision: [$w_check get]" + 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" + } 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 +} + +} diff --git a/lib/choose_rev.tcl b/lib/choose_rev.tcl index 04f77c0..01e8efe 100644 --- a/lib/choose_rev.tcl +++ b/lib/choose_rev.tcl @@ -87,17 +87,38 @@ constructor new {path {title {}}} { return $this } +method none {text} { + if {[winfo exists $w.none_r]} { + $w.none_r configure -text $text + return + } + + radiobutton $w.none_r \ + -anchor w \ + -text $text \ + -value none \ + -variable @revtype + grid $w.none_r -sticky we -padx {0 5} -columnspan 2 + if {$revtype eq {}} { + set revtype none + } +} + method get {} { switch -- $revtype { head { return $c_head } trck { return $c_trck } tag { return $c_tag } expr { return $c_expr } + none { return {} } default { error "unknown type of revision" } } } method get_commit {} { + if {$revtype eq {none}} { + return {} + } set rev [get $this] return [git rev-parse --verify "${rev}^0"] } -- cgit v0.10.2-6-g49f6 From 6f2a3fc812623bc82fb4997adb5a9a547b5dae1a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 4 Jul 2007 00:15:41 -0400 Subject: git-gui: Optimize for newstyle refs/remotes layout Most people using Git 1.5.x and later are using the newer style of remotes layout where all of their tracking branches are in refs/remotes and refs/heads contains only the user's own local branches. In such a situation we can avoid calling is_tracking_branch for each head we are considering because we know that all of the heads must be local branches if no fetch option or Pull: line maps a branch into that namespace. If however any remote maps a remote branch into a local tracking branch that resides in refs/heads we do exactly what we did before, which requires scanning through all fetch lines in case any patterns are matched. I also switched some regexp/regsub calls to string match as this can be a faster operation for prefix matching. Signed-off-by: Shawn O. Pearce diff --git a/lib/branch.tcl b/lib/branch.tcl index e56f674..de638d0 100644 --- a/lib/branch.tcl +++ b/lib/branch.tcl @@ -3,13 +3,16 @@ 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 [open "| git for-each-ref --format=%(refname) $rh" r] 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 diff --git a/lib/remote.tcl b/lib/remote.tcl index b54824a..55a6dc3 100644 --- a/lib/remote.tcl +++ b/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 } } @@ -20,9 +19,10 @@ proc all_tracking_branches {} { set all_trackings {} set cmd {} - foreach name [array names tracking_branches] { - if {[regsub {/\*$} $name {} name]} { - lappend cmd $name + foreach spec $tracking_branches { + set name [lindex $spec 0] + if {[string range $name end-1 end] eq {/*}} { + lappend cmd [string range $name 0 end-2] } else { regsub ^refs/(heads|remotes)/ $name {} name lappend all_trackings $name @@ -43,11 +43,14 @@ proc all_tracking_branches {} { 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 +65,16 @@ 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 equal -length 5 refs/ $src]} { + set src $rh_str$src + } + if {![string equal -length 5 refs/ $dst]} { + set dst $rh_str$dst } - set tracking_branches($dst) [list $name $src] + if {[string equal -length $rh_len $rh_str $dst]} { + set some_heads_tracking 1 + } + lappend trck [list $dst $name $src] } close $fd } @@ -81,13 +90,20 @@ 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 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] } -- cgit v0.10.2-6-g49f6 From 79a060e477a743ad49508aec9a491fe1aa7d7c3b Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 4 Jul 2007 01:10:41 -0400 Subject: git-gui: Maintain remote and source ref for tracking branches In the next change I want to let the user create their local branch name to match the remote branch name, so that the existing push dialog can push the branch back up to the remote repository without needing to do any sort of remapping. To do that we need to know exactly what branch name the remote system is using. So all_tracking_branches returns a list of specifications, where each specification is itself a list of: - local ref name (destination we fetch into) - remote name (repository we fetch from) - remote ref name (source ref we fetch from) Signed-off-by: Shawn O. Pearce diff --git a/lib/choose_rev.tcl b/lib/choose_rev.tcl index 01e8efe..b3b615e 100644 --- a/lib/choose_rev.tcl +++ b/lib/choose_rev.tcl @@ -11,6 +11,8 @@ field c_trck {}; # selected tracking branch field c_tag {}; # selected tag field c_expr {}; # current revision expression +field trck_spec ; # array of specifications + constructor new {path {title {}}} { global all_heads current_branch @@ -37,19 +39,30 @@ constructor new {path {title {}}} { trace add variable @c_head write [cb _select head] } - set all_trackings [all_tracking_branches] - if {$all_trackings ne {}} { - set c_trck [lindex $all_trackings 0] + set trck_list [all_tracking_branches] + if {$trck_list ne {}} { + set nam [list] + foreach spec $trck_list { + set txt [lindex $spec 0] + regsub ^refs/(heads/|remotes/)? $txt {} txt + set trck_spec($txt) $spec + lappend nam $txt + } + set nam [lsort -unique $nam] + radiobutton $w.trck_r \ -text {Tracking Branch:} \ -value trck \ -variable @revtype - eval tk_optionMenu $w.trck_m @c_trck $all_trackings + eval tk_optionMenu $w.trck_m @c_trck $nam grid $w.trck_r $w.trck_m -sticky w + + set c_trck [lindex $nam 0] if {$revtype eq {}} { set revtype trck } trace add variable @c_trck write [cb _select trck] + unset nam spec txt } set all_tags [load_all_tags] @@ -115,12 +128,22 @@ method get {} { } } +method get_expr {} { + switch -- $revtype { + head { return refs/heads/$c_head } + trck { return [lindex $trck_spec($c_trck) 0] } + tag { return refs/tags/$c_tag } + expr { return $c_expr } + none { return {} } + default { error "unknown type of revision" } + } +} + method get_commit {} { if {$revtype eq {none}} { return {} } - set rev [get $this] - return [git rev-parse --verify "${rev}^0"] + return [git rev-parse --verify "[get_expr $this]^0"] } method _validate {d S} { diff --git a/lib/remote.tcl b/lib/remote.tcl index 55a6dc3..fabec05 100644 --- a/lib/remote.tcl +++ b/lib/remote.tcl @@ -17,28 +17,41 @@ proc is_tracking_branch {name} { proc all_tracking_branches {} { global tracking_branches - set all_trackings {} - set cmd {} + set all [list] + set pat [list] + set cmd [list] + foreach spec $tracking_branches { - set name [lindex $spec 0] - if {[string range $name end-1 end] eq {/*}} { - lappend cmd [string range $name 0 end-2] + 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 {}} { + if {$pat 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 + 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 {} { @@ -65,6 +78,9 @@ proc load_all_remotes {} { while {[gets $fd line] >= 0} { if {![regexp {^Pull:[ ]*([^:]+):(.+)$} \ $line line src dst]} continue + 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 } @@ -90,6 +106,9 @@ proc load_all_remotes {} { } foreach line $fl { if {![regexp {^([^:]+):(.+)$} $line line src dst]} continue + 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 } -- cgit v0.10.2-6-g49f6 From dd87efc8cc71ceb6aa9a27e900976c317c927846 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 4 Jul 2007 02:19:53 -0400 Subject: git-gui: Allow users to match remote branch names locally Some workflows have users create a local branch that matches a remote branch they have fetched from another repository. If the user wants to push their changes back to that remote repository then they probably want to use the same branch name locally so that git-gui's push dialog can setup the push refspec automatically. To prevent typos with the local branch name we now offer an option to use the remote tracking branch name as the new local branch name. Signed-off-by: Shawn O. Pearce diff --git a/lib/branch_create.tcl b/lib/branch_create.tcl index ef63f81..7f82424 100644 --- a/lib/branch_create.tcl +++ b/lib/branch_create.tcl @@ -8,6 +8,8 @@ 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_checkout 1; # automatically checkout the new branch? constructor dialog {} { @@ -32,10 +34,12 @@ constructor dialog {} { 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_r \ + labelframe $w.desc -text {Branch Name} + radiobutton $w.desc.name_r \ -anchor w \ - -text {Name:} + -text {Name:} \ + -value user \ + -variable @name_type set w_name $w.desc.name_t entry $w_name \ -borderwidth 1 \ @@ -46,6 +50,13 @@ constructor dialog {} { -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 @@ -75,7 +86,33 @@ method _create {} { global null_sha1 repo_config global all_heads - set newbranch $name + switch -- $name_type { + user { + set newbranch $name + } + match { + set spec [$w_rev get_tracking_branch] + 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 \ diff --git a/lib/choose_rev.tcl b/lib/choose_rev.tcl index b3b615e..8b92412 100644 --- a/lib/choose_rev.tcl +++ b/lib/choose_rev.tcl @@ -128,6 +128,14 @@ method get {} { } } +method get_tracking_branch {} { + if {$revtype eq {trck}} { + return $trck_spec($c_trck) + } else { + return {} + } +} + method get_expr {} { switch -- $revtype { head { return refs/heads/$c_head } -- cgit v0.10.2-6-g49f6 From 774173aa5f24023a82ce79f2a71fe3f664b747ad Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 4 Jul 2007 04:21:57 -0400 Subject: git-gui: Fast-forward existing branch in branch create dialog If the user elects to create a local branch that has the same name as an existing branch and we can fast-forward the local branch to the selected revision we might as well do the fast-forward for the user, rather than making them first switch to the branch then merge the selected revision into it. After all, its really just a fast forward. No history is lost. The resulting branch checkout may also be faster if the branch we are switching from is closer to the new revision. Likewise we also now allow the user to reset the local branch if it already exists but would not fast-forward. However before we do the actual reset we tell the user what commits they are going to lose by showing the oneline subject and abbreviated sha1, and we also let them inspect the range of commits in gitk. Signed-off-by: Shawn O. Pearce diff --git a/lib/branch_create.tcl b/lib/branch_create.tcl index 7f82424..0272d6f 100644 --- a/lib/branch_create.tcl +++ b/lib/branch_create.tcl @@ -10,7 +10,9 @@ 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 reset_ok 0; # did the user agree to reset? constructor dialog {} { global repo_config @@ -63,12 +65,33 @@ constructor dialog {} { set w_rev [::choose_rev::new $w.rev {Starting Revision}] pack $w.rev -anchor nw -fill x -pady 5 -padx 5 - labelframe $w.postActions -text {Post Creation Actions} - checkbutton $w.postActions.checkout \ - -text {Checkout after creation} \ + 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 no \ + -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.checkout \ + -text {Checkout After Creation} \ -variable @opt_checkout - pack $w.postActions.checkout -anchor nw - pack $w.postActions -anchor nw -fill x -pady 5 -padx 5 + pack $w.options.checkout -anchor nw + pack $w.options -anchor nw -fill x -pady 5 -padx 5 set name $repo_config(gui.newbranchtemplate) @@ -84,7 +107,7 @@ constructor dialog {} { method _create {} { global null_sha1 repo_config - global all_heads + global all_heads current_branch switch -- $name_type { user { @@ -124,61 +147,214 @@ method _create {} { focus $w_name return } - if {![catch {git show-ref --verify -- "refs/heads/$newbranch"}]} { + + if {$newbranch eq $current_branch} { tk_messageBox \ -icon error \ -type ok \ -title [wm title $w] \ -parent $w \ - -message "Branch '$newbranch' already exists." + -message "'$newbranch' already exists and is the current branch." 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 "We do not like '$newbranch' as a branch name." + -message "'$newbranch' is not an acceptable branch name." focus $w_name return } - if {[catch {set cmt [$w_rev get_commit]}]} { + if {[catch {set new [$w_rev get_commit]}]} { tk_messageBox \ -icon error \ -type ok \ -title [wm title $w] \ -parent $w \ - -message "Invalid starting revision: [$w_rev get]" + -message "Invalid revision: [$w_rev get]" return } - if {[catch { - git update-ref \ - -m "branch: Created from [$w_rev get]" \ - "refs/heads/$newbranch" \ - $cmt \ - $null_sha1 - } err]} { + + set ref refs/heads/$newbranch + if {[catch {set cur [git rev-parse --verify "$ref^0"]}]} { + # Assume it does not exist, and that is what the error was. + # + set reflog_msg "branch: Created from [$w_rev get]" + set cur $null_sha1 + } elseif {$opt_merge eq {no}} { tk_messageBox \ -icon error \ -type ok \ -title [wm title $w] \ -parent $w \ - -message "Failed to create '$newbranch'.\n\n$err" + -message "Branch '$newbranch' already exists." + focus $w_name return + } else { + set mrb {} + catch {set mrb [git merge-base $new $cur]} + switch -- $opt_merge { + 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 [$w_rev get]: Fast-forward" + } else { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Branch '$newbranch' already exists.\n\nIt cannot fast-forward to [$w_rev get].\nA merge is required." + focus $w_name + return + } + } + reset { + if {$mrb eq $cur} { + # The current branch is older. + # + set reflog_msg "merge [$w_rev get]: Fast-forward" + } else { + # The current branch will lose things. + # + if {[_confirm_reset $this $newbranch $cur $new]} { + set reflog_msg "reset [$w_rev get]" + } else { + return + } + } + } + default { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Branch '$newbranch' already exists." + focus $w_name + return + } + } + } + + if {$new ne $cur} { + if {[catch { + git update-ref -m $reflog_msg $ref $new $cur + } err]} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Failed to create '$newbranch'.\n\n$err" + return + } + } + + if {$cur eq $null_sha1} { + lappend all_heads $newbranch + set all_heads [lsort -uniq $all_heads] + populate_branch_menu } - lappend all_heads $newbranch - set all_heads [lsort $all_heads] - populate_branch_menu destroy $w if {$opt_checkout} { switch_branch $newbranch } } +method _confirm_reset {newbranch cur new} { + set reset_ok 0 + set gitk [list do_gitk [list $cur ^$new]] + + set c $w.confirm_reset + toplevel $c + wm title $c "Confirm Branch Reset" + wm geometry $c "+[winfo rootx $w]+[winfo rooty $w]" + + pack [label $c.msg1 \ + -anchor w \ + -justify left \ + -text "Resetting '$newbranch' to [$w_rev get] will lose the following commits:" \ + ] -anchor w + + set list $c.list.l + frame $c.list + text $list \ + -font font_diff \ + -width 80 \ + -height 10 \ + -wrap none \ + -xscrollcommand [list $c.list.sbx set] \ + -yscrollcommand [list $c.list.sby set] + scrollbar $c.list.sbx -orient h -command [list $list xview] + scrollbar $c.list.sby -orient v -command [list $list yview] + pack $c.list.sbx -fill x -side bottom + pack $c.list.sby -fill y -side right + pack $list -fill both -expand 1 + pack $c.list -fill both -expand 1 -padx 5 -pady 5 + + pack [label $c.msg2 \ + -anchor w \ + -justify left \ + -text "Recovering lost commits may not be easy." \ + ] + pack [label $c.msg3 \ + -anchor w \ + -justify left \ + -text "Reset '$newbranch'?" \ + ] + + frame $c.buttons + button $c.buttons.visualize \ + -text Visualize \ + -command $gitk + pack $c.buttons.visualize -side left + button $c.buttons.reset \ + -text Reset \ + -command " + set @reset_ok 1 + destroy $c + " + pack $c.buttons.reset -side right + button $c.buttons.cancel \ + -default active \ + -text Cancel \ + -command [list destroy $c] + pack $c.buttons.cancel -side right -padx 5 + pack $c.buttons -side bottom -fill x -pady 10 -padx 10 + + set fd [open "| git rev-list --pretty=oneline $cur ^$new" r] + 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 $c $gitk + + bind $c " + grab $c + focus $c.buttons.cancel + " + bind $c [list destroy $c] + bind $c [list destroy $c] + tkwait window $c + return $reset_ok +} + method _validate {d S} { if {$d == 1} { if {[regexp {[~^:?*\[\0- ]} $S]} { -- cgit v0.10.2-6-g49f6 From 7618e6b1c1676dfdc2cc4c8af9e259c3e885825f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 4 Jul 2007 16:38:13 -0400 Subject: git-gui: Enhance choose_rev to handle hundreds of branches One of my production repositories has hundreds of remote tracking branches. Trying to navigate these through a popup menu is just not possible. The list is far larger than the screen and it does not scroll fast enough to efficiently select a branch name when trying to create a branch or delete a branch. This is major rewrite of the revision chooser mega-widget. We now use a single listbox for all three major types of named refs (heads, tracking branches, tags) and a radio button group to pick which of those namespaces should be shown in the listbox. A filter field is shown to the right allowing the end-user to key in a glob specification to filter the list they are viewing. The filter is always taken as substring, so we assume * both starts and ends the pattern the user wanted but otherwise treat it as a glob pattern. This new picker works out really nicely. What used to take me at least a minute to find and select a branch now takes mere seconds. Signed-off-by: Shawn O. Pearce diff --git a/lib/branch_create.tcl b/lib/branch_create.tcl index 0272d6f..375f575 100644 --- a/lib/branch_create.tcl +++ b/lib/branch_create.tcl @@ -63,7 +63,7 @@ constructor dialog {} { 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 x -pady 5 -padx 5 + pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5 labelframe $w.options -text {Options} @@ -170,13 +170,7 @@ method _create {} { return } - if {[catch {set new [$w_rev get_commit]}]} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Invalid revision: [$w_rev get]" + if {[catch {set new [$w_rev commit_or_die]}]} { return } diff --git a/lib/branch_delete.tcl b/lib/branch_delete.tcl index 16ca693..138e841 100644 --- a/lib/branch_delete.tcl +++ b/lib/branch_delete.tcl @@ -40,6 +40,7 @@ constructor dialog {} { -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 @@ -80,13 +81,7 @@ method _select {} { method _delete {} { global all_heads - if {[catch {set check_cmt [$w_check get_commit]} err]} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Invalid revision: [$w_check get]" + if {[catch {set check_cmt [$w_check commit_or_die]}]} { return } diff --git a/lib/choose_rev.tcl b/lib/choose_rev.tcl index 8b92412..f19da0f 100644 --- a/lib/choose_rev.tcl +++ b/lib/choose_rev.tcl @@ -3,15 +3,19 @@ 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 revtype {}; # type of revision chosen +field w_list ; # list of currently filtered specs +field w_filter ; # filter entry for $w_list -field c_head {}; # selected local branch head -field c_trck {}; # selected tracking branch -field c_tag {}; # selected tag field c_expr {}; # current revision expression - -field trck_spec ; # array of specifications +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 all_heads current_branch @@ -25,61 +29,6 @@ constructor new {path {title {}}} { } bind $w [cb _delete %W] - if {$all_heads ne {}} { - set c_head $current_branch - radiobutton $w.head_r \ - -text {Local Branch:} \ - -value head \ - -variable @revtype - eval tk_optionMenu $w.head_m @c_head $all_heads - grid $w.head_r $w.head_m -sticky w - if {$revtype eq {}} { - set revtype head - } - trace add variable @c_head write [cb _select head] - } - - set trck_list [all_tracking_branches] - if {$trck_list ne {}} { - set nam [list] - foreach spec $trck_list { - set txt [lindex $spec 0] - regsub ^refs/(heads/|remotes/)? $txt {} txt - set trck_spec($txt) $spec - lappend nam $txt - } - set nam [lsort -unique $nam] - - radiobutton $w.trck_r \ - -text {Tracking Branch:} \ - -value trck \ - -variable @revtype - eval tk_optionMenu $w.trck_m @c_trck $nam - grid $w.trck_r $w.trck_m -sticky w - - set c_trck [lindex $nam 0] - if {$revtype eq {}} { - set revtype trck - } - trace add variable @c_trck write [cb _select trck] - unset nam spec txt - } - - set all_tags [load_all_tags] - if {$all_tags ne {}} { - set c_tag [lindex $all_tags 0] - radiobutton $w.tag_r \ - -text {Tag:} \ - -value tag \ - -variable @revtype - eval tk_optionMenu $w.tag_m @c_tag $all_tags - grid $w.tag_r $w.tag_m -sticky w - if {$revtype eq {}} { - set revtype tag - } - trace add variable @c_tag write [cb _select tag] - } - radiobutton $w.expr_r \ -text {Revision Expression:} \ -value expr \ @@ -92,66 +41,186 @@ constructor new {path {title {}}} { -validate key \ -validatecommand [cb _validate %d %S] grid $w.expr_r $w.expr_t -sticky we -padx {0 5} - if {$revtype eq {}} { - set revtype expr - } + + 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 + grid rowconfigure $w 2 -weight 1 + + trace add variable @revtype write [cb _select] + bind $w_filter [list focus $w_list]\;break + bind $w_filter [list focus $w_list] + + set spec_head [list] + foreach name $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 {[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 set $i + break + } + incr i + } + } + return $this } method none {text} { - if {[winfo exists $w.none_r]} { - $w.none_r configure -text $text - return - } - - radiobutton $w.none_r \ - -anchor w \ - -text $text \ - -value none \ - -variable @revtype - grid $w.none_r -sticky we -padx {0 5} -columnspan 2 - if {$revtype eq {}} { - set revtype none + 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 { return $c_head } - trck { return $c_trck } - tag { return $c_tag } - expr { return $c_expr } - none { return {} } + head - + trck - + tag { + set i [$w_list curselection] + if {$i ne {}} { + return [lindex $cur_specs $i 0] + } else { + return {} + } + } + + expr { return $c_expr } + none { return {} } default { error "unknown type of revision" } } } method get_tracking_branch {} { - if {$revtype eq {trck}} { - return $trck_spec($c_trck) - } else { + set i [$w_list curselection] + if {$i eq {} || $revtype ne {trck}} { return {} } + return [lrange [lindex $cur_specs $i] 1 end] } -method get_expr {} { - switch -- $revtype { - head { return refs/heads/$c_head } - trck { return [lindex $trck_spec($c_trck) 0] } - tag { return refs/tags/$c_tag } - expr { return $c_expr } - none { return {} } - default { error "unknown type of revision" } +method get_commit {} { + set e [_expr $this] + if {$e eq {}} { + return {} } + return [git rev-parse --verify "$e^0"] } -method get_commit {} { - if {$revtype eq {none}} { - return {} +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." + } + } + none { return {} } + default { error "unknown type of revision" } } - return [git rev-parse --verify "[get_expr $this]^0"] } method _validate {d S} { @@ -166,8 +235,55 @@ method _validate {d S} { return 1 } -method _select {value args} { - set revtype $value +method _filter {P} { + if {[regexp {\s} $P]} { + return 0 + } + _rebuild $this $P + return 1 +} + +method _select {args} { + _rebuild $this $filter + if {[$w_filter cget -state] eq {normal}} { + focus $w_filter + } +} + +method _rebuild {pat} { + set ste normal + switch -- $revtype { + head { set new $spec_head } + trck { set new $spec_trck } + tag { set new $spec_tag } + expr - + 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 {[$w_filter cget -state] ne $ste} { + $w_list configure -state $ste + $w_filter configure -state $ste + } } method _delete {current} { @@ -176,4 +292,34 @@ method _delete {current} { } } +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 +} + } -- cgit v0.10.2-6-g49f6 From 560eddc00c02e82077998200e8d8a45c31af924c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 5 Jul 2007 00:07:11 -0400 Subject: git-gui: Sort tags descending by tagger date When trying to create a branch from a tag most people are looking for a recent tag, not one that is ancient history. Rather than sorting tags by their string we now sort them by taggerdate, as this places the recent tags at the top of the list and the very old ones at the end. Tag date works nicely as an approximation of the actual history order of commits. Signed-off-by: Shawn O. Pearce diff --git a/lib/branch.tcl b/lib/branch.tcl index de638d0..a6e6921 100644 --- a/lib/branch.tcl +++ b/lib/branch.tcl @@ -21,14 +21,13 @@ proc load_all_heads {} { proc load_all_tags {} { set all_tags [list] - set fd [open "| git for-each-ref --format=%(refname) refs/tags" r] + set fd [open "| git for-each-ref --sort=-taggerdate --format=%(refname) refs/tags" r] while {[gets $fd line] > 0} { if {![regsub ^refs/tags/ $line {} name]} continue lappend all_tags $name } close $fd - - return [lsort $all_tags] + return $all_tags } proc populate_branch_menu {} { -- cgit v0.10.2-6-g49f6 From 7cf044266779d69d3a16322b4d505bc87267a005 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 5 Jul 2007 01:07:06 -0400 Subject: git-gui: Option to default new branches to match tracking branches In some workflows users will want to almost always just create a new local branch that matches a remote branch. In this type of workflow it is handy to have the new branch dialog default to "Match Tracking Branch" and "Starting Revision"-Tracking Branch", with the focus in the branch filter field. This can save users working on this type of workflow at least two mouse clicks every time they create a new local branch or switch to one with a fast-forward. Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index 63b2045..99df2d9 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1339,6 +1339,7 @@ set default_config(merge.verbosity) 2 set default_config(user.name) {} set default_config(user.email) {} +set default_config(gui.matchtrackingbranch) false set default_config(gui.pruneduringfetch) false set default_config(gui.trustmtime) false set default_config(gui.diffcontext) 5 diff --git a/lib/branch_create.tcl b/lib/branch_create.tcl index 375f575..df3f435 100644 --- a/lib/branch_create.tcl +++ b/lib/branch_create.tcl @@ -93,13 +93,14 @@ constructor dialog {} { 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 " - grab $w - $w_name icursor end - focus $w_name - " + bind $w [cb _visible] bind $w [list destroy $w] bind $w [cb _create]\;break tkwait window $w @@ -361,4 +362,18 @@ method _validate {d S} { 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/lib/choose_rev.tcl b/lib/choose_rev.tcl index f19da0f..e6af073 100644 --- a/lib/choose_rev.tcl +++ b/lib/choose_rev.tcl @@ -159,6 +159,10 @@ method get {} { } } +method pick_tracking_branch {} { + set revtype trck +} + method get_tracking_branch {} { set i [$w_list curselection] if {$i eq {} || $revtype ne {trck}} { diff --git a/lib/option.tcl b/lib/option.tcl index ae19a8f..7433042 100644 --- a/lib/option.tcl +++ b/lib/option.tcl @@ -191,6 +191,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}} } { -- cgit v0.10.2-6-g49f6 From ba1964be26b8b9e3441591257a2c87f5811d6b50 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 5 Jul 2007 02:23:53 -0400 Subject: git-gui: Automatically refresh tracking branches when needed If the user is creating a new local branch and has selected to use a tracking branch as the starting revision they probably want to make sure they are using the absolute latest version available of that branch. We now offer a checkbox "Fetch Tracking Branch" (on by default) that instructs git-gui to run git-fetch on just that one branch before resolving the branch name into a commit SHA-1 and making (or updating) the local branch. Signed-off-by: Shawn O. Pearce diff --git a/lib/branch_create.tcl b/lib/branch_create.tcl index df3f435..f708957 100644 --- a/lib/branch_create.tcl +++ b/lib/branch_create.tcl @@ -12,6 +12,7 @@ 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 {} { @@ -87,6 +88,11 @@ constructor dialog {} { 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 @@ -107,15 +113,15 @@ constructor dialog {} { } method _create {} { - global null_sha1 repo_config - global all_heads current_branch + global repo_config current_branch + global M1B + set spec [$w_rev get_tracking_branch] switch -- $name_type { user { set newbranch $name } match { - set spec [$w_rev get_tracking_branch] if {$spec eq {}} { tk_messageBox \ -icon error \ @@ -171,6 +177,52 @@ method _create {} { return } + if {$spec ne {} && $opt_fetch} { + set l_trck [lindex $spec 0] + set remote [lindex $spec 1] + set r_head [lindex $spec 2] + regsub ^refs/heads/ $r_head {} r_head + + set c $w.fetch_trck + toplevel $c + wm title $c "Refreshing Tracking Branch" + wm geometry $c "+[winfo rootx $w]+[winfo rooty $w]" + + set e [::console::embed \ + $c.console \ + "Fetching $r_head from $remote"] + pack $c.console -fill both -expand 1 + $e exec \ + [list git fetch $remote +$r_head:$l_trck] \ + [cb _finish_fetch $newbranch $c $e] + + bind $c [list grab $c] + bind $c <$M1B-Key-w> break + bind $c <$M1B-Key-W> break + wm protocol $c WM_DELETE_WINDOW [cb _noop] + } else { + _finish_create $this $newbranch + } +} + +method _noop {} {} + +method _finish_fetch {newbranch c e ok} { + wm protocol $c WM_DELETE_WINDOW {} + + if {$ok} { + destroy $c + _finish_create $this $newbranch + } else { + $e done $ok + button $c.close -text Close -command [list destroy $c] + pack $c.close -side bottom -anchor e -padx 10 -pady 10 + } +} + +method _finish_create {newbranch} { + global null_sha1 all_heads + if {[catch {set new [$w_rev commit_or_die]}]} { return } diff --git a/lib/console.tcl b/lib/console.tcl index ce25d92..297e8de 100644 --- a/lib/console.tcl +++ b/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 @@ -57,15 +73,17 @@ 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 [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 "focus $w" } method exec {cmd {after {}}} { @@ -159,16 +177,20 @@ 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 } -- cgit v0.10.2-6-g49f6 From 311e02a4a5c7b82c996214f06d58e2b51b3ecc22 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 5 Jul 2007 19:19:37 -0400 Subject: git-gui: Better handling of detached HEAD If the current branch is not a symbolic-ref that points to a name in the refs/heads/ namespace we now just assume that the head is a detached head. In this case we return the special branch name of HEAD rather than empty string, as HEAD is a valid revision specification and the empty string is not. I have also slightly improved the current-branch function by using string functions to parse the symbolic-ref data. This should be slightly faster than using a regsub. I think the code is clearer too. Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index 99df2d9..516bd97 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -285,14 +285,24 @@ proc git {args} { } proc current-branch {} { - set ref {} set fd [open [gitdir HEAD] r] - if {[gets $fd ref] <16 - || ![regsub {^ref: refs/heads/} $ref {} ref]} { + if {[gets $fd ref] < 1} { set ref {} } close $fd - return $ref + + set pfx {ref: refs/heads/} + set len [string length $pfx] + if {[string equal -length $len $pfx $ref]} { + # We're on a branch. It might not exist. But + # HEAD looks good enough to be a branch. + # + return [string range $ref $len end] + } else { + # Assume this is a detached head. + # + return HEAD + } } auto_load tk_optionMenu -- cgit v0.10.2-6-g49f6 From 699d5601f59938d62ec2506f319c25a656a403da Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 5 Jul 2007 23:16:13 -0400 Subject: git-gui: Refactor our ui_status_value update technique I'm really starting to dislike global variables. The ui_status_value global varible is just one of those that seems to appear in a lot of code and in many cases we didn't even declare it "global" within the proc that updates it so we haven't always been getting all of the updates we expected to see. This change introduces two new global procs: ui_status $msg; # Sets the status bar to show $msg. ui_ready; # Changes the status bar to show "Ready." The second (special) form is used because we often update the area with this message once we are done processing a block of work and want the user to know we have completed it. I'm not fixing the cases that appear in lib/branch.tcl right now as I'm actually in the middle of a huge refactoring of that code to support making a detached HEAD checkout. Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index 516bd97..88ef643 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -527,7 +527,7 @@ proc PARENT {} { proc rescan {after {honor_trustmtime 1}} { global HEAD PARENT MERGE_HEAD commit_type - global ui_index ui_workdir ui_status_value ui_comm + global ui_index ui_workdir ui_comm global rescan_active file_states global repo_config @@ -566,7 +566,7 @@ proc rescan {after {honor_trustmtime 1}} { rescan_stage2 {} $after } else { set rescan_active 1 - set ui_status_value {Refreshing file status...} + ui_status {Refreshing file status...} set cmd [list git update-index] lappend cmd -q lappend cmd --unmerged @@ -580,7 +580,6 @@ proc rescan {after {honor_trustmtime 1}} { } proc rescan_stage2 {fd after} { - global ui_status_value global rescan_active buf_rdi buf_rdf buf_rlo if {$fd ne {}} { @@ -601,7 +600,7 @@ proc rescan_stage2 {fd after} { set buf_rlo {} set rescan_active 3 - set ui_status_value {Scanning for modified files ...} + ui_status {Scanning for modified files ...} set fd_di [open "| git diff-index --cached -z [PARENT]" r] set fd_df [open "| git diff-files -z" r] set fd_lo [open $ls_others r] @@ -761,6 +760,16 @@ proc mapdesc {state path} { return $r } +proc ui_status {msg} { + set ::ui_status_value $msg +} + +proc ui_ready {{test {}}} { + if {$test eq {} || $::ui_status_value eq $test} { + ui_status Ready. + } +} + proc escape_path {path} { regsub -all {\\} $path "\\\\" path regsub -all "\n" $path "\\n" path @@ -1112,7 +1121,7 @@ proc incr_font_size {font {amt 1}} { set starting_gitk_msg {Starting gitk... please wait...} proc do_gitk {revs} { - global env ui_status_value starting_gitk_msg + global env starting_gitk_msg # -- Always start gitk through whatever we were loaded with. This # lets us bypass using shell process on Windows systems. @@ -1129,11 +1138,9 @@ proc do_gitk {revs} { error_popup "Unable to start gitk:\n\n$exe does not exist" } else { eval exec $cmd & - set ui_status_value $starting_gitk_msg + ui_status $starting_gitk_msg after 10000 { - if {$ui_status_value eq $starting_gitk_msg} { - set ui_status_value {Ready.} - } + ui_ready $starting_gitk_msg } } } @@ -1182,7 +1189,7 @@ proc do_quit {} { } proc do_rescan {} { - rescan {set ui_status_value {Ready.}} + rescan ui_ready } proc do_commit {} { @@ -1217,12 +1224,12 @@ proc toggle_or_diff {w x y} { update_indexinfo \ "Unstaging [short_path $path] from commit" \ [list $path] \ - [concat $after {set ui_status_value {Ready.}}] + [concat $after [list ui_ready]] } elseif {$w eq $ui_workdir} { update_index \ "Adding [short_path $path]" \ [list $path] \ - [concat $after {set ui_status_value {Ready.}}] + [concat $after [list ui_ready]] } } else { show_diff $path $w $lno @@ -1640,20 +1647,19 @@ if {[is_MacOSX]} { # if {[is_Cygwin] && [file exists /usr/local/miga/lib/gui-miga]} { proc do_miga {} { - global ui_status_value if {![lock_index update]} return set cmd [list sh --login -c "/usr/local/miga/lib/gui-miga \"[pwd]\""] set miga_fd [open "|$cmd" r] fconfigure $miga_fd -blocking 0 fileevent $miga_fd readable [list miga_done $miga_fd] - set ui_status_value {Running miga...} + ui_status {Running miga...} } proc miga_done {fd} { read $fd 512 if {[eof $fd]} { close $fd unlock_index - rescan [list set ui_status_value {Ready.}] + rescan ui_ready } } .mbar add cascade -label Tools -menu .mbar.tools diff --git a/lib/commit.tcl b/lib/commit.tcl index 0de2a28..c5f608b 100644 --- a/lib/commit.tcl +++ b/lib/commit.tcl @@ -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,9 +234,7 @@ proc commit_prehook_wait {fd_ph curHEAD msg} { } proc commit_writetree {curHEAD msg} { - global ui_status_value - - set ui_status_value {Committing changes...} + ui_status {Committing changes...} set fd_wt [open "| git write-tree" r] fileevent $fd_wt readable \ [list commit_committree $fd_wt $curHEAD $msg] @@ -244,15 +242,15 @@ proc commit_writetree {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 } @@ -269,7 +267,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 +292,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 +314,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 } @@ -410,6 +408,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/lib/diff.tcl b/lib/diff.tcl index 29436b5..05bf751 100644 --- a/lib/diff.tcl +++ b/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,7 +127,7 @@ 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 } @@ -157,7 +157,7 @@ proc show_diff {path w {lno {}}} { if {[catch {set fd [open $cmd r]} 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 diff --git a/lib/index.tcl b/lib/index.tcl index 4274285..7c175a9 100644 --- a/lib/index.tcl +++ b/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,7 +12,7 @@ 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 \ @@ -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,7 +85,7 @@ 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 \ @@ -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,7 +162,7 @@ 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 \ @@ -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/lib/merge.tcl b/lib/merge.tcl index 889182f..f0a02ea 100644 --- a/lib/merge.tcl +++ b/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]] @@ -150,14 +150,14 @@ You can attempt this merge again by merging only one branch at a time." $w 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 {} { @@ -285,7 +285,7 @@ Continue with aborting the current $op?"] eq {yes}} { set fd [open "| git read-tree --reset -u HEAD" r] 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 } @@ -308,7 +308,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.}} } } -- cgit v0.10.2-6-g49f6 From d41b43eb4c73044d0fff2057211fc78e4e7b2094 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 8 Jul 2007 18:40:56 -0400 Subject: git-gui: Refactor branch switch to support detached head This is a major rewrite of the way we perform switching between branches and the subsequent update of the working directory. Like core Git we now use a single code path to perform all changes: our new checkout_op class. We also use it for branch creation/update as it integrates the tracking branch fetch process along with a very basic merge (fast-forward and reset only currently). Because some users have literally hundreds of local branches we use the standard revision picker (with its branch filtering tool) to select the local branch, rather than keeping all of the local branches in the Branch menu. The branch menu listing out all of the available branches is simply not sane for those types of huge repositories. Users can now checkout a detached head by ticking off the option in the checkout dialog. This option is off by default for the obvious reason, but it can be easily enabled for any local branch by simply checking it. We also detach the head if any non local branch was selected, or if a revision expression was entered. Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index 88ef643..ac04367 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -284,7 +284,9 @@ proc git {args} { return [eval exec git $args] } -proc current-branch {} { +proc load_current_branch {} { + global current_branch is_detached + set fd [open [gitdir HEAD] r] if {[gets $fd ref] < 1} { set ref {} @@ -297,11 +299,13 @@ proc current-branch {} { # We're on a branch. It might not exist. But # HEAD looks good enough to be a branch. # - return [string range $ref $len end] + set current_branch [string range $ref $len end] + set is_detached 0 } else { # Assume this is a detached head. # - return HEAD + set current_branch HEAD + set is_detached 1 } } @@ -442,6 +446,7 @@ set MERGE_HEAD [list] set commit_type {} set empty_tree {} set current_branch {} +set is_detached 0 set current_diff_path {} set selected_commit_type new @@ -491,7 +496,7 @@ proc repository_state {ctvar hdvar mhvar} { set mh [list] - set current_branch [current-branch] + load_current_branch if {[catch {set hd [git rev-parse --verify HEAD]}]} { set hd {} set ct initial @@ -557,11 +562,6 @@ proc rescan {after {honor_trustmtime 1}} { $ui_comm edit modified false } - if {[is_enabled branch]} { - load_all_heads - populate_branch_menu - } - if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} { rescan_stage2 {} $after } else { @@ -1519,6 +1519,12 @@ if {[is_enabled branch]} { lappend disable_on_lock [list .mbar.branch entryconf \ [.mbar.branch index last] -state] + .mbar.branch add command -label {Checkout...} \ + -command branch_checkout::dialog \ + -accelerator $M1T-O + lappend disable_on_lock [list .mbar.branch entryconf \ + [.mbar.branch index last] -state] + .mbar.branch add command -label {Rename...} \ -command branch_rename::dialog lappend disable_on_lock [list .mbar.branch entryconf \ @@ -1739,7 +1745,7 @@ switch -- $subcommand { browser { set subcommand_args {rev?} switch [llength $argv] { - 0 { set current_branch [current-branch] } + 0 { load_current_branch } 1 { set current_branch [lindex $argv 0] } default usage } @@ -1773,7 +1779,7 @@ blame { unset is_path if {$head eq {}} { - set current_branch [current-branch] + load_current_branch } else { set current_branch $head } @@ -2240,6 +2246,8 @@ bind $ui_diff {focus %W} if {[is_enabled branch]} { bind . <$M1B-Key-n> branch_create::dialog bind . <$M1B-Key-N> branch_create::dialog + bind . <$M1B-Key-o> branch_checkout::dialog + bind . <$M1B-Key-O> branch_checkout::dialog } if {[is_enabled transport]} { bind . <$M1B-Key-p> do_push_anywhere @@ -2326,9 +2334,7 @@ user.email settings into your personal # if {[is_enabled transport]} { load_all_remotes - load_all_heads - populate_branch_menu populate_fetch_menu populate_push_menu } diff --git a/lib/branch.tcl b/lib/branch.tcl index a6e6921..b948d92 100644 --- a/lib/branch.tcl +++ b/lib/branch.tcl @@ -2,7 +2,6 @@ # Copyright (C) 2006, 2007 Shawn Pearce proc load_all_heads {} { - global all_heads global some_heads_tracking set rh refs/heads @@ -16,7 +15,7 @@ proc load_all_heads {} { } close $fd - set all_heads [lsort $all_heads] + return [lsort $all_heads] } proc load_all_tags {} { @@ -30,173 +29,7 @@ proc load_all_tags {} { return $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 radio_selector {varname value args} { upvar #0 $varname var set var $value } - -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/lib/branch_checkout.tcl b/lib/branch_checkout.tcl new file mode 100644 index 0000000..62230ef --- /dev/null +++ b/lib/branch_checkout.tcl @@ -0,0 +1,88 @@ +# 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}] + 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 [cb _visible] + bind $w [list destroy $w] + bind $w [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/lib/branch_create.tcl b/lib/branch_create.tcl index f708957..def615d 100644 --- a/lib/branch_create.tcl +++ b/lib/branch_create.tcl @@ -73,7 +73,7 @@ constructor dialog {} { pack $w.options.merge.l -side left radiobutton $w.options.merge.no \ -text No \ - -value no \ + -value none \ -variable @opt_merge pack $w.options.merge.no -side left radiobutton $w.options.merge.ff \ @@ -113,7 +113,7 @@ constructor dialog {} { } method _create {} { - global repo_config current_branch + global repo_config global M1B set spec [$w_rev get_tracking_branch] @@ -155,17 +155,6 @@ method _create {} { return } - if {$newbranch eq $current_branch} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "'$newbranch' already exists and is the current branch." - focus $w_name - return - } - if {[catch {git check-ref-format "heads/$newbranch"}]} { tk_messageBox \ -icon error \ @@ -178,228 +167,28 @@ method _create {} { } if {$spec ne {} && $opt_fetch} { - set l_trck [lindex $spec 0] - set remote [lindex $spec 1] - set r_head [lindex $spec 2] - regsub ^refs/heads/ $r_head {} r_head - - set c $w.fetch_trck - toplevel $c - wm title $c "Refreshing Tracking Branch" - wm geometry $c "+[winfo rootx $w]+[winfo rooty $w]" - - set e [::console::embed \ - $c.console \ - "Fetching $r_head from $remote"] - pack $c.console -fill both -expand 1 - $e exec \ - [list git fetch $remote +$r_head:$l_trck] \ - [cb _finish_fetch $newbranch $c $e] - - bind $c [list grab $c] - bind $c <$M1B-Key-w> break - bind $c <$M1B-Key-W> break - wm protocol $c WM_DELETE_WINDOW [cb _noop] - } else { - _finish_create $this $newbranch - } -} - -method _noop {} {} - -method _finish_fetch {newbranch c e ok} { - wm protocol $c WM_DELETE_WINDOW {} - - if {$ok} { - destroy $c - _finish_create $this $newbranch - } else { - $e done $ok - button $c.close -text Close -command [list destroy $c] - pack $c.close -side bottom -anchor e -padx 10 -pady 10 - } -} - -method _finish_create {newbranch} { - global null_sha1 all_heads - - if {[catch {set new [$w_rev commit_or_die]}]} { + set new {} + } elseif {[catch {set new [$w_rev commit_or_die]}]} { return } - set ref refs/heads/$newbranch - if {[catch {set cur [git rev-parse --verify "$ref^0"]}]} { - # Assume it does not exist, and that is what the error was. - # - set reflog_msg "branch: Created from [$w_rev get]" - set cur $null_sha1 - } elseif {$opt_merge eq {no}} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Branch '$newbranch' already exists." - focus $w_name - return - } else { - set mrb {} - catch {set mrb [git merge-base $new $cur]} - switch -- $opt_merge { - 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 [$w_rev get]: Fast-forward" - } else { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Branch '$newbranch' already exists.\n\nIt cannot fast-forward to [$w_rev get].\nA merge is required." - focus $w_name - return - } - } - reset { - if {$mrb eq $cur} { - # The current branch is older. - # - set reflog_msg "merge [$w_rev get]: Fast-forward" - } else { - # The current branch will lose things. - # - if {[_confirm_reset $this $newbranch $cur $new]} { - set reflog_msg "reset [$w_rev get]" - } else { - return - } - } - } - default { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Branch '$newbranch' already exists." - focus $w_name - return - } - } - } - - if {$new ne $cur} { - if {[catch { - git update-ref -m $reflog_msg $ref $new $cur - } err]} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Failed to create '$newbranch'.\n\n$err" - return - } - } - - if {$cur eq $null_sha1} { - lappend all_heads $newbranch - set all_heads [lsort -uniq $all_heads] - populate_branch_menu - } - - destroy $w - if {$opt_checkout} { - switch_branch $newbranch + 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 } -} - -method _confirm_reset {newbranch cur new} { - set reset_ok 0 - set gitk [list do_gitk [list $cur ^$new]] - - set c $w.confirm_reset - toplevel $c - wm title $c "Confirm Branch Reset" - wm geometry $c "+[winfo rootx $w]+[winfo rooty $w]" - pack [label $c.msg1 \ - -anchor w \ - -justify left \ - -text "Resetting '$newbranch' to [$w_rev get] will lose the following commits:" \ - ] -anchor w - - set list $c.list.l - frame $c.list - text $list \ - -font font_diff \ - -width 80 \ - -height 10 \ - -wrap none \ - -xscrollcommand [list $c.list.sbx set] \ - -yscrollcommand [list $c.list.sby set] - scrollbar $c.list.sbx -orient h -command [list $list xview] - scrollbar $c.list.sby -orient v -command [list $list yview] - pack $c.list.sbx -fill x -side bottom - pack $c.list.sby -fill y -side right - pack $list -fill both -expand 1 - pack $c.list -fill both -expand 1 -padx 5 -pady 5 - - pack [label $c.msg2 \ - -anchor w \ - -justify left \ - -text "Recovering lost commits may not be easy." \ - ] - pack [label $c.msg3 \ - -anchor w \ - -justify left \ - -text "Reset '$newbranch'?" \ - ] - - frame $c.buttons - button $c.buttons.visualize \ - -text Visualize \ - -command $gitk - pack $c.buttons.visualize -side left - button $c.buttons.reset \ - -text Reset \ - -command " - set @reset_ok 1 - destroy $c - " - pack $c.buttons.reset -side right - button $c.buttons.cancel \ - -default active \ - -text Cancel \ - -command [list destroy $c] - pack $c.buttons.cancel -side right -padx 5 - pack $c.buttons -side bottom -fill x -pady 10 -padx 10 - - set fd [open "| git rev-list --pretty=oneline $cur ^$new" r] - 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" + if {[$co run]} { + destroy $w + } else { + focus $w_name } - close $fd - $list configure -state disabled - - bind $c $gitk - - bind $c " - grab $c - focus $c.buttons.cancel - " - bind $c [list destroy $c] - bind $c [list destroy $c] - tkwait window $c - return $reset_ok } method _validate {d S} { diff --git a/lib/branch_delete.tcl b/lib/branch_delete.tcl index 138e841..c7573c6 100644 --- a/lib/branch_delete.tcl +++ b/lib/branch_delete.tcl @@ -9,7 +9,7 @@ field w_check ; # revision picker for merge test field w_delete ; # delete button constructor dialog {} { - global all_heads current_branch + global current_branch make_toplevel top w wm title $top "[appname] ([reponame]): Delete Branch" @@ -54,7 +54,7 @@ constructor dialog {} { $w_check none {Always (Do not perform merge test.)} pack $w.check -anchor nw -fill x -pady 5 -padx 5 - foreach h $all_heads { + foreach h [load_all_heads] { if {$h ne $current_branch} { $w_heads insert end $h } @@ -79,8 +79,6 @@ method _select {} { } method _delete {} { - global all_heads - if {[catch {set check_cmt [$w_check commit_or_die]}]} { return } @@ -133,11 +131,6 @@ Delete the selected branches?} 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] - } } } @@ -150,8 +143,6 @@ Delete the selected branches?} -message "Failed to delete branches:\n$failed" } - set all_heads [lsort $all_heads] - populate_branch_menu destroy $w } diff --git a/lib/branch_rename.tcl b/lib/branch_rename.tcl index 4051016..1cadc31 100644 --- a/lib/branch_rename.tcl +++ b/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/lib/checkout_op.tcl b/lib/checkout_op.tcl new file mode 100644 index 0000000..844d8e5 --- /dev/null +++ b/lib/checkout_op.tcl @@ -0,0 +1,569 @@ +# 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 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 " + [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 cmd [list git update-index] + lappend cmd -q + lappend cmd --unmerged + lappend cmd --ignore-missing + lappend cmd --refresh + set fd [open "| $cmd" r] + 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 + + ui_status "Updating working directory to '[_name $this]'..." + set cmd [list git read-tree] + lappend cmd -m + lappend cmd -u + lappend cmd --exclude-per-directory=.gitignore + lappend cmd $HEAD + lappend cmd $new_hash + set fd [open "| $cmd" r] + fconfigure $fd -blocking 0 -translation binary + fileevent $fd readable [cb _readtree_wait $fd] +} + +method _readtree_wait {fd} { + global selected_commit_type commit_type HEAD MERGE_HEAD PARENT + global current_branch is_detached + global ui_comm + + # -- We never get interesting output on stdout; only stderr. + # + read $fd + fconfigure $fd -blocking 1 + if {![eof $fd]} { + fconfigure $fd -blocking 0 + return + } + + set name [_name $this] + + # -- 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} err]} { + regsub {^fatal: } $err {} err + warn_popup "File level merge required. + +$err + +Staying on branch '$current_branch'." + ui_status "Aborted checkout of '$name' (file level merging is required)." + unlock_index + delete_this + return + } + + 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 [open "| git rev-list --pretty=oneline $cur ^$new_hash" r] + 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 $gitk + bind $w " + grab $w + focus $w.buttons.cancel + " + bind $w [list destroy $w] + bind $w [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/lib/choose_rev.tcl b/lib/choose_rev.tcl index e6af073..7f2b4e6 100644 --- a/lib/choose_rev.tcl +++ b/lib/choose_rev.tcl @@ -18,7 +18,7 @@ field spec_trck ; # list of all tracking branch specs field spec_tag ; # list of all tag specs constructor new {path {title {}}} { - global all_heads current_branch + global current_branch is_detached set w $path @@ -29,6 +29,15 @@ constructor new {path {title {}}} { } bind $w [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 \ @@ -86,14 +95,18 @@ constructor new {path {title {}}} { grid $w.list -sticky nswe -padx {20 5} -columnspan 2 grid columnconfigure $w 1 -weight 1 - grid rowconfigure $w 2 -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 [list focus $w_list]\;break bind $w_filter [list focus $w_list] set spec_head [list] - foreach name $all_heads { + foreach name [load_all_heads] { lappend spec_head [list $name refs/heads/$name] } @@ -109,7 +122,8 @@ constructor new {path {title {}}} { lappend spec_tag [list $name refs/tags/$name] } - if {[llength $spec_head] > 0} { set revtype head + 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 @@ -153,6 +167,7 @@ method get {} { } } + HEAD { return HEAD } expr { return $c_expr } none { return {} } default { error "unknown type of revision" } @@ -163,6 +178,20 @@ method pick_tracking_branch {} { set revtype trck } +method focus_filter {} { + if {[$w_filter cget -state] eq {normal}} { + focus $w_filter + } +} + +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}} { @@ -222,6 +251,7 @@ method _expr {} { error "Revision expression is empty." } } + HEAD { return HEAD } none { return {} } default { error "unknown type of revision" } } @@ -249,9 +279,7 @@ method _filter {P} { method _select {args} { _rebuild $this $filter - if {[$w_filter cget -state] eq {normal}} { - focus $w_filter - } + focus_filter $this } method _rebuild {pat} { @@ -261,6 +289,7 @@ method _rebuild {pat} { trck { set new $spec_trck } tag { set new $spec_tag } expr - + HEAD - none { set new [list] set ste disabled diff --git a/lib/commit.tcl b/lib/commit.tcl index c5f608b..d0e4996 100644 --- a/lib/commit.tcl +++ b/lib/commit.tcl @@ -359,14 +359,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 diff --git a/lib/console.tcl b/lib/console.tcl index 297e8de..27a880e 100644 --- a/lib/console.tcl +++ b/lib/console.tcl @@ -173,6 +173,14 @@ 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]} { diff --git a/lib/transport.tcl b/lib/transport.tcl index e8ebc6e..3a22bd4 100644 --- a/lib/transport.tcl +++ b/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 -- cgit v0.10.2-6-g49f6 From 02087abccea681bc0e1f6a9607e97df79ebbf2d9 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 8 Jul 2007 21:19:59 -0400 Subject: git-gui: Unabbreviate commit SHA-1s prior to display If the end-user feeds us an abbreviated SHA-1 on the command line for `git gui browser` or `git gui blame` we now unabbreviate the value through `git rev-parse` so that the title section of the blame or browser window shows the user the complete SHA-1 as Git determined it to be. If the abbreviated value was ambiguous we now complain with the standard error message(s) as reported by git-rev-parse --verify, so that the user can understand what might be wrong and correct their command line. Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index ac04367..1844c90 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1746,7 +1746,18 @@ browser { set subcommand_args {rev?} switch [llength $argv] { 0 { load_current_branch } - 1 { set current_branch [lindex $argv 0] } + 1 { + set current_branch [lindex $argv 0] + if {[regexp {^[0-9a-f]{1,39}$} $current_branch]} { + if {[catch { + set current_branch \ + [git rev-parse --verify $current_branch] + } err]} { + puts stderr $err + exit 1 + } + } + } default usage } browser::new $current_branch @@ -1781,6 +1792,14 @@ blame { if {$head eq {}} { load_current_branch } else { + if {[regexp {^[0-9a-f]{1,39}$} $head]} { + if {[catch { + set head [git rev-parse --verify $head] + } err]} { + puts stderr $err + exit 1 + } + } set current_branch $head } -- cgit v0.10.2-6-g49f6 From 84d3d7b84c600d90486205bf801580009dc523a8 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 8 Jul 2007 21:29:54 -0400 Subject: git-gui: Default selection to first matching ref If we have specifications listed in our revision picker mega-widget then we should default the selection within that widget to the first ref available. This way the user does not need to use the spacebar to activate the selection of a ref within the box; instead they can navigate up/down with the arrow keys and be done with it. Signed-off-by: Shawn O. Pearce diff --git a/lib/choose_rev.tcl b/lib/choose_rev.tcl index 7f2b4e6..1aab56f 100644 --- a/lib/choose_rev.tcl +++ b/lib/choose_rev.tcl @@ -133,6 +133,7 @@ constructor new {path {title {}}} { 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 } @@ -312,6 +313,10 @@ method _rebuild {pat} { $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 -- cgit v0.10.2-6-g49f6 From 827c71199da9b762e0758fe96302d0c8b7a04bb9 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 8 Jul 2007 21:34:28 -0400 Subject: git-gui: Allow double-click in checkout dialog to start checkout If the user double clicks a branch in the checkout dialog then they probably want to start the checkout process on that branch. I found myself doing this without realizing it, and of course it did nothing as there was no action bound to the listbox's Double-Button-1 event handler. Since I did it without thinking, others will probably also try, and expect the same behavior. Signed-off-by: Shawn O. Pearce diff --git a/lib/branch_checkout.tcl b/lib/branch_checkout.tcl index 62230ef..72c45b4 100644 --- a/lib/branch_checkout.tcl +++ b/lib/branch_checkout.tcl @@ -30,6 +30,7 @@ constructor dialog {} { pack $w.buttons -side bottom -fill x -pady 10 -padx 10 set w_rev [::choose_rev::new $w.rev {Revision}] + $w_rev bind_listbox [cb _checkout] pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5 labelframe $w.options -text {Options} diff --git a/lib/choose_rev.tcl b/lib/choose_rev.tcl index 1aab56f..afd8170 100644 --- a/lib/choose_rev.tcl +++ b/lib/choose_rev.tcl @@ -185,6 +185,10 @@ method focus_filter {} { } } +method bind_listbox {event script} { + bind $w_list $event $script +} + method get_local_branch {} { if {$revtype eq {head}} { return [_expr $this] -- cgit v0.10.2-6-g49f6 From b29bd5ca3b0060513b7b96380fc43f7ab4f12156 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 8 Jul 2007 22:01:47 -0400 Subject: git-gui: Extract blame viewer status bar into mega-widget Our blame viewer has had a very fancy progress bar at the bottom of the window that shows the current status of the blame engine, which includes the number of lines completed as both a text and a graphical meter. I want to reuse this meter system in other places, such as during a branch switch where read-tree -v can give us a progress meter for any long-running operation. This change extracts the code and refactors it as a widget that we can take advantage of in locations other than in the blame viewer. Signed-off-by: Shawn O. Pearce diff --git a/lib/blame.tcl b/lib/blame.tcl index 76b5168..13bfab3 100644 --- a/lib/blame.tcl +++ b/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 @@ -242,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 \ @@ -360,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 { @@ -386,7 +366,7 @@ 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] @@ -510,13 +490,16 @@ method _exec_blame {cur_w cur_d options cur_s} { lappend cmd -- $path set fd [open "| $cmd" r] 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 @@ -697,26 +680,13 @@ method _read_blame {fd cur_w cur_d cur_s} { { 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 diff --git a/lib/status_bar.tcl b/lib/status_bar.tcl new file mode 100644 index 0000000..a6dea29 --- /dev/null +++ b/lib/status_bar.tcl @@ -0,0 +1,76 @@ +# 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 + +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 [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 +} + +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 stop {msg} { + destroy $w_c + set status $msg +} + +method show {msg} { + set status $msg +} + +method _delete {current} { + if {$current eq $w} { + delete_this + } +} + +} -- cgit v0.10.2-6-g49f6 From 51530d1722b0a4cccc630043fc4b46d075bf8558 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 8 Jul 2007 22:06:33 -0400 Subject: git-gui: Change the main window progress bar to use status_bar Now that we have a fancy status bar mega-widget we can reuse that within our main window. This opens the door for implementating future improvements like a progress bar. Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index 1844c90..b2c9a9c 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -438,7 +438,6 @@ set _reponame [lindex [file split \ set current_diff_path {} set current_diff_side {} set diff_actions [list] -set ui_status_value {Initializing...} set HEAD {} set PARENT {} @@ -761,13 +760,11 @@ proc mapdesc {state path} { } proc ui_status {msg} { - set ::ui_status_value $msg + $::main_status show $msg } proc ui_ready {{test {}}} { - if {$test eq {} || $::ui_status_value eq $test} { - ui_status Ready. - } + $::main_status show {Ready.} $test } proc escape_path {path} { @@ -2207,12 +2204,9 @@ unset ui_diff_applyhunk # -- Status Bar # -label .status -textvariable ui_status_value \ - -anchor w \ - -justify left \ - -borderwidth 1 \ - -relief sunken +set main_status [::status_bar::new .status] pack .status -anchor w -side bottom -fill x +$main_status show {Initializing...} # -- Load geometry # diff --git a/lib/status_bar.tcl b/lib/status_bar.tcl index a6dea29..0e2ac07 100644 --- a/lib/status_bar.tcl +++ b/lib/status_bar.tcl @@ -63,8 +63,10 @@ method stop {msg} { set status $msg } -method show {msg} { - set status $msg +method show {msg {test {}}} { + if {$test eq {} || $status eq $test} { + set status $msg + } } method _delete {current} { -- cgit v0.10.2-6-g49f6 From b79223064e163aa0ce7f5b63d12158e87f8729e4 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 8 Jul 2007 22:48:19 -0400 Subject: git-gui: Show a progress meter for checking out files Sometimes switching between branches can take more than a second or two, in which case `git checkout` would normally have shown a small progress meter to the user on the terminal to let them know that we are in fact working, and give them a reasonable idea of when we may finish. We now do obtain that progress meter from read-tree -v and include it in our main window's status bar. This allows users to see how many files we have checked out, how many remain, and what percentage of the operation is completed. It should help to keep users from getting bored during a large checkout operation. Signed-off-by: Shawn O. Pearce diff --git a/lib/checkout_op.tcl b/lib/checkout_op.tcl index 844d8e5..5d02daa 100644 --- a/lib/checkout_op.tcl +++ b/lib/checkout_op.tcl @@ -19,6 +19,7 @@ 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 @@ -309,51 +310,69 @@ method _name {} { method _readtree {} { global HEAD - ui_status "Updating working directory to '[_name $this]'..." + set readtree_d {} + $::main_status start \ + "Updating working directory to '[_name $this]'..." \ + {files checked out} + set cmd [list git read-tree] lappend cmd -m lappend cmd -u + lappend cmd -v lappend cmd --exclude-per-directory=.gitignore lappend cmd $HEAD lappend cmd $new_hash - set fd [open "| $cmd" r] + + if {[catch { + set fd [open "| $cmd 2>@1" r] + } err]} { + # Older versions of Tcl 8.4 don't have this 2>@1 IO + # redirect operator. Fallback to |& cat for those. + # + set fd [open "| $cmd |& cat" r] + } + fconfigure $fd -blocking 0 -translation binary fileevent $fd readable [cb _readtree_wait $fd] } method _readtree_wait {fd} { - global selected_commit_type commit_type HEAD MERGE_HEAD PARENT - global current_branch is_detached - global ui_comm + global current_branch + + set buf [read $fd] + $::main_status update_meter $buf + append readtree_d $buf - # -- We never get interesting output on stdout; only stderr. - # - read $fd fconfigure $fd -blocking 1 if {![eof $fd]} { fconfigure $fd -blocking 0 return } - set name [_name $this] - - # -- 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} err]} { + 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'." - ui_status "Aborted checkout of '$name' (file level merging is required)." 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" diff --git a/lib/status_bar.tcl b/lib/status_bar.tcl index 0e2ac07..72a8fe1 100644 --- a/lib/status_bar.tcl +++ b/lib/status_bar.tcl @@ -9,6 +9,7 @@ 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 @@ -45,6 +46,7 @@ method start {msg uds} { set status $msg set prefix $msg set units $uds + set meter {} } method update {have total} { @@ -58,9 +60,25 @@ method update {have total} { $w_c coords bar 0 0 $pdone 20 } -method stop {msg} { +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 - set status $msg + if {$msg ne {}} { + set status $msg + } } method show {msg {test {}}} { -- cgit v0.10.2-6-g49f6 From 0b81261622afad691501ee51d7811048cf4a5fce Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 9 Jul 2007 01:17:09 -0400 Subject: git-gui: Always use absolute path to all git executables Rather than making the C library search for git every time we want to execute it we now search for the main git wrapper at startup, do symlink resolution, and then always use the absolute path that we found to execute the binary later on. This should save us some cycles, especially on stat challenged systems like Cygwin/Win32. While I was working on this change I also converted all of our existing pipes ([open "| git ..."]) to use two new pipe wrapper functions. These functions take additional options like --nice and --stderr which instructs Tcl to take special action, like running the underlying git program through `nice` (if available) or redirect stderr to stdout for capture in Tcl. Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index b2c9a9c..09f49ce 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -117,6 +117,7 @@ set _gitdir {} set _gitexec {} set _reponame {} set _iscygwin {} +set _search_path {} proc appname {} { global _appname @@ -128,7 +129,7 @@ proc gitdir {args} { if {$args eq {}} { return $_gitdir } - return [eval [concat [list file join $_gitdir] $args]] + return [eval [list file join $_gitdir] $args] } proc gitexec {args} { @@ -137,11 +138,19 @@ proc gitexec {args} { if {[catch {set _gitexec [git --exec-path]} err]} { error "Git not installed?\n\n$err" } + if {[is_Cygwin]} { + set _gitexec [exec cygpath \ + --windows \ + --absolute \ + $_gitexec] + } else { + set _gitexec [file normalize $_gitexec] + } } if {$args eq {}} { return $_gitexec } - return [eval [concat [list file join $_gitexec] $args]] + return [eval [list file join $_gitexec] $args] } proc reponame {} { @@ -237,7 +246,7 @@ proc load_config {include_global} { array unset global_config if {$include_global} { catch { - set fd_rc [open "| git config --global --list" r] + set fd_rc [git_read config --global --list] while {[gets $fd_rc line] >= 0} { if {[regexp {^([^=]+)=(.*)$} $line line name value]} { if {[is_many_config $name]} { @@ -253,7 +262,7 @@ proc load_config {include_global} { array unset repo_config catch { - set fd_rc [open "| git config --list" r] + set fd_rc [git_read config --list] while {[gets $fd_rc line] >= 0} { if {[regexp {^([^=]+)=(.*)$} $line line name value]} { if {[is_many_config $name]} { @@ -280,8 +289,172 @@ proc load_config {include_global} { ## ## handy utils +proc _git_cmd {name} { + global _git_cmd_path + + if {[catch {set v $_git_cmd_path($name)}]} { + switch -- $name { + --version - + --exec-path { return [list $::_git $name] } + } + + set p [gitexec git-$name$::_search_exe] + if {[file exists $p]} { + set v [list $p] + } elseif {[is_Cygwin]} { + # On Cygwin git is a proper Cygwin program and knows + # how to properly restart the Cygwin environment and + # spawn its non-.exe support program. + # + set v [list $::_git $name] + } elseif {[is_Windows] + && $::_sh ne {} + && [file exists [gitexec git-$name]]} { + # Assume this is a UNIX shell script. We can + # probably execute it through a Bourne shell. + # + set v [list $::_sh [gitexec git-$name]] + } else { + error "No [gitexec git-$name]" + } + set _git_cmd_path($name) $v + } + return $v +} + +proc _which {what} { + global env _search_exe _search_path + + if {$_search_path eq {}} { + if {[is_Cygwin]} { + set _search_path [split [exec cygpath \ + --windows \ + --path \ + --absolute \ + $env(PATH)] {;}] + set _search_exe .exe + } elseif {[is_Windows]} { + set _search_path [split $env(PATH) {;}] + set _search_exe .exe + } else { + set _search_path [split $env(PATH) :] + set _search_exe {} + } + } + + foreach p $_search_path { + set p [file join $p $what$_search_exe] + if {[file exists $p]} { + return [file normalize $p] + } + } + return {} +} + proc git {args} { - return [eval exec git $args] + set opt [list exec] + + while {1} { + switch -- [lindex $args 0] { + --nice { + global _nice + if {$_nice ne {}} { + lappend opt $_nice + } + } + + default { + break + } + + } + + set args [lrange $args 1 end] + } + + set cmdp [_git_cmd [lindex $args 0]] + set args [lrange $args 1 end] + + return [eval $opt $cmdp $args] +} + +proc git_read {args} { + set opt [list |] + + while {1} { + switch -- [lindex $args 0] { + --nice { + global _nice + if {$_nice ne {}} { + lappend opt $_nice + } + } + + --stderr { + lappend args 2>@1 + } + + default { + break + } + + } + + set args [lrange $args 1 end] + } + + set cmdp [_git_cmd [lindex $args 0]] + set args [lrange $args 1 end] + + if {[catch { + set fd [open [concat $opt $cmdp $args] r] + } err]} { + if { [lindex $args end] eq {2>@1} + && $err eq {can not find channel named "1"} + } { + # Older versions of Tcl 8.4 don't have this 2>@1 IO + # redirect operator. Fallback to |& cat for those. + # The command was not actually started, so its safe + # to try to start it a second time. + # + set fd [open [concat \ + $opt \ + $cmdp \ + [lrange $args 0 end-1] \ + [list |& cat] \ + ] r] + } else { + error $err + } + } + return $fd +} + +proc git_write {args} { + set opt [list |] + + while {1} { + switch -- [lindex $args 0] { + --nice { + global _nice + if {$_nice ne {}} { + lappend opt $_nice + } + } + + default { + break + } + + } + + set args [lrange $args 1 end] + } + + set cmdp [_git_cmd [lindex $args 0]] + set args [lrange $args 1 end] + + return [open [concat $opt $cmdp $args] w] } proc load_current_branch {} { @@ -320,6 +493,19 @@ proc tk_optionMenu {w varName args} { ###################################################################### ## +## find git + +set _git [_which git] +if {$_git eq {}} { + catch {wm withdraw .} + error_popup "Cannot find git in PATH." + exit 1 +} +set _nice [_which nice] +set _sh [_which sh] + +###################################################################### +## ## version check if {[catch {set _git_version [git --version]} err]} { @@ -566,12 +752,12 @@ proc rescan {after {honor_trustmtime 1}} { } else { set rescan_active 1 ui_status {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] + set fd_rf [git_read update-index \ + -q \ + --unmerged \ + --ignore-missing \ + --refresh \ + ] fconfigure $fd_rf -blocking 0 -translation binary fileevent $fd_rf readable \ [list rescan_stage2 $fd_rf $after] @@ -587,8 +773,7 @@ proc rescan_stage2 {fd after} { close $fd } - set ls_others [list | git ls-files --others -z \ - --exclude-per-directory=.gitignore] + set ls_others [list --exclude-per-directory=.gitignore] set info_exclude [gitdir info exclude] if {[file readable $info_exclude]} { lappend ls_others "--exclude-from=$info_exclude" @@ -600,9 +785,9 @@ proc rescan_stage2 {fd after} { set rescan_active 3 ui_status {Scanning for modified files ...} - set fd_di [open "| git diff-index --cached -z [PARENT]" r] - set fd_df [open "| git diff-files -z" r] - set fd_lo [open $ls_others r] + set fd_di [git_read diff-index --cached -z [PARENT]] + set fd_df [git_read diff-files -z] + set fd_lo [eval git_read ls-files --others -z $ls_others] fconfigure $fd_di -blocking 0 -translation binary -encoding binary fconfigure $fd_df -blocking 0 -translation binary -encoding binary diff --git a/lib/blame.tcl b/lib/blame.tcl index 13bfab3..4bdb9a2 100644 --- a/lib/blame.tcl +++ b/lib/blame.tcl @@ -371,8 +371,7 @@ method _load {jump} { 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] @@ -475,20 +474,14 @@ 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] set current_fd $fd @@ -767,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/lib/branch.tcl b/lib/branch.tcl index b948d92..777eeb7 100644 --- a/lib/branch.tcl +++ b/lib/branch.tcl @@ -7,7 +7,7 @@ proc load_all_heads {} { 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) $rh" r] + set fd [git_read for-each-ref --format=%(refname) $rh] while {[gets $fd line] > 0} { if {!$some_heads_tracking || ![is_tracking_branch $line]} { lappend all_heads [string range $line $rh_len end] @@ -20,7 +20,10 @@ proc load_all_heads {} { proc load_all_tags {} { set all_tags [list] - set fd [open "| git for-each-ref --sort=-taggerdate --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 diff --git a/lib/browser.tcl b/lib/browser.tcl index 3d6341b..4d33052 100644 --- a/lib/browser.tcl +++ b/lib/browser.tcl @@ -178,8 +178,7 @@ 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] } diff --git a/lib/checkout_op.tcl b/lib/checkout_op.tcl index 5d02daa..00a994b 100644 --- a/lib/checkout_op.tcl +++ b/lib/checkout_op.tcl @@ -274,12 +274,12 @@ The rescan will be automatically started now. _readtree $this } else { ui_status {Refreshing file status...} - set cmd [list git update-index] - lappend cmd -q - lappend cmd --unmerged - lappend cmd --ignore-missing - lappend cmd --refresh - set fd [open "| $cmd" r] + set fd [git_read update-index \ + -q \ + --unmerged \ + --ignore-missing \ + --refresh \ + ] fconfigure $fd -blocking 0 -translation binary fileevent $fd readable [cb _refresh_wait $fd] } @@ -315,23 +315,14 @@ method _readtree {} { "Updating working directory to '[_name $this]'..." \ {files checked out} - set cmd [list git read-tree] - lappend cmd -m - lappend cmd -u - lappend cmd -v - lappend cmd --exclude-per-directory=.gitignore - lappend cmd $HEAD - lappend cmd $new_hash - - if {[catch { - set fd [open "| $cmd 2>@1" r] - } err]} { - # Older versions of Tcl 8.4 don't have this 2>@1 IO - # redirect operator. Fallback to |& cat for those. - # - set fd [open "| $cmd |& cat" r] - } - + 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] } @@ -524,7 +515,7 @@ method _confirm_reset {cur} { pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - set fd [open "| git rev-list --pretty=oneline $cur ^$new_hash" r] + 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] diff --git a/lib/commit.tcl b/lib/commit.tcl index d0e4996..dc7c88c 100644 --- a/lib/commit.tcl +++ b/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 @@ -235,7 +235,7 @@ proc commit_prehook_wait {fd_ph curHEAD msg} { proc commit_writetree {curHEAD msg} { ui_status {Committing changes...} - set fd_wt [open "| git write-tree" r] + set fd_wt [git_read write-tree] fileevent $fd_wt readable \ [list commit_committree $fd_wt $curHEAD $msg] } diff --git a/lib/database.tcl b/lib/database.tcl index 43e4a28..87c815d 100644 --- a/lib/database.tcl +++ b/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/lib/diff.tcl b/lib/diff.tcl index 05bf751..9cb9d06 100644 --- a/lib/diff.tcl +++ b/lib/diff.tcl @@ -131,7 +131,7 @@ proc show_diff {path w {lno {}}} { return } - set cmd [list | git] + set cmd [list] if {$w eq $ui_index} { lappend cmd diff-index lappend cmd --cached @@ -154,7 +154,7 @@ 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 ui_status "Unable to display [escape_path $path]" @@ -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/lib/index.tcl b/lib/index.tcl index 7c175a9..3ea72e1 100644 --- a/lib/index.tcl +++ b/lib/index.tcl @@ -17,7 +17,7 @@ proc update_indexinfo {msg pathList after} { $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 \ @@ -90,7 +90,7 @@ proc update_index {msg pathList after} { $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 \ @@ -167,13 +167,13 @@ proc checkout_index {msg pathList after} { $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 \ diff --git a/lib/merge.tcl b/lib/merge.tcl index f0a02ea..288d7ac 100644 --- a/lib/merge.tcl +++ b/lib/merge.tcl @@ -146,7 +146,7 @@ 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]] @@ -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 { @@ -282,7 +284,7 @@ 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]] ui_status {Aborting... please wait...} diff --git a/lib/option.tcl b/lib/option.tcl index 7433042..aa9f783 100644 --- a/lib/option.tcl +++ b/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" diff --git a/lib/remote.tcl b/lib/remote.tcl index fabec05..e235ca8 100644 --- a/lib/remote.tcl +++ b/lib/remote.tcl @@ -32,7 +32,7 @@ proc all_tracking_branches {} { } if {$pat ne {}} { - set fd [open "| git for-each-ref --format=%(refname) $cmd" r] + 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] diff --git a/lib/remote_branch_delete.tcl b/lib/remote_branch_delete.tcl index d7e2b8d..c88a360 100644 --- a/lib/remote_branch_delete.tcl +++ b/lib/remote_branch_delete.tcl @@ -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 \ -- cgit v0.10.2-6-g49f6 From 02efd48f520d14012fc82c999172306a94862dee Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 9 Jul 2007 02:10:39 -0400 Subject: git-gui: Correct gitk installation location The master Makefile in git.git installs gitk into bindir, not gitexecdir, which means gitk is located as a sibling of the git wrapper and not as though it were a git helper tool. We can also avoid some Tcl concat operations by letting eval do all of the heavy lifting; we have two proper Tcl lists ($cmd and $revs) that we are joining together and $revs is currently never an empty list. Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index 09f49ce..53cf898 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1303,24 +1303,16 @@ proc incr_font_size {font {amt 1}} { set starting_gitk_msg {Starting gitk... please wait...} proc do_gitk {revs} { - global env starting_gitk_msg - # -- Always start gitk through whatever we were loaded with. This # lets us bypass using shell process on Windows systems. # - set cmd [list [info nameofexecutable]] - set exe [gitexec gitk] - lappend cmd $exe - if {$revs ne {}} { - append cmd { } - append cmd $revs - } - + set exe [file join [file dirname $::_git] gitk] + set cmd [list [info nameofexecutable] $exe] if {! [file exists $exe]} { error_popup "Unable to start gitk:\n\n$exe does not exist" } else { - eval exec $cmd & - ui_status $starting_gitk_msg + eval exec $cmd $revs & + ui_status $::starting_gitk_msg after 10000 { ui_ready $starting_gitk_msg } -- cgit v0.10.2-6-g49f6 From c67298902ca36579b4cc43c1868cdb41279ef21b Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 9 Jul 2007 02:13:00 -0400 Subject: git-gui: Assume unfound commands are known by git wrapper If we cannot locate a command in $gitexecdir on our own then it may just be because we are supposed to run it by `git $name` rather than by `git-$name`. Many commands are now builtins, more are likely to go in that direction, and we may see the hardlinks in $gitexecdir go away in future versions of git. Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index 53cf898..9b342f0 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -315,7 +315,10 @@ proc _git_cmd {name} { # set v [list $::_sh [gitexec git-$name]] } else { - error "No [gitexec git-$name]" + # Assume it is builtin to git somehow and we + # aren't actually able to see a file for it. + # + set v [list $::_git $name] } set _git_cmd_path($name) $v } -- cgit v0.10.2-6-g49f6 From 70a7595cc07f38d4a83dff1d4697eb49c2e65b2c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 9 Jul 2007 02:30:24 -0400 Subject: git-gui: Treat `git version` as `git --version` We know that the version subcommand of git is special. It does not currently have an executable link installed into $gitexecdir and we therefore would never match it with one of our file exists tests. So we forward any invocations to it directly to the git wrapper, as it is a builtin within that executable. Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index 9b342f0..a3ac5da 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -294,6 +294,7 @@ proc _git_cmd {name} { if {[catch {set v $_git_cmd_path($name)}]} { switch -- $name { + version - --version - --exec-path { return [list $::_git $name] } } -- cgit v0.10.2-6-g49f6 From c136f2b8b9eaac7a702799de25648b74ef12618a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 9 Jul 2007 02:47:33 -0400 Subject: git-gui: Perform our own magic shbang detection on Windows If we cannot locate a .exe for a git tool that we want to run than it may just be a Bourne shell script as these are popular in Git. In such a case the first line of the file will say "#!/bin/sh" so a UNIX kernel knows what program to start to parse and run that. But Windows doesn't support shbang lines, and neither does the Tcl that comes with Cygwin. We can pass control off to the git wrapper as that is a real Cygwin program and can therefore start the Bourne shell script, but that is at least two fork+exec calls to get the program running. One to do the fork+exec of the git wrapper and another to start the Bourne shell script. If the program is run multiple times it is rather expensive as the magic shbang detection won't be cached across executions. On MinGW/MSYS we don't have the luxury of such magic detection. The MSYS team has taught some of this magic to the git wrapper, but again its slower than it needs to be as the git wrapper must still go and run the Bourne shell after it is called. We now attempt to guess the shbang line on Windows by reading the first line of the file and building our own command line path from it. Currently we support Bourne shell (sh), Perl and Python. That is the entire set of shbang lines that appear in git.git today. Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index a3ac5da..7e6952c 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -302,19 +302,31 @@ proc _git_cmd {name} { set p [gitexec git-$name$::_search_exe] if {[file exists $p]} { set v [list $p] - } elseif {[is_Cygwin]} { - # On Cygwin git is a proper Cygwin program and knows - # how to properly restart the Cygwin environment and - # spawn its non-.exe support program. + } elseif {[is_Windows] && [file exists [gitexec git-$name]]} { + # Try to determine what sort of magic will make + # git-$name go and do its thing, because native + # Tcl on Windows doesn't know it. # - set v [list $::_git $name] - } elseif {[is_Windows] - && $::_sh ne {} - && [file exists [gitexec git-$name]]} { - # Assume this is a UNIX shell script. We can - # probably execute it through a Bourne shell. - # - set v [list $::_sh [gitexec git-$name]] + set p [gitexec git-$name] + set f [open $p r] + set s [gets $f] + close $f + + switch -glob -- $s { + #!*sh { set i sh } + #!*perl { set i perl } + #!*python { set i python } + default { error "git-$name is not supported: $s" } + } + + upvar #0 _$i interp + if {![info exists interp]} { + set interp [_which $i] + } + if {$interp eq {}} { + error "git-$name requires $i (not in PATH)" + } + set v [list $interp $p] } else { # Assume it is builtin to git somehow and we # aren't actually able to see a file for it. @@ -506,7 +518,6 @@ if {$_git eq {}} { exit 1 } set _nice [_which nice] -set _sh [_which sh] ###################################################################### ## -- cgit v0.10.2-6-g49f6 From 74c4763c76a111809747652210962ad09896b74f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 9 Jul 2007 03:07:05 -0400 Subject: git-gui: Teach console widget to use git_read Now that we are pretty strict about setting up own absolute paths to any git helper (saving a marginal runtime cost to resolve the tool) we can do the same in our console widget by making sure all console execs go through git_read if they are a git subcommand, and if not make sure they at least try to use the Tcl 2>@1 IO redirection if possible, as it should be faster than |& cat. Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index 7e6952c..3efecdd 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -394,6 +394,29 @@ proc git {args} { return [eval $opt $cmdp $args] } +proc _open_stdout_stderr {cmd} { + if {[catch { + set fd [open $cmd r] + } err]} { + if { [lindex $cmd end] eq {2>@1} + && $err eq {can not find channel named "1"} + } { + # Older versions of Tcl 8.4 don't have this 2>@1 IO + # redirect operator. Fallback to |& cat for those. + # The command was not actually started, so its safe + # to try to start it a second time. + # + set fd [open [concat \ + [lrange $cmd 0 end-1] \ + [list |& cat] \ + ] r] + } else { + error $err + } + } + return $fd +} + proc git_read {args} { set opt [list |] @@ -422,28 +445,7 @@ proc git_read {args} { set cmdp [_git_cmd [lindex $args 0]] set args [lrange $args 1 end] - if {[catch { - set fd [open [concat $opt $cmdp $args] r] - } err]} { - if { [lindex $args end] eq {2>@1} - && $err eq {can not find channel named "1"} - } { - # Older versions of Tcl 8.4 don't have this 2>@1 IO - # redirect operator. Fallback to |& cat for those. - # The command was not actually started, so its safe - # to try to start it a second time. - # - set fd [open [concat \ - $opt \ - $cmdp \ - [lrange $args 0 end-1] \ - [list |& cat] \ - ] r] - } else { - error $err - } - } - return $fd + return [_open_stdout_stderr [concat $opt $cmdp $args]] } proc git_write {args} { diff --git a/lib/console.tcl b/lib/console.tcl index 27a880e..03d0354 100644 --- a/lib/console.tcl +++ b/lib/console.tcl @@ -87,19 +87,12 @@ method _init {} { } 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] } -- cgit v0.10.2-6-g49f6 From 7eafa2f1578e605e1e68d8ccba9d600cc6b89173 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 9 Jul 2007 03:28:41 -0400 Subject: git-gui: Improve the Windows and Mac OS X shortcut creators We now embed any GIT_* and SSH_* environment variables as well as the path to the git wrapper executable into the Mac OS X .app file. This should allow us to restore the environment properly when we restart. We also try to use proper Bourne shell single quoting when we can, as this avoids any sort of problems that might occur due to a path containing shell metacharacters. Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index 3efecdd..2077261 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -475,6 +475,11 @@ proc git_write {args} { return [open [concat $opt $cmdp $args] w] } +proc sq {value} { + regsub -all ' $value "'\\''" value + return "'$value'" +} + proc load_current_branch {} { global current_branch is_detached diff --git a/lib/shortcut.tcl b/lib/shortcut.tcl index a0a1b7d..64ced9d 100644 --- a/lib/shortcut.tcl +++ b/lib/shortcut.tcl @@ -13,10 +13,11 @@ proc do_windows_shortcut {} { 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]\"" @@ -62,17 +63,11 @@ 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 -nonewline $fd "GIT_DIR=[sq [$gd]]" + puts -nonewline $fd " [sq $me]" puts $fd "&\"" close $fd } err]} { @@ -90,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] @@ -123,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 -- cgit v0.10.2-6-g49f6 From 5922446794ee1bfcd4bede739467211cd46aa005 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 9 Jul 2007 11:01:02 -0400 Subject: git-gui: Paper bag fix for Cygwin shortcut creation We cannot execute the git directory, it is not a valid Tcl command name. Instead we just want to pass it as an argument to our sq proc. Signed-off-by: Shawn O. Pearce diff --git a/lib/shortcut.tcl b/lib/shortcut.tcl index 64ced9d..7086162 100644 --- a/lib/shortcut.tcl +++ b/lib/shortcut.tcl @@ -66,7 +66,7 @@ proc do_cygwin_shortcut {} { puts $fd "@ECHO Entering [reponame]" puts $fd "@ECHO Starting git-gui... please wait..." puts -nonewline $fd "@\"$sh\" --login -c \"" - puts -nonewline $fd "GIT_DIR=[sq [$gd]]" + puts -nonewline $fd "GIT_DIR=[sq $gd]" puts -nonewline $fd " [sq $me]" puts $fd "&\"" close $fd -- cgit v0.10.2-6-g49f6 From 6a5955fac3bd5a1369e73512b9606769f0c95e7c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 9 Jul 2007 11:09:27 -0400 Subject: git-gui: Use sh.exe in Cygwin shortcuts Because we are trying to execute /bin/sh we know it must be a real Windows executable and thus ends with the standard .exe suffix. Signed-off-by: Shawn O. Pearce diff --git a/lib/shortcut.tcl b/lib/shortcut.tcl index 7086162..26adb99 100644 --- a/lib/shortcut.tcl +++ b/lib/shortcut.tcl @@ -54,7 +54,7 @@ proc do_cygwin_shortcut {} { set sh [exec cygpath \ --windows \ --absolute \ - /bin/sh] + /bin/sh.exe] set me [exec cygpath \ --unix \ --absolute \ -- cgit v0.10.2-6-g49f6 From 264f4a32fa898a47e37ccb6f2580746d6f36453f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 9 Jul 2007 11:10:26 -0400 Subject: git-gui: Include a space in Cygwin shortcut command lines Signed-off-by: Shawn O. Pearce diff --git a/lib/shortcut.tcl b/lib/shortcut.tcl index 26adb99..c36be2f 100644 --- a/lib/shortcut.tcl +++ b/lib/shortcut.tcl @@ -68,7 +68,7 @@ proc do_cygwin_shortcut {} { puts -nonewline $fd "@\"$sh\" --login -c \"" puts -nonewline $fd "GIT_DIR=[sq $gd]" puts -nonewline $fd " [sq $me]" - puts $fd "&\"" + puts $fd " &\"" close $fd } err]} { error_popup "Cannot write script:\n\n$err" -- cgit v0.10.2-6-g49f6 From b215883de9322b8b475a04b4768d6ba5455373d1 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 12 Jul 2007 02:45:23 -0400 Subject: git-gui: Change prior tree SHA-1 verification to use git_read This cat-file was done on maint, where we did not have git_read available to us. But here on master we do, so we should make use of it. Signed-off-by: Shawn O. Pearce diff --git a/lib/commit.tcl b/lib/commit.tcl index 3172d7c..46a78c1 100644 --- a/lib/commit.tcl +++ b/lib/commit.tcl @@ -258,7 +258,7 @@ proc commit_committree {fd_wt curHEAD msg} { # -- Verify this wasn't an empty change. # if {$commit_type eq {normal}} { - set fd_ot [open "| git cat-file commit $PARENT" r] + 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 -- cgit v0.10.2-6-g49f6