##// END OF EJS Templates
Remove trailing spaces, fix indentation
Thomas Arendsen Hein -
r5143:d4fa6baf default
parent child Browse files
Show More
@@ -1,3758 +1,3758 b''
1 #!/usr/bin/env wish
1 #!/usr/bin/env wish
2
2
3 # Copyright (C) 2005 Paul Mackerras. All rights reserved.
3 # Copyright (C) 2005 Paul Mackerras. All rights reserved.
4 # This program is free software; it may be used, copied, modified
4 # This program is free software; it may be used, copied, modified
5 # and distributed under the terms of the GNU General Public Licence,
5 # and distributed under the terms of the GNU General Public Licence,
6 # either version 2, or (at your option) any later version.
6 # either version 2, or (at your option) any later version.
7
7
8
8
9 # Modified version of Tip 171:
9 # Modified version of Tip 171:
10 # http://www.tcl.tk/cgi-bin/tct/tip/171.html
10 # http://www.tcl.tk/cgi-bin/tct/tip/171.html
11 #
11 #
12 # The in_mousewheel global was added to fix strange reentrancy issues.
12 # The in_mousewheel global was added to fix strange reentrancy issues.
13 # The whole snipped is activated only under windows, mouse wheel
13 # The whole snipped is activated only under windows, mouse wheel
14 # bindings working already under MacOSX and Linux.
14 # bindings working already under MacOSX and Linux.
15
15
16 if {[tk windowingsystem] eq "win32"} {
16 if {[tk windowingsystem] eq "win32"} {
17
17
18 set mw_classes [list Text Listbox Table TreeCtrl]
18 set mw_classes [list Text Listbox Table TreeCtrl]
19 foreach class $mw_classes { bind $class <MouseWheel> {} }
19 foreach class $mw_classes { bind $class <MouseWheel> {} }
20
20
21 set in_mousewheel 0
21 set in_mousewheel 0
22
22
23 proc ::tk::MouseWheel {wFired X Y D {shifted 0}} {
23 proc ::tk::MouseWheel {wFired X Y D {shifted 0}} {
24 global in_mousewheel
24 global in_mousewheel
25 if { $in_mousewheel != 0 } { return }
25 if { $in_mousewheel != 0 } { return }
26 # Set event to check based on call
26 # Set event to check based on call
27 set evt "<[expr {$shifted?{Shift-}:{}}]MouseWheel>"
27 set evt "<[expr {$shifted?{Shift-}:{}}]MouseWheel>"
28 # do not double-fire in case the class already has a binding
28 # do not double-fire in case the class already has a binding
29 if {[bind [winfo class $wFired] $evt] ne ""} { return }
29 if {[bind [winfo class $wFired] $evt] ne ""} { return }
30 # obtain the window the mouse is over
30 # obtain the window the mouse is over
31 set w [winfo containing $X $Y]
31 set w [winfo containing $X $Y]
32 # if we are outside the app, try and scroll the focus widget
32 # if we are outside the app, try and scroll the focus widget
33 if {![winfo exists $w]} { catch {set w [focus]} }
33 if {![winfo exists $w]} { catch {set w [focus]} }
34 if {[winfo exists $w]} {
34 if {[winfo exists $w]} {
35
35
36 if {[bind $w $evt] ne ""} {
36 if {[bind $w $evt] ne ""} {
37 # Awkward ... this widget has a MouseWheel binding, but to
37 # Awkward ... this widget has a MouseWheel binding, but to
38 # trigger successfully in it, we must give it focus.
38 # trigger successfully in it, we must give it focus.
39 catch {focus} old
39 catch {focus} old
40 if {$w ne $old} { focus $w }
40 if {$w ne $old} { focus $w }
41 set in_mousewheel 1
41 set in_mousewheel 1
42 event generate $w $evt -rootx $X -rooty $Y -delta $D
42 event generate $w $evt -rootx $X -rooty $Y -delta $D
43 set in_mousewheel 0
43 set in_mousewheel 0
44 if {$w ne $old} { focus $old }
44 if {$w ne $old} { focus $old }
45 return
45 return
46 }
46 }
47
47
48 # aqua and x11/win32 have different delta handling
48 # aqua and x11/win32 have different delta handling
49 if {[tk windowingsystem] ne "aqua"} {
49 if {[tk windowingsystem] ne "aqua"} {
50 set delta [expr {- ($D / 30)}]
50 set delta [expr {- ($D / 30)}]
51 } else {
51 } else {
52 set delta [expr {- ($D)}]
52 set delta [expr {- ($D)}]
53 }
53 }
54 # scrollbars have different call conventions
54 # scrollbars have different call conventions
55 if {[string match "*Scrollbar" [winfo class $w]]} {
55 if {[string match "*Scrollbar" [winfo class $w]]} {
56 catch {tk::ScrollByUnits $w \
56 catch {tk::ScrollByUnits $w \
57 [string index [$w cget -orient] 0] $delta}
57 [string index [$w cget -orient] 0] $delta}
58 } else {
58 } else {
59 set cmd [list $w [expr {$shifted ? "xview" : "yview"}] \
59 set cmd [list $w [expr {$shifted ? "xview" : "yview"}] \
60 scroll $delta units]
60 scroll $delta units]
61 # Walking up to find the proper widget (handles cases like
61 # Walking up to find the proper widget (handles cases like
62 # embedded widgets in a canvas)
62 # embedded widgets in a canvas)
63 while {[catch $cmd] && [winfo toplevel $w] ne $w} {
63 while {[catch $cmd] && [winfo toplevel $w] ne $w} {
64 set w [winfo parent $w]
64 set w [winfo parent $w]
65 }
65 }
66 }
66 }
67 }
67 }
68 }
68 }
69
69
70 bind all <MouseWheel> [list ::tk::MouseWheel %W %X %Y %D 0]
70 bind all <MouseWheel> [list ::tk::MouseWheel %W %X %Y %D 0]
71
71
72 # end of win32 section
72 # end of win32 section
73 }
73 }
74
74
75
75
76 proc gitdir {} {
76 proc gitdir {} {
77 global env
77 global env
78 if {[info exists env(GIT_DIR)]} {
78 if {[info exists env(GIT_DIR)]} {
79 return $env(GIT_DIR)
79 return $env(GIT_DIR)
80 } else {
80 } else {
81 return ".hg"
81 return ".hg"
82 }
82 }
83 }
83 }
84
84
85 proc getcommits {rargs} {
85 proc getcommits {rargs} {
86 global commits commfd phase canv mainfont env
86 global commits commfd phase canv mainfont env
87 global startmsecs nextupdate ncmupdate
87 global startmsecs nextupdate ncmupdate
88 global ctext maincursor textcursor leftover
88 global ctext maincursor textcursor leftover
89
89
90 # check that we can find a .git directory somewhere...
90 # check that we can find a .git directory somewhere...
91 set gitdir [gitdir]
91 set gitdir [gitdir]
92 if {![file isdirectory $gitdir]} {
92 if {![file isdirectory $gitdir]} {
93 error_popup "Cannot find the git directory \"$gitdir\"."
93 error_popup "Cannot find the git directory \"$gitdir\"."
94 exit 1
94 exit 1
95 }
95 }
96 set commits {}
96 set commits {}
97 set phase getcommits
97 set phase getcommits
98 set startmsecs [clock clicks -milliseconds]
98 set startmsecs [clock clicks -milliseconds]
99 set nextupdate [expr $startmsecs + 100]
99 set nextupdate [expr $startmsecs + 100]
100 set ncmupdate 1
100 set ncmupdate 1
101 set limit 0
101 set limit 0
102 set revargs {}
102 set revargs {}
103 for {set i 0} {$i < [llength $rargs]} {incr i} {
103 for {set i 0} {$i < [llength $rargs]} {incr i} {
104 set opt [lindex $rargs $i]
104 set opt [lindex $rargs $i]
105 if {$opt == "--limit"} {
105 if {$opt == "--limit"} {
106 incr i
106 incr i
107 set limit [lindex $rargs $i]
107 set limit [lindex $rargs $i]
108 } else {
108 } else {
109 lappend revargs $opt
109 lappend revargs $opt
110 }
110 }
111 }
111 }
112 if [catch {
112 if [catch {
113 set parse_args [concat --default HEAD $revargs]
113 set parse_args [concat --default HEAD $revargs]
114 set parse_temp [eval exec {$env(HG)} --config ui.report_untrusted=false debug-rev-parse $parse_args]
114 set parse_temp [eval exec {$env(HG)} --config ui.report_untrusted=false debug-rev-parse $parse_args]
115 regsub -all "\r\n" $parse_temp "\n" parse_temp
115 regsub -all "\r\n" $parse_temp "\n" parse_temp
116 set parsed_args [split $parse_temp "\n"]
116 set parsed_args [split $parse_temp "\n"]
117 } err] {
117 } err] {
118 # if git-rev-parse failed for some reason...
118 # if git-rev-parse failed for some reason...
119 if {$rargs == {}} {
119 if {$rargs == {}} {
120 set revargs HEAD
120 set revargs HEAD
121 }
121 }
122 set parsed_args $revargs
122 set parsed_args $revargs
123 }
123 }
124 if {$limit > 0} {
124 if {$limit > 0} {
125 set parsed_args [concat -n $limit $parsed_args]
125 set parsed_args [concat -n $limit $parsed_args]
126 }
126 }
127 if [catch {
127 if [catch {
128 set commfd [open "|{$env(HG)} --config ui.report_untrusted=false debug-rev-list --header --topo-order --parents $parsed_args" r]
128 set commfd [open "|{$env(HG)} --config ui.report_untrusted=false debug-rev-list --header --topo-order --parents $parsed_args" r]
129 } err] {
129 } err] {
130 puts stderr "Error executing hg debug-rev-list: $err"
130 puts stderr "Error executing hg debug-rev-list: $err"
131 exit 1
131 exit 1
132 }
132 }
133 set leftover {}
133 set leftover {}
134 fconfigure $commfd -blocking 0 -translation lf
134 fconfigure $commfd -blocking 0 -translation lf
135 fileevent $commfd readable [list getcommitlines $commfd]
135 fileevent $commfd readable [list getcommitlines $commfd]
136 $canv delete all
136 $canv delete all
137 $canv create text 3 3 -anchor nw -text "Reading commits..." \
137 $canv create text 3 3 -anchor nw -text "Reading commits..." \
138 -font $mainfont -tags textitems
138 -font $mainfont -tags textitems
139 . config -cursor watch
139 . config -cursor watch
140 settextcursor watch
140 settextcursor watch
141 }
141 }
142
142
143 proc getcommitlines {commfd} {
143 proc getcommitlines {commfd} {
144 global commits parents cdate children
144 global commits parents cdate children
145 global commitlisted phase commitinfo nextupdate
145 global commitlisted phase commitinfo nextupdate
146 global stopped redisplaying leftover
146 global stopped redisplaying leftover
147
147
148 set stuff [read $commfd]
148 set stuff [read $commfd]
149 if {$stuff == {}} {
149 if {$stuff == {}} {
150 if {![eof $commfd]} return
150 if {![eof $commfd]} return
151 # set it blocking so we wait for the process to terminate
151 # set it blocking so we wait for the process to terminate
152 fconfigure $commfd -blocking 1
152 fconfigure $commfd -blocking 1
153 if {![catch {close $commfd} err]} {
153 if {![catch {close $commfd} err]} {
154 after idle finishcommits
154 after idle finishcommits
155 return
155 return
156 }
156 }
157 if {[string range $err 0 4] == "usage"} {
157 if {[string range $err 0 4] == "usage"} {
158 set err \
158 set err \
159 {Gitk: error reading commits: bad arguments to git-rev-list.
159 {Gitk: error reading commits: bad arguments to git-rev-list.
160 (Note: arguments to gitk are passed to git-rev-list
160 (Note: arguments to gitk are passed to git-rev-list
161 to allow selection of commits to be displayed.)}
161 to allow selection of commits to be displayed.)}
162 } else {
162 } else {
163 set err "Error reading commits: $err"
163 set err "Error reading commits: $err"
164 }
164 }
165 error_popup $err
165 error_popup $err
166 exit 1
166 exit 1
167 }
167 }
168 set start 0
168 set start 0
169 while 1 {
169 while 1 {
170 set i [string first "\0" $stuff $start]
170 set i [string first "\0" $stuff $start]
171 if {$i < 0} {
171 if {$i < 0} {
172 append leftover [string range $stuff $start end]
172 append leftover [string range $stuff $start end]
173 return
173 return
174 }
174 }
175 set cmit [string range $stuff $start [expr {$i - 1}]]
175 set cmit [string range $stuff $start [expr {$i - 1}]]
176 if {$start == 0} {
176 if {$start == 0} {
177 set cmit "$leftover$cmit"
177 set cmit "$leftover$cmit"
178 set leftover {}
178 set leftover {}
179 }
179 }
180 set start [expr {$i + 1}]
180 set start [expr {$i + 1}]
181 regsub -all "\r\n" $cmit "\n" cmit
181 regsub -all "\r\n" $cmit "\n" cmit
182 set j [string first "\n" $cmit]
182 set j [string first "\n" $cmit]
183 set ok 0
183 set ok 0
184 if {$j >= 0} {
184 if {$j >= 0} {
185 set ids [string range $cmit 0 [expr {$j - 1}]]
185 set ids [string range $cmit 0 [expr {$j - 1}]]
186 set ok 1
186 set ok 1
187 foreach id $ids {
187 foreach id $ids {
188 if {![regexp {^[0-9a-f]{12}$} $id]} {
188 if {![regexp {^[0-9a-f]{12}$} $id]} {
189 set ok 0
189 set ok 0
190 break
190 break
191 }
191 }
192 }
192 }
193 }
193 }
194 if {!$ok} {
194 if {!$ok} {
195 set shortcmit $cmit
195 set shortcmit $cmit
196 if {[string length $shortcmit] > 80} {
196 if {[string length $shortcmit] > 80} {
197 set shortcmit "[string range $shortcmit 0 80]..."
197 set shortcmit "[string range $shortcmit 0 80]..."
198 }
198 }
199 error_popup "Can't parse hg debug-rev-list output: {$shortcmit}"
199 error_popup "Can't parse hg debug-rev-list output: {$shortcmit}"
200 exit 1
200 exit 1
201 }
201 }
202 set id [lindex $ids 0]
202 set id [lindex $ids 0]
203 set olds [lrange $ids 1 end]
203 set olds [lrange $ids 1 end]
204 set cmit [string range $cmit [expr {$j + 1}] end]
204 set cmit [string range $cmit [expr {$j + 1}] end]
205 lappend commits $id
205 lappend commits $id
206 set commitlisted($id) 1
206 set commitlisted($id) 1
207 parsecommit $id $cmit 1 [lrange $ids 1 end]
207 parsecommit $id $cmit 1 [lrange $ids 1 end]
208 drawcommit $id
208 drawcommit $id
209 if {[clock clicks -milliseconds] >= $nextupdate} {
209 if {[clock clicks -milliseconds] >= $nextupdate} {
210 doupdate 1
210 doupdate 1
211 }
211 }
212 while {$redisplaying} {
212 while {$redisplaying} {
213 set redisplaying 0
213 set redisplaying 0
214 if {$stopped == 1} {
214 if {$stopped == 1} {
215 set stopped 0
215 set stopped 0
216 set phase "getcommits"
216 set phase "getcommits"
217 foreach id $commits {
217 foreach id $commits {
218 drawcommit $id
218 drawcommit $id
219 if {$stopped} break
219 if {$stopped} break
220 if {[clock clicks -milliseconds] >= $nextupdate} {
220 if {[clock clicks -milliseconds] >= $nextupdate} {
221 doupdate 1
221 doupdate 1
222 }
222 }
223 }
223 }
224 }
224 }
225 }
225 }
226 }
226 }
227 }
227 }
228
228
229 proc doupdate {reading} {
229 proc doupdate {reading} {
230 global commfd nextupdate numcommits ncmupdate
230 global commfd nextupdate numcommits ncmupdate
231
231
232 if {$reading} {
232 if {$reading} {
233 fileevent $commfd readable {}
233 fileevent $commfd readable {}
234 }
234 }
235 update
235 update
236 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
236 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
237 if {$numcommits < 100} {
237 if {$numcommits < 100} {
238 set ncmupdate [expr {$numcommits + 1}]
238 set ncmupdate [expr {$numcommits + 1}]
239 } elseif {$numcommits < 10000} {
239 } elseif {$numcommits < 10000} {
240 set ncmupdate [expr {$numcommits + 10}]
240 set ncmupdate [expr {$numcommits + 10}]
241 } else {
241 } else {
242 set ncmupdate [expr {$numcommits + 100}]
242 set ncmupdate [expr {$numcommits + 100}]
243 }
243 }
244 if {$reading} {
244 if {$reading} {
245 fileevent $commfd readable [list getcommitlines $commfd]
245 fileevent $commfd readable [list getcommitlines $commfd]
246 }
246 }
247 }
247 }
248
248
249 proc readcommit {id} {
249 proc readcommit {id} {
250 global env
250 global env
251 if [catch {set contents [exec $env(HG) --config ui.report_untrusted=false debug-cat-file commit $id]}] return
251 if [catch {set contents [exec $env(HG) --config ui.report_untrusted=false debug-cat-file commit $id]}] return
252 parsecommit $id $contents 0 {}
252 parsecommit $id $contents 0 {}
253 }
253 }
254
254
255 proc parsecommit {id contents listed olds} {
255 proc parsecommit {id contents listed olds} {
256 global commitinfo children nchildren parents nparents cdate ncleft
256 global commitinfo children nchildren parents nparents cdate ncleft
257
257
258 set inhdr 1
258 set inhdr 1
259 set comment {}
259 set comment {}
260 set headline {}
260 set headline {}
261 set auname {}
261 set auname {}
262 set audate {}
262 set audate {}
263 set comname {}
263 set comname {}
264 set comdate {}
264 set comdate {}
265 set rev {}
265 set rev {}
266 if {![info exists nchildren($id)]} {
266 if {![info exists nchildren($id)]} {
267 set children($id) {}
267 set children($id) {}
268 set nchildren($id) 0
268 set nchildren($id) 0
269 set ncleft($id) 0
269 set ncleft($id) 0
270 }
270 }
271 set parents($id) $olds
271 set parents($id) $olds
272 set nparents($id) [llength $olds]
272 set nparents($id) [llength $olds]
273 foreach p $olds {
273 foreach p $olds {
274 if {![info exists nchildren($p)]} {
274 if {![info exists nchildren($p)]} {
275 set children($p) [list $id]
275 set children($p) [list $id]
276 set nchildren($p) 1
276 set nchildren($p) 1
277 set ncleft($p) 1
277 set ncleft($p) 1
278 } elseif {[lsearch -exact $children($p) $id] < 0} {
278 } elseif {[lsearch -exact $children($p) $id] < 0} {
279 lappend children($p) $id
279 lappend children($p) $id
280 incr nchildren($p)
280 incr nchildren($p)
281 incr ncleft($p)
281 incr ncleft($p)
282 }
282 }
283 }
283 }
284 regsub -all "\r\n" $contents "\n" contents
284 regsub -all "\r\n" $contents "\n" contents
285 foreach line [split $contents "\n"] {
285 foreach line [split $contents "\n"] {
286 if {$inhdr} {
286 if {$inhdr} {
287 set line [split $line]
287 set line [split $line]
288 if {$line == {}} {
288 if {$line == {}} {
289 set inhdr 0
289 set inhdr 0
290 } else {
290 } else {
291 set tag [lindex $line 0]
291 set tag [lindex $line 0]
292 if {$tag == "author"} {
292 if {$tag == "author"} {
293 set x [expr {[llength $line] - 2}]
293 set x [expr {[llength $line] - 2}]
294 set audate [lindex $line $x]
294 set audate [lindex $line $x]
295 set auname [join [lrange $line 1 [expr {$x - 1}]]]
295 set auname [join [lrange $line 1 [expr {$x - 1}]]]
296 } elseif {$tag == "committer"} {
296 } elseif {$tag == "committer"} {
297 set x [expr {[llength $line] - 2}]
297 set x [expr {[llength $line] - 2}]
298 set comdate [lindex $line $x]
298 set comdate [lindex $line $x]
299 set comname [join [lrange $line 1 [expr {$x - 1}]]]
299 set comname [join [lrange $line 1 [expr {$x - 1}]]]
300 } elseif {$tag == "revision"} {
300 } elseif {$tag == "revision"} {
301 set rev [lindex $line 1]
301 set rev [lindex $line 1]
302 }
302 }
303 }
303 }
304 } else {
304 } else {
305 if {$comment == {}} {
305 if {$comment == {}} {
306 set headline [string trim $line]
306 set headline [string trim $line]
307 } else {
307 } else {
308 append comment "\n"
308 append comment "\n"
309 }
309 }
310 if {!$listed} {
310 if {!$listed} {
311 # git-rev-list indents the comment by 4 spaces;
311 # git-rev-list indents the comment by 4 spaces;
312 # if we got this via git-cat-file, add the indentation
312 # if we got this via git-cat-file, add the indentation
313 append comment " "
313 append comment " "
314 }
314 }
315 append comment $line
315 append comment $line
316 }
316 }
317 }
317 }
318 if {$audate != {}} {
318 if {$audate != {}} {
319 set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
319 set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
320 }
320 }
321 if {$comdate != {}} {
321 if {$comdate != {}} {
322 set cdate($id) $comdate
322 set cdate($id) $comdate
323 set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
323 set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
324 }
324 }
325 set commitinfo($id) [list $headline $auname $audate \
325 set commitinfo($id) [list $headline $auname $audate \
326 $comname $comdate $comment $rev]
326 $comname $comdate $comment $rev]
327 }
327 }
328
328
329 proc readrefs {} {
329 proc readrefs {} {
330 global tagids idtags headids idheads tagcontents env
330 global tagids idtags headids idheads tagcontents env
331
331
332 set tags [exec $env(HG) --config ui.report_untrusted=false tags]
332 set tags [exec $env(HG) --config ui.report_untrusted=false tags]
333 regsub -all "\r\n" $tags "\n" tags
333 regsub -all "\r\n" $tags "\n" tags
334 set lines [split $tags "\n"]
334 set lines [split $tags "\n"]
335 foreach f $lines {
335 foreach f $lines {
336 regexp {(\S+)$} $f full
336 regexp {(\S+)$} $f full
337 regsub {\s+(\S+)$} $f "" direct
337 regsub {\s+(\S+)$} $f "" direct
338 set sha [split $full ':']
338 set sha [split $full ':']
339 set tag [lindex $sha 1]
339 set tag [lindex $sha 1]
340 lappend tagids($direct) $tag
340 lappend tagids($direct) $tag
341 lappend idtags($tag) $direct
341 lappend idtags($tag) $direct
342 }
342 }
343 }
343 }
344
344
345 proc readotherrefs {base dname excl} {
345 proc readotherrefs {base dname excl} {
346 global otherrefids idotherrefs
346 global otherrefids idotherrefs
347
347
348 set git [gitdir]
348 set git [gitdir]
349 set files [glob -nocomplain -types f [file join $git $base *]]
349 set files [glob -nocomplain -types f [file join $git $base *]]
350 foreach f $files {
350 foreach f $files {
351 catch {
351 catch {
352 set fd [open $f r]
352 set fd [open $f r]
353 set line [read $fd 40]
353 set line [read $fd 40]
354 if {[regexp {^[0-9a-f]{12}} $line id]} {
354 if {[regexp {^[0-9a-f]{12}} $line id]} {
355 set name "$dname[file tail $f]"
355 set name "$dname[file tail $f]"
356 set otherrefids($name) $id
356 set otherrefids($name) $id
357 lappend idotherrefs($id) $name
357 lappend idotherrefs($id) $name
358 }
358 }
359 close $fd
359 close $fd
360 }
360 }
361 }
361 }
362 set dirs [glob -nocomplain -types d [file join $git $base *]]
362 set dirs [glob -nocomplain -types d [file join $git $base *]]
363 foreach d $dirs {
363 foreach d $dirs {
364 set dir [file tail $d]
364 set dir [file tail $d]
365 if {[lsearch -exact $excl $dir] >= 0} continue
365 if {[lsearch -exact $excl $dir] >= 0} continue
366 readotherrefs [file join $base $dir] "$dname$dir/" {}
366 readotherrefs [file join $base $dir] "$dname$dir/" {}
367 }
367 }
368 }
368 }
369
369
370 proc allcansmousewheel {delta} {
370 proc allcansmousewheel {delta} {
371 set delta [expr -5*(int($delta)/abs($delta))]
371 set delta [expr -5*(int($delta)/abs($delta))]
372 allcanvs yview scroll $delta units
372 allcanvs yview scroll $delta units
373 }
373 }
374
374
375 proc error_popup msg {
375 proc error_popup msg {
376 set w .error
376 set w .error
377 toplevel $w
377 toplevel $w
378 wm transient $w .
378 wm transient $w .
379 message $w.m -text $msg -justify center -aspect 400
379 message $w.m -text $msg -justify center -aspect 400
380 pack $w.m -side top -fill x -padx 20 -pady 20
380 pack $w.m -side top -fill x -padx 20 -pady 20
381 button $w.ok -text OK -command "destroy $w"
381 button $w.ok -text OK -command "destroy $w"
382 pack $w.ok -side bottom -fill x
382 pack $w.ok -side bottom -fill x
383 bind $w <Visibility> "grab $w; focus $w"
383 bind $w <Visibility> "grab $w; focus $w"
384 tkwait window $w
384 tkwait window $w
385 }
385 }
386
386
387 proc makewindow {} {
387 proc makewindow {} {
388 global canv canv2 canv3 linespc charspc ctext cflist textfont
388 global canv canv2 canv3 linespc charspc ctext cflist textfont
389 global findtype findtypemenu findloc findstring fstring geometry
389 global findtype findtypemenu findloc findstring fstring geometry
390 global entries sha1entry sha1string sha1but
390 global entries sha1entry sha1string sha1but
391 global maincursor textcursor curtextcursor
391 global maincursor textcursor curtextcursor
392 global rowctxmenu gaudydiff mergemax
392 global rowctxmenu gaudydiff mergemax
393
393
394 menu .bar
394 menu .bar
395 .bar add cascade -label "File" -menu .bar.file
395 .bar add cascade -label "File" -menu .bar.file
396 menu .bar.file
396 menu .bar.file
397 .bar.file add command -label "Reread references" -command rereadrefs
397 .bar.file add command -label "Reread references" -command rereadrefs
398 .bar.file add command -label "Quit" -command doquit
398 .bar.file add command -label "Quit" -command doquit
399 menu .bar.help
399 menu .bar.help
400 .bar add cascade -label "Help" -menu .bar.help
400 .bar add cascade -label "Help" -menu .bar.help
401 .bar.help add command -label "About gitk" -command about
401 .bar.help add command -label "About gitk" -command about
402 . configure -menu .bar
402 . configure -menu .bar
403
403
404 if {![info exists geometry(canv1)]} {
404 if {![info exists geometry(canv1)]} {
405 set geometry(canv1) [expr 45 * $charspc]
405 set geometry(canv1) [expr 45 * $charspc]
406 set geometry(canv2) [expr 30 * $charspc]
406 set geometry(canv2) [expr 30 * $charspc]
407 set geometry(canv3) [expr 15 * $charspc]
407 set geometry(canv3) [expr 15 * $charspc]
408 set geometry(canvh) [expr 25 * $linespc + 4]
408 set geometry(canvh) [expr 25 * $linespc + 4]
409 set geometry(ctextw) 80
409 set geometry(ctextw) 80
410 set geometry(ctexth) 30
410 set geometry(ctexth) 30
411 set geometry(cflistw) 30
411 set geometry(cflistw) 30
412 }
412 }
413 panedwindow .ctop -orient vertical
413 panedwindow .ctop -orient vertical
414 if {[info exists geometry(width)]} {
414 if {[info exists geometry(width)]} {
415 .ctop conf -width $geometry(width) -height $geometry(height)
415 .ctop conf -width $geometry(width) -height $geometry(height)
416 set texth [expr {$geometry(height) - $geometry(canvh) - 56}]
416 set texth [expr {$geometry(height) - $geometry(canvh) - 56}]
417 set geometry(ctexth) [expr {($texth - 8) /
417 set geometry(ctexth) [expr {($texth - 8) /
418 [font metrics $textfont -linespace]}]
418 [font metrics $textfont -linespace]}]
419 }
419 }
420 frame .ctop.top
420 frame .ctop.top
421 frame .ctop.top.bar
421 frame .ctop.top.bar
422 pack .ctop.top.bar -side bottom -fill x
422 pack .ctop.top.bar -side bottom -fill x
423 set cscroll .ctop.top.csb
423 set cscroll .ctop.top.csb
424 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
424 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
425 pack $cscroll -side right -fill y
425 pack $cscroll -side right -fill y
426 panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
426 panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
427 pack .ctop.top.clist -side top -fill both -expand 1
427 pack .ctop.top.clist -side top -fill both -expand 1
428 .ctop add .ctop.top
428 .ctop add .ctop.top
429 set canv .ctop.top.clist.canv
429 set canv .ctop.top.clist.canv
430 canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
430 canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
431 -bg white -bd 0 \
431 -bg white -bd 0 \
432 -yscrollincr $linespc -yscrollcommand "$cscroll set" -selectbackground grey
432 -yscrollincr $linespc -yscrollcommand "$cscroll set" -selectbackground grey
433 .ctop.top.clist add $canv
433 .ctop.top.clist add $canv
434 set canv2 .ctop.top.clist.canv2
434 set canv2 .ctop.top.clist.canv2
435 canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
435 canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
436 -bg white -bd 0 -yscrollincr $linespc -selectbackground grey
436 -bg white -bd 0 -yscrollincr $linespc -selectbackground grey
437 .ctop.top.clist add $canv2
437 .ctop.top.clist add $canv2
438 set canv3 .ctop.top.clist.canv3
438 set canv3 .ctop.top.clist.canv3
439 canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
439 canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
440 -bg white -bd 0 -yscrollincr $linespc -selectbackground grey
440 -bg white -bd 0 -yscrollincr $linespc -selectbackground grey
441 .ctop.top.clist add $canv3
441 .ctop.top.clist add $canv3
442 bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
442 bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
443
443
444 set sha1entry .ctop.top.bar.sha1
444 set sha1entry .ctop.top.bar.sha1
445 set entries $sha1entry
445 set entries $sha1entry
446 set sha1but .ctop.top.bar.sha1label
446 set sha1but .ctop.top.bar.sha1label
447 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
447 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
448 -command gotocommit -width 8
448 -command gotocommit -width 8
449 $sha1but conf -disabledforeground [$sha1but cget -foreground]
449 $sha1but conf -disabledforeground [$sha1but cget -foreground]
450 pack .ctop.top.bar.sha1label -side left
450 pack .ctop.top.bar.sha1label -side left
451 entry $sha1entry -width 40 -font $textfont -textvariable sha1string
451 entry $sha1entry -width 40 -font $textfont -textvariable sha1string
452 trace add variable sha1string write sha1change
452 trace add variable sha1string write sha1change
453 pack $sha1entry -side left -pady 2
453 pack $sha1entry -side left -pady 2
454
454
455 image create bitmap bm-left -data {
455 image create bitmap bm-left -data {
456 #define left_width 16
456 #define left_width 16
457 #define left_height 16
457 #define left_height 16
458 static unsigned char left_bits[] = {
458 static unsigned char left_bits[] = {
459 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
459 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
460 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
460 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
461 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
461 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
462 }
462 }
463 image create bitmap bm-right -data {
463 image create bitmap bm-right -data {
464 #define right_width 16
464 #define right_width 16
465 #define right_height 16
465 #define right_height 16
466 static unsigned char right_bits[] = {
466 static unsigned char right_bits[] = {
467 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
467 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
468 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
468 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
469 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
469 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
470 }
470 }
471 button .ctop.top.bar.leftbut -image bm-left -command goback \
471 button .ctop.top.bar.leftbut -image bm-left -command goback \
472 -state disabled -width 26
472 -state disabled -width 26
473 pack .ctop.top.bar.leftbut -side left -fill y
473 pack .ctop.top.bar.leftbut -side left -fill y
474 button .ctop.top.bar.rightbut -image bm-right -command goforw \
474 button .ctop.top.bar.rightbut -image bm-right -command goforw \
475 -state disabled -width 26
475 -state disabled -width 26
476 pack .ctop.top.bar.rightbut -side left -fill y
476 pack .ctop.top.bar.rightbut -side left -fill y
477
477
478 button .ctop.top.bar.findbut -text "Find" -command dofind
478 button .ctop.top.bar.findbut -text "Find" -command dofind
479 pack .ctop.top.bar.findbut -side left
479 pack .ctop.top.bar.findbut -side left
480 set findstring {}
480 set findstring {}
481 set fstring .ctop.top.bar.findstring
481 set fstring .ctop.top.bar.findstring
482 lappend entries $fstring
482 lappend entries $fstring
483 entry $fstring -width 30 -font $textfont -textvariable findstring
483 entry $fstring -width 30 -font $textfont -textvariable findstring
484 pack $fstring -side left -expand 1 -fill x
484 pack $fstring -side left -expand 1 -fill x
485 set findtype Exact
485 set findtype Exact
486 set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \
486 set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \
487 findtype Exact IgnCase Regexp]
487 findtype Exact IgnCase Regexp]
488 set findloc "All fields"
488 set findloc "All fields"
489 tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
489 tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
490 Comments Author Committer Files Pickaxe
490 Comments Author Committer Files Pickaxe
491 pack .ctop.top.bar.findloc -side right
491 pack .ctop.top.bar.findloc -side right
492 pack .ctop.top.bar.findtype -side right
492 pack .ctop.top.bar.findtype -side right
493 # for making sure type==Exact whenever loc==Pickaxe
493 # for making sure type==Exact whenever loc==Pickaxe
494 trace add variable findloc write findlocchange
494 trace add variable findloc write findlocchange
495
495
496 panedwindow .ctop.cdet -orient horizontal
496 panedwindow .ctop.cdet -orient horizontal
497 .ctop add .ctop.cdet
497 .ctop add .ctop.cdet
498 frame .ctop.cdet.left
498 frame .ctop.cdet.left
499 set ctext .ctop.cdet.left.ctext
499 set ctext .ctop.cdet.left.ctext
500 text $ctext -bg white -state disabled -font $textfont \
500 text $ctext -bg white -state disabled -font $textfont \
501 -width $geometry(ctextw) -height $geometry(ctexth) \
501 -width $geometry(ctextw) -height $geometry(ctexth) \
502 -yscrollcommand ".ctop.cdet.left.sb set" \
502 -yscrollcommand ".ctop.cdet.left.sb set" \
503 -xscrollcommand ".ctop.cdet.left.hb set" -wrap none
503 -xscrollcommand ".ctop.cdet.left.hb set" -wrap none
504 scrollbar .ctop.cdet.left.sb -command "$ctext yview"
504 scrollbar .ctop.cdet.left.sb -command "$ctext yview"
505 scrollbar .ctop.cdet.left.hb -orient horizontal -command "$ctext xview"
505 scrollbar .ctop.cdet.left.hb -orient horizontal -command "$ctext xview"
506 pack .ctop.cdet.left.sb -side right -fill y
506 pack .ctop.cdet.left.sb -side right -fill y
507 pack .ctop.cdet.left.hb -side bottom -fill x
507 pack .ctop.cdet.left.hb -side bottom -fill x
508 pack $ctext -side left -fill both -expand 1
508 pack $ctext -side left -fill both -expand 1
509 .ctop.cdet add .ctop.cdet.left
509 .ctop.cdet add .ctop.cdet.left
510
510
511 $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
511 $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
512 if {$gaudydiff} {
512 if {$gaudydiff} {
513 $ctext tag conf hunksep -back blue -fore white
513 $ctext tag conf hunksep -back blue -fore white
514 $ctext tag conf d0 -back "#ff8080"
514 $ctext tag conf d0 -back "#ff8080"
515 $ctext tag conf d1 -back green
515 $ctext tag conf d1 -back green
516 } else {
516 } else {
517 $ctext tag conf hunksep -fore blue
517 $ctext tag conf hunksep -fore blue
518 $ctext tag conf d0 -fore red
518 $ctext tag conf d0 -fore red
519 $ctext tag conf d1 -fore "#00a000"
519 $ctext tag conf d1 -fore "#00a000"
520 $ctext tag conf m0 -fore red
520 $ctext tag conf m0 -fore red
521 $ctext tag conf m1 -fore blue
521 $ctext tag conf m1 -fore blue
522 $ctext tag conf m2 -fore green
522 $ctext tag conf m2 -fore green
523 $ctext tag conf m3 -fore purple
523 $ctext tag conf m3 -fore purple
524 $ctext tag conf m4 -fore brown
524 $ctext tag conf m4 -fore brown
525 $ctext tag conf mmax -fore darkgrey
525 $ctext tag conf mmax -fore darkgrey
526 set mergemax 5
526 set mergemax 5
527 $ctext tag conf mresult -font [concat $textfont bold]
527 $ctext tag conf mresult -font [concat $textfont bold]
528 $ctext tag conf msep -font [concat $textfont bold]
528 $ctext tag conf msep -font [concat $textfont bold]
529 $ctext tag conf found -back yellow
529 $ctext tag conf found -back yellow
530 }
530 }
531
531
532 frame .ctop.cdet.right
532 frame .ctop.cdet.right
533 set cflist .ctop.cdet.right.cfiles
533 set cflist .ctop.cdet.right.cfiles
534 listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
534 listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
535 -yscrollcommand ".ctop.cdet.right.sb set"
535 -yscrollcommand ".ctop.cdet.right.sb set"
536 scrollbar .ctop.cdet.right.sb -command "$cflist yview"
536 scrollbar .ctop.cdet.right.sb -command "$cflist yview"
537 pack .ctop.cdet.right.sb -side right -fill y
537 pack .ctop.cdet.right.sb -side right -fill y
538 pack $cflist -side left -fill both -expand 1
538 pack $cflist -side left -fill both -expand 1
539 .ctop.cdet add .ctop.cdet.right
539 .ctop.cdet add .ctop.cdet.right
540 bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
540 bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
541
541
542 pack .ctop -side top -fill both -expand 1
542 pack .ctop -side top -fill both -expand 1
543
543
544 bindall <1> {selcanvline %W %x %y}
544 bindall <1> {selcanvline %W %x %y}
545 #bindall <B1-Motion> {selcanvline %W %x %y}
545 #bindall <B1-Motion> {selcanvline %W %x %y}
546 bindall <MouseWheel> "allcansmousewheel %D"
546 bindall <MouseWheel> "allcansmousewheel %D"
547 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
547 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
548 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
548 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
549 bindall <2> "allcanvs scan mark 0 %y"
549 bindall <2> "allcanvs scan mark 0 %y"
550 bindall <B2-Motion> "allcanvs scan dragto 0 %y"
550 bindall <B2-Motion> "allcanvs scan dragto 0 %y"
551 bind . <Key-Up> "selnextline -1"
551 bind . <Key-Up> "selnextline -1"
552 bind . <Key-Down> "selnextline 1"
552 bind . <Key-Down> "selnextline 1"
553 bind . <Key-Prior> "allcanvs yview scroll -1 pages"
553 bind . <Key-Prior> "allcanvs yview scroll -1 pages"
554 bind . <Key-Next> "allcanvs yview scroll 1 pages"
554 bind . <Key-Next> "allcanvs yview scroll 1 pages"
555 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
555 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
556 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
556 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
557 bindkey <Key-space> "$ctext yview scroll 1 pages"
557 bindkey <Key-space> "$ctext yview scroll 1 pages"
558 bindkey p "selnextline -1"
558 bindkey p "selnextline -1"
559 bindkey n "selnextline 1"
559 bindkey n "selnextline 1"
560 bindkey b "$ctext yview scroll -1 pages"
560 bindkey b "$ctext yview scroll -1 pages"
561 bindkey d "$ctext yview scroll 18 units"
561 bindkey d "$ctext yview scroll 18 units"
562 bindkey u "$ctext yview scroll -18 units"
562 bindkey u "$ctext yview scroll -18 units"
563 bindkey / {findnext 1}
563 bindkey / {findnext 1}
564 bindkey <Key-Return> {findnext 0}
564 bindkey <Key-Return> {findnext 0}
565 bindkey ? findprev
565 bindkey ? findprev
566 bindkey f nextfile
566 bindkey f nextfile
567 bind . <Control-q> doquit
567 bind . <Control-q> doquit
568 bind . <Control-w> doquit
568 bind . <Control-w> doquit
569 bind . <Control-f> dofind
569 bind . <Control-f> dofind
570 bind . <Control-g> {findnext 0}
570 bind . <Control-g> {findnext 0}
571 bind . <Control-r> findprev
571 bind . <Control-r> findprev
572 bind . <Control-equal> {incrfont 1}
572 bind . <Control-equal> {incrfont 1}
573 bind . <Control-KP_Add> {incrfont 1}
573 bind . <Control-KP_Add> {incrfont 1}
574 bind . <Control-minus> {incrfont -1}
574 bind . <Control-minus> {incrfont -1}
575 bind . <Control-KP_Subtract> {incrfont -1}
575 bind . <Control-KP_Subtract> {incrfont -1}
576 bind $cflist <<ListboxSelect>> listboxsel
576 bind $cflist <<ListboxSelect>> listboxsel
577 bind . <Destroy> {savestuff %W}
577 bind . <Destroy> {savestuff %W}
578 bind . <Button-1> "click %W"
578 bind . <Button-1> "click %W"
579 bind $fstring <Key-Return> dofind
579 bind $fstring <Key-Return> dofind
580 bind $sha1entry <Key-Return> gotocommit
580 bind $sha1entry <Key-Return> gotocommit
581 bind $sha1entry <<PasteSelection>> clearsha1
581 bind $sha1entry <<PasteSelection>> clearsha1
582
582
583 set maincursor [. cget -cursor]
583 set maincursor [. cget -cursor]
584 set textcursor [$ctext cget -cursor]
584 set textcursor [$ctext cget -cursor]
585 set curtextcursor $textcursor
585 set curtextcursor $textcursor
586
586
587 set rowctxmenu .rowctxmenu
587 set rowctxmenu .rowctxmenu
588 menu $rowctxmenu -tearoff 0
588 menu $rowctxmenu -tearoff 0
589 $rowctxmenu add command -label "Diff this -> selected" \
589 $rowctxmenu add command -label "Diff this -> selected" \
590 -command {diffvssel 0}
590 -command {diffvssel 0}
591 $rowctxmenu add command -label "Diff selected -> this" \
591 $rowctxmenu add command -label "Diff selected -> this" \
592 -command {diffvssel 1}
592 -command {diffvssel 1}
593 $rowctxmenu add command -label "Make patch" -command mkpatch
593 $rowctxmenu add command -label "Make patch" -command mkpatch
594 $rowctxmenu add command -label "Create tag" -command mktag
594 $rowctxmenu add command -label "Create tag" -command mktag
595 $rowctxmenu add command -label "Write commit to file" -command writecommit
595 $rowctxmenu add command -label "Write commit to file" -command writecommit
596 }
596 }
597
597
598 # when we make a key binding for the toplevel, make sure
598 # when we make a key binding for the toplevel, make sure
599 # it doesn't get triggered when that key is pressed in the
599 # it doesn't get triggered when that key is pressed in the
600 # find string entry widget.
600 # find string entry widget.
601 proc bindkey {ev script} {
601 proc bindkey {ev script} {
602 global entries
602 global entries
603 bind . $ev $script
603 bind . $ev $script
604 set escript [bind Entry $ev]
604 set escript [bind Entry $ev]
605 if {$escript == {}} {
605 if {$escript == {}} {
606 set escript [bind Entry <Key>]
606 set escript [bind Entry <Key>]
607 }
607 }
608 foreach e $entries {
608 foreach e $entries {
609 bind $e $ev "$escript; break"
609 bind $e $ev "$escript; break"
610 }
610 }
611 }
611 }
612
612
613 # set the focus back to the toplevel for any click outside
613 # set the focus back to the toplevel for any click outside
614 # the entry widgets
614 # the entry widgets
615 proc click {w} {
615 proc click {w} {
616 global entries
616 global entries
617 foreach e $entries {
617 foreach e $entries {
618 if {$w == $e} return
618 if {$w == $e} return
619 }
619 }
620 focus .
620 focus .
621 }
621 }
622
622
623 proc savestuff {w} {
623 proc savestuff {w} {
624 global canv canv2 canv3 ctext cflist mainfont textfont
624 global canv canv2 canv3 ctext cflist mainfont textfont
625 global stuffsaved findmergefiles gaudydiff maxgraphpct
625 global stuffsaved findmergefiles gaudydiff maxgraphpct
626 global maxwidth
626 global maxwidth
627
627
628 if {$stuffsaved} return
628 if {$stuffsaved} return
629 if {![winfo viewable .]} return
629 if {![winfo viewable .]} return
630 catch {
630 catch {
631 set f [open "~/.gitk-new" w]
631 set f [open "~/.gitk-new" w]
632 puts $f [list set mainfont $mainfont]
632 puts $f [list set mainfont $mainfont]
633 puts $f [list set textfont $textfont]
633 puts $f [list set textfont $textfont]
634 puts $f [list set findmergefiles $findmergefiles]
634 puts $f [list set findmergefiles $findmergefiles]
635 puts $f [list set gaudydiff $gaudydiff]
635 puts $f [list set gaudydiff $gaudydiff]
636 puts $f [list set maxgraphpct $maxgraphpct]
636 puts $f [list set maxgraphpct $maxgraphpct]
637 puts $f [list set maxwidth $maxwidth]
637 puts $f [list set maxwidth $maxwidth]
638 puts $f "set geometry(width) [winfo width .ctop]"
638 puts $f "set geometry(width) [winfo width .ctop]"
639 puts $f "set geometry(height) [winfo height .ctop]"
639 puts $f "set geometry(height) [winfo height .ctop]"
640 puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
640 puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
641 puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
641 puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
642 puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
642 puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
643 puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
643 puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
644 set wid [expr {([winfo width $ctext] - 8) \
644 set wid [expr {([winfo width $ctext] - 8) \
645 / [font measure $textfont "0"]}]
645 / [font measure $textfont "0"]}]
646 puts $f "set geometry(ctextw) $wid"
646 puts $f "set geometry(ctextw) $wid"
647 set wid [expr {([winfo width $cflist] - 11) \
647 set wid [expr {([winfo width $cflist] - 11) \
648 / [font measure [$cflist cget -font] "0"]}]
648 / [font measure [$cflist cget -font] "0"]}]
649 puts $f "set geometry(cflistw) $wid"
649 puts $f "set geometry(cflistw) $wid"
650 close $f
650 close $f
651 file rename -force "~/.gitk-new" "~/.gitk"
651 file rename -force "~/.gitk-new" "~/.gitk"
652 }
652 }
653 set stuffsaved 1
653 set stuffsaved 1
654 }
654 }
655
655
656 proc resizeclistpanes {win w} {
656 proc resizeclistpanes {win w} {
657 global oldwidth
657 global oldwidth
658 if [info exists oldwidth($win)] {
658 if [info exists oldwidth($win)] {
659 set s0 [$win sash coord 0]
659 set s0 [$win sash coord 0]
660 set s1 [$win sash coord 1]
660 set s1 [$win sash coord 1]
661 if {$w < 60} {
661 if {$w < 60} {
662 set sash0 [expr {int($w/2 - 2)}]
662 set sash0 [expr {int($w/2 - 2)}]
663 set sash1 [expr {int($w*5/6 - 2)}]
663 set sash1 [expr {int($w*5/6 - 2)}]
664 } else {
664 } else {
665 set factor [expr {1.0 * $w / $oldwidth($win)}]
665 set factor [expr {1.0 * $w / $oldwidth($win)}]
666 set sash0 [expr {int($factor * [lindex $s0 0])}]
666 set sash0 [expr {int($factor * [lindex $s0 0])}]
667 set sash1 [expr {int($factor * [lindex $s1 0])}]
667 set sash1 [expr {int($factor * [lindex $s1 0])}]
668 if {$sash0 < 30} {
668 if {$sash0 < 30} {
669 set sash0 30
669 set sash0 30
670 }
670 }
671 if {$sash1 < $sash0 + 20} {
671 if {$sash1 < $sash0 + 20} {
672 set sash1 [expr $sash0 + 20]
672 set sash1 [expr $sash0 + 20]
673 }
673 }
674 if {$sash1 > $w - 10} {
674 if {$sash1 > $w - 10} {
675 set sash1 [expr $w - 10]
675 set sash1 [expr $w - 10]
676 if {$sash0 > $sash1 - 20} {
676 if {$sash0 > $sash1 - 20} {
677 set sash0 [expr $sash1 - 20]
677 set sash0 [expr $sash1 - 20]
678 }
678 }
679 }
679 }
680 }
680 }
681 $win sash place 0 $sash0 [lindex $s0 1]
681 $win sash place 0 $sash0 [lindex $s0 1]
682 $win sash place 1 $sash1 [lindex $s1 1]
682 $win sash place 1 $sash1 [lindex $s1 1]
683 }
683 }
684 set oldwidth($win) $w
684 set oldwidth($win) $w
685 }
685 }
686
686
687 proc resizecdetpanes {win w} {
687 proc resizecdetpanes {win w} {
688 global oldwidth
688 global oldwidth
689 if [info exists oldwidth($win)] {
689 if [info exists oldwidth($win)] {
690 set s0 [$win sash coord 0]
690 set s0 [$win sash coord 0]
691 if {$w < 60} {
691 if {$w < 60} {
692 set sash0 [expr {int($w*3/4 - 2)}]
692 set sash0 [expr {int($w*3/4 - 2)}]
693 } else {
693 } else {
694 set factor [expr {1.0 * $w / $oldwidth($win)}]
694 set factor [expr {1.0 * $w / $oldwidth($win)}]
695 set sash0 [expr {int($factor * [lindex $s0 0])}]
695 set sash0 [expr {int($factor * [lindex $s0 0])}]
696 if {$sash0 < 45} {
696 if {$sash0 < 45} {
697 set sash0 45
697 set sash0 45
698 }
698 }
699 if {$sash0 > $w - 15} {
699 if {$sash0 > $w - 15} {
700 set sash0 [expr $w - 15]
700 set sash0 [expr $w - 15]
701 }
701 }
702 }
702 }
703 $win sash place 0 $sash0 [lindex $s0 1]
703 $win sash place 0 $sash0 [lindex $s0 1]
704 }
704 }
705 set oldwidth($win) $w
705 set oldwidth($win) $w
706 }
706 }
707
707
708 proc allcanvs args {
708 proc allcanvs args {
709 global canv canv2 canv3
709 global canv canv2 canv3
710 eval $canv $args
710 eval $canv $args
711 eval $canv2 $args
711 eval $canv2 $args
712 eval $canv3 $args
712 eval $canv3 $args
713 }
713 }
714
714
715 proc bindall {event action} {
715 proc bindall {event action} {
716 global canv canv2 canv3
716 global canv canv2 canv3
717 bind $canv $event $action
717 bind $canv $event $action
718 bind $canv2 $event $action
718 bind $canv2 $event $action
719 bind $canv3 $event $action
719 bind $canv3 $event $action
720 }
720 }
721
721
722 proc about {} {
722 proc about {} {
723 set w .about
723 set w .about
724 if {[winfo exists $w]} {
724 if {[winfo exists $w]} {
725 raise $w
725 raise $w
726 return
726 return
727 }
727 }
728 toplevel $w
728 toplevel $w
729 wm title $w "About gitk"
729 wm title $w "About gitk"
730 message $w.m -text {
730 message $w.m -text {
731 Gitk version 1.2
731 Gitk version 1.2
732
732
733 Copyright οΏ½ 2005 Paul Mackerras
733 Copyright οΏ½ 2005 Paul Mackerras
734
734
735 Use and redistribute under the terms of the GNU General Public License} \
735 Use and redistribute under the terms of the GNU General Public License} \
736 -justify center -aspect 400
736 -justify center -aspect 400
737 pack $w.m -side top -fill x -padx 20 -pady 20
737 pack $w.m -side top -fill x -padx 20 -pady 20
738 button $w.ok -text Close -command "destroy $w"
738 button $w.ok -text Close -command "destroy $w"
739 pack $w.ok -side bottom
739 pack $w.ok -side bottom
740 }
740 }
741
741
742 proc assigncolor {id} {
742 proc assigncolor {id} {
743 global commitinfo colormap commcolors colors nextcolor
743 global commitinfo colormap commcolors colors nextcolor
744 global parents nparents children nchildren
744 global parents nparents children nchildren
745 global cornercrossings crossings
745 global cornercrossings crossings
746
746
747 if [info exists colormap($id)] return
747 if [info exists colormap($id)] return
748 set ncolors [llength $colors]
748 set ncolors [llength $colors]
749 if {$nparents($id) <= 1 && $nchildren($id) == 1} {
749 if {$nparents($id) <= 1 && $nchildren($id) == 1} {
750 set child [lindex $children($id) 0]
750 set child [lindex $children($id) 0]
751 if {[info exists colormap($child)]
751 if {[info exists colormap($child)]
752 && $nparents($child) == 1} {
752 && $nparents($child) == 1} {
753 set colormap($id) $colormap($child)
753 set colormap($id) $colormap($child)
754 return
754 return
755 }
755 }
756 }
756 }
757 set badcolors {}
757 set badcolors {}
758 if {[info exists cornercrossings($id)]} {
758 if {[info exists cornercrossings($id)]} {
759 foreach x $cornercrossings($id) {
759 foreach x $cornercrossings($id) {
760 if {[info exists colormap($x)]
760 if {[info exists colormap($x)]
761 && [lsearch -exact $badcolors $colormap($x)] < 0} {
761 && [lsearch -exact $badcolors $colormap($x)] < 0} {
762 lappend badcolors $colormap($x)
762 lappend badcolors $colormap($x)
763 }
763 }
764 }
764 }
765 if {[llength $badcolors] >= $ncolors} {
765 if {[llength $badcolors] >= $ncolors} {
766 set badcolors {}
766 set badcolors {}
767 }
767 }
768 }
768 }
769 set origbad $badcolors
769 set origbad $badcolors
770 if {[llength $badcolors] < $ncolors - 1} {
770 if {[llength $badcolors] < $ncolors - 1} {
771 if {[info exists crossings($id)]} {
771 if {[info exists crossings($id)]} {
772 foreach x $crossings($id) {
772 foreach x $crossings($id) {
773 if {[info exists colormap($x)]
773 if {[info exists colormap($x)]
774 && [lsearch -exact $badcolors $colormap($x)] < 0} {
774 && [lsearch -exact $badcolors $colormap($x)] < 0} {
775 lappend badcolors $colormap($x)
775 lappend badcolors $colormap($x)
776 }
776 }
777 }
777 }
778 if {[llength $badcolors] >= $ncolors} {
778 if {[llength $badcolors] >= $ncolors} {
779 set badcolors $origbad
779 set badcolors $origbad
780 }
780 }
781 }
781 }
782 set origbad $badcolors
782 set origbad $badcolors
783 }
783 }
784 if {[llength $badcolors] < $ncolors - 1} {
784 if {[llength $badcolors] < $ncolors - 1} {
785 foreach child $children($id) {
785 foreach child $children($id) {
786 if {[info exists colormap($child)]
786 if {[info exists colormap($child)]
787 && [lsearch -exact $badcolors $colormap($child)] < 0} {
787 && [lsearch -exact $badcolors $colormap($child)] < 0} {
788 lappend badcolors $colormap($child)
788 lappend badcolors $colormap($child)
789 }
789 }
790 if {[info exists parents($child)]} {
790 if {[info exists parents($child)]} {
791 foreach p $parents($child) {
791 foreach p $parents($child) {
792 if {[info exists colormap($p)]
792 if {[info exists colormap($p)]
793 && [lsearch -exact $badcolors $colormap($p)] < 0} {
793 && [lsearch -exact $badcolors $colormap($p)] < 0} {
794 lappend badcolors $colormap($p)
794 lappend badcolors $colormap($p)
795 }
795 }
796 }
796 }
797 }
797 }
798 }
798 }
799 if {[llength $badcolors] >= $ncolors} {
799 if {[llength $badcolors] >= $ncolors} {
800 set badcolors $origbad
800 set badcolors $origbad
801 }
801 }
802 }
802 }
803 for {set i 0} {$i <= $ncolors} {incr i} {
803 for {set i 0} {$i <= $ncolors} {incr i} {
804 set c [lindex $colors $nextcolor]
804 set c [lindex $colors $nextcolor]
805 if {[incr nextcolor] >= $ncolors} {
805 if {[incr nextcolor] >= $ncolors} {
806 set nextcolor 0
806 set nextcolor 0
807 }
807 }
808 if {[lsearch -exact $badcolors $c]} break
808 if {[lsearch -exact $badcolors $c]} break
809 }
809 }
810 set colormap($id) $c
810 set colormap($id) $c
811 }
811 }
812
812
813 proc initgraph {} {
813 proc initgraph {} {
814 global canvy canvy0 lineno numcommits nextcolor linespc
814 global canvy canvy0 lineno numcommits nextcolor linespc
815 global mainline mainlinearrow sidelines
815 global mainline mainlinearrow sidelines
816 global nchildren ncleft
816 global nchildren ncleft
817 global displist nhyperspace
817 global displist nhyperspace
818
818
819 allcanvs delete all
819 allcanvs delete all
820 set nextcolor 0
820 set nextcolor 0
821 set canvy $canvy0
821 set canvy $canvy0
822 set lineno -1
822 set lineno -1
823 set numcommits 0
823 set numcommits 0
824 catch {unset mainline}
824 catch {unset mainline}
825 catch {unset mainlinearrow}
825 catch {unset mainlinearrow}
826 catch {unset sidelines}
826 catch {unset sidelines}
827 foreach id [array names nchildren] {
827 foreach id [array names nchildren] {
828 set ncleft($id) $nchildren($id)
828 set ncleft($id) $nchildren($id)
829 }
829 }
830 set displist {}
830 set displist {}
831 set nhyperspace 0
831 set nhyperspace 0
832 }
832 }
833
833
834 proc bindline {t id} {
834 proc bindline {t id} {
835 global canv
835 global canv
836
836
837 $canv bind $t <Enter> "lineenter %x %y $id"
837 $canv bind $t <Enter> "lineenter %x %y $id"
838 $canv bind $t <Motion> "linemotion %x %y $id"
838 $canv bind $t <Motion> "linemotion %x %y $id"
839 $canv bind $t <Leave> "lineleave $id"
839 $canv bind $t <Leave> "lineleave $id"
840 $canv bind $t <Button-1> "lineclick %x %y $id 1"
840 $canv bind $t <Button-1> "lineclick %x %y $id 1"
841 }
841 }
842
842
843 proc drawlines {id xtra} {
843 proc drawlines {id xtra} {
844 global mainline mainlinearrow sidelines lthickness colormap canv
844 global mainline mainlinearrow sidelines lthickness colormap canv
845
845
846 $canv delete lines.$id
846 $canv delete lines.$id
847 if {[info exists mainline($id)]} {
847 if {[info exists mainline($id)]} {
848 set t [$canv create line $mainline($id) \
848 set t [$canv create line $mainline($id) \
849 -width [expr {($xtra + 1) * $lthickness}] \
849 -width [expr {($xtra + 1) * $lthickness}] \
850 -fill $colormap($id) -tags lines.$id \
850 -fill $colormap($id) -tags lines.$id \
851 -arrow $mainlinearrow($id)]
851 -arrow $mainlinearrow($id)]
852 $canv lower $t
852 $canv lower $t
853 bindline $t $id
853 bindline $t $id
854 }
854 }
855 if {[info exists sidelines($id)]} {
855 if {[info exists sidelines($id)]} {
856 foreach ls $sidelines($id) {
856 foreach ls $sidelines($id) {
857 set coords [lindex $ls 0]
857 set coords [lindex $ls 0]
858 set thick [lindex $ls 1]
858 set thick [lindex $ls 1]
859 set arrow [lindex $ls 2]
859 set arrow [lindex $ls 2]
860 set t [$canv create line $coords -fill $colormap($id) \
860 set t [$canv create line $coords -fill $colormap($id) \
861 -width [expr {($thick + $xtra) * $lthickness}] \
861 -width [expr {($thick + $xtra) * $lthickness}] \
862 -arrow $arrow -tags lines.$id]
862 -arrow $arrow -tags lines.$id]
863 $canv lower $t
863 $canv lower $t
864 bindline $t $id
864 bindline $t $id
865 }
865 }
866 }
866 }
867 }
867 }
868
868
869 # level here is an index in displist
869 # level here is an index in displist
870 proc drawcommitline {level} {
870 proc drawcommitline {level} {
871 global parents children nparents displist
871 global parents children nparents displist
872 global canv canv2 canv3 mainfont namefont canvy linespc
872 global canv canv2 canv3 mainfont namefont canvy linespc
873 global lineid linehtag linentag linedtag commitinfo
873 global lineid linehtag linentag linedtag commitinfo
874 global colormap numcommits currentparents dupparents
874 global colormap numcommits currentparents dupparents
875 global idtags idline idheads idotherrefs
875 global idtags idline idheads idotherrefs
876 global lineno lthickness mainline mainlinearrow sidelines
876 global lineno lthickness mainline mainlinearrow sidelines
877 global commitlisted rowtextx idpos lastuse displist
877 global commitlisted rowtextx idpos lastuse displist
878 global oldnlines olddlevel olddisplist
878 global oldnlines olddlevel olddisplist
879
879
880 incr numcommits
880 incr numcommits
881 incr lineno
881 incr lineno
882 set id [lindex $displist $level]
882 set id [lindex $displist $level]
883 set lastuse($id) $lineno
883 set lastuse($id) $lineno
884 set lineid($lineno) $id
884 set lineid($lineno) $id
885 set idline($id) $lineno
885 set idline($id) $lineno
886 set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
886 set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
887 if {![info exists commitinfo($id)]} {
887 if {![info exists commitinfo($id)]} {
888 readcommit $id
888 readcommit $id
889 if {![info exists commitinfo($id)]} {
889 if {![info exists commitinfo($id)]} {
890 set commitinfo($id) {"No commit information available"}
890 set commitinfo($id) {"No commit information available"}
891 set nparents($id) 0
891 set nparents($id) 0
892 }
892 }
893 }
893 }
894 assigncolor $id
894 assigncolor $id
895 set currentparents {}
895 set currentparents {}
896 set dupparents {}
896 set dupparents {}
897 if {[info exists commitlisted($id)] && [info exists parents($id)]} {
897 if {[info exists commitlisted($id)] && [info exists parents($id)]} {
898 foreach p $parents($id) {
898 foreach p $parents($id) {
899 if {[lsearch -exact $currentparents $p] < 0} {
899 if {[lsearch -exact $currentparents $p] < 0} {
900 lappend currentparents $p
900 lappend currentparents $p
901 } else {
901 } else {
902 # remember that this parent was listed twice
902 # remember that this parent was listed twice
903 lappend dupparents $p
903 lappend dupparents $p
904 }
904 }
905 }
905 }
906 }
906 }
907 set x [xcoord $level $level $lineno]
907 set x [xcoord $level $level $lineno]
908 set y1 $canvy
908 set y1 $canvy
909 set canvy [expr $canvy + $linespc]
909 set canvy [expr $canvy + $linespc]
910 allcanvs conf -scrollregion \
910 allcanvs conf -scrollregion \
911 [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
911 [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
912 if {[info exists mainline($id)]} {
912 if {[info exists mainline($id)]} {
913 lappend mainline($id) $x $y1
913 lappend mainline($id) $x $y1
914 if {$mainlinearrow($id) ne "none"} {
914 if {$mainlinearrow($id) ne "none"} {
915 set mainline($id) [trimdiagstart $mainline($id)]
915 set mainline($id) [trimdiagstart $mainline($id)]
916 }
916 }
917 }
917 }
918 drawlines $id 0
918 drawlines $id 0
919 set orad [expr {$linespc / 3}]
919 set orad [expr {$linespc / 3}]
920 set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
920 set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
921 [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
921 [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
922 -fill $ofill -outline black -width 1]
922 -fill $ofill -outline black -width 1]
923 $canv raise $t
923 $canv raise $t
924 $canv bind $t <1> {selcanvline {} %x %y}
924 $canv bind $t <1> {selcanvline {} %x %y}
925 set xt [xcoord [llength $displist] $level $lineno]
925 set xt [xcoord [llength $displist] $level $lineno]
926 if {[llength $currentparents] > 2} {
926 if {[llength $currentparents] > 2} {
927 set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
927 set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
928 }
928 }
929 set rowtextx($lineno) $xt
929 set rowtextx($lineno) $xt
930 set idpos($id) [list $x $xt $y1]
930 set idpos($id) [list $x $xt $y1]
931 if {[info exists idtags($id)] || [info exists idheads($id)]
931 if {[info exists idtags($id)] || [info exists idheads($id)]
932 || [info exists idotherrefs($id)]} {
932 || [info exists idotherrefs($id)]} {
933 set xt [drawtags $id $x $xt $y1]
933 set xt [drawtags $id $x $xt $y1]
934 }
934 }
935 set headline [lindex $commitinfo($id) 0]
935 set headline [lindex $commitinfo($id) 0]
936 set name [lindex $commitinfo($id) 1]
936 set name [lindex $commitinfo($id) 1]
937 set date [lindex $commitinfo($id) 2]
937 set date [lindex $commitinfo($id) 2]
938 set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
938 set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
939 -text $headline -font $mainfont ]
939 -text $headline -font $mainfont ]
940 $canv bind $linehtag($lineno) <Button-3> "rowmenu %X %Y $id"
940 $canv bind $linehtag($lineno) <Button-3> "rowmenu %X %Y $id"
941 set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
941 set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
942 -text $name -font $namefont]
942 -text $name -font $namefont]
943 set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
943 set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
944 -text $date -font $mainfont]
944 -text $date -font $mainfont]
945
945
946 set olddlevel $level
946 set olddlevel $level
947 set olddisplist $displist
947 set olddisplist $displist
948 set oldnlines [llength $displist]
948 set oldnlines [llength $displist]
949 }
949 }
950
950
951 proc drawtags {id x xt y1} {
951 proc drawtags {id x xt y1} {
952 global idtags idheads idotherrefs
952 global idtags idheads idotherrefs
953 global linespc lthickness
953 global linespc lthickness
954 global canv mainfont idline rowtextx
954 global canv mainfont idline rowtextx
955
955
956 set marks {}
956 set marks {}
957 set ntags 0
957 set ntags 0
958 set nheads 0
958 set nheads 0
959 if {[info exists idtags($id)]} {
959 if {[info exists idtags($id)]} {
960 set marks $idtags($id)
960 set marks $idtags($id)
961 set ntags [llength $marks]
961 set ntags [llength $marks]
962 }
962 }
963 if {[info exists idheads($id)]} {
963 if {[info exists idheads($id)]} {
964 set marks [concat $marks $idheads($id)]
964 set marks [concat $marks $idheads($id)]
965 set nheads [llength $idheads($id)]
965 set nheads [llength $idheads($id)]
966 }
966 }
967 if {[info exists idotherrefs($id)]} {
967 if {[info exists idotherrefs($id)]} {
968 set marks [concat $marks $idotherrefs($id)]
968 set marks [concat $marks $idotherrefs($id)]
969 }
969 }
970 if {$marks eq {}} {
970 if {$marks eq {}} {
971 return $xt
971 return $xt
972 }
972 }
973
973
974 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
974 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
975 set yt [expr $y1 - 0.5 * $linespc]
975 set yt [expr $y1 - 0.5 * $linespc]
976 set yb [expr $yt + $linespc - 1]
976 set yb [expr $yt + $linespc - 1]
977 set xvals {}
977 set xvals {}
978 set wvals {}
978 set wvals {}
979 foreach tag $marks {
979 foreach tag $marks {
980 set wid [font measure $mainfont $tag]
980 set wid [font measure $mainfont $tag]
981 lappend xvals $xt
981 lappend xvals $xt
982 lappend wvals $wid
982 lappend wvals $wid
983 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
983 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
984 }
984 }
985 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
985 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
986 -width $lthickness -fill black -tags tag.$id]
986 -width $lthickness -fill black -tags tag.$id]
987 $canv lower $t
987 $canv lower $t
988 foreach tag $marks x $xvals wid $wvals {
988 foreach tag $marks x $xvals wid $wvals {
989 set xl [expr $x + $delta]
989 set xl [expr $x + $delta]
990 set xr [expr $x + $delta + $wid + $lthickness]
990 set xr [expr $x + $delta + $wid + $lthickness]
991 if {[incr ntags -1] >= 0} {
991 if {[incr ntags -1] >= 0} {
992 # draw a tag
992 # draw a tag
993 set t [$canv create polygon $x [expr $yt + $delta] $xl $yt \
993 set t [$canv create polygon $x [expr $yt + $delta] $xl $yt \
994 $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
994 $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
995 -width 1 -outline black -fill yellow -tags tag.$id]
995 -width 1 -outline black -fill yellow -tags tag.$id]
996 $canv bind $t <1> [list showtag $tag 1]
996 $canv bind $t <1> [list showtag $tag 1]
997 set rowtextx($idline($id)) [expr {$xr + $linespc}]
997 set rowtextx($idline($id)) [expr {$xr + $linespc}]
998 } else {
998 } else {
999 # draw a head or other ref
999 # draw a head or other ref
1000 if {[incr nheads -1] >= 0} {
1000 if {[incr nheads -1] >= 0} {
1001 set col green
1001 set col green
1002 } else {
1002 } else {
1003 set col "#ddddff"
1003 set col "#ddddff"
1004 }
1004 }
1005 set xl [expr $xl - $delta/2]
1005 set xl [expr $xl - $delta/2]
1006 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
1006 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
1007 -width 1 -outline black -fill $col -tags tag.$id
1007 -width 1 -outline black -fill $col -tags tag.$id
1008 }
1008 }
1009 set t [$canv create text $xl $y1 -anchor w -text $tag \
1009 set t [$canv create text $xl $y1 -anchor w -text $tag \
1010 -font $mainfont -tags tag.$id]
1010 -font $mainfont -tags tag.$id]
1011 if {$ntags >= 0} {
1011 if {$ntags >= 0} {
1012 $canv bind $t <1> [list showtag $tag 1]
1012 $canv bind $t <1> [list showtag $tag 1]
1013 }
1013 }
1014 }
1014 }
1015 return $xt
1015 return $xt
1016 }
1016 }
1017
1017
1018 proc notecrossings {id lo hi corner} {
1018 proc notecrossings {id lo hi corner} {
1019 global olddisplist crossings cornercrossings
1019 global olddisplist crossings cornercrossings
1020
1020
1021 for {set i $lo} {[incr i] < $hi} {} {
1021 for {set i $lo} {[incr i] < $hi} {} {
1022 set p [lindex $olddisplist $i]
1022 set p [lindex $olddisplist $i]
1023 if {$p == {}} continue
1023 if {$p == {}} continue
1024 if {$i == $corner} {
1024 if {$i == $corner} {
1025 if {![info exists cornercrossings($id)]
1025 if {![info exists cornercrossings($id)]
1026 || [lsearch -exact $cornercrossings($id) $p] < 0} {
1026 || [lsearch -exact $cornercrossings($id) $p] < 0} {
1027 lappend cornercrossings($id) $p
1027 lappend cornercrossings($id) $p
1028 }
1028 }
1029 if {![info exists cornercrossings($p)]
1029 if {![info exists cornercrossings($p)]
1030 || [lsearch -exact $cornercrossings($p) $id] < 0} {
1030 || [lsearch -exact $cornercrossings($p) $id] < 0} {
1031 lappend cornercrossings($p) $id
1031 lappend cornercrossings($p) $id
1032 }
1032 }
1033 } else {
1033 } else {
1034 if {![info exists crossings($id)]
1034 if {![info exists crossings($id)]
1035 || [lsearch -exact $crossings($id) $p] < 0} {
1035 || [lsearch -exact $crossings($id) $p] < 0} {
1036 lappend crossings($id) $p
1036 lappend crossings($id) $p
1037 }
1037 }
1038 if {![info exists crossings($p)]
1038 if {![info exists crossings($p)]
1039 || [lsearch -exact $crossings($p) $id] < 0} {
1039 || [lsearch -exact $crossings($p) $id] < 0} {
1040 lappend crossings($p) $id
1040 lappend crossings($p) $id
1041 }
1041 }
1042 }
1042 }
1043 }
1043 }
1044 }
1044 }
1045
1045
1046 proc xcoord {i level ln} {
1046 proc xcoord {i level ln} {
1047 global canvx0 xspc1 xspc2
1047 global canvx0 xspc1 xspc2
1048
1048
1049 set x [expr {$canvx0 + $i * $xspc1($ln)}]
1049 set x [expr {$canvx0 + $i * $xspc1($ln)}]
1050 if {$i > 0 && $i == $level} {
1050 if {$i > 0 && $i == $level} {
1051 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
1051 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
1052 } elseif {$i > $level} {
1052 } elseif {$i > $level} {
1053 set x [expr {$x + $xspc2 - $xspc1($ln)}]
1053 set x [expr {$x + $xspc2 - $xspc1($ln)}]
1054 }
1054 }
1055 return $x
1055 return $x
1056 }
1056 }
1057
1057
1058 # it seems Tk can't draw arrows on the end of diagonal line segments...
1058 # it seems Tk can't draw arrows on the end of diagonal line segments...
1059 proc trimdiagend {line} {
1059 proc trimdiagend {line} {
1060 while {[llength $line] > 4} {
1060 while {[llength $line] > 4} {
1061 set x1 [lindex $line end-3]
1061 set x1 [lindex $line end-3]
1062 set y1 [lindex $line end-2]
1062 set y1 [lindex $line end-2]
1063 set x2 [lindex $line end-1]
1063 set x2 [lindex $line end-1]
1064 set y2 [lindex $line end]
1064 set y2 [lindex $line end]
1065 if {($x1 == $x2) != ($y1 == $y2)} break
1065 if {($x1 == $x2) != ($y1 == $y2)} break
1066 set line [lreplace $line end-1 end]
1066 set line [lreplace $line end-1 end]
1067 }
1067 }
1068 return $line
1068 return $line
1069 }
1069 }
1070
1070
1071 proc trimdiagstart {line} {
1071 proc trimdiagstart {line} {
1072 while {[llength $line] > 4} {
1072 while {[llength $line] > 4} {
1073 set x1 [lindex $line 0]
1073 set x1 [lindex $line 0]
1074 set y1 [lindex $line 1]
1074 set y1 [lindex $line 1]
1075 set x2 [lindex $line 2]
1075 set x2 [lindex $line 2]
1076 set y2 [lindex $line 3]
1076 set y2 [lindex $line 3]
1077 if {($x1 == $x2) != ($y1 == $y2)} break
1077 if {($x1 == $x2) != ($y1 == $y2)} break
1078 set line [lreplace $line 0 1]
1078 set line [lreplace $line 0 1]
1079 }
1079 }
1080 return $line
1080 return $line
1081 }
1081 }
1082
1082
1083 proc drawslants {id needonscreen nohs} {
1083 proc drawslants {id needonscreen nohs} {
1084 global canv mainline mainlinearrow sidelines
1084 global canv mainline mainlinearrow sidelines
1085 global canvx0 canvy xspc1 xspc2 lthickness
1085 global canvx0 canvy xspc1 xspc2 lthickness
1086 global currentparents dupparents
1086 global currentparents dupparents
1087 global lthickness linespc canvy colormap lineno geometry
1087 global lthickness linespc canvy colormap lineno geometry
1088 global maxgraphpct maxwidth
1088 global maxgraphpct maxwidth
1089 global displist onscreen lastuse
1089 global displist onscreen lastuse
1090 global parents commitlisted
1090 global parents commitlisted
1091 global oldnlines olddlevel olddisplist
1091 global oldnlines olddlevel olddisplist
1092 global nhyperspace numcommits nnewparents
1092 global nhyperspace numcommits nnewparents
1093
1093
1094 if {$lineno < 0} {
1094 if {$lineno < 0} {
1095 lappend displist $id
1095 lappend displist $id
1096 set onscreen($id) 1
1096 set onscreen($id) 1
1097 return 0
1097 return 0
1098 }
1098 }
1099
1099
1100 set y1 [expr {$canvy - $linespc}]
1100 set y1 [expr {$canvy - $linespc}]
1101 set y2 $canvy
1101 set y2 $canvy
1102
1102
1103 # work out what we need to get back on screen
1103 # work out what we need to get back on screen
1104 set reins {}
1104 set reins {}
1105 if {$onscreen($id) < 0} {
1105 if {$onscreen($id) < 0} {
1106 # next to do isn't displayed, better get it on screen...
1106 # next to do isn't displayed, better get it on screen...
1107 lappend reins [list $id 0]
1107 lappend reins [list $id 0]
1108 }
1108 }
1109 # make sure all the previous commits's parents are on the screen
1109 # make sure all the previous commits's parents are on the screen
1110 foreach p $currentparents {
1110 foreach p $currentparents {
1111 if {$onscreen($p) < 0} {
1111 if {$onscreen($p) < 0} {
1112 lappend reins [list $p 0]
1112 lappend reins [list $p 0]
1113 }
1113 }
1114 }
1114 }
1115 # bring back anything requested by caller
1115 # bring back anything requested by caller
1116 if {$needonscreen ne {}} {
1116 if {$needonscreen ne {}} {
1117 lappend reins $needonscreen
1117 lappend reins $needonscreen
1118 }
1118 }
1119
1119
1120 # try the shortcut
1120 # try the shortcut
1121 if {$currentparents == $id && $onscreen($id) == 0 && $reins eq {}} {
1121 if {$currentparents == $id && $onscreen($id) == 0 && $reins eq {}} {
1122 set dlevel $olddlevel
1122 set dlevel $olddlevel
1123 set x [xcoord $dlevel $dlevel $lineno]
1123 set x [xcoord $dlevel $dlevel $lineno]
1124 set mainline($id) [list $x $y1]
1124 set mainline($id) [list $x $y1]
1125 set mainlinearrow($id) none
1125 set mainlinearrow($id) none
1126 set lastuse($id) $lineno
1126 set lastuse($id) $lineno
1127 set displist [lreplace $displist $dlevel $dlevel $id]
1127 set displist [lreplace $displist $dlevel $dlevel $id]
1128 set onscreen($id) 1
1128 set onscreen($id) 1
1129 set xspc1([expr {$lineno + 1}]) $xspc1($lineno)
1129 set xspc1([expr {$lineno + 1}]) $xspc1($lineno)
1130 return $dlevel
1130 return $dlevel
1131 }
1131 }
1132
1132
1133 # update displist
1133 # update displist
1134 set displist [lreplace $displist $olddlevel $olddlevel]
1134 set displist [lreplace $displist $olddlevel $olddlevel]
1135 set j $olddlevel
1135 set j $olddlevel
1136 foreach p $currentparents {
1136 foreach p $currentparents {
1137 set lastuse($p) $lineno
1137 set lastuse($p) $lineno
1138 if {$onscreen($p) == 0} {
1138 if {$onscreen($p) == 0} {
1139 set displist [linsert $displist $j $p]
1139 set displist [linsert $displist $j $p]
1140 set onscreen($p) 1
1140 set onscreen($p) 1
1141 incr j
1141 incr j
1142 }
1142 }
1143 }
1143 }
1144 if {$onscreen($id) == 0} {
1144 if {$onscreen($id) == 0} {
1145 lappend displist $id
1145 lappend displist $id
1146 set onscreen($id) 1
1146 set onscreen($id) 1
1147 }
1147 }
1148
1148
1149 # remove the null entry if present
1149 # remove the null entry if present
1150 set nullentry [lsearch -exact $displist {}]
1150 set nullentry [lsearch -exact $displist {}]
1151 if {$nullentry >= 0} {
1151 if {$nullentry >= 0} {
1152 set displist [lreplace $displist $nullentry $nullentry]
1152 set displist [lreplace $displist $nullentry $nullentry]
1153 }
1153 }
1154
1154
1155 # bring back the ones we need now (if we did it earlier
1155 # bring back the ones we need now (if we did it earlier
1156 # it would change displist and invalidate olddlevel)
1156 # it would change displist and invalidate olddlevel)
1157 foreach pi $reins {
1157 foreach pi $reins {
1158 # test again in case of duplicates in reins
1158 # test again in case of duplicates in reins
1159 set p [lindex $pi 0]
1159 set p [lindex $pi 0]
1160 if {$onscreen($p) < 0} {
1160 if {$onscreen($p) < 0} {
1161 set onscreen($p) 1
1161 set onscreen($p) 1
1162 set lastuse($p) $lineno
1162 set lastuse($p) $lineno
1163 set displist [linsert $displist [lindex $pi 1] $p]
1163 set displist [linsert $displist [lindex $pi 1] $p]
1164 incr nhyperspace -1
1164 incr nhyperspace -1
1165 }
1165 }
1166 }
1166 }
1167
1167
1168 set lastuse($id) $lineno
1168 set lastuse($id) $lineno
1169
1169
1170 # see if we need to make any lines jump off into hyperspace
1170 # see if we need to make any lines jump off into hyperspace
1171 set displ [llength $displist]
1171 set displ [llength $displist]
1172 if {$displ > $maxwidth} {
1172 if {$displ > $maxwidth} {
1173 set ages {}
1173 set ages {}
1174 foreach x $displist {
1174 foreach x $displist {
1175 lappend ages [list $lastuse($x) $x]
1175 lappend ages [list $lastuse($x) $x]
1176 }
1176 }
1177 set ages [lsort -integer -index 0 $ages]
1177 set ages [lsort -integer -index 0 $ages]
1178 set k 0
1178 set k 0
1179 while {$displ > $maxwidth} {
1179 while {$displ > $maxwidth} {
1180 set use [lindex $ages $k 0]
1180 set use [lindex $ages $k 0]
1181 set victim [lindex $ages $k 1]
1181 set victim [lindex $ages $k 1]
1182 if {$use >= $lineno - 5} break
1182 if {$use >= $lineno - 5} break
1183 incr k
1183 incr k
1184 if {[lsearch -exact $nohs $victim] >= 0} continue
1184 if {[lsearch -exact $nohs $victim] >= 0} continue
1185 set i [lsearch -exact $displist $victim]
1185 set i [lsearch -exact $displist $victim]
1186 set displist [lreplace $displist $i $i]
1186 set displist [lreplace $displist $i $i]
1187 set onscreen($victim) -1
1187 set onscreen($victim) -1
1188 incr nhyperspace
1188 incr nhyperspace
1189 incr displ -1
1189 incr displ -1
1190 if {$i < $nullentry} {
1190 if {$i < $nullentry} {
1191 incr nullentry -1
1191 incr nullentry -1
1192 }
1192 }
1193 set x [lindex $mainline($victim) end-1]
1193 set x [lindex $mainline($victim) end-1]
1194 lappend mainline($victim) $x $y1
1194 lappend mainline($victim) $x $y1
1195 set line [trimdiagend $mainline($victim)]
1195 set line [trimdiagend $mainline($victim)]
1196 set arrow "last"
1196 set arrow "last"
1197 if {$mainlinearrow($victim) ne "none"} {
1197 if {$mainlinearrow($victim) ne "none"} {
1198 set line [trimdiagstart $line]
1198 set line [trimdiagstart $line]
1199 set arrow "both"
1199 set arrow "both"
1200 }
1200 }
1201 lappend sidelines($victim) [list $line 1 $arrow]
1201 lappend sidelines($victim) [list $line 1 $arrow]
1202 unset mainline($victim)
1202 unset mainline($victim)
1203 }
1203 }
1204 }
1204 }
1205
1205
1206 set dlevel [lsearch -exact $displist $id]
1206 set dlevel [lsearch -exact $displist $id]
1207
1207
1208 # If we are reducing, put in a null entry
1208 # If we are reducing, put in a null entry
1209 if {$displ < $oldnlines} {
1209 if {$displ < $oldnlines} {
1210 # does the next line look like a merge?
1210 # does the next line look like a merge?
1211 # i.e. does it have > 1 new parent?
1211 # i.e. does it have > 1 new parent?
1212 if {$nnewparents($id) > 1} {
1212 if {$nnewparents($id) > 1} {
1213 set i [expr {$dlevel + 1}]
1213 set i [expr {$dlevel + 1}]
1214 } elseif {$nnewparents([lindex $olddisplist $olddlevel]) == 0} {
1214 } elseif {$nnewparents([lindex $olddisplist $olddlevel]) == 0} {
1215 set i $olddlevel
1215 set i $olddlevel
1216 if {$nullentry >= 0 && $nullentry < $i} {
1216 if {$nullentry >= 0 && $nullentry < $i} {
1217 incr i -1
1217 incr i -1
1218 }
1218 }
1219 } elseif {$nullentry >= 0} {
1219 } elseif {$nullentry >= 0} {
1220 set i $nullentry
1220 set i $nullentry
1221 while {$i < $displ
1221 while {$i < $displ
1222 && [lindex $olddisplist $i] == [lindex $displist $i]} {
1222 && [lindex $olddisplist $i] == [lindex $displist $i]} {
1223 incr i
1223 incr i
1224 }
1224 }
1225 } else {
1225 } else {
1226 set i $olddlevel
1226 set i $olddlevel
1227 if {$dlevel >= $i} {
1227 if {$dlevel >= $i} {
1228 incr i
1228 incr i
1229 }
1229 }
1230 }
1230 }
1231 if {$i < $displ} {
1231 if {$i < $displ} {
1232 set displist [linsert $displist $i {}]
1232 set displist [linsert $displist $i {}]
1233 incr displ
1233 incr displ
1234 if {$dlevel >= $i} {
1234 if {$dlevel >= $i} {
1235 incr dlevel
1235 incr dlevel
1236 }
1236 }
1237 }
1237 }
1238 }
1238 }
1239
1239
1240 # decide on the line spacing for the next line
1240 # decide on the line spacing for the next line
1241 set lj [expr {$lineno + 1}]
1241 set lj [expr {$lineno + 1}]
1242 set maxw [expr {$maxgraphpct * $geometry(canv1) / 100}]
1242 set maxw [expr {$maxgraphpct * $geometry(canv1) / 100}]
1243 if {$displ <= 1 || $canvx0 + $displ * $xspc2 <= $maxw} {
1243 if {$displ <= 1 || $canvx0 + $displ * $xspc2 <= $maxw} {
1244 set xspc1($lj) $xspc2
1244 set xspc1($lj) $xspc2
1245 } else {
1245 } else {
1246 set xspc1($lj) [expr {($maxw - $canvx0 - $xspc2) / ($displ - 1)}]
1246 set xspc1($lj) [expr {($maxw - $canvx0 - $xspc2) / ($displ - 1)}]
1247 if {$xspc1($lj) < $lthickness} {
1247 if {$xspc1($lj) < $lthickness} {
1248 set xspc1($lj) $lthickness
1248 set xspc1($lj) $lthickness
1249 }
1249 }
1250 }
1250 }
1251
1251
1252 foreach idi $reins {
1252 foreach idi $reins {
1253 set id [lindex $idi 0]
1253 set id [lindex $idi 0]
1254 set j [lsearch -exact $displist $id]
1254 set j [lsearch -exact $displist $id]
1255 set xj [xcoord $j $dlevel $lj]
1255 set xj [xcoord $j $dlevel $lj]
1256 set mainline($id) [list $xj $y2]
1256 set mainline($id) [list $xj $y2]
1257 set mainlinearrow($id) first
1257 set mainlinearrow($id) first
1258 }
1258 }
1259
1259
1260 set i -1
1260 set i -1
1261 foreach id $olddisplist {
1261 foreach id $olddisplist {
1262 incr i
1262 incr i
1263 if {$id == {}} continue
1263 if {$id == {}} continue
1264 if {$onscreen($id) <= 0} continue
1264 if {$onscreen($id) <= 0} continue
1265 set xi [xcoord $i $olddlevel $lineno]
1265 set xi [xcoord $i $olddlevel $lineno]
1266 if {$i == $olddlevel} {
1266 if {$i == $olddlevel} {
1267 foreach p $currentparents {
1267 foreach p $currentparents {
1268 set j [lsearch -exact $displist $p]
1268 set j [lsearch -exact $displist $p]
1269 set coords [list $xi $y1]
1269 set coords [list $xi $y1]
1270 set xj [xcoord $j $dlevel $lj]
1270 set xj [xcoord $j $dlevel $lj]
1271 if {$xj < $xi - $linespc} {
1271 if {$xj < $xi - $linespc} {
1272 lappend coords [expr {$xj + $linespc}] $y1
1272 lappend coords [expr {$xj + $linespc}] $y1
1273 notecrossings $p $j $i [expr {$j + 1}]
1273 notecrossings $p $j $i [expr {$j + 1}]
1274 } elseif {$xj > $xi + $linespc} {
1274 } elseif {$xj > $xi + $linespc} {
1275 lappend coords [expr {$xj - $linespc}] $y1
1275 lappend coords [expr {$xj - $linespc}] $y1
1276 notecrossings $p $i $j [expr {$j - 1}]
1276 notecrossings $p $i $j [expr {$j - 1}]
1277 }
1277 }
1278 if {[lsearch -exact $dupparents $p] >= 0} {
1278 if {[lsearch -exact $dupparents $p] >= 0} {
1279 # draw a double-width line to indicate the doubled parent
1279 # draw a double-width line to indicate the doubled parent
1280 lappend coords $xj $y2
1280 lappend coords $xj $y2
1281 lappend sidelines($p) [list $coords 2 none]
1281 lappend sidelines($p) [list $coords 2 none]
1282 if {![info exists mainline($p)]} {
1282 if {![info exists mainline($p)]} {
1283 set mainline($p) [list $xj $y2]
1283 set mainline($p) [list $xj $y2]
1284 set mainlinearrow($p) none
1284 set mainlinearrow($p) none
1285 }
1285 }
1286 } else {
1286 } else {
1287 # normal case, no parent duplicated
1287 # normal case, no parent duplicated
1288 set yb $y2
1288 set yb $y2
1289 set dx [expr {abs($xi - $xj)}]
1289 set dx [expr {abs($xi - $xj)}]
1290 if {0 && $dx < $linespc} {
1290 if {0 && $dx < $linespc} {
1291 set yb [expr {$y1 + $dx}]
1291 set yb [expr {$y1 + $dx}]
1292 }
1292 }
1293 if {![info exists mainline($p)]} {
1293 if {![info exists mainline($p)]} {
1294 if {$xi != $xj} {
1294 if {$xi != $xj} {
1295 lappend coords $xj $yb
1295 lappend coords $xj $yb
1296 }
1296 }
1297 set mainline($p) $coords
1297 set mainline($p) $coords
1298 set mainlinearrow($p) none
1298 set mainlinearrow($p) none
1299 } else {
1299 } else {
1300 lappend coords $xj $yb
1300 lappend coords $xj $yb
1301 if {$yb < $y2} {
1301 if {$yb < $y2} {
1302 lappend coords $xj $y2
1302 lappend coords $xj $y2
1303 }
1303 }
1304 lappend sidelines($p) [list $coords 1 none]
1304 lappend sidelines($p) [list $coords 1 none]
1305 }
1305 }
1306 }
1306 }
1307 }
1307 }
1308 } else {
1308 } else {
1309 set j $i
1309 set j $i
1310 if {[lindex $displist $i] != $id} {
1310 if {[lindex $displist $i] != $id} {
1311 set j [lsearch -exact $displist $id]
1311 set j [lsearch -exact $displist $id]
1312 }
1312 }
1313 if {$j != $i || $xspc1($lineno) != $xspc1($lj)
1313 if {$j != $i || $xspc1($lineno) != $xspc1($lj)
1314 || ($olddlevel < $i && $i < $dlevel)
1314 || ($olddlevel < $i && $i < $dlevel)
1315 || ($dlevel < $i && $i < $olddlevel)} {
1315 || ($dlevel < $i && $i < $olddlevel)} {
1316 set xj [xcoord $j $dlevel $lj]
1316 set xj [xcoord $j $dlevel $lj]
1317 lappend mainline($id) $xi $y1 $xj $y2
1317 lappend mainline($id) $xi $y1 $xj $y2
1318 }
1318 }
1319 }
1319 }
1320 }
1320 }
1321 return $dlevel
1321 return $dlevel
1322 }
1322 }
1323
1323
1324 # search for x in a list of lists
1324 # search for x in a list of lists
1325 proc llsearch {llist x} {
1325 proc llsearch {llist x} {
1326 set i 0
1326 set i 0
1327 foreach l $llist {
1327 foreach l $llist {
1328 if {$l == $x || [lsearch -exact $l $x] >= 0} {
1328 if {$l == $x || [lsearch -exact $l $x] >= 0} {
1329 return $i
1329 return $i
1330 }
1330 }
1331 incr i
1331 incr i
1332 }
1332 }
1333 return -1
1333 return -1
1334 }
1334 }
1335
1335
1336 proc drawmore {reading} {
1336 proc drawmore {reading} {
1337 global displayorder numcommits ncmupdate nextupdate
1337 global displayorder numcommits ncmupdate nextupdate
1338 global stopped nhyperspace parents commitlisted
1338 global stopped nhyperspace parents commitlisted
1339 global maxwidth onscreen displist currentparents olddlevel
1339 global maxwidth onscreen displist currentparents olddlevel
1340
1340
1341 set n [llength $displayorder]
1341 set n [llength $displayorder]
1342 while {$numcommits < $n} {
1342 while {$numcommits < $n} {
1343 set id [lindex $displayorder $numcommits]
1343 set id [lindex $displayorder $numcommits]
1344 set ctxend [expr {$numcommits + 10}]
1344 set ctxend [expr {$numcommits + 10}]
1345 if {!$reading && $ctxend > $n} {
1345 if {!$reading && $ctxend > $n} {
1346 set ctxend $n
1346 set ctxend $n
1347 }
1347 }
1348 set dlist {}
1348 set dlist {}
1349 if {$numcommits > 0} {
1349 if {$numcommits > 0} {
1350 set dlist [lreplace $displist $olddlevel $olddlevel]
1350 set dlist [lreplace $displist $olddlevel $olddlevel]
1351 set i $olddlevel
1351 set i $olddlevel
1352 foreach p $currentparents {
1352 foreach p $currentparents {
1353 if {$onscreen($p) == 0} {
1353 if {$onscreen($p) == 0} {
1354 set dlist [linsert $dlist $i $p]
1354 set dlist [linsert $dlist $i $p]
1355 incr i
1355 incr i
1356 }
1356 }
1357 }
1357 }
1358 }
1358 }
1359 set nohs {}
1359 set nohs {}
1360 set reins {}
1360 set reins {}
1361 set isfat [expr {[llength $dlist] > $maxwidth}]
1361 set isfat [expr {[llength $dlist] > $maxwidth}]
1362 if {$nhyperspace > 0 || $isfat} {
1362 if {$nhyperspace > 0 || $isfat} {
1363 if {$ctxend > $n} break
1363 if {$ctxend > $n} break
1364 # work out what to bring back and
1364 # work out what to bring back and
1365 # what we want to don't want to send into hyperspace
1365 # what we want to don't want to send into hyperspace
1366 set room 1
1366 set room 1
1367 for {set k $numcommits} {$k < $ctxend} {incr k} {
1367 for {set k $numcommits} {$k < $ctxend} {incr k} {
1368 set x [lindex $displayorder $k]
1368 set x [lindex $displayorder $k]
1369 set i [llsearch $dlist $x]
1369 set i [llsearch $dlist $x]
1370 if {$i < 0} {
1370 if {$i < 0} {
1371 set i [llength $dlist]
1371 set i [llength $dlist]
1372 lappend dlist $x
1372 lappend dlist $x
1373 }
1373 }
1374 if {[lsearch -exact $nohs $x] < 0} {
1374 if {[lsearch -exact $nohs $x] < 0} {
1375 lappend nohs $x
1375 lappend nohs $x
1376 }
1376 }
1377 if {$reins eq {} && $onscreen($x) < 0 && $room} {
1377 if {$reins eq {} && $onscreen($x) < 0 && $room} {
1378 set reins [list $x $i]
1378 set reins [list $x $i]
1379 }
1379 }
1380 set newp {}
1380 set newp {}
1381 if {[info exists commitlisted($x)]} {
1381 if {[info exists commitlisted($x)]} {
1382 set right 0
1382 set right 0
1383 foreach p $parents($x) {
1383 foreach p $parents($x) {
1384 if {[llsearch $dlist $p] < 0} {
1384 if {[llsearch $dlist $p] < 0} {
1385 lappend newp $p
1385 lappend newp $p
1386 if {[lsearch -exact $nohs $p] < 0} {
1386 if {[lsearch -exact $nohs $p] < 0} {
1387 lappend nohs $p
1387 lappend nohs $p
1388 }
1388 }
1389 if {$reins eq {} && $onscreen($p) < 0 && $room} {
1389 if {$reins eq {} && $onscreen($p) < 0 && $room} {
1390 set reins [list $p [expr {$i + $right}]]
1390 set reins [list $p [expr {$i + $right}]]
1391 }
1391 }
1392 }
1392 }
1393 set right 1
1393 set right 1
1394 }
1394 }
1395 }
1395 }
1396 set l [lindex $dlist $i]
1396 set l [lindex $dlist $i]
1397 if {[llength $l] == 1} {
1397 if {[llength $l] == 1} {
1398 set l $newp
1398 set l $newp
1399 } else {
1399 } else {
1400 set j [lsearch -exact $l $x]
1400 set j [lsearch -exact $l $x]
1401 set l [concat [lreplace $l $j $j] $newp]
1401 set l [concat [lreplace $l $j $j] $newp]
1402 }
1402 }
1403 set dlist [lreplace $dlist $i $i $l]
1403 set dlist [lreplace $dlist $i $i $l]
1404 if {$room && $isfat && [llength $newp] <= 1} {
1404 if {$room && $isfat && [llength $newp] <= 1} {
1405 set room 0
1405 set room 0
1406 }
1406 }
1407 }
1407 }
1408 }
1408 }
1409
1409
1410 set dlevel [drawslants $id $reins $nohs]
1410 set dlevel [drawslants $id $reins $nohs]
1411 drawcommitline $dlevel
1411 drawcommitline $dlevel
1412 if {[clock clicks -milliseconds] >= $nextupdate
1412 if {[clock clicks -milliseconds] >= $nextupdate
1413 && $numcommits >= $ncmupdate} {
1413 && $numcommits >= $ncmupdate} {
1414 doupdate $reading
1414 doupdate $reading
1415 if {$stopped} break
1415 if {$stopped} break
1416 }
1416 }
1417 }
1417 }
1418 }
1418 }
1419
1419
1420 # level here is an index in todo
1420 # level here is an index in todo
1421 proc updatetodo {level noshortcut} {
1421 proc updatetodo {level noshortcut} {
1422 global ncleft todo nnewparents
1422 global ncleft todo nnewparents
1423 global commitlisted parents onscreen
1423 global commitlisted parents onscreen
1424
1424
1425 set id [lindex $todo $level]
1425 set id [lindex $todo $level]
1426 set olds {}
1426 set olds {}
1427 if {[info exists commitlisted($id)]} {
1427 if {[info exists commitlisted($id)]} {
1428 foreach p $parents($id) {
1428 foreach p $parents($id) {
1429 if {[lsearch -exact $olds $p] < 0} {
1429 if {[lsearch -exact $olds $p] < 0} {
1430 lappend olds $p
1430 lappend olds $p
1431 }
1431 }
1432 }
1432 }
1433 }
1433 }
1434 if {!$noshortcut && [llength $olds] == 1} {
1434 if {!$noshortcut && [llength $olds] == 1} {
1435 set p [lindex $olds 0]
1435 set p [lindex $olds 0]
1436 if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
1436 if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
1437 set ncleft($p) 0
1437 set ncleft($p) 0
1438 set todo [lreplace $todo $level $level $p]
1438 set todo [lreplace $todo $level $level $p]
1439 set onscreen($p) 0
1439 set onscreen($p) 0
1440 set nnewparents($id) 1
1440 set nnewparents($id) 1
1441 return 0
1441 return 0
1442 }
1442 }
1443 }
1443 }
1444
1444
1445 set todo [lreplace $todo $level $level]
1445 set todo [lreplace $todo $level $level]
1446 set i $level
1446 set i $level
1447 set n 0
1447 set n 0
1448 foreach p $olds {
1448 foreach p $olds {
1449 incr ncleft($p) -1
1449 incr ncleft($p) -1
1450 set k [lsearch -exact $todo $p]
1450 set k [lsearch -exact $todo $p]
1451 if {$k < 0} {
1451 if {$k < 0} {
1452 set todo [linsert $todo $i $p]
1452 set todo [linsert $todo $i $p]
1453 set onscreen($p) 0
1453 set onscreen($p) 0
1454 incr i
1454 incr i
1455 incr n
1455 incr n
1456 }
1456 }
1457 }
1457 }
1458 set nnewparents($id) $n
1458 set nnewparents($id) $n
1459
1459
1460 return 1
1460 return 1
1461 }
1461 }
1462
1462
1463 proc decidenext {{noread 0}} {
1463 proc decidenext {{noread 0}} {
1464 global ncleft todo
1464 global ncleft todo
1465 global datemode cdate
1465 global datemode cdate
1466 global commitinfo
1466 global commitinfo
1467
1467
1468 # choose which one to do next time around
1468 # choose which one to do next time around
1469 set todol [llength $todo]
1469 set todol [llength $todo]
1470 set level -1
1470 set level -1
1471 set latest {}
1471 set latest {}
1472 for {set k $todol} {[incr k -1] >= 0} {} {
1472 for {set k $todol} {[incr k -1] >= 0} {} {
1473 set p [lindex $todo $k]
1473 set p [lindex $todo $k]
1474 if {$ncleft($p) == 0} {
1474 if {$ncleft($p) == 0} {
1475 if {$datemode} {
1475 if {$datemode} {
1476 if {![info exists commitinfo($p)]} {
1476 if {![info exists commitinfo($p)]} {
1477 if {$noread} {
1477 if {$noread} {
1478 return {}
1478 return {}
1479 }
1479 }
1480 readcommit $p
1480 readcommit $p
1481 }
1481 }
1482 if {$latest == {} || $cdate($p) > $latest} {
1482 if {$latest == {} || $cdate($p) > $latest} {
1483 set level $k
1483 set level $k
1484 set latest $cdate($p)
1484 set latest $cdate($p)
1485 }
1485 }
1486 } else {
1486 } else {
1487 set level $k
1487 set level $k
1488 break
1488 break
1489 }
1489 }
1490 }
1490 }
1491 }
1491 }
1492 if {$level < 0} {
1492 if {$level < 0} {
1493 if {$todo != {}} {
1493 if {$todo != {}} {
1494 puts "ERROR: none of the pending commits can be done yet:"
1494 puts "ERROR: none of the pending commits can be done yet:"
1495 foreach p $todo {
1495 foreach p $todo {
1496 puts " $p ($ncleft($p))"
1496 puts " $p ($ncleft($p))"
1497 }
1497 }
1498 }
1498 }
1499 return -1
1499 return -1
1500 }
1500 }
1501
1501
1502 return $level
1502 return $level
1503 }
1503 }
1504
1504
1505 proc drawcommit {id} {
1505 proc drawcommit {id} {
1506 global phase todo nchildren datemode nextupdate
1506 global phase todo nchildren datemode nextupdate
1507 global numcommits ncmupdate displayorder todo onscreen
1507 global numcommits ncmupdate displayorder todo onscreen
1508
1508
1509 if {$phase != "incrdraw"} {
1509 if {$phase != "incrdraw"} {
1510 set phase incrdraw
1510 set phase incrdraw
1511 set displayorder {}
1511 set displayorder {}
1512 set todo {}
1512 set todo {}
1513 initgraph
1513 initgraph
1514 }
1514 }
1515 if {$nchildren($id) == 0} {
1515 if {$nchildren($id) == 0} {
1516 lappend todo $id
1516 lappend todo $id
1517 set onscreen($id) 0
1517 set onscreen($id) 0
1518 }
1518 }
1519 set level [decidenext 1]
1519 set level [decidenext 1]
1520 if {$level == {} || $id != [lindex $todo $level]} {
1520 if {$level == {} || $id != [lindex $todo $level]} {
1521 return
1521 return
1522 }
1522 }
1523 while 1 {
1523 while 1 {
1524 lappend displayorder [lindex $todo $level]
1524 lappend displayorder [lindex $todo $level]
1525 if {[updatetodo $level $datemode]} {
1525 if {[updatetodo $level $datemode]} {
1526 set level [decidenext 1]
1526 set level [decidenext 1]
1527 if {$level == {}} break
1527 if {$level == {}} break
1528 }
1528 }
1529 set id [lindex $todo $level]
1529 set id [lindex $todo $level]
1530 if {![info exists commitlisted($id)]} {
1530 if {![info exists commitlisted($id)]} {
1531 break
1531 break
1532 }
1532 }
1533 }
1533 }
1534 drawmore 1
1534 drawmore 1
1535 }
1535 }
1536
1536
1537 proc finishcommits {} {
1537 proc finishcommits {} {
1538 global phase
1538 global phase
1539 global canv mainfont ctext maincursor textcursor
1539 global canv mainfont ctext maincursor textcursor
1540
1540
1541 if {$phase != "incrdraw"} {
1541 if {$phase != "incrdraw"} {
1542 $canv delete all
1542 $canv delete all
1543 $canv create text 3 3 -anchor nw -text "No commits selected" \
1543 $canv create text 3 3 -anchor nw -text "No commits selected" \
1544 -font $mainfont -tags textitems
1544 -font $mainfont -tags textitems
1545 set phase {}
1545 set phase {}
1546 } else {
1546 } else {
1547 drawrest
1547 drawrest
1548 }
1548 }
1549 . config -cursor $maincursor
1549 . config -cursor $maincursor
1550 settextcursor $textcursor
1550 settextcursor $textcursor
1551 }
1551 }
1552
1552
1553 # Don't change the text pane cursor if it is currently the hand cursor,
1553 # Don't change the text pane cursor if it is currently the hand cursor,
1554 # showing that we are over a sha1 ID link.
1554 # showing that we are over a sha1 ID link.
1555 proc settextcursor {c} {
1555 proc settextcursor {c} {
1556 global ctext curtextcursor
1556 global ctext curtextcursor
1557
1557
1558 if {[$ctext cget -cursor] == $curtextcursor} {
1558 if {[$ctext cget -cursor] == $curtextcursor} {
1559 $ctext config -cursor $c
1559 $ctext config -cursor $c
1560 }
1560 }
1561 set curtextcursor $c
1561 set curtextcursor $c
1562 }
1562 }
1563
1563
1564 proc drawgraph {} {
1564 proc drawgraph {} {
1565 global nextupdate startmsecs ncmupdate
1565 global nextupdate startmsecs ncmupdate
1566 global displayorder onscreen
1566 global displayorder onscreen
1567
1567
1568 if {$displayorder == {}} return
1568 if {$displayorder == {}} return
1569 set startmsecs [clock clicks -milliseconds]
1569 set startmsecs [clock clicks -milliseconds]
1570 set nextupdate [expr $startmsecs + 100]
1570 set nextupdate [expr $startmsecs + 100]
1571 set ncmupdate 1
1571 set ncmupdate 1
1572 initgraph
1572 initgraph
1573 foreach id $displayorder {
1573 foreach id $displayorder {
1574 set onscreen($id) 0
1574 set onscreen($id) 0
1575 }
1575 }
1576 drawmore 0
1576 drawmore 0
1577 }
1577 }
1578
1578
1579 proc drawrest {} {
1579 proc drawrest {} {
1580 global phase stopped redisplaying selectedline
1580 global phase stopped redisplaying selectedline
1581 global datemode todo displayorder
1581 global datemode todo displayorder
1582 global numcommits ncmupdate
1582 global numcommits ncmupdate
1583 global nextupdate startmsecs
1583 global nextupdate startmsecs
1584
1584
1585 set level [decidenext]
1585 set level [decidenext]
1586 if {$level >= 0} {
1586 if {$level >= 0} {
1587 set phase drawgraph
1587 set phase drawgraph
1588 while 1 {
1588 while 1 {
1589 lappend displayorder [lindex $todo $level]
1589 lappend displayorder [lindex $todo $level]
1590 set hard [updatetodo $level $datemode]
1590 set hard [updatetodo $level $datemode]
1591 if {$hard} {
1591 if {$hard} {
1592 set level [decidenext]
1592 set level [decidenext]
1593 if {$level < 0} break
1593 if {$level < 0} break
1594 }
1594 }
1595 }
1595 }
1596 drawmore 0
1596 drawmore 0
1597 }
1597 }
1598 set phase {}
1598 set phase {}
1599 set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
1599 set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
1600 #puts "overall $drawmsecs ms for $numcommits commits"
1600 #puts "overall $drawmsecs ms for $numcommits commits"
1601 if {$redisplaying} {
1601 if {$redisplaying} {
1602 if {$stopped == 0 && [info exists selectedline]} {
1602 if {$stopped == 0 && [info exists selectedline]} {
1603 selectline $selectedline 0
1603 selectline $selectedline 0
1604 }
1604 }
1605 if {$stopped == 1} {
1605 if {$stopped == 1} {
1606 set stopped 0
1606 set stopped 0
1607 after idle drawgraph
1607 after idle drawgraph
1608 } else {
1608 } else {
1609 set redisplaying 0
1609 set redisplaying 0
1610 }
1610 }
1611 }
1611 }
1612 }
1612 }
1613
1613
1614 proc findmatches {f} {
1614 proc findmatches {f} {
1615 global findtype foundstring foundstrlen
1615 global findtype foundstring foundstrlen
1616 if {$findtype == "Regexp"} {
1616 if {$findtype == "Regexp"} {
1617 set matches [regexp -indices -all -inline $foundstring $f]
1617 set matches [regexp -indices -all -inline $foundstring $f]
1618 } else {
1618 } else {
1619 if {$findtype == "IgnCase"} {
1619 if {$findtype == "IgnCase"} {
1620 set str [string tolower $f]
1620 set str [string tolower $f]
1621 } else {
1621 } else {
1622 set str $f
1622 set str $f
1623 }
1623 }
1624 set matches {}
1624 set matches {}
1625 set i 0
1625 set i 0
1626 while {[set j [string first $foundstring $str $i]] >= 0} {
1626 while {[set j [string first $foundstring $str $i]] >= 0} {
1627 lappend matches [list $j [expr $j+$foundstrlen-1]]
1627 lappend matches [list $j [expr $j+$foundstrlen-1]]
1628 set i [expr $j + $foundstrlen]
1628 set i [expr $j + $foundstrlen]
1629 }
1629 }
1630 }
1630 }
1631 return $matches
1631 return $matches
1632 }
1632 }
1633
1633
1634 proc dofind {} {
1634 proc dofind {} {
1635 global findtype findloc findstring markedmatches commitinfo
1635 global findtype findloc findstring markedmatches commitinfo
1636 global numcommits lineid linehtag linentag linedtag
1636 global numcommits lineid linehtag linentag linedtag
1637 global mainfont namefont canv canv2 canv3 selectedline
1637 global mainfont namefont canv canv2 canv3 selectedline
1638 global matchinglines foundstring foundstrlen
1638 global matchinglines foundstring foundstrlen
1639
1639
1640 stopfindproc
1640 stopfindproc
1641 unmarkmatches
1641 unmarkmatches
1642 focus .
1642 focus .
1643 set matchinglines {}
1643 set matchinglines {}
1644 if {$findloc == "Pickaxe"} {
1644 if {$findloc == "Pickaxe"} {
1645 findpatches
1645 findpatches
1646 return
1646 return
1647 }
1647 }
1648 if {$findtype == "IgnCase"} {
1648 if {$findtype == "IgnCase"} {
1649 set foundstring [string tolower $findstring]
1649 set foundstring [string tolower $findstring]
1650 } else {
1650 } else {
1651 set foundstring $findstring
1651 set foundstring $findstring
1652 }
1652 }
1653 set foundstrlen [string length $findstring]
1653 set foundstrlen [string length $findstring]
1654 if {$foundstrlen == 0} return
1654 if {$foundstrlen == 0} return
1655 if {$findloc == "Files"} {
1655 if {$findloc == "Files"} {
1656 findfiles
1656 findfiles
1657 return
1657 return
1658 }
1658 }
1659 if {![info exists selectedline]} {
1659 if {![info exists selectedline]} {
1660 set oldsel -1
1660 set oldsel -1
1661 } else {
1661 } else {
1662 set oldsel $selectedline
1662 set oldsel $selectedline
1663 }
1663 }
1664 set didsel 0
1664 set didsel 0
1665 set fldtypes {Headline Author Date Committer CDate Comment}
1665 set fldtypes {Headline Author Date Committer CDate Comment}
1666 for {set l 0} {$l < $numcommits} {incr l} {
1666 for {set l 0} {$l < $numcommits} {incr l} {
1667 set id $lineid($l)
1667 set id $lineid($l)
1668 set info $commitinfo($id)
1668 set info $commitinfo($id)
1669 set doesmatch 0
1669 set doesmatch 0
1670 foreach f $info ty $fldtypes {
1670 foreach f $info ty $fldtypes {
1671 if {$findloc != "All fields" && $findloc != $ty} {
1671 if {$findloc != "All fields" && $findloc != $ty} {
1672 continue
1672 continue
1673 }
1673 }
1674 set matches [findmatches $f]
1674 set matches [findmatches $f]
1675 if {$matches == {}} continue
1675 if {$matches == {}} continue
1676 set doesmatch 1
1676 set doesmatch 1
1677 if {$ty == "Headline"} {
1677 if {$ty == "Headline"} {
1678 markmatches $canv $l $f $linehtag($l) $matches $mainfont
1678 markmatches $canv $l $f $linehtag($l) $matches $mainfont
1679 } elseif {$ty == "Author"} {
1679 } elseif {$ty == "Author"} {
1680 markmatches $canv2 $l $f $linentag($l) $matches $namefont
1680 markmatches $canv2 $l $f $linentag($l) $matches $namefont
1681 } elseif {$ty == "Date"} {
1681 } elseif {$ty == "Date"} {
1682 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1682 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1683 }
1683 }
1684 }
1684 }
1685 if {$doesmatch} {
1685 if {$doesmatch} {
1686 lappend matchinglines $l
1686 lappend matchinglines $l
1687 if {!$didsel && $l > $oldsel} {
1687 if {!$didsel && $l > $oldsel} {
1688 findselectline $l
1688 findselectline $l
1689 set didsel 1
1689 set didsel 1
1690 }
1690 }
1691 }
1691 }
1692 }
1692 }
1693 if {$matchinglines == {}} {
1693 if {$matchinglines == {}} {
1694 bell
1694 bell
1695 } elseif {!$didsel} {
1695 } elseif {!$didsel} {
1696 findselectline [lindex $matchinglines 0]
1696 findselectline [lindex $matchinglines 0]
1697 }
1697 }
1698 }
1698 }
1699
1699
1700 proc findselectline {l} {
1700 proc findselectline {l} {
1701 global findloc commentend ctext
1701 global findloc commentend ctext
1702 selectline $l 1
1702 selectline $l 1
1703 if {$findloc == "All fields" || $findloc == "Comments"} {
1703 if {$findloc == "All fields" || $findloc == "Comments"} {
1704 # highlight the matches in the comments
1704 # highlight the matches in the comments
1705 set f [$ctext get 1.0 $commentend]
1705 set f [$ctext get 1.0 $commentend]
1706 set matches [findmatches $f]
1706 set matches [findmatches $f]
1707 foreach match $matches {
1707 foreach match $matches {
1708 set start [lindex $match 0]
1708 set start [lindex $match 0]
1709 set end [expr [lindex $match 1] + 1]
1709 set end [expr [lindex $match 1] + 1]
1710 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
1710 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
1711 }
1711 }
1712 }
1712 }
1713 }
1713 }
1714
1714
1715 proc findnext {restart} {
1715 proc findnext {restart} {
1716 global matchinglines selectedline
1716 global matchinglines selectedline
1717 if {![info exists matchinglines]} {
1717 if {![info exists matchinglines]} {
1718 if {$restart} {
1718 if {$restart} {
1719 dofind
1719 dofind
1720 }
1720 }
1721 return
1721 return
1722 }
1722 }
1723 if {![info exists selectedline]} return
1723 if {![info exists selectedline]} return
1724 foreach l $matchinglines {
1724 foreach l $matchinglines {
1725 if {$l > $selectedline} {
1725 if {$l > $selectedline} {
1726 findselectline $l
1726 findselectline $l
1727 return
1727 return
1728 }
1728 }
1729 }
1729 }
1730 bell
1730 bell
1731 }
1731 }
1732
1732
1733 proc findprev {} {
1733 proc findprev {} {
1734 global matchinglines selectedline
1734 global matchinglines selectedline
1735 if {![info exists matchinglines]} {
1735 if {![info exists matchinglines]} {
1736 dofind
1736 dofind
1737 return
1737 return
1738 }
1738 }
1739 if {![info exists selectedline]} return
1739 if {![info exists selectedline]} return
1740 set prev {}
1740 set prev {}
1741 foreach l $matchinglines {
1741 foreach l $matchinglines {
1742 if {$l >= $selectedline} break
1742 if {$l >= $selectedline} break
1743 set prev $l
1743 set prev $l
1744 }
1744 }
1745 if {$prev != {}} {
1745 if {$prev != {}} {
1746 findselectline $prev
1746 findselectline $prev
1747 } else {
1747 } else {
1748 bell
1748 bell
1749 }
1749 }
1750 }
1750 }
1751
1751
1752 proc findlocchange {name ix op} {
1752 proc findlocchange {name ix op} {
1753 global findloc findtype findtypemenu
1753 global findloc findtype findtypemenu
1754 if {$findloc == "Pickaxe"} {
1754 if {$findloc == "Pickaxe"} {
1755 set findtype Exact
1755 set findtype Exact
1756 set state disabled
1756 set state disabled
1757 } else {
1757 } else {
1758 set state normal
1758 set state normal
1759 }
1759 }
1760 $findtypemenu entryconf 1 -state $state
1760 $findtypemenu entryconf 1 -state $state
1761 $findtypemenu entryconf 2 -state $state
1761 $findtypemenu entryconf 2 -state $state
1762 }
1762 }
1763
1763
1764 proc stopfindproc {{done 0}} {
1764 proc stopfindproc {{done 0}} {
1765 global findprocpid findprocfile findids
1765 global findprocpid findprocfile findids
1766 global ctext findoldcursor phase maincursor textcursor
1766 global ctext findoldcursor phase maincursor textcursor
1767 global findinprogress
1767 global findinprogress
1768
1768
1769 catch {unset findids}
1769 catch {unset findids}
1770 if {[info exists findprocpid]} {
1770 if {[info exists findprocpid]} {
1771 if {!$done} {
1771 if {!$done} {
1772 catch {exec kill $findprocpid}
1772 catch {exec kill $findprocpid}
1773 }
1773 }
1774 catch {close $findprocfile}
1774 catch {close $findprocfile}
1775 unset findprocpid
1775 unset findprocpid
1776 }
1776 }
1777 if {[info exists findinprogress]} {
1777 if {[info exists findinprogress]} {
1778 unset findinprogress
1778 unset findinprogress
1779 if {$phase != "incrdraw"} {
1779 if {$phase != "incrdraw"} {
1780 . config -cursor $maincursor
1780 . config -cursor $maincursor
1781 settextcursor $textcursor
1781 settextcursor $textcursor
1782 }
1782 }
1783 }
1783 }
1784 }
1784 }
1785
1785
1786 proc findpatches {} {
1786 proc findpatches {} {
1787 global findstring selectedline numcommits
1787 global findstring selectedline numcommits
1788 global findprocpid findprocfile
1788 global findprocpid findprocfile
1789 global finddidsel ctext lineid findinprogress
1789 global finddidsel ctext lineid findinprogress
1790 global findinsertpos
1790 global findinsertpos
1791 global env
1791 global env
1792
1792
1793 if {$numcommits == 0} return
1793 if {$numcommits == 0} return
1794
1794
1795 # make a list of all the ids to search, starting at the one
1795 # make a list of all the ids to search, starting at the one
1796 # after the selected line (if any)
1796 # after the selected line (if any)
1797 if {[info exists selectedline]} {
1797 if {[info exists selectedline]} {
1798 set l $selectedline
1798 set l $selectedline
1799 } else {
1799 } else {
1800 set l -1
1800 set l -1
1801 }
1801 }
1802 set inputids {}
1802 set inputids {}
1803 for {set i 0} {$i < $numcommits} {incr i} {
1803 for {set i 0} {$i < $numcommits} {incr i} {
1804 if {[incr l] >= $numcommits} {
1804 if {[incr l] >= $numcommits} {
1805 set l 0
1805 set l 0
1806 }
1806 }
1807 append inputids $lineid($l) "\n"
1807 append inputids $lineid($l) "\n"
1808 }
1808 }
1809
1809
1810 if {[catch {
1810 if {[catch {
1811 set f [open [list | $env(HG) --config ui.report_untrusted=false debug-diff-tree --stdin -s -r -S$findstring << $inputids] r]
1811 set f [open [list | $env(HG) --config ui.report_untrusted=false debug-diff-tree --stdin -s -r -S$findstring << $inputids] r]
1812 } err]} {
1812 } err]} {
1813 error_popup "Error starting search process: $err"
1813 error_popup "Error starting search process: $err"
1814 return
1814 return
1815 }
1815 }
1816
1816
1817 set findinsertpos end
1817 set findinsertpos end
1818 set findprocfile $f
1818 set findprocfile $f
1819 set findprocpid [pid $f]
1819 set findprocpid [pid $f]
1820 fconfigure $f -blocking 0
1820 fconfigure $f -blocking 0
1821 fileevent $f readable readfindproc
1821 fileevent $f readable readfindproc
1822 set finddidsel 0
1822 set finddidsel 0
1823 . config -cursor watch
1823 . config -cursor watch
1824 settextcursor watch
1824 settextcursor watch
1825 set findinprogress 1
1825 set findinprogress 1
1826 }
1826 }
1827
1827
1828 proc readfindproc {} {
1828 proc readfindproc {} {
1829 global findprocfile finddidsel
1829 global findprocfile finddidsel
1830 global idline matchinglines findinsertpos
1830 global idline matchinglines findinsertpos
1831
1831
1832 set n [gets $findprocfile line]
1832 set n [gets $findprocfile line]
1833 if {$n < 0} {
1833 if {$n < 0} {
1834 if {[eof $findprocfile]} {
1834 if {[eof $findprocfile]} {
1835 stopfindproc 1
1835 stopfindproc 1
1836 if {!$finddidsel} {
1836 if {!$finddidsel} {
1837 bell
1837 bell
1838 }
1838 }
1839 }
1839 }
1840 return
1840 return
1841 }
1841 }
1842 if {![regexp {^[0-9a-f]{12}} $line id]} {
1842 if {![regexp {^[0-9a-f]{12}} $line id]} {
1843 error_popup "Can't parse git-diff-tree output: $line"
1843 error_popup "Can't parse git-diff-tree output: $line"
1844 stopfindproc
1844 stopfindproc
1845 return
1845 return
1846 }
1846 }
1847 if {![info exists idline($id)]} {
1847 if {![info exists idline($id)]} {
1848 puts stderr "spurious id: $id"
1848 puts stderr "spurious id: $id"
1849 return
1849 return
1850 }
1850 }
1851 set l $idline($id)
1851 set l $idline($id)
1852 insertmatch $l $id
1852 insertmatch $l $id
1853 }
1853 }
1854
1854
1855 proc insertmatch {l id} {
1855 proc insertmatch {l id} {
1856 global matchinglines findinsertpos finddidsel
1856 global matchinglines findinsertpos finddidsel
1857
1857
1858 if {$findinsertpos == "end"} {
1858 if {$findinsertpos == "end"} {
1859 if {$matchinglines != {} && $l < [lindex $matchinglines 0]} {
1859 if {$matchinglines != {} && $l < [lindex $matchinglines 0]} {
1860 set matchinglines [linsert $matchinglines 0 $l]
1860 set matchinglines [linsert $matchinglines 0 $l]
1861 set findinsertpos 1
1861 set findinsertpos 1
1862 } else {
1862 } else {
1863 lappend matchinglines $l
1863 lappend matchinglines $l
1864 }
1864 }
1865 } else {
1865 } else {
1866 set matchinglines [linsert $matchinglines $findinsertpos $l]
1866 set matchinglines [linsert $matchinglines $findinsertpos $l]
1867 incr findinsertpos
1867 incr findinsertpos
1868 }
1868 }
1869 markheadline $l $id
1869 markheadline $l $id
1870 if {!$finddidsel} {
1870 if {!$finddidsel} {
1871 findselectline $l
1871 findselectline $l
1872 set finddidsel 1
1872 set finddidsel 1
1873 }
1873 }
1874 }
1874 }
1875
1875
1876 proc findfiles {} {
1876 proc findfiles {} {
1877 global selectedline numcommits lineid ctext
1877 global selectedline numcommits lineid ctext
1878 global ffileline finddidsel parents nparents
1878 global ffileline finddidsel parents nparents
1879 global findinprogress findstartline findinsertpos
1879 global findinprogress findstartline findinsertpos
1880 global treediffs fdiffids fdiffsneeded fdiffpos
1880 global treediffs fdiffids fdiffsneeded fdiffpos
1881 global findmergefiles
1881 global findmergefiles
1882 global env
1882 global env
1883
1883
1884 if {$numcommits == 0} return
1884 if {$numcommits == 0} return
1885
1885
1886 if {[info exists selectedline]} {
1886 if {[info exists selectedline]} {
1887 set l [expr {$selectedline + 1}]
1887 set l [expr {$selectedline + 1}]
1888 } else {
1888 } else {
1889 set l 0
1889 set l 0
1890 }
1890 }
1891 set ffileline $l
1891 set ffileline $l
1892 set findstartline $l
1892 set findstartline $l
1893 set diffsneeded {}
1893 set diffsneeded {}
1894 set fdiffsneeded {}
1894 set fdiffsneeded {}
1895 while 1 {
1895 while 1 {
1896 set id $lineid($l)
1896 set id $lineid($l)
1897 if {$findmergefiles || $nparents($id) == 1} {
1897 if {$findmergefiles || $nparents($id) == 1} {
1898 foreach p $parents($id) {
1898 foreach p $parents($id) {
1899 if {![info exists treediffs([list $id $p])]} {
1899 if {![info exists treediffs([list $id $p])]} {
1900 append diffsneeded "$id $p\n"
1900 append diffsneeded "$id $p\n"
1901 lappend fdiffsneeded [list $id $p]
1901 lappend fdiffsneeded [list $id $p]
1902 }
1902 }
1903 }
1903 }
1904 }
1904 }
1905 if {[incr l] >= $numcommits} {
1905 if {[incr l] >= $numcommits} {
1906 set l 0
1906 set l 0
1907 }
1907 }
1908 if {$l == $findstartline} break
1908 if {$l == $findstartline} break
1909 }
1909 }
1910
1910
1911 # start off a git-diff-tree process if needed
1911 # start off a git-diff-tree process if needed
1912 if {$diffsneeded ne {}} {
1912 if {$diffsneeded ne {}} {
1913 if {[catch {
1913 if {[catch {
1914 set df [open [list | $env(HG) --config ui.report_untrusted=false debug-diff-tree -r --stdin << $diffsneeded] r]
1914 set df [open [list | $env(HG) --config ui.report_untrusted=false debug-diff-tree -r --stdin << $diffsneeded] r]
1915 } err ]} {
1915 } err ]} {
1916 error_popup "Error starting search process: $err"
1916 error_popup "Error starting search process: $err"
1917 return
1917 return
1918 }
1918 }
1919 catch {unset fdiffids}
1919 catch {unset fdiffids}
1920 set fdiffpos 0
1920 set fdiffpos 0
1921 fconfigure $df -blocking 0
1921 fconfigure $df -blocking 0
1922 fileevent $df readable [list readfilediffs $df]
1922 fileevent $df readable [list readfilediffs $df]
1923 }
1923 }
1924
1924
1925 set finddidsel 0
1925 set finddidsel 0
1926 set findinsertpos end
1926 set findinsertpos end
1927 set id $lineid($l)
1927 set id $lineid($l)
1928 set p [lindex $parents($id) 0]
1928 set p [lindex $parents($id) 0]
1929 . config -cursor watch
1929 . config -cursor watch
1930 settextcursor watch
1930 settextcursor watch
1931 set findinprogress 1
1931 set findinprogress 1
1932 findcont [list $id $p]
1932 findcont [list $id $p]
1933 update
1933 update
1934 }
1934 }
1935
1935
1936 proc readfilediffs {df} {
1936 proc readfilediffs {df} {
1937 global findids fdiffids fdiffs
1937 global findids fdiffids fdiffs
1938
1938
1939 set n [gets $df line]
1939 set n [gets $df line]
1940 if {$n < 0} {
1940 if {$n < 0} {
1941 if {[eof $df]} {
1941 if {[eof $df]} {
1942 donefilediff
1942 donefilediff
1943 if {[catch {close $df} err]} {
1943 if {[catch {close $df} err]} {
1944 stopfindproc
1944 stopfindproc
1945 bell
1945 bell
1946 error_popup "Error in hg debug-diff-tree: $err"
1946 error_popup "Error in hg debug-diff-tree: $err"
1947 } elseif {[info exists findids]} {
1947 } elseif {[info exists findids]} {
1948 set ids $findids
1948 set ids $findids
1949 stopfindproc
1949 stopfindproc
1950 bell
1950 bell
1951 error_popup "Couldn't find diffs for {$ids}"
1951 error_popup "Couldn't find diffs for {$ids}"
1952 }
1952 }
1953 }
1953 }
1954 return
1954 return
1955 }
1955 }
1956 if {[regexp {^([0-9a-f]{12}) \(from ([0-9a-f]{12})\)} $line match id p]} {
1956 if {[regexp {^([0-9a-f]{12}) \(from ([0-9a-f]{12})\)} $line match id p]} {
1957 # start of a new string of diffs
1957 # start of a new string of diffs
1958 donefilediff
1958 donefilediff
1959 set fdiffids [list $id $p]
1959 set fdiffids [list $id $p]
1960 set fdiffs {}
1960 set fdiffs {}
1961 } elseif {[string match ":*" $line]} {
1961 } elseif {[string match ":*" $line]} {
1962 lappend fdiffs [lindex $line 5]
1962 lappend fdiffs [lindex $line 5]
1963 }
1963 }
1964 }
1964 }
1965
1965
1966 proc donefilediff {} {
1966 proc donefilediff {} {
1967 global fdiffids fdiffs treediffs findids
1967 global fdiffids fdiffs treediffs findids
1968 global fdiffsneeded fdiffpos
1968 global fdiffsneeded fdiffpos
1969
1969
1970 if {[info exists fdiffids]} {
1970 if {[info exists fdiffids]} {
1971 while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids
1971 while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids
1972 && $fdiffpos < [llength $fdiffsneeded]} {
1972 && $fdiffpos < [llength $fdiffsneeded]} {
1973 # git-diff-tree doesn't output anything for a commit
1973 # git-diff-tree doesn't output anything for a commit
1974 # which doesn't change anything
1974 # which doesn't change anything
1975 set nullids [lindex $fdiffsneeded $fdiffpos]
1975 set nullids [lindex $fdiffsneeded $fdiffpos]
1976 set treediffs($nullids) {}
1976 set treediffs($nullids) {}
1977 if {[info exists findids] && $nullids eq $findids} {
1977 if {[info exists findids] && $nullids eq $findids} {
1978 unset findids
1978 unset findids
1979 findcont $nullids
1979 findcont $nullids
1980 }
1980 }
1981 incr fdiffpos
1981 incr fdiffpos
1982 }
1982 }
1983 incr fdiffpos
1983 incr fdiffpos
1984
1984
1985 if {![info exists treediffs($fdiffids)]} {
1985 if {![info exists treediffs($fdiffids)]} {
1986 set treediffs($fdiffids) $fdiffs
1986 set treediffs($fdiffids) $fdiffs
1987 }
1987 }
1988 if {[info exists findids] && $fdiffids eq $findids} {
1988 if {[info exists findids] && $fdiffids eq $findids} {
1989 unset findids
1989 unset findids
1990 findcont $fdiffids
1990 findcont $fdiffids
1991 }
1991 }
1992 }
1992 }
1993 }
1993 }
1994
1994
1995 proc findcont {ids} {
1995 proc findcont {ids} {
1996 global findids treediffs parents nparents
1996 global findids treediffs parents nparents
1997 global ffileline findstartline finddidsel
1997 global ffileline findstartline finddidsel
1998 global lineid numcommits matchinglines findinprogress
1998 global lineid numcommits matchinglines findinprogress
1999 global findmergefiles
1999 global findmergefiles
2000
2000
2001 set id [lindex $ids 0]
2001 set id [lindex $ids 0]
2002 set p [lindex $ids 1]
2002 set p [lindex $ids 1]
2003 set pi [lsearch -exact $parents($id) $p]
2003 set pi [lsearch -exact $parents($id) $p]
2004 set l $ffileline
2004 set l $ffileline
2005 while 1 {
2005 while 1 {
2006 if {$findmergefiles || $nparents($id) == 1} {
2006 if {$findmergefiles || $nparents($id) == 1} {
2007 if {![info exists treediffs($ids)]} {
2007 if {![info exists treediffs($ids)]} {
2008 set findids $ids
2008 set findids $ids
2009 set ffileline $l
2009 set ffileline $l
2010 return
2010 return
2011 }
2011 }
2012 set doesmatch 0
2012 set doesmatch 0
2013 foreach f $treediffs($ids) {
2013 foreach f $treediffs($ids) {
2014 set x [findmatches $f]
2014 set x [findmatches $f]
2015 if {$x != {}} {
2015 if {$x != {}} {
2016 set doesmatch 1
2016 set doesmatch 1
2017 break
2017 break
2018 }
2018 }
2019 }
2019 }
2020 if {$doesmatch} {
2020 if {$doesmatch} {
2021 insertmatch $l $id
2021 insertmatch $l $id
2022 set pi $nparents($id)
2022 set pi $nparents($id)
2023 }
2023 }
2024 } else {
2024 } else {
2025 set pi $nparents($id)
2025 set pi $nparents($id)
2026 }
2026 }
2027 if {[incr pi] >= $nparents($id)} {
2027 if {[incr pi] >= $nparents($id)} {
2028 set pi 0
2028 set pi 0
2029 if {[incr l] >= $numcommits} {
2029 if {[incr l] >= $numcommits} {
2030 set l 0
2030 set l 0
2031 }
2031 }
2032 if {$l == $findstartline} break
2032 if {$l == $findstartline} break
2033 set id $lineid($l)
2033 set id $lineid($l)
2034 }
2034 }
2035 set p [lindex $parents($id) $pi]
2035 set p [lindex $parents($id) $pi]
2036 set ids [list $id $p]
2036 set ids [list $id $p]
2037 }
2037 }
2038 stopfindproc
2038 stopfindproc
2039 if {!$finddidsel} {
2039 if {!$finddidsel} {
2040 bell
2040 bell
2041 }
2041 }
2042 }
2042 }
2043
2043
2044 # mark a commit as matching by putting a yellow background
2044 # mark a commit as matching by putting a yellow background
2045 # behind the headline
2045 # behind the headline
2046 proc markheadline {l id} {
2046 proc markheadline {l id} {
2047 global canv mainfont linehtag commitinfo
2047 global canv mainfont linehtag commitinfo
2048
2048
2049 set bbox [$canv bbox $linehtag($l)]
2049 set bbox [$canv bbox $linehtag($l)]
2050 set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
2050 set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
2051 $canv lower $t
2051 $canv lower $t
2052 }
2052 }
2053
2053
2054 # mark the bits of a headline, author or date that match a find string
2054 # mark the bits of a headline, author or date that match a find string
2055 proc markmatches {canv l str tag matches font} {
2055 proc markmatches {canv l str tag matches font} {
2056 set bbox [$canv bbox $tag]
2056 set bbox [$canv bbox $tag]
2057 set x0 [lindex $bbox 0]
2057 set x0 [lindex $bbox 0]
2058 set y0 [lindex $bbox 1]
2058 set y0 [lindex $bbox 1]
2059 set y1 [lindex $bbox 3]
2059 set y1 [lindex $bbox 3]
2060 foreach match $matches {
2060 foreach match $matches {
2061 set start [lindex $match 0]
2061 set start [lindex $match 0]
2062 set end [lindex $match 1]
2062 set end [lindex $match 1]
2063 if {$start > $end} continue
2063 if {$start > $end} continue
2064 set xoff [font measure $font [string range $str 0 [expr $start-1]]]
2064 set xoff [font measure $font [string range $str 0 [expr $start-1]]]
2065 set xlen [font measure $font [string range $str 0 [expr $end]]]
2065 set xlen [font measure $font [string range $str 0 [expr $end]]]
2066 set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
2066 set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
2067 -outline {} -tags matches -fill yellow]
2067 -outline {} -tags matches -fill yellow]
2068 $canv lower $t
2068 $canv lower $t
2069 }
2069 }
2070 }
2070 }
2071
2071
2072 proc unmarkmatches {} {
2072 proc unmarkmatches {} {
2073 global matchinglines findids
2073 global matchinglines findids
2074 allcanvs delete matches
2074 allcanvs delete matches
2075 catch {unset matchinglines}
2075 catch {unset matchinglines}
2076 catch {unset findids}
2076 catch {unset findids}
2077 }
2077 }
2078
2078
2079 proc selcanvline {w x y} {
2079 proc selcanvline {w x y} {
2080 global canv canvy0 ctext linespc
2080 global canv canvy0 ctext linespc
2081 global lineid linehtag linentag linedtag rowtextx
2081 global lineid linehtag linentag linedtag rowtextx
2082 set ymax [lindex [$canv cget -scrollregion] 3]
2082 set ymax [lindex [$canv cget -scrollregion] 3]
2083 if {$ymax == {}} return
2083 if {$ymax == {}} return
2084 set yfrac [lindex [$canv yview] 0]
2084 set yfrac [lindex [$canv yview] 0]
2085 set y [expr {$y + $yfrac * $ymax}]
2085 set y [expr {$y + $yfrac * $ymax}]
2086 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
2086 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
2087 if {$l < 0} {
2087 if {$l < 0} {
2088 set l 0
2088 set l 0
2089 }
2089 }
2090 if {$w eq $canv} {
2090 if {$w eq $canv} {
2091 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
2091 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
2092 }
2092 }
2093 unmarkmatches
2093 unmarkmatches
2094 selectline $l 1
2094 selectline $l 1
2095 }
2095 }
2096
2096
2097 proc commit_descriptor {p} {
2097 proc commit_descriptor {p} {
2098 global commitinfo
2098 global commitinfo
2099 set l "..."
2099 set l "..."
2100 if {[info exists commitinfo($p)]} {
2100 if {[info exists commitinfo($p)]} {
2101 set l [lindex $commitinfo($p) 0]
2101 set l [lindex $commitinfo($p) 0]
2102 set r [lindex $commitinfo($p) 6]
2102 set r [lindex $commitinfo($p) 6]
2103 }
2103 }
2104 return "$r:$p ($l)"
2104 return "$r:$p ($l)"
2105 }
2105 }
2106
2106
2107 # append some text to the ctext widget, and make any SHA1 ID
2107 # append some text to the ctext widget, and make any SHA1 ID
2108 # that we know about be a clickable link.
2108 # that we know about be a clickable link.
2109 proc appendwithlinks {text} {
2109 proc appendwithlinks {text} {
2110 global ctext idline linknum
2110 global ctext idline linknum
2111
2111
2112 set start [$ctext index "end - 1c"]
2112 set start [$ctext index "end - 1c"]
2113 $ctext insert end $text
2113 $ctext insert end $text
2114 $ctext insert end "\n"
2114 $ctext insert end "\n"
2115 set links [regexp -indices -all -inline {[0-9a-f]{12}} $text]
2115 set links [regexp -indices -all -inline {[0-9a-f]{12}} $text]
2116 foreach l $links {
2116 foreach l $links {
2117 set s [lindex $l 0]
2117 set s [lindex $l 0]
2118 set e [lindex $l 1]
2118 set e [lindex $l 1]
2119 set linkid [string range $text $s $e]
2119 set linkid [string range $text $s $e]
2120 if {![info exists idline($linkid)]} continue
2120 if {![info exists idline($linkid)]} continue
2121 incr e
2121 incr e
2122 $ctext tag add link "$start + $s c" "$start + $e c"
2122 $ctext tag add link "$start + $s c" "$start + $e c"
2123 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
2123 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
2124 $ctext tag bind link$linknum <1> [list selectline $idline($linkid) 1]
2124 $ctext tag bind link$linknum <1> [list selectline $idline($linkid) 1]
2125 incr linknum
2125 incr linknum
2126 }
2126 }
2127 $ctext tag conf link -foreground blue -underline 1
2127 $ctext tag conf link -foreground blue -underline 1
2128 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
2128 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
2129 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
2129 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
2130 }
2130 }
2131
2131
2132 proc selectline {l isnew} {
2132 proc selectline {l isnew} {
2133 global canv canv2 canv3 ctext commitinfo selectedline
2133 global canv canv2 canv3 ctext commitinfo selectedline
2134 global lineid linehtag linentag linedtag
2134 global lineid linehtag linentag linedtag
2135 global canvy0 linespc parents nparents children
2135 global canvy0 linespc parents nparents children
2136 global cflist currentid sha1entry
2136 global cflist currentid sha1entry
2137 global commentend idtags idline linknum
2137 global commentend idtags idline linknum
2138
2138
2139 $canv delete hover
2139 $canv delete hover
2140 normalline
2140 normalline
2141 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
2141 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
2142 $canv delete secsel
2142 $canv delete secsel
2143 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
2143 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
2144 -tags secsel -fill [$canv cget -selectbackground]]
2144 -tags secsel -fill [$canv cget -selectbackground]]
2145 $canv lower $t
2145 $canv lower $t
2146 $canv2 delete secsel
2146 $canv2 delete secsel
2147 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
2147 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
2148 -tags secsel -fill [$canv2 cget -selectbackground]]
2148 -tags secsel -fill [$canv2 cget -selectbackground]]
2149 $canv2 lower $t
2149 $canv2 lower $t
2150 $canv3 delete secsel
2150 $canv3 delete secsel
2151 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
2151 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
2152 -tags secsel -fill [$canv3 cget -selectbackground]]
2152 -tags secsel -fill [$canv3 cget -selectbackground]]
2153 $canv3 lower $t
2153 $canv3 lower $t
2154 set y [expr {$canvy0 + $l * $linespc}]
2154 set y [expr {$canvy0 + $l * $linespc}]
2155 set ymax [lindex [$canv cget -scrollregion] 3]
2155 set ymax [lindex [$canv cget -scrollregion] 3]
2156 set ytop [expr {$y - $linespc - 1}]
2156 set ytop [expr {$y - $linespc - 1}]
2157 set ybot [expr {$y + $linespc + 1}]
2157 set ybot [expr {$y + $linespc + 1}]
2158 set wnow [$canv yview]
2158 set wnow [$canv yview]
2159 set wtop [expr [lindex $wnow 0] * $ymax]
2159 set wtop [expr [lindex $wnow 0] * $ymax]
2160 set wbot [expr [lindex $wnow 1] * $ymax]
2160 set wbot [expr [lindex $wnow 1] * $ymax]
2161 set wh [expr {$wbot - $wtop}]
2161 set wh [expr {$wbot - $wtop}]
2162 set newtop $wtop
2162 set newtop $wtop
2163 if {$ytop < $wtop} {
2163 if {$ytop < $wtop} {
2164 if {$ybot < $wtop} {
2164 if {$ybot < $wtop} {
2165 set newtop [expr {$y - $wh / 2.0}]
2165 set newtop [expr {$y - $wh / 2.0}]
2166 } else {
2166 } else {
2167 set newtop $ytop
2167 set newtop $ytop
2168 if {$newtop > $wtop - $linespc} {
2168 if {$newtop > $wtop - $linespc} {
2169 set newtop [expr {$wtop - $linespc}]
2169 set newtop [expr {$wtop - $linespc}]
2170 }
2170 }
2171 }
2171 }
2172 } elseif {$ybot > $wbot} {
2172 } elseif {$ybot > $wbot} {
2173 if {$ytop > $wbot} {
2173 if {$ytop > $wbot} {
2174 set newtop [expr {$y - $wh / 2.0}]
2174 set newtop [expr {$y - $wh / 2.0}]
2175 } else {
2175 } else {
2176 set newtop [expr {$ybot - $wh}]
2176 set newtop [expr {$ybot - $wh}]
2177 if {$newtop < $wtop + $linespc} {
2177 if {$newtop < $wtop + $linespc} {
2178 set newtop [expr {$wtop + $linespc}]
2178 set newtop [expr {$wtop + $linespc}]
2179 }
2179 }
2180 }
2180 }
2181 }
2181 }
2182 if {$newtop != $wtop} {
2182 if {$newtop != $wtop} {
2183 if {$newtop < 0} {
2183 if {$newtop < 0} {
2184 set newtop 0
2184 set newtop 0
2185 }
2185 }
2186 allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
2186 allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
2187 }
2187 }
2188
2188
2189 if {$isnew} {
2189 if {$isnew} {
2190 addtohistory [list selectline $l 0]
2190 addtohistory [list selectline $l 0]
2191 }
2191 }
2192
2192
2193 set selectedline $l
2193 set selectedline $l
2194
2194
2195 set id $lineid($l)
2195 set id $lineid($l)
2196 set currentid $id
2196 set currentid $id
2197 $sha1entry delete 0 end
2197 $sha1entry delete 0 end
2198 $sha1entry insert 0 $id
2198 $sha1entry insert 0 $id
2199 $sha1entry selection from 0
2199 $sha1entry selection from 0
2200 $sha1entry selection to end
2200 $sha1entry selection to end
2201
2201
2202 $ctext conf -state normal
2202 $ctext conf -state normal
2203 $ctext delete 0.0 end
2203 $ctext delete 0.0 end
2204 set linknum 0
2204 set linknum 0
2205 $ctext mark set fmark.0 0.0
2205 $ctext mark set fmark.0 0.0
2206 $ctext mark gravity fmark.0 left
2206 $ctext mark gravity fmark.0 left
2207 set info $commitinfo($id)
2207 set info $commitinfo($id)
2208 $ctext insert end "Revision: [lindex $info 6]\n"
2208 $ctext insert end "Revision: [lindex $info 6]\n"
2209 $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
2209 $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
2210 $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
2210 $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
2211 if {[info exists idtags($id)]} {
2211 if {[info exists idtags($id)]} {
2212 $ctext insert end "Tags:"
2212 $ctext insert end "Tags:"
2213 foreach tag $idtags($id) {
2213 foreach tag $idtags($id) {
2214 $ctext insert end " $tag"
2214 $ctext insert end " $tag"
2215 }
2215 }
2216 $ctext insert end "\n"
2216 $ctext insert end "\n"
2217 }
2217 }
2218
2218
2219 set comment {}
2219 set comment {}
2220 if {[info exists parents($id)]} {
2220 if {[info exists parents($id)]} {
2221 foreach p $parents($id) {
2221 foreach p $parents($id) {
2222 append comment "Parent: [commit_descriptor $p]\n"
2222 append comment "Parent: [commit_descriptor $p]\n"
2223 }
2223 }
2224 }
2224 }
2225 if {[info exists children($id)]} {
2225 if {[info exists children($id)]} {
2226 foreach c $children($id) {
2226 foreach c $children($id) {
2227 append comment "Child: [commit_descriptor $c]\n"
2227 append comment "Child: [commit_descriptor $c]\n"
2228 }
2228 }
2229 }
2229 }
2230 append comment "\n"
2230 append comment "\n"
2231 append comment [lindex $info 5]
2231 append comment [lindex $info 5]
2232
2232
2233 # make anything that looks like a SHA1 ID be a clickable link
2233 # make anything that looks like a SHA1 ID be a clickable link
2234 appendwithlinks $comment
2234 appendwithlinks $comment
2235
2235
2236 $ctext tag delete Comments
2236 $ctext tag delete Comments
2237 $ctext tag remove found 1.0 end
2237 $ctext tag remove found 1.0 end
2238 $ctext conf -state disabled
2238 $ctext conf -state disabled
2239 set commentend [$ctext index "end - 1c"]
2239 set commentend [$ctext index "end - 1c"]
2240
2240
2241 $cflist delete 0 end
2241 $cflist delete 0 end
2242 $cflist insert end "Comments"
2242 $cflist insert end "Comments"
2243 if {$nparents($id) == 1} {
2243 if {$nparents($id) == 1} {
2244 startdiff [concat $id $parents($id)]
2244 startdiff [concat $id $parents($id)]
2245 } elseif {$nparents($id) > 1} {
2245 } elseif {$nparents($id) > 1} {
2246 mergediff $id
2246 mergediff $id
2247 }
2247 }
2248 }
2248 }
2249
2249
2250 proc selnextline {dir} {
2250 proc selnextline {dir} {
2251 global selectedline
2251 global selectedline
2252 if {![info exists selectedline]} return
2252 if {![info exists selectedline]} return
2253 set l [expr $selectedline + $dir]
2253 set l [expr $selectedline + $dir]
2254 unmarkmatches
2254 unmarkmatches
2255 selectline $l 1
2255 selectline $l 1
2256 }
2256 }
2257
2257
2258 proc unselectline {} {
2258 proc unselectline {} {
2259 global selectedline
2259 global selectedline
2260
2260
2261 catch {unset selectedline}
2261 catch {unset selectedline}
2262 allcanvs delete secsel
2262 allcanvs delete secsel
2263 }
2263 }
2264
2264
2265 proc addtohistory {cmd} {
2265 proc addtohistory {cmd} {
2266 global history historyindex
2266 global history historyindex
2267
2267
2268 if {$historyindex > 0
2268 if {$historyindex > 0
2269 && [lindex $history [expr {$historyindex - 1}]] == $cmd} {
2269 && [lindex $history [expr {$historyindex - 1}]] == $cmd} {
2270 return
2270 return
2271 }
2271 }
2272
2272
2273 if {$historyindex < [llength $history]} {
2273 if {$historyindex < [llength $history]} {
2274 set history [lreplace $history $historyindex end $cmd]
2274 set history [lreplace $history $historyindex end $cmd]
2275 } else {
2275 } else {
2276 lappend history $cmd
2276 lappend history $cmd
2277 }
2277 }
2278 incr historyindex
2278 incr historyindex
2279 if {$historyindex > 1} {
2279 if {$historyindex > 1} {
2280 .ctop.top.bar.leftbut conf -state normal
2280 .ctop.top.bar.leftbut conf -state normal
2281 } else {
2281 } else {
2282 .ctop.top.bar.leftbut conf -state disabled
2282 .ctop.top.bar.leftbut conf -state disabled
2283 }
2283 }
2284 .ctop.top.bar.rightbut conf -state disabled
2284 .ctop.top.bar.rightbut conf -state disabled
2285 }
2285 }
2286
2286
2287 proc goback {} {
2287 proc goback {} {
2288 global history historyindex
2288 global history historyindex
2289
2289
2290 if {$historyindex > 1} {
2290 if {$historyindex > 1} {
2291 incr historyindex -1
2291 incr historyindex -1
2292 set cmd [lindex $history [expr {$historyindex - 1}]]
2292 set cmd [lindex $history [expr {$historyindex - 1}]]
2293 eval $cmd
2293 eval $cmd
2294 .ctop.top.bar.rightbut conf -state normal
2294 .ctop.top.bar.rightbut conf -state normal
2295 }
2295 }
2296 if {$historyindex <= 1} {
2296 if {$historyindex <= 1} {
2297 .ctop.top.bar.leftbut conf -state disabled
2297 .ctop.top.bar.leftbut conf -state disabled
2298 }
2298 }
2299 }
2299 }
2300
2300
2301 proc goforw {} {
2301 proc goforw {} {
2302 global history historyindex
2302 global history historyindex
2303
2303
2304 if {$historyindex < [llength $history]} {
2304 if {$historyindex < [llength $history]} {
2305 set cmd [lindex $history $historyindex]
2305 set cmd [lindex $history $historyindex]
2306 incr historyindex
2306 incr historyindex
2307 eval $cmd
2307 eval $cmd
2308 .ctop.top.bar.leftbut conf -state normal
2308 .ctop.top.bar.leftbut conf -state normal
2309 }
2309 }
2310 if {$historyindex >= [llength $history]} {
2310 if {$historyindex >= [llength $history]} {
2311 .ctop.top.bar.rightbut conf -state disabled
2311 .ctop.top.bar.rightbut conf -state disabled
2312 }
2312 }
2313 }
2313 }
2314
2314
2315 proc mergediff {id} {
2315 proc mergediff {id} {
2316 global parents diffmergeid diffmergegca mergefilelist diffpindex
2316 global parents diffmergeid diffmergegca mergefilelist diffpindex
2317
2317
2318 set diffmergeid $id
2318 set diffmergeid $id
2319 set diffpindex -1
2319 set diffpindex -1
2320 set diffmergegca [findgca $parents($id)]
2320 set diffmergegca [findgca $parents($id)]
2321 if {[info exists mergefilelist($id)]} {
2321 if {[info exists mergefilelist($id)]} {
2322 if {$mergefilelist($id) ne {}} {
2322 if {$mergefilelist($id) ne {}} {
2323 showmergediff
2323 showmergediff
2324 }
2324 }
2325 } else {
2325 } else {
2326 contmergediff {}
2326 contmergediff {}
2327 }
2327 }
2328 }
2328 }
2329
2329
2330 proc findgca {ids} {
2330 proc findgca {ids} {
2331 global env
2331 global env
2332 set gca {}
2332 set gca {}
2333 foreach id $ids {
2333 foreach id $ids {
2334 if {$gca eq {}} {
2334 if {$gca eq {}} {
2335 set gca $id
2335 set gca $id
2336 } else {
2336 } else {
2337 if {[catch {
2337 if {[catch {
2338 set gca [exec $env(HG) --config ui.report_untrusted=false debug-merge-base $gca $id]
2338 set gca [exec $env(HG) --config ui.report_untrusted=false debug-merge-base $gca $id]
2339 } err]} {
2339 } err]} {
2340 return {}
2340 return {}
2341 }
2341 }
2342 }
2342 }
2343 }
2343 }
2344 return $gca
2344 return $gca
2345 }
2345 }
2346
2346
2347 proc contmergediff {ids} {
2347 proc contmergediff {ids} {
2348 global diffmergeid diffpindex parents nparents diffmergegca
2348 global diffmergeid diffpindex parents nparents diffmergegca
2349 global treediffs mergefilelist diffids treepending
2349 global treediffs mergefilelist diffids treepending
2350
2350
2351 # diff the child against each of the parents, and diff
2351 # diff the child against each of the parents, and diff
2352 # each of the parents against the GCA.
2352 # each of the parents against the GCA.
2353 while 1 {
2353 while 1 {
2354 if {[lindex $ids 0] == $diffmergeid && $diffmergegca ne {}} {
2354 if {[lindex $ids 0] == $diffmergeid && $diffmergegca ne {}} {
2355 set ids [list [lindex $ids 1] $diffmergegca]
2355 set ids [list [lindex $ids 1] $diffmergegca]
2356 } else {
2356 } else {
2357 if {[incr diffpindex] >= $nparents($diffmergeid)} break
2357 if {[incr diffpindex] >= $nparents($diffmergeid)} break
2358 set p [lindex $parents($diffmergeid) $diffpindex]
2358 set p [lindex $parents($diffmergeid) $diffpindex]
2359 set ids [list $diffmergeid $p]
2359 set ids [list $diffmergeid $p]
2360 }
2360 }
2361 if {![info exists treediffs($ids)]} {
2361 if {![info exists treediffs($ids)]} {
2362 set diffids $ids
2362 set diffids $ids
2363 if {![info exists treepending]} {
2363 if {![info exists treepending]} {
2364 gettreediffs $ids
2364 gettreediffs $ids
2365 }
2365 }
2366 return
2366 return
2367 }
2367 }
2368 }
2368 }
2369
2369
2370 # If a file in some parent is different from the child and also
2370 # If a file in some parent is different from the child and also
2371 # different from the GCA, then it's interesting.
2371 # different from the GCA, then it's interesting.
2372 # If we don't have a GCA, then a file is interesting if it is
2372 # If we don't have a GCA, then a file is interesting if it is
2373 # different from the child in all the parents.
2373 # different from the child in all the parents.
2374 if {$diffmergegca ne {}} {
2374 if {$diffmergegca ne {}} {
2375 set files {}
2375 set files {}
2376 foreach p $parents($diffmergeid) {
2376 foreach p $parents($diffmergeid) {
2377 set gcadiffs $treediffs([list $p $diffmergegca])
2377 set gcadiffs $treediffs([list $p $diffmergegca])
2378 foreach f $treediffs([list $diffmergeid $p]) {
2378 foreach f $treediffs([list $diffmergeid $p]) {
2379 if {[lsearch -exact $files $f] < 0
2379 if {[lsearch -exact $files $f] < 0
2380 && [lsearch -exact $gcadiffs $f] >= 0} {
2380 && [lsearch -exact $gcadiffs $f] >= 0} {
2381 lappend files $f
2381 lappend files $f
2382 }
2382 }
2383 }
2383 }
2384 }
2384 }
2385 set files [lsort $files]
2385 set files [lsort $files]
2386 } else {
2386 } else {
2387 set p [lindex $parents($diffmergeid) 0]
2387 set p [lindex $parents($diffmergeid) 0]
2388 set files $treediffs([list $diffmergeid $p])
2388 set files $treediffs([list $diffmergeid $p])
2389 for {set i 1} {$i < $nparents($diffmergeid) && $files ne {}} {incr i} {
2389 for {set i 1} {$i < $nparents($diffmergeid) && $files ne {}} {incr i} {
2390 set p [lindex $parents($diffmergeid) $i]
2390 set p [lindex $parents($diffmergeid) $i]
2391 set df $treediffs([list $diffmergeid $p])
2391 set df $treediffs([list $diffmergeid $p])
2392 set nf {}
2392 set nf {}
2393 foreach f $files {
2393 foreach f $files {
2394 if {[lsearch -exact $df $f] >= 0} {
2394 if {[lsearch -exact $df $f] >= 0} {
2395 lappend nf $f
2395 lappend nf $f
2396 }
2396 }
2397 }
2397 }
2398 set files $nf
2398 set files $nf
2399 }
2399 }
2400 }
2400 }
2401
2401
2402 set mergefilelist($diffmergeid) $files
2402 set mergefilelist($diffmergeid) $files
2403 if {$files ne {}} {
2403 if {$files ne {}} {
2404 showmergediff
2404 showmergediff
2405 }
2405 }
2406 }
2406 }
2407
2407
2408 proc showmergediff {} {
2408 proc showmergediff {} {
2409 global cflist diffmergeid mergefilelist parents
2409 global cflist diffmergeid mergefilelist parents
2410 global diffopts diffinhunk currentfile currenthunk filelines
2410 global diffopts diffinhunk currentfile currenthunk filelines
2411 global diffblocked groupfilelast mergefds groupfilenum grouphunks
2411 global diffblocked groupfilelast mergefds groupfilenum grouphunks
2412 global env
2412 global env
2413
2413
2414 set files $mergefilelist($diffmergeid)
2414 set files $mergefilelist($diffmergeid)
2415 foreach f $files {
2415 foreach f $files {
2416 $cflist insert end $f
2416 $cflist insert end $f
2417 }
2417 }
2418 set env(GIT_DIFF_OPTS) $diffopts
2418 set env(GIT_DIFF_OPTS) $diffopts
2419 set flist {}
2419 set flist {}
2420 catch {unset currentfile}
2420 catch {unset currentfile}
2421 catch {unset currenthunk}
2421 catch {unset currenthunk}
2422 catch {unset filelines}
2422 catch {unset filelines}
2423 catch {unset groupfilenum}
2423 catch {unset groupfilenum}
2424 catch {unset grouphunks}
2424 catch {unset grouphunks}
2425 set groupfilelast -1
2425 set groupfilelast -1
2426 foreach p $parents($diffmergeid) {
2426 foreach p $parents($diffmergeid) {
2427 set cmd [list | $env(HG) --config ui.report_untrusted=false debug-diff-tree -p $p $diffmergeid]
2427 set cmd [list | $env(HG) --config ui.report_untrusted=false debug-diff-tree -p $p $diffmergeid]
2428 set cmd [concat $cmd $mergefilelist($diffmergeid)]
2428 set cmd [concat $cmd $mergefilelist($diffmergeid)]
2429 if {[catch {set f [open $cmd r]} err]} {
2429 if {[catch {set f [open $cmd r]} err]} {
2430 error_popup "Error getting diffs: $err"
2430 error_popup "Error getting diffs: $err"
2431 foreach f $flist {
2431 foreach f $flist {
2432 catch {close $f}
2432 catch {close $f}
2433 }
2433 }
2434 return
2434 return
2435 }
2435 }
2436 lappend flist $f
2436 lappend flist $f
2437 set ids [list $diffmergeid $p]
2437 set ids [list $diffmergeid $p]
2438 set mergefds($ids) $f
2438 set mergefds($ids) $f
2439 set diffinhunk($ids) 0
2439 set diffinhunk($ids) 0
2440 set diffblocked($ids) 0
2440 set diffblocked($ids) 0
2441 fconfigure $f -blocking 0
2441 fconfigure $f -blocking 0
2442 fileevent $f readable [list getmergediffline $f $ids $diffmergeid]
2442 fileevent $f readable [list getmergediffline $f $ids $diffmergeid]
2443 }
2443 }
2444 }
2444 }
2445
2445
2446 proc getmergediffline {f ids id} {
2446 proc getmergediffline {f ids id} {
2447 global diffmergeid diffinhunk diffoldlines diffnewlines
2447 global diffmergeid diffinhunk diffoldlines diffnewlines
2448 global currentfile currenthunk
2448 global currentfile currenthunk
2449 global diffoldstart diffnewstart diffoldlno diffnewlno
2449 global diffoldstart diffnewstart diffoldlno diffnewlno
2450 global diffblocked mergefilelist
2450 global diffblocked mergefilelist
2451 global noldlines nnewlines difflcounts filelines
2451 global noldlines nnewlines difflcounts filelines
2452
2452
2453 set n [gets $f line]
2453 set n [gets $f line]
2454 if {$n < 0} {
2454 if {$n < 0} {
2455 if {![eof $f]} return
2455 if {![eof $f]} return
2456 }
2456 }
2457
2457
2458 if {!([info exists diffmergeid] && $diffmergeid == $id)} {
2458 if {!([info exists diffmergeid] && $diffmergeid == $id)} {
2459 if {$n < 0} {
2459 if {$n < 0} {
2460 close $f
2460 close $f
2461 }
2461 }
2462 return
2462 return
2463 }
2463 }
2464
2464
2465 if {$diffinhunk($ids) != 0} {
2465 if {$diffinhunk($ids) != 0} {
2466 set fi $currentfile($ids)
2466 set fi $currentfile($ids)
2467 if {$n > 0 && [regexp {^[-+ \\]} $line match]} {
2467 if {$n > 0 && [regexp {^[-+ \\]} $line match]} {
2468 # continuing an existing hunk
2468 # continuing an existing hunk
2469 set line [string range $line 1 end]
2469 set line [string range $line 1 end]
2470 set p [lindex $ids 1]
2470 set p [lindex $ids 1]
2471 if {$match eq "-" || $match eq " "} {
2471 if {$match eq "-" || $match eq " "} {
2472 set filelines($p,$fi,$diffoldlno($ids)) $line
2472 set filelines($p,$fi,$diffoldlno($ids)) $line
2473 incr diffoldlno($ids)
2473 incr diffoldlno($ids)
2474 }
2474 }
2475 if {$match eq "+" || $match eq " "} {
2475 if {$match eq "+" || $match eq " "} {
2476 set filelines($id,$fi,$diffnewlno($ids)) $line
2476 set filelines($id,$fi,$diffnewlno($ids)) $line
2477 incr diffnewlno($ids)
2477 incr diffnewlno($ids)
2478 }
2478 }
2479 if {$match eq " "} {
2479 if {$match eq " "} {
2480 if {$diffinhunk($ids) == 2} {
2480 if {$diffinhunk($ids) == 2} {
2481 lappend difflcounts($ids) \
2481 lappend difflcounts($ids) \
2482 [list $noldlines($ids) $nnewlines($ids)]
2482 [list $noldlines($ids) $nnewlines($ids)]
2483 set noldlines($ids) 0
2483 set noldlines($ids) 0
2484 set diffinhunk($ids) 1
2484 set diffinhunk($ids) 1
2485 }
2485 }
2486 incr noldlines($ids)
2486 incr noldlines($ids)
2487 } elseif {$match eq "-" || $match eq "+"} {
2487 } elseif {$match eq "-" || $match eq "+"} {
2488 if {$diffinhunk($ids) == 1} {
2488 if {$diffinhunk($ids) == 1} {
2489 lappend difflcounts($ids) [list $noldlines($ids)]
2489 lappend difflcounts($ids) [list $noldlines($ids)]
2490 set noldlines($ids) 0
2490 set noldlines($ids) 0
2491 set nnewlines($ids) 0
2491 set nnewlines($ids) 0
2492 set diffinhunk($ids) 2
2492 set diffinhunk($ids) 2
2493 }
2493 }
2494 if {$match eq "-"} {
2494 if {$match eq "-"} {
2495 incr noldlines($ids)
2495 incr noldlines($ids)
2496 } else {
2496 } else {
2497 incr nnewlines($ids)
2497 incr nnewlines($ids)
2498 }
2498 }
2499 }
2499 }
2500 # and if it's \ No newline at end of line, then what?
2500 # and if it's \ No newline at end of line, then what?
2501 return
2501 return
2502 }
2502 }
2503 # end of a hunk
2503 # end of a hunk
2504 if {$diffinhunk($ids) == 1 && $noldlines($ids) != 0} {
2504 if {$diffinhunk($ids) == 1 && $noldlines($ids) != 0} {
2505 lappend difflcounts($ids) [list $noldlines($ids)]
2505 lappend difflcounts($ids) [list $noldlines($ids)]
2506 } elseif {$diffinhunk($ids) == 2
2506 } elseif {$diffinhunk($ids) == 2
2507 && ($noldlines($ids) != 0 || $nnewlines($ids) != 0)} {
2507 && ($noldlines($ids) != 0 || $nnewlines($ids) != 0)} {
2508 lappend difflcounts($ids) [list $noldlines($ids) $nnewlines($ids)]
2508 lappend difflcounts($ids) [list $noldlines($ids) $nnewlines($ids)]
2509 }
2509 }
2510 set currenthunk($ids) [list $currentfile($ids) \
2510 set currenthunk($ids) [list $currentfile($ids) \
2511 $diffoldstart($ids) $diffnewstart($ids) \
2511 $diffoldstart($ids) $diffnewstart($ids) \
2512 $diffoldlno($ids) $diffnewlno($ids) \
2512 $diffoldlno($ids) $diffnewlno($ids) \
2513 $difflcounts($ids)]
2513 $difflcounts($ids)]
2514 set diffinhunk($ids) 0
2514 set diffinhunk($ids) 0
2515 # -1 = need to block, 0 = unblocked, 1 = is blocked
2515 # -1 = need to block, 0 = unblocked, 1 = is blocked
2516 set diffblocked($ids) -1
2516 set diffblocked($ids) -1
2517 processhunks
2517 processhunks
2518 if {$diffblocked($ids) == -1} {
2518 if {$diffblocked($ids) == -1} {
2519 fileevent $f readable {}
2519 fileevent $f readable {}
2520 set diffblocked($ids) 1
2520 set diffblocked($ids) 1
2521 }
2521 }
2522 }
2522 }
2523
2523
2524 if {$n < 0} {
2524 if {$n < 0} {
2525 # eof
2525 # eof
2526 if {!$diffblocked($ids)} {
2526 if {!$diffblocked($ids)} {
2527 close $f
2527 close $f
2528 set currentfile($ids) [llength $mergefilelist($diffmergeid)]
2528 set currentfile($ids) [llength $mergefilelist($diffmergeid)]
2529 set currenthunk($ids) [list $currentfile($ids) 0 0 0 0 {}]
2529 set currenthunk($ids) [list $currentfile($ids) 0 0 0 0 {}]
2530 processhunks
2530 processhunks
2531 }
2531 }
2532 } elseif {[regexp {^diff --git a/(.*) b/} $line match fname]} {
2532 } elseif {[regexp {^diff --git a/(.*) b/} $line match fname]} {
2533 # start of a new file
2533 # start of a new file
2534 set currentfile($ids) \
2534 set currentfile($ids) \
2535 [lsearch -exact $mergefilelist($diffmergeid) $fname]
2535 [lsearch -exact $mergefilelist($diffmergeid) $fname]
2536 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2536 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2537 $line match f1l f1c f2l f2c rest]} {
2537 $line match f1l f1c f2l f2c rest]} {
2538 if {[info exists currentfile($ids)] && $currentfile($ids) >= 0} {
2538 if {[info exists currentfile($ids)] && $currentfile($ids) >= 0} {
2539 # start of a new hunk
2539 # start of a new hunk
2540 if {$f1l == 0 && $f1c == 0} {
2540 if {$f1l == 0 && $f1c == 0} {
2541 set f1l 1
2541 set f1l 1
2542 }
2542 }
2543 if {$f2l == 0 && $f2c == 0} {
2543 if {$f2l == 0 && $f2c == 0} {
2544 set f2l 1
2544 set f2l 1
2545 }
2545 }
2546 set diffinhunk($ids) 1
2546 set diffinhunk($ids) 1
2547 set diffoldstart($ids) $f1l
2547 set diffoldstart($ids) $f1l
2548 set diffnewstart($ids) $f2l
2548 set diffnewstart($ids) $f2l
2549 set diffoldlno($ids) $f1l
2549 set diffoldlno($ids) $f1l
2550 set diffnewlno($ids) $f2l
2550 set diffnewlno($ids) $f2l
2551 set difflcounts($ids) {}
2551 set difflcounts($ids) {}
2552 set noldlines($ids) 0
2552 set noldlines($ids) 0
2553 set nnewlines($ids) 0
2553 set nnewlines($ids) 0
2554 }
2554 }
2555 }
2555 }
2556 }
2556 }
2557
2557
2558 proc processhunks {} {
2558 proc processhunks {} {
2559 global diffmergeid parents nparents currenthunk
2559 global diffmergeid parents nparents currenthunk
2560 global mergefilelist diffblocked mergefds
2560 global mergefilelist diffblocked mergefds
2561 global grouphunks grouplinestart grouplineend groupfilenum
2561 global grouphunks grouplinestart grouplineend groupfilenum
2562
2562
2563 set nfiles [llength $mergefilelist($diffmergeid)]
2563 set nfiles [llength $mergefilelist($diffmergeid)]
2564 while 1 {
2564 while 1 {
2565 set fi $nfiles
2565 set fi $nfiles
2566 set lno 0
2566 set lno 0
2567 # look for the earliest hunk
2567 # look for the earliest hunk
2568 foreach p $parents($diffmergeid) {
2568 foreach p $parents($diffmergeid) {
2569 set ids [list $diffmergeid $p]
2569 set ids [list $diffmergeid $p]
2570 if {![info exists currenthunk($ids)]} return
2570 if {![info exists currenthunk($ids)]} return
2571 set i [lindex $currenthunk($ids) 0]
2571 set i [lindex $currenthunk($ids) 0]
2572 set l [lindex $currenthunk($ids) 2]
2572 set l [lindex $currenthunk($ids) 2]
2573 if {$i < $fi || ($i == $fi && $l < $lno)} {
2573 if {$i < $fi || ($i == $fi && $l < $lno)} {
2574 set fi $i
2574 set fi $i
2575 set lno $l
2575 set lno $l
2576 set pi $p
2576 set pi $p
2577 }
2577 }
2578 }
2578 }
2579
2579
2580 if {$fi < $nfiles} {
2580 if {$fi < $nfiles} {
2581 set ids [list $diffmergeid $pi]
2581 set ids [list $diffmergeid $pi]
2582 set hunk $currenthunk($ids)
2582 set hunk $currenthunk($ids)
2583 unset currenthunk($ids)
2583 unset currenthunk($ids)
2584 if {$diffblocked($ids) > 0} {
2584 if {$diffblocked($ids) > 0} {
2585 fileevent $mergefds($ids) readable \
2585 fileevent $mergefds($ids) readable \
2586 [list getmergediffline $mergefds($ids) $ids $diffmergeid]
2586 [list getmergediffline $mergefds($ids) $ids $diffmergeid]
2587 }
2587 }
2588 set diffblocked($ids) 0
2588 set diffblocked($ids) 0
2589
2589
2590 if {[info exists groupfilenum] && $groupfilenum == $fi
2590 if {[info exists groupfilenum] && $groupfilenum == $fi
2591 && $lno <= $grouplineend} {
2591 && $lno <= $grouplineend} {
2592 # add this hunk to the pending group
2592 # add this hunk to the pending group
2593 lappend grouphunks($pi) $hunk
2593 lappend grouphunks($pi) $hunk
2594 set endln [lindex $hunk 4]
2594 set endln [lindex $hunk 4]
2595 if {$endln > $grouplineend} {
2595 if {$endln > $grouplineend} {
2596 set grouplineend $endln
2596 set grouplineend $endln
2597 }
2597 }
2598 continue
2598 continue
2599 }
2599 }
2600 }
2600 }
2601
2601
2602 # succeeding stuff doesn't belong in this group, so
2602 # succeeding stuff doesn't belong in this group, so
2603 # process the group now
2603 # process the group now
2604 if {[info exists groupfilenum]} {
2604 if {[info exists groupfilenum]} {
2605 processgroup
2605 processgroup
2606 unset groupfilenum
2606 unset groupfilenum
2607 unset grouphunks
2607 unset grouphunks
2608 }
2608 }
2609
2609
2610 if {$fi >= $nfiles} break
2610 if {$fi >= $nfiles} break
2611
2611
2612 # start a new group
2612 # start a new group
2613 set groupfilenum $fi
2613 set groupfilenum $fi
2614 set grouphunks($pi) [list $hunk]
2614 set grouphunks($pi) [list $hunk]
2615 set grouplinestart $lno
2615 set grouplinestart $lno
2616 set grouplineend [lindex $hunk 4]
2616 set grouplineend [lindex $hunk 4]
2617 }
2617 }
2618 }
2618 }
2619
2619
2620 proc processgroup {} {
2620 proc processgroup {} {
2621 global groupfilelast groupfilenum difffilestart
2621 global groupfilelast groupfilenum difffilestart
2622 global mergefilelist diffmergeid ctext filelines
2622 global mergefilelist diffmergeid ctext filelines
2623 global parents diffmergeid diffoffset
2623 global parents diffmergeid diffoffset
2624 global grouphunks grouplinestart grouplineend nparents
2624 global grouphunks grouplinestart grouplineend nparents
2625 global mergemax
2625 global mergemax
2626
2626
2627 $ctext conf -state normal
2627 $ctext conf -state normal
2628 set id $diffmergeid
2628 set id $diffmergeid
2629 set f $groupfilenum
2629 set f $groupfilenum
2630 if {$groupfilelast != $f} {
2630 if {$groupfilelast != $f} {
2631 $ctext insert end "\n"
2631 $ctext insert end "\n"
2632 set here [$ctext index "end - 1c"]
2632 set here [$ctext index "end - 1c"]
2633 set difffilestart($f) $here
2633 set difffilestart($f) $here
2634 set mark fmark.[expr {$f + 1}]
2634 set mark fmark.[expr {$f + 1}]
2635 $ctext mark set $mark $here
2635 $ctext mark set $mark $here
2636 $ctext mark gravity $mark left
2636 $ctext mark gravity $mark left
2637 set header [lindex $mergefilelist($id) $f]
2637 set header [lindex $mergefilelist($id) $f]
2638 set l [expr {(78 - [string length $header]) / 2}]
2638 set l [expr {(78 - [string length $header]) / 2}]
2639 set pad [string range "----------------------------------------" 1 $l]
2639 set pad [string range "----------------------------------------" 1 $l]
2640 $ctext insert end "$pad $header $pad\n" filesep
2640 $ctext insert end "$pad $header $pad\n" filesep
2641 set groupfilelast $f
2641 set groupfilelast $f
2642 foreach p $parents($id) {
2642 foreach p $parents($id) {
2643 set diffoffset($p) 0
2643 set diffoffset($p) 0
2644 }
2644 }
2645 }
2645 }
2646
2646
2647 $ctext insert end "@@" msep
2647 $ctext insert end "@@" msep
2648 set nlines [expr {$grouplineend - $grouplinestart}]
2648 set nlines [expr {$grouplineend - $grouplinestart}]
2649 set events {}
2649 set events {}
2650 set pnum 0
2650 set pnum 0
2651 foreach p $parents($id) {
2651 foreach p $parents($id) {
2652 set startline [expr {$grouplinestart + $diffoffset($p)}]
2652 set startline [expr {$grouplinestart + $diffoffset($p)}]
2653 set ol $startline
2653 set ol $startline
2654 set nl $grouplinestart
2654 set nl $grouplinestart
2655 if {[info exists grouphunks($p)]} {
2655 if {[info exists grouphunks($p)]} {
2656 foreach h $grouphunks($p) {
2656 foreach h $grouphunks($p) {
2657 set l [lindex $h 2]
2657 set l [lindex $h 2]
2658 if {$nl < $l} {
2658 if {$nl < $l} {
2659 for {} {$nl < $l} {incr nl} {
2659 for {} {$nl < $l} {incr nl} {
2660 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2660 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2661 incr ol
2661 incr ol
2662 }
2662 }
2663 }
2663 }
2664 foreach chunk [lindex $h 5] {
2664 foreach chunk [lindex $h 5] {
2665 if {[llength $chunk] == 2} {
2665 if {[llength $chunk] == 2} {
2666 set olc [lindex $chunk 0]
2666 set olc [lindex $chunk 0]
2667 set nlc [lindex $chunk 1]
2667 set nlc [lindex $chunk 1]
2668 set nnl [expr {$nl + $nlc}]
2668 set nnl [expr {$nl + $nlc}]
2669 lappend events [list $nl $nnl $pnum $olc $nlc]
2669 lappend events [list $nl $nnl $pnum $olc $nlc]
2670 incr ol $olc
2670 incr ol $olc
2671 set nl $nnl
2671 set nl $nnl
2672 } else {
2672 } else {
2673 incr ol [lindex $chunk 0]
2673 incr ol [lindex $chunk 0]
2674 incr nl [lindex $chunk 0]
2674 incr nl [lindex $chunk 0]
2675 }
2675 }
2676 }
2676 }
2677 }
2677 }
2678 }
2678 }
2679 if {$nl < $grouplineend} {
2679 if {$nl < $grouplineend} {
2680 for {} {$nl < $grouplineend} {incr nl} {
2680 for {} {$nl < $grouplineend} {incr nl} {
2681 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2681 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2682 incr ol
2682 incr ol
2683 }
2683 }
2684 }
2684 }
2685 set nlines [expr {$ol - $startline}]
2685 set nlines [expr {$ol - $startline}]
2686 $ctext insert end " -$startline,$nlines" msep
2686 $ctext insert end " -$startline,$nlines" msep
2687 incr pnum
2687 incr pnum
2688 }
2688 }
2689
2689
2690 set nlines [expr {$grouplineend - $grouplinestart}]
2690 set nlines [expr {$grouplineend - $grouplinestart}]
2691 $ctext insert end " +$grouplinestart,$nlines @@\n" msep
2691 $ctext insert end " +$grouplinestart,$nlines @@\n" msep
2692
2692
2693 set events [lsort -integer -index 0 $events]
2693 set events [lsort -integer -index 0 $events]
2694 set nevents [llength $events]
2694 set nevents [llength $events]
2695 set nmerge $nparents($diffmergeid)
2695 set nmerge $nparents($diffmergeid)
2696 set l $grouplinestart
2696 set l $grouplinestart
2697 for {set i 0} {$i < $nevents} {set i $j} {
2697 for {set i 0} {$i < $nevents} {set i $j} {
2698 set nl [lindex $events $i 0]
2698 set nl [lindex $events $i 0]
2699 while {$l < $nl} {
2699 while {$l < $nl} {
2700 $ctext insert end " $filelines($id,$f,$l)\n"
2700 $ctext insert end " $filelines($id,$f,$l)\n"
2701 incr l
2701 incr l
2702 }
2702 }
2703 set e [lindex $events $i]
2703 set e [lindex $events $i]
2704 set enl [lindex $e 1]
2704 set enl [lindex $e 1]
2705 set j $i
2705 set j $i
2706 set active {}
2706 set active {}
2707 while 1 {
2707 while 1 {
2708 set pnum [lindex $e 2]
2708 set pnum [lindex $e 2]
2709 set olc [lindex $e 3]
2709 set olc [lindex $e 3]
2710 set nlc [lindex $e 4]
2710 set nlc [lindex $e 4]
2711 if {![info exists delta($pnum)]} {
2711 if {![info exists delta($pnum)]} {
2712 set delta($pnum) [expr {$olc - $nlc}]
2712 set delta($pnum) [expr {$olc - $nlc}]
2713 lappend active $pnum
2713 lappend active $pnum
2714 } else {
2714 } else {
2715 incr delta($pnum) [expr {$olc - $nlc}]
2715 incr delta($pnum) [expr {$olc - $nlc}]
2716 }
2716 }
2717 if {[incr j] >= $nevents} break
2717 if {[incr j] >= $nevents} break
2718 set e [lindex $events $j]
2718 set e [lindex $events $j]
2719 if {[lindex $e 0] >= $enl} break
2719 if {[lindex $e 0] >= $enl} break
2720 if {[lindex $e 1] > $enl} {
2720 if {[lindex $e 1] > $enl} {
2721 set enl [lindex $e 1]
2721 set enl [lindex $e 1]
2722 }
2722 }
2723 }
2723 }
2724 set nlc [expr {$enl - $l}]
2724 set nlc [expr {$enl - $l}]
2725 set ncol mresult
2725 set ncol mresult
2726 set bestpn -1
2726 set bestpn -1
2727 if {[llength $active] == $nmerge - 1} {
2727 if {[llength $active] == $nmerge - 1} {
2728 # no diff for one of the parents, i.e. it's identical
2728 # no diff for one of the parents, i.e. it's identical
2729 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2729 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2730 if {![info exists delta($pnum)]} {
2730 if {![info exists delta($pnum)]} {
2731 if {$pnum < $mergemax} {
2731 if {$pnum < $mergemax} {
2732 lappend ncol m$pnum
2732 lappend ncol m$pnum
2733 } else {
2733 } else {
2734 lappend ncol mmax
2734 lappend ncol mmax
2735 }
2735 }
2736 break
2736 break
2737 }
2737 }
2738 }
2738 }
2739 } elseif {[llength $active] == $nmerge} {
2739 } elseif {[llength $active] == $nmerge} {
2740 # all parents are different, see if one is very similar
2740 # all parents are different, see if one is very similar
2741 set bestsim 30
2741 set bestsim 30
2742 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2742 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2743 set sim [similarity $pnum $l $nlc $f \
2743 set sim [similarity $pnum $l $nlc $f \
2744 [lrange $events $i [expr {$j-1}]]]
2744 [lrange $events $i [expr {$j-1}]]]
2745 if {$sim > $bestsim} {
2745 if {$sim > $bestsim} {
2746 set bestsim $sim
2746 set bestsim $sim
2747 set bestpn $pnum
2747 set bestpn $pnum
2748 }
2748 }
2749 }
2749 }
2750 if {$bestpn >= 0} {
2750 if {$bestpn >= 0} {
2751 lappend ncol m$bestpn
2751 lappend ncol m$bestpn
2752 }
2752 }
2753 }
2753 }
2754 set pnum -1
2754 set pnum -1
2755 foreach p $parents($id) {
2755 foreach p $parents($id) {
2756 incr pnum
2756 incr pnum
2757 if {![info exists delta($pnum)] || $pnum == $bestpn} continue
2757 if {![info exists delta($pnum)] || $pnum == $bestpn} continue
2758 set olc [expr {$nlc + $delta($pnum)}]
2758 set olc [expr {$nlc + $delta($pnum)}]
2759 set ol [expr {$l + $diffoffset($p)}]
2759 set ol [expr {$l + $diffoffset($p)}]
2760 incr diffoffset($p) $delta($pnum)
2760 incr diffoffset($p) $delta($pnum)
2761 unset delta($pnum)
2761 unset delta($pnum)
2762 for {} {$olc > 0} {incr olc -1} {
2762 for {} {$olc > 0} {incr olc -1} {
2763 $ctext insert end "-$filelines($p,$f,$ol)\n" m$pnum
2763 $ctext insert end "-$filelines($p,$f,$ol)\n" m$pnum
2764 incr ol
2764 incr ol
2765 }
2765 }
2766 }
2766 }
2767 set endl [expr {$l + $nlc}]
2767 set endl [expr {$l + $nlc}]
2768 if {$bestpn >= 0} {
2768 if {$bestpn >= 0} {
2769 # show this pretty much as a normal diff
2769 # show this pretty much as a normal diff
2770 set p [lindex $parents($id) $bestpn]
2770 set p [lindex $parents($id) $bestpn]
2771 set ol [expr {$l + $diffoffset($p)}]
2771 set ol [expr {$l + $diffoffset($p)}]
2772 incr diffoffset($p) $delta($bestpn)
2772 incr diffoffset($p) $delta($bestpn)
2773 unset delta($bestpn)
2773 unset delta($bestpn)
2774 for {set k $i} {$k < $j} {incr k} {
2774 for {set k $i} {$k < $j} {incr k} {
2775 set e [lindex $events $k]
2775 set e [lindex $events $k]
2776 if {[lindex $e 2] != $bestpn} continue
2776 if {[lindex $e 2] != $bestpn} continue
2777 set nl [lindex $e 0]
2777 set nl [lindex $e 0]
2778 set ol [expr {$ol + $nl - $l}]
2778 set ol [expr {$ol + $nl - $l}]
2779 for {} {$l < $nl} {incr l} {
2779 for {} {$l < $nl} {incr l} {
2780 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2780 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2781 }
2781 }
2782 set c [lindex $e 3]
2782 set c [lindex $e 3]
2783 for {} {$c > 0} {incr c -1} {
2783 for {} {$c > 0} {incr c -1} {
2784 $ctext insert end "-$filelines($p,$f,$ol)\n" m$bestpn
2784 $ctext insert end "-$filelines($p,$f,$ol)\n" m$bestpn
2785 incr ol
2785 incr ol
2786 }
2786 }
2787 set nl [lindex $e 1]
2787 set nl [lindex $e 1]
2788 for {} {$l < $nl} {incr l} {
2788 for {} {$l < $nl} {incr l} {
2789 $ctext insert end "+$filelines($id,$f,$l)\n" mresult
2789 $ctext insert end "+$filelines($id,$f,$l)\n" mresult
2790 }
2790 }
2791 }
2791 }
2792 }
2792 }
2793 for {} {$l < $endl} {incr l} {
2793 for {} {$l < $endl} {incr l} {
2794 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2794 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2795 }
2795 }
2796 }
2796 }
2797 while {$l < $grouplineend} {
2797 while {$l < $grouplineend} {
2798 $ctext insert end " $filelines($id,$f,$l)\n"
2798 $ctext insert end " $filelines($id,$f,$l)\n"
2799 incr l
2799 incr l
2800 }
2800 }
2801 $ctext conf -state disabled
2801 $ctext conf -state disabled
2802 }
2802 }
2803
2803
2804 proc similarity {pnum l nlc f events} {
2804 proc similarity {pnum l nlc f events} {
2805 global diffmergeid parents diffoffset filelines
2805 global diffmergeid parents diffoffset filelines
2806
2806
2807 set id $diffmergeid
2807 set id $diffmergeid
2808 set p [lindex $parents($id) $pnum]
2808 set p [lindex $parents($id) $pnum]
2809 set ol [expr {$l + $diffoffset($p)}]
2809 set ol [expr {$l + $diffoffset($p)}]
2810 set endl [expr {$l + $nlc}]
2810 set endl [expr {$l + $nlc}]
2811 set same 0
2811 set same 0
2812 set diff 0
2812 set diff 0
2813 foreach e $events {
2813 foreach e $events {
2814 if {[lindex $e 2] != $pnum} continue
2814 if {[lindex $e 2] != $pnum} continue
2815 set nl [lindex $e 0]
2815 set nl [lindex $e 0]
2816 set ol [expr {$ol + $nl - $l}]
2816 set ol [expr {$ol + $nl - $l}]
2817 for {} {$l < $nl} {incr l} {
2817 for {} {$l < $nl} {incr l} {
2818 incr same [string length $filelines($id,$f,$l)]
2818 incr same [string length $filelines($id,$f,$l)]
2819 incr same
2819 incr same
2820 }
2820 }
2821 set oc [lindex $e 3]
2821 set oc [lindex $e 3]
2822 for {} {$oc > 0} {incr oc -1} {
2822 for {} {$oc > 0} {incr oc -1} {
2823 incr diff [string length $filelines($p,$f,$ol)]
2823 incr diff [string length $filelines($p,$f,$ol)]
2824 incr diff
2824 incr diff
2825 incr ol
2825 incr ol
2826 }
2826 }
2827 set nl [lindex $e 1]
2827 set nl [lindex $e 1]
2828 for {} {$l < $nl} {incr l} {
2828 for {} {$l < $nl} {incr l} {
2829 incr diff [string length $filelines($id,$f,$l)]
2829 incr diff [string length $filelines($id,$f,$l)]
2830 incr diff
2830 incr diff
2831 }
2831 }
2832 }
2832 }
2833 for {} {$l < $endl} {incr l} {
2833 for {} {$l < $endl} {incr l} {
2834 incr same [string length $filelines($id,$f,$l)]
2834 incr same [string length $filelines($id,$f,$l)]
2835 incr same
2835 incr same
2836 }
2836 }
2837 if {$same == 0} {
2837 if {$same == 0} {
2838 return 0
2838 return 0
2839 }
2839 }
2840 return [expr {200 * $same / (2 * $same + $diff)}]
2840 return [expr {200 * $same / (2 * $same + $diff)}]
2841 }
2841 }
2842
2842
2843 proc startdiff {ids} {
2843 proc startdiff {ids} {
2844 global treediffs diffids treepending diffmergeid
2844 global treediffs diffids treepending diffmergeid
2845
2845
2846 set diffids $ids
2846 set diffids $ids
2847 catch {unset diffmergeid}
2847 catch {unset diffmergeid}
2848 if {![info exists treediffs($ids)]} {
2848 if {![info exists treediffs($ids)]} {
2849 if {![info exists treepending]} {
2849 if {![info exists treepending]} {
2850 gettreediffs $ids
2850 gettreediffs $ids
2851 }
2851 }
2852 } else {
2852 } else {
2853 addtocflist $ids
2853 addtocflist $ids
2854 }
2854 }
2855 }
2855 }
2856
2856
2857 proc addtocflist {ids} {
2857 proc addtocflist {ids} {
2858 global treediffs cflist
2858 global treediffs cflist
2859 foreach f $treediffs($ids) {
2859 foreach f $treediffs($ids) {
2860 $cflist insert end $f
2860 $cflist insert end $f
2861 }
2861 }
2862 getblobdiffs $ids
2862 getblobdiffs $ids
2863 }
2863 }
2864
2864
2865 proc gettreediffs {ids} {
2865 proc gettreediffs {ids} {
2866 global treediff parents treepending env
2866 global treediff parents treepending env
2867 set treepending $ids
2867 set treepending $ids
2868 set treediff {}
2868 set treediff {}
2869 set id [lindex $ids 0]
2869 set id [lindex $ids 0]
2870 set p [lindex $ids 1]
2870 set p [lindex $ids 1]
2871 if [catch {set gdtf [open "|{$env(HG)} --config ui.report_untrusted=false debug-diff-tree -r $p $id" r]}] return
2871 if [catch {set gdtf [open "|{$env(HG)} --config ui.report_untrusted=false debug-diff-tree -r $p $id" r]}] return
2872 fconfigure $gdtf -blocking 0
2872 fconfigure $gdtf -blocking 0
2873 fileevent $gdtf readable [list gettreediffline $gdtf $ids]
2873 fileevent $gdtf readable [list gettreediffline $gdtf $ids]
2874 }
2874 }
2875
2875
2876 proc gettreediffline {gdtf ids} {
2876 proc gettreediffline {gdtf ids} {
2877 global treediff treediffs treepending diffids diffmergeid
2877 global treediff treediffs treepending diffids diffmergeid
2878
2878
2879 set n [gets $gdtf line]
2879 set n [gets $gdtf line]
2880 if {$n < 0} {
2880 if {$n < 0} {
2881 if {![eof $gdtf]} return
2881 if {![eof $gdtf]} return
2882 close $gdtf
2882 close $gdtf
2883 set treediffs($ids) $treediff
2883 set treediffs($ids) $treediff
2884 unset treepending
2884 unset treepending
2885 if {$ids != $diffids} {
2885 if {$ids != $diffids} {
2886 gettreediffs $diffids
2886 gettreediffs $diffids
2887 } else {
2887 } else {
2888 if {[info exists diffmergeid]} {
2888 if {[info exists diffmergeid]} {
2889 contmergediff $ids
2889 contmergediff $ids
2890 } else {
2890 } else {
2891 addtocflist $ids
2891 addtocflist $ids
2892 }
2892 }
2893 }
2893 }
2894 return
2894 return
2895 }
2895 }
2896 set tab1 [expr [string first "\t" $line] + 1]
2896 set tab1 [expr [string first "\t" $line] + 1]
2897 set tab2 [expr [string first "\t" $line $tab1] - 1]
2897 set tab2 [expr [string first "\t" $line $tab1] - 1]
2898 set file [string range $line $tab1 $tab2]
2898 set file [string range $line $tab1 $tab2]
2899 lappend treediff $file
2899 lappend treediff $file
2900 }
2900 }
2901
2901
2902 proc getblobdiffs {ids} {
2902 proc getblobdiffs {ids} {
2903 global diffopts blobdifffd diffids env curdifftag curtagstart
2903 global diffopts blobdifffd diffids env curdifftag curtagstart
2904 global difffilestart nextupdate diffinhdr treediffs
2904 global difffilestart nextupdate diffinhdr treediffs
2905
2905
2906 set id [lindex $ids 0]
2906 set id [lindex $ids 0]
2907 set p [lindex $ids 1]
2907 set p [lindex $ids 1]
2908 set env(GIT_DIFF_OPTS) $diffopts
2908 set env(GIT_DIFF_OPTS) $diffopts
2909 set cmd [list | $env(HG) --config ui.report_untrusted=false debug-diff-tree -r -p -C $p $id]
2909 set cmd [list | $env(HG) --config ui.report_untrusted=false debug-diff-tree -r -p -C $p $id]
2910 if {[catch {set bdf [open $cmd r]} err]} {
2910 if {[catch {set bdf [open $cmd r]} err]} {
2911 puts "error getting diffs: $err"
2911 puts "error getting diffs: $err"
2912 return
2912 return
2913 }
2913 }
2914 set diffinhdr 0
2914 set diffinhdr 0
2915 fconfigure $bdf -blocking 0
2915 fconfigure $bdf -blocking 0
2916 set blobdifffd($ids) $bdf
2916 set blobdifffd($ids) $bdf
2917 set curdifftag Comments
2917 set curdifftag Comments
2918 set curtagstart 0.0
2918 set curtagstart 0.0
2919 catch {unset difffilestart}
2919 catch {unset difffilestart}
2920 fileevent $bdf readable [list getblobdiffline $bdf $diffids]
2920 fileevent $bdf readable [list getblobdiffline $bdf $diffids]
2921 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
2921 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
2922 }
2922 }
2923
2923
2924 proc getblobdiffline {bdf ids} {
2924 proc getblobdiffline {bdf ids} {
2925 global diffids blobdifffd ctext curdifftag curtagstart
2925 global diffids blobdifffd ctext curdifftag curtagstart
2926 global diffnexthead diffnextnote difffilestart
2926 global diffnexthead diffnextnote difffilestart
2927 global nextupdate diffinhdr treediffs
2927 global nextupdate diffinhdr treediffs
2928 global gaudydiff
2928 global gaudydiff
2929
2929
2930 set n [gets $bdf line]
2930 set n [gets $bdf line]
2931 if {$n < 0} {
2931 if {$n < 0} {
2932 if {[eof $bdf]} {
2932 if {[eof $bdf]} {
2933 close $bdf
2933 close $bdf
2934 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
2934 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
2935 $ctext tag add $curdifftag $curtagstart end
2935 $ctext tag add $curdifftag $curtagstart end
2936 }
2936 }
2937 }
2937 }
2938 return
2938 return
2939 }
2939 }
2940 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
2940 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
2941 return
2941 return
2942 }
2942 }
2943 regsub -all "\r" $line "" line
2943 regsub -all "\r" $line "" line
2944 $ctext conf -state normal
2944 $ctext conf -state normal
2945 if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
2945 if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
2946 # start of a new file
2946 # start of a new file
2947 $ctext insert end "\n"
2947 $ctext insert end "\n"
2948 $ctext tag add $curdifftag $curtagstart end
2948 $ctext tag add $curdifftag $curtagstart end
2949 set curtagstart [$ctext index "end - 1c"]
2949 set curtagstart [$ctext index "end - 1c"]
2950 set header $newname
2950 set header $newname
2951 set here [$ctext index "end - 1c"]
2951 set here [$ctext index "end - 1c"]
2952 set i [lsearch -exact $treediffs($diffids) $fname]
2952 set i [lsearch -exact $treediffs($diffids) $fname]
2953 if {$i >= 0} {
2953 if {$i >= 0} {
2954 set difffilestart($i) $here
2954 set difffilestart($i) $here
2955 incr i
2955 incr i
2956 $ctext mark set fmark.$i $here
2956 $ctext mark set fmark.$i $here
2957 $ctext mark gravity fmark.$i left
2957 $ctext mark gravity fmark.$i left
2958 }
2958 }
2959 if {$newname != $fname} {
2959 if {$newname != $fname} {
2960 set i [lsearch -exact $treediffs($diffids) $newname]
2960 set i [lsearch -exact $treediffs($diffids) $newname]
2961 if {$i >= 0} {
2961 if {$i >= 0} {
2962 set difffilestart($i) $here
2962 set difffilestart($i) $here
2963 incr i
2963 incr i
2964 $ctext mark set fmark.$i $here
2964 $ctext mark set fmark.$i $here
2965 $ctext mark gravity fmark.$i left
2965 $ctext mark gravity fmark.$i left
2966 }
2966 }
2967 }
2967 }
2968 set curdifftag "f:$fname"
2968 set curdifftag "f:$fname"
2969 $ctext tag delete $curdifftag
2969 $ctext tag delete $curdifftag
2970 set l [expr {(78 - [string length $header]) / 2}]
2970 set l [expr {(78 - [string length $header]) / 2}]
2971 set pad [string range "----------------------------------------" 1 $l]
2971 set pad [string range "----------------------------------------" 1 $l]
2972 $ctext insert end "$pad $header $pad\n" filesep
2972 $ctext insert end "$pad $header $pad\n" filesep
2973 set diffinhdr 1
2973 set diffinhdr 1
2974 } elseif {[regexp {^(---|\+\+\+)} $line]} {
2974 } elseif {[regexp {^(---|\+\+\+)} $line]} {
2975 set diffinhdr 0
2975 set diffinhdr 0
2976 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2976 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2977 $line match f1l f1c f2l f2c rest]} {
2977 $line match f1l f1c f2l f2c rest]} {
2978 if {$gaudydiff} {
2978 if {$gaudydiff} {
2979 $ctext insert end "\t" hunksep
2979 $ctext insert end "\t" hunksep
2980 $ctext insert end " $f1l " d0 " $f2l " d1
2980 $ctext insert end " $f1l " d0 " $f2l " d1
2981 $ctext insert end " $rest \n" hunksep
2981 $ctext insert end " $rest \n" hunksep
2982 } else {
2982 } else {
2983 $ctext insert end "$line\n" hunksep
2983 $ctext insert end "$line\n" hunksep
2984 }
2984 }
2985 set diffinhdr 0
2985 set diffinhdr 0
2986 } else {
2986 } else {
2987 set x [string range $line 0 0]
2987 set x [string range $line 0 0]
2988 if {$x == "-" || $x == "+"} {
2988 if {$x == "-" || $x == "+"} {
2989 set tag [expr {$x == "+"}]
2989 set tag [expr {$x == "+"}]
2990 if {$gaudydiff} {
2990 if {$gaudydiff} {
2991 set line [string range $line 1 end]
2991 set line [string range $line 1 end]
2992 }
2992 }
2993 $ctext insert end "$line\n" d$tag
2993 $ctext insert end "$line\n" d$tag
2994 } elseif {$x == " "} {
2994 } elseif {$x == " "} {
2995 if {$gaudydiff} {
2995 if {$gaudydiff} {
2996 set line [string range $line 1 end]
2996 set line [string range $line 1 end]
2997 }
2997 }
2998 $ctext insert end "$line\n"
2998 $ctext insert end "$line\n"
2999 } elseif {$diffinhdr || $x == "\\"} {
2999 } elseif {$diffinhdr || $x == "\\"} {
3000 # e.g. "\ No newline at end of file"
3000 # e.g. "\ No newline at end of file"
3001 $ctext insert end "$line\n" filesep
3001 $ctext insert end "$line\n" filesep
3002 } elseif {$line != ""} {
3002 } elseif {$line != ""} {
3003 # Something else we don't recognize
3003 # Something else we don't recognize
3004 if {$curdifftag != "Comments"} {
3004 if {$curdifftag != "Comments"} {
3005 $ctext insert end "\n"
3005 $ctext insert end "\n"
3006 $ctext tag add $curdifftag $curtagstart end
3006 $ctext tag add $curdifftag $curtagstart end
3007 set curtagstart [$ctext index "end - 1c"]
3007 set curtagstart [$ctext index "end - 1c"]
3008 set curdifftag Comments
3008 set curdifftag Comments
3009 }
3009 }
3010 $ctext insert end "$line\n" filesep
3010 $ctext insert end "$line\n" filesep
3011 }
3011 }
3012 }
3012 }
3013 $ctext conf -state disabled
3013 $ctext conf -state disabled
3014 if {[clock clicks -milliseconds] >= $nextupdate} {
3014 if {[clock clicks -milliseconds] >= $nextupdate} {
3015 incr nextupdate 100
3015 incr nextupdate 100
3016 fileevent $bdf readable {}
3016 fileevent $bdf readable {}
3017 update
3017 update
3018 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
3018 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
3019 }
3019 }
3020 }
3020 }
3021
3021
3022 proc nextfile {} {
3022 proc nextfile {} {
3023 global difffilestart ctext
3023 global difffilestart ctext
3024 set here [$ctext index @0,0]
3024 set here [$ctext index @0,0]
3025 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
3025 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
3026 if {[$ctext compare $difffilestart($i) > $here]} {
3026 if {[$ctext compare $difffilestart($i) > $here]} {
3027 if {![info exists pos]
3027 if {![info exists pos]
3028 || [$ctext compare $difffilestart($i) < $pos]} {
3028 || [$ctext compare $difffilestart($i) < $pos]} {
3029 set pos $difffilestart($i)
3029 set pos $difffilestart($i)
3030 }
3030 }
3031 }
3031 }
3032 }
3032 }
3033 if {[info exists pos]} {
3033 if {[info exists pos]} {
3034 $ctext yview $pos
3034 $ctext yview $pos
3035 }
3035 }
3036 }
3036 }
3037
3037
3038 proc listboxsel {} {
3038 proc listboxsel {} {
3039 global ctext cflist currentid
3039 global ctext cflist currentid
3040 if {![info exists currentid]} return
3040 if {![info exists currentid]} return
3041 set sel [lsort [$cflist curselection]]
3041 set sel [lsort [$cflist curselection]]
3042 if {$sel eq {}} return
3042 if {$sel eq {}} return
3043 set first [lindex $sel 0]
3043 set first [lindex $sel 0]
3044 catch {$ctext yview fmark.$first}
3044 catch {$ctext yview fmark.$first}
3045 }
3045 }
3046
3046
3047 proc setcoords {} {
3047 proc setcoords {} {
3048 global linespc charspc canvx0 canvy0 mainfont
3048 global linespc charspc canvx0 canvy0 mainfont
3049 global xspc1 xspc2 lthickness
3049 global xspc1 xspc2 lthickness
3050
3050
3051 set linespc [font metrics $mainfont -linespace]
3051 set linespc [font metrics $mainfont -linespace]
3052 set charspc [font measure $mainfont "m"]
3052 set charspc [font measure $mainfont "m"]
3053 set canvy0 [expr 3 + 0.5 * $linespc]
3053 set canvy0 [expr 3 + 0.5 * $linespc]
3054 set canvx0 [expr 3 + 0.5 * $linespc]
3054 set canvx0 [expr 3 + 0.5 * $linespc]
3055 set lthickness [expr {int($linespc / 9) + 1}]
3055 set lthickness [expr {int($linespc / 9) + 1}]
3056 set xspc1(0) $linespc
3056 set xspc1(0) $linespc
3057 set xspc2 $linespc
3057 set xspc2 $linespc
3058 }
3058 }
3059
3059
3060 proc redisplay {} {
3060 proc redisplay {} {
3061 global stopped redisplaying phase
3061 global stopped redisplaying phase
3062 if {$stopped > 1} return
3062 if {$stopped > 1} return
3063 if {$phase == "getcommits"} return
3063 if {$phase == "getcommits"} return
3064 set redisplaying 1
3064 set redisplaying 1
3065 if {$phase == "drawgraph" || $phase == "incrdraw"} {
3065 if {$phase == "drawgraph" || $phase == "incrdraw"} {
3066 set stopped 1
3066 set stopped 1
3067 } else {
3067 } else {
3068 drawgraph
3068 drawgraph
3069 }
3069 }
3070 }
3070 }
3071
3071
3072 proc incrfont {inc} {
3072 proc incrfont {inc} {
3073 global mainfont namefont textfont ctext canv phase
3073 global mainfont namefont textfont ctext canv phase
3074 global stopped entries
3074 global stopped entries
3075 unmarkmatches
3075 unmarkmatches
3076 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
3076 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
3077 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
3077 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
3078 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
3078 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
3079 setcoords
3079 setcoords
3080 $ctext conf -font $textfont
3080 $ctext conf -font $textfont
3081 $ctext tag conf filesep -font [concat $textfont bold]
3081 $ctext tag conf filesep -font [concat $textfont bold]
3082 foreach e $entries {
3082 foreach e $entries {
3083 $e conf -font $mainfont
3083 $e conf -font $mainfont
3084 }
3084 }
3085 if {$phase == "getcommits"} {
3085 if {$phase == "getcommits"} {
3086 $canv itemconf textitems -font $mainfont
3086 $canv itemconf textitems -font $mainfont
3087 }
3087 }
3088 redisplay
3088 redisplay
3089 }
3089 }
3090
3090
3091 proc clearsha1 {} {
3091 proc clearsha1 {} {
3092 global sha1entry sha1string
3092 global sha1entry sha1string
3093 if {[string length $sha1string] == 40} {
3093 if {[string length $sha1string] == 40} {
3094 $sha1entry delete 0 end
3094 $sha1entry delete 0 end
3095 }
3095 }
3096 }
3096 }
3097
3097
3098 proc sha1change {n1 n2 op} {
3098 proc sha1change {n1 n2 op} {
3099 global sha1string currentid sha1but
3099 global sha1string currentid sha1but
3100 if {$sha1string == {}
3100 if {$sha1string == {}
3101 || ([info exists currentid] && $sha1string == $currentid)} {
3101 || ([info exists currentid] && $sha1string == $currentid)} {
3102 set state disabled
3102 set state disabled
3103 } else {
3103 } else {
3104 set state normal
3104 set state normal
3105 }
3105 }
3106 if {[$sha1but cget -state] == $state} return
3106 if {[$sha1but cget -state] == $state} return
3107 if {$state == "normal"} {
3107 if {$state == "normal"} {
3108 $sha1but conf -state normal -relief raised -text "Goto: "
3108 $sha1but conf -state normal -relief raised -text "Goto: "
3109 } else {
3109 } else {
3110 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
3110 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
3111 }
3111 }
3112 }
3112 }
3113
3113
3114 proc gotocommit {} {
3114 proc gotocommit {} {
3115 global sha1string currentid idline tagids
3115 global sha1string currentid idline tagids
3116 global lineid numcommits
3116 global lineid numcommits
3117
3117
3118 if {$sha1string == {}
3118 if {$sha1string == {}
3119 || ([info exists currentid] && $sha1string == $currentid)} return
3119 || ([info exists currentid] && $sha1string == $currentid)} return
3120 if {[info exists tagids($sha1string)]} {
3120 if {[info exists tagids($sha1string)]} {
3121 set id $tagids($sha1string)
3121 set id $tagids($sha1string)
3122 } else {
3122 } else {
3123 set id [string tolower $sha1string]
3123 set id [string tolower $sha1string]
3124 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
3124 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
3125 set matches {}
3125 set matches {}
3126 for {set l 0} {$l < $numcommits} {incr l} {
3126 for {set l 0} {$l < $numcommits} {incr l} {
3127 if {[string match $id* $lineid($l)]} {
3127 if {[string match $id* $lineid($l)]} {
3128 lappend matches $lineid($l)
3128 lappend matches $lineid($l)
3129 }
3129 }
3130 }
3130 }
3131 if {$matches ne {}} {
3131 if {$matches ne {}} {
3132 if {[llength $matches] > 1} {
3132 if {[llength $matches] > 1} {
3133 error_popup "Short SHA1 id $id is ambiguous"
3133 error_popup "Short SHA1 id $id is ambiguous"
3134 return
3134 return
3135 }
3135 }
3136 set id [lindex $matches 0]
3136 set id [lindex $matches 0]
3137 }
3137 }
3138 }
3138 }
3139 }
3139 }
3140 if {[info exists idline($id)]} {
3140 if {[info exists idline($id)]} {
3141 selectline $idline($id) 1
3141 selectline $idline($id) 1
3142 return
3142 return
3143 }
3143 }
3144 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
3144 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
3145 set type "SHA1 id"
3145 set type "SHA1 id"
3146 } else {
3146 } else {
3147 set type "Tag"
3147 set type "Tag"
3148 }
3148 }
3149 error_popup "$type $sha1string is not known"
3149 error_popup "$type $sha1string is not known"
3150 }
3150 }
3151
3151
3152 proc lineenter {x y id} {
3152 proc lineenter {x y id} {
3153 global hoverx hovery hoverid hovertimer
3153 global hoverx hovery hoverid hovertimer
3154 global commitinfo canv
3154 global commitinfo canv
3155
3155
3156 if {![info exists commitinfo($id)]} return
3156 if {![info exists commitinfo($id)]} return
3157 set hoverx $x
3157 set hoverx $x
3158 set hovery $y
3158 set hovery $y
3159 set hoverid $id
3159 set hoverid $id
3160 if {[info exists hovertimer]} {
3160 if {[info exists hovertimer]} {
3161 after cancel $hovertimer
3161 after cancel $hovertimer
3162 }
3162 }
3163 set hovertimer [after 500 linehover]
3163 set hovertimer [after 500 linehover]
3164 $canv delete hover
3164 $canv delete hover
3165 }
3165 }
3166
3166
3167 proc linemotion {x y id} {
3167 proc linemotion {x y id} {
3168 global hoverx hovery hoverid hovertimer
3168 global hoverx hovery hoverid hovertimer
3169
3169
3170 if {[info exists hoverid] && $id == $hoverid} {
3170 if {[info exists hoverid] && $id == $hoverid} {
3171 set hoverx $x
3171 set hoverx $x
3172 set hovery $y
3172 set hovery $y
3173 if {[info exists hovertimer]} {
3173 if {[info exists hovertimer]} {
3174 after cancel $hovertimer
3174 after cancel $hovertimer
3175 }
3175 }
3176 set hovertimer [after 500 linehover]
3176 set hovertimer [after 500 linehover]
3177 }
3177 }
3178 }
3178 }
3179
3179
3180 proc lineleave {id} {
3180 proc lineleave {id} {
3181 global hoverid hovertimer canv
3181 global hoverid hovertimer canv
3182
3182
3183 if {[info exists hoverid] && $id == $hoverid} {
3183 if {[info exists hoverid] && $id == $hoverid} {
3184 $canv delete hover
3184 $canv delete hover
3185 if {[info exists hovertimer]} {
3185 if {[info exists hovertimer]} {
3186 after cancel $hovertimer
3186 after cancel $hovertimer
3187 unset hovertimer
3187 unset hovertimer
3188 }
3188 }
3189 unset hoverid
3189 unset hoverid
3190 }
3190 }
3191 }
3191 }
3192
3192
3193 proc linehover {} {
3193 proc linehover {} {
3194 global hoverx hovery hoverid hovertimer
3194 global hoverx hovery hoverid hovertimer
3195 global canv linespc lthickness
3195 global canv linespc lthickness
3196 global commitinfo mainfont
3196 global commitinfo mainfont
3197
3197
3198 set text [lindex $commitinfo($hoverid) 0]
3198 set text [lindex $commitinfo($hoverid) 0]
3199 set ymax [lindex [$canv cget -scrollregion] 3]
3199 set ymax [lindex [$canv cget -scrollregion] 3]
3200 if {$ymax == {}} return
3200 if {$ymax == {}} return
3201 set yfrac [lindex [$canv yview] 0]
3201 set yfrac [lindex [$canv yview] 0]
3202 set x [expr {$hoverx + 2 * $linespc}]
3202 set x [expr {$hoverx + 2 * $linespc}]
3203 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
3203 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
3204 set x0 [expr {$x - 2 * $lthickness}]
3204 set x0 [expr {$x - 2 * $lthickness}]
3205 set y0 [expr {$y - 2 * $lthickness}]
3205 set y0 [expr {$y - 2 * $lthickness}]
3206 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
3206 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
3207 set y1 [expr {$y + $linespc + 2 * $lthickness}]
3207 set y1 [expr {$y + $linespc + 2 * $lthickness}]
3208 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
3208 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
3209 -fill \#ffff80 -outline black -width 1 -tags hover]
3209 -fill \#ffff80 -outline black -width 1 -tags hover]
3210 $canv raise $t
3210 $canv raise $t
3211 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
3211 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
3212 $canv raise $t
3212 $canv raise $t
3213 }
3213 }
3214
3214
3215 proc clickisonarrow {id y} {
3215 proc clickisonarrow {id y} {
3216 global mainline mainlinearrow sidelines lthickness
3216 global mainline mainlinearrow sidelines lthickness
3217
3217
3218 set thresh [expr {2 * $lthickness + 6}]
3218 set thresh [expr {2 * $lthickness + 6}]
3219 if {[info exists mainline($id)]} {
3219 if {[info exists mainline($id)]} {
3220 if {$mainlinearrow($id) ne "none"} {
3220 if {$mainlinearrow($id) ne "none"} {
3221 if {abs([lindex $mainline($id) 1] - $y) < $thresh} {
3221 if {abs([lindex $mainline($id) 1] - $y) < $thresh} {
3222 return "up"
3222 return "up"
3223 }
3223 }
3224 }
3224 }
3225 }
3225 }
3226 if {[info exists sidelines($id)]} {
3226 if {[info exists sidelines($id)]} {
3227 foreach ls $sidelines($id) {
3227 foreach ls $sidelines($id) {
3228 set coords [lindex $ls 0]
3228 set coords [lindex $ls 0]
3229 set arrow [lindex $ls 2]
3229 set arrow [lindex $ls 2]
3230 if {$arrow eq "first" || $arrow eq "both"} {
3230 if {$arrow eq "first" || $arrow eq "both"} {
3231 if {abs([lindex $coords 1] - $y) < $thresh} {
3231 if {abs([lindex $coords 1] - $y) < $thresh} {
3232 return "up"
3232 return "up"
3233 }
3233 }
3234 }
3234 }
3235 if {$arrow eq "last" || $arrow eq "both"} {
3235 if {$arrow eq "last" || $arrow eq "both"} {
3236 if {abs([lindex $coords end] - $y) < $thresh} {
3236 if {abs([lindex $coords end] - $y) < $thresh} {
3237 return "down"
3237 return "down"
3238 }
3238 }
3239 }
3239 }
3240 }
3240 }
3241 }
3241 }
3242 return {}
3242 return {}
3243 }
3243 }
3244
3244
3245 proc arrowjump {id dirn y} {
3245 proc arrowjump {id dirn y} {
3246 global mainline sidelines canv
3246 global mainline sidelines canv
3247
3247
3248 set yt {}
3248 set yt {}
3249 if {$dirn eq "down"} {
3249 if {$dirn eq "down"} {
3250 if {[info exists mainline($id)]} {
3250 if {[info exists mainline($id)]} {
3251 set y1 [lindex $mainline($id) 1]
3251 set y1 [lindex $mainline($id) 1]
3252 if {$y1 > $y} {
3252 if {$y1 > $y} {
3253 set yt $y1
3253 set yt $y1
3254 }
3254 }
3255 }
3255 }
3256 if {[info exists sidelines($id)]} {
3256 if {[info exists sidelines($id)]} {
3257 foreach ls $sidelines($id) {
3257 foreach ls $sidelines($id) {
3258 set y1 [lindex $ls 0 1]
3258 set y1 [lindex $ls 0 1]
3259 if {$y1 > $y && ($yt eq {} || $y1 < $yt)} {
3259 if {$y1 > $y && ($yt eq {} || $y1 < $yt)} {
3260 set yt $y1
3260 set yt $y1
3261 }
3261 }
3262 }
3262 }
3263 }
3263 }
3264 } else {
3264 } else {
3265 if {[info exists sidelines($id)]} {
3265 if {[info exists sidelines($id)]} {
3266 foreach ls $sidelines($id) {
3266 foreach ls $sidelines($id) {
3267 set y1 [lindex $ls 0 end]
3267 set y1 [lindex $ls 0 end]
3268 if {$y1 < $y && ($yt eq {} || $y1 > $yt)} {
3268 if {$y1 < $y && ($yt eq {} || $y1 > $yt)} {
3269 set yt $y1
3269 set yt $y1
3270 }
3270 }
3271 }
3271 }
3272 }
3272 }
3273 }
3273 }
3274 if {$yt eq {}} return
3274 if {$yt eq {}} return
3275 set ymax [lindex [$canv cget -scrollregion] 3]
3275 set ymax [lindex [$canv cget -scrollregion] 3]
3276 if {$ymax eq {} || $ymax <= 0} return
3276 if {$ymax eq {} || $ymax <= 0} return
3277 set view [$canv yview]
3277 set view [$canv yview]
3278 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
3278 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
3279 set yfrac [expr {$yt / $ymax - $yspan / 2}]
3279 set yfrac [expr {$yt / $ymax - $yspan / 2}]
3280 if {$yfrac < 0} {
3280 if {$yfrac < 0} {
3281 set yfrac 0
3281 set yfrac 0
3282 }
3282 }
3283 $canv yview moveto $yfrac
3283 $canv yview moveto $yfrac
3284 }
3284 }
3285
3285
3286 proc lineclick {x y id isnew} {
3286 proc lineclick {x y id isnew} {
3287 global ctext commitinfo children cflist canv thickerline
3287 global ctext commitinfo children cflist canv thickerline
3288
3288
3289 unmarkmatches
3289 unmarkmatches
3290 unselectline
3290 unselectline
3291 normalline
3291 normalline
3292 $canv delete hover
3292 $canv delete hover
3293 # draw this line thicker than normal
3293 # draw this line thicker than normal
3294 drawlines $id 1
3294 drawlines $id 1
3295 set thickerline $id
3295 set thickerline $id
3296 if {$isnew} {
3296 if {$isnew} {
3297 set ymax [lindex [$canv cget -scrollregion] 3]
3297 set ymax [lindex [$canv cget -scrollregion] 3]
3298 if {$ymax eq {}} return
3298 if {$ymax eq {}} return
3299 set yfrac [lindex [$canv yview] 0]
3299 set yfrac [lindex [$canv yview] 0]
3300 set y [expr {$y + $yfrac * $ymax}]
3300 set y [expr {$y + $yfrac * $ymax}]
3301 }
3301 }
3302 set dirn [clickisonarrow $id $y]
3302 set dirn [clickisonarrow $id $y]
3303 if {$dirn ne {}} {
3303 if {$dirn ne {}} {
3304 arrowjump $id $dirn $y
3304 arrowjump $id $dirn $y
3305 return
3305 return
3306 }
3306 }
3307
3307
3308 if {$isnew} {
3308 if {$isnew} {
3309 addtohistory [list lineclick $x $y $id 0]
3309 addtohistory [list lineclick $x $y $id 0]
3310 }
3310 }
3311 # fill the details pane with info about this line
3311 # fill the details pane with info about this line
3312 $ctext conf -state normal
3312 $ctext conf -state normal
3313 $ctext delete 0.0 end
3313 $ctext delete 0.0 end
3314 $ctext tag conf link -foreground blue -underline 1
3314 $ctext tag conf link -foreground blue -underline 1
3315 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3315 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3316 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3316 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3317 $ctext insert end "Parent:\t"
3317 $ctext insert end "Parent:\t"
3318 $ctext insert end $id [list link link0]
3318 $ctext insert end $id [list link link0]
3319 $ctext tag bind link0 <1> [list selbyid $id]
3319 $ctext tag bind link0 <1> [list selbyid $id]
3320 set info $commitinfo($id)
3320 set info $commitinfo($id)
3321 $ctext insert end "\n\t[lindex $info 0]\n"
3321 $ctext insert end "\n\t[lindex $info 0]\n"
3322 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
3322 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
3323 $ctext insert end "\tDate:\t[lindex $info 2]\n"
3323 $ctext insert end "\tDate:\t[lindex $info 2]\n"
3324 if {[info exists children($id)]} {
3324 if {[info exists children($id)]} {
3325 $ctext insert end "\nChildren:"
3325 $ctext insert end "\nChildren:"
3326 set i 0
3326 set i 0
3327 foreach child $children($id) {
3327 foreach child $children($id) {
3328 incr i
3328 incr i
3329 set info $commitinfo($child)
3329 set info $commitinfo($child)
3330 $ctext insert end "\n\t"
3330 $ctext insert end "\n\t"
3331 $ctext insert end $child [list link link$i]
3331 $ctext insert end $child [list link link$i]
3332 $ctext tag bind link$i <1> [list selbyid $child]
3332 $ctext tag bind link$i <1> [list selbyid $child]
3333 $ctext insert end "\n\t[lindex $info 0]"
3333 $ctext insert end "\n\t[lindex $info 0]"
3334 $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
3334 $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
3335 $ctext insert end "\n\tDate:\t[lindex $info 2]\n"
3335 $ctext insert end "\n\tDate:\t[lindex $info 2]\n"
3336 }
3336 }
3337 }
3337 }
3338 $ctext conf -state disabled
3338 $ctext conf -state disabled
3339
3339
3340 $cflist delete 0 end
3340 $cflist delete 0 end
3341 }
3341 }
3342
3342
3343 proc normalline {} {
3343 proc normalline {} {
3344 global thickerline
3344 global thickerline
3345 if {[info exists thickerline]} {
3345 if {[info exists thickerline]} {
3346 drawlines $thickerline 0
3346 drawlines $thickerline 0
3347 unset thickerline
3347 unset thickerline
3348 }
3348 }
3349 }
3349 }
3350
3350
3351 proc selbyid {id} {
3351 proc selbyid {id} {
3352 global idline
3352 global idline
3353 if {[info exists idline($id)]} {
3353 if {[info exists idline($id)]} {
3354 selectline $idline($id) 1
3354 selectline $idline($id) 1
3355 }
3355 }
3356 }
3356 }
3357
3357
3358 proc mstime {} {
3358 proc mstime {} {
3359 global startmstime
3359 global startmstime
3360 if {![info exists startmstime]} {
3360 if {![info exists startmstime]} {
3361 set startmstime [clock clicks -milliseconds]
3361 set startmstime [clock clicks -milliseconds]
3362 }
3362 }
3363 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
3363 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
3364 }
3364 }
3365
3365
3366 proc rowmenu {x y id} {
3366 proc rowmenu {x y id} {
3367 global rowctxmenu idline selectedline rowmenuid
3367 global rowctxmenu idline selectedline rowmenuid
3368
3368
3369 if {![info exists selectedline] || $idline($id) eq $selectedline} {
3369 if {![info exists selectedline] || $idline($id) eq $selectedline} {
3370 set state disabled
3370 set state disabled
3371 } else {
3371 } else {
3372 set state normal
3372 set state normal
3373 }
3373 }
3374 $rowctxmenu entryconfigure 0 -state $state
3374 $rowctxmenu entryconfigure 0 -state $state
3375 $rowctxmenu entryconfigure 1 -state $state
3375 $rowctxmenu entryconfigure 1 -state $state
3376 $rowctxmenu entryconfigure 2 -state $state
3376 $rowctxmenu entryconfigure 2 -state $state
3377 set rowmenuid $id
3377 set rowmenuid $id
3378 tk_popup $rowctxmenu $x $y
3378 tk_popup $rowctxmenu $x $y
3379 }
3379 }
3380
3380
3381 proc diffvssel {dirn} {
3381 proc diffvssel {dirn} {
3382 global rowmenuid selectedline lineid
3382 global rowmenuid selectedline lineid
3383
3383
3384 if {![info exists selectedline]} return
3384 if {![info exists selectedline]} return
3385 if {$dirn} {
3385 if {$dirn} {
3386 set oldid $lineid($selectedline)
3386 set oldid $lineid($selectedline)
3387 set newid $rowmenuid
3387 set newid $rowmenuid
3388 } else {
3388 } else {
3389 set oldid $rowmenuid
3389 set oldid $rowmenuid
3390 set newid $lineid($selectedline)
3390 set newid $lineid($selectedline)
3391 }
3391 }
3392 addtohistory [list doseldiff $oldid $newid]
3392 addtohistory [list doseldiff $oldid $newid]
3393 doseldiff $oldid $newid
3393 doseldiff $oldid $newid
3394 }
3394 }
3395
3395
3396 proc doseldiff {oldid newid} {
3396 proc doseldiff {oldid newid} {
3397 global ctext cflist
3397 global ctext cflist
3398 global commitinfo
3398 global commitinfo
3399
3399
3400 $ctext conf -state normal
3400 $ctext conf -state normal
3401 $ctext delete 0.0 end
3401 $ctext delete 0.0 end
3402 $ctext mark set fmark.0 0.0
3402 $ctext mark set fmark.0 0.0
3403 $ctext mark gravity fmark.0 left
3403 $ctext mark gravity fmark.0 left
3404 $cflist delete 0 end
3404 $cflist delete 0 end
3405 $cflist insert end "Top"
3405 $cflist insert end "Top"
3406 $ctext insert end "From "
3406 $ctext insert end "From "
3407 $ctext tag conf link -foreground blue -underline 1
3407 $ctext tag conf link -foreground blue -underline 1
3408 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3408 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3409 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3409 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3410 $ctext tag bind link0 <1> [list selbyid $oldid]
3410 $ctext tag bind link0 <1> [list selbyid $oldid]
3411 $ctext insert end $oldid [list link link0]
3411 $ctext insert end $oldid [list link link0]
3412 $ctext insert end "\n "
3412 $ctext insert end "\n "
3413 $ctext insert end [lindex $commitinfo($oldid) 0]
3413 $ctext insert end [lindex $commitinfo($oldid) 0]
3414 $ctext insert end "\n\nTo "
3414 $ctext insert end "\n\nTo "
3415 $ctext tag bind link1 <1> [list selbyid $newid]
3415 $ctext tag bind link1 <1> [list selbyid $newid]
3416 $ctext insert end $newid [list link link1]
3416 $ctext insert end $newid [list link link1]
3417 $ctext insert end "\n "
3417 $ctext insert end "\n "
3418 $ctext insert end [lindex $commitinfo($newid) 0]
3418 $ctext insert end [lindex $commitinfo($newid) 0]
3419 $ctext insert end "\n"
3419 $ctext insert end "\n"
3420 $ctext conf -state disabled
3420 $ctext conf -state disabled
3421 $ctext tag delete Comments
3421 $ctext tag delete Comments
3422 $ctext tag remove found 1.0 end
3422 $ctext tag remove found 1.0 end
3423 startdiff [list $newid $oldid]
3423 startdiff [list $newid $oldid]
3424 }
3424 }
3425
3425
3426 proc mkpatch {} {
3426 proc mkpatch {} {
3427 global rowmenuid currentid commitinfo patchtop patchnum
3427 global rowmenuid currentid commitinfo patchtop patchnum
3428
3428
3429 if {![info exists currentid]} return
3429 if {![info exists currentid]} return
3430 set oldid $currentid
3430 set oldid $currentid
3431 set oldhead [lindex $commitinfo($oldid) 0]
3431 set oldhead [lindex $commitinfo($oldid) 0]
3432 set newid $rowmenuid
3432 set newid $rowmenuid
3433 set newhead [lindex $commitinfo($newid) 0]
3433 set newhead [lindex $commitinfo($newid) 0]
3434 set top .patch
3434 set top .patch
3435 set patchtop $top
3435 set patchtop $top
3436 catch {destroy $top}
3436 catch {destroy $top}
3437 toplevel $top
3437 toplevel $top
3438 label $top.title -text "Generate patch"
3438 label $top.title -text "Generate patch"
3439 grid $top.title - -pady 10
3439 grid $top.title - -pady 10
3440 label $top.from -text "From:"
3440 label $top.from -text "From:"
3441 entry $top.fromsha1 -width 40 -relief flat
3441 entry $top.fromsha1 -width 40 -relief flat
3442 $top.fromsha1 insert 0 $oldid
3442 $top.fromsha1 insert 0 $oldid
3443 $top.fromsha1 conf -state readonly
3443 $top.fromsha1 conf -state readonly
3444 grid $top.from $top.fromsha1 -sticky w
3444 grid $top.from $top.fromsha1 -sticky w
3445 entry $top.fromhead -width 60 -relief flat
3445 entry $top.fromhead -width 60 -relief flat
3446 $top.fromhead insert 0 $oldhead
3446 $top.fromhead insert 0 $oldhead
3447 $top.fromhead conf -state readonly
3447 $top.fromhead conf -state readonly
3448 grid x $top.fromhead -sticky w
3448 grid x $top.fromhead -sticky w
3449 label $top.to -text "To:"
3449 label $top.to -text "To:"
3450 entry $top.tosha1 -width 40 -relief flat
3450 entry $top.tosha1 -width 40 -relief flat
3451 $top.tosha1 insert 0 $newid
3451 $top.tosha1 insert 0 $newid
3452 $top.tosha1 conf -state readonly
3452 $top.tosha1 conf -state readonly
3453 grid $top.to $top.tosha1 -sticky w
3453 grid $top.to $top.tosha1 -sticky w
3454 entry $top.tohead -width 60 -relief flat
3454 entry $top.tohead -width 60 -relief flat
3455 $top.tohead insert 0 $newhead
3455 $top.tohead insert 0 $newhead
3456 $top.tohead conf -state readonly
3456 $top.tohead conf -state readonly
3457 grid x $top.tohead -sticky w
3457 grid x $top.tohead -sticky w
3458 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
3458 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
3459 grid $top.rev x -pady 10
3459 grid $top.rev x -pady 10
3460 label $top.flab -text "Output file:"
3460 label $top.flab -text "Output file:"
3461 entry $top.fname -width 60
3461 entry $top.fname -width 60
3462 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
3462 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
3463 incr patchnum
3463 incr patchnum
3464 grid $top.flab $top.fname -sticky w
3464 grid $top.flab $top.fname -sticky w
3465 frame $top.buts
3465 frame $top.buts
3466 button $top.buts.gen -text "Generate" -command mkpatchgo
3466 button $top.buts.gen -text "Generate" -command mkpatchgo
3467 button $top.buts.can -text "Cancel" -command mkpatchcan
3467 button $top.buts.can -text "Cancel" -command mkpatchcan
3468 grid $top.buts.gen $top.buts.can
3468 grid $top.buts.gen $top.buts.can
3469 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3469 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3470 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3470 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3471 grid $top.buts - -pady 10 -sticky ew
3471 grid $top.buts - -pady 10 -sticky ew
3472 focus $top.fname
3472 focus $top.fname
3473 }
3473 }
3474
3474
3475 proc mkpatchrev {} {
3475 proc mkpatchrev {} {
3476 global patchtop
3476 global patchtop
3477
3477
3478 set oldid [$patchtop.fromsha1 get]
3478 set oldid [$patchtop.fromsha1 get]
3479 set oldhead [$patchtop.fromhead get]
3479 set oldhead [$patchtop.fromhead get]
3480 set newid [$patchtop.tosha1 get]
3480 set newid [$patchtop.tosha1 get]
3481 set newhead [$patchtop.tohead get]
3481 set newhead [$patchtop.tohead get]
3482 foreach e [list fromsha1 fromhead tosha1 tohead] \
3482 foreach e [list fromsha1 fromhead tosha1 tohead] \
3483 v [list $newid $newhead $oldid $oldhead] {
3483 v [list $newid $newhead $oldid $oldhead] {
3484 $patchtop.$e conf -state normal
3484 $patchtop.$e conf -state normal
3485 $patchtop.$e delete 0 end
3485 $patchtop.$e delete 0 end
3486 $patchtop.$e insert 0 $v
3486 $patchtop.$e insert 0 $v
3487 $patchtop.$e conf -state readonly
3487 $patchtop.$e conf -state readonly
3488 }
3488 }
3489 }
3489 }
3490
3490
3491 proc mkpatchgo {} {
3491 proc mkpatchgo {} {
3492 global patchtop env
3492 global patchtop env
3493
3493
3494 set oldid [$patchtop.fromsha1 get]
3494 set oldid [$patchtop.fromsha1 get]
3495 set newid [$patchtop.tosha1 get]
3495 set newid [$patchtop.tosha1 get]
3496 set fname [$patchtop.fname get]
3496 set fname [$patchtop.fname get]
3497 if {[catch {exec $env(HG) --config ui.report_untrusted=false debug-diff-tree -p $oldid $newid >$fname &} err]} {
3497 if {[catch {exec $env(HG) --config ui.report_untrusted=false debug-diff-tree -p $oldid $newid >$fname &} err]} {
3498 error_popup "Error creating patch: $err"
3498 error_popup "Error creating patch: $err"
3499 }
3499 }
3500 catch {destroy $patchtop}
3500 catch {destroy $patchtop}
3501 unset patchtop
3501 unset patchtop
3502 }
3502 }
3503
3503
3504 proc mkpatchcan {} {
3504 proc mkpatchcan {} {
3505 global patchtop
3505 global patchtop
3506
3506
3507 catch {destroy $patchtop}
3507 catch {destroy $patchtop}
3508 unset patchtop
3508 unset patchtop
3509 }
3509 }
3510
3510
3511 proc mktag {} {
3511 proc mktag {} {
3512 global rowmenuid mktagtop commitinfo
3512 global rowmenuid mktagtop commitinfo
3513
3513
3514 set top .maketag
3514 set top .maketag
3515 set mktagtop $top
3515 set mktagtop $top
3516 catch {destroy $top}
3516 catch {destroy $top}
3517 toplevel $top
3517 toplevel $top
3518 label $top.title -text "Create tag"
3518 label $top.title -text "Create tag"
3519 grid $top.title - -pady 10
3519 grid $top.title - -pady 10
3520 label $top.id -text "ID:"
3520 label $top.id -text "ID:"
3521 entry $top.sha1 -width 40 -relief flat
3521 entry $top.sha1 -width 40 -relief flat
3522 $top.sha1 insert 0 $rowmenuid
3522 $top.sha1 insert 0 $rowmenuid
3523 $top.sha1 conf -state readonly
3523 $top.sha1 conf -state readonly
3524 grid $top.id $top.sha1 -sticky w
3524 grid $top.id $top.sha1 -sticky w
3525 entry $top.head -width 60 -relief flat
3525 entry $top.head -width 60 -relief flat
3526 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3526 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3527 $top.head conf -state readonly
3527 $top.head conf -state readonly
3528 grid x $top.head -sticky w
3528 grid x $top.head -sticky w
3529 label $top.tlab -text "Tag name:"
3529 label $top.tlab -text "Tag name:"
3530 entry $top.tag -width 60
3530 entry $top.tag -width 60
3531 grid $top.tlab $top.tag -sticky w
3531 grid $top.tlab $top.tag -sticky w
3532 frame $top.buts
3532 frame $top.buts
3533 button $top.buts.gen -text "Create" -command mktaggo
3533 button $top.buts.gen -text "Create" -command mktaggo
3534 button $top.buts.can -text "Cancel" -command mktagcan
3534 button $top.buts.can -text "Cancel" -command mktagcan
3535 grid $top.buts.gen $top.buts.can
3535 grid $top.buts.gen $top.buts.can
3536 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3536 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3537 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3537 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3538 grid $top.buts - -pady 10 -sticky ew
3538 grid $top.buts - -pady 10 -sticky ew
3539 focus $top.tag
3539 focus $top.tag
3540 }
3540 }
3541
3541
3542 proc domktag {} {
3542 proc domktag {} {
3543 global mktagtop env tagids idtags
3543 global mktagtop env tagids idtags
3544
3544
3545 set id [$mktagtop.sha1 get]
3545 set id [$mktagtop.sha1 get]
3546 set tag [$mktagtop.tag get]
3546 set tag [$mktagtop.tag get]
3547 if {$tag == {}} {
3547 if {$tag == {}} {
3548 error_popup "No tag name specified"
3548 error_popup "No tag name specified"
3549 return
3549 return
3550 }
3550 }
3551 if {[info exists tagids($tag)]} {
3551 if {[info exists tagids($tag)]} {
3552 error_popup "Tag \"$tag\" already exists"
3552 error_popup "Tag \"$tag\" already exists"
3553 return
3553 return
3554 }
3554 }
3555 if {[catch {
3555 if {[catch {
3556 set out [exec $env(HG) --config ui.report_untrusted=false tag -r $id $tag]
3556 set out [exec $env(HG) --config ui.report_untrusted=false tag -r $id $tag]
3557 } err]} {
3557 } err]} {
3558 error_popup "Error creating tag: $err"
3558 error_popup "Error creating tag: $err"
3559 return
3559 return
3560 }
3560 }
3561
3561
3562 set tagids($tag) $id
3562 set tagids($tag) $id
3563 lappend idtags($id) $tag
3563 lappend idtags($id) $tag
3564 redrawtags $id
3564 redrawtags $id
3565 }
3565 }
3566
3566
3567 proc redrawtags {id} {
3567 proc redrawtags {id} {
3568 global canv linehtag idline idpos selectedline
3568 global canv linehtag idline idpos selectedline
3569
3569
3570 if {![info exists idline($id)]} return
3570 if {![info exists idline($id)]} return
3571 $canv delete tag.$id
3571 $canv delete tag.$id
3572 set xt [eval drawtags $id $idpos($id)]
3572 set xt [eval drawtags $id $idpos($id)]
3573 $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
3573 $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
3574 if {[info exists selectedline] && $selectedline == $idline($id)} {
3574 if {[info exists selectedline] && $selectedline == $idline($id)} {
3575 selectline $selectedline 0
3575 selectline $selectedline 0
3576 }
3576 }
3577 }
3577 }
3578
3578
3579 proc mktagcan {} {
3579 proc mktagcan {} {
3580 global mktagtop
3580 global mktagtop
3581
3581
3582 catch {destroy $mktagtop}
3582 catch {destroy $mktagtop}
3583 unset mktagtop
3583 unset mktagtop
3584 }
3584 }
3585
3585
3586 proc mktaggo {} {
3586 proc mktaggo {} {
3587 domktag
3587 domktag
3588 mktagcan
3588 mktagcan
3589 }
3589 }
3590
3590
3591 proc writecommit {} {
3591 proc writecommit {} {
3592 global rowmenuid wrcomtop commitinfo wrcomcmd
3592 global rowmenuid wrcomtop commitinfo wrcomcmd
3593
3593
3594 set top .writecommit
3594 set top .writecommit
3595 set wrcomtop $top
3595 set wrcomtop $top
3596 catch {destroy $top}
3596 catch {destroy $top}
3597 toplevel $top
3597 toplevel $top
3598 label $top.title -text "Write commit to file"
3598 label $top.title -text "Write commit to file"
3599 grid $top.title - -pady 10
3599 grid $top.title - -pady 10
3600 label $top.id -text "ID:"
3600 label $top.id -text "ID:"
3601 entry $top.sha1 -width 40 -relief flat
3601 entry $top.sha1 -width 40 -relief flat
3602 $top.sha1 insert 0 $rowmenuid
3602 $top.sha1 insert 0 $rowmenuid
3603 $top.sha1 conf -state readonly
3603 $top.sha1 conf -state readonly
3604 grid $top.id $top.sha1 -sticky w
3604 grid $top.id $top.sha1 -sticky w
3605 entry $top.head -width 60 -relief flat
3605 entry $top.head -width 60 -relief flat
3606 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3606 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3607 $top.head conf -state readonly
3607 $top.head conf -state readonly
3608 grid x $top.head -sticky w
3608 grid x $top.head -sticky w
3609 label $top.clab -text "Command:"
3609 label $top.clab -text "Command:"
3610 entry $top.cmd -width 60 -textvariable wrcomcmd
3610 entry $top.cmd -width 60 -textvariable wrcomcmd
3611 grid $top.clab $top.cmd -sticky w -pady 10
3611 grid $top.clab $top.cmd -sticky w -pady 10
3612 label $top.flab -text "Output file:"
3612 label $top.flab -text "Output file:"
3613 entry $top.fname -width 60
3613 entry $top.fname -width 60
3614 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
3614 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
3615 grid $top.flab $top.fname -sticky w
3615 grid $top.flab $top.fname -sticky w
3616 frame $top.buts
3616 frame $top.buts
3617 button $top.buts.gen -text "Write" -command wrcomgo
3617 button $top.buts.gen -text "Write" -command wrcomgo
3618 button $top.buts.can -text "Cancel" -command wrcomcan
3618 button $top.buts.can -text "Cancel" -command wrcomcan
3619 grid $top.buts.gen $top.buts.can
3619 grid $top.buts.gen $top.buts.can
3620 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3620 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3621 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3621 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3622 grid $top.buts - -pady 10 -sticky ew
3622 grid $top.buts - -pady 10 -sticky ew
3623 focus $top.fname
3623 focus $top.fname
3624 }
3624 }
3625
3625
3626 proc wrcomgo {} {
3626 proc wrcomgo {} {
3627 global wrcomtop
3627 global wrcomtop
3628
3628
3629 set id [$wrcomtop.sha1 get]
3629 set id [$wrcomtop.sha1 get]
3630 set cmd "echo $id | [$wrcomtop.cmd get]"
3630 set cmd "echo $id | [$wrcomtop.cmd get]"
3631 set fname [$wrcomtop.fname get]
3631 set fname [$wrcomtop.fname get]
3632 if {[catch {exec sh -c $cmd > $fname &} err]} {
3632 if {[catch {exec sh -c $cmd > $fname &} err]} {
3633 error_popup "Error writing commit: $err"
3633 error_popup "Error writing commit: $err"
3634 }
3634 }
3635 catch {destroy $wrcomtop}
3635 catch {destroy $wrcomtop}
3636 unset wrcomtop
3636 unset wrcomtop
3637 }
3637 }
3638
3638
3639 proc wrcomcan {} {
3639 proc wrcomcan {} {
3640 global wrcomtop
3640 global wrcomtop
3641
3641
3642 catch {destroy $wrcomtop}
3642 catch {destroy $wrcomtop}
3643 unset wrcomtop
3643 unset wrcomtop
3644 }
3644 }
3645
3645
3646 proc listrefs {id} {
3646 proc listrefs {id} {
3647 global idtags idheads idotherrefs
3647 global idtags idheads idotherrefs
3648
3648
3649 set x {}
3649 set x {}
3650 if {[info exists idtags($id)]} {
3650 if {[info exists idtags($id)]} {
3651 set x $idtags($id)
3651 set x $idtags($id)
3652 }
3652 }
3653 set y {}
3653 set y {}
3654 if {[info exists idheads($id)]} {
3654 if {[info exists idheads($id)]} {
3655 set y $idheads($id)
3655 set y $idheads($id)
3656 }
3656 }
3657 set z {}
3657 set z {}
3658 if {[info exists idotherrefs($id)]} {
3658 if {[info exists idotherrefs($id)]} {
3659 set z $idotherrefs($id)
3659 set z $idotherrefs($id)
3660 }
3660 }
3661 return [list $x $y $z]
3661 return [list $x $y $z]
3662 }
3662 }
3663
3663
3664 proc rereadrefs {} {
3664 proc rereadrefs {} {
3665 global idtags idheads idotherrefs
3665 global idtags idheads idotherrefs
3666 global tagids headids otherrefids
3666 global tagids headids otherrefids
3667
3667
3668 set refids [concat [array names idtags] \
3668 set refids [concat [array names idtags] \
3669 [array names idheads] [array names idotherrefs]]
3669 [array names idheads] [array names idotherrefs]]
3670 foreach id $refids {
3670 foreach id $refids {
3671 if {![info exists ref($id)]} {
3671 if {![info exists ref($id)]} {
3672 set ref($id) [listrefs $id]
3672 set ref($id) [listrefs $id]
3673 }
3673 }
3674 }
3674 }
3675 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
3675 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
3676 catch {unset $v}
3676 catch {unset $v}
3677 }
3677 }
3678 readrefs
3678 readrefs
3679 set refids [lsort -unique [concat $refids [array names idtags] \
3679 set refids [lsort -unique [concat $refids [array names idtags] \
3680 [array names idheads] [array names idotherrefs]]]
3680 [array names idheads] [array names idotherrefs]]]
3681 foreach id $refids {
3681 foreach id $refids {
3682 set v [listrefs $id]
3682 set v [listrefs $id]
3683 if {![info exists ref($id)] || $ref($id) != $v} {
3683 if {![info exists ref($id)] || $ref($id) != $v} {
3684 redrawtags $id
3684 redrawtags $id
3685 }
3685 }
3686 }
3686 }
3687 }
3687 }
3688
3688
3689 proc showtag {tag isnew} {
3689 proc showtag {tag isnew} {
3690 global ctext cflist tagcontents tagids linknum
3690 global ctext cflist tagcontents tagids linknum
3691
3691
3692 if {$isnew} {
3692 if {$isnew} {
3693 addtohistory [list showtag $tag 0]
3693 addtohistory [list showtag $tag 0]
3694 }
3694 }
3695 $ctext conf -state normal
3695 $ctext conf -state normal
3696 $ctext delete 0.0 end
3696 $ctext delete 0.0 end
3697 set linknum 0
3697 set linknum 0
3698 if {[info exists tagcontents($tag)]} {
3698 if {[info exists tagcontents($tag)]} {
3699 set text $tagcontents($tag)
3699 set text $tagcontents($tag)
3700 } else {
3700 } else {
3701 set text "Tag: $tag\nId: $tagids($tag)"
3701 set text "Tag: $tag\nId: $tagids($tag)"
3702 }
3702 }
3703 appendwithlinks $text
3703 appendwithlinks $text
3704 $ctext conf -state disabled
3704 $ctext conf -state disabled
3705 $cflist delete 0 end
3705 $cflist delete 0 end
3706 }
3706 }
3707
3707
3708 proc doquit {} {
3708 proc doquit {} {
3709 global stopped
3709 global stopped
3710 set stopped 100
3710 set stopped 100
3711 destroy .
3711 destroy .
3712 }
3712 }
3713
3713
3714 # defaults...
3714 # defaults...
3715 set datemode 0
3715 set datemode 0
3716 set boldnames 0
3716 set boldnames 0
3717 set diffopts "-U 5 -p"
3717 set diffopts "-U 5 -p"
3718 set wrcomcmd "\"\$HG\" --config ui.report_untrusted=false debug-diff-tree --stdin -p --pretty"
3718 set wrcomcmd "\"\$HG\" --config ui.report_untrusted=false debug-diff-tree --stdin -p --pretty"
3719
3719
3720 set mainfont {Helvetica 9}
3720 set mainfont {Helvetica 9}
3721 set textfont {Courier 9}
3721 set textfont {Courier 9}
3722 set findmergefiles 0
3722 set findmergefiles 0
3723 set gaudydiff 0
3723 set gaudydiff 0
3724 set maxgraphpct 50
3724 set maxgraphpct 50
3725 set maxwidth 16
3725 set maxwidth 16
3726
3726
3727 set colors {green red blue magenta darkgrey brown orange}
3727 set colors {green red blue magenta darkgrey brown orange}
3728
3728
3729 catch {source ~/.gitk}
3729 catch {source ~/.gitk}
3730
3730
3731 set namefont $mainfont
3731 set namefont $mainfont
3732 if {$boldnames} {
3732 if {$boldnames} {
3733 lappend namefont bold
3733 lappend namefont bold
3734 }
3734 }
3735
3735
3736 set revtreeargs {}
3736 set revtreeargs {}
3737 foreach arg $argv {
3737 foreach arg $argv {
3738 switch -regexp -- $arg {
3738 switch -regexp -- $arg {
3739 "^$" { }
3739 "^$" { }
3740 "^-b" { set boldnames 1 }
3740 "^-b" { set boldnames 1 }
3741 "^-d" { set datemode 1 }
3741 "^-d" { set datemode 1 }
3742 default {
3742 default {
3743 lappend revtreeargs $arg
3743 lappend revtreeargs $arg
3744 }
3744 }
3745 }
3745 }
3746 }
3746 }
3747
3747
3748 set history {}
3748 set history {}
3749 set historyindex 0
3749 set historyindex 0
3750
3750
3751 set stopped 0
3751 set stopped 0
3752 set redisplaying 0
3752 set redisplaying 0
3753 set stuffsaved 0
3753 set stuffsaved 0
3754 set patchnum 0
3754 set patchnum 0
3755 setcoords
3755 setcoords
3756 makewindow
3756 makewindow
3757 readrefs
3757 readrefs
3758 getcommits $revtreeargs
3758 getcommits $revtreeargs
@@ -1,450 +1,450 b''
1 # convert.py Foreign SCM converter
1 # convert.py Foreign SCM converter
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from common import NoRepo, converter_source, converter_sink
8 from common import NoRepo, converter_source, converter_sink
9 from cvs import convert_cvs
9 from cvs import convert_cvs
10 from git import convert_git
10 from git import convert_git
11 from hg import mercurial_source, mercurial_sink
11 from hg import mercurial_source, mercurial_sink
12 from subversion import convert_svn, debugsvnlog
12 from subversion import convert_svn, debugsvnlog
13
13
14 import os, shlex, shutil
14 import os, shlex, shutil
15 from mercurial import hg, ui, util, commands
15 from mercurial import hg, ui, util, commands
16 from mercurial.i18n import _
16 from mercurial.i18n import _
17
17
18 commands.norepo += " convert debugsvnlog"
18 commands.norepo += " convert debugsvnlog"
19
19
20 converters = [convert_cvs, convert_git, convert_svn, mercurial_source,
20 converters = [convert_cvs, convert_git, convert_svn, mercurial_source,
21 mercurial_sink]
21 mercurial_sink]
22
22
23 def convertsource(ui, path, **opts):
23 def convertsource(ui, path, **opts):
24 for c in converters:
24 for c in converters:
25 try:
25 try:
26 return c.getcommit and c(ui, path, **opts)
26 return c.getcommit and c(ui, path, **opts)
27 except (AttributeError, NoRepo):
27 except (AttributeError, NoRepo):
28 pass
28 pass
29 raise util.Abort('%s: unknown repository type' % path)
29 raise util.Abort('%s: unknown repository type' % path)
30
30
31 def convertsink(ui, path):
31 def convertsink(ui, path):
32 if not os.path.isdir(path):
32 if not os.path.isdir(path):
33 raise util.Abort("%s: not a directory" % path)
33 raise util.Abort("%s: not a directory" % path)
34 for c in converters:
34 for c in converters:
35 try:
35 try:
36 return c.putcommit and c(ui, path)
36 return c.putcommit and c(ui, path)
37 except (AttributeError, NoRepo):
37 except (AttributeError, NoRepo):
38 pass
38 pass
39 raise util.Abort('%s: unknown repository type' % path)
39 raise util.Abort('%s: unknown repository type' % path)
40
40
41 class convert(object):
41 class convert(object):
42 def __init__(self, ui, source, dest, revmapfile, filemapper, opts):
42 def __init__(self, ui, source, dest, revmapfile, filemapper, opts):
43
43
44 self.source = source
44 self.source = source
45 self.dest = dest
45 self.dest = dest
46 self.ui = ui
46 self.ui = ui
47 self.opts = opts
47 self.opts = opts
48 self.commitcache = {}
48 self.commitcache = {}
49 self.revmapfile = revmapfile
49 self.revmapfile = revmapfile
50 self.revmapfilefd = None
50 self.revmapfilefd = None
51 self.authors = {}
51 self.authors = {}
52 self.authorfile = None
52 self.authorfile = None
53 self.mapfile = filemapper
53 self.mapfile = filemapper
54
54
55 self.map = {}
55 self.map = {}
56 try:
56 try:
57 origrevmapfile = open(self.revmapfile, 'r')
57 origrevmapfile = open(self.revmapfile, 'r')
58 for l in origrevmapfile:
58 for l in origrevmapfile:
59 sv, dv = l[:-1].split()
59 sv, dv = l[:-1].split()
60 self.map[sv] = dv
60 self.map[sv] = dv
61 origrevmapfile.close()
61 origrevmapfile.close()
62 except IOError:
62 except IOError:
63 pass
63 pass
64
64
65 # Read first the dst author map if any
65 # Read first the dst author map if any
66 authorfile = self.dest.authorfile()
66 authorfile = self.dest.authorfile()
67 if authorfile and os.path.exists(authorfile):
67 if authorfile and os.path.exists(authorfile):
68 self.readauthormap(authorfile)
68 self.readauthormap(authorfile)
69 # Extend/Override with new author map if necessary
69 # Extend/Override with new author map if necessary
70 if opts.get('authors'):
70 if opts.get('authors'):
71 self.readauthormap(opts.get('authors'))
71 self.readauthormap(opts.get('authors'))
72 self.authorfile = self.dest.authorfile()
72 self.authorfile = self.dest.authorfile()
73
73
74 def walktree(self, heads):
74 def walktree(self, heads):
75 '''Return a mapping that identifies the uncommitted parents of every
75 '''Return a mapping that identifies the uncommitted parents of every
76 uncommitted changeset.'''
76 uncommitted changeset.'''
77 visit = heads
77 visit = heads
78 known = {}
78 known = {}
79 parents = {}
79 parents = {}
80 while visit:
80 while visit:
81 n = visit.pop(0)
81 n = visit.pop(0)
82 if n in known or n in self.map: continue
82 if n in known or n in self.map: continue
83 known[n] = 1
83 known[n] = 1
84 self.commitcache[n] = self.source.getcommit(n)
84 self.commitcache[n] = self.source.getcommit(n)
85 cp = self.commitcache[n].parents
85 cp = self.commitcache[n].parents
86 parents[n] = []
86 parents[n] = []
87 for p in cp:
87 for p in cp:
88 parents[n].append(p)
88 parents[n].append(p)
89 visit.append(p)
89 visit.append(p)
90
90
91 return parents
91 return parents
92
92
93 def toposort(self, parents):
93 def toposort(self, parents):
94 '''Return an ordering such that every uncommitted changeset is
94 '''Return an ordering such that every uncommitted changeset is
95 preceeded by all its uncommitted ancestors.'''
95 preceeded by all its uncommitted ancestors.'''
96 visit = parents.keys()
96 visit = parents.keys()
97 seen = {}
97 seen = {}
98 children = {}
98 children = {}
99
99
100 while visit:
100 while visit:
101 n = visit.pop(0)
101 n = visit.pop(0)
102 if n in seen: continue
102 if n in seen: continue
103 seen[n] = 1
103 seen[n] = 1
104 # Ensure that nodes without parents are present in the 'children'
104 # Ensure that nodes without parents are present in the 'children'
105 # mapping.
105 # mapping.
106 children.setdefault(n, [])
106 children.setdefault(n, [])
107 for p in parents[n]:
107 for p in parents[n]:
108 if not p in self.map:
108 if not p in self.map:
109 visit.append(p)
109 visit.append(p)
110 children.setdefault(p, []).append(n)
110 children.setdefault(p, []).append(n)
111
111
112 s = []
112 s = []
113 removed = {}
113 removed = {}
114 visit = children.keys()
114 visit = children.keys()
115 while visit:
115 while visit:
116 n = visit.pop(0)
116 n = visit.pop(0)
117 if n in removed: continue
117 if n in removed: continue
118 dep = 0
118 dep = 0
119 if n in parents:
119 if n in parents:
120 for p in parents[n]:
120 for p in parents[n]:
121 if p in self.map: continue
121 if p in self.map: continue
122 if p not in removed:
122 if p not in removed:
123 # we're still dependent
123 # we're still dependent
124 visit.append(n)
124 visit.append(n)
125 dep = 1
125 dep = 1
126 break
126 break
127
127
128 if not dep:
128 if not dep:
129 # all n's parents are in the list
129 # all n's parents are in the list
130 removed[n] = 1
130 removed[n] = 1
131 if n not in self.map:
131 if n not in self.map:
132 s.append(n)
132 s.append(n)
133 if n in children:
133 if n in children:
134 for c in children[n]:
134 for c in children[n]:
135 visit.insert(0, c)
135 visit.insert(0, c)
136
136
137 if self.opts.get('datesort'):
137 if self.opts.get('datesort'):
138 depth = {}
138 depth = {}
139 for n in s:
139 for n in s:
140 depth[n] = 0
140 depth[n] = 0
141 pl = [p for p in self.commitcache[n].parents
141 pl = [p for p in self.commitcache[n].parents
142 if p not in self.map]
142 if p not in self.map]
143 if pl:
143 if pl:
144 depth[n] = max([depth[p] for p in pl]) + 1
144 depth[n] = max([depth[p] for p in pl]) + 1
145
145
146 s = [(depth[n], self.commitcache[n].date, n) for n in s]
146 s = [(depth[n], self.commitcache[n].date, n) for n in s]
147 s.sort()
147 s.sort()
148 s = [e[2] for e in s]
148 s = [e[2] for e in s]
149
149
150 return s
150 return s
151
151
152 def mapentry(self, src, dst):
152 def mapentry(self, src, dst):
153 if self.revmapfilefd is None:
153 if self.revmapfilefd is None:
154 try:
154 try:
155 self.revmapfilefd = open(self.revmapfile, "a")
155 self.revmapfilefd = open(self.revmapfile, "a")
156 except IOError, (errno, strerror):
156 except IOError, (errno, strerror):
157 raise util.Abort("Could not open map file %s: %s, %s\n" % (self.revmapfile, errno, strerror))
157 raise util.Abort("Could not open map file %s: %s, %s\n" % (self.revmapfile, errno, strerror))
158 self.map[src] = dst
158 self.map[src] = dst
159 self.revmapfilefd.write("%s %s\n" % (src, dst))
159 self.revmapfilefd.write("%s %s\n" % (src, dst))
160 self.revmapfilefd.flush()
160 self.revmapfilefd.flush()
161
161
162 def writeauthormap(self):
162 def writeauthormap(self):
163 authorfile = self.authorfile
163 authorfile = self.authorfile
164 if authorfile:
164 if authorfile:
165 self.ui.status('Writing author map file %s\n' % authorfile)
165 self.ui.status('Writing author map file %s\n' % authorfile)
166 ofile = open(authorfile, 'w+')
166 ofile = open(authorfile, 'w+')
167 for author in self.authors:
167 for author in self.authors:
168 ofile.write("%s=%s\n" % (author, self.authors[author]))
168 ofile.write("%s=%s\n" % (author, self.authors[author]))
169 ofile.close()
169 ofile.close()
170
170
171 def readauthormap(self, authorfile):
171 def readauthormap(self, authorfile):
172 afile = open(authorfile, 'r')
172 afile = open(authorfile, 'r')
173 for line in afile:
173 for line in afile:
174 try:
174 try:
175 srcauthor = line.split('=')[0].strip()
175 srcauthor = line.split('=')[0].strip()
176 dstauthor = line.split('=')[1].strip()
176 dstauthor = line.split('=')[1].strip()
177 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
177 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
178 self.ui.status(
178 self.ui.status(
179 'Overriding mapping for author %s, was %s, will be %s\n'
179 'Overriding mapping for author %s, was %s, will be %s\n'
180 % (srcauthor, self.authors[srcauthor], dstauthor))
180 % (srcauthor, self.authors[srcauthor], dstauthor))
181 else:
181 else:
182 self.ui.debug('Mapping author %s to %s\n'
182 self.ui.debug('Mapping author %s to %s\n'
183 % (srcauthor, dstauthor))
183 % (srcauthor, dstauthor))
184 self.authors[srcauthor] = dstauthor
184 self.authors[srcauthor] = dstauthor
185 except IndexError:
185 except IndexError:
186 self.ui.warn(
186 self.ui.warn(
187 'Ignoring bad line in author file map %s: %s\n'
187 'Ignoring bad line in author file map %s: %s\n'
188 % (authorfile, line))
188 % (authorfile, line))
189 afile.close()
189 afile.close()
190
190
191 def copy(self, rev):
191 def copy(self, rev):
192 commit = self.commitcache[rev]
192 commit = self.commitcache[rev]
193 do_copies = hasattr(self.dest, 'copyfile')
193 do_copies = hasattr(self.dest, 'copyfile')
194 filenames = []
194 filenames = []
195
195
196 files, copies = self.source.getchanges(rev)
196 files, copies = self.source.getchanges(rev)
197 for f, v in files:
197 for f, v in files:
198 newf = self.mapfile(f)
198 newf = self.mapfile(f)
199 if not newf:
199 if not newf:
200 continue
200 continue
201 filenames.append(newf)
201 filenames.append(newf)
202 try:
202 try:
203 data = self.source.getfile(f, v)
203 data = self.source.getfile(f, v)
204 except IOError, inst:
204 except IOError, inst:
205 self.dest.delfile(newf)
205 self.dest.delfile(newf)
206 else:
206 else:
207 e = self.source.getmode(f, v)
207 e = self.source.getmode(f, v)
208 self.dest.putfile(newf, e, data)
208 self.dest.putfile(newf, e, data)
209 if do_copies:
209 if do_copies:
210 if f in copies:
210 if f in copies:
211 copyf = self.mapfile(copies[f])
211 copyf = self.mapfile(copies[f])
212 if copyf:
212 if copyf:
213 # Merely marks that a copy happened.
213 # Merely marks that a copy happened.
214 self.dest.copyfile(copyf, newf)
214 self.dest.copyfile(copyf, newf)
215
215
216 parents = [self.map[r] for r in commit.parents]
216 parents = [self.map[r] for r in commit.parents]
217 newnode = self.dest.putcommit(filenames, parents, commit)
217 newnode = self.dest.putcommit(filenames, parents, commit)
218 self.mapentry(rev, newnode)
218 self.mapentry(rev, newnode)
219
219
220 def convert(self):
220 def convert(self):
221 try:
221 try:
222 self.dest.before()
222 self.dest.before()
223 self.source.setrevmap(self.map)
223 self.source.setrevmap(self.map)
224 self.ui.status("scanning source...\n")
224 self.ui.status("scanning source...\n")
225 heads = self.source.getheads()
225 heads = self.source.getheads()
226 parents = self.walktree(heads)
226 parents = self.walktree(heads)
227 self.ui.status("sorting...\n")
227 self.ui.status("sorting...\n")
228 t = self.toposort(parents)
228 t = self.toposort(parents)
229 num = len(t)
229 num = len(t)
230 c = None
230 c = None
231
231
232 self.ui.status("converting...\n")
232 self.ui.status("converting...\n")
233 for c in t:
233 for c in t:
234 num -= 1
234 num -= 1
235 desc = self.commitcache[c].desc
235 desc = self.commitcache[c].desc
236 if "\n" in desc:
236 if "\n" in desc:
237 desc = desc.splitlines()[0]
237 desc = desc.splitlines()[0]
238 author = self.commitcache[c].author
238 author = self.commitcache[c].author
239 author = self.authors.get(author, author)
239 author = self.authors.get(author, author)
240 self.commitcache[c].author = author
240 self.commitcache[c].author = author
241 self.ui.status("%d %s\n" % (num, desc))
241 self.ui.status("%d %s\n" % (num, desc))
242 self.copy(c)
242 self.copy(c)
243
243
244 tags = self.source.gettags()
244 tags = self.source.gettags()
245 ctags = {}
245 ctags = {}
246 for k in tags:
246 for k in tags:
247 v = tags[k]
247 v = tags[k]
248 if v in self.map:
248 if v in self.map:
249 ctags[k] = self.map[v]
249 ctags[k] = self.map[v]
250
250
251 if c and ctags:
251 if c and ctags:
252 nrev = self.dest.puttags(ctags)
252 nrev = self.dest.puttags(ctags)
253 # write another hash correspondence to override the previous
253 # write another hash correspondence to override the previous
254 # one so we don't end up with extra tag heads
254 # one so we don't end up with extra tag heads
255 if nrev:
255 if nrev:
256 self.mapentry(c, nrev)
256 self.mapentry(c, nrev)
257
257
258 self.writeauthormap()
258 self.writeauthormap()
259 finally:
259 finally:
260 self.cleanup()
260 self.cleanup()
261
261
262 def cleanup(self):
262 def cleanup(self):
263 self.dest.after()
263 self.dest.after()
264 if self.revmapfilefd:
264 if self.revmapfilefd:
265 self.revmapfilefd.close()
265 self.revmapfilefd.close()
266
266
267 def rpairs(name):
267 def rpairs(name):
268 e = len(name)
268 e = len(name)
269 while e != -1:
269 while e != -1:
270 yield name[:e], name[e+1:]
270 yield name[:e], name[e+1:]
271 e = name.rfind('/', 0, e)
271 e = name.rfind('/', 0, e)
272
272
273 class filemapper(object):
273 class filemapper(object):
274 '''Map and filter filenames when importing.
274 '''Map and filter filenames when importing.
275 A name can be mapped to itself, a new name, or None (omit from new
275 A name can be mapped to itself, a new name, or None (omit from new
276 repository).'''
276 repository).'''
277
277
278 def __init__(self, ui, path=None):
278 def __init__(self, ui, path=None):
279 self.ui = ui
279 self.ui = ui
280 self.include = {}
280 self.include = {}
281 self.exclude = {}
281 self.exclude = {}
282 self.rename = {}
282 self.rename = {}
283 if path:
283 if path:
284 if self.parse(path):
284 if self.parse(path):
285 raise util.Abort(_('errors in filemap'))
285 raise util.Abort(_('errors in filemap'))
286
286
287 def parse(self, path):
287 def parse(self, path):
288 errs = 0
288 errs = 0
289 def check(name, mapping, listname):
289 def check(name, mapping, listname):
290 if name in mapping:
290 if name in mapping:
291 self.ui.warn(_('%s:%d: %r already in %s list\n') %
291 self.ui.warn(_('%s:%d: %r already in %s list\n') %
292 (lex.infile, lex.lineno, name, listname))
292 (lex.infile, lex.lineno, name, listname))
293 return 1
293 return 1
294 return 0
294 return 0
295 lex = shlex.shlex(open(path), path, True)
295 lex = shlex.shlex(open(path), path, True)
296 lex.wordchars += '!@#$%^&*()-=+[]{}|;:,./<>?'
296 lex.wordchars += '!@#$%^&*()-=+[]{}|;:,./<>?'
297 cmd = lex.get_token()
297 cmd = lex.get_token()
298 while cmd:
298 while cmd:
299 if cmd == 'include':
299 if cmd == 'include':
300 name = lex.get_token()
300 name = lex.get_token()
301 errs += check(name, self.exclude, 'exclude')
301 errs += check(name, self.exclude, 'exclude')
302 self.include[name] = name
302 self.include[name] = name
303 elif cmd == 'exclude':
303 elif cmd == 'exclude':
304 name = lex.get_token()
304 name = lex.get_token()
305 errs += check(name, self.include, 'include')
305 errs += check(name, self.include, 'include')
306 errs += check(name, self.rename, 'rename')
306 errs += check(name, self.rename, 'rename')
307 self.exclude[name] = name
307 self.exclude[name] = name
308 elif cmd == 'rename':
308 elif cmd == 'rename':
309 src = lex.get_token()
309 src = lex.get_token()
310 dest = lex.get_token()
310 dest = lex.get_token()
311 errs += check(src, self.exclude, 'exclude')
311 errs += check(src, self.exclude, 'exclude')
312 self.rename[src] = dest
312 self.rename[src] = dest
313 elif cmd == 'source':
313 elif cmd == 'source':
314 errs += self.parse(lex.get_token())
314 errs += self.parse(lex.get_token())
315 else:
315 else:
316 self.ui.warn(_('%s:%d: unknown directive %r\n') %
316 self.ui.warn(_('%s:%d: unknown directive %r\n') %
317 (lex.infile, lex.lineno, cmd))
317 (lex.infile, lex.lineno, cmd))
318 errs += 1
318 errs += 1
319 cmd = lex.get_token()
319 cmd = lex.get_token()
320 return errs
320 return errs
321
321
322 def lookup(self, name, mapping):
322 def lookup(self, name, mapping):
323 for pre, suf in rpairs(name):
323 for pre, suf in rpairs(name):
324 try:
324 try:
325 return mapping[pre], pre, suf
325 return mapping[pre], pre, suf
326 except KeyError, err:
326 except KeyError, err:
327 pass
327 pass
328 return '', name, ''
328 return '', name, ''
329
329
330 def __call__(self, name):
330 def __call__(self, name):
331 if self.include:
331 if self.include:
332 inc = self.lookup(name, self.include)[0]
332 inc = self.lookup(name, self.include)[0]
333 else:
333 else:
334 inc = name
334 inc = name
335 if self.exclude:
335 if self.exclude:
336 exc = self.lookup(name, self.exclude)[0]
336 exc = self.lookup(name, self.exclude)[0]
337 else:
337 else:
338 exc = ''
338 exc = ''
339 if not inc or exc:
339 if not inc or exc:
340 return None
340 return None
341 newpre, pre, suf = self.lookup(name, self.rename)
341 newpre, pre, suf = self.lookup(name, self.rename)
342 if newpre:
342 if newpre:
343 if newpre == '.':
343 if newpre == '.':
344 return suf
344 return suf
345 if suf:
345 if suf:
346 return newpre + '/' + suf
346 return newpre + '/' + suf
347 return newpre
347 return newpre
348 return name
348 return name
349
349
350 def _convert(ui, src, dest=None, revmapfile=None, **opts):
350 def _convert(ui, src, dest=None, revmapfile=None, **opts):
351 """Convert a foreign SCM repository to a Mercurial one.
351 """Convert a foreign SCM repository to a Mercurial one.
352
352
353 Accepted source formats:
353 Accepted source formats:
354 - GIT
354 - GIT
355 - CVS
355 - CVS
356 - SVN
356 - SVN
357
357
358 Accepted destination formats:
358 Accepted destination formats:
359 - Mercurial
359 - Mercurial
360
360
361 If no revision is given, all revisions will be converted. Otherwise,
361 If no revision is given, all revisions will be converted. Otherwise,
362 convert will only import up to the named revision (given in a format
362 convert will only import up to the named revision (given in a format
363 understood by the source).
363 understood by the source).
364
364
365 If no destination directory name is specified, it defaults to the
365 If no destination directory name is specified, it defaults to the
366 basename of the source with '-hg' appended. If the destination
366 basename of the source with '-hg' appended. If the destination
367 repository doesn't exist, it will be created.
367 repository doesn't exist, it will be created.
368
368
369 If <revmapfile> isn't given, it will be put in a default location
369 If <revmapfile> isn't given, it will be put in a default location
370 (<dest>/.hg/shamap by default). The <revmapfile> is a simple text
370 (<dest>/.hg/shamap by default). The <revmapfile> is a simple text
371 file that maps each source commit ID to the destination ID for
371 file that maps each source commit ID to the destination ID for
372 that revision, like so:
372 that revision, like so:
373 <source ID> <destination ID>
373 <source ID> <destination ID>
374
374
375 If the file doesn't exist, it's automatically created. It's updated
375 If the file doesn't exist, it's automatically created. It's updated
376 on each commit copied, so convert-repo can be interrupted and can
376 on each commit copied, so convert-repo can be interrupted and can
377 be run repeatedly to copy new commits.
377 be run repeatedly to copy new commits.
378
378
379 The [username mapping] file is a simple text file that maps each source
379 The [username mapping] file is a simple text file that maps each source
380 commit author to a destination commit author. It is handy for source SCMs
380 commit author to a destination commit author. It is handy for source SCMs
381 that use unix logins to identify authors (eg: CVS). One line per author
381 that use unix logins to identify authors (eg: CVS). One line per author
382 mapping and the line format is:
382 mapping and the line format is:
383 srcauthor=whatever string you want
383 srcauthor=whatever string you want
384 """
384 """
385
385
386 util._encoding = 'UTF-8'
386 util._encoding = 'UTF-8'
387
387
388 if not dest:
388 if not dest:
389 dest = hg.defaultdest(src) + "-hg"
389 dest = hg.defaultdest(src) + "-hg"
390 ui.status("assuming destination %s\n" % dest)
390 ui.status("assuming destination %s\n" % dest)
391
391
392 # Try to be smart and initalize things when required
392 # Try to be smart and initalize things when required
393 created = False
393 created = False
394 if os.path.isdir(dest):
394 if os.path.isdir(dest):
395 if len(os.listdir(dest)) > 0:
395 if len(os.listdir(dest)) > 0:
396 try:
396 try:
397 hg.repository(ui, dest)
397 hg.repository(ui, dest)
398 ui.status("destination %s is a Mercurial repository\n" % dest)
398 ui.status("destination %s is a Mercurial repository\n" % dest)
399 except hg.RepoError:
399 except hg.RepoError:
400 raise util.Abort(
400 raise util.Abort(
401 "destination directory %s is not empty.\n"
401 "destination directory %s is not empty.\n"
402 "Please specify an empty directory to be initialized\n"
402 "Please specify an empty directory to be initialized\n"
403 "or an already initialized mercurial repository"
403 "or an already initialized mercurial repository"
404 % dest)
404 % dest)
405 else:
405 else:
406 ui.status("initializing destination %s repository\n" % dest)
406 ui.status("initializing destination %s repository\n" % dest)
407 hg.repository(ui, dest, create=True)
407 hg.repository(ui, dest, create=True)
408 created = True
408 created = True
409 elif os.path.exists(dest):
409 elif os.path.exists(dest):
410 raise util.Abort("destination %s exists and is not a directory" % dest)
410 raise util.Abort("destination %s exists and is not a directory" % dest)
411 else:
411 else:
412 ui.status("initializing destination %s repository\n" % dest)
412 ui.status("initializing destination %s repository\n" % dest)
413 hg.repository(ui, dest, create=True)
413 hg.repository(ui, dest, create=True)
414 created = True
414 created = True
415
415
416 destc = convertsink(ui, dest)
416 destc = convertsink(ui, dest)
417
417
418 try:
418 try:
419 srcc = convertsource(ui, src, rev=opts.get('rev'))
419 srcc = convertsource(ui, src, rev=opts.get('rev'))
420 except Exception:
420 except Exception:
421 if created:
421 if created:
422 shutil.rmtree(dest, True)
422 shutil.rmtree(dest, True)
423 raise
423 raise
424
424
425 if not revmapfile:
425 if not revmapfile:
426 try:
426 try:
427 revmapfile = destc.revmapfile()
427 revmapfile = destc.revmapfile()
428 except:
428 except:
429 revmapfile = os.path.join(destc, "map")
429 revmapfile = os.path.join(destc, "map")
430
430
431
431
432 c = convert(ui, srcc, destc, revmapfile, filemapper(ui, opts['filemap']),
432 c = convert(ui, srcc, destc, revmapfile, filemapper(ui, opts['filemap']),
433 opts)
433 opts)
434 c.convert()
434 c.convert()
435
435
436
436
437 cmdtable = {
437 cmdtable = {
438 "convert":
438 "convert":
439 (_convert,
439 (_convert,
440 [('A', 'authors', '', 'username mapping filename'),
440 [('A', 'authors', '', 'username mapping filename'),
441 ('', 'filemap', '', 'remap file names using contents of file'),
441 ('', 'filemap', '', 'remap file names using contents of file'),
442 ('r', 'rev', '', 'import up to target revision REV'),
442 ('r', 'rev', '', 'import up to target revision REV'),
443 ('', 'datesort', None, 'try to sort changesets by date')],
443 ('', 'datesort', None, 'try to sort changesets by date')],
444 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
444 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
445 "debugsvnlog":
445 "debugsvnlog":
446 (debugsvnlog,
446 (debugsvnlog,
447 [],
447 [],
448 'hg debugsvnlog'),
448 'hg debugsvnlog'),
449 }
449 }
450
450
@@ -1,136 +1,136 b''
1 # common code for the convert extension
1 # common code for the convert extension
2 import base64
2 import base64
3 import cPickle as pickle
3 import cPickle as pickle
4
4
5 def encodeargs(args):
5 def encodeargs(args):
6 def encodearg(s):
6 def encodearg(s):
7 lines = base64.encodestring(s)
7 lines = base64.encodestring(s)
8 lines = [l.splitlines()[0] for l in lines]
8 lines = [l.splitlines()[0] for l in lines]
9 return ''.join(lines)
9 return ''.join(lines)
10
10
11 s = pickle.dumps(args)
11 s = pickle.dumps(args)
12 return encodearg(s)
12 return encodearg(s)
13
13
14 def decodeargs(s):
14 def decodeargs(s):
15 s = base64.decodestring(s)
15 s = base64.decodestring(s)
16 return pickle.loads(s)
16 return pickle.loads(s)
17
17
18 class NoRepo(Exception): pass
18 class NoRepo(Exception): pass
19
19
20 class commit(object):
20 class commit(object):
21 def __init__(self, author, date, desc, parents, branch=None, rev=None):
21 def __init__(self, author, date, desc, parents, branch=None, rev=None):
22 self.author = author
22 self.author = author
23 self.date = date
23 self.date = date
24 self.desc = desc
24 self.desc = desc
25 self.parents = parents
25 self.parents = parents
26 self.branch = branch
26 self.branch = branch
27 self.rev = rev
27 self.rev = rev
28
28
29 class converter_source(object):
29 class converter_source(object):
30 """Conversion source interface"""
30 """Conversion source interface"""
31
31
32 def __init__(self, ui, path, rev=None):
32 def __init__(self, ui, path, rev=None):
33 """Initialize conversion source (or raise NoRepo("message")
33 """Initialize conversion source (or raise NoRepo("message")
34 exception if path is not a valid repository)"""
34 exception if path is not a valid repository)"""
35 self.ui = ui
35 self.ui = ui
36 self.path = path
36 self.path = path
37 self.rev = rev
37 self.rev = rev
38
38
39 self.encoding = 'utf-8'
39 self.encoding = 'utf-8'
40
40
41 def setrevmap(self, revmap):
41 def setrevmap(self, revmap):
42 """set the map of already-converted revisions"""
42 """set the map of already-converted revisions"""
43 pass
43 pass
44
44
45 def getheads(self):
45 def getheads(self):
46 """Return a list of this repository's heads"""
46 """Return a list of this repository's heads"""
47 raise NotImplementedError()
47 raise NotImplementedError()
48
48
49 def getfile(self, name, rev):
49 def getfile(self, name, rev):
50 """Return file contents as a string"""
50 """Return file contents as a string"""
51 raise NotImplementedError()
51 raise NotImplementedError()
52
52
53 def getmode(self, name, rev):
53 def getmode(self, name, rev):
54 """Return file mode, eg. '', 'x', or 'l'"""
54 """Return file mode, eg. '', 'x', or 'l'"""
55 raise NotImplementedError()
55 raise NotImplementedError()
56
56
57 def getchanges(self, version):
57 def getchanges(self, version):
58 """Returns a tuple of (files, copies)
58 """Returns a tuple of (files, copies)
59 Files is a sorted list of (filename, id) tuples for all files changed
59 Files is a sorted list of (filename, id) tuples for all files changed
60 in version, where id is the source revision id of the file.
60 in version, where id is the source revision id of the file.
61
61
62 copies is a dictionary of dest: source
62 copies is a dictionary of dest: source
63 """
63 """
64 raise NotImplementedError()
64 raise NotImplementedError()
65
65
66 def getcommit(self, version):
66 def getcommit(self, version):
67 """Return the commit object for version"""
67 """Return the commit object for version"""
68 raise NotImplementedError()
68 raise NotImplementedError()
69
69
70 def gettags(self):
70 def gettags(self):
71 """Return the tags as a dictionary of name: revision"""
71 """Return the tags as a dictionary of name: revision"""
72 raise NotImplementedError()
72 raise NotImplementedError()
73
73
74 def recode(self, s, encoding=None):
74 def recode(self, s, encoding=None):
75 if not encoding:
75 if not encoding:
76 encoding = self.encoding or 'utf-8'
76 encoding = self.encoding or 'utf-8'
77
77
78 try:
78 try:
79 return s.decode(encoding).encode("utf-8")
79 return s.decode(encoding).encode("utf-8")
80 except:
80 except:
81 try:
81 try:
82 return s.decode("latin-1").encode("utf-8")
82 return s.decode("latin-1").encode("utf-8")
83 except:
83 except:
84 return s.decode(encoding, "replace").encode("utf-8")
84 return s.decode(encoding, "replace").encode("utf-8")
85
85
86 class converter_sink(object):
86 class converter_sink(object):
87 """Conversion sink (target) interface"""
87 """Conversion sink (target) interface"""
88
88
89 def __init__(self, ui, path):
89 def __init__(self, ui, path):
90 """Initialize conversion sink (or raise NoRepo("message")
90 """Initialize conversion sink (or raise NoRepo("message")
91 exception if path is not a valid repository)"""
91 exception if path is not a valid repository)"""
92 raise NotImplementedError()
92 raise NotImplementedError()
93
93
94 def getheads(self):
94 def getheads(self):
95 """Return a list of this repository's heads"""
95 """Return a list of this repository's heads"""
96 raise NotImplementedError()
96 raise NotImplementedError()
97
97
98 def revmapfile(self):
98 def revmapfile(self):
99 """Path to a file that will contain lines
99 """Path to a file that will contain lines
100 source_rev_id sink_rev_id
100 source_rev_id sink_rev_id
101 mapping equivalent revision identifiers for each system."""
101 mapping equivalent revision identifiers for each system."""
102 raise NotImplementedError()
102 raise NotImplementedError()
103
103
104 def authorfile(self):
104 def authorfile(self):
105 """Path to a file that will contain lines
105 """Path to a file that will contain lines
106 srcauthor=dstauthor
106 srcauthor=dstauthor
107 mapping equivalent authors identifiers for each system."""
107 mapping equivalent authors identifiers for each system."""
108 return None
108 return None
109
109
110 def putfile(self, f, e, data):
110 def putfile(self, f, e, data):
111 """Put file for next putcommit().
111 """Put file for next putcommit().
112 f: path to file
112 f: path to file
113 e: '', 'x', or 'l' (regular file, executable, or symlink)
113 e: '', 'x', or 'l' (regular file, executable, or symlink)
114 data: file contents"""
114 data: file contents"""
115 raise NotImplementedError()
115 raise NotImplementedError()
116
116
117 def delfile(self, f):
117 def delfile(self, f):
118 """Delete file for next putcommit().
118 """Delete file for next putcommit().
119 f: path to file"""
119 f: path to file"""
120 raise NotImplementedError()
120 raise NotImplementedError()
121
121
122 def putcommit(self, files, parents, commit):
122 def putcommit(self, files, parents, commit):
123 """Create a revision with all changed files listed in 'files'
123 """Create a revision with all changed files listed in 'files'
124 and having listed parents. 'commit' is a commit object containing
124 and having listed parents. 'commit' is a commit object containing
125 at a minimum the author, date, and message for this changeset.
125 at a minimum the author, date, and message for this changeset.
126 Called after putfile() and delfile() calls. Note that the sink
126 Called after putfile() and delfile() calls. Note that the sink
127 repository is not told to update itself to a particular revision
127 repository is not told to update itself to a particular revision
128 (or even what that revision would be) before it receives the
128 (or even what that revision would be) before it receives the
129 file data."""
129 file data."""
130 raise NotImplementedError()
130 raise NotImplementedError()
131
131
132 def puttags(self, tags):
132 def puttags(self, tags):
133 """Put tags into sink.
133 """Put tags into sink.
134 tags: {tagname: sink_rev_id, ...}"""
134 tags: {tagname: sink_rev_id, ...}"""
135 raise NotImplementedError()
135 raise NotImplementedError()
136
136
@@ -1,178 +1,178 b''
1 # hg backend for convert extension
1 # hg backend for convert extension
2
2
3 # Note for hg->hg conversion: Old versions of Mercurial didn't trim
3 # Note for hg->hg conversion: Old versions of Mercurial didn't trim
4 # the whitespace from the ends of commit messages, but new versions
4 # the whitespace from the ends of commit messages, but new versions
5 # do. Changesets created by those older versions, then converted, may
5 # do. Changesets created by those older versions, then converted, may
6 # thus have different hashes for changesets that are otherwise
6 # thus have different hashes for changesets that are otherwise
7 # identical.
7 # identical.
8
8
9
9
10 import os, time
10 import os, time
11 from mercurial.i18n import _
11 from mercurial.i18n import _
12 from mercurial.node import *
12 from mercurial.node import *
13 from mercurial import hg, lock, revlog, util
13 from mercurial import hg, lock, revlog, util
14
14
15 from common import NoRepo, commit, converter_source, converter_sink
15 from common import NoRepo, commit, converter_source, converter_sink
16
16
17 class mercurial_sink(converter_sink):
17 class mercurial_sink(converter_sink):
18 def __init__(self, ui, path):
18 def __init__(self, ui, path):
19 self.path = path
19 self.path = path
20 self.ui = ui
20 self.ui = ui
21 try:
21 try:
22 self.repo = hg.repository(self.ui, path)
22 self.repo = hg.repository(self.ui, path)
23 except:
23 except:
24 raise NoRepo("could not open hg repo %s as sink" % path)
24 raise NoRepo("could not open hg repo %s as sink" % path)
25 self.lock = None
25 self.lock = None
26 self.wlock = None
26 self.wlock = None
27 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
27 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
28
28
29 def before(self):
29 def before(self):
30 self.wlock = self.repo.wlock()
30 self.wlock = self.repo.wlock()
31 self.lock = self.repo.lock()
31 self.lock = self.repo.lock()
32
32
33 def after(self):
33 def after(self):
34 self.lock = None
34 self.lock = None
35 self.wlock = None
35 self.wlock = None
36
36
37 def revmapfile(self):
37 def revmapfile(self):
38 return os.path.join(self.path, ".hg", "shamap")
38 return os.path.join(self.path, ".hg", "shamap")
39
39
40 def authorfile(self):
40 def authorfile(self):
41 return os.path.join(self.path, ".hg", "authormap")
41 return os.path.join(self.path, ".hg", "authormap")
42
42
43 def getheads(self):
43 def getheads(self):
44 h = self.repo.changelog.heads()
44 h = self.repo.changelog.heads()
45 return [ hex(x) for x in h ]
45 return [ hex(x) for x in h ]
46
46
47 def putfile(self, f, e, data):
47 def putfile(self, f, e, data):
48 self.repo.wwrite(f, data, e)
48 self.repo.wwrite(f, data, e)
49 if f not in self.repo.dirstate:
49 if f not in self.repo.dirstate:
50 self.repo.dirstate.add(f)
50 self.repo.dirstate.add(f)
51
51
52 def copyfile(self, source, dest):
52 def copyfile(self, source, dest):
53 self.repo.copy(source, dest)
53 self.repo.copy(source, dest)
54
54
55 def delfile(self, f):
55 def delfile(self, f):
56 try:
56 try:
57 os.unlink(self.repo.wjoin(f))
57 os.unlink(self.repo.wjoin(f))
58 #self.repo.remove([f])
58 #self.repo.remove([f])
59 except:
59 except:
60 pass
60 pass
61
61
62 def putcommit(self, files, parents, commit):
62 def putcommit(self, files, parents, commit):
63 if not files:
63 if not files:
64 return hex(self.repo.changelog.tip())
64 return hex(self.repo.changelog.tip())
65
65
66 seen = {hex(nullid): 1}
66 seen = {hex(nullid): 1}
67 pl = []
67 pl = []
68 for p in parents:
68 for p in parents:
69 if p not in seen:
69 if p not in seen:
70 pl.append(p)
70 pl.append(p)
71 seen[p] = 1
71 seen[p] = 1
72 parents = pl
72 parents = pl
73
73
74 if len(parents) < 2: parents.append("0" * 40)
74 if len(parents) < 2: parents.append("0" * 40)
75 if len(parents) < 2: parents.append("0" * 40)
75 if len(parents) < 2: parents.append("0" * 40)
76 p2 = parents.pop(0)
76 p2 = parents.pop(0)
77
77
78 text = commit.desc
78 text = commit.desc
79 extra = {}
79 extra = {}
80 if self.branchnames and commit.branch:
80 if self.branchnames and commit.branch:
81 extra['branch'] = commit.branch
81 extra['branch'] = commit.branch
82 if commit.rev:
82 if commit.rev:
83 extra['convert_revision'] = commit.rev
83 extra['convert_revision'] = commit.rev
84
84
85 while parents:
85 while parents:
86 p1 = p2
86 p1 = p2
87 p2 = parents.pop(0)
87 p2 = parents.pop(0)
88 a = self.repo.rawcommit(files, text, commit.author, commit.date,
88 a = self.repo.rawcommit(files, text, commit.author, commit.date,
89 bin(p1), bin(p2), extra=extra)
89 bin(p1), bin(p2), extra=extra)
90 self.repo.dirstate.invalidate()
90 self.repo.dirstate.invalidate()
91 text = "(octopus merge fixup)\n"
91 text = "(octopus merge fixup)\n"
92 p2 = hg.hex(self.repo.changelog.tip())
92 p2 = hg.hex(self.repo.changelog.tip())
93
93
94 return p2
94 return p2
95
95
96 def puttags(self, tags):
96 def puttags(self, tags):
97 try:
97 try:
98 old = self.repo.wfile(".hgtags").read()
98 old = self.repo.wfile(".hgtags").read()
99 oldlines = old.splitlines(1)
99 oldlines = old.splitlines(1)
100 oldlines.sort()
100 oldlines.sort()
101 except:
101 except:
102 oldlines = []
102 oldlines = []
103
103
104 k = tags.keys()
104 k = tags.keys()
105 k.sort()
105 k.sort()
106 newlines = []
106 newlines = []
107 for tag in k:
107 for tag in k:
108 newlines.append("%s %s\n" % (tags[tag], tag))
108 newlines.append("%s %s\n" % (tags[tag], tag))
109
109
110 newlines.sort()
110 newlines.sort()
111
111
112 if newlines != oldlines:
112 if newlines != oldlines:
113 self.ui.status("updating tags\n")
113 self.ui.status("updating tags\n")
114 f = self.repo.wfile(".hgtags", "w")
114 f = self.repo.wfile(".hgtags", "w")
115 f.write("".join(newlines))
115 f.write("".join(newlines))
116 f.close()
116 f.close()
117 if not oldlines: self.repo.add([".hgtags"])
117 if not oldlines: self.repo.add([".hgtags"])
118 date = "%s 0" % int(time.mktime(time.gmtime()))
118 date = "%s 0" % int(time.mktime(time.gmtime()))
119 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
119 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
120 date, self.repo.changelog.tip(), nullid)
120 date, self.repo.changelog.tip(), nullid)
121 return hex(self.repo.changelog.tip())
121 return hex(self.repo.changelog.tip())
122
122
123 class mercurial_source(converter_source):
123 class mercurial_source(converter_source):
124 def __init__(self, ui, path, rev=None):
124 def __init__(self, ui, path, rev=None):
125 converter_source.__init__(self, ui, path, rev)
125 converter_source.__init__(self, ui, path, rev)
126 self.repo = hg.repository(self.ui, path)
126 self.repo = hg.repository(self.ui, path)
127 self.lastrev = None
127 self.lastrev = None
128 self.lastctx = None
128 self.lastctx = None
129
129
130 def changectx(self, rev):
130 def changectx(self, rev):
131 if self.lastrev != rev:
131 if self.lastrev != rev:
132 self.lastctx = self.repo.changectx(rev)
132 self.lastctx = self.repo.changectx(rev)
133 self.lastrev = rev
133 self.lastrev = rev
134 return self.lastctx
134 return self.lastctx
135
135
136 def getheads(self):
136 def getheads(self):
137 if self.rev:
137 if self.rev:
138 return [hex(self.repo.changectx(self.rev).node())]
138 return [hex(self.repo.changectx(self.rev).node())]
139 else:
139 else:
140 return [hex(node) for node in self.repo.heads()]
140 return [hex(node) for node in self.repo.heads()]
141
141
142 def getfile(self, name, rev):
142 def getfile(self, name, rev):
143 try:
143 try:
144 return self.changectx(rev).filectx(name).data()
144 return self.changectx(rev).filectx(name).data()
145 except revlog.LookupError, err:
145 except revlog.LookupError, err:
146 raise IOError(err)
146 raise IOError(err)
147
147
148 def getmode(self, name, rev):
148 def getmode(self, name, rev):
149 m = self.changectx(rev).manifest()
149 m = self.changectx(rev).manifest()
150 return (m.execf(name) and 'x' or '') + (m.linkf(name) and 'l' or '')
150 return (m.execf(name) and 'x' or '') + (m.linkf(name) and 'l' or '')
151
151
152 def getchanges(self, rev):
152 def getchanges(self, rev):
153 ctx = self.changectx(rev)
153 ctx = self.changectx(rev)
154 m, a, r = self.repo.status(ctx.parents()[0].node(), ctx.node())[:3]
154 m, a, r = self.repo.status(ctx.parents()[0].node(), ctx.node())[:3]
155 changes = [(name, rev) for name in m + a + r]
155 changes = [(name, rev) for name in m + a + r]
156 changes.sort()
156 changes.sort()
157 return (changes, self.getcopies(ctx))
157 return (changes, self.getcopies(ctx))
158
158
159 def getcopies(self, ctx):
159 def getcopies(self, ctx):
160 added = self.repo.status(ctx.parents()[0].node(), ctx.node())[1]
160 added = self.repo.status(ctx.parents()[0].node(), ctx.node())[1]
161 copies = {}
161 copies = {}
162 for name in added:
162 for name in added:
163 try:
163 try:
164 copies[name] = ctx.filectx(name).renamed()[0]
164 copies[name] = ctx.filectx(name).renamed()[0]
165 except TypeError:
165 except TypeError:
166 pass
166 pass
167 return copies
167 return copies
168
168
169 def getcommit(self, rev):
169 def getcommit(self, rev):
170 ctx = self.changectx(rev)
170 ctx = self.changectx(rev)
171 parents = [hex(p.node()) for p in ctx.parents() if p.node() != nullid]
171 parents = [hex(p.node()) for p in ctx.parents() if p.node() != nullid]
172 return commit(author=ctx.user(), date=util.datestr(ctx.date()),
172 return commit(author=ctx.user(), date=util.datestr(ctx.date()),
173 desc=ctx.description(), parents=parents,
173 desc=ctx.description(), parents=parents,
174 branch=ctx.branch())
174 branch=ctx.branch())
175
175
176 def gettags(self):
176 def gettags(self):
177 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
177 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
178 return dict([(name, hex(node)) for name, node in tags])
178 return dict([(name, hex(node)) for name, node in tags])
@@ -1,645 +1,645 b''
1 # Subversion 1.4/1.5 Python API backend
1 # Subversion 1.4/1.5 Python API backend
2 #
2 #
3 # Copyright(C) 2007 Daniel Holth et al
3 # Copyright(C) 2007 Daniel Holth et al
4 #
4 #
5 # Configuration options:
5 # Configuration options:
6 #
6 #
7 # convert.svn.trunk
7 # convert.svn.trunk
8 # Relative path to the trunk (default: "trunk")
8 # Relative path to the trunk (default: "trunk")
9 # convert.svn.branches
9 # convert.svn.branches
10 # Relative path to tree of branches (default: "branches")
10 # Relative path to tree of branches (default: "branches")
11 #
11 #
12 # Set these in a hgrc, or on the command line as follows:
12 # Set these in a hgrc, or on the command line as follows:
13 #
13 #
14 # hg convert --config convert.svn.trunk=wackoname [...]
14 # hg convert --config convert.svn.trunk=wackoname [...]
15
15
16 import locale
16 import locale
17 import os
17 import os
18 import sys
18 import sys
19 import cPickle as pickle
19 import cPickle as pickle
20 from mercurial import util
20 from mercurial import util
21
21
22 # Subversion stuff. Works best with very recent Python SVN bindings
22 # Subversion stuff. Works best with very recent Python SVN bindings
23 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
23 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
24 # these bindings.
24 # these bindings.
25
25
26 from cStringIO import StringIO
26 from cStringIO import StringIO
27
27
28 from common import NoRepo, commit, converter_source, encodeargs, decodeargs
28 from common import NoRepo, commit, converter_source, encodeargs, decodeargs
29
29
30 try:
30 try:
31 from svn.core import SubversionException, Pool
31 from svn.core import SubversionException, Pool
32 import svn
32 import svn
33 import svn.client
33 import svn.client
34 import svn.core
34 import svn.core
35 import svn.ra
35 import svn.ra
36 import svn.delta
36 import svn.delta
37 import transport
37 import transport
38 except ImportError:
38 except ImportError:
39 pass
39 pass
40
40
41 def geturl(path):
41 def geturl(path):
42 try:
42 try:
43 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
43 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
44 except SubversionException:
44 except SubversionException:
45 pass
45 pass
46 if os.path.isdir(path):
46 if os.path.isdir(path):
47 return 'file://%s' % os.path.normpath(os.path.abspath(path))
47 return 'file://%s' % os.path.normpath(os.path.abspath(path))
48 return path
48 return path
49
49
50 def optrev(number):
50 def optrev(number):
51 optrev = svn.core.svn_opt_revision_t()
51 optrev = svn.core.svn_opt_revision_t()
52 optrev.kind = svn.core.svn_opt_revision_number
52 optrev.kind = svn.core.svn_opt_revision_number
53 optrev.value.number = number
53 optrev.value.number = number
54 return optrev
54 return optrev
55
55
56 class changedpath(object):
56 class changedpath(object):
57 def __init__(self, p):
57 def __init__(self, p):
58 self.copyfrom_path = p.copyfrom_path
58 self.copyfrom_path = p.copyfrom_path
59 self.copyfrom_rev = p.copyfrom_rev
59 self.copyfrom_rev = p.copyfrom_rev
60 self.action = p.action
60 self.action = p.action
61
61
62 def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
62 def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
63 strict_node_history=False):
63 strict_node_history=False):
64 protocol = -1
64 protocol = -1
65 def receiver(orig_paths, revnum, author, date, message, pool):
65 def receiver(orig_paths, revnum, author, date, message, pool):
66 if orig_paths is not None:
66 if orig_paths is not None:
67 for k, v in orig_paths.iteritems():
67 for k, v in orig_paths.iteritems():
68 orig_paths[k] = changedpath(v)
68 orig_paths[k] = changedpath(v)
69 pickle.dump((orig_paths, revnum, author, date, message),
69 pickle.dump((orig_paths, revnum, author, date, message),
70 fp, protocol)
70 fp, protocol)
71
71
72 try:
72 try:
73 # Use an ra of our own so that our parent can consume
73 # Use an ra of our own so that our parent can consume
74 # our results without confusing the server.
74 # our results without confusing the server.
75 t = transport.SvnRaTransport(url=url)
75 t = transport.SvnRaTransport(url=url)
76 svn.ra.get_log(t.ra, paths, start, end, limit,
76 svn.ra.get_log(t.ra, paths, start, end, limit,
77 discover_changed_paths,
77 discover_changed_paths,
78 strict_node_history,
78 strict_node_history,
79 receiver)
79 receiver)
80 except SubversionException, (inst, num):
80 except SubversionException, (inst, num):
81 pickle.dump(num, fp, protocol)
81 pickle.dump(num, fp, protocol)
82 else:
82 else:
83 pickle.dump(None, fp, protocol)
83 pickle.dump(None, fp, protocol)
84 fp.close()
84 fp.close()
85
85
86 def debugsvnlog(ui, **opts):
86 def debugsvnlog(ui, **opts):
87 """Fetch SVN log in a subprocess and channel them back to parent to
87 """Fetch SVN log in a subprocess and channel them back to parent to
88 avoid memory collection issues.
88 avoid memory collection issues.
89 """
89 """
90 util.set_binary(sys.stdin)
90 util.set_binary(sys.stdin)
91 util.set_binary(sys.stdout)
91 util.set_binary(sys.stdout)
92 args = decodeargs(sys.stdin.read())
92 args = decodeargs(sys.stdin.read())
93 get_log_child(sys.stdout, *args)
93 get_log_child(sys.stdout, *args)
94
94
95 # SVN conversion code stolen from bzr-svn and tailor
95 # SVN conversion code stolen from bzr-svn and tailor
96 class convert_svn(converter_source):
96 class convert_svn(converter_source):
97 def __init__(self, ui, url, rev=None):
97 def __init__(self, ui, url, rev=None):
98 super(convert_svn, self).__init__(ui, url, rev=rev)
98 super(convert_svn, self).__init__(ui, url, rev=rev)
99
99
100 try:
100 try:
101 SubversionException
101 SubversionException
102 except NameError:
102 except NameError:
103 msg = 'subversion python bindings could not be loaded\n'
103 msg = 'subversion python bindings could not be loaded\n'
104 ui.warn(msg)
104 ui.warn(msg)
105 raise NoRepo(msg)
105 raise NoRepo(msg)
106
106
107 self.encoding = locale.getpreferredencoding()
107 self.encoding = locale.getpreferredencoding()
108 self.lastrevs = {}
108 self.lastrevs = {}
109
109
110 latest = None
110 latest = None
111 if rev:
111 if rev:
112 try:
112 try:
113 latest = int(rev)
113 latest = int(rev)
114 except ValueError:
114 except ValueError:
115 raise NoRepo('svn: revision %s is not an integer' % rev)
115 raise NoRepo('svn: revision %s is not an integer' % rev)
116 try:
116 try:
117 # Support file://path@rev syntax. Useful e.g. to convert
117 # Support file://path@rev syntax. Useful e.g. to convert
118 # deleted branches.
118 # deleted branches.
119 at = url.rfind('@')
119 at = url.rfind('@')
120 if at >= 0:
120 if at >= 0:
121 latest = int(url[at+1:])
121 latest = int(url[at+1:])
122 url = url[:at]
122 url = url[:at]
123 except ValueError, e:
123 except ValueError, e:
124 pass
124 pass
125 self.url = geturl(url)
125 self.url = geturl(url)
126 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
126 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
127 try:
127 try:
128 self.transport = transport.SvnRaTransport(url=self.url)
128 self.transport = transport.SvnRaTransport(url=self.url)
129 self.ra = self.transport.ra
129 self.ra = self.transport.ra
130 self.ctx = self.transport.client
130 self.ctx = self.transport.client
131 self.base = svn.ra.get_repos_root(self.ra)
131 self.base = svn.ra.get_repos_root(self.ra)
132 self.module = self.url[len(self.base):]
132 self.module = self.url[len(self.base):]
133 self.modulemap = {} # revision, module
133 self.modulemap = {} # revision, module
134 self.commits = {}
134 self.commits = {}
135 self.paths = {}
135 self.paths = {}
136 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
136 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
137 except SubversionException, e:
137 except SubversionException, e:
138 raise NoRepo("couldn't open SVN repo %s" % self.url)
138 raise NoRepo("couldn't open SVN repo %s" % self.url)
139
139
140 try:
140 try:
141 self.get_blacklist()
141 self.get_blacklist()
142 except IOError, e:
142 except IOError, e:
143 pass
143 pass
144
144
145 self.last_changed = self.latest(self.module, latest)
145 self.last_changed = self.latest(self.module, latest)
146
146
147 self.head = self.revid(self.last_changed)
147 self.head = self.revid(self.last_changed)
148
148
149 def setrevmap(self, revmap):
149 def setrevmap(self, revmap):
150 lastrevs = {}
150 lastrevs = {}
151 for revid in revmap.keys():
151 for revid in revmap.keys():
152 uuid, module, revnum = self.revsplit(revid)
152 uuid, module, revnum = self.revsplit(revid)
153 lastrevnum = lastrevs.setdefault(module, revnum)
153 lastrevnum = lastrevs.setdefault(module, revnum)
154 if revnum > lastrevnum:
154 if revnum > lastrevnum:
155 lastrevs[module] = revnum
155 lastrevs[module] = revnum
156 self.lastrevs = lastrevs
156 self.lastrevs = lastrevs
157
157
158 def exists(self, path, optrev):
158 def exists(self, path, optrev):
159 try:
159 try:
160 return svn.client.ls(self.url.rstrip('/') + '/' + path,
160 return svn.client.ls(self.url.rstrip('/') + '/' + path,
161 optrev, False, self.ctx)
161 optrev, False, self.ctx)
162 except SubversionException, err:
162 except SubversionException, err:
163 return []
163 return []
164
164
165 def getheads(self):
165 def getheads(self):
166 # detect standard /branches, /tags, /trunk layout
166 # detect standard /branches, /tags, /trunk layout
167 rev = optrev(self.last_changed)
167 rev = optrev(self.last_changed)
168 rpath = self.url.strip('/')
168 rpath = self.url.strip('/')
169 cfgtrunk = self.ui.config('convert', 'svn.trunk')
169 cfgtrunk = self.ui.config('convert', 'svn.trunk')
170 cfgbranches = self.ui.config('convert', 'svn.branches')
170 cfgbranches = self.ui.config('convert', 'svn.branches')
171 trunk = (cfgtrunk or 'trunk').strip('/')
171 trunk = (cfgtrunk or 'trunk').strip('/')
172 branches = (cfgbranches or 'branches').strip('/')
172 branches = (cfgbranches or 'branches').strip('/')
173 if self.exists(trunk, rev) and self.exists(branches, rev):
173 if self.exists(trunk, rev) and self.exists(branches, rev):
174 self.ui.note('found trunk at %r and branches at %r\n' %
174 self.ui.note('found trunk at %r and branches at %r\n' %
175 (trunk, branches))
175 (trunk, branches))
176 oldmodule = self.module
176 oldmodule = self.module
177 self.module += '/' + trunk
177 self.module += '/' + trunk
178 lt = self.latest(self.module, self.last_changed)
178 lt = self.latest(self.module, self.last_changed)
179 self.head = self.revid(lt)
179 self.head = self.revid(lt)
180 self.heads = [self.head]
180 self.heads = [self.head]
181 branchnames = svn.client.ls(rpath + '/' + branches, rev, False,
181 branchnames = svn.client.ls(rpath + '/' + branches, rev, False,
182 self.ctx)
182 self.ctx)
183 for branch in branchnames.keys():
183 for branch in branchnames.keys():
184 if oldmodule:
184 if oldmodule:
185 module = '/' + oldmodule + '/' + branches + '/' + branch
185 module = '/' + oldmodule + '/' + branches + '/' + branch
186 else:
186 else:
187 module = '/' + branches + '/' + branch
187 module = '/' + branches + '/' + branch
188 brevnum = self.latest(module, self.last_changed)
188 brevnum = self.latest(module, self.last_changed)
189 brev = self.revid(brevnum, module)
189 brev = self.revid(brevnum, module)
190 self.ui.note('found branch %s at %d\n' % (branch, brevnum))
190 self.ui.note('found branch %s at %d\n' % (branch, brevnum))
191 self.heads.append(brev)
191 self.heads.append(brev)
192 elif cfgtrunk or cfgbranches:
192 elif cfgtrunk or cfgbranches:
193 raise util.Abort('trunk/branch layout expected, but not found')
193 raise util.Abort('trunk/branch layout expected, but not found')
194 else:
194 else:
195 self.ui.note('working with one branch\n')
195 self.ui.note('working with one branch\n')
196 self.heads = [self.head]
196 self.heads = [self.head]
197 return self.heads
197 return self.heads
198
198
199 def getfile(self, file, rev):
199 def getfile(self, file, rev):
200 data, mode = self._getfile(file, rev)
200 data, mode = self._getfile(file, rev)
201 self.modecache[(file, rev)] = mode
201 self.modecache[(file, rev)] = mode
202 return data
202 return data
203
203
204 def getmode(self, file, rev):
204 def getmode(self, file, rev):
205 return self.modecache[(file, rev)]
205 return self.modecache[(file, rev)]
206
206
207 def getchanges(self, rev):
207 def getchanges(self, rev):
208 self.modecache = {}
208 self.modecache = {}
209 (paths, parents) = self.paths[rev]
209 (paths, parents) = self.paths[rev]
210 files, copies = self.expandpaths(rev, paths, parents)
210 files, copies = self.expandpaths(rev, paths, parents)
211 files.sort()
211 files.sort()
212 files = zip(files, [rev] * len(files))
212 files = zip(files, [rev] * len(files))
213
213
214 # caller caches the result, so free it here to release memory
214 # caller caches the result, so free it here to release memory
215 del self.paths[rev]
215 del self.paths[rev]
216 return (files, copies)
216 return (files, copies)
217
217
218 def getcommit(self, rev):
218 def getcommit(self, rev):
219 if rev not in self.commits:
219 if rev not in self.commits:
220 uuid, module, revnum = self.revsplit(rev)
220 uuid, module, revnum = self.revsplit(rev)
221 self.module = module
221 self.module = module
222 self.reparent(module)
222 self.reparent(module)
223 stop = self.lastrevs.get(module, 0)
223 stop = self.lastrevs.get(module, 0)
224 self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
224 self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
225 commit = self.commits[rev]
225 commit = self.commits[rev]
226 # caller caches the result, so free it here to release memory
226 # caller caches the result, so free it here to release memory
227 del self.commits[rev]
227 del self.commits[rev]
228 return commit
228 return commit
229
229
230 def get_log(self, paths, start, end, limit=0, discover_changed_paths=True,
230 def get_log(self, paths, start, end, limit=0, discover_changed_paths=True,
231 strict_node_history=False):
231 strict_node_history=False):
232
232
233 def parent(fp):
233 def parent(fp):
234 while True:
234 while True:
235 entry = pickle.load(fp)
235 entry = pickle.load(fp)
236 try:
236 try:
237 orig_paths, revnum, author, date, message = entry
237 orig_paths, revnum, author, date, message = entry
238 except:
238 except:
239 if entry is None:
239 if entry is None:
240 break
240 break
241 raise SubversionException("child raised exception", entry)
241 raise SubversionException("child raised exception", entry)
242 yield entry
242 yield entry
243
243
244 args = [self.url, paths, start, end, limit, discover_changed_paths,
244 args = [self.url, paths, start, end, limit, discover_changed_paths,
245 strict_node_history]
245 strict_node_history]
246 arg = encodeargs(args)
246 arg = encodeargs(args)
247 hgexe = util.hgexecutable()
247 hgexe = util.hgexecutable()
248 cmd = '"%s "debugsvnlog""' % util.shellquote(hgexe)
248 cmd = '"%s "debugsvnlog""' % util.shellquote(hgexe)
249 stdin, stdout = os.popen2(cmd, 'b')
249 stdin, stdout = os.popen2(cmd, 'b')
250
250
251 stdin.write(arg)
251 stdin.write(arg)
252 stdin.close()
252 stdin.close()
253
253
254 for p in parent(stdout):
254 for p in parent(stdout):
255 yield p
255 yield p
256
256
257 def gettags(self):
257 def gettags(self):
258 tags = {}
258 tags = {}
259 start = self.revnum(self.head)
259 start = self.revnum(self.head)
260 try:
260 try:
261 for entry in self.get_log(['/tags'], 0, start):
261 for entry in self.get_log(['/tags'], 0, start):
262 orig_paths, revnum, author, date, message = entry
262 orig_paths, revnum, author, date, message = entry
263 for path in orig_paths:
263 for path in orig_paths:
264 if not path.startswith('/tags/'):
264 if not path.startswith('/tags/'):
265 continue
265 continue
266 ent = orig_paths[path]
266 ent = orig_paths[path]
267 source = ent.copyfrom_path
267 source = ent.copyfrom_path
268 rev = ent.copyfrom_rev
268 rev = ent.copyfrom_rev
269 tag = path.split('/', 2)[2]
269 tag = path.split('/', 2)[2]
270 tags[tag] = self.revid(rev, module=source)
270 tags[tag] = self.revid(rev, module=source)
271 except SubversionException, (inst, num):
271 except SubversionException, (inst, num):
272 self.ui.note('no tags found at revision %d\n' % start)
272 self.ui.note('no tags found at revision %d\n' % start)
273 return tags
273 return tags
274
274
275 # -- helper functions --
275 # -- helper functions --
276
276
277 def revid(self, revnum, module=None):
277 def revid(self, revnum, module=None):
278 if not module:
278 if not module:
279 module = self.module
279 module = self.module
280 return (u"svn:%s%s@%s" % (self.uuid, module, revnum)).decode(self.encoding)
280 return (u"svn:%s%s@%s" % (self.uuid, module, revnum)).decode(self.encoding)
281
281
282 def revnum(self, rev):
282 def revnum(self, rev):
283 return int(rev.split('@')[-1])
283 return int(rev.split('@')[-1])
284
284
285 def revsplit(self, rev):
285 def revsplit(self, rev):
286 url, revnum = rev.encode(self.encoding).split('@', 1)
286 url, revnum = rev.encode(self.encoding).split('@', 1)
287 revnum = int(revnum)
287 revnum = int(revnum)
288 parts = url.split('/', 1)
288 parts = url.split('/', 1)
289 uuid = parts.pop(0)[4:]
289 uuid = parts.pop(0)[4:]
290 mod = ''
290 mod = ''
291 if parts:
291 if parts:
292 mod = '/' + parts[0]
292 mod = '/' + parts[0]
293 return uuid, mod, revnum
293 return uuid, mod, revnum
294
294
295 def latest(self, path, stop=0):
295 def latest(self, path, stop=0):
296 'find the latest revision affecting path, up to stop'
296 'find the latest revision affecting path, up to stop'
297 if not stop:
297 if not stop:
298 stop = svn.ra.get_latest_revnum(self.ra)
298 stop = svn.ra.get_latest_revnum(self.ra)
299 try:
299 try:
300 self.reparent('')
300 self.reparent('')
301 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
301 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
302 self.reparent(self.module)
302 self.reparent(self.module)
303 except SubversionException:
303 except SubversionException:
304 dirent = None
304 dirent = None
305 if not dirent:
305 if not dirent:
306 raise util.Abort('%s not found up to revision %d' % (path, stop))
306 raise util.Abort('%s not found up to revision %d' % (path, stop))
307
307
308 return dirent.created_rev
308 return dirent.created_rev
309
309
310 def get_blacklist(self):
310 def get_blacklist(self):
311 """Avoid certain revision numbers.
311 """Avoid certain revision numbers.
312 It is not uncommon for two nearby revisions to cancel each other
312 It is not uncommon for two nearby revisions to cancel each other
313 out, e.g. 'I copied trunk into a subdirectory of itself instead
313 out, e.g. 'I copied trunk into a subdirectory of itself instead
314 of making a branch'. The converted repository is significantly
314 of making a branch'. The converted repository is significantly
315 smaller if we ignore such revisions."""
315 smaller if we ignore such revisions."""
316 self.blacklist = set()
316 self.blacklist = set()
317 blacklist = self.blacklist
317 blacklist = self.blacklist
318 for line in file("blacklist.txt", "r"):
318 for line in file("blacklist.txt", "r"):
319 if not line.startswith("#"):
319 if not line.startswith("#"):
320 try:
320 try:
321 svn_rev = int(line.strip())
321 svn_rev = int(line.strip())
322 blacklist.add(svn_rev)
322 blacklist.add(svn_rev)
323 except ValueError, e:
323 except ValueError, e:
324 pass # not an integer or a comment
324 pass # not an integer or a comment
325
325
326 def is_blacklisted(self, svn_rev):
326 def is_blacklisted(self, svn_rev):
327 return svn_rev in self.blacklist
327 return svn_rev in self.blacklist
328
328
329 def reparent(self, module):
329 def reparent(self, module):
330 svn_url = self.base + module
330 svn_url = self.base + module
331 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
331 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
332 svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
332 svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
333
333
334 def expandpaths(self, rev, paths, parents):
334 def expandpaths(self, rev, paths, parents):
335 def get_entry_from_path(path, module=self.module):
335 def get_entry_from_path(path, module=self.module):
336 # Given the repository url of this wc, say
336 # Given the repository url of this wc, say
337 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
337 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
338 # extract the "entry" portion (a relative path) from what
338 # extract the "entry" portion (a relative path) from what
339 # svn log --xml says, ie
339 # svn log --xml says, ie
340 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
340 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
341 # that is to say "tests/PloneTestCase.py"
341 # that is to say "tests/PloneTestCase.py"
342 if path.startswith(module):
342 if path.startswith(module):
343 relative = path[len(module):]
343 relative = path[len(module):]
344 if relative.startswith('/'):
344 if relative.startswith('/'):
345 return relative[1:]
345 return relative[1:]
346 else:
346 else:
347 return relative
347 return relative
348
348
349 # The path is outside our tracked tree...
349 # The path is outside our tracked tree...
350 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
350 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
351 return None
351 return None
352
352
353 entries = []
353 entries = []
354 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
354 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
355 copies = {}
355 copies = {}
356 revnum = self.revnum(rev)
356 revnum = self.revnum(rev)
357
357
358 if revnum in self.modulemap:
358 if revnum in self.modulemap:
359 new_module = self.modulemap[revnum]
359 new_module = self.modulemap[revnum]
360 if new_module != self.module:
360 if new_module != self.module:
361 self.module = new_module
361 self.module = new_module
362 self.reparent(self.module)
362 self.reparent(self.module)
363
363
364 for path, ent in paths:
364 for path, ent in paths:
365 entrypath = get_entry_from_path(path, module=self.module)
365 entrypath = get_entry_from_path(path, module=self.module)
366 entry = entrypath.decode(self.encoding)
366 entry = entrypath.decode(self.encoding)
367
367
368 kind = svn.ra.check_path(self.ra, entrypath, revnum)
368 kind = svn.ra.check_path(self.ra, entrypath, revnum)
369 if kind == svn.core.svn_node_file:
369 if kind == svn.core.svn_node_file:
370 if ent.copyfrom_path:
370 if ent.copyfrom_path:
371 copyfrom_path = get_entry_from_path(ent.copyfrom_path)
371 copyfrom_path = get_entry_from_path(ent.copyfrom_path)
372 if copyfrom_path:
372 if copyfrom_path:
373 self.ui.debug("Copied to %s from %s@%s\n" % (entry, copyfrom_path, ent.copyfrom_rev))
373 self.ui.debug("Copied to %s from %s@%s\n" % (entry, copyfrom_path, ent.copyfrom_rev))
374 # It's probably important for hg that the source
374 # It's probably important for hg that the source
375 # exists in the revision's parent, not just the
375 # exists in the revision's parent, not just the
376 # ent.copyfrom_rev
376 # ent.copyfrom_rev
377 fromkind = svn.ra.check_path(self.ra, copyfrom_path, ent.copyfrom_rev)
377 fromkind = svn.ra.check_path(self.ra, copyfrom_path, ent.copyfrom_rev)
378 if fromkind != 0:
378 if fromkind != 0:
379 copies[self.recode(entry)] = self.recode(copyfrom_path)
379 copies[self.recode(entry)] = self.recode(copyfrom_path)
380 entries.append(self.recode(entry))
380 entries.append(self.recode(entry))
381 elif kind == 0: # gone, but had better be a deleted *file*
381 elif kind == 0: # gone, but had better be a deleted *file*
382 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
382 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
383
383
384 # if a branch is created but entries are removed in the same
384 # if a branch is created but entries are removed in the same
385 # changeset, get the right fromrev
385 # changeset, get the right fromrev
386 if parents:
386 if parents:
387 uuid, old_module, fromrev = self.revsplit(parents[0])
387 uuid, old_module, fromrev = self.revsplit(parents[0])
388 else:
388 else:
389 fromrev = revnum - 1
389 fromrev = revnum - 1
390 # might always need to be revnum - 1 in these 3 lines?
390 # might always need to be revnum - 1 in these 3 lines?
391 old_module = self.modulemap.get(fromrev, self.module)
391 old_module = self.modulemap.get(fromrev, self.module)
392
392
393 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
393 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
394 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
394 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
395
395
396 def lookup_parts(p):
396 def lookup_parts(p):
397 rc = None
397 rc = None
398 parts = p.split("/")
398 parts = p.split("/")
399 for i in range(len(parts)):
399 for i in range(len(parts)):
400 part = "/".join(parts[:i])
400 part = "/".join(parts[:i])
401 info = part, copyfrom.get(part, None)
401 info = part, copyfrom.get(part, None)
402 if info[1] is not None:
402 if info[1] is not None:
403 self.ui.debug("Found parent directory %s\n" % info[1])
403 self.ui.debug("Found parent directory %s\n" % info[1])
404 rc = info
404 rc = info
405 return rc
405 return rc
406
406
407 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
407 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
408
408
409 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
409 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
410
410
411 # need to remove fragment from lookup_parts and replace with copyfrom_path
411 # need to remove fragment from lookup_parts and replace with copyfrom_path
412 if frompath is not None:
412 if frompath is not None:
413 self.ui.debug("munge-o-matic\n")
413 self.ui.debug("munge-o-matic\n")
414 self.ui.debug(entrypath + '\n')
414 self.ui.debug(entrypath + '\n')
415 self.ui.debug(entrypath[len(frompath):] + '\n')
415 self.ui.debug(entrypath[len(frompath):] + '\n')
416 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
416 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
417 fromrev = froment.copyfrom_rev
417 fromrev = froment.copyfrom_rev
418 self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
418 self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
419
419
420 fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
420 fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
421 if fromkind == svn.core.svn_node_file: # a deleted file
421 if fromkind == svn.core.svn_node_file: # a deleted file
422 entries.append(self.recode(entry))
422 entries.append(self.recode(entry))
423 elif fromkind == svn.core.svn_node_dir:
423 elif fromkind == svn.core.svn_node_dir:
424 # print "Deleted/moved non-file:", revnum, path, ent
424 # print "Deleted/moved non-file:", revnum, path, ent
425 # children = self._find_children(path, revnum - 1)
425 # children = self._find_children(path, revnum - 1)
426 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
426 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
427 # Sometimes this is tricky. For example: in
427 # Sometimes this is tricky. For example: in
428 # The Subversion Repository revision 6940 a dir
428 # The Subversion Repository revision 6940 a dir
429 # was copied and one of its files was deleted
429 # was copied and one of its files was deleted
430 # from the new location in the same commit. This
430 # from the new location in the same commit. This
431 # code can't deal with that yet.
431 # code can't deal with that yet.
432 if ent.action == 'C':
432 if ent.action == 'C':
433 children = self._find_children(path, fromrev)
433 children = self._find_children(path, fromrev)
434 else:
434 else:
435 oroot = entrypath.strip('/')
435 oroot = entrypath.strip('/')
436 nroot = path.strip('/')
436 nroot = path.strip('/')
437 children = self._find_children(oroot, fromrev)
437 children = self._find_children(oroot, fromrev)
438 children = [s.replace(oroot,nroot) for s in children]
438 children = [s.replace(oroot,nroot) for s in children]
439 # Mark all [files, not directories] as deleted.
439 # Mark all [files, not directories] as deleted.
440 for child in children:
440 for child in children:
441 # Can we move a child directory and its
441 # Can we move a child directory and its
442 # parent in the same commit? (probably can). Could
442 # parent in the same commit? (probably can). Could
443 # cause problems if instead of revnum -1,
443 # cause problems if instead of revnum -1,
444 # we have to look in (copyfrom_path, revnum - 1)
444 # we have to look in (copyfrom_path, revnum - 1)
445 entrypath = get_entry_from_path("/" + child, module=old_module)
445 entrypath = get_entry_from_path("/" + child, module=old_module)
446 if entrypath:
446 if entrypath:
447 entry = self.recode(entrypath.decode(self.encoding))
447 entry = self.recode(entrypath.decode(self.encoding))
448 if entry in copies:
448 if entry in copies:
449 # deleted file within a copy
449 # deleted file within a copy
450 del copies[entry]
450 del copies[entry]
451 else:
451 else:
452 entries.append(entry)
452 entries.append(entry)
453 else:
453 else:
454 self.ui.debug('unknown path in revision %d: %s\n' % \
454 self.ui.debug('unknown path in revision %d: %s\n' % \
455 (revnum, path))
455 (revnum, path))
456 elif kind == svn.core.svn_node_dir:
456 elif kind == svn.core.svn_node_dir:
457 # Should probably synthesize normal file entries
457 # Should probably synthesize normal file entries
458 # and handle as above to clean up copy/rename handling.
458 # and handle as above to clean up copy/rename handling.
459
459
460 # If the directory just had a prop change,
460 # If the directory just had a prop change,
461 # then we shouldn't need to look for its children.
461 # then we shouldn't need to look for its children.
462 # Also this could create duplicate entries. Not sure
462 # Also this could create duplicate entries. Not sure
463 # whether this will matter. Maybe should make entries a set.
463 # whether this will matter. Maybe should make entries a set.
464 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
464 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
465 # This will fail if a directory was copied
465 # This will fail if a directory was copied
466 # from another branch and then some of its files
466 # from another branch and then some of its files
467 # were deleted in the same transaction.
467 # were deleted in the same transaction.
468 children = self._find_children(path, revnum)
468 children = self._find_children(path, revnum)
469 children.sort()
469 children.sort()
470 for child in children:
470 for child in children:
471 # Can we move a child directory and its
471 # Can we move a child directory and its
472 # parent in the same commit? (probably can). Could
472 # parent in the same commit? (probably can). Could
473 # cause problems if instead of revnum -1,
473 # cause problems if instead of revnum -1,
474 # we have to look in (copyfrom_path, revnum - 1)
474 # we have to look in (copyfrom_path, revnum - 1)
475 entrypath = get_entry_from_path("/" + child, module=self.module)
475 entrypath = get_entry_from_path("/" + child, module=self.module)
476 # print child, self.module, entrypath
476 # print child, self.module, entrypath
477 if entrypath:
477 if entrypath:
478 # Need to filter out directories here...
478 # Need to filter out directories here...
479 kind = svn.ra.check_path(self.ra, entrypath, revnum)
479 kind = svn.ra.check_path(self.ra, entrypath, revnum)
480 if kind != svn.core.svn_node_dir:
480 if kind != svn.core.svn_node_dir:
481 entries.append(self.recode(entrypath))
481 entries.append(self.recode(entrypath))
482
482
483 # Copies here (must copy all from source)
483 # Copies here (must copy all from source)
484 # Probably not a real problem for us if
484 # Probably not a real problem for us if
485 # source does not exist
485 # source does not exist
486
486
487 # Can do this with the copy command "hg copy"
487 # Can do this with the copy command "hg copy"
488 # if ent.copyfrom_path:
488 # if ent.copyfrom_path:
489 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
489 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
490 # module=self.module)
490 # module=self.module)
491 # copyto_entry = entrypath
491 # copyto_entry = entrypath
492 #
492 #
493 # print "copy directory", copyfrom_entry, 'to', copyto_entry
493 # print "copy directory", copyfrom_entry, 'to', copyto_entry
494 #
494 #
495 # copies.append((copyfrom_entry, copyto_entry))
495 # copies.append((copyfrom_entry, copyto_entry))
496
496
497 if ent.copyfrom_path:
497 if ent.copyfrom_path:
498 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
498 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
499 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
499 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
500 if copyfrom_entry:
500 if copyfrom_entry:
501 copyfrom[path] = ent
501 copyfrom[path] = ent
502 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
502 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
503
503
504 # Good, /probably/ a regular copy. Really should check
504 # Good, /probably/ a regular copy. Really should check
505 # to see whether the parent revision actually contains
505 # to see whether the parent revision actually contains
506 # the directory in question.
506 # the directory in question.
507 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
507 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
508 children.sort()
508 children.sort()
509 for child in children:
509 for child in children:
510 entrypath = get_entry_from_path("/" + child, module=self.module)
510 entrypath = get_entry_from_path("/" + child, module=self.module)
511 if entrypath:
511 if entrypath:
512 entry = entrypath.decode(self.encoding)
512 entry = entrypath.decode(self.encoding)
513 # print "COPY COPY From", copyfrom_entry, entry
513 # print "COPY COPY From", copyfrom_entry, entry
514 copyto_path = path + entry[len(copyfrom_entry):]
514 copyto_path = path + entry[len(copyfrom_entry):]
515 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
515 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
516 # print "COPY", entry, "COPY To", copyto_entry
516 # print "COPY", entry, "COPY To", copyto_entry
517 copies[self.recode(copyto_entry)] = self.recode(entry)
517 copies[self.recode(copyto_entry)] = self.recode(entry)
518 # copy from quux splort/quuxfile
518 # copy from quux splort/quuxfile
519
519
520 return (entries, copies)
520 return (entries, copies)
521
521
522 def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
522 def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
523 self.child_cset = None
523 self.child_cset = None
524 def parselogentry(orig_paths, revnum, author, date, message):
524 def parselogentry(orig_paths, revnum, author, date, message):
525 self.ui.debug("parsing revision %d (%d changes)\n" %
525 self.ui.debug("parsing revision %d (%d changes)\n" %
526 (revnum, len(orig_paths)))
526 (revnum, len(orig_paths)))
527
527
528 if revnum in self.modulemap:
528 if revnum in self.modulemap:
529 new_module = self.modulemap[revnum]
529 new_module = self.modulemap[revnum]
530 if new_module != self.module:
530 if new_module != self.module:
531 self.module = new_module
531 self.module = new_module
532 self.reparent(self.module)
532 self.reparent(self.module)
533
533
534 rev = self.revid(revnum)
534 rev = self.revid(revnum)
535 # branch log might return entries for a parent we already have
535 # branch log might return entries for a parent we already have
536 if (rev in self.commits or
536 if (rev in self.commits or
537 (revnum < self.lastrevs.get(self.module, 0))):
537 (revnum < self.lastrevs.get(self.module, 0))):
538 return
538 return
539
539
540 parents = []
540 parents = []
541 orig_paths = orig_paths.items()
541 orig_paths = orig_paths.items()
542 orig_paths.sort()
542 orig_paths.sort()
543
543
544 # check whether this revision is the start of a branch
544 # check whether this revision is the start of a branch
545 path, ent = orig_paths and orig_paths[0] or (None, None)
545 path, ent = orig_paths and orig_paths[0] or (None, None)
546 if ent and path == self.module:
546 if ent and path == self.module:
547 if ent.copyfrom_path:
547 if ent.copyfrom_path:
548 # ent.copyfrom_rev may not be the actual last revision
548 # ent.copyfrom_rev may not be the actual last revision
549 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
549 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
550 self.modulemap[prev] = ent.copyfrom_path
550 self.modulemap[prev] = ent.copyfrom_path
551 parents = [self.revid(prev, ent.copyfrom_path)]
551 parents = [self.revid(prev, ent.copyfrom_path)]
552 self.ui.note('found parent of branch %s at %d: %s\n' % \
552 self.ui.note('found parent of branch %s at %d: %s\n' % \
553 (self.module, prev, ent.copyfrom_path))
553 (self.module, prev, ent.copyfrom_path))
554 else:
554 else:
555 self.ui.debug("No copyfrom path, don't know what to do.\n")
555 self.ui.debug("No copyfrom path, don't know what to do.\n")
556
556
557 self.modulemap[revnum] = self.module # track backwards in time
557 self.modulemap[revnum] = self.module # track backwards in time
558
558
559 paths = []
559 paths = []
560 # filter out unrelated paths
560 # filter out unrelated paths
561 for path, ent in orig_paths:
561 for path, ent in orig_paths:
562 if not path.startswith(self.module):
562 if not path.startswith(self.module):
563 self.ui.debug("boring@%s: %s\n" % (revnum, path))
563 self.ui.debug("boring@%s: %s\n" % (revnum, path))
564 continue
564 continue
565 paths.append((path, ent))
565 paths.append((path, ent))
566
566
567 self.paths[rev] = (paths, parents)
567 self.paths[rev] = (paths, parents)
568
568
569 # Example SVN datetime. Includes microseconds.
569 # Example SVN datetime. Includes microseconds.
570 # ISO-8601 conformant
570 # ISO-8601 conformant
571 # '2007-01-04T17:35:00.902377Z'
571 # '2007-01-04T17:35:00.902377Z'
572 date = util.parsedate(date[:18] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
572 date = util.parsedate(date[:18] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
573
573
574 log = message and self.recode(message)
574 log = message and self.recode(message)
575 author = author and self.recode(author) or ''
575 author = author and self.recode(author) or ''
576 try:
576 try:
577 branch = self.module.split("/")[-1]
577 branch = self.module.split("/")[-1]
578 if branch == 'trunk':
578 if branch == 'trunk':
579 branch = ''
579 branch = ''
580 except IndexError:
580 except IndexError:
581 branch = None
581 branch = None
582
582
583 cset = commit(author=author,
583 cset = commit(author=author,
584 date=util.datestr(date),
584 date=util.datestr(date),
585 desc=log,
585 desc=log,
586 parents=parents,
586 parents=parents,
587 branch=branch,
587 branch=branch,
588 rev=rev.encode('utf-8'))
588 rev=rev.encode('utf-8'))
589
589
590 self.commits[rev] = cset
590 self.commits[rev] = cset
591 if self.child_cset and not self.child_cset.parents:
591 if self.child_cset and not self.child_cset.parents:
592 self.child_cset.parents = [rev]
592 self.child_cset.parents = [rev]
593 self.child_cset = cset
593 self.child_cset = cset
594
594
595 self.ui.note('fetching revision log for "%s" from %d to %d\n' %
595 self.ui.note('fetching revision log for "%s" from %d to %d\n' %
596 (self.module, from_revnum, to_revnum))
596 (self.module, from_revnum, to_revnum))
597
597
598 try:
598 try:
599 for entry in self.get_log([self.module], from_revnum, to_revnum):
599 for entry in self.get_log([self.module], from_revnum, to_revnum):
600 orig_paths, revnum, author, date, message = entry
600 orig_paths, revnum, author, date, message = entry
601 if self.is_blacklisted(revnum):
601 if self.is_blacklisted(revnum):
602 self.ui.note('skipping blacklisted revision %d\n' % revnum)
602 self.ui.note('skipping blacklisted revision %d\n' % revnum)
603 continue
603 continue
604 if orig_paths is None:
604 if orig_paths is None:
605 self.ui.debug('revision %d has no entries\n' % revnum)
605 self.ui.debug('revision %d has no entries\n' % revnum)
606 continue
606 continue
607 parselogentry(orig_paths, revnum, author, date, message)
607 parselogentry(orig_paths, revnum, author, date, message)
608 except SubversionException, (inst, num):
608 except SubversionException, (inst, num):
609 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
609 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
610 raise NoSuchRevision(branch=self,
610 raise NoSuchRevision(branch=self,
611 revision="Revision number %d" % to_revnum)
611 revision="Revision number %d" % to_revnum)
612 raise
612 raise
613
613
614 def _getfile(self, file, rev):
614 def _getfile(self, file, rev):
615 io = StringIO()
615 io = StringIO()
616 # TODO: ra.get_file transmits the whole file instead of diffs.
616 # TODO: ra.get_file transmits the whole file instead of diffs.
617 mode = ''
617 mode = ''
618 try:
618 try:
619 revnum = self.revnum(rev)
619 revnum = self.revnum(rev)
620 if self.module != self.modulemap[revnum]:
620 if self.module != self.modulemap[revnum]:
621 self.module = self.modulemap[revnum]
621 self.module = self.modulemap[revnum]
622 self.reparent(self.module)
622 self.reparent(self.module)
623 info = svn.ra.get_file(self.ra, file, revnum, io)
623 info = svn.ra.get_file(self.ra, file, revnum, io)
624 if isinstance(info, list):
624 if isinstance(info, list):
625 info = info[-1]
625 info = info[-1]
626 mode = ("svn:executable" in info) and 'x' or ''
626 mode = ("svn:executable" in info) and 'x' or ''
627 mode = ("svn:special" in info) and 'l' or mode
627 mode = ("svn:special" in info) and 'l' or mode
628 except SubversionException, e:
628 except SubversionException, e:
629 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
629 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
630 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
630 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
631 if e.apr_err in notfound: # File not found
631 if e.apr_err in notfound: # File not found
632 raise IOError()
632 raise IOError()
633 raise
633 raise
634 data = io.getvalue()
634 data = io.getvalue()
635 if mode == 'l':
635 if mode == 'l':
636 link_prefix = "link "
636 link_prefix = "link "
637 if data.startswith(link_prefix):
637 if data.startswith(link_prefix):
638 data = data[len(link_prefix):]
638 data = data[len(link_prefix):]
639 return data, mode
639 return data, mode
640
640
641 def _find_children(self, path, revnum):
641 def _find_children(self, path, revnum):
642 path = path.strip('/')
642 path = path.strip('/')
643 pool = Pool()
643 pool = Pool()
644 rpath = '/'.join([self.base, path]).strip('/')
644 rpath = '/'.join([self.base, path]).strip('/')
645 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
645 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
@@ -1,215 +1,215 b''
1 # extdiff.py - external diff program support for mercurial
1 # extdiff.py - external diff program support for mercurial
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7 #
7 #
8 # The `extdiff' Mercurial extension allows you to use external programs
8 # The `extdiff' Mercurial extension allows you to use external programs
9 # to compare revisions, or revision with working dir. The external diff
9 # to compare revisions, or revision with working dir. The external diff
10 # programs are called with a configurable set of options and two
10 # programs are called with a configurable set of options and two
11 # non-option arguments: paths to directories containing snapshots of
11 # non-option arguments: paths to directories containing snapshots of
12 # files to compare.
12 # files to compare.
13 #
13 #
14 # To enable this extension:
14 # To enable this extension:
15 #
15 #
16 # [extensions]
16 # [extensions]
17 # hgext.extdiff =
17 # hgext.extdiff =
18 #
18 #
19 # The `extdiff' extension also allows to configure new diff commands, so
19 # The `extdiff' extension also allows to configure new diff commands, so
20 # you do not need to type "hg extdiff -p kdiff3" always.
20 # you do not need to type "hg extdiff -p kdiff3" always.
21 #
21 #
22 # [extdiff]
22 # [extdiff]
23 # # add new command that runs GNU diff(1) in 'context diff' mode
23 # # add new command that runs GNU diff(1) in 'context diff' mode
24 # cmd.cdiff = gdiff
24 # cmd.cdiff = gdiff
25 # opts.cdiff = -Nprc5
25 # opts.cdiff = -Nprc5
26
26
27 # # add new command called vdiff, runs kdiff3
27 # # add new command called vdiff, runs kdiff3
28 # cmd.vdiff = kdiff3
28 # cmd.vdiff = kdiff3
29
29
30 # # add new command called meld, runs meld (no need to name twice)
30 # # add new command called meld, runs meld (no need to name twice)
31 # cmd.meld =
31 # cmd.meld =
32
32
33 # # add new command called vimdiff, runs gvimdiff with DirDiff plugin
33 # # add new command called vimdiff, runs gvimdiff with DirDiff plugin
34 # #(see http://www.vim.org/scripts/script.php?script_id=102)
34 # #(see http://www.vim.org/scripts/script.php?script_id=102)
35 # # Non english user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
35 # # Non english user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
36 # # your .vimrc
36 # # your .vimrc
37 # cmd.vimdiff = gvim
37 # cmd.vimdiff = gvim
38 # opts.vimdiff = -f '+next' '+execute "DirDiff" argv(0) argv(1)'
38 # opts.vimdiff = -f '+next' '+execute "DirDiff" argv(0) argv(1)'
39 #
39 #
40 # Each custom diff commands can have two parts: a `cmd' and an `opts'
40 # Each custom diff commands can have two parts: a `cmd' and an `opts'
41 # part. The cmd.xxx option defines the name of an executable program
41 # part. The cmd.xxx option defines the name of an executable program
42 # that will be run, and opts.xxx defines a set of command-line options
42 # that will be run, and opts.xxx defines a set of command-line options
43 # which will be inserted to the command between the program name and
43 # which will be inserted to the command between the program name and
44 # the files/directories to diff (i.e. the cdiff example above).
44 # the files/directories to diff (i.e. the cdiff example above).
45 #
45 #
46 # You can use -I/-X and list of file or directory names like normal
46 # You can use -I/-X and list of file or directory names like normal
47 # "hg diff" command. The `extdiff' extension makes snapshots of only
47 # "hg diff" command. The `extdiff' extension makes snapshots of only
48 # needed files, so running the external diff program will actually be
48 # needed files, so running the external diff program will actually be
49 # pretty fast (at least faster than having to compare the entire tree).
49 # pretty fast (at least faster than having to compare the entire tree).
50
50
51 from mercurial.i18n import _
51 from mercurial.i18n import _
52 from mercurial.node import *
52 from mercurial.node import *
53 from mercurial import cmdutil, util
53 from mercurial import cmdutil, util
54 import os, shutil, tempfile
54 import os, shutil, tempfile
55
55
56
56
57 def snapshot_node(ui, repo, files, node, tmproot):
57 def snapshot_node(ui, repo, files, node, tmproot):
58 '''snapshot files as of some revision'''
58 '''snapshot files as of some revision'''
59 mf = repo.changectx(node).manifest()
59 mf = repo.changectx(node).manifest()
60 dirname = os.path.basename(repo.root)
60 dirname = os.path.basename(repo.root)
61 if dirname == "":
61 if dirname == "":
62 dirname = "root"
62 dirname = "root"
63 dirname = '%s.%s' % (dirname, short(node))
63 dirname = '%s.%s' % (dirname, short(node))
64 base = os.path.join(tmproot, dirname)
64 base = os.path.join(tmproot, dirname)
65 os.mkdir(base)
65 os.mkdir(base)
66 ui.note(_('making snapshot of %d files from rev %s\n') %
66 ui.note(_('making snapshot of %d files from rev %s\n') %
67 (len(files), short(node)))
67 (len(files), short(node)))
68 for fn in files:
68 for fn in files:
69 if not fn in mf:
69 if not fn in mf:
70 # skipping new file after a merge ?
70 # skipping new file after a merge ?
71 continue
71 continue
72 wfn = util.pconvert(fn)
72 wfn = util.pconvert(fn)
73 ui.note(' %s\n' % wfn)
73 ui.note(' %s\n' % wfn)
74 dest = os.path.join(base, wfn)
74 dest = os.path.join(base, wfn)
75 destdir = os.path.dirname(dest)
75 destdir = os.path.dirname(dest)
76 if not os.path.isdir(destdir):
76 if not os.path.isdir(destdir):
77 os.makedirs(destdir)
77 os.makedirs(destdir)
78 data = repo.wwritedata(wfn, repo.file(wfn).read(mf[wfn]))
78 data = repo.wwritedata(wfn, repo.file(wfn).read(mf[wfn]))
79 open(dest, 'wb').write(data)
79 open(dest, 'wb').write(data)
80 return dirname
80 return dirname
81
81
82
82
83 def snapshot_wdir(ui, repo, files, tmproot):
83 def snapshot_wdir(ui, repo, files, tmproot):
84 '''snapshot files from working directory.
84 '''snapshot files from working directory.
85 if not using snapshot, -I/-X does not work and recursive diff
85 if not using snapshot, -I/-X does not work and recursive diff
86 in tools like kdiff3 and meld displays too many files.'''
86 in tools like kdiff3 and meld displays too many files.'''
87 dirname = os.path.basename(repo.root)
87 dirname = os.path.basename(repo.root)
88 if dirname == "":
88 if dirname == "":
89 dirname = "root"
89 dirname = "root"
90 base = os.path.join(tmproot, dirname)
90 base = os.path.join(tmproot, dirname)
91 os.mkdir(base)
91 os.mkdir(base)
92 ui.note(_('making snapshot of %d files from working dir\n') %
92 ui.note(_('making snapshot of %d files from working dir\n') %
93 (len(files)))
93 (len(files)))
94 for fn in files:
94 for fn in files:
95 wfn = util.pconvert(fn)
95 wfn = util.pconvert(fn)
96 ui.note(' %s\n' % wfn)
96 ui.note(' %s\n' % wfn)
97 dest = os.path.join(base, wfn)
97 dest = os.path.join(base, wfn)
98 destdir = os.path.dirname(dest)
98 destdir = os.path.dirname(dest)
99 if not os.path.isdir(destdir):
99 if not os.path.isdir(destdir):
100 os.makedirs(destdir)
100 os.makedirs(destdir)
101 fp = open(dest, 'wb')
101 fp = open(dest, 'wb')
102 for chunk in util.filechunkiter(repo.wopener(wfn)):
102 for chunk in util.filechunkiter(repo.wopener(wfn)):
103 fp.write(chunk)
103 fp.write(chunk)
104 return dirname
104 return dirname
105
105
106
106
107 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
107 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
108 node1, node2 = cmdutil.revpair(repo, opts['rev'])
108 node1, node2 = cmdutil.revpair(repo, opts['rev'])
109 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
109 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
110 modified, added, removed, deleted, unknown = repo.status(
110 modified, added, removed, deleted, unknown = repo.status(
111 node1, node2, files, match=matchfn)[:5]
111 node1, node2, files, match=matchfn)[:5]
112 if not (modified or added or removed):
112 if not (modified or added or removed):
113 return 0
113 return 0
114
114
115 tmproot = tempfile.mkdtemp(prefix='extdiff.')
115 tmproot = tempfile.mkdtemp(prefix='extdiff.')
116 dir2root = ''
116 dir2root = ''
117 try:
117 try:
118 # Always make a copy of node1
118 # Always make a copy of node1
119 dir1 = snapshot_node(ui, repo, modified + removed, node1, tmproot)
119 dir1 = snapshot_node(ui, repo, modified + removed, node1, tmproot)
120 changes = len(modified) + len(removed) + len(added)
120 changes = len(modified) + len(removed) + len(added)
121
121
122 # If node2 in not the wc or there is >1 change, copy it
122 # If node2 in not the wc or there is >1 change, copy it
123 if node2:
123 if node2:
124 dir2 = snapshot_node(ui, repo, modified + added, node2, tmproot)
124 dir2 = snapshot_node(ui, repo, modified + added, node2, tmproot)
125 elif changes > 1:
125 elif changes > 1:
126 dir2 = snapshot_wdir(ui, repo, modified + added, tmproot)
126 dir2 = snapshot_wdir(ui, repo, modified + added, tmproot)
127 else:
127 else:
128 # This lets the diff tool open the changed file directly
128 # This lets the diff tool open the changed file directly
129 dir2 = ''
129 dir2 = ''
130 dir2root = repo.root
130 dir2root = repo.root
131
131
132 # If only one change, diff the files instead of the directories
132 # If only one change, diff the files instead of the directories
133 if changes == 1 :
133 if changes == 1 :
134 if len(modified):
134 if len(modified):
135 dir1 = os.path.join(dir1, util.localpath(modified[0]))
135 dir1 = os.path.join(dir1, util.localpath(modified[0]))
136 dir2 = os.path.join(dir2root, dir2, util.localpath(modified[0]))
136 dir2 = os.path.join(dir2root, dir2, util.localpath(modified[0]))
137 elif len(removed) :
137 elif len(removed) :
138 dir1 = os.path.join(dir1, util.localpath(removed[0]))
138 dir1 = os.path.join(dir1, util.localpath(removed[0]))
139 dir2 = os.devnull
139 dir2 = os.devnull
140 else:
140 else:
141 dir1 = os.devnull
141 dir1 = os.devnull
142 dir2 = os.path.join(dir2root, dir2, util.localpath(added[0]))
142 dir2 = os.path.join(dir2root, dir2, util.localpath(added[0]))
143
143
144 cmdline = ('%s %s %s %s' %
144 cmdline = ('%s %s %s %s' %
145 (util.shellquote(diffcmd), ' '.join(diffopts),
145 (util.shellquote(diffcmd), ' '.join(diffopts),
146 util.shellquote(dir1), util.shellquote(dir2)))
146 util.shellquote(dir1), util.shellquote(dir2)))
147 ui.debug('running %r in %s\n' % (cmdline, tmproot))
147 ui.debug('running %r in %s\n' % (cmdline, tmproot))
148 util.system(cmdline, cwd=tmproot)
148 util.system(cmdline, cwd=tmproot)
149 return 1
149 return 1
150 finally:
150 finally:
151 ui.note(_('cleaning up temp directory\n'))
151 ui.note(_('cleaning up temp directory\n'))
152 shutil.rmtree(tmproot)
152 shutil.rmtree(tmproot)
153
153
154 def extdiff(ui, repo, *pats, **opts):
154 def extdiff(ui, repo, *pats, **opts):
155 '''use external program to diff repository (or selected files)
155 '''use external program to diff repository (or selected files)
156
156
157 Show differences between revisions for the specified files, using
157 Show differences between revisions for the specified files, using
158 an external program. The default program used is diff, with
158 an external program. The default program used is diff, with
159 default options "-Npru".
159 default options "-Npru".
160
160
161 To select a different program, use the -p option. The program
161 To select a different program, use the -p option. The program
162 will be passed the names of two directories to compare. To pass
162 will be passed the names of two directories to compare. To pass
163 additional options to the program, use the -o option. These will
163 additional options to the program, use the -o option. These will
164 be passed before the names of the directories to compare.
164 be passed before the names of the directories to compare.
165
165
166 When two revision arguments are given, then changes are
166 When two revision arguments are given, then changes are
167 shown between those revisions. If only one revision is
167 shown between those revisions. If only one revision is
168 specified then that revision is compared to the working
168 specified then that revision is compared to the working
169 directory, and, when no revisions are specified, the
169 directory, and, when no revisions are specified, the
170 working directory files are compared to its parent.'''
170 working directory files are compared to its parent.'''
171 program = opts['program'] or 'diff'
171 program = opts['program'] or 'diff'
172 if opts['program']:
172 if opts['program']:
173 option = opts['option']
173 option = opts['option']
174 else:
174 else:
175 option = opts['option'] or ['-Npru']
175 option = opts['option'] or ['-Npru']
176 return dodiff(ui, repo, program, option, pats, opts)
176 return dodiff(ui, repo, program, option, pats, opts)
177
177
178 cmdtable = {
178 cmdtable = {
179 "extdiff":
179 "extdiff":
180 (extdiff,
180 (extdiff,
181 [('p', 'program', '', _('comparison program to run')),
181 [('p', 'program', '', _('comparison program to run')),
182 ('o', 'option', [], _('pass option to comparison program')),
182 ('o', 'option', [], _('pass option to comparison program')),
183 ('r', 'rev', [], _('revision')),
183 ('r', 'rev', [], _('revision')),
184 ('I', 'include', [], _('include names matching the given patterns')),
184 ('I', 'include', [], _('include names matching the given patterns')),
185 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
185 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
186 _('hg extdiff [OPT]... [FILE]...')),
186 _('hg extdiff [OPT]... [FILE]...')),
187 }
187 }
188
188
189 def uisetup(ui):
189 def uisetup(ui):
190 for cmd, path in ui.configitems('extdiff'):
190 for cmd, path in ui.configitems('extdiff'):
191 if not cmd.startswith('cmd.'): continue
191 if not cmd.startswith('cmd.'): continue
192 cmd = cmd[4:]
192 cmd = cmd[4:]
193 if not path: path = cmd
193 if not path: path = cmd
194 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
194 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
195 diffopts = diffopts and [diffopts] or []
195 diffopts = diffopts and [diffopts] or []
196 def save(cmd, path, diffopts):
196 def save(cmd, path, diffopts):
197 '''use closure to save diff command to use'''
197 '''use closure to save diff command to use'''
198 def mydiff(ui, repo, *pats, **opts):
198 def mydiff(ui, repo, *pats, **opts):
199 return dodiff(ui, repo, path, diffopts, pats, opts)
199 return dodiff(ui, repo, path, diffopts, pats, opts)
200 mydiff.__doc__ = '''use %(path)r to diff repository (or selected files)
200 mydiff.__doc__ = '''use %(path)r to diff repository (or selected files)
201
201
202 Show differences between revisions for the specified
202 Show differences between revisions for the specified
203 files, using the %(path)r program.
203 files, using the %(path)r program.
204
204
205 When two revision arguments are given, then changes are
205 When two revision arguments are given, then changes are
206 shown between those revisions. If only one revision is
206 shown between those revisions. If only one revision is
207 specified then that revision is compared to the working
207 specified then that revision is compared to the working
208 directory, and, when no revisions are specified, the
208 directory, and, when no revisions are specified, the
209 working directory files are compared to its parent.''' % {
209 working directory files are compared to its parent.''' % {
210 'path': path,
210 'path': path,
211 }
211 }
212 return mydiff
212 return mydiff
213 cmdtable[cmd] = (save(cmd, path, diffopts),
213 cmdtable[cmd] = (save(cmd, path, diffopts),
214 cmdtable['extdiff'][1][1:],
214 cmdtable['extdiff'][1][1:],
215 _('hg %s [OPTION]... [FILE]...') % cmd)
215 _('hg %s [OPTION]... [FILE]...') % cmd)
@@ -1,361 +1,361 b''
1 # Copyright (C) 2007 Brendan Cully <brendan@kublai.com>
1 # Copyright (C) 2007 Brendan Cully <brendan@kublai.com>
2 # Published under the GNU GPL
2 # Published under the GNU GPL
3
3
4 '''
4 '''
5 imerge - interactive merge
5 imerge - interactive merge
6 '''
6 '''
7
7
8 from mercurial.i18n import _
8 from mercurial.i18n import _
9 from mercurial.node import *
9 from mercurial.node import *
10 from mercurial import commands, cmdutil, fancyopts, hg, merge, util
10 from mercurial import commands, cmdutil, fancyopts, hg, merge, util
11 import os, tarfile
11 import os, tarfile
12
12
13 class InvalidStateFileException(Exception): pass
13 class InvalidStateFileException(Exception): pass
14
14
15 class ImergeStateFile(object):
15 class ImergeStateFile(object):
16 def __init__(self, im):
16 def __init__(self, im):
17 self.im = im
17 self.im = im
18
18
19 def save(self, dest):
19 def save(self, dest):
20 tf = tarfile.open(dest, 'w:gz')
20 tf = tarfile.open(dest, 'w:gz')
21
21
22 st = os.path.join(self.im.path, 'status')
22 st = os.path.join(self.im.path, 'status')
23 tf.add(st, os.path.join('.hg', 'imerge', 'status'))
23 tf.add(st, os.path.join('.hg', 'imerge', 'status'))
24
24
25 for f in self.im.resolved:
25 for f in self.im.resolved:
26 (fd, fo) = self.im.conflicts[f]
26 (fd, fo) = self.im.conflicts[f]
27 abssrc = self.im.repo.wjoin(fd)
27 abssrc = self.im.repo.wjoin(fd)
28 tf.add(abssrc, fd)
28 tf.add(abssrc, fd)
29
29
30 tf.close()
30 tf.close()
31
31
32 def load(self, source):
32 def load(self, source):
33 wlock = self.im.repo.wlock()
33 wlock = self.im.repo.wlock()
34 lock = self.im.repo.lock()
34 lock = self.im.repo.lock()
35
35
36 tf = tarfile.open(source, 'r')
36 tf = tarfile.open(source, 'r')
37 contents = tf.getnames()
37 contents = tf.getnames()
38 statusfile = os.path.join('.hg', 'imerge', 'status')
38 statusfile = os.path.join('.hg', 'imerge', 'status')
39 if statusfile not in contents:
39 if statusfile not in contents:
40 raise InvalidStateFileException('no status file')
40 raise InvalidStateFileException('no status file')
41
41
42 tf.extract(statusfile, self.im.repo.root)
42 tf.extract(statusfile, self.im.repo.root)
43 p1, p2 = self.im.load()
43 p1, p2 = self.im.load()
44 if self.im.repo.dirstate.parents()[0] != p1.node():
44 if self.im.repo.dirstate.parents()[0] != p1.node():
45 hg.clean(self.im.repo, p1.node())
45 hg.clean(self.im.repo, p1.node())
46 self.im.start(p2.node())
46 self.im.start(p2.node())
47 for tarinfo in tf:
47 for tarinfo in tf:
48 tf.extract(tarinfo, self.im.repo.root)
48 tf.extract(tarinfo, self.im.repo.root)
49 self.im.load()
49 self.im.load()
50
50
51 class Imerge(object):
51 class Imerge(object):
52 def __init__(self, ui, repo):
52 def __init__(self, ui, repo):
53 self.ui = ui
53 self.ui = ui
54 self.repo = repo
54 self.repo = repo
55
55
56 self.path = repo.join('imerge')
56 self.path = repo.join('imerge')
57 self.opener = util.opener(self.path)
57 self.opener = util.opener(self.path)
58
58
59 self.wctx = self.repo.workingctx()
59 self.wctx = self.repo.workingctx()
60 self.conflicts = {}
60 self.conflicts = {}
61 self.resolved = []
61 self.resolved = []
62
62
63 def merging(self):
63 def merging(self):
64 return len(self.wctx.parents()) > 1
64 return len(self.wctx.parents()) > 1
65
65
66 def load(self):
66 def load(self):
67 # status format. \0-delimited file, fields are
67 # status format. \0-delimited file, fields are
68 # p1, p2, conflict count, conflict filenames, resolved filenames
68 # p1, p2, conflict count, conflict filenames, resolved filenames
69 # conflict filenames are tuples of localname, remoteorig, remotenew
69 # conflict filenames are tuples of localname, remoteorig, remotenew
70
70
71 statusfile = self.opener('status')
71 statusfile = self.opener('status')
72
72
73 status = statusfile.read().split('\0')
73 status = statusfile.read().split('\0')
74 if len(status) < 3:
74 if len(status) < 3:
75 raise util.Abort('invalid imerge status file')
75 raise util.Abort('invalid imerge status file')
76
76
77 try:
77 try:
78 parents = [self.repo.changectx(n) for n in status[:2]]
78 parents = [self.repo.changectx(n) for n in status[:2]]
79 except LookupError:
79 except LookupError:
80 raise util.Abort('merge parent %s not in repository' % short(p))
80 raise util.Abort('merge parent %s not in repository' % short(p))
81
81
82 status = status[2:]
82 status = status[2:]
83 conflicts = int(status.pop(0)) * 3
83 conflicts = int(status.pop(0)) * 3
84 self.resolved = status[conflicts:]
84 self.resolved = status[conflicts:]
85 for i in xrange(0, conflicts, 3):
85 for i in xrange(0, conflicts, 3):
86 self.conflicts[status[i]] = (status[i+1], status[i+2])
86 self.conflicts[status[i]] = (status[i+1], status[i+2])
87
87
88 return parents
88 return parents
89
89
90 def save(self):
90 def save(self):
91 lock = self.repo.lock()
91 lock = self.repo.lock()
92
92
93 if not os.path.isdir(self.path):
93 if not os.path.isdir(self.path):
94 os.mkdir(self.path)
94 os.mkdir(self.path)
95 statusfile = self.opener('status', 'wb')
95 statusfile = self.opener('status', 'wb')
96
96
97 out = [hex(n.node()) for n in self.wctx.parents()]
97 out = [hex(n.node()) for n in self.wctx.parents()]
98 out.append(str(len(self.conflicts)))
98 out.append(str(len(self.conflicts)))
99 conflicts = self.conflicts.items()
99 conflicts = self.conflicts.items()
100 conflicts.sort()
100 conflicts.sort()
101 for fw, fd_fo in conflicts:
101 for fw, fd_fo in conflicts:
102 out.append(fw)
102 out.append(fw)
103 out.extend(fd_fo)
103 out.extend(fd_fo)
104 out.extend(self.resolved)
104 out.extend(self.resolved)
105
105
106 statusfile.write('\0'.join(out))
106 statusfile.write('\0'.join(out))
107
107
108 def remaining(self):
108 def remaining(self):
109 return [f for f in self.conflicts if f not in self.resolved]
109 return [f for f in self.conflicts if f not in self.resolved]
110
110
111 def filemerge(self, fn):
111 def filemerge(self, fn):
112 wlock = self.repo.wlock()
112 wlock = self.repo.wlock()
113
113
114 (fd, fo) = self.conflicts[fn]
114 (fd, fo) = self.conflicts[fn]
115 p2 = self.wctx.parents()[1]
115 p2 = self.wctx.parents()[1]
116 return merge.filemerge(self.repo, fn, fd, fo, self.wctx, p2)
116 return merge.filemerge(self.repo, fn, fd, fo, self.wctx, p2)
117
117
118 def start(self, rev=None):
118 def start(self, rev=None):
119 _filemerge = merge.filemerge
119 _filemerge = merge.filemerge
120 def filemerge(repo, fw, fd, fo, wctx, mctx):
120 def filemerge(repo, fw, fd, fo, wctx, mctx):
121 self.conflicts[fw] = (fd, fo)
121 self.conflicts[fw] = (fd, fo)
122
122
123 merge.filemerge = filemerge
123 merge.filemerge = filemerge
124 commands.merge(self.ui, self.repo, rev=rev)
124 commands.merge(self.ui, self.repo, rev=rev)
125 merge.filemerge = _filemerge
125 merge.filemerge = _filemerge
126
126
127 self.wctx = self.repo.workingctx()
127 self.wctx = self.repo.workingctx()
128 self.save()
128 self.save()
129
129
130 def resume(self):
130 def resume(self):
131 self.load()
131 self.load()
132
132
133 dp = self.repo.dirstate.parents()
133 dp = self.repo.dirstate.parents()
134 p1, p2 = self.wctx.parents()
134 p1, p2 = self.wctx.parents()
135 if p1.node() != dp[0] or p2.node() != dp[1]:
135 if p1.node() != dp[0] or p2.node() != dp[1]:
136 raise util.Abort('imerge state does not match working directory')
136 raise util.Abort('imerge state does not match working directory')
137
137
138 def next(self):
138 def next(self):
139 remaining = self.remaining()
139 remaining = self.remaining()
140 return remaining and remaining[0]
140 return remaining and remaining[0]
141
141
142 def resolve(self, files):
142 def resolve(self, files):
143 resolved = dict.fromkeys(self.resolved)
143 resolved = dict.fromkeys(self.resolved)
144 for fn in files:
144 for fn in files:
145 if fn not in self.conflicts:
145 if fn not in self.conflicts:
146 raise util.Abort('%s is not in the merge set' % fn)
146 raise util.Abort('%s is not in the merge set' % fn)
147 resolved[fn] = True
147 resolved[fn] = True
148 self.resolved = resolved.keys()
148 self.resolved = resolved.keys()
149 self.resolved.sort()
149 self.resolved.sort()
150 self.save()
150 self.save()
151 return 0
151 return 0
152
152
153 def unresolve(self, files):
153 def unresolve(self, files):
154 resolved = dict.fromkeys(self.resolved)
154 resolved = dict.fromkeys(self.resolved)
155 for fn in files:
155 for fn in files:
156 if fn not in resolved:
156 if fn not in resolved:
157 raise util.Abort('%s is not resolved' % fn)
157 raise util.Abort('%s is not resolved' % fn)
158 del resolved[fn]
158 del resolved[fn]
159 self.resolved = resolved.keys()
159 self.resolved = resolved.keys()
160 self.resolved.sort()
160 self.resolved.sort()
161 self.save()
161 self.save()
162 return 0
162 return 0
163
163
164 def pickle(self, dest):
164 def pickle(self, dest):
165 '''write current merge state to file to be resumed elsewhere'''
165 '''write current merge state to file to be resumed elsewhere'''
166 state = ImergeStateFile(self)
166 state = ImergeStateFile(self)
167 return state.save(dest)
167 return state.save(dest)
168
168
169 def unpickle(self, source):
169 def unpickle(self, source):
170 '''read merge state from file'''
170 '''read merge state from file'''
171 state = ImergeStateFile(self)
171 state = ImergeStateFile(self)
172 return state.load(source)
172 return state.load(source)
173
173
174 def load(im, source):
174 def load(im, source):
175 if im.merging():
175 if im.merging():
176 raise util.Abort('there is already a merge in progress '
176 raise util.Abort('there is already a merge in progress '
177 '(update -C <rev> to abort it)' )
177 '(update -C <rev> to abort it)' )
178 m, a, r, d = im.repo.status()[:4]
178 m, a, r, d = im.repo.status()[:4]
179 if m or a or r or d:
179 if m or a or r or d:
180 raise util.Abort('working directory has uncommitted changes')
180 raise util.Abort('working directory has uncommitted changes')
181
181
182 rc = im.unpickle(source)
182 rc = im.unpickle(source)
183 if not rc:
183 if not rc:
184 status(im)
184 status(im)
185 return rc
185 return rc
186
186
187 def merge_(im, filename=None):
187 def merge_(im, filename=None):
188 if not filename:
188 if not filename:
189 filename = im.next()
189 filename = im.next()
190 if not filename:
190 if not filename:
191 im.ui.write('all conflicts resolved\n')
191 im.ui.write('all conflicts resolved\n')
192 return 0
192 return 0
193
193
194 rc = im.filemerge(filename)
194 rc = im.filemerge(filename)
195 if not rc:
195 if not rc:
196 im.resolve([filename])
196 im.resolve([filename])
197 if not im.next():
197 if not im.next():
198 im.ui.write('all conflicts resolved\n')
198 im.ui.write('all conflicts resolved\n')
199 return 0
199 return 0
200 return rc
200 return rc
201
201
202 def next(im):
202 def next(im):
203 n = im.next()
203 n = im.next()
204 if n:
204 if n:
205 im.ui.write('%s\n' % n)
205 im.ui.write('%s\n' % n)
206 else:
206 else:
207 im.ui.write('all conflicts resolved\n')
207 im.ui.write('all conflicts resolved\n')
208 return 0
208 return 0
209
209
210 def resolve(im, *files):
210 def resolve(im, *files):
211 if not files:
211 if not files:
212 raise util.Abort('resolve requires at least one filename')
212 raise util.Abort('resolve requires at least one filename')
213 return im.resolve(files)
213 return im.resolve(files)
214
214
215 def save(im, dest):
215 def save(im, dest):
216 return im.pickle(dest)
216 return im.pickle(dest)
217
217
218 def status(im, **opts):
218 def status(im, **opts):
219 if not opts.get('resolved') and not opts.get('unresolved'):
219 if not opts.get('resolved') and not opts.get('unresolved'):
220 opts['resolved'] = True
220 opts['resolved'] = True
221 opts['unresolved'] = True
221 opts['unresolved'] = True
222
222
223 if im.ui.verbose:
223 if im.ui.verbose:
224 p1, p2 = [short(p.node()) for p in im.wctx.parents()]
224 p1, p2 = [short(p.node()) for p in im.wctx.parents()]
225 im.ui.note(_('merging %s and %s\n') % (p1, p2))
225 im.ui.note(_('merging %s and %s\n') % (p1, p2))
226
226
227 conflicts = im.conflicts.keys()
227 conflicts = im.conflicts.keys()
228 conflicts.sort()
228 conflicts.sort()
229 remaining = dict.fromkeys(im.remaining())
229 remaining = dict.fromkeys(im.remaining())
230 st = []
230 st = []
231 for fn in conflicts:
231 for fn in conflicts:
232 if opts.get('no_status'):
232 if opts.get('no_status'):
233 mode = ''
233 mode = ''
234 elif fn in remaining:
234 elif fn in remaining:
235 mode = 'U '
235 mode = 'U '
236 else:
236 else:
237 mode = 'R '
237 mode = 'R '
238 if ((opts.get('resolved') and fn not in remaining)
238 if ((opts.get('resolved') and fn not in remaining)
239 or (opts.get('unresolved') and fn in remaining)):
239 or (opts.get('unresolved') and fn in remaining)):
240 st.append((mode, fn))
240 st.append((mode, fn))
241 st.sort()
241 st.sort()
242 for (mode, fn) in st:
242 for (mode, fn) in st:
243 if im.ui.verbose:
243 if im.ui.verbose:
244 fo, fd = im.conflicts[fn]
244 fo, fd = im.conflicts[fn]
245 if fd != fn:
245 if fd != fn:
246 fn = '%s (%s)' % (fn, fd)
246 fn = '%s (%s)' % (fn, fd)
247 im.ui.write('%s%s\n' % (mode, fn))
247 im.ui.write('%s%s\n' % (mode, fn))
248 if opts.get('unresolved') and not remaining:
248 if opts.get('unresolved') and not remaining:
249 im.ui.write(_('all conflicts resolved\n'))
249 im.ui.write(_('all conflicts resolved\n'))
250
250
251 return 0
251 return 0
252
252
253 def unresolve(im, *files):
253 def unresolve(im, *files):
254 if not files:
254 if not files:
255 raise util.Abort('unresolve requires at least one filename')
255 raise util.Abort('unresolve requires at least one filename')
256 return im.unresolve(files)
256 return im.unresolve(files)
257
257
258 subcmdtable = {
258 subcmdtable = {
259 'load': (load, []),
259 'load': (load, []),
260 'merge': (merge_, []),
260 'merge': (merge_, []),
261 'next': (next, []),
261 'next': (next, []),
262 'resolve': (resolve, []),
262 'resolve': (resolve, []),
263 'save': (save, []),
263 'save': (save, []),
264 'status': (status,
264 'status': (status,
265 [('n', 'no-status', None, _('hide status prefix')),
265 [('n', 'no-status', None, _('hide status prefix')),
266 ('', 'resolved', None, _('only show resolved conflicts')),
266 ('', 'resolved', None, _('only show resolved conflicts')),
267 ('', 'unresolved', None, _('only show unresolved conflicts'))]),
267 ('', 'unresolved', None, _('only show unresolved conflicts'))]),
268 'unresolve': (unresolve, [])
268 'unresolve': (unresolve, [])
269 }
269 }
270
270
271 def dispatch(im, args, opts):
271 def dispatch(im, args, opts):
272 def complete(s, choices):
272 def complete(s, choices):
273 candidates = []
273 candidates = []
274 for choice in choices:
274 for choice in choices:
275 if choice.startswith(s):
275 if choice.startswith(s):
276 candidates.append(choice)
276 candidates.append(choice)
277 return candidates
277 return candidates
278
278
279 c, args = args[0], list(args[1:])
279 c, args = args[0], list(args[1:])
280 cmd = complete(c, subcmdtable.keys())
280 cmd = complete(c, subcmdtable.keys())
281 if not cmd:
281 if not cmd:
282 raise cmdutil.UnknownCommand('imerge ' + c)
282 raise cmdutil.UnknownCommand('imerge ' + c)
283 if len(cmd) > 1:
283 if len(cmd) > 1:
284 cmd.sort()
284 cmd.sort()
285 raise cmdutil.AmbiguousCommand('imerge ' + c, cmd)
285 raise cmdutil.AmbiguousCommand('imerge ' + c, cmd)
286 cmd = cmd[0]
286 cmd = cmd[0]
287
287
288 func, optlist = subcmdtable[cmd]
288 func, optlist = subcmdtable[cmd]
289 opts = {}
289 opts = {}
290 try:
290 try:
291 args = fancyopts.fancyopts(args, optlist, opts)
291 args = fancyopts.fancyopts(args, optlist, opts)
292 return func(im, *args, **opts)
292 return func(im, *args, **opts)
293 except fancyopts.getopt.GetoptError, inst:
293 except fancyopts.getopt.GetoptError, inst:
294 raise cmdutil.ParseError('imerge', '%s: %s' % (cmd, inst))
294 raise cmdutil.ParseError('imerge', '%s: %s' % (cmd, inst))
295 except TypeError:
295 except TypeError:
296 raise cmdutil.ParseError('imerge', _('%s: invalid arguments') % cmd)
296 raise cmdutil.ParseError('imerge', _('%s: invalid arguments') % cmd)
297
297
298 def imerge(ui, repo, *args, **opts):
298 def imerge(ui, repo, *args, **opts):
299 '''interactive merge
299 '''interactive merge
300
300
301 imerge lets you split a merge into pieces. When you start a merge
301 imerge lets you split a merge into pieces. When you start a merge
302 with imerge, the names of all files with conflicts are recorded.
302 with imerge, the names of all files with conflicts are recorded.
303 You can then merge any of these files, and if the merge is
303 You can then merge any of these files, and if the merge is
304 successful, they will be marked as resolved. When all files are
304 successful, they will be marked as resolved. When all files are
305 resolved, the merge is complete.
305 resolved, the merge is complete.
306
306
307 If no merge is in progress, hg imerge [rev] will merge the working
307 If no merge is in progress, hg imerge [rev] will merge the working
308 directory with rev (defaulting to the other head if the repository
308 directory with rev (defaulting to the other head if the repository
309 only has two heads). You may also resume a saved merge with
309 only has two heads). You may also resume a saved merge with
310 hg imerge load <file>.
310 hg imerge load <file>.
311
311
312 If a merge is in progress, hg imerge will default to merging the
312 If a merge is in progress, hg imerge will default to merging the
313 next unresolved file.
313 next unresolved file.
314
314
315 The following subcommands are available:
315 The following subcommands are available:
316
316
317 status:
317 status:
318 show the current state of the merge
318 show the current state of the merge
319 next:
319 next:
320 show the next unresolved file merge
320 show the next unresolved file merge
321 merge [<file>]:
321 merge [<file>]:
322 merge <file>. If the file merge is successful, the file will be
322 merge <file>. If the file merge is successful, the file will be
323 recorded as resolved. If no file is given, the next unresolved
323 recorded as resolved. If no file is given, the next unresolved
324 file will be merged.
324 file will be merged.
325 resolve <file>...:
325 resolve <file>...:
326 mark files as successfully merged
326 mark files as successfully merged
327 unresolve <file>...:
327 unresolve <file>...:
328 mark files as requiring merging.
328 mark files as requiring merging.
329 save <file>:
329 save <file>:
330 save the state of the merge to a file to be resumed elsewhere
330 save the state of the merge to a file to be resumed elsewhere
331 load <file>:
331 load <file>:
332 load the state of the merge from a file created by save
332 load the state of the merge from a file created by save
333 '''
333 '''
334
334
335 im = Imerge(ui, repo)
335 im = Imerge(ui, repo)
336
336
337 if im.merging():
337 if im.merging():
338 im.resume()
338 im.resume()
339 else:
339 else:
340 rev = opts.get('rev')
340 rev = opts.get('rev')
341 if rev and args:
341 if rev and args:
342 raise util.Abort('please specify just one revision')
342 raise util.Abort('please specify just one revision')
343
343
344 if len(args) == 2 and args[0] == 'load':
344 if len(args) == 2 and args[0] == 'load':
345 pass
345 pass
346 else:
346 else:
347 if args:
347 if args:
348 rev = args[0]
348 rev = args[0]
349 im.start(rev=rev)
349 im.start(rev=rev)
350 args = ['status']
350 args = ['status']
351
351
352 if not args:
352 if not args:
353 args = ['merge']
353 args = ['merge']
354
354
355 return dispatch(im, args, opts)
355 return dispatch(im, args, opts)
356
356
357 cmdtable = {
357 cmdtable = {
358 '^imerge':
358 '^imerge':
359 (imerge,
359 (imerge,
360 [('r', 'rev', '', _('revision to merge'))], 'hg imerge [command]')
360 [('r', 'rev', '', _('revision to merge'))], 'hg imerge [command]')
361 }
361 }
@@ -1,382 +1,382 b''
1 # record.py
1 # record.py
2 #
2 #
3 # Copyright 2007 Bryan O'Sullivan <bos@serpentine.com>
3 # Copyright 2007 Bryan O'Sullivan <bos@serpentine.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of
5 # This software may be used and distributed according to the terms of
6 # the GNU General Public License, incorporated herein by reference.
6 # the GNU General Public License, incorporated herein by reference.
7
7
8 '''interactive change selection during commit'''
8 '''interactive change selection during commit'''
9
9
10 from mercurial.i18n import _
10 from mercurial.i18n import _
11 from mercurial import cmdutil, commands, cmdutil, hg, mdiff, patch, revlog
11 from mercurial import cmdutil, commands, cmdutil, hg, mdiff, patch, revlog
12 from mercurial import util
12 from mercurial import util
13 import copy, cStringIO, errno, operator, os, re, shutil, tempfile
13 import copy, cStringIO, errno, operator, os, re, shutil, tempfile
14
14
15 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
15 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
16
16
17 def scanpatch(fp):
17 def scanpatch(fp):
18 lr = patch.linereader(fp)
18 lr = patch.linereader(fp)
19
19
20 def scanwhile(first, p):
20 def scanwhile(first, p):
21 lines = [first]
21 lines = [first]
22 while True:
22 while True:
23 line = lr.readline()
23 line = lr.readline()
24 if not line:
24 if not line:
25 break
25 break
26 if p(line):
26 if p(line):
27 lines.append(line)
27 lines.append(line)
28 else:
28 else:
29 lr.push(line)
29 lr.push(line)
30 break
30 break
31 return lines
31 return lines
32
32
33 while True:
33 while True:
34 line = lr.readline()
34 line = lr.readline()
35 if not line:
35 if not line:
36 break
36 break
37 if line.startswith('diff --git a/'):
37 if line.startswith('diff --git a/'):
38 def notheader(line):
38 def notheader(line):
39 s = line.split(None, 1)
39 s = line.split(None, 1)
40 return not s or s[0] not in ('---', 'diff')
40 return not s or s[0] not in ('---', 'diff')
41 header = scanwhile(line, notheader)
41 header = scanwhile(line, notheader)
42 fromfile = lr.readline()
42 fromfile = lr.readline()
43 if fromfile.startswith('---'):
43 if fromfile.startswith('---'):
44 tofile = lr.readline()
44 tofile = lr.readline()
45 header += [fromfile, tofile]
45 header += [fromfile, tofile]
46 else:
46 else:
47 lr.push(fromfile)
47 lr.push(fromfile)
48 yield 'file', header
48 yield 'file', header
49 elif line[0] == ' ':
49 elif line[0] == ' ':
50 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
50 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
51 elif line[0] in '-+':
51 elif line[0] in '-+':
52 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
52 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
53 else:
53 else:
54 m = lines_re.match(line)
54 m = lines_re.match(line)
55 if m:
55 if m:
56 yield 'range', m.groups()
56 yield 'range', m.groups()
57 else:
57 else:
58 raise patch.PatchError('unknown patch content: %r' % line)
58 raise patch.PatchError('unknown patch content: %r' % line)
59
59
60 class header(object):
60 class header(object):
61 diff_re = re.compile('diff --git a/(.*) b/(.*)$')
61 diff_re = re.compile('diff --git a/(.*) b/(.*)$')
62 allhunks_re = re.compile('(?:index|new file|deleted file) ')
62 allhunks_re = re.compile('(?:index|new file|deleted file) ')
63 pretty_re = re.compile('(?:new file|deleted file) ')
63 pretty_re = re.compile('(?:new file|deleted file) ')
64 special_re = re.compile('(?:index|new|deleted|copy|rename) ')
64 special_re = re.compile('(?:index|new|deleted|copy|rename) ')
65
65
66 def __init__(self, header):
66 def __init__(self, header):
67 self.header = header
67 self.header = header
68 self.hunks = []
68 self.hunks = []
69
69
70 def binary(self):
70 def binary(self):
71 for h in self.header:
71 for h in self.header:
72 if h.startswith('index '):
72 if h.startswith('index '):
73 return True
73 return True
74
74
75 def pretty(self, fp):
75 def pretty(self, fp):
76 for h in self.header:
76 for h in self.header:
77 if h.startswith('index '):
77 if h.startswith('index '):
78 fp.write(_('this modifies a binary file (all or nothing)\n'))
78 fp.write(_('this modifies a binary file (all or nothing)\n'))
79 break
79 break
80 if self.pretty_re.match(h):
80 if self.pretty_re.match(h):
81 fp.write(h)
81 fp.write(h)
82 if self.binary():
82 if self.binary():
83 fp.write(_('this is a binary file\n'))
83 fp.write(_('this is a binary file\n'))
84 break
84 break
85 if h.startswith('---'):
85 if h.startswith('---'):
86 fp.write(_('%d hunks, %d lines changed\n') %
86 fp.write(_('%d hunks, %d lines changed\n') %
87 (len(self.hunks),
87 (len(self.hunks),
88 sum([h.added + h.removed for h in self.hunks])))
88 sum([h.added + h.removed for h in self.hunks])))
89 break
89 break
90 fp.write(h)
90 fp.write(h)
91
91
92 def write(self, fp):
92 def write(self, fp):
93 fp.write(''.join(self.header))
93 fp.write(''.join(self.header))
94
94
95 def allhunks(self):
95 def allhunks(self):
96 for h in self.header:
96 for h in self.header:
97 if self.allhunks_re.match(h):
97 if self.allhunks_re.match(h):
98 return True
98 return True
99
99
100 def files(self):
100 def files(self):
101 fromfile, tofile = self.diff_re.match(self.header[0]).groups()
101 fromfile, tofile = self.diff_re.match(self.header[0]).groups()
102 if fromfile == tofile:
102 if fromfile == tofile:
103 return [fromfile]
103 return [fromfile]
104 return [fromfile, tofile]
104 return [fromfile, tofile]
105
105
106 def filename(self):
106 def filename(self):
107 return self.files()[-1]
107 return self.files()[-1]
108
108
109 def __repr__(self):
109 def __repr__(self):
110 return '<header %s>' % (' '.join(map(repr, self.files())))
110 return '<header %s>' % (' '.join(map(repr, self.files())))
111
111
112 def special(self):
112 def special(self):
113 for h in self.header:
113 for h in self.header:
114 if self.special_re.match(h):
114 if self.special_re.match(h):
115 return True
115 return True
116
116
117 def countchanges(hunk):
117 def countchanges(hunk):
118 add = len([h for h in hunk if h[0] == '+'])
118 add = len([h for h in hunk if h[0] == '+'])
119 rem = len([h for h in hunk if h[0] == '-'])
119 rem = len([h for h in hunk if h[0] == '-'])
120 return add, rem
120 return add, rem
121
121
122 class hunk(object):
122 class hunk(object):
123 maxcontext = 3
123 maxcontext = 3
124
124
125 def __init__(self, header, fromline, toline, proc, before, hunk, after):
125 def __init__(self, header, fromline, toline, proc, before, hunk, after):
126 def trimcontext(number, lines):
126 def trimcontext(number, lines):
127 delta = len(lines) - self.maxcontext
127 delta = len(lines) - self.maxcontext
128 if False and delta > 0:
128 if False and delta > 0:
129 return number + delta, lines[:self.maxcontext]
129 return number + delta, lines[:self.maxcontext]
130 return number, lines
130 return number, lines
131
131
132 self.header = header
132 self.header = header
133 self.fromline, self.before = trimcontext(fromline, before)
133 self.fromline, self.before = trimcontext(fromline, before)
134 self.toline, self.after = trimcontext(toline, after)
134 self.toline, self.after = trimcontext(toline, after)
135 self.proc = proc
135 self.proc = proc
136 self.hunk = hunk
136 self.hunk = hunk
137 self.added, self.removed = countchanges(self.hunk)
137 self.added, self.removed = countchanges(self.hunk)
138
138
139 def write(self, fp):
139 def write(self, fp):
140 delta = len(self.before) + len(self.after)
140 delta = len(self.before) + len(self.after)
141 fromlen = delta + self.removed
141 fromlen = delta + self.removed
142 tolen = delta + self.added
142 tolen = delta + self.added
143 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
143 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
144 (self.fromline, fromlen, self.toline, tolen,
144 (self.fromline, fromlen, self.toline, tolen,
145 self.proc and (' ' + self.proc)))
145 self.proc and (' ' + self.proc)))
146 fp.write(''.join(self.before + self.hunk + self.after))
146 fp.write(''.join(self.before + self.hunk + self.after))
147
147
148 pretty = write
148 pretty = write
149
149
150 def filename(self):
150 def filename(self):
151 return self.header.filename()
151 return self.header.filename()
152
152
153 def __repr__(self):
153 def __repr__(self):
154 return '<hunk %r@%d>' % (self.filename(), self.fromline)
154 return '<hunk %r@%d>' % (self.filename(), self.fromline)
155
155
156 def parsepatch(fp):
156 def parsepatch(fp):
157 class parser(object):
157 class parser(object):
158 def __init__(self):
158 def __init__(self):
159 self.fromline = 0
159 self.fromline = 0
160 self.toline = 0
160 self.toline = 0
161 self.proc = ''
161 self.proc = ''
162 self.header = None
162 self.header = None
163 self.context = []
163 self.context = []
164 self.before = []
164 self.before = []
165 self.hunk = []
165 self.hunk = []
166 self.stream = []
166 self.stream = []
167
167
168 def addrange(self, (fromstart, fromend, tostart, toend, proc)):
168 def addrange(self, (fromstart, fromend, tostart, toend, proc)):
169 self.fromline = int(fromstart)
169 self.fromline = int(fromstart)
170 self.toline = int(tostart)
170 self.toline = int(tostart)
171 self.proc = proc
171 self.proc = proc
172
172
173 def addcontext(self, context):
173 def addcontext(self, context):
174 if self.hunk:
174 if self.hunk:
175 h = hunk(self.header, self.fromline, self.toline, self.proc,
175 h = hunk(self.header, self.fromline, self.toline, self.proc,
176 self.before, self.hunk, context)
176 self.before, self.hunk, context)
177 self.header.hunks.append(h)
177 self.header.hunks.append(h)
178 self.stream.append(h)
178 self.stream.append(h)
179 self.fromline += len(self.before) + h.removed
179 self.fromline += len(self.before) + h.removed
180 self.toline += len(self.before) + h.added
180 self.toline += len(self.before) + h.added
181 self.before = []
181 self.before = []
182 self.hunk = []
182 self.hunk = []
183 self.proc = ''
183 self.proc = ''
184 self.context = context
184 self.context = context
185
185
186 def addhunk(self, hunk):
186 def addhunk(self, hunk):
187 if self.context:
187 if self.context:
188 self.before = self.context
188 self.before = self.context
189 self.context = []
189 self.context = []
190 self.hunk = data
190 self.hunk = data
191
191
192 def newfile(self, hdr):
192 def newfile(self, hdr):
193 self.addcontext([])
193 self.addcontext([])
194 h = header(hdr)
194 h = header(hdr)
195 self.stream.append(h)
195 self.stream.append(h)
196 self.header = h
196 self.header = h
197
197
198 def finished(self):
198 def finished(self):
199 self.addcontext([])
199 self.addcontext([])
200 return self.stream
200 return self.stream
201
201
202 transitions = {
202 transitions = {
203 'file': {'context': addcontext,
203 'file': {'context': addcontext,
204 'file': newfile,
204 'file': newfile,
205 'hunk': addhunk,
205 'hunk': addhunk,
206 'range': addrange},
206 'range': addrange},
207 'context': {'file': newfile,
207 'context': {'file': newfile,
208 'hunk': addhunk,
208 'hunk': addhunk,
209 'range': addrange},
209 'range': addrange},
210 'hunk': {'context': addcontext,
210 'hunk': {'context': addcontext,
211 'file': newfile,
211 'file': newfile,
212 'range': addrange},
212 'range': addrange},
213 'range': {'context': addcontext,
213 'range': {'context': addcontext,
214 'hunk': addhunk},
214 'hunk': addhunk},
215 }
215 }
216
216
217 p = parser()
217 p = parser()
218
218
219 state = 'context'
219 state = 'context'
220 for newstate, data in scanpatch(fp):
220 for newstate, data in scanpatch(fp):
221 try:
221 try:
222 p.transitions[state][newstate](p, data)
222 p.transitions[state][newstate](p, data)
223 except KeyError:
223 except KeyError:
224 raise patch.PatchError('unhandled transition: %s -> %s' %
224 raise patch.PatchError('unhandled transition: %s -> %s' %
225 (state, newstate))
225 (state, newstate))
226 state = newstate
226 state = newstate
227 return p.finished()
227 return p.finished()
228
228
229 def filterpatch(ui, chunks):
229 def filterpatch(ui, chunks):
230 chunks = list(chunks)
230 chunks = list(chunks)
231 chunks.reverse()
231 chunks.reverse()
232 seen = {}
232 seen = {}
233 def consumefile():
233 def consumefile():
234 consumed = []
234 consumed = []
235 while chunks:
235 while chunks:
236 if isinstance(chunks[-1], header):
236 if isinstance(chunks[-1], header):
237 break
237 break
238 else:
238 else:
239 consumed.append(chunks.pop())
239 consumed.append(chunks.pop())
240 return consumed
240 return consumed
241 resp = None
241 resp = None
242 applied = {}
242 applied = {}
243 while chunks:
243 while chunks:
244 chunk = chunks.pop()
244 chunk = chunks.pop()
245 if isinstance(chunk, header):
245 if isinstance(chunk, header):
246 fixoffset = 0
246 fixoffset = 0
247 hdr = ''.join(chunk.header)
247 hdr = ''.join(chunk.header)
248 if hdr in seen:
248 if hdr in seen:
249 consumefile()
249 consumefile()
250 continue
250 continue
251 seen[hdr] = True
251 seen[hdr] = True
252 if not resp:
252 if not resp:
253 chunk.pretty(ui)
253 chunk.pretty(ui)
254 r = resp or ui.prompt(_('record changes to %s? [y]es [n]o') %
254 r = resp or ui.prompt(_('record changes to %s? [y]es [n]o') %
255 _(' and ').join(map(repr, chunk.files())),
255 _(' and ').join(map(repr, chunk.files())),
256 '(?:|[yYnNqQaA])$') or 'y'
256 '(?:|[yYnNqQaA])$') or 'y'
257 if r in 'aA':
257 if r in 'aA':
258 r = 'y'
258 r = 'y'
259 resp = 'y'
259 resp = 'y'
260 if r in 'qQ':
260 if r in 'qQ':
261 raise util.Abort(_('user quit'))
261 raise util.Abort(_('user quit'))
262 if r in 'yY':
262 if r in 'yY':
263 applied[chunk.filename()] = [chunk]
263 applied[chunk.filename()] = [chunk]
264 if chunk.allhunks():
264 if chunk.allhunks():
265 applied[chunk.filename()] += consumefile()
265 applied[chunk.filename()] += consumefile()
266 else:
266 else:
267 consumefile()
267 consumefile()
268 else:
268 else:
269 if not resp:
269 if not resp:
270 chunk.pretty(ui)
270 chunk.pretty(ui)
271 r = resp or ui.prompt(_('record this change to %r? [y]es [n]o') %
271 r = resp or ui.prompt(_('record this change to %r? [y]es [n]o') %
272 chunk.filename(), '(?:|[yYnNqQaA])$') or 'y'
272 chunk.filename(), '(?:|[yYnNqQaA])$') or 'y'
273 if r in 'aA':
273 if r in 'aA':
274 r = 'y'
274 r = 'y'
275 resp = 'y'
275 resp = 'y'
276 if r in 'qQ':
276 if r in 'qQ':
277 raise util.Abort(_('user quit'))
277 raise util.Abort(_('user quit'))
278 if r in 'yY':
278 if r in 'yY':
279 if fixoffset:
279 if fixoffset:
280 chunk = copy.copy(chunk)
280 chunk = copy.copy(chunk)
281 chunk.toline += fixoffset
281 chunk.toline += fixoffset
282 applied[chunk.filename()].append(chunk)
282 applied[chunk.filename()].append(chunk)
283 else:
283 else:
284 fixoffset += chunk.removed - chunk.added
284 fixoffset += chunk.removed - chunk.added
285 return reduce(operator.add, [h for h in applied.itervalues()
285 return reduce(operator.add, [h for h in applied.itervalues()
286 if h[0].special() or len(h) > 1], [])
286 if h[0].special() or len(h) > 1], [])
287
287
288 def record(ui, repo, *pats, **opts):
288 def record(ui, repo, *pats, **opts):
289 '''interactively select changes to commit'''
289 '''interactively select changes to commit'''
290
290
291 if not ui.interactive:
291 if not ui.interactive:
292 raise util.Abort(_('running non-interactively, use commit instead'))
292 raise util.Abort(_('running non-interactively, use commit instead'))
293
293
294 def recordfunc(ui, repo, files, message, match, opts):
294 def recordfunc(ui, repo, files, message, match, opts):
295 if files:
295 if files:
296 changes = None
296 changes = None
297 else:
297 else:
298 changes = repo.status(files=files, match=match)[:5]
298 changes = repo.status(files=files, match=match)[:5]
299 modified, added, removed = changes[:3]
299 modified, added, removed = changes[:3]
300 files = modified + added + removed
300 files = modified + added + removed
301 diffopts = mdiff.diffopts(git=True, nodates=True)
301 diffopts = mdiff.diffopts(git=True, nodates=True)
302 fp = cStringIO.StringIO()
302 fp = cStringIO.StringIO()
303 patch.diff(repo, repo.dirstate.parents()[0], files=files,
303 patch.diff(repo, repo.dirstate.parents()[0], files=files,
304 match=match, changes=changes, opts=diffopts, fp=fp)
304 match=match, changes=changes, opts=diffopts, fp=fp)
305 fp.seek(0)
305 fp.seek(0)
306
306
307 chunks = filterpatch(ui, parsepatch(fp))
307 chunks = filterpatch(ui, parsepatch(fp))
308 del fp
308 del fp
309
309
310 contenders = {}
310 contenders = {}
311 for h in chunks:
311 for h in chunks:
312 try: contenders.update(dict.fromkeys(h.files()))
312 try: contenders.update(dict.fromkeys(h.files()))
313 except AttributeError: pass
313 except AttributeError: pass
314
314
315 newfiles = [f for f in files if f in contenders]
315 newfiles = [f for f in files if f in contenders]
316
316
317 if not newfiles:
317 if not newfiles:
318 ui.status(_('no changes to record\n'))
318 ui.status(_('no changes to record\n'))
319 return 0
319 return 0
320
320
321 if changes is None:
321 if changes is None:
322 changes = repo.status(files=newfiles, match=match)[:5]
322 changes = repo.status(files=newfiles, match=match)[:5]
323 modified = dict.fromkeys(changes[0])
323 modified = dict.fromkeys(changes[0])
324
324
325 backups = {}
325 backups = {}
326 backupdir = repo.join('record-backups')
326 backupdir = repo.join('record-backups')
327 try:
327 try:
328 os.mkdir(backupdir)
328 os.mkdir(backupdir)
329 except OSError, err:
329 except OSError, err:
330 if err.errno != errno.EEXIST:
330 if err.errno != errno.EEXIST:
331 raise
331 raise
332 try:
332 try:
333 for f in newfiles:
333 for f in newfiles:
334 if f not in modified:
334 if f not in modified:
335 continue
335 continue
336 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
336 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
337 dir=backupdir)
337 dir=backupdir)
338 os.close(fd)
338 os.close(fd)
339 ui.debug('backup %r as %r\n' % (f, tmpname))
339 ui.debug('backup %r as %r\n' % (f, tmpname))
340 util.copyfile(repo.wjoin(f), tmpname)
340 util.copyfile(repo.wjoin(f), tmpname)
341 backups[f] = tmpname
341 backups[f] = tmpname
342
342
343 fp = cStringIO.StringIO()
343 fp = cStringIO.StringIO()
344 for c in chunks:
344 for c in chunks:
345 if c.filename() in backups:
345 if c.filename() in backups:
346 c.write(fp)
346 c.write(fp)
347 dopatch = fp.tell()
347 dopatch = fp.tell()
348 fp.seek(0)
348 fp.seek(0)
349
349
350 if backups:
350 if backups:
351 hg.revert(repo, repo.dirstate.parents()[0], backups.has_key)
351 hg.revert(repo, repo.dirstate.parents()[0], backups.has_key)
352
352
353 if dopatch:
353 if dopatch:
354 ui.debug('applying patch\n')
354 ui.debug('applying patch\n')
355 ui.debug(fp.getvalue())
355 ui.debug(fp.getvalue())
356 patch.internalpatch(fp, ui, 1, repo.root)
356 patch.internalpatch(fp, ui, 1, repo.root)
357 del fp
357 del fp
358
358
359 repo.commit(newfiles, message, opts['user'], opts['date'], match,
359 repo.commit(newfiles, message, opts['user'], opts['date'], match,
360 force_editor=opts.get('force_editor'))
360 force_editor=opts.get('force_editor'))
361 return 0
361 return 0
362 finally:
362 finally:
363 try:
363 try:
364 for realname, tmpname in backups.iteritems():
364 for realname, tmpname in backups.iteritems():
365 ui.debug('restoring %r to %r\n' % (tmpname, realname))
365 ui.debug('restoring %r to %r\n' % (tmpname, realname))
366 util.copyfile(tmpname, repo.wjoin(realname))
366 util.copyfile(tmpname, repo.wjoin(realname))
367 os.unlink(tmpname)
367 os.unlink(tmpname)
368 os.rmdir(backupdir)
368 os.rmdir(backupdir)
369 except OSError:
369 except OSError:
370 pass
370 pass
371 return cmdutil.commit(ui, repo, recordfunc, pats, opts)
371 return cmdutil.commit(ui, repo, recordfunc, pats, opts)
372
372
373 cmdtable = {
373 cmdtable = {
374 "record":
374 "record":
375 (record,
375 (record,
376 [('A', 'addremove', None,
376 [('A', 'addremove', None,
377 _('mark new/missing files as added/removed before committing')),
377 _('mark new/missing files as added/removed before committing')),
378 ('d', 'date', '', _('record datecode as commit date')),
378 ('d', 'date', '', _('record datecode as commit date')),
379 ('u', 'user', '', _('record user as commiter')),
379 ('u', 'user', '', _('record user as commiter')),
380 ] + commands.walkopts + commands.commitopts,
380 ] + commands.walkopts + commands.commitopts,
381 _('hg record [OPTION]... [FILE]...')),
381 _('hg record [OPTION]... [FILE]...')),
382 }
382 }
@@ -1,1330 +1,1330 b''
1 # patch.py - patch file parsing routines
1 # patch.py - patch file parsing routines
2 #
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 from i18n import _
9 from i18n import _
10 from node import *
10 from node import *
11 import base85, cmdutil, mdiff, util, context, revlog, diffhelpers
11 import base85, cmdutil, mdiff, util, context, revlog, diffhelpers
12 import cStringIO, email.Parser, os, popen2, re, sha
12 import cStringIO, email.Parser, os, popen2, re, sha
13 import sys, tempfile, zlib
13 import sys, tempfile, zlib
14
14
15 class PatchError(Exception):
15 class PatchError(Exception):
16 pass
16 pass
17
17
18 class NoHunks(PatchError):
18 class NoHunks(PatchError):
19 pass
19 pass
20
20
21 # helper functions
21 # helper functions
22
22
23 def copyfile(src, dst, basedir=None):
23 def copyfile(src, dst, basedir=None):
24 if not basedir:
24 if not basedir:
25 basedir = os.getcwd()
25 basedir = os.getcwd()
26
26
27 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
27 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
28 if os.path.exists(absdst):
28 if os.path.exists(absdst):
29 raise util.Abort(_("cannot create %s: destination already exists") %
29 raise util.Abort(_("cannot create %s: destination already exists") %
30 dst)
30 dst)
31
31
32 targetdir = os.path.dirname(absdst)
32 targetdir = os.path.dirname(absdst)
33 if not os.path.isdir(targetdir):
33 if not os.path.isdir(targetdir):
34 os.makedirs(targetdir)
34 os.makedirs(targetdir)
35
35
36 util.copyfile(abssrc, absdst)
36 util.copyfile(abssrc, absdst)
37
37
38 # public functions
38 # public functions
39
39
40 def extract(ui, fileobj):
40 def extract(ui, fileobj):
41 '''extract patch from data read from fileobj.
41 '''extract patch from data read from fileobj.
42
42
43 patch can be a normal patch or contained in an email message.
43 patch can be a normal patch or contained in an email message.
44
44
45 return tuple (filename, message, user, date, node, p1, p2).
45 return tuple (filename, message, user, date, node, p1, p2).
46 Any item in the returned tuple can be None. If filename is None,
46 Any item in the returned tuple can be None. If filename is None,
47 fileobj did not contain a patch. Caller must unlink filename when done.'''
47 fileobj did not contain a patch. Caller must unlink filename when done.'''
48
48
49 # attempt to detect the start of a patch
49 # attempt to detect the start of a patch
50 # (this heuristic is borrowed from quilt)
50 # (this heuristic is borrowed from quilt)
51 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
51 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
52 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
52 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
53 '(---|\*\*\*)[ \t])', re.MULTILINE)
53 '(---|\*\*\*)[ \t])', re.MULTILINE)
54
54
55 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
55 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
56 tmpfp = os.fdopen(fd, 'w')
56 tmpfp = os.fdopen(fd, 'w')
57 try:
57 try:
58 msg = email.Parser.Parser().parse(fileobj)
58 msg = email.Parser.Parser().parse(fileobj)
59
59
60 subject = msg['Subject']
60 subject = msg['Subject']
61 user = msg['From']
61 user = msg['From']
62 # should try to parse msg['Date']
62 # should try to parse msg['Date']
63 date = None
63 date = None
64 nodeid = None
64 nodeid = None
65 branch = None
65 branch = None
66 parents = []
66 parents = []
67
67
68 if subject:
68 if subject:
69 if subject.startswith('[PATCH'):
69 if subject.startswith('[PATCH'):
70 pend = subject.find(']')
70 pend = subject.find(']')
71 if pend >= 0:
71 if pend >= 0:
72 subject = subject[pend+1:].lstrip()
72 subject = subject[pend+1:].lstrip()
73 subject = subject.replace('\n\t', ' ')
73 subject = subject.replace('\n\t', ' ')
74 ui.debug('Subject: %s\n' % subject)
74 ui.debug('Subject: %s\n' % subject)
75 if user:
75 if user:
76 ui.debug('From: %s\n' % user)
76 ui.debug('From: %s\n' % user)
77 diffs_seen = 0
77 diffs_seen = 0
78 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
78 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
79 message = ''
79 message = ''
80 for part in msg.walk():
80 for part in msg.walk():
81 content_type = part.get_content_type()
81 content_type = part.get_content_type()
82 ui.debug('Content-Type: %s\n' % content_type)
82 ui.debug('Content-Type: %s\n' % content_type)
83 if content_type not in ok_types:
83 if content_type not in ok_types:
84 continue
84 continue
85 payload = part.get_payload(decode=True)
85 payload = part.get_payload(decode=True)
86 m = diffre.search(payload)
86 m = diffre.search(payload)
87 if m:
87 if m:
88 hgpatch = False
88 hgpatch = False
89 ignoretext = False
89 ignoretext = False
90
90
91 ui.debug(_('found patch at byte %d\n') % m.start(0))
91 ui.debug(_('found patch at byte %d\n') % m.start(0))
92 diffs_seen += 1
92 diffs_seen += 1
93 cfp = cStringIO.StringIO()
93 cfp = cStringIO.StringIO()
94 for line in payload[:m.start(0)].splitlines():
94 for line in payload[:m.start(0)].splitlines():
95 if line.startswith('# HG changeset patch'):
95 if line.startswith('# HG changeset patch'):
96 ui.debug(_('patch generated by hg export\n'))
96 ui.debug(_('patch generated by hg export\n'))
97 hgpatch = True
97 hgpatch = True
98 # drop earlier commit message content
98 # drop earlier commit message content
99 cfp.seek(0)
99 cfp.seek(0)
100 cfp.truncate()
100 cfp.truncate()
101 subject = None
101 subject = None
102 elif hgpatch:
102 elif hgpatch:
103 if line.startswith('# User '):
103 if line.startswith('# User '):
104 user = line[7:]
104 user = line[7:]
105 ui.debug('From: %s\n' % user)
105 ui.debug('From: %s\n' % user)
106 elif line.startswith("# Date "):
106 elif line.startswith("# Date "):
107 date = line[7:]
107 date = line[7:]
108 elif line.startswith("# Branch "):
108 elif line.startswith("# Branch "):
109 branch = line[9:]
109 branch = line[9:]
110 elif line.startswith("# Node ID "):
110 elif line.startswith("# Node ID "):
111 nodeid = line[10:]
111 nodeid = line[10:]
112 elif line.startswith("# Parent "):
112 elif line.startswith("# Parent "):
113 parents.append(line[10:])
113 parents.append(line[10:])
114 elif line == '---' and 'git-send-email' in msg['X-Mailer']:
114 elif line == '---' and 'git-send-email' in msg['X-Mailer']:
115 ignoretext = True
115 ignoretext = True
116 if not line.startswith('# ') and not ignoretext:
116 if not line.startswith('# ') and not ignoretext:
117 cfp.write(line)
117 cfp.write(line)
118 cfp.write('\n')
118 cfp.write('\n')
119 message = cfp.getvalue()
119 message = cfp.getvalue()
120 if tmpfp:
120 if tmpfp:
121 tmpfp.write(payload)
121 tmpfp.write(payload)
122 if not payload.endswith('\n'):
122 if not payload.endswith('\n'):
123 tmpfp.write('\n')
123 tmpfp.write('\n')
124 elif not diffs_seen and message and content_type == 'text/plain':
124 elif not diffs_seen and message and content_type == 'text/plain':
125 message += '\n' + payload
125 message += '\n' + payload
126 except:
126 except:
127 tmpfp.close()
127 tmpfp.close()
128 os.unlink(tmpname)
128 os.unlink(tmpname)
129 raise
129 raise
130
130
131 if subject and not message.startswith(subject):
131 if subject and not message.startswith(subject):
132 message = '%s\n%s' % (subject, message)
132 message = '%s\n%s' % (subject, message)
133 tmpfp.close()
133 tmpfp.close()
134 if not diffs_seen:
134 if not diffs_seen:
135 os.unlink(tmpname)
135 os.unlink(tmpname)
136 return None, message, user, date, branch, None, None, None
136 return None, message, user, date, branch, None, None, None
137 p1 = parents and parents.pop(0) or None
137 p1 = parents and parents.pop(0) or None
138 p2 = parents and parents.pop(0) or None
138 p2 = parents and parents.pop(0) or None
139 return tmpname, message, user, date, branch, nodeid, p1, p2
139 return tmpname, message, user, date, branch, nodeid, p1, p2
140
140
141 GP_PATCH = 1 << 0 # we have to run patch
141 GP_PATCH = 1 << 0 # we have to run patch
142 GP_FILTER = 1 << 1 # there's some copy/rename operation
142 GP_FILTER = 1 << 1 # there's some copy/rename operation
143 GP_BINARY = 1 << 2 # there's a binary patch
143 GP_BINARY = 1 << 2 # there's a binary patch
144
144
145 def readgitpatch(fp, firstline=None):
145 def readgitpatch(fp, firstline=None):
146 """extract git-style metadata about patches from <patchname>"""
146 """extract git-style metadata about patches from <patchname>"""
147 class gitpatch:
147 class gitpatch:
148 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
148 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
149 def __init__(self, path):
149 def __init__(self, path):
150 self.path = path
150 self.path = path
151 self.oldpath = None
151 self.oldpath = None
152 self.mode = None
152 self.mode = None
153 self.op = 'MODIFY'
153 self.op = 'MODIFY'
154 self.copymod = False
154 self.copymod = False
155 self.lineno = 0
155 self.lineno = 0
156 self.binary = False
156 self.binary = False
157
157
158 def reader(fp, firstline):
158 def reader(fp, firstline):
159 if firstline is not None:
159 if firstline is not None:
160 yield firstline
160 yield firstline
161 for line in fp:
161 for line in fp:
162 yield line
162 yield line
163
163
164 # Filter patch for git information
164 # Filter patch for git information
165 gitre = re.compile('diff --git a/(.*) b/(.*)')
165 gitre = re.compile('diff --git a/(.*) b/(.*)')
166 gp = None
166 gp = None
167 gitpatches = []
167 gitpatches = []
168 # Can have a git patch with only metadata, causing patch to complain
168 # Can have a git patch with only metadata, causing patch to complain
169 dopatch = 0
169 dopatch = 0
170
170
171 lineno = 0
171 lineno = 0
172 for line in reader(fp, firstline):
172 for line in reader(fp, firstline):
173 lineno += 1
173 lineno += 1
174 if line.startswith('diff --git'):
174 if line.startswith('diff --git'):
175 m = gitre.match(line)
175 m = gitre.match(line)
176 if m:
176 if m:
177 if gp:
177 if gp:
178 gitpatches.append(gp)
178 gitpatches.append(gp)
179 src, dst = m.group(1, 2)
179 src, dst = m.group(1, 2)
180 gp = gitpatch(dst)
180 gp = gitpatch(dst)
181 gp.lineno = lineno
181 gp.lineno = lineno
182 elif gp:
182 elif gp:
183 if line.startswith('--- '):
183 if line.startswith('--- '):
184 if gp.op in ('COPY', 'RENAME'):
184 if gp.op in ('COPY', 'RENAME'):
185 gp.copymod = True
185 gp.copymod = True
186 dopatch |= GP_FILTER
186 dopatch |= GP_FILTER
187 gitpatches.append(gp)
187 gitpatches.append(gp)
188 gp = None
188 gp = None
189 dopatch |= GP_PATCH
189 dopatch |= GP_PATCH
190 continue
190 continue
191 if line.startswith('rename from '):
191 if line.startswith('rename from '):
192 gp.op = 'RENAME'
192 gp.op = 'RENAME'
193 gp.oldpath = line[12:].rstrip()
193 gp.oldpath = line[12:].rstrip()
194 elif line.startswith('rename to '):
194 elif line.startswith('rename to '):
195 gp.path = line[10:].rstrip()
195 gp.path = line[10:].rstrip()
196 elif line.startswith('copy from '):
196 elif line.startswith('copy from '):
197 gp.op = 'COPY'
197 gp.op = 'COPY'
198 gp.oldpath = line[10:].rstrip()
198 gp.oldpath = line[10:].rstrip()
199 elif line.startswith('copy to '):
199 elif line.startswith('copy to '):
200 gp.path = line[8:].rstrip()
200 gp.path = line[8:].rstrip()
201 elif line.startswith('deleted file'):
201 elif line.startswith('deleted file'):
202 gp.op = 'DELETE'
202 gp.op = 'DELETE'
203 elif line.startswith('new file mode '):
203 elif line.startswith('new file mode '):
204 gp.op = 'ADD'
204 gp.op = 'ADD'
205 gp.mode = int(line.rstrip()[-6:], 8)
205 gp.mode = int(line.rstrip()[-6:], 8)
206 elif line.startswith('new mode '):
206 elif line.startswith('new mode '):
207 gp.mode = int(line.rstrip()[-6:], 8)
207 gp.mode = int(line.rstrip()[-6:], 8)
208 elif line.startswith('GIT binary patch'):
208 elif line.startswith('GIT binary patch'):
209 dopatch |= GP_BINARY
209 dopatch |= GP_BINARY
210 gp.binary = True
210 gp.binary = True
211 if gp:
211 if gp:
212 gitpatches.append(gp)
212 gitpatches.append(gp)
213
213
214 if not gitpatches:
214 if not gitpatches:
215 dopatch = GP_PATCH
215 dopatch = GP_PATCH
216
216
217 return (dopatch, gitpatches)
217 return (dopatch, gitpatches)
218
218
219 def patch(patchname, ui, strip=1, cwd=None, files={}):
219 def patch(patchname, ui, strip=1, cwd=None, files={}):
220 """apply <patchname> to the working directory.
220 """apply <patchname> to the working directory.
221 returns whether patch was applied with fuzz factor."""
221 returns whether patch was applied with fuzz factor."""
222 patcher = ui.config('ui', 'patch')
222 patcher = ui.config('ui', 'patch')
223 args = []
223 args = []
224 try:
224 try:
225 if patcher:
225 if patcher:
226 return externalpatch(patcher, args, patchname, ui, strip, cwd,
226 return externalpatch(patcher, args, patchname, ui, strip, cwd,
227 files)
227 files)
228 else:
228 else:
229 try:
229 try:
230 return internalpatch(patchname, ui, strip, cwd, files)
230 return internalpatch(patchname, ui, strip, cwd, files)
231 except NoHunks:
231 except NoHunks:
232 patcher = util.find_exe('gpatch') or util.find_exe('patch')
232 patcher = util.find_exe('gpatch') or util.find_exe('patch')
233 ui.debug('no valid hunks found; trying with %r instead\n' %
233 ui.debug('no valid hunks found; trying with %r instead\n' %
234 patcher)
234 patcher)
235 if util.needbinarypatch():
235 if util.needbinarypatch():
236 args.append('--binary')
236 args.append('--binary')
237 return externalpatch(patcher, args, patchname, ui, strip, cwd,
237 return externalpatch(patcher, args, patchname, ui, strip, cwd,
238 files)
238 files)
239 except PatchError, err:
239 except PatchError, err:
240 s = str(err)
240 s = str(err)
241 if s:
241 if s:
242 raise util.Abort(s)
242 raise util.Abort(s)
243 else:
243 else:
244 raise util.Abort(_('patch failed to apply'))
244 raise util.Abort(_('patch failed to apply'))
245
245
246 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
246 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
247 """use <patcher> to apply <patchname> to the working directory.
247 """use <patcher> to apply <patchname> to the working directory.
248 returns whether patch was applied with fuzz factor."""
248 returns whether patch was applied with fuzz factor."""
249
249
250 fuzz = False
250 fuzz = False
251 if cwd:
251 if cwd:
252 args.append('-d %s' % util.shellquote(cwd))
252 args.append('-d %s' % util.shellquote(cwd))
253 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
253 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
254 util.shellquote(patchname)))
254 util.shellquote(patchname)))
255
255
256 for line in fp:
256 for line in fp:
257 line = line.rstrip()
257 line = line.rstrip()
258 ui.note(line + '\n')
258 ui.note(line + '\n')
259 if line.startswith('patching file '):
259 if line.startswith('patching file '):
260 pf = util.parse_patch_output(line)
260 pf = util.parse_patch_output(line)
261 printed_file = False
261 printed_file = False
262 files.setdefault(pf, (None, None))
262 files.setdefault(pf, (None, None))
263 elif line.find('with fuzz') >= 0:
263 elif line.find('with fuzz') >= 0:
264 fuzz = True
264 fuzz = True
265 if not printed_file:
265 if not printed_file:
266 ui.warn(pf + '\n')
266 ui.warn(pf + '\n')
267 printed_file = True
267 printed_file = True
268 ui.warn(line + '\n')
268 ui.warn(line + '\n')
269 elif line.find('saving rejects to file') >= 0:
269 elif line.find('saving rejects to file') >= 0:
270 ui.warn(line + '\n')
270 ui.warn(line + '\n')
271 elif line.find('FAILED') >= 0:
271 elif line.find('FAILED') >= 0:
272 if not printed_file:
272 if not printed_file:
273 ui.warn(pf + '\n')
273 ui.warn(pf + '\n')
274 printed_file = True
274 printed_file = True
275 ui.warn(line + '\n')
275 ui.warn(line + '\n')
276 code = fp.close()
276 code = fp.close()
277 if code:
277 if code:
278 raise PatchError(_("patch command failed: %s") %
278 raise PatchError(_("patch command failed: %s") %
279 util.explain_exit(code)[0])
279 util.explain_exit(code)[0])
280 return fuzz
280 return fuzz
281
281
282 def internalpatch(patchobj, ui, strip, cwd, files={}):
282 def internalpatch(patchobj, ui, strip, cwd, files={}):
283 """use builtin patch to apply <patchobj> to the working directory.
283 """use builtin patch to apply <patchobj> to the working directory.
284 returns whether patch was applied with fuzz factor."""
284 returns whether patch was applied with fuzz factor."""
285 try:
285 try:
286 fp = file(patchobj, 'rb')
286 fp = file(patchobj, 'rb')
287 except TypeError:
287 except TypeError:
288 fp = patchobj
288 fp = patchobj
289 if cwd:
289 if cwd:
290 curdir = os.getcwd()
290 curdir = os.getcwd()
291 os.chdir(cwd)
291 os.chdir(cwd)
292 try:
292 try:
293 ret = applydiff(ui, fp, files, strip=strip)
293 ret = applydiff(ui, fp, files, strip=strip)
294 finally:
294 finally:
295 if cwd:
295 if cwd:
296 os.chdir(curdir)
296 os.chdir(curdir)
297 if ret < 0:
297 if ret < 0:
298 raise PatchError
298 raise PatchError
299 return ret > 0
299 return ret > 0
300
300
301 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
301 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
302 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
302 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
303 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
303 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
304
304
305 class patchfile:
305 class patchfile:
306 def __init__(self, ui, fname):
306 def __init__(self, ui, fname):
307 self.fname = fname
307 self.fname = fname
308 self.ui = ui
308 self.ui = ui
309 try:
309 try:
310 fp = file(fname, 'rb')
310 fp = file(fname, 'rb')
311 self.lines = fp.readlines()
311 self.lines = fp.readlines()
312 self.exists = True
312 self.exists = True
313 except IOError:
313 except IOError:
314 dirname = os.path.dirname(fname)
314 dirname = os.path.dirname(fname)
315 if dirname and not os.path.isdir(dirname):
315 if dirname and not os.path.isdir(dirname):
316 dirs = dirname.split(os.path.sep)
316 dirs = dirname.split(os.path.sep)
317 d = ""
317 d = ""
318 for x in dirs:
318 for x in dirs:
319 d = os.path.join(d, x)
319 d = os.path.join(d, x)
320 if not os.path.isdir(d):
320 if not os.path.isdir(d):
321 os.mkdir(d)
321 os.mkdir(d)
322 self.lines = []
322 self.lines = []
323 self.exists = False
323 self.exists = False
324
324
325 self.hash = {}
325 self.hash = {}
326 self.dirty = 0
326 self.dirty = 0
327 self.offset = 0
327 self.offset = 0
328 self.rej = []
328 self.rej = []
329 self.fileprinted = False
329 self.fileprinted = False
330 self.printfile(False)
330 self.printfile(False)
331 self.hunks = 0
331 self.hunks = 0
332
332
333 def printfile(self, warn):
333 def printfile(self, warn):
334 if self.fileprinted:
334 if self.fileprinted:
335 return
335 return
336 if warn or self.ui.verbose:
336 if warn or self.ui.verbose:
337 self.fileprinted = True
337 self.fileprinted = True
338 s = _("patching file %s\n") % self.fname
338 s = _("patching file %s\n") % self.fname
339 if warn:
339 if warn:
340 self.ui.warn(s)
340 self.ui.warn(s)
341 else:
341 else:
342 self.ui.note(s)
342 self.ui.note(s)
343
343
344
344
345 def findlines(self, l, linenum):
345 def findlines(self, l, linenum):
346 # looks through the hash and finds candidate lines. The
346 # looks through the hash and finds candidate lines. The
347 # result is a list of line numbers sorted based on distance
347 # result is a list of line numbers sorted based on distance
348 # from linenum
348 # from linenum
349 def sorter(a, b):
349 def sorter(a, b):
350 vala = abs(a - linenum)
350 vala = abs(a - linenum)
351 valb = abs(b - linenum)
351 valb = abs(b - linenum)
352 return cmp(vala, valb)
352 return cmp(vala, valb)
353
353
354 try:
354 try:
355 cand = self.hash[l]
355 cand = self.hash[l]
356 except:
356 except:
357 return []
357 return []
358
358
359 if len(cand) > 1:
359 if len(cand) > 1:
360 # resort our list of potentials forward then back.
360 # resort our list of potentials forward then back.
361 cand.sort(cmp=sorter)
361 cand.sort(cmp=sorter)
362 return cand
362 return cand
363
363
364 def hashlines(self):
364 def hashlines(self):
365 self.hash = {}
365 self.hash = {}
366 for x in xrange(len(self.lines)):
366 for x in xrange(len(self.lines)):
367 s = self.lines[x]
367 s = self.lines[x]
368 self.hash.setdefault(s, []).append(x)
368 self.hash.setdefault(s, []).append(x)
369
369
370 def write_rej(self):
370 def write_rej(self):
371 # our rejects are a little different from patch(1). This always
371 # our rejects are a little different from patch(1). This always
372 # creates rejects in the same form as the original patch. A file
372 # creates rejects in the same form as the original patch. A file
373 # header is inserted so that you can run the reject through patch again
373 # header is inserted so that you can run the reject through patch again
374 # without having to type the filename.
374 # without having to type the filename.
375
375
376 if not self.rej:
376 if not self.rej:
377 return
377 return
378 if self.hunks != 1:
378 if self.hunks != 1:
379 hunkstr = "s"
379 hunkstr = "s"
380 else:
380 else:
381 hunkstr = ""
381 hunkstr = ""
382
382
383 fname = self.fname + ".rej"
383 fname = self.fname + ".rej"
384 self.ui.warn(
384 self.ui.warn(
385 _("%d out of %d hunk%s FAILED -- saving rejects to file %s\n") %
385 _("%d out of %d hunk%s FAILED -- saving rejects to file %s\n") %
386 (len(self.rej), self.hunks, hunkstr, fname))
386 (len(self.rej), self.hunks, hunkstr, fname))
387 try: os.unlink(fname)
387 try: os.unlink(fname)
388 except:
388 except:
389 pass
389 pass
390 fp = file(fname, 'wb')
390 fp = file(fname, 'wb')
391 base = os.path.basename(self.fname)
391 base = os.path.basename(self.fname)
392 fp.write("--- %s\n+++ %s\n" % (base, base))
392 fp.write("--- %s\n+++ %s\n" % (base, base))
393 for x in self.rej:
393 for x in self.rej:
394 for l in x.hunk:
394 for l in x.hunk:
395 fp.write(l)
395 fp.write(l)
396 if l[-1] != '\n':
396 if l[-1] != '\n':
397 fp.write("\n\ No newline at end of file\n")
397 fp.write("\n\ No newline at end of file\n")
398
398
399 def write(self, dest=None):
399 def write(self, dest=None):
400 if self.dirty:
400 if self.dirty:
401 if not dest:
401 if not dest:
402 dest = self.fname
402 dest = self.fname
403 st = None
403 st = None
404 try:
404 try:
405 st = os.lstat(dest)
405 st = os.lstat(dest)
406 if st.st_nlink > 1:
406 if st.st_nlink > 1:
407 os.unlink(dest)
407 os.unlink(dest)
408 except: pass
408 except: pass
409 fp = file(dest, 'wb')
409 fp = file(dest, 'wb')
410 if st:
410 if st:
411 os.chmod(dest, st.st_mode)
411 os.chmod(dest, st.st_mode)
412 fp.writelines(self.lines)
412 fp.writelines(self.lines)
413 fp.close()
413 fp.close()
414
414
415 def close(self):
415 def close(self):
416 self.write()
416 self.write()
417 self.write_rej()
417 self.write_rej()
418
418
419 def apply(self, h, reverse):
419 def apply(self, h, reverse):
420 if not h.complete():
420 if not h.complete():
421 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
421 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
422 (h.number, h.desc, len(h.a), h.lena, len(h.b),
422 (h.number, h.desc, len(h.a), h.lena, len(h.b),
423 h.lenb))
423 h.lenb))
424
424
425 self.hunks += 1
425 self.hunks += 1
426 if reverse:
426 if reverse:
427 h.reverse()
427 h.reverse()
428
428
429 if self.exists and h.createfile():
429 if self.exists and h.createfile():
430 self.ui.warn(_("file %s already exists\n") % self.fname)
430 self.ui.warn(_("file %s already exists\n") % self.fname)
431 self.rej.append(h)
431 self.rej.append(h)
432 return -1
432 return -1
433
433
434 if isinstance(h, binhunk):
434 if isinstance(h, binhunk):
435 if h.rmfile():
435 if h.rmfile():
436 os.unlink(self.fname)
436 os.unlink(self.fname)
437 else:
437 else:
438 self.lines[:] = h.new()
438 self.lines[:] = h.new()
439 self.offset += len(h.new())
439 self.offset += len(h.new())
440 self.dirty = 1
440 self.dirty = 1
441 return 0
441 return 0
442
442
443 # fast case first, no offsets, no fuzz
443 # fast case first, no offsets, no fuzz
444 old = h.old()
444 old = h.old()
445 # patch starts counting at 1 unless we are adding the file
445 # patch starts counting at 1 unless we are adding the file
446 if h.starta == 0:
446 if h.starta == 0:
447 start = 0
447 start = 0
448 else:
448 else:
449 start = h.starta + self.offset - 1
449 start = h.starta + self.offset - 1
450 orig_start = start
450 orig_start = start
451 if diffhelpers.testhunk(old, self.lines, start) == 0:
451 if diffhelpers.testhunk(old, self.lines, start) == 0:
452 if h.rmfile():
452 if h.rmfile():
453 os.unlink(self.fname)
453 os.unlink(self.fname)
454 else:
454 else:
455 self.lines[start : start + h.lena] = h.new()
455 self.lines[start : start + h.lena] = h.new()
456 self.offset += h.lenb - h.lena
456 self.offset += h.lenb - h.lena
457 self.dirty = 1
457 self.dirty = 1
458 return 0
458 return 0
459
459
460 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
460 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
461 self.hashlines()
461 self.hashlines()
462 if h.hunk[-1][0] != ' ':
462 if h.hunk[-1][0] != ' ':
463 # if the hunk tried to put something at the bottom of the file
463 # if the hunk tried to put something at the bottom of the file
464 # override the start line and use eof here
464 # override the start line and use eof here
465 search_start = len(self.lines)
465 search_start = len(self.lines)
466 else:
466 else:
467 search_start = orig_start
467 search_start = orig_start
468
468
469 for fuzzlen in xrange(3):
469 for fuzzlen in xrange(3):
470 for toponly in [ True, False ]:
470 for toponly in [ True, False ]:
471 old = h.old(fuzzlen, toponly)
471 old = h.old(fuzzlen, toponly)
472
472
473 cand = self.findlines(old[0][1:], search_start)
473 cand = self.findlines(old[0][1:], search_start)
474 for l in cand:
474 for l in cand:
475 if diffhelpers.testhunk(old, self.lines, l) == 0:
475 if diffhelpers.testhunk(old, self.lines, l) == 0:
476 newlines = h.new(fuzzlen, toponly)
476 newlines = h.new(fuzzlen, toponly)
477 self.lines[l : l + len(old)] = newlines
477 self.lines[l : l + len(old)] = newlines
478 self.offset += len(newlines) - len(old)
478 self.offset += len(newlines) - len(old)
479 self.dirty = 1
479 self.dirty = 1
480 if fuzzlen:
480 if fuzzlen:
481 fuzzstr = "with fuzz %d " % fuzzlen
481 fuzzstr = "with fuzz %d " % fuzzlen
482 f = self.ui.warn
482 f = self.ui.warn
483 self.printfile(True)
483 self.printfile(True)
484 else:
484 else:
485 fuzzstr = ""
485 fuzzstr = ""
486 f = self.ui.note
486 f = self.ui.note
487 offset = l - orig_start - fuzzlen
487 offset = l - orig_start - fuzzlen
488 if offset == 1:
488 if offset == 1:
489 linestr = "line"
489 linestr = "line"
490 else:
490 else:
491 linestr = "lines"
491 linestr = "lines"
492 f(_("Hunk #%d succeeded at %d %s(offset %d %s).\n") %
492 f(_("Hunk #%d succeeded at %d %s(offset %d %s).\n") %
493 (h.number, l+1, fuzzstr, offset, linestr))
493 (h.number, l+1, fuzzstr, offset, linestr))
494 return fuzzlen
494 return fuzzlen
495 self.printfile(True)
495 self.printfile(True)
496 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
496 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
497 self.rej.append(h)
497 self.rej.append(h)
498 return -1
498 return -1
499
499
500 class hunk:
500 class hunk:
501 def __init__(self, desc, num, lr, context):
501 def __init__(self, desc, num, lr, context):
502 self.number = num
502 self.number = num
503 self.desc = desc
503 self.desc = desc
504 self.hunk = [ desc ]
504 self.hunk = [ desc ]
505 self.a = []
505 self.a = []
506 self.b = []
506 self.b = []
507 if context:
507 if context:
508 self.read_context_hunk(lr)
508 self.read_context_hunk(lr)
509 else:
509 else:
510 self.read_unified_hunk(lr)
510 self.read_unified_hunk(lr)
511
511
512 def read_unified_hunk(self, lr):
512 def read_unified_hunk(self, lr):
513 m = unidesc.match(self.desc)
513 m = unidesc.match(self.desc)
514 if not m:
514 if not m:
515 raise PatchError(_("bad hunk #%d") % self.number)
515 raise PatchError(_("bad hunk #%d") % self.number)
516 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
516 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
517 if self.lena == None:
517 if self.lena == None:
518 self.lena = 1
518 self.lena = 1
519 else:
519 else:
520 self.lena = int(self.lena)
520 self.lena = int(self.lena)
521 if self.lenb == None:
521 if self.lenb == None:
522 self.lenb = 1
522 self.lenb = 1
523 else:
523 else:
524 self.lenb = int(self.lenb)
524 self.lenb = int(self.lenb)
525 self.starta = int(self.starta)
525 self.starta = int(self.starta)
526 self.startb = int(self.startb)
526 self.startb = int(self.startb)
527 diffhelpers.addlines(lr.fp, self.hunk, self.lena, self.lenb, self.a, self.b)
527 diffhelpers.addlines(lr.fp, self.hunk, self.lena, self.lenb, self.a, self.b)
528 # if we hit eof before finishing out the hunk, the last line will
528 # if we hit eof before finishing out the hunk, the last line will
529 # be zero length. Lets try to fix it up.
529 # be zero length. Lets try to fix it up.
530 while len(self.hunk[-1]) == 0:
530 while len(self.hunk[-1]) == 0:
531 del self.hunk[-1]
531 del self.hunk[-1]
532 del self.a[-1]
532 del self.a[-1]
533 del self.b[-1]
533 del self.b[-1]
534 self.lena -= 1
534 self.lena -= 1
535 self.lenb -= 1
535 self.lenb -= 1
536
536
537 def read_context_hunk(self, lr):
537 def read_context_hunk(self, lr):
538 self.desc = lr.readline()
538 self.desc = lr.readline()
539 m = contextdesc.match(self.desc)
539 m = contextdesc.match(self.desc)
540 if not m:
540 if not m:
541 raise PatchError(_("bad hunk #%d") % self.number)
541 raise PatchError(_("bad hunk #%d") % self.number)
542 foo, self.starta, foo2, aend, foo3 = m.groups()
542 foo, self.starta, foo2, aend, foo3 = m.groups()
543 self.starta = int(self.starta)
543 self.starta = int(self.starta)
544 if aend == None:
544 if aend == None:
545 aend = self.starta
545 aend = self.starta
546 self.lena = int(aend) - self.starta
546 self.lena = int(aend) - self.starta
547 if self.starta:
547 if self.starta:
548 self.lena += 1
548 self.lena += 1
549 for x in xrange(self.lena):
549 for x in xrange(self.lena):
550 l = lr.readline()
550 l = lr.readline()
551 if l.startswith('---'):
551 if l.startswith('---'):
552 lr.push(l)
552 lr.push(l)
553 break
553 break
554 s = l[2:]
554 s = l[2:]
555 if l.startswith('- ') or l.startswith('! '):
555 if l.startswith('- ') or l.startswith('! '):
556 u = '-' + s
556 u = '-' + s
557 elif l.startswith(' '):
557 elif l.startswith(' '):
558 u = ' ' + s
558 u = ' ' + s
559 else:
559 else:
560 raise PatchError(_("bad hunk #%d old text line %d") %
560 raise PatchError(_("bad hunk #%d old text line %d") %
561 (self.number, x))
561 (self.number, x))
562 self.a.append(u)
562 self.a.append(u)
563 self.hunk.append(u)
563 self.hunk.append(u)
564
564
565 l = lr.readline()
565 l = lr.readline()
566 if l.startswith('\ '):
566 if l.startswith('\ '):
567 s = self.a[-1][:-1]
567 s = self.a[-1][:-1]
568 self.a[-1] = s
568 self.a[-1] = s
569 self.hunk[-1] = s
569 self.hunk[-1] = s
570 l = lr.readline()
570 l = lr.readline()
571 m = contextdesc.match(l)
571 m = contextdesc.match(l)
572 if not m:
572 if not m:
573 raise PatchError(_("bad hunk #%d") % self.number)
573 raise PatchError(_("bad hunk #%d") % self.number)
574 foo, self.startb, foo2, bend, foo3 = m.groups()
574 foo, self.startb, foo2, bend, foo3 = m.groups()
575 self.startb = int(self.startb)
575 self.startb = int(self.startb)
576 if bend == None:
576 if bend == None:
577 bend = self.startb
577 bend = self.startb
578 self.lenb = int(bend) - self.startb
578 self.lenb = int(bend) - self.startb
579 if self.startb:
579 if self.startb:
580 self.lenb += 1
580 self.lenb += 1
581 hunki = 1
581 hunki = 1
582 for x in xrange(self.lenb):
582 for x in xrange(self.lenb):
583 l = lr.readline()
583 l = lr.readline()
584 if l.startswith('\ '):
584 if l.startswith('\ '):
585 s = self.b[-1][:-1]
585 s = self.b[-1][:-1]
586 self.b[-1] = s
586 self.b[-1] = s
587 self.hunk[hunki-1] = s
587 self.hunk[hunki-1] = s
588 continue
588 continue
589 if not l:
589 if not l:
590 lr.push(l)
590 lr.push(l)
591 break
591 break
592 s = l[2:]
592 s = l[2:]
593 if l.startswith('+ ') or l.startswith('! '):
593 if l.startswith('+ ') or l.startswith('! '):
594 u = '+' + s
594 u = '+' + s
595 elif l.startswith(' '):
595 elif l.startswith(' '):
596 u = ' ' + s
596 u = ' ' + s
597 elif len(self.b) == 0:
597 elif len(self.b) == 0:
598 # this can happen when the hunk does not add any lines
598 # this can happen when the hunk does not add any lines
599 lr.push(l)
599 lr.push(l)
600 break
600 break
601 else:
601 else:
602 raise PatchError(_("bad hunk #%d old text line %d") %
602 raise PatchError(_("bad hunk #%d old text line %d") %
603 (self.number, x))
603 (self.number, x))
604 self.b.append(s)
604 self.b.append(s)
605 while True:
605 while True:
606 if hunki >= len(self.hunk):
606 if hunki >= len(self.hunk):
607 h = ""
607 h = ""
608 else:
608 else:
609 h = self.hunk[hunki]
609 h = self.hunk[hunki]
610 hunki += 1
610 hunki += 1
611 if h == u:
611 if h == u:
612 break
612 break
613 elif h.startswith('-'):
613 elif h.startswith('-'):
614 continue
614 continue
615 else:
615 else:
616 self.hunk.insert(hunki-1, u)
616 self.hunk.insert(hunki-1, u)
617 break
617 break
618
618
619 if not self.a:
619 if not self.a:
620 # this happens when lines were only added to the hunk
620 # this happens when lines were only added to the hunk
621 for x in self.hunk:
621 for x in self.hunk:
622 if x.startswith('-') or x.startswith(' '):
622 if x.startswith('-') or x.startswith(' '):
623 self.a.append(x)
623 self.a.append(x)
624 if not self.b:
624 if not self.b:
625 # this happens when lines were only deleted from the hunk
625 # this happens when lines were only deleted from the hunk
626 for x in self.hunk:
626 for x in self.hunk:
627 if x.startswith('+') or x.startswith(' '):
627 if x.startswith('+') or x.startswith(' '):
628 self.b.append(x[1:])
628 self.b.append(x[1:])
629 # @@ -start,len +start,len @@
629 # @@ -start,len +start,len @@
630 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
630 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
631 self.startb, self.lenb)
631 self.startb, self.lenb)
632 self.hunk[0] = self.desc
632 self.hunk[0] = self.desc
633
633
634 def reverse(self):
634 def reverse(self):
635 origlena = self.lena
635 origlena = self.lena
636 origstarta = self.starta
636 origstarta = self.starta
637 self.lena = self.lenb
637 self.lena = self.lenb
638 self.starta = self.startb
638 self.starta = self.startb
639 self.lenb = origlena
639 self.lenb = origlena
640 self.startb = origstarta
640 self.startb = origstarta
641 self.a = []
641 self.a = []
642 self.b = []
642 self.b = []
643 # self.hunk[0] is the @@ description
643 # self.hunk[0] is the @@ description
644 for x in xrange(1, len(self.hunk)):
644 for x in xrange(1, len(self.hunk)):
645 o = self.hunk[x]
645 o = self.hunk[x]
646 if o.startswith('-'):
646 if o.startswith('-'):
647 n = '+' + o[1:]
647 n = '+' + o[1:]
648 self.b.append(o[1:])
648 self.b.append(o[1:])
649 elif o.startswith('+'):
649 elif o.startswith('+'):
650 n = '-' + o[1:]
650 n = '-' + o[1:]
651 self.a.append(n)
651 self.a.append(n)
652 else:
652 else:
653 n = o
653 n = o
654 self.b.append(o[1:])
654 self.b.append(o[1:])
655 self.a.append(o)
655 self.a.append(o)
656 self.hunk[x] = o
656 self.hunk[x] = o
657
657
658 def fix_newline(self):
658 def fix_newline(self):
659 diffhelpers.fix_newline(self.hunk, self.a, self.b)
659 diffhelpers.fix_newline(self.hunk, self.a, self.b)
660
660
661 def complete(self):
661 def complete(self):
662 return len(self.a) == self.lena and len(self.b) == self.lenb
662 return len(self.a) == self.lena and len(self.b) == self.lenb
663
663
664 def createfile(self):
664 def createfile(self):
665 return self.starta == 0 and self.lena == 0
665 return self.starta == 0 and self.lena == 0
666
666
667 def rmfile(self):
667 def rmfile(self):
668 return self.startb == 0 and self.lenb == 0
668 return self.startb == 0 and self.lenb == 0
669
669
670 def fuzzit(self, l, fuzz, toponly):
670 def fuzzit(self, l, fuzz, toponly):
671 # this removes context lines from the top and bottom of list 'l'. It
671 # this removes context lines from the top and bottom of list 'l'. It
672 # checks the hunk to make sure only context lines are removed, and then
672 # checks the hunk to make sure only context lines are removed, and then
673 # returns a new shortened list of lines.
673 # returns a new shortened list of lines.
674 fuzz = min(fuzz, len(l)-1)
674 fuzz = min(fuzz, len(l)-1)
675 if fuzz:
675 if fuzz:
676 top = 0
676 top = 0
677 bot = 0
677 bot = 0
678 hlen = len(self.hunk)
678 hlen = len(self.hunk)
679 for x in xrange(hlen-1):
679 for x in xrange(hlen-1):
680 # the hunk starts with the @@ line, so use x+1
680 # the hunk starts with the @@ line, so use x+1
681 if self.hunk[x+1][0] == ' ':
681 if self.hunk[x+1][0] == ' ':
682 top += 1
682 top += 1
683 else:
683 else:
684 break
684 break
685 if not toponly:
685 if not toponly:
686 for x in xrange(hlen-1):
686 for x in xrange(hlen-1):
687 if self.hunk[hlen-bot-1][0] == ' ':
687 if self.hunk[hlen-bot-1][0] == ' ':
688 bot += 1
688 bot += 1
689 else:
689 else:
690 break
690 break
691
691
692 # top and bot now count context in the hunk
692 # top and bot now count context in the hunk
693 # adjust them if either one is short
693 # adjust them if either one is short
694 context = max(top, bot, 3)
694 context = max(top, bot, 3)
695 if bot < context:
695 if bot < context:
696 bot = max(0, fuzz - (context - bot))
696 bot = max(0, fuzz - (context - bot))
697 else:
697 else:
698 bot = min(fuzz, bot)
698 bot = min(fuzz, bot)
699 if top < context:
699 if top < context:
700 top = max(0, fuzz - (context - top))
700 top = max(0, fuzz - (context - top))
701 else:
701 else:
702 top = min(fuzz, top)
702 top = min(fuzz, top)
703
703
704 return l[top:len(l)-bot]
704 return l[top:len(l)-bot]
705 return l
705 return l
706
706
707 def old(self, fuzz=0, toponly=False):
707 def old(self, fuzz=0, toponly=False):
708 return self.fuzzit(self.a, fuzz, toponly)
708 return self.fuzzit(self.a, fuzz, toponly)
709
709
710 def newctrl(self):
710 def newctrl(self):
711 res = []
711 res = []
712 for x in self.hunk:
712 for x in self.hunk:
713 c = x[0]
713 c = x[0]
714 if c == ' ' or c == '+':
714 if c == ' ' or c == '+':
715 res.append(x)
715 res.append(x)
716 return res
716 return res
717
717
718 def new(self, fuzz=0, toponly=False):
718 def new(self, fuzz=0, toponly=False):
719 return self.fuzzit(self.b, fuzz, toponly)
719 return self.fuzzit(self.b, fuzz, toponly)
720
720
721 class binhunk:
721 class binhunk:
722 'A binary patch file. Only understands literals so far.'
722 'A binary patch file. Only understands literals so far.'
723 def __init__(self, gitpatch):
723 def __init__(self, gitpatch):
724 self.gitpatch = gitpatch
724 self.gitpatch = gitpatch
725 self.text = None
725 self.text = None
726 self.hunk = ['GIT binary patch\n']
726 self.hunk = ['GIT binary patch\n']
727
727
728 def createfile(self):
728 def createfile(self):
729 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
729 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
730
730
731 def rmfile(self):
731 def rmfile(self):
732 return self.gitpatch.op == 'DELETE'
732 return self.gitpatch.op == 'DELETE'
733
733
734 def complete(self):
734 def complete(self):
735 return self.text is not None
735 return self.text is not None
736
736
737 def new(self):
737 def new(self):
738 return [self.text]
738 return [self.text]
739
739
740 def extract(self, fp):
740 def extract(self, fp):
741 line = fp.readline()
741 line = fp.readline()
742 self.hunk.append(line)
742 self.hunk.append(line)
743 while line and not line.startswith('literal '):
743 while line and not line.startswith('literal '):
744 line = fp.readline()
744 line = fp.readline()
745 self.hunk.append(line)
745 self.hunk.append(line)
746 if not line:
746 if not line:
747 raise PatchError(_('could not extract binary patch'))
747 raise PatchError(_('could not extract binary patch'))
748 size = int(line[8:].rstrip())
748 size = int(line[8:].rstrip())
749 dec = []
749 dec = []
750 line = fp.readline()
750 line = fp.readline()
751 self.hunk.append(line)
751 self.hunk.append(line)
752 while len(line) > 1:
752 while len(line) > 1:
753 l = line[0]
753 l = line[0]
754 if l <= 'Z' and l >= 'A':
754 if l <= 'Z' and l >= 'A':
755 l = ord(l) - ord('A') + 1
755 l = ord(l) - ord('A') + 1
756 else:
756 else:
757 l = ord(l) - ord('a') + 27
757 l = ord(l) - ord('a') + 27
758 dec.append(base85.b85decode(line[1:-1])[:l])
758 dec.append(base85.b85decode(line[1:-1])[:l])
759 line = fp.readline()
759 line = fp.readline()
760 self.hunk.append(line)
760 self.hunk.append(line)
761 text = zlib.decompress(''.join(dec))
761 text = zlib.decompress(''.join(dec))
762 if len(text) != size:
762 if len(text) != size:
763 raise PatchError(_('binary patch is %d bytes, not %d') %
763 raise PatchError(_('binary patch is %d bytes, not %d') %
764 len(text), size)
764 len(text), size)
765 self.text = text
765 self.text = text
766
766
767 def parsefilename(str):
767 def parsefilename(str):
768 # --- filename \t|space stuff
768 # --- filename \t|space stuff
769 s = str[4:]
769 s = str[4:]
770 i = s.find('\t')
770 i = s.find('\t')
771 if i < 0:
771 if i < 0:
772 i = s.find(' ')
772 i = s.find(' ')
773 if i < 0:
773 if i < 0:
774 return s
774 return s
775 return s[:i]
775 return s[:i]
776
776
777 def selectfile(afile_orig, bfile_orig, hunk, strip, reverse):
777 def selectfile(afile_orig, bfile_orig, hunk, strip, reverse):
778 def pathstrip(path, count=1):
778 def pathstrip(path, count=1):
779 pathlen = len(path)
779 pathlen = len(path)
780 i = 0
780 i = 0
781 if count == 0:
781 if count == 0:
782 return path.rstrip()
782 return path.rstrip()
783 while count > 0:
783 while count > 0:
784 i = path.find('/', i)
784 i = path.find('/', i)
785 if i == -1:
785 if i == -1:
786 raise PatchError(_("unable to strip away %d dirs from %s") %
786 raise PatchError(_("unable to strip away %d dirs from %s") %
787 (count, path))
787 (count, path))
788 i += 1
788 i += 1
789 # consume '//' in the path
789 # consume '//' in the path
790 while i < pathlen - 1 and path[i] == '/':
790 while i < pathlen - 1 and path[i] == '/':
791 i += 1
791 i += 1
792 count -= 1
792 count -= 1
793 return path[i:].rstrip()
793 return path[i:].rstrip()
794
794
795 nulla = afile_orig == "/dev/null"
795 nulla = afile_orig == "/dev/null"
796 nullb = bfile_orig == "/dev/null"
796 nullb = bfile_orig == "/dev/null"
797 afile = pathstrip(afile_orig, strip)
797 afile = pathstrip(afile_orig, strip)
798 gooda = os.path.exists(afile) and not nulla
798 gooda = os.path.exists(afile) and not nulla
799 bfile = pathstrip(bfile_orig, strip)
799 bfile = pathstrip(bfile_orig, strip)
800 if afile == bfile:
800 if afile == bfile:
801 goodb = gooda
801 goodb = gooda
802 else:
802 else:
803 goodb = os.path.exists(bfile) and not nullb
803 goodb = os.path.exists(bfile) and not nullb
804 createfunc = hunk.createfile
804 createfunc = hunk.createfile
805 if reverse:
805 if reverse:
806 createfunc = hunk.rmfile
806 createfunc = hunk.rmfile
807 if not goodb and not gooda and not createfunc():
807 if not goodb and not gooda and not createfunc():
808 raise PatchError(_("unable to find %s or %s for patching") %
808 raise PatchError(_("unable to find %s or %s for patching") %
809 (afile, bfile))
809 (afile, bfile))
810 if gooda and goodb:
810 if gooda and goodb:
811 fname = bfile
811 fname = bfile
812 if afile in bfile:
812 if afile in bfile:
813 fname = afile
813 fname = afile
814 elif gooda:
814 elif gooda:
815 fname = afile
815 fname = afile
816 elif not nullb:
816 elif not nullb:
817 fname = bfile
817 fname = bfile
818 if afile in bfile:
818 if afile in bfile:
819 fname = afile
819 fname = afile
820 elif not nulla:
820 elif not nulla:
821 fname = afile
821 fname = afile
822 return fname
822 return fname
823
823
824 class linereader:
824 class linereader:
825 # simple class to allow pushing lines back into the input stream
825 # simple class to allow pushing lines back into the input stream
826 def __init__(self, fp):
826 def __init__(self, fp):
827 self.fp = fp
827 self.fp = fp
828 self.buf = []
828 self.buf = []
829
829
830 def push(self, line):
830 def push(self, line):
831 self.buf.append(line)
831 self.buf.append(line)
832
832
833 def readline(self):
833 def readline(self):
834 if self.buf:
834 if self.buf:
835 l = self.buf[0]
835 l = self.buf[0]
836 del self.buf[0]
836 del self.buf[0]
837 return l
837 return l
838 return self.fp.readline()
838 return self.fp.readline()
839
839
840 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False,
840 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False,
841 rejmerge=None, updatedir=None):
841 rejmerge=None, updatedir=None):
842 """reads a patch from fp and tries to apply it. The dict 'changed' is
842 """reads a patch from fp and tries to apply it. The dict 'changed' is
843 filled in with all of the filenames changed by the patch. Returns 0
843 filled in with all of the filenames changed by the patch. Returns 0
844 for a clean patch, -1 if any rejects were found and 1 if there was
844 for a clean patch, -1 if any rejects were found and 1 if there was
845 any fuzz."""
845 any fuzz."""
846
846
847 def scangitpatch(fp, firstline, cwd=None):
847 def scangitpatch(fp, firstline, cwd=None):
848 '''git patches can modify a file, then copy that file to
848 '''git patches can modify a file, then copy that file to
849 a new file, but expect the source to be the unmodified form.
849 a new file, but expect the source to be the unmodified form.
850 So we scan the patch looking for that case so we can do
850 So we scan the patch looking for that case so we can do
851 the copies ahead of time.'''
851 the copies ahead of time.'''
852
852
853 pos = 0
853 pos = 0
854 try:
854 try:
855 pos = fp.tell()
855 pos = fp.tell()
856 except IOError:
856 except IOError:
857 fp = cStringIO.StringIO(fp.read())
857 fp = cStringIO.StringIO(fp.read())
858
858
859 (dopatch, gitpatches) = readgitpatch(fp, firstline)
859 (dopatch, gitpatches) = readgitpatch(fp, firstline)
860 for gp in gitpatches:
860 for gp in gitpatches:
861 if gp.copymod:
861 if gp.copymod:
862 copyfile(gp.oldpath, gp.path, basedir=cwd)
862 copyfile(gp.oldpath, gp.path, basedir=cwd)
863
863
864 fp.seek(pos)
864 fp.seek(pos)
865
865
866 return fp, dopatch, gitpatches
866 return fp, dopatch, gitpatches
867
867
868 current_hunk = None
868 current_hunk = None
869 current_file = None
869 current_file = None
870 afile = ""
870 afile = ""
871 bfile = ""
871 bfile = ""
872 state = None
872 state = None
873 hunknum = 0
873 hunknum = 0
874 rejects = 0
874 rejects = 0
875
875
876 git = False
876 git = False
877 gitre = re.compile('diff --git (a/.*) (b/.*)')
877 gitre = re.compile('diff --git (a/.*) (b/.*)')
878
878
879 # our states
879 # our states
880 BFILE = 1
880 BFILE = 1
881 err = 0
881 err = 0
882 context = None
882 context = None
883 lr = linereader(fp)
883 lr = linereader(fp)
884 dopatch = True
884 dopatch = True
885 gitworkdone = False
885 gitworkdone = False
886
886
887 while True:
887 while True:
888 newfile = False
888 newfile = False
889 x = lr.readline()
889 x = lr.readline()
890 if not x:
890 if not x:
891 break
891 break
892 if current_hunk:
892 if current_hunk:
893 if x.startswith('\ '):
893 if x.startswith('\ '):
894 current_hunk.fix_newline()
894 current_hunk.fix_newline()
895 ret = current_file.apply(current_hunk, reverse)
895 ret = current_file.apply(current_hunk, reverse)
896 if ret >= 0:
896 if ret >= 0:
897 changed.setdefault(current_file.fname, (None, None))
897 changed.setdefault(current_file.fname, (None, None))
898 if ret > 0:
898 if ret > 0:
899 err = 1
899 err = 1
900 current_hunk = None
900 current_hunk = None
901 gitworkdone = False
901 gitworkdone = False
902 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
902 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
903 ((context or context == None) and x.startswith('***************')))):
903 ((context or context == None) and x.startswith('***************')))):
904 try:
904 try:
905 if context == None and x.startswith('***************'):
905 if context == None and x.startswith('***************'):
906 context = True
906 context = True
907 current_hunk = hunk(x, hunknum + 1, lr, context)
907 current_hunk = hunk(x, hunknum + 1, lr, context)
908 except PatchError, err:
908 except PatchError, err:
909 ui.debug(err)
909 ui.debug(err)
910 current_hunk = None
910 current_hunk = None
911 continue
911 continue
912 hunknum += 1
912 hunknum += 1
913 if not current_file:
913 if not current_file:
914 if sourcefile:
914 if sourcefile:
915 current_file = patchfile(ui, sourcefile)
915 current_file = patchfile(ui, sourcefile)
916 else:
916 else:
917 current_file = selectfile(afile, bfile, current_hunk,
917 current_file = selectfile(afile, bfile, current_hunk,
918 strip, reverse)
918 strip, reverse)
919 current_file = patchfile(ui, current_file)
919 current_file = patchfile(ui, current_file)
920 elif state == BFILE and x.startswith('GIT binary patch'):
920 elif state == BFILE and x.startswith('GIT binary patch'):
921 current_hunk = binhunk(changed[bfile[2:]][1])
921 current_hunk = binhunk(changed[bfile[2:]][1])
922 if not current_file:
922 if not current_file:
923 if sourcefile:
923 if sourcefile:
924 current_file = patchfile(ui, sourcefile)
924 current_file = patchfile(ui, sourcefile)
925 else:
925 else:
926 current_file = selectfile(afile, bfile, current_hunk,
926 current_file = selectfile(afile, bfile, current_hunk,
927 strip, reverse)
927 strip, reverse)
928 current_file = patchfile(ui, current_file)
928 current_file = patchfile(ui, current_file)
929 hunknum += 1
929 hunknum += 1
930 current_hunk.extract(fp)
930 current_hunk.extract(fp)
931 elif x.startswith('diff --git'):
931 elif x.startswith('diff --git'):
932 # check for git diff, scanning the whole patch file if needed
932 # check for git diff, scanning the whole patch file if needed
933 m = gitre.match(x)
933 m = gitre.match(x)
934 if m:
934 if m:
935 afile, bfile = m.group(1, 2)
935 afile, bfile = m.group(1, 2)
936 if not git:
936 if not git:
937 git = True
937 git = True
938 fp, dopatch, gitpatches = scangitpatch(fp, x)
938 fp, dopatch, gitpatches = scangitpatch(fp, x)
939 for gp in gitpatches:
939 for gp in gitpatches:
940 changed[gp.path] = (gp.op, gp)
940 changed[gp.path] = (gp.op, gp)
941 # else error?
941 # else error?
942 # copy/rename + modify should modify target, not source
942 # copy/rename + modify should modify target, not source
943 if changed.get(bfile[2:], (None, None))[0] in ('COPY',
943 if changed.get(bfile[2:], (None, None))[0] in ('COPY',
944 'RENAME'):
944 'RENAME'):
945 afile = bfile
945 afile = bfile
946 gitworkdone = True
946 gitworkdone = True
947 newfile = True
947 newfile = True
948 elif x.startswith('---'):
948 elif x.startswith('---'):
949 # check for a unified diff
949 # check for a unified diff
950 l2 = lr.readline()
950 l2 = lr.readline()
951 if not l2.startswith('+++'):
951 if not l2.startswith('+++'):
952 lr.push(l2)
952 lr.push(l2)
953 continue
953 continue
954 newfile = True
954 newfile = True
955 context = False
955 context = False
956 afile = parsefilename(x)
956 afile = parsefilename(x)
957 bfile = parsefilename(l2)
957 bfile = parsefilename(l2)
958 elif x.startswith('***'):
958 elif x.startswith('***'):
959 # check for a context diff
959 # check for a context diff
960 l2 = lr.readline()
960 l2 = lr.readline()
961 if not l2.startswith('---'):
961 if not l2.startswith('---'):
962 lr.push(l2)
962 lr.push(l2)
963 continue
963 continue
964 l3 = lr.readline()
964 l3 = lr.readline()
965 lr.push(l3)
965 lr.push(l3)
966 if not l3.startswith("***************"):
966 if not l3.startswith("***************"):
967 lr.push(l2)
967 lr.push(l2)
968 continue
968 continue
969 newfile = True
969 newfile = True
970 context = True
970 context = True
971 afile = parsefilename(x)
971 afile = parsefilename(x)
972 bfile = parsefilename(l2)
972 bfile = parsefilename(l2)
973
973
974 if newfile:
974 if newfile:
975 if current_file:
975 if current_file:
976 current_file.close()
976 current_file.close()
977 if rejmerge:
977 if rejmerge:
978 rejmerge(current_file)
978 rejmerge(current_file)
979 rejects += len(current_file.rej)
979 rejects += len(current_file.rej)
980 state = BFILE
980 state = BFILE
981 current_file = None
981 current_file = None
982 hunknum = 0
982 hunknum = 0
983 if current_hunk:
983 if current_hunk:
984 if current_hunk.complete():
984 if current_hunk.complete():
985 ret = current_file.apply(current_hunk, reverse)
985 ret = current_file.apply(current_hunk, reverse)
986 if ret >= 0:
986 if ret >= 0:
987 changed.setdefault(current_file.fname, (None, None))
987 changed.setdefault(current_file.fname, (None, None))
988 if ret > 0:
988 if ret > 0:
989 err = 1
989 err = 1
990 else:
990 else:
991 fname = current_file and current_file.fname or None
991 fname = current_file and current_file.fname or None
992 raise PatchError(_("malformed patch %s %s") % (fname,
992 raise PatchError(_("malformed patch %s %s") % (fname,
993 current_hunk.desc))
993 current_hunk.desc))
994 if current_file:
994 if current_file:
995 current_file.close()
995 current_file.close()
996 if rejmerge:
996 if rejmerge:
997 rejmerge(current_file)
997 rejmerge(current_file)
998 rejects += len(current_file.rej)
998 rejects += len(current_file.rej)
999 if updatedir and git:
999 if updatedir and git:
1000 updatedir(gitpatches)
1000 updatedir(gitpatches)
1001 if rejects:
1001 if rejects:
1002 return -1
1002 return -1
1003 if hunknum == 0 and dopatch and not gitworkdone:
1003 if hunknum == 0 and dopatch and not gitworkdone:
1004 raise NoHunks
1004 raise NoHunks
1005 return err
1005 return err
1006
1006
1007 def diffopts(ui, opts={}, untrusted=False):
1007 def diffopts(ui, opts={}, untrusted=False):
1008 def get(key, name=None):
1008 def get(key, name=None):
1009 return (opts.get(key) or
1009 return (opts.get(key) or
1010 ui.configbool('diff', name or key, None, untrusted=untrusted))
1010 ui.configbool('diff', name or key, None, untrusted=untrusted))
1011 return mdiff.diffopts(
1011 return mdiff.diffopts(
1012 text=opts.get('text'),
1012 text=opts.get('text'),
1013 git=get('git'),
1013 git=get('git'),
1014 nodates=get('nodates'),
1014 nodates=get('nodates'),
1015 showfunc=get('show_function', 'showfunc'),
1015 showfunc=get('show_function', 'showfunc'),
1016 ignorews=get('ignore_all_space', 'ignorews'),
1016 ignorews=get('ignore_all_space', 'ignorews'),
1017 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1017 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1018 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'))
1018 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'))
1019
1019
1020 def updatedir(ui, repo, patches):
1020 def updatedir(ui, repo, patches):
1021 '''Update dirstate after patch application according to metadata'''
1021 '''Update dirstate after patch application according to metadata'''
1022 if not patches:
1022 if not patches:
1023 return
1023 return
1024 copies = []
1024 copies = []
1025 removes = {}
1025 removes = {}
1026 cfiles = patches.keys()
1026 cfiles = patches.keys()
1027 cwd = repo.getcwd()
1027 cwd = repo.getcwd()
1028 if cwd:
1028 if cwd:
1029 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1029 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1030 for f in patches:
1030 for f in patches:
1031 ctype, gp = patches[f]
1031 ctype, gp = patches[f]
1032 if ctype == 'RENAME':
1032 if ctype == 'RENAME':
1033 copies.append((gp.oldpath, gp.path, gp.copymod))
1033 copies.append((gp.oldpath, gp.path, gp.copymod))
1034 removes[gp.oldpath] = 1
1034 removes[gp.oldpath] = 1
1035 elif ctype == 'COPY':
1035 elif ctype == 'COPY':
1036 copies.append((gp.oldpath, gp.path, gp.copymod))
1036 copies.append((gp.oldpath, gp.path, gp.copymod))
1037 elif ctype == 'DELETE':
1037 elif ctype == 'DELETE':
1038 removes[gp.path] = 1
1038 removes[gp.path] = 1
1039 for src, dst, after in copies:
1039 for src, dst, after in copies:
1040 if not after:
1040 if not after:
1041 copyfile(src, dst, repo.root)
1041 copyfile(src, dst, repo.root)
1042 repo.copy(src, dst)
1042 repo.copy(src, dst)
1043 removes = removes.keys()
1043 removes = removes.keys()
1044 if removes:
1044 if removes:
1045 removes.sort()
1045 removes.sort()
1046 repo.remove(removes, True)
1046 repo.remove(removes, True)
1047 for f in patches:
1047 for f in patches:
1048 ctype, gp = patches[f]
1048 ctype, gp = patches[f]
1049 if gp and gp.mode:
1049 if gp and gp.mode:
1050 x = gp.mode & 0100 != 0
1050 x = gp.mode & 0100 != 0
1051 l = gp.mode & 020000 != 0
1051 l = gp.mode & 020000 != 0
1052 dst = os.path.join(repo.root, gp.path)
1052 dst = os.path.join(repo.root, gp.path)
1053 # patch won't create empty files
1053 # patch won't create empty files
1054 if ctype == 'ADD' and not os.path.exists(dst):
1054 if ctype == 'ADD' and not os.path.exists(dst):
1055 repo.wwrite(gp.path, '', x and 'x' or '')
1055 repo.wwrite(gp.path, '', x and 'x' or '')
1056 else:
1056 else:
1057 util.set_link(dst, l)
1057 util.set_link(dst, l)
1058 if not l:
1058 if not l:
1059 util.set_exec(dst, x)
1059 util.set_exec(dst, x)
1060 cmdutil.addremove(repo, cfiles)
1060 cmdutil.addremove(repo, cfiles)
1061 files = patches.keys()
1061 files = patches.keys()
1062 files.extend([r for r in removes if r not in files])
1062 files.extend([r for r in removes if r not in files])
1063 files.sort()
1063 files.sort()
1064
1064
1065 return files
1065 return files
1066
1066
1067 def b85diff(to, tn):
1067 def b85diff(to, tn):
1068 '''print base85-encoded binary diff'''
1068 '''print base85-encoded binary diff'''
1069 def gitindex(text):
1069 def gitindex(text):
1070 if not text:
1070 if not text:
1071 return '0' * 40
1071 return '0' * 40
1072 l = len(text)
1072 l = len(text)
1073 s = sha.new('blob %d\0' % l)
1073 s = sha.new('blob %d\0' % l)
1074 s.update(text)
1074 s.update(text)
1075 return s.hexdigest()
1075 return s.hexdigest()
1076
1076
1077 def fmtline(line):
1077 def fmtline(line):
1078 l = len(line)
1078 l = len(line)
1079 if l <= 26:
1079 if l <= 26:
1080 l = chr(ord('A') + l - 1)
1080 l = chr(ord('A') + l - 1)
1081 else:
1081 else:
1082 l = chr(l - 26 + ord('a') - 1)
1082 l = chr(l - 26 + ord('a') - 1)
1083 return '%c%s\n' % (l, base85.b85encode(line, True))
1083 return '%c%s\n' % (l, base85.b85encode(line, True))
1084
1084
1085 def chunk(text, csize=52):
1085 def chunk(text, csize=52):
1086 l = len(text)
1086 l = len(text)
1087 i = 0
1087 i = 0
1088 while i < l:
1088 while i < l:
1089 yield text[i:i+csize]
1089 yield text[i:i+csize]
1090 i += csize
1090 i += csize
1091
1091
1092 tohash = gitindex(to)
1092 tohash = gitindex(to)
1093 tnhash = gitindex(tn)
1093 tnhash = gitindex(tn)
1094 if tohash == tnhash:
1094 if tohash == tnhash:
1095 return ""
1095 return ""
1096
1096
1097 # TODO: deltas
1097 # TODO: deltas
1098 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1098 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1099 (tohash, tnhash, len(tn))]
1099 (tohash, tnhash, len(tn))]
1100 for l in chunk(zlib.compress(tn)):
1100 for l in chunk(zlib.compress(tn)):
1101 ret.append(fmtline(l))
1101 ret.append(fmtline(l))
1102 ret.append('\n')
1102 ret.append('\n')
1103 return ''.join(ret)
1103 return ''.join(ret)
1104
1104
1105 def diff(repo, node1=None, node2=None, files=None, match=util.always,
1105 def diff(repo, node1=None, node2=None, files=None, match=util.always,
1106 fp=None, changes=None, opts=None):
1106 fp=None, changes=None, opts=None):
1107 '''print diff of changes to files between two nodes, or node and
1107 '''print diff of changes to files between two nodes, or node and
1108 working directory.
1108 working directory.
1109
1109
1110 if node1 is None, use first dirstate parent instead.
1110 if node1 is None, use first dirstate parent instead.
1111 if node2 is None, compare node1 with working directory.'''
1111 if node2 is None, compare node1 with working directory.'''
1112
1112
1113 if opts is None:
1113 if opts is None:
1114 opts = mdiff.defaultopts
1114 opts = mdiff.defaultopts
1115 if fp is None:
1115 if fp is None:
1116 fp = repo.ui
1116 fp = repo.ui
1117
1117
1118 if not node1:
1118 if not node1:
1119 node1 = repo.dirstate.parents()[0]
1119 node1 = repo.dirstate.parents()[0]
1120
1120
1121 ccache = {}
1121 ccache = {}
1122 def getctx(r):
1122 def getctx(r):
1123 if r not in ccache:
1123 if r not in ccache:
1124 ccache[r] = context.changectx(repo, r)
1124 ccache[r] = context.changectx(repo, r)
1125 return ccache[r]
1125 return ccache[r]
1126
1126
1127 flcache = {}
1127 flcache = {}
1128 def getfilectx(f, ctx):
1128 def getfilectx(f, ctx):
1129 flctx = ctx.filectx(f, filelog=flcache.get(f))
1129 flctx = ctx.filectx(f, filelog=flcache.get(f))
1130 if f not in flcache:
1130 if f not in flcache:
1131 flcache[f] = flctx._filelog
1131 flcache[f] = flctx._filelog
1132 return flctx
1132 return flctx
1133
1133
1134 # reading the data for node1 early allows it to play nicely
1134 # reading the data for node1 early allows it to play nicely
1135 # with repo.status and the revlog cache.
1135 # with repo.status and the revlog cache.
1136 ctx1 = context.changectx(repo, node1)
1136 ctx1 = context.changectx(repo, node1)
1137 # force manifest reading
1137 # force manifest reading
1138 man1 = ctx1.manifest()
1138 man1 = ctx1.manifest()
1139 date1 = util.datestr(ctx1.date())
1139 date1 = util.datestr(ctx1.date())
1140
1140
1141 if not changes:
1141 if not changes:
1142 changes = repo.status(node1, node2, files, match=match)[:5]
1142 changes = repo.status(node1, node2, files, match=match)[:5]
1143 modified, added, removed, deleted, unknown = changes
1143 modified, added, removed, deleted, unknown = changes
1144
1144
1145 if not modified and not added and not removed:
1145 if not modified and not added and not removed:
1146 return
1146 return
1147
1147
1148 if node2:
1148 if node2:
1149 ctx2 = context.changectx(repo, node2)
1149 ctx2 = context.changectx(repo, node2)
1150 execf2 = ctx2.manifest().execf
1150 execf2 = ctx2.manifest().execf
1151 linkf2 = ctx2.manifest().linkf
1151 linkf2 = ctx2.manifest().linkf
1152 else:
1152 else:
1153 ctx2 = context.workingctx(repo)
1153 ctx2 = context.workingctx(repo)
1154 execf2 = util.execfunc(repo.root, None)
1154 execf2 = util.execfunc(repo.root, None)
1155 linkf2 = util.linkfunc(repo.root, None)
1155 linkf2 = util.linkfunc(repo.root, None)
1156 if execf2 is None:
1156 if execf2 is None:
1157 mc = ctx2.parents()[0].manifest().copy()
1157 mc = ctx2.parents()[0].manifest().copy()
1158 execf2 = mc.execf
1158 execf2 = mc.execf
1159 linkf2 = mc.linkf
1159 linkf2 = mc.linkf
1160
1160
1161 # returns False if there was no rename between ctx1 and ctx2
1161 # returns False if there was no rename between ctx1 and ctx2
1162 # returns None if the file was created between ctx1 and ctx2
1162 # returns None if the file was created between ctx1 and ctx2
1163 # returns the (file, node) present in ctx1 that was renamed to f in ctx2
1163 # returns the (file, node) present in ctx1 that was renamed to f in ctx2
1164 def renamed(f):
1164 def renamed(f):
1165 startrev = ctx1.rev()
1165 startrev = ctx1.rev()
1166 c = ctx2
1166 c = ctx2
1167 crev = c.rev()
1167 crev = c.rev()
1168 if crev is None:
1168 if crev is None:
1169 crev = repo.changelog.count()
1169 crev = repo.changelog.count()
1170 orig = f
1170 orig = f
1171 while crev > startrev:
1171 while crev > startrev:
1172 if f in c.files():
1172 if f in c.files():
1173 try:
1173 try:
1174 src = getfilectx(f, c).renamed()
1174 src = getfilectx(f, c).renamed()
1175 except revlog.LookupError:
1175 except revlog.LookupError:
1176 return None
1176 return None
1177 if src:
1177 if src:
1178 f = src[0]
1178 f = src[0]
1179 crev = c.parents()[0].rev()
1179 crev = c.parents()[0].rev()
1180 # try to reuse
1180 # try to reuse
1181 c = getctx(crev)
1181 c = getctx(crev)
1182 if f not in man1:
1182 if f not in man1:
1183 return None
1183 return None
1184 if f == orig:
1184 if f == orig:
1185 return False
1185 return False
1186 return f
1186 return f
1187
1187
1188 if repo.ui.quiet:
1188 if repo.ui.quiet:
1189 r = None
1189 r = None
1190 else:
1190 else:
1191 hexfunc = repo.ui.debugflag and hex or short
1191 hexfunc = repo.ui.debugflag and hex or short
1192 r = [hexfunc(node) for node in [node1, node2] if node]
1192 r = [hexfunc(node) for node in [node1, node2] if node]
1193
1193
1194 if opts.git:
1194 if opts.git:
1195 copied = {}
1195 copied = {}
1196 for f in added:
1196 for f in added:
1197 src = renamed(f)
1197 src = renamed(f)
1198 if src:
1198 if src:
1199 copied[f] = src
1199 copied[f] = src
1200 srcs = [x[1] for x in copied.items()]
1200 srcs = [x[1] for x in copied.items()]
1201
1201
1202 all = modified + added + removed
1202 all = modified + added + removed
1203 all.sort()
1203 all.sort()
1204 gone = {}
1204 gone = {}
1205
1205
1206 for f in all:
1206 for f in all:
1207 to = None
1207 to = None
1208 tn = None
1208 tn = None
1209 dodiff = True
1209 dodiff = True
1210 header = []
1210 header = []
1211 if f in man1:
1211 if f in man1:
1212 to = getfilectx(f, ctx1).data()
1212 to = getfilectx(f, ctx1).data()
1213 if f not in removed:
1213 if f not in removed:
1214 tn = getfilectx(f, ctx2).data()
1214 tn = getfilectx(f, ctx2).data()
1215 if opts.git:
1215 if opts.git:
1216 def gitmode(x, l):
1216 def gitmode(x, l):
1217 return l and '120000' or (x and '100755' or '100644')
1217 return l and '120000' or (x and '100755' or '100644')
1218 def addmodehdr(header, omode, nmode):
1218 def addmodehdr(header, omode, nmode):
1219 if omode != nmode:
1219 if omode != nmode:
1220 header.append('old mode %s\n' % omode)
1220 header.append('old mode %s\n' % omode)
1221 header.append('new mode %s\n' % nmode)
1221 header.append('new mode %s\n' % nmode)
1222
1222
1223 a, b = f, f
1223 a, b = f, f
1224 if f in added:
1224 if f in added:
1225 mode = gitmode(execf2(f), linkf2(f))
1225 mode = gitmode(execf2(f), linkf2(f))
1226 if f in copied:
1226 if f in copied:
1227 a = copied[f]
1227 a = copied[f]
1228 omode = gitmode(man1.execf(a), man1.linkf(a))
1228 omode = gitmode(man1.execf(a), man1.linkf(a))
1229 addmodehdr(header, omode, mode)
1229 addmodehdr(header, omode, mode)
1230 if a in removed and a not in gone:
1230 if a in removed and a not in gone:
1231 op = 'rename'
1231 op = 'rename'
1232 gone[a] = 1
1232 gone[a] = 1
1233 else:
1233 else:
1234 op = 'copy'
1234 op = 'copy'
1235 header.append('%s from %s\n' % (op, a))
1235 header.append('%s from %s\n' % (op, a))
1236 header.append('%s to %s\n' % (op, f))
1236 header.append('%s to %s\n' % (op, f))
1237 to = getfilectx(a, ctx1).data()
1237 to = getfilectx(a, ctx1).data()
1238 else:
1238 else:
1239 header.append('new file mode %s\n' % mode)
1239 header.append('new file mode %s\n' % mode)
1240 if util.binary(tn):
1240 if util.binary(tn):
1241 dodiff = 'binary'
1241 dodiff = 'binary'
1242 elif f in removed:
1242 elif f in removed:
1243 if f in srcs:
1243 if f in srcs:
1244 dodiff = False
1244 dodiff = False
1245 else:
1245 else:
1246 mode = gitmode(man1.execf(f), man1.linkf(f))
1246 mode = gitmode(man1.execf(f), man1.linkf(f))
1247 header.append('deleted file mode %s\n' % mode)
1247 header.append('deleted file mode %s\n' % mode)
1248 else:
1248 else:
1249 omode = gitmode(man1.execf(f), man1.linkf(f))
1249 omode = gitmode(man1.execf(f), man1.linkf(f))
1250 nmode = gitmode(execf2(f), linkf2(f))
1250 nmode = gitmode(execf2(f), linkf2(f))
1251 addmodehdr(header, omode, nmode)
1251 addmodehdr(header, omode, nmode)
1252 if util.binary(to) or util.binary(tn):
1252 if util.binary(to) or util.binary(tn):
1253 dodiff = 'binary'
1253 dodiff = 'binary'
1254 r = None
1254 r = None
1255 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
1255 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
1256 if dodiff:
1256 if dodiff:
1257 if dodiff == 'binary':
1257 if dodiff == 'binary':
1258 text = b85diff(to, tn)
1258 text = b85diff(to, tn)
1259 else:
1259 else:
1260 text = mdiff.unidiff(to, date1,
1260 text = mdiff.unidiff(to, date1,
1261 # ctx2 date may be dynamic
1261 # ctx2 date may be dynamic
1262 tn, util.datestr(ctx2.date()),
1262 tn, util.datestr(ctx2.date()),
1263 f, r, opts=opts)
1263 f, r, opts=opts)
1264 if text or len(header) > 1:
1264 if text or len(header) > 1:
1265 fp.write(''.join(header))
1265 fp.write(''.join(header))
1266 fp.write(text)
1266 fp.write(text)
1267
1267
1268 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1268 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1269 opts=None):
1269 opts=None):
1270 '''export changesets as hg patches.'''
1270 '''export changesets as hg patches.'''
1271
1271
1272 total = len(revs)
1272 total = len(revs)
1273 revwidth = max([len(str(rev)) for rev in revs])
1273 revwidth = max([len(str(rev)) for rev in revs])
1274
1274
1275 def single(rev, seqno, fp):
1275 def single(rev, seqno, fp):
1276 ctx = repo.changectx(rev)
1276 ctx = repo.changectx(rev)
1277 node = ctx.node()
1277 node = ctx.node()
1278 parents = [p.node() for p in ctx.parents() if p]
1278 parents = [p.node() for p in ctx.parents() if p]
1279 branch = ctx.branch()
1279 branch = ctx.branch()
1280 if switch_parent:
1280 if switch_parent:
1281 parents.reverse()
1281 parents.reverse()
1282 prev = (parents and parents[0]) or nullid
1282 prev = (parents and parents[0]) or nullid
1283
1283
1284 if not fp:
1284 if not fp:
1285 fp = cmdutil.make_file(repo, template, node, total=total,
1285 fp = cmdutil.make_file(repo, template, node, total=total,
1286 seqno=seqno, revwidth=revwidth)
1286 seqno=seqno, revwidth=revwidth)
1287 if fp != sys.stdout and hasattr(fp, 'name'):
1287 if fp != sys.stdout and hasattr(fp, 'name'):
1288 repo.ui.note("%s\n" % fp.name)
1288 repo.ui.note("%s\n" % fp.name)
1289
1289
1290 fp.write("# HG changeset patch\n")
1290 fp.write("# HG changeset patch\n")
1291 fp.write("# User %s\n" % ctx.user())
1291 fp.write("# User %s\n" % ctx.user())
1292 fp.write("# Date %d %d\n" % ctx.date())
1292 fp.write("# Date %d %d\n" % ctx.date())
1293 if branch and (branch != 'default'):
1293 if branch and (branch != 'default'):
1294 fp.write("# Branch %s\n" % branch)
1294 fp.write("# Branch %s\n" % branch)
1295 fp.write("# Node ID %s\n" % hex(node))
1295 fp.write("# Node ID %s\n" % hex(node))
1296 fp.write("# Parent %s\n" % hex(prev))
1296 fp.write("# Parent %s\n" % hex(prev))
1297 if len(parents) > 1:
1297 if len(parents) > 1:
1298 fp.write("# Parent %s\n" % hex(parents[1]))
1298 fp.write("# Parent %s\n" % hex(parents[1]))
1299 fp.write(ctx.description().rstrip())
1299 fp.write(ctx.description().rstrip())
1300 fp.write("\n\n")
1300 fp.write("\n\n")
1301
1301
1302 diff(repo, prev, node, fp=fp, opts=opts)
1302 diff(repo, prev, node, fp=fp, opts=opts)
1303 if fp not in (sys.stdout, repo.ui):
1303 if fp not in (sys.stdout, repo.ui):
1304 fp.close()
1304 fp.close()
1305
1305
1306 for seqno, rev in enumerate(revs):
1306 for seqno, rev in enumerate(revs):
1307 single(rev, seqno+1, fp)
1307 single(rev, seqno+1, fp)
1308
1308
1309 def diffstat(patchlines):
1309 def diffstat(patchlines):
1310 if not util.find_exe('diffstat'):
1310 if not util.find_exe('diffstat'):
1311 return
1311 return
1312 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
1312 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
1313 try:
1313 try:
1314 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
1314 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
1315 try:
1315 try:
1316 for line in patchlines: print >> p.tochild, line
1316 for line in patchlines: print >> p.tochild, line
1317 p.tochild.close()
1317 p.tochild.close()
1318 if p.wait(): return
1318 if p.wait(): return
1319 fp = os.fdopen(fd, 'r')
1319 fp = os.fdopen(fd, 'r')
1320 stat = []
1320 stat = []
1321 for line in fp: stat.append(line.lstrip())
1321 for line in fp: stat.append(line.lstrip())
1322 last = stat.pop()
1322 last = stat.pop()
1323 stat.insert(0, last)
1323 stat.insert(0, last)
1324 stat = ''.join(stat)
1324 stat = ''.join(stat)
1325 if stat.startswith('0 files'): raise ValueError
1325 if stat.startswith('0 files'): raise ValueError
1326 return stat
1326 return stat
1327 except: raise
1327 except: raise
1328 finally:
1328 finally:
1329 try: os.unlink(name)
1329 try: os.unlink(name)
1330 except: pass
1330 except: pass
@@ -1,40 +1,40 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 echo "[extensions]" >> $HGRCPATH
3 echo "[extensions]" >> $HGRCPATH
4 echo "extdiff=" >> $HGRCPATH
4 echo "extdiff=" >> $HGRCPATH
5
5
6 hg init a
6 hg init a
7 cd a
7 cd a
8 echo a > a
8 echo a > a
9 echo b > b
9 echo b > b
10 hg add
10 hg add
11 # should diff cloned directories
11 # should diff cloned directories
12 hg extdiff -o -r $opt
12 hg extdiff -o -r $opt
13
13
14 echo "[extdiff]" >> $HGRCPATH
14 echo "[extdiff]" >> $HGRCPATH
15 echo "cmd.falabala=echo" >> $HGRCPATH
15 echo "cmd.falabala=echo" >> $HGRCPATH
16 echo "opts.falabala=diffing" >> $HGRCPATH
16 echo "opts.falabala=diffing" >> $HGRCPATH
17
17
18 hg falabala
18 hg falabala
19
19
20 hg help falabala
20 hg help falabala
21
21
22 hg ci -d '0 0' -mtest1
22 hg ci -d '0 0' -mtest1
23
23
24 echo b >> a
24 echo b >> a
25 hg ci -d '1 0' -mtest2
25 hg ci -d '1 0' -mtest2
26
26
27 # should diff cloned files directly
27 # should diff cloned files directly
28 hg falabala -r 0:1
28 hg falabala -r 0:1
29
29
30 # test diff during merge
30 # test diff during merge
31 hg update 0
31 hg update 0
32 echo c >> c
32 echo c >> c
33 hg add c
33 hg add c
34 hg ci -m "new branch" -d '1 0'
34 hg ci -m "new branch" -d '1 0'
35 hg update -C 1
35 hg update -C 1
36 hg merge tip
36 hg merge tip
37 # should diff cloned file against wc file
37 # should diff cloned file against wc file
38 hg falabala > out || echo "diff-like tools yield a non-zero exit code"
38 hg falabala > out || echo "diff-like tools yield a non-zero exit code"
39 # cleanup the output since the wc is a tmp directory
39 # cleanup the output since the wc is a tmp directory
40 sed 's:\(.* \).*\(\/test-extdiff\):\1[tmp]\2:' out
40 sed 's:\(.* \).*\(\/test-extdiff\):\1[tmp]\2:' out
General Comments 0
You need to be logged in to leave comments. Login now