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