diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/branch.tcl | 169 | ||||
-rw-r--r-- | lib/branch_checkout.tcl | 88 | ||||
-rw-r--r-- | lib/branch_create.tcl | 247 | ||||
-rw-r--r-- | lib/branch_delete.tcl | 13 | ||||
-rw-r--r-- | lib/branch_rename.tcl | 14 | ||||
-rw-r--r-- | lib/checkout_op.tcl | 569 | ||||
-rw-r--r-- | lib/choose_rev.tcl | 43 | ||||
-rw-r--r-- | lib/commit.tcl | 8 | ||||
-rw-r--r-- | lib/console.tcl | 8 | ||||
-rw-r--r-- | lib/transport.tcl | 4 |
10 files changed, 727 insertions, 436 deletions
diff --git a/lib/branch.tcl b/lib/branch.tcl index a6e6921174..b948d926ae 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 0000000000..62230efe43 --- /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 <Visibility> [cb _visible] + bind $w <Key-Escape> [list destroy $w] + bind $w <Key-Return> [cb _checkout]\;break + tkwait window $w +} + +method _checkout {} { + set spec [$w_rev get_tracking_branch] + if {$spec ne {} && $opt_fetch} { + set new {} + } elseif {[catch {set new [$w_rev commit_or_die]}]} { + return + } + + if {$opt_detach} { + set ref {} + } else { + set ref [$w_rev get_local_branch] + } + + set co [::checkout_op::new [$w_rev get] $new $ref] + $co parent $w + $co enable_checkout 1 + if {$spec ne {} && $opt_fetch} { + $co enable_fetch $spec + } + + if {[$co run]} { + destroy $w + } else { + $w_rev focus_filter + } +} + +method _visible {} { + grab $w + $w_rev focus_filter +} + +} diff --git a/lib/branch_create.tcl b/lib/branch_create.tcl index f708957b22..def615d19d 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 <Visibility> [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 <Key-v> $gitk - - bind $c <Visibility> " - grab $c - focus $c.buttons.cancel - " - bind $c <Key-Return> [list destroy $c] - bind $c <Key-Escape> [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 138e84192c..c7573c6c72 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 405101637f..1cadc31d20 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 0000000000..844d8e551c --- /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 <Visibility> " + [list grab $w] + [list focus $w] + " + wm protocol $w WM_DELETE_WINDOW [cb _noop] + tkwait window $w + + if {!$fetch_ok} { + delete_this + return 0 + } + } + + if {$new_ref ne {}} { + # If we have a ref we need to update it before we can + # proceed with a checkout (if one was enabled). + # + if {![_update_ref $this]} { + delete_this + return 0 + } + } + + if {$checkout} { + _checkout $this + return 1 + } + + delete_this + return 1 +} + +method _noop {} {} + +method _finish_fetch {ok} { + if {$ok} { + set l_trck [lindex $fetch_spec 0] + if {[catch {set new_hash [git rev-parse --verify "$l_trck^0"]} err]} { + set ok 0 + $w_cons insert "fatal: Cannot resolve $l_trck" + $w_cons insert $err + } + } + + $w_cons done $ok + set w_cons {} + wm protocol $w WM_DELETE_WINDOW {} + + if {$ok} { + destroy $w + set w {} + } else { + button $w.close -text Close -command [list destroy $w] + pack $w.close -side bottom -anchor e -padx 10 -pady 10 + } + + set fetch_ok $ok +} + +method _update_ref {} { + global null_sha1 current_branch + + set ref $new_ref + set new $new_hash + + set is_current 0 + set rh refs/heads/ + set rn [string length $rh] + if {[string equal -length $rn $rh $ref]} { + set newbranch [string range $ref $rn end] + if {$current_branch eq $newbranch} { + set is_current 1 + } + } else { + set newbranch $ref + } + + if {[catch {set cur [git rev-parse --verify "$ref^0"]}]} { + # Assume it does not exist, and that is what the error was. + # + if {!$create} { + _error $this "Branch '$newbranch' does not exist." + return 0 + } + + set reflog_msg "branch: Created from $new_expr" + set cur $null_sha1 + } elseif {$create && $merge_type eq {none}} { + # We were told to create it, but not do a merge. + # Bad. Name shouldn't have existed. + # + _error $this "Branch '$newbranch' already exists." + return 0 + } elseif {!$create && $merge_type eq {none}} { + # We aren't creating, it exists and we don't merge. + # We are probably just a simple branch switch. + # Use whatever value we just read. + # + set new $cur + set new_hash $cur + } elseif {$new eq $cur} { + # No merge would be required, don't compute anything. + # + } else { + set mrb {} + catch {set mrb [git merge-base $new $cur]} + switch -- $merge_type { + ff { + if {$mrb eq $new} { + # The current branch is actually newer. + # + set new $cur + } elseif {$mrb eq $cur} { + # The current branch is older. + # + set reflog_msg "merge $new_expr: Fast-forward" + } else { + _error $this "Branch '$newbranch' already exists.\n\nIt cannot fast-forward to $new_expr.\nA merge is required." + return 0 + } + } + reset { + if {$mrb eq $cur} { + # The current branch is older. + # + set reflog_msg "merge $new_expr: Fast-forward" + } else { + # The current branch will lose things. + # + if {[_confirm_reset $this $cur]} { + set reflog_msg "reset $new_expr" + } else { + return 0 + } + } + } + default { + _error $this "Only 'ff' and 'reset' merge is currently supported." + return 0 + } + } + } + + if {$new ne $cur} { + if {$is_current} { + # No so fast. We should defer this in case + # we cannot update the working directory. + # + set update_old $cur + return 1 + } + + if {[catch { + git update-ref -m $reflog_msg $ref $new $cur + } err]} { + _error $this "Failed to update '$newbranch'.\n\n$err" + return 0 + } + } + + return 1 +} + +method _checkout {} { + if {[lock_index checkout_op]} { + after idle [cb _start_checkout] + } else { + _error $this "Index is already locked." + delete_this + } +} + +method _start_checkout {} { + global HEAD commit_type + + # -- Our in memory state should match the repository. + # + repository_state curType curHEAD curMERGE_HEAD + if {[string match amend* $commit_type] + && $curType eq {normal} + && $curHEAD eq $HEAD} { + } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} { + info_popup {Last scanned state does not match repository state. + +Another Git program has modified this repository since the last scan. A rescan must be performed before the current branch can be changed. + +The rescan will be automatically started now. +} + unlock_index + rescan ui_ready + delete_this + return + } + + if {[is_config_true gui.trustmtime]} { + _readtree $this + } else { + ui_status {Refreshing file status...} + set 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 <Key-v> $gitk + bind $w <Visibility> " + grab $w + focus $w.buttons.cancel + " + bind $w <Key-Return> [list destroy $w] + bind $w <Key-Escape> [list destroy $w] + tkwait window $w + return $reset_ok +} + +method _error {msg} { + if {[winfo ismapped $parent_w]} { + set p $parent_w + } else { + set p . + } + + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $p] \ + -parent $p \ + -message $msg +} + +method _toplevel {title} { + regsub -all {::} $this {__} w + set w .$w + + if {[winfo ismapped $parent_w]} { + set p $parent_w + } else { + set p . + } + + toplevel $w + wm title $w $title + wm geometry $w "+[winfo rootx $p]+[winfo rooty $p]" +} + +method _fatal {err} { + error_popup "Failed to set current branch. + +This working directory is only partially switched. We successfully updated your files, but failed to update an internal Git file. + +This should not have occurred. [appname] will now close and give up. + +$err" + exit 1 +} + +} diff --git a/lib/choose_rev.tcl b/lib/choose_rev.tcl index e6af073595..7f2b4e603d 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 <Destroy> [cb _delete %W] + if {$is_detached} { + radiobutton $w.detachedhead_r \ + -anchor w \ + -text {This Detached Checkout} \ + -value HEAD \ + -variable @revtype + grid $w.detachedhead_r -sticky we -padx {0 5} -columnspan 2 + } + radiobutton $w.expr_r \ -text {Revision Expression:} \ -value expr \ @@ -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 <Key-Return> [list focus $w_list]\;break bind $w_filter <Key-Down> [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 c5f608beb0..d0e4996bae 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 297e8defa4..27a880e408 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 e8ebc6eda0..3a22bd40d4 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 |