Code

git-gui: Save the merge base during checkout_op processing
[git.git] / lib / merge.tcl
1 # git-gui branch merge support
2 # Copyright (C) 2006, 2007 Shawn Pearce
4 class merge {
6 field w         ; # top level window
7 field w_list    ; # widget of available branches
8 field list      ; # list of available branches
10 method _can_merge {} {
11         global HEAD commit_type file_states
13         if {[string match amend* $commit_type]} {
14                 info_popup {Cannot merge while amending.
16 You must finish amending this commit before starting any type of merge.
17 }
18                 return 0
19         }
21         if {[committer_ident] eq {}} {return 0}
22         if {![lock_index merge]} {return 0}
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.
30 Another Git program has modified this repository since the last scan.  A rescan must be performed before a merge can be performed.
32 The rescan will be automatically started now.
33 }
34                 unlock_index
35                 rescan ui_ready
36                 return 0
37         }
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.
47 File [short_path $path] has merge conflicts.
49 You 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.
57 File [short_path $path] is modified.
59 You 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         }
67         return 1
68 }
70 method _rev {} {
71         set i [$w_list curselection]
72         if {$i >= 0} {
73                 return [lindex [lindex $list $i] 0]
74         }
75         return {}
76 }
78 method _visualize {} {
79         set rev [_rev $this]
80         if {$rev ne {}} {
81                 do_gitk [list $rev --not HEAD]
82         }
83 }
85 method _start {} {
86         global HEAD current_branch
88         set name [_rev $this]
89         if {$name eq {}} {
90                 return
91         }
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]
99         wm protocol $w WM_DELETE_WINDOW {}
100         destroy $w
103 method _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
115 constructor dialog {} {
116         global current_branch
117         global M1B
119         if {![_can_merge $this]} {
120                 delete_this
121                 return
122         }
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
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]
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         }
162         set _visualize [cb _visualize]
163         set _start [cb _start]
165         label $w.header \
166                 -text "Merge Into $current_branch" \
167                 -font font_uibold
168         pack $w.header -side top -fill x
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
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
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         }
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
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
220 method _visible {} {
221         grab $w
222         focus $w_list
225 method _cancel {} {
226         wm protocol $w WM_DELETE_WINDOW {}
227         unlock_index
228         destroy $w
229         delete_this
234 namespace eval merge {
236 proc reset_hard {} {
237         global HEAD commit_type file_states
239         if {[string match amend* $commit_type]} {
240                 info_popup {Cannot abort while amending.
242 You must finish amending this commit.
244                 return
245         }
247         if {![lock_index abort]} return
249         if {[string match *merge* $commit_type]} {
250                 set op merge
251         } else {
252                 set op commit
253         }
255         if {[ask_popup "Abort $op?
257 Aborting the current $op will cause *ALL* uncommitted changes to be lost.
259 Continue 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         }
269 proc _reset_wait {fd} {
270         global ui_comm
272         read $fd
273         if {[eof $fd]} {
274                 close $fd
275                 unlock_index
277                 $ui_comm delete 0.0 end
278                 $ui_comm edit modified false
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]}
286                 rescan {ui_status {Abort completed.  Ready.}}
287         }