eval $cmd
}
-proc info_popup {msg} {
+proc info_popup {msg {parent .}} {
set title [appname]
if {[reponame] ne {}} {
append title " ([reponame])"
}
tk_messageBox \
- -parent . \
+ -parent $parent \
-icon info \
-type ok \
-title $title \
global HEAD commit_type file_states ui_comm repo_config
global ui_status_value pch_error
- if {![lock_index update]} return
if {[committer_ident] eq {}} return
+ if {![lock_index update]} return
# -- Our in memory state should match the repository.
#
"Fetching new changes from $remote"]
set cmd [list git fetch]
lappend cmd $remote
- console_exec $w $cmd
+ console_exec $w $cmd console_done
}
proc push_to {remote} {
"push $remote" \
"Pushing changes to $remote"]
set cmd [list git push]
+ lappend cmd -v
lappend cmd $remote
- console_exec $w $cmd
+ console_exec $w $cmd console_done
}
######################################################################
}
set cons [new_console "push $r_url" "Pushing $cnt $unit to $r_url"]
- console_exec $cons $cmd
+ console_exec $cons $cmd console_done
destroy $w
}
tkwait window $w
}
+######################################################################
+##
+## merge
+
+proc can_merge {} {
+ global HEAD commit_type file_states
+
+ if {[string match amend* $commit_type]} {
+ info_popup {Cannot merge while amending.
+
+You must finish amending this commit before
+starting any type of merge.
+}
+ return 0
+ }
+
+ if {[committer_ident] eq {}} {return 0}
+ if {![lock_index merge]} {return 0}
+
+ # -- Our in memory state should match the repository.
+ #
+ repository_state curType curHEAD curMERGE_HEAD
+ if {$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 a merge can be performed.
+
+The rescan will be automatically started now.
+}
+ unlock_index
+ rescan {set ui_status_value {Ready.}}
+ return 0
+ }
+
+ foreach path [array names file_states] {
+ switch -glob -- [lindex $file_states($path) 0] {
+ U? {
+ error_popup "You are in the middle of a conflicted merge.
+
+File [short_path $path] has merge conflicts.
+
+You must resolve them, add the file, and commit to
+complete the current merge. Only then can you
+begin another merge.
+"
+ unlock_index
+ return 0
+ }
+ }
+ }
+
+ return 1
+}
+
+proc visualize_local_merge {w} {
+ set revs {}
+ foreach i [$w.source.l curselection] {
+ lappend revs [$w.source.l get $i]
+ }
+ if {$revs eq {}} return
+ lappend revs --not HEAD
+ do_gitk $revs
+}
+
+proc start_local_merge_action {w} {
+ global HEAD ui_status_value current_branch
+
+ set cmd [list git merge]
+ set names {}
+ set revcnt 0
+ foreach i [$w.source.l curselection] {
+ set b [$w.source.l get $i]
+ lappend cmd $b
+ lappend names $b
+ incr revcnt
+ }
+
+ if {$revcnt == 0} {
+ return
+ } elseif {$revcnt == 1} {
+ set unit branch
+ } elseif {$revcnt <= 15} {
+ set unit branches
+ } else {
+ tk_messageBox \
+ -icon error \
+ -type ok \
+ -title [wm title $w] \
+ -parent $w \
+ -message "Too many branches selected.
+
+You have requested to merge $revcnt branches
+in an octopus merge. This exceeds Git's
+internal limit of 15 branches per merge.
+
+Please select fewer branches. To merge more
+than 15 branches, merge the branches in batches.
+"
+ return
+ }
+
+ set msg "Merging $current_branch, [join $names {, }]"
+ set ui_status_value "$msg..."
+ set cons [new_console "Merge" $msg]
+ console_exec $cons $cmd [list finish_merge $revcnt]
+ bind $w <Destroy> {}
+ destroy $w
+}
+
+proc finish_merge {revcnt w ok} {
+ console_done $w $ok
+ if {$ok} {
+ set msg {Merge completed successfully.}
+ } else {
+ if {$revcnt != 1} {
+ info_popup "Octopus merge failed.
+
+Your merge of $revcnt branches has failed.
+
+There are file-level conflicts between the
+branches which must be resolved manually.
+
+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]
+ fconfigure $fd -blocking 0 -translation binary
+ fileevent $fd readable [list reset_hard_wait $fd]
+ set ui_status_value {Aborting... please wait...}
+ return
+ }
+
+ set msg {Merge failed. Conflict resolution is required.}
+ }
+ unlock_index
+ rescan [list set ui_status_value $msg]
+}
+
+proc do_local_merge {} {
+ global current_branch
+
+ if {![can_merge]} return
+
+ set w .merge_setup
+ toplevel $w
+ wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
+
+ label $w.header \
+ -text "Merge Into $current_branch" \
+ -font font_uibold
+ pack $w.header -side top -fill x
+
+ frame $w.buttons
+ button $w.buttons.visualize -text Visualize \
+ -font font_ui \
+ -command [list visualize_local_merge $w]
+ pack $w.buttons.visualize -side left
+ button $w.buttons.create -text Merge \
+ -font font_ui \
+ -command [list start_local_merge_action $w]
+ pack $w.buttons.create -side right
+ button $w.buttons.cancel -text {Cancel} \
+ -font font_ui \
+ -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.source \
+ -text {Source Branches} \
+ -font font_ui
+ listbox $w.source.l \
+ -height 10 \
+ -width 25 \
+ -selectmode extended \
+ -yscrollcommand [list $w.source.sby set] \
+ -font font_ui
+ scrollbar $w.source.sby -command [list $w.source.l yview]
+ pack $w.source.sby -side right -fill y
+ pack $w.source.l -side left -fill both -expand 1
+ pack $w.source -fill both -expand 1 -pady 5 -padx 5
+
+ set cmd [list git for-each-ref]
+ lappend cmd {--format=%(objectname) %(refname)}
+ lappend cmd refs/heads
+ lappend cmd refs/remotes
+ set fr_fd [open "| $cmd" r]
+ fconfigure $fr_fd -translation binary
+ while {[gets $fr_fd line] > 0} {
+ set line [split $line { }]
+ set sha1([lindex $line 0]) [lindex $line 1]
+ }
+ close $fr_fd
+
+ set to_show {}
+ set fr_fd [open "| git rev-list --all --not HEAD"]
+ while {[gets $fr_fd line] > 0} {
+ if {[catch {set ref $sha1($line)}]} continue
+ regsub ^refs/(heads|remotes)/ $ref {} ref
+ lappend to_show $ref
+ }
+ close $fr_fd
+
+ foreach ref [lsort -unique $to_show] {
+ $w.source.l insert end $ref
+ }
+
+ bind $w <Visibility> "grab $w"
+ bind $w <Key-Escape> "unlock_index;destroy $w"
+ bind $w <Destroy> unlock_index
+ wm title $w "[appname] ([reponame]): Merge"
+ tkwait window $w
+}
+
+proc do_reset_hard {} {
+ global HEAD commit_type file_states
+
+ if {[string match amend* $commit_type]} {
+ info_popup {Cannot abort while amending.
+
+You must finish amending this commit.
+}
+ return
+ }
+
+ if {![lock_index abort]} return
+
+ if {[string match *merge* $commit_type]} {
+ set op merge
+ } else {
+ set op commit
+ }
+
+ if {[ask_popup "Abort $op?
+
+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]
+ fconfigure $fd -blocking 0 -translation binary
+ fileevent $fd readable [list reset_hard_wait $fd]
+ set ui_status_value {Aborting... please wait...}
+ } else {
+ unlock_index
+ }
+}
+
+proc reset_hard_wait {fd} {
+ global ui_comm
+
+ read $fd
+ if {[eof $fd]} {
+ close $fd
+ unlock_index
+
+ $ui_comm delete 0.0 end
+ $ui_comm edit modified false
+
+ catch {file delete [gitdir MERGE_HEAD]}
+ catch {file delete [gitdir rr-cache MERGE_RR]}
+ catch {file delete [gitdir SQUASH_MSG]}
+ catch {file delete [gitdir MERGE_MSG]}
+ catch {file delete [gitdir GITGUI_MSG]}
+
+ rescan {set ui_status_value {Abort completed. Ready.}}
+ }
+}
+
######################################################################
##
## icons
return $w
}
-proc console_exec {w cmd {after {}}} {
+proc console_exec {w cmd after} {
# -- Windows tosses the enviroment when we exec our child.
# But most users need that so we have to relogin. :-(
#
}
proc console_read {w fd after} {
- global console_cr console_data
+ global console_cr
set buf [read $fd]
if {$buf ne {}} {
fconfigure $fd -blocking 1
if {[eof $fd]} {
if {[catch {close $fd}]} {
- if {![winfo exists $w]} {console_init $w}
- $w.m.s conf -background red -text {Error: Command Failed}
- $w.ok conf -state normal
set ok 0
- } elseif {[winfo exists $w]} {
- $w.m.s conf -background green -text {Success}
- $w.ok conf -state normal
+ } else {
set ok 1
}
- array unset console_cr $w
- array unset console_data $w
- if {$after ne {}} {
- uplevel #0 $after $ok
- }
+ uplevel #0 $after $w $ok
return
}
fconfigure $fd -blocking 0
}
+proc console_chain {cmdlist w {ok 1}} {
+ if {$ok} {
+ if {[llength $cmdlist] == 0} {
+ console_done $w $ok
+ return
+ }
+
+ set cmd [lindex $cmdlist 0]
+ set cmdlist [lrange $cmdlist 1 end]
+
+ if {[lindex $cmd 0] eq {console_exec}} {
+ console_exec $w \
+ [lindex $cmd 1] \
+ [list console_chain $cmdlist]
+ } else {
+ uplevel #0 $cmd $cmdlist $w $ok
+ }
+ } else {
+ console_done $w $ok
+ }
+}
+
+proc console_done {args} {
+ global console_cr console_data
+
+ switch -- [llength $args] {
+ 2 {
+ set w [lindex $args 0]
+ set ok [lindex $args 1]
+ }
+ 3 {
+ set w [lindex $args 1]
+ set ok [lindex $args 2]
+ }
+ default {
+ error "wrong number of args: console_done ?ignored? w ok"
+ }
+ }
+
+ if {$ok} {
+ if {[winfo exists $w]} {
+ $w.m.s conf -background green -text {Success}
+ $w.ok conf -state normal
+ }
+ } else {
+ if {![winfo exists $w]} {
+ console_init $w
+ }
+ $w.m.s conf -background red -text {Error: Command Failed}
+ $w.ok conf -state normal
+ }
+
+ array unset console_cr $w
+ array unset console_data $w
+}
+
######################################################################
##
## ui commands
proc do_gc {} {
set w [new_console {gc} {Compressing the object database}]
- console_exec $w {git gc}
+ console_chain {
+ {console_exec {git pack-refs --prune}}
+ {console_exec {git reflog expire --all}}
+ {console_exec {git repack -a -d -l}}
+ {console_exec {git rerere gc}}
+ } $w
}
proc do_fsck_objects {} {
lappend cmd --full
lappend cmd --cache
lappend cmd --strict
- console_exec $w $cmd
+ console_exec $w $cmd console_done
}
set is_quitting 0
}
.mbar add cascade -label Commit -menu .mbar.commit
if {!$single_commit} {
+ .mbar add cascade -label Merge -menu .mbar.merge
.mbar add cascade -label Fetch -menu .mbar.fetch
.mbar add cascade -label Push -menu .mbar.push
}
lappend disable_on_lock \
[list .mbar.commit entryconf [.mbar.commit index last] -state]
-# -- Transport menus
-#
-if {!$single_commit} {
- menu .mbar.fetch
- menu .mbar.push
-
- .mbar.push add command -label {Push...} \
- -command do_push_anywhere \
- -font font_ui
-}
-
if {[is_MacOSX]} {
# -- Apple Menu (Mac OS X only)
#
pack .branch.cb -side left -fill x
pack .branch -side top -fill x
+if {!$single_commit} {
+ menu .mbar.merge
+ .mbar.merge add command -label {Local Merge...} \
+ -command do_local_merge \
+ -font font_ui
+ lappend disable_on_lock \
+ [list .mbar.merge entryconf [.mbar.merge index last] -state]
+ .mbar.merge add command -label {Abort Merge...} \
+ -command do_reset_hard \
+ -font font_ui
+ lappend disable_on_lock \
+ [list .mbar.merge entryconf [.mbar.merge index last] -state]
+
+
+ menu .mbar.fetch
+
+ menu .mbar.push
+ .mbar.push add command -label {Push...} \
+ -command do_push_anywhere \
+ -font font_ui
+}
+
# -- Main Window Layout
#
panedwindow .vpane -orient vertical