lib / merge.tclon commit git-gui: Avoid unnecessary symbolic-ref call during checkout (7d5266a)
   1# git-gui branch merge support
   2# Copyright (C) 2006, 2007 Shawn Pearce
   3
   4class merge {
   5
   6field w         ; # top level window
   7field w_list    ; # widget of available branches
   8field list      ; # list of available branches
   9
  10method _can_merge {} {
  11        global HEAD commit_type file_states
  12
  13        if {[string match amend* $commit_type]} {
  14                info_popup {Cannot merge while amending.
  15
  16You must finish amending this commit before starting any type of merge.
  17}
  18                return 0
  19        }
  20
  21        if {[committer_ident] eq {}} {return 0}
  22        if {![lock_index merge]} {return 0}
  23
  24        # -- Our in memory state should match the repository.
  25        #
  26        repository_state curType curHEAD curMERGE_HEAD
  27        if {$commit_type ne $curType || $HEAD ne $curHEAD} {
  28                info_popup {Last scanned state does not match repository state.
  29
  30Another Git program has modified this repository since the last scan.  A rescan must be performed before a merge can be performed.
  31
  32The rescan will be automatically started now.
  33}
  34                unlock_index
  35                rescan ui_ready
  36                return 0
  37        }
  38
  39        foreach path [array names file_states] {
  40                switch -glob -- [lindex $file_states($path) 0] {
  41                _O {
  42                        continue; # and pray it works!
  43                }
  44                U? {
  45                        error_popup "You are in the middle of a conflicted merge.
  46
  47File [short_path $path] has merge conflicts.
  48
  49You must resolve them, add the file, and commit to complete the current merge.  Only then can you begin another merge.
  50"
  51                        unlock_index
  52                        return 0
  53                }
  54                ?? {
  55                        error_popup "You are in the middle of a change.
  56
  57File [short_path $path] is modified.
  58
  59You should complete the current commit before starting a merge.  Doing so will help you abort a failed merge, should the need arise.
  60"
  61                        unlock_index
  62                        return 0
  63                }
  64                }
  65        }
  66
  67        return 1
  68}
  69
  70method _rev {} {
  71        set i [$w_list curselection]
  72        if {$i >= 0} {
  73                return [lindex [lindex $list $i] 0]
  74        }
  75        return {}
  76}
  77
  78method _visualize {} {
  79        set rev [_rev $this]
  80        if {$rev ne {}} {
  81                do_gitk [list $rev --not HEAD]
  82        }
  83}
  84
  85method _start {} {
  86        global HEAD current_branch
  87
  88        set name [_rev $this]
  89        if {$name eq {}} {
  90                return
  91        }
  92
  93        set cmd [list git merge $name]
  94        set msg "Merging $current_branch and $name"
  95        ui_status "$msg..."
  96        set cons [console::new "Merge" $cmd]
  97        console::exec $cons $cmd [cb _finish $cons]
  98
  99        wm protocol $w WM_DELETE_WINDOW {}
 100        destroy $w
 101}
 102
 103method _finish {cons ok} {
 104        console::done $cons $ok
 105        if {$ok} {
 106                set msg {Merge completed successfully.}
 107        } else {
 108                set msg {Merge failed.  Conflict resolution is required.}
 109        }
 110        unlock_index
 111        rescan [list ui_status $msg]
 112        delete_this
 113}
 114
 115constructor dialog {} {
 116        global current_branch
 117        global M1B
 118
 119        if {![_can_merge $this]} {
 120                delete_this
 121                return
 122        }
 123
 124        set fmt {list %(objectname) %(*objectname) %(refname) %(subject)}
 125        set fr_fd [git_read for-each-ref \
 126                --tcl \
 127                --format=$fmt \
 128                refs/heads \
 129                refs/remotes \
 130                refs/tags \
 131                ]
 132        fconfigure $fr_fd -translation binary
 133        while {[gets $fr_fd line] > 0} {
 134                set line [eval $line]
 135                set ref [lindex $line 2]
 136                regsub ^refs/(heads|remotes|tags)/ $ref {} ref
 137                set subj($ref) [lindex $line 3]
 138                lappend sha1([lindex $line 0]) $ref
 139                if {[lindex $line 1] ne {}} {
 140                        lappend sha1([lindex $line 1]) $ref
 141                }
 142        }
 143        close $fr_fd
 144
 145        set list [list]
 146        set fr_fd [git_read rev-list --all --not HEAD]
 147        while {[gets $fr_fd line] > 0} {
 148                if {[catch {set ref $sha1($line)}]} continue
 149                foreach n $ref {
 150                        lappend list [list $n $line]
 151                }
 152        }
 153        close $fr_fd
 154        set list [lsort -unique $list]
 155
 156        make_toplevel top w
 157        wm title $top "[appname] ([reponame]): Merge"
 158        if {$top ne {.}} {
 159                wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
 160        }
 161
 162        set _visualize [cb _visualize]
 163        set _start [cb _start]
 164
 165        label $w.header \
 166                -text "Merge Into $current_branch" \
 167                -font font_uibold
 168        pack $w.header -side top -fill x
 169
 170        frame $w.buttons
 171        button $w.buttons.visualize -text Visualize -command $_visualize
 172        pack $w.buttons.visualize -side left
 173        button $w.buttons.create -text Merge -command $_start
 174        pack $w.buttons.create -side right
 175        button $w.buttons.cancel \
 176                -text {Cancel} \
 177                -command [cb _cancel]
 178        pack $w.buttons.cancel -side right -padx 5
 179        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
 180
 181        labelframe $w.source -text {Source Branches}
 182        set w_list $w.source.l
 183        listbox $w_list \
 184                -height 10 \
 185                -width 70 \
 186                -font font_diff \
 187                -selectmode browse \
 188                -yscrollcommand [list $w.source.sby set]
 189        scrollbar $w.source.sby -command [list $w_list yview]
 190        pack $w.source.sby -side right -fill y
 191        pack $w_list -side left -fill both -expand 1
 192        pack $w.source -fill both -expand 1 -pady 5 -padx 5
 193
 194        foreach ref $list {
 195                set n [lindex $ref 0]
 196                if {[string length $n] > 20} {
 197                        set n "[string range $n 0 16]..."
 198                }
 199                $w_list insert end [format {%s %-20s %s} \
 200                        [string range [lindex $ref 1] 0 5] \
 201                        $n \
 202                        $subj([lindex $ref 0])]
 203        }
 204
 205        bind $w_list <Key-K> [list event generate %W <Shift-Key-Up>]
 206        bind $w_list <Key-J> [list event generate %W <Shift-Key-Down>]
 207        bind $w_list <Key-k> [list event generate %W <Key-Up>]
 208        bind $w_list <Key-j> [list event generate %W <Key-Down>]
 209        bind $w_list <Key-h> [list event generate %W <Key-Left>]
 210        bind $w_list <Key-l> [list event generate %W <Key-Right>]
 211        bind $w_list <Key-v> $_visualize
 212
 213        bind $w <$M1B-Key-Return> $_start
 214        bind $w <Visibility> [cb _visible]
 215        bind $w <Key-Escape> [cb _cancel]
 216        wm protocol $w WM_DELETE_WINDOW [cb _cancel]
 217        tkwait window $w
 218}
 219
 220method _visible {} {
 221        grab $w
 222        focus $w_list
 223}
 224
 225method _cancel {} {
 226        wm protocol $w WM_DELETE_WINDOW {}
 227        unlock_index
 228        destroy $w
 229        delete_this
 230}
 231
 232}
 233
 234namespace eval merge {
 235
 236proc reset_hard {} {
 237        global HEAD commit_type file_states
 238
 239        if {[string match amend* $commit_type]} {
 240                info_popup {Cannot abort while amending.
 241
 242You must finish amending this commit.
 243}
 244                return
 245        }
 246
 247        if {![lock_index abort]} return
 248
 249        if {[string match *merge* $commit_type]} {
 250                set op merge
 251        } else {
 252                set op commit
 253        }
 254
 255        if {[ask_popup "Abort $op?
 256
 257Aborting the current $op will cause *ALL* uncommitted changes to be lost.
 258
 259Continue with aborting the current $op?"] eq {yes}} {
 260                set fd [git_read read-tree --reset -u HEAD]
 261                fconfigure $fd -blocking 0 -translation binary
 262                fileevent $fd readable [namespace code [list _reset_wait $fd]]
 263                ui_status {Aborting... please wait...}
 264        } else {
 265                unlock_index
 266        }
 267}
 268
 269proc _reset_wait {fd} {
 270        global ui_comm
 271
 272        read $fd
 273        if {[eof $fd]} {
 274                close $fd
 275                unlock_index
 276
 277                $ui_comm delete 0.0 end
 278                $ui_comm edit modified false
 279
 280                catch {file delete [gitdir MERGE_HEAD]}
 281                catch {file delete [gitdir rr-cache MERGE_RR]}
 282                catch {file delete [gitdir SQUASH_MSG]}
 283                catch {file delete [gitdir MERGE_MSG]}
 284                catch {file delete [gitdir GITGUI_MSG]}
 285
 286                rescan {ui_status {Abort completed.  Ready.}}
 287        }
 288}
 289
 290}