6caf25f2bea19efbad2190587ea5c414604871f0
   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 _refs {} {
  71        set r {}
  72        foreach i [$w_list curselection] {
  73                lappend r [lindex [lindex $list $i] 0]
  74        }
  75        return $r
  76}
  77
  78method _visualize {} {
  79        set revs [_refs $this]
  80        if {$revs eq {}} return
  81        lappend revs --not HEAD
  82        do_gitk $revs
  83}
  84
  85method _start {} {
  86        global HEAD current_branch
  87
  88        set cmd [list git merge]
  89        set names [_refs $this]
  90        set revcnt [llength $names]
  91        append cmd { } $names
  92
  93        if {$revcnt == 0} {
  94                return
  95        } elseif {$revcnt == 1} {
  96                set unit branch
  97        } elseif {$revcnt <= 15} {
  98                set unit branches
  99
 100                if {[tk_dialog \
 101                $w.confirm_octopus \
 102                [wm title $w] \
 103                "Use octopus merge strategy?
 104
 105You are merging $revcnt branches at once.  This requires using the octopus merge driver, which may not succeed if there are file-level conflicts.
 106" \
 107                question \
 108                0 \
 109                {Cancel} \
 110                {Use octopus} \
 111                ] != 1} return
 112        } else {
 113                tk_messageBox \
 114                        -icon error \
 115                        -type ok \
 116                        -title [wm title $w] \
 117                        -parent $w \
 118                        -message "Too many branches selected.
 119
 120You have requested to merge $revcnt branches in an octopus merge.  This exceeds Git's internal limit of 15 branches per merge.
 121
 122Please select fewer branches.  To merge more than 15 branches, merge the branches in batches.
 123"
 124                return
 125        }
 126
 127        set msg "Merging $current_branch, [join $names {, }]"
 128        ui_status "$msg..."
 129        set cons [console::new "Merge" $msg]
 130        console::exec $cons $cmd [cb _finish $revcnt $cons]
 131
 132        wm protocol $w WM_DELETE_WINDOW {}
 133        destroy $w
 134}
 135
 136method _finish {revcnt cons ok} {
 137        console::done $cons $ok
 138        if {$ok} {
 139                set msg {Merge completed successfully.}
 140        } else {
 141                if {$revcnt != 1} {
 142                        info_popup "Octopus merge failed.
 143
 144Your merge of $revcnt branches has failed.
 145
 146There are file-level conflicts between the branches which must be resolved manually.
 147
 148The working directory will now be reset.
 149
 150You can attempt this merge again by merging only one branch at a time." $w
 151
 152                        set fd [git_read read-tree --reset -u HEAD]
 153                        fconfigure $fd -blocking 0 -translation binary
 154                        fileevent $fd readable [cb _reset_wait $fd]
 155                        ui_status {Aborting... please wait...}
 156                        return
 157                }
 158
 159                set msg {Merge failed.  Conflict resolution is required.}
 160        }
 161        unlock_index
 162        rescan [list ui_status $msg]
 163        delete_this
 164}
 165
 166constructor dialog {} {
 167        global current_branch
 168        global M1B
 169
 170        if {![_can_merge $this]} {
 171                delete_this
 172                return
 173        }
 174
 175        set fmt {list %(objectname) %(*objectname) %(refname) %(subject)}
 176        set fr_fd [git_read for-each-ref \
 177                --tcl \
 178                --format=$fmt \
 179                refs/heads \
 180                refs/remotes \
 181                refs/tags \
 182                ]
 183        fconfigure $fr_fd -translation binary
 184        while {[gets $fr_fd line] > 0} {
 185                set line [eval $line]
 186                set ref [lindex $line 2]
 187                regsub ^refs/(heads|remotes|tags)/ $ref {} ref
 188                set subj($ref) [lindex $line 3]
 189                lappend sha1([lindex $line 0]) $ref
 190                if {[lindex $line 1] ne {}} {
 191                        lappend sha1([lindex $line 1]) $ref
 192                }
 193        }
 194        close $fr_fd
 195
 196        set list [list]
 197        set fr_fd [git_read rev-list --all --not HEAD]
 198        while {[gets $fr_fd line] > 0} {
 199                if {[catch {set ref $sha1($line)}]} continue
 200                foreach n $ref {
 201                        lappend list [list $n $line]
 202                }
 203        }
 204        close $fr_fd
 205        set list [lsort -unique $list]
 206
 207        make_toplevel top w
 208        wm title $top "[appname] ([reponame]): Merge"
 209        if {$top ne {.}} {
 210                wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
 211        }
 212
 213        set _visualize [cb _visualize]
 214        set _start [cb _start]
 215
 216        label $w.header \
 217                -text "Merge Into $current_branch" \
 218                -font font_uibold
 219        pack $w.header -side top -fill x
 220
 221        frame $w.buttons
 222        button $w.buttons.visualize -text Visualize -command $_visualize
 223        pack $w.buttons.visualize -side left
 224        button $w.buttons.create -text Merge -command $_start
 225        pack $w.buttons.create -side right
 226        button $w.buttons.cancel \
 227                -text {Cancel} \
 228                -command [cb _cancel]
 229        pack $w.buttons.cancel -side right -padx 5
 230        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
 231
 232        labelframe $w.source -text {Source Branches}
 233        set w_list $w.source.l
 234        listbox $w_list \
 235                -height 10 \
 236                -width 70 \
 237                -font font_diff \
 238                -selectmode extended \
 239                -yscrollcommand [list $w.source.sby set]
 240        scrollbar $w.source.sby -command [list $w_list yview]
 241        pack $w.source.sby -side right -fill y
 242        pack $w_list -side left -fill both -expand 1
 243        pack $w.source -fill both -expand 1 -pady 5 -padx 5
 244
 245        foreach ref $list {
 246                set n [lindex $ref 0]
 247                if {[string length $n] > 20} {
 248                        set n "[string range $n 0 16]..."
 249                }
 250                $w_list insert end [format {%s %-20s %s} \
 251                        [string range [lindex $ref 1] 0 5] \
 252                        $n \
 253                        $subj([lindex $ref 0])]
 254        }
 255
 256        bind $w_list <Key-K> [list event generate %W <Shift-Key-Up>]
 257        bind $w_list <Key-J> [list event generate %W <Shift-Key-Down>]
 258        bind $w_list <Key-k> [list event generate %W <Key-Up>]
 259        bind $w_list <Key-j> [list event generate %W <Key-Down>]
 260        bind $w_list <Key-h> [list event generate %W <Key-Left>]
 261        bind $w_list <Key-l> [list event generate %W <Key-Right>]
 262        bind $w_list <Key-v> $_visualize
 263
 264        bind $w <$M1B-Key-Return> $_start
 265        bind $w <Visibility> [cb _visible]
 266        bind $w <Key-Escape> [cb _cancel]
 267        wm protocol $w WM_DELETE_WINDOW [cb _cancel]
 268        tkwait window $w
 269}
 270
 271method _visible {} {
 272        grab $w
 273        focus $w_list
 274}
 275
 276method _cancel {} {
 277        wm protocol $w WM_DELETE_WINDOW {}
 278        unlock_index
 279        destroy $w
 280        delete_this
 281}
 282
 283}
 284
 285namespace eval merge {
 286
 287proc reset_hard {} {
 288        global HEAD commit_type file_states
 289
 290        if {[string match amend* $commit_type]} {
 291                info_popup {Cannot abort while amending.
 292
 293You must finish amending this commit.
 294}
 295                return
 296        }
 297
 298        if {![lock_index abort]} return
 299
 300        if {[string match *merge* $commit_type]} {
 301                set op merge
 302        } else {
 303                set op commit
 304        }
 305
 306        if {[ask_popup "Abort $op?
 307
 308Aborting the current $op will cause *ALL* uncommitted changes to be lost.
 309
 310Continue with aborting the current $op?"] eq {yes}} {
 311                set fd [git_read read-tree --reset -u HEAD]
 312                fconfigure $fd -blocking 0 -translation binary
 313                fileevent $fd readable [namespace code [list _reset_wait $fd]]
 314                ui_status {Aborting... please wait...}
 315        } else {
 316                unlock_index
 317        }
 318}
 319
 320proc _reset_wait {fd} {
 321        global ui_comm
 322
 323        read $fd
 324        if {[eof $fd]} {
 325                close $fd
 326                unlock_index
 327
 328                $ui_comm delete 0.0 end
 329                $ui_comm edit modified false
 330
 331                catch {file delete [gitdir MERGE_HEAD]}
 332                catch {file delete [gitdir rr-cache MERGE_RR]}
 333                catch {file delete [gitdir SQUASH_MSG]}
 334                catch {file delete [gitdir MERGE_MSG]}
 335                catch {file delete [gitdir GITGUI_MSG]}
 336
 337                rescan {ui_status {Abort completed.  Ready.}}
 338        }
 339}
 340
 341}