##// END OF EJS Templates
localrepo: sort hg bookmark output...
David Soria Parra -
r13388:a184dbd9 merge default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,9 b''
1 @echo off
2 rem Double-click this file to (re)build Mercurial for Windows in place.
3 rem Useful for testing and development.
4 cd ..\..
5 del /Q mercurial\*.pyd
6 del /Q mercurial\*.pyc
7 rmdir /Q /S mercurial\locale
8 python setup.py build_py -c -d . build_ext -i build_mo
9 pause
@@ -1,4010 +1,4010 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 # See hgk.py for extension usage and configuration.
8 # See hgk.py for extension usage and configuration.
9
9
10
10
11 # Modified version of Tip 171:
11 # Modified version of Tip 171:
12 # http://www.tcl.tk/cgi-bin/tct/tip/171.html
12 # http://www.tcl.tk/cgi-bin/tct/tip/171.html
13 #
13 #
14 # The in_mousewheel global was added to fix strange reentrancy issues.
14 # The in_mousewheel global was added to fix strange reentrancy issues.
15 # The whole snipped is activated only under windows, mouse wheel
15 # The whole snipped is activated only under windows, mouse wheel
16 # bindings working already under MacOSX and Linux.
16 # bindings working already under MacOSX and Linux.
17
17
18 if {[tk windowingsystem] eq "win32"} {
18 if {[tk windowingsystem] eq "win32"} {
19
19
20 set mw_classes [list Text Listbox Table TreeCtrl]
20 set mw_classes [list Text Listbox Table TreeCtrl]
21 foreach class $mw_classes { bind $class <MouseWheel> {} }
21 foreach class $mw_classes { bind $class <MouseWheel> {} }
22
22
23 set in_mousewheel 0
23 set in_mousewheel 0
24
24
25 proc ::tk::MouseWheel {wFired X Y D {shifted 0}} {
25 proc ::tk::MouseWheel {wFired X Y D {shifted 0}} {
26 global in_mousewheel
26 global in_mousewheel
27 if { $in_mousewheel != 0 } { return }
27 if { $in_mousewheel != 0 } { return }
28 # Set event to check based on call
28 # Set event to check based on call
29 set evt "<[expr {$shifted?{Shift-}:{}}]MouseWheel>"
29 set evt "<[expr {$shifted?{Shift-}:{}}]MouseWheel>"
30 # do not double-fire in case the class already has a binding
30 # do not double-fire in case the class already has a binding
31 if {[bind [winfo class $wFired] $evt] ne ""} { return }
31 if {[bind [winfo class $wFired] $evt] ne ""} { return }
32 # obtain the window the mouse is over
32 # obtain the window the mouse is over
33 set w [winfo containing $X $Y]
33 set w [winfo containing $X $Y]
34 # if we are outside the app, try and scroll the focus widget
34 # if we are outside the app, try and scroll the focus widget
35 if {![winfo exists $w]} { catch {set w [focus]} }
35 if {![winfo exists $w]} { catch {set w [focus]} }
36 if {[winfo exists $w]} {
36 if {[winfo exists $w]} {
37
37
38 if {[bind $w $evt] ne ""} {
38 if {[bind $w $evt] ne ""} {
39 # Awkward ... this widget has a MouseWheel binding, but to
39 # Awkward ... this widget has a MouseWheel binding, but to
40 # trigger successfully in it, we must give it focus.
40 # trigger successfully in it, we must give it focus.
41 catch {focus} old
41 catch {focus} old
42 if {$w ne $old} { focus $w }
42 if {$w ne $old} { focus $w }
43 set in_mousewheel 1
43 set in_mousewheel 1
44 event generate $w $evt -rootx $X -rooty $Y -delta $D
44 event generate $w $evt -rootx $X -rooty $Y -delta $D
45 set in_mousewheel 0
45 set in_mousewheel 0
46 if {$w ne $old} { focus $old }
46 if {$w ne $old} { focus $old }
47 return
47 return
48 }
48 }
49
49
50 # aqua and x11/win32 have different delta handling
50 # aqua and x11/win32 have different delta handling
51 if {[tk windowingsystem] ne "aqua"} {
51 if {[tk windowingsystem] ne "aqua"} {
52 set delta [expr {- ($D / 30)}]
52 set delta [expr {- ($D / 30)}]
53 } else {
53 } else {
54 set delta [expr {- ($D)}]
54 set delta [expr {- ($D)}]
55 }
55 }
56 # scrollbars have different call conventions
56 # scrollbars have different call conventions
57 if {[string match "*Scrollbar" [winfo class $w]]} {
57 if {[string match "*Scrollbar" [winfo class $w]]} {
58 catch {tk::ScrollByUnits $w \
58 catch {tk::ScrollByUnits $w \
59 [string index [$w cget -orient] 0] $delta}
59 [string index [$w cget -orient] 0] $delta}
60 } else {
60 } else {
61 set cmd [list $w [expr {$shifted ? "xview" : "yview"}] \
61 set cmd [list $w [expr {$shifted ? "xview" : "yview"}] \
62 scroll $delta units]
62 scroll $delta units]
63 # Walking up to find the proper widget (handles cases like
63 # Walking up to find the proper widget (handles cases like
64 # embedded widgets in a canvas)
64 # embedded widgets in a canvas)
65 while {[catch $cmd] && [winfo toplevel $w] ne $w} {
65 while {[catch $cmd] && [winfo toplevel $w] ne $w} {
66 set w [winfo parent $w]
66 set w [winfo parent $w]
67 }
67 }
68 }
68 }
69 }
69 }
70 }
70 }
71
71
72 bind all <MouseWheel> [list ::tk::MouseWheel %W %X %Y %D 0]
72 bind all <MouseWheel> [list ::tk::MouseWheel %W %X %Y %D 0]
73
73
74 # end of win32 section
74 # end of win32 section
75 }
75 }
76
76
77
77
78 # Unify right mouse button handling.
78 # Unify right mouse button handling.
79 # See "mouse buttons on macintosh" thread on comp.lang.tcl
79 # See "mouse buttons on macintosh" thread on comp.lang.tcl
80 if {[tk windowingsystem] eq "aqua"} {
80 if {[tk windowingsystem] eq "aqua"} {
81 event add <<B3>> <Control-ButtonPress-1>
81 event add <<B3>> <Control-ButtonPress-1>
82 event add <<B3>> <Button-2>
82 event add <<B3>> <Button-2>
83 } else {
83 } else {
84 event add <<B3>> <Button-3>
84 event add <<B3>> <Button-3>
85 }
85 }
86
86
87 proc gitdir {} {
87 proc gitdir {} {
88 global env
88 global env
89 if {[info exists env(GIT_DIR)]} {
89 if {[info exists env(GIT_DIR)]} {
90 return $env(GIT_DIR)
90 return $env(GIT_DIR)
91 } else {
91 } else {
92 return ".hg"
92 return ".hg"
93 }
93 }
94 }
94 }
95
95
96 proc getcommits {rargs} {
96 proc getcommits {rargs} {
97 global commits commfd phase canv mainfont env
97 global commits commfd phase canv mainfont env
98 global startmsecs nextupdate ncmupdate
98 global startmsecs nextupdate ncmupdate
99 global ctext maincursor textcursor leftover
99 global ctext maincursor textcursor leftover
100
100
101 # check that we can find a .git directory somewhere...
101 # check that we can find a .git directory somewhere...
102 set gitdir [gitdir]
102 set gitdir [gitdir]
103 if {![file isdirectory $gitdir]} {
103 if {![file isdirectory $gitdir]} {
104 error_popup "Cannot find the git directory \"$gitdir\"."
104 error_popup "Cannot find the git directory \"$gitdir\"."
105 exit 1
105 exit 1
106 }
106 }
107 set commits {}
107 set commits {}
108 set phase getcommits
108 set phase getcommits
109 set startmsecs [clock clicks -milliseconds]
109 set startmsecs [clock clicks -milliseconds]
110 set nextupdate [expr $startmsecs + 100]
110 set nextupdate [expr $startmsecs + 100]
111 set ncmupdate 1
111 set ncmupdate 1
112 set limit 0
112 set limit 0
113 set revargs {}
113 set revargs {}
114 for {set i 0} {$i < [llength $rargs]} {incr i} {
114 for {set i 0} {$i < [llength $rargs]} {incr i} {
115 set opt [lindex $rargs $i]
115 set opt [lindex $rargs $i]
116 if {$opt == "--limit"} {
116 if {$opt == "--limit"} {
117 incr i
117 incr i
118 set limit [lindex $rargs $i]
118 set limit [lindex $rargs $i]
119 } else {
119 } else {
120 lappend revargs $opt
120 lappend revargs $opt
121 }
121 }
122 }
122 }
123 if [catch {
123 if [catch {
124 set parse_args [concat --default HEAD $revargs]
124 set parse_args [concat --default HEAD $revargs]
125 set parse_temp [eval exec {$env(HG)} --config ui.report_untrusted=false debug-rev-parse $parse_args]
125 set parse_temp [eval exec {$env(HG)} --config ui.report_untrusted=false debug-rev-parse $parse_args]
126 regsub -all "\r\n" $parse_temp "\n" parse_temp
126 regsub -all "\r\n" $parse_temp "\n" parse_temp
127 set parsed_args [split $parse_temp "\n"]
127 set parsed_args [split $parse_temp "\n"]
128 } err] {
128 } err] {
129 # if git-rev-parse failed for some reason...
129 # if git-rev-parse failed for some reason...
130 if {$rargs == {}} {
130 if {$rargs == {}} {
131 set revargs HEAD
131 set revargs HEAD
132 }
132 }
133 set parsed_args $revargs
133 set parsed_args $revargs
134 }
134 }
135 if {$limit > 0} {
135 if {$limit > 0} {
136 set parsed_args [concat -n $limit $parsed_args]
136 set parsed_args [concat -n $limit $parsed_args]
137 }
137 }
138 if [catch {
138 if [catch {
139 set commfd [open "|{$env(HG)} --config ui.report_untrusted=false debug-rev-list --header --topo-order --parents $parsed_args" r]
139 set commfd [open "|{$env(HG)} --config ui.report_untrusted=false debug-rev-list --header --topo-order --parents $parsed_args" r]
140 } err] {
140 } err] {
141 puts stderr "Error executing hg debug-rev-list: $err"
141 puts stderr "Error executing hg debug-rev-list: $err"
142 exit 1
142 exit 1
143 }
143 }
144 set leftover {}
144 set leftover {}
145 fconfigure $commfd -blocking 0 -translation lf
145 fconfigure $commfd -blocking 0 -translation lf
146 fileevent $commfd readable [list getcommitlines $commfd]
146 fileevent $commfd readable [list getcommitlines $commfd]
147 $canv delete all
147 $canv delete all
148 $canv create text 3 3 -anchor nw -text "Reading commits..." \
148 $canv create text 3 3 -anchor nw -text "Reading commits..." \
149 -font $mainfont -tags textitems
149 -font $mainfont -tags textitems
150 . config -cursor watch
150 . config -cursor watch
151 settextcursor watch
151 settextcursor watch
152 }
152 }
153
153
154 proc getcommitlines {commfd} {
154 proc getcommitlines {commfd} {
155 global commits parents cdate children
155 global commits parents cdate children
156 global commitlisted phase commitinfo nextupdate
156 global commitlisted phase commitinfo nextupdate
157 global stopped redisplaying leftover
157 global stopped redisplaying leftover
158
158
159 set stuff [read $commfd]
159 set stuff [read $commfd]
160 if {$stuff == {}} {
160 if {$stuff == {}} {
161 if {![eof $commfd]} return
161 if {![eof $commfd]} return
162 # set it blocking so we wait for the process to terminate
162 # set it blocking so we wait for the process to terminate
163 fconfigure $commfd -blocking 1
163 fconfigure $commfd -blocking 1
164 if {![catch {close $commfd} err]} {
164 if {![catch {close $commfd} err]} {
165 after idle finishcommits
165 after idle finishcommits
166 return
166 return
167 }
167 }
168 if {[string range $err 0 4] == "usage"} {
168 if {[string range $err 0 4] == "usage"} {
169 set err \
169 set err \
170 {Gitk: error reading commits: bad arguments to git-rev-list.
170 {Gitk: error reading commits: bad arguments to git-rev-list.
171 (Note: arguments to gitk are passed to git-rev-list
171 (Note: arguments to gitk are passed to git-rev-list
172 to allow selection of commits to be displayed.)}
172 to allow selection of commits to be displayed.)}
173 } else {
173 } else {
174 set err "Error reading commits: $err"
174 set err "Error reading commits: $err"
175 }
175 }
176 error_popup $err
176 error_popup $err
177 exit 1
177 exit 1
178 }
178 }
179 set start 0
179 set start 0
180 while 1 {
180 while 1 {
181 set i [string first "\0" $stuff $start]
181 set i [string first "\0" $stuff $start]
182 if {$i < 0} {
182 if {$i < 0} {
183 append leftover [string range $stuff $start end]
183 append leftover [string range $stuff $start end]
184 return
184 return
185 }
185 }
186 set cmit [string range $stuff $start [expr {$i - 1}]]
186 set cmit [string range $stuff $start [expr {$i - 1}]]
187 if {$start == 0} {
187 if {$start == 0} {
188 set cmit "$leftover$cmit"
188 set cmit "$leftover$cmit"
189 set leftover {}
189 set leftover {}
190 }
190 }
191 set start [expr {$i + 1}]
191 set start [expr {$i + 1}]
192 regsub -all "\r\n" $cmit "\n" cmit
192 regsub -all "\r\n" $cmit "\n" cmit
193 set j [string first "\n" $cmit]
193 set j [string first "\n" $cmit]
194 set ok 0
194 set ok 0
195 if {$j >= 0} {
195 if {$j >= 0} {
196 set ids [string range $cmit 0 [expr {$j - 1}]]
196 set ids [string range $cmit 0 [expr {$j - 1}]]
197 set ok 1
197 set ok 1
198 foreach id $ids {
198 foreach id $ids {
199 if {![regexp {^[0-9a-f]{12}$} $id]} {
199 if {![regexp {^[0-9a-f]{12}$} $id]} {
200 set ok 0
200 set ok 0
201 break
201 break
202 }
202 }
203 }
203 }
204 }
204 }
205 if {!$ok} {
205 if {!$ok} {
206 set shortcmit $cmit
206 set shortcmit $cmit
207 if {[string length $shortcmit] > 80} {
207 if {[string length $shortcmit] > 80} {
208 set shortcmit "[string range $shortcmit 0 80]..."
208 set shortcmit "[string range $shortcmit 0 80]..."
209 }
209 }
210 error_popup "Can't parse hg debug-rev-list output: {$shortcmit}"
210 error_popup "Can't parse hg debug-rev-list output: {$shortcmit}"
211 exit 1
211 exit 1
212 }
212 }
213 set id [lindex $ids 0]
213 set id [lindex $ids 0]
214 set olds [lrange $ids 1 end]
214 set olds [lrange $ids 1 end]
215 set cmit [string range $cmit [expr {$j + 1}] end]
215 set cmit [string range $cmit [expr {$j + 1}] end]
216 lappend commits $id
216 lappend commits $id
217 set commitlisted($id) 1
217 set commitlisted($id) 1
218 parsecommit $id $cmit 1 [lrange $ids 1 end]
218 parsecommit $id $cmit 1 [lrange $ids 1 end]
219 drawcommit $id
219 drawcommit $id
220 if {[clock clicks -milliseconds] >= $nextupdate} {
220 if {[clock clicks -milliseconds] >= $nextupdate} {
221 doupdate 1
221 doupdate 1
222 }
222 }
223 while {$redisplaying} {
223 while {$redisplaying} {
224 set redisplaying 0
224 set redisplaying 0
225 if {$stopped == 1} {
225 if {$stopped == 1} {
226 set stopped 0
226 set stopped 0
227 set phase "getcommits"
227 set phase "getcommits"
228 foreach id $commits {
228 foreach id $commits {
229 drawcommit $id
229 drawcommit $id
230 if {$stopped} break
230 if {$stopped} break
231 if {[clock clicks -milliseconds] >= $nextupdate} {
231 if {[clock clicks -milliseconds] >= $nextupdate} {
232 doupdate 1
232 doupdate 1
233 }
233 }
234 }
234 }
235 }
235 }
236 }
236 }
237 }
237 }
238 }
238 }
239
239
240 proc doupdate {reading} {
240 proc doupdate {reading} {
241 global commfd nextupdate numcommits ncmupdate
241 global commfd nextupdate numcommits ncmupdate
242
242
243 if {$reading} {
243 if {$reading} {
244 fileevent $commfd readable {}
244 fileevent $commfd readable {}
245 }
245 }
246 update
246 update
247 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
247 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
248 if {$numcommits < 100} {
248 if {$numcommits < 100} {
249 set ncmupdate [expr {$numcommits + 1}]
249 set ncmupdate [expr {$numcommits + 1}]
250 } elseif {$numcommits < 10000} {
250 } elseif {$numcommits < 10000} {
251 set ncmupdate [expr {$numcommits + 10}]
251 set ncmupdate [expr {$numcommits + 10}]
252 } else {
252 } else {
253 set ncmupdate [expr {$numcommits + 100}]
253 set ncmupdate [expr {$numcommits + 100}]
254 }
254 }
255 if {$reading} {
255 if {$reading} {
256 fileevent $commfd readable [list getcommitlines $commfd]
256 fileevent $commfd readable [list getcommitlines $commfd]
257 }
257 }
258 }
258 }
259
259
260 proc readcommit {id} {
260 proc readcommit {id} {
261 global env
261 global env
262 if [catch {set contents [exec $env(HG) --config ui.report_untrusted=false debug-cat-file commit $id]}] return
262 if [catch {set contents [exec $env(HG) --config ui.report_untrusted=false debug-cat-file commit $id]}] return
263 parsecommit $id $contents 0 {}
263 parsecommit $id $contents 0 {}
264 }
264 }
265
265
266 proc parsecommit {id contents listed olds} {
266 proc parsecommit {id contents listed olds} {
267 global commitinfo children nchildren parents nparents cdate ncleft
267 global commitinfo children nchildren parents nparents cdate ncleft
268 global firstparents
268 global firstparents
269
269
270 set inhdr 1
270 set inhdr 1
271 set comment {}
271 set comment {}
272 set headline {}
272 set headline {}
273 set auname {}
273 set auname {}
274 set audate {}
274 set audate {}
275 set comname {}
275 set comname {}
276 set comdate {}
276 set comdate {}
277 set rev {}
277 set rev {}
278 set branch {}
278 set branch {}
279 if {![info exists nchildren($id)]} {
279 if {![info exists nchildren($id)]} {
280 set children($id) {}
280 set children($id) {}
281 set nchildren($id) 0
281 set nchildren($id) 0
282 set ncleft($id) 0
282 set ncleft($id) 0
283 }
283 }
284 set parents($id) $olds
284 set parents($id) $olds
285 set nparents($id) [llength $olds]
285 set nparents($id) [llength $olds]
286 foreach p $olds {
286 foreach p $olds {
287 if {![info exists nchildren($p)]} {
287 if {![info exists nchildren($p)]} {
288 set children($p) [list $id]
288 set children($p) [list $id]
289 set nchildren($p) 1
289 set nchildren($p) 1
290 set ncleft($p) 1
290 set ncleft($p) 1
291 } elseif {[lsearch -exact $children($p) $id] < 0} {
291 } elseif {[lsearch -exact $children($p) $id] < 0} {
292 lappend children($p) $id
292 lappend children($p) $id
293 incr nchildren($p)
293 incr nchildren($p)
294 incr ncleft($p)
294 incr ncleft($p)
295 }
295 }
296 }
296 }
297 regsub -all "\r\n" $contents "\n" contents
297 regsub -all "\r\n" $contents "\n" contents
298 foreach line [split $contents "\n"] {
298 foreach line [split $contents "\n"] {
299 if {$inhdr} {
299 if {$inhdr} {
300 set line [split $line]
300 set line [split $line]
301 if {$line == {}} {
301 if {$line == {}} {
302 set inhdr 0
302 set inhdr 0
303 } else {
303 } else {
304 set tag [lindex $line 0]
304 set tag [lindex $line 0]
305 if {$tag == "author"} {
305 if {$tag == "author"} {
306 set x [expr {[llength $line] - 2}]
306 set x [expr {[llength $line] - 2}]
307 set audate [lindex $line $x]
307 set audate [lindex $line $x]
308 set auname [join [lrange $line 1 [expr {$x - 1}]]]
308 set auname [join [lrange $line 1 [expr {$x - 1}]]]
309 } elseif {$tag == "committer"} {
309 } elseif {$tag == "committer"} {
310 set x [expr {[llength $line] - 2}]
310 set x [expr {[llength $line] - 2}]
311 set comdate [lindex $line $x]
311 set comdate [lindex $line $x]
312 set comname [join [lrange $line 1 [expr {$x - 1}]]]
312 set comname [join [lrange $line 1 [expr {$x - 1}]]]
313 } elseif {$tag == "revision"} {
313 } elseif {$tag == "revision"} {
314 set rev [lindex $line 1]
314 set rev [lindex $line 1]
315 } elseif {$tag == "branch"} {
315 } elseif {$tag == "branch"} {
316 set branch [join [lrange $line 1 end]]
316 set branch [join [lrange $line 1 end]]
317 }
317 }
318 }
318 }
319 } else {
319 } else {
320 if {$comment == {}} {
320 if {$comment == {}} {
321 set headline [string trim $line]
321 set headline [string trim $line]
322 } else {
322 } else {
323 append comment "\n"
323 append comment "\n"
324 }
324 }
325 if {!$listed} {
325 if {!$listed} {
326 # git-rev-list indents the comment by 4 spaces;
326 # git-rev-list indents the comment by 4 spaces;
327 # if we got this via git-cat-file, add the indentation
327 # if we got this via git-cat-file, add the indentation
328 append comment " "
328 append comment " "
329 }
329 }
330 append comment $line
330 append comment $line
331 }
331 }
332 }
332 }
333 if {$audate != {}} {
333 if {$audate != {}} {
334 set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
334 set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
335 }
335 }
336 if {$comdate != {}} {
336 if {$comdate != {}} {
337 set cdate($id) $comdate
337 set cdate($id) $comdate
338 set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
338 set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
339 }
339 }
340 set commitinfo($id) [list $headline $auname $audate \
340 set commitinfo($id) [list $headline $auname $audate \
341 $comname $comdate $comment $rev $branch]
341 $comname $comdate $comment $rev $branch]
342
342
343 if {[info exists firstparents]} {
343 if {[info exists firstparents]} {
344 set i [lsearch $firstparents $id]
344 set i [lsearch $firstparents $id]
345 if {$i != -1} {
345 if {$i != -1} {
346 # remove the parent from firstparents, possible building
346 # remove the parent from firstparents, possible building
347 # an empty list
347 # an empty list
348 set firstparents [concat \
348 set firstparents [concat \
349 [lrange $firstparents 0 [expr $i - 1]] \
349 [lrange $firstparents 0 [expr $i - 1]] \
350 [lrange $firstparents [expr $i + 1] end]]
350 [lrange $firstparents [expr $i + 1] end]]
351 if {$firstparents eq {}} {
351 if {$firstparents eq {}} {
352 # we have found all parents of the first changeset
352 # we have found all parents of the first changeset
353 # which means that we can safely select the first line
353 # which means that we can safely select the first line
354 after idle {
354 after idle {
355 selectline 0 0
355 selectline 0 0
356 }
356 }
357 }
357 }
358 }
358 }
359 } else {
359 } else {
360 # this is the first changeset, save the parents
360 # this is the first changeset, save the parents
361 set firstparents $olds
361 set firstparents $olds
362 if {$firstparents eq {}} {
362 if {$firstparents eq {}} {
363 # a repository with a single changeset
363 # a repository with a single changeset
364 after idle {
364 after idle {
365 selectline 0 0
365 selectline 0 0
366 }
366 }
367 }
367 }
368 }
368 }
369 }
369 }
370
370
371 proc readrefs {} {
371 proc readrefs {} {
372 global tagids idtags headids idheads tagcontents env curid
372 global tagids idtags headids idheads tagcontents env curid
373
373
374 set status [catch {exec $env(HG) --config ui.report_untrusted=false id} curid]
374 set status [catch {exec $env(HG) --config ui.report_untrusted=false id} curid]
375 if { $status != 0 } {
375 if { $status != 0 } {
376 puts $::errorInfo
376 puts $::errorInfo
377 if { ![string equal $::errorCode NONE] } {
377 if { ![string equal $::errorCode NONE] } {
378 exit 2
378 exit 2
379 }
379 }
380 }
380 }
381 regexp -- {[[:xdigit:]]+} $curid curid
381 regexp -- {[[:xdigit:]]+} $curid curid
382
382
383 set status [catch {exec $env(HG) --config ui.report_untrusted=false tags} tags]
383 set status [catch {exec $env(HG) --config ui.report_untrusted=false tags} tags]
384 if { $status != 0 } {
384 if { $status != 0 } {
385 puts $::errorInfo
385 puts $::errorInfo
386 if { ![string equal $::errorCode NONE] } {
386 if { ![string equal $::errorCode NONE] } {
387 exit 2
387 exit 2
388 }
388 }
389 }
389 }
390 regsub -all "\r\n" $tags "\n" tags
390 regsub -all "\r\n" $tags "\n" tags
391
391
392 set lines [split $tags "\n"]
392 set lines [split $tags "\n"]
393 foreach f $lines {
393 foreach f $lines {
394 regexp {(\S+)$} $f full
394 regexp {(\S+)$} $f full
395 regsub {\s+(\S+)$} $f "" direct
395 regsub {\s+(\S+)$} $f "" direct
396 set sha [split $full ':']
396 set sha [split $full ':']
397 set tag [lindex $sha 1]
397 set tag [lindex $sha 1]
398 lappend tagids($direct) $tag
398 lappend tagids($direct) $tag
399 lappend idtags($tag) $direct
399 lappend idtags($tag) $direct
400 }
400 }
401
401
402 set status [catch {exec $env(HG) --config ui.report_untrusted=false heads} heads]
402 set status [catch {exec $env(HG) --config ui.report_untrusted=false heads} heads]
403 if { $status != 0 } {
403 if { $status != 0 } {
404 puts $::errorInfo
404 puts $::errorInfo
405 if { ![string equal $::errorCode NONE] } {
405 if { ![string equal $::errorCode NONE] } {
406 exit 2
406 exit 2
407 }
407 }
408 }
408 }
409 regsub -all "\r\n" $heads "\n" heads
409 regsub -all "\r\n" $heads "\n" heads
410
410
411 set lines [split $heads "\n"]
411 set lines [split $heads "\n"]
412 foreach f $lines {
412 foreach f $lines {
413 set match ""
413 set match ""
414 regexp {changeset:\s+(\S+):(\S+)$} $f match id sha
414 regexp {changeset:\s+(\S+):(\S+)$} $f match id sha
415 if {$match != ""} {
415 if {$match != ""} {
416 lappend idheads($sha) $id
416 lappend idheads($sha) $id
417 }
417 }
418 }
418 }
419
419
420 }
420 }
421
421
422 proc readotherrefs {base dname excl} {
422 proc readotherrefs {base dname excl} {
423 global otherrefids idotherrefs
423 global otherrefids idotherrefs
424
424
425 set git [gitdir]
425 set git [gitdir]
426 set files [glob -nocomplain -types f [file join $git $base *]]
426 set files [glob -nocomplain -types f [file join $git $base *]]
427 foreach f $files {
427 foreach f $files {
428 catch {
428 catch {
429 set fd [open $f r]
429 set fd [open $f r]
430 set line [read $fd 40]
430 set line [read $fd 40]
431 if {[regexp {^[0-9a-f]{12}} $line id]} {
431 if {[regexp {^[0-9a-f]{12}} $line id]} {
432 set name "$dname[file tail $f]"
432 set name "$dname[file tail $f]"
433 set otherrefids($name) $id
433 set otherrefids($name) $id
434 lappend idotherrefs($id) $name
434 lappend idotherrefs($id) $name
435 }
435 }
436 close $fd
436 close $fd
437 }
437 }
438 }
438 }
439 set dirs [glob -nocomplain -types d [file join $git $base *]]
439 set dirs [glob -nocomplain -types d [file join $git $base *]]
440 foreach d $dirs {
440 foreach d $dirs {
441 set dir [file tail $d]
441 set dir [file tail $d]
442 if {[lsearch -exact $excl $dir] >= 0} continue
442 if {[lsearch -exact $excl $dir] >= 0} continue
443 readotherrefs [file join $base $dir] "$dname$dir/" {}
443 readotherrefs [file join $base $dir] "$dname$dir/" {}
444 }
444 }
445 }
445 }
446
446
447 proc allcansmousewheel {delta} {
447 proc allcansmousewheel {delta} {
448 set delta [expr -5*(int($delta)/abs($delta))]
448 set delta [expr -5*(int($delta)/abs($delta))]
449 allcanvs yview scroll $delta units
449 allcanvs yview scroll $delta units
450 }
450 }
451
451
452 proc error_popup msg {
452 proc error_popup msg {
453 set w .error
453 set w .error
454 toplevel $w
454 toplevel $w
455 wm transient $w .
455 wm transient $w .
456 message $w.m -text $msg -justify center -aspect 400
456 message $w.m -text $msg -justify center -aspect 400
457 pack $w.m -side top -fill x -padx 20 -pady 20
457 pack $w.m -side top -fill x -padx 20 -pady 20
458 button $w.ok -text OK -command "destroy $w"
458 button $w.ok -text OK -command "destroy $w"
459 pack $w.ok -side bottom -fill x
459 pack $w.ok -side bottom -fill x
460 bind $w <Visibility> "grab $w; focus $w"
460 bind $w <Visibility> "grab $w; focus $w"
461 tkwait window $w
461 tkwait window $w
462 }
462 }
463
463
464 proc makewindow {} {
464 proc makewindow {} {
465 global canv canv2 canv3 linespc charspc ctext cflist textfont
465 global canv canv2 canv3 linespc charspc ctext cflist textfont
466 global findtype findtypemenu findloc findstring fstring geometry
466 global findtype findtypemenu findloc findstring fstring geometry
467 global entries sha1entry sha1string sha1but
467 global entries sha1entry sha1string sha1but
468 global maincursor textcursor curtextcursor
468 global maincursor textcursor curtextcursor
469 global rowctxmenu gaudydiff mergemax
469 global rowctxmenu gaudydiff mergemax
470 global hgvdiff bgcolor fgcolor diffremcolor diffaddcolor diffmerge1color
470 global hgvdiff bgcolor fgcolor diffremcolor diffaddcolor diffmerge1color
471 global diffmerge2color hunksepcolor
471 global diffmerge2color hunksepcolor
472 global posx posy
472 global posx posy
473
473
474 if {[info exists posx]} {
474 if {[info exists posx]} {
475 wm geometry . +$posx+$posy
475 wm geometry . +$posx+$posy
476 }
476 }
477
477
478 menu .bar
478 menu .bar
479 .bar add cascade -label "File" -menu .bar.file
479 .bar add cascade -label "File" -menu .bar.file
480 menu .bar.file
480 menu .bar.file
481 .bar.file add command -label "Reread references" -command rereadrefs
481 .bar.file add command -label "Reread references" -command rereadrefs
482 .bar.file add command -label "Quit" -command doquit
482 .bar.file add command -label "Quit" -command doquit
483 menu .bar.help
483 menu .bar.help
484 .bar add cascade -label "Help" -menu .bar.help
484 .bar add cascade -label "Help" -menu .bar.help
485 .bar.help add command -label "About gitk" -command about
485 .bar.help add command -label "About hgk" -command about
486 . configure -menu .bar
486 . configure -menu .bar
487
487
488 if {![info exists geometry(canv1)]} {
488 if {![info exists geometry(canv1)]} {
489 set geometry(canv1) [expr 45 * $charspc]
489 set geometry(canv1) [expr 45 * $charspc]
490 set geometry(canv2) [expr 30 * $charspc]
490 set geometry(canv2) [expr 30 * $charspc]
491 set geometry(canv3) [expr 15 * $charspc]
491 set geometry(canv3) [expr 15 * $charspc]
492 set geometry(canvh) [expr 25 * $linespc + 4]
492 set geometry(canvh) [expr 25 * $linespc + 4]
493 set geometry(ctextw) 80
493 set geometry(ctextw) 80
494 set geometry(ctexth) 30
494 set geometry(ctexth) 30
495 set geometry(cflistw) 30
495 set geometry(cflistw) 30
496 }
496 }
497 panedwindow .ctop -orient vertical
497 panedwindow .ctop -orient vertical
498 if {[info exists geometry(width)]} {
498 if {[info exists geometry(width)]} {
499 .ctop conf -width $geometry(width) -height $geometry(height)
499 .ctop conf -width $geometry(width) -height $geometry(height)
500 set texth [expr {$geometry(height) - $geometry(canvh) - 56}]
500 set texth [expr {$geometry(height) - $geometry(canvh) - 56}]
501 set geometry(ctexth) [expr {($texth - 8) /
501 set geometry(ctexth) [expr {($texth - 8) /
502 [font metrics $textfont -linespace]}]
502 [font metrics $textfont -linespace]}]
503 }
503 }
504 frame .ctop.top
504 frame .ctop.top
505 frame .ctop.top.bar
505 frame .ctop.top.bar
506 pack .ctop.top.bar -side bottom -fill x
506 pack .ctop.top.bar -side bottom -fill x
507 set cscroll .ctop.top.csb
507 set cscroll .ctop.top.csb
508 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
508 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
509 pack $cscroll -side right -fill y
509 pack $cscroll -side right -fill y
510 panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
510 panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
511 pack .ctop.top.clist -side top -fill both -expand 1
511 pack .ctop.top.clist -side top -fill both -expand 1
512 .ctop add .ctop.top
512 .ctop add .ctop.top
513 set canv .ctop.top.clist.canv
513 set canv .ctop.top.clist.canv
514 canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
514 canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
515 -bg $bgcolor -bd 0 \
515 -bg $bgcolor -bd 0 \
516 -yscrollincr $linespc -yscrollcommand "$cscroll set" -selectbackground grey
516 -yscrollincr $linespc -yscrollcommand "$cscroll set" -selectbackground grey
517 .ctop.top.clist add $canv
517 .ctop.top.clist add $canv
518 set canv2 .ctop.top.clist.canv2
518 set canv2 .ctop.top.clist.canv2
519 canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
519 canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
520 -bg $bgcolor -bd 0 -yscrollincr $linespc -selectbackground grey
520 -bg $bgcolor -bd 0 -yscrollincr $linespc -selectbackground grey
521 .ctop.top.clist add $canv2
521 .ctop.top.clist add $canv2
522 set canv3 .ctop.top.clist.canv3
522 set canv3 .ctop.top.clist.canv3
523 canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
523 canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
524 -bg $bgcolor -bd 0 -yscrollincr $linespc -selectbackground grey
524 -bg $bgcolor -bd 0 -yscrollincr $linespc -selectbackground grey
525 .ctop.top.clist add $canv3
525 .ctop.top.clist add $canv3
526 bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
526 bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
527
527
528 set sha1entry .ctop.top.bar.sha1
528 set sha1entry .ctop.top.bar.sha1
529 set entries $sha1entry
529 set entries $sha1entry
530 set sha1but .ctop.top.bar.sha1label
530 set sha1but .ctop.top.bar.sha1label
531 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
531 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
532 -command gotocommit -width 8
532 -command gotocommit -width 8
533 $sha1but conf -disabledforeground [$sha1but cget -foreground]
533 $sha1but conf -disabledforeground [$sha1but cget -foreground]
534 pack .ctop.top.bar.sha1label -side left
534 pack .ctop.top.bar.sha1label -side left
535 entry $sha1entry -width 40 -font $textfont -textvariable sha1string
535 entry $sha1entry -width 40 -font $textfont -textvariable sha1string
536 trace add variable sha1string write sha1change
536 trace add variable sha1string write sha1change
537 pack $sha1entry -side left -pady 2
537 pack $sha1entry -side left -pady 2
538
538
539 image create bitmap bm-left -data {
539 image create bitmap bm-left -data {
540 #define left_width 16
540 #define left_width 16
541 #define left_height 16
541 #define left_height 16
542 static unsigned char left_bits[] = {
542 static unsigned char left_bits[] = {
543 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
543 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
544 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
544 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
545 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
545 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
546 }
546 }
547 image create bitmap bm-right -data {
547 image create bitmap bm-right -data {
548 #define right_width 16
548 #define right_width 16
549 #define right_height 16
549 #define right_height 16
550 static unsigned char right_bits[] = {
550 static unsigned char right_bits[] = {
551 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
551 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
552 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
552 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
553 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
553 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
554 }
554 }
555 button .ctop.top.bar.leftbut -image bm-left -command goback \
555 button .ctop.top.bar.leftbut -image bm-left -command goback \
556 -state disabled -width 26
556 -state disabled -width 26
557 pack .ctop.top.bar.leftbut -side left -fill y
557 pack .ctop.top.bar.leftbut -side left -fill y
558 button .ctop.top.bar.rightbut -image bm-right -command goforw \
558 button .ctop.top.bar.rightbut -image bm-right -command goforw \
559 -state disabled -width 26
559 -state disabled -width 26
560 pack .ctop.top.bar.rightbut -side left -fill y
560 pack .ctop.top.bar.rightbut -side left -fill y
561
561
562 button .ctop.top.bar.findbut -text "Find" -command dofind
562 button .ctop.top.bar.findbut -text "Find" -command dofind
563 pack .ctop.top.bar.findbut -side left
563 pack .ctop.top.bar.findbut -side left
564 set findstring {}
564 set findstring {}
565 set fstring .ctop.top.bar.findstring
565 set fstring .ctop.top.bar.findstring
566 lappend entries $fstring
566 lappend entries $fstring
567 entry $fstring -width 30 -font $textfont -textvariable findstring
567 entry $fstring -width 30 -font $textfont -textvariable findstring
568 pack $fstring -side left -expand 1 -fill x
568 pack $fstring -side left -expand 1 -fill x
569 set findtype Exact
569 set findtype Exact
570 set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \
570 set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \
571 findtype Exact IgnCase Regexp]
571 findtype Exact IgnCase Regexp]
572 set findloc "All fields"
572 set findloc "All fields"
573 tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
573 tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
574 Comments Author Committer Files Pickaxe
574 Comments Author Committer Files Pickaxe
575 pack .ctop.top.bar.findloc -side right
575 pack .ctop.top.bar.findloc -side right
576 pack .ctop.top.bar.findtype -side right
576 pack .ctop.top.bar.findtype -side right
577 # for making sure type==Exact whenever loc==Pickaxe
577 # for making sure type==Exact whenever loc==Pickaxe
578 trace add variable findloc write findlocchange
578 trace add variable findloc write findlocchange
579
579
580 panedwindow .ctop.cdet -orient horizontal
580 panedwindow .ctop.cdet -orient horizontal
581 .ctop add .ctop.cdet
581 .ctop add .ctop.cdet
582 frame .ctop.cdet.left
582 frame .ctop.cdet.left
583 set ctext .ctop.cdet.left.ctext
583 set ctext .ctop.cdet.left.ctext
584 text $ctext -fg $fgcolor -bg $bgcolor -state disabled -font $textfont \
584 text $ctext -fg $fgcolor -bg $bgcolor -state disabled -font $textfont \
585 -width $geometry(ctextw) -height $geometry(ctexth) \
585 -width $geometry(ctextw) -height $geometry(ctexth) \
586 -yscrollcommand ".ctop.cdet.left.sb set" \
586 -yscrollcommand ".ctop.cdet.left.sb set" \
587 -xscrollcommand ".ctop.cdet.left.hb set" -wrap none
587 -xscrollcommand ".ctop.cdet.left.hb set" -wrap none
588 scrollbar .ctop.cdet.left.sb -command "$ctext yview"
588 scrollbar .ctop.cdet.left.sb -command "$ctext yview"
589 scrollbar .ctop.cdet.left.hb -orient horizontal -command "$ctext xview"
589 scrollbar .ctop.cdet.left.hb -orient horizontal -command "$ctext xview"
590 pack .ctop.cdet.left.sb -side right -fill y
590 pack .ctop.cdet.left.sb -side right -fill y
591 pack .ctop.cdet.left.hb -side bottom -fill x
591 pack .ctop.cdet.left.hb -side bottom -fill x
592 pack $ctext -side left -fill both -expand 1
592 pack $ctext -side left -fill both -expand 1
593 .ctop.cdet add .ctop.cdet.left
593 .ctop.cdet add .ctop.cdet.left
594
594
595 $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
595 $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
596 if {$gaudydiff} {
596 if {$gaudydiff} {
597 $ctext tag conf hunksep -back blue -fore white
597 $ctext tag conf hunksep -back blue -fore white
598 $ctext tag conf d0 -back "#ff8080"
598 $ctext tag conf d0 -back "#ff8080"
599 $ctext tag conf d1 -back green
599 $ctext tag conf d1 -back green
600 } else {
600 } else {
601 $ctext tag conf hunksep -fore $hunksepcolor
601 $ctext tag conf hunksep -fore $hunksepcolor
602 $ctext tag conf d0 -fore $diffremcolor
602 $ctext tag conf d0 -fore $diffremcolor
603 $ctext tag conf d1 -fore $diffaddcolor
603 $ctext tag conf d1 -fore $diffaddcolor
604
604
605 # The mX colours seem to be used in merge changesets, where m0
605 # The mX colours seem to be used in merge changesets, where m0
606 # is first parent, m1 is second parent and so on. Git can have
606 # is first parent, m1 is second parent and so on. Git can have
607 # several parents, Hg cannot, so I think the m2..mmax would be
607 # several parents, Hg cannot, so I think the m2..mmax would be
608 # unused.
608 # unused.
609 $ctext tag conf m0 -fore $diffmerge1color
609 $ctext tag conf m0 -fore $diffmerge1color
610 $ctext tag conf m1 -fore $diffmerge2color
610 $ctext tag conf m1 -fore $diffmerge2color
611 $ctext tag conf m2 -fore green
611 $ctext tag conf m2 -fore green
612 $ctext tag conf m3 -fore purple
612 $ctext tag conf m3 -fore purple
613 $ctext tag conf m4 -fore brown
613 $ctext tag conf m4 -fore brown
614 $ctext tag conf mmax -fore darkgrey
614 $ctext tag conf mmax -fore darkgrey
615 set mergemax 5
615 set mergemax 5
616 $ctext tag conf mresult -font [concat $textfont bold]
616 $ctext tag conf mresult -font [concat $textfont bold]
617 $ctext tag conf msep -font [concat $textfont bold]
617 $ctext tag conf msep -font [concat $textfont bold]
618 $ctext tag conf found -back yellow
618 $ctext tag conf found -back yellow
619 }
619 }
620
620
621 frame .ctop.cdet.right
621 frame .ctop.cdet.right
622 set cflist .ctop.cdet.right.cfiles
622 set cflist .ctop.cdet.right.cfiles
623 listbox $cflist -fg $fgcolor -bg $bgcolor \
623 listbox $cflist -fg $fgcolor -bg $bgcolor \
624 -selectmode extended -width $geometry(cflistw) \
624 -selectmode extended -width $geometry(cflistw) \
625 -yscrollcommand ".ctop.cdet.right.sb set"
625 -yscrollcommand ".ctop.cdet.right.sb set"
626 scrollbar .ctop.cdet.right.sb -command "$cflist yview"
626 scrollbar .ctop.cdet.right.sb -command "$cflist yview"
627 pack .ctop.cdet.right.sb -side right -fill y
627 pack .ctop.cdet.right.sb -side right -fill y
628 pack $cflist -side left -fill both -expand 1
628 pack $cflist -side left -fill both -expand 1
629 .ctop.cdet add .ctop.cdet.right
629 .ctop.cdet add .ctop.cdet.right
630 bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
630 bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
631
631
632 pack .ctop -side top -fill both -expand 1
632 pack .ctop -side top -fill both -expand 1
633
633
634 bindall <1> {selcanvline %W %x %y}
634 bindall <1> {selcanvline %W %x %y}
635 #bindall <B1-Motion> {selcanvline %W %x %y}
635 #bindall <B1-Motion> {selcanvline %W %x %y}
636 bindall <MouseWheel> "allcansmousewheel %D"
636 bindall <MouseWheel> "allcansmousewheel %D"
637 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
637 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
638 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
638 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
639 bindall <2> "allcanvs scan mark 0 %y"
639 bindall <2> "allcanvs scan mark 0 %y"
640 bindall <B2-Motion> "allcanvs scan dragto 0 %y"
640 bindall <B2-Motion> "allcanvs scan dragto 0 %y"
641 bind . <Key-Up> "selnextline -1"
641 bind . <Key-Up> "selnextline -1"
642 bind . <Key-Down> "selnextline 1"
642 bind . <Key-Down> "selnextline 1"
643 bind . <Key-Prior> "allcanvs yview scroll -1 pages"
643 bind . <Key-Prior> "allcanvs yview scroll -1 pages"
644 bind . <Key-Next> "allcanvs yview scroll 1 pages"
644 bind . <Key-Next> "allcanvs yview scroll 1 pages"
645 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
645 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
646 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
646 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
647 bindkey <Key-space> "$ctext yview scroll 1 pages"
647 bindkey <Key-space> "$ctext yview scroll 1 pages"
648 bindkey p "selnextline -1"
648 bindkey p "selnextline -1"
649 bindkey n "selnextline 1"
649 bindkey n "selnextline 1"
650 bindkey b "$ctext yview scroll -1 pages"
650 bindkey b "$ctext yview scroll -1 pages"
651 bindkey d "$ctext yview scroll 18 units"
651 bindkey d "$ctext yview scroll 18 units"
652 bindkey u "$ctext yview scroll -18 units"
652 bindkey u "$ctext yview scroll -18 units"
653 bindkey / {findnext 1}
653 bindkey / {findnext 1}
654 bindkey <Key-Return> {findnext 0}
654 bindkey <Key-Return> {findnext 0}
655 bindkey ? findprev
655 bindkey ? findprev
656 bindkey f nextfile
656 bindkey f nextfile
657 bind . <Control-q> doquit
657 bind . <Control-q> doquit
658 bind . <Control-w> doquit
658 bind . <Control-w> doquit
659 bind . <Control-f> dofind
659 bind . <Control-f> dofind
660 bind . <Control-g> {findnext 0}
660 bind . <Control-g> {findnext 0}
661 bind . <Control-r> findprev
661 bind . <Control-r> findprev
662 bind . <Control-equal> {incrfont 1}
662 bind . <Control-equal> {incrfont 1}
663 bind . <Control-KP_Add> {incrfont 1}
663 bind . <Control-KP_Add> {incrfont 1}
664 bind . <Control-minus> {incrfont -1}
664 bind . <Control-minus> {incrfont -1}
665 bind . <Control-KP_Subtract> {incrfont -1}
665 bind . <Control-KP_Subtract> {incrfont -1}
666 bind $cflist <<ListboxSelect>> listboxsel
666 bind $cflist <<ListboxSelect>> listboxsel
667 bind . <Destroy> {savestuff %W}
667 bind . <Destroy> {savestuff %W}
668 bind . <Button-1> "click %W"
668 bind . <Button-1> "click %W"
669 bind $fstring <Key-Return> dofind
669 bind $fstring <Key-Return> dofind
670 bind $sha1entry <Key-Return> gotocommit
670 bind $sha1entry <Key-Return> gotocommit
671 bind $sha1entry <<PasteSelection>> clearsha1
671 bind $sha1entry <<PasteSelection>> clearsha1
672
672
673 set maincursor [. cget -cursor]
673 set maincursor [. cget -cursor]
674 set textcursor [$ctext cget -cursor]
674 set textcursor [$ctext cget -cursor]
675 set curtextcursor $textcursor
675 set curtextcursor $textcursor
676
676
677 set rowctxmenu .rowctxmenu
677 set rowctxmenu .rowctxmenu
678 menu $rowctxmenu -tearoff 0
678 menu $rowctxmenu -tearoff 0
679 $rowctxmenu add command -label "Diff this -> selected" \
679 $rowctxmenu add command -label "Diff this -> selected" \
680 -command {diffvssel 0}
680 -command {diffvssel 0}
681 $rowctxmenu add command -label "Diff selected -> this" \
681 $rowctxmenu add command -label "Diff selected -> this" \
682 -command {diffvssel 1}
682 -command {diffvssel 1}
683 $rowctxmenu add command -label "Make patch" -command mkpatch
683 $rowctxmenu add command -label "Make patch" -command mkpatch
684 $rowctxmenu add command -label "Create tag" -command mktag
684 $rowctxmenu add command -label "Create tag" -command mktag
685 $rowctxmenu add command -label "Write commit to file" -command writecommit
685 $rowctxmenu add command -label "Write commit to file" -command writecommit
686 if { $hgvdiff ne "" } {
686 if { $hgvdiff ne "" } {
687 $rowctxmenu add command -label "Visual diff with parent" \
687 $rowctxmenu add command -label "Visual diff with parent" \
688 -command {vdiff 1}
688 -command {vdiff 1}
689 $rowctxmenu add command -label "Visual diff with selected" \
689 $rowctxmenu add command -label "Visual diff with selected" \
690 -command {vdiff 0}
690 -command {vdiff 0}
691 }
691 }
692 }
692 }
693
693
694 # when we make a key binding for the toplevel, make sure
694 # when we make a key binding for the toplevel, make sure
695 # it doesn't get triggered when that key is pressed in the
695 # it doesn't get triggered when that key is pressed in the
696 # find string entry widget.
696 # find string entry widget.
697 proc bindkey {ev script} {
697 proc bindkey {ev script} {
698 global entries
698 global entries
699 bind . $ev $script
699 bind . $ev $script
700 set escript [bind Entry $ev]
700 set escript [bind Entry $ev]
701 if {$escript == {}} {
701 if {$escript == {}} {
702 set escript [bind Entry <Key>]
702 set escript [bind Entry <Key>]
703 }
703 }
704 foreach e $entries {
704 foreach e $entries {
705 bind $e $ev "$escript; break"
705 bind $e $ev "$escript; break"
706 }
706 }
707 }
707 }
708
708
709 # set the focus back to the toplevel for any click outside
709 # set the focus back to the toplevel for any click outside
710 # the entry widgets
710 # the entry widgets
711 proc click {w} {
711 proc click {w} {
712 global entries
712 global entries
713 foreach e $entries {
713 foreach e $entries {
714 if {$w == $e} return
714 if {$w == $e} return
715 }
715 }
716 focus .
716 focus .
717 }
717 }
718
718
719 proc savestuff {w} {
719 proc savestuff {w} {
720 global canv canv2 canv3 ctext cflist mainfont textfont
720 global canv canv2 canv3 ctext cflist mainfont textfont
721 global stuffsaved findmergefiles gaudydiff maxgraphpct
721 global stuffsaved findmergefiles gaudydiff maxgraphpct
722 global maxwidth authorcolors curidfont bgcolor fgcolor
722 global maxwidth authorcolors curidfont bgcolor fgcolor
723 global diffremcolor diffaddcolor hunksepcolor
723 global diffremcolor diffaddcolor hunksepcolor
724 global diffmerge1color diffmerge2color
724 global diffmerge1color diffmerge2color
725
725
726 if {$stuffsaved} return
726 if {$stuffsaved} return
727 if {![winfo viewable .]} return
727 if {![winfo viewable .]} return
728 catch {
728 catch {
729 set f [open "~/.hgk-new" w]
729 set f [open "~/.hgk-new" w]
730 puts $f [list set mainfont $mainfont]
730 puts $f [list set mainfont $mainfont]
731 puts $f [list set curidfont $curidfont]
731 puts $f [list set curidfont $curidfont]
732 puts $f [list set textfont $textfont]
732 puts $f [list set textfont $textfont]
733 puts $f [list set findmergefiles $findmergefiles]
733 puts $f [list set findmergefiles $findmergefiles]
734 puts $f [list set gaudydiff $gaudydiff]
734 puts $f [list set gaudydiff $gaudydiff]
735 puts $f [list set maxgraphpct $maxgraphpct]
735 puts $f [list set maxgraphpct $maxgraphpct]
736 puts $f [list set maxwidth $maxwidth]
736 puts $f [list set maxwidth $maxwidth]
737 puts $f "set geometry(width) [winfo width .ctop]"
737 puts $f "set geometry(width) [winfo width .ctop]"
738 puts $f "set geometry(height) [winfo height .ctop]"
738 puts $f "set geometry(height) [winfo height .ctop]"
739 puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
739 puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
740 puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
740 puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
741 puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
741 puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
742 puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
742 puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
743 set wid [expr {([winfo width $ctext] - 8) \
743 set wid [expr {([winfo width $ctext] - 8) \
744 / [font measure $textfont "0"]}]
744 / [font measure $textfont "0"]}]
745 puts $f "set geometry(ctextw) $wid"
745 puts $f "set geometry(ctextw) $wid"
746 set wid [expr {([winfo width $cflist] - 11) \
746 set wid [expr {([winfo width $cflist] - 11) \
747 / [font measure [$cflist cget -font] "0"]}]
747 / [font measure [$cflist cget -font] "0"]}]
748 puts $f "set geometry(cflistw) $wid"
748 puts $f "set geometry(cflistw) $wid"
749 puts $f "#"
749 puts $f "#"
750 puts $f "# main window position:"
750 puts $f "# main window position:"
751 puts $f "set posx [winfo x .]"
751 puts $f "set posx [winfo x .]"
752 puts $f "set posy [winfo y .]"
752 puts $f "set posy [winfo y .]"
753 puts $f "#"
753 puts $f "#"
754 puts $f "# authorcolors format:"
754 puts $f "# authorcolors format:"
755 puts $f "#"
755 puts $f "#"
756 puts $f "# zero or more sublists of"
756 puts $f "# zero or more sublists of"
757 puts $f "#"
757 puts $f "#"
758 puts $f "# { regex color }"
758 puts $f "# { regex color }"
759 puts $f "#"
759 puts $f "#"
760 puts $f "# followed by a list of colors"
760 puts $f "# followed by a list of colors"
761 puts $f "#"
761 puts $f "#"
762 puts $f "# If the commit author matches a regex in a sublist,"
762 puts $f "# If the commit author matches a regex in a sublist,"
763 puts $f "# the commit will be colored by that color"
763 puts $f "# the commit will be colored by that color"
764 puts $f "# otherwise the next unused entry from the list of colors"
764 puts $f "# otherwise the next unused entry from the list of colors"
765 puts $f "# will be assigned to this commit and also all other commits"
765 puts $f "# will be assigned to this commit and also all other commits"
766 puts $f "# of the same author. When the list of colors is exhausted,"
766 puts $f "# of the same author. When the list of colors is exhausted,"
767 puts $f "# the last entry will be reused."
767 puts $f "# the last entry will be reused."
768 puts $f "#"
768 puts $f "#"
769 puts $f "set authorcolors {$authorcolors}"
769 puts $f "set authorcolors {$authorcolors}"
770 puts $f "#"
770 puts $f "#"
771 puts $f "# The background color in the text windows"
771 puts $f "# The background color in the text windows"
772 puts $f "set bgcolor $bgcolor"
772 puts $f "set bgcolor $bgcolor"
773 puts $f "#"
773 puts $f "#"
774 puts $f "# The text color used in the diff and file list view"
774 puts $f "# The text color used in the diff and file list view"
775 puts $f "set fgcolor $fgcolor"
775 puts $f "set fgcolor $fgcolor"
776 puts $f "#"
776 puts $f "#"
777 puts $f "# Color to display + lines in diffs"
777 puts $f "# Color to display + lines in diffs"
778 puts $f "set diffaddcolor $diffaddcolor"
778 puts $f "set diffaddcolor $diffaddcolor"
779 puts $f "#"
779 puts $f "#"
780 puts $f "# Color to display - lines in diffs"
780 puts $f "# Color to display - lines in diffs"
781 puts $f "set diffremcolor $diffremcolor"
781 puts $f "set diffremcolor $diffremcolor"
782 puts $f "#"
782 puts $f "#"
783 puts $f "# Merge diffs: Color to signal lines from first parent"
783 puts $f "# Merge diffs: Color to signal lines from first parent"
784 puts $f "set diffmerge1color $diffmerge1color"
784 puts $f "set diffmerge1color $diffmerge1color"
785 puts $f "#"
785 puts $f "#"
786 puts $f "# Merge diffs: Color to signal lines from second parent"
786 puts $f "# Merge diffs: Color to signal lines from second parent"
787 puts $f "set diffmerge2color $diffmerge2color"
787 puts $f "set diffmerge2color $diffmerge2color"
788 puts $f "#"
788 puts $f "#"
789 puts $f "# Hunkseparator (@@ -lineno,lines +lineno,lines @@) color"
789 puts $f "# Hunkseparator (@@ -lineno,lines +lineno,lines @@) color"
790 puts $f "set hunksepcolor $hunksepcolor"
790 puts $f "set hunksepcolor $hunksepcolor"
791 close $f
791 close $f
792 file rename -force "~/.hgk-new" "~/.hgk"
792 file rename -force "~/.hgk-new" "~/.hgk"
793 }
793 }
794 set stuffsaved 1
794 set stuffsaved 1
795 }
795 }
796
796
797 proc resizeclistpanes {win w} {
797 proc resizeclistpanes {win w} {
798 global oldwidth
798 global oldwidth
799 if [info exists oldwidth($win)] {
799 if [info exists oldwidth($win)] {
800 set s0 [$win sash coord 0]
800 set s0 [$win sash coord 0]
801 set s1 [$win sash coord 1]
801 set s1 [$win sash coord 1]
802 if {$w < 60} {
802 if {$w < 60} {
803 set sash0 [expr {int($w/2 - 2)}]
803 set sash0 [expr {int($w/2 - 2)}]
804 set sash1 [expr {int($w*5/6 - 2)}]
804 set sash1 [expr {int($w*5/6 - 2)}]
805 } else {
805 } else {
806 set factor [expr {1.0 * $w / $oldwidth($win)}]
806 set factor [expr {1.0 * $w / $oldwidth($win)}]
807 set sash0 [expr {int($factor * [lindex $s0 0])}]
807 set sash0 [expr {int($factor * [lindex $s0 0])}]
808 set sash1 [expr {int($factor * [lindex $s1 0])}]
808 set sash1 [expr {int($factor * [lindex $s1 0])}]
809 if {$sash0 < 30} {
809 if {$sash0 < 30} {
810 set sash0 30
810 set sash0 30
811 }
811 }
812 if {$sash1 < $sash0 + 20} {
812 if {$sash1 < $sash0 + 20} {
813 set sash1 [expr $sash0 + 20]
813 set sash1 [expr $sash0 + 20]
814 }
814 }
815 if {$sash1 > $w - 10} {
815 if {$sash1 > $w - 10} {
816 set sash1 [expr $w - 10]
816 set sash1 [expr $w - 10]
817 if {$sash0 > $sash1 - 20} {
817 if {$sash0 > $sash1 - 20} {
818 set sash0 [expr $sash1 - 20]
818 set sash0 [expr $sash1 - 20]
819 }
819 }
820 }
820 }
821 }
821 }
822 $win sash place 0 $sash0 [lindex $s0 1]
822 $win sash place 0 $sash0 [lindex $s0 1]
823 $win sash place 1 $sash1 [lindex $s1 1]
823 $win sash place 1 $sash1 [lindex $s1 1]
824 }
824 }
825 set oldwidth($win) $w
825 set oldwidth($win) $w
826 }
826 }
827
827
828 proc resizecdetpanes {win w} {
828 proc resizecdetpanes {win w} {
829 global oldwidth
829 global oldwidth
830 if [info exists oldwidth($win)] {
830 if [info exists oldwidth($win)] {
831 set s0 [$win sash coord 0]
831 set s0 [$win sash coord 0]
832 if {$w < 60} {
832 if {$w < 60} {
833 set sash0 [expr {int($w*3/4 - 2)}]
833 set sash0 [expr {int($w*3/4 - 2)}]
834 } else {
834 } else {
835 set factor [expr {1.0 * $w / $oldwidth($win)}]
835 set factor [expr {1.0 * $w / $oldwidth($win)}]
836 set sash0 [expr {int($factor * [lindex $s0 0])}]
836 set sash0 [expr {int($factor * [lindex $s0 0])}]
837 if {$sash0 < 45} {
837 if {$sash0 < 45} {
838 set sash0 45
838 set sash0 45
839 }
839 }
840 if {$sash0 > $w - 15} {
840 if {$sash0 > $w - 15} {
841 set sash0 [expr $w - 15]
841 set sash0 [expr $w - 15]
842 }
842 }
843 }
843 }
844 $win sash place 0 $sash0 [lindex $s0 1]
844 $win sash place 0 $sash0 [lindex $s0 1]
845 }
845 }
846 set oldwidth($win) $w
846 set oldwidth($win) $w
847 }
847 }
848
848
849 proc allcanvs args {
849 proc allcanvs args {
850 global canv canv2 canv3
850 global canv canv2 canv3
851 eval $canv $args
851 eval $canv $args
852 eval $canv2 $args
852 eval $canv2 $args
853 eval $canv3 $args
853 eval $canv3 $args
854 }
854 }
855
855
856 proc bindall {event action} {
856 proc bindall {event action} {
857 global canv canv2 canv3
857 global canv canv2 canv3
858 bind $canv $event $action
858 bind $canv $event $action
859 bind $canv2 $event $action
859 bind $canv2 $event $action
860 bind $canv3 $event $action
860 bind $canv3 $event $action
861 }
861 }
862
862
863 proc about {} {
863 proc about {} {
864 set w .about
864 set w .about
865 if {[winfo exists $w]} {
865 if {[winfo exists $w]} {
866 raise $w
866 raise $w
867 return
867 return
868 }
868 }
869 toplevel $w
869 toplevel $w
870 wm title $w "About gitk"
870 wm title $w "About hgk"
871 message $w.m -text {
871 message $w.m -text {
872 Gitk version 1.2
872 Hgk version 1.2
873
873
874 Copyright � 2005 Paul Mackerras
874 Copyright � 2005 Paul Mackerras
875
875
876 Use and redistribute under the terms of the GNU General Public License} \
876 Use and redistribute under the terms of the GNU General Public License} \
877 -justify center -aspect 400
877 -justify center -aspect 400
878 pack $w.m -side top -fill x -padx 20 -pady 20
878 pack $w.m -side top -fill x -padx 20 -pady 20
879 button $w.ok -text Close -command "destroy $w"
879 button $w.ok -text Close -command "destroy $w"
880 pack $w.ok -side bottom
880 pack $w.ok -side bottom
881 }
881 }
882
882
883 set aunextcolor 0
883 set aunextcolor 0
884 proc assignauthorcolor {name} {
884 proc assignauthorcolor {name} {
885 global authorcolors aucolormap aunextcolor
885 global authorcolors aucolormap aunextcolor
886 if [info exists aucolormap($name)] return
886 if [info exists aucolormap($name)] return
887
887
888 set randomcolors {black}
888 set randomcolors {black}
889 for {set i 0} {$i < [llength $authorcolors]} {incr i} {
889 for {set i 0} {$i < [llength $authorcolors]} {incr i} {
890 set col [lindex $authorcolors $i]
890 set col [lindex $authorcolors $i]
891 if {[llength $col] > 1} {
891 if {[llength $col] > 1} {
892 set re [lindex $col 0]
892 set re [lindex $col 0]
893 set c [lindex $col 1]
893 set c [lindex $col 1]
894 if {[regexp -- $re $name]} {
894 if {[regexp -- $re $name]} {
895 set aucolormap($name) $c
895 set aucolormap($name) $c
896 return
896 return
897 }
897 }
898 } else {
898 } else {
899 set randomcolors [lrange $authorcolors $i end]
899 set randomcolors [lrange $authorcolors $i end]
900 break
900 break
901 }
901 }
902 }
902 }
903
903
904 set ncolors [llength $randomcolors]
904 set ncolors [llength $randomcolors]
905 set c [lindex $randomcolors $aunextcolor]
905 set c [lindex $randomcolors $aunextcolor]
906 if {[incr aunextcolor] >= $ncolors} {
906 if {[incr aunextcolor] >= $ncolors} {
907 incr aunextcolor -1
907 incr aunextcolor -1
908 }
908 }
909 set aucolormap($name) $c
909 set aucolormap($name) $c
910 }
910 }
911
911
912 proc assigncolor {id} {
912 proc assigncolor {id} {
913 global commitinfo colormap commcolors colors nextcolor
913 global commitinfo colormap commcolors colors nextcolor
914 global parents nparents children nchildren
914 global parents nparents children nchildren
915 global cornercrossings crossings
915 global cornercrossings crossings
916
916
917 if [info exists colormap($id)] return
917 if [info exists colormap($id)] return
918 set ncolors [llength $colors]
918 set ncolors [llength $colors]
919 if {$nparents($id) <= 1 && $nchildren($id) == 1} {
919 if {$nparents($id) <= 1 && $nchildren($id) == 1} {
920 set child [lindex $children($id) 0]
920 set child [lindex $children($id) 0]
921 if {[info exists colormap($child)]
921 if {[info exists colormap($child)]
922 && $nparents($child) == 1} {
922 && $nparents($child) == 1} {
923 set colormap($id) $colormap($child)
923 set colormap($id) $colormap($child)
924 return
924 return
925 }
925 }
926 }
926 }
927 set badcolors {}
927 set badcolors {}
928 if {[info exists cornercrossings($id)]} {
928 if {[info exists cornercrossings($id)]} {
929 foreach x $cornercrossings($id) {
929 foreach x $cornercrossings($id) {
930 if {[info exists colormap($x)]
930 if {[info exists colormap($x)]
931 && [lsearch -exact $badcolors $colormap($x)] < 0} {
931 && [lsearch -exact $badcolors $colormap($x)] < 0} {
932 lappend badcolors $colormap($x)
932 lappend badcolors $colormap($x)
933 }
933 }
934 }
934 }
935 if {[llength $badcolors] >= $ncolors} {
935 if {[llength $badcolors] >= $ncolors} {
936 set badcolors {}
936 set badcolors {}
937 }
937 }
938 }
938 }
939 set origbad $badcolors
939 set origbad $badcolors
940 if {[llength $badcolors] < $ncolors - 1} {
940 if {[llength $badcolors] < $ncolors - 1} {
941 if {[info exists crossings($id)]} {
941 if {[info exists crossings($id)]} {
942 foreach x $crossings($id) {
942 foreach x $crossings($id) {
943 if {[info exists colormap($x)]
943 if {[info exists colormap($x)]
944 && [lsearch -exact $badcolors $colormap($x)] < 0} {
944 && [lsearch -exact $badcolors $colormap($x)] < 0} {
945 lappend badcolors $colormap($x)
945 lappend badcolors $colormap($x)
946 }
946 }
947 }
947 }
948 if {[llength $badcolors] >= $ncolors} {
948 if {[llength $badcolors] >= $ncolors} {
949 set badcolors $origbad
949 set badcolors $origbad
950 }
950 }
951 }
951 }
952 set origbad $badcolors
952 set origbad $badcolors
953 }
953 }
954 if {[llength $badcolors] < $ncolors - 1} {
954 if {[llength $badcolors] < $ncolors - 1} {
955 foreach child $children($id) {
955 foreach child $children($id) {
956 if {[info exists colormap($child)]
956 if {[info exists colormap($child)]
957 && [lsearch -exact $badcolors $colormap($child)] < 0} {
957 && [lsearch -exact $badcolors $colormap($child)] < 0} {
958 lappend badcolors $colormap($child)
958 lappend badcolors $colormap($child)
959 }
959 }
960 if {[info exists parents($child)]} {
960 if {[info exists parents($child)]} {
961 foreach p $parents($child) {
961 foreach p $parents($child) {
962 if {[info exists colormap($p)]
962 if {[info exists colormap($p)]
963 && [lsearch -exact $badcolors $colormap($p)] < 0} {
963 && [lsearch -exact $badcolors $colormap($p)] < 0} {
964 lappend badcolors $colormap($p)
964 lappend badcolors $colormap($p)
965 }
965 }
966 }
966 }
967 }
967 }
968 }
968 }
969 if {[llength $badcolors] >= $ncolors} {
969 if {[llength $badcolors] >= $ncolors} {
970 set badcolors $origbad
970 set badcolors $origbad
971 }
971 }
972 }
972 }
973 for {set i 0} {$i <= $ncolors} {incr i} {
973 for {set i 0} {$i <= $ncolors} {incr i} {
974 set c [lindex $colors $nextcolor]
974 set c [lindex $colors $nextcolor]
975 if {[incr nextcolor] >= $ncolors} {
975 if {[incr nextcolor] >= $ncolors} {
976 set nextcolor 0
976 set nextcolor 0
977 }
977 }
978 if {[lsearch -exact $badcolors $c]} break
978 if {[lsearch -exact $badcolors $c]} break
979 }
979 }
980 set colormap($id) $c
980 set colormap($id) $c
981 }
981 }
982
982
983 proc initgraph {} {
983 proc initgraph {} {
984 global canvy canvy0 lineno numcommits nextcolor linespc
984 global canvy canvy0 lineno numcommits nextcolor linespc
985 global mainline mainlinearrow sidelines
985 global mainline mainlinearrow sidelines
986 global nchildren ncleft
986 global nchildren ncleft
987 global displist nhyperspace
987 global displist nhyperspace
988
988
989 allcanvs delete all
989 allcanvs delete all
990 set nextcolor 0
990 set nextcolor 0
991 set canvy $canvy0
991 set canvy $canvy0
992 set lineno -1
992 set lineno -1
993 set numcommits 0
993 set numcommits 0
994 catch {unset mainline}
994 catch {unset mainline}
995 catch {unset mainlinearrow}
995 catch {unset mainlinearrow}
996 catch {unset sidelines}
996 catch {unset sidelines}
997 foreach id [array names nchildren] {
997 foreach id [array names nchildren] {
998 set ncleft($id) $nchildren($id)
998 set ncleft($id) $nchildren($id)
999 }
999 }
1000 set displist {}
1000 set displist {}
1001 set nhyperspace 0
1001 set nhyperspace 0
1002 }
1002 }
1003
1003
1004 proc bindline {t id} {
1004 proc bindline {t id} {
1005 global canv
1005 global canv
1006
1006
1007 $canv bind $t <Enter> "lineenter %x %y $id"
1007 $canv bind $t <Enter> "lineenter %x %y $id"
1008 $canv bind $t <Motion> "linemotion %x %y $id"
1008 $canv bind $t <Motion> "linemotion %x %y $id"
1009 $canv bind $t <Leave> "lineleave $id"
1009 $canv bind $t <Leave> "lineleave $id"
1010 $canv bind $t <Button-1> "lineclick %x %y $id 1"
1010 $canv bind $t <Button-1> "lineclick %x %y $id 1"
1011 }
1011 }
1012
1012
1013 proc drawlines {id xtra} {
1013 proc drawlines {id xtra} {
1014 global mainline mainlinearrow sidelines lthickness colormap canv
1014 global mainline mainlinearrow sidelines lthickness colormap canv
1015
1015
1016 $canv delete lines.$id
1016 $canv delete lines.$id
1017 if {[info exists mainline($id)]} {
1017 if {[info exists mainline($id)]} {
1018 set t [$canv create line $mainline($id) \
1018 set t [$canv create line $mainline($id) \
1019 -width [expr {($xtra + 1) * $lthickness}] \
1019 -width [expr {($xtra + 1) * $lthickness}] \
1020 -fill $colormap($id) -tags lines.$id \
1020 -fill $colormap($id) -tags lines.$id \
1021 -arrow $mainlinearrow($id)]
1021 -arrow $mainlinearrow($id)]
1022 $canv lower $t
1022 $canv lower $t
1023 bindline $t $id
1023 bindline $t $id
1024 }
1024 }
1025 if {[info exists sidelines($id)]} {
1025 if {[info exists sidelines($id)]} {
1026 foreach ls $sidelines($id) {
1026 foreach ls $sidelines($id) {
1027 set coords [lindex $ls 0]
1027 set coords [lindex $ls 0]
1028 set thick [lindex $ls 1]
1028 set thick [lindex $ls 1]
1029 set arrow [lindex $ls 2]
1029 set arrow [lindex $ls 2]
1030 set t [$canv create line $coords -fill $colormap($id) \
1030 set t [$canv create line $coords -fill $colormap($id) \
1031 -width [expr {($thick + $xtra) * $lthickness}] \
1031 -width [expr {($thick + $xtra) * $lthickness}] \
1032 -arrow $arrow -tags lines.$id]
1032 -arrow $arrow -tags lines.$id]
1033 $canv lower $t
1033 $canv lower $t
1034 bindline $t $id
1034 bindline $t $id
1035 }
1035 }
1036 }
1036 }
1037 }
1037 }
1038
1038
1039 # level here is an index in displist
1039 # level here is an index in displist
1040 proc drawcommitline {level} {
1040 proc drawcommitline {level} {
1041 global parents children nparents displist
1041 global parents children nparents displist
1042 global canv canv2 canv3 mainfont namefont canvy linespc
1042 global canv canv2 canv3 mainfont namefont canvy linespc
1043 global lineid linehtag linentag linedtag commitinfo
1043 global lineid linehtag linentag linedtag commitinfo
1044 global colormap numcommits currentparents dupparents
1044 global colormap numcommits currentparents dupparents
1045 global idtags idline idheads idotherrefs
1045 global idtags idline idheads idotherrefs
1046 global lineno lthickness mainline mainlinearrow sidelines
1046 global lineno lthickness mainline mainlinearrow sidelines
1047 global commitlisted rowtextx idpos lastuse displist
1047 global commitlisted rowtextx idpos lastuse displist
1048 global oldnlines olddlevel olddisplist
1048 global oldnlines olddlevel olddisplist
1049 global aucolormap curid curidfont
1049 global aucolormap curid curidfont
1050
1050
1051 incr numcommits
1051 incr numcommits
1052 incr lineno
1052 incr lineno
1053 set id [lindex $displist $level]
1053 set id [lindex $displist $level]
1054 set lastuse($id) $lineno
1054 set lastuse($id) $lineno
1055 set lineid($lineno) $id
1055 set lineid($lineno) $id
1056 set idline($id) $lineno
1056 set idline($id) $lineno
1057 set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
1057 set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
1058 if {![info exists commitinfo($id)]} {
1058 if {![info exists commitinfo($id)]} {
1059 readcommit $id
1059 readcommit $id
1060 if {![info exists commitinfo($id)]} {
1060 if {![info exists commitinfo($id)]} {
1061 set commitinfo($id) {"No commit information available"}
1061 set commitinfo($id) {"No commit information available"}
1062 set nparents($id) 0
1062 set nparents($id) 0
1063 }
1063 }
1064 }
1064 }
1065 assigncolor $id
1065 assigncolor $id
1066 set currentparents {}
1066 set currentparents {}
1067 set dupparents {}
1067 set dupparents {}
1068 if {[info exists commitlisted($id)] && [info exists parents($id)]} {
1068 if {[info exists commitlisted($id)] && [info exists parents($id)]} {
1069 foreach p $parents($id) {
1069 foreach p $parents($id) {
1070 if {[lsearch -exact $currentparents $p] < 0} {
1070 if {[lsearch -exact $currentparents $p] < 0} {
1071 lappend currentparents $p
1071 lappend currentparents $p
1072 } else {
1072 } else {
1073 # remember that this parent was listed twice
1073 # remember that this parent was listed twice
1074 lappend dupparents $p
1074 lappend dupparents $p
1075 }
1075 }
1076 }
1076 }
1077 }
1077 }
1078 set x [xcoord $level $level $lineno]
1078 set x [xcoord $level $level $lineno]
1079 set y1 $canvy
1079 set y1 $canvy
1080 set canvy [expr $canvy + $linespc]
1080 set canvy [expr $canvy + $linespc]
1081 allcanvs conf -scrollregion \
1081 allcanvs conf -scrollregion \
1082 [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
1082 [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
1083 if {[info exists mainline($id)]} {
1083 if {[info exists mainline($id)]} {
1084 lappend mainline($id) $x $y1
1084 lappend mainline($id) $x $y1
1085 if {$mainlinearrow($id) ne "none"} {
1085 if {$mainlinearrow($id) ne "none"} {
1086 set mainline($id) [trimdiagstart $mainline($id)]
1086 set mainline($id) [trimdiagstart $mainline($id)]
1087 }
1087 }
1088 }
1088 }
1089 drawlines $id 0
1089 drawlines $id 0
1090 set orad [expr {$linespc / 3}]
1090 set orad [expr {$linespc / 3}]
1091 set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
1091 set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
1092 [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
1092 [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
1093 -fill $ofill -outline black -width 1]
1093 -fill $ofill -outline black -width 1]
1094 $canv raise $t
1094 $canv raise $t
1095 $canv bind $t <1> {selcanvline {} %x %y}
1095 $canv bind $t <1> {selcanvline {} %x %y}
1096 set xt [xcoord [llength $displist] $level $lineno]
1096 set xt [xcoord [llength $displist] $level $lineno]
1097 if {[llength $currentparents] > 2} {
1097 if {[llength $currentparents] > 2} {
1098 set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
1098 set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
1099 }
1099 }
1100 set rowtextx($lineno) $xt
1100 set rowtextx($lineno) $xt
1101 set idpos($id) [list $x $xt $y1]
1101 set idpos($id) [list $x $xt $y1]
1102 if {[info exists idtags($id)] || [info exists idheads($id)]
1102 if {[info exists idtags($id)] || [info exists idheads($id)]
1103 || [info exists idotherrefs($id)]} {
1103 || [info exists idotherrefs($id)]} {
1104 set xt [drawtags $id $x $xt $y1]
1104 set xt [drawtags $id $x $xt $y1]
1105 }
1105 }
1106 set headline [lindex $commitinfo($id) 0]
1106 set headline [lindex $commitinfo($id) 0]
1107 set name [lindex $commitinfo($id) 1]
1107 set name [lindex $commitinfo($id) 1]
1108 assignauthorcolor $name
1108 assignauthorcolor $name
1109 set fg $aucolormap($name)
1109 set fg $aucolormap($name)
1110 if {$id == $curid} {
1110 if {$id == $curid} {
1111 set fn $curidfont
1111 set fn $curidfont
1112 } else {
1112 } else {
1113 set fn $mainfont
1113 set fn $mainfont
1114 }
1114 }
1115
1115
1116 set date [lindex $commitinfo($id) 2]
1116 set date [lindex $commitinfo($id) 2]
1117 set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
1117 set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
1118 -text $headline -font $fn \
1118 -text $headline -font $fn \
1119 -fill $fg]
1119 -fill $fg]
1120 $canv bind $linehtag($lineno) <<B3>> "rowmenu %X %Y $id"
1120 $canv bind $linehtag($lineno) <<B3>> "rowmenu %X %Y $id"
1121 set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
1121 set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
1122 -text $name -font $namefont \
1122 -text $name -font $namefont \
1123 -fill $fg]
1123 -fill $fg]
1124 set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
1124 set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
1125 -text $date -font $mainfont \
1125 -text $date -font $mainfont \
1126 -fill $fg]
1126 -fill $fg]
1127
1127
1128 set olddlevel $level
1128 set olddlevel $level
1129 set olddisplist $displist
1129 set olddisplist $displist
1130 set oldnlines [llength $displist]
1130 set oldnlines [llength $displist]
1131 }
1131 }
1132
1132
1133 proc drawtags {id x xt y1} {
1133 proc drawtags {id x xt y1} {
1134 global idtags idheads idotherrefs commitinfo
1134 global idtags idheads idotherrefs commitinfo
1135 global linespc lthickness
1135 global linespc lthickness
1136 global canv mainfont idline rowtextx
1136 global canv mainfont idline rowtextx
1137
1137
1138 set marks {}
1138 set marks {}
1139 set ntags 0
1139 set ntags 0
1140 set nheads 0
1140 set nheads 0
1141 if {[info exists idtags($id)]} {
1141 if {[info exists idtags($id)]} {
1142 set marks $idtags($id)
1142 set marks $idtags($id)
1143 set ntags [llength $marks]
1143 set ntags [llength $marks]
1144 }
1144 }
1145 if {[info exists idheads($id)]} {
1145 if {[info exists idheads($id)]} {
1146 set headmark [lindex $commitinfo($id) 7]
1146 set headmark [lindex $commitinfo($id) 7]
1147 if {$headmark ne "default"} {
1147 if {$headmark ne "default"} {
1148 lappend marks $headmark
1148 lappend marks $headmark
1149 set nheads 1
1149 set nheads 1
1150 }
1150 }
1151 }
1151 }
1152 if {[info exists idotherrefs($id)]} {
1152 if {[info exists idotherrefs($id)]} {
1153 set marks [concat $marks $idotherrefs($id)]
1153 set marks [concat $marks $idotherrefs($id)]
1154 }
1154 }
1155 if {$marks eq {}} {
1155 if {$marks eq {}} {
1156 return $xt
1156 return $xt
1157 }
1157 }
1158
1158
1159 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
1159 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
1160 set yt [expr $y1 - 0.5 * $linespc]
1160 set yt [expr $y1 - 0.5 * $linespc]
1161 set yb [expr $yt + $linespc - 1]
1161 set yb [expr $yt + $linespc - 1]
1162 set xvals {}
1162 set xvals {}
1163 set wvals {}
1163 set wvals {}
1164 foreach tag $marks {
1164 foreach tag $marks {
1165 set wid [font measure $mainfont $tag]
1165 set wid [font measure $mainfont $tag]
1166 lappend xvals $xt
1166 lappend xvals $xt
1167 lappend wvals $wid
1167 lappend wvals $wid
1168 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
1168 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
1169 }
1169 }
1170 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
1170 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
1171 -width $lthickness -fill black -tags tag.$id]
1171 -width $lthickness -fill black -tags tag.$id]
1172 $canv lower $t
1172 $canv lower $t
1173 foreach tag $marks x $xvals wid $wvals {
1173 foreach tag $marks x $xvals wid $wvals {
1174 set xl [expr $x + $delta]
1174 set xl [expr $x + $delta]
1175 set xr [expr $x + $delta + $wid + $lthickness]
1175 set xr [expr $x + $delta + $wid + $lthickness]
1176 if {[incr ntags -1] >= 0} {
1176 if {[incr ntags -1] >= 0} {
1177 # draw a tag
1177 # draw a tag
1178 set t [$canv create polygon $x [expr $yt + $delta] $xl $yt \
1178 set t [$canv create polygon $x [expr $yt + $delta] $xl $yt \
1179 $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
1179 $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
1180 -width 1 -outline black -fill yellow -tags tag.$id]
1180 -width 1 -outline black -fill yellow -tags tag.$id]
1181 $canv bind $t <1> [list showtag $tag 1]
1181 $canv bind $t <1> [list showtag $tag 1]
1182 set rowtextx($idline($id)) [expr {$xr + $linespc}]
1182 set rowtextx($idline($id)) [expr {$xr + $linespc}]
1183 } else {
1183 } else {
1184 # draw a head or other ref
1184 # draw a head or other ref
1185 if {[incr nheads -1] >= 0} {
1185 if {[incr nheads -1] >= 0} {
1186 set col green
1186 set col green
1187 } else {
1187 } else {
1188 set col "#ddddff"
1188 set col "#ddddff"
1189 }
1189 }
1190 set xl [expr $xl - $delta/2]
1190 set xl [expr $xl - $delta/2]
1191 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
1191 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
1192 -width 1 -outline black -fill $col -tags tag.$id
1192 -width 1 -outline black -fill $col -tags tag.$id
1193 }
1193 }
1194 set t [$canv create text $xl $y1 -anchor w -text $tag \
1194 set t [$canv create text $xl $y1 -anchor w -text $tag \
1195 -font $mainfont -tags tag.$id]
1195 -font $mainfont -tags tag.$id]
1196 if {$ntags >= 0} {
1196 if {$ntags >= 0} {
1197 $canv bind $t <1> [list showtag $tag 1]
1197 $canv bind $t <1> [list showtag $tag 1]
1198 }
1198 }
1199 }
1199 }
1200 return $xt
1200 return $xt
1201 }
1201 }
1202
1202
1203 proc notecrossings {id lo hi corner} {
1203 proc notecrossings {id lo hi corner} {
1204 global olddisplist crossings cornercrossings
1204 global olddisplist crossings cornercrossings
1205
1205
1206 for {set i $lo} {[incr i] < $hi} {} {
1206 for {set i $lo} {[incr i] < $hi} {} {
1207 set p [lindex $olddisplist $i]
1207 set p [lindex $olddisplist $i]
1208 if {$p == {}} continue
1208 if {$p == {}} continue
1209 if {$i == $corner} {
1209 if {$i == $corner} {
1210 if {![info exists cornercrossings($id)]
1210 if {![info exists cornercrossings($id)]
1211 || [lsearch -exact $cornercrossings($id) $p] < 0} {
1211 || [lsearch -exact $cornercrossings($id) $p] < 0} {
1212 lappend cornercrossings($id) $p
1212 lappend cornercrossings($id) $p
1213 }
1213 }
1214 if {![info exists cornercrossings($p)]
1214 if {![info exists cornercrossings($p)]
1215 || [lsearch -exact $cornercrossings($p) $id] < 0} {
1215 || [lsearch -exact $cornercrossings($p) $id] < 0} {
1216 lappend cornercrossings($p) $id
1216 lappend cornercrossings($p) $id
1217 }
1217 }
1218 } else {
1218 } else {
1219 if {![info exists crossings($id)]
1219 if {![info exists crossings($id)]
1220 || [lsearch -exact $crossings($id) $p] < 0} {
1220 || [lsearch -exact $crossings($id) $p] < 0} {
1221 lappend crossings($id) $p
1221 lappend crossings($id) $p
1222 }
1222 }
1223 if {![info exists crossings($p)]
1223 if {![info exists crossings($p)]
1224 || [lsearch -exact $crossings($p) $id] < 0} {
1224 || [lsearch -exact $crossings($p) $id] < 0} {
1225 lappend crossings($p) $id
1225 lappend crossings($p) $id
1226 }
1226 }
1227 }
1227 }
1228 }
1228 }
1229 }
1229 }
1230
1230
1231 proc xcoord {i level ln} {
1231 proc xcoord {i level ln} {
1232 global canvx0 xspc1 xspc2
1232 global canvx0 xspc1 xspc2
1233
1233
1234 set x [expr {$canvx0 + $i * $xspc1($ln)}]
1234 set x [expr {$canvx0 + $i * $xspc1($ln)}]
1235 if {$i > 0 && $i == $level} {
1235 if {$i > 0 && $i == $level} {
1236 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
1236 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
1237 } elseif {$i > $level} {
1237 } elseif {$i > $level} {
1238 set x [expr {$x + $xspc2 - $xspc1($ln)}]
1238 set x [expr {$x + $xspc2 - $xspc1($ln)}]
1239 }
1239 }
1240 return $x
1240 return $x
1241 }
1241 }
1242
1242
1243 # it seems Tk can't draw arrows on the end of diagonal line segments...
1243 # it seems Tk can't draw arrows on the end of diagonal line segments...
1244 proc trimdiagend {line} {
1244 proc trimdiagend {line} {
1245 while {[llength $line] > 4} {
1245 while {[llength $line] > 4} {
1246 set x1 [lindex $line end-3]
1246 set x1 [lindex $line end-3]
1247 set y1 [lindex $line end-2]
1247 set y1 [lindex $line end-2]
1248 set x2 [lindex $line end-1]
1248 set x2 [lindex $line end-1]
1249 set y2 [lindex $line end]
1249 set y2 [lindex $line end]
1250 if {($x1 == $x2) != ($y1 == $y2)} break
1250 if {($x1 == $x2) != ($y1 == $y2)} break
1251 set line [lreplace $line end-1 end]
1251 set line [lreplace $line end-1 end]
1252 }
1252 }
1253 return $line
1253 return $line
1254 }
1254 }
1255
1255
1256 proc trimdiagstart {line} {
1256 proc trimdiagstart {line} {
1257 while {[llength $line] > 4} {
1257 while {[llength $line] > 4} {
1258 set x1 [lindex $line 0]
1258 set x1 [lindex $line 0]
1259 set y1 [lindex $line 1]
1259 set y1 [lindex $line 1]
1260 set x2 [lindex $line 2]
1260 set x2 [lindex $line 2]
1261 set y2 [lindex $line 3]
1261 set y2 [lindex $line 3]
1262 if {($x1 == $x2) != ($y1 == $y2)} break
1262 if {($x1 == $x2) != ($y1 == $y2)} break
1263 set line [lreplace $line 0 1]
1263 set line [lreplace $line 0 1]
1264 }
1264 }
1265 return $line
1265 return $line
1266 }
1266 }
1267
1267
1268 proc drawslants {id needonscreen nohs} {
1268 proc drawslants {id needonscreen nohs} {
1269 global canv mainline mainlinearrow sidelines
1269 global canv mainline mainlinearrow sidelines
1270 global canvx0 canvy xspc1 xspc2 lthickness
1270 global canvx0 canvy xspc1 xspc2 lthickness
1271 global currentparents dupparents
1271 global currentparents dupparents
1272 global lthickness linespc canvy colormap lineno geometry
1272 global lthickness linespc canvy colormap lineno geometry
1273 global maxgraphpct maxwidth
1273 global maxgraphpct maxwidth
1274 global displist onscreen lastuse
1274 global displist onscreen lastuse
1275 global parents commitlisted
1275 global parents commitlisted
1276 global oldnlines olddlevel olddisplist
1276 global oldnlines olddlevel olddisplist
1277 global nhyperspace numcommits nnewparents
1277 global nhyperspace numcommits nnewparents
1278
1278
1279 if {$lineno < 0} {
1279 if {$lineno < 0} {
1280 lappend displist $id
1280 lappend displist $id
1281 set onscreen($id) 1
1281 set onscreen($id) 1
1282 return 0
1282 return 0
1283 }
1283 }
1284
1284
1285 set y1 [expr {$canvy - $linespc}]
1285 set y1 [expr {$canvy - $linespc}]
1286 set y2 $canvy
1286 set y2 $canvy
1287
1287
1288 # work out what we need to get back on screen
1288 # work out what we need to get back on screen
1289 set reins {}
1289 set reins {}
1290 if {$onscreen($id) < 0} {
1290 if {$onscreen($id) < 0} {
1291 # next to do isn't displayed, better get it on screen...
1291 # next to do isn't displayed, better get it on screen...
1292 lappend reins [list $id 0]
1292 lappend reins [list $id 0]
1293 }
1293 }
1294 # make sure all the previous commits's parents are on the screen
1294 # make sure all the previous commits's parents are on the screen
1295 foreach p $currentparents {
1295 foreach p $currentparents {
1296 if {$onscreen($p) < 0} {
1296 if {$onscreen($p) < 0} {
1297 lappend reins [list $p 0]
1297 lappend reins [list $p 0]
1298 }
1298 }
1299 }
1299 }
1300 # bring back anything requested by caller
1300 # bring back anything requested by caller
1301 if {$needonscreen ne {}} {
1301 if {$needonscreen ne {}} {
1302 lappend reins $needonscreen
1302 lappend reins $needonscreen
1303 }
1303 }
1304
1304
1305 # try the shortcut
1305 # try the shortcut
1306 if {$currentparents == $id && $onscreen($id) == 0 && $reins eq {}} {
1306 if {$currentparents == $id && $onscreen($id) == 0 && $reins eq {}} {
1307 set dlevel $olddlevel
1307 set dlevel $olddlevel
1308 set x [xcoord $dlevel $dlevel $lineno]
1308 set x [xcoord $dlevel $dlevel $lineno]
1309 set mainline($id) [list $x $y1]
1309 set mainline($id) [list $x $y1]
1310 set mainlinearrow($id) none
1310 set mainlinearrow($id) none
1311 set lastuse($id) $lineno
1311 set lastuse($id) $lineno
1312 set displist [lreplace $displist $dlevel $dlevel $id]
1312 set displist [lreplace $displist $dlevel $dlevel $id]
1313 set onscreen($id) 1
1313 set onscreen($id) 1
1314 set xspc1([expr {$lineno + 1}]) $xspc1($lineno)
1314 set xspc1([expr {$lineno + 1}]) $xspc1($lineno)
1315 return $dlevel
1315 return $dlevel
1316 }
1316 }
1317
1317
1318 # update displist
1318 # update displist
1319 set displist [lreplace $displist $olddlevel $olddlevel]
1319 set displist [lreplace $displist $olddlevel $olddlevel]
1320 set j $olddlevel
1320 set j $olddlevel
1321 foreach p $currentparents {
1321 foreach p $currentparents {
1322 set lastuse($p) $lineno
1322 set lastuse($p) $lineno
1323 if {$onscreen($p) == 0} {
1323 if {$onscreen($p) == 0} {
1324 set displist [linsert $displist $j $p]
1324 set displist [linsert $displist $j $p]
1325 set onscreen($p) 1
1325 set onscreen($p) 1
1326 incr j
1326 incr j
1327 }
1327 }
1328 }
1328 }
1329 if {$onscreen($id) == 0} {
1329 if {$onscreen($id) == 0} {
1330 lappend displist $id
1330 lappend displist $id
1331 set onscreen($id) 1
1331 set onscreen($id) 1
1332 }
1332 }
1333
1333
1334 # remove the null entry if present
1334 # remove the null entry if present
1335 set nullentry [lsearch -exact $displist {}]
1335 set nullentry [lsearch -exact $displist {}]
1336 if {$nullentry >= 0} {
1336 if {$nullentry >= 0} {
1337 set displist [lreplace $displist $nullentry $nullentry]
1337 set displist [lreplace $displist $nullentry $nullentry]
1338 }
1338 }
1339
1339
1340 # bring back the ones we need now (if we did it earlier
1340 # bring back the ones we need now (if we did it earlier
1341 # it would change displist and invalidate olddlevel)
1341 # it would change displist and invalidate olddlevel)
1342 foreach pi $reins {
1342 foreach pi $reins {
1343 # test again in case of duplicates in reins
1343 # test again in case of duplicates in reins
1344 set p [lindex $pi 0]
1344 set p [lindex $pi 0]
1345 if {$onscreen($p) < 0} {
1345 if {$onscreen($p) < 0} {
1346 set onscreen($p) 1
1346 set onscreen($p) 1
1347 set lastuse($p) $lineno
1347 set lastuse($p) $lineno
1348 set displist [linsert $displist [lindex $pi 1] $p]
1348 set displist [linsert $displist [lindex $pi 1] $p]
1349 incr nhyperspace -1
1349 incr nhyperspace -1
1350 }
1350 }
1351 }
1351 }
1352
1352
1353 set lastuse($id) $lineno
1353 set lastuse($id) $lineno
1354
1354
1355 # see if we need to make any lines jump off into hyperspace
1355 # see if we need to make any lines jump off into hyperspace
1356 set displ [llength $displist]
1356 set displ [llength $displist]
1357 if {$displ > $maxwidth} {
1357 if {$displ > $maxwidth} {
1358 set ages {}
1358 set ages {}
1359 foreach x $displist {
1359 foreach x $displist {
1360 lappend ages [list $lastuse($x) $x]
1360 lappend ages [list $lastuse($x) $x]
1361 }
1361 }
1362 set ages [lsort -integer -index 0 $ages]
1362 set ages [lsort -integer -index 0 $ages]
1363 set k 0
1363 set k 0
1364 while {$displ > $maxwidth} {
1364 while {$displ > $maxwidth} {
1365 set use [lindex $ages $k 0]
1365 set use [lindex $ages $k 0]
1366 set victim [lindex $ages $k 1]
1366 set victim [lindex $ages $k 1]
1367 if {$use >= $lineno - 5} break
1367 if {$use >= $lineno - 5} break
1368 incr k
1368 incr k
1369 if {[lsearch -exact $nohs $victim] >= 0} continue
1369 if {[lsearch -exact $nohs $victim] >= 0} continue
1370 set i [lsearch -exact $displist $victim]
1370 set i [lsearch -exact $displist $victim]
1371 set displist [lreplace $displist $i $i]
1371 set displist [lreplace $displist $i $i]
1372 set onscreen($victim) -1
1372 set onscreen($victim) -1
1373 incr nhyperspace
1373 incr nhyperspace
1374 incr displ -1
1374 incr displ -1
1375 if {$i < $nullentry} {
1375 if {$i < $nullentry} {
1376 incr nullentry -1
1376 incr nullentry -1
1377 }
1377 }
1378 set x [lindex $mainline($victim) end-1]
1378 set x [lindex $mainline($victim) end-1]
1379 lappend mainline($victim) $x $y1
1379 lappend mainline($victim) $x $y1
1380 set line [trimdiagend $mainline($victim)]
1380 set line [trimdiagend $mainline($victim)]
1381 set arrow "last"
1381 set arrow "last"
1382 if {$mainlinearrow($victim) ne "none"} {
1382 if {$mainlinearrow($victim) ne "none"} {
1383 set line [trimdiagstart $line]
1383 set line [trimdiagstart $line]
1384 set arrow "both"
1384 set arrow "both"
1385 }
1385 }
1386 lappend sidelines($victim) [list $line 1 $arrow]
1386 lappend sidelines($victim) [list $line 1 $arrow]
1387 unset mainline($victim)
1387 unset mainline($victim)
1388 }
1388 }
1389 }
1389 }
1390
1390
1391 set dlevel [lsearch -exact $displist $id]
1391 set dlevel [lsearch -exact $displist $id]
1392
1392
1393 # If we are reducing, put in a null entry
1393 # If we are reducing, put in a null entry
1394 if {$displ < $oldnlines} {
1394 if {$displ < $oldnlines} {
1395 # does the next line look like a merge?
1395 # does the next line look like a merge?
1396 # i.e. does it have > 1 new parent?
1396 # i.e. does it have > 1 new parent?
1397 if {$nnewparents($id) > 1} {
1397 if {$nnewparents($id) > 1} {
1398 set i [expr {$dlevel + 1}]
1398 set i [expr {$dlevel + 1}]
1399 } elseif {$nnewparents([lindex $olddisplist $olddlevel]) == 0} {
1399 } elseif {$nnewparents([lindex $olddisplist $olddlevel]) == 0} {
1400 set i $olddlevel
1400 set i $olddlevel
1401 if {$nullentry >= 0 && $nullentry < $i} {
1401 if {$nullentry >= 0 && $nullentry < $i} {
1402 incr i -1
1402 incr i -1
1403 }
1403 }
1404 } elseif {$nullentry >= 0} {
1404 } elseif {$nullentry >= 0} {
1405 set i $nullentry
1405 set i $nullentry
1406 while {$i < $displ
1406 while {$i < $displ
1407 && [lindex $olddisplist $i] == [lindex $displist $i]} {
1407 && [lindex $olddisplist $i] == [lindex $displist $i]} {
1408 incr i
1408 incr i
1409 }
1409 }
1410 } else {
1410 } else {
1411 set i $olddlevel
1411 set i $olddlevel
1412 if {$dlevel >= $i} {
1412 if {$dlevel >= $i} {
1413 incr i
1413 incr i
1414 }
1414 }
1415 }
1415 }
1416 if {$i < $displ} {
1416 if {$i < $displ} {
1417 set displist [linsert $displist $i {}]
1417 set displist [linsert $displist $i {}]
1418 incr displ
1418 incr displ
1419 if {$dlevel >= $i} {
1419 if {$dlevel >= $i} {
1420 incr dlevel
1420 incr dlevel
1421 }
1421 }
1422 }
1422 }
1423 }
1423 }
1424
1424
1425 # decide on the line spacing for the next line
1425 # decide on the line spacing for the next line
1426 set lj [expr {$lineno + 1}]
1426 set lj [expr {$lineno + 1}]
1427 set maxw [expr {$maxgraphpct * $geometry(canv1) / 100}]
1427 set maxw [expr {$maxgraphpct * $geometry(canv1) / 100}]
1428 if {$displ <= 1 || $canvx0 + $displ * $xspc2 <= $maxw} {
1428 if {$displ <= 1 || $canvx0 + $displ * $xspc2 <= $maxw} {
1429 set xspc1($lj) $xspc2
1429 set xspc1($lj) $xspc2
1430 } else {
1430 } else {
1431 set xspc1($lj) [expr {($maxw - $canvx0 - $xspc2) / ($displ - 1)}]
1431 set xspc1($lj) [expr {($maxw - $canvx0 - $xspc2) / ($displ - 1)}]
1432 if {$xspc1($lj) < $lthickness} {
1432 if {$xspc1($lj) < $lthickness} {
1433 set xspc1($lj) $lthickness
1433 set xspc1($lj) $lthickness
1434 }
1434 }
1435 }
1435 }
1436
1436
1437 foreach idi $reins {
1437 foreach idi $reins {
1438 set id [lindex $idi 0]
1438 set id [lindex $idi 0]
1439 set j [lsearch -exact $displist $id]
1439 set j [lsearch -exact $displist $id]
1440 set xj [xcoord $j $dlevel $lj]
1440 set xj [xcoord $j $dlevel $lj]
1441 set mainline($id) [list $xj $y2]
1441 set mainline($id) [list $xj $y2]
1442 set mainlinearrow($id) first
1442 set mainlinearrow($id) first
1443 }
1443 }
1444
1444
1445 set i -1
1445 set i -1
1446 foreach id $olddisplist {
1446 foreach id $olddisplist {
1447 incr i
1447 incr i
1448 if {$id == {}} continue
1448 if {$id == {}} continue
1449 if {$onscreen($id) <= 0} continue
1449 if {$onscreen($id) <= 0} continue
1450 set xi [xcoord $i $olddlevel $lineno]
1450 set xi [xcoord $i $olddlevel $lineno]
1451 if {$i == $olddlevel} {
1451 if {$i == $olddlevel} {
1452 foreach p $currentparents {
1452 foreach p $currentparents {
1453 set j [lsearch -exact $displist $p]
1453 set j [lsearch -exact $displist $p]
1454 set coords [list $xi $y1]
1454 set coords [list $xi $y1]
1455 set xj [xcoord $j $dlevel $lj]
1455 set xj [xcoord $j $dlevel $lj]
1456 if {$xj < $xi - $linespc} {
1456 if {$xj < $xi - $linespc} {
1457 lappend coords [expr {$xj + $linespc}] $y1
1457 lappend coords [expr {$xj + $linespc}] $y1
1458 notecrossings $p $j $i [expr {$j + 1}]
1458 notecrossings $p $j $i [expr {$j + 1}]
1459 } elseif {$xj > $xi + $linespc} {
1459 } elseif {$xj > $xi + $linespc} {
1460 lappend coords [expr {$xj - $linespc}] $y1
1460 lappend coords [expr {$xj - $linespc}] $y1
1461 notecrossings $p $i $j [expr {$j - 1}]
1461 notecrossings $p $i $j [expr {$j - 1}]
1462 }
1462 }
1463 if {[lsearch -exact $dupparents $p] >= 0} {
1463 if {[lsearch -exact $dupparents $p] >= 0} {
1464 # draw a double-width line to indicate the doubled parent
1464 # draw a double-width line to indicate the doubled parent
1465 lappend coords $xj $y2
1465 lappend coords $xj $y2
1466 lappend sidelines($p) [list $coords 2 none]
1466 lappend sidelines($p) [list $coords 2 none]
1467 if {![info exists mainline($p)]} {
1467 if {![info exists mainline($p)]} {
1468 set mainline($p) [list $xj $y2]
1468 set mainline($p) [list $xj $y2]
1469 set mainlinearrow($p) none
1469 set mainlinearrow($p) none
1470 }
1470 }
1471 } else {
1471 } else {
1472 # normal case, no parent duplicated
1472 # normal case, no parent duplicated
1473 set yb $y2
1473 set yb $y2
1474 set dx [expr {abs($xi - $xj)}]
1474 set dx [expr {abs($xi - $xj)}]
1475 if {0 && $dx < $linespc} {
1475 if {0 && $dx < $linespc} {
1476 set yb [expr {$y1 + $dx}]
1476 set yb [expr {$y1 + $dx}]
1477 }
1477 }
1478 if {![info exists mainline($p)]} {
1478 if {![info exists mainline($p)]} {
1479 if {$xi != $xj} {
1479 if {$xi != $xj} {
1480 lappend coords $xj $yb
1480 lappend coords $xj $yb
1481 }
1481 }
1482 set mainline($p) $coords
1482 set mainline($p) $coords
1483 set mainlinearrow($p) none
1483 set mainlinearrow($p) none
1484 } else {
1484 } else {
1485 lappend coords $xj $yb
1485 lappend coords $xj $yb
1486 if {$yb < $y2} {
1486 if {$yb < $y2} {
1487 lappend coords $xj $y2
1487 lappend coords $xj $y2
1488 }
1488 }
1489 lappend sidelines($p) [list $coords 1 none]
1489 lappend sidelines($p) [list $coords 1 none]
1490 }
1490 }
1491 }
1491 }
1492 }
1492 }
1493 } else {
1493 } else {
1494 set j $i
1494 set j $i
1495 if {[lindex $displist $i] != $id} {
1495 if {[lindex $displist $i] != $id} {
1496 set j [lsearch -exact $displist $id]
1496 set j [lsearch -exact $displist $id]
1497 }
1497 }
1498 if {$j != $i || $xspc1($lineno) != $xspc1($lj)
1498 if {$j != $i || $xspc1($lineno) != $xspc1($lj)
1499 || ($olddlevel < $i && $i < $dlevel)
1499 || ($olddlevel < $i && $i < $dlevel)
1500 || ($dlevel < $i && $i < $olddlevel)} {
1500 || ($dlevel < $i && $i < $olddlevel)} {
1501 set xj [xcoord $j $dlevel $lj]
1501 set xj [xcoord $j $dlevel $lj]
1502 lappend mainline($id) $xi $y1 $xj $y2
1502 lappend mainline($id) $xi $y1 $xj $y2
1503 }
1503 }
1504 }
1504 }
1505 }
1505 }
1506 return $dlevel
1506 return $dlevel
1507 }
1507 }
1508
1508
1509 # search for x in a list of lists
1509 # search for x in a list of lists
1510 proc llsearch {llist x} {
1510 proc llsearch {llist x} {
1511 set i 0
1511 set i 0
1512 foreach l $llist {
1512 foreach l $llist {
1513 if {$l == $x || [lsearch -exact $l $x] >= 0} {
1513 if {$l == $x || [lsearch -exact $l $x] >= 0} {
1514 return $i
1514 return $i
1515 }
1515 }
1516 incr i
1516 incr i
1517 }
1517 }
1518 return -1
1518 return -1
1519 }
1519 }
1520
1520
1521 proc drawmore {reading} {
1521 proc drawmore {reading} {
1522 global displayorder numcommits ncmupdate nextupdate
1522 global displayorder numcommits ncmupdate nextupdate
1523 global stopped nhyperspace parents commitlisted
1523 global stopped nhyperspace parents commitlisted
1524 global maxwidth onscreen displist currentparents olddlevel
1524 global maxwidth onscreen displist currentparents olddlevel
1525
1525
1526 set n [llength $displayorder]
1526 set n [llength $displayorder]
1527 while {$numcommits < $n} {
1527 while {$numcommits < $n} {
1528 set id [lindex $displayorder $numcommits]
1528 set id [lindex $displayorder $numcommits]
1529 set ctxend [expr {$numcommits + 10}]
1529 set ctxend [expr {$numcommits + 10}]
1530 if {!$reading && $ctxend > $n} {
1530 if {!$reading && $ctxend > $n} {
1531 set ctxend $n
1531 set ctxend $n
1532 }
1532 }
1533 set dlist {}
1533 set dlist {}
1534 if {$numcommits > 0} {
1534 if {$numcommits > 0} {
1535 set dlist [lreplace $displist $olddlevel $olddlevel]
1535 set dlist [lreplace $displist $olddlevel $olddlevel]
1536 set i $olddlevel
1536 set i $olddlevel
1537 foreach p $currentparents {
1537 foreach p $currentparents {
1538 if {$onscreen($p) == 0} {
1538 if {$onscreen($p) == 0} {
1539 set dlist [linsert $dlist $i $p]
1539 set dlist [linsert $dlist $i $p]
1540 incr i
1540 incr i
1541 }
1541 }
1542 }
1542 }
1543 }
1543 }
1544 set nohs {}
1544 set nohs {}
1545 set reins {}
1545 set reins {}
1546 set isfat [expr {[llength $dlist] > $maxwidth}]
1546 set isfat [expr {[llength $dlist] > $maxwidth}]
1547 if {$nhyperspace > 0 || $isfat} {
1547 if {$nhyperspace > 0 || $isfat} {
1548 if {$ctxend > $n} break
1548 if {$ctxend > $n} break
1549 # work out what to bring back and
1549 # work out what to bring back and
1550 # what we want to don't want to send into hyperspace
1550 # what we want to don't want to send into hyperspace
1551 set room 1
1551 set room 1
1552 for {set k $numcommits} {$k < $ctxend} {incr k} {
1552 for {set k $numcommits} {$k < $ctxend} {incr k} {
1553 set x [lindex $displayorder $k]
1553 set x [lindex $displayorder $k]
1554 set i [llsearch $dlist $x]
1554 set i [llsearch $dlist $x]
1555 if {$i < 0} {
1555 if {$i < 0} {
1556 set i [llength $dlist]
1556 set i [llength $dlist]
1557 lappend dlist $x
1557 lappend dlist $x
1558 }
1558 }
1559 if {[lsearch -exact $nohs $x] < 0} {
1559 if {[lsearch -exact $nohs $x] < 0} {
1560 lappend nohs $x
1560 lappend nohs $x
1561 }
1561 }
1562 if {$reins eq {} && $onscreen($x) < 0 && $room} {
1562 if {$reins eq {} && $onscreen($x) < 0 && $room} {
1563 set reins [list $x $i]
1563 set reins [list $x $i]
1564 }
1564 }
1565 set newp {}
1565 set newp {}
1566 if {[info exists commitlisted($x)]} {
1566 if {[info exists commitlisted($x)]} {
1567 set right 0
1567 set right 0
1568 foreach p $parents($x) {
1568 foreach p $parents($x) {
1569 if {[llsearch $dlist $p] < 0} {
1569 if {[llsearch $dlist $p] < 0} {
1570 lappend newp $p
1570 lappend newp $p
1571 if {[lsearch -exact $nohs $p] < 0} {
1571 if {[lsearch -exact $nohs $p] < 0} {
1572 lappend nohs $p
1572 lappend nohs $p
1573 }
1573 }
1574 if {$reins eq {} && $onscreen($p) < 0 && $room} {
1574 if {$reins eq {} && $onscreen($p) < 0 && $room} {
1575 set reins [list $p [expr {$i + $right}]]
1575 set reins [list $p [expr {$i + $right}]]
1576 }
1576 }
1577 }
1577 }
1578 set right 1
1578 set right 1
1579 }
1579 }
1580 }
1580 }
1581 set l [lindex $dlist $i]
1581 set l [lindex $dlist $i]
1582 if {[llength $l] == 1} {
1582 if {[llength $l] == 1} {
1583 set l $newp
1583 set l $newp
1584 } else {
1584 } else {
1585 set j [lsearch -exact $l $x]
1585 set j [lsearch -exact $l $x]
1586 set l [concat [lreplace $l $j $j] $newp]
1586 set l [concat [lreplace $l $j $j] $newp]
1587 }
1587 }
1588 set dlist [lreplace $dlist $i $i $l]
1588 set dlist [lreplace $dlist $i $i $l]
1589 if {$room && $isfat && [llength $newp] <= 1} {
1589 if {$room && $isfat && [llength $newp] <= 1} {
1590 set room 0
1590 set room 0
1591 }
1591 }
1592 }
1592 }
1593 }
1593 }
1594
1594
1595 set dlevel [drawslants $id $reins $nohs]
1595 set dlevel [drawslants $id $reins $nohs]
1596 drawcommitline $dlevel
1596 drawcommitline $dlevel
1597 if {[clock clicks -milliseconds] >= $nextupdate
1597 if {[clock clicks -milliseconds] >= $nextupdate
1598 && $numcommits >= $ncmupdate} {
1598 && $numcommits >= $ncmupdate} {
1599 doupdate $reading
1599 doupdate $reading
1600 if {$stopped} break
1600 if {$stopped} break
1601 }
1601 }
1602 }
1602 }
1603 }
1603 }
1604
1604
1605 # level here is an index in todo
1605 # level here is an index in todo
1606 proc updatetodo {level noshortcut} {
1606 proc updatetodo {level noshortcut} {
1607 global ncleft todo nnewparents
1607 global ncleft todo nnewparents
1608 global commitlisted parents onscreen
1608 global commitlisted parents onscreen
1609
1609
1610 set id [lindex $todo $level]
1610 set id [lindex $todo $level]
1611 set olds {}
1611 set olds {}
1612 if {[info exists commitlisted($id)]} {
1612 if {[info exists commitlisted($id)]} {
1613 foreach p $parents($id) {
1613 foreach p $parents($id) {
1614 if {[lsearch -exact $olds $p] < 0} {
1614 if {[lsearch -exact $olds $p] < 0} {
1615 lappend olds $p
1615 lappend olds $p
1616 }
1616 }
1617 }
1617 }
1618 }
1618 }
1619 if {!$noshortcut && [llength $olds] == 1} {
1619 if {!$noshortcut && [llength $olds] == 1} {
1620 set p [lindex $olds 0]
1620 set p [lindex $olds 0]
1621 if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
1621 if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
1622 set ncleft($p) 0
1622 set ncleft($p) 0
1623 set todo [lreplace $todo $level $level $p]
1623 set todo [lreplace $todo $level $level $p]
1624 set onscreen($p) 0
1624 set onscreen($p) 0
1625 set nnewparents($id) 1
1625 set nnewparents($id) 1
1626 return 0
1626 return 0
1627 }
1627 }
1628 }
1628 }
1629
1629
1630 set todo [lreplace $todo $level $level]
1630 set todo [lreplace $todo $level $level]
1631 set i $level
1631 set i $level
1632 set n 0
1632 set n 0
1633 foreach p $olds {
1633 foreach p $olds {
1634 incr ncleft($p) -1
1634 incr ncleft($p) -1
1635 set k [lsearch -exact $todo $p]
1635 set k [lsearch -exact $todo $p]
1636 if {$k < 0} {
1636 if {$k < 0} {
1637 set todo [linsert $todo $i $p]
1637 set todo [linsert $todo $i $p]
1638 set onscreen($p) 0
1638 set onscreen($p) 0
1639 incr i
1639 incr i
1640 incr n
1640 incr n
1641 }
1641 }
1642 }
1642 }
1643 set nnewparents($id) $n
1643 set nnewparents($id) $n
1644
1644
1645 return 1
1645 return 1
1646 }
1646 }
1647
1647
1648 proc decidenext {{noread 0}} {
1648 proc decidenext {{noread 0}} {
1649 global ncleft todo
1649 global ncleft todo
1650 global datemode cdate
1650 global datemode cdate
1651 global commitinfo
1651 global commitinfo
1652
1652
1653 # choose which one to do next time around
1653 # choose which one to do next time around
1654 set todol [llength $todo]
1654 set todol [llength $todo]
1655 set level -1
1655 set level -1
1656 set latest {}
1656 set latest {}
1657 for {set k $todol} {[incr k -1] >= 0} {} {
1657 for {set k $todol} {[incr k -1] >= 0} {} {
1658 set p [lindex $todo $k]
1658 set p [lindex $todo $k]
1659 if {$ncleft($p) == 0} {
1659 if {$ncleft($p) == 0} {
1660 if {$datemode} {
1660 if {$datemode} {
1661 if {![info exists commitinfo($p)]} {
1661 if {![info exists commitinfo($p)]} {
1662 if {$noread} {
1662 if {$noread} {
1663 return {}
1663 return {}
1664 }
1664 }
1665 readcommit $p
1665 readcommit $p
1666 }
1666 }
1667 if {$latest == {} || $cdate($p) > $latest} {
1667 if {$latest == {} || $cdate($p) > $latest} {
1668 set level $k
1668 set level $k
1669 set latest $cdate($p)
1669 set latest $cdate($p)
1670 }
1670 }
1671 } else {
1671 } else {
1672 set level $k
1672 set level $k
1673 break
1673 break
1674 }
1674 }
1675 }
1675 }
1676 }
1676 }
1677 if {$level < 0} {
1677 if {$level < 0} {
1678 if {$todo != {}} {
1678 if {$todo != {}} {
1679 puts "ERROR: none of the pending commits can be done yet:"
1679 puts "ERROR: none of the pending commits can be done yet:"
1680 foreach p $todo {
1680 foreach p $todo {
1681 puts " $p ($ncleft($p))"
1681 puts " $p ($ncleft($p))"
1682 }
1682 }
1683 }
1683 }
1684 return -1
1684 return -1
1685 }
1685 }
1686
1686
1687 return $level
1687 return $level
1688 }
1688 }
1689
1689
1690 proc drawcommit {id} {
1690 proc drawcommit {id} {
1691 global phase todo nchildren datemode nextupdate
1691 global phase todo nchildren datemode nextupdate
1692 global numcommits ncmupdate displayorder todo onscreen
1692 global numcommits ncmupdate displayorder todo onscreen
1693
1693
1694 if {$phase != "incrdraw"} {
1694 if {$phase != "incrdraw"} {
1695 set phase incrdraw
1695 set phase incrdraw
1696 set displayorder {}
1696 set displayorder {}
1697 set todo {}
1697 set todo {}
1698 initgraph
1698 initgraph
1699 }
1699 }
1700 if {$nchildren($id) == 0} {
1700 if {$nchildren($id) == 0} {
1701 lappend todo $id
1701 lappend todo $id
1702 set onscreen($id) 0
1702 set onscreen($id) 0
1703 }
1703 }
1704 set level [decidenext 1]
1704 set level [decidenext 1]
1705 if {$level == {} || $id != [lindex $todo $level]} {
1705 if {$level == {} || $id != [lindex $todo $level]} {
1706 return
1706 return
1707 }
1707 }
1708 while 1 {
1708 while 1 {
1709 lappend displayorder [lindex $todo $level]
1709 lappend displayorder [lindex $todo $level]
1710 if {[updatetodo $level $datemode]} {
1710 if {[updatetodo $level $datemode]} {
1711 set level [decidenext 1]
1711 set level [decidenext 1]
1712 if {$level == {}} break
1712 if {$level == {}} break
1713 }
1713 }
1714 set id [lindex $todo $level]
1714 set id [lindex $todo $level]
1715 if {![info exists commitlisted($id)]} {
1715 if {![info exists commitlisted($id)]} {
1716 break
1716 break
1717 }
1717 }
1718 }
1718 }
1719 drawmore 1
1719 drawmore 1
1720 }
1720 }
1721
1721
1722 proc finishcommits {} {
1722 proc finishcommits {} {
1723 global phase
1723 global phase
1724 global canv mainfont ctext maincursor textcursor
1724 global canv mainfont ctext maincursor textcursor
1725
1725
1726 if {$phase != "incrdraw"} {
1726 if {$phase != "incrdraw"} {
1727 $canv delete all
1727 $canv delete all
1728 $canv create text 3 3 -anchor nw -text "No commits selected" \
1728 $canv create text 3 3 -anchor nw -text "No commits selected" \
1729 -font $mainfont -tags textitems
1729 -font $mainfont -tags textitems
1730 set phase {}
1730 set phase {}
1731 } else {
1731 } else {
1732 drawrest
1732 drawrest
1733 }
1733 }
1734 . config -cursor $maincursor
1734 . config -cursor $maincursor
1735 settextcursor $textcursor
1735 settextcursor $textcursor
1736 }
1736 }
1737
1737
1738 # Don't change the text pane cursor if it is currently the hand cursor,
1738 # Don't change the text pane cursor if it is currently the hand cursor,
1739 # showing that we are over a sha1 ID link.
1739 # showing that we are over a sha1 ID link.
1740 proc settextcursor {c} {
1740 proc settextcursor {c} {
1741 global ctext curtextcursor
1741 global ctext curtextcursor
1742
1742
1743 if {[$ctext cget -cursor] == $curtextcursor} {
1743 if {[$ctext cget -cursor] == $curtextcursor} {
1744 $ctext config -cursor $c
1744 $ctext config -cursor $c
1745 }
1745 }
1746 set curtextcursor $c
1746 set curtextcursor $c
1747 }
1747 }
1748
1748
1749 proc drawgraph {} {
1749 proc drawgraph {} {
1750 global nextupdate startmsecs ncmupdate
1750 global nextupdate startmsecs ncmupdate
1751 global displayorder onscreen
1751 global displayorder onscreen
1752
1752
1753 if {$displayorder == {}} return
1753 if {$displayorder == {}} return
1754 set startmsecs [clock clicks -milliseconds]
1754 set startmsecs [clock clicks -milliseconds]
1755 set nextupdate [expr $startmsecs + 100]
1755 set nextupdate [expr $startmsecs + 100]
1756 set ncmupdate 1
1756 set ncmupdate 1
1757 initgraph
1757 initgraph
1758 foreach id $displayorder {
1758 foreach id $displayorder {
1759 set onscreen($id) 0
1759 set onscreen($id) 0
1760 }
1760 }
1761 drawmore 0
1761 drawmore 0
1762 }
1762 }
1763
1763
1764 proc drawrest {} {
1764 proc drawrest {} {
1765 global phase stopped redisplaying selectedline
1765 global phase stopped redisplaying selectedline
1766 global datemode todo displayorder
1766 global datemode todo displayorder
1767 global numcommits ncmupdate
1767 global numcommits ncmupdate
1768 global nextupdate startmsecs
1768 global nextupdate startmsecs
1769
1769
1770 set level [decidenext]
1770 set level [decidenext]
1771 if {$level >= 0} {
1771 if {$level >= 0} {
1772 set phase drawgraph
1772 set phase drawgraph
1773 while 1 {
1773 while 1 {
1774 lappend displayorder [lindex $todo $level]
1774 lappend displayorder [lindex $todo $level]
1775 set hard [updatetodo $level $datemode]
1775 set hard [updatetodo $level $datemode]
1776 if {$hard} {
1776 if {$hard} {
1777 set level [decidenext]
1777 set level [decidenext]
1778 if {$level < 0} break
1778 if {$level < 0} break
1779 }
1779 }
1780 }
1780 }
1781 drawmore 0
1781 drawmore 0
1782 }
1782 }
1783 set phase {}
1783 set phase {}
1784 set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
1784 set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
1785 #puts "overall $drawmsecs ms for $numcommits commits"
1785 #puts "overall $drawmsecs ms for $numcommits commits"
1786 if {$redisplaying} {
1786 if {$redisplaying} {
1787 if {$stopped == 0 && [info exists selectedline]} {
1787 if {$stopped == 0 && [info exists selectedline]} {
1788 selectline $selectedline 0
1788 selectline $selectedline 0
1789 }
1789 }
1790 if {$stopped == 1} {
1790 if {$stopped == 1} {
1791 set stopped 0
1791 set stopped 0
1792 after idle drawgraph
1792 after idle drawgraph
1793 } else {
1793 } else {
1794 set redisplaying 0
1794 set redisplaying 0
1795 }
1795 }
1796 }
1796 }
1797 }
1797 }
1798
1798
1799 proc findmatches {f} {
1799 proc findmatches {f} {
1800 global findtype foundstring foundstrlen
1800 global findtype foundstring foundstrlen
1801 if {$findtype == "Regexp"} {
1801 if {$findtype == "Regexp"} {
1802 set matches [regexp -indices -all -inline $foundstring $f]
1802 set matches [regexp -indices -all -inline $foundstring $f]
1803 } else {
1803 } else {
1804 if {$findtype == "IgnCase"} {
1804 if {$findtype == "IgnCase"} {
1805 set str [string tolower $f]
1805 set str [string tolower $f]
1806 } else {
1806 } else {
1807 set str $f
1807 set str $f
1808 }
1808 }
1809 set matches {}
1809 set matches {}
1810 set i 0
1810 set i 0
1811 while {[set j [string first $foundstring $str $i]] >= 0} {
1811 while {[set j [string first $foundstring $str $i]] >= 0} {
1812 lappend matches [list $j [expr $j+$foundstrlen-1]]
1812 lappend matches [list $j [expr $j+$foundstrlen-1]]
1813 set i [expr $j + $foundstrlen]
1813 set i [expr $j + $foundstrlen]
1814 }
1814 }
1815 }
1815 }
1816 return $matches
1816 return $matches
1817 }
1817 }
1818
1818
1819 proc dofind {} {
1819 proc dofind {} {
1820 global findtype findloc findstring markedmatches commitinfo
1820 global findtype findloc findstring markedmatches commitinfo
1821 global numcommits lineid linehtag linentag linedtag
1821 global numcommits lineid linehtag linentag linedtag
1822 global mainfont namefont canv canv2 canv3 selectedline
1822 global mainfont namefont canv canv2 canv3 selectedline
1823 global matchinglines foundstring foundstrlen
1823 global matchinglines foundstring foundstrlen
1824
1824
1825 stopfindproc
1825 stopfindproc
1826 unmarkmatches
1826 unmarkmatches
1827 focus .
1827 focus .
1828 set matchinglines {}
1828 set matchinglines {}
1829 if {$findloc == "Pickaxe"} {
1829 if {$findloc == "Pickaxe"} {
1830 findpatches
1830 findpatches
1831 return
1831 return
1832 }
1832 }
1833 if {$findtype == "IgnCase"} {
1833 if {$findtype == "IgnCase"} {
1834 set foundstring [string tolower $findstring]
1834 set foundstring [string tolower $findstring]
1835 } else {
1835 } else {
1836 set foundstring $findstring
1836 set foundstring $findstring
1837 }
1837 }
1838 set foundstrlen [string length $findstring]
1838 set foundstrlen [string length $findstring]
1839 if {$foundstrlen == 0} return
1839 if {$foundstrlen == 0} return
1840 if {$findloc == "Files"} {
1840 if {$findloc == "Files"} {
1841 findfiles
1841 findfiles
1842 return
1842 return
1843 }
1843 }
1844 if {![info exists selectedline]} {
1844 if {![info exists selectedline]} {
1845 set oldsel -1
1845 set oldsel -1
1846 } else {
1846 } else {
1847 set oldsel $selectedline
1847 set oldsel $selectedline
1848 }
1848 }
1849 set didsel 0
1849 set didsel 0
1850 set fldtypes {Headline Author Date Committer CDate Comment}
1850 set fldtypes {Headline Author Date Committer CDate Comment}
1851 for {set l 0} {$l < $numcommits} {incr l} {
1851 for {set l 0} {$l < $numcommits} {incr l} {
1852 set id $lineid($l)
1852 set id $lineid($l)
1853 set info $commitinfo($id)
1853 set info $commitinfo($id)
1854 set doesmatch 0
1854 set doesmatch 0
1855 foreach f $info ty $fldtypes {
1855 foreach f $info ty $fldtypes {
1856 if {$findloc != "All fields" && $findloc != $ty} {
1856 if {$findloc != "All fields" && $findloc != $ty} {
1857 continue
1857 continue
1858 }
1858 }
1859 set matches [findmatches $f]
1859 set matches [findmatches $f]
1860 if {$matches == {}} continue
1860 if {$matches == {}} continue
1861 set doesmatch 1
1861 set doesmatch 1
1862 if {$ty == "Headline"} {
1862 if {$ty == "Headline"} {
1863 markmatches $canv $l $f $linehtag($l) $matches $mainfont
1863 markmatches $canv $l $f $linehtag($l) $matches $mainfont
1864 } elseif {$ty == "Author"} {
1864 } elseif {$ty == "Author"} {
1865 markmatches $canv2 $l $f $linentag($l) $matches $namefont
1865 markmatches $canv2 $l $f $linentag($l) $matches $namefont
1866 } elseif {$ty == "Date"} {
1866 } elseif {$ty == "Date"} {
1867 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1867 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1868 }
1868 }
1869 }
1869 }
1870 if {$doesmatch} {
1870 if {$doesmatch} {
1871 lappend matchinglines $l
1871 lappend matchinglines $l
1872 if {!$didsel && $l > $oldsel} {
1872 if {!$didsel && $l > $oldsel} {
1873 findselectline $l
1873 findselectline $l
1874 set didsel 1
1874 set didsel 1
1875 }
1875 }
1876 }
1876 }
1877 }
1877 }
1878 if {$matchinglines == {}} {
1878 if {$matchinglines == {}} {
1879 bell
1879 bell
1880 } elseif {!$didsel} {
1880 } elseif {!$didsel} {
1881 findselectline [lindex $matchinglines 0]
1881 findselectline [lindex $matchinglines 0]
1882 }
1882 }
1883 }
1883 }
1884
1884
1885 proc findselectline {l} {
1885 proc findselectline {l} {
1886 global findloc commentend ctext
1886 global findloc commentend ctext
1887 selectline $l 1
1887 selectline $l 1
1888 if {$findloc == "All fields" || $findloc == "Comments"} {
1888 if {$findloc == "All fields" || $findloc == "Comments"} {
1889 # highlight the matches in the comments
1889 # highlight the matches in the comments
1890 set f [$ctext get 1.0 $commentend]
1890 set f [$ctext get 1.0 $commentend]
1891 set matches [findmatches $f]
1891 set matches [findmatches $f]
1892 foreach match $matches {
1892 foreach match $matches {
1893 set start [lindex $match 0]
1893 set start [lindex $match 0]
1894 set end [expr [lindex $match 1] + 1]
1894 set end [expr [lindex $match 1] + 1]
1895 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
1895 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
1896 }
1896 }
1897 }
1897 }
1898 }
1898 }
1899
1899
1900 proc findnext {restart} {
1900 proc findnext {restart} {
1901 global matchinglines selectedline
1901 global matchinglines selectedline
1902 if {![info exists matchinglines]} {
1902 if {![info exists matchinglines]} {
1903 if {$restart} {
1903 if {$restart} {
1904 dofind
1904 dofind
1905 }
1905 }
1906 return
1906 return
1907 }
1907 }
1908 if {![info exists selectedline]} return
1908 if {![info exists selectedline]} return
1909 foreach l $matchinglines {
1909 foreach l $matchinglines {
1910 if {$l > $selectedline} {
1910 if {$l > $selectedline} {
1911 findselectline $l
1911 findselectline $l
1912 return
1912 return
1913 }
1913 }
1914 }
1914 }
1915 bell
1915 bell
1916 }
1916 }
1917
1917
1918 proc findprev {} {
1918 proc findprev {} {
1919 global matchinglines selectedline
1919 global matchinglines selectedline
1920 if {![info exists matchinglines]} {
1920 if {![info exists matchinglines]} {
1921 dofind
1921 dofind
1922 return
1922 return
1923 }
1923 }
1924 if {![info exists selectedline]} return
1924 if {![info exists selectedline]} return
1925 set prev {}
1925 set prev {}
1926 foreach l $matchinglines {
1926 foreach l $matchinglines {
1927 if {$l >= $selectedline} break
1927 if {$l >= $selectedline} break
1928 set prev $l
1928 set prev $l
1929 }
1929 }
1930 if {$prev != {}} {
1930 if {$prev != {}} {
1931 findselectline $prev
1931 findselectline $prev
1932 } else {
1932 } else {
1933 bell
1933 bell
1934 }
1934 }
1935 }
1935 }
1936
1936
1937 proc findlocchange {name ix op} {
1937 proc findlocchange {name ix op} {
1938 global findloc findtype findtypemenu
1938 global findloc findtype findtypemenu
1939 if {$findloc == "Pickaxe"} {
1939 if {$findloc == "Pickaxe"} {
1940 set findtype Exact
1940 set findtype Exact
1941 set state disabled
1941 set state disabled
1942 } else {
1942 } else {
1943 set state normal
1943 set state normal
1944 }
1944 }
1945 $findtypemenu entryconf 1 -state $state
1945 $findtypemenu entryconf 1 -state $state
1946 $findtypemenu entryconf 2 -state $state
1946 $findtypemenu entryconf 2 -state $state
1947 }
1947 }
1948
1948
1949 proc stopfindproc {{done 0}} {
1949 proc stopfindproc {{done 0}} {
1950 global findprocpid findprocfile findids
1950 global findprocpid findprocfile findids
1951 global ctext findoldcursor phase maincursor textcursor
1951 global ctext findoldcursor phase maincursor textcursor
1952 global findinprogress
1952 global findinprogress
1953
1953
1954 catch {unset findids}
1954 catch {unset findids}
1955 if {[info exists findprocpid]} {
1955 if {[info exists findprocpid]} {
1956 if {!$done} {
1956 if {!$done} {
1957 catch {exec kill $findprocpid}
1957 catch {exec kill $findprocpid}
1958 }
1958 }
1959 catch {close $findprocfile}
1959 catch {close $findprocfile}
1960 unset findprocpid
1960 unset findprocpid
1961 }
1961 }
1962 if {[info exists findinprogress]} {
1962 if {[info exists findinprogress]} {
1963 unset findinprogress
1963 unset findinprogress
1964 if {$phase != "incrdraw"} {
1964 if {$phase != "incrdraw"} {
1965 . config -cursor $maincursor
1965 . config -cursor $maincursor
1966 settextcursor $textcursor
1966 settextcursor $textcursor
1967 }
1967 }
1968 }
1968 }
1969 }
1969 }
1970
1970
1971 proc findpatches {} {
1971 proc findpatches {} {
1972 global findstring selectedline numcommits
1972 global findstring selectedline numcommits
1973 global findprocpid findprocfile
1973 global findprocpid findprocfile
1974 global finddidsel ctext lineid findinprogress
1974 global finddidsel ctext lineid findinprogress
1975 global findinsertpos
1975 global findinsertpos
1976 global env
1976 global env
1977
1977
1978 if {$numcommits == 0} return
1978 if {$numcommits == 0} return
1979
1979
1980 # make a list of all the ids to search, starting at the one
1980 # make a list of all the ids to search, starting at the one
1981 # after the selected line (if any)
1981 # after the selected line (if any)
1982 if {[info exists selectedline]} {
1982 if {[info exists selectedline]} {
1983 set l $selectedline
1983 set l $selectedline
1984 } else {
1984 } else {
1985 set l -1
1985 set l -1
1986 }
1986 }
1987 set inputids {}
1987 set inputids {}
1988 for {set i 0} {$i < $numcommits} {incr i} {
1988 for {set i 0} {$i < $numcommits} {incr i} {
1989 if {[incr l] >= $numcommits} {
1989 if {[incr l] >= $numcommits} {
1990 set l 0
1990 set l 0
1991 }
1991 }
1992 append inputids $lineid($l) "\n"
1992 append inputids $lineid($l) "\n"
1993 }
1993 }
1994
1994
1995 if {[catch {
1995 if {[catch {
1996 set f [open [list | $env(HG) --config ui.report_untrusted=false debug-diff-tree --stdin -s -r -S$findstring << $inputids] r]
1996 set f [open [list | $env(HG) --config ui.report_untrusted=false debug-diff-tree --stdin -s -r -S$findstring << $inputids] r]
1997 } err]} {
1997 } err]} {
1998 error_popup "Error starting search process: $err"
1998 error_popup "Error starting search process: $err"
1999 return
1999 return
2000 }
2000 }
2001
2001
2002 set findinsertpos end
2002 set findinsertpos end
2003 set findprocfile $f
2003 set findprocfile $f
2004 set findprocpid [pid $f]
2004 set findprocpid [pid $f]
2005 fconfigure $f -blocking 0
2005 fconfigure $f -blocking 0
2006 fileevent $f readable readfindproc
2006 fileevent $f readable readfindproc
2007 set finddidsel 0
2007 set finddidsel 0
2008 . config -cursor watch
2008 . config -cursor watch
2009 settextcursor watch
2009 settextcursor watch
2010 set findinprogress 1
2010 set findinprogress 1
2011 }
2011 }
2012
2012
2013 proc readfindproc {} {
2013 proc readfindproc {} {
2014 global findprocfile finddidsel
2014 global findprocfile finddidsel
2015 global idline matchinglines findinsertpos
2015 global idline matchinglines findinsertpos
2016
2016
2017 set n [gets $findprocfile line]
2017 set n [gets $findprocfile line]
2018 if {$n < 0} {
2018 if {$n < 0} {
2019 if {[eof $findprocfile]} {
2019 if {[eof $findprocfile]} {
2020 stopfindproc 1
2020 stopfindproc 1
2021 if {!$finddidsel} {
2021 if {!$finddidsel} {
2022 bell
2022 bell
2023 }
2023 }
2024 }
2024 }
2025 return
2025 return
2026 }
2026 }
2027 if {![regexp {^[0-9a-f]{12}} $line id]} {
2027 if {![regexp {^[0-9a-f]{12}} $line id]} {
2028 error_popup "Can't parse git-diff-tree output: $line"
2028 error_popup "Can't parse git-diff-tree output: $line"
2029 stopfindproc
2029 stopfindproc
2030 return
2030 return
2031 }
2031 }
2032 if {![info exists idline($id)]} {
2032 if {![info exists idline($id)]} {
2033 puts stderr "spurious id: $id"
2033 puts stderr "spurious id: $id"
2034 return
2034 return
2035 }
2035 }
2036 set l $idline($id)
2036 set l $idline($id)
2037 insertmatch $l $id
2037 insertmatch $l $id
2038 }
2038 }
2039
2039
2040 proc insertmatch {l id} {
2040 proc insertmatch {l id} {
2041 global matchinglines findinsertpos finddidsel
2041 global matchinglines findinsertpos finddidsel
2042
2042
2043 if {$findinsertpos == "end"} {
2043 if {$findinsertpos == "end"} {
2044 if {$matchinglines != {} && $l < [lindex $matchinglines 0]} {
2044 if {$matchinglines != {} && $l < [lindex $matchinglines 0]} {
2045 set matchinglines [linsert $matchinglines 0 $l]
2045 set matchinglines [linsert $matchinglines 0 $l]
2046 set findinsertpos 1
2046 set findinsertpos 1
2047 } else {
2047 } else {
2048 lappend matchinglines $l
2048 lappend matchinglines $l
2049 }
2049 }
2050 } else {
2050 } else {
2051 set matchinglines [linsert $matchinglines $findinsertpos $l]
2051 set matchinglines [linsert $matchinglines $findinsertpos $l]
2052 incr findinsertpos
2052 incr findinsertpos
2053 }
2053 }
2054 markheadline $l $id
2054 markheadline $l $id
2055 if {!$finddidsel} {
2055 if {!$finddidsel} {
2056 findselectline $l
2056 findselectline $l
2057 set finddidsel 1
2057 set finddidsel 1
2058 }
2058 }
2059 }
2059 }
2060
2060
2061 proc findfiles {} {
2061 proc findfiles {} {
2062 global selectedline numcommits lineid ctext
2062 global selectedline numcommits lineid ctext
2063 global ffileline finddidsel parents nparents
2063 global ffileline finddidsel parents nparents
2064 global findinprogress findstartline findinsertpos
2064 global findinprogress findstartline findinsertpos
2065 global treediffs fdiffids fdiffsneeded fdiffpos
2065 global treediffs fdiffids fdiffsneeded fdiffpos
2066 global findmergefiles
2066 global findmergefiles
2067 global env
2067 global env
2068
2068
2069 if {$numcommits == 0} return
2069 if {$numcommits == 0} return
2070
2070
2071 if {[info exists selectedline]} {
2071 if {[info exists selectedline]} {
2072 set l [expr {$selectedline + 1}]
2072 set l [expr {$selectedline + 1}]
2073 } else {
2073 } else {
2074 set l 0
2074 set l 0
2075 }
2075 }
2076 set ffileline $l
2076 set ffileline $l
2077 set findstartline $l
2077 set findstartline $l
2078 set diffsneeded {}
2078 set diffsneeded {}
2079 set fdiffsneeded {}
2079 set fdiffsneeded {}
2080 while 1 {
2080 while 1 {
2081 set id $lineid($l)
2081 set id $lineid($l)
2082 if {$findmergefiles || $nparents($id) == 1} {
2082 if {$findmergefiles || $nparents($id) == 1} {
2083 foreach p $parents($id) {
2083 foreach p $parents($id) {
2084 if {![info exists treediffs([list $id $p])]} {
2084 if {![info exists treediffs([list $id $p])]} {
2085 append diffsneeded "$id $p\n"
2085 append diffsneeded "$id $p\n"
2086 lappend fdiffsneeded [list $id $p]
2086 lappend fdiffsneeded [list $id $p]
2087 }
2087 }
2088 }
2088 }
2089 }
2089 }
2090 if {[incr l] >= $numcommits} {
2090 if {[incr l] >= $numcommits} {
2091 set l 0
2091 set l 0
2092 }
2092 }
2093 if {$l == $findstartline} break
2093 if {$l == $findstartline} break
2094 }
2094 }
2095
2095
2096 # start off a git-diff-tree process if needed
2096 # start off a git-diff-tree process if needed
2097 if {$diffsneeded ne {}} {
2097 if {$diffsneeded ne {}} {
2098 if {[catch {
2098 if {[catch {
2099 set df [open [list | $env(HG) --config ui.report_untrusted=false debug-diff-tree -r --stdin << $diffsneeded] r]
2099 set df [open [list | $env(HG) --config ui.report_untrusted=false debug-diff-tree -r --stdin << $diffsneeded] r]
2100 } err ]} {
2100 } err ]} {
2101 error_popup "Error starting search process: $err"
2101 error_popup "Error starting search process: $err"
2102 return
2102 return
2103 }
2103 }
2104 catch {unset fdiffids}
2104 catch {unset fdiffids}
2105 set fdiffpos 0
2105 set fdiffpos 0
2106 fconfigure $df -blocking 0
2106 fconfigure $df -blocking 0
2107 fileevent $df readable [list readfilediffs $df]
2107 fileevent $df readable [list readfilediffs $df]
2108 }
2108 }
2109
2109
2110 set finddidsel 0
2110 set finddidsel 0
2111 set findinsertpos end
2111 set findinsertpos end
2112 set id $lineid($l)
2112 set id $lineid($l)
2113 set p [lindex $parents($id) 0]
2113 set p [lindex $parents($id) 0]
2114 . config -cursor watch
2114 . config -cursor watch
2115 settextcursor watch
2115 settextcursor watch
2116 set findinprogress 1
2116 set findinprogress 1
2117 findcont [list $id $p]
2117 findcont [list $id $p]
2118 update
2118 update
2119 }
2119 }
2120
2120
2121 proc readfilediffs {df} {
2121 proc readfilediffs {df} {
2122 global findids fdiffids fdiffs
2122 global findids fdiffids fdiffs
2123
2123
2124 set n [gets $df line]
2124 set n [gets $df line]
2125 if {$n < 0} {
2125 if {$n < 0} {
2126 if {[eof $df]} {
2126 if {[eof $df]} {
2127 donefilediff
2127 donefilediff
2128 if {[catch {close $df} err]} {
2128 if {[catch {close $df} err]} {
2129 stopfindproc
2129 stopfindproc
2130 bell
2130 bell
2131 error_popup "Error in hg debug-diff-tree: $err"
2131 error_popup "Error in hg debug-diff-tree: $err"
2132 } elseif {[info exists findids]} {
2132 } elseif {[info exists findids]} {
2133 set ids $findids
2133 set ids $findids
2134 stopfindproc
2134 stopfindproc
2135 bell
2135 bell
2136 error_popup "Couldn't find diffs for {$ids}"
2136 error_popup "Couldn't find diffs for {$ids}"
2137 }
2137 }
2138 }
2138 }
2139 return
2139 return
2140 }
2140 }
2141 if {[regexp {^([0-9a-f]{12}) \(from ([0-9a-f]{12})\)} $line match id p]} {
2141 if {[regexp {^([0-9a-f]{12}) \(from ([0-9a-f]{12})\)} $line match id p]} {
2142 # start of a new string of diffs
2142 # start of a new string of diffs
2143 donefilediff
2143 donefilediff
2144 set fdiffids [list $id $p]
2144 set fdiffids [list $id $p]
2145 set fdiffs {}
2145 set fdiffs {}
2146 } elseif {[string match ":*" $line]} {
2146 } elseif {[string match ":*" $line]} {
2147 lappend fdiffs [lindex $line 5]
2147 lappend fdiffs [lindex $line 5]
2148 }
2148 }
2149 }
2149 }
2150
2150
2151 proc donefilediff {} {
2151 proc donefilediff {} {
2152 global fdiffids fdiffs treediffs findids
2152 global fdiffids fdiffs treediffs findids
2153 global fdiffsneeded fdiffpos
2153 global fdiffsneeded fdiffpos
2154
2154
2155 if {[info exists fdiffids]} {
2155 if {[info exists fdiffids]} {
2156 while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids
2156 while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids
2157 && $fdiffpos < [llength $fdiffsneeded]} {
2157 && $fdiffpos < [llength $fdiffsneeded]} {
2158 # git-diff-tree doesn't output anything for a commit
2158 # git-diff-tree doesn't output anything for a commit
2159 # which doesn't change anything
2159 # which doesn't change anything
2160 set nullids [lindex $fdiffsneeded $fdiffpos]
2160 set nullids [lindex $fdiffsneeded $fdiffpos]
2161 set treediffs($nullids) {}
2161 set treediffs($nullids) {}
2162 if {[info exists findids] && $nullids eq $findids} {
2162 if {[info exists findids] && $nullids eq $findids} {
2163 unset findids
2163 unset findids
2164 findcont $nullids
2164 findcont $nullids
2165 }
2165 }
2166 incr fdiffpos
2166 incr fdiffpos
2167 }
2167 }
2168 incr fdiffpos
2168 incr fdiffpos
2169
2169
2170 if {![info exists treediffs($fdiffids)]} {
2170 if {![info exists treediffs($fdiffids)]} {
2171 set treediffs($fdiffids) $fdiffs
2171 set treediffs($fdiffids) $fdiffs
2172 }
2172 }
2173 if {[info exists findids] && $fdiffids eq $findids} {
2173 if {[info exists findids] && $fdiffids eq $findids} {
2174 unset findids
2174 unset findids
2175 findcont $fdiffids
2175 findcont $fdiffids
2176 }
2176 }
2177 }
2177 }
2178 }
2178 }
2179
2179
2180 proc findcont {ids} {
2180 proc findcont {ids} {
2181 global findids treediffs parents nparents
2181 global findids treediffs parents nparents
2182 global ffileline findstartline finddidsel
2182 global ffileline findstartline finddidsel
2183 global lineid numcommits matchinglines findinprogress
2183 global lineid numcommits matchinglines findinprogress
2184 global findmergefiles
2184 global findmergefiles
2185
2185
2186 set id [lindex $ids 0]
2186 set id [lindex $ids 0]
2187 set p [lindex $ids 1]
2187 set p [lindex $ids 1]
2188 set pi [lsearch -exact $parents($id) $p]
2188 set pi [lsearch -exact $parents($id) $p]
2189 set l $ffileline
2189 set l $ffileline
2190 while 1 {
2190 while 1 {
2191 if {$findmergefiles || $nparents($id) == 1} {
2191 if {$findmergefiles || $nparents($id) == 1} {
2192 if {![info exists treediffs($ids)]} {
2192 if {![info exists treediffs($ids)]} {
2193 set findids $ids
2193 set findids $ids
2194 set ffileline $l
2194 set ffileline $l
2195 return
2195 return
2196 }
2196 }
2197 set doesmatch 0
2197 set doesmatch 0
2198 foreach f $treediffs($ids) {
2198 foreach f $treediffs($ids) {
2199 set x [findmatches $f]
2199 set x [findmatches $f]
2200 if {$x != {}} {
2200 if {$x != {}} {
2201 set doesmatch 1
2201 set doesmatch 1
2202 break
2202 break
2203 }
2203 }
2204 }
2204 }
2205 if {$doesmatch} {
2205 if {$doesmatch} {
2206 insertmatch $l $id
2206 insertmatch $l $id
2207 set pi $nparents($id)
2207 set pi $nparents($id)
2208 }
2208 }
2209 } else {
2209 } else {
2210 set pi $nparents($id)
2210 set pi $nparents($id)
2211 }
2211 }
2212 if {[incr pi] >= $nparents($id)} {
2212 if {[incr pi] >= $nparents($id)} {
2213 set pi 0
2213 set pi 0
2214 if {[incr l] >= $numcommits} {
2214 if {[incr l] >= $numcommits} {
2215 set l 0
2215 set l 0
2216 }
2216 }
2217 if {$l == $findstartline} break
2217 if {$l == $findstartline} break
2218 set id $lineid($l)
2218 set id $lineid($l)
2219 }
2219 }
2220 set p [lindex $parents($id) $pi]
2220 set p [lindex $parents($id) $pi]
2221 set ids [list $id $p]
2221 set ids [list $id $p]
2222 }
2222 }
2223 stopfindproc
2223 stopfindproc
2224 if {!$finddidsel} {
2224 if {!$finddidsel} {
2225 bell
2225 bell
2226 }
2226 }
2227 }
2227 }
2228
2228
2229 # mark a commit as matching by putting a yellow background
2229 # mark a commit as matching by putting a yellow background
2230 # behind the headline
2230 # behind the headline
2231 proc markheadline {l id} {
2231 proc markheadline {l id} {
2232 global canv mainfont linehtag commitinfo
2232 global canv mainfont linehtag commitinfo
2233
2233
2234 set bbox [$canv bbox $linehtag($l)]
2234 set bbox [$canv bbox $linehtag($l)]
2235 set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
2235 set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
2236 $canv lower $t
2236 $canv lower $t
2237 }
2237 }
2238
2238
2239 # mark the bits of a headline, author or date that match a find string
2239 # mark the bits of a headline, author or date that match a find string
2240 proc markmatches {canv l str tag matches font} {
2240 proc markmatches {canv l str tag matches font} {
2241 set bbox [$canv bbox $tag]
2241 set bbox [$canv bbox $tag]
2242 set x0 [lindex $bbox 0]
2242 set x0 [lindex $bbox 0]
2243 set y0 [lindex $bbox 1]
2243 set y0 [lindex $bbox 1]
2244 set y1 [lindex $bbox 3]
2244 set y1 [lindex $bbox 3]
2245 foreach match $matches {
2245 foreach match $matches {
2246 set start [lindex $match 0]
2246 set start [lindex $match 0]
2247 set end [lindex $match 1]
2247 set end [lindex $match 1]
2248 if {$start > $end} continue
2248 if {$start > $end} continue
2249 set xoff [font measure $font [string range $str 0 [expr $start-1]]]
2249 set xoff [font measure $font [string range $str 0 [expr $start-1]]]
2250 set xlen [font measure $font [string range $str 0 [expr $end]]]
2250 set xlen [font measure $font [string range $str 0 [expr $end]]]
2251 set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
2251 set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
2252 -outline {} -tags matches -fill yellow]
2252 -outline {} -tags matches -fill yellow]
2253 $canv lower $t
2253 $canv lower $t
2254 }
2254 }
2255 }
2255 }
2256
2256
2257 proc unmarkmatches {} {
2257 proc unmarkmatches {} {
2258 global matchinglines findids
2258 global matchinglines findids
2259 allcanvs delete matches
2259 allcanvs delete matches
2260 catch {unset matchinglines}
2260 catch {unset matchinglines}
2261 catch {unset findids}
2261 catch {unset findids}
2262 }
2262 }
2263
2263
2264 proc selcanvline {w x y} {
2264 proc selcanvline {w x y} {
2265 global canv canvy0 ctext linespc
2265 global canv canvy0 ctext linespc
2266 global lineid linehtag linentag linedtag rowtextx
2266 global lineid linehtag linentag linedtag rowtextx
2267 set ymax [lindex [$canv cget -scrollregion] 3]
2267 set ymax [lindex [$canv cget -scrollregion] 3]
2268 if {$ymax == {}} return
2268 if {$ymax == {}} return
2269 set yfrac [lindex [$canv yview] 0]
2269 set yfrac [lindex [$canv yview] 0]
2270 set y [expr {$y + $yfrac * $ymax}]
2270 set y [expr {$y + $yfrac * $ymax}]
2271 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
2271 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
2272 if {$l < 0} {
2272 if {$l < 0} {
2273 set l 0
2273 set l 0
2274 }
2274 }
2275 if {$w eq $canv} {
2275 if {$w eq $canv} {
2276 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
2276 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
2277 }
2277 }
2278 unmarkmatches
2278 unmarkmatches
2279 selectline $l 1
2279 selectline $l 1
2280 }
2280 }
2281
2281
2282 proc commit_descriptor {p} {
2282 proc commit_descriptor {p} {
2283 global commitinfo
2283 global commitinfo
2284 set l "..."
2284 set l "..."
2285 if {[info exists commitinfo($p)]} {
2285 if {[info exists commitinfo($p)]} {
2286 set l [lindex $commitinfo($p) 0]
2286 set l [lindex $commitinfo($p) 0]
2287 set r [lindex $commitinfo($p) 6]
2287 set r [lindex $commitinfo($p) 6]
2288 }
2288 }
2289 return "$r:$p ($l)"
2289 return "$r:$p ($l)"
2290 }
2290 }
2291
2291
2292 # append some text to the ctext widget, and make any SHA1 ID
2292 # append some text to the ctext widget, and make any SHA1 ID
2293 # that we know about be a clickable link.
2293 # that we know about be a clickable link.
2294 proc appendwithlinks {text} {
2294 proc appendwithlinks {text} {
2295 global ctext idline linknum
2295 global ctext idline linknum
2296
2296
2297 set start [$ctext index "end - 1c"]
2297 set start [$ctext index "end - 1c"]
2298 $ctext insert end $text
2298 $ctext insert end $text
2299 $ctext insert end "\n"
2299 $ctext insert end "\n"
2300 set links [regexp -indices -all -inline {[0-9a-f]{12}} $text]
2300 set links [regexp -indices -all -inline {[0-9a-f]{12}} $text]
2301 foreach l $links {
2301 foreach l $links {
2302 set s [lindex $l 0]
2302 set s [lindex $l 0]
2303 set e [lindex $l 1]
2303 set e [lindex $l 1]
2304 set linkid [string range $text $s $e]
2304 set linkid [string range $text $s $e]
2305 if {![info exists idline($linkid)]} continue
2305 if {![info exists idline($linkid)]} continue
2306 incr e
2306 incr e
2307 $ctext tag add link "$start + $s c" "$start + $e c"
2307 $ctext tag add link "$start + $s c" "$start + $e c"
2308 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
2308 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
2309 $ctext tag bind link$linknum <1> [list selectline $idline($linkid) 1]
2309 $ctext tag bind link$linknum <1> [list selectline $idline($linkid) 1]
2310 incr linknum
2310 incr linknum
2311 }
2311 }
2312 $ctext tag conf link -foreground blue -underline 1
2312 $ctext tag conf link -foreground blue -underline 1
2313 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
2313 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
2314 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
2314 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
2315 }
2315 }
2316
2316
2317 proc selectline {l isnew} {
2317 proc selectline {l isnew} {
2318 global canv canv2 canv3 ctext commitinfo selectedline
2318 global canv canv2 canv3 ctext commitinfo selectedline
2319 global lineid linehtag linentag linedtag
2319 global lineid linehtag linentag linedtag
2320 global canvy0 linespc parents nparents children
2320 global canvy0 linespc parents nparents children
2321 global cflist currentid sha1entry
2321 global cflist currentid sha1entry
2322 global commentend idtags idline linknum
2322 global commentend idtags idline linknum
2323
2323
2324 $canv delete hover
2324 $canv delete hover
2325 normalline
2325 normalline
2326 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
2326 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
2327 $canv delete secsel
2327 $canv delete secsel
2328 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
2328 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
2329 -tags secsel -fill [$canv cget -selectbackground]]
2329 -tags secsel -fill [$canv cget -selectbackground]]
2330 $canv lower $t
2330 $canv lower $t
2331 $canv2 delete secsel
2331 $canv2 delete secsel
2332 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
2332 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
2333 -tags secsel -fill [$canv2 cget -selectbackground]]
2333 -tags secsel -fill [$canv2 cget -selectbackground]]
2334 $canv2 lower $t
2334 $canv2 lower $t
2335 $canv3 delete secsel
2335 $canv3 delete secsel
2336 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
2336 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
2337 -tags secsel -fill [$canv3 cget -selectbackground]]
2337 -tags secsel -fill [$canv3 cget -selectbackground]]
2338 $canv3 lower $t
2338 $canv3 lower $t
2339 set y [expr {$canvy0 + $l * $linespc}]
2339 set y [expr {$canvy0 + $l * $linespc}]
2340 set ymax [lindex [$canv cget -scrollregion] 3]
2340 set ymax [lindex [$canv cget -scrollregion] 3]
2341 set ytop [expr {$y - $linespc - 1}]
2341 set ytop [expr {$y - $linespc - 1}]
2342 set ybot [expr {$y + $linespc + 1}]
2342 set ybot [expr {$y + $linespc + 1}]
2343 set wnow [$canv yview]
2343 set wnow [$canv yview]
2344 set wtop [expr [lindex $wnow 0] * $ymax]
2344 set wtop [expr [lindex $wnow 0] * $ymax]
2345 set wbot [expr [lindex $wnow 1] * $ymax]
2345 set wbot [expr [lindex $wnow 1] * $ymax]
2346 set wh [expr {$wbot - $wtop}]
2346 set wh [expr {$wbot - $wtop}]
2347 set newtop $wtop
2347 set newtop $wtop
2348 if {$ytop < $wtop} {
2348 if {$ytop < $wtop} {
2349 if {$ybot < $wtop} {
2349 if {$ybot < $wtop} {
2350 set newtop [expr {$y - $wh / 2.0}]
2350 set newtop [expr {$y - $wh / 2.0}]
2351 } else {
2351 } else {
2352 set newtop $ytop
2352 set newtop $ytop
2353 if {$newtop > $wtop - $linespc} {
2353 if {$newtop > $wtop - $linespc} {
2354 set newtop [expr {$wtop - $linespc}]
2354 set newtop [expr {$wtop - $linespc}]
2355 }
2355 }
2356 }
2356 }
2357 } elseif {$ybot > $wbot} {
2357 } elseif {$ybot > $wbot} {
2358 if {$ytop > $wbot} {
2358 if {$ytop > $wbot} {
2359 set newtop [expr {$y - $wh / 2.0}]
2359 set newtop [expr {$y - $wh / 2.0}]
2360 } else {
2360 } else {
2361 set newtop [expr {$ybot - $wh}]
2361 set newtop [expr {$ybot - $wh}]
2362 if {$newtop < $wtop + $linespc} {
2362 if {$newtop < $wtop + $linespc} {
2363 set newtop [expr {$wtop + $linespc}]
2363 set newtop [expr {$wtop + $linespc}]
2364 }
2364 }
2365 }
2365 }
2366 }
2366 }
2367 if {$newtop != $wtop} {
2367 if {$newtop != $wtop} {
2368 if {$newtop < 0} {
2368 if {$newtop < 0} {
2369 set newtop 0
2369 set newtop 0
2370 }
2370 }
2371 allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
2371 allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
2372 }
2372 }
2373
2373
2374 if {$isnew} {
2374 if {$isnew} {
2375 addtohistory [list selectline $l 0]
2375 addtohistory [list selectline $l 0]
2376 }
2376 }
2377
2377
2378 set selectedline $l
2378 set selectedline $l
2379
2379
2380 set id $lineid($l)
2380 set id $lineid($l)
2381 set currentid $id
2381 set currentid $id
2382 $sha1entry delete 0 end
2382 $sha1entry delete 0 end
2383 $sha1entry insert 0 $id
2383 $sha1entry insert 0 $id
2384 $sha1entry selection from 0
2384 $sha1entry selection from 0
2385 $sha1entry selection to end
2385 $sha1entry selection to end
2386
2386
2387 $ctext conf -state normal
2387 $ctext conf -state normal
2388 $ctext delete 0.0 end
2388 $ctext delete 0.0 end
2389 set linknum 0
2389 set linknum 0
2390 $ctext mark set fmark.0 0.0
2390 $ctext mark set fmark.0 0.0
2391 $ctext mark gravity fmark.0 left
2391 $ctext mark gravity fmark.0 left
2392 set info $commitinfo($id)
2392 set info $commitinfo($id)
2393 $ctext insert end "Revision: [lindex $info 6]\n"
2393 $ctext insert end "Revision: [lindex $info 6]\n"
2394 if {[llength [lindex $info 7]] > 0} {
2394 if {[llength [lindex $info 7]] > 0} {
2395 $ctext insert end "Branch: [lindex $info 7]\n"
2395 $ctext insert end "Branch: [lindex $info 7]\n"
2396 }
2396 }
2397 $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
2397 $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
2398 $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
2398 $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
2399 if {[info exists idtags($id)]} {
2399 if {[info exists idtags($id)]} {
2400 $ctext insert end "Tags:"
2400 $ctext insert end "Tags:"
2401 foreach tag $idtags($id) {
2401 foreach tag $idtags($id) {
2402 $ctext insert end " $tag"
2402 $ctext insert end " $tag"
2403 }
2403 }
2404 $ctext insert end "\n"
2404 $ctext insert end "\n"
2405 }
2405 }
2406
2406
2407 set comment {}
2407 set comment {}
2408 if {[info exists parents($id)]} {
2408 if {[info exists parents($id)]} {
2409 foreach p $parents($id) {
2409 foreach p $parents($id) {
2410 append comment "Parent: [commit_descriptor $p]\n"
2410 append comment "Parent: [commit_descriptor $p]\n"
2411 }
2411 }
2412 }
2412 }
2413 if {[info exists children($id)]} {
2413 if {[info exists children($id)]} {
2414 foreach c $children($id) {
2414 foreach c $children($id) {
2415 append comment "Child: [commit_descriptor $c]\n"
2415 append comment "Child: [commit_descriptor $c]\n"
2416 }
2416 }
2417 }
2417 }
2418 append comment "\n"
2418 append comment "\n"
2419 append comment [lindex $info 5]
2419 append comment [lindex $info 5]
2420
2420
2421 # make anything that looks like a SHA1 ID be a clickable link
2421 # make anything that looks like a SHA1 ID be a clickable link
2422 appendwithlinks $comment
2422 appendwithlinks $comment
2423
2423
2424 $ctext tag delete Comments
2424 $ctext tag delete Comments
2425 $ctext tag remove found 1.0 end
2425 $ctext tag remove found 1.0 end
2426 $ctext conf -state disabled
2426 $ctext conf -state disabled
2427 set commentend [$ctext index "end - 1c"]
2427 set commentend [$ctext index "end - 1c"]
2428
2428
2429 $cflist delete 0 end
2429 $cflist delete 0 end
2430 $cflist insert end "Comments"
2430 $cflist insert end "Comments"
2431 if {$nparents($id) <= 1} {
2431 if {$nparents($id) <= 1} {
2432 set parent "null"
2432 set parent "null"
2433 if {$nparents($id) == 1} {
2433 if {$nparents($id) == 1} {
2434 set parent $parents($id)
2434 set parent $parents($id)
2435 }
2435 }
2436 startdiff [concat $id $parent]
2436 startdiff [concat $id $parent]
2437 } elseif {$nparents($id) > 1} {
2437 } elseif {$nparents($id) > 1} {
2438 mergediff $id
2438 mergediff $id
2439 }
2439 }
2440 }
2440 }
2441
2441
2442 proc selnextline {dir} {
2442 proc selnextline {dir} {
2443 global selectedline
2443 global selectedline
2444 if {![info exists selectedline]} return
2444 if {![info exists selectedline]} return
2445 set l [expr $selectedline + $dir]
2445 set l [expr $selectedline + $dir]
2446 unmarkmatches
2446 unmarkmatches
2447 selectline $l 1
2447 selectline $l 1
2448 }
2448 }
2449
2449
2450 proc unselectline {} {
2450 proc unselectline {} {
2451 global selectedline
2451 global selectedline
2452
2452
2453 catch {unset selectedline}
2453 catch {unset selectedline}
2454 allcanvs delete secsel
2454 allcanvs delete secsel
2455 }
2455 }
2456
2456
2457 proc addtohistory {cmd} {
2457 proc addtohistory {cmd} {
2458 global history historyindex
2458 global history historyindex
2459
2459
2460 if {$historyindex > 0
2460 if {$historyindex > 0
2461 && [lindex $history [expr {$historyindex - 1}]] == $cmd} {
2461 && [lindex $history [expr {$historyindex - 1}]] == $cmd} {
2462 return
2462 return
2463 }
2463 }
2464
2464
2465 if {$historyindex < [llength $history]} {
2465 if {$historyindex < [llength $history]} {
2466 set history [lreplace $history $historyindex end $cmd]
2466 set history [lreplace $history $historyindex end $cmd]
2467 } else {
2467 } else {
2468 lappend history $cmd
2468 lappend history $cmd
2469 }
2469 }
2470 incr historyindex
2470 incr historyindex
2471 if {$historyindex > 1} {
2471 if {$historyindex > 1} {
2472 .ctop.top.bar.leftbut conf -state normal
2472 .ctop.top.bar.leftbut conf -state normal
2473 } else {
2473 } else {
2474 .ctop.top.bar.leftbut conf -state disabled
2474 .ctop.top.bar.leftbut conf -state disabled
2475 }
2475 }
2476 .ctop.top.bar.rightbut conf -state disabled
2476 .ctop.top.bar.rightbut conf -state disabled
2477 }
2477 }
2478
2478
2479 proc goback {} {
2479 proc goback {} {
2480 global history historyindex
2480 global history historyindex
2481
2481
2482 if {$historyindex > 1} {
2482 if {$historyindex > 1} {
2483 incr historyindex -1
2483 incr historyindex -1
2484 set cmd [lindex $history [expr {$historyindex - 1}]]
2484 set cmd [lindex $history [expr {$historyindex - 1}]]
2485 eval $cmd
2485 eval $cmd
2486 .ctop.top.bar.rightbut conf -state normal
2486 .ctop.top.bar.rightbut conf -state normal
2487 }
2487 }
2488 if {$historyindex <= 1} {
2488 if {$historyindex <= 1} {
2489 .ctop.top.bar.leftbut conf -state disabled
2489 .ctop.top.bar.leftbut conf -state disabled
2490 }
2490 }
2491 }
2491 }
2492
2492
2493 proc goforw {} {
2493 proc goforw {} {
2494 global history historyindex
2494 global history historyindex
2495
2495
2496 if {$historyindex < [llength $history]} {
2496 if {$historyindex < [llength $history]} {
2497 set cmd [lindex $history $historyindex]
2497 set cmd [lindex $history $historyindex]
2498 incr historyindex
2498 incr historyindex
2499 eval $cmd
2499 eval $cmd
2500 .ctop.top.bar.leftbut conf -state normal
2500 .ctop.top.bar.leftbut conf -state normal
2501 }
2501 }
2502 if {$historyindex >= [llength $history]} {
2502 if {$historyindex >= [llength $history]} {
2503 .ctop.top.bar.rightbut conf -state disabled
2503 .ctop.top.bar.rightbut conf -state disabled
2504 }
2504 }
2505 }
2505 }
2506
2506
2507 proc mergediff {id} {
2507 proc mergediff {id} {
2508 global parents diffmergeid diffmergegca mergefilelist diffpindex
2508 global parents diffmergeid diffmergegca mergefilelist diffpindex
2509
2509
2510 set diffmergeid $id
2510 set diffmergeid $id
2511 set diffpindex -1
2511 set diffpindex -1
2512 set diffmergegca [findgca $parents($id)]
2512 set diffmergegca [findgca $parents($id)]
2513 if {[info exists mergefilelist($id)]} {
2513 if {[info exists mergefilelist($id)]} {
2514 if {$mergefilelist($id) ne {}} {
2514 if {$mergefilelist($id) ne {}} {
2515 showmergediff
2515 showmergediff
2516 }
2516 }
2517 } else {
2517 } else {
2518 contmergediff {}
2518 contmergediff {}
2519 }
2519 }
2520 }
2520 }
2521
2521
2522 proc findgca {ids} {
2522 proc findgca {ids} {
2523 global env
2523 global env
2524 set gca {}
2524 set gca {}
2525 foreach id $ids {
2525 foreach id $ids {
2526 if {$gca eq {}} {
2526 if {$gca eq {}} {
2527 set gca $id
2527 set gca $id
2528 } else {
2528 } else {
2529 if {[catch {
2529 if {[catch {
2530 set gca [exec $env(HG) --config ui.report_untrusted=false debug-merge-base $gca $id]
2530 set gca [exec $env(HG) --config ui.report_untrusted=false debug-merge-base $gca $id]
2531 } err]} {
2531 } err]} {
2532 return {}
2532 return {}
2533 }
2533 }
2534 }
2534 }
2535 }
2535 }
2536 return $gca
2536 return $gca
2537 }
2537 }
2538
2538
2539 proc contmergediff {ids} {
2539 proc contmergediff {ids} {
2540 global diffmergeid diffpindex parents nparents diffmergegca
2540 global diffmergeid diffpindex parents nparents diffmergegca
2541 global treediffs mergefilelist diffids treepending
2541 global treediffs mergefilelist diffids treepending
2542
2542
2543 # diff the child against each of the parents, and diff
2543 # diff the child against each of the parents, and diff
2544 # each of the parents against the GCA.
2544 # each of the parents against the GCA.
2545 while 1 {
2545 while 1 {
2546 if {[lindex $ids 0] == $diffmergeid && $diffmergegca ne {}} {
2546 if {[lindex $ids 0] == $diffmergeid && $diffmergegca ne {}} {
2547 set ids [list [lindex $ids 1] $diffmergegca]
2547 set ids [list [lindex $ids 1] $diffmergegca]
2548 } else {
2548 } else {
2549 if {[incr diffpindex] >= $nparents($diffmergeid)} break
2549 if {[incr diffpindex] >= $nparents($diffmergeid)} break
2550 set p [lindex $parents($diffmergeid) $diffpindex]
2550 set p [lindex $parents($diffmergeid) $diffpindex]
2551 set ids [list $diffmergeid $p]
2551 set ids [list $diffmergeid $p]
2552 }
2552 }
2553 if {![info exists treediffs($ids)]} {
2553 if {![info exists treediffs($ids)]} {
2554 set diffids $ids
2554 set diffids $ids
2555 if {![info exists treepending]} {
2555 if {![info exists treepending]} {
2556 gettreediffs $ids
2556 gettreediffs $ids
2557 }
2557 }
2558 return
2558 return
2559 }
2559 }
2560 }
2560 }
2561
2561
2562 # If a file in some parent is different from the child and also
2562 # If a file in some parent is different from the child and also
2563 # different from the GCA, then it's interesting.
2563 # different from the GCA, then it's interesting.
2564 # If we don't have a GCA, then a file is interesting if it is
2564 # If we don't have a GCA, then a file is interesting if it is
2565 # different from the child in all the parents.
2565 # different from the child in all the parents.
2566 if {$diffmergegca ne {}} {
2566 if {$diffmergegca ne {}} {
2567 set files {}
2567 set files {}
2568 foreach p $parents($diffmergeid) {
2568 foreach p $parents($diffmergeid) {
2569 set gcadiffs $treediffs([list $p $diffmergegca])
2569 set gcadiffs $treediffs([list $p $diffmergegca])
2570 foreach f $treediffs([list $diffmergeid $p]) {
2570 foreach f $treediffs([list $diffmergeid $p]) {
2571 if {[lsearch -exact $files $f] < 0
2571 if {[lsearch -exact $files $f] < 0
2572 && [lsearch -exact $gcadiffs $f] >= 0} {
2572 && [lsearch -exact $gcadiffs $f] >= 0} {
2573 lappend files $f
2573 lappend files $f
2574 }
2574 }
2575 }
2575 }
2576 }
2576 }
2577 set files [lsort $files]
2577 set files [lsort $files]
2578 } else {
2578 } else {
2579 set p [lindex $parents($diffmergeid) 0]
2579 set p [lindex $parents($diffmergeid) 0]
2580 set files $treediffs([list $diffmergeid $p])
2580 set files $treediffs([list $diffmergeid $p])
2581 for {set i 1} {$i < $nparents($diffmergeid) && $files ne {}} {incr i} {
2581 for {set i 1} {$i < $nparents($diffmergeid) && $files ne {}} {incr i} {
2582 set p [lindex $parents($diffmergeid) $i]
2582 set p [lindex $parents($diffmergeid) $i]
2583 set df $treediffs([list $diffmergeid $p])
2583 set df $treediffs([list $diffmergeid $p])
2584 set nf {}
2584 set nf {}
2585 foreach f $files {
2585 foreach f $files {
2586 if {[lsearch -exact $df $f] >= 0} {
2586 if {[lsearch -exact $df $f] >= 0} {
2587 lappend nf $f
2587 lappend nf $f
2588 }
2588 }
2589 }
2589 }
2590 set files $nf
2590 set files $nf
2591 }
2591 }
2592 }
2592 }
2593
2593
2594 set mergefilelist($diffmergeid) $files
2594 set mergefilelist($diffmergeid) $files
2595 if {$files ne {}} {
2595 if {$files ne {}} {
2596 showmergediff
2596 showmergediff
2597 }
2597 }
2598 }
2598 }
2599
2599
2600 proc showmergediff {} {
2600 proc showmergediff {} {
2601 global cflist diffmergeid mergefilelist parents
2601 global cflist diffmergeid mergefilelist parents
2602 global diffopts diffinhunk currentfile currenthunk filelines
2602 global diffopts diffinhunk currentfile currenthunk filelines
2603 global diffblocked groupfilelast mergefds groupfilenum grouphunks
2603 global diffblocked groupfilelast mergefds groupfilenum grouphunks
2604 global env
2604 global env
2605
2605
2606 set files $mergefilelist($diffmergeid)
2606 set files $mergefilelist($diffmergeid)
2607 foreach f $files {
2607 foreach f $files {
2608 $cflist insert end $f
2608 $cflist insert end $f
2609 }
2609 }
2610 set env(GIT_DIFF_OPTS) $diffopts
2610 set env(GIT_DIFF_OPTS) $diffopts
2611 set flist {}
2611 set flist {}
2612 catch {unset currentfile}
2612 catch {unset currentfile}
2613 catch {unset currenthunk}
2613 catch {unset currenthunk}
2614 catch {unset filelines}
2614 catch {unset filelines}
2615 catch {unset groupfilenum}
2615 catch {unset groupfilenum}
2616 catch {unset grouphunks}
2616 catch {unset grouphunks}
2617 set groupfilelast -1
2617 set groupfilelast -1
2618 foreach p $parents($diffmergeid) {
2618 foreach p $parents($diffmergeid) {
2619 set cmd [list | $env(HG) --config ui.report_untrusted=false debug-diff-tree -p $p $diffmergeid]
2619 set cmd [list | $env(HG) --config ui.report_untrusted=false debug-diff-tree -p $p $diffmergeid]
2620 set cmd [concat $cmd $mergefilelist($diffmergeid)]
2620 set cmd [concat $cmd $mergefilelist($diffmergeid)]
2621 if {[catch {set f [open $cmd r]} err]} {
2621 if {[catch {set f [open $cmd r]} err]} {
2622 error_popup "Error getting diffs: $err"
2622 error_popup "Error getting diffs: $err"
2623 foreach f $flist {
2623 foreach f $flist {
2624 catch {close $f}
2624 catch {close $f}
2625 }
2625 }
2626 return
2626 return
2627 }
2627 }
2628 lappend flist $f
2628 lappend flist $f
2629 set ids [list $diffmergeid $p]
2629 set ids [list $diffmergeid $p]
2630 set mergefds($ids) $f
2630 set mergefds($ids) $f
2631 set diffinhunk($ids) 0
2631 set diffinhunk($ids) 0
2632 set diffblocked($ids) 0
2632 set diffblocked($ids) 0
2633 fconfigure $f -blocking 0
2633 fconfigure $f -blocking 0
2634 fileevent $f readable [list getmergediffline $f $ids $diffmergeid]
2634 fileevent $f readable [list getmergediffline $f $ids $diffmergeid]
2635 }
2635 }
2636 }
2636 }
2637
2637
2638 proc getmergediffline {f ids id} {
2638 proc getmergediffline {f ids id} {
2639 global diffmergeid diffinhunk diffoldlines diffnewlines
2639 global diffmergeid diffinhunk diffoldlines diffnewlines
2640 global currentfile currenthunk
2640 global currentfile currenthunk
2641 global diffoldstart diffnewstart diffoldlno diffnewlno
2641 global diffoldstart diffnewstart diffoldlno diffnewlno
2642 global diffblocked mergefilelist
2642 global diffblocked mergefilelist
2643 global noldlines nnewlines difflcounts filelines
2643 global noldlines nnewlines difflcounts filelines
2644
2644
2645 set n [gets $f line]
2645 set n [gets $f line]
2646 if {$n < 0} {
2646 if {$n < 0} {
2647 if {![eof $f]} return
2647 if {![eof $f]} return
2648 }
2648 }
2649
2649
2650 if {!([info exists diffmergeid] && $diffmergeid == $id)} {
2650 if {!([info exists diffmergeid] && $diffmergeid == $id)} {
2651 if {$n < 0} {
2651 if {$n < 0} {
2652 close $f
2652 close $f
2653 }
2653 }
2654 return
2654 return
2655 }
2655 }
2656
2656
2657 if {$diffinhunk($ids) != 0} {
2657 if {$diffinhunk($ids) != 0} {
2658 set fi $currentfile($ids)
2658 set fi $currentfile($ids)
2659 if {$n > 0 && [regexp {^[-+ \\]} $line match]} {
2659 if {$n > 0 && [regexp {^[-+ \\]} $line match]} {
2660 # continuing an existing hunk
2660 # continuing an existing hunk
2661 set line [string range $line 1 end]
2661 set line [string range $line 1 end]
2662 set p [lindex $ids 1]
2662 set p [lindex $ids 1]
2663 if {$match eq "-" || $match eq " "} {
2663 if {$match eq "-" || $match eq " "} {
2664 set filelines($p,$fi,$diffoldlno($ids)) $line
2664 set filelines($p,$fi,$diffoldlno($ids)) $line
2665 incr diffoldlno($ids)
2665 incr diffoldlno($ids)
2666 }
2666 }
2667 if {$match eq "+" || $match eq " "} {
2667 if {$match eq "+" || $match eq " "} {
2668 set filelines($id,$fi,$diffnewlno($ids)) $line
2668 set filelines($id,$fi,$diffnewlno($ids)) $line
2669 incr diffnewlno($ids)
2669 incr diffnewlno($ids)
2670 }
2670 }
2671 if {$match eq " "} {
2671 if {$match eq " "} {
2672 if {$diffinhunk($ids) == 2} {
2672 if {$diffinhunk($ids) == 2} {
2673 lappend difflcounts($ids) \
2673 lappend difflcounts($ids) \
2674 [list $noldlines($ids) $nnewlines($ids)]
2674 [list $noldlines($ids) $nnewlines($ids)]
2675 set noldlines($ids) 0
2675 set noldlines($ids) 0
2676 set diffinhunk($ids) 1
2676 set diffinhunk($ids) 1
2677 }
2677 }
2678 incr noldlines($ids)
2678 incr noldlines($ids)
2679 } elseif {$match eq "-" || $match eq "+"} {
2679 } elseif {$match eq "-" || $match eq "+"} {
2680 if {$diffinhunk($ids) == 1} {
2680 if {$diffinhunk($ids) == 1} {
2681 lappend difflcounts($ids) [list $noldlines($ids)]
2681 lappend difflcounts($ids) [list $noldlines($ids)]
2682 set noldlines($ids) 0
2682 set noldlines($ids) 0
2683 set nnewlines($ids) 0
2683 set nnewlines($ids) 0
2684 set diffinhunk($ids) 2
2684 set diffinhunk($ids) 2
2685 }
2685 }
2686 if {$match eq "-"} {
2686 if {$match eq "-"} {
2687 incr noldlines($ids)
2687 incr noldlines($ids)
2688 } else {
2688 } else {
2689 incr nnewlines($ids)
2689 incr nnewlines($ids)
2690 }
2690 }
2691 }
2691 }
2692 # and if it's \ No newline at end of line, then what?
2692 # and if it's \ No newline at end of line, then what?
2693 return
2693 return
2694 }
2694 }
2695 # end of a hunk
2695 # end of a hunk
2696 if {$diffinhunk($ids) == 1 && $noldlines($ids) != 0} {
2696 if {$diffinhunk($ids) == 1 && $noldlines($ids) != 0} {
2697 lappend difflcounts($ids) [list $noldlines($ids)]
2697 lappend difflcounts($ids) [list $noldlines($ids)]
2698 } elseif {$diffinhunk($ids) == 2
2698 } elseif {$diffinhunk($ids) == 2
2699 && ($noldlines($ids) != 0 || $nnewlines($ids) != 0)} {
2699 && ($noldlines($ids) != 0 || $nnewlines($ids) != 0)} {
2700 lappend difflcounts($ids) [list $noldlines($ids) $nnewlines($ids)]
2700 lappend difflcounts($ids) [list $noldlines($ids) $nnewlines($ids)]
2701 }
2701 }
2702 set currenthunk($ids) [list $currentfile($ids) \
2702 set currenthunk($ids) [list $currentfile($ids) \
2703 $diffoldstart($ids) $diffnewstart($ids) \
2703 $diffoldstart($ids) $diffnewstart($ids) \
2704 $diffoldlno($ids) $diffnewlno($ids) \
2704 $diffoldlno($ids) $diffnewlno($ids) \
2705 $difflcounts($ids)]
2705 $difflcounts($ids)]
2706 set diffinhunk($ids) 0
2706 set diffinhunk($ids) 0
2707 # -1 = need to block, 0 = unblocked, 1 = is blocked
2707 # -1 = need to block, 0 = unblocked, 1 = is blocked
2708 set diffblocked($ids) -1
2708 set diffblocked($ids) -1
2709 processhunks
2709 processhunks
2710 if {$diffblocked($ids) == -1} {
2710 if {$diffblocked($ids) == -1} {
2711 fileevent $f readable {}
2711 fileevent $f readable {}
2712 set diffblocked($ids) 1
2712 set diffblocked($ids) 1
2713 }
2713 }
2714 }
2714 }
2715
2715
2716 if {$n < 0} {
2716 if {$n < 0} {
2717 # eof
2717 # eof
2718 if {!$diffblocked($ids)} {
2718 if {!$diffblocked($ids)} {
2719 close $f
2719 close $f
2720 set currentfile($ids) [llength $mergefilelist($diffmergeid)]
2720 set currentfile($ids) [llength $mergefilelist($diffmergeid)]
2721 set currenthunk($ids) [list $currentfile($ids) 0 0 0 0 {}]
2721 set currenthunk($ids) [list $currentfile($ids) 0 0 0 0 {}]
2722 processhunks
2722 processhunks
2723 }
2723 }
2724 } elseif {[regexp {^diff --git a/(.*) b/} $line match fname]} {
2724 } elseif {[regexp {^diff --git a/(.*) b/} $line match fname]} {
2725 # start of a new file
2725 # start of a new file
2726 set currentfile($ids) \
2726 set currentfile($ids) \
2727 [lsearch -exact $mergefilelist($diffmergeid) $fname]
2727 [lsearch -exact $mergefilelist($diffmergeid) $fname]
2728 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2728 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2729 $line match f1l f1c f2l f2c rest]} {
2729 $line match f1l f1c f2l f2c rest]} {
2730 if {[info exists currentfile($ids)] && $currentfile($ids) >= 0} {
2730 if {[info exists currentfile($ids)] && $currentfile($ids) >= 0} {
2731 # start of a new hunk
2731 # start of a new hunk
2732 if {$f1l == 0 && $f1c == 0} {
2732 if {$f1l == 0 && $f1c == 0} {
2733 set f1l 1
2733 set f1l 1
2734 }
2734 }
2735 if {$f2l == 0 && $f2c == 0} {
2735 if {$f2l == 0 && $f2c == 0} {
2736 set f2l 1
2736 set f2l 1
2737 }
2737 }
2738 set diffinhunk($ids) 1
2738 set diffinhunk($ids) 1
2739 set diffoldstart($ids) $f1l
2739 set diffoldstart($ids) $f1l
2740 set diffnewstart($ids) $f2l
2740 set diffnewstart($ids) $f2l
2741 set diffoldlno($ids) $f1l
2741 set diffoldlno($ids) $f1l
2742 set diffnewlno($ids) $f2l
2742 set diffnewlno($ids) $f2l
2743 set difflcounts($ids) {}
2743 set difflcounts($ids) {}
2744 set noldlines($ids) 0
2744 set noldlines($ids) 0
2745 set nnewlines($ids) 0
2745 set nnewlines($ids) 0
2746 }
2746 }
2747 }
2747 }
2748 }
2748 }
2749
2749
2750 proc processhunks {} {
2750 proc processhunks {} {
2751 global diffmergeid parents nparents currenthunk
2751 global diffmergeid parents nparents currenthunk
2752 global mergefilelist diffblocked mergefds
2752 global mergefilelist diffblocked mergefds
2753 global grouphunks grouplinestart grouplineend groupfilenum
2753 global grouphunks grouplinestart grouplineend groupfilenum
2754
2754
2755 set nfiles [llength $mergefilelist($diffmergeid)]
2755 set nfiles [llength $mergefilelist($diffmergeid)]
2756 while 1 {
2756 while 1 {
2757 set fi $nfiles
2757 set fi $nfiles
2758 set lno 0
2758 set lno 0
2759 # look for the earliest hunk
2759 # look for the earliest hunk
2760 foreach p $parents($diffmergeid) {
2760 foreach p $parents($diffmergeid) {
2761 set ids [list $diffmergeid $p]
2761 set ids [list $diffmergeid $p]
2762 if {![info exists currenthunk($ids)]} return
2762 if {![info exists currenthunk($ids)]} return
2763 set i [lindex $currenthunk($ids) 0]
2763 set i [lindex $currenthunk($ids) 0]
2764 set l [lindex $currenthunk($ids) 2]
2764 set l [lindex $currenthunk($ids) 2]
2765 if {$i < $fi || ($i == $fi && $l < $lno)} {
2765 if {$i < $fi || ($i == $fi && $l < $lno)} {
2766 set fi $i
2766 set fi $i
2767 set lno $l
2767 set lno $l
2768 set pi $p
2768 set pi $p
2769 }
2769 }
2770 }
2770 }
2771
2771
2772 if {$fi < $nfiles} {
2772 if {$fi < $nfiles} {
2773 set ids [list $diffmergeid $pi]
2773 set ids [list $diffmergeid $pi]
2774 set hunk $currenthunk($ids)
2774 set hunk $currenthunk($ids)
2775 unset currenthunk($ids)
2775 unset currenthunk($ids)
2776 if {$diffblocked($ids) > 0} {
2776 if {$diffblocked($ids) > 0} {
2777 fileevent $mergefds($ids) readable \
2777 fileevent $mergefds($ids) readable \
2778 [list getmergediffline $mergefds($ids) $ids $diffmergeid]
2778 [list getmergediffline $mergefds($ids) $ids $diffmergeid]
2779 }
2779 }
2780 set diffblocked($ids) 0
2780 set diffblocked($ids) 0
2781
2781
2782 if {[info exists groupfilenum] && $groupfilenum == $fi
2782 if {[info exists groupfilenum] && $groupfilenum == $fi
2783 && $lno <= $grouplineend} {
2783 && $lno <= $grouplineend} {
2784 # add this hunk to the pending group
2784 # add this hunk to the pending group
2785 lappend grouphunks($pi) $hunk
2785 lappend grouphunks($pi) $hunk
2786 set endln [lindex $hunk 4]
2786 set endln [lindex $hunk 4]
2787 if {$endln > $grouplineend} {
2787 if {$endln > $grouplineend} {
2788 set grouplineend $endln
2788 set grouplineend $endln
2789 }
2789 }
2790 continue
2790 continue
2791 }
2791 }
2792 }
2792 }
2793
2793
2794 # succeeding stuff doesn't belong in this group, so
2794 # succeeding stuff doesn't belong in this group, so
2795 # process the group now
2795 # process the group now
2796 if {[info exists groupfilenum]} {
2796 if {[info exists groupfilenum]} {
2797 processgroup
2797 processgroup
2798 unset groupfilenum
2798 unset groupfilenum
2799 unset grouphunks
2799 unset grouphunks
2800 }
2800 }
2801
2801
2802 if {$fi >= $nfiles} break
2802 if {$fi >= $nfiles} break
2803
2803
2804 # start a new group
2804 # start a new group
2805 set groupfilenum $fi
2805 set groupfilenum $fi
2806 set grouphunks($pi) [list $hunk]
2806 set grouphunks($pi) [list $hunk]
2807 set grouplinestart $lno
2807 set grouplinestart $lno
2808 set grouplineend [lindex $hunk 4]
2808 set grouplineend [lindex $hunk 4]
2809 }
2809 }
2810 }
2810 }
2811
2811
2812 proc processgroup {} {
2812 proc processgroup {} {
2813 global groupfilelast groupfilenum difffilestart
2813 global groupfilelast groupfilenum difffilestart
2814 global mergefilelist diffmergeid ctext filelines
2814 global mergefilelist diffmergeid ctext filelines
2815 global parents diffmergeid diffoffset
2815 global parents diffmergeid diffoffset
2816 global grouphunks grouplinestart grouplineend nparents
2816 global grouphunks grouplinestart grouplineend nparents
2817 global mergemax
2817 global mergemax
2818
2818
2819 $ctext conf -state normal
2819 $ctext conf -state normal
2820 set id $diffmergeid
2820 set id $diffmergeid
2821 set f $groupfilenum
2821 set f $groupfilenum
2822 if {$groupfilelast != $f} {
2822 if {$groupfilelast != $f} {
2823 $ctext insert end "\n"
2823 $ctext insert end "\n"
2824 set here [$ctext index "end - 1c"]
2824 set here [$ctext index "end - 1c"]
2825 set difffilestart($f) $here
2825 set difffilestart($f) $here
2826 set mark fmark.[expr {$f + 1}]
2826 set mark fmark.[expr {$f + 1}]
2827 $ctext mark set $mark $here
2827 $ctext mark set $mark $here
2828 $ctext mark gravity $mark left
2828 $ctext mark gravity $mark left
2829 set header [lindex $mergefilelist($id) $f]
2829 set header [lindex $mergefilelist($id) $f]
2830 set l [expr {(78 - [string length $header]) / 2}]
2830 set l [expr {(78 - [string length $header]) / 2}]
2831 set pad [string range "----------------------------------------" 1 $l]
2831 set pad [string range "----------------------------------------" 1 $l]
2832 $ctext insert end "$pad $header $pad\n" filesep
2832 $ctext insert end "$pad $header $pad\n" filesep
2833 set groupfilelast $f
2833 set groupfilelast $f
2834 foreach p $parents($id) {
2834 foreach p $parents($id) {
2835 set diffoffset($p) 0
2835 set diffoffset($p) 0
2836 }
2836 }
2837 }
2837 }
2838
2838
2839 $ctext insert end "@@" msep
2839 $ctext insert end "@@" msep
2840 set nlines [expr {$grouplineend - $grouplinestart}]
2840 set nlines [expr {$grouplineend - $grouplinestart}]
2841 set events {}
2841 set events {}
2842 set pnum 0
2842 set pnum 0
2843 foreach p $parents($id) {
2843 foreach p $parents($id) {
2844 set startline [expr {$grouplinestart + $diffoffset($p)}]
2844 set startline [expr {$grouplinestart + $diffoffset($p)}]
2845 set ol $startline
2845 set ol $startline
2846 set nl $grouplinestart
2846 set nl $grouplinestart
2847 if {[info exists grouphunks($p)]} {
2847 if {[info exists grouphunks($p)]} {
2848 foreach h $grouphunks($p) {
2848 foreach h $grouphunks($p) {
2849 set l [lindex $h 2]
2849 set l [lindex $h 2]
2850 if {$nl < $l} {
2850 if {$nl < $l} {
2851 for {} {$nl < $l} {incr nl} {
2851 for {} {$nl < $l} {incr nl} {
2852 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2852 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2853 incr ol
2853 incr ol
2854 }
2854 }
2855 }
2855 }
2856 foreach chunk [lindex $h 5] {
2856 foreach chunk [lindex $h 5] {
2857 if {[llength $chunk] == 2} {
2857 if {[llength $chunk] == 2} {
2858 set olc [lindex $chunk 0]
2858 set olc [lindex $chunk 0]
2859 set nlc [lindex $chunk 1]
2859 set nlc [lindex $chunk 1]
2860 set nnl [expr {$nl + $nlc}]
2860 set nnl [expr {$nl + $nlc}]
2861 lappend events [list $nl $nnl $pnum $olc $nlc]
2861 lappend events [list $nl $nnl $pnum $olc $nlc]
2862 incr ol $olc
2862 incr ol $olc
2863 set nl $nnl
2863 set nl $nnl
2864 } else {
2864 } else {
2865 incr ol [lindex $chunk 0]
2865 incr ol [lindex $chunk 0]
2866 incr nl [lindex $chunk 0]
2866 incr nl [lindex $chunk 0]
2867 }
2867 }
2868 }
2868 }
2869 }
2869 }
2870 }
2870 }
2871 if {$nl < $grouplineend} {
2871 if {$nl < $grouplineend} {
2872 for {} {$nl < $grouplineend} {incr nl} {
2872 for {} {$nl < $grouplineend} {incr nl} {
2873 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2873 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2874 incr ol
2874 incr ol
2875 }
2875 }
2876 }
2876 }
2877 set nlines [expr {$ol - $startline}]
2877 set nlines [expr {$ol - $startline}]
2878 $ctext insert end " -$startline,$nlines" msep
2878 $ctext insert end " -$startline,$nlines" msep
2879 incr pnum
2879 incr pnum
2880 }
2880 }
2881
2881
2882 set nlines [expr {$grouplineend - $grouplinestart}]
2882 set nlines [expr {$grouplineend - $grouplinestart}]
2883 $ctext insert end " +$grouplinestart,$nlines @@\n" msep
2883 $ctext insert end " +$grouplinestart,$nlines @@\n" msep
2884
2884
2885 set events [lsort -integer -index 0 $events]
2885 set events [lsort -integer -index 0 $events]
2886 set nevents [llength $events]
2886 set nevents [llength $events]
2887 set nmerge $nparents($diffmergeid)
2887 set nmerge $nparents($diffmergeid)
2888 set l $grouplinestart
2888 set l $grouplinestart
2889 for {set i 0} {$i < $nevents} {set i $j} {
2889 for {set i 0} {$i < $nevents} {set i $j} {
2890 set nl [lindex $events $i 0]
2890 set nl [lindex $events $i 0]
2891 while {$l < $nl} {
2891 while {$l < $nl} {
2892 $ctext insert end " $filelines($id,$f,$l)\n"
2892 $ctext insert end " $filelines($id,$f,$l)\n"
2893 incr l
2893 incr l
2894 }
2894 }
2895 set e [lindex $events $i]
2895 set e [lindex $events $i]
2896 set enl [lindex $e 1]
2896 set enl [lindex $e 1]
2897 set j $i
2897 set j $i
2898 set active {}
2898 set active {}
2899 while 1 {
2899 while 1 {
2900 set pnum [lindex $e 2]
2900 set pnum [lindex $e 2]
2901 set olc [lindex $e 3]
2901 set olc [lindex $e 3]
2902 set nlc [lindex $e 4]
2902 set nlc [lindex $e 4]
2903 if {![info exists delta($pnum)]} {
2903 if {![info exists delta($pnum)]} {
2904 set delta($pnum) [expr {$olc - $nlc}]
2904 set delta($pnum) [expr {$olc - $nlc}]
2905 lappend active $pnum
2905 lappend active $pnum
2906 } else {
2906 } else {
2907 incr delta($pnum) [expr {$olc - $nlc}]
2907 incr delta($pnum) [expr {$olc - $nlc}]
2908 }
2908 }
2909 if {[incr j] >= $nevents} break
2909 if {[incr j] >= $nevents} break
2910 set e [lindex $events $j]
2910 set e [lindex $events $j]
2911 if {[lindex $e 0] >= $enl} break
2911 if {[lindex $e 0] >= $enl} break
2912 if {[lindex $e 1] > $enl} {
2912 if {[lindex $e 1] > $enl} {
2913 set enl [lindex $e 1]
2913 set enl [lindex $e 1]
2914 }
2914 }
2915 }
2915 }
2916 set nlc [expr {$enl - $l}]
2916 set nlc [expr {$enl - $l}]
2917 set ncol mresult
2917 set ncol mresult
2918 set bestpn -1
2918 set bestpn -1
2919 if {[llength $active] == $nmerge - 1} {
2919 if {[llength $active] == $nmerge - 1} {
2920 # no diff for one of the parents, i.e. it's identical
2920 # no diff for one of the parents, i.e. it's identical
2921 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2921 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2922 if {![info exists delta($pnum)]} {
2922 if {![info exists delta($pnum)]} {
2923 if {$pnum < $mergemax} {
2923 if {$pnum < $mergemax} {
2924 lappend ncol m$pnum
2924 lappend ncol m$pnum
2925 } else {
2925 } else {
2926 lappend ncol mmax
2926 lappend ncol mmax
2927 }
2927 }
2928 break
2928 break
2929 }
2929 }
2930 }
2930 }
2931 } elseif {[llength $active] == $nmerge} {
2931 } elseif {[llength $active] == $nmerge} {
2932 # all parents are different, see if one is very similar
2932 # all parents are different, see if one is very similar
2933 set bestsim 30
2933 set bestsim 30
2934 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2934 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2935 set sim [similarity $pnum $l $nlc $f \
2935 set sim [similarity $pnum $l $nlc $f \
2936 [lrange $events $i [expr {$j-1}]]]
2936 [lrange $events $i [expr {$j-1}]]]
2937 if {$sim > $bestsim} {
2937 if {$sim > $bestsim} {
2938 set bestsim $sim
2938 set bestsim $sim
2939 set bestpn $pnum
2939 set bestpn $pnum
2940 }
2940 }
2941 }
2941 }
2942 if {$bestpn >= 0} {
2942 if {$bestpn >= 0} {
2943 lappend ncol m$bestpn
2943 lappend ncol m$bestpn
2944 }
2944 }
2945 }
2945 }
2946 set pnum -1
2946 set pnum -1
2947 foreach p $parents($id) {
2947 foreach p $parents($id) {
2948 incr pnum
2948 incr pnum
2949 if {![info exists delta($pnum)] || $pnum == $bestpn} continue
2949 if {![info exists delta($pnum)] || $pnum == $bestpn} continue
2950 set olc [expr {$nlc + $delta($pnum)}]
2950 set olc [expr {$nlc + $delta($pnum)}]
2951 set ol [expr {$l + $diffoffset($p)}]
2951 set ol [expr {$l + $diffoffset($p)}]
2952 incr diffoffset($p) $delta($pnum)
2952 incr diffoffset($p) $delta($pnum)
2953 unset delta($pnum)
2953 unset delta($pnum)
2954 for {} {$olc > 0} {incr olc -1} {
2954 for {} {$olc > 0} {incr olc -1} {
2955 $ctext insert end "-$filelines($p,$f,$ol)\n" m$pnum
2955 $ctext insert end "-$filelines($p,$f,$ol)\n" m$pnum
2956 incr ol
2956 incr ol
2957 }
2957 }
2958 }
2958 }
2959 set endl [expr {$l + $nlc}]
2959 set endl [expr {$l + $nlc}]
2960 if {$bestpn >= 0} {
2960 if {$bestpn >= 0} {
2961 # show this pretty much as a normal diff
2961 # show this pretty much as a normal diff
2962 set p [lindex $parents($id) $bestpn]
2962 set p [lindex $parents($id) $bestpn]
2963 set ol [expr {$l + $diffoffset($p)}]
2963 set ol [expr {$l + $diffoffset($p)}]
2964 incr diffoffset($p) $delta($bestpn)
2964 incr diffoffset($p) $delta($bestpn)
2965 unset delta($bestpn)
2965 unset delta($bestpn)
2966 for {set k $i} {$k < $j} {incr k} {
2966 for {set k $i} {$k < $j} {incr k} {
2967 set e [lindex $events $k]
2967 set e [lindex $events $k]
2968 if {[lindex $e 2] != $bestpn} continue
2968 if {[lindex $e 2] != $bestpn} continue
2969 set nl [lindex $e 0]
2969 set nl [lindex $e 0]
2970 set ol [expr {$ol + $nl - $l}]
2970 set ol [expr {$ol + $nl - $l}]
2971 for {} {$l < $nl} {incr l} {
2971 for {} {$l < $nl} {incr l} {
2972 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2972 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2973 }
2973 }
2974 set c [lindex $e 3]
2974 set c [lindex $e 3]
2975 for {} {$c > 0} {incr c -1} {
2975 for {} {$c > 0} {incr c -1} {
2976 $ctext insert end "-$filelines($p,$f,$ol)\n" m$bestpn
2976 $ctext insert end "-$filelines($p,$f,$ol)\n" m$bestpn
2977 incr ol
2977 incr ol
2978 }
2978 }
2979 set nl [lindex $e 1]
2979 set nl [lindex $e 1]
2980 for {} {$l < $nl} {incr l} {
2980 for {} {$l < $nl} {incr l} {
2981 $ctext insert end "+$filelines($id,$f,$l)\n" mresult
2981 $ctext insert end "+$filelines($id,$f,$l)\n" mresult
2982 }
2982 }
2983 }
2983 }
2984 }
2984 }
2985 for {} {$l < $endl} {incr l} {
2985 for {} {$l < $endl} {incr l} {
2986 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2986 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2987 }
2987 }
2988 }
2988 }
2989 while {$l < $grouplineend} {
2989 while {$l < $grouplineend} {
2990 $ctext insert end " $filelines($id,$f,$l)\n"
2990 $ctext insert end " $filelines($id,$f,$l)\n"
2991 incr l
2991 incr l
2992 }
2992 }
2993 $ctext conf -state disabled
2993 $ctext conf -state disabled
2994 }
2994 }
2995
2995
2996 proc similarity {pnum l nlc f events} {
2996 proc similarity {pnum l nlc f events} {
2997 global diffmergeid parents diffoffset filelines
2997 global diffmergeid parents diffoffset filelines
2998
2998
2999 set id $diffmergeid
2999 set id $diffmergeid
3000 set p [lindex $parents($id) $pnum]
3000 set p [lindex $parents($id) $pnum]
3001 set ol [expr {$l + $diffoffset($p)}]
3001 set ol [expr {$l + $diffoffset($p)}]
3002 set endl [expr {$l + $nlc}]
3002 set endl [expr {$l + $nlc}]
3003 set same 0
3003 set same 0
3004 set diff 0
3004 set diff 0
3005 foreach e $events {
3005 foreach e $events {
3006 if {[lindex $e 2] != $pnum} continue
3006 if {[lindex $e 2] != $pnum} continue
3007 set nl [lindex $e 0]
3007 set nl [lindex $e 0]
3008 set ol [expr {$ol + $nl - $l}]
3008 set ol [expr {$ol + $nl - $l}]
3009 for {} {$l < $nl} {incr l} {
3009 for {} {$l < $nl} {incr l} {
3010 incr same [string length $filelines($id,$f,$l)]
3010 incr same [string length $filelines($id,$f,$l)]
3011 incr same
3011 incr same
3012 }
3012 }
3013 set oc [lindex $e 3]
3013 set oc [lindex $e 3]
3014 for {} {$oc > 0} {incr oc -1} {
3014 for {} {$oc > 0} {incr oc -1} {
3015 incr diff [string length $filelines($p,$f,$ol)]
3015 incr diff [string length $filelines($p,$f,$ol)]
3016 incr diff
3016 incr diff
3017 incr ol
3017 incr ol
3018 }
3018 }
3019 set nl [lindex $e 1]
3019 set nl [lindex $e 1]
3020 for {} {$l < $nl} {incr l} {
3020 for {} {$l < $nl} {incr l} {
3021 incr diff [string length $filelines($id,$f,$l)]
3021 incr diff [string length $filelines($id,$f,$l)]
3022 incr diff
3022 incr diff
3023 }
3023 }
3024 }
3024 }
3025 for {} {$l < $endl} {incr l} {
3025 for {} {$l < $endl} {incr l} {
3026 incr same [string length $filelines($id,$f,$l)]
3026 incr same [string length $filelines($id,$f,$l)]
3027 incr same
3027 incr same
3028 }
3028 }
3029 if {$same == 0} {
3029 if {$same == 0} {
3030 return 0
3030 return 0
3031 }
3031 }
3032 return [expr {200 * $same / (2 * $same + $diff)}]
3032 return [expr {200 * $same / (2 * $same + $diff)}]
3033 }
3033 }
3034
3034
3035 proc startdiff {ids} {
3035 proc startdiff {ids} {
3036 global treediffs diffids treepending diffmergeid
3036 global treediffs diffids treepending diffmergeid
3037
3037
3038 set diffids $ids
3038 set diffids $ids
3039 catch {unset diffmergeid}
3039 catch {unset diffmergeid}
3040 if {![info exists treediffs($ids)]} {
3040 if {![info exists treediffs($ids)]} {
3041 if {![info exists treepending]} {
3041 if {![info exists treepending]} {
3042 gettreediffs $ids
3042 gettreediffs $ids
3043 }
3043 }
3044 } else {
3044 } else {
3045 addtocflist $ids
3045 addtocflist $ids
3046 }
3046 }
3047 }
3047 }
3048
3048
3049 proc addtocflist {ids} {
3049 proc addtocflist {ids} {
3050 global treediffs cflist
3050 global treediffs cflist
3051 foreach f $treediffs($ids) {
3051 foreach f $treediffs($ids) {
3052 $cflist insert end $f
3052 $cflist insert end $f
3053 }
3053 }
3054 getblobdiffs $ids
3054 getblobdiffs $ids
3055 }
3055 }
3056
3056
3057 proc gettreediffs {ids} {
3057 proc gettreediffs {ids} {
3058 global treediff parents treepending env
3058 global treediff parents treepending env
3059 set treepending $ids
3059 set treepending $ids
3060 set treediff {}
3060 set treediff {}
3061 set id [lindex $ids 0]
3061 set id [lindex $ids 0]
3062 set p [lindex $ids 1]
3062 set p [lindex $ids 1]
3063 if [catch {set gdtf [open "|{$env(HG)} --config ui.report_untrusted=false debug-diff-tree -r $p $id" r]}] return
3063 if [catch {set gdtf [open "|{$env(HG)} --config ui.report_untrusted=false debug-diff-tree -r $p $id" r]}] return
3064 fconfigure $gdtf -blocking 0
3064 fconfigure $gdtf -blocking 0
3065 fileevent $gdtf readable [list gettreediffline $gdtf $ids]
3065 fileevent $gdtf readable [list gettreediffline $gdtf $ids]
3066 }
3066 }
3067
3067
3068 proc gettreediffline {gdtf ids} {
3068 proc gettreediffline {gdtf ids} {
3069 global treediff treediffs treepending diffids diffmergeid
3069 global treediff treediffs treepending diffids diffmergeid
3070
3070
3071 set n [gets $gdtf line]
3071 set n [gets $gdtf line]
3072 if {$n < 0} {
3072 if {$n < 0} {
3073 if {![eof $gdtf]} return
3073 if {![eof $gdtf]} return
3074 close $gdtf
3074 close $gdtf
3075 set treediffs($ids) $treediff
3075 set treediffs($ids) $treediff
3076 unset treepending
3076 unset treepending
3077 if {$ids != $diffids} {
3077 if {$ids != $diffids} {
3078 gettreediffs $diffids
3078 gettreediffs $diffids
3079 } else {
3079 } else {
3080 if {[info exists diffmergeid]} {
3080 if {[info exists diffmergeid]} {
3081 contmergediff $ids
3081 contmergediff $ids
3082 } else {
3082 } else {
3083 addtocflist $ids
3083 addtocflist $ids
3084 }
3084 }
3085 }
3085 }
3086 return
3086 return
3087 }
3087 }
3088 set tab1 [expr [string first "\t" $line] + 1]
3088 set tab1 [expr [string first "\t" $line] + 1]
3089 set tab2 [expr [string first "\t" $line $tab1] - 1]
3089 set tab2 [expr [string first "\t" $line $tab1] - 1]
3090 set file [string range $line $tab1 $tab2]
3090 set file [string range $line $tab1 $tab2]
3091 lappend treediff $file
3091 lappend treediff $file
3092 }
3092 }
3093
3093
3094 proc getblobdiffs {ids} {
3094 proc getblobdiffs {ids} {
3095 global diffopts blobdifffd diffids env curdifftag curtagstart
3095 global diffopts blobdifffd diffids env curdifftag curtagstart
3096 global difffilestart nextupdate diffinhdr treediffs
3096 global difffilestart nextupdate diffinhdr treediffs
3097
3097
3098 set id [lindex $ids 0]
3098 set id [lindex $ids 0]
3099 set p [lindex $ids 1]
3099 set p [lindex $ids 1]
3100 set env(GIT_DIFF_OPTS) $diffopts
3100 set env(GIT_DIFF_OPTS) $diffopts
3101 set cmd [list | $env(HG) --config ui.report_untrusted=false debug-diff-tree -r -p -C $p $id]
3101 set cmd [list | $env(HG) --config ui.report_untrusted=false debug-diff-tree -r -p -C $p $id]
3102 if {[catch {set bdf [open $cmd r]} err]} {
3102 if {[catch {set bdf [open $cmd r]} err]} {
3103 puts "error getting diffs: $err"
3103 puts "error getting diffs: $err"
3104 return
3104 return
3105 }
3105 }
3106 set diffinhdr 0
3106 set diffinhdr 0
3107 fconfigure $bdf -blocking 0
3107 fconfigure $bdf -blocking 0
3108 set blobdifffd($ids) $bdf
3108 set blobdifffd($ids) $bdf
3109 set curdifftag Comments
3109 set curdifftag Comments
3110 set curtagstart 0.0
3110 set curtagstart 0.0
3111 catch {unset difffilestart}
3111 catch {unset difffilestart}
3112 fileevent $bdf readable [list getblobdiffline $bdf $diffids]
3112 fileevent $bdf readable [list getblobdiffline $bdf $diffids]
3113 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
3113 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
3114 }
3114 }
3115
3115
3116 proc getblobdiffline {bdf ids} {
3116 proc getblobdiffline {bdf ids} {
3117 global diffids blobdifffd ctext curdifftag curtagstart
3117 global diffids blobdifffd ctext curdifftag curtagstart
3118 global diffnexthead diffnextnote difffilestart
3118 global diffnexthead diffnextnote difffilestart
3119 global nextupdate diffinhdr treediffs
3119 global nextupdate diffinhdr treediffs
3120 global gaudydiff
3120 global gaudydiff
3121
3121
3122 set n [gets $bdf line]
3122 set n [gets $bdf line]
3123 if {$n < 0} {
3123 if {$n < 0} {
3124 if {[eof $bdf]} {
3124 if {[eof $bdf]} {
3125 close $bdf
3125 close $bdf
3126 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
3126 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
3127 $ctext tag add $curdifftag $curtagstart end
3127 $ctext tag add $curdifftag $curtagstart end
3128 }
3128 }
3129 }
3129 }
3130 return
3130 return
3131 }
3131 }
3132 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
3132 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
3133 return
3133 return
3134 }
3134 }
3135 regsub -all "\r" $line "" line
3135 regsub -all "\r" $line "" line
3136 $ctext conf -state normal
3136 $ctext conf -state normal
3137 if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
3137 if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
3138 # start of a new file
3138 # start of a new file
3139 $ctext insert end "\n"
3139 $ctext insert end "\n"
3140 $ctext tag add $curdifftag $curtagstart end
3140 $ctext tag add $curdifftag $curtagstart end
3141 set curtagstart [$ctext index "end - 1c"]
3141 set curtagstart [$ctext index "end - 1c"]
3142 set header $newname
3142 set header $newname
3143 set here [$ctext index "end - 1c"]
3143 set here [$ctext index "end - 1c"]
3144 set i [lsearch -exact $treediffs($diffids) $fname]
3144 set i [lsearch -exact $treediffs($diffids) $fname]
3145 if {$i >= 0} {
3145 if {$i >= 0} {
3146 set difffilestart($i) $here
3146 set difffilestart($i) $here
3147 incr i
3147 incr i
3148 $ctext mark set fmark.$i $here
3148 $ctext mark set fmark.$i $here
3149 $ctext mark gravity fmark.$i left
3149 $ctext mark gravity fmark.$i left
3150 }
3150 }
3151 if {$newname != $fname} {
3151 if {$newname != $fname} {
3152 set i [lsearch -exact $treediffs($diffids) $newname]
3152 set i [lsearch -exact $treediffs($diffids) $newname]
3153 if {$i >= 0} {
3153 if {$i >= 0} {
3154 set difffilestart($i) $here
3154 set difffilestart($i) $here
3155 incr i
3155 incr i
3156 $ctext mark set fmark.$i $here
3156 $ctext mark set fmark.$i $here
3157 $ctext mark gravity fmark.$i left
3157 $ctext mark gravity fmark.$i left
3158 }
3158 }
3159 }
3159 }
3160 set curdifftag "f:$fname"
3160 set curdifftag "f:$fname"
3161 $ctext tag delete $curdifftag
3161 $ctext tag delete $curdifftag
3162 set l [expr {(78 - [string length $header]) / 2}]
3162 set l [expr {(78 - [string length $header]) / 2}]
3163 set pad [string range "----------------------------------------" 1 $l]
3163 set pad [string range "----------------------------------------" 1 $l]
3164 $ctext insert end "$pad $header $pad\n" filesep
3164 $ctext insert end "$pad $header $pad\n" filesep
3165 set diffinhdr 1
3165 set diffinhdr 1
3166 } elseif {[regexp {^(---|\+\+\+) } $line] && $diffinhdr} {
3166 } elseif {[regexp {^(---|\+\+\+) } $line] && $diffinhdr} {
3167 set diffinhdr 1
3167 set diffinhdr 1
3168 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
3168 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
3169 $line match f1l f1c f2l f2c rest]} {
3169 $line match f1l f1c f2l f2c rest]} {
3170 if {$gaudydiff} {
3170 if {$gaudydiff} {
3171 $ctext insert end "\t" hunksep
3171 $ctext insert end "\t" hunksep
3172 $ctext insert end " $f1l " d0 " $f2l " d1
3172 $ctext insert end " $f1l " d0 " $f2l " d1
3173 $ctext insert end " $rest \n" hunksep
3173 $ctext insert end " $rest \n" hunksep
3174 } else {
3174 } else {
3175 $ctext insert end "$line\n" hunksep
3175 $ctext insert end "$line\n" hunksep
3176 }
3176 }
3177 set diffinhdr 0
3177 set diffinhdr 0
3178 } else {
3178 } else {
3179 set x [string range $line 0 0]
3179 set x [string range $line 0 0]
3180 if {$x == "-" || $x == "+"} {
3180 if {$x == "-" || $x == "+"} {
3181 set tag [expr {$x == "+"}]
3181 set tag [expr {$x == "+"}]
3182 if {$gaudydiff} {
3182 if {$gaudydiff} {
3183 set line [string range $line 1 end]
3183 set line [string range $line 1 end]
3184 }
3184 }
3185 $ctext insert end "$line\n" d$tag
3185 $ctext insert end "$line\n" d$tag
3186 } elseif {$x == " "} {
3186 } elseif {$x == " "} {
3187 if {$gaudydiff} {
3187 if {$gaudydiff} {
3188 set line [string range $line 1 end]
3188 set line [string range $line 1 end]
3189 }
3189 }
3190 $ctext insert end "$line\n"
3190 $ctext insert end "$line\n"
3191 } elseif {$diffinhdr || $x == "\\"} {
3191 } elseif {$diffinhdr || $x == "\\"} {
3192 # e.g. "\ No newline at end of file"
3192 # e.g. "\ No newline at end of file"
3193 $ctext insert end "$line\n" filesep
3193 $ctext insert end "$line\n" filesep
3194 } elseif {$line != ""} {
3194 } elseif {$line != ""} {
3195 # Something else we don't recognize
3195 # Something else we don't recognize
3196 if {$curdifftag != "Comments"} {
3196 if {$curdifftag != "Comments"} {
3197 $ctext insert end "\n"
3197 $ctext insert end "\n"
3198 $ctext tag add $curdifftag $curtagstart end
3198 $ctext tag add $curdifftag $curtagstart end
3199 set curtagstart [$ctext index "end - 1c"]
3199 set curtagstart [$ctext index "end - 1c"]
3200 set curdifftag Comments
3200 set curdifftag Comments
3201 }
3201 }
3202 $ctext insert end "$line\n" filesep
3202 $ctext insert end "$line\n" filesep
3203 }
3203 }
3204 }
3204 }
3205 $ctext conf -state disabled
3205 $ctext conf -state disabled
3206 if {[clock clicks -milliseconds] >= $nextupdate} {
3206 if {[clock clicks -milliseconds] >= $nextupdate} {
3207 incr nextupdate 100
3207 incr nextupdate 100
3208 fileevent $bdf readable {}
3208 fileevent $bdf readable {}
3209 update
3209 update
3210 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
3210 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
3211 }
3211 }
3212 }
3212 }
3213
3213
3214 proc nextfile {} {
3214 proc nextfile {} {
3215 global difffilestart ctext
3215 global difffilestart ctext
3216 set here [$ctext index @0,0]
3216 set here [$ctext index @0,0]
3217 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
3217 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
3218 if {[$ctext compare $difffilestart($i) > $here]} {
3218 if {[$ctext compare $difffilestart($i) > $here]} {
3219 if {![info exists pos]
3219 if {![info exists pos]
3220 || [$ctext compare $difffilestart($i) < $pos]} {
3220 || [$ctext compare $difffilestart($i) < $pos]} {
3221 set pos $difffilestart($i)
3221 set pos $difffilestart($i)
3222 }
3222 }
3223 }
3223 }
3224 }
3224 }
3225 if {[info exists pos]} {
3225 if {[info exists pos]} {
3226 $ctext yview $pos
3226 $ctext yview $pos
3227 }
3227 }
3228 }
3228 }
3229
3229
3230 proc listboxsel {} {
3230 proc listboxsel {} {
3231 global ctext cflist currentid
3231 global ctext cflist currentid
3232 if {![info exists currentid]} return
3232 if {![info exists currentid]} return
3233 set sel [lsort [$cflist curselection]]
3233 set sel [lsort [$cflist curselection]]
3234 if {$sel eq {}} return
3234 if {$sel eq {}} return
3235 set first [lindex $sel 0]
3235 set first [lindex $sel 0]
3236 catch {$ctext yview fmark.$first}
3236 catch {$ctext yview fmark.$first}
3237 }
3237 }
3238
3238
3239 proc setcoords {} {
3239 proc setcoords {} {
3240 global linespc charspc canvx0 canvy0 mainfont
3240 global linespc charspc canvx0 canvy0 mainfont
3241 global xspc1 xspc2 lthickness
3241 global xspc1 xspc2 lthickness
3242
3242
3243 set linespc [font metrics $mainfont -linespace]
3243 set linespc [font metrics $mainfont -linespace]
3244 set charspc [font measure $mainfont "m"]
3244 set charspc [font measure $mainfont "m"]
3245 set canvy0 [expr 3 + 0.5 * $linespc]
3245 set canvy0 [expr 3 + 0.5 * $linespc]
3246 set canvx0 [expr 3 + 0.5 * $linespc]
3246 set canvx0 [expr 3 + 0.5 * $linespc]
3247 set lthickness [expr {int($linespc / 9) + 1}]
3247 set lthickness [expr {int($linespc / 9) + 1}]
3248 set xspc1(0) $linespc
3248 set xspc1(0) $linespc
3249 set xspc2 $linespc
3249 set xspc2 $linespc
3250 }
3250 }
3251
3251
3252 proc redisplay {} {
3252 proc redisplay {} {
3253 global stopped redisplaying phase
3253 global stopped redisplaying phase
3254 if {$stopped > 1} return
3254 if {$stopped > 1} return
3255 if {$phase == "getcommits"} return
3255 if {$phase == "getcommits"} return
3256 set redisplaying 1
3256 set redisplaying 1
3257 if {$phase == "drawgraph" || $phase == "incrdraw"} {
3257 if {$phase == "drawgraph" || $phase == "incrdraw"} {
3258 set stopped 1
3258 set stopped 1
3259 } else {
3259 } else {
3260 drawgraph
3260 drawgraph
3261 }
3261 }
3262 }
3262 }
3263
3263
3264 proc incrfont {inc} {
3264 proc incrfont {inc} {
3265 global mainfont namefont textfont ctext canv phase
3265 global mainfont namefont textfont ctext canv phase
3266 global stopped entries curidfont
3266 global stopped entries curidfont
3267 unmarkmatches
3267 unmarkmatches
3268 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
3268 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
3269 set curidfont [lreplace $curidfont 1 1 [expr {[lindex $curidfont 1] + $inc}]]
3269 set curidfont [lreplace $curidfont 1 1 [expr {[lindex $curidfont 1] + $inc}]]
3270 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
3270 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
3271 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
3271 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
3272 setcoords
3272 setcoords
3273 $ctext conf -font $textfont
3273 $ctext conf -font $textfont
3274 $ctext tag conf filesep -font [concat $textfont bold]
3274 $ctext tag conf filesep -font [concat $textfont bold]
3275 foreach e $entries {
3275 foreach e $entries {
3276 $e conf -font $mainfont
3276 $e conf -font $mainfont
3277 }
3277 }
3278 if {$phase == "getcommits"} {
3278 if {$phase == "getcommits"} {
3279 $canv itemconf textitems -font $mainfont
3279 $canv itemconf textitems -font $mainfont
3280 }
3280 }
3281 redisplay
3281 redisplay
3282 }
3282 }
3283
3283
3284 proc clearsha1 {} {
3284 proc clearsha1 {} {
3285 global sha1entry sha1string
3285 global sha1entry sha1string
3286 if {[string length $sha1string] == 40} {
3286 if {[string length $sha1string] == 40} {
3287 $sha1entry delete 0 end
3287 $sha1entry delete 0 end
3288 }
3288 }
3289 }
3289 }
3290
3290
3291 proc sha1change {n1 n2 op} {
3291 proc sha1change {n1 n2 op} {
3292 global sha1string currentid sha1but
3292 global sha1string currentid sha1but
3293 if {$sha1string == {}
3293 if {$sha1string == {}
3294 || ([info exists currentid] && $sha1string == $currentid)} {
3294 || ([info exists currentid] && $sha1string == $currentid)} {
3295 set state disabled
3295 set state disabled
3296 } else {
3296 } else {
3297 set state normal
3297 set state normal
3298 }
3298 }
3299 if {[$sha1but cget -state] == $state} return
3299 if {[$sha1but cget -state] == $state} return
3300 if {$state == "normal"} {
3300 if {$state == "normal"} {
3301 $sha1but conf -state normal -relief raised -text "Goto: "
3301 $sha1but conf -state normal -relief raised -text "Goto: "
3302 } else {
3302 } else {
3303 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
3303 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
3304 }
3304 }
3305 }
3305 }
3306
3306
3307 proc gotocommit {} {
3307 proc gotocommit {} {
3308 global sha1string currentid idline tagids
3308 global sha1string currentid idline tagids
3309 global lineid numcommits
3309 global lineid numcommits
3310
3310
3311 if {$sha1string == {}
3311 if {$sha1string == {}
3312 || ([info exists currentid] && $sha1string == $currentid)} return
3312 || ([info exists currentid] && $sha1string == $currentid)} return
3313 if {[info exists tagids($sha1string)]} {
3313 if {[info exists tagids($sha1string)]} {
3314 set id $tagids($sha1string)
3314 set id $tagids($sha1string)
3315 } else {
3315 } else {
3316 set id [string tolower $sha1string]
3316 set id [string tolower $sha1string]
3317 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
3317 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
3318 set matches {}
3318 set matches {}
3319 for {set l 0} {$l < $numcommits} {incr l} {
3319 for {set l 0} {$l < $numcommits} {incr l} {
3320 if {[string match $id* $lineid($l)]} {
3320 if {[string match $id* $lineid($l)]} {
3321 lappend matches $lineid($l)
3321 lappend matches $lineid($l)
3322 }
3322 }
3323 }
3323 }
3324 if {$matches ne {}} {
3324 if {$matches ne {}} {
3325 if {[llength $matches] > 1} {
3325 if {[llength $matches] > 1} {
3326 error_popup "Short SHA1 id $id is ambiguous"
3326 error_popup "Short SHA1 id $id is ambiguous"
3327 return
3327 return
3328 }
3328 }
3329 set id [lindex $matches 0]
3329 set id [lindex $matches 0]
3330 }
3330 }
3331 }
3331 }
3332 }
3332 }
3333 if {[info exists idline($id)]} {
3333 if {[info exists idline($id)]} {
3334 selectline $idline($id) 1
3334 selectline $idline($id) 1
3335 return
3335 return
3336 }
3336 }
3337 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
3337 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
3338 set type "SHA1 id"
3338 set type "SHA1 id"
3339 } else {
3339 } else {
3340 set type "Tag"
3340 set type "Tag"
3341 }
3341 }
3342 error_popup "$type $sha1string is not known"
3342 error_popup "$type $sha1string is not known"
3343 }
3343 }
3344
3344
3345 proc lineenter {x y id} {
3345 proc lineenter {x y id} {
3346 global hoverx hovery hoverid hovertimer
3346 global hoverx hovery hoverid hovertimer
3347 global commitinfo canv
3347 global commitinfo canv
3348
3348
3349 if {![info exists commitinfo($id)]} return
3349 if {![info exists commitinfo($id)]} return
3350 set hoverx $x
3350 set hoverx $x
3351 set hovery $y
3351 set hovery $y
3352 set hoverid $id
3352 set hoverid $id
3353 if {[info exists hovertimer]} {
3353 if {[info exists hovertimer]} {
3354 after cancel $hovertimer
3354 after cancel $hovertimer
3355 }
3355 }
3356 set hovertimer [after 500 linehover]
3356 set hovertimer [after 500 linehover]
3357 $canv delete hover
3357 $canv delete hover
3358 }
3358 }
3359
3359
3360 proc linemotion {x y id} {
3360 proc linemotion {x y id} {
3361 global hoverx hovery hoverid hovertimer
3361 global hoverx hovery hoverid hovertimer
3362
3362
3363 if {[info exists hoverid] && $id == $hoverid} {
3363 if {[info exists hoverid] && $id == $hoverid} {
3364 set hoverx $x
3364 set hoverx $x
3365 set hovery $y
3365 set hovery $y
3366 if {[info exists hovertimer]} {
3366 if {[info exists hovertimer]} {
3367 after cancel $hovertimer
3367 after cancel $hovertimer
3368 }
3368 }
3369 set hovertimer [after 500 linehover]
3369 set hovertimer [after 500 linehover]
3370 }
3370 }
3371 }
3371 }
3372
3372
3373 proc lineleave {id} {
3373 proc lineleave {id} {
3374 global hoverid hovertimer canv
3374 global hoverid hovertimer canv
3375
3375
3376 if {[info exists hoverid] && $id == $hoverid} {
3376 if {[info exists hoverid] && $id == $hoverid} {
3377 $canv delete hover
3377 $canv delete hover
3378 if {[info exists hovertimer]} {
3378 if {[info exists hovertimer]} {
3379 after cancel $hovertimer
3379 after cancel $hovertimer
3380 unset hovertimer
3380 unset hovertimer
3381 }
3381 }
3382 unset hoverid
3382 unset hoverid
3383 }
3383 }
3384 }
3384 }
3385
3385
3386 proc linehover {} {
3386 proc linehover {} {
3387 global hoverx hovery hoverid hovertimer
3387 global hoverx hovery hoverid hovertimer
3388 global canv linespc lthickness
3388 global canv linespc lthickness
3389 global commitinfo mainfont
3389 global commitinfo mainfont
3390
3390
3391 set text [lindex $commitinfo($hoverid) 0]
3391 set text [lindex $commitinfo($hoverid) 0]
3392 set ymax [lindex [$canv cget -scrollregion] 3]
3392 set ymax [lindex [$canv cget -scrollregion] 3]
3393 if {$ymax == {}} return
3393 if {$ymax == {}} return
3394 set yfrac [lindex [$canv yview] 0]
3394 set yfrac [lindex [$canv yview] 0]
3395 set x [expr {$hoverx + 2 * $linespc}]
3395 set x [expr {$hoverx + 2 * $linespc}]
3396 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
3396 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
3397 set x0 [expr {$x - 2 * $lthickness}]
3397 set x0 [expr {$x - 2 * $lthickness}]
3398 set y0 [expr {$y - 2 * $lthickness}]
3398 set y0 [expr {$y - 2 * $lthickness}]
3399 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
3399 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
3400 set y1 [expr {$y + $linespc + 2 * $lthickness}]
3400 set y1 [expr {$y + $linespc + 2 * $lthickness}]
3401 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
3401 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
3402 -fill \#ffff80 -outline black -width 1 -tags hover]
3402 -fill \#ffff80 -outline black -width 1 -tags hover]
3403 $canv raise $t
3403 $canv raise $t
3404 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
3404 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
3405 $canv raise $t
3405 $canv raise $t
3406 }
3406 }
3407
3407
3408 proc clickisonarrow {id y} {
3408 proc clickisonarrow {id y} {
3409 global mainline mainlinearrow sidelines lthickness
3409 global mainline mainlinearrow sidelines lthickness
3410
3410
3411 set thresh [expr {2 * $lthickness + 6}]
3411 set thresh [expr {2 * $lthickness + 6}]
3412 if {[info exists mainline($id)]} {
3412 if {[info exists mainline($id)]} {
3413 if {$mainlinearrow($id) ne "none"} {
3413 if {$mainlinearrow($id) ne "none"} {
3414 if {abs([lindex $mainline($id) 1] - $y) < $thresh} {
3414 if {abs([lindex $mainline($id) 1] - $y) < $thresh} {
3415 return "up"
3415 return "up"
3416 }
3416 }
3417 }
3417 }
3418 }
3418 }
3419 if {[info exists sidelines($id)]} {
3419 if {[info exists sidelines($id)]} {
3420 foreach ls $sidelines($id) {
3420 foreach ls $sidelines($id) {
3421 set coords [lindex $ls 0]
3421 set coords [lindex $ls 0]
3422 set arrow [lindex $ls 2]
3422 set arrow [lindex $ls 2]
3423 if {$arrow eq "first" || $arrow eq "both"} {
3423 if {$arrow eq "first" || $arrow eq "both"} {
3424 if {abs([lindex $coords 1] - $y) < $thresh} {
3424 if {abs([lindex $coords 1] - $y) < $thresh} {
3425 return "up"
3425 return "up"
3426 }
3426 }
3427 }
3427 }
3428 if {$arrow eq "last" || $arrow eq "both"} {
3428 if {$arrow eq "last" || $arrow eq "both"} {
3429 if {abs([lindex $coords end] - $y) < $thresh} {
3429 if {abs([lindex $coords end] - $y) < $thresh} {
3430 return "down"
3430 return "down"
3431 }
3431 }
3432 }
3432 }
3433 }
3433 }
3434 }
3434 }
3435 return {}
3435 return {}
3436 }
3436 }
3437
3437
3438 proc arrowjump {id dirn y} {
3438 proc arrowjump {id dirn y} {
3439 global mainline sidelines canv
3439 global mainline sidelines canv
3440
3440
3441 set yt {}
3441 set yt {}
3442 if {$dirn eq "down"} {
3442 if {$dirn eq "down"} {
3443 if {[info exists mainline($id)]} {
3443 if {[info exists mainline($id)]} {
3444 set y1 [lindex $mainline($id) 1]
3444 set y1 [lindex $mainline($id) 1]
3445 if {$y1 > $y} {
3445 if {$y1 > $y} {
3446 set yt $y1
3446 set yt $y1
3447 }
3447 }
3448 }
3448 }
3449 if {[info exists sidelines($id)]} {
3449 if {[info exists sidelines($id)]} {
3450 foreach ls $sidelines($id) {
3450 foreach ls $sidelines($id) {
3451 set y1 [lindex $ls 0 1]
3451 set y1 [lindex $ls 0 1]
3452 if {$y1 > $y && ($yt eq {} || $y1 < $yt)} {
3452 if {$y1 > $y && ($yt eq {} || $y1 < $yt)} {
3453 set yt $y1
3453 set yt $y1
3454 }
3454 }
3455 }
3455 }
3456 }
3456 }
3457 } else {
3457 } else {
3458 if {[info exists sidelines($id)]} {
3458 if {[info exists sidelines($id)]} {
3459 foreach ls $sidelines($id) {
3459 foreach ls $sidelines($id) {
3460 set y1 [lindex $ls 0 end]
3460 set y1 [lindex $ls 0 end]
3461 if {$y1 < $y && ($yt eq {} || $y1 > $yt)} {
3461 if {$y1 < $y && ($yt eq {} || $y1 > $yt)} {
3462 set yt $y1
3462 set yt $y1
3463 }
3463 }
3464 }
3464 }
3465 }
3465 }
3466 }
3466 }
3467 if {$yt eq {}} return
3467 if {$yt eq {}} return
3468 set ymax [lindex [$canv cget -scrollregion] 3]
3468 set ymax [lindex [$canv cget -scrollregion] 3]
3469 if {$ymax eq {} || $ymax <= 0} return
3469 if {$ymax eq {} || $ymax <= 0} return
3470 set view [$canv yview]
3470 set view [$canv yview]
3471 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
3471 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
3472 set yfrac [expr {$yt / $ymax - $yspan / 2}]
3472 set yfrac [expr {$yt / $ymax - $yspan / 2}]
3473 if {$yfrac < 0} {
3473 if {$yfrac < 0} {
3474 set yfrac 0
3474 set yfrac 0
3475 }
3475 }
3476 $canv yview moveto $yfrac
3476 $canv yview moveto $yfrac
3477 }
3477 }
3478
3478
3479 proc lineclick {x y id isnew} {
3479 proc lineclick {x y id isnew} {
3480 global ctext commitinfo children cflist canv thickerline
3480 global ctext commitinfo children cflist canv thickerline
3481
3481
3482 unmarkmatches
3482 unmarkmatches
3483 unselectline
3483 unselectline
3484 normalline
3484 normalline
3485 $canv delete hover
3485 $canv delete hover
3486 # draw this line thicker than normal
3486 # draw this line thicker than normal
3487 drawlines $id 1
3487 drawlines $id 1
3488 set thickerline $id
3488 set thickerline $id
3489 if {$isnew} {
3489 if {$isnew} {
3490 set ymax [lindex [$canv cget -scrollregion] 3]
3490 set ymax [lindex [$canv cget -scrollregion] 3]
3491 if {$ymax eq {}} return
3491 if {$ymax eq {}} return
3492 set yfrac [lindex [$canv yview] 0]
3492 set yfrac [lindex [$canv yview] 0]
3493 set y [expr {$y + $yfrac * $ymax}]
3493 set y [expr {$y + $yfrac * $ymax}]
3494 }
3494 }
3495 set dirn [clickisonarrow $id $y]
3495 set dirn [clickisonarrow $id $y]
3496 if {$dirn ne {}} {
3496 if {$dirn ne {}} {
3497 arrowjump $id $dirn $y
3497 arrowjump $id $dirn $y
3498 return
3498 return
3499 }
3499 }
3500
3500
3501 if {$isnew} {
3501 if {$isnew} {
3502 addtohistory [list lineclick $x $y $id 0]
3502 addtohistory [list lineclick $x $y $id 0]
3503 }
3503 }
3504 # fill the details pane with info about this line
3504 # fill the details pane with info about this line
3505 $ctext conf -state normal
3505 $ctext conf -state normal
3506 $ctext delete 0.0 end
3506 $ctext delete 0.0 end
3507 $ctext tag conf link -foreground blue -underline 1
3507 $ctext tag conf link -foreground blue -underline 1
3508 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3508 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3509 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3509 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3510 $ctext insert end "Parent:\t"
3510 $ctext insert end "Parent:\t"
3511 $ctext insert end $id [list link link0]
3511 $ctext insert end $id [list link link0]
3512 $ctext tag bind link0 <1> [list selbyid $id]
3512 $ctext tag bind link0 <1> [list selbyid $id]
3513 set info $commitinfo($id)
3513 set info $commitinfo($id)
3514 $ctext insert end "\n\t[lindex $info 0]\n"
3514 $ctext insert end "\n\t[lindex $info 0]\n"
3515 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
3515 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
3516 $ctext insert end "\tDate:\t[lindex $info 2]\n"
3516 $ctext insert end "\tDate:\t[lindex $info 2]\n"
3517 if {[info exists children($id)]} {
3517 if {[info exists children($id)]} {
3518 $ctext insert end "\nChildren:"
3518 $ctext insert end "\nChildren:"
3519 set i 0
3519 set i 0
3520 foreach child $children($id) {
3520 foreach child $children($id) {
3521 incr i
3521 incr i
3522 set info $commitinfo($child)
3522 set info $commitinfo($child)
3523 $ctext insert end "\n\t"
3523 $ctext insert end "\n\t"
3524 $ctext insert end $child [list link link$i]
3524 $ctext insert end $child [list link link$i]
3525 $ctext tag bind link$i <1> [list selbyid $child]
3525 $ctext tag bind link$i <1> [list selbyid $child]
3526 $ctext insert end "\n\t[lindex $info 0]"
3526 $ctext insert end "\n\t[lindex $info 0]"
3527 $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
3527 $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
3528 $ctext insert end "\n\tDate:\t[lindex $info 2]\n"
3528 $ctext insert end "\n\tDate:\t[lindex $info 2]\n"
3529 }
3529 }
3530 }
3530 }
3531 $ctext conf -state disabled
3531 $ctext conf -state disabled
3532
3532
3533 $cflist delete 0 end
3533 $cflist delete 0 end
3534 }
3534 }
3535
3535
3536 proc normalline {} {
3536 proc normalline {} {
3537 global thickerline
3537 global thickerline
3538 if {[info exists thickerline]} {
3538 if {[info exists thickerline]} {
3539 drawlines $thickerline 0
3539 drawlines $thickerline 0
3540 unset thickerline
3540 unset thickerline
3541 }
3541 }
3542 }
3542 }
3543
3543
3544 proc selbyid {id} {
3544 proc selbyid {id} {
3545 global idline
3545 global idline
3546 if {[info exists idline($id)]} {
3546 if {[info exists idline($id)]} {
3547 selectline $idline($id) 1
3547 selectline $idline($id) 1
3548 }
3548 }
3549 }
3549 }
3550
3550
3551 proc mstime {} {
3551 proc mstime {} {
3552 global startmstime
3552 global startmstime
3553 if {![info exists startmstime]} {
3553 if {![info exists startmstime]} {
3554 set startmstime [clock clicks -milliseconds]
3554 set startmstime [clock clicks -milliseconds]
3555 }
3555 }
3556 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
3556 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
3557 }
3557 }
3558
3558
3559 proc rowmenu {x y id} {
3559 proc rowmenu {x y id} {
3560 global rowctxmenu idline selectedline rowmenuid hgvdiff
3560 global rowctxmenu idline selectedline rowmenuid hgvdiff
3561
3561
3562 if {![info exists selectedline] || $idline($id) eq $selectedline} {
3562 if {![info exists selectedline] || $idline($id) eq $selectedline} {
3563 set state disabled
3563 set state disabled
3564 } else {
3564 } else {
3565 set state normal
3565 set state normal
3566 }
3566 }
3567 $rowctxmenu entryconfigure 0 -state $state
3567 $rowctxmenu entryconfigure 0 -state $state
3568 $rowctxmenu entryconfigure 1 -state $state
3568 $rowctxmenu entryconfigure 1 -state $state
3569 $rowctxmenu entryconfigure 2 -state $state
3569 $rowctxmenu entryconfigure 2 -state $state
3570 if { $hgvdiff ne "" } {
3570 if { $hgvdiff ne "" } {
3571 $rowctxmenu entryconfigure 6 -state $state
3571 $rowctxmenu entryconfigure 6 -state $state
3572 }
3572 }
3573 set rowmenuid $id
3573 set rowmenuid $id
3574 tk_popup $rowctxmenu $x $y
3574 tk_popup $rowctxmenu $x $y
3575 }
3575 }
3576
3576
3577 proc diffvssel {dirn} {
3577 proc diffvssel {dirn} {
3578 global rowmenuid selectedline lineid
3578 global rowmenuid selectedline lineid
3579
3579
3580 if {![info exists selectedline]} return
3580 if {![info exists selectedline]} return
3581 if {$dirn} {
3581 if {$dirn} {
3582 set oldid $lineid($selectedline)
3582 set oldid $lineid($selectedline)
3583 set newid $rowmenuid
3583 set newid $rowmenuid
3584 } else {
3584 } else {
3585 set oldid $rowmenuid
3585 set oldid $rowmenuid
3586 set newid $lineid($selectedline)
3586 set newid $lineid($selectedline)
3587 }
3587 }
3588 addtohistory [list doseldiff $oldid $newid]
3588 addtohistory [list doseldiff $oldid $newid]
3589 doseldiff $oldid $newid
3589 doseldiff $oldid $newid
3590 }
3590 }
3591
3591
3592 proc doseldiff {oldid newid} {
3592 proc doseldiff {oldid newid} {
3593 global ctext cflist
3593 global ctext cflist
3594 global commitinfo
3594 global commitinfo
3595
3595
3596 $ctext conf -state normal
3596 $ctext conf -state normal
3597 $ctext delete 0.0 end
3597 $ctext delete 0.0 end
3598 $ctext mark set fmark.0 0.0
3598 $ctext mark set fmark.0 0.0
3599 $ctext mark gravity fmark.0 left
3599 $ctext mark gravity fmark.0 left
3600 $cflist delete 0 end
3600 $cflist delete 0 end
3601 $cflist insert end "Top"
3601 $cflist insert end "Top"
3602 $ctext insert end "From "
3602 $ctext insert end "From "
3603 $ctext tag conf link -foreground blue -underline 1
3603 $ctext tag conf link -foreground blue -underline 1
3604 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3604 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3605 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3605 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3606 $ctext tag bind link0 <1> [list selbyid $oldid]
3606 $ctext tag bind link0 <1> [list selbyid $oldid]
3607 $ctext insert end $oldid [list link link0]
3607 $ctext insert end $oldid [list link link0]
3608 $ctext insert end "\n "
3608 $ctext insert end "\n "
3609 $ctext insert end [lindex $commitinfo($oldid) 0]
3609 $ctext insert end [lindex $commitinfo($oldid) 0]
3610 $ctext insert end "\n\nTo "
3610 $ctext insert end "\n\nTo "
3611 $ctext tag bind link1 <1> [list selbyid $newid]
3611 $ctext tag bind link1 <1> [list selbyid $newid]
3612 $ctext insert end $newid [list link link1]
3612 $ctext insert end $newid [list link link1]
3613 $ctext insert end "\n "
3613 $ctext insert end "\n "
3614 $ctext insert end [lindex $commitinfo($newid) 0]
3614 $ctext insert end [lindex $commitinfo($newid) 0]
3615 $ctext insert end "\n"
3615 $ctext insert end "\n"
3616 $ctext conf -state disabled
3616 $ctext conf -state disabled
3617 $ctext tag delete Comments
3617 $ctext tag delete Comments
3618 $ctext tag remove found 1.0 end
3618 $ctext tag remove found 1.0 end
3619 startdiff [list $newid $oldid]
3619 startdiff [list $newid $oldid]
3620 }
3620 }
3621
3621
3622 proc mkpatch {} {
3622 proc mkpatch {} {
3623 global rowmenuid currentid commitinfo patchtop patchnum
3623 global rowmenuid currentid commitinfo patchtop patchnum
3624
3624
3625 if {![info exists currentid]} return
3625 if {![info exists currentid]} return
3626 set oldid $currentid
3626 set oldid $currentid
3627 set oldhead [lindex $commitinfo($oldid) 0]
3627 set oldhead [lindex $commitinfo($oldid) 0]
3628 set newid $rowmenuid
3628 set newid $rowmenuid
3629 set newhead [lindex $commitinfo($newid) 0]
3629 set newhead [lindex $commitinfo($newid) 0]
3630 set top .patch
3630 set top .patch
3631 set patchtop $top
3631 set patchtop $top
3632 catch {destroy $top}
3632 catch {destroy $top}
3633 toplevel $top
3633 toplevel $top
3634 label $top.title -text "Generate patch"
3634 label $top.title -text "Generate patch"
3635 grid $top.title - -pady 10
3635 grid $top.title - -pady 10
3636 label $top.from -text "From:"
3636 label $top.from -text "From:"
3637 entry $top.fromsha1 -width 40 -relief flat
3637 entry $top.fromsha1 -width 40 -relief flat
3638 $top.fromsha1 insert 0 $oldid
3638 $top.fromsha1 insert 0 $oldid
3639 $top.fromsha1 conf -state readonly
3639 $top.fromsha1 conf -state readonly
3640 grid $top.from $top.fromsha1 -sticky w
3640 grid $top.from $top.fromsha1 -sticky w
3641 entry $top.fromhead -width 60 -relief flat
3641 entry $top.fromhead -width 60 -relief flat
3642 $top.fromhead insert 0 $oldhead
3642 $top.fromhead insert 0 $oldhead
3643 $top.fromhead conf -state readonly
3643 $top.fromhead conf -state readonly
3644 grid x $top.fromhead -sticky w
3644 grid x $top.fromhead -sticky w
3645 label $top.to -text "To:"
3645 label $top.to -text "To:"
3646 entry $top.tosha1 -width 40 -relief flat
3646 entry $top.tosha1 -width 40 -relief flat
3647 $top.tosha1 insert 0 $newid
3647 $top.tosha1 insert 0 $newid
3648 $top.tosha1 conf -state readonly
3648 $top.tosha1 conf -state readonly
3649 grid $top.to $top.tosha1 -sticky w
3649 grid $top.to $top.tosha1 -sticky w
3650 entry $top.tohead -width 60 -relief flat
3650 entry $top.tohead -width 60 -relief flat
3651 $top.tohead insert 0 $newhead
3651 $top.tohead insert 0 $newhead
3652 $top.tohead conf -state readonly
3652 $top.tohead conf -state readonly
3653 grid x $top.tohead -sticky w
3653 grid x $top.tohead -sticky w
3654 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
3654 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
3655 grid $top.rev x -pady 10
3655 grid $top.rev x -pady 10
3656 label $top.flab -text "Output file:"
3656 label $top.flab -text "Output file:"
3657 entry $top.fname -width 60
3657 entry $top.fname -width 60
3658 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
3658 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
3659 incr patchnum
3659 incr patchnum
3660 grid $top.flab $top.fname -sticky w
3660 grid $top.flab $top.fname -sticky w
3661 frame $top.buts
3661 frame $top.buts
3662 button $top.buts.gen -text "Generate" -command mkpatchgo
3662 button $top.buts.gen -text "Generate" -command mkpatchgo
3663 button $top.buts.can -text "Cancel" -command mkpatchcan
3663 button $top.buts.can -text "Cancel" -command mkpatchcan
3664 grid $top.buts.gen $top.buts.can
3664 grid $top.buts.gen $top.buts.can
3665 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3665 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3666 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3666 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3667 grid $top.buts - -pady 10 -sticky ew
3667 grid $top.buts - -pady 10 -sticky ew
3668 focus $top.fname
3668 focus $top.fname
3669 }
3669 }
3670
3670
3671 proc mkpatchrev {} {
3671 proc mkpatchrev {} {
3672 global patchtop
3672 global patchtop
3673
3673
3674 set oldid [$patchtop.fromsha1 get]
3674 set oldid [$patchtop.fromsha1 get]
3675 set oldhead [$patchtop.fromhead get]
3675 set oldhead [$patchtop.fromhead get]
3676 set newid [$patchtop.tosha1 get]
3676 set newid [$patchtop.tosha1 get]
3677 set newhead [$patchtop.tohead get]
3677 set newhead [$patchtop.tohead get]
3678 foreach e [list fromsha1 fromhead tosha1 tohead] \
3678 foreach e [list fromsha1 fromhead tosha1 tohead] \
3679 v [list $newid $newhead $oldid $oldhead] {
3679 v [list $newid $newhead $oldid $oldhead] {
3680 $patchtop.$e conf -state normal
3680 $patchtop.$e conf -state normal
3681 $patchtop.$e delete 0 end
3681 $patchtop.$e delete 0 end
3682 $patchtop.$e insert 0 $v
3682 $patchtop.$e insert 0 $v
3683 $patchtop.$e conf -state readonly
3683 $patchtop.$e conf -state readonly
3684 }
3684 }
3685 }
3685 }
3686
3686
3687 proc mkpatchgo {} {
3687 proc mkpatchgo {} {
3688 global patchtop env
3688 global patchtop env
3689
3689
3690 set oldid [$patchtop.fromsha1 get]
3690 set oldid [$patchtop.fromsha1 get]
3691 set newid [$patchtop.tosha1 get]
3691 set newid [$patchtop.tosha1 get]
3692 set fname [$patchtop.fname get]
3692 set fname [$patchtop.fname get]
3693 if {[catch {exec $env(HG) --config ui.report_untrusted=false debug-diff-tree -p $oldid $newid >$fname &} err]} {
3693 if {[catch {exec $env(HG) --config ui.report_untrusted=false debug-diff-tree -p $oldid $newid >$fname &} err]} {
3694 error_popup "Error creating patch: $err"
3694 error_popup "Error creating patch: $err"
3695 }
3695 }
3696 catch {destroy $patchtop}
3696 catch {destroy $patchtop}
3697 unset patchtop
3697 unset patchtop
3698 }
3698 }
3699
3699
3700 proc mkpatchcan {} {
3700 proc mkpatchcan {} {
3701 global patchtop
3701 global patchtop
3702
3702
3703 catch {destroy $patchtop}
3703 catch {destroy $patchtop}
3704 unset patchtop
3704 unset patchtop
3705 }
3705 }
3706
3706
3707 proc mktag {} {
3707 proc mktag {} {
3708 global rowmenuid mktagtop commitinfo
3708 global rowmenuid mktagtop commitinfo
3709
3709
3710 set top .maketag
3710 set top .maketag
3711 set mktagtop $top
3711 set mktagtop $top
3712 catch {destroy $top}
3712 catch {destroy $top}
3713 toplevel $top
3713 toplevel $top
3714 label $top.title -text "Create tag"
3714 label $top.title -text "Create tag"
3715 grid $top.title - -pady 10
3715 grid $top.title - -pady 10
3716 label $top.id -text "ID:"
3716 label $top.id -text "ID:"
3717 entry $top.sha1 -width 40 -relief flat
3717 entry $top.sha1 -width 40 -relief flat
3718 $top.sha1 insert 0 $rowmenuid
3718 $top.sha1 insert 0 $rowmenuid
3719 $top.sha1 conf -state readonly
3719 $top.sha1 conf -state readonly
3720 grid $top.id $top.sha1 -sticky w
3720 grid $top.id $top.sha1 -sticky w
3721 entry $top.head -width 60 -relief flat
3721 entry $top.head -width 60 -relief flat
3722 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3722 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3723 $top.head conf -state readonly
3723 $top.head conf -state readonly
3724 grid x $top.head -sticky w
3724 grid x $top.head -sticky w
3725 label $top.tlab -text "Tag name:"
3725 label $top.tlab -text "Tag name:"
3726 entry $top.tag -width 60
3726 entry $top.tag -width 60
3727 grid $top.tlab $top.tag -sticky w
3727 grid $top.tlab $top.tag -sticky w
3728 frame $top.buts
3728 frame $top.buts
3729 button $top.buts.gen -text "Create" -command mktaggo
3729 button $top.buts.gen -text "Create" -command mktaggo
3730 button $top.buts.can -text "Cancel" -command mktagcan
3730 button $top.buts.can -text "Cancel" -command mktagcan
3731 grid $top.buts.gen $top.buts.can
3731 grid $top.buts.gen $top.buts.can
3732 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3732 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3733 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3733 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3734 grid $top.buts - -pady 10 -sticky ew
3734 grid $top.buts - -pady 10 -sticky ew
3735 focus $top.tag
3735 focus $top.tag
3736 }
3736 }
3737
3737
3738 proc domktag {} {
3738 proc domktag {} {
3739 global mktagtop env tagids idtags
3739 global mktagtop env tagids idtags
3740
3740
3741 set id [$mktagtop.sha1 get]
3741 set id [$mktagtop.sha1 get]
3742 set tag [$mktagtop.tag get]
3742 set tag [$mktagtop.tag get]
3743 if {$tag == {}} {
3743 if {$tag == {}} {
3744 error_popup "No tag name specified"
3744 error_popup "No tag name specified"
3745 return
3745 return
3746 }
3746 }
3747 if {[info exists tagids($tag)]} {
3747 if {[info exists tagids($tag)]} {
3748 error_popup "Tag \"$tag\" already exists"
3748 error_popup "Tag \"$tag\" already exists"
3749 return
3749 return
3750 }
3750 }
3751 if {[catch {
3751 if {[catch {
3752 set out [exec $env(HG) --config ui.report_untrusted=false tag -r $id $tag]
3752 set out [exec $env(HG) --config ui.report_untrusted=false tag -r $id $tag]
3753 } err]} {
3753 } err]} {
3754 error_popup "Error creating tag: $err"
3754 error_popup "Error creating tag: $err"
3755 return
3755 return
3756 }
3756 }
3757
3757
3758 set tagids($tag) $id
3758 set tagids($tag) $id
3759 lappend idtags($id) $tag
3759 lappend idtags($id) $tag
3760 redrawtags $id
3760 redrawtags $id
3761 }
3761 }
3762
3762
3763 proc redrawtags {id} {
3763 proc redrawtags {id} {
3764 global canv linehtag idline idpos selectedline
3764 global canv linehtag idline idpos selectedline
3765
3765
3766 if {![info exists idline($id)]} return
3766 if {![info exists idline($id)]} return
3767 $canv delete tag.$id
3767 $canv delete tag.$id
3768 set xt [eval drawtags $id $idpos($id)]
3768 set xt [eval drawtags $id $idpos($id)]
3769 $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
3769 $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
3770 if {[info exists selectedline] && $selectedline == $idline($id)} {
3770 if {[info exists selectedline] && $selectedline == $idline($id)} {
3771 selectline $selectedline 0
3771 selectline $selectedline 0
3772 }
3772 }
3773 }
3773 }
3774
3774
3775 proc mktagcan {} {
3775 proc mktagcan {} {
3776 global mktagtop
3776 global mktagtop
3777
3777
3778 catch {destroy $mktagtop}
3778 catch {destroy $mktagtop}
3779 unset mktagtop
3779 unset mktagtop
3780 }
3780 }
3781
3781
3782 proc mktaggo {} {
3782 proc mktaggo {} {
3783 domktag
3783 domktag
3784 mktagcan
3784 mktagcan
3785 }
3785 }
3786
3786
3787 proc writecommit {} {
3787 proc writecommit {} {
3788 global rowmenuid wrcomtop commitinfo wrcomcmd
3788 global rowmenuid wrcomtop commitinfo wrcomcmd
3789
3789
3790 set top .writecommit
3790 set top .writecommit
3791 set wrcomtop $top
3791 set wrcomtop $top
3792 catch {destroy $top}
3792 catch {destroy $top}
3793 toplevel $top
3793 toplevel $top
3794 label $top.title -text "Write commit to file"
3794 label $top.title -text "Write commit to file"
3795 grid $top.title - -pady 10
3795 grid $top.title - -pady 10
3796 label $top.id -text "ID:"
3796 label $top.id -text "ID:"
3797 entry $top.sha1 -width 40 -relief flat
3797 entry $top.sha1 -width 40 -relief flat
3798 $top.sha1 insert 0 $rowmenuid
3798 $top.sha1 insert 0 $rowmenuid
3799 $top.sha1 conf -state readonly
3799 $top.sha1 conf -state readonly
3800 grid $top.id $top.sha1 -sticky w
3800 grid $top.id $top.sha1 -sticky w
3801 entry $top.head -width 60 -relief flat
3801 entry $top.head -width 60 -relief flat
3802 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3802 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3803 $top.head conf -state readonly
3803 $top.head conf -state readonly
3804 grid x $top.head -sticky w
3804 grid x $top.head -sticky w
3805 label $top.clab -text "Command:"
3805 label $top.clab -text "Command:"
3806 entry $top.cmd -width 60 -textvariable wrcomcmd
3806 entry $top.cmd -width 60 -textvariable wrcomcmd
3807 grid $top.clab $top.cmd -sticky w -pady 10
3807 grid $top.clab $top.cmd -sticky w -pady 10
3808 label $top.flab -text "Output file:"
3808 label $top.flab -text "Output file:"
3809 entry $top.fname -width 60
3809 entry $top.fname -width 60
3810 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
3810 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
3811 grid $top.flab $top.fname -sticky w
3811 grid $top.flab $top.fname -sticky w
3812 frame $top.buts
3812 frame $top.buts
3813 button $top.buts.gen -text "Write" -command wrcomgo
3813 button $top.buts.gen -text "Write" -command wrcomgo
3814 button $top.buts.can -text "Cancel" -command wrcomcan
3814 button $top.buts.can -text "Cancel" -command wrcomcan
3815 grid $top.buts.gen $top.buts.can
3815 grid $top.buts.gen $top.buts.can
3816 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3816 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3817 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3817 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3818 grid $top.buts - -pady 10 -sticky ew
3818 grid $top.buts - -pady 10 -sticky ew
3819 focus $top.fname
3819 focus $top.fname
3820 }
3820 }
3821
3821
3822 proc wrcomgo {} {
3822 proc wrcomgo {} {
3823 global wrcomtop
3823 global wrcomtop
3824
3824
3825 set id [$wrcomtop.sha1 get]
3825 set id [$wrcomtop.sha1 get]
3826 set cmd "echo $id | [$wrcomtop.cmd get]"
3826 set cmd "echo $id | [$wrcomtop.cmd get]"
3827 set fname [$wrcomtop.fname get]
3827 set fname [$wrcomtop.fname get]
3828 if {[catch {exec sh -c $cmd > $fname &} err]} {
3828 if {[catch {exec sh -c $cmd > $fname &} err]} {
3829 error_popup "Error writing commit: $err"
3829 error_popup "Error writing commit: $err"
3830 }
3830 }
3831 catch {destroy $wrcomtop}
3831 catch {destroy $wrcomtop}
3832 unset wrcomtop
3832 unset wrcomtop
3833 }
3833 }
3834
3834
3835 proc wrcomcan {} {
3835 proc wrcomcan {} {
3836 global wrcomtop
3836 global wrcomtop
3837
3837
3838 catch {destroy $wrcomtop}
3838 catch {destroy $wrcomtop}
3839 unset wrcomtop
3839 unset wrcomtop
3840 }
3840 }
3841
3841
3842 proc listrefs {id} {
3842 proc listrefs {id} {
3843 global idtags idheads idotherrefs
3843 global idtags idheads idotherrefs
3844
3844
3845 set x {}
3845 set x {}
3846 if {[info exists idtags($id)]} {
3846 if {[info exists idtags($id)]} {
3847 set x $idtags($id)
3847 set x $idtags($id)
3848 }
3848 }
3849 set y {}
3849 set y {}
3850 if {[info exists idheads($id)]} {
3850 if {[info exists idheads($id)]} {
3851 set y $idheads($id)
3851 set y $idheads($id)
3852 }
3852 }
3853 set z {}
3853 set z {}
3854 if {[info exists idotherrefs($id)]} {
3854 if {[info exists idotherrefs($id)]} {
3855 set z $idotherrefs($id)
3855 set z $idotherrefs($id)
3856 }
3856 }
3857 return [list $x $y $z]
3857 return [list $x $y $z]
3858 }
3858 }
3859
3859
3860 proc rereadrefs {} {
3860 proc rereadrefs {} {
3861 global idtags idheads idotherrefs
3861 global idtags idheads idotherrefs
3862 global tagids headids otherrefids
3862 global tagids headids otherrefids
3863
3863
3864 set refids [concat [array names idtags] \
3864 set refids [concat [array names idtags] \
3865 [array names idheads] [array names idotherrefs]]
3865 [array names idheads] [array names idotherrefs]]
3866 foreach id $refids {
3866 foreach id $refids {
3867 if {![info exists ref($id)]} {
3867 if {![info exists ref($id)]} {
3868 set ref($id) [listrefs $id]
3868 set ref($id) [listrefs $id]
3869 }
3869 }
3870 }
3870 }
3871 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
3871 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
3872 catch {unset $v}
3872 catch {unset $v}
3873 }
3873 }
3874 readrefs
3874 readrefs
3875 set refids [lsort -unique [concat $refids [array names idtags] \
3875 set refids [lsort -unique [concat $refids [array names idtags] \
3876 [array names idheads] [array names idotherrefs]]]
3876 [array names idheads] [array names idotherrefs]]]
3877 foreach id $refids {
3877 foreach id $refids {
3878 set v [listrefs $id]
3878 set v [listrefs $id]
3879 if {![info exists ref($id)] || $ref($id) != $v} {
3879 if {![info exists ref($id)] || $ref($id) != $v} {
3880 redrawtags $id
3880 redrawtags $id
3881 }
3881 }
3882 }
3882 }
3883 }
3883 }
3884
3884
3885 proc vdiff {withparent} {
3885 proc vdiff {withparent} {
3886 global env rowmenuid selectedline lineid hgvdiff
3886 global env rowmenuid selectedline lineid hgvdiff
3887
3887
3888 if {![info exists rowmenuid]} return
3888 if {![info exists rowmenuid]} return
3889 set curid $rowmenuid
3889 set curid $rowmenuid
3890
3890
3891 if {$withparent} {
3891 if {$withparent} {
3892 set parents [exec $env(HG) --config ui.report_untrusted=false parents --rev $curid --template "{node}\n"]
3892 set parents [exec $env(HG) --config ui.report_untrusted=false parents --rev $curid --template "{node}\n"]
3893 set firstparent [lindex [split $parents "\n"] 0]
3893 set firstparent [lindex [split $parents "\n"] 0]
3894 set otherid $firstparent
3894 set otherid $firstparent
3895 } else {
3895 } else {
3896 if {![info exists selectedline]} return
3896 if {![info exists selectedline]} return
3897 set otherid $lineid($selectedline)
3897 set otherid $lineid($selectedline)
3898 }
3898 }
3899 set range "$otherid:$curid"
3899 set range "$otherid:$curid"
3900 if {[catch {exec $env(HG) --config ui.report_untrusted=false $hgvdiff -r $range} err]} {
3900 if {[catch {exec $env(HG) --config ui.report_untrusted=false $hgvdiff -r $range} err]} {
3901 # Ignore errors, this is just visualization
3901 # Ignore errors, this is just visualization
3902 }
3902 }
3903 }
3903 }
3904
3904
3905 proc showtag {tag isnew} {
3905 proc showtag {tag isnew} {
3906 global ctext cflist tagcontents tagids linknum
3906 global ctext cflist tagcontents tagids linknum
3907
3907
3908 if {$isnew} {
3908 if {$isnew} {
3909 addtohistory [list showtag $tag 0]
3909 addtohistory [list showtag $tag 0]
3910 }
3910 }
3911 $ctext conf -state normal
3911 $ctext conf -state normal
3912 $ctext delete 0.0 end
3912 $ctext delete 0.0 end
3913 set linknum 0
3913 set linknum 0
3914 if {[info exists tagcontents($tag)]} {
3914 if {[info exists tagcontents($tag)]} {
3915 set text $tagcontents($tag)
3915 set text $tagcontents($tag)
3916 } else {
3916 } else {
3917 set text "Tag: $tag\nId: $tagids($tag)"
3917 set text "Tag: $tag\nId: $tagids($tag)"
3918 }
3918 }
3919 appendwithlinks $text
3919 appendwithlinks $text
3920 $ctext conf -state disabled
3920 $ctext conf -state disabled
3921 $cflist delete 0 end
3921 $cflist delete 0 end
3922 }
3922 }
3923
3923
3924 proc doquit {} {
3924 proc doquit {} {
3925 global stopped
3925 global stopped
3926 set stopped 100
3926 set stopped 100
3927 destroy .
3927 destroy .
3928 }
3928 }
3929
3929
3930 proc getconfig {} {
3930 proc getconfig {} {
3931 global env
3931 global env
3932
3932
3933 set lines [exec $env(HG) debug-config]
3933 set lines [exec $env(HG) debug-config]
3934 regsub -all "\r\n" $lines "\n" config
3934 regsub -all "\r\n" $lines "\n" config
3935 set config {}
3935 set config {}
3936 foreach line [split $lines "\n"] {
3936 foreach line [split $lines "\n"] {
3937 regsub "^(k|v)=" $line "" line
3937 regsub "^(k|v)=" $line "" line
3938 lappend config $line
3938 lappend config $line
3939 }
3939 }
3940 return $config
3940 return $config
3941 }
3941 }
3942
3942
3943 # defaults...
3943 # defaults...
3944 set datemode 0
3944 set datemode 0
3945 set boldnames 0
3945 set boldnames 0
3946 set diffopts "-U 5 -p"
3946 set diffopts "-U 5 -p"
3947 set wrcomcmd "\"\$HG\" --config ui.report_untrusted=false debug-diff-tree --stdin -p --pretty"
3947 set wrcomcmd "\"\$HG\" --config ui.report_untrusted=false debug-diff-tree --stdin -p --pretty"
3948
3948
3949 set mainfont {Helvetica 9}
3949 set mainfont {Helvetica 9}
3950 set curidfont {}
3950 set curidfont {}
3951 set textfont {Courier 9}
3951 set textfont {Courier 9}
3952 set findmergefiles 0
3952 set findmergefiles 0
3953 set gaudydiff 0
3953 set gaudydiff 0
3954 set maxgraphpct 50
3954 set maxgraphpct 50
3955 set maxwidth 16
3955 set maxwidth 16
3956
3956
3957 set colors {green red blue magenta darkgrey brown orange}
3957 set colors {green red blue magenta darkgrey brown orange}
3958 set authorcolors {
3958 set authorcolors {
3959 black blue deeppink mediumorchid blue burlywood4 goldenrod slateblue red2 navy dimgrey
3959 black blue deeppink mediumorchid blue burlywood4 goldenrod slateblue red2 navy dimgrey
3960 }
3960 }
3961 set bgcolor white
3961 set bgcolor white
3962
3962
3963 # This color should probably be some system color (provided by tk),
3963 # This color should probably be some system color (provided by tk),
3964 # but as the bgcolor has always been set to white, I choose to ignore
3964 # but as the bgcolor has always been set to white, I choose to ignore
3965 set fgcolor black
3965 set fgcolor black
3966 set diffaddcolor "#00a000"
3966 set diffaddcolor "#00a000"
3967 set diffremcolor red
3967 set diffremcolor red
3968 set diffmerge1color red
3968 set diffmerge1color red
3969 set diffmerge2color blue
3969 set diffmerge2color blue
3970 set hunksepcolor blue
3970 set hunksepcolor blue
3971
3971
3972 catch {source ~/.hgk}
3972 catch {source ~/.hgk}
3973
3973
3974 if {$curidfont == ""} { # initialize late based on current mainfont
3974 if {$curidfont == ""} { # initialize late based on current mainfont
3975 set curidfont "$mainfont bold italic underline"
3975 set curidfont "$mainfont bold italic underline"
3976 }
3976 }
3977
3977
3978 set namefont $mainfont
3978 set namefont $mainfont
3979 if {$boldnames} {
3979 if {$boldnames} {
3980 lappend namefont bold
3980 lappend namefont bold
3981 }
3981 }
3982
3982
3983 set revtreeargs {}
3983 set revtreeargs {}
3984 foreach arg $argv {
3984 foreach arg $argv {
3985 switch -regexp -- $arg {
3985 switch -regexp -- $arg {
3986 "^$" { }
3986 "^$" { }
3987 "^-b" { set boldnames 1 }
3987 "^-b" { set boldnames 1 }
3988 "^-d" { set datemode 1 }
3988 "^-d" { set datemode 1 }
3989 default {
3989 default {
3990 lappend revtreeargs $arg
3990 lappend revtreeargs $arg
3991 }
3991 }
3992 }
3992 }
3993 }
3993 }
3994
3994
3995 set history {}
3995 set history {}
3996 set historyindex 0
3996 set historyindex 0
3997
3997
3998 set stopped 0
3998 set stopped 0
3999 set redisplaying 0
3999 set redisplaying 0
4000 set stuffsaved 0
4000 set stuffsaved 0
4001 set patchnum 0
4001 set patchnum 0
4002
4002
4003 array set config [getconfig]
4003 array set config [getconfig]
4004 set hgvdiff $config(vdiff)
4004 set hgvdiff $config(vdiff)
4005 setcoords
4005 setcoords
4006 makewindow
4006 makewindow
4007 readrefs
4007 readrefs
4008 set hgroot [exec $env(HG) root]
4008 set hgroot [exec $env(HG) root]
4009 wm title . "hgk $hgroot"
4009 wm title . "hgk $hgroot"
4010 getcommits $revtreeargs
4010 getcommits $revtreeargs
@@ -1,40 +1,31 b''
1 <?xml version="1.0" encoding="utf-8"?>
1 <?xml version="1.0" encoding="utf-8"?>
2 <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
2 <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
3
3
4 <?include guids.wxi ?>
4 <?include guids.wxi ?>
5 <?include defines.wxi ?>
5 <?include defines.wxi ?>
6
6
7 <Fragment>
7 <Fragment>
8 <DirectoryRef Id="INSTALLDIR" FileSource="$(var.SourceDir)">
8 <DirectoryRef Id="INSTALLDIR" FileSource="$(var.SourceDir)">
9 <Component Id="distOutput" Guid="$(var.dist.guid)" Win64='$(var.IsX64)'>
9 <Component Id="distOutput" Guid="$(var.dist.guid)" Win64='$(var.IsX64)'>
10 <File Name="library.zip" KeyPath="yes" />
10 <File Name="library.zip" KeyPath="yes" />
11 <File Name="mercurial.base85.pyd" />
11 <File Name="mercurial.base85.pyd" />
12 <File Name="mercurial.bdiff.pyd" />
12 <File Name="mercurial.bdiff.pyd" />
13 <File Name="mercurial.diffhelpers.pyd" />
13 <File Name="mercurial.diffhelpers.pyd" />
14 <File Name="mercurial.mpatch.pyd" />
14 <File Name="mercurial.mpatch.pyd" />
15 <File Name="mercurial.osutil.pyd" />
15 <File Name="mercurial.osutil.pyd" />
16 <File Name="mercurial.parsers.pyd" />
16 <File Name="mercurial.parsers.pyd" />
17 <File Name="pyexpat.pyd" />
17 <File Name="pyexpat.pyd" />
18 <File Name="python26.dll" />
18 <File Name="python26.dll" />
19 <File Name="pythoncom26.dll" />
20 <File Name="pywintypes26.dll" />
21 <File Name="bz2.pyd" />
19 <File Name="bz2.pyd" />
22 <File Name="select.pyd" />
20 <File Name="select.pyd" />
23 <File Name="unicodedata.pyd" />
21 <File Name="unicodedata.pyd" />
24 <File Name="win32api.pyd" />
22 <File Name="_ctypes.pyd" />
25 <File Name="win32com.shell.shell.pyd" />
26 <File Name="win32console.pyd" />
27 <File Name="win32file.pyd" />
28 <File Name="win32gui.pyd" />
29 <File Name="win32pipe.pyd" />
30 <File Name="win32process.pyd" />
31 <File Name="_elementtree.pyd" />
23 <File Name="_elementtree.pyd" />
32 <File Name="_hashlib.pyd" />
24 <File Name="_hashlib.pyd" />
33 <File Name="_socket.pyd" />
25 <File Name="_socket.pyd" />
34 <File Name="_ssl.pyd" />
26 <File Name="_ssl.pyd" />
35 <File Name="_win32sysloader.pyd" />
36 </Component>
27 </Component>
37 </DirectoryRef>
28 </DirectoryRef>
38 </Fragment>
29 </Fragment>
39
30
40 </Wix>
31 </Wix>
@@ -1,52 +1,52 b''
1 <Include>
1 <Include>
2 <!-- These are component GUIDs used for Mercurial installers.
2 <!-- These are component GUIDs used for Mercurial installers.
3 YOU MUST CHANGE ALL GUIDs below when copying this file
3 YOU MUST CHANGE ALL GUIDs below when copying this file
4 and replace 'Mercurial' in this notice with the name of
4 and replace 'Mercurial' in this notice with the name of
5 your project. Component GUIDs have global namespace! -->
5 your project. Component GUIDs have global namespace! -->
6
6
7 <!-- contrib.wxs -->
7 <!-- contrib.wxs -->
8 <?define contrib.guid = {F17D27B7-4A6B-4cd2-AE72-FED3CFAA585E} ?>
8 <?define contrib.guid = {F17D27B7-4A6B-4cd2-AE72-FED3CFAA585E} ?>
9 <?define contrib.vim.guid = {BB04903A-652D-4C4F-9590-2BD07A2304F2} ?>
9 <?define contrib.vim.guid = {BB04903A-652D-4C4F-9590-2BD07A2304F2} ?>
10
10
11 <!-- dist.wxs -->
11 <!-- dist.wxs -->
12 <?define dist.guid = {0F63D160-0740-4BAF-BF25-0C6930310F51} ?>
12 <?define dist.guid = {C3B634A4-1B05-4A40-94A9-38EE853CF693} ?>
13
13
14 <!-- doc.wxs -->
14 <!-- doc.wxs -->
15 <?define doc.hg.1.html.guid = {AAAA3FDA-EDC5-4220-B59D-D342722358A2} ?>
15 <?define doc.hg.1.html.guid = {AAAA3FDA-EDC5-4220-B59D-D342722358A2} ?>
16 <?define doc.hgignore.5.html.guid = {AA9118C4-F3A0-4429-A5F4-5A1906B2D67F} ?>
16 <?define doc.hgignore.5.html.guid = {AA9118C4-F3A0-4429-A5F4-5A1906B2D67F} ?>
17 <?define doc.hgrc.5.html = {E0CEA1EB-FA01-408c-844B-EE5965165BAE} ?>
17 <?define doc.hgrc.5.html = {E0CEA1EB-FA01-408c-844B-EE5965165BAE} ?>
18 <?define doc.style.css = {172F8262-98E0-4711-BD39-4DAE0D77EF05} ?>
18 <?define doc.style.css = {172F8262-98E0-4711-BD39-4DAE0D77EF05} ?>
19
19
20 <!-- help.wxs -->
20 <!-- help.wxs -->
21 <?define helpFolder.guid = {21FE9CF9-933E-4C2E-B2EC-413A569FB996} ?>
21 <?define helpFolder.guid = {21FE9CF9-933E-4C2E-B2EC-413A569FB996} ?>
22
22
23 <!-- i18n.wxs -->
23 <!-- i18n.wxs -->
24 <?define i18nFolder.guid = {EADFA693-A0B5-4f31-87C9-3997CFAC1B42} ?>
24 <?define i18nFolder.guid = {EADFA693-A0B5-4f31-87C9-3997CFAC1B42} ?>
25
25
26 <!-- templates.wxs -->
26 <!-- templates.wxs -->
27 <?define templates.root.guid = {111509CB-4C96-4035-80BC-F66A99CD5ACB} ?>
27 <?define templates.root.guid = {111509CB-4C96-4035-80BC-F66A99CD5ACB} ?>
28 <?define templates.atom.guid = {45FCDF84-DE27-44f4-AF6C-C41F5994AE0D} ?>
28 <?define templates.atom.guid = {45FCDF84-DE27-44f4-AF6C-C41F5994AE0D} ?>
29 <?define templates.coal.guid = {B63CCAAB-4EAF-43b4-901E-4BD13F5B78FC} ?>
29 <?define templates.coal.guid = {B63CCAAB-4EAF-43b4-901E-4BD13F5B78FC} ?>
30 <?define templates.gitweb.guid = {D8BFE3ED-06DD-4C4D-A00D-6D825955F922} ?>
30 <?define templates.gitweb.guid = {D8BFE3ED-06DD-4C4D-A00D-6D825955F922} ?>
31 <?define templates.monoblue.guid = {A394B4D5-2AF7-4AAC-AEA8-E92176E5501E} ?>
31 <?define templates.monoblue.guid = {A394B4D5-2AF7-4AAC-AEA8-E92176E5501E} ?>
32 <?define templates.paper.guid = {7C94B80D-FD0D-44E7-8489-F30A9E20A47F} ?>
32 <?define templates.paper.guid = {7C94B80D-FD0D-44E7-8489-F30A9E20A47F} ?>
33 <?define templates.raw.guid = {04DE03A2-FBFD-4c5f-8DEA-5436DDF4689D} ?>
33 <?define templates.raw.guid = {04DE03A2-FBFD-4c5f-8DEA-5436DDF4689D} ?>
34 <?define templates.rss.guid = {A7D608DE-0CF6-44f4-AF1E-EE30CC237FDA} ?>
34 <?define templates.rss.guid = {A7D608DE-0CF6-44f4-AF1E-EE30CC237FDA} ?>
35 <?define templates.spartan.guid = {80222625-FA8F-44b1-86CE-1781EF375D09} ?>
35 <?define templates.spartan.guid = {80222625-FA8F-44b1-86CE-1781EF375D09} ?>
36 <?define templates.static.guid = {68C9F843-DE7E-480f-9DA2-D220B19D02C3} ?>
36 <?define templates.static.guid = {68C9F843-DE7E-480f-9DA2-D220B19D02C3} ?>
37
37
38 <!-- mercurial.wxs -->
38 <!-- mercurial.wxs -->
39 <?define ProductUpgradeCode = {A1CC6134-E945-4399-BE36-EB0017FDF7CF} ?>
39 <?define ProductUpgradeCode = {A1CC6134-E945-4399-BE36-EB0017FDF7CF} ?>
40
40
41 <?define ComponentMainExecutableGUID = {D102B8FA-059B-4ACC-9FA3-8C78C3B58EEF} ?>
41 <?define ComponentMainExecutableGUID = {D102B8FA-059B-4ACC-9FA3-8C78C3B58EEF} ?>
42
42
43 <?define ReadMe.guid = {56A8E372-991D-4DCA-B91D-93D775974CF5} ?>
43 <?define ReadMe.guid = {56A8E372-991D-4DCA-B91D-93D775974CF5} ?>
44 <?define COPYING.guid = {B7801DBA-1C49-4BF4-91AD-33C65F5C7895} ?>
44 <?define COPYING.guid = {B7801DBA-1C49-4BF4-91AD-33C65F5C7895} ?>
45 <?define mercurial.rc.guid = {1D5FAEEE-7E6E-43B1-9F7F-802714316B15} ?>
45 <?define mercurial.rc.guid = {1D5FAEEE-7E6E-43B1-9F7F-802714316B15} ?>
46 <?define mergetools.rc.guid = {E8A1DC29-FF40-4B5F-BD12-80B9F7BF0CCD} ?>
46 <?define mergetools.rc.guid = {E8A1DC29-FF40-4B5F-BD12-80B9F7BF0CCD} ?>
47 <?define paths.rc.guid = {F9ADF21D-5F0B-4934-8CD9-14BE63664721} ?>
47 <?define paths.rc.guid = {F9ADF21D-5F0B-4934-8CD9-14BE63664721} ?>
48 <?define cacert.pem.guid = {EC1B2630-FE21-46E6-915B-A6545AF703D4} ?>
48 <?define cacert.pem.guid = {EC1B2630-FE21-46E6-915B-A6545AF703D4} ?>
49 <?define ProgramMenuDir.guid = {D5A63320-1238-489B-B68B-CF053E9577CA} ?>
49 <?define ProgramMenuDir.guid = {D5A63320-1238-489B-B68B-CF053E9577CA} ?>
50 <?define hgcmd.guid = {65CCC756-E72E-4C5F-901E-D575EDC80DB3} ?>
50 <?define hgcmd.guid = {65CCC756-E72E-4C5F-901E-D575EDC80DB3} ?>
51
51
52 </Include>
52 </Include>
@@ -1,166 +1,166 b''
1 # Mercurial bookmark support code
1 # Mercurial bookmark support code
2 #
2 #
3 # Copyright 2008 David Soria Parra <dsp@php.net>
3 # Copyright 2008 David Soria Parra <dsp@php.net>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from mercurial.i18n import _
8 from mercurial.i18n import _
9 from mercurial.node import nullid, nullrev, bin, hex, short
9 from mercurial.node import nullid, nullrev, bin, hex, short
10 from mercurial import encoding
10 from mercurial import encoding
11 import os
11 import os
12
12
13 def read(repo):
13 def read(repo):
14 '''Parse .hg/bookmarks file and return a dictionary
14 '''Parse .hg/bookmarks file and return a dictionary
15
15
16 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
16 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
17 in the .hg/bookmarks file.
17 in the .hg/bookmarks file.
18 Read the file and return a (name=>nodeid) dictionary
18 Read the file and return a (name=>nodeid) dictionary
19 '''
19 '''
20 try:
20 try:
21 bookmarks = {}
21 bookmarks = {}
22 for line in repo.opener('bookmarks'):
22 for line in repo.opener('bookmarks'):
23 sha, refspec = line.strip().split(' ', 1)
23 sha, refspec = line.strip().split(' ', 1)
24 refspec = encoding.tolocal(refspec)
24 refspec = encoding.tolocal(refspec)
25 bookmarks[refspec] = repo.changelog.lookup(sha)
25 bookmarks[refspec] = repo.changelog.lookup(sha)
26 except:
26 except:
27 pass
27 pass
28 return bookmarks
28 return bookmarks
29
29
30 def readcurrent(repo):
30 def readcurrent(repo):
31 '''Get the current bookmark
31 '''Get the current bookmark
32
32
33 If we use gittishsh branches we have a current bookmark that
33 If we use gittishsh branches we have a current bookmark that
34 we are on. This function returns the name of the bookmark. It
34 we are on. This function returns the name of the bookmark. It
35 is stored in .hg/bookmarks.current
35 is stored in .hg/bookmarks.current
36 '''
36 '''
37 mark = None
37 mark = None
38 if os.path.exists(repo.join('bookmarks.current')):
38 if os.path.exists(repo.join('bookmarks.current')):
39 file = repo.opener('bookmarks.current')
39 file = repo.opener('bookmarks.current')
40 # No readline() in posixfile_nt, reading everything is cheap
40 # No readline() in posixfile_nt, reading everything is cheap
41 mark = (file.readlines() or [''])[0]
41 mark = encoding.tolocal((file.readlines() or [''])[0])
42 if mark == '':
42 if mark == '':
43 mark = None
43 mark = None
44 file.close()
44 file.close()
45 return mark
45 return mark
46
46
47 def write(repo):
47 def write(repo):
48 '''Write bookmarks
48 '''Write bookmarks
49
49
50 Write the given bookmark => hash dictionary to the .hg/bookmarks file
50 Write the given bookmark => hash dictionary to the .hg/bookmarks file
51 in a format equal to those of localtags.
51 in a format equal to those of localtags.
52
52
53 We also store a backup of the previous state in undo.bookmarks that
53 We also store a backup of the previous state in undo.bookmarks that
54 can be copied back on rollback.
54 can be copied back on rollback.
55 '''
55 '''
56 refs = repo._bookmarks
56 refs = repo._bookmarks
57
57
58 try:
58 try:
59 bms = repo.opener('bookmarks').read()
59 bms = repo.opener('bookmarks').read()
60 except IOError:
60 except IOError:
61 bms = ''
61 bms = ''
62 repo.opener('undo.bookmarks', 'w').write(bms)
62 repo.opener('undo.bookmarks', 'w').write(bms)
63
63
64 if repo._bookmarkcurrent not in refs:
64 if repo._bookmarkcurrent not in refs:
65 setcurrent(repo, None)
65 setcurrent(repo, None)
66 wlock = repo.wlock()
66 wlock = repo.wlock()
67 try:
67 try:
68 file = repo.opener('bookmarks', 'w', atomictemp=True)
68 file = repo.opener('bookmarks', 'w', atomictemp=True)
69 for refspec, node in refs.iteritems():
69 for refspec, node in refs.iteritems():
70 file.write("%s %s\n" % (hex(node), encoding.fromlocal(refspec)))
70 file.write("%s %s\n" % (hex(node), encoding.fromlocal(refspec)))
71 file.rename()
71 file.rename()
72
72
73 # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
73 # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
74 try:
74 try:
75 os.utime(repo.sjoin('00changelog.i'), None)
75 os.utime(repo.sjoin('00changelog.i'), None)
76 except OSError:
76 except OSError:
77 pass
77 pass
78
78
79 finally:
79 finally:
80 wlock.release()
80 wlock.release()
81
81
82 def setcurrent(repo, mark):
82 def setcurrent(repo, mark):
83 '''Set the name of the bookmark that we are currently on
83 '''Set the name of the bookmark that we are currently on
84
84
85 Set the name of the bookmark that we are on (hg update <bookmark>).
85 Set the name of the bookmark that we are on (hg update <bookmark>).
86 The name is recorded in .hg/bookmarks.current
86 The name is recorded in .hg/bookmarks.current
87 '''
87 '''
88 current = repo._bookmarkcurrent
88 current = repo._bookmarkcurrent
89 if current == mark:
89 if current == mark:
90 return
90 return
91
91
92 refs = repo._bookmarks
92 refs = repo._bookmarks
93
93
94 # do not update if we do update to a rev equal to the current bookmark
94 # do not update if we do update to a rev equal to the current bookmark
95 if (mark and mark not in refs and
95 if (mark and mark not in refs and
96 current and refs[current] == repo.changectx('.').node()):
96 current and refs[current] == repo.changectx('.').node()):
97 return
97 return
98 if mark not in refs:
98 if mark not in refs:
99 mark = ''
99 mark = ''
100 wlock = repo.wlock()
100 wlock = repo.wlock()
101 try:
101 try:
102 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
102 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
103 file.write(mark)
103 file.write(mark)
104 file.rename()
104 file.rename()
105 finally:
105 finally:
106 wlock.release()
106 wlock.release()
107 repo._bookmarkcurrent = mark
107 repo._bookmarkcurrent = mark
108
108
109 def update(repo, parents, node):
109 def update(repo, parents, node):
110 marks = repo._bookmarks
110 marks = repo._bookmarks
111 update = False
111 update = False
112 if repo.ui.configbool('bookmarks', 'track.current'):
112 if repo.ui.configbool('bookmarks', 'track.current'):
113 mark = repo._bookmarkcurrent
113 mark = repo._bookmarkcurrent
114 if mark and marks[mark] in parents:
114 if mark and marks[mark] in parents:
115 marks[mark] = node
115 marks[mark] = node
116 update = True
116 update = True
117 else:
117 else:
118 for mark, n in marks.items():
118 for mark, n in marks.items():
119 if n in parents:
119 if n in parents:
120 marks[mark] = node
120 marks[mark] = node
121 update = True
121 update = True
122 if update:
122 if update:
123 write(repo)
123 write(repo)
124
124
125 def listbookmarks(repo):
125 def listbookmarks(repo):
126 # We may try to list bookmarks on a repo type that does not
126 # We may try to list bookmarks on a repo type that does not
127 # support it (e.g., statichttprepository).
127 # support it (e.g., statichttprepository).
128 if not hasattr(repo, '_bookmarks'):
128 if not hasattr(repo, '_bookmarks'):
129 return {}
129 return {}
130
130
131 d = {}
131 d = {}
132 for k, v in repo._bookmarks.iteritems():
132 for k, v in repo._bookmarks.iteritems():
133 d[k] = hex(v)
133 d[k] = hex(v)
134 return d
134 return d
135
135
136 def pushbookmark(repo, key, old, new):
136 def pushbookmark(repo, key, old, new):
137 w = repo.wlock()
137 w = repo.wlock()
138 try:
138 try:
139 marks = repo._bookmarks
139 marks = repo._bookmarks
140 if hex(marks.get(key, '')) != old:
140 if hex(marks.get(key, '')) != old:
141 return False
141 return False
142 if new == '':
142 if new == '':
143 del marks[key]
143 del marks[key]
144 else:
144 else:
145 if new not in repo:
145 if new not in repo:
146 return False
146 return False
147 marks[key] = repo[new].node()
147 marks[key] = repo[new].node()
148 write(repo)
148 write(repo)
149 return True
149 return True
150 finally:
150 finally:
151 w.release()
151 w.release()
152
152
153 def diff(ui, repo, remote):
153 def diff(ui, repo, remote):
154 ui.status(_("searching for changed bookmarks\n"))
154 ui.status(_("searching for changed bookmarks\n"))
155
155
156 lmarks = repo.listkeys('bookmarks')
156 lmarks = repo.listkeys('bookmarks')
157 rmarks = remote.listkeys('bookmarks')
157 rmarks = remote.listkeys('bookmarks')
158
158
159 diff = sorted(set(rmarks) - set(lmarks))
159 diff = sorted(set(rmarks) - set(lmarks))
160 for k in diff:
160 for k in diff:
161 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
161 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
162
162
163 if len(diff) <= 0:
163 if len(diff) <= 0:
164 ui.status(_("no changed bookmarks found\n"))
164 ui.status(_("no changed bookmarks found\n"))
165 return 1
165 return 1
166 return 0
166 return 0
@@ -1,324 +1,319 b''
1 # bundlerepo.py - repository class for viewing uncompressed bundles
1 # bundlerepo.py - repository class for viewing uncompressed bundles
2 #
2 #
3 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
3 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """Repository class for viewing uncompressed bundles.
8 """Repository class for viewing uncompressed bundles.
9
9
10 This provides a read-only repository interface to bundles as if they
10 This provides a read-only repository interface to bundles as if they
11 were part of the actual repository.
11 were part of the actual repository.
12 """
12 """
13
13
14 from node import nullid
14 from node import nullid
15 from i18n import _
15 from i18n import _
16 import os, struct, tempfile, shutil
16 import os, struct, tempfile, shutil
17 import changegroup, util, mdiff, discovery
17 import changegroup, util, mdiff, discovery
18 import localrepo, changelog, manifest, filelog, revlog, error
18 import localrepo, changelog, manifest, filelog, revlog, error
19
19
20 class bundlerevlog(revlog.revlog):
20 class bundlerevlog(revlog.revlog):
21 def __init__(self, opener, indexfile, bundle,
21 def __init__(self, opener, indexfile, bundle,
22 linkmapper=None):
22 linkmapper=None):
23 # How it works:
23 # How it works:
24 # to retrieve a revision, we need to know the offset of
24 # to retrieve a revision, we need to know the offset of
25 # the revision in the bundle (an unbundle object).
25 # the revision in the bundle (an unbundle object).
26 #
26 #
27 # We store this offset in the index (start), to differentiate a
27 # We store this offset in the index (start), to differentiate a
28 # rev in the bundle and from a rev in the revlog, we check
28 # rev in the bundle and from a rev in the revlog, we check
29 # len(index[r]). If the tuple is bigger than 7, it is a bundle
29 # len(index[r]). If the tuple is bigger than 7, it is a bundle
30 # (it is bigger since we store the node to which the delta is)
30 # (it is bigger since we store the node to which the delta is)
31 #
31 #
32 revlog.revlog.__init__(self, opener, indexfile)
32 revlog.revlog.__init__(self, opener, indexfile)
33 self.bundle = bundle
33 self.bundle = bundle
34 self.basemap = {}
34 self.basemap = {}
35 def chunkpositer():
35 def chunkpositer():
36 while 1:
36 while 1:
37 chunk = bundle.chunk()
37 chunk = bundle.chunk()
38 if not chunk:
38 if not chunk:
39 break
39 break
40 pos = bundle.tell()
40 pos = bundle.tell()
41 yield chunk, pos - len(chunk)
41 yield chunk, pos - len(chunk)
42 n = len(self)
42 n = len(self)
43 prev = None
43 prev = None
44 for chunk, start in chunkpositer():
44 for chunk, start in chunkpositer():
45 size = len(chunk)
45 size = len(chunk)
46 if size < 80:
46 if size < 80:
47 raise util.Abort(_("invalid changegroup"))
47 raise util.Abort(_("invalid changegroup"))
48 start += 80
48 start += 80
49 size -= 80
49 size -= 80
50 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
50 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
51 if node in self.nodemap:
51 if node in self.nodemap:
52 prev = node
52 prev = node
53 continue
53 continue
54 for p in (p1, p2):
54 for p in (p1, p2):
55 if not p in self.nodemap:
55 if not p in self.nodemap:
56 raise error.LookupError(p, self.indexfile,
56 raise error.LookupError(p, self.indexfile,
57 _("unknown parent"))
57 _("unknown parent"))
58 if linkmapper is None:
58 if linkmapper is None:
59 link = n
59 link = n
60 else:
60 else:
61 link = linkmapper(cs)
61 link = linkmapper(cs)
62
62
63 if not prev:
63 if not prev:
64 prev = p1
64 prev = p1
65 # start, size, full unc. size, base (unused), link, p1, p2, node
65 # start, size, full unc. size, base (unused), link, p1, p2, node
66 e = (revlog.offset_type(start, 0), size, -1, -1, link,
66 e = (revlog.offset_type(start, 0), size, -1, -1, link,
67 self.rev(p1), self.rev(p2), node)
67 self.rev(p1), self.rev(p2), node)
68 self.basemap[n] = prev
68 self.basemap[n] = prev
69 self.index.insert(-1, e)
69 self.index.insert(-1, e)
70 self.nodemap[node] = n
70 self.nodemap[node] = n
71 prev = node
71 prev = node
72 n += 1
72 n += 1
73
73
74 def inbundle(self, rev):
74 def inbundle(self, rev):
75 """is rev from the bundle"""
75 """is rev from the bundle"""
76 if rev < 0:
76 if rev < 0:
77 return False
77 return False
78 return rev in self.basemap
78 return rev in self.basemap
79 def bundlebase(self, rev):
79 def bundlebase(self, rev):
80 return self.basemap[rev]
80 return self.basemap[rev]
81 def _chunk(self, rev):
81 def _chunk(self, rev):
82 # Warning: in case of bundle, the diff is against bundlebase,
82 # Warning: in case of bundle, the diff is against bundlebase,
83 # not against rev - 1
83 # not against rev - 1
84 # XXX: could use some caching
84 # XXX: could use some caching
85 if not self.inbundle(rev):
85 if not self.inbundle(rev):
86 return revlog.revlog._chunk(self, rev)
86 return revlog.revlog._chunk(self, rev)
87 self.bundle.seek(self.start(rev))
87 self.bundle.seek(self.start(rev))
88 return self.bundle.read(self.length(rev))
88 return self.bundle.read(self.length(rev))
89
89
90 def revdiff(self, rev1, rev2):
90 def revdiff(self, rev1, rev2):
91 """return or calculate a delta between two revisions"""
91 """return or calculate a delta between two revisions"""
92 if self.inbundle(rev1) and self.inbundle(rev2):
92 if self.inbundle(rev1) and self.inbundle(rev2):
93 # hot path for bundle
93 # hot path for bundle
94 revb = self.rev(self.bundlebase(rev2))
94 revb = self.rev(self.bundlebase(rev2))
95 if revb == rev1:
95 if revb == rev1:
96 return self._chunk(rev2)
96 return self._chunk(rev2)
97 elif not self.inbundle(rev1) and not self.inbundle(rev2):
97 elif not self.inbundle(rev1) and not self.inbundle(rev2):
98 return revlog.revlog.revdiff(self, rev1, rev2)
98 return revlog.revlog.revdiff(self, rev1, rev2)
99
99
100 return mdiff.textdiff(self.revision(self.node(rev1)),
100 return mdiff.textdiff(self.revision(self.node(rev1)),
101 self.revision(self.node(rev2)))
101 self.revision(self.node(rev2)))
102
102
103 def revision(self, node):
103 def revision(self, node):
104 """return an uncompressed revision of a given"""
104 """return an uncompressed revision of a given"""
105 if node == nullid:
105 if node == nullid:
106 return ""
106 return ""
107
107
108 text = None
108 text = None
109 chain = []
109 chain = []
110 iter_node = node
110 iter_node = node
111 rev = self.rev(iter_node)
111 rev = self.rev(iter_node)
112 # reconstruct the revision if it is from a changegroup
112 # reconstruct the revision if it is from a changegroup
113 while self.inbundle(rev):
113 while self.inbundle(rev):
114 if self._cache and self._cache[0] == iter_node:
114 if self._cache and self._cache[0] == iter_node:
115 text = self._cache[2]
115 text = self._cache[2]
116 break
116 break
117 chain.append(rev)
117 chain.append(rev)
118 iter_node = self.bundlebase(rev)
118 iter_node = self.bundlebase(rev)
119 rev = self.rev(iter_node)
119 rev = self.rev(iter_node)
120 if text is None:
120 if text is None:
121 text = revlog.revlog.revision(self, iter_node)
121 text = revlog.revlog.revision(self, iter_node)
122
122
123 while chain:
123 while chain:
124 delta = self._chunk(chain.pop())
124 delta = self._chunk(chain.pop())
125 text = mdiff.patches(text, [delta])
125 text = mdiff.patches(text, [delta])
126
126
127 p1, p2 = self.parents(node)
127 p1, p2 = self.parents(node)
128 if node != revlog.hash(text, p1, p2):
128 if node != revlog.hash(text, p1, p2):
129 raise error.RevlogError(_("integrity check failed on %s:%d")
129 raise error.RevlogError(_("integrity check failed on %s:%d")
130 % (self.datafile, self.rev(node)))
130 % (self.datafile, self.rev(node)))
131
131
132 self._cache = (node, self.rev(node), text)
132 self._cache = (node, self.rev(node), text)
133 return text
133 return text
134
134
135 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
135 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
136 raise NotImplementedError
136 raise NotImplementedError
137 def addgroup(self, revs, linkmapper, transaction):
137 def addgroup(self, revs, linkmapper, transaction):
138 raise NotImplementedError
138 raise NotImplementedError
139 def strip(self, rev, minlink):
139 def strip(self, rev, minlink):
140 raise NotImplementedError
140 raise NotImplementedError
141 def checksize(self):
141 def checksize(self):
142 raise NotImplementedError
142 raise NotImplementedError
143
143
144 class bundlechangelog(bundlerevlog, changelog.changelog):
144 class bundlechangelog(bundlerevlog, changelog.changelog):
145 def __init__(self, opener, bundle):
145 def __init__(self, opener, bundle):
146 changelog.changelog.__init__(self, opener)
146 changelog.changelog.__init__(self, opener)
147 bundlerevlog.__init__(self, opener, self.indexfile, bundle)
147 bundlerevlog.__init__(self, opener, self.indexfile, bundle)
148
148
149 class bundlemanifest(bundlerevlog, manifest.manifest):
149 class bundlemanifest(bundlerevlog, manifest.manifest):
150 def __init__(self, opener, bundle, linkmapper):
150 def __init__(self, opener, bundle, linkmapper):
151 manifest.manifest.__init__(self, opener)
151 manifest.manifest.__init__(self, opener)
152 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
152 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
153 linkmapper)
153 linkmapper)
154
154
155 class bundlefilelog(bundlerevlog, filelog.filelog):
155 class bundlefilelog(bundlerevlog, filelog.filelog):
156 def __init__(self, opener, path, bundle, linkmapper):
156 def __init__(self, opener, path, bundle, linkmapper):
157 filelog.filelog.__init__(self, opener, path)
157 filelog.filelog.__init__(self, opener, path)
158 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
158 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
159 linkmapper)
159 linkmapper)
160
160
161 class bundlerepository(localrepo.localrepository):
161 class bundlerepository(localrepo.localrepository):
162 def __init__(self, ui, path, bundlename):
162 def __init__(self, ui, path, bundlename):
163 self._tempparent = None
163 self._tempparent = None
164 try:
164 try:
165 localrepo.localrepository.__init__(self, ui, path)
165 localrepo.localrepository.__init__(self, ui, path)
166 except error.RepoError:
166 except error.RepoError:
167 self._tempparent = tempfile.mkdtemp()
167 self._tempparent = tempfile.mkdtemp()
168 localrepo.instance(ui, self._tempparent, 1)
168 localrepo.instance(ui, self._tempparent, 1)
169 localrepo.localrepository.__init__(self, ui, self._tempparent)
169 localrepo.localrepository.__init__(self, ui, self._tempparent)
170
170
171 if path:
171 if path:
172 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
172 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
173 else:
173 else:
174 self._url = 'bundle:' + bundlename
174 self._url = 'bundle:' + bundlename
175
175
176 self.tempfile = None
176 self.tempfile = None
177 f = util.posixfile(bundlename, "rb")
177 f = util.posixfile(bundlename, "rb")
178 self.bundle = changegroup.readbundle(f, bundlename)
178 self.bundle = changegroup.readbundle(f, bundlename)
179 if self.bundle.compressed():
179 if self.bundle.compressed():
180 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
180 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
181 suffix=".hg10un", dir=self.path)
181 suffix=".hg10un", dir=self.path)
182 self.tempfile = temp
182 self.tempfile = temp
183 fptemp = os.fdopen(fdtemp, 'wb')
183 fptemp = os.fdopen(fdtemp, 'wb')
184
184
185 try:
185 try:
186 fptemp.write("HG10UN")
186 fptemp.write("HG10UN")
187 while 1:
187 while 1:
188 chunk = self.bundle.read(2**18)
188 chunk = self.bundle.read(2**18)
189 if not chunk:
189 if not chunk:
190 break
190 break
191 fptemp.write(chunk)
191 fptemp.write(chunk)
192 finally:
192 finally:
193 fptemp.close()
193 fptemp.close()
194
194
195 f = util.posixfile(self.tempfile, "rb")
195 f = util.posixfile(self.tempfile, "rb")
196 self.bundle = changegroup.readbundle(f, bundlename)
196 self.bundle = changegroup.readbundle(f, bundlename)
197
197
198 # dict with the mapping 'filename' -> position in the bundle
198 # dict with the mapping 'filename' -> position in the bundle
199 self.bundlefilespos = {}
199 self.bundlefilespos = {}
200
200
201 @util.propertycache
201 @util.propertycache
202 def changelog(self):
202 def changelog(self):
203 c = bundlechangelog(self.sopener, self.bundle)
203 c = bundlechangelog(self.sopener, self.bundle)
204 self.manstart = self.bundle.tell()
204 self.manstart = self.bundle.tell()
205 return c
205 return c
206
206
207 @util.propertycache
207 @util.propertycache
208 def manifest(self):
208 def manifest(self):
209 self.bundle.seek(self.manstart)
209 self.bundle.seek(self.manstart)
210 m = bundlemanifest(self.sopener, self.bundle, self.changelog.rev)
210 m = bundlemanifest(self.sopener, self.bundle, self.changelog.rev)
211 self.filestart = self.bundle.tell()
211 self.filestart = self.bundle.tell()
212 return m
212 return m
213
213
214 @util.propertycache
214 @util.propertycache
215 def manstart(self):
215 def manstart(self):
216 self.changelog
216 self.changelog
217 return self.manstart
217 return self.manstart
218
218
219 @util.propertycache
219 @util.propertycache
220 def filestart(self):
220 def filestart(self):
221 self.manifest
221 self.manifest
222 return self.filestart
222 return self.filestart
223
223
224 def url(self):
224 def url(self):
225 return self._url
225 return self._url
226
226
227 def file(self, f):
227 def file(self, f):
228 if not self.bundlefilespos:
228 if not self.bundlefilespos:
229 self.bundle.seek(self.filestart)
229 self.bundle.seek(self.filestart)
230 while 1:
230 while 1:
231 chunk = self.bundle.chunk()
231 chunk = self.bundle.chunk()
232 if not chunk:
232 if not chunk:
233 break
233 break
234 self.bundlefilespos[chunk] = self.bundle.tell()
234 self.bundlefilespos[chunk] = self.bundle.tell()
235 while 1:
235 while 1:
236 c = self.bundle.chunk()
236 c = self.bundle.chunk()
237 if not c:
237 if not c:
238 break
238 break
239
239
240 if f[0] == '/':
240 if f[0] == '/':
241 f = f[1:]
241 f = f[1:]
242 if f in self.bundlefilespos:
242 if f in self.bundlefilespos:
243 self.bundle.seek(self.bundlefilespos[f])
243 self.bundle.seek(self.bundlefilespos[f])
244 return bundlefilelog(self.sopener, f, self.bundle,
244 return bundlefilelog(self.sopener, f, self.bundle,
245 self.changelog.rev)
245 self.changelog.rev)
246 else:
246 else:
247 return filelog.filelog(self.sopener, f)
247 return filelog.filelog(self.sopener, f)
248
248
249 def close(self):
249 def close(self):
250 """Close assigned bundle file immediately."""
250 """Close assigned bundle file immediately."""
251 self.bundle.close()
251 self.bundle.close()
252 if self.tempfile is not None:
252 if self.tempfile is not None:
253 os.unlink(self.tempfile)
253 os.unlink(self.tempfile)
254
255 def __del__(self):
256 del self.bundle
257 if self.tempfile is not None:
258 os.unlink(self.tempfile)
259 if self._tempparent:
254 if self._tempparent:
260 shutil.rmtree(self._tempparent, True)
255 shutil.rmtree(self._tempparent, True)
261
256
262 def cancopy(self):
257 def cancopy(self):
263 return False
258 return False
264
259
265 def getcwd(self):
260 def getcwd(self):
266 return os.getcwd() # always outside the repo
261 return os.getcwd() # always outside the repo
267
262
268 def instance(ui, path, create):
263 def instance(ui, path, create):
269 if create:
264 if create:
270 raise util.Abort(_('cannot create new bundle repository'))
265 raise util.Abort(_('cannot create new bundle repository'))
271 parentpath = ui.config("bundle", "mainreporoot", "")
266 parentpath = ui.config("bundle", "mainreporoot", "")
272 if parentpath:
267 if parentpath:
273 # Try to make the full path relative so we get a nice, short URL.
268 # Try to make the full path relative so we get a nice, short URL.
274 # In particular, we don't want temp dir names in test outputs.
269 # In particular, we don't want temp dir names in test outputs.
275 cwd = os.getcwd()
270 cwd = os.getcwd()
276 if parentpath == cwd:
271 if parentpath == cwd:
277 parentpath = ''
272 parentpath = ''
278 else:
273 else:
279 cwd = os.path.join(cwd,'')
274 cwd = os.path.join(cwd,'')
280 if parentpath.startswith(cwd):
275 if parentpath.startswith(cwd):
281 parentpath = parentpath[len(cwd):]
276 parentpath = parentpath[len(cwd):]
282 path = util.drop_scheme('file', path)
277 path = util.drop_scheme('file', path)
283 if path.startswith('bundle:'):
278 if path.startswith('bundle:'):
284 path = util.drop_scheme('bundle', path)
279 path = util.drop_scheme('bundle', path)
285 s = path.split("+", 1)
280 s = path.split("+", 1)
286 if len(s) == 1:
281 if len(s) == 1:
287 repopath, bundlename = parentpath, s[0]
282 repopath, bundlename = parentpath, s[0]
288 else:
283 else:
289 repopath, bundlename = s
284 repopath, bundlename = s
290 else:
285 else:
291 repopath, bundlename = parentpath, path
286 repopath, bundlename = parentpath, path
292 return bundlerepository(ui, repopath, bundlename)
287 return bundlerepository(ui, repopath, bundlename)
293
288
294 def getremotechanges(ui, repo, other, revs=None, bundlename=None, force=False):
289 def getremotechanges(ui, repo, other, revs=None, bundlename=None, force=False):
295 tmp = discovery.findcommonincoming(repo, other, heads=revs, force=force)
290 tmp = discovery.findcommonincoming(repo, other, heads=revs, force=force)
296 common, incoming, rheads = tmp
291 common, incoming, rheads = tmp
297 if not incoming:
292 if not incoming:
298 try:
293 try:
299 os.unlink(bundlename)
294 os.unlink(bundlename)
300 except:
295 except:
301 pass
296 pass
302 return other, None, None
297 return other, None, None
303
298
304 bundle = None
299 bundle = None
305 if bundlename or not other.local():
300 if bundlename or not other.local():
306 # create a bundle (uncompressed if other repo is not local)
301 # create a bundle (uncompressed if other repo is not local)
307
302
308 if revs is None and other.capable('changegroupsubset'):
303 if revs is None and other.capable('changegroupsubset'):
309 revs = rheads
304 revs = rheads
310
305
311 if revs is None:
306 if revs is None:
312 cg = other.changegroup(incoming, "incoming")
307 cg = other.changegroup(incoming, "incoming")
313 else:
308 else:
314 cg = other.changegroupsubset(incoming, revs, 'incoming')
309 cg = other.changegroupsubset(incoming, revs, 'incoming')
315 bundletype = other.local() and "HG10BZ" or "HG10UN"
310 bundletype = other.local() and "HG10BZ" or "HG10UN"
316 fname = bundle = changegroup.writebundle(cg, bundlename, bundletype)
311 fname = bundle = changegroup.writebundle(cg, bundlename, bundletype)
317 # keep written bundle?
312 # keep written bundle?
318 if bundlename:
313 if bundlename:
319 bundle = None
314 bundle = None
320 if not other.local():
315 if not other.local():
321 # use the created uncompressed bundlerepo
316 # use the created uncompressed bundlerepo
322 other = bundlerepository(ui, repo.root, fname)
317 other = bundlerepository(ui, repo.root, fname)
323 return (other, incoming, bundle)
318 return (other, incoming, bundle)
324
319
@@ -1,1380 +1,1383 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
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 of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, errno, re, glob, tempfile
10 import os, sys, errno, re, glob, tempfile
11 import util, templater, patch, error, encoding, templatekw
11 import util, templater, patch, error, encoding, templatekw
12 import match as matchmod
12 import match as matchmod
13 import similar, revset, subrepo
13 import similar, revset, subrepo
14
14
15 revrangesep = ':'
15 revrangesep = ':'
16
16
17 def parsealiases(cmd):
17 def parsealiases(cmd):
18 return cmd.lstrip("^").split("|")
18 return cmd.lstrip("^").split("|")
19
19
20 def findpossible(cmd, table, strict=False):
20 def findpossible(cmd, table, strict=False):
21 """
21 """
22 Return cmd -> (aliases, command table entry)
22 Return cmd -> (aliases, command table entry)
23 for each matching command.
23 for each matching command.
24 Return debug commands (or their aliases) only if no normal command matches.
24 Return debug commands (or their aliases) only if no normal command matches.
25 """
25 """
26 choice = {}
26 choice = {}
27 debugchoice = {}
27 debugchoice = {}
28 for e in table.keys():
28 for e in table.keys():
29 aliases = parsealiases(e)
29 aliases = parsealiases(e)
30 found = None
30 found = None
31 if cmd in aliases:
31 if cmd in aliases:
32 found = cmd
32 found = cmd
33 elif not strict:
33 elif not strict:
34 for a in aliases:
34 for a in aliases:
35 if a.startswith(cmd):
35 if a.startswith(cmd):
36 found = a
36 found = a
37 break
37 break
38 if found is not None:
38 if found is not None:
39 if aliases[0].startswith("debug") or found.startswith("debug"):
39 if aliases[0].startswith("debug") or found.startswith("debug"):
40 debugchoice[found] = (aliases, table[e])
40 debugchoice[found] = (aliases, table[e])
41 else:
41 else:
42 choice[found] = (aliases, table[e])
42 choice[found] = (aliases, table[e])
43
43
44 if not choice and debugchoice:
44 if not choice and debugchoice:
45 choice = debugchoice
45 choice = debugchoice
46
46
47 return choice
47 return choice
48
48
49 def findcmd(cmd, table, strict=True):
49 def findcmd(cmd, table, strict=True):
50 """Return (aliases, command table entry) for command string."""
50 """Return (aliases, command table entry) for command string."""
51 choice = findpossible(cmd, table, strict)
51 choice = findpossible(cmd, table, strict)
52
52
53 if cmd in choice:
53 if cmd in choice:
54 return choice[cmd]
54 return choice[cmd]
55
55
56 if len(choice) > 1:
56 if len(choice) > 1:
57 clist = choice.keys()
57 clist = choice.keys()
58 clist.sort()
58 clist.sort()
59 raise error.AmbiguousCommand(cmd, clist)
59 raise error.AmbiguousCommand(cmd, clist)
60
60
61 if choice:
61 if choice:
62 return choice.values()[0]
62 return choice.values()[0]
63
63
64 raise error.UnknownCommand(cmd)
64 raise error.UnknownCommand(cmd)
65
65
66 def findrepo(p):
66 def findrepo(p):
67 while not os.path.isdir(os.path.join(p, ".hg")):
67 while not os.path.isdir(os.path.join(p, ".hg")):
68 oldp, p = p, os.path.dirname(p)
68 oldp, p = p, os.path.dirname(p)
69 if p == oldp:
69 if p == oldp:
70 return None
70 return None
71
71
72 return p
72 return p
73
73
74 def bail_if_changed(repo):
74 def bail_if_changed(repo):
75 if repo.dirstate.parents()[1] != nullid:
75 if repo.dirstate.parents()[1] != nullid:
76 raise util.Abort(_('outstanding uncommitted merge'))
76 raise util.Abort(_('outstanding uncommitted merge'))
77 modified, added, removed, deleted = repo.status()[:4]
77 modified, added, removed, deleted = repo.status()[:4]
78 if modified or added or removed or deleted:
78 if modified or added or removed or deleted:
79 raise util.Abort(_("outstanding uncommitted changes"))
79 raise util.Abort(_("outstanding uncommitted changes"))
80
80
81 def logmessage(opts):
81 def logmessage(opts):
82 """ get the log message according to -m and -l option """
82 """ get the log message according to -m and -l option """
83 message = opts.get('message')
83 message = opts.get('message')
84 logfile = opts.get('logfile')
84 logfile = opts.get('logfile')
85
85
86 if message and logfile:
86 if message and logfile:
87 raise util.Abort(_('options --message and --logfile are mutually '
87 raise util.Abort(_('options --message and --logfile are mutually '
88 'exclusive'))
88 'exclusive'))
89 if not message and logfile:
89 if not message and logfile:
90 try:
90 try:
91 if logfile == '-':
91 if logfile == '-':
92 message = sys.stdin.read()
92 message = sys.stdin.read()
93 else:
93 else:
94 message = open(logfile).read()
94 message = open(logfile).read()
95 except IOError, inst:
95 except IOError, inst:
96 raise util.Abort(_("can't read commit message '%s': %s") %
96 raise util.Abort(_("can't read commit message '%s': %s") %
97 (logfile, inst.strerror))
97 (logfile, inst.strerror))
98 return message
98 return message
99
99
100 def loglimit(opts):
100 def loglimit(opts):
101 """get the log limit according to option -l/--limit"""
101 """get the log limit according to option -l/--limit"""
102 limit = opts.get('limit')
102 limit = opts.get('limit')
103 if limit:
103 if limit:
104 try:
104 try:
105 limit = int(limit)
105 limit = int(limit)
106 except ValueError:
106 except ValueError:
107 raise util.Abort(_('limit must be a positive integer'))
107 raise util.Abort(_('limit must be a positive integer'))
108 if limit <= 0:
108 if limit <= 0:
109 raise util.Abort(_('limit must be positive'))
109 raise util.Abort(_('limit must be positive'))
110 else:
110 else:
111 limit = None
111 limit = None
112 return limit
112 return limit
113
113
114 def revsingle(repo, revspec, default='.'):
114 def revsingle(repo, revspec, default='.'):
115 if not revspec:
115 if not revspec:
116 return repo[default]
116 return repo[default]
117
117
118 l = revrange(repo, [revspec])
118 l = revrange(repo, [revspec])
119 if len(l) < 1:
119 if len(l) < 1:
120 raise util.Abort(_('empty revision set'))
120 raise util.Abort(_('empty revision set'))
121 return repo[l[-1]]
121 return repo[l[-1]]
122
122
123 def revpair(repo, revs):
123 def revpair(repo, revs):
124 if not revs:
124 if not revs:
125 return repo.dirstate.parents()[0], None
125 return repo.dirstate.parents()[0], None
126
126
127 l = revrange(repo, revs)
127 l = revrange(repo, revs)
128
128
129 if len(l) == 0:
129 if len(l) == 0:
130 return repo.dirstate.parents()[0], None
130 return repo.dirstate.parents()[0], None
131
131
132 if len(l) == 1:
132 if len(l) == 1:
133 return repo.lookup(l[0]), None
133 return repo.lookup(l[0]), None
134
134
135 return repo.lookup(l[0]), repo.lookup(l[-1])
135 return repo.lookup(l[0]), repo.lookup(l[-1])
136
136
137 def revrange(repo, revs):
137 def revrange(repo, revs):
138 """Yield revision as strings from a list of revision specifications."""
138 """Yield revision as strings from a list of revision specifications."""
139
139
140 def revfix(repo, val, defval):
140 def revfix(repo, val, defval):
141 if not val and val != 0 and defval is not None:
141 if not val and val != 0 and defval is not None:
142 return defval
142 return defval
143 return repo.changelog.rev(repo.lookup(val))
143 return repo.changelog.rev(repo.lookup(val))
144
144
145 seen, l = set(), []
145 seen, l = set(), []
146 for spec in revs:
146 for spec in revs:
147 # attempt to parse old-style ranges first to deal with
147 # attempt to parse old-style ranges first to deal with
148 # things like old-tag which contain query metacharacters
148 # things like old-tag which contain query metacharacters
149 try:
149 try:
150 if isinstance(spec, int):
150 if isinstance(spec, int):
151 seen.add(spec)
151 seen.add(spec)
152 l.append(spec)
152 l.append(spec)
153 continue
153 continue
154
154
155 if revrangesep in spec:
155 if revrangesep in spec:
156 start, end = spec.split(revrangesep, 1)
156 start, end = spec.split(revrangesep, 1)
157 start = revfix(repo, start, 0)
157 start = revfix(repo, start, 0)
158 end = revfix(repo, end, len(repo) - 1)
158 end = revfix(repo, end, len(repo) - 1)
159 step = start > end and -1 or 1
159 step = start > end and -1 or 1
160 for rev in xrange(start, end + step, step):
160 for rev in xrange(start, end + step, step):
161 if rev in seen:
161 if rev in seen:
162 continue
162 continue
163 seen.add(rev)
163 seen.add(rev)
164 l.append(rev)
164 l.append(rev)
165 continue
165 continue
166 elif spec and spec in repo: # single unquoted rev
166 elif spec and spec in repo: # single unquoted rev
167 rev = revfix(repo, spec, None)
167 rev = revfix(repo, spec, None)
168 if rev in seen:
168 if rev in seen:
169 continue
169 continue
170 seen.add(rev)
170 seen.add(rev)
171 l.append(rev)
171 l.append(rev)
172 continue
172 continue
173 except error.RepoLookupError:
173 except error.RepoLookupError:
174 pass
174 pass
175
175
176 # fall through to new-style queries if old-style fails
176 # fall through to new-style queries if old-style fails
177 m = revset.match(spec)
177 m = revset.match(spec)
178 for r in m(repo, range(len(repo))):
178 for r in m(repo, range(len(repo))):
179 if r not in seen:
179 if r not in seen:
180 l.append(r)
180 l.append(r)
181 seen.update(l)
181 seen.update(l)
182
182
183 return l
183 return l
184
184
185 def make_filename(repo, pat, node,
185 def make_filename(repo, pat, node,
186 total=None, seqno=None, revwidth=None, pathname=None):
186 total=None, seqno=None, revwidth=None, pathname=None):
187 node_expander = {
187 node_expander = {
188 'H': lambda: hex(node),
188 'H': lambda: hex(node),
189 'R': lambda: str(repo.changelog.rev(node)),
189 'R': lambda: str(repo.changelog.rev(node)),
190 'h': lambda: short(node),
190 'h': lambda: short(node),
191 }
191 }
192 expander = {
192 expander = {
193 '%': lambda: '%',
193 '%': lambda: '%',
194 'b': lambda: os.path.basename(repo.root),
194 'b': lambda: os.path.basename(repo.root),
195 }
195 }
196
196
197 try:
197 try:
198 if node:
198 if node:
199 expander.update(node_expander)
199 expander.update(node_expander)
200 if node:
200 if node:
201 expander['r'] = (lambda:
201 expander['r'] = (lambda:
202 str(repo.changelog.rev(node)).zfill(revwidth or 0))
202 str(repo.changelog.rev(node)).zfill(revwidth or 0))
203 if total is not None:
203 if total is not None:
204 expander['N'] = lambda: str(total)
204 expander['N'] = lambda: str(total)
205 if seqno is not None:
205 if seqno is not None:
206 expander['n'] = lambda: str(seqno)
206 expander['n'] = lambda: str(seqno)
207 if total is not None and seqno is not None:
207 if total is not None and seqno is not None:
208 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
208 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
209 if pathname is not None:
209 if pathname is not None:
210 expander['s'] = lambda: os.path.basename(pathname)
210 expander['s'] = lambda: os.path.basename(pathname)
211 expander['d'] = lambda: os.path.dirname(pathname) or '.'
211 expander['d'] = lambda: os.path.dirname(pathname) or '.'
212 expander['p'] = lambda: pathname
212 expander['p'] = lambda: pathname
213
213
214 newname = []
214 newname = []
215 patlen = len(pat)
215 patlen = len(pat)
216 i = 0
216 i = 0
217 while i < patlen:
217 while i < patlen:
218 c = pat[i]
218 c = pat[i]
219 if c == '%':
219 if c == '%':
220 i += 1
220 i += 1
221 c = pat[i]
221 c = pat[i]
222 c = expander[c]()
222 c = expander[c]()
223 newname.append(c)
223 newname.append(c)
224 i += 1
224 i += 1
225 return ''.join(newname)
225 return ''.join(newname)
226 except KeyError, inst:
226 except KeyError, inst:
227 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
227 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
228 inst.args[0])
228 inst.args[0])
229
229
230 def make_file(repo, pat, node=None,
230 def make_file(repo, pat, node=None,
231 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
231 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
232
232
233 writable = 'w' in mode or 'a' in mode
233 writable = 'w' in mode or 'a' in mode
234
234
235 if not pat or pat == '-':
235 if not pat or pat == '-':
236 fp = writable and sys.stdout or sys.stdin
236 fp = writable and sys.stdout or sys.stdin
237 return os.fdopen(os.dup(fp.fileno()), mode)
237 return os.fdopen(os.dup(fp.fileno()), mode)
238 if hasattr(pat, 'write') and writable:
238 if hasattr(pat, 'write') and writable:
239 return pat
239 return pat
240 if hasattr(pat, 'read') and 'r' in mode:
240 if hasattr(pat, 'read') and 'r' in mode:
241 return pat
241 return pat
242 return open(make_filename(repo, pat, node, total, seqno, revwidth,
242 return open(make_filename(repo, pat, node, total, seqno, revwidth,
243 pathname),
243 pathname),
244 mode)
244 mode)
245
245
246 def expandpats(pats):
246 def expandpats(pats):
247 if not util.expandglobs:
247 if not util.expandglobs:
248 return list(pats)
248 return list(pats)
249 ret = []
249 ret = []
250 for p in pats:
250 for p in pats:
251 kind, name = matchmod._patsplit(p, None)
251 kind, name = matchmod._patsplit(p, None)
252 if kind is None:
252 if kind is None:
253 try:
253 try:
254 globbed = glob.glob(name)
254 globbed = glob.glob(name)
255 except re.error:
255 except re.error:
256 globbed = [name]
256 globbed = [name]
257 if globbed:
257 if globbed:
258 ret.extend(globbed)
258 ret.extend(globbed)
259 continue
259 continue
260 ret.append(p)
260 ret.append(p)
261 return ret
261 return ret
262
262
263 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
263 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
264 if not globbed and default == 'relpath':
264 if not globbed and default == 'relpath':
265 pats = expandpats(pats or [])
265 pats = expandpats(pats or [])
266 m = matchmod.match(repo.root, repo.getcwd(), pats,
266 m = matchmod.match(repo.root, repo.getcwd(), pats,
267 opts.get('include'), opts.get('exclude'), default,
267 opts.get('include'), opts.get('exclude'), default,
268 auditor=repo.auditor)
268 auditor=repo.auditor)
269 def badfn(f, msg):
269 def badfn(f, msg):
270 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
270 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
271 m.bad = badfn
271 m.bad = badfn
272 return m
272 return m
273
273
274 def matchall(repo):
274 def matchall(repo):
275 return matchmod.always(repo.root, repo.getcwd())
275 return matchmod.always(repo.root, repo.getcwd())
276
276
277 def matchfiles(repo, files):
277 def matchfiles(repo, files):
278 return matchmod.exact(repo.root, repo.getcwd(), files)
278 return matchmod.exact(repo.root, repo.getcwd(), files)
279
279
280 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
280 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
281 if dry_run is None:
281 if dry_run is None:
282 dry_run = opts.get('dry_run')
282 dry_run = opts.get('dry_run')
283 if similarity is None:
283 if similarity is None:
284 similarity = float(opts.get('similarity') or 0)
284 similarity = float(opts.get('similarity') or 0)
285 # we'd use status here, except handling of symlinks and ignore is tricky
285 # we'd use status here, except handling of symlinks and ignore is tricky
286 added, unknown, deleted, removed = [], [], [], []
286 added, unknown, deleted, removed = [], [], [], []
287 audit_path = util.path_auditor(repo.root)
287 audit_path = util.path_auditor(repo.root)
288 m = match(repo, pats, opts)
288 m = match(repo, pats, opts)
289 for abs in repo.walk(m):
289 for abs in repo.walk(m):
290 target = repo.wjoin(abs)
290 target = repo.wjoin(abs)
291 good = True
291 good = True
292 try:
292 try:
293 audit_path(abs)
293 audit_path(abs)
294 except:
294 except:
295 good = False
295 good = False
296 rel = m.rel(abs)
296 rel = m.rel(abs)
297 exact = m.exact(abs)
297 exact = m.exact(abs)
298 if good and abs not in repo.dirstate:
298 if good and abs not in repo.dirstate:
299 unknown.append(abs)
299 unknown.append(abs)
300 if repo.ui.verbose or not exact:
300 if repo.ui.verbose or not exact:
301 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
301 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
302 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
302 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
303 or (os.path.isdir(target) and not os.path.islink(target))):
303 or (os.path.isdir(target) and not os.path.islink(target))):
304 deleted.append(abs)
304 deleted.append(abs)
305 if repo.ui.verbose or not exact:
305 if repo.ui.verbose or not exact:
306 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
306 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
307 # for finding renames
307 # for finding renames
308 elif repo.dirstate[abs] == 'r':
308 elif repo.dirstate[abs] == 'r':
309 removed.append(abs)
309 removed.append(abs)
310 elif repo.dirstate[abs] == 'a':
310 elif repo.dirstate[abs] == 'a':
311 added.append(abs)
311 added.append(abs)
312 copies = {}
312 copies = {}
313 if similarity > 0:
313 if similarity > 0:
314 for old, new, score in similar.findrenames(repo,
314 for old, new, score in similar.findrenames(repo,
315 added + unknown, removed + deleted, similarity):
315 added + unknown, removed + deleted, similarity):
316 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
316 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
317 repo.ui.status(_('recording removal of %s as rename to %s '
317 repo.ui.status(_('recording removal of %s as rename to %s '
318 '(%d%% similar)\n') %
318 '(%d%% similar)\n') %
319 (m.rel(old), m.rel(new), score * 100))
319 (m.rel(old), m.rel(new), score * 100))
320 copies[new] = old
320 copies[new] = old
321
321
322 if not dry_run:
322 if not dry_run:
323 wctx = repo[None]
323 wctx = repo[None]
324 wlock = repo.wlock()
324 wlock = repo.wlock()
325 try:
325 try:
326 wctx.remove(deleted)
326 wctx.remove(deleted)
327 wctx.add(unknown)
327 wctx.add(unknown)
328 for new, old in copies.iteritems():
328 for new, old in copies.iteritems():
329 wctx.copy(old, new)
329 wctx.copy(old, new)
330 finally:
330 finally:
331 wlock.release()
331 wlock.release()
332
332
333 def updatedir(ui, repo, patches, similarity=0):
333 def updatedir(ui, repo, patches, similarity=0):
334 '''Update dirstate after patch application according to metadata'''
334 '''Update dirstate after patch application according to metadata'''
335 if not patches:
335 if not patches:
336 return
336 return
337 copies = []
337 copies = []
338 removes = set()
338 removes = set()
339 cfiles = patches.keys()
339 cfiles = patches.keys()
340 cwd = repo.getcwd()
340 cwd = repo.getcwd()
341 if cwd:
341 if cwd:
342 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
342 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
343 for f in patches:
343 for f in patches:
344 gp = patches[f]
344 gp = patches[f]
345 if not gp:
345 if not gp:
346 continue
346 continue
347 if gp.op == 'RENAME':
347 if gp.op == 'RENAME':
348 copies.append((gp.oldpath, gp.path))
348 copies.append((gp.oldpath, gp.path))
349 removes.add(gp.oldpath)
349 removes.add(gp.oldpath)
350 elif gp.op == 'COPY':
350 elif gp.op == 'COPY':
351 copies.append((gp.oldpath, gp.path))
351 copies.append((gp.oldpath, gp.path))
352 elif gp.op == 'DELETE':
352 elif gp.op == 'DELETE':
353 removes.add(gp.path)
353 removes.add(gp.path)
354
354
355 wctx = repo[None]
355 wctx = repo[None]
356 for src, dst in copies:
356 for src, dst in copies:
357 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
357 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
358 if (not similarity) and removes:
358 if (not similarity) and removes:
359 wctx.remove(sorted(removes), True)
359 wctx.remove(sorted(removes), True)
360
360
361 for f in patches:
361 for f in patches:
362 gp = patches[f]
362 gp = patches[f]
363 if gp and gp.mode:
363 if gp and gp.mode:
364 islink, isexec = gp.mode
364 islink, isexec = gp.mode
365 dst = repo.wjoin(gp.path)
365 dst = repo.wjoin(gp.path)
366 # patch won't create empty files
366 # patch won't create empty files
367 if gp.op == 'ADD' and not os.path.lexists(dst):
367 if gp.op == 'ADD' and not os.path.lexists(dst):
368 flags = (isexec and 'x' or '') + (islink and 'l' or '')
368 flags = (isexec and 'x' or '') + (islink and 'l' or '')
369 repo.wwrite(gp.path, '', flags)
369 repo.wwrite(gp.path, '', flags)
370 util.set_flags(dst, islink, isexec)
370 util.set_flags(dst, islink, isexec)
371 addremove(repo, cfiles, similarity=similarity)
371 addremove(repo, cfiles, similarity=similarity)
372 files = patches.keys()
372 files = patches.keys()
373 files.extend([r for r in removes if r not in files])
373 files.extend([r for r in removes if r not in files])
374 return sorted(files)
374 return sorted(files)
375
375
376 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
376 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
377 """Update the dirstate to reflect the intent of copying src to dst. For
377 """Update the dirstate to reflect the intent of copying src to dst. For
378 different reasons it might not end with dst being marked as copied from src.
378 different reasons it might not end with dst being marked as copied from src.
379 """
379 """
380 origsrc = repo.dirstate.copied(src) or src
380 origsrc = repo.dirstate.copied(src) or src
381 if dst == origsrc: # copying back a copy?
381 if dst == origsrc: # copying back a copy?
382 if repo.dirstate[dst] not in 'mn' and not dryrun:
382 if repo.dirstate[dst] not in 'mn' and not dryrun:
383 repo.dirstate.normallookup(dst)
383 repo.dirstate.normallookup(dst)
384 else:
384 else:
385 if repo.dirstate[origsrc] == 'a' and origsrc == src:
385 if repo.dirstate[origsrc] == 'a' and origsrc == src:
386 if not ui.quiet:
386 if not ui.quiet:
387 ui.warn(_("%s has not been committed yet, so no copy "
387 ui.warn(_("%s has not been committed yet, so no copy "
388 "data will be stored for %s.\n")
388 "data will be stored for %s.\n")
389 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
389 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
390 if repo.dirstate[dst] in '?r' and not dryrun:
390 if repo.dirstate[dst] in '?r' and not dryrun:
391 wctx.add([dst])
391 wctx.add([dst])
392 elif not dryrun:
392 elif not dryrun:
393 wctx.copy(origsrc, dst)
393 wctx.copy(origsrc, dst)
394
394
395 def copy(ui, repo, pats, opts, rename=False):
395 def copy(ui, repo, pats, opts, rename=False):
396 # called with the repo lock held
396 # called with the repo lock held
397 #
397 #
398 # hgsep => pathname that uses "/" to separate directories
398 # hgsep => pathname that uses "/" to separate directories
399 # ossep => pathname that uses os.sep to separate directories
399 # ossep => pathname that uses os.sep to separate directories
400 cwd = repo.getcwd()
400 cwd = repo.getcwd()
401 targets = {}
401 targets = {}
402 after = opts.get("after")
402 after = opts.get("after")
403 dryrun = opts.get("dry_run")
403 dryrun = opts.get("dry_run")
404 wctx = repo[None]
404 wctx = repo[None]
405
405
406 def walkpat(pat):
406 def walkpat(pat):
407 srcs = []
407 srcs = []
408 badstates = after and '?' or '?r'
408 badstates = after and '?' or '?r'
409 m = match(repo, [pat], opts, globbed=True)
409 m = match(repo, [pat], opts, globbed=True)
410 for abs in repo.walk(m):
410 for abs in repo.walk(m):
411 state = repo.dirstate[abs]
411 state = repo.dirstate[abs]
412 rel = m.rel(abs)
412 rel = m.rel(abs)
413 exact = m.exact(abs)
413 exact = m.exact(abs)
414 if state in badstates:
414 if state in badstates:
415 if exact and state == '?':
415 if exact and state == '?':
416 ui.warn(_('%s: not copying - file is not managed\n') % rel)
416 ui.warn(_('%s: not copying - file is not managed\n') % rel)
417 if exact and state == 'r':
417 if exact and state == 'r':
418 ui.warn(_('%s: not copying - file has been marked for'
418 ui.warn(_('%s: not copying - file has been marked for'
419 ' remove\n') % rel)
419 ' remove\n') % rel)
420 continue
420 continue
421 # abs: hgsep
421 # abs: hgsep
422 # rel: ossep
422 # rel: ossep
423 srcs.append((abs, rel, exact))
423 srcs.append((abs, rel, exact))
424 return srcs
424 return srcs
425
425
426 # abssrc: hgsep
426 # abssrc: hgsep
427 # relsrc: ossep
427 # relsrc: ossep
428 # otarget: ossep
428 # otarget: ossep
429 def copyfile(abssrc, relsrc, otarget, exact):
429 def copyfile(abssrc, relsrc, otarget, exact):
430 abstarget = util.canonpath(repo.root, cwd, otarget)
430 abstarget = util.canonpath(repo.root, cwd, otarget)
431 reltarget = repo.pathto(abstarget, cwd)
431 reltarget = repo.pathto(abstarget, cwd)
432 target = repo.wjoin(abstarget)
432 target = repo.wjoin(abstarget)
433 src = repo.wjoin(abssrc)
433 src = repo.wjoin(abssrc)
434 state = repo.dirstate[abstarget]
434 state = repo.dirstate[abstarget]
435
435
436 # check for collisions
436 # check for collisions
437 prevsrc = targets.get(abstarget)
437 prevsrc = targets.get(abstarget)
438 if prevsrc is not None:
438 if prevsrc is not None:
439 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
439 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
440 (reltarget, repo.pathto(abssrc, cwd),
440 (reltarget, repo.pathto(abssrc, cwd),
441 repo.pathto(prevsrc, cwd)))
441 repo.pathto(prevsrc, cwd)))
442 return
442 return
443
443
444 # check for overwrites
444 # check for overwrites
445 exists = os.path.lexists(target)
445 exists = os.path.lexists(target)
446 if not after and exists or after and state in 'mn':
446 if not after and exists or after and state in 'mn':
447 if not opts['force']:
447 if not opts['force']:
448 ui.warn(_('%s: not overwriting - file exists\n') %
448 ui.warn(_('%s: not overwriting - file exists\n') %
449 reltarget)
449 reltarget)
450 return
450 return
451
451
452 if after:
452 if after:
453 if not exists:
453 if not exists:
454 if rename:
454 if rename:
455 ui.warn(_('%s: not recording move - %s does not exist\n') %
455 ui.warn(_('%s: not recording move - %s does not exist\n') %
456 (relsrc, reltarget))
456 (relsrc, reltarget))
457 else:
457 else:
458 ui.warn(_('%s: not recording copy - %s does not exist\n') %
458 ui.warn(_('%s: not recording copy - %s does not exist\n') %
459 (relsrc, reltarget))
459 (relsrc, reltarget))
460 return
460 return
461 elif not dryrun:
461 elif not dryrun:
462 try:
462 try:
463 if exists:
463 if exists:
464 os.unlink(target)
464 os.unlink(target)
465 targetdir = os.path.dirname(target) or '.'
465 targetdir = os.path.dirname(target) or '.'
466 if not os.path.isdir(targetdir):
466 if not os.path.isdir(targetdir):
467 os.makedirs(targetdir)
467 os.makedirs(targetdir)
468 util.copyfile(src, target)
468 util.copyfile(src, target)
469 except IOError, inst:
469 except IOError, inst:
470 if inst.errno == errno.ENOENT:
470 if inst.errno == errno.ENOENT:
471 ui.warn(_('%s: deleted in working copy\n') % relsrc)
471 ui.warn(_('%s: deleted in working copy\n') % relsrc)
472 else:
472 else:
473 ui.warn(_('%s: cannot copy - %s\n') %
473 ui.warn(_('%s: cannot copy - %s\n') %
474 (relsrc, inst.strerror))
474 (relsrc, inst.strerror))
475 return True # report a failure
475 return True # report a failure
476
476
477 if ui.verbose or not exact:
477 if ui.verbose or not exact:
478 if rename:
478 if rename:
479 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
479 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
480 else:
480 else:
481 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
481 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
482
482
483 targets[abstarget] = abssrc
483 targets[abstarget] = abssrc
484
484
485 # fix up dirstate
485 # fix up dirstate
486 dirstatecopy(ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd)
486 dirstatecopy(ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd)
487 if rename and not dryrun:
487 if rename and not dryrun:
488 wctx.remove([abssrc], not after)
488 wctx.remove([abssrc], not after)
489
489
490 # pat: ossep
490 # pat: ossep
491 # dest ossep
491 # dest ossep
492 # srcs: list of (hgsep, hgsep, ossep, bool)
492 # srcs: list of (hgsep, hgsep, ossep, bool)
493 # return: function that takes hgsep and returns ossep
493 # return: function that takes hgsep and returns ossep
494 def targetpathfn(pat, dest, srcs):
494 def targetpathfn(pat, dest, srcs):
495 if os.path.isdir(pat):
495 if os.path.isdir(pat):
496 abspfx = util.canonpath(repo.root, cwd, pat)
496 abspfx = util.canonpath(repo.root, cwd, pat)
497 abspfx = util.localpath(abspfx)
497 abspfx = util.localpath(abspfx)
498 if destdirexists:
498 if destdirexists:
499 striplen = len(os.path.split(abspfx)[0])
499 striplen = len(os.path.split(abspfx)[0])
500 else:
500 else:
501 striplen = len(abspfx)
501 striplen = len(abspfx)
502 if striplen:
502 if striplen:
503 striplen += len(os.sep)
503 striplen += len(os.sep)
504 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
504 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
505 elif destdirexists:
505 elif destdirexists:
506 res = lambda p: os.path.join(dest,
506 res = lambda p: os.path.join(dest,
507 os.path.basename(util.localpath(p)))
507 os.path.basename(util.localpath(p)))
508 else:
508 else:
509 res = lambda p: dest
509 res = lambda p: dest
510 return res
510 return res
511
511
512 # pat: ossep
512 # pat: ossep
513 # dest ossep
513 # dest ossep
514 # srcs: list of (hgsep, hgsep, ossep, bool)
514 # srcs: list of (hgsep, hgsep, ossep, bool)
515 # return: function that takes hgsep and returns ossep
515 # return: function that takes hgsep and returns ossep
516 def targetpathafterfn(pat, dest, srcs):
516 def targetpathafterfn(pat, dest, srcs):
517 if matchmod.patkind(pat):
517 if matchmod.patkind(pat):
518 # a mercurial pattern
518 # a mercurial pattern
519 res = lambda p: os.path.join(dest,
519 res = lambda p: os.path.join(dest,
520 os.path.basename(util.localpath(p)))
520 os.path.basename(util.localpath(p)))
521 else:
521 else:
522 abspfx = util.canonpath(repo.root, cwd, pat)
522 abspfx = util.canonpath(repo.root, cwd, pat)
523 if len(abspfx) < len(srcs[0][0]):
523 if len(abspfx) < len(srcs[0][0]):
524 # A directory. Either the target path contains the last
524 # A directory. Either the target path contains the last
525 # component of the source path or it does not.
525 # component of the source path or it does not.
526 def evalpath(striplen):
526 def evalpath(striplen):
527 score = 0
527 score = 0
528 for s in srcs:
528 for s in srcs:
529 t = os.path.join(dest, util.localpath(s[0])[striplen:])
529 t = os.path.join(dest, util.localpath(s[0])[striplen:])
530 if os.path.lexists(t):
530 if os.path.lexists(t):
531 score += 1
531 score += 1
532 return score
532 return score
533
533
534 abspfx = util.localpath(abspfx)
534 abspfx = util.localpath(abspfx)
535 striplen = len(abspfx)
535 striplen = len(abspfx)
536 if striplen:
536 if striplen:
537 striplen += len(os.sep)
537 striplen += len(os.sep)
538 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
538 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
539 score = evalpath(striplen)
539 score = evalpath(striplen)
540 striplen1 = len(os.path.split(abspfx)[0])
540 striplen1 = len(os.path.split(abspfx)[0])
541 if striplen1:
541 if striplen1:
542 striplen1 += len(os.sep)
542 striplen1 += len(os.sep)
543 if evalpath(striplen1) > score:
543 if evalpath(striplen1) > score:
544 striplen = striplen1
544 striplen = striplen1
545 res = lambda p: os.path.join(dest,
545 res = lambda p: os.path.join(dest,
546 util.localpath(p)[striplen:])
546 util.localpath(p)[striplen:])
547 else:
547 else:
548 # a file
548 # a file
549 if destdirexists:
549 if destdirexists:
550 res = lambda p: os.path.join(dest,
550 res = lambda p: os.path.join(dest,
551 os.path.basename(util.localpath(p)))
551 os.path.basename(util.localpath(p)))
552 else:
552 else:
553 res = lambda p: dest
553 res = lambda p: dest
554 return res
554 return res
555
555
556
556
557 pats = expandpats(pats)
557 pats = expandpats(pats)
558 if not pats:
558 if not pats:
559 raise util.Abort(_('no source or destination specified'))
559 raise util.Abort(_('no source or destination specified'))
560 if len(pats) == 1:
560 if len(pats) == 1:
561 raise util.Abort(_('no destination specified'))
561 raise util.Abort(_('no destination specified'))
562 dest = pats.pop()
562 dest = pats.pop()
563 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
563 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
564 if not destdirexists:
564 if not destdirexists:
565 if len(pats) > 1 or matchmod.patkind(pats[0]):
565 if len(pats) > 1 or matchmod.patkind(pats[0]):
566 raise util.Abort(_('with multiple sources, destination must be an '
566 raise util.Abort(_('with multiple sources, destination must be an '
567 'existing directory'))
567 'existing directory'))
568 if util.endswithsep(dest):
568 if util.endswithsep(dest):
569 raise util.Abort(_('destination %s is not a directory') % dest)
569 raise util.Abort(_('destination %s is not a directory') % dest)
570
570
571 tfn = targetpathfn
571 tfn = targetpathfn
572 if after:
572 if after:
573 tfn = targetpathafterfn
573 tfn = targetpathafterfn
574 copylist = []
574 copylist = []
575 for pat in pats:
575 for pat in pats:
576 srcs = walkpat(pat)
576 srcs = walkpat(pat)
577 if not srcs:
577 if not srcs:
578 continue
578 continue
579 copylist.append((tfn(pat, dest, srcs), srcs))
579 copylist.append((tfn(pat, dest, srcs), srcs))
580 if not copylist:
580 if not copylist:
581 raise util.Abort(_('no files to copy'))
581 raise util.Abort(_('no files to copy'))
582
582
583 errors = 0
583 errors = 0
584 for targetpath, srcs in copylist:
584 for targetpath, srcs in copylist:
585 for abssrc, relsrc, exact in srcs:
585 for abssrc, relsrc, exact in srcs:
586 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
586 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
587 errors += 1
587 errors += 1
588
588
589 if errors:
589 if errors:
590 ui.warn(_('(consider using --after)\n'))
590 ui.warn(_('(consider using --after)\n'))
591
591
592 return errors != 0
592 return errors != 0
593
593
594 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
594 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
595 runargs=None, appendpid=False):
595 runargs=None, appendpid=False):
596 '''Run a command as a service.'''
596 '''Run a command as a service.'''
597
597
598 if opts['daemon'] and not opts['daemon_pipefds']:
598 if opts['daemon'] and not opts['daemon_pipefds']:
599 # Signal child process startup with file removal
599 # Signal child process startup with file removal
600 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
600 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
601 os.close(lockfd)
601 os.close(lockfd)
602 try:
602 try:
603 if not runargs:
603 if not runargs:
604 runargs = util.hgcmd() + sys.argv[1:]
604 runargs = util.hgcmd() + sys.argv[1:]
605 runargs.append('--daemon-pipefds=%s' % lockpath)
605 runargs.append('--daemon-pipefds=%s' % lockpath)
606 # Don't pass --cwd to the child process, because we've already
606 # Don't pass --cwd to the child process, because we've already
607 # changed directory.
607 # changed directory.
608 for i in xrange(1, len(runargs)):
608 for i in xrange(1, len(runargs)):
609 if runargs[i].startswith('--cwd='):
609 if runargs[i].startswith('--cwd='):
610 del runargs[i]
610 del runargs[i]
611 break
611 break
612 elif runargs[i].startswith('--cwd'):
612 elif runargs[i].startswith('--cwd'):
613 del runargs[i:i + 2]
613 del runargs[i:i + 2]
614 break
614 break
615 def condfn():
615 def condfn():
616 return not os.path.exists(lockpath)
616 return not os.path.exists(lockpath)
617 pid = util.rundetached(runargs, condfn)
617 pid = util.rundetached(runargs, condfn)
618 if pid < 0:
618 if pid < 0:
619 raise util.Abort(_('child process failed to start'))
619 raise util.Abort(_('child process failed to start'))
620 finally:
620 finally:
621 try:
621 try:
622 os.unlink(lockpath)
622 os.unlink(lockpath)
623 except OSError, e:
623 except OSError, e:
624 if e.errno != errno.ENOENT:
624 if e.errno != errno.ENOENT:
625 raise
625 raise
626 if parentfn:
626 if parentfn:
627 return parentfn(pid)
627 return parentfn(pid)
628 else:
628 else:
629 return
629 return
630
630
631 if initfn:
631 if initfn:
632 initfn()
632 initfn()
633
633
634 if opts['pid_file']:
634 if opts['pid_file']:
635 mode = appendpid and 'a' or 'w'
635 mode = appendpid and 'a' or 'w'
636 fp = open(opts['pid_file'], mode)
636 fp = open(opts['pid_file'], mode)
637 fp.write(str(os.getpid()) + '\n')
637 fp.write(str(os.getpid()) + '\n')
638 fp.close()
638 fp.close()
639
639
640 if opts['daemon_pipefds']:
640 if opts['daemon_pipefds']:
641 lockpath = opts['daemon_pipefds']
641 lockpath = opts['daemon_pipefds']
642 try:
642 try:
643 os.setsid()
643 os.setsid()
644 except AttributeError:
644 except AttributeError:
645 pass
645 pass
646 os.unlink(lockpath)
646 os.unlink(lockpath)
647 util.hidewindow()
647 util.hidewindow()
648 sys.stdout.flush()
648 sys.stdout.flush()
649 sys.stderr.flush()
649 sys.stderr.flush()
650
650
651 nullfd = os.open(util.nulldev, os.O_RDWR)
651 nullfd = os.open(util.nulldev, os.O_RDWR)
652 logfilefd = nullfd
652 logfilefd = nullfd
653 if logfile:
653 if logfile:
654 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
654 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
655 os.dup2(nullfd, 0)
655 os.dup2(nullfd, 0)
656 os.dup2(logfilefd, 1)
656 os.dup2(logfilefd, 1)
657 os.dup2(logfilefd, 2)
657 os.dup2(logfilefd, 2)
658 if nullfd not in (0, 1, 2):
658 if nullfd not in (0, 1, 2):
659 os.close(nullfd)
659 os.close(nullfd)
660 if logfile and logfilefd not in (0, 1, 2):
660 if logfile and logfilefd not in (0, 1, 2):
661 os.close(logfilefd)
661 os.close(logfilefd)
662
662
663 if runfn:
663 if runfn:
664 return runfn()
664 return runfn()
665
665
666 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
666 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
667 opts=None):
667 opts=None):
668 '''export changesets as hg patches.'''
668 '''export changesets as hg patches.'''
669
669
670 total = len(revs)
670 total = len(revs)
671 revwidth = max([len(str(rev)) for rev in revs])
671 revwidth = max([len(str(rev)) for rev in revs])
672
672
673 def single(rev, seqno, fp):
673 def single(rev, seqno, fp):
674 ctx = repo[rev]
674 ctx = repo[rev]
675 node = ctx.node()
675 node = ctx.node()
676 parents = [p.node() for p in ctx.parents() if p]
676 parents = [p.node() for p in ctx.parents() if p]
677 branch = ctx.branch()
677 branch = ctx.branch()
678 if switch_parent:
678 if switch_parent:
679 parents.reverse()
679 parents.reverse()
680 prev = (parents and parents[0]) or nullid
680 prev = (parents and parents[0]) or nullid
681
681
682 if not fp:
682 if not fp:
683 fp = make_file(repo, template, node, total=total, seqno=seqno,
683 fp = make_file(repo, template, node, total=total, seqno=seqno,
684 revwidth=revwidth, mode='ab')
684 revwidth=revwidth, mode='ab')
685 if fp != sys.stdout and hasattr(fp, 'name'):
685 if fp != sys.stdout and hasattr(fp, 'name'):
686 repo.ui.note("%s\n" % fp.name)
686 repo.ui.note("%s\n" % fp.name)
687
687
688 fp.write("# HG changeset patch\n")
688 fp.write("# HG changeset patch\n")
689 fp.write("# User %s\n" % ctx.user())
689 fp.write("# User %s\n" % ctx.user())
690 fp.write("# Date %d %d\n" % ctx.date())
690 fp.write("# Date %d %d\n" % ctx.date())
691 if branch and branch != 'default':
691 if branch and branch != 'default':
692 fp.write("# Branch %s\n" % branch)
692 fp.write("# Branch %s\n" % branch)
693 fp.write("# Node ID %s\n" % hex(node))
693 fp.write("# Node ID %s\n" % hex(node))
694 fp.write("# Parent %s\n" % hex(prev))
694 fp.write("# Parent %s\n" % hex(prev))
695 if len(parents) > 1:
695 if len(parents) > 1:
696 fp.write("# Parent %s\n" % hex(parents[1]))
696 fp.write("# Parent %s\n" % hex(parents[1]))
697 fp.write(ctx.description().rstrip())
697 fp.write(ctx.description().rstrip())
698 fp.write("\n\n")
698 fp.write("\n\n")
699
699
700 for chunk in patch.diff(repo, prev, node, opts=opts):
700 for chunk in patch.diff(repo, prev, node, opts=opts):
701 fp.write(chunk)
701 fp.write(chunk)
702
702
703 fp.flush()
703 fp.flush()
704
704
705 for seqno, rev in enumerate(revs):
705 for seqno, rev in enumerate(revs):
706 single(rev, seqno + 1, fp)
706 single(rev, seqno + 1, fp)
707
707
708 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
708 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
709 changes=None, stat=False, fp=None, prefix='',
709 changes=None, stat=False, fp=None, prefix='',
710 listsubrepos=False):
710 listsubrepos=False):
711 '''show diff or diffstat.'''
711 '''show diff or diffstat.'''
712 if fp is None:
712 if fp is None:
713 write = ui.write
713 write = ui.write
714 else:
714 else:
715 def write(s, **kw):
715 def write(s, **kw):
716 fp.write(s)
716 fp.write(s)
717
717
718 if stat:
718 if stat:
719 diffopts = diffopts.copy(context=0)
719 diffopts = diffopts.copy(context=0)
720 width = 80
720 width = 80
721 if not ui.plain():
721 if not ui.plain():
722 width = ui.termwidth()
722 width = ui.termwidth()
723 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
723 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
724 prefix=prefix)
724 prefix=prefix)
725 for chunk, label in patch.diffstatui(util.iterlines(chunks),
725 for chunk, label in patch.diffstatui(util.iterlines(chunks),
726 width=width,
726 width=width,
727 git=diffopts.git):
727 git=diffopts.git):
728 write(chunk, label=label)
728 write(chunk, label=label)
729 else:
729 else:
730 for chunk, label in patch.diffui(repo, node1, node2, match,
730 for chunk, label in patch.diffui(repo, node1, node2, match,
731 changes, diffopts, prefix=prefix):
731 changes, diffopts, prefix=prefix):
732 write(chunk, label=label)
732 write(chunk, label=label)
733
733
734 if listsubrepos:
734 if listsubrepos:
735 ctx1 = repo[node1]
735 ctx1 = repo[node1]
736 ctx2 = repo[node2]
736 ctx2 = repo[node2]
737 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
737 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
738 if node2 is not None:
738 if node2 is not None:
739 node2 = ctx2.substate[subpath][1]
739 node2 = ctx2.substate[subpath][1]
740 submatch = matchmod.narrowmatcher(subpath, match)
740 submatch = matchmod.narrowmatcher(subpath, match)
741 sub.diff(diffopts, node2, submatch, changes=changes,
741 sub.diff(diffopts, node2, submatch, changes=changes,
742 stat=stat, fp=fp, prefix=prefix)
742 stat=stat, fp=fp, prefix=prefix)
743
743
744 class changeset_printer(object):
744 class changeset_printer(object):
745 '''show changeset information when templating not requested.'''
745 '''show changeset information when templating not requested.'''
746
746
747 def __init__(self, ui, repo, patch, diffopts, buffered):
747 def __init__(self, ui, repo, patch, diffopts, buffered):
748 self.ui = ui
748 self.ui = ui
749 self.repo = repo
749 self.repo = repo
750 self.buffered = buffered
750 self.buffered = buffered
751 self.patch = patch
751 self.patch = patch
752 self.diffopts = diffopts
752 self.diffopts = diffopts
753 self.header = {}
753 self.header = {}
754 self.hunk = {}
754 self.hunk = {}
755 self.lastheader = None
755 self.lastheader = None
756 self.footer = None
756 self.footer = None
757
757
758 def flush(self, rev):
758 def flush(self, rev):
759 if rev in self.header:
759 if rev in self.header:
760 h = self.header[rev]
760 h = self.header[rev]
761 if h != self.lastheader:
761 if h != self.lastheader:
762 self.lastheader = h
762 self.lastheader = h
763 self.ui.write(h)
763 self.ui.write(h)
764 del self.header[rev]
764 del self.header[rev]
765 if rev in self.hunk:
765 if rev in self.hunk:
766 self.ui.write(self.hunk[rev])
766 self.ui.write(self.hunk[rev])
767 del self.hunk[rev]
767 del self.hunk[rev]
768 return 1
768 return 1
769 return 0
769 return 0
770
770
771 def close(self):
771 def close(self):
772 if self.footer:
772 if self.footer:
773 self.ui.write(self.footer)
773 self.ui.write(self.footer)
774
774
775 def show(self, ctx, copies=None, matchfn=None, **props):
775 def show(self, ctx, copies=None, matchfn=None, **props):
776 if self.buffered:
776 if self.buffered:
777 self.ui.pushbuffer()
777 self.ui.pushbuffer()
778 self._show(ctx, copies, matchfn, props)
778 self._show(ctx, copies, matchfn, props)
779 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
779 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
780 else:
780 else:
781 self._show(ctx, copies, matchfn, props)
781 self._show(ctx, copies, matchfn, props)
782
782
783 def _show(self, ctx, copies, matchfn, props):
783 def _show(self, ctx, copies, matchfn, props):
784 '''show a single changeset or file revision'''
784 '''show a single changeset or file revision'''
785 changenode = ctx.node()
785 changenode = ctx.node()
786 rev = ctx.rev()
786 rev = ctx.rev()
787
787
788 if self.ui.quiet:
788 if self.ui.quiet:
789 self.ui.write("%d:%s\n" % (rev, short(changenode)),
789 self.ui.write("%d:%s\n" % (rev, short(changenode)),
790 label='log.node')
790 label='log.node')
791 return
791 return
792
792
793 log = self.repo.changelog
793 log = self.repo.changelog
794 date = util.datestr(ctx.date())
794 date = util.datestr(ctx.date())
795
795
796 hexfunc = self.ui.debugflag and hex or short
796 hexfunc = self.ui.debugflag and hex or short
797
797
798 parents = [(p, hexfunc(log.node(p)))
798 parents = [(p, hexfunc(log.node(p)))
799 for p in self._meaningful_parentrevs(log, rev)]
799 for p in self._meaningful_parentrevs(log, rev)]
800
800
801 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
801 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
802 label='log.changeset')
802 label='log.changeset')
803
803
804 branch = ctx.branch()
804 branch = ctx.branch()
805 # don't show the default branch name
805 # don't show the default branch name
806 if branch != 'default':
806 if branch != 'default':
807 self.ui.write(_("branch: %s\n") % branch,
807 self.ui.write(_("branch: %s\n") % branch,
808 label='log.branch')
808 label='log.branch')
809 for bookmark in self.repo.nodebookmarks(changenode):
810 self.ui.write(_("bookmark: %s\n") % bookmark,
811 label='log.bookmark')
809 for tag in self.repo.nodetags(changenode):
812 for tag in self.repo.nodetags(changenode):
810 self.ui.write(_("tag: %s\n") % tag,
813 self.ui.write(_("tag: %s\n") % tag,
811 label='log.tag')
814 label='log.tag')
812 for parent in parents:
815 for parent in parents:
813 self.ui.write(_("parent: %d:%s\n") % parent,
816 self.ui.write(_("parent: %d:%s\n") % parent,
814 label='log.parent')
817 label='log.parent')
815
818
816 if self.ui.debugflag:
819 if self.ui.debugflag:
817 mnode = ctx.manifestnode()
820 mnode = ctx.manifestnode()
818 self.ui.write(_("manifest: %d:%s\n") %
821 self.ui.write(_("manifest: %d:%s\n") %
819 (self.repo.manifest.rev(mnode), hex(mnode)),
822 (self.repo.manifest.rev(mnode), hex(mnode)),
820 label='ui.debug log.manifest')
823 label='ui.debug log.manifest')
821 self.ui.write(_("user: %s\n") % ctx.user(),
824 self.ui.write(_("user: %s\n") % ctx.user(),
822 label='log.user')
825 label='log.user')
823 self.ui.write(_("date: %s\n") % date,
826 self.ui.write(_("date: %s\n") % date,
824 label='log.date')
827 label='log.date')
825
828
826 if self.ui.debugflag:
829 if self.ui.debugflag:
827 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
830 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
828 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
831 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
829 files):
832 files):
830 if value:
833 if value:
831 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
834 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
832 label='ui.debug log.files')
835 label='ui.debug log.files')
833 elif ctx.files() and self.ui.verbose:
836 elif ctx.files() and self.ui.verbose:
834 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
837 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
835 label='ui.note log.files')
838 label='ui.note log.files')
836 if copies and self.ui.verbose:
839 if copies and self.ui.verbose:
837 copies = ['%s (%s)' % c for c in copies]
840 copies = ['%s (%s)' % c for c in copies]
838 self.ui.write(_("copies: %s\n") % ' '.join(copies),
841 self.ui.write(_("copies: %s\n") % ' '.join(copies),
839 label='ui.note log.copies')
842 label='ui.note log.copies')
840
843
841 extra = ctx.extra()
844 extra = ctx.extra()
842 if extra and self.ui.debugflag:
845 if extra and self.ui.debugflag:
843 for key, value in sorted(extra.items()):
846 for key, value in sorted(extra.items()):
844 self.ui.write(_("extra: %s=%s\n")
847 self.ui.write(_("extra: %s=%s\n")
845 % (key, value.encode('string_escape')),
848 % (key, value.encode('string_escape')),
846 label='ui.debug log.extra')
849 label='ui.debug log.extra')
847
850
848 description = ctx.description().strip()
851 description = ctx.description().strip()
849 if description:
852 if description:
850 if self.ui.verbose:
853 if self.ui.verbose:
851 self.ui.write(_("description:\n"),
854 self.ui.write(_("description:\n"),
852 label='ui.note log.description')
855 label='ui.note log.description')
853 self.ui.write(description,
856 self.ui.write(description,
854 label='ui.note log.description')
857 label='ui.note log.description')
855 self.ui.write("\n\n")
858 self.ui.write("\n\n")
856 else:
859 else:
857 self.ui.write(_("summary: %s\n") %
860 self.ui.write(_("summary: %s\n") %
858 description.splitlines()[0],
861 description.splitlines()[0],
859 label='log.summary')
862 label='log.summary')
860 self.ui.write("\n")
863 self.ui.write("\n")
861
864
862 self.showpatch(changenode, matchfn)
865 self.showpatch(changenode, matchfn)
863
866
864 def showpatch(self, node, matchfn):
867 def showpatch(self, node, matchfn):
865 if not matchfn:
868 if not matchfn:
866 matchfn = self.patch
869 matchfn = self.patch
867 if matchfn:
870 if matchfn:
868 stat = self.diffopts.get('stat')
871 stat = self.diffopts.get('stat')
869 diff = self.diffopts.get('patch')
872 diff = self.diffopts.get('patch')
870 diffopts = patch.diffopts(self.ui, self.diffopts)
873 diffopts = patch.diffopts(self.ui, self.diffopts)
871 prev = self.repo.changelog.parents(node)[0]
874 prev = self.repo.changelog.parents(node)[0]
872 if stat:
875 if stat:
873 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
876 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
874 match=matchfn, stat=True)
877 match=matchfn, stat=True)
875 if diff:
878 if diff:
876 if stat:
879 if stat:
877 self.ui.write("\n")
880 self.ui.write("\n")
878 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
881 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
879 match=matchfn, stat=False)
882 match=matchfn, stat=False)
880 self.ui.write("\n")
883 self.ui.write("\n")
881
884
882 def _meaningful_parentrevs(self, log, rev):
885 def _meaningful_parentrevs(self, log, rev):
883 """Return list of meaningful (or all if debug) parentrevs for rev.
886 """Return list of meaningful (or all if debug) parentrevs for rev.
884
887
885 For merges (two non-nullrev revisions) both parents are meaningful.
888 For merges (two non-nullrev revisions) both parents are meaningful.
886 Otherwise the first parent revision is considered meaningful if it
889 Otherwise the first parent revision is considered meaningful if it
887 is not the preceding revision.
890 is not the preceding revision.
888 """
891 """
889 parents = log.parentrevs(rev)
892 parents = log.parentrevs(rev)
890 if not self.ui.debugflag and parents[1] == nullrev:
893 if not self.ui.debugflag and parents[1] == nullrev:
891 if parents[0] >= rev - 1:
894 if parents[0] >= rev - 1:
892 parents = []
895 parents = []
893 else:
896 else:
894 parents = [parents[0]]
897 parents = [parents[0]]
895 return parents
898 return parents
896
899
897
900
898 class changeset_templater(changeset_printer):
901 class changeset_templater(changeset_printer):
899 '''format changeset information.'''
902 '''format changeset information.'''
900
903
901 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
904 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
902 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
905 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
903 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
906 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
904 defaulttempl = {
907 defaulttempl = {
905 'parent': '{rev}:{node|formatnode} ',
908 'parent': '{rev}:{node|formatnode} ',
906 'manifest': '{rev}:{node|formatnode}',
909 'manifest': '{rev}:{node|formatnode}',
907 'file_copy': '{name} ({source})',
910 'file_copy': '{name} ({source})',
908 'extra': '{key}={value|stringescape}'
911 'extra': '{key}={value|stringescape}'
909 }
912 }
910 # filecopy is preserved for compatibility reasons
913 # filecopy is preserved for compatibility reasons
911 defaulttempl['filecopy'] = defaulttempl['file_copy']
914 defaulttempl['filecopy'] = defaulttempl['file_copy']
912 self.t = templater.templater(mapfile, {'formatnode': formatnode},
915 self.t = templater.templater(mapfile, {'formatnode': formatnode},
913 cache=defaulttempl)
916 cache=defaulttempl)
914 self.cache = {}
917 self.cache = {}
915
918
916 def use_template(self, t):
919 def use_template(self, t):
917 '''set template string to use'''
920 '''set template string to use'''
918 self.t.cache['changeset'] = t
921 self.t.cache['changeset'] = t
919
922
920 def _meaningful_parentrevs(self, ctx):
923 def _meaningful_parentrevs(self, ctx):
921 """Return list of meaningful (or all if debug) parentrevs for rev.
924 """Return list of meaningful (or all if debug) parentrevs for rev.
922 """
925 """
923 parents = ctx.parents()
926 parents = ctx.parents()
924 if len(parents) > 1:
927 if len(parents) > 1:
925 return parents
928 return parents
926 if self.ui.debugflag:
929 if self.ui.debugflag:
927 return [parents[0], self.repo['null']]
930 return [parents[0], self.repo['null']]
928 if parents[0].rev() >= ctx.rev() - 1:
931 if parents[0].rev() >= ctx.rev() - 1:
929 return []
932 return []
930 return parents
933 return parents
931
934
932 def _show(self, ctx, copies, matchfn, props):
935 def _show(self, ctx, copies, matchfn, props):
933 '''show a single changeset or file revision'''
936 '''show a single changeset or file revision'''
934
937
935 showlist = templatekw.showlist
938 showlist = templatekw.showlist
936
939
937 # showparents() behaviour depends on ui trace level which
940 # showparents() behaviour depends on ui trace level which
938 # causes unexpected behaviours at templating level and makes
941 # causes unexpected behaviours at templating level and makes
939 # it harder to extract it in a standalone function. Its
942 # it harder to extract it in a standalone function. Its
940 # behaviour cannot be changed so leave it here for now.
943 # behaviour cannot be changed so leave it here for now.
941 def showparents(**args):
944 def showparents(**args):
942 ctx = args['ctx']
945 ctx = args['ctx']
943 parents = [[('rev', p.rev()), ('node', p.hex())]
946 parents = [[('rev', p.rev()), ('node', p.hex())]
944 for p in self._meaningful_parentrevs(ctx)]
947 for p in self._meaningful_parentrevs(ctx)]
945 return showlist('parent', parents, **args)
948 return showlist('parent', parents, **args)
946
949
947 props = props.copy()
950 props = props.copy()
948 props.update(templatekw.keywords)
951 props.update(templatekw.keywords)
949 props['parents'] = showparents
952 props['parents'] = showparents
950 props['templ'] = self.t
953 props['templ'] = self.t
951 props['ctx'] = ctx
954 props['ctx'] = ctx
952 props['repo'] = self.repo
955 props['repo'] = self.repo
953 props['revcache'] = {'copies': copies}
956 props['revcache'] = {'copies': copies}
954 props['cache'] = self.cache
957 props['cache'] = self.cache
955
958
956 # find correct templates for current mode
959 # find correct templates for current mode
957
960
958 tmplmodes = [
961 tmplmodes = [
959 (True, None),
962 (True, None),
960 (self.ui.verbose, 'verbose'),
963 (self.ui.verbose, 'verbose'),
961 (self.ui.quiet, 'quiet'),
964 (self.ui.quiet, 'quiet'),
962 (self.ui.debugflag, 'debug'),
965 (self.ui.debugflag, 'debug'),
963 ]
966 ]
964
967
965 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
968 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
966 for mode, postfix in tmplmodes:
969 for mode, postfix in tmplmodes:
967 for type in types:
970 for type in types:
968 cur = postfix and ('%s_%s' % (type, postfix)) or type
971 cur = postfix and ('%s_%s' % (type, postfix)) or type
969 if mode and cur in self.t:
972 if mode and cur in self.t:
970 types[type] = cur
973 types[type] = cur
971
974
972 try:
975 try:
973
976
974 # write header
977 # write header
975 if types['header']:
978 if types['header']:
976 h = templater.stringify(self.t(types['header'], **props))
979 h = templater.stringify(self.t(types['header'], **props))
977 if self.buffered:
980 if self.buffered:
978 self.header[ctx.rev()] = h
981 self.header[ctx.rev()] = h
979 else:
982 else:
980 if self.lastheader != h:
983 if self.lastheader != h:
981 self.lastheader = h
984 self.lastheader = h
982 self.ui.write(h)
985 self.ui.write(h)
983
986
984 # write changeset metadata, then patch if requested
987 # write changeset metadata, then patch if requested
985 key = types['changeset']
988 key = types['changeset']
986 self.ui.write(templater.stringify(self.t(key, **props)))
989 self.ui.write(templater.stringify(self.t(key, **props)))
987 self.showpatch(ctx.node(), matchfn)
990 self.showpatch(ctx.node(), matchfn)
988
991
989 if types['footer']:
992 if types['footer']:
990 if not self.footer:
993 if not self.footer:
991 self.footer = templater.stringify(self.t(types['footer'],
994 self.footer = templater.stringify(self.t(types['footer'],
992 **props))
995 **props))
993
996
994 except KeyError, inst:
997 except KeyError, inst:
995 msg = _("%s: no key named '%s'")
998 msg = _("%s: no key named '%s'")
996 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
999 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
997 except SyntaxError, inst:
1000 except SyntaxError, inst:
998 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1001 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
999
1002
1000 def show_changeset(ui, repo, opts, buffered=False):
1003 def show_changeset(ui, repo, opts, buffered=False):
1001 """show one changeset using template or regular display.
1004 """show one changeset using template or regular display.
1002
1005
1003 Display format will be the first non-empty hit of:
1006 Display format will be the first non-empty hit of:
1004 1. option 'template'
1007 1. option 'template'
1005 2. option 'style'
1008 2. option 'style'
1006 3. [ui] setting 'logtemplate'
1009 3. [ui] setting 'logtemplate'
1007 4. [ui] setting 'style'
1010 4. [ui] setting 'style'
1008 If all of these values are either the unset or the empty string,
1011 If all of these values are either the unset or the empty string,
1009 regular display via changeset_printer() is done.
1012 regular display via changeset_printer() is done.
1010 """
1013 """
1011 # options
1014 # options
1012 patch = False
1015 patch = False
1013 if opts.get('patch') or opts.get('stat'):
1016 if opts.get('patch') or opts.get('stat'):
1014 patch = matchall(repo)
1017 patch = matchall(repo)
1015
1018
1016 tmpl = opts.get('template')
1019 tmpl = opts.get('template')
1017 style = None
1020 style = None
1018 if tmpl:
1021 if tmpl:
1019 tmpl = templater.parsestring(tmpl, quoted=False)
1022 tmpl = templater.parsestring(tmpl, quoted=False)
1020 else:
1023 else:
1021 style = opts.get('style')
1024 style = opts.get('style')
1022
1025
1023 # ui settings
1026 # ui settings
1024 if not (tmpl or style):
1027 if not (tmpl or style):
1025 tmpl = ui.config('ui', 'logtemplate')
1028 tmpl = ui.config('ui', 'logtemplate')
1026 if tmpl:
1029 if tmpl:
1027 tmpl = templater.parsestring(tmpl)
1030 tmpl = templater.parsestring(tmpl)
1028 else:
1031 else:
1029 style = util.expandpath(ui.config('ui', 'style', ''))
1032 style = util.expandpath(ui.config('ui', 'style', ''))
1030
1033
1031 if not (tmpl or style):
1034 if not (tmpl or style):
1032 return changeset_printer(ui, repo, patch, opts, buffered)
1035 return changeset_printer(ui, repo, patch, opts, buffered)
1033
1036
1034 mapfile = None
1037 mapfile = None
1035 if style and not tmpl:
1038 if style and not tmpl:
1036 mapfile = style
1039 mapfile = style
1037 if not os.path.split(mapfile)[0]:
1040 if not os.path.split(mapfile)[0]:
1038 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1041 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1039 or templater.templatepath(mapfile))
1042 or templater.templatepath(mapfile))
1040 if mapname:
1043 if mapname:
1041 mapfile = mapname
1044 mapfile = mapname
1042
1045
1043 try:
1046 try:
1044 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
1047 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
1045 except SyntaxError, inst:
1048 except SyntaxError, inst:
1046 raise util.Abort(inst.args[0])
1049 raise util.Abort(inst.args[0])
1047 if tmpl:
1050 if tmpl:
1048 t.use_template(tmpl)
1051 t.use_template(tmpl)
1049 return t
1052 return t
1050
1053
1051 def finddate(ui, repo, date):
1054 def finddate(ui, repo, date):
1052 """Find the tipmost changeset that matches the given date spec"""
1055 """Find the tipmost changeset that matches the given date spec"""
1053
1056
1054 df = util.matchdate(date)
1057 df = util.matchdate(date)
1055 m = matchall(repo)
1058 m = matchall(repo)
1056 results = {}
1059 results = {}
1057
1060
1058 def prep(ctx, fns):
1061 def prep(ctx, fns):
1059 d = ctx.date()
1062 d = ctx.date()
1060 if df(d[0]):
1063 if df(d[0]):
1061 results[ctx.rev()] = d
1064 results[ctx.rev()] = d
1062
1065
1063 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1066 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1064 rev = ctx.rev()
1067 rev = ctx.rev()
1065 if rev in results:
1068 if rev in results:
1066 ui.status(_("Found revision %s from %s\n") %
1069 ui.status(_("Found revision %s from %s\n") %
1067 (rev, util.datestr(results[rev])))
1070 (rev, util.datestr(results[rev])))
1068 return str(rev)
1071 return str(rev)
1069
1072
1070 raise util.Abort(_("revision matching date not found"))
1073 raise util.Abort(_("revision matching date not found"))
1071
1074
1072 def walkchangerevs(repo, match, opts, prepare):
1075 def walkchangerevs(repo, match, opts, prepare):
1073 '''Iterate over files and the revs in which they changed.
1076 '''Iterate over files and the revs in which they changed.
1074
1077
1075 Callers most commonly need to iterate backwards over the history
1078 Callers most commonly need to iterate backwards over the history
1076 in which they are interested. Doing so has awful (quadratic-looking)
1079 in which they are interested. Doing so has awful (quadratic-looking)
1077 performance, so we use iterators in a "windowed" way.
1080 performance, so we use iterators in a "windowed" way.
1078
1081
1079 We walk a window of revisions in the desired order. Within the
1082 We walk a window of revisions in the desired order. Within the
1080 window, we first walk forwards to gather data, then in the desired
1083 window, we first walk forwards to gather data, then in the desired
1081 order (usually backwards) to display it.
1084 order (usually backwards) to display it.
1082
1085
1083 This function returns an iterator yielding contexts. Before
1086 This function returns an iterator yielding contexts. Before
1084 yielding each context, the iterator will first call the prepare
1087 yielding each context, the iterator will first call the prepare
1085 function on each context in the window in forward order.'''
1088 function on each context in the window in forward order.'''
1086
1089
1087 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1090 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1088 if start < end:
1091 if start < end:
1089 while start < end:
1092 while start < end:
1090 yield start, min(windowsize, end - start)
1093 yield start, min(windowsize, end - start)
1091 start += windowsize
1094 start += windowsize
1092 if windowsize < sizelimit:
1095 if windowsize < sizelimit:
1093 windowsize *= 2
1096 windowsize *= 2
1094 else:
1097 else:
1095 while start > end:
1098 while start > end:
1096 yield start, min(windowsize, start - end - 1)
1099 yield start, min(windowsize, start - end - 1)
1097 start -= windowsize
1100 start -= windowsize
1098 if windowsize < sizelimit:
1101 if windowsize < sizelimit:
1099 windowsize *= 2
1102 windowsize *= 2
1100
1103
1101 follow = opts.get('follow') or opts.get('follow_first')
1104 follow = opts.get('follow') or opts.get('follow_first')
1102
1105
1103 if not len(repo):
1106 if not len(repo):
1104 return []
1107 return []
1105
1108
1106 if follow:
1109 if follow:
1107 defrange = '%s:0' % repo['.'].rev()
1110 defrange = '%s:0' % repo['.'].rev()
1108 else:
1111 else:
1109 defrange = '-1:0'
1112 defrange = '-1:0'
1110 revs = revrange(repo, opts['rev'] or [defrange])
1113 revs = revrange(repo, opts['rev'] or [defrange])
1111 if not revs:
1114 if not revs:
1112 return []
1115 return []
1113 wanted = set()
1116 wanted = set()
1114 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1117 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1115 fncache = {}
1118 fncache = {}
1116 change = util.cachefunc(repo.changectx)
1119 change = util.cachefunc(repo.changectx)
1117
1120
1118 # First step is to fill wanted, the set of revisions that we want to yield.
1121 # First step is to fill wanted, the set of revisions that we want to yield.
1119 # When it does not induce extra cost, we also fill fncache for revisions in
1122 # When it does not induce extra cost, we also fill fncache for revisions in
1120 # wanted: a cache of filenames that were changed (ctx.files()) and that
1123 # wanted: a cache of filenames that were changed (ctx.files()) and that
1121 # match the file filtering conditions.
1124 # match the file filtering conditions.
1122
1125
1123 if not slowpath and not match.files():
1126 if not slowpath and not match.files():
1124 # No files, no patterns. Display all revs.
1127 # No files, no patterns. Display all revs.
1125 wanted = set(revs)
1128 wanted = set(revs)
1126 copies = []
1129 copies = []
1127
1130
1128 if not slowpath:
1131 if not slowpath:
1129 # We only have to read through the filelog to find wanted revisions
1132 # We only have to read through the filelog to find wanted revisions
1130
1133
1131 minrev, maxrev = min(revs), max(revs)
1134 minrev, maxrev = min(revs), max(revs)
1132 def filerevgen(filelog, last):
1135 def filerevgen(filelog, last):
1133 """
1136 """
1134 Only files, no patterns. Check the history of each file.
1137 Only files, no patterns. Check the history of each file.
1135
1138
1136 Examines filelog entries within minrev, maxrev linkrev range
1139 Examines filelog entries within minrev, maxrev linkrev range
1137 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1140 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1138 tuples in backwards order
1141 tuples in backwards order
1139 """
1142 """
1140 cl_count = len(repo)
1143 cl_count = len(repo)
1141 revs = []
1144 revs = []
1142 for j in xrange(0, last + 1):
1145 for j in xrange(0, last + 1):
1143 linkrev = filelog.linkrev(j)
1146 linkrev = filelog.linkrev(j)
1144 if linkrev < minrev:
1147 if linkrev < minrev:
1145 continue
1148 continue
1146 # only yield rev for which we have the changelog, it can
1149 # only yield rev for which we have the changelog, it can
1147 # happen while doing "hg log" during a pull or commit
1150 # happen while doing "hg log" during a pull or commit
1148 if linkrev >= cl_count:
1151 if linkrev >= cl_count:
1149 break
1152 break
1150
1153
1151 parentlinkrevs = []
1154 parentlinkrevs = []
1152 for p in filelog.parentrevs(j):
1155 for p in filelog.parentrevs(j):
1153 if p != nullrev:
1156 if p != nullrev:
1154 parentlinkrevs.append(filelog.linkrev(p))
1157 parentlinkrevs.append(filelog.linkrev(p))
1155 n = filelog.node(j)
1158 n = filelog.node(j)
1156 revs.append((linkrev, parentlinkrevs,
1159 revs.append((linkrev, parentlinkrevs,
1157 follow and filelog.renamed(n)))
1160 follow and filelog.renamed(n)))
1158
1161
1159 return reversed(revs)
1162 return reversed(revs)
1160 def iterfiles():
1163 def iterfiles():
1161 for filename in match.files():
1164 for filename in match.files():
1162 yield filename, None
1165 yield filename, None
1163 for filename_node in copies:
1166 for filename_node in copies:
1164 yield filename_node
1167 yield filename_node
1165 for file_, node in iterfiles():
1168 for file_, node in iterfiles():
1166 filelog = repo.file(file_)
1169 filelog = repo.file(file_)
1167 if not len(filelog):
1170 if not len(filelog):
1168 if node is None:
1171 if node is None:
1169 # A zero count may be a directory or deleted file, so
1172 # A zero count may be a directory or deleted file, so
1170 # try to find matching entries on the slow path.
1173 # try to find matching entries on the slow path.
1171 if follow:
1174 if follow:
1172 raise util.Abort(
1175 raise util.Abort(
1173 _('cannot follow nonexistent file: "%s"') % file_)
1176 _('cannot follow nonexistent file: "%s"') % file_)
1174 slowpath = True
1177 slowpath = True
1175 break
1178 break
1176 else:
1179 else:
1177 continue
1180 continue
1178
1181
1179 if node is None:
1182 if node is None:
1180 last = len(filelog) - 1
1183 last = len(filelog) - 1
1181 else:
1184 else:
1182 last = filelog.rev(node)
1185 last = filelog.rev(node)
1183
1186
1184
1187
1185 # keep track of all ancestors of the file
1188 # keep track of all ancestors of the file
1186 ancestors = set([filelog.linkrev(last)])
1189 ancestors = set([filelog.linkrev(last)])
1187
1190
1188 # iterate from latest to oldest revision
1191 # iterate from latest to oldest revision
1189 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1192 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1190 if not follow:
1193 if not follow:
1191 if rev > maxrev:
1194 if rev > maxrev:
1192 continue
1195 continue
1193 else:
1196 else:
1194 # Note that last might not be the first interesting
1197 # Note that last might not be the first interesting
1195 # rev to us:
1198 # rev to us:
1196 # if the file has been changed after maxrev, we'll
1199 # if the file has been changed after maxrev, we'll
1197 # have linkrev(last) > maxrev, and we still need
1200 # have linkrev(last) > maxrev, and we still need
1198 # to explore the file graph
1201 # to explore the file graph
1199 if rev not in ancestors:
1202 if rev not in ancestors:
1200 continue
1203 continue
1201 # XXX insert 1327 fix here
1204 # XXX insert 1327 fix here
1202 if flparentlinkrevs:
1205 if flparentlinkrevs:
1203 ancestors.update(flparentlinkrevs)
1206 ancestors.update(flparentlinkrevs)
1204
1207
1205 fncache.setdefault(rev, []).append(file_)
1208 fncache.setdefault(rev, []).append(file_)
1206 wanted.add(rev)
1209 wanted.add(rev)
1207 if copied:
1210 if copied:
1208 copies.append(copied)
1211 copies.append(copied)
1209 if slowpath:
1212 if slowpath:
1210 # We have to read the changelog to match filenames against
1213 # We have to read the changelog to match filenames against
1211 # changed files
1214 # changed files
1212
1215
1213 if follow:
1216 if follow:
1214 raise util.Abort(_('can only follow copies/renames for explicit '
1217 raise util.Abort(_('can only follow copies/renames for explicit '
1215 'filenames'))
1218 'filenames'))
1216
1219
1217 # The slow path checks files modified in every changeset.
1220 # The slow path checks files modified in every changeset.
1218 for i in sorted(revs):
1221 for i in sorted(revs):
1219 ctx = change(i)
1222 ctx = change(i)
1220 matches = filter(match, ctx.files())
1223 matches = filter(match, ctx.files())
1221 if matches:
1224 if matches:
1222 fncache[i] = matches
1225 fncache[i] = matches
1223 wanted.add(i)
1226 wanted.add(i)
1224
1227
1225 class followfilter(object):
1228 class followfilter(object):
1226 def __init__(self, onlyfirst=False):
1229 def __init__(self, onlyfirst=False):
1227 self.startrev = nullrev
1230 self.startrev = nullrev
1228 self.roots = set()
1231 self.roots = set()
1229 self.onlyfirst = onlyfirst
1232 self.onlyfirst = onlyfirst
1230
1233
1231 def match(self, rev):
1234 def match(self, rev):
1232 def realparents(rev):
1235 def realparents(rev):
1233 if self.onlyfirst:
1236 if self.onlyfirst:
1234 return repo.changelog.parentrevs(rev)[0:1]
1237 return repo.changelog.parentrevs(rev)[0:1]
1235 else:
1238 else:
1236 return filter(lambda x: x != nullrev,
1239 return filter(lambda x: x != nullrev,
1237 repo.changelog.parentrevs(rev))
1240 repo.changelog.parentrevs(rev))
1238
1241
1239 if self.startrev == nullrev:
1242 if self.startrev == nullrev:
1240 self.startrev = rev
1243 self.startrev = rev
1241 return True
1244 return True
1242
1245
1243 if rev > self.startrev:
1246 if rev > self.startrev:
1244 # forward: all descendants
1247 # forward: all descendants
1245 if not self.roots:
1248 if not self.roots:
1246 self.roots.add(self.startrev)
1249 self.roots.add(self.startrev)
1247 for parent in realparents(rev):
1250 for parent in realparents(rev):
1248 if parent in self.roots:
1251 if parent in self.roots:
1249 self.roots.add(rev)
1252 self.roots.add(rev)
1250 return True
1253 return True
1251 else:
1254 else:
1252 # backwards: all parents
1255 # backwards: all parents
1253 if not self.roots:
1256 if not self.roots:
1254 self.roots.update(realparents(self.startrev))
1257 self.roots.update(realparents(self.startrev))
1255 if rev in self.roots:
1258 if rev in self.roots:
1256 self.roots.remove(rev)
1259 self.roots.remove(rev)
1257 self.roots.update(realparents(rev))
1260 self.roots.update(realparents(rev))
1258 return True
1261 return True
1259
1262
1260 return False
1263 return False
1261
1264
1262 # it might be worthwhile to do this in the iterator if the rev range
1265 # it might be worthwhile to do this in the iterator if the rev range
1263 # is descending and the prune args are all within that range
1266 # is descending and the prune args are all within that range
1264 for rev in opts.get('prune', ()):
1267 for rev in opts.get('prune', ()):
1265 rev = repo.changelog.rev(repo.lookup(rev))
1268 rev = repo.changelog.rev(repo.lookup(rev))
1266 ff = followfilter()
1269 ff = followfilter()
1267 stop = min(revs[0], revs[-1])
1270 stop = min(revs[0], revs[-1])
1268 for x in xrange(rev, stop - 1, -1):
1271 for x in xrange(rev, stop - 1, -1):
1269 if ff.match(x):
1272 if ff.match(x):
1270 wanted.discard(x)
1273 wanted.discard(x)
1271
1274
1272 # Now that wanted is correctly initialized, we can iterate over the
1275 # Now that wanted is correctly initialized, we can iterate over the
1273 # revision range, yielding only revisions in wanted.
1276 # revision range, yielding only revisions in wanted.
1274 def iterate():
1277 def iterate():
1275 if follow and not match.files():
1278 if follow and not match.files():
1276 ff = followfilter(onlyfirst=opts.get('follow_first'))
1279 ff = followfilter(onlyfirst=opts.get('follow_first'))
1277 def want(rev):
1280 def want(rev):
1278 return ff.match(rev) and rev in wanted
1281 return ff.match(rev) and rev in wanted
1279 else:
1282 else:
1280 def want(rev):
1283 def want(rev):
1281 return rev in wanted
1284 return rev in wanted
1282
1285
1283 for i, window in increasing_windows(0, len(revs)):
1286 for i, window in increasing_windows(0, len(revs)):
1284 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1287 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1285 for rev in sorted(nrevs):
1288 for rev in sorted(nrevs):
1286 fns = fncache.get(rev)
1289 fns = fncache.get(rev)
1287 ctx = change(rev)
1290 ctx = change(rev)
1288 if not fns:
1291 if not fns:
1289 def fns_generator():
1292 def fns_generator():
1290 for f in ctx.files():
1293 for f in ctx.files():
1291 if match(f):
1294 if match(f):
1292 yield f
1295 yield f
1293 fns = fns_generator()
1296 fns = fns_generator()
1294 prepare(ctx, fns)
1297 prepare(ctx, fns)
1295 for rev in nrevs:
1298 for rev in nrevs:
1296 yield change(rev)
1299 yield change(rev)
1297 return iterate()
1300 return iterate()
1298
1301
1299 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1302 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1300 join = lambda f: os.path.join(prefix, f)
1303 join = lambda f: os.path.join(prefix, f)
1301 bad = []
1304 bad = []
1302 oldbad = match.bad
1305 oldbad = match.bad
1303 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1306 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1304 names = []
1307 names = []
1305 wctx = repo[None]
1308 wctx = repo[None]
1306 for f in repo.walk(match):
1309 for f in repo.walk(match):
1307 exact = match.exact(f)
1310 exact = match.exact(f)
1308 if exact or f not in repo.dirstate:
1311 if exact or f not in repo.dirstate:
1309 names.append(f)
1312 names.append(f)
1310 if ui.verbose or not exact:
1313 if ui.verbose or not exact:
1311 ui.status(_('adding %s\n') % match.rel(join(f)))
1314 ui.status(_('adding %s\n') % match.rel(join(f)))
1312
1315
1313 if listsubrepos:
1316 if listsubrepos:
1314 for subpath in wctx.substate:
1317 for subpath in wctx.substate:
1315 sub = wctx.sub(subpath)
1318 sub = wctx.sub(subpath)
1316 try:
1319 try:
1317 submatch = matchmod.narrowmatcher(subpath, match)
1320 submatch = matchmod.narrowmatcher(subpath, match)
1318 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1321 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1319 except error.LookupError:
1322 except error.LookupError:
1320 ui.status(_("skipping missing subrepository: %s\n")
1323 ui.status(_("skipping missing subrepository: %s\n")
1321 % join(subpath))
1324 % join(subpath))
1322
1325
1323 if not dryrun:
1326 if not dryrun:
1324 rejected = wctx.add(names, prefix)
1327 rejected = wctx.add(names, prefix)
1325 bad.extend(f for f in rejected if f in match.files())
1328 bad.extend(f for f in rejected if f in match.files())
1326 return bad
1329 return bad
1327
1330
1328 def commit(ui, repo, commitfunc, pats, opts):
1331 def commit(ui, repo, commitfunc, pats, opts):
1329 '''commit the specified files or all outstanding changes'''
1332 '''commit the specified files or all outstanding changes'''
1330 date = opts.get('date')
1333 date = opts.get('date')
1331 if date:
1334 if date:
1332 opts['date'] = util.parsedate(date)
1335 opts['date'] = util.parsedate(date)
1333 message = logmessage(opts)
1336 message = logmessage(opts)
1334
1337
1335 # extract addremove carefully -- this function can be called from a command
1338 # extract addremove carefully -- this function can be called from a command
1336 # that doesn't support addremove
1339 # that doesn't support addremove
1337 if opts.get('addremove'):
1340 if opts.get('addremove'):
1338 addremove(repo, pats, opts)
1341 addremove(repo, pats, opts)
1339
1342
1340 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1343 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1341
1344
1342 def commiteditor(repo, ctx, subs):
1345 def commiteditor(repo, ctx, subs):
1343 if ctx.description():
1346 if ctx.description():
1344 return ctx.description()
1347 return ctx.description()
1345 return commitforceeditor(repo, ctx, subs)
1348 return commitforceeditor(repo, ctx, subs)
1346
1349
1347 def commitforceeditor(repo, ctx, subs):
1350 def commitforceeditor(repo, ctx, subs):
1348 edittext = []
1351 edittext = []
1349 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1352 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1350 if ctx.description():
1353 if ctx.description():
1351 edittext.append(ctx.description())
1354 edittext.append(ctx.description())
1352 edittext.append("")
1355 edittext.append("")
1353 edittext.append("") # Empty line between message and comments.
1356 edittext.append("") # Empty line between message and comments.
1354 edittext.append(_("HG: Enter commit message."
1357 edittext.append(_("HG: Enter commit message."
1355 " Lines beginning with 'HG:' are removed."))
1358 " Lines beginning with 'HG:' are removed."))
1356 edittext.append(_("HG: Leave message empty to abort commit."))
1359 edittext.append(_("HG: Leave message empty to abort commit."))
1357 edittext.append("HG: --")
1360 edittext.append("HG: --")
1358 edittext.append(_("HG: user: %s") % ctx.user())
1361 edittext.append(_("HG: user: %s") % ctx.user())
1359 if ctx.p2():
1362 if ctx.p2():
1360 edittext.append(_("HG: branch merge"))
1363 edittext.append(_("HG: branch merge"))
1361 if ctx.branch():
1364 if ctx.branch():
1362 edittext.append(_("HG: branch '%s'") % ctx.branch())
1365 edittext.append(_("HG: branch '%s'") % ctx.branch())
1363 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1366 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1364 edittext.extend([_("HG: added %s") % f for f in added])
1367 edittext.extend([_("HG: added %s") % f for f in added])
1365 edittext.extend([_("HG: changed %s") % f for f in modified])
1368 edittext.extend([_("HG: changed %s") % f for f in modified])
1366 edittext.extend([_("HG: removed %s") % f for f in removed])
1369 edittext.extend([_("HG: removed %s") % f for f in removed])
1367 if not added and not modified and not removed:
1370 if not added and not modified and not removed:
1368 edittext.append(_("HG: no files changed"))
1371 edittext.append(_("HG: no files changed"))
1369 edittext.append("")
1372 edittext.append("")
1370 # run editor in the repository root
1373 # run editor in the repository root
1371 olddir = os.getcwd()
1374 olddir = os.getcwd()
1372 os.chdir(repo.root)
1375 os.chdir(repo.root)
1373 text = repo.ui.edit("\n".join(edittext), ctx.user())
1376 text = repo.ui.edit("\n".join(edittext), ctx.user())
1374 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1377 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1375 os.chdir(olddir)
1378 os.chdir(olddir)
1376
1379
1377 if not text.strip():
1380 if not text.strip():
1378 raise util.Abort(_("empty commit message"))
1381 raise util.Abort(_("empty commit message"))
1379
1382
1380 return text
1383 return text
@@ -1,4714 +1,4714 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
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 of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from lock import release
9 from lock import release
10 from i18n import _, gettext
10 from i18n import _, gettext
11 import os, re, sys, difflib, time, tempfile
11 import os, re, sys, difflib, time, tempfile
12 import hg, util, revlog, extensions, copies, error, bookmarks
12 import hg, util, revlog, extensions, copies, error, bookmarks
13 import patch, help, mdiff, url, encoding, templatekw, discovery
13 import patch, help, mdiff, url, encoding, templatekw, discovery
14 import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server
14 import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server
15 import merge as mergemod
15 import merge as mergemod
16 import minirst, revset
16 import minirst, revset
17 import dagparser
17 import dagparser
18
18
19 # Commands start here, listed alphabetically
19 # Commands start here, listed alphabetically
20
20
21 def add(ui, repo, *pats, **opts):
21 def add(ui, repo, *pats, **opts):
22 """add the specified files on the next commit
22 """add the specified files on the next commit
23
23
24 Schedule files to be version controlled and added to the
24 Schedule files to be version controlled and added to the
25 repository.
25 repository.
26
26
27 The files will be added to the repository at the next commit. To
27 The files will be added to the repository at the next commit. To
28 undo an add before that, see :hg:`forget`.
28 undo an add before that, see :hg:`forget`.
29
29
30 If no names are given, add all files to the repository.
30 If no names are given, add all files to the repository.
31
31
32 .. container:: verbose
32 .. container:: verbose
33
33
34 An example showing how new (unknown) files are added
34 An example showing how new (unknown) files are added
35 automatically by :hg:`add`::
35 automatically by :hg:`add`::
36
36
37 $ ls
37 $ ls
38 foo.c
38 foo.c
39 $ hg status
39 $ hg status
40 ? foo.c
40 ? foo.c
41 $ hg add
41 $ hg add
42 adding foo.c
42 adding foo.c
43 $ hg status
43 $ hg status
44 A foo.c
44 A foo.c
45
45
46 Returns 0 if all files are successfully added.
46 Returns 0 if all files are successfully added.
47 """
47 """
48
48
49 m = cmdutil.match(repo, pats, opts)
49 m = cmdutil.match(repo, pats, opts)
50 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
50 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
51 opts.get('subrepos'), prefix="")
51 opts.get('subrepos'), prefix="")
52 return rejected and 1 or 0
52 return rejected and 1 or 0
53
53
54 def addremove(ui, repo, *pats, **opts):
54 def addremove(ui, repo, *pats, **opts):
55 """add all new files, delete all missing files
55 """add all new files, delete all missing files
56
56
57 Add all new files and remove all missing files from the
57 Add all new files and remove all missing files from the
58 repository.
58 repository.
59
59
60 New files are ignored if they match any of the patterns in
60 New files are ignored if they match any of the patterns in
61 ``.hgignore``. As with add, these changes take effect at the next
61 ``.hgignore``. As with add, these changes take effect at the next
62 commit.
62 commit.
63
63
64 Use the -s/--similarity option to detect renamed files. With a
64 Use the -s/--similarity option to detect renamed files. With a
65 parameter greater than 0, this compares every removed file with
65 parameter greater than 0, this compares every removed file with
66 every added file and records those similar enough as renames. This
66 every added file and records those similar enough as renames. This
67 option takes a percentage between 0 (disabled) and 100 (files must
67 option takes a percentage between 0 (disabled) and 100 (files must
68 be identical) as its parameter. Detecting renamed files this way
68 be identical) as its parameter. Detecting renamed files this way
69 can be expensive. After using this option, :hg:`status -C` can be
69 can be expensive. After using this option, :hg:`status -C` can be
70 used to check which files were identified as moved or renamed.
70 used to check which files were identified as moved or renamed.
71
71
72 Returns 0 if all files are successfully added.
72 Returns 0 if all files are successfully added.
73 """
73 """
74 try:
74 try:
75 sim = float(opts.get('similarity') or 100)
75 sim = float(opts.get('similarity') or 100)
76 except ValueError:
76 except ValueError:
77 raise util.Abort(_('similarity must be a number'))
77 raise util.Abort(_('similarity must be a number'))
78 if sim < 0 or sim > 100:
78 if sim < 0 or sim > 100:
79 raise util.Abort(_('similarity must be between 0 and 100'))
79 raise util.Abort(_('similarity must be between 0 and 100'))
80 return cmdutil.addremove(repo, pats, opts, similarity=sim / 100.0)
80 return cmdutil.addremove(repo, pats, opts, similarity=sim / 100.0)
81
81
82 def annotate(ui, repo, *pats, **opts):
82 def annotate(ui, repo, *pats, **opts):
83 """show changeset information by line for each file
83 """show changeset information by line for each file
84
84
85 List changes in files, showing the revision id responsible for
85 List changes in files, showing the revision id responsible for
86 each line
86 each line
87
87
88 This command is useful for discovering when a change was made and
88 This command is useful for discovering when a change was made and
89 by whom.
89 by whom.
90
90
91 Without the -a/--text option, annotate will avoid processing files
91 Without the -a/--text option, annotate will avoid processing files
92 it detects as binary. With -a, annotate will annotate the file
92 it detects as binary. With -a, annotate will annotate the file
93 anyway, although the results will probably be neither useful
93 anyway, although the results will probably be neither useful
94 nor desirable.
94 nor desirable.
95
95
96 Returns 0 on success.
96 Returns 0 on success.
97 """
97 """
98 if opts.get('follow'):
98 if opts.get('follow'):
99 # --follow is deprecated and now just an alias for -f/--file
99 # --follow is deprecated and now just an alias for -f/--file
100 # to mimic the behavior of Mercurial before version 1.5
100 # to mimic the behavior of Mercurial before version 1.5
101 opts['file'] = 1
101 opts['file'] = 1
102
102
103 datefunc = ui.quiet and util.shortdate or util.datestr
103 datefunc = ui.quiet and util.shortdate or util.datestr
104 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
104 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
105
105
106 if not pats:
106 if not pats:
107 raise util.Abort(_('at least one filename or pattern is required'))
107 raise util.Abort(_('at least one filename or pattern is required'))
108
108
109 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
109 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
110 ('number', lambda x: str(x[0].rev())),
110 ('number', lambda x: str(x[0].rev())),
111 ('changeset', lambda x: short(x[0].node())),
111 ('changeset', lambda x: short(x[0].node())),
112 ('date', getdate),
112 ('date', getdate),
113 ('file', lambda x: x[0].path()),
113 ('file', lambda x: x[0].path()),
114 ]
114 ]
115
115
116 if (not opts.get('user') and not opts.get('changeset')
116 if (not opts.get('user') and not opts.get('changeset')
117 and not opts.get('date') and not opts.get('file')):
117 and not opts.get('date') and not opts.get('file')):
118 opts['number'] = 1
118 opts['number'] = 1
119
119
120 linenumber = opts.get('line_number') is not None
120 linenumber = opts.get('line_number') is not None
121 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
121 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
122 raise util.Abort(_('at least one of -n/-c is required for -l'))
122 raise util.Abort(_('at least one of -n/-c is required for -l'))
123
123
124 funcmap = [func for op, func in opmap if opts.get(op)]
124 funcmap = [func for op, func in opmap if opts.get(op)]
125 if linenumber:
125 if linenumber:
126 lastfunc = funcmap[-1]
126 lastfunc = funcmap[-1]
127 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
127 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
128
128
129 ctx = cmdutil.revsingle(repo, opts.get('rev'))
129 ctx = cmdutil.revsingle(repo, opts.get('rev'))
130 m = cmdutil.match(repo, pats, opts)
130 m = cmdutil.match(repo, pats, opts)
131 follow = not opts.get('no_follow')
131 follow = not opts.get('no_follow')
132 for abs in ctx.walk(m):
132 for abs in ctx.walk(m):
133 fctx = ctx[abs]
133 fctx = ctx[abs]
134 if not opts.get('text') and util.binary(fctx.data()):
134 if not opts.get('text') and util.binary(fctx.data()):
135 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
135 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
136 continue
136 continue
137
137
138 lines = fctx.annotate(follow=follow, linenumber=linenumber)
138 lines = fctx.annotate(follow=follow, linenumber=linenumber)
139 pieces = []
139 pieces = []
140
140
141 for f in funcmap:
141 for f in funcmap:
142 l = [f(n) for n, dummy in lines]
142 l = [f(n) for n, dummy in lines]
143 if l:
143 if l:
144 sized = [(x, encoding.colwidth(x)) for x in l]
144 sized = [(x, encoding.colwidth(x)) for x in l]
145 ml = max([w for x, w in sized])
145 ml = max([w for x, w in sized])
146 pieces.append(["%s%s" % (' ' * (ml - w), x) for x, w in sized])
146 pieces.append(["%s%s" % (' ' * (ml - w), x) for x, w in sized])
147
147
148 if pieces:
148 if pieces:
149 for p, l in zip(zip(*pieces), lines):
149 for p, l in zip(zip(*pieces), lines):
150 ui.write("%s: %s" % (" ".join(p), l[1]))
150 ui.write("%s: %s" % (" ".join(p), l[1]))
151
151
152 def archive(ui, repo, dest, **opts):
152 def archive(ui, repo, dest, **opts):
153 '''create an unversioned archive of a repository revision
153 '''create an unversioned archive of a repository revision
154
154
155 By default, the revision used is the parent of the working
155 By default, the revision used is the parent of the working
156 directory; use -r/--rev to specify a different revision.
156 directory; use -r/--rev to specify a different revision.
157
157
158 The archive type is automatically detected based on file
158 The archive type is automatically detected based on file
159 extension (or override using -t/--type).
159 extension (or override using -t/--type).
160
160
161 Valid types are:
161 Valid types are:
162
162
163 :``files``: a directory full of files (default)
163 :``files``: a directory full of files (default)
164 :``tar``: tar archive, uncompressed
164 :``tar``: tar archive, uncompressed
165 :``tbz2``: tar archive, compressed using bzip2
165 :``tbz2``: tar archive, compressed using bzip2
166 :``tgz``: tar archive, compressed using gzip
166 :``tgz``: tar archive, compressed using gzip
167 :``uzip``: zip archive, uncompressed
167 :``uzip``: zip archive, uncompressed
168 :``zip``: zip archive, compressed using deflate
168 :``zip``: zip archive, compressed using deflate
169
169
170 The exact name of the destination archive or directory is given
170 The exact name of the destination archive or directory is given
171 using a format string; see :hg:`help export` for details.
171 using a format string; see :hg:`help export` for details.
172
172
173 Each member added to an archive file has a directory prefix
173 Each member added to an archive file has a directory prefix
174 prepended. Use -p/--prefix to specify a format string for the
174 prepended. Use -p/--prefix to specify a format string for the
175 prefix. The default is the basename of the archive, with suffixes
175 prefix. The default is the basename of the archive, with suffixes
176 removed.
176 removed.
177
177
178 Returns 0 on success.
178 Returns 0 on success.
179 '''
179 '''
180
180
181 ctx = cmdutil.revsingle(repo, opts.get('rev'))
181 ctx = cmdutil.revsingle(repo, opts.get('rev'))
182 if not ctx:
182 if not ctx:
183 raise util.Abort(_('no working directory: please specify a revision'))
183 raise util.Abort(_('no working directory: please specify a revision'))
184 node = ctx.node()
184 node = ctx.node()
185 dest = cmdutil.make_filename(repo, dest, node)
185 dest = cmdutil.make_filename(repo, dest, node)
186 if os.path.realpath(dest) == repo.root:
186 if os.path.realpath(dest) == repo.root:
187 raise util.Abort(_('repository root cannot be destination'))
187 raise util.Abort(_('repository root cannot be destination'))
188
188
189 kind = opts.get('type') or archival.guesskind(dest) or 'files'
189 kind = opts.get('type') or archival.guesskind(dest) or 'files'
190 prefix = opts.get('prefix')
190 prefix = opts.get('prefix')
191
191
192 if dest == '-':
192 if dest == '-':
193 if kind == 'files':
193 if kind == 'files':
194 raise util.Abort(_('cannot archive plain files to stdout'))
194 raise util.Abort(_('cannot archive plain files to stdout'))
195 dest = sys.stdout
195 dest = sys.stdout
196 if not prefix:
196 if not prefix:
197 prefix = os.path.basename(repo.root) + '-%h'
197 prefix = os.path.basename(repo.root) + '-%h'
198
198
199 prefix = cmdutil.make_filename(repo, prefix, node)
199 prefix = cmdutil.make_filename(repo, prefix, node)
200 matchfn = cmdutil.match(repo, [], opts)
200 matchfn = cmdutil.match(repo, [], opts)
201 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
201 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
202 matchfn, prefix, subrepos=opts.get('subrepos'))
202 matchfn, prefix, subrepos=opts.get('subrepos'))
203
203
204 def backout(ui, repo, node=None, rev=None, **opts):
204 def backout(ui, repo, node=None, rev=None, **opts):
205 '''reverse effect of earlier changeset
205 '''reverse effect of earlier changeset
206
206
207 Prepare a new changeset with the effect of REV undone in the
207 Prepare a new changeset with the effect of REV undone in the
208 current working directory.
208 current working directory.
209
209
210 If REV is the parent of the working directory, then this changeset
210 If REV is the parent of the working directory, then this changeset
211 is committed automatically. Otherwise, hg needs to merge the
211 is committed automatically. Otherwise, hg needs to merge the
212 changes and the merged result is left uncommitted.
212 changes and the merged result is left uncommitted.
213
213
214 By default, the pending changeset will have one parent,
214 By default, the pending changeset will have one parent,
215 maintaining a linear history. With --merge, the pending changeset
215 maintaining a linear history. With --merge, the pending changeset
216 will instead have two parents: the old parent of the working
216 will instead have two parents: the old parent of the working
217 directory and a child of REV that simply undoes REV.
217 directory and a child of REV that simply undoes REV.
218
218
219 Before version 1.7, the default behavior was equivalent to
219 Before version 1.7, the default behavior was equivalent to
220 specifying --merge followed by :hg:`update --clean .` to cancel
220 specifying --merge followed by :hg:`update --clean .` to cancel
221 the merge and leave the child of REV as a head to be merged
221 the merge and leave the child of REV as a head to be merged
222 separately.
222 separately.
223
223
224 See :hg:`help dates` for a list of formats valid for -d/--date.
224 See :hg:`help dates` for a list of formats valid for -d/--date.
225
225
226 Returns 0 on success.
226 Returns 0 on success.
227 '''
227 '''
228 if rev and node:
228 if rev and node:
229 raise util.Abort(_("please specify just one revision"))
229 raise util.Abort(_("please specify just one revision"))
230
230
231 if not rev:
231 if not rev:
232 rev = node
232 rev = node
233
233
234 if not rev:
234 if not rev:
235 raise util.Abort(_("please specify a revision to backout"))
235 raise util.Abort(_("please specify a revision to backout"))
236
236
237 date = opts.get('date')
237 date = opts.get('date')
238 if date:
238 if date:
239 opts['date'] = util.parsedate(date)
239 opts['date'] = util.parsedate(date)
240
240
241 cmdutil.bail_if_changed(repo)
241 cmdutil.bail_if_changed(repo)
242 node = cmdutil.revsingle(repo, rev).node()
242 node = cmdutil.revsingle(repo, rev).node()
243
243
244 op1, op2 = repo.dirstate.parents()
244 op1, op2 = repo.dirstate.parents()
245 a = repo.changelog.ancestor(op1, node)
245 a = repo.changelog.ancestor(op1, node)
246 if a != node:
246 if a != node:
247 raise util.Abort(_('cannot backout change on a different branch'))
247 raise util.Abort(_('cannot backout change on a different branch'))
248
248
249 p1, p2 = repo.changelog.parents(node)
249 p1, p2 = repo.changelog.parents(node)
250 if p1 == nullid:
250 if p1 == nullid:
251 raise util.Abort(_('cannot backout a change with no parents'))
251 raise util.Abort(_('cannot backout a change with no parents'))
252 if p2 != nullid:
252 if p2 != nullid:
253 if not opts.get('parent'):
253 if not opts.get('parent'):
254 raise util.Abort(_('cannot backout a merge changeset without '
254 raise util.Abort(_('cannot backout a merge changeset without '
255 '--parent'))
255 '--parent'))
256 p = repo.lookup(opts['parent'])
256 p = repo.lookup(opts['parent'])
257 if p not in (p1, p2):
257 if p not in (p1, p2):
258 raise util.Abort(_('%s is not a parent of %s') %
258 raise util.Abort(_('%s is not a parent of %s') %
259 (short(p), short(node)))
259 (short(p), short(node)))
260 parent = p
260 parent = p
261 else:
261 else:
262 if opts.get('parent'):
262 if opts.get('parent'):
263 raise util.Abort(_('cannot use --parent on non-merge changeset'))
263 raise util.Abort(_('cannot use --parent on non-merge changeset'))
264 parent = p1
264 parent = p1
265
265
266 # the backout should appear on the same branch
266 # the backout should appear on the same branch
267 branch = repo.dirstate.branch()
267 branch = repo.dirstate.branch()
268 hg.clean(repo, node, show_stats=False)
268 hg.clean(repo, node, show_stats=False)
269 repo.dirstate.setbranch(branch)
269 repo.dirstate.setbranch(branch)
270 revert_opts = opts.copy()
270 revert_opts = opts.copy()
271 revert_opts['date'] = None
271 revert_opts['date'] = None
272 revert_opts['all'] = True
272 revert_opts['all'] = True
273 revert_opts['rev'] = hex(parent)
273 revert_opts['rev'] = hex(parent)
274 revert_opts['no_backup'] = None
274 revert_opts['no_backup'] = None
275 revert(ui, repo, **revert_opts)
275 revert(ui, repo, **revert_opts)
276 if not opts.get('merge') and op1 != node:
276 if not opts.get('merge') and op1 != node:
277 try:
277 try:
278 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
278 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
279 return hg.update(repo, op1)
279 return hg.update(repo, op1)
280 finally:
280 finally:
281 ui.setconfig('ui', 'forcemerge', '')
281 ui.setconfig('ui', 'forcemerge', '')
282
282
283 commit_opts = opts.copy()
283 commit_opts = opts.copy()
284 commit_opts['addremove'] = False
284 commit_opts['addremove'] = False
285 if not commit_opts['message'] and not commit_opts['logfile']:
285 if not commit_opts['message'] and not commit_opts['logfile']:
286 # we don't translate commit messages
286 # we don't translate commit messages
287 commit_opts['message'] = "Backed out changeset %s" % short(node)
287 commit_opts['message'] = "Backed out changeset %s" % short(node)
288 commit_opts['force_editor'] = True
288 commit_opts['force_editor'] = True
289 commit(ui, repo, **commit_opts)
289 commit(ui, repo, **commit_opts)
290 def nice(node):
290 def nice(node):
291 return '%d:%s' % (repo.changelog.rev(node), short(node))
291 return '%d:%s' % (repo.changelog.rev(node), short(node))
292 ui.status(_('changeset %s backs out changeset %s\n') %
292 ui.status(_('changeset %s backs out changeset %s\n') %
293 (nice(repo.changelog.tip()), nice(node)))
293 (nice(repo.changelog.tip()), nice(node)))
294 if opts.get('merge') and op1 != node:
294 if opts.get('merge') and op1 != node:
295 hg.clean(repo, op1, show_stats=False)
295 hg.clean(repo, op1, show_stats=False)
296 ui.status(_('merging with changeset %s\n')
296 ui.status(_('merging with changeset %s\n')
297 % nice(repo.changelog.tip()))
297 % nice(repo.changelog.tip()))
298 try:
298 try:
299 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
299 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
300 return hg.merge(repo, hex(repo.changelog.tip()))
300 return hg.merge(repo, hex(repo.changelog.tip()))
301 finally:
301 finally:
302 ui.setconfig('ui', 'forcemerge', '')
302 ui.setconfig('ui', 'forcemerge', '')
303 return 0
303 return 0
304
304
305 def bisect(ui, repo, rev=None, extra=None, command=None,
305 def bisect(ui, repo, rev=None, extra=None, command=None,
306 reset=None, good=None, bad=None, skip=None, noupdate=None):
306 reset=None, good=None, bad=None, skip=None, noupdate=None):
307 """subdivision search of changesets
307 """subdivision search of changesets
308
308
309 This command helps to find changesets which introduce problems. To
309 This command helps to find changesets which introduce problems. To
310 use, mark the earliest changeset you know exhibits the problem as
310 use, mark the earliest changeset you know exhibits the problem as
311 bad, then mark the latest changeset which is free from the problem
311 bad, then mark the latest changeset which is free from the problem
312 as good. Bisect will update your working directory to a revision
312 as good. Bisect will update your working directory to a revision
313 for testing (unless the -U/--noupdate option is specified). Once
313 for testing (unless the -U/--noupdate option is specified). Once
314 you have performed tests, mark the working directory as good or
314 you have performed tests, mark the working directory as good or
315 bad, and bisect will either update to another candidate changeset
315 bad, and bisect will either update to another candidate changeset
316 or announce that it has found the bad revision.
316 or announce that it has found the bad revision.
317
317
318 As a shortcut, you can also use the revision argument to mark a
318 As a shortcut, you can also use the revision argument to mark a
319 revision as good or bad without checking it out first.
319 revision as good or bad without checking it out first.
320
320
321 If you supply a command, it will be used for automatic bisection.
321 If you supply a command, it will be used for automatic bisection.
322 Its exit status will be used to mark revisions as good or bad:
322 Its exit status will be used to mark revisions as good or bad:
323 status 0 means good, 125 means to skip the revision, 127
323 status 0 means good, 125 means to skip the revision, 127
324 (command not found) will abort the bisection, and any other
324 (command not found) will abort the bisection, and any other
325 non-zero exit status means the revision is bad.
325 non-zero exit status means the revision is bad.
326
326
327 Returns 0 on success.
327 Returns 0 on success.
328 """
328 """
329 def print_result(nodes, good):
329 def print_result(nodes, good):
330 displayer = cmdutil.show_changeset(ui, repo, {})
330 displayer = cmdutil.show_changeset(ui, repo, {})
331 if len(nodes) == 1:
331 if len(nodes) == 1:
332 # narrowed it down to a single revision
332 # narrowed it down to a single revision
333 if good:
333 if good:
334 ui.write(_("The first good revision is:\n"))
334 ui.write(_("The first good revision is:\n"))
335 else:
335 else:
336 ui.write(_("The first bad revision is:\n"))
336 ui.write(_("The first bad revision is:\n"))
337 displayer.show(repo[nodes[0]])
337 displayer.show(repo[nodes[0]])
338 parents = repo[nodes[0]].parents()
338 parents = repo[nodes[0]].parents()
339 if len(parents) > 1:
339 if len(parents) > 1:
340 side = good and state['bad'] or state['good']
340 side = good and state['bad'] or state['good']
341 num = len(set(i.node() for i in parents) & set(side))
341 num = len(set(i.node() for i in parents) & set(side))
342 if num == 1:
342 if num == 1:
343 common = parents[0].ancestor(parents[1])
343 common = parents[0].ancestor(parents[1])
344 ui.write(_('Not all ancestors of this changeset have been'
344 ui.write(_('Not all ancestors of this changeset have been'
345 ' checked.\nTo check the other ancestors, start'
345 ' checked.\nTo check the other ancestors, start'
346 ' from the common ancestor, %s.\n' % common))
346 ' from the common ancestor, %s.\n' % common))
347 else:
347 else:
348 # multiple possible revisions
348 # multiple possible revisions
349 if good:
349 if good:
350 ui.write(_("Due to skipped revisions, the first "
350 ui.write(_("Due to skipped revisions, the first "
351 "good revision could be any of:\n"))
351 "good revision could be any of:\n"))
352 else:
352 else:
353 ui.write(_("Due to skipped revisions, the first "
353 ui.write(_("Due to skipped revisions, the first "
354 "bad revision could be any of:\n"))
354 "bad revision could be any of:\n"))
355 for n in nodes:
355 for n in nodes:
356 displayer.show(repo[n])
356 displayer.show(repo[n])
357 displayer.close()
357 displayer.close()
358
358
359 def check_state(state, interactive=True):
359 def check_state(state, interactive=True):
360 if not state['good'] or not state['bad']:
360 if not state['good'] or not state['bad']:
361 if (good or bad or skip or reset) and interactive:
361 if (good or bad or skip or reset) and interactive:
362 return
362 return
363 if not state['good']:
363 if not state['good']:
364 raise util.Abort(_('cannot bisect (no known good revisions)'))
364 raise util.Abort(_('cannot bisect (no known good revisions)'))
365 else:
365 else:
366 raise util.Abort(_('cannot bisect (no known bad revisions)'))
366 raise util.Abort(_('cannot bisect (no known bad revisions)'))
367 return True
367 return True
368
368
369 # backward compatibility
369 # backward compatibility
370 if rev in "good bad reset init".split():
370 if rev in "good bad reset init".split():
371 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
371 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
372 cmd, rev, extra = rev, extra, None
372 cmd, rev, extra = rev, extra, None
373 if cmd == "good":
373 if cmd == "good":
374 good = True
374 good = True
375 elif cmd == "bad":
375 elif cmd == "bad":
376 bad = True
376 bad = True
377 else:
377 else:
378 reset = True
378 reset = True
379 elif extra or good + bad + skip + reset + bool(command) > 1:
379 elif extra or good + bad + skip + reset + bool(command) > 1:
380 raise util.Abort(_('incompatible arguments'))
380 raise util.Abort(_('incompatible arguments'))
381
381
382 if reset:
382 if reset:
383 p = repo.join("bisect.state")
383 p = repo.join("bisect.state")
384 if os.path.exists(p):
384 if os.path.exists(p):
385 os.unlink(p)
385 os.unlink(p)
386 return
386 return
387
387
388 state = hbisect.load_state(repo)
388 state = hbisect.load_state(repo)
389
389
390 if command:
390 if command:
391 changesets = 1
391 changesets = 1
392 try:
392 try:
393 while changesets:
393 while changesets:
394 # update state
394 # update state
395 status = util.system(command)
395 status = util.system(command)
396 if status == 125:
396 if status == 125:
397 transition = "skip"
397 transition = "skip"
398 elif status == 0:
398 elif status == 0:
399 transition = "good"
399 transition = "good"
400 # status < 0 means process was killed
400 # status < 0 means process was killed
401 elif status == 127:
401 elif status == 127:
402 raise util.Abort(_("failed to execute %s") % command)
402 raise util.Abort(_("failed to execute %s") % command)
403 elif status < 0:
403 elif status < 0:
404 raise util.Abort(_("%s killed") % command)
404 raise util.Abort(_("%s killed") % command)
405 else:
405 else:
406 transition = "bad"
406 transition = "bad"
407 ctx = cmdutil.revsingle(repo, rev)
407 ctx = cmdutil.revsingle(repo, rev)
408 rev = None # clear for future iterations
408 rev = None # clear for future iterations
409 state[transition].append(ctx.node())
409 state[transition].append(ctx.node())
410 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
410 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
411 check_state(state, interactive=False)
411 check_state(state, interactive=False)
412 # bisect
412 # bisect
413 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
413 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
414 # update to next check
414 # update to next check
415 cmdutil.bail_if_changed(repo)
415 cmdutil.bail_if_changed(repo)
416 hg.clean(repo, nodes[0], show_stats=False)
416 hg.clean(repo, nodes[0], show_stats=False)
417 finally:
417 finally:
418 hbisect.save_state(repo, state)
418 hbisect.save_state(repo, state)
419 print_result(nodes, good)
419 print_result(nodes, good)
420 return
420 return
421
421
422 # update state
422 # update state
423
423
424 if rev:
424 if rev:
425 nodes = [repo.lookup(i) for i in cmdutil.revrange(repo, [rev])]
425 nodes = [repo.lookup(i) for i in cmdutil.revrange(repo, [rev])]
426 else:
426 else:
427 nodes = [repo.lookup('.')]
427 nodes = [repo.lookup('.')]
428
428
429 if good or bad or skip:
429 if good or bad or skip:
430 if good:
430 if good:
431 state['good'] += nodes
431 state['good'] += nodes
432 elif bad:
432 elif bad:
433 state['bad'] += nodes
433 state['bad'] += nodes
434 elif skip:
434 elif skip:
435 state['skip'] += nodes
435 state['skip'] += nodes
436 hbisect.save_state(repo, state)
436 hbisect.save_state(repo, state)
437
437
438 if not check_state(state):
438 if not check_state(state):
439 return
439 return
440
440
441 # actually bisect
441 # actually bisect
442 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
442 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
443 if changesets == 0:
443 if changesets == 0:
444 print_result(nodes, good)
444 print_result(nodes, good)
445 else:
445 else:
446 assert len(nodes) == 1 # only a single node can be tested next
446 assert len(nodes) == 1 # only a single node can be tested next
447 node = nodes[0]
447 node = nodes[0]
448 # compute the approximate number of remaining tests
448 # compute the approximate number of remaining tests
449 tests, size = 0, 2
449 tests, size = 0, 2
450 while size <= changesets:
450 while size <= changesets:
451 tests, size = tests + 1, size * 2
451 tests, size = tests + 1, size * 2
452 rev = repo.changelog.rev(node)
452 rev = repo.changelog.rev(node)
453 ui.write(_("Testing changeset %d:%s "
453 ui.write(_("Testing changeset %d:%s "
454 "(%d changesets remaining, ~%d tests)\n")
454 "(%d changesets remaining, ~%d tests)\n")
455 % (rev, short(node), changesets, tests))
455 % (rev, short(node), changesets, tests))
456 if not noupdate:
456 if not noupdate:
457 cmdutil.bail_if_changed(repo)
457 cmdutil.bail_if_changed(repo)
458 return hg.clean(repo, node)
458 return hg.clean(repo, node)
459
459
460 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
460 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
461 '''track a line of development with movable markers
461 '''track a line of development with movable markers
462
462
463 Bookmarks are pointers to certain commits that move when
463 Bookmarks are pointers to certain commits that move when
464 committing. Bookmarks are local. They can be renamed, copied and
464 committing. Bookmarks are local. They can be renamed, copied and
465 deleted. It is possible to use bookmark names in :hg:`merge` and
465 deleted. It is possible to use bookmark names in :hg:`merge` and
466 :hg:`update` to merge and update respectively to a given bookmark.
466 :hg:`update` to merge and update respectively to a given bookmark.
467
467
468 You can use :hg:`bookmark NAME` to set a bookmark on the working
468 You can use :hg:`bookmark NAME` to set a bookmark on the working
469 directory's parent revision with the given name. If you specify
469 directory's parent revision with the given name. If you specify
470 a revision using -r REV (where REV may be an existing bookmark),
470 a revision using -r REV (where REV may be an existing bookmark),
471 the bookmark is assigned to that revision.
471 the bookmark is assigned to that revision.
472
472
473 Bookmarks can be pushed and pulled between repositories (see :hg:`help
473 Bookmarks can be pushed and pulled between repositories (see :hg:`help
474 push` and :hg:`help pull`). This requires the bookmark extension to be
474 push` and :hg:`help pull`). This requires the bookmark extension to be
475 enabled for both the local and remote repositories.
475 enabled for both the local and remote repositories.
476 '''
476 '''
477 hexfn = ui.debugflag and hex or short
477 hexfn = ui.debugflag and hex or short
478 marks = repo._bookmarks
478 marks = repo._bookmarks
479 cur = repo.changectx('.').node()
479 cur = repo.changectx('.').node()
480
480
481 if rename:
481 if rename:
482 if rename not in marks:
482 if rename not in marks:
483 raise util.Abort(_("a bookmark of this name does not exist"))
483 raise util.Abort(_("a bookmark of this name does not exist"))
484 if mark in marks and not force:
484 if mark in marks and not force:
485 raise util.Abort(_("a bookmark of the same name already exists"))
485 raise util.Abort(_("a bookmark of the same name already exists"))
486 if mark is None:
486 if mark is None:
487 raise util.Abort(_("new bookmark name required"))
487 raise util.Abort(_("new bookmark name required"))
488 marks[mark] = marks[rename]
488 marks[mark] = marks[rename]
489 del marks[rename]
489 del marks[rename]
490 if repo._bookmarkcurrent == rename:
490 if repo._bookmarkcurrent == rename:
491 bookmarks.setcurrent(repo, mark)
491 bookmarks.setcurrent(repo, mark)
492 bookmarks.write(repo)
492 bookmarks.write(repo)
493 return
493 return
494
494
495 if delete:
495 if delete:
496 if mark is None:
496 if mark is None:
497 raise util.Abort(_("bookmark name required"))
497 raise util.Abort(_("bookmark name required"))
498 if mark not in marks:
498 if mark not in marks:
499 raise util.Abort(_("a bookmark of this name does not exist"))
499 raise util.Abort(_("a bookmark of this name does not exist"))
500 if mark == repo._bookmarkcurrent:
500 if mark == repo._bookmarkcurrent:
501 bookmarks.setcurrent(repo, None)
501 bookmarks.setcurrent(repo, None)
502 del marks[mark]
502 del marks[mark]
503 bookmarks.write(repo)
503 bookmarks.write(repo)
504 return
504 return
505
505
506 if mark is not None:
506 if mark is not None:
507 if "\n" in mark:
507 if "\n" in mark:
508 raise util.Abort(_("bookmark name cannot contain newlines"))
508 raise util.Abort(_("bookmark name cannot contain newlines"))
509 mark = mark.strip()
509 mark = mark.strip()
510 if not mark:
510 if not mark:
511 raise util.Abort(_("bookmark names cannot consist entirely of "
511 raise util.Abort(_("bookmark names cannot consist entirely of "
512 "whitespace"))
512 "whitespace"))
513 if mark in marks and not force:
513 if mark in marks and not force:
514 raise util.Abort(_("a bookmark of the same name already exists"))
514 raise util.Abort(_("a bookmark of the same name already exists"))
515 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
515 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
516 and not force):
516 and not force):
517 raise util.Abort(
517 raise util.Abort(
518 _("a bookmark cannot have the name of an existing branch"))
518 _("a bookmark cannot have the name of an existing branch"))
519 if rev:
519 if rev:
520 marks[mark] = repo.lookup(rev)
520 marks[mark] = repo.lookup(rev)
521 else:
521 else:
522 marks[mark] = repo.changectx('.').node()
522 marks[mark] = repo.changectx('.').node()
523 bookmarks.setcurrent(repo, mark)
523 bookmarks.setcurrent(repo, mark)
524 bookmarks.write(repo)
524 bookmarks.write(repo)
525 return
525 return
526
526
527 if mark is None:
527 if mark is None:
528 if rev:
528 if rev:
529 raise util.Abort(_("bookmark name required"))
529 raise util.Abort(_("bookmark name required"))
530 if len(marks) == 0:
530 if len(marks) == 0:
531 ui.status(_("no bookmarks set\n"))
531 ui.status(_("no bookmarks set\n"))
532 else:
532 else:
533 for bmark, n in marks.iteritems():
533 for bmark, n in sorted(marks.iteritems()):
534 if ui.configbool('bookmarks', 'track.current'):
534 if ui.configbool('bookmarks', 'track.current'):
535 current = repo._bookmarkcurrent
535 current = repo._bookmarkcurrent
536 if bmark == current and n == cur:
536 if bmark == current and n == cur:
537 prefix, label = '*', 'bookmarks.current'
537 prefix, label = '*', 'bookmarks.current'
538 else:
538 else:
539 prefix, label = ' ', ''
539 prefix, label = ' ', ''
540 else:
540 else:
541 if n == cur:
541 if n == cur:
542 prefix, label = '*', 'bookmarks.current'
542 prefix, label = '*', 'bookmarks.current'
543 else:
543 else:
544 prefix, label = ' ', ''
544 prefix, label = ' ', ''
545
545
546 if ui.quiet:
546 if ui.quiet:
547 ui.write("%s\n" % bmark, label=label)
547 ui.write("%s\n" % bmark, label=label)
548 else:
548 else:
549 ui.write(" %s %-25s %d:%s\n" % (
549 ui.write(" %s %-25s %d:%s\n" % (
550 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
550 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
551 label=label)
551 label=label)
552 return
552 return
553
553
554 def branch(ui, repo, label=None, **opts):
554 def branch(ui, repo, label=None, **opts):
555 """set or show the current branch name
555 """set or show the current branch name
556
556
557 With no argument, show the current branch name. With one argument,
557 With no argument, show the current branch name. With one argument,
558 set the working directory branch name (the branch will not exist
558 set the working directory branch name (the branch will not exist
559 in the repository until the next commit). Standard practice
559 in the repository until the next commit). Standard practice
560 recommends that primary development take place on the 'default'
560 recommends that primary development take place on the 'default'
561 branch.
561 branch.
562
562
563 Unless -f/--force is specified, branch will not let you set a
563 Unless -f/--force is specified, branch will not let you set a
564 branch name that already exists, even if it's inactive.
564 branch name that already exists, even if it's inactive.
565
565
566 Use -C/--clean to reset the working directory branch to that of
566 Use -C/--clean to reset the working directory branch to that of
567 the parent of the working directory, negating a previous branch
567 the parent of the working directory, negating a previous branch
568 change.
568 change.
569
569
570 Use the command :hg:`update` to switch to an existing branch. Use
570 Use the command :hg:`update` to switch to an existing branch. Use
571 :hg:`commit --close-branch` to mark this branch as closed.
571 :hg:`commit --close-branch` to mark this branch as closed.
572
572
573 Returns 0 on success.
573 Returns 0 on success.
574 """
574 """
575
575
576 if opts.get('clean'):
576 if opts.get('clean'):
577 label = repo[None].parents()[0].branch()
577 label = repo[None].parents()[0].branch()
578 repo.dirstate.setbranch(label)
578 repo.dirstate.setbranch(label)
579 ui.status(_('reset working directory to branch %s\n') % label)
579 ui.status(_('reset working directory to branch %s\n') % label)
580 elif label:
580 elif label:
581 if not opts.get('force') and label in repo.branchtags():
581 if not opts.get('force') and label in repo.branchtags():
582 if label not in [p.branch() for p in repo.parents()]:
582 if label not in [p.branch() for p in repo.parents()]:
583 raise util.Abort(_('a branch of the same name already exists'
583 raise util.Abort(_('a branch of the same name already exists'
584 " (use 'hg update' to switch to it)"))
584 " (use 'hg update' to switch to it)"))
585 repo.dirstate.setbranch(label)
585 repo.dirstate.setbranch(label)
586 ui.status(_('marked working directory as branch %s\n') % label)
586 ui.status(_('marked working directory as branch %s\n') % label)
587 else:
587 else:
588 ui.write("%s\n" % repo.dirstate.branch())
588 ui.write("%s\n" % repo.dirstate.branch())
589
589
590 def branches(ui, repo, active=False, closed=False):
590 def branches(ui, repo, active=False, closed=False):
591 """list repository named branches
591 """list repository named branches
592
592
593 List the repository's named branches, indicating which ones are
593 List the repository's named branches, indicating which ones are
594 inactive. If -c/--closed is specified, also list branches which have
594 inactive. If -c/--closed is specified, also list branches which have
595 been marked closed (see :hg:`commit --close-branch`).
595 been marked closed (see :hg:`commit --close-branch`).
596
596
597 If -a/--active is specified, only show active branches. A branch
597 If -a/--active is specified, only show active branches. A branch
598 is considered active if it contains repository heads.
598 is considered active if it contains repository heads.
599
599
600 Use the command :hg:`update` to switch to an existing branch.
600 Use the command :hg:`update` to switch to an existing branch.
601
601
602 Returns 0.
602 Returns 0.
603 """
603 """
604
604
605 hexfunc = ui.debugflag and hex or short
605 hexfunc = ui.debugflag and hex or short
606 activebranches = [repo[n].branch() for n in repo.heads()]
606 activebranches = [repo[n].branch() for n in repo.heads()]
607 def testactive(tag, node):
607 def testactive(tag, node):
608 realhead = tag in activebranches
608 realhead = tag in activebranches
609 open = node in repo.branchheads(tag, closed=False)
609 open = node in repo.branchheads(tag, closed=False)
610 return realhead and open
610 return realhead and open
611 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
611 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
612 for tag, node in repo.branchtags().items()],
612 for tag, node in repo.branchtags().items()],
613 reverse=True)
613 reverse=True)
614
614
615 for isactive, node, tag in branches:
615 for isactive, node, tag in branches:
616 if (not active) or isactive:
616 if (not active) or isactive:
617 if ui.quiet:
617 if ui.quiet:
618 ui.write("%s\n" % tag)
618 ui.write("%s\n" % tag)
619 else:
619 else:
620 hn = repo.lookup(node)
620 hn = repo.lookup(node)
621 if isactive:
621 if isactive:
622 label = 'branches.active'
622 label = 'branches.active'
623 notice = ''
623 notice = ''
624 elif hn not in repo.branchheads(tag, closed=False):
624 elif hn not in repo.branchheads(tag, closed=False):
625 if not closed:
625 if not closed:
626 continue
626 continue
627 label = 'branches.closed'
627 label = 'branches.closed'
628 notice = _(' (closed)')
628 notice = _(' (closed)')
629 else:
629 else:
630 label = 'branches.inactive'
630 label = 'branches.inactive'
631 notice = _(' (inactive)')
631 notice = _(' (inactive)')
632 if tag == repo.dirstate.branch():
632 if tag == repo.dirstate.branch():
633 label = 'branches.current'
633 label = 'branches.current'
634 rev = str(node).rjust(31 - encoding.colwidth(tag))
634 rev = str(node).rjust(31 - encoding.colwidth(tag))
635 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
635 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
636 tag = ui.label(tag, label)
636 tag = ui.label(tag, label)
637 ui.write("%s %s%s\n" % (tag, rev, notice))
637 ui.write("%s %s%s\n" % (tag, rev, notice))
638
638
639 def bundle(ui, repo, fname, dest=None, **opts):
639 def bundle(ui, repo, fname, dest=None, **opts):
640 """create a changegroup file
640 """create a changegroup file
641
641
642 Generate a compressed changegroup file collecting changesets not
642 Generate a compressed changegroup file collecting changesets not
643 known to be in another repository.
643 known to be in another repository.
644
644
645 If you omit the destination repository, then hg assumes the
645 If you omit the destination repository, then hg assumes the
646 destination will have all the nodes you specify with --base
646 destination will have all the nodes you specify with --base
647 parameters. To create a bundle containing all changesets, use
647 parameters. To create a bundle containing all changesets, use
648 -a/--all (or --base null).
648 -a/--all (or --base null).
649
649
650 You can change compression method with the -t/--type option.
650 You can change compression method with the -t/--type option.
651 The available compression methods are: none, bzip2, and
651 The available compression methods are: none, bzip2, and
652 gzip (by default, bundles are compressed using bzip2).
652 gzip (by default, bundles are compressed using bzip2).
653
653
654 The bundle file can then be transferred using conventional means
654 The bundle file can then be transferred using conventional means
655 and applied to another repository with the unbundle or pull
655 and applied to another repository with the unbundle or pull
656 command. This is useful when direct push and pull are not
656 command. This is useful when direct push and pull are not
657 available or when exporting an entire repository is undesirable.
657 available or when exporting an entire repository is undesirable.
658
658
659 Applying bundles preserves all changeset contents including
659 Applying bundles preserves all changeset contents including
660 permissions, copy/rename information, and revision history.
660 permissions, copy/rename information, and revision history.
661
661
662 Returns 0 on success, 1 if no changes found.
662 Returns 0 on success, 1 if no changes found.
663 """
663 """
664 revs = None
664 revs = None
665 if 'rev' in opts:
665 if 'rev' in opts:
666 revs = cmdutil.revrange(repo, opts['rev'])
666 revs = cmdutil.revrange(repo, opts['rev'])
667
667
668 if opts.get('all'):
668 if opts.get('all'):
669 base = ['null']
669 base = ['null']
670 else:
670 else:
671 base = cmdutil.revrange(repo, opts.get('base'))
671 base = cmdutil.revrange(repo, opts.get('base'))
672 if base:
672 if base:
673 if dest:
673 if dest:
674 raise util.Abort(_("--base is incompatible with specifying "
674 raise util.Abort(_("--base is incompatible with specifying "
675 "a destination"))
675 "a destination"))
676 base = [repo.lookup(rev) for rev in base]
676 base = [repo.lookup(rev) for rev in base]
677 # create the right base
677 # create the right base
678 # XXX: nodesbetween / changegroup* should be "fixed" instead
678 # XXX: nodesbetween / changegroup* should be "fixed" instead
679 o = []
679 o = []
680 has = set((nullid,))
680 has = set((nullid,))
681 for n in base:
681 for n in base:
682 has.update(repo.changelog.reachable(n))
682 has.update(repo.changelog.reachable(n))
683 if revs:
683 if revs:
684 revs = [repo.lookup(rev) for rev in revs]
684 revs = [repo.lookup(rev) for rev in revs]
685 visit = revs[:]
685 visit = revs[:]
686 has.difference_update(visit)
686 has.difference_update(visit)
687 else:
687 else:
688 visit = repo.changelog.heads()
688 visit = repo.changelog.heads()
689 seen = {}
689 seen = {}
690 while visit:
690 while visit:
691 n = visit.pop(0)
691 n = visit.pop(0)
692 parents = [p for p in repo.changelog.parents(n) if p not in has]
692 parents = [p for p in repo.changelog.parents(n) if p not in has]
693 if len(parents) == 0:
693 if len(parents) == 0:
694 if n not in has:
694 if n not in has:
695 o.append(n)
695 o.append(n)
696 else:
696 else:
697 for p in parents:
697 for p in parents:
698 if p not in seen:
698 if p not in seen:
699 seen[p] = 1
699 seen[p] = 1
700 visit.append(p)
700 visit.append(p)
701 else:
701 else:
702 dest = ui.expandpath(dest or 'default-push', dest or 'default')
702 dest = ui.expandpath(dest or 'default-push', dest or 'default')
703 dest, branches = hg.parseurl(dest, opts.get('branch'))
703 dest, branches = hg.parseurl(dest, opts.get('branch'))
704 other = hg.repository(hg.remoteui(repo, opts), dest)
704 other = hg.repository(hg.remoteui(repo, opts), dest)
705 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
705 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
706 if revs:
706 if revs:
707 revs = [repo.lookup(rev) for rev in revs]
707 revs = [repo.lookup(rev) for rev in revs]
708 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
708 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
709
709
710 if not o:
710 if not o:
711 ui.status(_("no changes found\n"))
711 ui.status(_("no changes found\n"))
712 return 1
712 return 1
713
713
714 if revs:
714 if revs:
715 cg = repo.changegroupsubset(o, revs, 'bundle')
715 cg = repo.changegroupsubset(o, revs, 'bundle')
716 else:
716 else:
717 cg = repo.changegroup(o, 'bundle')
717 cg = repo.changegroup(o, 'bundle')
718
718
719 bundletype = opts.get('type', 'bzip2').lower()
719 bundletype = opts.get('type', 'bzip2').lower()
720 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
720 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
721 bundletype = btypes.get(bundletype)
721 bundletype = btypes.get(bundletype)
722 if bundletype not in changegroup.bundletypes:
722 if bundletype not in changegroup.bundletypes:
723 raise util.Abort(_('unknown bundle type specified with --type'))
723 raise util.Abort(_('unknown bundle type specified with --type'))
724
724
725 changegroup.writebundle(cg, fname, bundletype)
725 changegroup.writebundle(cg, fname, bundletype)
726
726
727 def cat(ui, repo, file1, *pats, **opts):
727 def cat(ui, repo, file1, *pats, **opts):
728 """output the current or given revision of files
728 """output the current or given revision of files
729
729
730 Print the specified files as they were at the given revision. If
730 Print the specified files as they were at the given revision. If
731 no revision is given, the parent of the working directory is used,
731 no revision is given, the parent of the working directory is used,
732 or tip if no revision is checked out.
732 or tip if no revision is checked out.
733
733
734 Output may be to a file, in which case the name of the file is
734 Output may be to a file, in which case the name of the file is
735 given using a format string. The formatting rules are the same as
735 given using a format string. The formatting rules are the same as
736 for the export command, with the following additions:
736 for the export command, with the following additions:
737
737
738 :``%s``: basename of file being printed
738 :``%s``: basename of file being printed
739 :``%d``: dirname of file being printed, or '.' if in repository root
739 :``%d``: dirname of file being printed, or '.' if in repository root
740 :``%p``: root-relative path name of file being printed
740 :``%p``: root-relative path name of file being printed
741
741
742 Returns 0 on success.
742 Returns 0 on success.
743 """
743 """
744 ctx = cmdutil.revsingle(repo, opts.get('rev'))
744 ctx = cmdutil.revsingle(repo, opts.get('rev'))
745 err = 1
745 err = 1
746 m = cmdutil.match(repo, (file1,) + pats, opts)
746 m = cmdutil.match(repo, (file1,) + pats, opts)
747 for abs in ctx.walk(m):
747 for abs in ctx.walk(m):
748 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
748 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
749 data = ctx[abs].data()
749 data = ctx[abs].data()
750 if opts.get('decode'):
750 if opts.get('decode'):
751 data = repo.wwritedata(abs, data)
751 data = repo.wwritedata(abs, data)
752 fp.write(data)
752 fp.write(data)
753 err = 0
753 err = 0
754 return err
754 return err
755
755
756 def clone(ui, source, dest=None, **opts):
756 def clone(ui, source, dest=None, **opts):
757 """make a copy of an existing repository
757 """make a copy of an existing repository
758
758
759 Create a copy of an existing repository in a new directory.
759 Create a copy of an existing repository in a new directory.
760
760
761 If no destination directory name is specified, it defaults to the
761 If no destination directory name is specified, it defaults to the
762 basename of the source.
762 basename of the source.
763
763
764 The location of the source is added to the new repository's
764 The location of the source is added to the new repository's
765 ``.hg/hgrc`` file, as the default to be used for future pulls.
765 ``.hg/hgrc`` file, as the default to be used for future pulls.
766
766
767 See :hg:`help urls` for valid source format details.
767 See :hg:`help urls` for valid source format details.
768
768
769 It is possible to specify an ``ssh://`` URL as the destination, but no
769 It is possible to specify an ``ssh://`` URL as the destination, but no
770 ``.hg/hgrc`` and working directory will be created on the remote side.
770 ``.hg/hgrc`` and working directory will be created on the remote side.
771 Please see :hg:`help urls` for important details about ``ssh://`` URLs.
771 Please see :hg:`help urls` for important details about ``ssh://`` URLs.
772
772
773 A set of changesets (tags, or branch names) to pull may be specified
773 A set of changesets (tags, or branch names) to pull may be specified
774 by listing each changeset (tag, or branch name) with -r/--rev.
774 by listing each changeset (tag, or branch name) with -r/--rev.
775 If -r/--rev is used, the cloned repository will contain only a subset
775 If -r/--rev is used, the cloned repository will contain only a subset
776 of the changesets of the source repository. Only the set of changesets
776 of the changesets of the source repository. Only the set of changesets
777 defined by all -r/--rev options (including all their ancestors)
777 defined by all -r/--rev options (including all their ancestors)
778 will be pulled into the destination repository.
778 will be pulled into the destination repository.
779 No subsequent changesets (including subsequent tags) will be present
779 No subsequent changesets (including subsequent tags) will be present
780 in the destination.
780 in the destination.
781
781
782 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
782 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
783 local source repositories.
783 local source repositories.
784
784
785 For efficiency, hardlinks are used for cloning whenever the source
785 For efficiency, hardlinks are used for cloning whenever the source
786 and destination are on the same filesystem (note this applies only
786 and destination are on the same filesystem (note this applies only
787 to the repository data, not to the working directory). Some
787 to the repository data, not to the working directory). Some
788 filesystems, such as AFS, implement hardlinking incorrectly, but
788 filesystems, such as AFS, implement hardlinking incorrectly, but
789 do not report errors. In these cases, use the --pull option to
789 do not report errors. In these cases, use the --pull option to
790 avoid hardlinking.
790 avoid hardlinking.
791
791
792 In some cases, you can clone repositories and the working directory
792 In some cases, you can clone repositories and the working directory
793 using full hardlinks with ::
793 using full hardlinks with ::
794
794
795 $ cp -al REPO REPOCLONE
795 $ cp -al REPO REPOCLONE
796
796
797 This is the fastest way to clone, but it is not always safe. The
797 This is the fastest way to clone, but it is not always safe. The
798 operation is not atomic (making sure REPO is not modified during
798 operation is not atomic (making sure REPO is not modified during
799 the operation is up to you) and you have to make sure your editor
799 the operation is up to you) and you have to make sure your editor
800 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
800 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
801 this is not compatible with certain extensions that place their
801 this is not compatible with certain extensions that place their
802 metadata under the .hg directory, such as mq.
802 metadata under the .hg directory, such as mq.
803
803
804 Mercurial will update the working directory to the first applicable
804 Mercurial will update the working directory to the first applicable
805 revision from this list:
805 revision from this list:
806
806
807 a) null if -U or the source repository has no changesets
807 a) null if -U or the source repository has no changesets
808 b) if -u . and the source repository is local, the first parent of
808 b) if -u . and the source repository is local, the first parent of
809 the source repository's working directory
809 the source repository's working directory
810 c) the changeset specified with -u (if a branch name, this means the
810 c) the changeset specified with -u (if a branch name, this means the
811 latest head of that branch)
811 latest head of that branch)
812 d) the changeset specified with -r
812 d) the changeset specified with -r
813 e) the tipmost head specified with -b
813 e) the tipmost head specified with -b
814 f) the tipmost head specified with the url#branch source syntax
814 f) the tipmost head specified with the url#branch source syntax
815 g) the tipmost head of the default branch
815 g) the tipmost head of the default branch
816 h) tip
816 h) tip
817
817
818 Returns 0 on success.
818 Returns 0 on success.
819 """
819 """
820 if opts.get('noupdate') and opts.get('updaterev'):
820 if opts.get('noupdate') and opts.get('updaterev'):
821 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
821 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
822
822
823 r = hg.clone(hg.remoteui(ui, opts), source, dest,
823 r = hg.clone(hg.remoteui(ui, opts), source, dest,
824 pull=opts.get('pull'),
824 pull=opts.get('pull'),
825 stream=opts.get('uncompressed'),
825 stream=opts.get('uncompressed'),
826 rev=opts.get('rev'),
826 rev=opts.get('rev'),
827 update=opts.get('updaterev') or not opts.get('noupdate'),
827 update=opts.get('updaterev') or not opts.get('noupdate'),
828 branch=opts.get('branch'))
828 branch=opts.get('branch'))
829
829
830 return r is None
830 return r is None
831
831
832 def commit(ui, repo, *pats, **opts):
832 def commit(ui, repo, *pats, **opts):
833 """commit the specified files or all outstanding changes
833 """commit the specified files or all outstanding changes
834
834
835 Commit changes to the given files into the repository. Unlike a
835 Commit changes to the given files into the repository. Unlike a
836 centralized SCM, this operation is a local operation. See
836 centralized SCM, this operation is a local operation. See
837 :hg:`push` for a way to actively distribute your changes.
837 :hg:`push` for a way to actively distribute your changes.
838
838
839 If a list of files is omitted, all changes reported by :hg:`status`
839 If a list of files is omitted, all changes reported by :hg:`status`
840 will be committed.
840 will be committed.
841
841
842 If you are committing the result of a merge, do not provide any
842 If you are committing the result of a merge, do not provide any
843 filenames or -I/-X filters.
843 filenames or -I/-X filters.
844
844
845 If no commit message is specified, Mercurial starts your
845 If no commit message is specified, Mercurial starts your
846 configured editor where you can enter a message. In case your
846 configured editor where you can enter a message. In case your
847 commit fails, you will find a backup of your message in
847 commit fails, you will find a backup of your message in
848 ``.hg/last-message.txt``.
848 ``.hg/last-message.txt``.
849
849
850 See :hg:`help dates` for a list of formats valid for -d/--date.
850 See :hg:`help dates` for a list of formats valid for -d/--date.
851
851
852 Returns 0 on success, 1 if nothing changed.
852 Returns 0 on success, 1 if nothing changed.
853 """
853 """
854 extra = {}
854 extra = {}
855 if opts.get('close_branch'):
855 if opts.get('close_branch'):
856 if repo['.'].node() not in repo.branchheads():
856 if repo['.'].node() not in repo.branchheads():
857 # The topo heads set is included in the branch heads set of the
857 # The topo heads set is included in the branch heads set of the
858 # current branch, so it's sufficient to test branchheads
858 # current branch, so it's sufficient to test branchheads
859 raise util.Abort(_('can only close branch heads'))
859 raise util.Abort(_('can only close branch heads'))
860 extra['close'] = 1
860 extra['close'] = 1
861 e = cmdutil.commiteditor
861 e = cmdutil.commiteditor
862 if opts.get('force_editor'):
862 if opts.get('force_editor'):
863 e = cmdutil.commitforceeditor
863 e = cmdutil.commitforceeditor
864
864
865 def commitfunc(ui, repo, message, match, opts):
865 def commitfunc(ui, repo, message, match, opts):
866 return repo.commit(message, opts.get('user'), opts.get('date'), match,
866 return repo.commit(message, opts.get('user'), opts.get('date'), match,
867 editor=e, extra=extra)
867 editor=e, extra=extra)
868
868
869 branch = repo[None].branch()
869 branch = repo[None].branch()
870 bheads = repo.branchheads(branch)
870 bheads = repo.branchheads(branch)
871
871
872 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
872 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
873 if not node:
873 if not node:
874 ui.status(_("nothing changed\n"))
874 ui.status(_("nothing changed\n"))
875 return 1
875 return 1
876
876
877 ctx = repo[node]
877 ctx = repo[node]
878 parents = ctx.parents()
878 parents = ctx.parents()
879
879
880 if bheads and not [x for x in parents
880 if bheads and not [x for x in parents
881 if x.node() in bheads and x.branch() == branch]:
881 if x.node() in bheads and x.branch() == branch]:
882 ui.status(_('created new head\n'))
882 ui.status(_('created new head\n'))
883 # The message is not printed for initial roots. For the other
883 # The message is not printed for initial roots. For the other
884 # changesets, it is printed in the following situations:
884 # changesets, it is printed in the following situations:
885 #
885 #
886 # Par column: for the 2 parents with ...
886 # Par column: for the 2 parents with ...
887 # N: null or no parent
887 # N: null or no parent
888 # B: parent is on another named branch
888 # B: parent is on another named branch
889 # C: parent is a regular non head changeset
889 # C: parent is a regular non head changeset
890 # H: parent was a branch head of the current branch
890 # H: parent was a branch head of the current branch
891 # Msg column: whether we print "created new head" message
891 # Msg column: whether we print "created new head" message
892 # In the following, it is assumed that there already exists some
892 # In the following, it is assumed that there already exists some
893 # initial branch heads of the current branch, otherwise nothing is
893 # initial branch heads of the current branch, otherwise nothing is
894 # printed anyway.
894 # printed anyway.
895 #
895 #
896 # Par Msg Comment
896 # Par Msg Comment
897 # NN y additional topo root
897 # NN y additional topo root
898 #
898 #
899 # BN y additional branch root
899 # BN y additional branch root
900 # CN y additional topo head
900 # CN y additional topo head
901 # HN n usual case
901 # HN n usual case
902 #
902 #
903 # BB y weird additional branch root
903 # BB y weird additional branch root
904 # CB y branch merge
904 # CB y branch merge
905 # HB n merge with named branch
905 # HB n merge with named branch
906 #
906 #
907 # CC y additional head from merge
907 # CC y additional head from merge
908 # CH n merge with a head
908 # CH n merge with a head
909 #
909 #
910 # HH n head merge: head count decreases
910 # HH n head merge: head count decreases
911
911
912 if not opts.get('close_branch'):
912 if not opts.get('close_branch'):
913 for r in parents:
913 for r in parents:
914 if r.extra().get('close') and r.branch() == branch:
914 if r.extra().get('close') and r.branch() == branch:
915 ui.status(_('reopening closed branch head %d\n') % r)
915 ui.status(_('reopening closed branch head %d\n') % r)
916
916
917 if ui.debugflag:
917 if ui.debugflag:
918 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
918 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
919 elif ui.verbose:
919 elif ui.verbose:
920 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
920 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
921
921
922 def copy(ui, repo, *pats, **opts):
922 def copy(ui, repo, *pats, **opts):
923 """mark files as copied for the next commit
923 """mark files as copied for the next commit
924
924
925 Mark dest as having copies of source files. If dest is a
925 Mark dest as having copies of source files. If dest is a
926 directory, copies are put in that directory. If dest is a file,
926 directory, copies are put in that directory. If dest is a file,
927 the source must be a single file.
927 the source must be a single file.
928
928
929 By default, this command copies the contents of files as they
929 By default, this command copies the contents of files as they
930 exist in the working directory. If invoked with -A/--after, the
930 exist in the working directory. If invoked with -A/--after, the
931 operation is recorded, but no copying is performed.
931 operation is recorded, but no copying is performed.
932
932
933 This command takes effect with the next commit. To undo a copy
933 This command takes effect with the next commit. To undo a copy
934 before that, see :hg:`revert`.
934 before that, see :hg:`revert`.
935
935
936 Returns 0 on success, 1 if errors are encountered.
936 Returns 0 on success, 1 if errors are encountered.
937 """
937 """
938 wlock = repo.wlock(False)
938 wlock = repo.wlock(False)
939 try:
939 try:
940 return cmdutil.copy(ui, repo, pats, opts)
940 return cmdutil.copy(ui, repo, pats, opts)
941 finally:
941 finally:
942 wlock.release()
942 wlock.release()
943
943
944 def debugancestor(ui, repo, *args):
944 def debugancestor(ui, repo, *args):
945 """find the ancestor revision of two revisions in a given index"""
945 """find the ancestor revision of two revisions in a given index"""
946 if len(args) == 3:
946 if len(args) == 3:
947 index, rev1, rev2 = args
947 index, rev1, rev2 = args
948 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
948 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
949 lookup = r.lookup
949 lookup = r.lookup
950 elif len(args) == 2:
950 elif len(args) == 2:
951 if not repo:
951 if not repo:
952 raise util.Abort(_("there is no Mercurial repository here "
952 raise util.Abort(_("there is no Mercurial repository here "
953 "(.hg not found)"))
953 "(.hg not found)"))
954 rev1, rev2 = args
954 rev1, rev2 = args
955 r = repo.changelog
955 r = repo.changelog
956 lookup = repo.lookup
956 lookup = repo.lookup
957 else:
957 else:
958 raise util.Abort(_('either two or three arguments required'))
958 raise util.Abort(_('either two or three arguments required'))
959 a = r.ancestor(lookup(rev1), lookup(rev2))
959 a = r.ancestor(lookup(rev1), lookup(rev2))
960 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
960 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
961
961
962 def debugbuilddag(ui, repo, text,
962 def debugbuilddag(ui, repo, text,
963 mergeable_file=False,
963 mergeable_file=False,
964 appended_file=False,
964 appended_file=False,
965 overwritten_file=False,
965 overwritten_file=False,
966 new_file=False):
966 new_file=False):
967 """builds a repo with a given dag from scratch in the current empty repo
967 """builds a repo with a given dag from scratch in the current empty repo
968
968
969 Elements:
969 Elements:
970
970
971 - "+n" is a linear run of n nodes based on the current default parent
971 - "+n" is a linear run of n nodes based on the current default parent
972 - "." is a single node based on the current default parent
972 - "." is a single node based on the current default parent
973 - "$" resets the default parent to null (implied at the start);
973 - "$" resets the default parent to null (implied at the start);
974 otherwise the default parent is always the last node created
974 otherwise the default parent is always the last node created
975 - "<p" sets the default parent to the backref p
975 - "<p" sets the default parent to the backref p
976 - "*p" is a fork at parent p, which is a backref
976 - "*p" is a fork at parent p, which is a backref
977 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
977 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
978 - "/p2" is a merge of the preceding node and p2
978 - "/p2" is a merge of the preceding node and p2
979 - ":tag" defines a local tag for the preceding node
979 - ":tag" defines a local tag for the preceding node
980 - "@branch" sets the named branch for subsequent nodes
980 - "@branch" sets the named branch for subsequent nodes
981 - "!command" runs the command using your shell
981 - "!command" runs the command using your shell
982 - "!!my command\\n" is like "!", but to the end of the line
982 - "!!my command\\n" is like "!", but to the end of the line
983 - "#...\\n" is a comment up to the end of the line
983 - "#...\\n" is a comment up to the end of the line
984
984
985 Whitespace between the above elements is ignored.
985 Whitespace between the above elements is ignored.
986
986
987 A backref is either
987 A backref is either
988
988
989 - a number n, which references the node curr-n, where curr is the current
989 - a number n, which references the node curr-n, where curr is the current
990 node, or
990 node, or
991 - the name of a local tag you placed earlier using ":tag", or
991 - the name of a local tag you placed earlier using ":tag", or
992 - empty to denote the default parent.
992 - empty to denote the default parent.
993
993
994 All string valued-elements are either strictly alphanumeric, or must
994 All string valued-elements are either strictly alphanumeric, or must
995 be enclosed in double quotes ("..."), with "\\" as escape character.
995 be enclosed in double quotes ("..."), with "\\" as escape character.
996
996
997 Note that the --overwritten-file and --appended-file options imply the
997 Note that the --overwritten-file and --appended-file options imply the
998 use of "HGMERGE=internal:local" during DAG buildup.
998 use of "HGMERGE=internal:local" during DAG buildup.
999 """
999 """
1000
1000
1001 if not (mergeable_file or appended_file or overwritten_file or new_file):
1001 if not (mergeable_file or appended_file or overwritten_file or new_file):
1002 raise util.Abort(_('need at least one of -m, -a, -o, -n'))
1002 raise util.Abort(_('need at least one of -m, -a, -o, -n'))
1003
1003
1004 if len(repo.changelog) > 0:
1004 if len(repo.changelog) > 0:
1005 raise util.Abort(_('repository is not empty'))
1005 raise util.Abort(_('repository is not empty'))
1006
1006
1007 if overwritten_file or appended_file:
1007 if overwritten_file or appended_file:
1008 # we don't want to fail in merges during buildup
1008 # we don't want to fail in merges during buildup
1009 os.environ['HGMERGE'] = 'internal:local'
1009 os.environ['HGMERGE'] = 'internal:local'
1010
1010
1011 def writefile(fname, text, fmode="wb"):
1011 def writefile(fname, text, fmode="wb"):
1012 f = open(fname, fmode)
1012 f = open(fname, fmode)
1013 try:
1013 try:
1014 f.write(text)
1014 f.write(text)
1015 finally:
1015 finally:
1016 f.close()
1016 f.close()
1017
1017
1018 if mergeable_file:
1018 if mergeable_file:
1019 linesperrev = 2
1019 linesperrev = 2
1020 # determine number of revs in DAG
1020 # determine number of revs in DAG
1021 n = 0
1021 n = 0
1022 for type, data in dagparser.parsedag(text):
1022 for type, data in dagparser.parsedag(text):
1023 if type == 'n':
1023 if type == 'n':
1024 n += 1
1024 n += 1
1025 # make a file with k lines per rev
1025 # make a file with k lines per rev
1026 writefile("mf", "\n".join(str(i) for i in xrange(0, n * linesperrev))
1026 writefile("mf", "\n".join(str(i) for i in xrange(0, n * linesperrev))
1027 + "\n")
1027 + "\n")
1028
1028
1029 at = -1
1029 at = -1
1030 atbranch = 'default'
1030 atbranch = 'default'
1031 for type, data in dagparser.parsedag(text):
1031 for type, data in dagparser.parsedag(text):
1032 if type == 'n':
1032 if type == 'n':
1033 ui.status('node %s\n' % str(data))
1033 ui.status('node %s\n' % str(data))
1034 id, ps = data
1034 id, ps = data
1035 p1 = ps[0]
1035 p1 = ps[0]
1036 if p1 != at:
1036 if p1 != at:
1037 update(ui, repo, node=str(p1), clean=True)
1037 update(ui, repo, node=str(p1), clean=True)
1038 at = p1
1038 at = p1
1039 if repo.dirstate.branch() != atbranch:
1039 if repo.dirstate.branch() != atbranch:
1040 branch(ui, repo, atbranch, force=True)
1040 branch(ui, repo, atbranch, force=True)
1041 if len(ps) > 1:
1041 if len(ps) > 1:
1042 p2 = ps[1]
1042 p2 = ps[1]
1043 merge(ui, repo, node=p2)
1043 merge(ui, repo, node=p2)
1044
1044
1045 if mergeable_file:
1045 if mergeable_file:
1046 f = open("mf", "rb+")
1046 f = open("mf", "rb+")
1047 try:
1047 try:
1048 lines = f.read().split("\n")
1048 lines = f.read().split("\n")
1049 lines[id * linesperrev] += " r%i" % id
1049 lines[id * linesperrev] += " r%i" % id
1050 f.seek(0)
1050 f.seek(0)
1051 f.write("\n".join(lines))
1051 f.write("\n".join(lines))
1052 finally:
1052 finally:
1053 f.close()
1053 f.close()
1054
1054
1055 if appended_file:
1055 if appended_file:
1056 writefile("af", "r%i\n" % id, "ab")
1056 writefile("af", "r%i\n" % id, "ab")
1057
1057
1058 if overwritten_file:
1058 if overwritten_file:
1059 writefile("of", "r%i\n" % id)
1059 writefile("of", "r%i\n" % id)
1060
1060
1061 if new_file:
1061 if new_file:
1062 writefile("nf%i" % id, "r%i\n" % id)
1062 writefile("nf%i" % id, "r%i\n" % id)
1063
1063
1064 commit(ui, repo, addremove=True, message="r%i" % id, date=(id, 0))
1064 commit(ui, repo, addremove=True, message="r%i" % id, date=(id, 0))
1065 at = id
1065 at = id
1066 elif type == 'l':
1066 elif type == 'l':
1067 id, name = data
1067 id, name = data
1068 ui.status('tag %s\n' % name)
1068 ui.status('tag %s\n' % name)
1069 tag(ui, repo, name, local=True)
1069 tag(ui, repo, name, local=True)
1070 elif type == 'a':
1070 elif type == 'a':
1071 ui.status('branch %s\n' % data)
1071 ui.status('branch %s\n' % data)
1072 atbranch = data
1072 atbranch = data
1073 elif type in 'cC':
1073 elif type in 'cC':
1074 r = util.system(data, cwd=repo.root)
1074 r = util.system(data, cwd=repo.root)
1075 if r:
1075 if r:
1076 desc, r = util.explain_exit(r)
1076 desc, r = util.explain_exit(r)
1077 raise util.Abort(_('%s command %s') % (data, desc))
1077 raise util.Abort(_('%s command %s') % (data, desc))
1078
1078
1079 def debugcommands(ui, cmd='', *args):
1079 def debugcommands(ui, cmd='', *args):
1080 """list all available commands and options"""
1080 """list all available commands and options"""
1081 for cmd, vals in sorted(table.iteritems()):
1081 for cmd, vals in sorted(table.iteritems()):
1082 cmd = cmd.split('|')[0].strip('^')
1082 cmd = cmd.split('|')[0].strip('^')
1083 opts = ', '.join([i[1] for i in vals[1]])
1083 opts = ', '.join([i[1] for i in vals[1]])
1084 ui.write('%s: %s\n' % (cmd, opts))
1084 ui.write('%s: %s\n' % (cmd, opts))
1085
1085
1086 def debugcomplete(ui, cmd='', **opts):
1086 def debugcomplete(ui, cmd='', **opts):
1087 """returns the completion list associated with the given command"""
1087 """returns the completion list associated with the given command"""
1088
1088
1089 if opts.get('options'):
1089 if opts.get('options'):
1090 options = []
1090 options = []
1091 otables = [globalopts]
1091 otables = [globalopts]
1092 if cmd:
1092 if cmd:
1093 aliases, entry = cmdutil.findcmd(cmd, table, False)
1093 aliases, entry = cmdutil.findcmd(cmd, table, False)
1094 otables.append(entry[1])
1094 otables.append(entry[1])
1095 for t in otables:
1095 for t in otables:
1096 for o in t:
1096 for o in t:
1097 if "(DEPRECATED)" in o[3]:
1097 if "(DEPRECATED)" in o[3]:
1098 continue
1098 continue
1099 if o[0]:
1099 if o[0]:
1100 options.append('-%s' % o[0])
1100 options.append('-%s' % o[0])
1101 options.append('--%s' % o[1])
1101 options.append('--%s' % o[1])
1102 ui.write("%s\n" % "\n".join(options))
1102 ui.write("%s\n" % "\n".join(options))
1103 return
1103 return
1104
1104
1105 cmdlist = cmdutil.findpossible(cmd, table)
1105 cmdlist = cmdutil.findpossible(cmd, table)
1106 if ui.verbose:
1106 if ui.verbose:
1107 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1107 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1108 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1108 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1109
1109
1110 def debugfsinfo(ui, path = "."):
1110 def debugfsinfo(ui, path = "."):
1111 """show information detected about current filesystem"""
1111 """show information detected about current filesystem"""
1112 open('.debugfsinfo', 'w').write('')
1112 open('.debugfsinfo', 'w').write('')
1113 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1113 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1114 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1114 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1115 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1115 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1116 and 'yes' or 'no'))
1116 and 'yes' or 'no'))
1117 os.unlink('.debugfsinfo')
1117 os.unlink('.debugfsinfo')
1118
1118
1119 def debugrebuildstate(ui, repo, rev="tip"):
1119 def debugrebuildstate(ui, repo, rev="tip"):
1120 """rebuild the dirstate as it would look like for the given revision"""
1120 """rebuild the dirstate as it would look like for the given revision"""
1121 ctx = cmdutil.revsingle(repo, rev)
1121 ctx = cmdutil.revsingle(repo, rev)
1122 wlock = repo.wlock()
1122 wlock = repo.wlock()
1123 try:
1123 try:
1124 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1124 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1125 finally:
1125 finally:
1126 wlock.release()
1126 wlock.release()
1127
1127
1128 def debugcheckstate(ui, repo):
1128 def debugcheckstate(ui, repo):
1129 """validate the correctness of the current dirstate"""
1129 """validate the correctness of the current dirstate"""
1130 parent1, parent2 = repo.dirstate.parents()
1130 parent1, parent2 = repo.dirstate.parents()
1131 m1 = repo[parent1].manifest()
1131 m1 = repo[parent1].manifest()
1132 m2 = repo[parent2].manifest()
1132 m2 = repo[parent2].manifest()
1133 errors = 0
1133 errors = 0
1134 for f in repo.dirstate:
1134 for f in repo.dirstate:
1135 state = repo.dirstate[f]
1135 state = repo.dirstate[f]
1136 if state in "nr" and f not in m1:
1136 if state in "nr" and f not in m1:
1137 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1137 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1138 errors += 1
1138 errors += 1
1139 if state in "a" and f in m1:
1139 if state in "a" and f in m1:
1140 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1140 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1141 errors += 1
1141 errors += 1
1142 if state in "m" and f not in m1 and f not in m2:
1142 if state in "m" and f not in m1 and f not in m2:
1143 ui.warn(_("%s in state %s, but not in either manifest\n") %
1143 ui.warn(_("%s in state %s, but not in either manifest\n") %
1144 (f, state))
1144 (f, state))
1145 errors += 1
1145 errors += 1
1146 for f in m1:
1146 for f in m1:
1147 state = repo.dirstate[f]
1147 state = repo.dirstate[f]
1148 if state not in "nrm":
1148 if state not in "nrm":
1149 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1149 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1150 errors += 1
1150 errors += 1
1151 if errors:
1151 if errors:
1152 error = _(".hg/dirstate inconsistent with current parent's manifest")
1152 error = _(".hg/dirstate inconsistent with current parent's manifest")
1153 raise util.Abort(error)
1153 raise util.Abort(error)
1154
1154
1155 def showconfig(ui, repo, *values, **opts):
1155 def showconfig(ui, repo, *values, **opts):
1156 """show combined config settings from all hgrc files
1156 """show combined config settings from all hgrc files
1157
1157
1158 With no arguments, print names and values of all config items.
1158 With no arguments, print names and values of all config items.
1159
1159
1160 With one argument of the form section.name, print just the value
1160 With one argument of the form section.name, print just the value
1161 of that config item.
1161 of that config item.
1162
1162
1163 With multiple arguments, print names and values of all config
1163 With multiple arguments, print names and values of all config
1164 items with matching section names.
1164 items with matching section names.
1165
1165
1166 With --debug, the source (filename and line number) is printed
1166 With --debug, the source (filename and line number) is printed
1167 for each config item.
1167 for each config item.
1168
1168
1169 Returns 0 on success.
1169 Returns 0 on success.
1170 """
1170 """
1171
1171
1172 for f in util.rcpath():
1172 for f in util.rcpath():
1173 ui.debug(_('read config from: %s\n') % f)
1173 ui.debug(_('read config from: %s\n') % f)
1174 untrusted = bool(opts.get('untrusted'))
1174 untrusted = bool(opts.get('untrusted'))
1175 if values:
1175 if values:
1176 sections = [v for v in values if '.' not in v]
1176 sections = [v for v in values if '.' not in v]
1177 items = [v for v in values if '.' in v]
1177 items = [v for v in values if '.' in v]
1178 if len(items) > 1 or items and sections:
1178 if len(items) > 1 or items and sections:
1179 raise util.Abort(_('only one config item permitted'))
1179 raise util.Abort(_('only one config item permitted'))
1180 for section, name, value in ui.walkconfig(untrusted=untrusted):
1180 for section, name, value in ui.walkconfig(untrusted=untrusted):
1181 sectname = section + '.' + name
1181 sectname = section + '.' + name
1182 if values:
1182 if values:
1183 for v in values:
1183 for v in values:
1184 if v == section:
1184 if v == section:
1185 ui.debug('%s: ' %
1185 ui.debug('%s: ' %
1186 ui.configsource(section, name, untrusted))
1186 ui.configsource(section, name, untrusted))
1187 ui.write('%s=%s\n' % (sectname, value))
1187 ui.write('%s=%s\n' % (sectname, value))
1188 elif v == sectname:
1188 elif v == sectname:
1189 ui.debug('%s: ' %
1189 ui.debug('%s: ' %
1190 ui.configsource(section, name, untrusted))
1190 ui.configsource(section, name, untrusted))
1191 ui.write(value, '\n')
1191 ui.write(value, '\n')
1192 else:
1192 else:
1193 ui.debug('%s: ' %
1193 ui.debug('%s: ' %
1194 ui.configsource(section, name, untrusted))
1194 ui.configsource(section, name, untrusted))
1195 ui.write('%s=%s\n' % (sectname, value))
1195 ui.write('%s=%s\n' % (sectname, value))
1196
1196
1197 def debugpushkey(ui, repopath, namespace, *keyinfo):
1197 def debugpushkey(ui, repopath, namespace, *keyinfo):
1198 '''access the pushkey key/value protocol
1198 '''access the pushkey key/value protocol
1199
1199
1200 With two args, list the keys in the given namespace.
1200 With two args, list the keys in the given namespace.
1201
1201
1202 With five args, set a key to new if it currently is set to old.
1202 With five args, set a key to new if it currently is set to old.
1203 Reports success or failure.
1203 Reports success or failure.
1204 '''
1204 '''
1205
1205
1206 target = hg.repository(ui, repopath)
1206 target = hg.repository(ui, repopath)
1207 if keyinfo:
1207 if keyinfo:
1208 key, old, new = keyinfo
1208 key, old, new = keyinfo
1209 r = target.pushkey(namespace, key, old, new)
1209 r = target.pushkey(namespace, key, old, new)
1210 ui.status(str(r) + '\n')
1210 ui.status(str(r) + '\n')
1211 return not r
1211 return not r
1212 else:
1212 else:
1213 for k, v in target.listkeys(namespace).iteritems():
1213 for k, v in target.listkeys(namespace).iteritems():
1214 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1214 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1215 v.encode('string-escape')))
1215 v.encode('string-escape')))
1216
1216
1217 def debugrevspec(ui, repo, expr):
1217 def debugrevspec(ui, repo, expr):
1218 '''parse and apply a revision specification'''
1218 '''parse and apply a revision specification'''
1219 if ui.verbose:
1219 if ui.verbose:
1220 tree = revset.parse(expr)
1220 tree = revset.parse(expr)
1221 ui.note(tree, "\n")
1221 ui.note(tree, "\n")
1222 func = revset.match(expr)
1222 func = revset.match(expr)
1223 for c in func(repo, range(len(repo))):
1223 for c in func(repo, range(len(repo))):
1224 ui.write("%s\n" % c)
1224 ui.write("%s\n" % c)
1225
1225
1226 def debugsetparents(ui, repo, rev1, rev2=None):
1226 def debugsetparents(ui, repo, rev1, rev2=None):
1227 """manually set the parents of the current working directory
1227 """manually set the parents of the current working directory
1228
1228
1229 This is useful for writing repository conversion tools, but should
1229 This is useful for writing repository conversion tools, but should
1230 be used with care.
1230 be used with care.
1231
1231
1232 Returns 0 on success.
1232 Returns 0 on success.
1233 """
1233 """
1234
1234
1235 r1 = cmdutil.revsingle(repo, rev1).node()
1235 r1 = cmdutil.revsingle(repo, rev1).node()
1236 r2 = cmdutil.revsingle(repo, rev2, 'null').node()
1236 r2 = cmdutil.revsingle(repo, rev2, 'null').node()
1237
1237
1238 wlock = repo.wlock()
1238 wlock = repo.wlock()
1239 try:
1239 try:
1240 repo.dirstate.setparents(r1, r2)
1240 repo.dirstate.setparents(r1, r2)
1241 finally:
1241 finally:
1242 wlock.release()
1242 wlock.release()
1243
1243
1244 def debugstate(ui, repo, nodates=None):
1244 def debugstate(ui, repo, nodates=None):
1245 """show the contents of the current dirstate"""
1245 """show the contents of the current dirstate"""
1246 timestr = ""
1246 timestr = ""
1247 showdate = not nodates
1247 showdate = not nodates
1248 for file_, ent in sorted(repo.dirstate._map.iteritems()):
1248 for file_, ent in sorted(repo.dirstate._map.iteritems()):
1249 if showdate:
1249 if showdate:
1250 if ent[3] == -1:
1250 if ent[3] == -1:
1251 # Pad or slice to locale representation
1251 # Pad or slice to locale representation
1252 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
1252 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
1253 time.localtime(0)))
1253 time.localtime(0)))
1254 timestr = 'unset'
1254 timestr = 'unset'
1255 timestr = (timestr[:locale_len] +
1255 timestr = (timestr[:locale_len] +
1256 ' ' * (locale_len - len(timestr)))
1256 ' ' * (locale_len - len(timestr)))
1257 else:
1257 else:
1258 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
1258 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
1259 time.localtime(ent[3]))
1259 time.localtime(ent[3]))
1260 if ent[1] & 020000:
1260 if ent[1] & 020000:
1261 mode = 'lnk'
1261 mode = 'lnk'
1262 else:
1262 else:
1263 mode = '%3o' % (ent[1] & 0777)
1263 mode = '%3o' % (ent[1] & 0777)
1264 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
1264 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
1265 for f in repo.dirstate.copies():
1265 for f in repo.dirstate.copies():
1266 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
1266 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
1267
1267
1268 def debugsub(ui, repo, rev=None):
1268 def debugsub(ui, repo, rev=None):
1269 ctx = cmdutil.revsingle(repo, rev, None)
1269 ctx = cmdutil.revsingle(repo, rev, None)
1270 for k, v in sorted(ctx.substate.items()):
1270 for k, v in sorted(ctx.substate.items()):
1271 ui.write('path %s\n' % k)
1271 ui.write('path %s\n' % k)
1272 ui.write(' source %s\n' % v[0])
1272 ui.write(' source %s\n' % v[0])
1273 ui.write(' revision %s\n' % v[1])
1273 ui.write(' revision %s\n' % v[1])
1274
1274
1275 def debugdag(ui, repo, file_=None, *revs, **opts):
1275 def debugdag(ui, repo, file_=None, *revs, **opts):
1276 """format the changelog or an index DAG as a concise textual description
1276 """format the changelog or an index DAG as a concise textual description
1277
1277
1278 If you pass a revlog index, the revlog's DAG is emitted. If you list
1278 If you pass a revlog index, the revlog's DAG is emitted. If you list
1279 revision numbers, they get labelled in the output as rN.
1279 revision numbers, they get labelled in the output as rN.
1280
1280
1281 Otherwise, the changelog DAG of the current repo is emitted.
1281 Otherwise, the changelog DAG of the current repo is emitted.
1282 """
1282 """
1283 spaces = opts.get('spaces')
1283 spaces = opts.get('spaces')
1284 dots = opts.get('dots')
1284 dots = opts.get('dots')
1285 if file_:
1285 if file_:
1286 rlog = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1286 rlog = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1287 revs = set((int(r) for r in revs))
1287 revs = set((int(r) for r in revs))
1288 def events():
1288 def events():
1289 for r in rlog:
1289 for r in rlog:
1290 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1290 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1291 if r in revs:
1291 if r in revs:
1292 yield 'l', (r, "r%i" % r)
1292 yield 'l', (r, "r%i" % r)
1293 elif repo:
1293 elif repo:
1294 cl = repo.changelog
1294 cl = repo.changelog
1295 tags = opts.get('tags')
1295 tags = opts.get('tags')
1296 branches = opts.get('branches')
1296 branches = opts.get('branches')
1297 if tags:
1297 if tags:
1298 labels = {}
1298 labels = {}
1299 for l, n in repo.tags().items():
1299 for l, n in repo.tags().items():
1300 labels.setdefault(cl.rev(n), []).append(l)
1300 labels.setdefault(cl.rev(n), []).append(l)
1301 def events():
1301 def events():
1302 b = "default"
1302 b = "default"
1303 for r in cl:
1303 for r in cl:
1304 if branches:
1304 if branches:
1305 newb = cl.read(cl.node(r))[5]['branch']
1305 newb = cl.read(cl.node(r))[5]['branch']
1306 if newb != b:
1306 if newb != b:
1307 yield 'a', newb
1307 yield 'a', newb
1308 b = newb
1308 b = newb
1309 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1309 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1310 if tags:
1310 if tags:
1311 ls = labels.get(r)
1311 ls = labels.get(r)
1312 if ls:
1312 if ls:
1313 for l in ls:
1313 for l in ls:
1314 yield 'l', (r, l)
1314 yield 'l', (r, l)
1315 else:
1315 else:
1316 raise util.Abort(_('need repo for changelog dag'))
1316 raise util.Abort(_('need repo for changelog dag'))
1317
1317
1318 for line in dagparser.dagtextlines(events(),
1318 for line in dagparser.dagtextlines(events(),
1319 addspaces=spaces,
1319 addspaces=spaces,
1320 wraplabels=True,
1320 wraplabels=True,
1321 wrapannotations=True,
1321 wrapannotations=True,
1322 wrapnonlinear=dots,
1322 wrapnonlinear=dots,
1323 usedots=dots,
1323 usedots=dots,
1324 maxlinewidth=70):
1324 maxlinewidth=70):
1325 ui.write(line)
1325 ui.write(line)
1326 ui.write("\n")
1326 ui.write("\n")
1327
1327
1328 def debugdata(ui, repo, file_, rev):
1328 def debugdata(ui, repo, file_, rev):
1329 """dump the contents of a data file revision"""
1329 """dump the contents of a data file revision"""
1330 r = None
1330 r = None
1331 if repo:
1331 if repo:
1332 filelog = repo.file(file_)
1332 filelog = repo.file(file_)
1333 if len(filelog):
1333 if len(filelog):
1334 r = filelog
1334 r = filelog
1335 if not r:
1335 if not r:
1336 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
1336 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
1337 try:
1337 try:
1338 ui.write(r.revision(r.lookup(rev)))
1338 ui.write(r.revision(r.lookup(rev)))
1339 except KeyError:
1339 except KeyError:
1340 raise util.Abort(_('invalid revision identifier %s') % rev)
1340 raise util.Abort(_('invalid revision identifier %s') % rev)
1341
1341
1342 def debugdate(ui, date, range=None, **opts):
1342 def debugdate(ui, date, range=None, **opts):
1343 """parse and display a date"""
1343 """parse and display a date"""
1344 if opts["extended"]:
1344 if opts["extended"]:
1345 d = util.parsedate(date, util.extendeddateformats)
1345 d = util.parsedate(date, util.extendeddateformats)
1346 else:
1346 else:
1347 d = util.parsedate(date)
1347 d = util.parsedate(date)
1348 ui.write("internal: %s %s\n" % d)
1348 ui.write("internal: %s %s\n" % d)
1349 ui.write("standard: %s\n" % util.datestr(d))
1349 ui.write("standard: %s\n" % util.datestr(d))
1350 if range:
1350 if range:
1351 m = util.matchdate(range)
1351 m = util.matchdate(range)
1352 ui.write("match: %s\n" % m(d[0]))
1352 ui.write("match: %s\n" % m(d[0]))
1353
1353
1354 def debugindex(ui, repo, file_, **opts):
1354 def debugindex(ui, repo, file_, **opts):
1355 """dump the contents of an index file"""
1355 """dump the contents of an index file"""
1356 r = None
1356 r = None
1357 if repo:
1357 if repo:
1358 filelog = repo.file(file_)
1358 filelog = repo.file(file_)
1359 if len(filelog):
1359 if len(filelog):
1360 r = filelog
1360 r = filelog
1361
1361
1362 format = opts.get('format', 0)
1362 format = opts.get('format', 0)
1363 if format not in (0, 1):
1363 if format not in (0, 1):
1364 raise util.Abort("unknown format %d" % format)
1364 raise util.Abort("unknown format %d" % format)
1365
1365
1366 if not r:
1366 if not r:
1367 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1367 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1368
1368
1369 if format == 0:
1369 if format == 0:
1370 ui.write(" rev offset length base linkrev"
1370 ui.write(" rev offset length base linkrev"
1371 " nodeid p1 p2\n")
1371 " nodeid p1 p2\n")
1372 elif format == 1:
1372 elif format == 1:
1373 ui.write(" rev flag offset length"
1373 ui.write(" rev flag offset length"
1374 " size base link p1 p2 nodeid\n")
1374 " size base link p1 p2 nodeid\n")
1375
1375
1376 for i in r:
1376 for i in r:
1377 node = r.node(i)
1377 node = r.node(i)
1378 if format == 0:
1378 if format == 0:
1379 try:
1379 try:
1380 pp = r.parents(node)
1380 pp = r.parents(node)
1381 except:
1381 except:
1382 pp = [nullid, nullid]
1382 pp = [nullid, nullid]
1383 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1383 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1384 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
1384 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
1385 short(node), short(pp[0]), short(pp[1])))
1385 short(node), short(pp[0]), short(pp[1])))
1386 elif format == 1:
1386 elif format == 1:
1387 pr = r.parentrevs(i)
1387 pr = r.parentrevs(i)
1388 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1388 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1389 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1389 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1390 r.base(i), r.linkrev(i), pr[0], pr[1], short(node)))
1390 r.base(i), r.linkrev(i), pr[0], pr[1], short(node)))
1391
1391
1392 def debugindexdot(ui, repo, file_):
1392 def debugindexdot(ui, repo, file_):
1393 """dump an index DAG as a graphviz dot file"""
1393 """dump an index DAG as a graphviz dot file"""
1394 r = None
1394 r = None
1395 if repo:
1395 if repo:
1396 filelog = repo.file(file_)
1396 filelog = repo.file(file_)
1397 if len(filelog):
1397 if len(filelog):
1398 r = filelog
1398 r = filelog
1399 if not r:
1399 if not r:
1400 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1400 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1401 ui.write("digraph G {\n")
1401 ui.write("digraph G {\n")
1402 for i in r:
1402 for i in r:
1403 node = r.node(i)
1403 node = r.node(i)
1404 pp = r.parents(node)
1404 pp = r.parents(node)
1405 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1405 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1406 if pp[1] != nullid:
1406 if pp[1] != nullid:
1407 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1407 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1408 ui.write("}\n")
1408 ui.write("}\n")
1409
1409
1410 def debuginstall(ui):
1410 def debuginstall(ui):
1411 '''test Mercurial installation
1411 '''test Mercurial installation
1412
1412
1413 Returns 0 on success.
1413 Returns 0 on success.
1414 '''
1414 '''
1415
1415
1416 def writetemp(contents):
1416 def writetemp(contents):
1417 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1417 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1418 f = os.fdopen(fd, "wb")
1418 f = os.fdopen(fd, "wb")
1419 f.write(contents)
1419 f.write(contents)
1420 f.close()
1420 f.close()
1421 return name
1421 return name
1422
1422
1423 problems = 0
1423 problems = 0
1424
1424
1425 # encoding
1425 # encoding
1426 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1426 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1427 try:
1427 try:
1428 encoding.fromlocal("test")
1428 encoding.fromlocal("test")
1429 except util.Abort, inst:
1429 except util.Abort, inst:
1430 ui.write(" %s\n" % inst)
1430 ui.write(" %s\n" % inst)
1431 ui.write(_(" (check that your locale is properly set)\n"))
1431 ui.write(_(" (check that your locale is properly set)\n"))
1432 problems += 1
1432 problems += 1
1433
1433
1434 # compiled modules
1434 # compiled modules
1435 ui.status(_("Checking installed modules (%s)...\n")
1435 ui.status(_("Checking installed modules (%s)...\n")
1436 % os.path.dirname(__file__))
1436 % os.path.dirname(__file__))
1437 try:
1437 try:
1438 import bdiff, mpatch, base85, osutil
1438 import bdiff, mpatch, base85, osutil
1439 except Exception, inst:
1439 except Exception, inst:
1440 ui.write(" %s\n" % inst)
1440 ui.write(" %s\n" % inst)
1441 ui.write(_(" One or more extensions could not be found"))
1441 ui.write(_(" One or more extensions could not be found"))
1442 ui.write(_(" (check that you compiled the extensions)\n"))
1442 ui.write(_(" (check that you compiled the extensions)\n"))
1443 problems += 1
1443 problems += 1
1444
1444
1445 # templates
1445 # templates
1446 ui.status(_("Checking templates...\n"))
1446 ui.status(_("Checking templates...\n"))
1447 try:
1447 try:
1448 import templater
1448 import templater
1449 templater.templater(templater.templatepath("map-cmdline.default"))
1449 templater.templater(templater.templatepath("map-cmdline.default"))
1450 except Exception, inst:
1450 except Exception, inst:
1451 ui.write(" %s\n" % inst)
1451 ui.write(" %s\n" % inst)
1452 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1452 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1453 problems += 1
1453 problems += 1
1454
1454
1455 # patch
1455 # patch
1456 ui.status(_("Checking patch...\n"))
1456 ui.status(_("Checking patch...\n"))
1457 patchproblems = 0
1457 patchproblems = 0
1458 a = "1\n2\n3\n4\n"
1458 a = "1\n2\n3\n4\n"
1459 b = "1\n2\n3\ninsert\n4\n"
1459 b = "1\n2\n3\ninsert\n4\n"
1460 fa = writetemp(a)
1460 fa = writetemp(a)
1461 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
1461 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
1462 os.path.basename(fa))
1462 os.path.basename(fa))
1463 fd = writetemp(d)
1463 fd = writetemp(d)
1464
1464
1465 files = {}
1465 files = {}
1466 try:
1466 try:
1467 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
1467 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
1468 except util.Abort, e:
1468 except util.Abort, e:
1469 ui.write(_(" patch call failed:\n"))
1469 ui.write(_(" patch call failed:\n"))
1470 ui.write(" " + str(e) + "\n")
1470 ui.write(" " + str(e) + "\n")
1471 patchproblems += 1
1471 patchproblems += 1
1472 else:
1472 else:
1473 if list(files) != [os.path.basename(fa)]:
1473 if list(files) != [os.path.basename(fa)]:
1474 ui.write(_(" unexpected patch output!\n"))
1474 ui.write(_(" unexpected patch output!\n"))
1475 patchproblems += 1
1475 patchproblems += 1
1476 a = open(fa).read()
1476 a = open(fa).read()
1477 if a != b:
1477 if a != b:
1478 ui.write(_(" patch test failed!\n"))
1478 ui.write(_(" patch test failed!\n"))
1479 patchproblems += 1
1479 patchproblems += 1
1480
1480
1481 if patchproblems:
1481 if patchproblems:
1482 if ui.config('ui', 'patch'):
1482 if ui.config('ui', 'patch'):
1483 ui.write(_(" (Current patch tool may be incompatible with patch,"
1483 ui.write(_(" (Current patch tool may be incompatible with patch,"
1484 " or misconfigured. Please check your configuration"
1484 " or misconfigured. Please check your configuration"
1485 " file)\n"))
1485 " file)\n"))
1486 else:
1486 else:
1487 ui.write(_(" Internal patcher failure, please report this error"
1487 ui.write(_(" Internal patcher failure, please report this error"
1488 " to http://mercurial.selenic.com/wiki/BugTracker\n"))
1488 " to http://mercurial.selenic.com/wiki/BugTracker\n"))
1489 problems += patchproblems
1489 problems += patchproblems
1490
1490
1491 os.unlink(fa)
1491 os.unlink(fa)
1492 os.unlink(fd)
1492 os.unlink(fd)
1493
1493
1494 # editor
1494 # editor
1495 ui.status(_("Checking commit editor...\n"))
1495 ui.status(_("Checking commit editor...\n"))
1496 editor = ui.geteditor()
1496 editor = ui.geteditor()
1497 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
1497 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
1498 if not cmdpath:
1498 if not cmdpath:
1499 if editor == 'vi':
1499 if editor == 'vi':
1500 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1500 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1501 ui.write(_(" (specify a commit editor in your configuration"
1501 ui.write(_(" (specify a commit editor in your configuration"
1502 " file)\n"))
1502 " file)\n"))
1503 else:
1503 else:
1504 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1504 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1505 ui.write(_(" (specify a commit editor in your configuration"
1505 ui.write(_(" (specify a commit editor in your configuration"
1506 " file)\n"))
1506 " file)\n"))
1507 problems += 1
1507 problems += 1
1508
1508
1509 # check username
1509 # check username
1510 ui.status(_("Checking username...\n"))
1510 ui.status(_("Checking username...\n"))
1511 try:
1511 try:
1512 ui.username()
1512 ui.username()
1513 except util.Abort, e:
1513 except util.Abort, e:
1514 ui.write(" %s\n" % e)
1514 ui.write(" %s\n" % e)
1515 ui.write(_(" (specify a username in your configuration file)\n"))
1515 ui.write(_(" (specify a username in your configuration file)\n"))
1516 problems += 1
1516 problems += 1
1517
1517
1518 if not problems:
1518 if not problems:
1519 ui.status(_("No problems detected\n"))
1519 ui.status(_("No problems detected\n"))
1520 else:
1520 else:
1521 ui.write(_("%s problems detected,"
1521 ui.write(_("%s problems detected,"
1522 " please check your install!\n") % problems)
1522 " please check your install!\n") % problems)
1523
1523
1524 return problems
1524 return problems
1525
1525
1526 def debugrename(ui, repo, file1, *pats, **opts):
1526 def debugrename(ui, repo, file1, *pats, **opts):
1527 """dump rename information"""
1527 """dump rename information"""
1528
1528
1529 ctx = cmdutil.revsingle(repo, opts.get('rev'))
1529 ctx = cmdutil.revsingle(repo, opts.get('rev'))
1530 m = cmdutil.match(repo, (file1,) + pats, opts)
1530 m = cmdutil.match(repo, (file1,) + pats, opts)
1531 for abs in ctx.walk(m):
1531 for abs in ctx.walk(m):
1532 fctx = ctx[abs]
1532 fctx = ctx[abs]
1533 o = fctx.filelog().renamed(fctx.filenode())
1533 o = fctx.filelog().renamed(fctx.filenode())
1534 rel = m.rel(abs)
1534 rel = m.rel(abs)
1535 if o:
1535 if o:
1536 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1536 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1537 else:
1537 else:
1538 ui.write(_("%s not renamed\n") % rel)
1538 ui.write(_("%s not renamed\n") % rel)
1539
1539
1540 def debugwalk(ui, repo, *pats, **opts):
1540 def debugwalk(ui, repo, *pats, **opts):
1541 """show how files match on given patterns"""
1541 """show how files match on given patterns"""
1542 m = cmdutil.match(repo, pats, opts)
1542 m = cmdutil.match(repo, pats, opts)
1543 items = list(repo.walk(m))
1543 items = list(repo.walk(m))
1544 if not items:
1544 if not items:
1545 return
1545 return
1546 fmt = 'f %%-%ds %%-%ds %%s' % (
1546 fmt = 'f %%-%ds %%-%ds %%s' % (
1547 max([len(abs) for abs in items]),
1547 max([len(abs) for abs in items]),
1548 max([len(m.rel(abs)) for abs in items]))
1548 max([len(m.rel(abs)) for abs in items]))
1549 for abs in items:
1549 for abs in items:
1550 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1550 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1551 ui.write("%s\n" % line.rstrip())
1551 ui.write("%s\n" % line.rstrip())
1552
1552
1553 def diff(ui, repo, *pats, **opts):
1553 def diff(ui, repo, *pats, **opts):
1554 """diff repository (or selected files)
1554 """diff repository (or selected files)
1555
1555
1556 Show differences between revisions for the specified files.
1556 Show differences between revisions for the specified files.
1557
1557
1558 Differences between files are shown using the unified diff format.
1558 Differences between files are shown using the unified diff format.
1559
1559
1560 .. note::
1560 .. note::
1561 diff may generate unexpected results for merges, as it will
1561 diff may generate unexpected results for merges, as it will
1562 default to comparing against the working directory's first
1562 default to comparing against the working directory's first
1563 parent changeset if no revisions are specified.
1563 parent changeset if no revisions are specified.
1564
1564
1565 When two revision arguments are given, then changes are shown
1565 When two revision arguments are given, then changes are shown
1566 between those revisions. If only one revision is specified then
1566 between those revisions. If only one revision is specified then
1567 that revision is compared to the working directory, and, when no
1567 that revision is compared to the working directory, and, when no
1568 revisions are specified, the working directory files are compared
1568 revisions are specified, the working directory files are compared
1569 to its parent.
1569 to its parent.
1570
1570
1571 Alternatively you can specify -c/--change with a revision to see
1571 Alternatively you can specify -c/--change with a revision to see
1572 the changes in that changeset relative to its first parent.
1572 the changes in that changeset relative to its first parent.
1573
1573
1574 Without the -a/--text option, diff will avoid generating diffs of
1574 Without the -a/--text option, diff will avoid generating diffs of
1575 files it detects as binary. With -a, diff will generate a diff
1575 files it detects as binary. With -a, diff will generate a diff
1576 anyway, probably with undesirable results.
1576 anyway, probably with undesirable results.
1577
1577
1578 Use the -g/--git option to generate diffs in the git extended diff
1578 Use the -g/--git option to generate diffs in the git extended diff
1579 format. For more information, read :hg:`help diffs`.
1579 format. For more information, read :hg:`help diffs`.
1580
1580
1581 Returns 0 on success.
1581 Returns 0 on success.
1582 """
1582 """
1583
1583
1584 revs = opts.get('rev')
1584 revs = opts.get('rev')
1585 change = opts.get('change')
1585 change = opts.get('change')
1586 stat = opts.get('stat')
1586 stat = opts.get('stat')
1587 reverse = opts.get('reverse')
1587 reverse = opts.get('reverse')
1588
1588
1589 if revs and change:
1589 if revs and change:
1590 msg = _('cannot specify --rev and --change at the same time')
1590 msg = _('cannot specify --rev and --change at the same time')
1591 raise util.Abort(msg)
1591 raise util.Abort(msg)
1592 elif change:
1592 elif change:
1593 node2 = repo.lookup(change)
1593 node2 = repo.lookup(change)
1594 node1 = repo[node2].parents()[0].node()
1594 node1 = repo[node2].parents()[0].node()
1595 else:
1595 else:
1596 node1, node2 = cmdutil.revpair(repo, revs)
1596 node1, node2 = cmdutil.revpair(repo, revs)
1597
1597
1598 if reverse:
1598 if reverse:
1599 node1, node2 = node2, node1
1599 node1, node2 = node2, node1
1600
1600
1601 diffopts = patch.diffopts(ui, opts)
1601 diffopts = patch.diffopts(ui, opts)
1602 m = cmdutil.match(repo, pats, opts)
1602 m = cmdutil.match(repo, pats, opts)
1603 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1603 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1604 listsubrepos=opts.get('subrepos'))
1604 listsubrepos=opts.get('subrepos'))
1605
1605
1606 def export(ui, repo, *changesets, **opts):
1606 def export(ui, repo, *changesets, **opts):
1607 """dump the header and diffs for one or more changesets
1607 """dump the header and diffs for one or more changesets
1608
1608
1609 Print the changeset header and diffs for one or more revisions.
1609 Print the changeset header and diffs for one or more revisions.
1610
1610
1611 The information shown in the changeset header is: author, date,
1611 The information shown in the changeset header is: author, date,
1612 branch name (if non-default), changeset hash, parent(s) and commit
1612 branch name (if non-default), changeset hash, parent(s) and commit
1613 comment.
1613 comment.
1614
1614
1615 .. note::
1615 .. note::
1616 export may generate unexpected diff output for merge
1616 export may generate unexpected diff output for merge
1617 changesets, as it will compare the merge changeset against its
1617 changesets, as it will compare the merge changeset against its
1618 first parent only.
1618 first parent only.
1619
1619
1620 Output may be to a file, in which case the name of the file is
1620 Output may be to a file, in which case the name of the file is
1621 given using a format string. The formatting rules are as follows:
1621 given using a format string. The formatting rules are as follows:
1622
1622
1623 :``%%``: literal "%" character
1623 :``%%``: literal "%" character
1624 :``%H``: changeset hash (40 hexadecimal digits)
1624 :``%H``: changeset hash (40 hexadecimal digits)
1625 :``%N``: number of patches being generated
1625 :``%N``: number of patches being generated
1626 :``%R``: changeset revision number
1626 :``%R``: changeset revision number
1627 :``%b``: basename of the exporting repository
1627 :``%b``: basename of the exporting repository
1628 :``%h``: short-form changeset hash (12 hexadecimal digits)
1628 :``%h``: short-form changeset hash (12 hexadecimal digits)
1629 :``%n``: zero-padded sequence number, starting at 1
1629 :``%n``: zero-padded sequence number, starting at 1
1630 :``%r``: zero-padded changeset revision number
1630 :``%r``: zero-padded changeset revision number
1631
1631
1632 Without the -a/--text option, export will avoid generating diffs
1632 Without the -a/--text option, export will avoid generating diffs
1633 of files it detects as binary. With -a, export will generate a
1633 of files it detects as binary. With -a, export will generate a
1634 diff anyway, probably with undesirable results.
1634 diff anyway, probably with undesirable results.
1635
1635
1636 Use the -g/--git option to generate diffs in the git extended diff
1636 Use the -g/--git option to generate diffs in the git extended diff
1637 format. See :hg:`help diffs` for more information.
1637 format. See :hg:`help diffs` for more information.
1638
1638
1639 With the --switch-parent option, the diff will be against the
1639 With the --switch-parent option, the diff will be against the
1640 second parent. It can be useful to review a merge.
1640 second parent. It can be useful to review a merge.
1641
1641
1642 Returns 0 on success.
1642 Returns 0 on success.
1643 """
1643 """
1644 changesets += tuple(opts.get('rev', []))
1644 changesets += tuple(opts.get('rev', []))
1645 if not changesets:
1645 if not changesets:
1646 raise util.Abort(_("export requires at least one changeset"))
1646 raise util.Abort(_("export requires at least one changeset"))
1647 revs = cmdutil.revrange(repo, changesets)
1647 revs = cmdutil.revrange(repo, changesets)
1648 if len(revs) > 1:
1648 if len(revs) > 1:
1649 ui.note(_('exporting patches:\n'))
1649 ui.note(_('exporting patches:\n'))
1650 else:
1650 else:
1651 ui.note(_('exporting patch:\n'))
1651 ui.note(_('exporting patch:\n'))
1652 cmdutil.export(repo, revs, template=opts.get('output'),
1652 cmdutil.export(repo, revs, template=opts.get('output'),
1653 switch_parent=opts.get('switch_parent'),
1653 switch_parent=opts.get('switch_parent'),
1654 opts=patch.diffopts(ui, opts))
1654 opts=patch.diffopts(ui, opts))
1655
1655
1656 def forget(ui, repo, *pats, **opts):
1656 def forget(ui, repo, *pats, **opts):
1657 """forget the specified files on the next commit
1657 """forget the specified files on the next commit
1658
1658
1659 Mark the specified files so they will no longer be tracked
1659 Mark the specified files so they will no longer be tracked
1660 after the next commit.
1660 after the next commit.
1661
1661
1662 This only removes files from the current branch, not from the
1662 This only removes files from the current branch, not from the
1663 entire project history, and it does not delete them from the
1663 entire project history, and it does not delete them from the
1664 working directory.
1664 working directory.
1665
1665
1666 To undo a forget before the next commit, see :hg:`add`.
1666 To undo a forget before the next commit, see :hg:`add`.
1667
1667
1668 Returns 0 on success.
1668 Returns 0 on success.
1669 """
1669 """
1670
1670
1671 if not pats:
1671 if not pats:
1672 raise util.Abort(_('no files specified'))
1672 raise util.Abort(_('no files specified'))
1673
1673
1674 m = cmdutil.match(repo, pats, opts)
1674 m = cmdutil.match(repo, pats, opts)
1675 s = repo.status(match=m, clean=True)
1675 s = repo.status(match=m, clean=True)
1676 forget = sorted(s[0] + s[1] + s[3] + s[6])
1676 forget = sorted(s[0] + s[1] + s[3] + s[6])
1677 errs = 0
1677 errs = 0
1678
1678
1679 for f in m.files():
1679 for f in m.files():
1680 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1680 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1681 ui.warn(_('not removing %s: file is already untracked\n')
1681 ui.warn(_('not removing %s: file is already untracked\n')
1682 % m.rel(f))
1682 % m.rel(f))
1683 errs = 1
1683 errs = 1
1684
1684
1685 for f in forget:
1685 for f in forget:
1686 if ui.verbose or not m.exact(f):
1686 if ui.verbose or not m.exact(f):
1687 ui.status(_('removing %s\n') % m.rel(f))
1687 ui.status(_('removing %s\n') % m.rel(f))
1688
1688
1689 repo[None].remove(forget, unlink=False)
1689 repo[None].remove(forget, unlink=False)
1690 return errs
1690 return errs
1691
1691
1692 def grep(ui, repo, pattern, *pats, **opts):
1692 def grep(ui, repo, pattern, *pats, **opts):
1693 """search for a pattern in specified files and revisions
1693 """search for a pattern in specified files and revisions
1694
1694
1695 Search revisions of files for a regular expression.
1695 Search revisions of files for a regular expression.
1696
1696
1697 This command behaves differently than Unix grep. It only accepts
1697 This command behaves differently than Unix grep. It only accepts
1698 Python/Perl regexps. It searches repository history, not the
1698 Python/Perl regexps. It searches repository history, not the
1699 working directory. It always prints the revision number in which a
1699 working directory. It always prints the revision number in which a
1700 match appears.
1700 match appears.
1701
1701
1702 By default, grep only prints output for the first revision of a
1702 By default, grep only prints output for the first revision of a
1703 file in which it finds a match. To get it to print every revision
1703 file in which it finds a match. To get it to print every revision
1704 that contains a change in match status ("-" for a match that
1704 that contains a change in match status ("-" for a match that
1705 becomes a non-match, or "+" for a non-match that becomes a match),
1705 becomes a non-match, or "+" for a non-match that becomes a match),
1706 use the --all flag.
1706 use the --all flag.
1707
1707
1708 Returns 0 if a match is found, 1 otherwise.
1708 Returns 0 if a match is found, 1 otherwise.
1709 """
1709 """
1710 reflags = 0
1710 reflags = 0
1711 if opts.get('ignore_case'):
1711 if opts.get('ignore_case'):
1712 reflags |= re.I
1712 reflags |= re.I
1713 try:
1713 try:
1714 regexp = re.compile(pattern, reflags)
1714 regexp = re.compile(pattern, reflags)
1715 except re.error, inst:
1715 except re.error, inst:
1716 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1716 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1717 return 1
1717 return 1
1718 sep, eol = ':', '\n'
1718 sep, eol = ':', '\n'
1719 if opts.get('print0'):
1719 if opts.get('print0'):
1720 sep = eol = '\0'
1720 sep = eol = '\0'
1721
1721
1722 getfile = util.lrucachefunc(repo.file)
1722 getfile = util.lrucachefunc(repo.file)
1723
1723
1724 def matchlines(body):
1724 def matchlines(body):
1725 begin = 0
1725 begin = 0
1726 linenum = 0
1726 linenum = 0
1727 while True:
1727 while True:
1728 match = regexp.search(body, begin)
1728 match = regexp.search(body, begin)
1729 if not match:
1729 if not match:
1730 break
1730 break
1731 mstart, mend = match.span()
1731 mstart, mend = match.span()
1732 linenum += body.count('\n', begin, mstart) + 1
1732 linenum += body.count('\n', begin, mstart) + 1
1733 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1733 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1734 begin = body.find('\n', mend) + 1 or len(body)
1734 begin = body.find('\n', mend) + 1 or len(body)
1735 lend = begin - 1
1735 lend = begin - 1
1736 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1736 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1737
1737
1738 class linestate(object):
1738 class linestate(object):
1739 def __init__(self, line, linenum, colstart, colend):
1739 def __init__(self, line, linenum, colstart, colend):
1740 self.line = line
1740 self.line = line
1741 self.linenum = linenum
1741 self.linenum = linenum
1742 self.colstart = colstart
1742 self.colstart = colstart
1743 self.colend = colend
1743 self.colend = colend
1744
1744
1745 def __hash__(self):
1745 def __hash__(self):
1746 return hash((self.linenum, self.line))
1746 return hash((self.linenum, self.line))
1747
1747
1748 def __eq__(self, other):
1748 def __eq__(self, other):
1749 return self.line == other.line
1749 return self.line == other.line
1750
1750
1751 matches = {}
1751 matches = {}
1752 copies = {}
1752 copies = {}
1753 def grepbody(fn, rev, body):
1753 def grepbody(fn, rev, body):
1754 matches[rev].setdefault(fn, [])
1754 matches[rev].setdefault(fn, [])
1755 m = matches[rev][fn]
1755 m = matches[rev][fn]
1756 for lnum, cstart, cend, line in matchlines(body):
1756 for lnum, cstart, cend, line in matchlines(body):
1757 s = linestate(line, lnum, cstart, cend)
1757 s = linestate(line, lnum, cstart, cend)
1758 m.append(s)
1758 m.append(s)
1759
1759
1760 def difflinestates(a, b):
1760 def difflinestates(a, b):
1761 sm = difflib.SequenceMatcher(None, a, b)
1761 sm = difflib.SequenceMatcher(None, a, b)
1762 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1762 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1763 if tag == 'insert':
1763 if tag == 'insert':
1764 for i in xrange(blo, bhi):
1764 for i in xrange(blo, bhi):
1765 yield ('+', b[i])
1765 yield ('+', b[i])
1766 elif tag == 'delete':
1766 elif tag == 'delete':
1767 for i in xrange(alo, ahi):
1767 for i in xrange(alo, ahi):
1768 yield ('-', a[i])
1768 yield ('-', a[i])
1769 elif tag == 'replace':
1769 elif tag == 'replace':
1770 for i in xrange(alo, ahi):
1770 for i in xrange(alo, ahi):
1771 yield ('-', a[i])
1771 yield ('-', a[i])
1772 for i in xrange(blo, bhi):
1772 for i in xrange(blo, bhi):
1773 yield ('+', b[i])
1773 yield ('+', b[i])
1774
1774
1775 def display(fn, ctx, pstates, states):
1775 def display(fn, ctx, pstates, states):
1776 rev = ctx.rev()
1776 rev = ctx.rev()
1777 datefunc = ui.quiet and util.shortdate or util.datestr
1777 datefunc = ui.quiet and util.shortdate or util.datestr
1778 found = False
1778 found = False
1779 filerevmatches = {}
1779 filerevmatches = {}
1780 if opts.get('all'):
1780 if opts.get('all'):
1781 iter = difflinestates(pstates, states)
1781 iter = difflinestates(pstates, states)
1782 else:
1782 else:
1783 iter = [('', l) for l in states]
1783 iter = [('', l) for l in states]
1784 for change, l in iter:
1784 for change, l in iter:
1785 cols = [fn, str(rev)]
1785 cols = [fn, str(rev)]
1786 before, match, after = None, None, None
1786 before, match, after = None, None, None
1787 if opts.get('line_number'):
1787 if opts.get('line_number'):
1788 cols.append(str(l.linenum))
1788 cols.append(str(l.linenum))
1789 if opts.get('all'):
1789 if opts.get('all'):
1790 cols.append(change)
1790 cols.append(change)
1791 if opts.get('user'):
1791 if opts.get('user'):
1792 cols.append(ui.shortuser(ctx.user()))
1792 cols.append(ui.shortuser(ctx.user()))
1793 if opts.get('date'):
1793 if opts.get('date'):
1794 cols.append(datefunc(ctx.date()))
1794 cols.append(datefunc(ctx.date()))
1795 if opts.get('files_with_matches'):
1795 if opts.get('files_with_matches'):
1796 c = (fn, rev)
1796 c = (fn, rev)
1797 if c in filerevmatches:
1797 if c in filerevmatches:
1798 continue
1798 continue
1799 filerevmatches[c] = 1
1799 filerevmatches[c] = 1
1800 else:
1800 else:
1801 before = l.line[:l.colstart]
1801 before = l.line[:l.colstart]
1802 match = l.line[l.colstart:l.colend]
1802 match = l.line[l.colstart:l.colend]
1803 after = l.line[l.colend:]
1803 after = l.line[l.colend:]
1804 ui.write(sep.join(cols))
1804 ui.write(sep.join(cols))
1805 if before is not None:
1805 if before is not None:
1806 ui.write(sep + before)
1806 ui.write(sep + before)
1807 ui.write(match, label='grep.match')
1807 ui.write(match, label='grep.match')
1808 ui.write(after)
1808 ui.write(after)
1809 ui.write(eol)
1809 ui.write(eol)
1810 found = True
1810 found = True
1811 return found
1811 return found
1812
1812
1813 skip = {}
1813 skip = {}
1814 revfiles = {}
1814 revfiles = {}
1815 matchfn = cmdutil.match(repo, pats, opts)
1815 matchfn = cmdutil.match(repo, pats, opts)
1816 found = False
1816 found = False
1817 follow = opts.get('follow')
1817 follow = opts.get('follow')
1818
1818
1819 def prep(ctx, fns):
1819 def prep(ctx, fns):
1820 rev = ctx.rev()
1820 rev = ctx.rev()
1821 pctx = ctx.parents()[0]
1821 pctx = ctx.parents()[0]
1822 parent = pctx.rev()
1822 parent = pctx.rev()
1823 matches.setdefault(rev, {})
1823 matches.setdefault(rev, {})
1824 matches.setdefault(parent, {})
1824 matches.setdefault(parent, {})
1825 files = revfiles.setdefault(rev, [])
1825 files = revfiles.setdefault(rev, [])
1826 for fn in fns:
1826 for fn in fns:
1827 flog = getfile(fn)
1827 flog = getfile(fn)
1828 try:
1828 try:
1829 fnode = ctx.filenode(fn)
1829 fnode = ctx.filenode(fn)
1830 except error.LookupError:
1830 except error.LookupError:
1831 continue
1831 continue
1832
1832
1833 copied = flog.renamed(fnode)
1833 copied = flog.renamed(fnode)
1834 copy = follow and copied and copied[0]
1834 copy = follow and copied and copied[0]
1835 if copy:
1835 if copy:
1836 copies.setdefault(rev, {})[fn] = copy
1836 copies.setdefault(rev, {})[fn] = copy
1837 if fn in skip:
1837 if fn in skip:
1838 if copy:
1838 if copy:
1839 skip[copy] = True
1839 skip[copy] = True
1840 continue
1840 continue
1841 files.append(fn)
1841 files.append(fn)
1842
1842
1843 if fn not in matches[rev]:
1843 if fn not in matches[rev]:
1844 grepbody(fn, rev, flog.read(fnode))
1844 grepbody(fn, rev, flog.read(fnode))
1845
1845
1846 pfn = copy or fn
1846 pfn = copy or fn
1847 if pfn not in matches[parent]:
1847 if pfn not in matches[parent]:
1848 try:
1848 try:
1849 fnode = pctx.filenode(pfn)
1849 fnode = pctx.filenode(pfn)
1850 grepbody(pfn, parent, flog.read(fnode))
1850 grepbody(pfn, parent, flog.read(fnode))
1851 except error.LookupError:
1851 except error.LookupError:
1852 pass
1852 pass
1853
1853
1854 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
1854 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
1855 rev = ctx.rev()
1855 rev = ctx.rev()
1856 parent = ctx.parents()[0].rev()
1856 parent = ctx.parents()[0].rev()
1857 for fn in sorted(revfiles.get(rev, [])):
1857 for fn in sorted(revfiles.get(rev, [])):
1858 states = matches[rev][fn]
1858 states = matches[rev][fn]
1859 copy = copies.get(rev, {}).get(fn)
1859 copy = copies.get(rev, {}).get(fn)
1860 if fn in skip:
1860 if fn in skip:
1861 if copy:
1861 if copy:
1862 skip[copy] = True
1862 skip[copy] = True
1863 continue
1863 continue
1864 pstates = matches.get(parent, {}).get(copy or fn, [])
1864 pstates = matches.get(parent, {}).get(copy or fn, [])
1865 if pstates or states:
1865 if pstates or states:
1866 r = display(fn, ctx, pstates, states)
1866 r = display(fn, ctx, pstates, states)
1867 found = found or r
1867 found = found or r
1868 if r and not opts.get('all'):
1868 if r and not opts.get('all'):
1869 skip[fn] = True
1869 skip[fn] = True
1870 if copy:
1870 if copy:
1871 skip[copy] = True
1871 skip[copy] = True
1872 del matches[rev]
1872 del matches[rev]
1873 del revfiles[rev]
1873 del revfiles[rev]
1874
1874
1875 return not found
1875 return not found
1876
1876
1877 def heads(ui, repo, *branchrevs, **opts):
1877 def heads(ui, repo, *branchrevs, **opts):
1878 """show current repository heads or show branch heads
1878 """show current repository heads or show branch heads
1879
1879
1880 With no arguments, show all repository branch heads.
1880 With no arguments, show all repository branch heads.
1881
1881
1882 Repository "heads" are changesets with no child changesets. They are
1882 Repository "heads" are changesets with no child changesets. They are
1883 where development generally takes place and are the usual targets
1883 where development generally takes place and are the usual targets
1884 for update and merge operations. Branch heads are changesets that have
1884 for update and merge operations. Branch heads are changesets that have
1885 no child changeset on the same branch.
1885 no child changeset on the same branch.
1886
1886
1887 If one or more REVs are given, only branch heads on the branches
1887 If one or more REVs are given, only branch heads on the branches
1888 associated with the specified changesets are shown.
1888 associated with the specified changesets are shown.
1889
1889
1890 If -c/--closed is specified, also show branch heads marked closed
1890 If -c/--closed is specified, also show branch heads marked closed
1891 (see :hg:`commit --close-branch`).
1891 (see :hg:`commit --close-branch`).
1892
1892
1893 If STARTREV is specified, only those heads that are descendants of
1893 If STARTREV is specified, only those heads that are descendants of
1894 STARTREV will be displayed.
1894 STARTREV will be displayed.
1895
1895
1896 If -t/--topo is specified, named branch mechanics will be ignored and only
1896 If -t/--topo is specified, named branch mechanics will be ignored and only
1897 changesets without children will be shown.
1897 changesets without children will be shown.
1898
1898
1899 Returns 0 if matching heads are found, 1 if not.
1899 Returns 0 if matching heads are found, 1 if not.
1900 """
1900 """
1901
1901
1902 start = None
1902 start = None
1903 if 'rev' in opts:
1903 if 'rev' in opts:
1904 start = cmdutil.revsingle(repo, opts['rev'], None).node()
1904 start = cmdutil.revsingle(repo, opts['rev'], None).node()
1905
1905
1906 if opts.get('topo'):
1906 if opts.get('topo'):
1907 heads = [repo[h] for h in repo.heads(start)]
1907 heads = [repo[h] for h in repo.heads(start)]
1908 else:
1908 else:
1909 heads = []
1909 heads = []
1910 for b, ls in repo.branchmap().iteritems():
1910 for b, ls in repo.branchmap().iteritems():
1911 if start is None:
1911 if start is None:
1912 heads += [repo[h] for h in ls]
1912 heads += [repo[h] for h in ls]
1913 continue
1913 continue
1914 startrev = repo.changelog.rev(start)
1914 startrev = repo.changelog.rev(start)
1915 descendants = set(repo.changelog.descendants(startrev))
1915 descendants = set(repo.changelog.descendants(startrev))
1916 descendants.add(startrev)
1916 descendants.add(startrev)
1917 rev = repo.changelog.rev
1917 rev = repo.changelog.rev
1918 heads += [repo[h] for h in ls if rev(h) in descendants]
1918 heads += [repo[h] for h in ls if rev(h) in descendants]
1919
1919
1920 if branchrevs:
1920 if branchrevs:
1921 branches = set(repo[br].branch() for br in branchrevs)
1921 branches = set(repo[br].branch() for br in branchrevs)
1922 heads = [h for h in heads if h.branch() in branches]
1922 heads = [h for h in heads if h.branch() in branches]
1923
1923
1924 if not opts.get('closed'):
1924 if not opts.get('closed'):
1925 heads = [h for h in heads if not h.extra().get('close')]
1925 heads = [h for h in heads if not h.extra().get('close')]
1926
1926
1927 if opts.get('active') and branchrevs:
1927 if opts.get('active') and branchrevs:
1928 dagheads = repo.heads(start)
1928 dagheads = repo.heads(start)
1929 heads = [h for h in heads if h.node() in dagheads]
1929 heads = [h for h in heads if h.node() in dagheads]
1930
1930
1931 if branchrevs:
1931 if branchrevs:
1932 haveheads = set(h.branch() for h in heads)
1932 haveheads = set(h.branch() for h in heads)
1933 if branches - haveheads:
1933 if branches - haveheads:
1934 headless = ', '.join(b for b in branches - haveheads)
1934 headless = ', '.join(b for b in branches - haveheads)
1935 msg = _('no open branch heads found on branches %s')
1935 msg = _('no open branch heads found on branches %s')
1936 if opts.get('rev'):
1936 if opts.get('rev'):
1937 msg += _(' (started at %s)' % opts['rev'])
1937 msg += _(' (started at %s)' % opts['rev'])
1938 ui.warn((msg + '\n') % headless)
1938 ui.warn((msg + '\n') % headless)
1939
1939
1940 if not heads:
1940 if not heads:
1941 return 1
1941 return 1
1942
1942
1943 heads = sorted(heads, key=lambda x: -x.rev())
1943 heads = sorted(heads, key=lambda x: -x.rev())
1944 displayer = cmdutil.show_changeset(ui, repo, opts)
1944 displayer = cmdutil.show_changeset(ui, repo, opts)
1945 for ctx in heads:
1945 for ctx in heads:
1946 displayer.show(ctx)
1946 displayer.show(ctx)
1947 displayer.close()
1947 displayer.close()
1948
1948
1949 def help_(ui, name=None, with_version=False, unknowncmd=False):
1949 def help_(ui, name=None, with_version=False, unknowncmd=False):
1950 """show help for a given topic or a help overview
1950 """show help for a given topic or a help overview
1951
1951
1952 With no arguments, print a list of commands with short help messages.
1952 With no arguments, print a list of commands with short help messages.
1953
1953
1954 Given a topic, extension, or command name, print help for that
1954 Given a topic, extension, or command name, print help for that
1955 topic.
1955 topic.
1956
1956
1957 Returns 0 if successful.
1957 Returns 0 if successful.
1958 """
1958 """
1959 option_lists = []
1959 option_lists = []
1960 textwidth = ui.termwidth() - 2
1960 textwidth = ui.termwidth() - 2
1961
1961
1962 def addglobalopts(aliases):
1962 def addglobalopts(aliases):
1963 if ui.verbose:
1963 if ui.verbose:
1964 option_lists.append((_("global options:"), globalopts))
1964 option_lists.append((_("global options:"), globalopts))
1965 if name == 'shortlist':
1965 if name == 'shortlist':
1966 option_lists.append((_('use "hg help" for the full list '
1966 option_lists.append((_('use "hg help" for the full list '
1967 'of commands'), ()))
1967 'of commands'), ()))
1968 else:
1968 else:
1969 if name == 'shortlist':
1969 if name == 'shortlist':
1970 msg = _('use "hg help" for the full list of commands '
1970 msg = _('use "hg help" for the full list of commands '
1971 'or "hg -v" for details')
1971 'or "hg -v" for details')
1972 elif aliases:
1972 elif aliases:
1973 msg = _('use "hg -v help%s" to show builtin aliases and '
1973 msg = _('use "hg -v help%s" to show builtin aliases and '
1974 'global options') % (name and " " + name or "")
1974 'global options') % (name and " " + name or "")
1975 else:
1975 else:
1976 msg = _('use "hg -v help %s" to show global options') % name
1976 msg = _('use "hg -v help %s" to show global options') % name
1977 option_lists.append((msg, ()))
1977 option_lists.append((msg, ()))
1978
1978
1979 def helpcmd(name):
1979 def helpcmd(name):
1980 if with_version:
1980 if with_version:
1981 version_(ui)
1981 version_(ui)
1982 ui.write('\n')
1982 ui.write('\n')
1983
1983
1984 try:
1984 try:
1985 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
1985 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
1986 except error.AmbiguousCommand, inst:
1986 except error.AmbiguousCommand, inst:
1987 # py3k fix: except vars can't be used outside the scope of the
1987 # py3k fix: except vars can't be used outside the scope of the
1988 # except block, nor can be used inside a lambda. python issue4617
1988 # except block, nor can be used inside a lambda. python issue4617
1989 prefix = inst.args[0]
1989 prefix = inst.args[0]
1990 select = lambda c: c.lstrip('^').startswith(prefix)
1990 select = lambda c: c.lstrip('^').startswith(prefix)
1991 helplist(_('list of commands:\n\n'), select)
1991 helplist(_('list of commands:\n\n'), select)
1992 return
1992 return
1993
1993
1994 # check if it's an invalid alias and display its error if it is
1994 # check if it's an invalid alias and display its error if it is
1995 if getattr(entry[0], 'badalias', False):
1995 if getattr(entry[0], 'badalias', False):
1996 if not unknowncmd:
1996 if not unknowncmd:
1997 entry[0](ui)
1997 entry[0](ui)
1998 return
1998 return
1999
1999
2000 # synopsis
2000 # synopsis
2001 if len(entry) > 2:
2001 if len(entry) > 2:
2002 if entry[2].startswith('hg'):
2002 if entry[2].startswith('hg'):
2003 ui.write("%s\n" % entry[2])
2003 ui.write("%s\n" % entry[2])
2004 else:
2004 else:
2005 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
2005 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
2006 else:
2006 else:
2007 ui.write('hg %s\n' % aliases[0])
2007 ui.write('hg %s\n' % aliases[0])
2008
2008
2009 # aliases
2009 # aliases
2010 if not ui.quiet and len(aliases) > 1:
2010 if not ui.quiet and len(aliases) > 1:
2011 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
2011 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
2012
2012
2013 # description
2013 # description
2014 doc = gettext(entry[0].__doc__)
2014 doc = gettext(entry[0].__doc__)
2015 if not doc:
2015 if not doc:
2016 doc = _("(no help text available)")
2016 doc = _("(no help text available)")
2017 if hasattr(entry[0], 'definition'): # aliased command
2017 if hasattr(entry[0], 'definition'): # aliased command
2018 if entry[0].definition.startswith('!'): # shell alias
2018 if entry[0].definition.startswith('!'): # shell alias
2019 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
2019 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
2020 else:
2020 else:
2021 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
2021 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
2022 if ui.quiet:
2022 if ui.quiet:
2023 doc = doc.splitlines()[0]
2023 doc = doc.splitlines()[0]
2024 keep = ui.verbose and ['verbose'] or []
2024 keep = ui.verbose and ['verbose'] or []
2025 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
2025 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
2026 ui.write("\n%s\n" % formatted)
2026 ui.write("\n%s\n" % formatted)
2027 if pruned:
2027 if pruned:
2028 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
2028 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
2029
2029
2030 if not ui.quiet:
2030 if not ui.quiet:
2031 # options
2031 # options
2032 if entry[1]:
2032 if entry[1]:
2033 option_lists.append((_("options:\n"), entry[1]))
2033 option_lists.append((_("options:\n"), entry[1]))
2034
2034
2035 addglobalopts(False)
2035 addglobalopts(False)
2036
2036
2037 def helplist(header, select=None):
2037 def helplist(header, select=None):
2038 h = {}
2038 h = {}
2039 cmds = {}
2039 cmds = {}
2040 for c, e in table.iteritems():
2040 for c, e in table.iteritems():
2041 f = c.split("|", 1)[0]
2041 f = c.split("|", 1)[0]
2042 if select and not select(f):
2042 if select and not select(f):
2043 continue
2043 continue
2044 if (not select and name != 'shortlist' and
2044 if (not select and name != 'shortlist' and
2045 e[0].__module__ != __name__):
2045 e[0].__module__ != __name__):
2046 continue
2046 continue
2047 if name == "shortlist" and not f.startswith("^"):
2047 if name == "shortlist" and not f.startswith("^"):
2048 continue
2048 continue
2049 f = f.lstrip("^")
2049 f = f.lstrip("^")
2050 if not ui.debugflag and f.startswith("debug"):
2050 if not ui.debugflag and f.startswith("debug"):
2051 continue
2051 continue
2052 doc = e[0].__doc__
2052 doc = e[0].__doc__
2053 if doc and 'DEPRECATED' in doc and not ui.verbose:
2053 if doc and 'DEPRECATED' in doc and not ui.verbose:
2054 continue
2054 continue
2055 doc = gettext(doc)
2055 doc = gettext(doc)
2056 if not doc:
2056 if not doc:
2057 doc = _("(no help text available)")
2057 doc = _("(no help text available)")
2058 h[f] = doc.splitlines()[0].rstrip()
2058 h[f] = doc.splitlines()[0].rstrip()
2059 cmds[f] = c.lstrip("^")
2059 cmds[f] = c.lstrip("^")
2060
2060
2061 if not h:
2061 if not h:
2062 ui.status(_('no commands defined\n'))
2062 ui.status(_('no commands defined\n'))
2063 return
2063 return
2064
2064
2065 ui.status(header)
2065 ui.status(header)
2066 fns = sorted(h)
2066 fns = sorted(h)
2067 m = max(map(len, fns))
2067 m = max(map(len, fns))
2068 for f in fns:
2068 for f in fns:
2069 if ui.verbose:
2069 if ui.verbose:
2070 commands = cmds[f].replace("|",", ")
2070 commands = cmds[f].replace("|",", ")
2071 ui.write(" %s:\n %s\n"%(commands, h[f]))
2071 ui.write(" %s:\n %s\n"%(commands, h[f]))
2072 else:
2072 else:
2073 ui.write('%s\n' % (util.wrap(h[f], textwidth,
2073 ui.write('%s\n' % (util.wrap(h[f], textwidth,
2074 initindent=' %-*s ' % (m, f),
2074 initindent=' %-*s ' % (m, f),
2075 hangindent=' ' * (m + 4))))
2075 hangindent=' ' * (m + 4))))
2076
2076
2077 if not ui.quiet:
2077 if not ui.quiet:
2078 addglobalopts(True)
2078 addglobalopts(True)
2079
2079
2080 def helptopic(name):
2080 def helptopic(name):
2081 for names, header, doc in help.helptable:
2081 for names, header, doc in help.helptable:
2082 if name in names:
2082 if name in names:
2083 break
2083 break
2084 else:
2084 else:
2085 raise error.UnknownCommand(name)
2085 raise error.UnknownCommand(name)
2086
2086
2087 # description
2087 # description
2088 if not doc:
2088 if not doc:
2089 doc = _("(no help text available)")
2089 doc = _("(no help text available)")
2090 if hasattr(doc, '__call__'):
2090 if hasattr(doc, '__call__'):
2091 doc = doc()
2091 doc = doc()
2092
2092
2093 ui.write("%s\n\n" % header)
2093 ui.write("%s\n\n" % header)
2094 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
2094 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
2095
2095
2096 def helpext(name):
2096 def helpext(name):
2097 try:
2097 try:
2098 mod = extensions.find(name)
2098 mod = extensions.find(name)
2099 doc = gettext(mod.__doc__) or _('no help text available')
2099 doc = gettext(mod.__doc__) or _('no help text available')
2100 except KeyError:
2100 except KeyError:
2101 mod = None
2101 mod = None
2102 doc = extensions.disabledext(name)
2102 doc = extensions.disabledext(name)
2103 if not doc:
2103 if not doc:
2104 raise error.UnknownCommand(name)
2104 raise error.UnknownCommand(name)
2105
2105
2106 if '\n' not in doc:
2106 if '\n' not in doc:
2107 head, tail = doc, ""
2107 head, tail = doc, ""
2108 else:
2108 else:
2109 head, tail = doc.split('\n', 1)
2109 head, tail = doc.split('\n', 1)
2110 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
2110 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
2111 if tail:
2111 if tail:
2112 ui.write(minirst.format(tail, textwidth))
2112 ui.write(minirst.format(tail, textwidth))
2113 ui.status('\n\n')
2113 ui.status('\n\n')
2114
2114
2115 if mod:
2115 if mod:
2116 try:
2116 try:
2117 ct = mod.cmdtable
2117 ct = mod.cmdtable
2118 except AttributeError:
2118 except AttributeError:
2119 ct = {}
2119 ct = {}
2120 modcmds = set([c.split('|', 1)[0] for c in ct])
2120 modcmds = set([c.split('|', 1)[0] for c in ct])
2121 helplist(_('list of commands:\n\n'), modcmds.__contains__)
2121 helplist(_('list of commands:\n\n'), modcmds.__contains__)
2122 else:
2122 else:
2123 ui.write(_('use "hg help extensions" for information on enabling '
2123 ui.write(_('use "hg help extensions" for information on enabling '
2124 'extensions\n'))
2124 'extensions\n'))
2125
2125
2126 def helpextcmd(name):
2126 def helpextcmd(name):
2127 cmd, ext, mod = extensions.disabledcmd(ui, name, ui.config('ui', 'strict'))
2127 cmd, ext, mod = extensions.disabledcmd(ui, name, ui.config('ui', 'strict'))
2128 doc = gettext(mod.__doc__).splitlines()[0]
2128 doc = gettext(mod.__doc__).splitlines()[0]
2129
2129
2130 msg = help.listexts(_("'%s' is provided by the following "
2130 msg = help.listexts(_("'%s' is provided by the following "
2131 "extension:") % cmd, {ext: doc}, len(ext),
2131 "extension:") % cmd, {ext: doc}, len(ext),
2132 indent=4)
2132 indent=4)
2133 ui.write(minirst.format(msg, textwidth))
2133 ui.write(minirst.format(msg, textwidth))
2134 ui.write('\n\n')
2134 ui.write('\n\n')
2135 ui.write(_('use "hg help extensions" for information on enabling '
2135 ui.write(_('use "hg help extensions" for information on enabling '
2136 'extensions\n'))
2136 'extensions\n'))
2137
2137
2138 help.addtopichook('revsets', revset.makedoc)
2138 help.addtopichook('revsets', revset.makedoc)
2139
2139
2140 if name and name != 'shortlist':
2140 if name and name != 'shortlist':
2141 i = None
2141 i = None
2142 if unknowncmd:
2142 if unknowncmd:
2143 queries = (helpextcmd,)
2143 queries = (helpextcmd,)
2144 else:
2144 else:
2145 queries = (helptopic, helpcmd, helpext, helpextcmd)
2145 queries = (helptopic, helpcmd, helpext, helpextcmd)
2146 for f in queries:
2146 for f in queries:
2147 try:
2147 try:
2148 f(name)
2148 f(name)
2149 i = None
2149 i = None
2150 break
2150 break
2151 except error.UnknownCommand, inst:
2151 except error.UnknownCommand, inst:
2152 i = inst
2152 i = inst
2153 if i:
2153 if i:
2154 raise i
2154 raise i
2155
2155
2156 else:
2156 else:
2157 # program name
2157 # program name
2158 if ui.verbose or with_version:
2158 if ui.verbose or with_version:
2159 version_(ui)
2159 version_(ui)
2160 else:
2160 else:
2161 ui.status(_("Mercurial Distributed SCM\n"))
2161 ui.status(_("Mercurial Distributed SCM\n"))
2162 ui.status('\n')
2162 ui.status('\n')
2163
2163
2164 # list of commands
2164 # list of commands
2165 if name == "shortlist":
2165 if name == "shortlist":
2166 header = _('basic commands:\n\n')
2166 header = _('basic commands:\n\n')
2167 else:
2167 else:
2168 header = _('list of commands:\n\n')
2168 header = _('list of commands:\n\n')
2169
2169
2170 helplist(header)
2170 helplist(header)
2171 if name != 'shortlist':
2171 if name != 'shortlist':
2172 exts, maxlength = extensions.enabled()
2172 exts, maxlength = extensions.enabled()
2173 text = help.listexts(_('enabled extensions:'), exts, maxlength)
2173 text = help.listexts(_('enabled extensions:'), exts, maxlength)
2174 if text:
2174 if text:
2175 ui.write("\n%s\n" % minirst.format(text, textwidth))
2175 ui.write("\n%s\n" % minirst.format(text, textwidth))
2176
2176
2177 # list all option lists
2177 # list all option lists
2178 opt_output = []
2178 opt_output = []
2179 multioccur = False
2179 multioccur = False
2180 for title, options in option_lists:
2180 for title, options in option_lists:
2181 opt_output.append(("\n%s" % title, None))
2181 opt_output.append(("\n%s" % title, None))
2182 for option in options:
2182 for option in options:
2183 if len(option) == 5:
2183 if len(option) == 5:
2184 shortopt, longopt, default, desc, optlabel = option
2184 shortopt, longopt, default, desc, optlabel = option
2185 else:
2185 else:
2186 shortopt, longopt, default, desc = option
2186 shortopt, longopt, default, desc = option
2187 optlabel = _("VALUE") # default label
2187 optlabel = _("VALUE") # default label
2188
2188
2189 if _("DEPRECATED") in desc and not ui.verbose:
2189 if _("DEPRECATED") in desc and not ui.verbose:
2190 continue
2190 continue
2191 if isinstance(default, list):
2191 if isinstance(default, list):
2192 numqualifier = " %s [+]" % optlabel
2192 numqualifier = " %s [+]" % optlabel
2193 multioccur = True
2193 multioccur = True
2194 elif (default is not None) and not isinstance(default, bool):
2194 elif (default is not None) and not isinstance(default, bool):
2195 numqualifier = " %s" % optlabel
2195 numqualifier = " %s" % optlabel
2196 else:
2196 else:
2197 numqualifier = ""
2197 numqualifier = ""
2198 opt_output.append(("%2s%s" %
2198 opt_output.append(("%2s%s" %
2199 (shortopt and "-%s" % shortopt,
2199 (shortopt and "-%s" % shortopt,
2200 longopt and " --%s%s" %
2200 longopt and " --%s%s" %
2201 (longopt, numqualifier)),
2201 (longopt, numqualifier)),
2202 "%s%s" % (desc,
2202 "%s%s" % (desc,
2203 default
2203 default
2204 and _(" (default: %s)") % default
2204 and _(" (default: %s)") % default
2205 or "")))
2205 or "")))
2206 if multioccur:
2206 if multioccur:
2207 msg = _("\n[+] marked option can be specified multiple times")
2207 msg = _("\n[+] marked option can be specified multiple times")
2208 if ui.verbose and name != 'shortlist':
2208 if ui.verbose and name != 'shortlist':
2209 opt_output.append((msg, None))
2209 opt_output.append((msg, None))
2210 else:
2210 else:
2211 opt_output.insert(-1, (msg, None))
2211 opt_output.insert(-1, (msg, None))
2212
2212
2213 if not name:
2213 if not name:
2214 ui.write(_("\nadditional help topics:\n\n"))
2214 ui.write(_("\nadditional help topics:\n\n"))
2215 topics = []
2215 topics = []
2216 for names, header, doc in help.helptable:
2216 for names, header, doc in help.helptable:
2217 topics.append((sorted(names, key=len, reverse=True)[0], header))
2217 topics.append((sorted(names, key=len, reverse=True)[0], header))
2218 topics_len = max([len(s[0]) for s in topics])
2218 topics_len = max([len(s[0]) for s in topics])
2219 for t, desc in topics:
2219 for t, desc in topics:
2220 ui.write(" %-*s %s\n" % (topics_len, t, desc))
2220 ui.write(" %-*s %s\n" % (topics_len, t, desc))
2221
2221
2222 if opt_output:
2222 if opt_output:
2223 colwidth = encoding.colwidth
2223 colwidth = encoding.colwidth
2224 # normalize: (opt or message, desc or None, width of opt)
2224 # normalize: (opt or message, desc or None, width of opt)
2225 entries = [desc and (opt, desc, colwidth(opt)) or (opt, None, 0)
2225 entries = [desc and (opt, desc, colwidth(opt)) or (opt, None, 0)
2226 for opt, desc in opt_output]
2226 for opt, desc in opt_output]
2227 hanging = max([e[2] for e in entries])
2227 hanging = max([e[2] for e in entries])
2228 for opt, desc, width in entries:
2228 for opt, desc, width in entries:
2229 if desc:
2229 if desc:
2230 initindent = ' %s%s ' % (opt, ' ' * (hanging - width))
2230 initindent = ' %s%s ' % (opt, ' ' * (hanging - width))
2231 hangindent = ' ' * (hanging + 3)
2231 hangindent = ' ' * (hanging + 3)
2232 ui.write('%s\n' % (util.wrap(desc, textwidth,
2232 ui.write('%s\n' % (util.wrap(desc, textwidth,
2233 initindent=initindent,
2233 initindent=initindent,
2234 hangindent=hangindent)))
2234 hangindent=hangindent)))
2235 else:
2235 else:
2236 ui.write("%s\n" % opt)
2236 ui.write("%s\n" % opt)
2237
2237
2238 def identify(ui, repo, source=None,
2238 def identify(ui, repo, source=None,
2239 rev=None, num=None, id=None, branch=None, tags=None):
2239 rev=None, num=None, id=None, branch=None, tags=None):
2240 """identify the working copy or specified revision
2240 """identify the working copy or specified revision
2241
2241
2242 With no revision, print a summary of the current state of the
2242 With no revision, print a summary of the current state of the
2243 repository.
2243 repository.
2244
2244
2245 Specifying a path to a repository root or Mercurial bundle will
2245 Specifying a path to a repository root or Mercurial bundle will
2246 cause lookup to operate on that repository/bundle.
2246 cause lookup to operate on that repository/bundle.
2247
2247
2248 This summary identifies the repository state using one or two
2248 This summary identifies the repository state using one or two
2249 parent hash identifiers, followed by a "+" if there are
2249 parent hash identifiers, followed by a "+" if there are
2250 uncommitted changes in the working directory, a list of tags for
2250 uncommitted changes in the working directory, a list of tags for
2251 this revision and a branch name for non-default branches.
2251 this revision and a branch name for non-default branches.
2252
2252
2253 Returns 0 if successful.
2253 Returns 0 if successful.
2254 """
2254 """
2255
2255
2256 if not repo and not source:
2256 if not repo and not source:
2257 raise util.Abort(_("there is no Mercurial repository here "
2257 raise util.Abort(_("there is no Mercurial repository here "
2258 "(.hg not found)"))
2258 "(.hg not found)"))
2259
2259
2260 hexfunc = ui.debugflag and hex or short
2260 hexfunc = ui.debugflag and hex or short
2261 default = not (num or id or branch or tags)
2261 default = not (num or id or branch or tags)
2262 output = []
2262 output = []
2263
2263
2264 revs = []
2264 revs = []
2265 if source:
2265 if source:
2266 source, branches = hg.parseurl(ui.expandpath(source))
2266 source, branches = hg.parseurl(ui.expandpath(source))
2267 repo = hg.repository(ui, source)
2267 repo = hg.repository(ui, source)
2268 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
2268 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
2269
2269
2270 if not repo.local():
2270 if not repo.local():
2271 if not rev and revs:
2271 if not rev and revs:
2272 rev = revs[0]
2272 rev = revs[0]
2273 if not rev:
2273 if not rev:
2274 rev = "tip"
2274 rev = "tip"
2275 if num or branch or tags:
2275 if num or branch or tags:
2276 raise util.Abort(
2276 raise util.Abort(
2277 "can't query remote revision number, branch, or tags")
2277 "can't query remote revision number, branch, or tags")
2278 output = [hexfunc(repo.lookup(rev))]
2278 output = [hexfunc(repo.lookup(rev))]
2279 elif not rev:
2279 elif not rev:
2280 ctx = repo[None]
2280 ctx = repo[None]
2281 parents = ctx.parents()
2281 parents = ctx.parents()
2282 changed = False
2282 changed = False
2283 if default or id or num:
2283 if default or id or num:
2284 changed = util.any(repo.status())
2284 changed = util.any(repo.status())
2285 if default or id:
2285 if default or id:
2286 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
2286 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
2287 (changed) and "+" or "")]
2287 (changed) and "+" or "")]
2288 if num:
2288 if num:
2289 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
2289 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
2290 (changed) and "+" or ""))
2290 (changed) and "+" or ""))
2291 else:
2291 else:
2292 ctx = cmdutil.revsingle(repo, rev)
2292 ctx = cmdutil.revsingle(repo, rev)
2293 if default or id:
2293 if default or id:
2294 output = [hexfunc(ctx.node())]
2294 output = [hexfunc(ctx.node())]
2295 if num:
2295 if num:
2296 output.append(str(ctx.rev()))
2296 output.append(str(ctx.rev()))
2297
2297
2298 if repo.local() and default and not ui.quiet:
2298 if repo.local() and default and not ui.quiet:
2299 b = ctx.branch()
2299 b = ctx.branch()
2300 if b != 'default':
2300 if b != 'default':
2301 output.append("(%s)" % b)
2301 output.append("(%s)" % b)
2302
2302
2303 # multiple tags for a single parent separated by '/'
2303 # multiple tags for a single parent separated by '/'
2304 t = "/".join(ctx.tags())
2304 t = "/".join(ctx.tags())
2305 if t:
2305 if t:
2306 output.append(t)
2306 output.append(t)
2307
2307
2308 if branch:
2308 if branch:
2309 output.append(ctx.branch())
2309 output.append(ctx.branch())
2310
2310
2311 if tags:
2311 if tags:
2312 output.extend(ctx.tags())
2312 output.extend(ctx.tags())
2313
2313
2314 ui.write("%s\n" % ' '.join(output))
2314 ui.write("%s\n" % ' '.join(output))
2315
2315
2316 def import_(ui, repo, patch1, *patches, **opts):
2316 def import_(ui, repo, patch1, *patches, **opts):
2317 """import an ordered set of patches
2317 """import an ordered set of patches
2318
2318
2319 Import a list of patches and commit them individually (unless
2319 Import a list of patches and commit them individually (unless
2320 --no-commit is specified).
2320 --no-commit is specified).
2321
2321
2322 If there are outstanding changes in the working directory, import
2322 If there are outstanding changes in the working directory, import
2323 will abort unless given the -f/--force flag.
2323 will abort unless given the -f/--force flag.
2324
2324
2325 You can import a patch straight from a mail message. Even patches
2325 You can import a patch straight from a mail message. Even patches
2326 as attachments work (to use the body part, it must have type
2326 as attachments work (to use the body part, it must have type
2327 text/plain or text/x-patch). From and Subject headers of email
2327 text/plain or text/x-patch). From and Subject headers of email
2328 message are used as default committer and commit message. All
2328 message are used as default committer and commit message. All
2329 text/plain body parts before first diff are added to commit
2329 text/plain body parts before first diff are added to commit
2330 message.
2330 message.
2331
2331
2332 If the imported patch was generated by :hg:`export`, user and
2332 If the imported patch was generated by :hg:`export`, user and
2333 description from patch override values from message headers and
2333 description from patch override values from message headers and
2334 body. Values given on command line with -m/--message and -u/--user
2334 body. Values given on command line with -m/--message and -u/--user
2335 override these.
2335 override these.
2336
2336
2337 If --exact is specified, import will set the working directory to
2337 If --exact is specified, import will set the working directory to
2338 the parent of each patch before applying it, and will abort if the
2338 the parent of each patch before applying it, and will abort if the
2339 resulting changeset has a different ID than the one recorded in
2339 resulting changeset has a different ID than the one recorded in
2340 the patch. This may happen due to character set problems or other
2340 the patch. This may happen due to character set problems or other
2341 deficiencies in the text patch format.
2341 deficiencies in the text patch format.
2342
2342
2343 With -s/--similarity, hg will attempt to discover renames and
2343 With -s/--similarity, hg will attempt to discover renames and
2344 copies in the patch in the same way as 'addremove'.
2344 copies in the patch in the same way as 'addremove'.
2345
2345
2346 To read a patch from standard input, use "-" as the patch name. If
2346 To read a patch from standard input, use "-" as the patch name. If
2347 a URL is specified, the patch will be downloaded from it.
2347 a URL is specified, the patch will be downloaded from it.
2348 See :hg:`help dates` for a list of formats valid for -d/--date.
2348 See :hg:`help dates` for a list of formats valid for -d/--date.
2349
2349
2350 Returns 0 on success.
2350 Returns 0 on success.
2351 """
2351 """
2352 patches = (patch1,) + patches
2352 patches = (patch1,) + patches
2353
2353
2354 date = opts.get('date')
2354 date = opts.get('date')
2355 if date:
2355 if date:
2356 opts['date'] = util.parsedate(date)
2356 opts['date'] = util.parsedate(date)
2357
2357
2358 try:
2358 try:
2359 sim = float(opts.get('similarity') or 0)
2359 sim = float(opts.get('similarity') or 0)
2360 except ValueError:
2360 except ValueError:
2361 raise util.Abort(_('similarity must be a number'))
2361 raise util.Abort(_('similarity must be a number'))
2362 if sim < 0 or sim > 100:
2362 if sim < 0 or sim > 100:
2363 raise util.Abort(_('similarity must be between 0 and 100'))
2363 raise util.Abort(_('similarity must be between 0 and 100'))
2364
2364
2365 if opts.get('exact') or not opts.get('force'):
2365 if opts.get('exact') or not opts.get('force'):
2366 cmdutil.bail_if_changed(repo)
2366 cmdutil.bail_if_changed(repo)
2367
2367
2368 d = opts["base"]
2368 d = opts["base"]
2369 strip = opts["strip"]
2369 strip = opts["strip"]
2370 wlock = lock = None
2370 wlock = lock = None
2371 msgs = []
2371 msgs = []
2372
2372
2373 def tryone(ui, hunk):
2373 def tryone(ui, hunk):
2374 tmpname, message, user, date, branch, nodeid, p1, p2 = \
2374 tmpname, message, user, date, branch, nodeid, p1, p2 = \
2375 patch.extract(ui, hunk)
2375 patch.extract(ui, hunk)
2376
2376
2377 if not tmpname:
2377 if not tmpname:
2378 return None
2378 return None
2379 commitid = _('to working directory')
2379 commitid = _('to working directory')
2380
2380
2381 try:
2381 try:
2382 cmdline_message = cmdutil.logmessage(opts)
2382 cmdline_message = cmdutil.logmessage(opts)
2383 if cmdline_message:
2383 if cmdline_message:
2384 # pickup the cmdline msg
2384 # pickup the cmdline msg
2385 message = cmdline_message
2385 message = cmdline_message
2386 elif message:
2386 elif message:
2387 # pickup the patch msg
2387 # pickup the patch msg
2388 message = message.strip()
2388 message = message.strip()
2389 else:
2389 else:
2390 # launch the editor
2390 # launch the editor
2391 message = None
2391 message = None
2392 ui.debug('message:\n%s\n' % message)
2392 ui.debug('message:\n%s\n' % message)
2393
2393
2394 wp = repo.parents()
2394 wp = repo.parents()
2395 if opts.get('exact'):
2395 if opts.get('exact'):
2396 if not nodeid or not p1:
2396 if not nodeid or not p1:
2397 raise util.Abort(_('not a Mercurial patch'))
2397 raise util.Abort(_('not a Mercurial patch'))
2398 p1 = repo.lookup(p1)
2398 p1 = repo.lookup(p1)
2399 p2 = repo.lookup(p2 or hex(nullid))
2399 p2 = repo.lookup(p2 or hex(nullid))
2400
2400
2401 if p1 != wp[0].node():
2401 if p1 != wp[0].node():
2402 hg.clean(repo, p1)
2402 hg.clean(repo, p1)
2403 repo.dirstate.setparents(p1, p2)
2403 repo.dirstate.setparents(p1, p2)
2404 elif p2:
2404 elif p2:
2405 try:
2405 try:
2406 p1 = repo.lookup(p1)
2406 p1 = repo.lookup(p1)
2407 p2 = repo.lookup(p2)
2407 p2 = repo.lookup(p2)
2408 if p1 == wp[0].node():
2408 if p1 == wp[0].node():
2409 repo.dirstate.setparents(p1, p2)
2409 repo.dirstate.setparents(p1, p2)
2410 except error.RepoError:
2410 except error.RepoError:
2411 pass
2411 pass
2412 if opts.get('exact') or opts.get('import_branch'):
2412 if opts.get('exact') or opts.get('import_branch'):
2413 repo.dirstate.setbranch(branch or 'default')
2413 repo.dirstate.setbranch(branch or 'default')
2414
2414
2415 files = {}
2415 files = {}
2416 try:
2416 try:
2417 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
2417 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
2418 files=files, eolmode=None)
2418 files=files, eolmode=None)
2419 finally:
2419 finally:
2420 files = cmdutil.updatedir(ui, repo, files,
2420 files = cmdutil.updatedir(ui, repo, files,
2421 similarity=sim / 100.0)
2421 similarity=sim / 100.0)
2422 if opts.get('no_commit'):
2422 if opts.get('no_commit'):
2423 if message:
2423 if message:
2424 msgs.append(message)
2424 msgs.append(message)
2425 else:
2425 else:
2426 if opts.get('exact'):
2426 if opts.get('exact'):
2427 m = None
2427 m = None
2428 else:
2428 else:
2429 m = cmdutil.matchfiles(repo, files or [])
2429 m = cmdutil.matchfiles(repo, files or [])
2430 n = repo.commit(message, opts.get('user') or user,
2430 n = repo.commit(message, opts.get('user') or user,
2431 opts.get('date') or date, match=m,
2431 opts.get('date') or date, match=m,
2432 editor=cmdutil.commiteditor)
2432 editor=cmdutil.commiteditor)
2433 if opts.get('exact'):
2433 if opts.get('exact'):
2434 if hex(n) != nodeid:
2434 if hex(n) != nodeid:
2435 repo.rollback()
2435 repo.rollback()
2436 raise util.Abort(_('patch is damaged'
2436 raise util.Abort(_('patch is damaged'
2437 ' or loses information'))
2437 ' or loses information'))
2438 # Force a dirstate write so that the next transaction
2438 # Force a dirstate write so that the next transaction
2439 # backups an up-do-date file.
2439 # backups an up-do-date file.
2440 repo.dirstate.write()
2440 repo.dirstate.write()
2441 if n:
2441 if n:
2442 commitid = short(n)
2442 commitid = short(n)
2443
2443
2444 return commitid
2444 return commitid
2445 finally:
2445 finally:
2446 os.unlink(tmpname)
2446 os.unlink(tmpname)
2447
2447
2448 try:
2448 try:
2449 wlock = repo.wlock()
2449 wlock = repo.wlock()
2450 lock = repo.lock()
2450 lock = repo.lock()
2451 lastcommit = None
2451 lastcommit = None
2452 for p in patches:
2452 for p in patches:
2453 pf = os.path.join(d, p)
2453 pf = os.path.join(d, p)
2454
2454
2455 if pf == '-':
2455 if pf == '-':
2456 ui.status(_("applying patch from stdin\n"))
2456 ui.status(_("applying patch from stdin\n"))
2457 pf = sys.stdin
2457 pf = sys.stdin
2458 else:
2458 else:
2459 ui.status(_("applying %s\n") % p)
2459 ui.status(_("applying %s\n") % p)
2460 pf = url.open(ui, pf)
2460 pf = url.open(ui, pf)
2461
2461
2462 haspatch = False
2462 haspatch = False
2463 for hunk in patch.split(pf):
2463 for hunk in patch.split(pf):
2464 commitid = tryone(ui, hunk)
2464 commitid = tryone(ui, hunk)
2465 if commitid:
2465 if commitid:
2466 haspatch = True
2466 haspatch = True
2467 if lastcommit:
2467 if lastcommit:
2468 ui.status(_('applied %s\n') % lastcommit)
2468 ui.status(_('applied %s\n') % lastcommit)
2469 lastcommit = commitid
2469 lastcommit = commitid
2470
2470
2471 if not haspatch:
2471 if not haspatch:
2472 raise util.Abort(_('no diffs found'))
2472 raise util.Abort(_('no diffs found'))
2473
2473
2474 if msgs:
2474 if msgs:
2475 repo.opener('last-message.txt', 'wb').write('\n* * *\n'.join(msgs))
2475 repo.opener('last-message.txt', 'wb').write('\n* * *\n'.join(msgs))
2476 finally:
2476 finally:
2477 release(lock, wlock)
2477 release(lock, wlock)
2478
2478
2479 def incoming(ui, repo, source="default", **opts):
2479 def incoming(ui, repo, source="default", **opts):
2480 """show new changesets found in source
2480 """show new changesets found in source
2481
2481
2482 Show new changesets found in the specified path/URL or the default
2482 Show new changesets found in the specified path/URL or the default
2483 pull location. These are the changesets that would have been pulled
2483 pull location. These are the changesets that would have been pulled
2484 if a pull at the time you issued this command.
2484 if a pull at the time you issued this command.
2485
2485
2486 For remote repository, using --bundle avoids downloading the
2486 For remote repository, using --bundle avoids downloading the
2487 changesets twice if the incoming is followed by a pull.
2487 changesets twice if the incoming is followed by a pull.
2488
2488
2489 See pull for valid source format details.
2489 See pull for valid source format details.
2490
2490
2491 Returns 0 if there are incoming changes, 1 otherwise.
2491 Returns 0 if there are incoming changes, 1 otherwise.
2492 """
2492 """
2493 if opts.get('bundle') and opts.get('subrepos'):
2493 if opts.get('bundle') and opts.get('subrepos'):
2494 raise util.Abort(_('cannot combine --bundle and --subrepos'))
2494 raise util.Abort(_('cannot combine --bundle and --subrepos'))
2495
2495
2496 if opts.get('bookmarks'):
2496 if opts.get('bookmarks'):
2497 source, branches = hg.parseurl(ui.expandpath(source),
2497 source, branches = hg.parseurl(ui.expandpath(source),
2498 opts.get('branch'))
2498 opts.get('branch'))
2499 other = hg.repository(hg.remoteui(repo, opts), source)
2499 other = hg.repository(hg.remoteui(repo, opts), source)
2500 ui.status(_('comparing with %s\n') % url.hidepassword(source))
2500 ui.status(_('comparing with %s\n') % url.hidepassword(source))
2501 return bookmarks.diff(ui, repo, other)
2501 return bookmarks.diff(ui, repo, other)
2502
2502
2503 ret = hg.incoming(ui, repo, source, opts)
2503 ret = hg.incoming(ui, repo, source, opts)
2504 return ret
2504 return ret
2505
2505
2506 def init(ui, dest=".", **opts):
2506 def init(ui, dest=".", **opts):
2507 """create a new repository in the given directory
2507 """create a new repository in the given directory
2508
2508
2509 Initialize a new repository in the given directory. If the given
2509 Initialize a new repository in the given directory. If the given
2510 directory does not exist, it will be created.
2510 directory does not exist, it will be created.
2511
2511
2512 If no directory is given, the current directory is used.
2512 If no directory is given, the current directory is used.
2513
2513
2514 It is possible to specify an ``ssh://`` URL as the destination.
2514 It is possible to specify an ``ssh://`` URL as the destination.
2515 See :hg:`help urls` for more information.
2515 See :hg:`help urls` for more information.
2516
2516
2517 Returns 0 on success.
2517 Returns 0 on success.
2518 """
2518 """
2519 hg.repository(hg.remoteui(ui, opts), ui.expandpath(dest), create=1)
2519 hg.repository(hg.remoteui(ui, opts), ui.expandpath(dest), create=1)
2520
2520
2521 def locate(ui, repo, *pats, **opts):
2521 def locate(ui, repo, *pats, **opts):
2522 """locate files matching specific patterns
2522 """locate files matching specific patterns
2523
2523
2524 Print files under Mercurial control in the working directory whose
2524 Print files under Mercurial control in the working directory whose
2525 names match the given patterns.
2525 names match the given patterns.
2526
2526
2527 By default, this command searches all directories in the working
2527 By default, this command searches all directories in the working
2528 directory. To search just the current directory and its
2528 directory. To search just the current directory and its
2529 subdirectories, use "--include .".
2529 subdirectories, use "--include .".
2530
2530
2531 If no patterns are given to match, this command prints the names
2531 If no patterns are given to match, this command prints the names
2532 of all files under Mercurial control in the working directory.
2532 of all files under Mercurial control in the working directory.
2533
2533
2534 If you want to feed the output of this command into the "xargs"
2534 If you want to feed the output of this command into the "xargs"
2535 command, use the -0 option to both this command and "xargs". This
2535 command, use the -0 option to both this command and "xargs". This
2536 will avoid the problem of "xargs" treating single filenames that
2536 will avoid the problem of "xargs" treating single filenames that
2537 contain whitespace as multiple filenames.
2537 contain whitespace as multiple filenames.
2538
2538
2539 Returns 0 if a match is found, 1 otherwise.
2539 Returns 0 if a match is found, 1 otherwise.
2540 """
2540 """
2541 end = opts.get('print0') and '\0' or '\n'
2541 end = opts.get('print0') and '\0' or '\n'
2542 rev = cmdutil.revsingle(repo, opts.get('rev'), None).node()
2542 rev = cmdutil.revsingle(repo, opts.get('rev'), None).node()
2543
2543
2544 ret = 1
2544 ret = 1
2545 m = cmdutil.match(repo, pats, opts, default='relglob')
2545 m = cmdutil.match(repo, pats, opts, default='relglob')
2546 m.bad = lambda x, y: False
2546 m.bad = lambda x, y: False
2547 for abs in repo[rev].walk(m):
2547 for abs in repo[rev].walk(m):
2548 if not rev and abs not in repo.dirstate:
2548 if not rev and abs not in repo.dirstate:
2549 continue
2549 continue
2550 if opts.get('fullpath'):
2550 if opts.get('fullpath'):
2551 ui.write(repo.wjoin(abs), end)
2551 ui.write(repo.wjoin(abs), end)
2552 else:
2552 else:
2553 ui.write(((pats and m.rel(abs)) or abs), end)
2553 ui.write(((pats and m.rel(abs)) or abs), end)
2554 ret = 0
2554 ret = 0
2555
2555
2556 return ret
2556 return ret
2557
2557
2558 def log(ui, repo, *pats, **opts):
2558 def log(ui, repo, *pats, **opts):
2559 """show revision history of entire repository or files
2559 """show revision history of entire repository or files
2560
2560
2561 Print the revision history of the specified files or the entire
2561 Print the revision history of the specified files or the entire
2562 project.
2562 project.
2563
2563
2564 File history is shown without following rename or copy history of
2564 File history is shown without following rename or copy history of
2565 files. Use -f/--follow with a filename to follow history across
2565 files. Use -f/--follow with a filename to follow history across
2566 renames and copies. --follow without a filename will only show
2566 renames and copies. --follow without a filename will only show
2567 ancestors or descendants of the starting revision. --follow-first
2567 ancestors or descendants of the starting revision. --follow-first
2568 only follows the first parent of merge revisions.
2568 only follows the first parent of merge revisions.
2569
2569
2570 If no revision range is specified, the default is ``tip:0`` unless
2570 If no revision range is specified, the default is ``tip:0`` unless
2571 --follow is set, in which case the working directory parent is
2571 --follow is set, in which case the working directory parent is
2572 used as the starting revision. You can specify a revision set for
2572 used as the starting revision. You can specify a revision set for
2573 log, see :hg:`help revsets` for more information.
2573 log, see :hg:`help revsets` for more information.
2574
2574
2575 See :hg:`help dates` for a list of formats valid for -d/--date.
2575 See :hg:`help dates` for a list of formats valid for -d/--date.
2576
2576
2577 By default this command prints revision number and changeset id,
2577 By default this command prints revision number and changeset id,
2578 tags, non-trivial parents, user, date and time, and a summary for
2578 tags, non-trivial parents, user, date and time, and a summary for
2579 each commit. When the -v/--verbose switch is used, the list of
2579 each commit. When the -v/--verbose switch is used, the list of
2580 changed files and full commit message are shown.
2580 changed files and full commit message are shown.
2581
2581
2582 .. note::
2582 .. note::
2583 log -p/--patch may generate unexpected diff output for merge
2583 log -p/--patch may generate unexpected diff output for merge
2584 changesets, as it will only compare the merge changeset against
2584 changesets, as it will only compare the merge changeset against
2585 its first parent. Also, only files different from BOTH parents
2585 its first parent. Also, only files different from BOTH parents
2586 will appear in files:.
2586 will appear in files:.
2587
2587
2588 Returns 0 on success.
2588 Returns 0 on success.
2589 """
2589 """
2590
2590
2591 matchfn = cmdutil.match(repo, pats, opts)
2591 matchfn = cmdutil.match(repo, pats, opts)
2592 limit = cmdutil.loglimit(opts)
2592 limit = cmdutil.loglimit(opts)
2593 count = 0
2593 count = 0
2594
2594
2595 endrev = None
2595 endrev = None
2596 if opts.get('copies') and opts.get('rev'):
2596 if opts.get('copies') and opts.get('rev'):
2597 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
2597 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
2598
2598
2599 df = False
2599 df = False
2600 if opts["date"]:
2600 if opts["date"]:
2601 df = util.matchdate(opts["date"])
2601 df = util.matchdate(opts["date"])
2602
2602
2603 branches = opts.get('branch', []) + opts.get('only_branch', [])
2603 branches = opts.get('branch', []) + opts.get('only_branch', [])
2604 opts['branch'] = [repo.lookupbranch(b) for b in branches]
2604 opts['branch'] = [repo.lookupbranch(b) for b in branches]
2605
2605
2606 displayer = cmdutil.show_changeset(ui, repo, opts, True)
2606 displayer = cmdutil.show_changeset(ui, repo, opts, True)
2607 def prep(ctx, fns):
2607 def prep(ctx, fns):
2608 rev = ctx.rev()
2608 rev = ctx.rev()
2609 parents = [p for p in repo.changelog.parentrevs(rev)
2609 parents = [p for p in repo.changelog.parentrevs(rev)
2610 if p != nullrev]
2610 if p != nullrev]
2611 if opts.get('no_merges') and len(parents) == 2:
2611 if opts.get('no_merges') and len(parents) == 2:
2612 return
2612 return
2613 if opts.get('only_merges') and len(parents) != 2:
2613 if opts.get('only_merges') and len(parents) != 2:
2614 return
2614 return
2615 if opts.get('branch') and ctx.branch() not in opts['branch']:
2615 if opts.get('branch') and ctx.branch() not in opts['branch']:
2616 return
2616 return
2617 if df and not df(ctx.date()[0]):
2617 if df and not df(ctx.date()[0]):
2618 return
2618 return
2619 if opts['user'] and not [k for k in opts['user']
2619 if opts['user'] and not [k for k in opts['user']
2620 if k.lower() in ctx.user().lower()]:
2620 if k.lower() in ctx.user().lower()]:
2621 return
2621 return
2622 if opts.get('keyword'):
2622 if opts.get('keyword'):
2623 for k in [kw.lower() for kw in opts['keyword']]:
2623 for k in [kw.lower() for kw in opts['keyword']]:
2624 if (k in ctx.user().lower() or
2624 if (k in ctx.user().lower() or
2625 k in ctx.description().lower() or
2625 k in ctx.description().lower() or
2626 k in " ".join(ctx.files()).lower()):
2626 k in " ".join(ctx.files()).lower()):
2627 break
2627 break
2628 else:
2628 else:
2629 return
2629 return
2630
2630
2631 copies = None
2631 copies = None
2632 if opts.get('copies') and rev:
2632 if opts.get('copies') and rev:
2633 copies = []
2633 copies = []
2634 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2634 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2635 for fn in ctx.files():
2635 for fn in ctx.files():
2636 rename = getrenamed(fn, rev)
2636 rename = getrenamed(fn, rev)
2637 if rename:
2637 if rename:
2638 copies.append((fn, rename[0]))
2638 copies.append((fn, rename[0]))
2639
2639
2640 revmatchfn = None
2640 revmatchfn = None
2641 if opts.get('patch') or opts.get('stat'):
2641 if opts.get('patch') or opts.get('stat'):
2642 if opts.get('follow') or opts.get('follow_first'):
2642 if opts.get('follow') or opts.get('follow_first'):
2643 # note: this might be wrong when following through merges
2643 # note: this might be wrong when following through merges
2644 revmatchfn = cmdutil.match(repo, fns, default='path')
2644 revmatchfn = cmdutil.match(repo, fns, default='path')
2645 else:
2645 else:
2646 revmatchfn = matchfn
2646 revmatchfn = matchfn
2647
2647
2648 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2648 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2649
2649
2650 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2650 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2651 if count == limit:
2651 if count == limit:
2652 break
2652 break
2653 if displayer.flush(ctx.rev()):
2653 if displayer.flush(ctx.rev()):
2654 count += 1
2654 count += 1
2655 displayer.close()
2655 displayer.close()
2656
2656
2657 def manifest(ui, repo, node=None, rev=None):
2657 def manifest(ui, repo, node=None, rev=None):
2658 """output the current or given revision of the project manifest
2658 """output the current or given revision of the project manifest
2659
2659
2660 Print a list of version controlled files for the given revision.
2660 Print a list of version controlled files for the given revision.
2661 If no revision is given, the first parent of the working directory
2661 If no revision is given, the first parent of the working directory
2662 is used, or the null revision if no revision is checked out.
2662 is used, or the null revision if no revision is checked out.
2663
2663
2664 With -v, print file permissions, symlink and executable bits.
2664 With -v, print file permissions, symlink and executable bits.
2665 With --debug, print file revision hashes.
2665 With --debug, print file revision hashes.
2666
2666
2667 Returns 0 on success.
2667 Returns 0 on success.
2668 """
2668 """
2669
2669
2670 if rev and node:
2670 if rev and node:
2671 raise util.Abort(_("please specify just one revision"))
2671 raise util.Abort(_("please specify just one revision"))
2672
2672
2673 if not node:
2673 if not node:
2674 node = rev
2674 node = rev
2675
2675
2676 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2676 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2677 ctx = cmdutil.revsingle(repo, node)
2677 ctx = cmdutil.revsingle(repo, node)
2678 for f in ctx:
2678 for f in ctx:
2679 if ui.debugflag:
2679 if ui.debugflag:
2680 ui.write("%40s " % hex(ctx.manifest()[f]))
2680 ui.write("%40s " % hex(ctx.manifest()[f]))
2681 if ui.verbose:
2681 if ui.verbose:
2682 ui.write(decor[ctx.flags(f)])
2682 ui.write(decor[ctx.flags(f)])
2683 ui.write("%s\n" % f)
2683 ui.write("%s\n" % f)
2684
2684
2685 def merge(ui, repo, node=None, **opts):
2685 def merge(ui, repo, node=None, **opts):
2686 """merge working directory with another revision
2686 """merge working directory with another revision
2687
2687
2688 The current working directory is updated with all changes made in
2688 The current working directory is updated with all changes made in
2689 the requested revision since the last common predecessor revision.
2689 the requested revision since the last common predecessor revision.
2690
2690
2691 Files that changed between either parent are marked as changed for
2691 Files that changed between either parent are marked as changed for
2692 the next commit and a commit must be performed before any further
2692 the next commit and a commit must be performed before any further
2693 updates to the repository are allowed. The next commit will have
2693 updates to the repository are allowed. The next commit will have
2694 two parents.
2694 two parents.
2695
2695
2696 ``--tool`` can be used to specify the merge tool used for file
2696 ``--tool`` can be used to specify the merge tool used for file
2697 merges. It overrides the HGMERGE environment variable and your
2697 merges. It overrides the HGMERGE environment variable and your
2698 configuration files.
2698 configuration files.
2699
2699
2700 If no revision is specified, the working directory's parent is a
2700 If no revision is specified, the working directory's parent is a
2701 head revision, and the current branch contains exactly one other
2701 head revision, and the current branch contains exactly one other
2702 head, the other head is merged with by default. Otherwise, an
2702 head, the other head is merged with by default. Otherwise, an
2703 explicit revision with which to merge with must be provided.
2703 explicit revision with which to merge with must be provided.
2704
2704
2705 :hg:`resolve` must be used to resolve unresolved files.
2705 :hg:`resolve` must be used to resolve unresolved files.
2706
2706
2707 To undo an uncommitted merge, use :hg:`update --clean .` which
2707 To undo an uncommitted merge, use :hg:`update --clean .` which
2708 will check out a clean copy of the original merge parent, losing
2708 will check out a clean copy of the original merge parent, losing
2709 all changes.
2709 all changes.
2710
2710
2711 Returns 0 on success, 1 if there are unresolved files.
2711 Returns 0 on success, 1 if there are unresolved files.
2712 """
2712 """
2713
2713
2714 if opts.get('rev') and node:
2714 if opts.get('rev') and node:
2715 raise util.Abort(_("please specify just one revision"))
2715 raise util.Abort(_("please specify just one revision"))
2716 if not node:
2716 if not node:
2717 node = opts.get('rev')
2717 node = opts.get('rev')
2718
2718
2719 if not node:
2719 if not node:
2720 branch = repo[None].branch()
2720 branch = repo[None].branch()
2721 bheads = repo.branchheads(branch)
2721 bheads = repo.branchheads(branch)
2722 if len(bheads) > 2:
2722 if len(bheads) > 2:
2723 raise util.Abort(_(
2723 raise util.Abort(_(
2724 'branch \'%s\' has %d heads - '
2724 'branch \'%s\' has %d heads - '
2725 'please merge with an explicit rev\n'
2725 'please merge with an explicit rev\n'
2726 '(run \'hg heads .\' to see heads)')
2726 '(run \'hg heads .\' to see heads)')
2727 % (branch, len(bheads)))
2727 % (branch, len(bheads)))
2728
2728
2729 parent = repo.dirstate.parents()[0]
2729 parent = repo.dirstate.parents()[0]
2730 if len(bheads) == 1:
2730 if len(bheads) == 1:
2731 if len(repo.heads()) > 1:
2731 if len(repo.heads()) > 1:
2732 raise util.Abort(_(
2732 raise util.Abort(_(
2733 'branch \'%s\' has one head - '
2733 'branch \'%s\' has one head - '
2734 'please merge with an explicit rev\n'
2734 'please merge with an explicit rev\n'
2735 '(run \'hg heads\' to see all heads)')
2735 '(run \'hg heads\' to see all heads)')
2736 % branch)
2736 % branch)
2737 msg = _('there is nothing to merge')
2737 msg = _('there is nothing to merge')
2738 if parent != repo.lookup(repo[None].branch()):
2738 if parent != repo.lookup(repo[None].branch()):
2739 msg = _('%s - use "hg update" instead') % msg
2739 msg = _('%s - use "hg update" instead') % msg
2740 raise util.Abort(msg)
2740 raise util.Abort(msg)
2741
2741
2742 if parent not in bheads:
2742 if parent not in bheads:
2743 raise util.Abort(_('working dir not at a head rev - '
2743 raise util.Abort(_('working dir not at a head rev - '
2744 'use "hg update" or merge with an explicit rev'))
2744 'use "hg update" or merge with an explicit rev'))
2745 node = parent == bheads[0] and bheads[-1] or bheads[0]
2745 node = parent == bheads[0] and bheads[-1] or bheads[0]
2746 else:
2746 else:
2747 node = cmdutil.revsingle(repo, node).node()
2747 node = cmdutil.revsingle(repo, node).node()
2748
2748
2749 if opts.get('preview'):
2749 if opts.get('preview'):
2750 # find nodes that are ancestors of p2 but not of p1
2750 # find nodes that are ancestors of p2 but not of p1
2751 p1 = repo.lookup('.')
2751 p1 = repo.lookup('.')
2752 p2 = repo.lookup(node)
2752 p2 = repo.lookup(node)
2753 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
2753 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
2754
2754
2755 displayer = cmdutil.show_changeset(ui, repo, opts)
2755 displayer = cmdutil.show_changeset(ui, repo, opts)
2756 for node in nodes:
2756 for node in nodes:
2757 displayer.show(repo[node])
2757 displayer.show(repo[node])
2758 displayer.close()
2758 displayer.close()
2759 return 0
2759 return 0
2760
2760
2761 try:
2761 try:
2762 # ui.forcemerge is an internal variable, do not document
2762 # ui.forcemerge is an internal variable, do not document
2763 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2763 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2764 return hg.merge(repo, node, force=opts.get('force'))
2764 return hg.merge(repo, node, force=opts.get('force'))
2765 finally:
2765 finally:
2766 ui.setconfig('ui', 'forcemerge', '')
2766 ui.setconfig('ui', 'forcemerge', '')
2767
2767
2768 def outgoing(ui, repo, dest=None, **opts):
2768 def outgoing(ui, repo, dest=None, **opts):
2769 """show changesets not found in the destination
2769 """show changesets not found in the destination
2770
2770
2771 Show changesets not found in the specified destination repository
2771 Show changesets not found in the specified destination repository
2772 or the default push location. These are the changesets that would
2772 or the default push location. These are the changesets that would
2773 be pushed if a push was requested.
2773 be pushed if a push was requested.
2774
2774
2775 See pull for details of valid destination formats.
2775 See pull for details of valid destination formats.
2776
2776
2777 Returns 0 if there are outgoing changes, 1 otherwise.
2777 Returns 0 if there are outgoing changes, 1 otherwise.
2778 """
2778 """
2779
2779
2780 if opts.get('bookmarks'):
2780 if opts.get('bookmarks'):
2781 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2781 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2782 dest, branches = hg.parseurl(dest, opts.get('branch'))
2782 dest, branches = hg.parseurl(dest, opts.get('branch'))
2783 other = hg.repository(hg.remoteui(repo, opts), dest)
2783 other = hg.repository(hg.remoteui(repo, opts), dest)
2784 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
2784 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
2785 return bookmarks.diff(ui, other, repo)
2785 return bookmarks.diff(ui, other, repo)
2786
2786
2787 ret = hg.outgoing(ui, repo, dest, opts)
2787 ret = hg.outgoing(ui, repo, dest, opts)
2788 return ret
2788 return ret
2789
2789
2790 def parents(ui, repo, file_=None, **opts):
2790 def parents(ui, repo, file_=None, **opts):
2791 """show the parents of the working directory or revision
2791 """show the parents of the working directory or revision
2792
2792
2793 Print the working directory's parent revisions. If a revision is
2793 Print the working directory's parent revisions. If a revision is
2794 given via -r/--rev, the parent of that revision will be printed.
2794 given via -r/--rev, the parent of that revision will be printed.
2795 If a file argument is given, the revision in which the file was
2795 If a file argument is given, the revision in which the file was
2796 last changed (before the working directory revision or the
2796 last changed (before the working directory revision or the
2797 argument to --rev if given) is printed.
2797 argument to --rev if given) is printed.
2798
2798
2799 Returns 0 on success.
2799 Returns 0 on success.
2800 """
2800 """
2801
2801
2802 ctx = cmdutil.revsingle(repo, opts.get('rev'), None)
2802 ctx = cmdutil.revsingle(repo, opts.get('rev'), None)
2803
2803
2804 if file_:
2804 if file_:
2805 m = cmdutil.match(repo, (file_,), opts)
2805 m = cmdutil.match(repo, (file_,), opts)
2806 if m.anypats() or len(m.files()) != 1:
2806 if m.anypats() or len(m.files()) != 1:
2807 raise util.Abort(_('can only specify an explicit filename'))
2807 raise util.Abort(_('can only specify an explicit filename'))
2808 file_ = m.files()[0]
2808 file_ = m.files()[0]
2809 filenodes = []
2809 filenodes = []
2810 for cp in ctx.parents():
2810 for cp in ctx.parents():
2811 if not cp:
2811 if not cp:
2812 continue
2812 continue
2813 try:
2813 try:
2814 filenodes.append(cp.filenode(file_))
2814 filenodes.append(cp.filenode(file_))
2815 except error.LookupError:
2815 except error.LookupError:
2816 pass
2816 pass
2817 if not filenodes:
2817 if not filenodes:
2818 raise util.Abort(_("'%s' not found in manifest!") % file_)
2818 raise util.Abort(_("'%s' not found in manifest!") % file_)
2819 fl = repo.file(file_)
2819 fl = repo.file(file_)
2820 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2820 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2821 else:
2821 else:
2822 p = [cp.node() for cp in ctx.parents()]
2822 p = [cp.node() for cp in ctx.parents()]
2823
2823
2824 displayer = cmdutil.show_changeset(ui, repo, opts)
2824 displayer = cmdutil.show_changeset(ui, repo, opts)
2825 for n in p:
2825 for n in p:
2826 if n != nullid:
2826 if n != nullid:
2827 displayer.show(repo[n])
2827 displayer.show(repo[n])
2828 displayer.close()
2828 displayer.close()
2829
2829
2830 def paths(ui, repo, search=None):
2830 def paths(ui, repo, search=None):
2831 """show aliases for remote repositories
2831 """show aliases for remote repositories
2832
2832
2833 Show definition of symbolic path name NAME. If no name is given,
2833 Show definition of symbolic path name NAME. If no name is given,
2834 show definition of all available names.
2834 show definition of all available names.
2835
2835
2836 Path names are defined in the [paths] section of your
2836 Path names are defined in the [paths] section of your
2837 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
2837 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
2838 repository, ``.hg/hgrc`` is used, too.
2838 repository, ``.hg/hgrc`` is used, too.
2839
2839
2840 The path names ``default`` and ``default-push`` have a special
2840 The path names ``default`` and ``default-push`` have a special
2841 meaning. When performing a push or pull operation, they are used
2841 meaning. When performing a push or pull operation, they are used
2842 as fallbacks if no location is specified on the command-line.
2842 as fallbacks if no location is specified on the command-line.
2843 When ``default-push`` is set, it will be used for push and
2843 When ``default-push`` is set, it will be used for push and
2844 ``default`` will be used for pull; otherwise ``default`` is used
2844 ``default`` will be used for pull; otherwise ``default`` is used
2845 as the fallback for both. When cloning a repository, the clone
2845 as the fallback for both. When cloning a repository, the clone
2846 source is written as ``default`` in ``.hg/hgrc``. Note that
2846 source is written as ``default`` in ``.hg/hgrc``. Note that
2847 ``default`` and ``default-push`` apply to all inbound (e.g.
2847 ``default`` and ``default-push`` apply to all inbound (e.g.
2848 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
2848 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
2849 :hg:`bundle`) operations.
2849 :hg:`bundle`) operations.
2850
2850
2851 See :hg:`help urls` for more information.
2851 See :hg:`help urls` for more information.
2852
2852
2853 Returns 0 on success.
2853 Returns 0 on success.
2854 """
2854 """
2855 if search:
2855 if search:
2856 for name, path in ui.configitems("paths"):
2856 for name, path in ui.configitems("paths"):
2857 if name == search:
2857 if name == search:
2858 ui.write("%s\n" % url.hidepassword(path))
2858 ui.write("%s\n" % url.hidepassword(path))
2859 return
2859 return
2860 ui.warn(_("not found!\n"))
2860 ui.warn(_("not found!\n"))
2861 return 1
2861 return 1
2862 else:
2862 else:
2863 for name, path in ui.configitems("paths"):
2863 for name, path in ui.configitems("paths"):
2864 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2864 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2865
2865
2866 def postincoming(ui, repo, modheads, optupdate, checkout):
2866 def postincoming(ui, repo, modheads, optupdate, checkout):
2867 if modheads == 0:
2867 if modheads == 0:
2868 return
2868 return
2869 if optupdate:
2869 if optupdate:
2870 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2870 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2871 return hg.update(repo, checkout)
2871 return hg.update(repo, checkout)
2872 else:
2872 else:
2873 ui.status(_("not updating, since new heads added\n"))
2873 ui.status(_("not updating, since new heads added\n"))
2874 if modheads > 1:
2874 if modheads > 1:
2875 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2875 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2876 else:
2876 else:
2877 ui.status(_("(run 'hg update' to get a working copy)\n"))
2877 ui.status(_("(run 'hg update' to get a working copy)\n"))
2878
2878
2879 def pull(ui, repo, source="default", **opts):
2879 def pull(ui, repo, source="default", **opts):
2880 """pull changes from the specified source
2880 """pull changes from the specified source
2881
2881
2882 Pull changes from a remote repository to a local one.
2882 Pull changes from a remote repository to a local one.
2883
2883
2884 This finds all changes from the repository at the specified path
2884 This finds all changes from the repository at the specified path
2885 or URL and adds them to a local repository (the current one unless
2885 or URL and adds them to a local repository (the current one unless
2886 -R is specified). By default, this does not update the copy of the
2886 -R is specified). By default, this does not update the copy of the
2887 project in the working directory.
2887 project in the working directory.
2888
2888
2889 Use :hg:`incoming` if you want to see what would have been added
2889 Use :hg:`incoming` if you want to see what would have been added
2890 by a pull at the time you issued this command. If you then decide
2890 by a pull at the time you issued this command. If you then decide
2891 to add those changes to the repository, you should use :hg:`pull
2891 to add those changes to the repository, you should use :hg:`pull
2892 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
2892 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
2893
2893
2894 If SOURCE is omitted, the 'default' path will be used.
2894 If SOURCE is omitted, the 'default' path will be used.
2895 See :hg:`help urls` for more information.
2895 See :hg:`help urls` for more information.
2896
2896
2897 Returns 0 on success, 1 if an update had unresolved files.
2897 Returns 0 on success, 1 if an update had unresolved files.
2898 """
2898 """
2899 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
2899 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
2900 other = hg.repository(hg.remoteui(repo, opts), source)
2900 other = hg.repository(hg.remoteui(repo, opts), source)
2901 ui.status(_('pulling from %s\n') % url.hidepassword(source))
2901 ui.status(_('pulling from %s\n') % url.hidepassword(source))
2902 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
2902 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
2903
2903
2904 if opts.get('bookmark'):
2904 if opts.get('bookmark'):
2905 if not revs:
2905 if not revs:
2906 revs = []
2906 revs = []
2907 rb = other.listkeys('bookmarks')
2907 rb = other.listkeys('bookmarks')
2908 for b in opts['bookmark']:
2908 for b in opts['bookmark']:
2909 if b not in rb:
2909 if b not in rb:
2910 raise util.Abort(_('remote bookmark %s not found!') % b)
2910 raise util.Abort(_('remote bookmark %s not found!') % b)
2911 revs.append(rb[b])
2911 revs.append(rb[b])
2912
2912
2913 if revs:
2913 if revs:
2914 try:
2914 try:
2915 revs = [other.lookup(rev) for rev in revs]
2915 revs = [other.lookup(rev) for rev in revs]
2916 except error.CapabilityError:
2916 except error.CapabilityError:
2917 err = _("other repository doesn't support revision lookup, "
2917 err = _("other repository doesn't support revision lookup, "
2918 "so a rev cannot be specified.")
2918 "so a rev cannot be specified.")
2919 raise util.Abort(err)
2919 raise util.Abort(err)
2920
2920
2921 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
2921 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
2922 if checkout:
2922 if checkout:
2923 checkout = str(repo.changelog.rev(other.lookup(checkout)))
2923 checkout = str(repo.changelog.rev(other.lookup(checkout)))
2924 repo._subtoppath = source
2924 repo._subtoppath = source
2925 try:
2925 try:
2926 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
2926 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
2927
2927
2928 finally:
2928 finally:
2929 del repo._subtoppath
2929 del repo._subtoppath
2930
2930
2931 # update specified bookmarks
2931 # update specified bookmarks
2932 if opts.get('bookmark'):
2932 if opts.get('bookmark'):
2933 for b in opts['bookmark']:
2933 for b in opts['bookmark']:
2934 # explicit pull overrides local bookmark if any
2934 # explicit pull overrides local bookmark if any
2935 ui.status(_("importing bookmark %s\n") % b)
2935 ui.status(_("importing bookmark %s\n") % b)
2936 repo._bookmarks[b] = repo[rb[b]].node()
2936 repo._bookmarks[b] = repo[rb[b]].node()
2937 bookmarks.write(repo)
2937 bookmarks.write(repo)
2938
2938
2939 return ret
2939 return ret
2940
2940
2941 def push(ui, repo, dest=None, **opts):
2941 def push(ui, repo, dest=None, **opts):
2942 """push changes to the specified destination
2942 """push changes to the specified destination
2943
2943
2944 Push changesets from the local repository to the specified
2944 Push changesets from the local repository to the specified
2945 destination.
2945 destination.
2946
2946
2947 This operation is symmetrical to pull: it is identical to a pull
2947 This operation is symmetrical to pull: it is identical to a pull
2948 in the destination repository from the current one.
2948 in the destination repository from the current one.
2949
2949
2950 By default, push will not allow creation of new heads at the
2950 By default, push will not allow creation of new heads at the
2951 destination, since multiple heads would make it unclear which head
2951 destination, since multiple heads would make it unclear which head
2952 to use. In this situation, it is recommended to pull and merge
2952 to use. In this situation, it is recommended to pull and merge
2953 before pushing.
2953 before pushing.
2954
2954
2955 Use --new-branch if you want to allow push to create a new named
2955 Use --new-branch if you want to allow push to create a new named
2956 branch that is not present at the destination. This allows you to
2956 branch that is not present at the destination. This allows you to
2957 only create a new branch without forcing other changes.
2957 only create a new branch without forcing other changes.
2958
2958
2959 Use -f/--force to override the default behavior and push all
2959 Use -f/--force to override the default behavior and push all
2960 changesets on all branches.
2960 changesets on all branches.
2961
2961
2962 If -r/--rev is used, the specified revision and all its ancestors
2962 If -r/--rev is used, the specified revision and all its ancestors
2963 will be pushed to the remote repository.
2963 will be pushed to the remote repository.
2964
2964
2965 Please see :hg:`help urls` for important details about ``ssh://``
2965 Please see :hg:`help urls` for important details about ``ssh://``
2966 URLs. If DESTINATION is omitted, a default path will be used.
2966 URLs. If DESTINATION is omitted, a default path will be used.
2967
2967
2968 Returns 0 if push was successful, 1 if nothing to push.
2968 Returns 0 if push was successful, 1 if nothing to push.
2969 """
2969 """
2970
2970
2971 if opts.get('bookmark'):
2971 if opts.get('bookmark'):
2972 for b in opts['bookmark']:
2972 for b in opts['bookmark']:
2973 # translate -B options to -r so changesets get pushed
2973 # translate -B options to -r so changesets get pushed
2974 if b in repo._bookmarks:
2974 if b in repo._bookmarks:
2975 opts.setdefault('rev', []).append(b)
2975 opts.setdefault('rev', []).append(b)
2976 else:
2976 else:
2977 # if we try to push a deleted bookmark, translate it to null
2977 # if we try to push a deleted bookmark, translate it to null
2978 # this lets simultaneous -r, -b options continue working
2978 # this lets simultaneous -r, -b options continue working
2979 opts.setdefault('rev', []).append("null")
2979 opts.setdefault('rev', []).append("null")
2980
2980
2981 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2981 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2982 dest, branches = hg.parseurl(dest, opts.get('branch'))
2982 dest, branches = hg.parseurl(dest, opts.get('branch'))
2983 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2983 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2984 other = hg.repository(hg.remoteui(repo, opts), dest)
2984 other = hg.repository(hg.remoteui(repo, opts), dest)
2985 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
2985 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
2986 if revs:
2986 if revs:
2987 revs = [repo.lookup(rev) for rev in revs]
2987 revs = [repo.lookup(rev) for rev in revs]
2988
2988
2989 repo._subtoppath = dest
2989 repo._subtoppath = dest
2990 try:
2990 try:
2991 # push subrepos depth-first for coherent ordering
2991 # push subrepos depth-first for coherent ordering
2992 c = repo['']
2992 c = repo['']
2993 subs = c.substate # only repos that are committed
2993 subs = c.substate # only repos that are committed
2994 for s in sorted(subs):
2994 for s in sorted(subs):
2995 if not c.sub(s).push(opts.get('force')):
2995 if not c.sub(s).push(opts.get('force')):
2996 return False
2996 return False
2997 finally:
2997 finally:
2998 del repo._subtoppath
2998 del repo._subtoppath
2999 result = repo.push(other, opts.get('force'), revs=revs,
2999 result = repo.push(other, opts.get('force'), revs=revs,
3000 newbranch=opts.get('new_branch'))
3000 newbranch=opts.get('new_branch'))
3001
3001
3002 result = (result == 0)
3002 result = (result == 0)
3003
3003
3004 if opts.get('bookmark'):
3004 if opts.get('bookmark'):
3005 rb = other.listkeys('bookmarks')
3005 rb = other.listkeys('bookmarks')
3006 for b in opts['bookmark']:
3006 for b in opts['bookmark']:
3007 # explicit push overrides remote bookmark if any
3007 # explicit push overrides remote bookmark if any
3008 if b in repo._bookmarks:
3008 if b in repo._bookmarks:
3009 ui.status(_("exporting bookmark %s\n") % b)
3009 ui.status(_("exporting bookmark %s\n") % b)
3010 new = repo[b].hex()
3010 new = repo[b].hex()
3011 elif b in rb:
3011 elif b in rb:
3012 ui.status(_("deleting remote bookmark %s\n") % b)
3012 ui.status(_("deleting remote bookmark %s\n") % b)
3013 new = '' # delete
3013 new = '' # delete
3014 else:
3014 else:
3015 ui.warn(_('bookmark %s does not exist on the local '
3015 ui.warn(_('bookmark %s does not exist on the local '
3016 'or remote repository!\n') % b)
3016 'or remote repository!\n') % b)
3017 return 2
3017 return 2
3018 old = rb.get(b, '')
3018 old = rb.get(b, '')
3019 r = other.pushkey('bookmarks', b, old, new)
3019 r = other.pushkey('bookmarks', b, old, new)
3020 if not r:
3020 if not r:
3021 ui.warn(_('updating bookmark %s failed!\n') % b)
3021 ui.warn(_('updating bookmark %s failed!\n') % b)
3022 if not result:
3022 if not result:
3023 result = 2
3023 result = 2
3024
3024
3025 return result
3025 return result
3026
3026
3027 def recover(ui, repo):
3027 def recover(ui, repo):
3028 """roll back an interrupted transaction
3028 """roll back an interrupted transaction
3029
3029
3030 Recover from an interrupted commit or pull.
3030 Recover from an interrupted commit or pull.
3031
3031
3032 This command tries to fix the repository status after an
3032 This command tries to fix the repository status after an
3033 interrupted operation. It should only be necessary when Mercurial
3033 interrupted operation. It should only be necessary when Mercurial
3034 suggests it.
3034 suggests it.
3035
3035
3036 Returns 0 if successful, 1 if nothing to recover or verify fails.
3036 Returns 0 if successful, 1 if nothing to recover or verify fails.
3037 """
3037 """
3038 if repo.recover():
3038 if repo.recover():
3039 return hg.verify(repo)
3039 return hg.verify(repo)
3040 return 1
3040 return 1
3041
3041
3042 def remove(ui, repo, *pats, **opts):
3042 def remove(ui, repo, *pats, **opts):
3043 """remove the specified files on the next commit
3043 """remove the specified files on the next commit
3044
3044
3045 Schedule the indicated files for removal from the repository.
3045 Schedule the indicated files for removal from the repository.
3046
3046
3047 This only removes files from the current branch, not from the
3047 This only removes files from the current branch, not from the
3048 entire project history. -A/--after can be used to remove only
3048 entire project history. -A/--after can be used to remove only
3049 files that have already been deleted, -f/--force can be used to
3049 files that have already been deleted, -f/--force can be used to
3050 force deletion, and -Af can be used to remove files from the next
3050 force deletion, and -Af can be used to remove files from the next
3051 revision without deleting them from the working directory.
3051 revision without deleting them from the working directory.
3052
3052
3053 The following table details the behavior of remove for different
3053 The following table details the behavior of remove for different
3054 file states (columns) and option combinations (rows). The file
3054 file states (columns) and option combinations (rows). The file
3055 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
3055 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
3056 reported by :hg:`status`). The actions are Warn, Remove (from
3056 reported by :hg:`status`). The actions are Warn, Remove (from
3057 branch) and Delete (from disk)::
3057 branch) and Delete (from disk)::
3058
3058
3059 A C M !
3059 A C M !
3060 none W RD W R
3060 none W RD W R
3061 -f R RD RD R
3061 -f R RD RD R
3062 -A W W W R
3062 -A W W W R
3063 -Af R R R R
3063 -Af R R R R
3064
3064
3065 This command schedules the files to be removed at the next commit.
3065 This command schedules the files to be removed at the next commit.
3066 To undo a remove before that, see :hg:`revert`.
3066 To undo a remove before that, see :hg:`revert`.
3067
3067
3068 Returns 0 on success, 1 if any warnings encountered.
3068 Returns 0 on success, 1 if any warnings encountered.
3069 """
3069 """
3070
3070
3071 ret = 0
3071 ret = 0
3072 after, force = opts.get('after'), opts.get('force')
3072 after, force = opts.get('after'), opts.get('force')
3073 if not pats and not after:
3073 if not pats and not after:
3074 raise util.Abort(_('no files specified'))
3074 raise util.Abort(_('no files specified'))
3075
3075
3076 m = cmdutil.match(repo, pats, opts)
3076 m = cmdutil.match(repo, pats, opts)
3077 s = repo.status(match=m, clean=True)
3077 s = repo.status(match=m, clean=True)
3078 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
3078 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
3079
3079
3080 for f in m.files():
3080 for f in m.files():
3081 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
3081 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
3082 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
3082 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
3083 ret = 1
3083 ret = 1
3084
3084
3085 if force:
3085 if force:
3086 remove, forget = modified + deleted + clean, added
3086 remove, forget = modified + deleted + clean, added
3087 elif after:
3087 elif after:
3088 remove, forget = deleted, []
3088 remove, forget = deleted, []
3089 for f in modified + added + clean:
3089 for f in modified + added + clean:
3090 ui.warn(_('not removing %s: file still exists (use -f'
3090 ui.warn(_('not removing %s: file still exists (use -f'
3091 ' to force removal)\n') % m.rel(f))
3091 ' to force removal)\n') % m.rel(f))
3092 ret = 1
3092 ret = 1
3093 else:
3093 else:
3094 remove, forget = deleted + clean, []
3094 remove, forget = deleted + clean, []
3095 for f in modified:
3095 for f in modified:
3096 ui.warn(_('not removing %s: file is modified (use -f'
3096 ui.warn(_('not removing %s: file is modified (use -f'
3097 ' to force removal)\n') % m.rel(f))
3097 ' to force removal)\n') % m.rel(f))
3098 ret = 1
3098 ret = 1
3099 for f in added:
3099 for f in added:
3100 ui.warn(_('not removing %s: file has been marked for add (use -f'
3100 ui.warn(_('not removing %s: file has been marked for add (use -f'
3101 ' to force removal)\n') % m.rel(f))
3101 ' to force removal)\n') % m.rel(f))
3102 ret = 1
3102 ret = 1
3103
3103
3104 for f in sorted(remove + forget):
3104 for f in sorted(remove + forget):
3105 if ui.verbose or not m.exact(f):
3105 if ui.verbose or not m.exact(f):
3106 ui.status(_('removing %s\n') % m.rel(f))
3106 ui.status(_('removing %s\n') % m.rel(f))
3107
3107
3108 repo[None].forget(forget)
3108 repo[None].forget(forget)
3109 repo[None].remove(remove, unlink=not after)
3109 repo[None].remove(remove, unlink=not after)
3110 return ret
3110 return ret
3111
3111
3112 def rename(ui, repo, *pats, **opts):
3112 def rename(ui, repo, *pats, **opts):
3113 """rename files; equivalent of copy + remove
3113 """rename files; equivalent of copy + remove
3114
3114
3115 Mark dest as copies of sources; mark sources for deletion. If dest
3115 Mark dest as copies of sources; mark sources for deletion. If dest
3116 is a directory, copies are put in that directory. If dest is a
3116 is a directory, copies are put in that directory. If dest is a
3117 file, there can only be one source.
3117 file, there can only be one source.
3118
3118
3119 By default, this command copies the contents of files as they
3119 By default, this command copies the contents of files as they
3120 exist in the working directory. If invoked with -A/--after, the
3120 exist in the working directory. If invoked with -A/--after, the
3121 operation is recorded, but no copying is performed.
3121 operation is recorded, but no copying is performed.
3122
3122
3123 This command takes effect at the next commit. To undo a rename
3123 This command takes effect at the next commit. To undo a rename
3124 before that, see :hg:`revert`.
3124 before that, see :hg:`revert`.
3125
3125
3126 Returns 0 on success, 1 if errors are encountered.
3126 Returns 0 on success, 1 if errors are encountered.
3127 """
3127 """
3128 wlock = repo.wlock(False)
3128 wlock = repo.wlock(False)
3129 try:
3129 try:
3130 return cmdutil.copy(ui, repo, pats, opts, rename=True)
3130 return cmdutil.copy(ui, repo, pats, opts, rename=True)
3131 finally:
3131 finally:
3132 wlock.release()
3132 wlock.release()
3133
3133
3134 def resolve(ui, repo, *pats, **opts):
3134 def resolve(ui, repo, *pats, **opts):
3135 """redo merges or set/view the merge status of files
3135 """redo merges or set/view the merge status of files
3136
3136
3137 Merges with unresolved conflicts are often the result of
3137 Merges with unresolved conflicts are often the result of
3138 non-interactive merging using the ``internal:merge`` configuration
3138 non-interactive merging using the ``internal:merge`` configuration
3139 setting, or a command-line merge tool like ``diff3``. The resolve
3139 setting, or a command-line merge tool like ``diff3``. The resolve
3140 command is used to manage the files involved in a merge, after
3140 command is used to manage the files involved in a merge, after
3141 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
3141 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
3142 working directory must have two parents).
3142 working directory must have two parents).
3143
3143
3144 The resolve command can be used in the following ways:
3144 The resolve command can be used in the following ways:
3145
3145
3146 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
3146 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
3147 files, discarding any previous merge attempts. Re-merging is not
3147 files, discarding any previous merge attempts. Re-merging is not
3148 performed for files already marked as resolved. Use ``--all/-a``
3148 performed for files already marked as resolved. Use ``--all/-a``
3149 to selects all unresolved files. ``--tool`` can be used to specify
3149 to selects all unresolved files. ``--tool`` can be used to specify
3150 the merge tool used for the given files. It overrides the HGMERGE
3150 the merge tool used for the given files. It overrides the HGMERGE
3151 environment variable and your configuration files.
3151 environment variable and your configuration files.
3152
3152
3153 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
3153 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
3154 (e.g. after having manually fixed-up the files). The default is
3154 (e.g. after having manually fixed-up the files). The default is
3155 to mark all unresolved files.
3155 to mark all unresolved files.
3156
3156
3157 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
3157 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
3158 default is to mark all resolved files.
3158 default is to mark all resolved files.
3159
3159
3160 - :hg:`resolve -l`: list files which had or still have conflicts.
3160 - :hg:`resolve -l`: list files which had or still have conflicts.
3161 In the printed list, ``U`` = unresolved and ``R`` = resolved.
3161 In the printed list, ``U`` = unresolved and ``R`` = resolved.
3162
3162
3163 Note that Mercurial will not let you commit files with unresolved
3163 Note that Mercurial will not let you commit files with unresolved
3164 merge conflicts. You must use :hg:`resolve -m ...` before you can
3164 merge conflicts. You must use :hg:`resolve -m ...` before you can
3165 commit after a conflicting merge.
3165 commit after a conflicting merge.
3166
3166
3167 Returns 0 on success, 1 if any files fail a resolve attempt.
3167 Returns 0 on success, 1 if any files fail a resolve attempt.
3168 """
3168 """
3169
3169
3170 all, mark, unmark, show, nostatus = \
3170 all, mark, unmark, show, nostatus = \
3171 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
3171 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
3172
3172
3173 if (show and (mark or unmark)) or (mark and unmark):
3173 if (show and (mark or unmark)) or (mark and unmark):
3174 raise util.Abort(_("too many options specified"))
3174 raise util.Abort(_("too many options specified"))
3175 if pats and all:
3175 if pats and all:
3176 raise util.Abort(_("can't specify --all and patterns"))
3176 raise util.Abort(_("can't specify --all and patterns"))
3177 if not (all or pats or show or mark or unmark):
3177 if not (all or pats or show or mark or unmark):
3178 raise util.Abort(_('no files or directories specified; '
3178 raise util.Abort(_('no files or directories specified; '
3179 'use --all to remerge all files'))
3179 'use --all to remerge all files'))
3180
3180
3181 ms = mergemod.mergestate(repo)
3181 ms = mergemod.mergestate(repo)
3182 m = cmdutil.match(repo, pats, opts)
3182 m = cmdutil.match(repo, pats, opts)
3183 ret = 0
3183 ret = 0
3184
3184
3185 for f in ms:
3185 for f in ms:
3186 if m(f):
3186 if m(f):
3187 if show:
3187 if show:
3188 if nostatus:
3188 if nostatus:
3189 ui.write("%s\n" % f)
3189 ui.write("%s\n" % f)
3190 else:
3190 else:
3191 ui.write("%s %s\n" % (ms[f].upper(), f),
3191 ui.write("%s %s\n" % (ms[f].upper(), f),
3192 label='resolve.' +
3192 label='resolve.' +
3193 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
3193 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
3194 elif mark:
3194 elif mark:
3195 ms.mark(f, "r")
3195 ms.mark(f, "r")
3196 elif unmark:
3196 elif unmark:
3197 ms.mark(f, "u")
3197 ms.mark(f, "u")
3198 else:
3198 else:
3199 wctx = repo[None]
3199 wctx = repo[None]
3200 mctx = wctx.parents()[-1]
3200 mctx = wctx.parents()[-1]
3201
3201
3202 # backup pre-resolve (merge uses .orig for its own purposes)
3202 # backup pre-resolve (merge uses .orig for its own purposes)
3203 a = repo.wjoin(f)
3203 a = repo.wjoin(f)
3204 util.copyfile(a, a + ".resolve")
3204 util.copyfile(a, a + ".resolve")
3205
3205
3206 try:
3206 try:
3207 # resolve file
3207 # resolve file
3208 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
3208 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
3209 if ms.resolve(f, wctx, mctx):
3209 if ms.resolve(f, wctx, mctx):
3210 ret = 1
3210 ret = 1
3211 finally:
3211 finally:
3212 ui.setconfig('ui', 'forcemerge', '')
3212 ui.setconfig('ui', 'forcemerge', '')
3213
3213
3214 # replace filemerge's .orig file with our resolve file
3214 # replace filemerge's .orig file with our resolve file
3215 util.rename(a + ".resolve", a + ".orig")
3215 util.rename(a + ".resolve", a + ".orig")
3216
3216
3217 ms.commit()
3217 ms.commit()
3218 return ret
3218 return ret
3219
3219
3220 def revert(ui, repo, *pats, **opts):
3220 def revert(ui, repo, *pats, **opts):
3221 """restore individual files or directories to an earlier state
3221 """restore individual files or directories to an earlier state
3222
3222
3223 .. note::
3223 .. note::
3224 This command is most likely not what you are looking for.
3224 This command is most likely not what you are looking for.
3225 Revert will partially overwrite content in the working
3225 Revert will partially overwrite content in the working
3226 directory without changing the working directory parents. Use
3226 directory without changing the working directory parents. Use
3227 :hg:`update -r rev` to check out earlier revisions, or
3227 :hg:`update -r rev` to check out earlier revisions, or
3228 :hg:`update --clean .` to undo a merge which has added another
3228 :hg:`update --clean .` to undo a merge which has added another
3229 parent.
3229 parent.
3230
3230
3231 With no revision specified, revert the named files or directories
3231 With no revision specified, revert the named files or directories
3232 to the contents they had in the parent of the working directory.
3232 to the contents they had in the parent of the working directory.
3233 This restores the contents of the affected files to an unmodified
3233 This restores the contents of the affected files to an unmodified
3234 state and unschedules adds, removes, copies, and renames. If the
3234 state and unschedules adds, removes, copies, and renames. If the
3235 working directory has two parents, you must explicitly specify a
3235 working directory has two parents, you must explicitly specify a
3236 revision.
3236 revision.
3237
3237
3238 Using the -r/--rev option, revert the given files or directories
3238 Using the -r/--rev option, revert the given files or directories
3239 to their contents as of a specific revision. This can be helpful
3239 to their contents as of a specific revision. This can be helpful
3240 to "roll back" some or all of an earlier change. See :hg:`help
3240 to "roll back" some or all of an earlier change. See :hg:`help
3241 dates` for a list of formats valid for -d/--date.
3241 dates` for a list of formats valid for -d/--date.
3242
3242
3243 Revert modifies the working directory. It does not commit any
3243 Revert modifies the working directory. It does not commit any
3244 changes, or change the parent of the working directory. If you
3244 changes, or change the parent of the working directory. If you
3245 revert to a revision other than the parent of the working
3245 revert to a revision other than the parent of the working
3246 directory, the reverted files will thus appear modified
3246 directory, the reverted files will thus appear modified
3247 afterwards.
3247 afterwards.
3248
3248
3249 If a file has been deleted, it is restored. If the executable mode
3249 If a file has been deleted, it is restored. If the executable mode
3250 of a file was changed, it is reset.
3250 of a file was changed, it is reset.
3251
3251
3252 If names are given, all files matching the names are reverted.
3252 If names are given, all files matching the names are reverted.
3253 If no arguments are given, no files are reverted.
3253 If no arguments are given, no files are reverted.
3254
3254
3255 Modified files are saved with a .orig suffix before reverting.
3255 Modified files are saved with a .orig suffix before reverting.
3256 To disable these backups, use --no-backup.
3256 To disable these backups, use --no-backup.
3257
3257
3258 Returns 0 on success.
3258 Returns 0 on success.
3259 """
3259 """
3260
3260
3261 if opts.get("date"):
3261 if opts.get("date"):
3262 if opts.get("rev"):
3262 if opts.get("rev"):
3263 raise util.Abort(_("you can't specify a revision and a date"))
3263 raise util.Abort(_("you can't specify a revision and a date"))
3264 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
3264 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
3265
3265
3266 parent, p2 = repo.dirstate.parents()
3266 parent, p2 = repo.dirstate.parents()
3267 if not opts.get('rev') and p2 != nullid:
3267 if not opts.get('rev') and p2 != nullid:
3268 raise util.Abort(_('uncommitted merge - '
3268 raise util.Abort(_('uncommitted merge - '
3269 'use "hg update", see "hg help revert"'))
3269 'use "hg update", see "hg help revert"'))
3270
3270
3271 if not pats and not opts.get('all'):
3271 if not pats and not opts.get('all'):
3272 raise util.Abort(_('no files or directories specified; '
3272 raise util.Abort(_('no files or directories specified; '
3273 'use --all to revert the whole repo'))
3273 'use --all to revert the whole repo'))
3274
3274
3275 ctx = cmdutil.revsingle(repo, opts.get('rev'))
3275 ctx = cmdutil.revsingle(repo, opts.get('rev'))
3276 node = ctx.node()
3276 node = ctx.node()
3277 mf = ctx.manifest()
3277 mf = ctx.manifest()
3278 if node == parent:
3278 if node == parent:
3279 pmf = mf
3279 pmf = mf
3280 else:
3280 else:
3281 pmf = None
3281 pmf = None
3282
3282
3283 # need all matching names in dirstate and manifest of target rev,
3283 # need all matching names in dirstate and manifest of target rev,
3284 # so have to walk both. do not print errors if files exist in one
3284 # so have to walk both. do not print errors if files exist in one
3285 # but not other.
3285 # but not other.
3286
3286
3287 names = {}
3287 names = {}
3288
3288
3289 wlock = repo.wlock()
3289 wlock = repo.wlock()
3290 try:
3290 try:
3291 # walk dirstate.
3291 # walk dirstate.
3292
3292
3293 m = cmdutil.match(repo, pats, opts)
3293 m = cmdutil.match(repo, pats, opts)
3294 m.bad = lambda x, y: False
3294 m.bad = lambda x, y: False
3295 for abs in repo.walk(m):
3295 for abs in repo.walk(m):
3296 names[abs] = m.rel(abs), m.exact(abs)
3296 names[abs] = m.rel(abs), m.exact(abs)
3297
3297
3298 # walk target manifest.
3298 # walk target manifest.
3299
3299
3300 def badfn(path, msg):
3300 def badfn(path, msg):
3301 if path in names:
3301 if path in names:
3302 return
3302 return
3303 path_ = path + '/'
3303 path_ = path + '/'
3304 for f in names:
3304 for f in names:
3305 if f.startswith(path_):
3305 if f.startswith(path_):
3306 return
3306 return
3307 ui.warn("%s: %s\n" % (m.rel(path), msg))
3307 ui.warn("%s: %s\n" % (m.rel(path), msg))
3308
3308
3309 m = cmdutil.match(repo, pats, opts)
3309 m = cmdutil.match(repo, pats, opts)
3310 m.bad = badfn
3310 m.bad = badfn
3311 for abs in repo[node].walk(m):
3311 for abs in repo[node].walk(m):
3312 if abs not in names:
3312 if abs not in names:
3313 names[abs] = m.rel(abs), m.exact(abs)
3313 names[abs] = m.rel(abs), m.exact(abs)
3314
3314
3315 m = cmdutil.matchfiles(repo, names)
3315 m = cmdutil.matchfiles(repo, names)
3316 changes = repo.status(match=m)[:4]
3316 changes = repo.status(match=m)[:4]
3317 modified, added, removed, deleted = map(set, changes)
3317 modified, added, removed, deleted = map(set, changes)
3318
3318
3319 # if f is a rename, also revert the source
3319 # if f is a rename, also revert the source
3320 cwd = repo.getcwd()
3320 cwd = repo.getcwd()
3321 for f in added:
3321 for f in added:
3322 src = repo.dirstate.copied(f)
3322 src = repo.dirstate.copied(f)
3323 if src and src not in names and repo.dirstate[src] == 'r':
3323 if src and src not in names and repo.dirstate[src] == 'r':
3324 removed.add(src)
3324 removed.add(src)
3325 names[src] = (repo.pathto(src, cwd), True)
3325 names[src] = (repo.pathto(src, cwd), True)
3326
3326
3327 def removeforget(abs):
3327 def removeforget(abs):
3328 if repo.dirstate[abs] == 'a':
3328 if repo.dirstate[abs] == 'a':
3329 return _('forgetting %s\n')
3329 return _('forgetting %s\n')
3330 return _('removing %s\n')
3330 return _('removing %s\n')
3331
3331
3332 revert = ([], _('reverting %s\n'))
3332 revert = ([], _('reverting %s\n'))
3333 add = ([], _('adding %s\n'))
3333 add = ([], _('adding %s\n'))
3334 remove = ([], removeforget)
3334 remove = ([], removeforget)
3335 undelete = ([], _('undeleting %s\n'))
3335 undelete = ([], _('undeleting %s\n'))
3336
3336
3337 disptable = (
3337 disptable = (
3338 # dispatch table:
3338 # dispatch table:
3339 # file state
3339 # file state
3340 # action if in target manifest
3340 # action if in target manifest
3341 # action if not in target manifest
3341 # action if not in target manifest
3342 # make backup if in target manifest
3342 # make backup if in target manifest
3343 # make backup if not in target manifest
3343 # make backup if not in target manifest
3344 (modified, revert, remove, True, True),
3344 (modified, revert, remove, True, True),
3345 (added, revert, remove, True, False),
3345 (added, revert, remove, True, False),
3346 (removed, undelete, None, False, False),
3346 (removed, undelete, None, False, False),
3347 (deleted, revert, remove, False, False),
3347 (deleted, revert, remove, False, False),
3348 )
3348 )
3349
3349
3350 for abs, (rel, exact) in sorted(names.items()):
3350 for abs, (rel, exact) in sorted(names.items()):
3351 mfentry = mf.get(abs)
3351 mfentry = mf.get(abs)
3352 target = repo.wjoin(abs)
3352 target = repo.wjoin(abs)
3353 def handle(xlist, dobackup):
3353 def handle(xlist, dobackup):
3354 xlist[0].append(abs)
3354 xlist[0].append(abs)
3355 if (dobackup and not opts.get('no_backup') and
3355 if (dobackup and not opts.get('no_backup') and
3356 os.path.lexists(target)):
3356 os.path.lexists(target)):
3357 bakname = "%s.orig" % rel
3357 bakname = "%s.orig" % rel
3358 ui.note(_('saving current version of %s as %s\n') %
3358 ui.note(_('saving current version of %s as %s\n') %
3359 (rel, bakname))
3359 (rel, bakname))
3360 if not opts.get('dry_run'):
3360 if not opts.get('dry_run'):
3361 util.rename(target, bakname)
3361 util.rename(target, bakname)
3362 if ui.verbose or not exact:
3362 if ui.verbose or not exact:
3363 msg = xlist[1]
3363 msg = xlist[1]
3364 if not isinstance(msg, basestring):
3364 if not isinstance(msg, basestring):
3365 msg = msg(abs)
3365 msg = msg(abs)
3366 ui.status(msg % rel)
3366 ui.status(msg % rel)
3367 for table, hitlist, misslist, backuphit, backupmiss in disptable:
3367 for table, hitlist, misslist, backuphit, backupmiss in disptable:
3368 if abs not in table:
3368 if abs not in table:
3369 continue
3369 continue
3370 # file has changed in dirstate
3370 # file has changed in dirstate
3371 if mfentry:
3371 if mfentry:
3372 handle(hitlist, backuphit)
3372 handle(hitlist, backuphit)
3373 elif misslist is not None:
3373 elif misslist is not None:
3374 handle(misslist, backupmiss)
3374 handle(misslist, backupmiss)
3375 break
3375 break
3376 else:
3376 else:
3377 if abs not in repo.dirstate:
3377 if abs not in repo.dirstate:
3378 if mfentry:
3378 if mfentry:
3379 handle(add, True)
3379 handle(add, True)
3380 elif exact:
3380 elif exact:
3381 ui.warn(_('file not managed: %s\n') % rel)
3381 ui.warn(_('file not managed: %s\n') % rel)
3382 continue
3382 continue
3383 # file has not changed in dirstate
3383 # file has not changed in dirstate
3384 if node == parent:
3384 if node == parent:
3385 if exact:
3385 if exact:
3386 ui.warn(_('no changes needed to %s\n') % rel)
3386 ui.warn(_('no changes needed to %s\n') % rel)
3387 continue
3387 continue
3388 if pmf is None:
3388 if pmf is None:
3389 # only need parent manifest in this unlikely case,
3389 # only need parent manifest in this unlikely case,
3390 # so do not read by default
3390 # so do not read by default
3391 pmf = repo[parent].manifest()
3391 pmf = repo[parent].manifest()
3392 if abs in pmf:
3392 if abs in pmf:
3393 if mfentry:
3393 if mfentry:
3394 # if version of file is same in parent and target
3394 # if version of file is same in parent and target
3395 # manifests, do nothing
3395 # manifests, do nothing
3396 if (pmf[abs] != mfentry or
3396 if (pmf[abs] != mfentry or
3397 pmf.flags(abs) != mf.flags(abs)):
3397 pmf.flags(abs) != mf.flags(abs)):
3398 handle(revert, False)
3398 handle(revert, False)
3399 else:
3399 else:
3400 handle(remove, False)
3400 handle(remove, False)
3401
3401
3402 if not opts.get('dry_run'):
3402 if not opts.get('dry_run'):
3403 def checkout(f):
3403 def checkout(f):
3404 fc = ctx[f]
3404 fc = ctx[f]
3405 repo.wwrite(f, fc.data(), fc.flags())
3405 repo.wwrite(f, fc.data(), fc.flags())
3406
3406
3407 audit_path = util.path_auditor(repo.root)
3407 audit_path = util.path_auditor(repo.root)
3408 for f in remove[0]:
3408 for f in remove[0]:
3409 if repo.dirstate[f] == 'a':
3409 if repo.dirstate[f] == 'a':
3410 repo.dirstate.forget(f)
3410 repo.dirstate.forget(f)
3411 continue
3411 continue
3412 audit_path(f)
3412 audit_path(f)
3413 try:
3413 try:
3414 util.unlinkpath(repo.wjoin(f))
3414 util.unlinkpath(repo.wjoin(f))
3415 except OSError:
3415 except OSError:
3416 pass
3416 pass
3417 repo.dirstate.remove(f)
3417 repo.dirstate.remove(f)
3418
3418
3419 normal = None
3419 normal = None
3420 if node == parent:
3420 if node == parent:
3421 # We're reverting to our parent. If possible, we'd like status
3421 # We're reverting to our parent. If possible, we'd like status
3422 # to report the file as clean. We have to use normallookup for
3422 # to report the file as clean. We have to use normallookup for
3423 # merges to avoid losing information about merged/dirty files.
3423 # merges to avoid losing information about merged/dirty files.
3424 if p2 != nullid:
3424 if p2 != nullid:
3425 normal = repo.dirstate.normallookup
3425 normal = repo.dirstate.normallookup
3426 else:
3426 else:
3427 normal = repo.dirstate.normal
3427 normal = repo.dirstate.normal
3428 for f in revert[0]:
3428 for f in revert[0]:
3429 checkout(f)
3429 checkout(f)
3430 if normal:
3430 if normal:
3431 normal(f)
3431 normal(f)
3432
3432
3433 for f in add[0]:
3433 for f in add[0]:
3434 checkout(f)
3434 checkout(f)
3435 repo.dirstate.add(f)
3435 repo.dirstate.add(f)
3436
3436
3437 normal = repo.dirstate.normallookup
3437 normal = repo.dirstate.normallookup
3438 if node == parent and p2 == nullid:
3438 if node == parent and p2 == nullid:
3439 normal = repo.dirstate.normal
3439 normal = repo.dirstate.normal
3440 for f in undelete[0]:
3440 for f in undelete[0]:
3441 checkout(f)
3441 checkout(f)
3442 normal(f)
3442 normal(f)
3443
3443
3444 finally:
3444 finally:
3445 wlock.release()
3445 wlock.release()
3446
3446
3447 def rollback(ui, repo, **opts):
3447 def rollback(ui, repo, **opts):
3448 """roll back the last transaction (dangerous)
3448 """roll back the last transaction (dangerous)
3449
3449
3450 This command should be used with care. There is only one level of
3450 This command should be used with care. There is only one level of
3451 rollback, and there is no way to undo a rollback. It will also
3451 rollback, and there is no way to undo a rollback. It will also
3452 restore the dirstate at the time of the last transaction, losing
3452 restore the dirstate at the time of the last transaction, losing
3453 any dirstate changes since that time. This command does not alter
3453 any dirstate changes since that time. This command does not alter
3454 the working directory.
3454 the working directory.
3455
3455
3456 Transactions are used to encapsulate the effects of all commands
3456 Transactions are used to encapsulate the effects of all commands
3457 that create new changesets or propagate existing changesets into a
3457 that create new changesets or propagate existing changesets into a
3458 repository. For example, the following commands are transactional,
3458 repository. For example, the following commands are transactional,
3459 and their effects can be rolled back:
3459 and their effects can be rolled back:
3460
3460
3461 - commit
3461 - commit
3462 - import
3462 - import
3463 - pull
3463 - pull
3464 - push (with this repository as the destination)
3464 - push (with this repository as the destination)
3465 - unbundle
3465 - unbundle
3466
3466
3467 This command is not intended for use on public repositories. Once
3467 This command is not intended for use on public repositories. Once
3468 changes are visible for pull by other users, rolling a transaction
3468 changes are visible for pull by other users, rolling a transaction
3469 back locally is ineffective (someone else may already have pulled
3469 back locally is ineffective (someone else may already have pulled
3470 the changes). Furthermore, a race is possible with readers of the
3470 the changes). Furthermore, a race is possible with readers of the
3471 repository; for example an in-progress pull from the repository
3471 repository; for example an in-progress pull from the repository
3472 may fail if a rollback is performed.
3472 may fail if a rollback is performed.
3473
3473
3474 Returns 0 on success, 1 if no rollback data is available.
3474 Returns 0 on success, 1 if no rollback data is available.
3475 """
3475 """
3476 return repo.rollback(opts.get('dry_run'))
3476 return repo.rollback(opts.get('dry_run'))
3477
3477
3478 def root(ui, repo):
3478 def root(ui, repo):
3479 """print the root (top) of the current working directory
3479 """print the root (top) of the current working directory
3480
3480
3481 Print the root directory of the current repository.
3481 Print the root directory of the current repository.
3482
3482
3483 Returns 0 on success.
3483 Returns 0 on success.
3484 """
3484 """
3485 ui.write(repo.root + "\n")
3485 ui.write(repo.root + "\n")
3486
3486
3487 def serve(ui, repo, **opts):
3487 def serve(ui, repo, **opts):
3488 """start stand-alone webserver
3488 """start stand-alone webserver
3489
3489
3490 Start a local HTTP repository browser and pull server. You can use
3490 Start a local HTTP repository browser and pull server. You can use
3491 this for ad-hoc sharing and browsing of repositories. It is
3491 this for ad-hoc sharing and browsing of repositories. It is
3492 recommended to use a real web server to serve a repository for
3492 recommended to use a real web server to serve a repository for
3493 longer periods of time.
3493 longer periods of time.
3494
3494
3495 Please note that the server does not implement access control.
3495 Please note that the server does not implement access control.
3496 This means that, by default, anybody can read from the server and
3496 This means that, by default, anybody can read from the server and
3497 nobody can write to it by default. Set the ``web.allow_push``
3497 nobody can write to it by default. Set the ``web.allow_push``
3498 option to ``*`` to allow everybody to push to the server. You
3498 option to ``*`` to allow everybody to push to the server. You
3499 should use a real web server if you need to authenticate users.
3499 should use a real web server if you need to authenticate users.
3500
3500
3501 By default, the server logs accesses to stdout and errors to
3501 By default, the server logs accesses to stdout and errors to
3502 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
3502 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
3503 files.
3503 files.
3504
3504
3505 To have the server choose a free port number to listen on, specify
3505 To have the server choose a free port number to listen on, specify
3506 a port number of 0; in this case, the server will print the port
3506 a port number of 0; in this case, the server will print the port
3507 number it uses.
3507 number it uses.
3508
3508
3509 Returns 0 on success.
3509 Returns 0 on success.
3510 """
3510 """
3511
3511
3512 if opts["stdio"]:
3512 if opts["stdio"]:
3513 if repo is None:
3513 if repo is None:
3514 raise error.RepoError(_("There is no Mercurial repository here"
3514 raise error.RepoError(_("There is no Mercurial repository here"
3515 " (.hg not found)"))
3515 " (.hg not found)"))
3516 s = sshserver.sshserver(ui, repo)
3516 s = sshserver.sshserver(ui, repo)
3517 s.serve_forever()
3517 s.serve_forever()
3518
3518
3519 # this way we can check if something was given in the command-line
3519 # this way we can check if something was given in the command-line
3520 if opts.get('port'):
3520 if opts.get('port'):
3521 opts['port'] = util.getport(opts.get('port'))
3521 opts['port'] = util.getport(opts.get('port'))
3522
3522
3523 baseui = repo and repo.baseui or ui
3523 baseui = repo and repo.baseui or ui
3524 optlist = ("name templates style address port prefix ipv6"
3524 optlist = ("name templates style address port prefix ipv6"
3525 " accesslog errorlog certificate encoding")
3525 " accesslog errorlog certificate encoding")
3526 for o in optlist.split():
3526 for o in optlist.split():
3527 val = opts.get(o, '')
3527 val = opts.get(o, '')
3528 if val in (None, ''): # should check against default options instead
3528 if val in (None, ''): # should check against default options instead
3529 continue
3529 continue
3530 baseui.setconfig("web", o, val)
3530 baseui.setconfig("web", o, val)
3531 if repo and repo.ui != baseui:
3531 if repo and repo.ui != baseui:
3532 repo.ui.setconfig("web", o, val)
3532 repo.ui.setconfig("web", o, val)
3533
3533
3534 o = opts.get('web_conf') or opts.get('webdir_conf')
3534 o = opts.get('web_conf') or opts.get('webdir_conf')
3535 if not o:
3535 if not o:
3536 if not repo:
3536 if not repo:
3537 raise error.RepoError(_("There is no Mercurial repository"
3537 raise error.RepoError(_("There is no Mercurial repository"
3538 " here (.hg not found)"))
3538 " here (.hg not found)"))
3539 o = repo.root
3539 o = repo.root
3540
3540
3541 app = hgweb.hgweb(o, baseui=ui)
3541 app = hgweb.hgweb(o, baseui=ui)
3542
3542
3543 class service(object):
3543 class service(object):
3544 def init(self):
3544 def init(self):
3545 util.set_signal_handler()
3545 util.set_signal_handler()
3546 self.httpd = hgweb.server.create_server(ui, app)
3546 self.httpd = hgweb.server.create_server(ui, app)
3547
3547
3548 if opts['port'] and not ui.verbose:
3548 if opts['port'] and not ui.verbose:
3549 return
3549 return
3550
3550
3551 if self.httpd.prefix:
3551 if self.httpd.prefix:
3552 prefix = self.httpd.prefix.strip('/') + '/'
3552 prefix = self.httpd.prefix.strip('/') + '/'
3553 else:
3553 else:
3554 prefix = ''
3554 prefix = ''
3555
3555
3556 port = ':%d' % self.httpd.port
3556 port = ':%d' % self.httpd.port
3557 if port == ':80':
3557 if port == ':80':
3558 port = ''
3558 port = ''
3559
3559
3560 bindaddr = self.httpd.addr
3560 bindaddr = self.httpd.addr
3561 if bindaddr == '0.0.0.0':
3561 if bindaddr == '0.0.0.0':
3562 bindaddr = '*'
3562 bindaddr = '*'
3563 elif ':' in bindaddr: # IPv6
3563 elif ':' in bindaddr: # IPv6
3564 bindaddr = '[%s]' % bindaddr
3564 bindaddr = '[%s]' % bindaddr
3565
3565
3566 fqaddr = self.httpd.fqaddr
3566 fqaddr = self.httpd.fqaddr
3567 if ':' in fqaddr:
3567 if ':' in fqaddr:
3568 fqaddr = '[%s]' % fqaddr
3568 fqaddr = '[%s]' % fqaddr
3569 if opts['port']:
3569 if opts['port']:
3570 write = ui.status
3570 write = ui.status
3571 else:
3571 else:
3572 write = ui.write
3572 write = ui.write
3573 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
3573 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
3574 (fqaddr, port, prefix, bindaddr, self.httpd.port))
3574 (fqaddr, port, prefix, bindaddr, self.httpd.port))
3575
3575
3576 def run(self):
3576 def run(self):
3577 self.httpd.serve_forever()
3577 self.httpd.serve_forever()
3578
3578
3579 service = service()
3579 service = service()
3580
3580
3581 cmdutil.service(opts, initfn=service.init, runfn=service.run)
3581 cmdutil.service(opts, initfn=service.init, runfn=service.run)
3582
3582
3583 def status(ui, repo, *pats, **opts):
3583 def status(ui, repo, *pats, **opts):
3584 """show changed files in the working directory
3584 """show changed files in the working directory
3585
3585
3586 Show status of files in the repository. If names are given, only
3586 Show status of files in the repository. If names are given, only
3587 files that match are shown. Files that are clean or ignored or
3587 files that match are shown. Files that are clean or ignored or
3588 the source of a copy/move operation, are not listed unless
3588 the source of a copy/move operation, are not listed unless
3589 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
3589 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
3590 Unless options described with "show only ..." are given, the
3590 Unless options described with "show only ..." are given, the
3591 options -mardu are used.
3591 options -mardu are used.
3592
3592
3593 Option -q/--quiet hides untracked (unknown and ignored) files
3593 Option -q/--quiet hides untracked (unknown and ignored) files
3594 unless explicitly requested with -u/--unknown or -i/--ignored.
3594 unless explicitly requested with -u/--unknown or -i/--ignored.
3595
3595
3596 .. note::
3596 .. note::
3597 status may appear to disagree with diff if permissions have
3597 status may appear to disagree with diff if permissions have
3598 changed or a merge has occurred. The standard diff format does
3598 changed or a merge has occurred. The standard diff format does
3599 not report permission changes and diff only reports changes
3599 not report permission changes and diff only reports changes
3600 relative to one merge parent.
3600 relative to one merge parent.
3601
3601
3602 If one revision is given, it is used as the base revision.
3602 If one revision is given, it is used as the base revision.
3603 If two revisions are given, the differences between them are
3603 If two revisions are given, the differences between them are
3604 shown. The --change option can also be used as a shortcut to list
3604 shown. The --change option can also be used as a shortcut to list
3605 the changed files of a revision from its first parent.
3605 the changed files of a revision from its first parent.
3606
3606
3607 The codes used to show the status of files are::
3607 The codes used to show the status of files are::
3608
3608
3609 M = modified
3609 M = modified
3610 A = added
3610 A = added
3611 R = removed
3611 R = removed
3612 C = clean
3612 C = clean
3613 ! = missing (deleted by non-hg command, but still tracked)
3613 ! = missing (deleted by non-hg command, but still tracked)
3614 ? = not tracked
3614 ? = not tracked
3615 I = ignored
3615 I = ignored
3616 = origin of the previous file listed as A (added)
3616 = origin of the previous file listed as A (added)
3617
3617
3618 Returns 0 on success.
3618 Returns 0 on success.
3619 """
3619 """
3620
3620
3621 revs = opts.get('rev')
3621 revs = opts.get('rev')
3622 change = opts.get('change')
3622 change = opts.get('change')
3623
3623
3624 if revs and change:
3624 if revs and change:
3625 msg = _('cannot specify --rev and --change at the same time')
3625 msg = _('cannot specify --rev and --change at the same time')
3626 raise util.Abort(msg)
3626 raise util.Abort(msg)
3627 elif change:
3627 elif change:
3628 node2 = repo.lookup(change)
3628 node2 = repo.lookup(change)
3629 node1 = repo[node2].parents()[0].node()
3629 node1 = repo[node2].parents()[0].node()
3630 else:
3630 else:
3631 node1, node2 = cmdutil.revpair(repo, revs)
3631 node1, node2 = cmdutil.revpair(repo, revs)
3632
3632
3633 cwd = (pats and repo.getcwd()) or ''
3633 cwd = (pats and repo.getcwd()) or ''
3634 end = opts.get('print0') and '\0' or '\n'
3634 end = opts.get('print0') and '\0' or '\n'
3635 copy = {}
3635 copy = {}
3636 states = 'modified added removed deleted unknown ignored clean'.split()
3636 states = 'modified added removed deleted unknown ignored clean'.split()
3637 show = [k for k in states if opts.get(k)]
3637 show = [k for k in states if opts.get(k)]
3638 if opts.get('all'):
3638 if opts.get('all'):
3639 show += ui.quiet and (states[:4] + ['clean']) or states
3639 show += ui.quiet and (states[:4] + ['clean']) or states
3640 if not show:
3640 if not show:
3641 show = ui.quiet and states[:4] or states[:5]
3641 show = ui.quiet and states[:4] or states[:5]
3642
3642
3643 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
3643 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
3644 'ignored' in show, 'clean' in show, 'unknown' in show,
3644 'ignored' in show, 'clean' in show, 'unknown' in show,
3645 opts.get('subrepos'))
3645 opts.get('subrepos'))
3646 changestates = zip(states, 'MAR!?IC', stat)
3646 changestates = zip(states, 'MAR!?IC', stat)
3647
3647
3648 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
3648 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
3649 ctxn = repo[nullid]
3649 ctxn = repo[nullid]
3650 ctx1 = repo[node1]
3650 ctx1 = repo[node1]
3651 ctx2 = repo[node2]
3651 ctx2 = repo[node2]
3652 added = stat[1]
3652 added = stat[1]
3653 if node2 is None:
3653 if node2 is None:
3654 added = stat[0] + stat[1] # merged?
3654 added = stat[0] + stat[1] # merged?
3655
3655
3656 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
3656 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
3657 if k in added:
3657 if k in added:
3658 copy[k] = v
3658 copy[k] = v
3659 elif v in added:
3659 elif v in added:
3660 copy[v] = k
3660 copy[v] = k
3661
3661
3662 for state, char, files in changestates:
3662 for state, char, files in changestates:
3663 if state in show:
3663 if state in show:
3664 format = "%s %%s%s" % (char, end)
3664 format = "%s %%s%s" % (char, end)
3665 if opts.get('no_status'):
3665 if opts.get('no_status'):
3666 format = "%%s%s" % end
3666 format = "%%s%s" % end
3667
3667
3668 for f in files:
3668 for f in files:
3669 ui.write(format % repo.pathto(f, cwd),
3669 ui.write(format % repo.pathto(f, cwd),
3670 label='status.' + state)
3670 label='status.' + state)
3671 if f in copy:
3671 if f in copy:
3672 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
3672 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
3673 label='status.copied')
3673 label='status.copied')
3674
3674
3675 def summary(ui, repo, **opts):
3675 def summary(ui, repo, **opts):
3676 """summarize working directory state
3676 """summarize working directory state
3677
3677
3678 This generates a brief summary of the working directory state,
3678 This generates a brief summary of the working directory state,
3679 including parents, branch, commit status, and available updates.
3679 including parents, branch, commit status, and available updates.
3680
3680
3681 With the --remote option, this will check the default paths for
3681 With the --remote option, this will check the default paths for
3682 incoming and outgoing changes. This can be time-consuming.
3682 incoming and outgoing changes. This can be time-consuming.
3683
3683
3684 Returns 0 on success.
3684 Returns 0 on success.
3685 """
3685 """
3686
3686
3687 ctx = repo[None]
3687 ctx = repo[None]
3688 parents = ctx.parents()
3688 parents = ctx.parents()
3689 pnode = parents[0].node()
3689 pnode = parents[0].node()
3690
3690
3691 for p in parents:
3691 for p in parents:
3692 # label with log.changeset (instead of log.parent) since this
3692 # label with log.changeset (instead of log.parent) since this
3693 # shows a working directory parent *changeset*:
3693 # shows a working directory parent *changeset*:
3694 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
3694 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
3695 label='log.changeset')
3695 label='log.changeset')
3696 ui.write(' '.join(p.tags()), label='log.tag')
3696 ui.write(' '.join(p.tags()), label='log.tag')
3697 if p.rev() == -1:
3697 if p.rev() == -1:
3698 if not len(repo):
3698 if not len(repo):
3699 ui.write(_(' (empty repository)'))
3699 ui.write(_(' (empty repository)'))
3700 else:
3700 else:
3701 ui.write(_(' (no revision checked out)'))
3701 ui.write(_(' (no revision checked out)'))
3702 ui.write('\n')
3702 ui.write('\n')
3703 if p.description():
3703 if p.description():
3704 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
3704 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
3705 label='log.summary')
3705 label='log.summary')
3706
3706
3707 branch = ctx.branch()
3707 branch = ctx.branch()
3708 bheads = repo.branchheads(branch)
3708 bheads = repo.branchheads(branch)
3709 m = _('branch: %s\n') % branch
3709 m = _('branch: %s\n') % branch
3710 if branch != 'default':
3710 if branch != 'default':
3711 ui.write(m, label='log.branch')
3711 ui.write(m, label='log.branch')
3712 else:
3712 else:
3713 ui.status(m, label='log.branch')
3713 ui.status(m, label='log.branch')
3714
3714
3715 st = list(repo.status(unknown=True))[:6]
3715 st = list(repo.status(unknown=True))[:6]
3716
3716
3717 c = repo.dirstate.copies()
3717 c = repo.dirstate.copies()
3718 copied, renamed = [], []
3718 copied, renamed = [], []
3719 for d, s in c.iteritems():
3719 for d, s in c.iteritems():
3720 if s in st[2]:
3720 if s in st[2]:
3721 st[2].remove(s)
3721 st[2].remove(s)
3722 renamed.append(d)
3722 renamed.append(d)
3723 else:
3723 else:
3724 copied.append(d)
3724 copied.append(d)
3725 if d in st[1]:
3725 if d in st[1]:
3726 st[1].remove(d)
3726 st[1].remove(d)
3727 st.insert(3, renamed)
3727 st.insert(3, renamed)
3728 st.insert(4, copied)
3728 st.insert(4, copied)
3729
3729
3730 ms = mergemod.mergestate(repo)
3730 ms = mergemod.mergestate(repo)
3731 st.append([f for f in ms if ms[f] == 'u'])
3731 st.append([f for f in ms if ms[f] == 'u'])
3732
3732
3733 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
3733 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
3734 st.append(subs)
3734 st.append(subs)
3735
3735
3736 labels = [ui.label(_('%d modified'), 'status.modified'),
3736 labels = [ui.label(_('%d modified'), 'status.modified'),
3737 ui.label(_('%d added'), 'status.added'),
3737 ui.label(_('%d added'), 'status.added'),
3738 ui.label(_('%d removed'), 'status.removed'),
3738 ui.label(_('%d removed'), 'status.removed'),
3739 ui.label(_('%d renamed'), 'status.copied'),
3739 ui.label(_('%d renamed'), 'status.copied'),
3740 ui.label(_('%d copied'), 'status.copied'),
3740 ui.label(_('%d copied'), 'status.copied'),
3741 ui.label(_('%d deleted'), 'status.deleted'),
3741 ui.label(_('%d deleted'), 'status.deleted'),
3742 ui.label(_('%d unknown'), 'status.unknown'),
3742 ui.label(_('%d unknown'), 'status.unknown'),
3743 ui.label(_('%d ignored'), 'status.ignored'),
3743 ui.label(_('%d ignored'), 'status.ignored'),
3744 ui.label(_('%d unresolved'), 'resolve.unresolved'),
3744 ui.label(_('%d unresolved'), 'resolve.unresolved'),
3745 ui.label(_('%d subrepos'), 'status.modified')]
3745 ui.label(_('%d subrepos'), 'status.modified')]
3746 t = []
3746 t = []
3747 for s, l in zip(st, labels):
3747 for s, l in zip(st, labels):
3748 if s:
3748 if s:
3749 t.append(l % len(s))
3749 t.append(l % len(s))
3750
3750
3751 t = ', '.join(t)
3751 t = ', '.join(t)
3752 cleanworkdir = False
3752 cleanworkdir = False
3753
3753
3754 if len(parents) > 1:
3754 if len(parents) > 1:
3755 t += _(' (merge)')
3755 t += _(' (merge)')
3756 elif branch != parents[0].branch():
3756 elif branch != parents[0].branch():
3757 t += _(' (new branch)')
3757 t += _(' (new branch)')
3758 elif (parents[0].extra().get('close') and
3758 elif (parents[0].extra().get('close') and
3759 pnode in repo.branchheads(branch, closed=True)):
3759 pnode in repo.branchheads(branch, closed=True)):
3760 t += _(' (head closed)')
3760 t += _(' (head closed)')
3761 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
3761 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
3762 t += _(' (clean)')
3762 t += _(' (clean)')
3763 cleanworkdir = True
3763 cleanworkdir = True
3764 elif pnode not in bheads:
3764 elif pnode not in bheads:
3765 t += _(' (new branch head)')
3765 t += _(' (new branch head)')
3766
3766
3767 if cleanworkdir:
3767 if cleanworkdir:
3768 ui.status(_('commit: %s\n') % t.strip())
3768 ui.status(_('commit: %s\n') % t.strip())
3769 else:
3769 else:
3770 ui.write(_('commit: %s\n') % t.strip())
3770 ui.write(_('commit: %s\n') % t.strip())
3771
3771
3772 # all ancestors of branch heads - all ancestors of parent = new csets
3772 # all ancestors of branch heads - all ancestors of parent = new csets
3773 new = [0] * len(repo)
3773 new = [0] * len(repo)
3774 cl = repo.changelog
3774 cl = repo.changelog
3775 for a in [cl.rev(n) for n in bheads]:
3775 for a in [cl.rev(n) for n in bheads]:
3776 new[a] = 1
3776 new[a] = 1
3777 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
3777 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
3778 new[a] = 1
3778 new[a] = 1
3779 for a in [p.rev() for p in parents]:
3779 for a in [p.rev() for p in parents]:
3780 if a >= 0:
3780 if a >= 0:
3781 new[a] = 0
3781 new[a] = 0
3782 for a in cl.ancestors(*[p.rev() for p in parents]):
3782 for a in cl.ancestors(*[p.rev() for p in parents]):
3783 new[a] = 0
3783 new[a] = 0
3784 new = sum(new)
3784 new = sum(new)
3785
3785
3786 if new == 0:
3786 if new == 0:
3787 ui.status(_('update: (current)\n'))
3787 ui.status(_('update: (current)\n'))
3788 elif pnode not in bheads:
3788 elif pnode not in bheads:
3789 ui.write(_('update: %d new changesets (update)\n') % new)
3789 ui.write(_('update: %d new changesets (update)\n') % new)
3790 else:
3790 else:
3791 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
3791 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
3792 (new, len(bheads)))
3792 (new, len(bheads)))
3793
3793
3794 if opts.get('remote'):
3794 if opts.get('remote'):
3795 t = []
3795 t = []
3796 source, branches = hg.parseurl(ui.expandpath('default'))
3796 source, branches = hg.parseurl(ui.expandpath('default'))
3797 other = hg.repository(hg.remoteui(repo, {}), source)
3797 other = hg.repository(hg.remoteui(repo, {}), source)
3798 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3798 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3799 ui.debug('comparing with %s\n' % url.hidepassword(source))
3799 ui.debug('comparing with %s\n' % url.hidepassword(source))
3800 repo.ui.pushbuffer()
3800 repo.ui.pushbuffer()
3801 common, incoming, rheads = discovery.findcommonincoming(repo, other)
3801 common, incoming, rheads = discovery.findcommonincoming(repo, other)
3802 repo.ui.popbuffer()
3802 repo.ui.popbuffer()
3803 if incoming:
3803 if incoming:
3804 t.append(_('1 or more incoming'))
3804 t.append(_('1 or more incoming'))
3805
3805
3806 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
3806 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
3807 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3807 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3808 other = hg.repository(hg.remoteui(repo, {}), dest)
3808 other = hg.repository(hg.remoteui(repo, {}), dest)
3809 ui.debug('comparing with %s\n' % url.hidepassword(dest))
3809 ui.debug('comparing with %s\n' % url.hidepassword(dest))
3810 repo.ui.pushbuffer()
3810 repo.ui.pushbuffer()
3811 o = discovery.findoutgoing(repo, other)
3811 o = discovery.findoutgoing(repo, other)
3812 repo.ui.popbuffer()
3812 repo.ui.popbuffer()
3813 o = repo.changelog.nodesbetween(o, None)[0]
3813 o = repo.changelog.nodesbetween(o, None)[0]
3814 if o:
3814 if o:
3815 t.append(_('%d outgoing') % len(o))
3815 t.append(_('%d outgoing') % len(o))
3816
3816
3817 if t:
3817 if t:
3818 ui.write(_('remote: %s\n') % (', '.join(t)))
3818 ui.write(_('remote: %s\n') % (', '.join(t)))
3819 else:
3819 else:
3820 ui.status(_('remote: (synced)\n'))
3820 ui.status(_('remote: (synced)\n'))
3821
3821
3822 def tag(ui, repo, name1, *names, **opts):
3822 def tag(ui, repo, name1, *names, **opts):
3823 """add one or more tags for the current or given revision
3823 """add one or more tags for the current or given revision
3824
3824
3825 Name a particular revision using <name>.
3825 Name a particular revision using <name>.
3826
3826
3827 Tags are used to name particular revisions of the repository and are
3827 Tags are used to name particular revisions of the repository and are
3828 very useful to compare different revisions, to go back to significant
3828 very useful to compare different revisions, to go back to significant
3829 earlier versions or to mark branch points as releases, etc. Changing
3829 earlier versions or to mark branch points as releases, etc. Changing
3830 an existing tag is normally disallowed; use -f/--force to override.
3830 an existing tag is normally disallowed; use -f/--force to override.
3831
3831
3832 If no revision is given, the parent of the working directory is
3832 If no revision is given, the parent of the working directory is
3833 used, or tip if no revision is checked out.
3833 used, or tip if no revision is checked out.
3834
3834
3835 To facilitate version control, distribution, and merging of tags,
3835 To facilitate version control, distribution, and merging of tags,
3836 they are stored as a file named ".hgtags" which is managed similarly
3836 they are stored as a file named ".hgtags" which is managed similarly
3837 to other project files and can be hand-edited if necessary. This
3837 to other project files and can be hand-edited if necessary. This
3838 also means that tagging creates a new commit. The file
3838 also means that tagging creates a new commit. The file
3839 ".hg/localtags" is used for local tags (not shared among
3839 ".hg/localtags" is used for local tags (not shared among
3840 repositories).
3840 repositories).
3841
3841
3842 Tag commits are usually made at the head of a branch. If the parent
3842 Tag commits are usually made at the head of a branch. If the parent
3843 of the working directory is not a branch head, :hg:`tag` aborts; use
3843 of the working directory is not a branch head, :hg:`tag` aborts; use
3844 -f/--force to force the tag commit to be based on a non-head
3844 -f/--force to force the tag commit to be based on a non-head
3845 changeset.
3845 changeset.
3846
3846
3847 See :hg:`help dates` for a list of formats valid for -d/--date.
3847 See :hg:`help dates` for a list of formats valid for -d/--date.
3848
3848
3849 Since tag names have priority over branch names during revision
3849 Since tag names have priority over branch names during revision
3850 lookup, using an existing branch name as a tag name is discouraged.
3850 lookup, using an existing branch name as a tag name is discouraged.
3851
3851
3852 Returns 0 on success.
3852 Returns 0 on success.
3853 """
3853 """
3854
3854
3855 rev_ = "."
3855 rev_ = "."
3856 names = [t.strip() for t in (name1,) + names]
3856 names = [t.strip() for t in (name1,) + names]
3857 if len(names) != len(set(names)):
3857 if len(names) != len(set(names)):
3858 raise util.Abort(_('tag names must be unique'))
3858 raise util.Abort(_('tag names must be unique'))
3859 for n in names:
3859 for n in names:
3860 if n in ['tip', '.', 'null']:
3860 if n in ['tip', '.', 'null']:
3861 raise util.Abort(_('the name \'%s\' is reserved') % n)
3861 raise util.Abort(_('the name \'%s\' is reserved') % n)
3862 if not n:
3862 if not n:
3863 raise util.Abort(_('tag names cannot consist entirely of whitespace'))
3863 raise util.Abort(_('tag names cannot consist entirely of whitespace'))
3864 if opts.get('rev') and opts.get('remove'):
3864 if opts.get('rev') and opts.get('remove'):
3865 raise util.Abort(_("--rev and --remove are incompatible"))
3865 raise util.Abort(_("--rev and --remove are incompatible"))
3866 if opts.get('rev'):
3866 if opts.get('rev'):
3867 rev_ = opts['rev']
3867 rev_ = opts['rev']
3868 message = opts.get('message')
3868 message = opts.get('message')
3869 if opts.get('remove'):
3869 if opts.get('remove'):
3870 expectedtype = opts.get('local') and 'local' or 'global'
3870 expectedtype = opts.get('local') and 'local' or 'global'
3871 for n in names:
3871 for n in names:
3872 if not repo.tagtype(n):
3872 if not repo.tagtype(n):
3873 raise util.Abort(_('tag \'%s\' does not exist') % n)
3873 raise util.Abort(_('tag \'%s\' does not exist') % n)
3874 if repo.tagtype(n) != expectedtype:
3874 if repo.tagtype(n) != expectedtype:
3875 if expectedtype == 'global':
3875 if expectedtype == 'global':
3876 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
3876 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
3877 else:
3877 else:
3878 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
3878 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
3879 rev_ = nullid
3879 rev_ = nullid
3880 if not message:
3880 if not message:
3881 # we don't translate commit messages
3881 # we don't translate commit messages
3882 message = 'Removed tag %s' % ', '.join(names)
3882 message = 'Removed tag %s' % ', '.join(names)
3883 elif not opts.get('force'):
3883 elif not opts.get('force'):
3884 for n in names:
3884 for n in names:
3885 if n in repo.tags():
3885 if n in repo.tags():
3886 raise util.Abort(_('tag \'%s\' already exists '
3886 raise util.Abort(_('tag \'%s\' already exists '
3887 '(use -f to force)') % n)
3887 '(use -f to force)') % n)
3888 if not opts.get('local'):
3888 if not opts.get('local'):
3889 p1, p2 = repo.dirstate.parents()
3889 p1, p2 = repo.dirstate.parents()
3890 if p2 != nullid:
3890 if p2 != nullid:
3891 raise util.Abort(_('uncommitted merge'))
3891 raise util.Abort(_('uncommitted merge'))
3892 bheads = repo.branchheads()
3892 bheads = repo.branchheads()
3893 if not opts.get('force') and bheads and p1 not in bheads:
3893 if not opts.get('force') and bheads and p1 not in bheads:
3894 raise util.Abort(_('not at a branch head (use -f to force)'))
3894 raise util.Abort(_('not at a branch head (use -f to force)'))
3895 r = cmdutil.revsingle(repo, rev_).node()
3895 r = cmdutil.revsingle(repo, rev_).node()
3896
3896
3897 if not message:
3897 if not message:
3898 # we don't translate commit messages
3898 # we don't translate commit messages
3899 message = ('Added tag %s for changeset %s' %
3899 message = ('Added tag %s for changeset %s' %
3900 (', '.join(names), short(r)))
3900 (', '.join(names), short(r)))
3901
3901
3902 date = opts.get('date')
3902 date = opts.get('date')
3903 if date:
3903 if date:
3904 date = util.parsedate(date)
3904 date = util.parsedate(date)
3905
3905
3906 if opts.get('edit'):
3906 if opts.get('edit'):
3907 message = ui.edit(message, ui.username())
3907 message = ui.edit(message, ui.username())
3908
3908
3909 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
3909 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
3910
3910
3911 def tags(ui, repo):
3911 def tags(ui, repo):
3912 """list repository tags
3912 """list repository tags
3913
3913
3914 This lists both regular and local tags. When the -v/--verbose
3914 This lists both regular and local tags. When the -v/--verbose
3915 switch is used, a third column "local" is printed for local tags.
3915 switch is used, a third column "local" is printed for local tags.
3916
3916
3917 Returns 0 on success.
3917 Returns 0 on success.
3918 """
3918 """
3919
3919
3920 hexfunc = ui.debugflag and hex or short
3920 hexfunc = ui.debugflag and hex or short
3921 tagtype = ""
3921 tagtype = ""
3922
3922
3923 for t, n in reversed(repo.tagslist()):
3923 for t, n in reversed(repo.tagslist()):
3924 if ui.quiet:
3924 if ui.quiet:
3925 ui.write("%s\n" % t)
3925 ui.write("%s\n" % t)
3926 continue
3926 continue
3927
3927
3928 try:
3928 try:
3929 hn = hexfunc(n)
3929 hn = hexfunc(n)
3930 r = "%5d:%s" % (repo.changelog.rev(n), hn)
3930 r = "%5d:%s" % (repo.changelog.rev(n), hn)
3931 except error.LookupError:
3931 except error.LookupError:
3932 r = " ?:%s" % hn
3932 r = " ?:%s" % hn
3933 else:
3933 else:
3934 spaces = " " * (30 - encoding.colwidth(t))
3934 spaces = " " * (30 - encoding.colwidth(t))
3935 if ui.verbose:
3935 if ui.verbose:
3936 if repo.tagtype(t) == 'local':
3936 if repo.tagtype(t) == 'local':
3937 tagtype = " local"
3937 tagtype = " local"
3938 else:
3938 else:
3939 tagtype = ""
3939 tagtype = ""
3940 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
3940 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
3941
3941
3942 def tip(ui, repo, **opts):
3942 def tip(ui, repo, **opts):
3943 """show the tip revision
3943 """show the tip revision
3944
3944
3945 The tip revision (usually just called the tip) is the changeset
3945 The tip revision (usually just called the tip) is the changeset
3946 most recently added to the repository (and therefore the most
3946 most recently added to the repository (and therefore the most
3947 recently changed head).
3947 recently changed head).
3948
3948
3949 If you have just made a commit, that commit will be the tip. If
3949 If you have just made a commit, that commit will be the tip. If
3950 you have just pulled changes from another repository, the tip of
3950 you have just pulled changes from another repository, the tip of
3951 that repository becomes the current tip. The "tip" tag is special
3951 that repository becomes the current tip. The "tip" tag is special
3952 and cannot be renamed or assigned to a different changeset.
3952 and cannot be renamed or assigned to a different changeset.
3953
3953
3954 Returns 0 on success.
3954 Returns 0 on success.
3955 """
3955 """
3956 displayer = cmdutil.show_changeset(ui, repo, opts)
3956 displayer = cmdutil.show_changeset(ui, repo, opts)
3957 displayer.show(repo[len(repo) - 1])
3957 displayer.show(repo[len(repo) - 1])
3958 displayer.close()
3958 displayer.close()
3959
3959
3960 def unbundle(ui, repo, fname1, *fnames, **opts):
3960 def unbundle(ui, repo, fname1, *fnames, **opts):
3961 """apply one or more changegroup files
3961 """apply one or more changegroup files
3962
3962
3963 Apply one or more compressed changegroup files generated by the
3963 Apply one or more compressed changegroup files generated by the
3964 bundle command.
3964 bundle command.
3965
3965
3966 Returns 0 on success, 1 if an update has unresolved files.
3966 Returns 0 on success, 1 if an update has unresolved files.
3967 """
3967 """
3968 fnames = (fname1,) + fnames
3968 fnames = (fname1,) + fnames
3969
3969
3970 lock = repo.lock()
3970 lock = repo.lock()
3971 try:
3971 try:
3972 for fname in fnames:
3972 for fname in fnames:
3973 f = url.open(ui, fname)
3973 f = url.open(ui, fname)
3974 gen = changegroup.readbundle(f, fname)
3974 gen = changegroup.readbundle(f, fname)
3975 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname,
3975 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname,
3976 lock=lock)
3976 lock=lock)
3977 finally:
3977 finally:
3978 lock.release()
3978 lock.release()
3979
3979
3980 return postincoming(ui, repo, modheads, opts.get('update'), None)
3980 return postincoming(ui, repo, modheads, opts.get('update'), None)
3981
3981
3982 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
3982 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
3983 """update working directory (or switch revisions)
3983 """update working directory (or switch revisions)
3984
3984
3985 Update the repository's working directory to the specified
3985 Update the repository's working directory to the specified
3986 changeset. If no changeset is specified, update to the tip of the
3986 changeset. If no changeset is specified, update to the tip of the
3987 current named branch.
3987 current named branch.
3988
3988
3989 If the changeset is not a descendant of the working directory's
3989 If the changeset is not a descendant of the working directory's
3990 parent, the update is aborted. With the -c/--check option, the
3990 parent, the update is aborted. With the -c/--check option, the
3991 working directory is checked for uncommitted changes; if none are
3991 working directory is checked for uncommitted changes; if none are
3992 found, the working directory is updated to the specified
3992 found, the working directory is updated to the specified
3993 changeset.
3993 changeset.
3994
3994
3995 The following rules apply when the working directory contains
3995 The following rules apply when the working directory contains
3996 uncommitted changes:
3996 uncommitted changes:
3997
3997
3998 1. If neither -c/--check nor -C/--clean is specified, and if
3998 1. If neither -c/--check nor -C/--clean is specified, and if
3999 the requested changeset is an ancestor or descendant of
3999 the requested changeset is an ancestor or descendant of
4000 the working directory's parent, the uncommitted changes
4000 the working directory's parent, the uncommitted changes
4001 are merged into the requested changeset and the merged
4001 are merged into the requested changeset and the merged
4002 result is left uncommitted. If the requested changeset is
4002 result is left uncommitted. If the requested changeset is
4003 not an ancestor or descendant (that is, it is on another
4003 not an ancestor or descendant (that is, it is on another
4004 branch), the update is aborted and the uncommitted changes
4004 branch), the update is aborted and the uncommitted changes
4005 are preserved.
4005 are preserved.
4006
4006
4007 2. With the -c/--check option, the update is aborted and the
4007 2. With the -c/--check option, the update is aborted and the
4008 uncommitted changes are preserved.
4008 uncommitted changes are preserved.
4009
4009
4010 3. With the -C/--clean option, uncommitted changes are discarded and
4010 3. With the -C/--clean option, uncommitted changes are discarded and
4011 the working directory is updated to the requested changeset.
4011 the working directory is updated to the requested changeset.
4012
4012
4013 Use null as the changeset to remove the working directory (like
4013 Use null as the changeset to remove the working directory (like
4014 :hg:`clone -U`).
4014 :hg:`clone -U`).
4015
4015
4016 If you want to update just one file to an older changeset, use
4016 If you want to update just one file to an older changeset, use
4017 :hg:`revert`.
4017 :hg:`revert`.
4018
4018
4019 See :hg:`help dates` for a list of formats valid for -d/--date.
4019 See :hg:`help dates` for a list of formats valid for -d/--date.
4020
4020
4021 Returns 0 on success, 1 if there are unresolved files.
4021 Returns 0 on success, 1 if there are unresolved files.
4022 """
4022 """
4023 if rev and node:
4023 if rev and node:
4024 raise util.Abort(_("please specify just one revision"))
4024 raise util.Abort(_("please specify just one revision"))
4025
4025
4026 if not rev:
4026 if not rev:
4027 rev = node
4027 rev = node
4028
4028
4029 rev = cmdutil.revsingle(repo, rev, rev).rev()
4029 rev = cmdutil.revsingle(repo, rev, rev).rev()
4030
4030
4031 if check and clean:
4031 if check and clean:
4032 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
4032 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
4033
4033
4034 if check:
4034 if check:
4035 # we could use dirty() but we can ignore merge and branch trivia
4035 # we could use dirty() but we can ignore merge and branch trivia
4036 c = repo[None]
4036 c = repo[None]
4037 if c.modified() or c.added() or c.removed():
4037 if c.modified() or c.added() or c.removed():
4038 raise util.Abort(_("uncommitted local changes"))
4038 raise util.Abort(_("uncommitted local changes"))
4039
4039
4040 if date:
4040 if date:
4041 if rev:
4041 if rev:
4042 raise util.Abort(_("you can't specify a revision and a date"))
4042 raise util.Abort(_("you can't specify a revision and a date"))
4043 rev = cmdutil.finddate(ui, repo, date)
4043 rev = cmdutil.finddate(ui, repo, date)
4044
4044
4045 if clean or check:
4045 if clean or check:
4046 ret = hg.clean(repo, rev)
4046 ret = hg.clean(repo, rev)
4047 else:
4047 else:
4048 ret = hg.update(repo, rev)
4048 ret = hg.update(repo, rev)
4049
4049
4050 if repo.ui.configbool('bookmarks', 'track.current'):
4050 if repo.ui.configbool('bookmarks', 'track.current'):
4051 bookmarks.setcurrent(repo, rev)
4051 bookmarks.setcurrent(repo, rev)
4052
4052
4053 return ret
4053 return ret
4054
4054
4055 def verify(ui, repo):
4055 def verify(ui, repo):
4056 """verify the integrity of the repository
4056 """verify the integrity of the repository
4057
4057
4058 Verify the integrity of the current repository.
4058 Verify the integrity of the current repository.
4059
4059
4060 This will perform an extensive check of the repository's
4060 This will perform an extensive check of the repository's
4061 integrity, validating the hashes and checksums of each entry in
4061 integrity, validating the hashes and checksums of each entry in
4062 the changelog, manifest, and tracked files, as well as the
4062 the changelog, manifest, and tracked files, as well as the
4063 integrity of their crosslinks and indices.
4063 integrity of their crosslinks and indices.
4064
4064
4065 Returns 0 on success, 1 if errors are encountered.
4065 Returns 0 on success, 1 if errors are encountered.
4066 """
4066 """
4067 return hg.verify(repo)
4067 return hg.verify(repo)
4068
4068
4069 def version_(ui):
4069 def version_(ui):
4070 """output version and copyright information"""
4070 """output version and copyright information"""
4071 ui.write(_("Mercurial Distributed SCM (version %s)\n")
4071 ui.write(_("Mercurial Distributed SCM (version %s)\n")
4072 % util.version())
4072 % util.version())
4073 ui.status(_(
4073 ui.status(_(
4074 "(see http://mercurial.selenic.com for more information)\n"
4074 "(see http://mercurial.selenic.com for more information)\n"
4075 "\nCopyright (C) 2005-2010 Matt Mackall and others\n"
4075 "\nCopyright (C) 2005-2010 Matt Mackall and others\n"
4076 "This is free software; see the source for copying conditions. "
4076 "This is free software; see the source for copying conditions. "
4077 "There is NO\nwarranty; "
4077 "There is NO\nwarranty; "
4078 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
4078 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
4079 ))
4079 ))
4080
4080
4081 # Command options and aliases are listed here, alphabetically
4081 # Command options and aliases are listed here, alphabetically
4082
4082
4083 globalopts = [
4083 globalopts = [
4084 ('R', 'repository', '',
4084 ('R', 'repository', '',
4085 _('repository root directory or name of overlay bundle file'),
4085 _('repository root directory or name of overlay bundle file'),
4086 _('REPO')),
4086 _('REPO')),
4087 ('', 'cwd', '',
4087 ('', 'cwd', '',
4088 _('change working directory'), _('DIR')),
4088 _('change working directory'), _('DIR')),
4089 ('y', 'noninteractive', None,
4089 ('y', 'noninteractive', None,
4090 _('do not prompt, assume \'yes\' for any required answers')),
4090 _('do not prompt, assume \'yes\' for any required answers')),
4091 ('q', 'quiet', None, _('suppress output')),
4091 ('q', 'quiet', None, _('suppress output')),
4092 ('v', 'verbose', None, _('enable additional output')),
4092 ('v', 'verbose', None, _('enable additional output')),
4093 ('', 'config', [],
4093 ('', 'config', [],
4094 _('set/override config option (use \'section.name=value\')'),
4094 _('set/override config option (use \'section.name=value\')'),
4095 _('CONFIG')),
4095 _('CONFIG')),
4096 ('', 'debug', None, _('enable debugging output')),
4096 ('', 'debug', None, _('enable debugging output')),
4097 ('', 'debugger', None, _('start debugger')),
4097 ('', 'debugger', None, _('start debugger')),
4098 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
4098 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
4099 _('ENCODE')),
4099 _('ENCODE')),
4100 ('', 'encodingmode', encoding.encodingmode,
4100 ('', 'encodingmode', encoding.encodingmode,
4101 _('set the charset encoding mode'), _('MODE')),
4101 _('set the charset encoding mode'), _('MODE')),
4102 ('', 'traceback', None, _('always print a traceback on exception')),
4102 ('', 'traceback', None, _('always print a traceback on exception')),
4103 ('', 'time', None, _('time how long the command takes')),
4103 ('', 'time', None, _('time how long the command takes')),
4104 ('', 'profile', None, _('print command execution profile')),
4104 ('', 'profile', None, _('print command execution profile')),
4105 ('', 'version', None, _('output version information and exit')),
4105 ('', 'version', None, _('output version information and exit')),
4106 ('h', 'help', None, _('display help and exit')),
4106 ('h', 'help', None, _('display help and exit')),
4107 ]
4107 ]
4108
4108
4109 dryrunopts = [('n', 'dry-run', None,
4109 dryrunopts = [('n', 'dry-run', None,
4110 _('do not perform actions, just print output'))]
4110 _('do not perform actions, just print output'))]
4111
4111
4112 remoteopts = [
4112 remoteopts = [
4113 ('e', 'ssh', '',
4113 ('e', 'ssh', '',
4114 _('specify ssh command to use'), _('CMD')),
4114 _('specify ssh command to use'), _('CMD')),
4115 ('', 'remotecmd', '',
4115 ('', 'remotecmd', '',
4116 _('specify hg command to run on the remote side'), _('CMD')),
4116 _('specify hg command to run on the remote side'), _('CMD')),
4117 ('', 'insecure', None,
4117 ('', 'insecure', None,
4118 _('do not verify server certificate (ignoring web.cacerts config)')),
4118 _('do not verify server certificate (ignoring web.cacerts config)')),
4119 ]
4119 ]
4120
4120
4121 walkopts = [
4121 walkopts = [
4122 ('I', 'include', [],
4122 ('I', 'include', [],
4123 _('include names matching the given patterns'), _('PATTERN')),
4123 _('include names matching the given patterns'), _('PATTERN')),
4124 ('X', 'exclude', [],
4124 ('X', 'exclude', [],
4125 _('exclude names matching the given patterns'), _('PATTERN')),
4125 _('exclude names matching the given patterns'), _('PATTERN')),
4126 ]
4126 ]
4127
4127
4128 commitopts = [
4128 commitopts = [
4129 ('m', 'message', '',
4129 ('m', 'message', '',
4130 _('use text as commit message'), _('TEXT')),
4130 _('use text as commit message'), _('TEXT')),
4131 ('l', 'logfile', '',
4131 ('l', 'logfile', '',
4132 _('read commit message from file'), _('FILE')),
4132 _('read commit message from file'), _('FILE')),
4133 ]
4133 ]
4134
4134
4135 commitopts2 = [
4135 commitopts2 = [
4136 ('d', 'date', '',
4136 ('d', 'date', '',
4137 _('record datecode as commit date'), _('DATE')),
4137 _('record datecode as commit date'), _('DATE')),
4138 ('u', 'user', '',
4138 ('u', 'user', '',
4139 _('record the specified user as committer'), _('USER')),
4139 _('record the specified user as committer'), _('USER')),
4140 ]
4140 ]
4141
4141
4142 templateopts = [
4142 templateopts = [
4143 ('', 'style', '',
4143 ('', 'style', '',
4144 _('display using template map file'), _('STYLE')),
4144 _('display using template map file'), _('STYLE')),
4145 ('', 'template', '',
4145 ('', 'template', '',
4146 _('display with template'), _('TEMPLATE')),
4146 _('display with template'), _('TEMPLATE')),
4147 ]
4147 ]
4148
4148
4149 logopts = [
4149 logopts = [
4150 ('p', 'patch', None, _('show patch')),
4150 ('p', 'patch', None, _('show patch')),
4151 ('g', 'git', None, _('use git extended diff format')),
4151 ('g', 'git', None, _('use git extended diff format')),
4152 ('l', 'limit', '',
4152 ('l', 'limit', '',
4153 _('limit number of changes displayed'), _('NUM')),
4153 _('limit number of changes displayed'), _('NUM')),
4154 ('M', 'no-merges', None, _('do not show merges')),
4154 ('M', 'no-merges', None, _('do not show merges')),
4155 ('', 'stat', None, _('output diffstat-style summary of changes')),
4155 ('', 'stat', None, _('output diffstat-style summary of changes')),
4156 ] + templateopts
4156 ] + templateopts
4157
4157
4158 diffopts = [
4158 diffopts = [
4159 ('a', 'text', None, _('treat all files as text')),
4159 ('a', 'text', None, _('treat all files as text')),
4160 ('g', 'git', None, _('use git extended diff format')),
4160 ('g', 'git', None, _('use git extended diff format')),
4161 ('', 'nodates', None, _('omit dates from diff headers'))
4161 ('', 'nodates', None, _('omit dates from diff headers'))
4162 ]
4162 ]
4163
4163
4164 diffopts2 = [
4164 diffopts2 = [
4165 ('p', 'show-function', None, _('show which function each change is in')),
4165 ('p', 'show-function', None, _('show which function each change is in')),
4166 ('', 'reverse', None, _('produce a diff that undoes the changes')),
4166 ('', 'reverse', None, _('produce a diff that undoes the changes')),
4167 ('w', 'ignore-all-space', None,
4167 ('w', 'ignore-all-space', None,
4168 _('ignore white space when comparing lines')),
4168 _('ignore white space when comparing lines')),
4169 ('b', 'ignore-space-change', None,
4169 ('b', 'ignore-space-change', None,
4170 _('ignore changes in the amount of white space')),
4170 _('ignore changes in the amount of white space')),
4171 ('B', 'ignore-blank-lines', None,
4171 ('B', 'ignore-blank-lines', None,
4172 _('ignore changes whose lines are all blank')),
4172 _('ignore changes whose lines are all blank')),
4173 ('U', 'unified', '',
4173 ('U', 'unified', '',
4174 _('number of lines of context to show'), _('NUM')),
4174 _('number of lines of context to show'), _('NUM')),
4175 ('', 'stat', None, _('output diffstat-style summary of changes')),
4175 ('', 'stat', None, _('output diffstat-style summary of changes')),
4176 ]
4176 ]
4177
4177
4178 similarityopts = [
4178 similarityopts = [
4179 ('s', 'similarity', '',
4179 ('s', 'similarity', '',
4180 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
4180 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
4181 ]
4181 ]
4182
4182
4183 subrepoopts = [
4183 subrepoopts = [
4184 ('S', 'subrepos', None,
4184 ('S', 'subrepos', None,
4185 _('recurse into subrepositories'))
4185 _('recurse into subrepositories'))
4186 ]
4186 ]
4187
4187
4188 table = {
4188 table = {
4189 "^add": (add, walkopts + subrepoopts + dryrunopts,
4189 "^add": (add, walkopts + subrepoopts + dryrunopts,
4190 _('[OPTION]... [FILE]...')),
4190 _('[OPTION]... [FILE]...')),
4191 "addremove":
4191 "addremove":
4192 (addremove, similarityopts + walkopts + dryrunopts,
4192 (addremove, similarityopts + walkopts + dryrunopts,
4193 _('[OPTION]... [FILE]...')),
4193 _('[OPTION]... [FILE]...')),
4194 "^annotate|blame":
4194 "^annotate|blame":
4195 (annotate,
4195 (annotate,
4196 [('r', 'rev', '',
4196 [('r', 'rev', '',
4197 _('annotate the specified revision'), _('REV')),
4197 _('annotate the specified revision'), _('REV')),
4198 ('', 'follow', None,
4198 ('', 'follow', None,
4199 _('follow copies/renames and list the filename (DEPRECATED)')),
4199 _('follow copies/renames and list the filename (DEPRECATED)')),
4200 ('', 'no-follow', None, _("don't follow copies and renames")),
4200 ('', 'no-follow', None, _("don't follow copies and renames")),
4201 ('a', 'text', None, _('treat all files as text')),
4201 ('a', 'text', None, _('treat all files as text')),
4202 ('u', 'user', None, _('list the author (long with -v)')),
4202 ('u', 'user', None, _('list the author (long with -v)')),
4203 ('f', 'file', None, _('list the filename')),
4203 ('f', 'file', None, _('list the filename')),
4204 ('d', 'date', None, _('list the date (short with -q)')),
4204 ('d', 'date', None, _('list the date (short with -q)')),
4205 ('n', 'number', None, _('list the revision number (default)')),
4205 ('n', 'number', None, _('list the revision number (default)')),
4206 ('c', 'changeset', None, _('list the changeset')),
4206 ('c', 'changeset', None, _('list the changeset')),
4207 ('l', 'line-number', None,
4207 ('l', 'line-number', None,
4208 _('show line number at the first appearance'))
4208 _('show line number at the first appearance'))
4209 ] + walkopts,
4209 ] + walkopts,
4210 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
4210 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
4211 "archive":
4211 "archive":
4212 (archive,
4212 (archive,
4213 [('', 'no-decode', None, _('do not pass files through decoders')),
4213 [('', 'no-decode', None, _('do not pass files through decoders')),
4214 ('p', 'prefix', '',
4214 ('p', 'prefix', '',
4215 _('directory prefix for files in archive'), _('PREFIX')),
4215 _('directory prefix for files in archive'), _('PREFIX')),
4216 ('r', 'rev', '',
4216 ('r', 'rev', '',
4217 _('revision to distribute'), _('REV')),
4217 _('revision to distribute'), _('REV')),
4218 ('t', 'type', '',
4218 ('t', 'type', '',
4219 _('type of distribution to create'), _('TYPE')),
4219 _('type of distribution to create'), _('TYPE')),
4220 ] + subrepoopts + walkopts,
4220 ] + subrepoopts + walkopts,
4221 _('[OPTION]... DEST')),
4221 _('[OPTION]... DEST')),
4222 "backout":
4222 "backout":
4223 (backout,
4223 (backout,
4224 [('', 'merge', None,
4224 [('', 'merge', None,
4225 _('merge with old dirstate parent after backout')),
4225 _('merge with old dirstate parent after backout')),
4226 ('', 'parent', '',
4226 ('', 'parent', '',
4227 _('parent to choose when backing out merge'), _('REV')),
4227 _('parent to choose when backing out merge'), _('REV')),
4228 ('t', 'tool', '',
4228 ('t', 'tool', '',
4229 _('specify merge tool')),
4229 _('specify merge tool')),
4230 ('r', 'rev', '',
4230 ('r', 'rev', '',
4231 _('revision to backout'), _('REV')),
4231 _('revision to backout'), _('REV')),
4232 ] + walkopts + commitopts + commitopts2,
4232 ] + walkopts + commitopts + commitopts2,
4233 _('[OPTION]... [-r] REV')),
4233 _('[OPTION]... [-r] REV')),
4234 "bisect":
4234 "bisect":
4235 (bisect,
4235 (bisect,
4236 [('r', 'reset', False, _('reset bisect state')),
4236 [('r', 'reset', False, _('reset bisect state')),
4237 ('g', 'good', False, _('mark changeset good')),
4237 ('g', 'good', False, _('mark changeset good')),
4238 ('b', 'bad', False, _('mark changeset bad')),
4238 ('b', 'bad', False, _('mark changeset bad')),
4239 ('s', 'skip', False, _('skip testing changeset')),
4239 ('s', 'skip', False, _('skip testing changeset')),
4240 ('c', 'command', '',
4240 ('c', 'command', '',
4241 _('use command to check changeset state'), _('CMD')),
4241 _('use command to check changeset state'), _('CMD')),
4242 ('U', 'noupdate', False, _('do not update to target'))],
4242 ('U', 'noupdate', False, _('do not update to target'))],
4243 _("[-gbsr] [-U] [-c CMD] [REV]")),
4243 _("[-gbsr] [-U] [-c CMD] [REV]")),
4244 "bookmarks":
4244 "bookmarks":
4245 (bookmark,
4245 (bookmark,
4246 [('f', 'force', False, _('force')),
4246 [('f', 'force', False, _('force')),
4247 ('r', 'rev', '', _('revision'), _('REV')),
4247 ('r', 'rev', '', _('revision'), _('REV')),
4248 ('d', 'delete', False, _('delete a given bookmark')),
4248 ('d', 'delete', False, _('delete a given bookmark')),
4249 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
4249 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
4250 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
4250 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
4251 "branch":
4251 "branch":
4252 (branch,
4252 (branch,
4253 [('f', 'force', None,
4253 [('f', 'force', None,
4254 _('set branch name even if it shadows an existing branch')),
4254 _('set branch name even if it shadows an existing branch')),
4255 ('C', 'clean', None, _('reset branch name to parent branch name'))],
4255 ('C', 'clean', None, _('reset branch name to parent branch name'))],
4256 _('[-fC] [NAME]')),
4256 _('[-fC] [NAME]')),
4257 "branches":
4257 "branches":
4258 (branches,
4258 (branches,
4259 [('a', 'active', False,
4259 [('a', 'active', False,
4260 _('show only branches that have unmerged heads')),
4260 _('show only branches that have unmerged heads')),
4261 ('c', 'closed', False,
4261 ('c', 'closed', False,
4262 _('show normal and closed branches'))],
4262 _('show normal and closed branches'))],
4263 _('[-ac]')),
4263 _('[-ac]')),
4264 "bundle":
4264 "bundle":
4265 (bundle,
4265 (bundle,
4266 [('f', 'force', None,
4266 [('f', 'force', None,
4267 _('run even when the destination is unrelated')),
4267 _('run even when the destination is unrelated')),
4268 ('r', 'rev', [],
4268 ('r', 'rev', [],
4269 _('a changeset intended to be added to the destination'),
4269 _('a changeset intended to be added to the destination'),
4270 _('REV')),
4270 _('REV')),
4271 ('b', 'branch', [],
4271 ('b', 'branch', [],
4272 _('a specific branch you would like to bundle'),
4272 _('a specific branch you would like to bundle'),
4273 _('BRANCH')),
4273 _('BRANCH')),
4274 ('', 'base', [],
4274 ('', 'base', [],
4275 _('a base changeset assumed to be available at the destination'),
4275 _('a base changeset assumed to be available at the destination'),
4276 _('REV')),
4276 _('REV')),
4277 ('a', 'all', None, _('bundle all changesets in the repository')),
4277 ('a', 'all', None, _('bundle all changesets in the repository')),
4278 ('t', 'type', 'bzip2',
4278 ('t', 'type', 'bzip2',
4279 _('bundle compression type to use'), _('TYPE')),
4279 _('bundle compression type to use'), _('TYPE')),
4280 ] + remoteopts,
4280 ] + remoteopts,
4281 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
4281 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
4282 "cat":
4282 "cat":
4283 (cat,
4283 (cat,
4284 [('o', 'output', '',
4284 [('o', 'output', '',
4285 _('print output to file with formatted name'), _('FORMAT')),
4285 _('print output to file with formatted name'), _('FORMAT')),
4286 ('r', 'rev', '',
4286 ('r', 'rev', '',
4287 _('print the given revision'), _('REV')),
4287 _('print the given revision'), _('REV')),
4288 ('', 'decode', None, _('apply any matching decode filter')),
4288 ('', 'decode', None, _('apply any matching decode filter')),
4289 ] + walkopts,
4289 ] + walkopts,
4290 _('[OPTION]... FILE...')),
4290 _('[OPTION]... FILE...')),
4291 "^clone":
4291 "^clone":
4292 (clone,
4292 (clone,
4293 [('U', 'noupdate', None,
4293 [('U', 'noupdate', None,
4294 _('the clone will include an empty working copy (only a repository)')),
4294 _('the clone will include an empty working copy (only a repository)')),
4295 ('u', 'updaterev', '',
4295 ('u', 'updaterev', '',
4296 _('revision, tag or branch to check out'), _('REV')),
4296 _('revision, tag or branch to check out'), _('REV')),
4297 ('r', 'rev', [],
4297 ('r', 'rev', [],
4298 _('include the specified changeset'), _('REV')),
4298 _('include the specified changeset'), _('REV')),
4299 ('b', 'branch', [],
4299 ('b', 'branch', [],
4300 _('clone only the specified branch'), _('BRANCH')),
4300 _('clone only the specified branch'), _('BRANCH')),
4301 ('', 'pull', None, _('use pull protocol to copy metadata')),
4301 ('', 'pull', None, _('use pull protocol to copy metadata')),
4302 ('', 'uncompressed', None,
4302 ('', 'uncompressed', None,
4303 _('use uncompressed transfer (fast over LAN)')),
4303 _('use uncompressed transfer (fast over LAN)')),
4304 ] + remoteopts,
4304 ] + remoteopts,
4305 _('[OPTION]... SOURCE [DEST]')),
4305 _('[OPTION]... SOURCE [DEST]')),
4306 "^commit|ci":
4306 "^commit|ci":
4307 (commit,
4307 (commit,
4308 [('A', 'addremove', None,
4308 [('A', 'addremove', None,
4309 _('mark new/missing files as added/removed before committing')),
4309 _('mark new/missing files as added/removed before committing')),
4310 ('', 'close-branch', None,
4310 ('', 'close-branch', None,
4311 _('mark a branch as closed, hiding it from the branch list')),
4311 _('mark a branch as closed, hiding it from the branch list')),
4312 ] + walkopts + commitopts + commitopts2,
4312 ] + walkopts + commitopts + commitopts2,
4313 _('[OPTION]... [FILE]...')),
4313 _('[OPTION]... [FILE]...')),
4314 "copy|cp":
4314 "copy|cp":
4315 (copy,
4315 (copy,
4316 [('A', 'after', None, _('record a copy that has already occurred')),
4316 [('A', 'after', None, _('record a copy that has already occurred')),
4317 ('f', 'force', None,
4317 ('f', 'force', None,
4318 _('forcibly copy over an existing managed file')),
4318 _('forcibly copy over an existing managed file')),
4319 ] + walkopts + dryrunopts,
4319 ] + walkopts + dryrunopts,
4320 _('[OPTION]... [SOURCE]... DEST')),
4320 _('[OPTION]... [SOURCE]... DEST')),
4321 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
4321 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
4322 "debugbuilddag":
4322 "debugbuilddag":
4323 (debugbuilddag,
4323 (debugbuilddag,
4324 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
4324 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
4325 ('a', 'appended-file', None, _('add single file all revs append to')),
4325 ('a', 'appended-file', None, _('add single file all revs append to')),
4326 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
4326 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
4327 ('n', 'new-file', None, _('add new file at each rev')),
4327 ('n', 'new-file', None, _('add new file at each rev')),
4328 ],
4328 ],
4329 _('[OPTION]... TEXT')),
4329 _('[OPTION]... TEXT')),
4330 "debugcheckstate": (debugcheckstate, [], ''),
4330 "debugcheckstate": (debugcheckstate, [], ''),
4331 "debugcommands": (debugcommands, [], _('[COMMAND]')),
4331 "debugcommands": (debugcommands, [], _('[COMMAND]')),
4332 "debugcomplete":
4332 "debugcomplete":
4333 (debugcomplete,
4333 (debugcomplete,
4334 [('o', 'options', None, _('show the command options'))],
4334 [('o', 'options', None, _('show the command options'))],
4335 _('[-o] CMD')),
4335 _('[-o] CMD')),
4336 "debugdag":
4336 "debugdag":
4337 (debugdag,
4337 (debugdag,
4338 [('t', 'tags', None, _('use tags as labels')),
4338 [('t', 'tags', None, _('use tags as labels')),
4339 ('b', 'branches', None, _('annotate with branch names')),
4339 ('b', 'branches', None, _('annotate with branch names')),
4340 ('', 'dots', None, _('use dots for runs')),
4340 ('', 'dots', None, _('use dots for runs')),
4341 ('s', 'spaces', None, _('separate elements by spaces')),
4341 ('s', 'spaces', None, _('separate elements by spaces')),
4342 ],
4342 ],
4343 _('[OPTION]... [FILE [REV]...]')),
4343 _('[OPTION]... [FILE [REV]...]')),
4344 "debugdate":
4344 "debugdate":
4345 (debugdate,
4345 (debugdate,
4346 [('e', 'extended', None, _('try extended date formats'))],
4346 [('e', 'extended', None, _('try extended date formats'))],
4347 _('[-e] DATE [RANGE]')),
4347 _('[-e] DATE [RANGE]')),
4348 "debugdata": (debugdata, [], _('FILE REV')),
4348 "debugdata": (debugdata, [], _('FILE REV')),
4349 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
4349 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
4350 "debugindex": (debugindex,
4350 "debugindex": (debugindex,
4351 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
4351 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
4352 _('FILE')),
4352 _('FILE')),
4353 "debugindexdot": (debugindexdot, [], _('FILE')),
4353 "debugindexdot": (debugindexdot, [], _('FILE')),
4354 "debuginstall": (debuginstall, [], ''),
4354 "debuginstall": (debuginstall, [], ''),
4355 "debugpushkey": (debugpushkey, [], _('REPO NAMESPACE [KEY OLD NEW]')),
4355 "debugpushkey": (debugpushkey, [], _('REPO NAMESPACE [KEY OLD NEW]')),
4356 "debugrebuildstate":
4356 "debugrebuildstate":
4357 (debugrebuildstate,
4357 (debugrebuildstate,
4358 [('r', 'rev', '',
4358 [('r', 'rev', '',
4359 _('revision to rebuild to'), _('REV'))],
4359 _('revision to rebuild to'), _('REV'))],
4360 _('[-r REV] [REV]')),
4360 _('[-r REV] [REV]')),
4361 "debugrename":
4361 "debugrename":
4362 (debugrename,
4362 (debugrename,
4363 [('r', 'rev', '',
4363 [('r', 'rev', '',
4364 _('revision to debug'), _('REV'))],
4364 _('revision to debug'), _('REV'))],
4365 _('[-r REV] FILE')),
4365 _('[-r REV] FILE')),
4366 "debugrevspec":
4366 "debugrevspec":
4367 (debugrevspec, [], ('REVSPEC')),
4367 (debugrevspec, [], ('REVSPEC')),
4368 "debugsetparents":
4368 "debugsetparents":
4369 (debugsetparents, [], _('REV1 [REV2]')),
4369 (debugsetparents, [], _('REV1 [REV2]')),
4370 "debugstate":
4370 "debugstate":
4371 (debugstate,
4371 (debugstate,
4372 [('', 'nodates', None, _('do not display the saved mtime'))],
4372 [('', 'nodates', None, _('do not display the saved mtime'))],
4373 _('[OPTION]...')),
4373 _('[OPTION]...')),
4374 "debugsub":
4374 "debugsub":
4375 (debugsub,
4375 (debugsub,
4376 [('r', 'rev', '',
4376 [('r', 'rev', '',
4377 _('revision to check'), _('REV'))],
4377 _('revision to check'), _('REV'))],
4378 _('[-r REV] [REV]')),
4378 _('[-r REV] [REV]')),
4379 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
4379 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
4380 "^diff":
4380 "^diff":
4381 (diff,
4381 (diff,
4382 [('r', 'rev', [],
4382 [('r', 'rev', [],
4383 _('revision'), _('REV')),
4383 _('revision'), _('REV')),
4384 ('c', 'change', '',
4384 ('c', 'change', '',
4385 _('change made by revision'), _('REV'))
4385 _('change made by revision'), _('REV'))
4386 ] + diffopts + diffopts2 + walkopts + subrepoopts,
4386 ] + diffopts + diffopts2 + walkopts + subrepoopts,
4387 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...')),
4387 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...')),
4388 "^export":
4388 "^export":
4389 (export,
4389 (export,
4390 [('o', 'output', '',
4390 [('o', 'output', '',
4391 _('print output to file with formatted name'), _('FORMAT')),
4391 _('print output to file with formatted name'), _('FORMAT')),
4392 ('', 'switch-parent', None, _('diff against the second parent')),
4392 ('', 'switch-parent', None, _('diff against the second parent')),
4393 ('r', 'rev', [],
4393 ('r', 'rev', [],
4394 _('revisions to export'), _('REV')),
4394 _('revisions to export'), _('REV')),
4395 ] + diffopts,
4395 ] + diffopts,
4396 _('[OPTION]... [-o OUTFILESPEC] REV...')),
4396 _('[OPTION]... [-o OUTFILESPEC] REV...')),
4397 "^forget":
4397 "^forget":
4398 (forget,
4398 (forget,
4399 [] + walkopts,
4399 [] + walkopts,
4400 _('[OPTION]... FILE...')),
4400 _('[OPTION]... FILE...')),
4401 "grep":
4401 "grep":
4402 (grep,
4402 (grep,
4403 [('0', 'print0', None, _('end fields with NUL')),
4403 [('0', 'print0', None, _('end fields with NUL')),
4404 ('', 'all', None, _('print all revisions that match')),
4404 ('', 'all', None, _('print all revisions that match')),
4405 ('f', 'follow', None,
4405 ('f', 'follow', None,
4406 _('follow changeset history,'
4406 _('follow changeset history,'
4407 ' or file history across copies and renames')),
4407 ' or file history across copies and renames')),
4408 ('i', 'ignore-case', None, _('ignore case when matching')),
4408 ('i', 'ignore-case', None, _('ignore case when matching')),
4409 ('l', 'files-with-matches', None,
4409 ('l', 'files-with-matches', None,
4410 _('print only filenames and revisions that match')),
4410 _('print only filenames and revisions that match')),
4411 ('n', 'line-number', None, _('print matching line numbers')),
4411 ('n', 'line-number', None, _('print matching line numbers')),
4412 ('r', 'rev', [],
4412 ('r', 'rev', [],
4413 _('only search files changed within revision range'), _('REV')),
4413 _('only search files changed within revision range'), _('REV')),
4414 ('u', 'user', None, _('list the author (long with -v)')),
4414 ('u', 'user', None, _('list the author (long with -v)')),
4415 ('d', 'date', None, _('list the date (short with -q)')),
4415 ('d', 'date', None, _('list the date (short with -q)')),
4416 ] + walkopts,
4416 ] + walkopts,
4417 _('[OPTION]... PATTERN [FILE]...')),
4417 _('[OPTION]... PATTERN [FILE]...')),
4418 "heads":
4418 "heads":
4419 (heads,
4419 (heads,
4420 [('r', 'rev', '',
4420 [('r', 'rev', '',
4421 _('show only heads which are descendants of STARTREV'),
4421 _('show only heads which are descendants of STARTREV'),
4422 _('STARTREV')),
4422 _('STARTREV')),
4423 ('t', 'topo', False, _('show topological heads only')),
4423 ('t', 'topo', False, _('show topological heads only')),
4424 ('a', 'active', False,
4424 ('a', 'active', False,
4425 _('show active branchheads only (DEPRECATED)')),
4425 _('show active branchheads only (DEPRECATED)')),
4426 ('c', 'closed', False,
4426 ('c', 'closed', False,
4427 _('show normal and closed branch heads')),
4427 _('show normal and closed branch heads')),
4428 ] + templateopts,
4428 ] + templateopts,
4429 _('[-ac] [-r STARTREV] [REV]...')),
4429 _('[-ac] [-r STARTREV] [REV]...')),
4430 "help": (help_, [], _('[TOPIC]')),
4430 "help": (help_, [], _('[TOPIC]')),
4431 "identify|id":
4431 "identify|id":
4432 (identify,
4432 (identify,
4433 [('r', 'rev', '',
4433 [('r', 'rev', '',
4434 _('identify the specified revision'), _('REV')),
4434 _('identify the specified revision'), _('REV')),
4435 ('n', 'num', None, _('show local revision number')),
4435 ('n', 'num', None, _('show local revision number')),
4436 ('i', 'id', None, _('show global revision id')),
4436 ('i', 'id', None, _('show global revision id')),
4437 ('b', 'branch', None, _('show branch')),
4437 ('b', 'branch', None, _('show branch')),
4438 ('t', 'tags', None, _('show tags'))],
4438 ('t', 'tags', None, _('show tags'))],
4439 _('[-nibt] [-r REV] [SOURCE]')),
4439 _('[-nibt] [-r REV] [SOURCE]')),
4440 "import|patch":
4440 "import|patch":
4441 (import_,
4441 (import_,
4442 [('p', 'strip', 1,
4442 [('p', 'strip', 1,
4443 _('directory strip option for patch. This has the same '
4443 _('directory strip option for patch. This has the same '
4444 'meaning as the corresponding patch option'),
4444 'meaning as the corresponding patch option'),
4445 _('NUM')),
4445 _('NUM')),
4446 ('b', 'base', '',
4446 ('b', 'base', '',
4447 _('base path'), _('PATH')),
4447 _('base path'), _('PATH')),
4448 ('f', 'force', None,
4448 ('f', 'force', None,
4449 _('skip check for outstanding uncommitted changes')),
4449 _('skip check for outstanding uncommitted changes')),
4450 ('', 'no-commit', None,
4450 ('', 'no-commit', None,
4451 _("don't commit, just update the working directory")),
4451 _("don't commit, just update the working directory")),
4452 ('', 'exact', None,
4452 ('', 'exact', None,
4453 _('apply patch to the nodes from which it was generated')),
4453 _('apply patch to the nodes from which it was generated')),
4454 ('', 'import-branch', None,
4454 ('', 'import-branch', None,
4455 _('use any branch information in patch (implied by --exact)'))] +
4455 _('use any branch information in patch (implied by --exact)'))] +
4456 commitopts + commitopts2 + similarityopts,
4456 commitopts + commitopts2 + similarityopts,
4457 _('[OPTION]... PATCH...')),
4457 _('[OPTION]... PATCH...')),
4458 "incoming|in":
4458 "incoming|in":
4459 (incoming,
4459 (incoming,
4460 [('f', 'force', None,
4460 [('f', 'force', None,
4461 _('run even if remote repository is unrelated')),
4461 _('run even if remote repository is unrelated')),
4462 ('n', 'newest-first', None, _('show newest record first')),
4462 ('n', 'newest-first', None, _('show newest record first')),
4463 ('', 'bundle', '',
4463 ('', 'bundle', '',
4464 _('file to store the bundles into'), _('FILE')),
4464 _('file to store the bundles into'), _('FILE')),
4465 ('r', 'rev', [],
4465 ('r', 'rev', [],
4466 _('a remote changeset intended to be added'), _('REV')),
4466 _('a remote changeset intended to be added'), _('REV')),
4467 ('B', 'bookmarks', False, _("compare bookmarks")),
4467 ('B', 'bookmarks', False, _("compare bookmarks")),
4468 ('b', 'branch', [],
4468 ('b', 'branch', [],
4469 _('a specific branch you would like to pull'), _('BRANCH')),
4469 _('a specific branch you would like to pull'), _('BRANCH')),
4470 ] + logopts + remoteopts + subrepoopts,
4470 ] + logopts + remoteopts + subrepoopts,
4471 _('[-p] [-n] [-M] [-f] [-r REV]...'
4471 _('[-p] [-n] [-M] [-f] [-r REV]...'
4472 ' [--bundle FILENAME] [SOURCE]')),
4472 ' [--bundle FILENAME] [SOURCE]')),
4473 "^init":
4473 "^init":
4474 (init,
4474 (init,
4475 remoteopts,
4475 remoteopts,
4476 _('[-e CMD] [--remotecmd CMD] [DEST]')),
4476 _('[-e CMD] [--remotecmd CMD] [DEST]')),
4477 "locate":
4477 "locate":
4478 (locate,
4478 (locate,
4479 [('r', 'rev', '',
4479 [('r', 'rev', '',
4480 _('search the repository as it is in REV'), _('REV')),
4480 _('search the repository as it is in REV'), _('REV')),
4481 ('0', 'print0', None,
4481 ('0', 'print0', None,
4482 _('end filenames with NUL, for use with xargs')),
4482 _('end filenames with NUL, for use with xargs')),
4483 ('f', 'fullpath', None,
4483 ('f', 'fullpath', None,
4484 _('print complete paths from the filesystem root')),
4484 _('print complete paths from the filesystem root')),
4485 ] + walkopts,
4485 ] + walkopts,
4486 _('[OPTION]... [PATTERN]...')),
4486 _('[OPTION]... [PATTERN]...')),
4487 "^log|history":
4487 "^log|history":
4488 (log,
4488 (log,
4489 [('f', 'follow', None,
4489 [('f', 'follow', None,
4490 _('follow changeset history,'
4490 _('follow changeset history,'
4491 ' or file history across copies and renames')),
4491 ' or file history across copies and renames')),
4492 ('', 'follow-first', None,
4492 ('', 'follow-first', None,
4493 _('only follow the first parent of merge changesets')),
4493 _('only follow the first parent of merge changesets')),
4494 ('d', 'date', '',
4494 ('d', 'date', '',
4495 _('show revisions matching date spec'), _('DATE')),
4495 _('show revisions matching date spec'), _('DATE')),
4496 ('C', 'copies', None, _('show copied files')),
4496 ('C', 'copies', None, _('show copied files')),
4497 ('k', 'keyword', [],
4497 ('k', 'keyword', [],
4498 _('do case-insensitive search for a given text'), _('TEXT')),
4498 _('do case-insensitive search for a given text'), _('TEXT')),
4499 ('r', 'rev', [],
4499 ('r', 'rev', [],
4500 _('show the specified revision or range'), _('REV')),
4500 _('show the specified revision or range'), _('REV')),
4501 ('', 'removed', None, _('include revisions where files were removed')),
4501 ('', 'removed', None, _('include revisions where files were removed')),
4502 ('m', 'only-merges', None, _('show only merges')),
4502 ('m', 'only-merges', None, _('show only merges')),
4503 ('u', 'user', [],
4503 ('u', 'user', [],
4504 _('revisions committed by user'), _('USER')),
4504 _('revisions committed by user'), _('USER')),
4505 ('', 'only-branch', [],
4505 ('', 'only-branch', [],
4506 _('show only changesets within the given named branch (DEPRECATED)'),
4506 _('show only changesets within the given named branch (DEPRECATED)'),
4507 _('BRANCH')),
4507 _('BRANCH')),
4508 ('b', 'branch', [],
4508 ('b', 'branch', [],
4509 _('show changesets within the given named branch'), _('BRANCH')),
4509 _('show changesets within the given named branch'), _('BRANCH')),
4510 ('P', 'prune', [],
4510 ('P', 'prune', [],
4511 _('do not display revision or any of its ancestors'), _('REV')),
4511 _('do not display revision or any of its ancestors'), _('REV')),
4512 ] + logopts + walkopts,
4512 ] + logopts + walkopts,
4513 _('[OPTION]... [FILE]')),
4513 _('[OPTION]... [FILE]')),
4514 "manifest":
4514 "manifest":
4515 (manifest,
4515 (manifest,
4516 [('r', 'rev', '',
4516 [('r', 'rev', '',
4517 _('revision to display'), _('REV'))],
4517 _('revision to display'), _('REV'))],
4518 _('[-r REV]')),
4518 _('[-r REV]')),
4519 "^merge":
4519 "^merge":
4520 (merge,
4520 (merge,
4521 [('f', 'force', None, _('force a merge with outstanding changes')),
4521 [('f', 'force', None, _('force a merge with outstanding changes')),
4522 ('t', 'tool', '', _('specify merge tool')),
4522 ('t', 'tool', '', _('specify merge tool')),
4523 ('r', 'rev', '',
4523 ('r', 'rev', '',
4524 _('revision to merge'), _('REV')),
4524 _('revision to merge'), _('REV')),
4525 ('P', 'preview', None,
4525 ('P', 'preview', None,
4526 _('review revisions to merge (no merge is performed)'))],
4526 _('review revisions to merge (no merge is performed)'))],
4527 _('[-P] [-f] [[-r] REV]')),
4527 _('[-P] [-f] [[-r] REV]')),
4528 "outgoing|out":
4528 "outgoing|out":
4529 (outgoing,
4529 (outgoing,
4530 [('f', 'force', None,
4530 [('f', 'force', None,
4531 _('run even when the destination is unrelated')),
4531 _('run even when the destination is unrelated')),
4532 ('r', 'rev', [],
4532 ('r', 'rev', [],
4533 _('a changeset intended to be included in the destination'),
4533 _('a changeset intended to be included in the destination'),
4534 _('REV')),
4534 _('REV')),
4535 ('n', 'newest-first', None, _('show newest record first')),
4535 ('n', 'newest-first', None, _('show newest record first')),
4536 ('B', 'bookmarks', False, _("compare bookmarks")),
4536 ('B', 'bookmarks', False, _("compare bookmarks")),
4537 ('b', 'branch', [],
4537 ('b', 'branch', [],
4538 _('a specific branch you would like to push'), _('BRANCH')),
4538 _('a specific branch you would like to push'), _('BRANCH')),
4539 ] + logopts + remoteopts + subrepoopts,
4539 ] + logopts + remoteopts + subrepoopts,
4540 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
4540 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
4541 "parents":
4541 "parents":
4542 (parents,
4542 (parents,
4543 [('r', 'rev', '',
4543 [('r', 'rev', '',
4544 _('show parents of the specified revision'), _('REV')),
4544 _('show parents of the specified revision'), _('REV')),
4545 ] + templateopts,
4545 ] + templateopts,
4546 _('[-r REV] [FILE]')),
4546 _('[-r REV] [FILE]')),
4547 "paths": (paths, [], _('[NAME]')),
4547 "paths": (paths, [], _('[NAME]')),
4548 "^pull":
4548 "^pull":
4549 (pull,
4549 (pull,
4550 [('u', 'update', None,
4550 [('u', 'update', None,
4551 _('update to new branch head if changesets were pulled')),
4551 _('update to new branch head if changesets were pulled')),
4552 ('f', 'force', None,
4552 ('f', 'force', None,
4553 _('run even when remote repository is unrelated')),
4553 _('run even when remote repository is unrelated')),
4554 ('r', 'rev', [],
4554 ('r', 'rev', [],
4555 _('a remote changeset intended to be added'), _('REV')),
4555 _('a remote changeset intended to be added'), _('REV')),
4556 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4556 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4557 ('b', 'branch', [],
4557 ('b', 'branch', [],
4558 _('a specific branch you would like to pull'), _('BRANCH')),
4558 _('a specific branch you would like to pull'), _('BRANCH')),
4559 ] + remoteopts,
4559 ] + remoteopts,
4560 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
4560 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
4561 "^push":
4561 "^push":
4562 (push,
4562 (push,
4563 [('f', 'force', None, _('force push')),
4563 [('f', 'force', None, _('force push')),
4564 ('r', 'rev', [],
4564 ('r', 'rev', [],
4565 _('a changeset intended to be included in the destination'),
4565 _('a changeset intended to be included in the destination'),
4566 _('REV')),
4566 _('REV')),
4567 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4567 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4568 ('b', 'branch', [],
4568 ('b', 'branch', [],
4569 _('a specific branch you would like to push'), _('BRANCH')),
4569 _('a specific branch you would like to push'), _('BRANCH')),
4570 ('', 'new-branch', False, _('allow pushing a new branch')),
4570 ('', 'new-branch', False, _('allow pushing a new branch')),
4571 ] + remoteopts,
4571 ] + remoteopts,
4572 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
4572 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
4573 "recover": (recover, []),
4573 "recover": (recover, []),
4574 "^remove|rm":
4574 "^remove|rm":
4575 (remove,
4575 (remove,
4576 [('A', 'after', None, _('record delete for missing files')),
4576 [('A', 'after', None, _('record delete for missing files')),
4577 ('f', 'force', None,
4577 ('f', 'force', None,
4578 _('remove (and delete) file even if added or modified')),
4578 _('remove (and delete) file even if added or modified')),
4579 ] + walkopts,
4579 ] + walkopts,
4580 _('[OPTION]... FILE...')),
4580 _('[OPTION]... FILE...')),
4581 "rename|move|mv":
4581 "rename|move|mv":
4582 (rename,
4582 (rename,
4583 [('A', 'after', None, _('record a rename that has already occurred')),
4583 [('A', 'after', None, _('record a rename that has already occurred')),
4584 ('f', 'force', None,
4584 ('f', 'force', None,
4585 _('forcibly copy over an existing managed file')),
4585 _('forcibly copy over an existing managed file')),
4586 ] + walkopts + dryrunopts,
4586 ] + walkopts + dryrunopts,
4587 _('[OPTION]... SOURCE... DEST')),
4587 _('[OPTION]... SOURCE... DEST')),
4588 "resolve":
4588 "resolve":
4589 (resolve,
4589 (resolve,
4590 [('a', 'all', None, _('select all unresolved files')),
4590 [('a', 'all', None, _('select all unresolved files')),
4591 ('l', 'list', None, _('list state of files needing merge')),
4591 ('l', 'list', None, _('list state of files needing merge')),
4592 ('m', 'mark', None, _('mark files as resolved')),
4592 ('m', 'mark', None, _('mark files as resolved')),
4593 ('u', 'unmark', None, _('mark files as unresolved')),
4593 ('u', 'unmark', None, _('mark files as unresolved')),
4594 ('t', 'tool', '', _('specify merge tool')),
4594 ('t', 'tool', '', _('specify merge tool')),
4595 ('n', 'no-status', None, _('hide status prefix'))]
4595 ('n', 'no-status', None, _('hide status prefix'))]
4596 + walkopts,
4596 + walkopts,
4597 _('[OPTION]... [FILE]...')),
4597 _('[OPTION]... [FILE]...')),
4598 "revert":
4598 "revert":
4599 (revert,
4599 (revert,
4600 [('a', 'all', None, _('revert all changes when no arguments given')),
4600 [('a', 'all', None, _('revert all changes when no arguments given')),
4601 ('d', 'date', '',
4601 ('d', 'date', '',
4602 _('tipmost revision matching date'), _('DATE')),
4602 _('tipmost revision matching date'), _('DATE')),
4603 ('r', 'rev', '',
4603 ('r', 'rev', '',
4604 _('revert to the specified revision'), _('REV')),
4604 _('revert to the specified revision'), _('REV')),
4605 ('', 'no-backup', None, _('do not save backup copies of files')),
4605 ('', 'no-backup', None, _('do not save backup copies of files')),
4606 ] + walkopts + dryrunopts,
4606 ] + walkopts + dryrunopts,
4607 _('[OPTION]... [-r REV] [NAME]...')),
4607 _('[OPTION]... [-r REV] [NAME]...')),
4608 "rollback": (rollback, dryrunopts),
4608 "rollback": (rollback, dryrunopts),
4609 "root": (root, []),
4609 "root": (root, []),
4610 "^serve":
4610 "^serve":
4611 (serve,
4611 (serve,
4612 [('A', 'accesslog', '',
4612 [('A', 'accesslog', '',
4613 _('name of access log file to write to'), _('FILE')),
4613 _('name of access log file to write to'), _('FILE')),
4614 ('d', 'daemon', None, _('run server in background')),
4614 ('d', 'daemon', None, _('run server in background')),
4615 ('', 'daemon-pipefds', '',
4615 ('', 'daemon-pipefds', '',
4616 _('used internally by daemon mode'), _('NUM')),
4616 _('used internally by daemon mode'), _('NUM')),
4617 ('E', 'errorlog', '',
4617 ('E', 'errorlog', '',
4618 _('name of error log file to write to'), _('FILE')),
4618 _('name of error log file to write to'), _('FILE')),
4619 # use string type, then we can check if something was passed
4619 # use string type, then we can check if something was passed
4620 ('p', 'port', '',
4620 ('p', 'port', '',
4621 _('port to listen on (default: 8000)'), _('PORT')),
4621 _('port to listen on (default: 8000)'), _('PORT')),
4622 ('a', 'address', '',
4622 ('a', 'address', '',
4623 _('address to listen on (default: all interfaces)'), _('ADDR')),
4623 _('address to listen on (default: all interfaces)'), _('ADDR')),
4624 ('', 'prefix', '',
4624 ('', 'prefix', '',
4625 _('prefix path to serve from (default: server root)'), _('PREFIX')),
4625 _('prefix path to serve from (default: server root)'), _('PREFIX')),
4626 ('n', 'name', '',
4626 ('n', 'name', '',
4627 _('name to show in web pages (default: working directory)'),
4627 _('name to show in web pages (default: working directory)'),
4628 _('NAME')),
4628 _('NAME')),
4629 ('', 'web-conf', '',
4629 ('', 'web-conf', '',
4630 _('name of the hgweb config file (see "hg help hgweb")'),
4630 _('name of the hgweb config file (see "hg help hgweb")'),
4631 _('FILE')),
4631 _('FILE')),
4632 ('', 'webdir-conf', '',
4632 ('', 'webdir-conf', '',
4633 _('name of the hgweb config file (DEPRECATED)'), _('FILE')),
4633 _('name of the hgweb config file (DEPRECATED)'), _('FILE')),
4634 ('', 'pid-file', '',
4634 ('', 'pid-file', '',
4635 _('name of file to write process ID to'), _('FILE')),
4635 _('name of file to write process ID to'), _('FILE')),
4636 ('', 'stdio', None, _('for remote clients')),
4636 ('', 'stdio', None, _('for remote clients')),
4637 ('t', 'templates', '',
4637 ('t', 'templates', '',
4638 _('web templates to use'), _('TEMPLATE')),
4638 _('web templates to use'), _('TEMPLATE')),
4639 ('', 'style', '',
4639 ('', 'style', '',
4640 _('template style to use'), _('STYLE')),
4640 _('template style to use'), _('STYLE')),
4641 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4641 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4642 ('', 'certificate', '',
4642 ('', 'certificate', '',
4643 _('SSL certificate file'), _('FILE'))],
4643 _('SSL certificate file'), _('FILE'))],
4644 _('[OPTION]...')),
4644 _('[OPTION]...')),
4645 "showconfig|debugconfig":
4645 "showconfig|debugconfig":
4646 (showconfig,
4646 (showconfig,
4647 [('u', 'untrusted', None, _('show untrusted configuration options'))],
4647 [('u', 'untrusted', None, _('show untrusted configuration options'))],
4648 _('[-u] [NAME]...')),
4648 _('[-u] [NAME]...')),
4649 "^summary|sum":
4649 "^summary|sum":
4650 (summary,
4650 (summary,
4651 [('', 'remote', None, _('check for push and pull'))], '[--remote]'),
4651 [('', 'remote', None, _('check for push and pull'))], '[--remote]'),
4652 "^status|st":
4652 "^status|st":
4653 (status,
4653 (status,
4654 [('A', 'all', None, _('show status of all files')),
4654 [('A', 'all', None, _('show status of all files')),
4655 ('m', 'modified', None, _('show only modified files')),
4655 ('m', 'modified', None, _('show only modified files')),
4656 ('a', 'added', None, _('show only added files')),
4656 ('a', 'added', None, _('show only added files')),
4657 ('r', 'removed', None, _('show only removed files')),
4657 ('r', 'removed', None, _('show only removed files')),
4658 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4658 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4659 ('c', 'clean', None, _('show only files without changes')),
4659 ('c', 'clean', None, _('show only files without changes')),
4660 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4660 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4661 ('i', 'ignored', None, _('show only ignored files')),
4661 ('i', 'ignored', None, _('show only ignored files')),
4662 ('n', 'no-status', None, _('hide status prefix')),
4662 ('n', 'no-status', None, _('hide status prefix')),
4663 ('C', 'copies', None, _('show source of copied files')),
4663 ('C', 'copies', None, _('show source of copied files')),
4664 ('0', 'print0', None,
4664 ('0', 'print0', None,
4665 _('end filenames with NUL, for use with xargs')),
4665 _('end filenames with NUL, for use with xargs')),
4666 ('', 'rev', [],
4666 ('', 'rev', [],
4667 _('show difference from revision'), _('REV')),
4667 _('show difference from revision'), _('REV')),
4668 ('', 'change', '',
4668 ('', 'change', '',
4669 _('list the changed files of a revision'), _('REV')),
4669 _('list the changed files of a revision'), _('REV')),
4670 ] + walkopts + subrepoopts,
4670 ] + walkopts + subrepoopts,
4671 _('[OPTION]... [FILE]...')),
4671 _('[OPTION]... [FILE]...')),
4672 "tag":
4672 "tag":
4673 (tag,
4673 (tag,
4674 [('f', 'force', None, _('force tag')),
4674 [('f', 'force', None, _('force tag')),
4675 ('l', 'local', None, _('make the tag local')),
4675 ('l', 'local', None, _('make the tag local')),
4676 ('r', 'rev', '',
4676 ('r', 'rev', '',
4677 _('revision to tag'), _('REV')),
4677 _('revision to tag'), _('REV')),
4678 ('', 'remove', None, _('remove a tag')),
4678 ('', 'remove', None, _('remove a tag')),
4679 # -l/--local is already there, commitopts cannot be used
4679 # -l/--local is already there, commitopts cannot be used
4680 ('e', 'edit', None, _('edit commit message')),
4680 ('e', 'edit', None, _('edit commit message')),
4681 ('m', 'message', '',
4681 ('m', 'message', '',
4682 _('use <text> as commit message'), _('TEXT')),
4682 _('use <text> as commit message'), _('TEXT')),
4683 ] + commitopts2,
4683 ] + commitopts2,
4684 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
4684 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
4685 "tags": (tags, [], ''),
4685 "tags": (tags, [], ''),
4686 "tip":
4686 "tip":
4687 (tip,
4687 (tip,
4688 [('p', 'patch', None, _('show patch')),
4688 [('p', 'patch', None, _('show patch')),
4689 ('g', 'git', None, _('use git extended diff format')),
4689 ('g', 'git', None, _('use git extended diff format')),
4690 ] + templateopts,
4690 ] + templateopts,
4691 _('[-p] [-g]')),
4691 _('[-p] [-g]')),
4692 "unbundle":
4692 "unbundle":
4693 (unbundle,
4693 (unbundle,
4694 [('u', 'update', None,
4694 [('u', 'update', None,
4695 _('update to new branch head if changesets were unbundled'))],
4695 _('update to new branch head if changesets were unbundled'))],
4696 _('[-u] FILE...')),
4696 _('[-u] FILE...')),
4697 "^update|up|checkout|co":
4697 "^update|up|checkout|co":
4698 (update,
4698 (update,
4699 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
4699 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
4700 ('c', 'check', None,
4700 ('c', 'check', None,
4701 _('update across branches if no uncommitted changes')),
4701 _('update across branches if no uncommitted changes')),
4702 ('d', 'date', '',
4702 ('d', 'date', '',
4703 _('tipmost revision matching date'), _('DATE')),
4703 _('tipmost revision matching date'), _('DATE')),
4704 ('r', 'rev', '',
4704 ('r', 'rev', '',
4705 _('revision'), _('REV'))],
4705 _('revision'), _('REV'))],
4706 _('[-c] [-C] [-d DATE] [[-r] REV]')),
4706 _('[-c] [-C] [-d DATE] [[-r] REV]')),
4707 "verify": (verify, []),
4707 "verify": (verify, []),
4708 "version": (version_, []),
4708 "version": (version_, []),
4709 }
4709 }
4710
4710
4711 norepo = ("clone init version help debugcommands debugcomplete"
4711 norepo = ("clone init version help debugcommands debugcomplete"
4712 " debugdate debuginstall debugfsinfo debugpushkey")
4712 " debugdate debuginstall debugfsinfo debugpushkey")
4713 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
4713 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
4714 " debugdata debugindex debugindexdot")
4714 " debugdata debugindex debugindexdot")
@@ -1,1103 +1,1105 b''
1 # context.py - changeset and file context objects for mercurial
1 # context.py - changeset and file context objects for mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import nullid, nullrev, short, hex
8 from node import nullid, nullrev, short, hex
9 from i18n import _
9 from i18n import _
10 import ancestor, bdiff, error, util, subrepo, patch, encoding
10 import ancestor, bdiff, error, util, subrepo, patch, encoding
11 import os, errno, stat
11 import os, errno, stat
12
12
13 propertycache = util.propertycache
13 propertycache = util.propertycache
14
14
15 class changectx(object):
15 class changectx(object):
16 """A changecontext object makes access to data related to a particular
16 """A changecontext object makes access to data related to a particular
17 changeset convenient."""
17 changeset convenient."""
18 def __init__(self, repo, changeid=''):
18 def __init__(self, repo, changeid=''):
19 """changeid is a revision number, node, or tag"""
19 """changeid is a revision number, node, or tag"""
20 if changeid == '':
20 if changeid == '':
21 changeid = '.'
21 changeid = '.'
22 self._repo = repo
22 self._repo = repo
23 if isinstance(changeid, (long, int)):
23 if isinstance(changeid, (long, int)):
24 self._rev = changeid
24 self._rev = changeid
25 self._node = self._repo.changelog.node(changeid)
25 self._node = self._repo.changelog.node(changeid)
26 else:
26 else:
27 self._node = self._repo.lookup(changeid)
27 self._node = self._repo.lookup(changeid)
28 self._rev = self._repo.changelog.rev(self._node)
28 self._rev = self._repo.changelog.rev(self._node)
29
29
30 def __str__(self):
30 def __str__(self):
31 return short(self.node())
31 return short(self.node())
32
32
33 def __int__(self):
33 def __int__(self):
34 return self.rev()
34 return self.rev()
35
35
36 def __repr__(self):
36 def __repr__(self):
37 return "<changectx %s>" % str(self)
37 return "<changectx %s>" % str(self)
38
38
39 def __hash__(self):
39 def __hash__(self):
40 try:
40 try:
41 return hash(self._rev)
41 return hash(self._rev)
42 except AttributeError:
42 except AttributeError:
43 return id(self)
43 return id(self)
44
44
45 def __eq__(self, other):
45 def __eq__(self, other):
46 try:
46 try:
47 return self._rev == other._rev
47 return self._rev == other._rev
48 except AttributeError:
48 except AttributeError:
49 return False
49 return False
50
50
51 def __ne__(self, other):
51 def __ne__(self, other):
52 return not (self == other)
52 return not (self == other)
53
53
54 def __nonzero__(self):
54 def __nonzero__(self):
55 return self._rev != nullrev
55 return self._rev != nullrev
56
56
57 @propertycache
57 @propertycache
58 def _changeset(self):
58 def _changeset(self):
59 return self._repo.changelog.read(self.node())
59 return self._repo.changelog.read(self.node())
60
60
61 @propertycache
61 @propertycache
62 def _manifest(self):
62 def _manifest(self):
63 return self._repo.manifest.read(self._changeset[0])
63 return self._repo.manifest.read(self._changeset[0])
64
64
65 @propertycache
65 @propertycache
66 def _manifestdelta(self):
66 def _manifestdelta(self):
67 return self._repo.manifest.readdelta(self._changeset[0])
67 return self._repo.manifest.readdelta(self._changeset[0])
68
68
69 @propertycache
69 @propertycache
70 def _parents(self):
70 def _parents(self):
71 p = self._repo.changelog.parentrevs(self._rev)
71 p = self._repo.changelog.parentrevs(self._rev)
72 if p[1] == nullrev:
72 if p[1] == nullrev:
73 p = p[:-1]
73 p = p[:-1]
74 return [changectx(self._repo, x) for x in p]
74 return [changectx(self._repo, x) for x in p]
75
75
76 @propertycache
76 @propertycache
77 def substate(self):
77 def substate(self):
78 return subrepo.state(self, self._repo.ui)
78 return subrepo.state(self, self._repo.ui)
79
79
80 def __contains__(self, key):
80 def __contains__(self, key):
81 return key in self._manifest
81 return key in self._manifest
82
82
83 def __getitem__(self, key):
83 def __getitem__(self, key):
84 return self.filectx(key)
84 return self.filectx(key)
85
85
86 def __iter__(self):
86 def __iter__(self):
87 for f in sorted(self._manifest):
87 for f in sorted(self._manifest):
88 yield f
88 yield f
89
89
90 def changeset(self):
90 def changeset(self):
91 return self._changeset
91 return self._changeset
92 def manifest(self):
92 def manifest(self):
93 return self._manifest
93 return self._manifest
94 def manifestnode(self):
94 def manifestnode(self):
95 return self._changeset[0]
95 return self._changeset[0]
96
96
97 def rev(self):
97 def rev(self):
98 return self._rev
98 return self._rev
99 def node(self):
99 def node(self):
100 return self._node
100 return self._node
101 def hex(self):
101 def hex(self):
102 return hex(self._node)
102 return hex(self._node)
103 def user(self):
103 def user(self):
104 return self._changeset[1]
104 return self._changeset[1]
105 def date(self):
105 def date(self):
106 return self._changeset[2]
106 return self._changeset[2]
107 def files(self):
107 def files(self):
108 return self._changeset[3]
108 return self._changeset[3]
109 def description(self):
109 def description(self):
110 return self._changeset[4]
110 return self._changeset[4]
111 def branch(self):
111 def branch(self):
112 return encoding.tolocal(self._changeset[5].get("branch"))
112 return encoding.tolocal(self._changeset[5].get("branch"))
113 def extra(self):
113 def extra(self):
114 return self._changeset[5]
114 return self._changeset[5]
115 def tags(self):
115 def tags(self):
116 return self._repo.nodetags(self._node)
116 return self._repo.nodetags(self._node)
117 def bookmarks(self):
118 return self._repo.nodebookmarks(self._node)
117
119
118 def parents(self):
120 def parents(self):
119 """return contexts for each parent changeset"""
121 """return contexts for each parent changeset"""
120 return self._parents
122 return self._parents
121
123
122 def p1(self):
124 def p1(self):
123 return self._parents[0]
125 return self._parents[0]
124
126
125 def p2(self):
127 def p2(self):
126 if len(self._parents) == 2:
128 if len(self._parents) == 2:
127 return self._parents[1]
129 return self._parents[1]
128 return changectx(self._repo, -1)
130 return changectx(self._repo, -1)
129
131
130 def children(self):
132 def children(self):
131 """return contexts for each child changeset"""
133 """return contexts for each child changeset"""
132 c = self._repo.changelog.children(self._node)
134 c = self._repo.changelog.children(self._node)
133 return [changectx(self._repo, x) for x in c]
135 return [changectx(self._repo, x) for x in c]
134
136
135 def ancestors(self):
137 def ancestors(self):
136 for a in self._repo.changelog.ancestors(self._rev):
138 for a in self._repo.changelog.ancestors(self._rev):
137 yield changectx(self._repo, a)
139 yield changectx(self._repo, a)
138
140
139 def descendants(self):
141 def descendants(self):
140 for d in self._repo.changelog.descendants(self._rev):
142 for d in self._repo.changelog.descendants(self._rev):
141 yield changectx(self._repo, d)
143 yield changectx(self._repo, d)
142
144
143 def _fileinfo(self, path):
145 def _fileinfo(self, path):
144 if '_manifest' in self.__dict__:
146 if '_manifest' in self.__dict__:
145 try:
147 try:
146 return self._manifest[path], self._manifest.flags(path)
148 return self._manifest[path], self._manifest.flags(path)
147 except KeyError:
149 except KeyError:
148 raise error.LookupError(self._node, path,
150 raise error.LookupError(self._node, path,
149 _('not found in manifest'))
151 _('not found in manifest'))
150 if '_manifestdelta' in self.__dict__ or path in self.files():
152 if '_manifestdelta' in self.__dict__ or path in self.files():
151 if path in self._manifestdelta:
153 if path in self._manifestdelta:
152 return self._manifestdelta[path], self._manifestdelta.flags(path)
154 return self._manifestdelta[path], self._manifestdelta.flags(path)
153 node, flag = self._repo.manifest.find(self._changeset[0], path)
155 node, flag = self._repo.manifest.find(self._changeset[0], path)
154 if not node:
156 if not node:
155 raise error.LookupError(self._node, path,
157 raise error.LookupError(self._node, path,
156 _('not found in manifest'))
158 _('not found in manifest'))
157
159
158 return node, flag
160 return node, flag
159
161
160 def filenode(self, path):
162 def filenode(self, path):
161 return self._fileinfo(path)[0]
163 return self._fileinfo(path)[0]
162
164
163 def flags(self, path):
165 def flags(self, path):
164 try:
166 try:
165 return self._fileinfo(path)[1]
167 return self._fileinfo(path)[1]
166 except error.LookupError:
168 except error.LookupError:
167 return ''
169 return ''
168
170
169 def filectx(self, path, fileid=None, filelog=None):
171 def filectx(self, path, fileid=None, filelog=None):
170 """get a file context from this changeset"""
172 """get a file context from this changeset"""
171 if fileid is None:
173 if fileid is None:
172 fileid = self.filenode(path)
174 fileid = self.filenode(path)
173 return filectx(self._repo, path, fileid=fileid,
175 return filectx(self._repo, path, fileid=fileid,
174 changectx=self, filelog=filelog)
176 changectx=self, filelog=filelog)
175
177
176 def ancestor(self, c2):
178 def ancestor(self, c2):
177 """
179 """
178 return the ancestor context of self and c2
180 return the ancestor context of self and c2
179 """
181 """
180 # deal with workingctxs
182 # deal with workingctxs
181 n2 = c2._node
183 n2 = c2._node
182 if n2 is None:
184 if n2 is None:
183 n2 = c2._parents[0]._node
185 n2 = c2._parents[0]._node
184 n = self._repo.changelog.ancestor(self._node, n2)
186 n = self._repo.changelog.ancestor(self._node, n2)
185 return changectx(self._repo, n)
187 return changectx(self._repo, n)
186
188
187 def walk(self, match):
189 def walk(self, match):
188 fset = set(match.files())
190 fset = set(match.files())
189 # for dirstate.walk, files=['.'] means "walk the whole tree".
191 # for dirstate.walk, files=['.'] means "walk the whole tree".
190 # follow that here, too
192 # follow that here, too
191 fset.discard('.')
193 fset.discard('.')
192 for fn in self:
194 for fn in self:
193 for ffn in fset:
195 for ffn in fset:
194 # match if the file is the exact name or a directory
196 # match if the file is the exact name or a directory
195 if ffn == fn or fn.startswith("%s/" % ffn):
197 if ffn == fn or fn.startswith("%s/" % ffn):
196 fset.remove(ffn)
198 fset.remove(ffn)
197 break
199 break
198 if match(fn):
200 if match(fn):
199 yield fn
201 yield fn
200 for fn in sorted(fset):
202 for fn in sorted(fset):
201 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
203 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
202 yield fn
204 yield fn
203
205
204 def sub(self, path):
206 def sub(self, path):
205 return subrepo.subrepo(self, path)
207 return subrepo.subrepo(self, path)
206
208
207 def diff(self, ctx2=None, match=None, **opts):
209 def diff(self, ctx2=None, match=None, **opts):
208 """Returns a diff generator for the given contexts and matcher"""
210 """Returns a diff generator for the given contexts and matcher"""
209 if ctx2 is None:
211 if ctx2 is None:
210 ctx2 = self.p1()
212 ctx2 = self.p1()
211 if ctx2 is not None and not isinstance(ctx2, changectx):
213 if ctx2 is not None and not isinstance(ctx2, changectx):
212 ctx2 = self._repo[ctx2]
214 ctx2 = self._repo[ctx2]
213 diffopts = patch.diffopts(self._repo.ui, opts)
215 diffopts = patch.diffopts(self._repo.ui, opts)
214 return patch.diff(self._repo, ctx2.node(), self.node(),
216 return patch.diff(self._repo, ctx2.node(), self.node(),
215 match=match, opts=diffopts)
217 match=match, opts=diffopts)
216
218
217 class filectx(object):
219 class filectx(object):
218 """A filecontext object makes access to data related to a particular
220 """A filecontext object makes access to data related to a particular
219 filerevision convenient."""
221 filerevision convenient."""
220 def __init__(self, repo, path, changeid=None, fileid=None,
222 def __init__(self, repo, path, changeid=None, fileid=None,
221 filelog=None, changectx=None):
223 filelog=None, changectx=None):
222 """changeid can be a changeset revision, node, or tag.
224 """changeid can be a changeset revision, node, or tag.
223 fileid can be a file revision or node."""
225 fileid can be a file revision or node."""
224 self._repo = repo
226 self._repo = repo
225 self._path = path
227 self._path = path
226
228
227 assert (changeid is not None
229 assert (changeid is not None
228 or fileid is not None
230 or fileid is not None
229 or changectx is not None), \
231 or changectx is not None), \
230 ("bad args: changeid=%r, fileid=%r, changectx=%r"
232 ("bad args: changeid=%r, fileid=%r, changectx=%r"
231 % (changeid, fileid, changectx))
233 % (changeid, fileid, changectx))
232
234
233 if filelog:
235 if filelog:
234 self._filelog = filelog
236 self._filelog = filelog
235
237
236 if changeid is not None:
238 if changeid is not None:
237 self._changeid = changeid
239 self._changeid = changeid
238 if changectx is not None:
240 if changectx is not None:
239 self._changectx = changectx
241 self._changectx = changectx
240 if fileid is not None:
242 if fileid is not None:
241 self._fileid = fileid
243 self._fileid = fileid
242
244
243 @propertycache
245 @propertycache
244 def _changectx(self):
246 def _changectx(self):
245 return changectx(self._repo, self._changeid)
247 return changectx(self._repo, self._changeid)
246
248
247 @propertycache
249 @propertycache
248 def _filelog(self):
250 def _filelog(self):
249 return self._repo.file(self._path)
251 return self._repo.file(self._path)
250
252
251 @propertycache
253 @propertycache
252 def _changeid(self):
254 def _changeid(self):
253 if '_changectx' in self.__dict__:
255 if '_changectx' in self.__dict__:
254 return self._changectx.rev()
256 return self._changectx.rev()
255 else:
257 else:
256 return self._filelog.linkrev(self._filerev)
258 return self._filelog.linkrev(self._filerev)
257
259
258 @propertycache
260 @propertycache
259 def _filenode(self):
261 def _filenode(self):
260 if '_fileid' in self.__dict__:
262 if '_fileid' in self.__dict__:
261 return self._filelog.lookup(self._fileid)
263 return self._filelog.lookup(self._fileid)
262 else:
264 else:
263 return self._changectx.filenode(self._path)
265 return self._changectx.filenode(self._path)
264
266
265 @propertycache
267 @propertycache
266 def _filerev(self):
268 def _filerev(self):
267 return self._filelog.rev(self._filenode)
269 return self._filelog.rev(self._filenode)
268
270
269 @propertycache
271 @propertycache
270 def _repopath(self):
272 def _repopath(self):
271 return self._path
273 return self._path
272
274
273 def __nonzero__(self):
275 def __nonzero__(self):
274 try:
276 try:
275 self._filenode
277 self._filenode
276 return True
278 return True
277 except error.LookupError:
279 except error.LookupError:
278 # file is missing
280 # file is missing
279 return False
281 return False
280
282
281 def __str__(self):
283 def __str__(self):
282 return "%s@%s" % (self.path(), short(self.node()))
284 return "%s@%s" % (self.path(), short(self.node()))
283
285
284 def __repr__(self):
286 def __repr__(self):
285 return "<filectx %s>" % str(self)
287 return "<filectx %s>" % str(self)
286
288
287 def __hash__(self):
289 def __hash__(self):
288 try:
290 try:
289 return hash((self._path, self._filenode))
291 return hash((self._path, self._filenode))
290 except AttributeError:
292 except AttributeError:
291 return id(self)
293 return id(self)
292
294
293 def __eq__(self, other):
295 def __eq__(self, other):
294 try:
296 try:
295 return (self._path == other._path
297 return (self._path == other._path
296 and self._filenode == other._filenode)
298 and self._filenode == other._filenode)
297 except AttributeError:
299 except AttributeError:
298 return False
300 return False
299
301
300 def __ne__(self, other):
302 def __ne__(self, other):
301 return not (self == other)
303 return not (self == other)
302
304
303 def filectx(self, fileid):
305 def filectx(self, fileid):
304 '''opens an arbitrary revision of the file without
306 '''opens an arbitrary revision of the file without
305 opening a new filelog'''
307 opening a new filelog'''
306 return filectx(self._repo, self._path, fileid=fileid,
308 return filectx(self._repo, self._path, fileid=fileid,
307 filelog=self._filelog)
309 filelog=self._filelog)
308
310
309 def filerev(self):
311 def filerev(self):
310 return self._filerev
312 return self._filerev
311 def filenode(self):
313 def filenode(self):
312 return self._filenode
314 return self._filenode
313 def flags(self):
315 def flags(self):
314 return self._changectx.flags(self._path)
316 return self._changectx.flags(self._path)
315 def filelog(self):
317 def filelog(self):
316 return self._filelog
318 return self._filelog
317
319
318 def rev(self):
320 def rev(self):
319 if '_changectx' in self.__dict__:
321 if '_changectx' in self.__dict__:
320 return self._changectx.rev()
322 return self._changectx.rev()
321 if '_changeid' in self.__dict__:
323 if '_changeid' in self.__dict__:
322 return self._changectx.rev()
324 return self._changectx.rev()
323 return self._filelog.linkrev(self._filerev)
325 return self._filelog.linkrev(self._filerev)
324
326
325 def linkrev(self):
327 def linkrev(self):
326 return self._filelog.linkrev(self._filerev)
328 return self._filelog.linkrev(self._filerev)
327 def node(self):
329 def node(self):
328 return self._changectx.node()
330 return self._changectx.node()
329 def hex(self):
331 def hex(self):
330 return hex(self.node())
332 return hex(self.node())
331 def user(self):
333 def user(self):
332 return self._changectx.user()
334 return self._changectx.user()
333 def date(self):
335 def date(self):
334 return self._changectx.date()
336 return self._changectx.date()
335 def files(self):
337 def files(self):
336 return self._changectx.files()
338 return self._changectx.files()
337 def description(self):
339 def description(self):
338 return self._changectx.description()
340 return self._changectx.description()
339 def branch(self):
341 def branch(self):
340 return self._changectx.branch()
342 return self._changectx.branch()
341 def extra(self):
343 def extra(self):
342 return self._changectx.extra()
344 return self._changectx.extra()
343 def manifest(self):
345 def manifest(self):
344 return self._changectx.manifest()
346 return self._changectx.manifest()
345 def changectx(self):
347 def changectx(self):
346 return self._changectx
348 return self._changectx
347
349
348 def data(self):
350 def data(self):
349 return self._filelog.read(self._filenode)
351 return self._filelog.read(self._filenode)
350 def path(self):
352 def path(self):
351 return self._path
353 return self._path
352 def size(self):
354 def size(self):
353 return self._filelog.size(self._filerev)
355 return self._filelog.size(self._filerev)
354
356
355 def cmp(self, fctx):
357 def cmp(self, fctx):
356 """compare with other file context
358 """compare with other file context
357
359
358 returns True if different than fctx.
360 returns True if different than fctx.
359 """
361 """
360 if (fctx._filerev is None and self._repo._encodefilterpats
362 if (fctx._filerev is None and self._repo._encodefilterpats
361 or self.size() == fctx.size()):
363 or self.size() == fctx.size()):
362 return self._filelog.cmp(self._filenode, fctx.data())
364 return self._filelog.cmp(self._filenode, fctx.data())
363
365
364 return True
366 return True
365
367
366 def renamed(self):
368 def renamed(self):
367 """check if file was actually renamed in this changeset revision
369 """check if file was actually renamed in this changeset revision
368
370
369 If rename logged in file revision, we report copy for changeset only
371 If rename logged in file revision, we report copy for changeset only
370 if file revisions linkrev points back to the changeset in question
372 if file revisions linkrev points back to the changeset in question
371 or both changeset parents contain different file revisions.
373 or both changeset parents contain different file revisions.
372 """
374 """
373
375
374 renamed = self._filelog.renamed(self._filenode)
376 renamed = self._filelog.renamed(self._filenode)
375 if not renamed:
377 if not renamed:
376 return renamed
378 return renamed
377
379
378 if self.rev() == self.linkrev():
380 if self.rev() == self.linkrev():
379 return renamed
381 return renamed
380
382
381 name = self.path()
383 name = self.path()
382 fnode = self._filenode
384 fnode = self._filenode
383 for p in self._changectx.parents():
385 for p in self._changectx.parents():
384 try:
386 try:
385 if fnode == p.filenode(name):
387 if fnode == p.filenode(name):
386 return None
388 return None
387 except error.LookupError:
389 except error.LookupError:
388 pass
390 pass
389 return renamed
391 return renamed
390
392
391 def parents(self):
393 def parents(self):
392 p = self._path
394 p = self._path
393 fl = self._filelog
395 fl = self._filelog
394 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
396 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
395
397
396 r = self._filelog.renamed(self._filenode)
398 r = self._filelog.renamed(self._filenode)
397 if r:
399 if r:
398 pl[0] = (r[0], r[1], None)
400 pl[0] = (r[0], r[1], None)
399
401
400 return [filectx(self._repo, p, fileid=n, filelog=l)
402 return [filectx(self._repo, p, fileid=n, filelog=l)
401 for p, n, l in pl if n != nullid]
403 for p, n, l in pl if n != nullid]
402
404
403 def children(self):
405 def children(self):
404 # hard for renames
406 # hard for renames
405 c = self._filelog.children(self._filenode)
407 c = self._filelog.children(self._filenode)
406 return [filectx(self._repo, self._path, fileid=x,
408 return [filectx(self._repo, self._path, fileid=x,
407 filelog=self._filelog) for x in c]
409 filelog=self._filelog) for x in c]
408
410
409 def annotate(self, follow=False, linenumber=None):
411 def annotate(self, follow=False, linenumber=None):
410 '''returns a list of tuples of (ctx, line) for each line
412 '''returns a list of tuples of (ctx, line) for each line
411 in the file, where ctx is the filectx of the node where
413 in the file, where ctx is the filectx of the node where
412 that line was last changed.
414 that line was last changed.
413 This returns tuples of ((ctx, linenumber), line) for each line,
415 This returns tuples of ((ctx, linenumber), line) for each line,
414 if "linenumber" parameter is NOT "None".
416 if "linenumber" parameter is NOT "None".
415 In such tuples, linenumber means one at the first appearance
417 In such tuples, linenumber means one at the first appearance
416 in the managed file.
418 in the managed file.
417 To reduce annotation cost,
419 To reduce annotation cost,
418 this returns fixed value(False is used) as linenumber,
420 this returns fixed value(False is used) as linenumber,
419 if "linenumber" parameter is "False".'''
421 if "linenumber" parameter is "False".'''
420
422
421 def decorate_compat(text, rev):
423 def decorate_compat(text, rev):
422 return ([rev] * len(text.splitlines()), text)
424 return ([rev] * len(text.splitlines()), text)
423
425
424 def without_linenumber(text, rev):
426 def without_linenumber(text, rev):
425 return ([(rev, False)] * len(text.splitlines()), text)
427 return ([(rev, False)] * len(text.splitlines()), text)
426
428
427 def with_linenumber(text, rev):
429 def with_linenumber(text, rev):
428 size = len(text.splitlines())
430 size = len(text.splitlines())
429 return ([(rev, i) for i in xrange(1, size + 1)], text)
431 return ([(rev, i) for i in xrange(1, size + 1)], text)
430
432
431 decorate = (((linenumber is None) and decorate_compat) or
433 decorate = (((linenumber is None) and decorate_compat) or
432 (linenumber and with_linenumber) or
434 (linenumber and with_linenumber) or
433 without_linenumber)
435 without_linenumber)
434
436
435 def pair(parent, child):
437 def pair(parent, child):
436 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
438 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
437 child[0][b1:b2] = parent[0][a1:a2]
439 child[0][b1:b2] = parent[0][a1:a2]
438 return child
440 return child
439
441
440 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
442 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
441 def getctx(path, fileid):
443 def getctx(path, fileid):
442 log = path == self._path and self._filelog or getlog(path)
444 log = path == self._path and self._filelog or getlog(path)
443 return filectx(self._repo, path, fileid=fileid, filelog=log)
445 return filectx(self._repo, path, fileid=fileid, filelog=log)
444 getctx = util.lrucachefunc(getctx)
446 getctx = util.lrucachefunc(getctx)
445
447
446 def parents(f):
448 def parents(f):
447 # we want to reuse filectx objects as much as possible
449 # we want to reuse filectx objects as much as possible
448 p = f._path
450 p = f._path
449 if f._filerev is None: # working dir
451 if f._filerev is None: # working dir
450 pl = [(n.path(), n.filerev()) for n in f.parents()]
452 pl = [(n.path(), n.filerev()) for n in f.parents()]
451 else:
453 else:
452 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
454 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
453
455
454 if follow:
456 if follow:
455 r = f.renamed()
457 r = f.renamed()
456 if r:
458 if r:
457 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
459 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
458
460
459 return [getctx(p, n) for p, n in pl if n != nullrev]
461 return [getctx(p, n) for p, n in pl if n != nullrev]
460
462
461 # use linkrev to find the first changeset where self appeared
463 # use linkrev to find the first changeset where self appeared
462 if self.rev() != self.linkrev():
464 if self.rev() != self.linkrev():
463 base = self.filectx(self.filerev())
465 base = self.filectx(self.filerev())
464 else:
466 else:
465 base = self
467 base = self
466
468
467 # find all ancestors
469 # find all ancestors
468 needed = {base: 1}
470 needed = {base: 1}
469 visit = [base]
471 visit = [base]
470 files = [base._path]
472 files = [base._path]
471 while visit:
473 while visit:
472 f = visit.pop(0)
474 f = visit.pop(0)
473 for p in parents(f):
475 for p in parents(f):
474 if p not in needed:
476 if p not in needed:
475 needed[p] = 1
477 needed[p] = 1
476 visit.append(p)
478 visit.append(p)
477 if p._path not in files:
479 if p._path not in files:
478 files.append(p._path)
480 files.append(p._path)
479 else:
481 else:
480 # count how many times we'll use this
482 # count how many times we'll use this
481 needed[p] += 1
483 needed[p] += 1
482
484
483 # sort by revision (per file) which is a topological order
485 # sort by revision (per file) which is a topological order
484 visit = []
486 visit = []
485 for f in files:
487 for f in files:
486 visit.extend(n for n in needed if n._path == f)
488 visit.extend(n for n in needed if n._path == f)
487
489
488 hist = {}
490 hist = {}
489 for f in sorted(visit, key=lambda x: x.rev()):
491 for f in sorted(visit, key=lambda x: x.rev()):
490 curr = decorate(f.data(), f)
492 curr = decorate(f.data(), f)
491 for p in parents(f):
493 for p in parents(f):
492 curr = pair(hist[p], curr)
494 curr = pair(hist[p], curr)
493 # trim the history of unneeded revs
495 # trim the history of unneeded revs
494 needed[p] -= 1
496 needed[p] -= 1
495 if not needed[p]:
497 if not needed[p]:
496 del hist[p]
498 del hist[p]
497 hist[f] = curr
499 hist[f] = curr
498
500
499 return zip(hist[f][0], hist[f][1].splitlines(True))
501 return zip(hist[f][0], hist[f][1].splitlines(True))
500
502
501 def ancestor(self, fc2, actx=None):
503 def ancestor(self, fc2, actx=None):
502 """
504 """
503 find the common ancestor file context, if any, of self, and fc2
505 find the common ancestor file context, if any, of self, and fc2
504
506
505 If actx is given, it must be the changectx of the common ancestor
507 If actx is given, it must be the changectx of the common ancestor
506 of self's and fc2's respective changesets.
508 of self's and fc2's respective changesets.
507 """
509 """
508
510
509 if actx is None:
511 if actx is None:
510 actx = self.changectx().ancestor(fc2.changectx())
512 actx = self.changectx().ancestor(fc2.changectx())
511
513
512 # the trivial case: changesets are unrelated, files must be too
514 # the trivial case: changesets are unrelated, files must be too
513 if not actx:
515 if not actx:
514 return None
516 return None
515
517
516 # the easy case: no (relevant) renames
518 # the easy case: no (relevant) renames
517 if fc2.path() == self.path() and self.path() in actx:
519 if fc2.path() == self.path() and self.path() in actx:
518 return actx[self.path()]
520 return actx[self.path()]
519 acache = {}
521 acache = {}
520
522
521 # prime the ancestor cache for the working directory
523 # prime the ancestor cache for the working directory
522 for c in (self, fc2):
524 for c in (self, fc2):
523 if c._filerev is None:
525 if c._filerev is None:
524 pl = [(n.path(), n.filenode()) for n in c.parents()]
526 pl = [(n.path(), n.filenode()) for n in c.parents()]
525 acache[(c._path, None)] = pl
527 acache[(c._path, None)] = pl
526
528
527 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
529 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
528 def parents(vertex):
530 def parents(vertex):
529 if vertex in acache:
531 if vertex in acache:
530 return acache[vertex]
532 return acache[vertex]
531 f, n = vertex
533 f, n = vertex
532 if f not in flcache:
534 if f not in flcache:
533 flcache[f] = self._repo.file(f)
535 flcache[f] = self._repo.file(f)
534 fl = flcache[f]
536 fl = flcache[f]
535 pl = [(f, p) for p in fl.parents(n) if p != nullid]
537 pl = [(f, p) for p in fl.parents(n) if p != nullid]
536 re = fl.renamed(n)
538 re = fl.renamed(n)
537 if re:
539 if re:
538 pl.append(re)
540 pl.append(re)
539 acache[vertex] = pl
541 acache[vertex] = pl
540 return pl
542 return pl
541
543
542 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
544 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
543 v = ancestor.ancestor(a, b, parents)
545 v = ancestor.ancestor(a, b, parents)
544 if v:
546 if v:
545 f, n = v
547 f, n = v
546 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
548 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
547
549
548 return None
550 return None
549
551
550 def ancestors(self):
552 def ancestors(self):
551 seen = set(str(self))
553 seen = set(str(self))
552 visit = [self]
554 visit = [self]
553 while visit:
555 while visit:
554 for parent in visit.pop(0).parents():
556 for parent in visit.pop(0).parents():
555 s = str(parent)
557 s = str(parent)
556 if s not in seen:
558 if s not in seen:
557 visit.append(parent)
559 visit.append(parent)
558 seen.add(s)
560 seen.add(s)
559 yield parent
561 yield parent
560
562
561 class workingctx(changectx):
563 class workingctx(changectx):
562 """A workingctx object makes access to data related to
564 """A workingctx object makes access to data related to
563 the current working directory convenient.
565 the current working directory convenient.
564 date - any valid date string or (unixtime, offset), or None.
566 date - any valid date string or (unixtime, offset), or None.
565 user - username string, or None.
567 user - username string, or None.
566 extra - a dictionary of extra values, or None.
568 extra - a dictionary of extra values, or None.
567 changes - a list of file lists as returned by localrepo.status()
569 changes - a list of file lists as returned by localrepo.status()
568 or None to use the repository status.
570 or None to use the repository status.
569 """
571 """
570 def __init__(self, repo, text="", user=None, date=None, extra=None,
572 def __init__(self, repo, text="", user=None, date=None, extra=None,
571 changes=None):
573 changes=None):
572 self._repo = repo
574 self._repo = repo
573 self._rev = None
575 self._rev = None
574 self._node = None
576 self._node = None
575 self._text = text
577 self._text = text
576 if date:
578 if date:
577 self._date = util.parsedate(date)
579 self._date = util.parsedate(date)
578 if user:
580 if user:
579 self._user = user
581 self._user = user
580 if changes:
582 if changes:
581 self._status = list(changes[:4])
583 self._status = list(changes[:4])
582 self._unknown = changes[4]
584 self._unknown = changes[4]
583 self._ignored = changes[5]
585 self._ignored = changes[5]
584 self._clean = changes[6]
586 self._clean = changes[6]
585 else:
587 else:
586 self._unknown = None
588 self._unknown = None
587 self._ignored = None
589 self._ignored = None
588 self._clean = None
590 self._clean = None
589
591
590 self._extra = {}
592 self._extra = {}
591 if extra:
593 if extra:
592 self._extra = extra.copy()
594 self._extra = extra.copy()
593 if 'branch' not in self._extra:
595 if 'branch' not in self._extra:
594 try:
596 try:
595 branch = encoding.fromlocal(self._repo.dirstate.branch())
597 branch = encoding.fromlocal(self._repo.dirstate.branch())
596 except UnicodeDecodeError:
598 except UnicodeDecodeError:
597 raise util.Abort(_('branch name not in UTF-8!'))
599 raise util.Abort(_('branch name not in UTF-8!'))
598 self._extra['branch'] = branch
600 self._extra['branch'] = branch
599 if self._extra['branch'] == '':
601 if self._extra['branch'] == '':
600 self._extra['branch'] = 'default'
602 self._extra['branch'] = 'default'
601
603
602 def __str__(self):
604 def __str__(self):
603 return str(self._parents[0]) + "+"
605 return str(self._parents[0]) + "+"
604
606
605 def __repr__(self):
607 def __repr__(self):
606 return "<workingctx %s>" % str(self)
608 return "<workingctx %s>" % str(self)
607
609
608 def __nonzero__(self):
610 def __nonzero__(self):
609 return True
611 return True
610
612
611 def __contains__(self, key):
613 def __contains__(self, key):
612 return self._repo.dirstate[key] not in "?r"
614 return self._repo.dirstate[key] not in "?r"
613
615
614 @propertycache
616 @propertycache
615 def _manifest(self):
617 def _manifest(self):
616 """generate a manifest corresponding to the working directory"""
618 """generate a manifest corresponding to the working directory"""
617
619
618 if self._unknown is None:
620 if self._unknown is None:
619 self.status(unknown=True)
621 self.status(unknown=True)
620
622
621 man = self._parents[0].manifest().copy()
623 man = self._parents[0].manifest().copy()
622 copied = self._repo.dirstate.copies()
624 copied = self._repo.dirstate.copies()
623 if len(self._parents) > 1:
625 if len(self._parents) > 1:
624 man2 = self.p2().manifest()
626 man2 = self.p2().manifest()
625 def getman(f):
627 def getman(f):
626 if f in man:
628 if f in man:
627 return man
629 return man
628 return man2
630 return man2
629 else:
631 else:
630 getman = lambda f: man
632 getman = lambda f: man
631 def cf(f):
633 def cf(f):
632 f = copied.get(f, f)
634 f = copied.get(f, f)
633 return getman(f).flags(f)
635 return getman(f).flags(f)
634 ff = self._repo.dirstate.flagfunc(cf)
636 ff = self._repo.dirstate.flagfunc(cf)
635 modified, added, removed, deleted = self._status
637 modified, added, removed, deleted = self._status
636 unknown = self._unknown
638 unknown = self._unknown
637 for i, l in (("a", added), ("m", modified), ("u", unknown)):
639 for i, l in (("a", added), ("m", modified), ("u", unknown)):
638 for f in l:
640 for f in l:
639 orig = copied.get(f, f)
641 orig = copied.get(f, f)
640 man[f] = getman(orig).get(orig, nullid) + i
642 man[f] = getman(orig).get(orig, nullid) + i
641 try:
643 try:
642 man.set(f, ff(f))
644 man.set(f, ff(f))
643 except OSError:
645 except OSError:
644 pass
646 pass
645
647
646 for f in deleted + removed:
648 for f in deleted + removed:
647 if f in man:
649 if f in man:
648 del man[f]
650 del man[f]
649
651
650 return man
652 return man
651
653
652 @propertycache
654 @propertycache
653 def _status(self):
655 def _status(self):
654 return self._repo.status()[:4]
656 return self._repo.status()[:4]
655
657
656 @propertycache
658 @propertycache
657 def _user(self):
659 def _user(self):
658 return self._repo.ui.username()
660 return self._repo.ui.username()
659
661
660 @propertycache
662 @propertycache
661 def _date(self):
663 def _date(self):
662 return util.makedate()
664 return util.makedate()
663
665
664 @propertycache
666 @propertycache
665 def _parents(self):
667 def _parents(self):
666 p = self._repo.dirstate.parents()
668 p = self._repo.dirstate.parents()
667 if p[1] == nullid:
669 if p[1] == nullid:
668 p = p[:-1]
670 p = p[:-1]
669 self._parents = [changectx(self._repo, x) for x in p]
671 self._parents = [changectx(self._repo, x) for x in p]
670 return self._parents
672 return self._parents
671
673
672 def status(self, ignored=False, clean=False, unknown=False):
674 def status(self, ignored=False, clean=False, unknown=False):
673 """Explicit status query
675 """Explicit status query
674 Unless this method is used to query the working copy status, the
676 Unless this method is used to query the working copy status, the
675 _status property will implicitly read the status using its default
677 _status property will implicitly read the status using its default
676 arguments."""
678 arguments."""
677 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
679 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
678 self._unknown = self._ignored = self._clean = None
680 self._unknown = self._ignored = self._clean = None
679 if unknown:
681 if unknown:
680 self._unknown = stat[4]
682 self._unknown = stat[4]
681 if ignored:
683 if ignored:
682 self._ignored = stat[5]
684 self._ignored = stat[5]
683 if clean:
685 if clean:
684 self._clean = stat[6]
686 self._clean = stat[6]
685 self._status = stat[:4]
687 self._status = stat[:4]
686 return stat
688 return stat
687
689
688 def manifest(self):
690 def manifest(self):
689 return self._manifest
691 return self._manifest
690 def user(self):
692 def user(self):
691 return self._user or self._repo.ui.username()
693 return self._user or self._repo.ui.username()
692 def date(self):
694 def date(self):
693 return self._date
695 return self._date
694 def description(self):
696 def description(self):
695 return self._text
697 return self._text
696 def files(self):
698 def files(self):
697 return sorted(self._status[0] + self._status[1] + self._status[2])
699 return sorted(self._status[0] + self._status[1] + self._status[2])
698
700
699 def modified(self):
701 def modified(self):
700 return self._status[0]
702 return self._status[0]
701 def added(self):
703 def added(self):
702 return self._status[1]
704 return self._status[1]
703 def removed(self):
705 def removed(self):
704 return self._status[2]
706 return self._status[2]
705 def deleted(self):
707 def deleted(self):
706 return self._status[3]
708 return self._status[3]
707 def unknown(self):
709 def unknown(self):
708 assert self._unknown is not None # must call status first
710 assert self._unknown is not None # must call status first
709 return self._unknown
711 return self._unknown
710 def ignored(self):
712 def ignored(self):
711 assert self._ignored is not None # must call status first
713 assert self._ignored is not None # must call status first
712 return self._ignored
714 return self._ignored
713 def clean(self):
715 def clean(self):
714 assert self._clean is not None # must call status first
716 assert self._clean is not None # must call status first
715 return self._clean
717 return self._clean
716 def branch(self):
718 def branch(self):
717 return encoding.tolocal(self._extra['branch'])
719 return encoding.tolocal(self._extra['branch'])
718 def extra(self):
720 def extra(self):
719 return self._extra
721 return self._extra
720
722
721 def tags(self):
723 def tags(self):
722 t = []
724 t = []
723 [t.extend(p.tags()) for p in self.parents()]
725 [t.extend(p.tags()) for p in self.parents()]
724 return t
726 return t
725
727
726 def children(self):
728 def children(self):
727 return []
729 return []
728
730
729 def flags(self, path):
731 def flags(self, path):
730 if '_manifest' in self.__dict__:
732 if '_manifest' in self.__dict__:
731 try:
733 try:
732 return self._manifest.flags(path)
734 return self._manifest.flags(path)
733 except KeyError:
735 except KeyError:
734 return ''
736 return ''
735
737
736 orig = self._repo.dirstate.copies().get(path, path)
738 orig = self._repo.dirstate.copies().get(path, path)
737
739
738 def findflag(ctx):
740 def findflag(ctx):
739 mnode = ctx.changeset()[0]
741 mnode = ctx.changeset()[0]
740 node, flag = self._repo.manifest.find(mnode, orig)
742 node, flag = self._repo.manifest.find(mnode, orig)
741 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
743 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
742 try:
744 try:
743 return ff(path)
745 return ff(path)
744 except OSError:
746 except OSError:
745 pass
747 pass
746
748
747 flag = findflag(self._parents[0])
749 flag = findflag(self._parents[0])
748 if flag is None and len(self.parents()) > 1:
750 if flag is None and len(self.parents()) > 1:
749 flag = findflag(self._parents[1])
751 flag = findflag(self._parents[1])
750 if flag is None or self._repo.dirstate[path] == 'r':
752 if flag is None or self._repo.dirstate[path] == 'r':
751 return ''
753 return ''
752 return flag
754 return flag
753
755
754 def filectx(self, path, filelog=None):
756 def filectx(self, path, filelog=None):
755 """get a file context from the working directory"""
757 """get a file context from the working directory"""
756 return workingfilectx(self._repo, path, workingctx=self,
758 return workingfilectx(self._repo, path, workingctx=self,
757 filelog=filelog)
759 filelog=filelog)
758
760
759 def ancestor(self, c2):
761 def ancestor(self, c2):
760 """return the ancestor context of self and c2"""
762 """return the ancestor context of self and c2"""
761 return self._parents[0].ancestor(c2) # punt on two parents for now
763 return self._parents[0].ancestor(c2) # punt on two parents for now
762
764
763 def walk(self, match):
765 def walk(self, match):
764 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
766 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
765 True, False))
767 True, False))
766
768
767 def dirty(self, missing=False):
769 def dirty(self, missing=False):
768 "check whether a working directory is modified"
770 "check whether a working directory is modified"
769 # check subrepos first
771 # check subrepos first
770 for s in self.substate:
772 for s in self.substate:
771 if self.sub(s).dirty():
773 if self.sub(s).dirty():
772 return True
774 return True
773 # check current working dir
775 # check current working dir
774 return (self.p2() or self.branch() != self.p1().branch() or
776 return (self.p2() or self.branch() != self.p1().branch() or
775 self.modified() or self.added() or self.removed() or
777 self.modified() or self.added() or self.removed() or
776 (missing and self.deleted()))
778 (missing and self.deleted()))
777
779
778 def add(self, list, prefix=""):
780 def add(self, list, prefix=""):
779 join = lambda f: os.path.join(prefix, f)
781 join = lambda f: os.path.join(prefix, f)
780 wlock = self._repo.wlock()
782 wlock = self._repo.wlock()
781 ui, ds = self._repo.ui, self._repo.dirstate
783 ui, ds = self._repo.ui, self._repo.dirstate
782 try:
784 try:
783 rejected = []
785 rejected = []
784 for f in list:
786 for f in list:
785 p = self._repo.wjoin(f)
787 p = self._repo.wjoin(f)
786 try:
788 try:
787 st = os.lstat(p)
789 st = os.lstat(p)
788 except:
790 except:
789 ui.warn(_("%s does not exist!\n") % join(f))
791 ui.warn(_("%s does not exist!\n") % join(f))
790 rejected.append(f)
792 rejected.append(f)
791 continue
793 continue
792 if st.st_size > 10000000:
794 if st.st_size > 10000000:
793 ui.warn(_("%s: up to %d MB of RAM may be required "
795 ui.warn(_("%s: up to %d MB of RAM may be required "
794 "to manage this file\n"
796 "to manage this file\n"
795 "(use 'hg revert %s' to cancel the "
797 "(use 'hg revert %s' to cancel the "
796 "pending addition)\n")
798 "pending addition)\n")
797 % (f, 3 * st.st_size // 1000000, join(f)))
799 % (f, 3 * st.st_size // 1000000, join(f)))
798 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
800 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
799 ui.warn(_("%s not added: only files and symlinks "
801 ui.warn(_("%s not added: only files and symlinks "
800 "supported currently\n") % join(f))
802 "supported currently\n") % join(f))
801 rejected.append(p)
803 rejected.append(p)
802 elif ds[f] in 'amn':
804 elif ds[f] in 'amn':
803 ui.warn(_("%s already tracked!\n") % join(f))
805 ui.warn(_("%s already tracked!\n") % join(f))
804 elif ds[f] == 'r':
806 elif ds[f] == 'r':
805 ds.normallookup(f)
807 ds.normallookup(f)
806 else:
808 else:
807 ds.add(f)
809 ds.add(f)
808 return rejected
810 return rejected
809 finally:
811 finally:
810 wlock.release()
812 wlock.release()
811
813
812 def forget(self, list):
814 def forget(self, list):
813 wlock = self._repo.wlock()
815 wlock = self._repo.wlock()
814 try:
816 try:
815 for f in list:
817 for f in list:
816 if self._repo.dirstate[f] != 'a':
818 if self._repo.dirstate[f] != 'a':
817 self._repo.ui.warn(_("%s not added!\n") % f)
819 self._repo.ui.warn(_("%s not added!\n") % f)
818 else:
820 else:
819 self._repo.dirstate.forget(f)
821 self._repo.dirstate.forget(f)
820 finally:
822 finally:
821 wlock.release()
823 wlock.release()
822
824
823 def ancestors(self):
825 def ancestors(self):
824 for a in self._repo.changelog.ancestors(
826 for a in self._repo.changelog.ancestors(
825 *[p.rev() for p in self._parents]):
827 *[p.rev() for p in self._parents]):
826 yield changectx(self._repo, a)
828 yield changectx(self._repo, a)
827
829
828 def remove(self, list, unlink=False):
830 def remove(self, list, unlink=False):
829 if unlink:
831 if unlink:
830 for f in list:
832 for f in list:
831 try:
833 try:
832 util.unlinkpath(self._repo.wjoin(f))
834 util.unlinkpath(self._repo.wjoin(f))
833 except OSError, inst:
835 except OSError, inst:
834 if inst.errno != errno.ENOENT:
836 if inst.errno != errno.ENOENT:
835 raise
837 raise
836 wlock = self._repo.wlock()
838 wlock = self._repo.wlock()
837 try:
839 try:
838 for f in list:
840 for f in list:
839 if unlink and os.path.lexists(self._repo.wjoin(f)):
841 if unlink and os.path.lexists(self._repo.wjoin(f)):
840 self._repo.ui.warn(_("%s still exists!\n") % f)
842 self._repo.ui.warn(_("%s still exists!\n") % f)
841 elif self._repo.dirstate[f] == 'a':
843 elif self._repo.dirstate[f] == 'a':
842 self._repo.dirstate.forget(f)
844 self._repo.dirstate.forget(f)
843 elif f not in self._repo.dirstate:
845 elif f not in self._repo.dirstate:
844 self._repo.ui.warn(_("%s not tracked!\n") % f)
846 self._repo.ui.warn(_("%s not tracked!\n") % f)
845 else:
847 else:
846 self._repo.dirstate.remove(f)
848 self._repo.dirstate.remove(f)
847 finally:
849 finally:
848 wlock.release()
850 wlock.release()
849
851
850 def undelete(self, list):
852 def undelete(self, list):
851 pctxs = self.parents()
853 pctxs = self.parents()
852 wlock = self._repo.wlock()
854 wlock = self._repo.wlock()
853 try:
855 try:
854 for f in list:
856 for f in list:
855 if self._repo.dirstate[f] != 'r':
857 if self._repo.dirstate[f] != 'r':
856 self._repo.ui.warn(_("%s not removed!\n") % f)
858 self._repo.ui.warn(_("%s not removed!\n") % f)
857 else:
859 else:
858 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
860 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
859 t = fctx.data()
861 t = fctx.data()
860 self._repo.wwrite(f, t, fctx.flags())
862 self._repo.wwrite(f, t, fctx.flags())
861 self._repo.dirstate.normal(f)
863 self._repo.dirstate.normal(f)
862 finally:
864 finally:
863 wlock.release()
865 wlock.release()
864
866
865 def copy(self, source, dest):
867 def copy(self, source, dest):
866 p = self._repo.wjoin(dest)
868 p = self._repo.wjoin(dest)
867 if not os.path.lexists(p):
869 if not os.path.lexists(p):
868 self._repo.ui.warn(_("%s does not exist!\n") % dest)
870 self._repo.ui.warn(_("%s does not exist!\n") % dest)
869 elif not (os.path.isfile(p) or os.path.islink(p)):
871 elif not (os.path.isfile(p) or os.path.islink(p)):
870 self._repo.ui.warn(_("copy failed: %s is not a file or a "
872 self._repo.ui.warn(_("copy failed: %s is not a file or a "
871 "symbolic link\n") % dest)
873 "symbolic link\n") % dest)
872 else:
874 else:
873 wlock = self._repo.wlock()
875 wlock = self._repo.wlock()
874 try:
876 try:
875 if self._repo.dirstate[dest] in '?r':
877 if self._repo.dirstate[dest] in '?r':
876 self._repo.dirstate.add(dest)
878 self._repo.dirstate.add(dest)
877 self._repo.dirstate.copy(source, dest)
879 self._repo.dirstate.copy(source, dest)
878 finally:
880 finally:
879 wlock.release()
881 wlock.release()
880
882
881 class workingfilectx(filectx):
883 class workingfilectx(filectx):
882 """A workingfilectx object makes access to data related to a particular
884 """A workingfilectx object makes access to data related to a particular
883 file in the working directory convenient."""
885 file in the working directory convenient."""
884 def __init__(self, repo, path, filelog=None, workingctx=None):
886 def __init__(self, repo, path, filelog=None, workingctx=None):
885 """changeid can be a changeset revision, node, or tag.
887 """changeid can be a changeset revision, node, or tag.
886 fileid can be a file revision or node."""
888 fileid can be a file revision or node."""
887 self._repo = repo
889 self._repo = repo
888 self._path = path
890 self._path = path
889 self._changeid = None
891 self._changeid = None
890 self._filerev = self._filenode = None
892 self._filerev = self._filenode = None
891
893
892 if filelog:
894 if filelog:
893 self._filelog = filelog
895 self._filelog = filelog
894 if workingctx:
896 if workingctx:
895 self._changectx = workingctx
897 self._changectx = workingctx
896
898
897 @propertycache
899 @propertycache
898 def _changectx(self):
900 def _changectx(self):
899 return workingctx(self._repo)
901 return workingctx(self._repo)
900
902
901 def __nonzero__(self):
903 def __nonzero__(self):
902 return True
904 return True
903
905
904 def __str__(self):
906 def __str__(self):
905 return "%s@%s" % (self.path(), self._changectx)
907 return "%s@%s" % (self.path(), self._changectx)
906
908
907 def __repr__(self):
909 def __repr__(self):
908 return "<workingfilectx %s>" % str(self)
910 return "<workingfilectx %s>" % str(self)
909
911
910 def data(self):
912 def data(self):
911 return self._repo.wread(self._path)
913 return self._repo.wread(self._path)
912 def renamed(self):
914 def renamed(self):
913 rp = self._repo.dirstate.copied(self._path)
915 rp = self._repo.dirstate.copied(self._path)
914 if not rp:
916 if not rp:
915 return None
917 return None
916 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
918 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
917
919
918 def parents(self):
920 def parents(self):
919 '''return parent filectxs, following copies if necessary'''
921 '''return parent filectxs, following copies if necessary'''
920 def filenode(ctx, path):
922 def filenode(ctx, path):
921 return ctx._manifest.get(path, nullid)
923 return ctx._manifest.get(path, nullid)
922
924
923 path = self._path
925 path = self._path
924 fl = self._filelog
926 fl = self._filelog
925 pcl = self._changectx._parents
927 pcl = self._changectx._parents
926 renamed = self.renamed()
928 renamed = self.renamed()
927
929
928 if renamed:
930 if renamed:
929 pl = [renamed + (None,)]
931 pl = [renamed + (None,)]
930 else:
932 else:
931 pl = [(path, filenode(pcl[0], path), fl)]
933 pl = [(path, filenode(pcl[0], path), fl)]
932
934
933 for pc in pcl[1:]:
935 for pc in pcl[1:]:
934 pl.append((path, filenode(pc, path), fl))
936 pl.append((path, filenode(pc, path), fl))
935
937
936 return [filectx(self._repo, p, fileid=n, filelog=l)
938 return [filectx(self._repo, p, fileid=n, filelog=l)
937 for p, n, l in pl if n != nullid]
939 for p, n, l in pl if n != nullid]
938
940
939 def children(self):
941 def children(self):
940 return []
942 return []
941
943
942 def size(self):
944 def size(self):
943 return os.lstat(self._repo.wjoin(self._path)).st_size
945 return os.lstat(self._repo.wjoin(self._path)).st_size
944 def date(self):
946 def date(self):
945 t, tz = self._changectx.date()
947 t, tz = self._changectx.date()
946 try:
948 try:
947 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
949 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
948 except OSError, err:
950 except OSError, err:
949 if err.errno != errno.ENOENT:
951 if err.errno != errno.ENOENT:
950 raise
952 raise
951 return (t, tz)
953 return (t, tz)
952
954
953 def cmp(self, fctx):
955 def cmp(self, fctx):
954 """compare with other file context
956 """compare with other file context
955
957
956 returns True if different than fctx.
958 returns True if different than fctx.
957 """
959 """
958 # fctx should be a filectx (not a wfctx)
960 # fctx should be a filectx (not a wfctx)
959 # invert comparison to reuse the same code path
961 # invert comparison to reuse the same code path
960 return fctx.cmp(self)
962 return fctx.cmp(self)
961
963
962 class memctx(object):
964 class memctx(object):
963 """Use memctx to perform in-memory commits via localrepo.commitctx().
965 """Use memctx to perform in-memory commits via localrepo.commitctx().
964
966
965 Revision information is supplied at initialization time while
967 Revision information is supplied at initialization time while
966 related files data and is made available through a callback
968 related files data and is made available through a callback
967 mechanism. 'repo' is the current localrepo, 'parents' is a
969 mechanism. 'repo' is the current localrepo, 'parents' is a
968 sequence of two parent revisions identifiers (pass None for every
970 sequence of two parent revisions identifiers (pass None for every
969 missing parent), 'text' is the commit message and 'files' lists
971 missing parent), 'text' is the commit message and 'files' lists
970 names of files touched by the revision (normalized and relative to
972 names of files touched by the revision (normalized and relative to
971 repository root).
973 repository root).
972
974
973 filectxfn(repo, memctx, path) is a callable receiving the
975 filectxfn(repo, memctx, path) is a callable receiving the
974 repository, the current memctx object and the normalized path of
976 repository, the current memctx object and the normalized path of
975 requested file, relative to repository root. It is fired by the
977 requested file, relative to repository root. It is fired by the
976 commit function for every file in 'files', but calls order is
978 commit function for every file in 'files', but calls order is
977 undefined. If the file is available in the revision being
979 undefined. If the file is available in the revision being
978 committed (updated or added), filectxfn returns a memfilectx
980 committed (updated or added), filectxfn returns a memfilectx
979 object. If the file was removed, filectxfn raises an
981 object. If the file was removed, filectxfn raises an
980 IOError. Moved files are represented by marking the source file
982 IOError. Moved files are represented by marking the source file
981 removed and the new file added with copy information (see
983 removed and the new file added with copy information (see
982 memfilectx).
984 memfilectx).
983
985
984 user receives the committer name and defaults to current
986 user receives the committer name and defaults to current
985 repository username, date is the commit date in any format
987 repository username, date is the commit date in any format
986 supported by util.parsedate() and defaults to current date, extra
988 supported by util.parsedate() and defaults to current date, extra
987 is a dictionary of metadata or is left empty.
989 is a dictionary of metadata or is left empty.
988 """
990 """
989 def __init__(self, repo, parents, text, files, filectxfn, user=None,
991 def __init__(self, repo, parents, text, files, filectxfn, user=None,
990 date=None, extra=None):
992 date=None, extra=None):
991 self._repo = repo
993 self._repo = repo
992 self._rev = None
994 self._rev = None
993 self._node = None
995 self._node = None
994 self._text = text
996 self._text = text
995 self._date = date and util.parsedate(date) or util.makedate()
997 self._date = date and util.parsedate(date) or util.makedate()
996 self._user = user
998 self._user = user
997 parents = [(p or nullid) for p in parents]
999 parents = [(p or nullid) for p in parents]
998 p1, p2 = parents
1000 p1, p2 = parents
999 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1001 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1000 files = sorted(set(files))
1002 files = sorted(set(files))
1001 self._status = [files, [], [], [], []]
1003 self._status = [files, [], [], [], []]
1002 self._filectxfn = filectxfn
1004 self._filectxfn = filectxfn
1003
1005
1004 self._extra = extra and extra.copy() or {}
1006 self._extra = extra and extra.copy() or {}
1005 if 'branch' not in self._extra:
1007 if 'branch' not in self._extra:
1006 self._extra['branch'] = 'default'
1008 self._extra['branch'] = 'default'
1007 elif self._extra.get('branch') == '':
1009 elif self._extra.get('branch') == '':
1008 self._extra['branch'] = 'default'
1010 self._extra['branch'] = 'default'
1009
1011
1010 def __str__(self):
1012 def __str__(self):
1011 return str(self._parents[0]) + "+"
1013 return str(self._parents[0]) + "+"
1012
1014
1013 def __int__(self):
1015 def __int__(self):
1014 return self._rev
1016 return self._rev
1015
1017
1016 def __nonzero__(self):
1018 def __nonzero__(self):
1017 return True
1019 return True
1018
1020
1019 def __getitem__(self, key):
1021 def __getitem__(self, key):
1020 return self.filectx(key)
1022 return self.filectx(key)
1021
1023
1022 def p1(self):
1024 def p1(self):
1023 return self._parents[0]
1025 return self._parents[0]
1024 def p2(self):
1026 def p2(self):
1025 return self._parents[1]
1027 return self._parents[1]
1026
1028
1027 def user(self):
1029 def user(self):
1028 return self._user or self._repo.ui.username()
1030 return self._user or self._repo.ui.username()
1029 def date(self):
1031 def date(self):
1030 return self._date
1032 return self._date
1031 def description(self):
1033 def description(self):
1032 return self._text
1034 return self._text
1033 def files(self):
1035 def files(self):
1034 return self.modified()
1036 return self.modified()
1035 def modified(self):
1037 def modified(self):
1036 return self._status[0]
1038 return self._status[0]
1037 def added(self):
1039 def added(self):
1038 return self._status[1]
1040 return self._status[1]
1039 def removed(self):
1041 def removed(self):
1040 return self._status[2]
1042 return self._status[2]
1041 def deleted(self):
1043 def deleted(self):
1042 return self._status[3]
1044 return self._status[3]
1043 def unknown(self):
1045 def unknown(self):
1044 return self._status[4]
1046 return self._status[4]
1045 def ignored(self):
1047 def ignored(self):
1046 return self._status[5]
1048 return self._status[5]
1047 def clean(self):
1049 def clean(self):
1048 return self._status[6]
1050 return self._status[6]
1049 def branch(self):
1051 def branch(self):
1050 return encoding.tolocal(self._extra['branch'])
1052 return encoding.tolocal(self._extra['branch'])
1051 def extra(self):
1053 def extra(self):
1052 return self._extra
1054 return self._extra
1053 def flags(self, f):
1055 def flags(self, f):
1054 return self[f].flags()
1056 return self[f].flags()
1055
1057
1056 def parents(self):
1058 def parents(self):
1057 """return contexts for each parent changeset"""
1059 """return contexts for each parent changeset"""
1058 return self._parents
1060 return self._parents
1059
1061
1060 def filectx(self, path, filelog=None):
1062 def filectx(self, path, filelog=None):
1061 """get a file context from the working directory"""
1063 """get a file context from the working directory"""
1062 return self._filectxfn(self._repo, self, path)
1064 return self._filectxfn(self._repo, self, path)
1063
1065
1064 def commit(self):
1066 def commit(self):
1065 """commit context to the repo"""
1067 """commit context to the repo"""
1066 return self._repo.commitctx(self)
1068 return self._repo.commitctx(self)
1067
1069
1068 class memfilectx(object):
1070 class memfilectx(object):
1069 """memfilectx represents an in-memory file to commit.
1071 """memfilectx represents an in-memory file to commit.
1070
1072
1071 See memctx for more details.
1073 See memctx for more details.
1072 """
1074 """
1073 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1075 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1074 """
1076 """
1075 path is the normalized file path relative to repository root.
1077 path is the normalized file path relative to repository root.
1076 data is the file content as a string.
1078 data is the file content as a string.
1077 islink is True if the file is a symbolic link.
1079 islink is True if the file is a symbolic link.
1078 isexec is True if the file is executable.
1080 isexec is True if the file is executable.
1079 copied is the source file path if current file was copied in the
1081 copied is the source file path if current file was copied in the
1080 revision being committed, or None."""
1082 revision being committed, or None."""
1081 self._path = path
1083 self._path = path
1082 self._data = data
1084 self._data = data
1083 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1085 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1084 self._copied = None
1086 self._copied = None
1085 if copied:
1087 if copied:
1086 self._copied = (copied, nullid)
1088 self._copied = (copied, nullid)
1087
1089
1088 def __nonzero__(self):
1090 def __nonzero__(self):
1089 return True
1091 return True
1090 def __str__(self):
1092 def __str__(self):
1091 return "%s@%s" % (self.path(), self._changectx)
1093 return "%s@%s" % (self.path(), self._changectx)
1092 def path(self):
1094 def path(self):
1093 return self._path
1095 return self._path
1094 def data(self):
1096 def data(self):
1095 return self._data
1097 return self._data
1096 def flags(self):
1098 def flags(self):
1097 return self._flags
1099 return self._flags
1098 def isexec(self):
1100 def isexec(self):
1099 return 'x' in self._flags
1101 return 'x' in self._flags
1100 def islink(self):
1102 def islink(self):
1101 return 'l' in self._flags
1103 return 'l' in self._flags
1102 def renamed(self):
1104 def renamed(self):
1103 return self._copied
1105 return self._copied
@@ -1,644 +1,648 b''
1 # dispatch.py - command dispatching for mercurial
1 # dispatch.py - command dispatching for mercurial
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 of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import os, sys, atexit, signal, pdb, socket, errno, shlex, time, traceback, re
9 import os, sys, atexit, signal, pdb, socket, errno, shlex, time, traceback, re
10 import util, commands, hg, fancyopts, extensions, hook, error
10 import util, commands, hg, fancyopts, extensions, hook, error
11 import cmdutil, encoding
11 import cmdutil, encoding
12 import ui as uimod
12 import ui as uimod
13
13
14 def run():
14 def run():
15 "run the command in sys.argv"
15 "run the command in sys.argv"
16 sys.exit(dispatch(sys.argv[1:]))
16 sys.exit(dispatch(sys.argv[1:]))
17
17
18 def dispatch(args):
18 def dispatch(args):
19 "run the command specified in args"
19 "run the command specified in args"
20 try:
20 try:
21 u = uimod.ui()
21 u = uimod.ui()
22 if '--traceback' in args:
22 if '--traceback' in args:
23 u.setconfig('ui', 'traceback', 'on')
23 u.setconfig('ui', 'traceback', 'on')
24 except util.Abort, inst:
24 except util.Abort, inst:
25 sys.stderr.write(_("abort: %s\n") % inst)
25 sys.stderr.write(_("abort: %s\n") % inst)
26 if inst.hint:
26 if inst.hint:
27 sys.stderr.write(_("(%s)\n") % inst.hint)
27 sys.stderr.write(_("(%s)\n") % inst.hint)
28 return -1
28 return -1
29 except error.ParseError, inst:
29 except error.ParseError, inst:
30 if len(inst.args) > 1:
30 if len(inst.args) > 1:
31 sys.stderr.write(_("hg: parse error at %s: %s\n") %
31 sys.stderr.write(_("hg: parse error at %s: %s\n") %
32 (inst.args[1], inst.args[0]))
32 (inst.args[1], inst.args[0]))
33 else:
33 else:
34 sys.stderr.write(_("hg: parse error: %s\n") % inst.args[0])
34 sys.stderr.write(_("hg: parse error: %s\n") % inst.args[0])
35 return -1
35 return -1
36 return _runcatch(u, args)
36 return _runcatch(u, args)
37
37
38 def _runcatch(ui, args):
38 def _runcatch(ui, args):
39 def catchterm(*args):
39 def catchterm(*args):
40 raise error.SignalInterrupt
40 raise error.SignalInterrupt
41
41
42 try:
42 try:
43 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
43 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
44 num = getattr(signal, name, None)
44 num = getattr(signal, name, None)
45 if num:
45 if num:
46 signal.signal(num, catchterm)
46 signal.signal(num, catchterm)
47 except ValueError:
47 except ValueError:
48 pass # happens if called in a thread
48 pass # happens if called in a thread
49
49
50 try:
50 try:
51 try:
51 try:
52 # enter the debugger before command execution
52 # enter the debugger before command execution
53 if '--debugger' in args:
53 if '--debugger' in args:
54 ui.warn(_("entering debugger - "
54 ui.warn(_("entering debugger - "
55 "type c to continue starting hg or h for help\n"))
55 "type c to continue starting hg or h for help\n"))
56 pdb.set_trace()
56 pdb.set_trace()
57 try:
57 try:
58 return _dispatch(ui, args)
58 return _dispatch(ui, args)
59 finally:
59 finally:
60 ui.flush()
60 ui.flush()
61 except:
61 except:
62 # enter the debugger when we hit an exception
62 # enter the debugger when we hit an exception
63 if '--debugger' in args:
63 if '--debugger' in args:
64 traceback.print_exc()
64 traceback.print_exc()
65 pdb.post_mortem(sys.exc_info()[2])
65 pdb.post_mortem(sys.exc_info()[2])
66 ui.traceback()
66 ui.traceback()
67 raise
67 raise
68
68
69 # Global exception handling, alphabetically
69 # Global exception handling, alphabetically
70 # Mercurial-specific first, followed by built-in and library exceptions
70 # Mercurial-specific first, followed by built-in and library exceptions
71 except error.AmbiguousCommand, inst:
71 except error.AmbiguousCommand, inst:
72 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
72 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
73 (inst.args[0], " ".join(inst.args[1])))
73 (inst.args[0], " ".join(inst.args[1])))
74 except error.ParseError, inst:
74 except error.ParseError, inst:
75 if len(inst.args) > 1:
75 if len(inst.args) > 1:
76 ui.warn(_("hg: parse error at %s: %s\n") %
76 ui.warn(_("hg: parse error at %s: %s\n") %
77 (inst.args[1], inst.args[0]))
77 (inst.args[1], inst.args[0]))
78 else:
78 else:
79 ui.warn(_("hg: parse error: %s\n") % inst.args[0])
79 ui.warn(_("hg: parse error: %s\n") % inst.args[0])
80 return -1
80 return -1
81 except error.LockHeld, inst:
81 except error.LockHeld, inst:
82 if inst.errno == errno.ETIMEDOUT:
82 if inst.errno == errno.ETIMEDOUT:
83 reason = _('timed out waiting for lock held by %s') % inst.locker
83 reason = _('timed out waiting for lock held by %s') % inst.locker
84 else:
84 else:
85 reason = _('lock held by %s') % inst.locker
85 reason = _('lock held by %s') % inst.locker
86 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
86 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
87 except error.LockUnavailable, inst:
87 except error.LockUnavailable, inst:
88 ui.warn(_("abort: could not lock %s: %s\n") %
88 ui.warn(_("abort: could not lock %s: %s\n") %
89 (inst.desc or inst.filename, inst.strerror))
89 (inst.desc or inst.filename, inst.strerror))
90 except error.CommandError, inst:
90 except error.CommandError, inst:
91 if inst.args[0]:
91 if inst.args[0]:
92 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
92 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
93 commands.help_(ui, inst.args[0])
93 commands.help_(ui, inst.args[0])
94 else:
94 else:
95 ui.warn(_("hg: %s\n") % inst.args[1])
95 ui.warn(_("hg: %s\n") % inst.args[1])
96 commands.help_(ui, 'shortlist')
96 commands.help_(ui, 'shortlist')
97 except error.RepoError, inst:
97 except error.RepoError, inst:
98 ui.warn(_("abort: %s!\n") % inst)
98 ui.warn(_("abort: %s!\n") % inst)
99 except error.ResponseError, inst:
99 except error.ResponseError, inst:
100 ui.warn(_("abort: %s") % inst.args[0])
100 ui.warn(_("abort: %s") % inst.args[0])
101 if not isinstance(inst.args[1], basestring):
101 if not isinstance(inst.args[1], basestring):
102 ui.warn(" %r\n" % (inst.args[1],))
102 ui.warn(" %r\n" % (inst.args[1],))
103 elif not inst.args[1]:
103 elif not inst.args[1]:
104 ui.warn(_(" empty string\n"))
104 ui.warn(_(" empty string\n"))
105 else:
105 else:
106 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
106 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
107 except error.RevlogError, inst:
107 except error.RevlogError, inst:
108 ui.warn(_("abort: %s!\n") % inst)
108 ui.warn(_("abort: %s!\n") % inst)
109 except error.SignalInterrupt:
109 except error.SignalInterrupt:
110 ui.warn(_("killed!\n"))
110 ui.warn(_("killed!\n"))
111 except error.UnknownCommand, inst:
111 except error.UnknownCommand, inst:
112 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
112 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
113 try:
113 try:
114 # check if the command is in a disabled extension
114 # check if the command is in a disabled extension
115 # (but don't check for extensions themselves)
115 # (but don't check for extensions themselves)
116 commands.help_(ui, inst.args[0], unknowncmd=True)
116 commands.help_(ui, inst.args[0], unknowncmd=True)
117 except error.UnknownCommand:
117 except error.UnknownCommand:
118 commands.help_(ui, 'shortlist')
118 commands.help_(ui, 'shortlist')
119 except util.Abort, inst:
119 except util.Abort, inst:
120 ui.warn(_("abort: %s\n") % inst)
120 ui.warn(_("abort: %s\n") % inst)
121 if inst.hint:
121 if inst.hint:
122 ui.warn(_("(%s)\n") % inst.hint)
122 ui.warn(_("(%s)\n") % inst.hint)
123 except ImportError, inst:
123 except ImportError, inst:
124 ui.warn(_("abort: %s!\n") % inst)
124 ui.warn(_("abort: %s!\n") % inst)
125 m = str(inst).split()[-1]
125 m = str(inst).split()[-1]
126 if m in "mpatch bdiff".split():
126 if m in "mpatch bdiff".split():
127 ui.warn(_("(did you forget to compile extensions?)\n"))
127 ui.warn(_("(did you forget to compile extensions?)\n"))
128 elif m in "zlib".split():
128 elif m in "zlib".split():
129 ui.warn(_("(is your Python install correct?)\n"))
129 ui.warn(_("(is your Python install correct?)\n"))
130 except IOError, inst:
130 except IOError, inst:
131 if hasattr(inst, "code"):
131 if hasattr(inst, "code"):
132 ui.warn(_("abort: %s\n") % inst)
132 ui.warn(_("abort: %s\n") % inst)
133 elif hasattr(inst, "reason"):
133 elif hasattr(inst, "reason"):
134 try: # usually it is in the form (errno, strerror)
134 try: # usually it is in the form (errno, strerror)
135 reason = inst.reason.args[1]
135 reason = inst.reason.args[1]
136 except: # it might be anything, for example a string
136 except: # it might be anything, for example a string
137 reason = inst.reason
137 reason = inst.reason
138 ui.warn(_("abort: error: %s\n") % reason)
138 ui.warn(_("abort: error: %s\n") % reason)
139 elif hasattr(inst, "args") and inst.args[0] == errno.EPIPE:
139 elif hasattr(inst, "args") and inst.args[0] == errno.EPIPE:
140 if ui.debugflag:
140 if ui.debugflag:
141 ui.warn(_("broken pipe\n"))
141 ui.warn(_("broken pipe\n"))
142 elif getattr(inst, "strerror", None):
142 elif getattr(inst, "strerror", None):
143 if getattr(inst, "filename", None):
143 if getattr(inst, "filename", None):
144 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
144 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
145 else:
145 else:
146 ui.warn(_("abort: %s\n") % inst.strerror)
146 ui.warn(_("abort: %s\n") % inst.strerror)
147 else:
147 else:
148 raise
148 raise
149 except OSError, inst:
149 except OSError, inst:
150 if getattr(inst, "filename", None):
150 if getattr(inst, "filename", None):
151 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
151 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
152 else:
152 else:
153 ui.warn(_("abort: %s\n") % inst.strerror)
153 ui.warn(_("abort: %s\n") % inst.strerror)
154 except KeyboardInterrupt:
154 except KeyboardInterrupt:
155 try:
155 try:
156 ui.warn(_("interrupted!\n"))
156 ui.warn(_("interrupted!\n"))
157 except IOError, inst:
157 except IOError, inst:
158 if inst.errno == errno.EPIPE:
158 if inst.errno == errno.EPIPE:
159 if ui.debugflag:
159 if ui.debugflag:
160 ui.warn(_("\nbroken pipe\n"))
160 ui.warn(_("\nbroken pipe\n"))
161 else:
161 else:
162 raise
162 raise
163 except MemoryError:
163 except MemoryError:
164 ui.warn(_("abort: out of memory\n"))
164 ui.warn(_("abort: out of memory\n"))
165 except SystemExit, inst:
165 except SystemExit, inst:
166 # Commands shouldn't sys.exit directly, but give a return code.
166 # Commands shouldn't sys.exit directly, but give a return code.
167 # Just in case catch this and and pass exit code to caller.
167 # Just in case catch this and and pass exit code to caller.
168 return inst.code
168 return inst.code
169 except socket.error, inst:
169 except socket.error, inst:
170 ui.warn(_("abort: %s\n") % inst.args[-1])
170 ui.warn(_("abort: %s\n") % inst.args[-1])
171 except:
171 except:
172 ui.warn(_("** unknown exception encountered,"
172 ui.warn(_("** unknown exception encountered,"
173 " please report by visiting\n"))
173 " please report by visiting\n"))
174 ui.warn(_("** http://mercurial.selenic.com/wiki/BugTracker\n"))
174 ui.warn(_("** http://mercurial.selenic.com/wiki/BugTracker\n"))
175 ui.warn(_("** Python %s\n") % sys.version.replace('\n', ''))
175 ui.warn(_("** Python %s\n") % sys.version.replace('\n', ''))
176 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
176 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
177 % util.version())
177 % util.version())
178 ui.warn(_("** Extensions loaded: %s\n")
178 ui.warn(_("** Extensions loaded: %s\n")
179 % ", ".join([x[0] for x in extensions.extensions()]))
179 % ", ".join([x[0] for x in extensions.extensions()]))
180 raise
180 raise
181
181
182 return -1
182 return -1
183
183
184 def aliasargs(fn):
184 def aliasargs(fn):
185 if hasattr(fn, 'args'):
185 if hasattr(fn, 'args'):
186 return fn.args
186 return fn.args
187 return []
187 return []
188
188
189 class cmdalias(object):
189 class cmdalias(object):
190 def __init__(self, name, definition, cmdtable):
190 def __init__(self, name, definition, cmdtable):
191 self.name = self.cmd = name
191 self.name = self.cmd = name
192 self.cmdname = ''
192 self.cmdname = ''
193 self.definition = definition
193 self.definition = definition
194 self.args = []
194 self.args = []
195 self.opts = []
195 self.opts = []
196 self.help = ''
196 self.help = ''
197 self.norepo = True
197 self.norepo = True
198 self.badalias = False
198 self.badalias = False
199
199
200 try:
200 try:
201 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
201 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
202 for alias, e in cmdtable.iteritems():
202 for alias, e in cmdtable.iteritems():
203 if e is entry:
203 if e is entry:
204 self.cmd = alias
204 self.cmd = alias
205 break
205 break
206 self.shadows = True
206 self.shadows = True
207 except error.UnknownCommand:
207 except error.UnknownCommand:
208 self.shadows = False
208 self.shadows = False
209
209
210 if not self.definition:
210 if not self.definition:
211 def fn(ui, *args):
211 def fn(ui, *args):
212 ui.warn(_("no definition for alias '%s'\n") % self.name)
212 ui.warn(_("no definition for alias '%s'\n") % self.name)
213 return 1
213 return 1
214 self.fn = fn
214 self.fn = fn
215 self.badalias = True
215 self.badalias = True
216
216
217 return
217 return
218
218
219 if self.definition.startswith('!'):
219 if self.definition.startswith('!'):
220 self.shell = True
220 self.shell = True
221 def fn(ui, *args):
221 def fn(ui, *args):
222 env = {'HG_ARGS': ' '.join((self.name,) + args)}
222 env = {'HG_ARGS': ' '.join((self.name,) + args)}
223 def _checkvar(m):
223 def _checkvar(m):
224 if int(m.groups()[0]) <= len(args):
224 if int(m.groups()[0]) <= len(args):
225 return m.group()
225 return m.group()
226 else:
226 else:
227 return ''
227 return ''
228 cmd = re.sub(r'\$(\d+)', _checkvar, self.definition[1:])
228 cmd = re.sub(r'\$(\d+)', _checkvar, self.definition[1:])
229 replace = dict((str(i + 1), arg) for i, arg in enumerate(args))
229 replace = dict((str(i + 1), arg) for i, arg in enumerate(args))
230 replace['0'] = self.name
230 replace['0'] = self.name
231 replace['@'] = ' '.join(args)
231 replace['@'] = ' '.join(args)
232 cmd = util.interpolate(r'\$', replace, cmd)
232 cmd = util.interpolate(r'\$', replace, cmd)
233 return util.system(cmd, environ=env)
233 return util.system(cmd, environ=env)
234 self.fn = fn
234 self.fn = fn
235 return
235 return
236
236
237 args = shlex.split(self.definition)
237 args = shlex.split(self.definition)
238 self.cmdname = cmd = args.pop(0)
238 self.cmdname = cmd = args.pop(0)
239 args = map(util.expandpath, args)
239 args = map(util.expandpath, args)
240
240
241 for invalidarg in ("--cwd", "-R", "--repository", "--repo"):
241 for invalidarg in ("--cwd", "-R", "--repository", "--repo"):
242 if _earlygetopt([invalidarg], args):
242 if _earlygetopt([invalidarg], args):
243 def fn(ui, *args):
243 def fn(ui, *args):
244 ui.warn(_("error in definition for alias '%s': %s may only "
244 ui.warn(_("error in definition for alias '%s': %s may only "
245 "be given on the command line\n")
245 "be given on the command line\n")
246 % (self.name, invalidarg))
246 % (self.name, invalidarg))
247 return 1
247 return 1
248
248
249 self.fn = fn
249 self.fn = fn
250 self.badalias = True
250 self.badalias = True
251 return
251 return
252
252
253 try:
253 try:
254 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
254 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
255 if len(tableentry) > 2:
255 if len(tableentry) > 2:
256 self.fn, self.opts, self.help = tableentry
256 self.fn, self.opts, self.help = tableentry
257 else:
257 else:
258 self.fn, self.opts = tableentry
258 self.fn, self.opts = tableentry
259
259
260 self.args = aliasargs(self.fn) + args
260 self.args = aliasargs(self.fn) + args
261 if cmd not in commands.norepo.split(' '):
261 if cmd not in commands.norepo.split(' '):
262 self.norepo = False
262 self.norepo = False
263 if self.help.startswith("hg " + cmd):
263 if self.help.startswith("hg " + cmd):
264 # drop prefix in old-style help lines so hg shows the alias
264 # drop prefix in old-style help lines so hg shows the alias
265 self.help = self.help[4 + len(cmd):]
265 self.help = self.help[4 + len(cmd):]
266 self.__doc__ = self.fn.__doc__
266 self.__doc__ = self.fn.__doc__
267
267
268 except error.UnknownCommand:
268 except error.UnknownCommand:
269 def fn(ui, *args):
269 def fn(ui, *args):
270 ui.warn(_("alias '%s' resolves to unknown command '%s'\n") \
270 ui.warn(_("alias '%s' resolves to unknown command '%s'\n") \
271 % (self.name, cmd))
271 % (self.name, cmd))
272 try:
272 try:
273 # check if the command is in a disabled extension
273 # check if the command is in a disabled extension
274 commands.help_(ui, cmd, unknowncmd=True)
274 commands.help_(ui, cmd, unknowncmd=True)
275 except error.UnknownCommand:
275 except error.UnknownCommand:
276 pass
276 pass
277 return 1
277 return 1
278 self.fn = fn
278 self.fn = fn
279 self.badalias = True
279 self.badalias = True
280 except error.AmbiguousCommand:
280 except error.AmbiguousCommand:
281 def fn(ui, *args):
281 def fn(ui, *args):
282 ui.warn(_("alias '%s' resolves to ambiguous command '%s'\n") \
282 ui.warn(_("alias '%s' resolves to ambiguous command '%s'\n") \
283 % (self.name, cmd))
283 % (self.name, cmd))
284 return 1
284 return 1
285 self.fn = fn
285 self.fn = fn
286 self.badalias = True
286 self.badalias = True
287
287
288 def __call__(self, ui, *args, **opts):
288 def __call__(self, ui, *args, **opts):
289 if self.shadows:
289 if self.shadows:
290 ui.debug("alias '%s' shadows command '%s'\n" %
290 ui.debug("alias '%s' shadows command '%s'\n" %
291 (self.name, self.cmdname))
291 (self.name, self.cmdname))
292
292
293 if self.definition.startswith('!'):
293 if self.definition.startswith('!'):
294 return self.fn(ui, *args, **opts)
294 return self.fn(ui, *args, **opts)
295 else:
295 else:
296 try:
296 try:
297 util.checksignature(self.fn)(ui, *args, **opts)
297 util.checksignature(self.fn)(ui, *args, **opts)
298 except error.SignatureError:
298 except error.SignatureError:
299 args = ' '.join([self.cmdname] + self.args)
299 args = ' '.join([self.cmdname] + self.args)
300 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
300 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
301 raise
301 raise
302
302
303 def addaliases(ui, cmdtable):
303 def addaliases(ui, cmdtable):
304 # aliases are processed after extensions have been loaded, so they
304 # aliases are processed after extensions have been loaded, so they
305 # may use extension commands. Aliases can also use other alias definitions,
305 # may use extension commands. Aliases can also use other alias definitions,
306 # but only if they have been defined prior to the current definition.
306 # but only if they have been defined prior to the current definition.
307 for alias, definition in ui.configitems('alias'):
307 for alias, definition in ui.configitems('alias'):
308 aliasdef = cmdalias(alias, definition, cmdtable)
308 aliasdef = cmdalias(alias, definition, cmdtable)
309 cmdtable[aliasdef.cmd] = (aliasdef, aliasdef.opts, aliasdef.help)
309 cmdtable[aliasdef.cmd] = (aliasdef, aliasdef.opts, aliasdef.help)
310 if aliasdef.norepo:
310 if aliasdef.norepo:
311 commands.norepo += ' %s' % alias
311 commands.norepo += ' %s' % alias
312
312
313 def _parse(ui, args):
313 def _parse(ui, args):
314 options = {}
314 options = {}
315 cmdoptions = {}
315 cmdoptions = {}
316
316
317 try:
317 try:
318 args = fancyopts.fancyopts(args, commands.globalopts, options)
318 args = fancyopts.fancyopts(args, commands.globalopts, options)
319 except fancyopts.getopt.GetoptError, inst:
319 except fancyopts.getopt.GetoptError, inst:
320 raise error.CommandError(None, inst)
320 raise error.CommandError(None, inst)
321
321
322 if args:
322 if args:
323 cmd, args = args[0], args[1:]
323 cmd, args = args[0], args[1:]
324 aliases, entry = cmdutil.findcmd(cmd, commands.table,
324 aliases, entry = cmdutil.findcmd(cmd, commands.table,
325 ui.config("ui", "strict"))
325 ui.config("ui", "strict"))
326 cmd = aliases[0]
326 cmd = aliases[0]
327 args = aliasargs(entry[0]) + args
327 args = aliasargs(entry[0]) + args
328 defaults = ui.config("defaults", cmd)
328 defaults = ui.config("defaults", cmd)
329 if defaults:
329 if defaults:
330 args = map(util.expandpath, shlex.split(defaults)) + args
330 args = map(util.expandpath, shlex.split(defaults)) + args
331 c = list(entry[1])
331 c = list(entry[1])
332 else:
332 else:
333 cmd = None
333 cmd = None
334 c = []
334 c = []
335
335
336 # combine global options into local
336 # combine global options into local
337 for o in commands.globalopts:
337 for o in commands.globalopts:
338 c.append((o[0], o[1], options[o[1]], o[3]))
338 c.append((o[0], o[1], options[o[1]], o[3]))
339
339
340 try:
340 try:
341 args = fancyopts.fancyopts(args, c, cmdoptions, True)
341 args = fancyopts.fancyopts(args, c, cmdoptions, True)
342 except fancyopts.getopt.GetoptError, inst:
342 except fancyopts.getopt.GetoptError, inst:
343 raise error.CommandError(cmd, inst)
343 raise error.CommandError(cmd, inst)
344
344
345 # separate global options back out
345 # separate global options back out
346 for o in commands.globalopts:
346 for o in commands.globalopts:
347 n = o[1]
347 n = o[1]
348 options[n] = cmdoptions[n]
348 options[n] = cmdoptions[n]
349 del cmdoptions[n]
349 del cmdoptions[n]
350
350
351 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
351 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
352
352
353 def _parseconfig(ui, config):
353 def _parseconfig(ui, config):
354 """parse the --config options from the command line"""
354 """parse the --config options from the command line"""
355 for cfg in config:
355 for cfg in config:
356 try:
356 try:
357 name, value = cfg.split('=', 1)
357 name, value = cfg.split('=', 1)
358 section, name = name.split('.', 1)
358 section, name = name.split('.', 1)
359 if not section or not name:
359 if not section or not name:
360 raise IndexError
360 raise IndexError
361 ui.setconfig(section, name, value)
361 ui.setconfig(section, name, value)
362 except (IndexError, ValueError):
362 except (IndexError, ValueError):
363 raise util.Abort(_('malformed --config option: %r '
363 raise util.Abort(_('malformed --config option: %r '
364 '(use --config section.name=value)') % cfg)
364 '(use --config section.name=value)') % cfg)
365
365
366 def _earlygetopt(aliases, args):
366 def _earlygetopt(aliases, args):
367 """Return list of values for an option (or aliases).
367 """Return list of values for an option (or aliases).
368
368
369 The values are listed in the order they appear in args.
369 The values are listed in the order they appear in args.
370 The options and values are removed from args.
370 The options and values are removed from args.
371 """
371 """
372 try:
372 try:
373 argcount = args.index("--")
373 argcount = args.index("--")
374 except ValueError:
374 except ValueError:
375 argcount = len(args)
375 argcount = len(args)
376 shortopts = [opt for opt in aliases if len(opt) == 2]
376 shortopts = [opt for opt in aliases if len(opt) == 2]
377 values = []
377 values = []
378 pos = 0
378 pos = 0
379 while pos < argcount:
379 while pos < argcount:
380 if args[pos] in aliases:
380 if args[pos] in aliases:
381 if pos + 1 >= argcount:
381 if pos + 1 >= argcount:
382 # ignore and let getopt report an error if there is no value
382 # ignore and let getopt report an error if there is no value
383 break
383 break
384 del args[pos]
384 del args[pos]
385 values.append(args.pop(pos))
385 values.append(args.pop(pos))
386 argcount -= 2
386 argcount -= 2
387 elif args[pos][:2] in shortopts:
387 elif args[pos][:2] in shortopts:
388 # short option can have no following space, e.g. hg log -Rfoo
388 # short option can have no following space, e.g. hg log -Rfoo
389 values.append(args.pop(pos)[2:])
389 values.append(args.pop(pos)[2:])
390 argcount -= 1
390 argcount -= 1
391 else:
391 else:
392 pos += 1
392 pos += 1
393 return values
393 return values
394
394
395 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
395 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
396 # run pre-hook, and abort if it fails
396 # run pre-hook, and abort if it fails
397 ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs),
397 ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs),
398 pats=cmdpats, opts=cmdoptions)
398 pats=cmdpats, opts=cmdoptions)
399 if ret:
399 if ret:
400 return ret
400 return ret
401 ret = _runcommand(ui, options, cmd, d)
401 ret = _runcommand(ui, options, cmd, d)
402 # run post-hook, passing command result
402 # run post-hook, passing command result
403 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
403 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
404 result=ret, pats=cmdpats, opts=cmdoptions)
404 result=ret, pats=cmdpats, opts=cmdoptions)
405 return ret
405 return ret
406
406
407 def _getlocal(ui, rpath):
407 def _getlocal(ui, rpath):
408 """Return (path, local ui object) for the given target path.
408 """Return (path, local ui object) for the given target path.
409
409
410 Takes paths in [cwd]/.hg/hgrc into account."
410 Takes paths in [cwd]/.hg/hgrc into account."
411 """
411 """
412 try:
412 try:
413 wd = os.getcwd()
413 wd = os.getcwd()
414 except OSError, e:
414 except OSError, e:
415 raise util.Abort(_("error getting current working directory: %s") %
415 raise util.Abort(_("error getting current working directory: %s") %
416 e.strerror)
416 e.strerror)
417 path = cmdutil.findrepo(wd) or ""
417 path = cmdutil.findrepo(wd) or ""
418 if not path:
418 if not path:
419 lui = ui
419 lui = ui
420 else:
420 else:
421 lui = ui.copy()
421 lui = ui.copy()
422 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
422 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
423
423
424 if rpath:
424 if rpath:
425 path = lui.expandpath(rpath[-1])
425 path = lui.expandpath(rpath[-1])
426 lui = ui.copy()
426 lui = ui.copy()
427 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
427 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
428
428
429 return path, lui
429 return path, lui
430
430
431 def _checkshellalias(ui, args):
431 def _checkshellalias(ui, args):
432 cwd = os.getcwd()
432 cwd = os.getcwd()
433 norepo = commands.norepo
433 norepo = commands.norepo
434 options = {}
434 options = {}
435
435
436 try:
436 try:
437 args = fancyopts.fancyopts(args, commands.globalopts, options)
437 args = fancyopts.fancyopts(args, commands.globalopts, options)
438 except fancyopts.getopt.GetoptError:
438 except fancyopts.getopt.GetoptError:
439 return
439 return
440
440
441 if not args:
441 if not args:
442 return
442 return
443
443
444 _parseconfig(ui, options['config'])
444 _parseconfig(ui, options['config'])
445 if options['cwd']:
445 if options['cwd']:
446 os.chdir(options['cwd'])
446 os.chdir(options['cwd'])
447
447
448 path, lui = _getlocal(ui, [options['repository']])
448 path, lui = _getlocal(ui, [options['repository']])
449
449
450 cmdtable = commands.table.copy()
450 cmdtable = commands.table.copy()
451 addaliases(lui, cmdtable)
451 addaliases(lui, cmdtable)
452
452
453 cmd = args[0]
453 cmd = args[0]
454 try:
454 try:
455 aliases, entry = cmdutil.findcmd(cmd, cmdtable, lui.config("ui", "strict"))
455 aliases, entry = cmdutil.findcmd(cmd, cmdtable, lui.config("ui", "strict"))
456 except (error.AmbiguousCommand, error.UnknownCommand):
456 except (error.AmbiguousCommand, error.UnknownCommand):
457 commands.norepo = norepo
457 commands.norepo = norepo
458 os.chdir(cwd)
458 os.chdir(cwd)
459 return
459 return
460
460
461 cmd = aliases[0]
461 cmd = aliases[0]
462 fn = entry[0]
462 fn = entry[0]
463
463
464 if cmd and hasattr(fn, 'shell'):
464 if cmd and hasattr(fn, 'shell'):
465 d = lambda: fn(ui, *args[1:])
465 d = lambda: fn(ui, *args[1:])
466 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d, [], {})
466 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d, [], {})
467
467
468 commands.norepo = norepo
468 commands.norepo = norepo
469 os.chdir(cwd)
469 os.chdir(cwd)
470
470
471 _loaded = set()
471 _loaded = set()
472 def _dispatch(ui, args):
472 def _dispatch(ui, args):
473 shellaliasfn = _checkshellalias(ui, args)
473 shellaliasfn = _checkshellalias(ui, args)
474 if shellaliasfn:
474 if shellaliasfn:
475 return shellaliasfn()
475 return shellaliasfn()
476
476
477 # read --config before doing anything else
477 # read --config before doing anything else
478 # (e.g. to change trust settings for reading .hg/hgrc)
478 # (e.g. to change trust settings for reading .hg/hgrc)
479 _parseconfig(ui, _earlygetopt(['--config'], args))
479 _parseconfig(ui, _earlygetopt(['--config'], args))
480
480
481 # check for cwd
481 # check for cwd
482 cwd = _earlygetopt(['--cwd'], args)
482 cwd = _earlygetopt(['--cwd'], args)
483 if cwd:
483 if cwd:
484 os.chdir(cwd[-1])
484 os.chdir(cwd[-1])
485
485
486 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
486 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
487 path, lui = _getlocal(ui, rpath)
487 path, lui = _getlocal(ui, rpath)
488
488
489 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
489 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
490 # reposetup. Programs like TortoiseHg will call _dispatch several
490 # reposetup. Programs like TortoiseHg will call _dispatch several
491 # times so we keep track of configured extensions in _loaded.
491 # times so we keep track of configured extensions in _loaded.
492 extensions.loadall(lui)
492 extensions.loadall(lui)
493 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
493 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
494 # Propagate any changes to lui.__class__ by extensions
494 # Propagate any changes to lui.__class__ by extensions
495 ui.__class__ = lui.__class__
495 ui.__class__ = lui.__class__
496
496
497 # (uisetup and extsetup are handled in extensions.loadall)
497 # (uisetup and extsetup are handled in extensions.loadall)
498
498
499 for name, module in exts:
499 for name, module in exts:
500 cmdtable = getattr(module, 'cmdtable', {})
500 cmdtable = getattr(module, 'cmdtable', {})
501 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
501 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
502 if overrides:
502 if overrides:
503 ui.warn(_("extension '%s' overrides commands: %s\n")
503 ui.warn(_("extension '%s' overrides commands: %s\n")
504 % (name, " ".join(overrides)))
504 % (name, " ".join(overrides)))
505 commands.table.update(cmdtable)
505 commands.table.update(cmdtable)
506 _loaded.add(name)
506 _loaded.add(name)
507
507
508 # (reposetup is handled in hg.repository)
508 # (reposetup is handled in hg.repository)
509
509
510 addaliases(lui, commands.table)
510 addaliases(lui, commands.table)
511
511
512 # check for fallback encoding
512 # check for fallback encoding
513 fallback = lui.config('ui', 'fallbackencoding')
513 fallback = lui.config('ui', 'fallbackencoding')
514 if fallback:
514 if fallback:
515 encoding.fallbackencoding = fallback
515 encoding.fallbackencoding = fallback
516
516
517 fullargs = args
517 fullargs = args
518 cmd, func, args, options, cmdoptions = _parse(lui, args)
518 cmd, func, args, options, cmdoptions = _parse(lui, args)
519
519
520 if options["config"]:
520 if options["config"]:
521 raise util.Abort(_("option --config may not be abbreviated!"))
521 raise util.Abort(_("option --config may not be abbreviated!"))
522 if options["cwd"]:
522 if options["cwd"]:
523 raise util.Abort(_("option --cwd may not be abbreviated!"))
523 raise util.Abort(_("option --cwd may not be abbreviated!"))
524 if options["repository"]:
524 if options["repository"]:
525 raise util.Abort(_(
525 raise util.Abort(_(
526 "Option -R has to be separated from other options (e.g. not -qR) "
526 "Option -R has to be separated from other options (e.g. not -qR) "
527 "and --repository may only be abbreviated as --repo!"))
527 "and --repository may only be abbreviated as --repo!"))
528
528
529 if options["encoding"]:
529 if options["encoding"]:
530 encoding.encoding = options["encoding"]
530 encoding.encoding = options["encoding"]
531 if options["encodingmode"]:
531 if options["encodingmode"]:
532 encoding.encodingmode = options["encodingmode"]
532 encoding.encodingmode = options["encodingmode"]
533 if options["time"]:
533 if options["time"]:
534 def get_times():
534 def get_times():
535 t = os.times()
535 t = os.times()
536 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
536 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
537 t = (t[0], t[1], t[2], t[3], time.clock())
537 t = (t[0], t[1], t[2], t[3], time.clock())
538 return t
538 return t
539 s = get_times()
539 s = get_times()
540 def print_time():
540 def print_time():
541 t = get_times()
541 t = get_times()
542 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
542 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
543 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
543 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
544 atexit.register(print_time)
544 atexit.register(print_time)
545
545
546 if options['verbose'] or options['debug'] or options['quiet']:
546 if options['verbose'] or options['debug'] or options['quiet']:
547 ui.setconfig('ui', 'verbose', str(bool(options['verbose'])))
547 ui.setconfig('ui', 'verbose', str(bool(options['verbose'])))
548 ui.setconfig('ui', 'debug', str(bool(options['debug'])))
548 ui.setconfig('ui', 'debug', str(bool(options['debug'])))
549 ui.setconfig('ui', 'quiet', str(bool(options['quiet'])))
549 ui.setconfig('ui', 'quiet', str(bool(options['quiet'])))
550 if options['traceback']:
550 if options['traceback']:
551 ui.setconfig('ui', 'traceback', 'on')
551 ui.setconfig('ui', 'traceback', 'on')
552 if options['noninteractive']:
552 if options['noninteractive']:
553 ui.setconfig('ui', 'interactive', 'off')
553 ui.setconfig('ui', 'interactive', 'off')
554
554
555 if cmdoptions.get('insecure', False):
555 if cmdoptions.get('insecure', False):
556 ui.setconfig('web', 'cacerts', '')
556 ui.setconfig('web', 'cacerts', '')
557
557
558 if options['help']:
558 if options['help']:
559 return commands.help_(ui, cmd, options['version'])
559 return commands.help_(ui, cmd, options['version'])
560 elif options['version']:
560 elif options['version']:
561 return commands.version_(ui)
561 return commands.version_(ui)
562 elif not cmd:
562 elif not cmd:
563 return commands.help_(ui, 'shortlist')
563 return commands.help_(ui, 'shortlist')
564
564
565 repo = None
565 repo = None
566 cmdpats = args[:]
566 cmdpats = args[:]
567 if cmd not in commands.norepo.split():
567 if cmd not in commands.norepo.split():
568 try:
568 try:
569 repo = hg.repository(ui, path=path)
569 repo = hg.repository(ui, path=path)
570 ui = repo.ui
570 ui = repo.ui
571 if not repo.local():
571 if not repo.local():
572 raise util.Abort(_("repository '%s' is not local") % path)
572 raise util.Abort(_("repository '%s' is not local") % path)
573 ui.setconfig("bundle", "mainreporoot", repo.root)
573 ui.setconfig("bundle", "mainreporoot", repo.root)
574 except error.RepoError:
574 except error.RepoError:
575 if cmd not in commands.optionalrepo.split():
575 if cmd not in commands.optionalrepo.split():
576 if args and not path: # try to infer -R from command args
576 if args and not path: # try to infer -R from command args
577 repos = map(cmdutil.findrepo, args)
577 repos = map(cmdutil.findrepo, args)
578 guess = repos[0]
578 guess = repos[0]
579 if guess and repos.count(guess) == len(repos):
579 if guess and repos.count(guess) == len(repos):
580 return _dispatch(ui, ['--repository', guess] + fullargs)
580 return _dispatch(ui, ['--repository', guess] + fullargs)
581 if not path:
581 if not path:
582 raise error.RepoError(_("There is no Mercurial repository"
582 raise error.RepoError(_("There is no Mercurial repository"
583 " here (.hg not found)"))
583 " here (.hg not found)"))
584 raise
584 raise
585 args.insert(0, repo)
585 args.insert(0, repo)
586 elif rpath:
586 elif rpath:
587 ui.warn(_("warning: --repository ignored\n"))
587 ui.warn(_("warning: --repository ignored\n"))
588
588
589 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
589 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
590 ui.log("command", msg + "\n")
590 ui.log("command", msg + "\n")
591 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
591 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
592 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
592 try:
593 cmdpats, cmdoptions)
593 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
594 cmdpats, cmdoptions)
595 finally:
596 if repo:
597 repo.close()
594
598
595 def _runcommand(ui, options, cmd, cmdfunc):
599 def _runcommand(ui, options, cmd, cmdfunc):
596 def checkargs():
600 def checkargs():
597 try:
601 try:
598 return cmdfunc()
602 return cmdfunc()
599 except error.SignatureError:
603 except error.SignatureError:
600 raise error.CommandError(cmd, _("invalid arguments"))
604 raise error.CommandError(cmd, _("invalid arguments"))
601
605
602 if options['profile']:
606 if options['profile']:
603 format = ui.config('profiling', 'format', default='text')
607 format = ui.config('profiling', 'format', default='text')
604
608
605 if not format in ['text', 'kcachegrind']:
609 if not format in ['text', 'kcachegrind']:
606 ui.warn(_("unrecognized profiling format '%s'"
610 ui.warn(_("unrecognized profiling format '%s'"
607 " - Ignored\n") % format)
611 " - Ignored\n") % format)
608 format = 'text'
612 format = 'text'
609
613
610 output = ui.config('profiling', 'output')
614 output = ui.config('profiling', 'output')
611
615
612 if output:
616 if output:
613 path = ui.expandpath(output)
617 path = ui.expandpath(output)
614 ostream = open(path, 'wb')
618 ostream = open(path, 'wb')
615 else:
619 else:
616 ostream = sys.stderr
620 ostream = sys.stderr
617
621
618 try:
622 try:
619 from mercurial import lsprof
623 from mercurial import lsprof
620 except ImportError:
624 except ImportError:
621 raise util.Abort(_(
625 raise util.Abort(_(
622 'lsprof not available - install from '
626 'lsprof not available - install from '
623 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
627 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
624 p = lsprof.Profiler()
628 p = lsprof.Profiler()
625 p.enable(subcalls=True)
629 p.enable(subcalls=True)
626 try:
630 try:
627 return checkargs()
631 return checkargs()
628 finally:
632 finally:
629 p.disable()
633 p.disable()
630
634
631 if format == 'kcachegrind':
635 if format == 'kcachegrind':
632 import lsprofcalltree
636 import lsprofcalltree
633 calltree = lsprofcalltree.KCacheGrind(p)
637 calltree = lsprofcalltree.KCacheGrind(p)
634 calltree.output(ostream)
638 calltree.output(ostream)
635 else:
639 else:
636 # format == 'text'
640 # format == 'text'
637 stats = lsprof.Stats(p.getstats())
641 stats = lsprof.Stats(p.getstats())
638 stats.sort()
642 stats.sort()
639 stats.pprint(top=10, file=ostream, climit=5)
643 stats.pprint(top=10, file=ostream, climit=5)
640
644
641 if output:
645 if output:
642 ostream.close()
646 ostream.close()
643 else:
647 else:
644 return checkargs()
648 return checkargs()
@@ -1,2006 +1,2012 b''
1 # localrepo.py - read/write repository class for mercurial
1 # localrepo.py - read/write repository class for mercurial
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 of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import bin, hex, nullid, nullrev, short
8 from node import bin, hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import repo, changegroup, subrepo, discovery, pushkey
10 import repo, changegroup, subrepo, discovery, pushkey
11 import changelog, dirstate, filelog, manifest, context, bookmarks
11 import changelog, dirstate, filelog, manifest, context, bookmarks
12 import lock, transaction, store, encoding
12 import lock, transaction, store, encoding
13 import util, extensions, hook, error
13 import util, extensions, hook, error
14 import match as matchmod
14 import match as matchmod
15 import merge as mergemod
15 import merge as mergemod
16 import tags as tagsmod
16 import tags as tagsmod
17 import url as urlmod
17 import url as urlmod
18 from lock import release
18 from lock import release
19 import weakref, errno, os, time, inspect
19 import weakref, errno, os, time, inspect
20 propertycache = util.propertycache
20 propertycache = util.propertycache
21
21
22 class localrepository(repo.repository):
22 class localrepository(repo.repository):
23 capabilities = set(('lookup', 'changegroupsubset', 'branchmap', 'pushkey'))
23 capabilities = set(('lookup', 'changegroupsubset', 'branchmap', 'pushkey'))
24 supportedformats = set(('revlogv1', 'parentdelta'))
24 supportedformats = set(('revlogv1', 'parentdelta'))
25 supported = supportedformats | set(('store', 'fncache', 'shared',
25 supported = supportedformats | set(('store', 'fncache', 'shared',
26 'dotencode'))
26 'dotencode'))
27
27
28 def __init__(self, baseui, path=None, create=0):
28 def __init__(self, baseui, path=None, create=0):
29 repo.repository.__init__(self)
29 repo.repository.__init__(self)
30 self.root = os.path.realpath(util.expandpath(path))
30 self.root = os.path.realpath(util.expandpath(path))
31 self.path = os.path.join(self.root, ".hg")
31 self.path = os.path.join(self.root, ".hg")
32 self.origroot = path
32 self.origroot = path
33 self.auditor = util.path_auditor(self.root, self._checknested)
33 self.auditor = util.path_auditor(self.root, self._checknested)
34 self.opener = util.opener(self.path)
34 self.opener = util.opener(self.path)
35 self.wopener = util.opener(self.root)
35 self.wopener = util.opener(self.root)
36 self.baseui = baseui
36 self.baseui = baseui
37 self.ui = baseui.copy()
37 self.ui = baseui.copy()
38
38
39 try:
39 try:
40 self.ui.readconfig(self.join("hgrc"), self.root)
40 self.ui.readconfig(self.join("hgrc"), self.root)
41 extensions.loadall(self.ui)
41 extensions.loadall(self.ui)
42 except IOError:
42 except IOError:
43 pass
43 pass
44
44
45 if not os.path.isdir(self.path):
45 if not os.path.isdir(self.path):
46 if create:
46 if create:
47 if not os.path.exists(path):
47 if not os.path.exists(path):
48 util.makedirs(path)
48 util.makedirs(path)
49 os.mkdir(self.path)
49 os.mkdir(self.path)
50 requirements = ["revlogv1"]
50 requirements = ["revlogv1"]
51 if self.ui.configbool('format', 'usestore', True):
51 if self.ui.configbool('format', 'usestore', True):
52 os.mkdir(os.path.join(self.path, "store"))
52 os.mkdir(os.path.join(self.path, "store"))
53 requirements.append("store")
53 requirements.append("store")
54 if self.ui.configbool('format', 'usefncache', True):
54 if self.ui.configbool('format', 'usefncache', True):
55 requirements.append("fncache")
55 requirements.append("fncache")
56 if self.ui.configbool('format', 'dotencode', True):
56 if self.ui.configbool('format', 'dotencode', True):
57 requirements.append('dotencode')
57 requirements.append('dotencode')
58 # create an invalid changelog
58 # create an invalid changelog
59 self.opener("00changelog.i", "a").write(
59 self.opener("00changelog.i", "a").write(
60 '\0\0\0\2' # represents revlogv2
60 '\0\0\0\2' # represents revlogv2
61 ' dummy changelog to prevent using the old repo layout'
61 ' dummy changelog to prevent using the old repo layout'
62 )
62 )
63 if self.ui.configbool('format', 'parentdelta', False):
63 if self.ui.configbool('format', 'parentdelta', False):
64 requirements.append("parentdelta")
64 requirements.append("parentdelta")
65 else:
65 else:
66 raise error.RepoError(_("repository %s not found") % path)
66 raise error.RepoError(_("repository %s not found") % path)
67 elif create:
67 elif create:
68 raise error.RepoError(_("repository %s already exists") % path)
68 raise error.RepoError(_("repository %s already exists") % path)
69 else:
69 else:
70 # find requirements
70 # find requirements
71 requirements = set()
71 requirements = set()
72 try:
72 try:
73 requirements = set(self.opener("requires").read().splitlines())
73 requirements = set(self.opener("requires").read().splitlines())
74 except IOError, inst:
74 except IOError, inst:
75 if inst.errno != errno.ENOENT:
75 if inst.errno != errno.ENOENT:
76 raise
76 raise
77 for r in requirements - self.supported:
77 for r in requirements - self.supported:
78 raise error.RepoError(_("requirement '%s' not supported") % r)
78 raise error.RepoError(_("requirement '%s' not supported") % r)
79
79
80 self.sharedpath = self.path
80 self.sharedpath = self.path
81 try:
81 try:
82 s = os.path.realpath(self.opener("sharedpath").read())
82 s = os.path.realpath(self.opener("sharedpath").read())
83 if not os.path.exists(s):
83 if not os.path.exists(s):
84 raise error.RepoError(
84 raise error.RepoError(
85 _('.hg/sharedpath points to nonexistent directory %s') % s)
85 _('.hg/sharedpath points to nonexistent directory %s') % s)
86 self.sharedpath = s
86 self.sharedpath = s
87 except IOError, inst:
87 except IOError, inst:
88 if inst.errno != errno.ENOENT:
88 if inst.errno != errno.ENOENT:
89 raise
89 raise
90
90
91 self.store = store.store(requirements, self.sharedpath, util.opener)
91 self.store = store.store(requirements, self.sharedpath, util.opener)
92 self.spath = self.store.path
92 self.spath = self.store.path
93 self.sopener = self.store.opener
93 self.sopener = self.store.opener
94 self.sjoin = self.store.join
94 self.sjoin = self.store.join
95 self.opener.createmode = self.store.createmode
95 self.opener.createmode = self.store.createmode
96 self._applyrequirements(requirements)
96 self._applyrequirements(requirements)
97 if create:
97 if create:
98 self._writerequirements()
98 self._writerequirements()
99
99
100 # These two define the set of tags for this repository. _tags
100 # These two define the set of tags for this repository. _tags
101 # maps tag name to node; _tagtypes maps tag name to 'global' or
101 # maps tag name to node; _tagtypes maps tag name to 'global' or
102 # 'local'. (Global tags are defined by .hgtags across all
102 # 'local'. (Global tags are defined by .hgtags across all
103 # heads, and local tags are defined in .hg/localtags.) They
103 # heads, and local tags are defined in .hg/localtags.) They
104 # constitute the in-memory cache of tags.
104 # constitute the in-memory cache of tags.
105 self._tags = None
105 self._tags = None
106 self._tagtypes = None
106 self._tagtypes = None
107
107
108 self._branchcache = None
108 self._branchcache = None
109 self._branchcachetip = None
109 self._branchcachetip = None
110 self.nodetagscache = None
110 self.nodetagscache = None
111 self.filterpats = {}
111 self.filterpats = {}
112 self._datafilters = {}
112 self._datafilters = {}
113 self._transref = self._lockref = self._wlockref = None
113 self._transref = self._lockref = self._wlockref = None
114
114
115 def _applyrequirements(self, requirements):
115 def _applyrequirements(self, requirements):
116 self.requirements = requirements
116 self.requirements = requirements
117 self.sopener.options = {}
117 self.sopener.options = {}
118 if 'parentdelta' in requirements:
118 if 'parentdelta' in requirements:
119 self.sopener.options['parentdelta'] = 1
119 self.sopener.options['parentdelta'] = 1
120
120
121 def _writerequirements(self):
121 def _writerequirements(self):
122 reqfile = self.opener("requires", "w")
122 reqfile = self.opener("requires", "w")
123 for r in self.requirements:
123 for r in self.requirements:
124 reqfile.write("%s\n" % r)
124 reqfile.write("%s\n" % r)
125 reqfile.close()
125 reqfile.close()
126
126
127 def _checknested(self, path):
127 def _checknested(self, path):
128 """Determine if path is a legal nested repository."""
128 """Determine if path is a legal nested repository."""
129 if not path.startswith(self.root):
129 if not path.startswith(self.root):
130 return False
130 return False
131 subpath = path[len(self.root) + 1:]
131 subpath = path[len(self.root) + 1:]
132
132
133 # XXX: Checking against the current working copy is wrong in
133 # XXX: Checking against the current working copy is wrong in
134 # the sense that it can reject things like
134 # the sense that it can reject things like
135 #
135 #
136 # $ hg cat -r 10 sub/x.txt
136 # $ hg cat -r 10 sub/x.txt
137 #
137 #
138 # if sub/ is no longer a subrepository in the working copy
138 # if sub/ is no longer a subrepository in the working copy
139 # parent revision.
139 # parent revision.
140 #
140 #
141 # However, it can of course also allow things that would have
141 # However, it can of course also allow things that would have
142 # been rejected before, such as the above cat command if sub/
142 # been rejected before, such as the above cat command if sub/
143 # is a subrepository now, but was a normal directory before.
143 # is a subrepository now, but was a normal directory before.
144 # The old path auditor would have rejected by mistake since it
144 # The old path auditor would have rejected by mistake since it
145 # panics when it sees sub/.hg/.
145 # panics when it sees sub/.hg/.
146 #
146 #
147 # All in all, checking against the working copy seems sensible
147 # All in all, checking against the working copy seems sensible
148 # since we want to prevent access to nested repositories on
148 # since we want to prevent access to nested repositories on
149 # the filesystem *now*.
149 # the filesystem *now*.
150 ctx = self[None]
150 ctx = self[None]
151 parts = util.splitpath(subpath)
151 parts = util.splitpath(subpath)
152 while parts:
152 while parts:
153 prefix = os.sep.join(parts)
153 prefix = os.sep.join(parts)
154 if prefix in ctx.substate:
154 if prefix in ctx.substate:
155 if prefix == subpath:
155 if prefix == subpath:
156 return True
156 return True
157 else:
157 else:
158 sub = ctx.sub(prefix)
158 sub = ctx.sub(prefix)
159 return sub.checknested(subpath[len(prefix) + 1:])
159 return sub.checknested(subpath[len(prefix) + 1:])
160 else:
160 else:
161 parts.pop()
161 parts.pop()
162 return False
162 return False
163
163
164 @util.propertycache
164 @util.propertycache
165 def _bookmarks(self):
165 def _bookmarks(self):
166 return bookmarks.read(self)
166 return bookmarks.read(self)
167
167
168 @util.propertycache
168 @util.propertycache
169 def _bookmarkcurrent(self):
169 def _bookmarkcurrent(self):
170 return bookmarks.readcurrent(self)
170 return bookmarks.readcurrent(self)
171
171
172 @propertycache
172 @propertycache
173 def changelog(self):
173 def changelog(self):
174 c = changelog.changelog(self.sopener)
174 c = changelog.changelog(self.sopener)
175 if 'HG_PENDING' in os.environ:
175 if 'HG_PENDING' in os.environ:
176 p = os.environ['HG_PENDING']
176 p = os.environ['HG_PENDING']
177 if p.startswith(self.root):
177 if p.startswith(self.root):
178 c.readpending('00changelog.i.a')
178 c.readpending('00changelog.i.a')
179 self.sopener.options['defversion'] = c.version
179 self.sopener.options['defversion'] = c.version
180 return c
180 return c
181
181
182 @propertycache
182 @propertycache
183 def manifest(self):
183 def manifest(self):
184 return manifest.manifest(self.sopener)
184 return manifest.manifest(self.sopener)
185
185
186 @propertycache
186 @propertycache
187 def dirstate(self):
187 def dirstate(self):
188 warned = [0]
188 warned = [0]
189 def validate(node):
189 def validate(node):
190 try:
190 try:
191 r = self.changelog.rev(node)
191 r = self.changelog.rev(node)
192 return node
192 return node
193 except error.LookupError:
193 except error.LookupError:
194 if not warned[0]:
194 if not warned[0]:
195 warned[0] = True
195 warned[0] = True
196 self.ui.warn(_("warning: ignoring unknown"
196 self.ui.warn(_("warning: ignoring unknown"
197 " working parent %s!\n") % short(node))
197 " working parent %s!\n") % short(node))
198 return nullid
198 return nullid
199
199
200 return dirstate.dirstate(self.opener, self.ui, self.root, validate)
200 return dirstate.dirstate(self.opener, self.ui, self.root, validate)
201
201
202 def __getitem__(self, changeid):
202 def __getitem__(self, changeid):
203 if changeid is None:
203 if changeid is None:
204 return context.workingctx(self)
204 return context.workingctx(self)
205 return context.changectx(self, changeid)
205 return context.changectx(self, changeid)
206
206
207 def __contains__(self, changeid):
207 def __contains__(self, changeid):
208 try:
208 try:
209 return bool(self.lookup(changeid))
209 return bool(self.lookup(changeid))
210 except error.RepoLookupError:
210 except error.RepoLookupError:
211 return False
211 return False
212
212
213 def __nonzero__(self):
213 def __nonzero__(self):
214 return True
214 return True
215
215
216 def __len__(self):
216 def __len__(self):
217 return len(self.changelog)
217 return len(self.changelog)
218
218
219 def __iter__(self):
219 def __iter__(self):
220 for i in xrange(len(self)):
220 for i in xrange(len(self)):
221 yield i
221 yield i
222
222
223 def url(self):
223 def url(self):
224 return 'file:' + self.root
224 return 'file:' + self.root
225
225
226 def hook(self, name, throw=False, **args):
226 def hook(self, name, throw=False, **args):
227 return hook.hook(self.ui, self, name, throw, **args)
227 return hook.hook(self.ui, self, name, throw, **args)
228
228
229 tag_disallowed = ':\r\n'
229 tag_disallowed = ':\r\n'
230
230
231 def _tag(self, names, node, message, local, user, date, extra={}):
231 def _tag(self, names, node, message, local, user, date, extra={}):
232 if isinstance(names, str):
232 if isinstance(names, str):
233 allchars = names
233 allchars = names
234 names = (names,)
234 names = (names,)
235 else:
235 else:
236 allchars = ''.join(names)
236 allchars = ''.join(names)
237 for c in self.tag_disallowed:
237 for c in self.tag_disallowed:
238 if c in allchars:
238 if c in allchars:
239 raise util.Abort(_('%r cannot be used in a tag name') % c)
239 raise util.Abort(_('%r cannot be used in a tag name') % c)
240
240
241 branches = self.branchmap()
241 branches = self.branchmap()
242 for name in names:
242 for name in names:
243 self.hook('pretag', throw=True, node=hex(node), tag=name,
243 self.hook('pretag', throw=True, node=hex(node), tag=name,
244 local=local)
244 local=local)
245 if name in branches:
245 if name in branches:
246 self.ui.warn(_("warning: tag %s conflicts with existing"
246 self.ui.warn(_("warning: tag %s conflicts with existing"
247 " branch name\n") % name)
247 " branch name\n") % name)
248
248
249 def writetags(fp, names, munge, prevtags):
249 def writetags(fp, names, munge, prevtags):
250 fp.seek(0, 2)
250 fp.seek(0, 2)
251 if prevtags and prevtags[-1] != '\n':
251 if prevtags and prevtags[-1] != '\n':
252 fp.write('\n')
252 fp.write('\n')
253 for name in names:
253 for name in names:
254 m = munge and munge(name) or name
254 m = munge and munge(name) or name
255 if self._tagtypes and name in self._tagtypes:
255 if self._tagtypes and name in self._tagtypes:
256 old = self._tags.get(name, nullid)
256 old = self._tags.get(name, nullid)
257 fp.write('%s %s\n' % (hex(old), m))
257 fp.write('%s %s\n' % (hex(old), m))
258 fp.write('%s %s\n' % (hex(node), m))
258 fp.write('%s %s\n' % (hex(node), m))
259 fp.close()
259 fp.close()
260
260
261 prevtags = ''
261 prevtags = ''
262 if local:
262 if local:
263 try:
263 try:
264 fp = self.opener('localtags', 'r+')
264 fp = self.opener('localtags', 'r+')
265 except IOError:
265 except IOError:
266 fp = self.opener('localtags', 'a')
266 fp = self.opener('localtags', 'a')
267 else:
267 else:
268 prevtags = fp.read()
268 prevtags = fp.read()
269
269
270 # local tags are stored in the current charset
270 # local tags are stored in the current charset
271 writetags(fp, names, None, prevtags)
271 writetags(fp, names, None, prevtags)
272 for name in names:
272 for name in names:
273 self.hook('tag', node=hex(node), tag=name, local=local)
273 self.hook('tag', node=hex(node), tag=name, local=local)
274 return
274 return
275
275
276 try:
276 try:
277 fp = self.wfile('.hgtags', 'rb+')
277 fp = self.wfile('.hgtags', 'rb+')
278 except IOError:
278 except IOError:
279 fp = self.wfile('.hgtags', 'ab')
279 fp = self.wfile('.hgtags', 'ab')
280 else:
280 else:
281 prevtags = fp.read()
281 prevtags = fp.read()
282
282
283 # committed tags are stored in UTF-8
283 # committed tags are stored in UTF-8
284 writetags(fp, names, encoding.fromlocal, prevtags)
284 writetags(fp, names, encoding.fromlocal, prevtags)
285
285
286 if '.hgtags' not in self.dirstate:
286 if '.hgtags' not in self.dirstate:
287 self[None].add(['.hgtags'])
287 self[None].add(['.hgtags'])
288
288
289 m = matchmod.exact(self.root, '', ['.hgtags'])
289 m = matchmod.exact(self.root, '', ['.hgtags'])
290 tagnode = self.commit(message, user, date, extra=extra, match=m)
290 tagnode = self.commit(message, user, date, extra=extra, match=m)
291
291
292 for name in names:
292 for name in names:
293 self.hook('tag', node=hex(node), tag=name, local=local)
293 self.hook('tag', node=hex(node), tag=name, local=local)
294
294
295 return tagnode
295 return tagnode
296
296
297 def tag(self, names, node, message, local, user, date):
297 def tag(self, names, node, message, local, user, date):
298 '''tag a revision with one or more symbolic names.
298 '''tag a revision with one or more symbolic names.
299
299
300 names is a list of strings or, when adding a single tag, names may be a
300 names is a list of strings or, when adding a single tag, names may be a
301 string.
301 string.
302
302
303 if local is True, the tags are stored in a per-repository file.
303 if local is True, the tags are stored in a per-repository file.
304 otherwise, they are stored in the .hgtags file, and a new
304 otherwise, they are stored in the .hgtags file, and a new
305 changeset is committed with the change.
305 changeset is committed with the change.
306
306
307 keyword arguments:
307 keyword arguments:
308
308
309 local: whether to store tags in non-version-controlled file
309 local: whether to store tags in non-version-controlled file
310 (default False)
310 (default False)
311
311
312 message: commit message to use if committing
312 message: commit message to use if committing
313
313
314 user: name of user to use if committing
314 user: name of user to use if committing
315
315
316 date: date tuple to use if committing'''
316 date: date tuple to use if committing'''
317
317
318 if not local:
318 if not local:
319 for x in self.status()[:5]:
319 for x in self.status()[:5]:
320 if '.hgtags' in x:
320 if '.hgtags' in x:
321 raise util.Abort(_('working copy of .hgtags is changed '
321 raise util.Abort(_('working copy of .hgtags is changed '
322 '(please commit .hgtags manually)'))
322 '(please commit .hgtags manually)'))
323
323
324 self.tags() # instantiate the cache
324 self.tags() # instantiate the cache
325 self._tag(names, node, message, local, user, date)
325 self._tag(names, node, message, local, user, date)
326
326
327 def tags(self):
327 def tags(self):
328 '''return a mapping of tag to node'''
328 '''return a mapping of tag to node'''
329 if self._tags is None:
329 if self._tags is None:
330 (self._tags, self._tagtypes) = self._findtags()
330 (self._tags, self._tagtypes) = self._findtags()
331
331
332 return self._tags
332 return self._tags
333
333
334 def _findtags(self):
334 def _findtags(self):
335 '''Do the hard work of finding tags. Return a pair of dicts
335 '''Do the hard work of finding tags. Return a pair of dicts
336 (tags, tagtypes) where tags maps tag name to node, and tagtypes
336 (tags, tagtypes) where tags maps tag name to node, and tagtypes
337 maps tag name to a string like \'global\' or \'local\'.
337 maps tag name to a string like \'global\' or \'local\'.
338 Subclasses or extensions are free to add their own tags, but
338 Subclasses or extensions are free to add their own tags, but
339 should be aware that the returned dicts will be retained for the
339 should be aware that the returned dicts will be retained for the
340 duration of the localrepo object.'''
340 duration of the localrepo object.'''
341
341
342 # XXX what tagtype should subclasses/extensions use? Currently
342 # XXX what tagtype should subclasses/extensions use? Currently
343 # mq and bookmarks add tags, but do not set the tagtype at all.
343 # mq and bookmarks add tags, but do not set the tagtype at all.
344 # Should each extension invent its own tag type? Should there
344 # Should each extension invent its own tag type? Should there
345 # be one tagtype for all such "virtual" tags? Or is the status
345 # be one tagtype for all such "virtual" tags? Or is the status
346 # quo fine?
346 # quo fine?
347
347
348 alltags = {} # map tag name to (node, hist)
348 alltags = {} # map tag name to (node, hist)
349 tagtypes = {}
349 tagtypes = {}
350
350
351 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
351 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
352 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
352 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
353
353
354 # Build the return dicts. Have to re-encode tag names because
354 # Build the return dicts. Have to re-encode tag names because
355 # the tags module always uses UTF-8 (in order not to lose info
355 # the tags module always uses UTF-8 (in order not to lose info
356 # writing to the cache), but the rest of Mercurial wants them in
356 # writing to the cache), but the rest of Mercurial wants them in
357 # local encoding.
357 # local encoding.
358 tags = {}
358 tags = {}
359 for (name, (node, hist)) in alltags.iteritems():
359 for (name, (node, hist)) in alltags.iteritems():
360 if node != nullid:
360 if node != nullid:
361 tags[encoding.tolocal(name)] = node
361 tags[encoding.tolocal(name)] = node
362 tags['tip'] = self.changelog.tip()
362 tags['tip'] = self.changelog.tip()
363 tags.update(self._bookmarks)
364 tagtypes = dict([(encoding.tolocal(name), value)
363 tagtypes = dict([(encoding.tolocal(name), value)
365 for (name, value) in tagtypes.iteritems()])
364 for (name, value) in tagtypes.iteritems()])
366 return (tags, tagtypes)
365 return (tags, tagtypes)
367
366
368 def tagtype(self, tagname):
367 def tagtype(self, tagname):
369 '''
368 '''
370 return the type of the given tag. result can be:
369 return the type of the given tag. result can be:
371
370
372 'local' : a local tag
371 'local' : a local tag
373 'global' : a global tag
372 'global' : a global tag
374 None : tag does not exist
373 None : tag does not exist
375 '''
374 '''
376
375
377 self.tags()
376 self.tags()
378
377
379 return self._tagtypes.get(tagname)
378 return self._tagtypes.get(tagname)
380
379
381 def tagslist(self):
380 def tagslist(self):
382 '''return a list of tags ordered by revision'''
381 '''return a list of tags ordered by revision'''
383 l = []
382 l = []
384 for t, n in self.tags().iteritems():
383 for t, n in self.tags().iteritems():
385 try:
384 try:
386 r = self.changelog.rev(n)
385 r = self.changelog.rev(n)
387 except:
386 except:
388 r = -2 # sort to the beginning of the list if unknown
387 r = -2 # sort to the beginning of the list if unknown
389 l.append((r, t, n))
388 l.append((r, t, n))
390 return [(t, n) for r, t, n in sorted(l)]
389 return [(t, n) for r, t, n in sorted(l)]
391
390
392 def nodetags(self, node):
391 def nodetags(self, node):
393 '''return the tags associated with a node'''
392 '''return the tags associated with a node'''
394 if not self.nodetagscache:
393 if not self.nodetagscache:
395 self.nodetagscache = {}
394 self.nodetagscache = {}
396 for t, n in self.tags().iteritems():
395 for t, n in self.tags().iteritems():
397 self.nodetagscache.setdefault(n, []).append(t)
396 self.nodetagscache.setdefault(n, []).append(t)
398 for tags in self.nodetagscache.itervalues():
397 for tags in self.nodetagscache.itervalues():
399 tags.sort()
398 tags.sort()
400 return self.nodetagscache.get(node, [])
399 return self.nodetagscache.get(node, [])
401
400
401 def nodebookmarks(self, node):
402 marks = []
403 for bookmark, n in self._bookmarks.iteritems():
404 if n == node:
405 marks.append(bookmark)
406 return sorted(marks)
407
402 def _branchtags(self, partial, lrev):
408 def _branchtags(self, partial, lrev):
403 # TODO: rename this function?
409 # TODO: rename this function?
404 tiprev = len(self) - 1
410 tiprev = len(self) - 1
405 if lrev != tiprev:
411 if lrev != tiprev:
406 ctxgen = (self[r] for r in xrange(lrev + 1, tiprev + 1))
412 ctxgen = (self[r] for r in xrange(lrev + 1, tiprev + 1))
407 self._updatebranchcache(partial, ctxgen)
413 self._updatebranchcache(partial, ctxgen)
408 self._writebranchcache(partial, self.changelog.tip(), tiprev)
414 self._writebranchcache(partial, self.changelog.tip(), tiprev)
409
415
410 return partial
416 return partial
411
417
412 def updatebranchcache(self):
418 def updatebranchcache(self):
413 tip = self.changelog.tip()
419 tip = self.changelog.tip()
414 if self._branchcache is not None and self._branchcachetip == tip:
420 if self._branchcache is not None and self._branchcachetip == tip:
415 return self._branchcache
421 return self._branchcache
416
422
417 oldtip = self._branchcachetip
423 oldtip = self._branchcachetip
418 self._branchcachetip = tip
424 self._branchcachetip = tip
419 if oldtip is None or oldtip not in self.changelog.nodemap:
425 if oldtip is None or oldtip not in self.changelog.nodemap:
420 partial, last, lrev = self._readbranchcache()
426 partial, last, lrev = self._readbranchcache()
421 else:
427 else:
422 lrev = self.changelog.rev(oldtip)
428 lrev = self.changelog.rev(oldtip)
423 partial = self._branchcache
429 partial = self._branchcache
424
430
425 self._branchtags(partial, lrev)
431 self._branchtags(partial, lrev)
426 # this private cache holds all heads (not just tips)
432 # this private cache holds all heads (not just tips)
427 self._branchcache = partial
433 self._branchcache = partial
428
434
429 def branchmap(self):
435 def branchmap(self):
430 '''returns a dictionary {branch: [branchheads]}'''
436 '''returns a dictionary {branch: [branchheads]}'''
431 self.updatebranchcache()
437 self.updatebranchcache()
432 return self._branchcache
438 return self._branchcache
433
439
434 def branchtags(self):
440 def branchtags(self):
435 '''return a dict where branch names map to the tipmost head of
441 '''return a dict where branch names map to the tipmost head of
436 the branch, open heads come before closed'''
442 the branch, open heads come before closed'''
437 bt = {}
443 bt = {}
438 for bn, heads in self.branchmap().iteritems():
444 for bn, heads in self.branchmap().iteritems():
439 tip = heads[-1]
445 tip = heads[-1]
440 for h in reversed(heads):
446 for h in reversed(heads):
441 if 'close' not in self.changelog.read(h)[5]:
447 if 'close' not in self.changelog.read(h)[5]:
442 tip = h
448 tip = h
443 break
449 break
444 bt[bn] = tip
450 bt[bn] = tip
445 return bt
451 return bt
446
452
447 def _readbranchcache(self):
453 def _readbranchcache(self):
448 partial = {}
454 partial = {}
449 try:
455 try:
450 f = self.opener("cache/branchheads")
456 f = self.opener("cache/branchheads")
451 lines = f.read().split('\n')
457 lines = f.read().split('\n')
452 f.close()
458 f.close()
453 except (IOError, OSError):
459 except (IOError, OSError):
454 return {}, nullid, nullrev
460 return {}, nullid, nullrev
455
461
456 try:
462 try:
457 last, lrev = lines.pop(0).split(" ", 1)
463 last, lrev = lines.pop(0).split(" ", 1)
458 last, lrev = bin(last), int(lrev)
464 last, lrev = bin(last), int(lrev)
459 if lrev >= len(self) or self[lrev].node() != last:
465 if lrev >= len(self) or self[lrev].node() != last:
460 # invalidate the cache
466 # invalidate the cache
461 raise ValueError('invalidating branch cache (tip differs)')
467 raise ValueError('invalidating branch cache (tip differs)')
462 for l in lines:
468 for l in lines:
463 if not l:
469 if not l:
464 continue
470 continue
465 node, label = l.split(" ", 1)
471 node, label = l.split(" ", 1)
466 label = encoding.tolocal(label.strip())
472 label = encoding.tolocal(label.strip())
467 partial.setdefault(label, []).append(bin(node))
473 partial.setdefault(label, []).append(bin(node))
468 except KeyboardInterrupt:
474 except KeyboardInterrupt:
469 raise
475 raise
470 except Exception, inst:
476 except Exception, inst:
471 if self.ui.debugflag:
477 if self.ui.debugflag:
472 self.ui.warn(str(inst), '\n')
478 self.ui.warn(str(inst), '\n')
473 partial, last, lrev = {}, nullid, nullrev
479 partial, last, lrev = {}, nullid, nullrev
474 return partial, last, lrev
480 return partial, last, lrev
475
481
476 def _writebranchcache(self, branches, tip, tiprev):
482 def _writebranchcache(self, branches, tip, tiprev):
477 try:
483 try:
478 f = self.opener("cache/branchheads", "w", atomictemp=True)
484 f = self.opener("cache/branchheads", "w", atomictemp=True)
479 f.write("%s %s\n" % (hex(tip), tiprev))
485 f.write("%s %s\n" % (hex(tip), tiprev))
480 for label, nodes in branches.iteritems():
486 for label, nodes in branches.iteritems():
481 for node in nodes:
487 for node in nodes:
482 f.write("%s %s\n" % (hex(node), encoding.fromlocal(label)))
488 f.write("%s %s\n" % (hex(node), encoding.fromlocal(label)))
483 f.rename()
489 f.rename()
484 except (IOError, OSError):
490 except (IOError, OSError):
485 pass
491 pass
486
492
487 def _updatebranchcache(self, partial, ctxgen):
493 def _updatebranchcache(self, partial, ctxgen):
488 # collect new branch entries
494 # collect new branch entries
489 newbranches = {}
495 newbranches = {}
490 for c in ctxgen:
496 for c in ctxgen:
491 newbranches.setdefault(c.branch(), []).append(c.node())
497 newbranches.setdefault(c.branch(), []).append(c.node())
492 # if older branchheads are reachable from new ones, they aren't
498 # if older branchheads are reachable from new ones, they aren't
493 # really branchheads. Note checking parents is insufficient:
499 # really branchheads. Note checking parents is insufficient:
494 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
500 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
495 for branch, newnodes in newbranches.iteritems():
501 for branch, newnodes in newbranches.iteritems():
496 bheads = partial.setdefault(branch, [])
502 bheads = partial.setdefault(branch, [])
497 bheads.extend(newnodes)
503 bheads.extend(newnodes)
498 if len(bheads) <= 1:
504 if len(bheads) <= 1:
499 continue
505 continue
500 # starting from tip means fewer passes over reachable
506 # starting from tip means fewer passes over reachable
501 while newnodes:
507 while newnodes:
502 latest = newnodes.pop()
508 latest = newnodes.pop()
503 if latest not in bheads:
509 if latest not in bheads:
504 continue
510 continue
505 minbhrev = self[min([self[bh].rev() for bh in bheads])].node()
511 minbhrev = self[min([self[bh].rev() for bh in bheads])].node()
506 reachable = self.changelog.reachable(latest, minbhrev)
512 reachable = self.changelog.reachable(latest, minbhrev)
507 reachable.remove(latest)
513 reachable.remove(latest)
508 bheads = [b for b in bheads if b not in reachable]
514 bheads = [b for b in bheads if b not in reachable]
509 partial[branch] = bheads
515 partial[branch] = bheads
510
516
511 def lookup(self, key):
517 def lookup(self, key):
512 if isinstance(key, int):
518 if isinstance(key, int):
513 return self.changelog.node(key)
519 return self.changelog.node(key)
514 elif key == '.':
520 elif key == '.':
515 return self.dirstate.parents()[0]
521 return self.dirstate.parents()[0]
516 elif key == 'null':
522 elif key == 'null':
517 return nullid
523 return nullid
518 elif key == 'tip':
524 elif key == 'tip':
519 return self.changelog.tip()
525 return self.changelog.tip()
520 n = self.changelog._match(key)
526 n = self.changelog._match(key)
521 if n:
527 if n:
522 return n
528 return n
523 if key in self._bookmarks:
529 if key in self._bookmarks:
524 return self._bookmarks[key]
530 return self._bookmarks[key]
525 if key in self.tags():
531 if key in self.tags():
526 return self.tags()[key]
532 return self.tags()[key]
527 if key in self.branchtags():
533 if key in self.branchtags():
528 return self.branchtags()[key]
534 return self.branchtags()[key]
529 n = self.changelog._partialmatch(key)
535 n = self.changelog._partialmatch(key)
530 if n:
536 if n:
531 return n
537 return n
532
538
533 # can't find key, check if it might have come from damaged dirstate
539 # can't find key, check if it might have come from damaged dirstate
534 if key in self.dirstate.parents():
540 if key in self.dirstate.parents():
535 raise error.Abort(_("working directory has unknown parent '%s'!")
541 raise error.Abort(_("working directory has unknown parent '%s'!")
536 % short(key))
542 % short(key))
537 try:
543 try:
538 if len(key) == 20:
544 if len(key) == 20:
539 key = hex(key)
545 key = hex(key)
540 except:
546 except:
541 pass
547 pass
542 raise error.RepoLookupError(_("unknown revision '%s'") % key)
548 raise error.RepoLookupError(_("unknown revision '%s'") % key)
543
549
544 def lookupbranch(self, key, remote=None):
550 def lookupbranch(self, key, remote=None):
545 repo = remote or self
551 repo = remote or self
546 if key in repo.branchmap():
552 if key in repo.branchmap():
547 return key
553 return key
548
554
549 repo = (remote and remote.local()) and remote or self
555 repo = (remote and remote.local()) and remote or self
550 return repo[key].branch()
556 return repo[key].branch()
551
557
552 def local(self):
558 def local(self):
553 return True
559 return True
554
560
555 def join(self, f):
561 def join(self, f):
556 return os.path.join(self.path, f)
562 return os.path.join(self.path, f)
557
563
558 def wjoin(self, f):
564 def wjoin(self, f):
559 return os.path.join(self.root, f)
565 return os.path.join(self.root, f)
560
566
561 def file(self, f):
567 def file(self, f):
562 if f[0] == '/':
568 if f[0] == '/':
563 f = f[1:]
569 f = f[1:]
564 return filelog.filelog(self.sopener, f)
570 return filelog.filelog(self.sopener, f)
565
571
566 def changectx(self, changeid):
572 def changectx(self, changeid):
567 return self[changeid]
573 return self[changeid]
568
574
569 def parents(self, changeid=None):
575 def parents(self, changeid=None):
570 '''get list of changectxs for parents of changeid'''
576 '''get list of changectxs for parents of changeid'''
571 return self[changeid].parents()
577 return self[changeid].parents()
572
578
573 def filectx(self, path, changeid=None, fileid=None):
579 def filectx(self, path, changeid=None, fileid=None):
574 """changeid can be a changeset revision, node, or tag.
580 """changeid can be a changeset revision, node, or tag.
575 fileid can be a file revision or node."""
581 fileid can be a file revision or node."""
576 return context.filectx(self, path, changeid, fileid)
582 return context.filectx(self, path, changeid, fileid)
577
583
578 def getcwd(self):
584 def getcwd(self):
579 return self.dirstate.getcwd()
585 return self.dirstate.getcwd()
580
586
581 def pathto(self, f, cwd=None):
587 def pathto(self, f, cwd=None):
582 return self.dirstate.pathto(f, cwd)
588 return self.dirstate.pathto(f, cwd)
583
589
584 def wfile(self, f, mode='r'):
590 def wfile(self, f, mode='r'):
585 return self.wopener(f, mode)
591 return self.wopener(f, mode)
586
592
587 def _link(self, f):
593 def _link(self, f):
588 return os.path.islink(self.wjoin(f))
594 return os.path.islink(self.wjoin(f))
589
595
590 def _loadfilter(self, filter):
596 def _loadfilter(self, filter):
591 if filter not in self.filterpats:
597 if filter not in self.filterpats:
592 l = []
598 l = []
593 for pat, cmd in self.ui.configitems(filter):
599 for pat, cmd in self.ui.configitems(filter):
594 if cmd == '!':
600 if cmd == '!':
595 continue
601 continue
596 mf = matchmod.match(self.root, '', [pat])
602 mf = matchmod.match(self.root, '', [pat])
597 fn = None
603 fn = None
598 params = cmd
604 params = cmd
599 for name, filterfn in self._datafilters.iteritems():
605 for name, filterfn in self._datafilters.iteritems():
600 if cmd.startswith(name):
606 if cmd.startswith(name):
601 fn = filterfn
607 fn = filterfn
602 params = cmd[len(name):].lstrip()
608 params = cmd[len(name):].lstrip()
603 break
609 break
604 if not fn:
610 if not fn:
605 fn = lambda s, c, **kwargs: util.filter(s, c)
611 fn = lambda s, c, **kwargs: util.filter(s, c)
606 # Wrap old filters not supporting keyword arguments
612 # Wrap old filters not supporting keyword arguments
607 if not inspect.getargspec(fn)[2]:
613 if not inspect.getargspec(fn)[2]:
608 oldfn = fn
614 oldfn = fn
609 fn = lambda s, c, **kwargs: oldfn(s, c)
615 fn = lambda s, c, **kwargs: oldfn(s, c)
610 l.append((mf, fn, params))
616 l.append((mf, fn, params))
611 self.filterpats[filter] = l
617 self.filterpats[filter] = l
612 return self.filterpats[filter]
618 return self.filterpats[filter]
613
619
614 def _filter(self, filterpats, filename, data):
620 def _filter(self, filterpats, filename, data):
615 for mf, fn, cmd in filterpats:
621 for mf, fn, cmd in filterpats:
616 if mf(filename):
622 if mf(filename):
617 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
623 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
618 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
624 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
619 break
625 break
620
626
621 return data
627 return data
622
628
623 @propertycache
629 @propertycache
624 def _encodefilterpats(self):
630 def _encodefilterpats(self):
625 return self._loadfilter('encode')
631 return self._loadfilter('encode')
626
632
627 @propertycache
633 @propertycache
628 def _decodefilterpats(self):
634 def _decodefilterpats(self):
629 return self._loadfilter('decode')
635 return self._loadfilter('decode')
630
636
631 def adddatafilter(self, name, filter):
637 def adddatafilter(self, name, filter):
632 self._datafilters[name] = filter
638 self._datafilters[name] = filter
633
639
634 def wread(self, filename):
640 def wread(self, filename):
635 if self._link(filename):
641 if self._link(filename):
636 data = os.readlink(self.wjoin(filename))
642 data = os.readlink(self.wjoin(filename))
637 else:
643 else:
638 data = self.wopener(filename, 'r').read()
644 data = self.wopener(filename, 'r').read()
639 return self._filter(self._encodefilterpats, filename, data)
645 return self._filter(self._encodefilterpats, filename, data)
640
646
641 def wwrite(self, filename, data, flags):
647 def wwrite(self, filename, data, flags):
642 data = self._filter(self._decodefilterpats, filename, data)
648 data = self._filter(self._decodefilterpats, filename, data)
643 if 'l' in flags:
649 if 'l' in flags:
644 self.wopener.symlink(data, filename)
650 self.wopener.symlink(data, filename)
645 else:
651 else:
646 self.wopener(filename, 'w').write(data)
652 self.wopener(filename, 'w').write(data)
647 if 'x' in flags:
653 if 'x' in flags:
648 util.set_flags(self.wjoin(filename), False, True)
654 util.set_flags(self.wjoin(filename), False, True)
649
655
650 def wwritedata(self, filename, data):
656 def wwritedata(self, filename, data):
651 return self._filter(self._decodefilterpats, filename, data)
657 return self._filter(self._decodefilterpats, filename, data)
652
658
653 def transaction(self, desc):
659 def transaction(self, desc):
654 tr = self._transref and self._transref() or None
660 tr = self._transref and self._transref() or None
655 if tr and tr.running():
661 if tr and tr.running():
656 return tr.nest()
662 return tr.nest()
657
663
658 # abort here if the journal already exists
664 # abort here if the journal already exists
659 if os.path.exists(self.sjoin("journal")):
665 if os.path.exists(self.sjoin("journal")):
660 raise error.RepoError(
666 raise error.RepoError(
661 _("abandoned transaction found - run hg recover"))
667 _("abandoned transaction found - run hg recover"))
662
668
663 # save dirstate for rollback
669 # save dirstate for rollback
664 try:
670 try:
665 ds = self.opener("dirstate").read()
671 ds = self.opener("dirstate").read()
666 except IOError:
672 except IOError:
667 ds = ""
673 ds = ""
668 self.opener("journal.dirstate", "w").write(ds)
674 self.opener("journal.dirstate", "w").write(ds)
669 self.opener("journal.branch", "w").write(
675 self.opener("journal.branch", "w").write(
670 encoding.fromlocal(self.dirstate.branch()))
676 encoding.fromlocal(self.dirstate.branch()))
671 self.opener("journal.desc", "w").write("%d\n%s\n" % (len(self), desc))
677 self.opener("journal.desc", "w").write("%d\n%s\n" % (len(self), desc))
672
678
673 renames = [(self.sjoin("journal"), self.sjoin("undo")),
679 renames = [(self.sjoin("journal"), self.sjoin("undo")),
674 (self.join("journal.dirstate"), self.join("undo.dirstate")),
680 (self.join("journal.dirstate"), self.join("undo.dirstate")),
675 (self.join("journal.branch"), self.join("undo.branch")),
681 (self.join("journal.branch"), self.join("undo.branch")),
676 (self.join("journal.desc"), self.join("undo.desc"))]
682 (self.join("journal.desc"), self.join("undo.desc"))]
677 tr = transaction.transaction(self.ui.warn, self.sopener,
683 tr = transaction.transaction(self.ui.warn, self.sopener,
678 self.sjoin("journal"),
684 self.sjoin("journal"),
679 aftertrans(renames),
685 aftertrans(renames),
680 self.store.createmode)
686 self.store.createmode)
681 self._transref = weakref.ref(tr)
687 self._transref = weakref.ref(tr)
682 return tr
688 return tr
683
689
684 def recover(self):
690 def recover(self):
685 lock = self.lock()
691 lock = self.lock()
686 try:
692 try:
687 if os.path.exists(self.sjoin("journal")):
693 if os.path.exists(self.sjoin("journal")):
688 self.ui.status(_("rolling back interrupted transaction\n"))
694 self.ui.status(_("rolling back interrupted transaction\n"))
689 transaction.rollback(self.sopener, self.sjoin("journal"),
695 transaction.rollback(self.sopener, self.sjoin("journal"),
690 self.ui.warn)
696 self.ui.warn)
691 self.invalidate()
697 self.invalidate()
692 return True
698 return True
693 else:
699 else:
694 self.ui.warn(_("no interrupted transaction available\n"))
700 self.ui.warn(_("no interrupted transaction available\n"))
695 return False
701 return False
696 finally:
702 finally:
697 lock.release()
703 lock.release()
698
704
699 def rollback(self, dryrun=False):
705 def rollback(self, dryrun=False):
700 wlock = lock = None
706 wlock = lock = None
701 try:
707 try:
702 wlock = self.wlock()
708 wlock = self.wlock()
703 lock = self.lock()
709 lock = self.lock()
704 if os.path.exists(self.sjoin("undo")):
710 if os.path.exists(self.sjoin("undo")):
705 try:
711 try:
706 args = self.opener("undo.desc", "r").read().splitlines()
712 args = self.opener("undo.desc", "r").read().splitlines()
707 if len(args) >= 3 and self.ui.verbose:
713 if len(args) >= 3 and self.ui.verbose:
708 desc = _("rolling back to revision %s"
714 desc = _("rolling back to revision %s"
709 " (undo %s: %s)\n") % (
715 " (undo %s: %s)\n") % (
710 int(args[0]) - 1, args[1], args[2])
716 int(args[0]) - 1, args[1], args[2])
711 elif len(args) >= 2:
717 elif len(args) >= 2:
712 desc = _("rolling back to revision %s (undo %s)\n") % (
718 desc = _("rolling back to revision %s (undo %s)\n") % (
713 int(args[0]) - 1, args[1])
719 int(args[0]) - 1, args[1])
714 except IOError:
720 except IOError:
715 desc = _("rolling back unknown transaction\n")
721 desc = _("rolling back unknown transaction\n")
716 self.ui.status(desc)
722 self.ui.status(desc)
717 if dryrun:
723 if dryrun:
718 return
724 return
719 transaction.rollback(self.sopener, self.sjoin("undo"),
725 transaction.rollback(self.sopener, self.sjoin("undo"),
720 self.ui.warn)
726 self.ui.warn)
721 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
727 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
722 if os.path.exists(self.join('undo.bookmarks')):
728 if os.path.exists(self.join('undo.bookmarks')):
723 util.rename(self.join('undo.bookmarks'),
729 util.rename(self.join('undo.bookmarks'),
724 self.join('bookmarks'))
730 self.join('bookmarks'))
725 try:
731 try:
726 branch = self.opener("undo.branch").read()
732 branch = self.opener("undo.branch").read()
727 self.dirstate.setbranch(branch)
733 self.dirstate.setbranch(branch)
728 except IOError:
734 except IOError:
729 self.ui.warn(_("Named branch could not be reset, "
735 self.ui.warn(_("Named branch could not be reset, "
730 "current branch still is: %s\n")
736 "current branch still is: %s\n")
731 % self.dirstate.branch())
737 % self.dirstate.branch())
732 self.invalidate()
738 self.invalidate()
733 self.dirstate.invalidate()
739 self.dirstate.invalidate()
734 self.destroyed()
740 self.destroyed()
735 else:
741 else:
736 self.ui.warn(_("no rollback information available\n"))
742 self.ui.warn(_("no rollback information available\n"))
737 return 1
743 return 1
738 finally:
744 finally:
739 release(lock, wlock)
745 release(lock, wlock)
740
746
741 def invalidatecaches(self):
747 def invalidatecaches(self):
742 self._tags = None
748 self._tags = None
743 self._tagtypes = None
749 self._tagtypes = None
744 self.nodetagscache = None
750 self.nodetagscache = None
745 self._branchcache = None # in UTF-8
751 self._branchcache = None # in UTF-8
746 self._branchcachetip = None
752 self._branchcachetip = None
747
753
748 def invalidate(self):
754 def invalidate(self):
749 for a in ("changelog", "manifest", "_bookmarks", "_bookmarkscurrent"):
755 for a in ("changelog", "manifest", "_bookmarks", "_bookmarkscurrent"):
750 if a in self.__dict__:
756 if a in self.__dict__:
751 delattr(self, a)
757 delattr(self, a)
752 self.invalidatecaches()
758 self.invalidatecaches()
753
759
754 def _lock(self, lockname, wait, releasefn, acquirefn, desc):
760 def _lock(self, lockname, wait, releasefn, acquirefn, desc):
755 try:
761 try:
756 l = lock.lock(lockname, 0, releasefn, desc=desc)
762 l = lock.lock(lockname, 0, releasefn, desc=desc)
757 except error.LockHeld, inst:
763 except error.LockHeld, inst:
758 if not wait:
764 if not wait:
759 raise
765 raise
760 self.ui.warn(_("waiting for lock on %s held by %r\n") %
766 self.ui.warn(_("waiting for lock on %s held by %r\n") %
761 (desc, inst.locker))
767 (desc, inst.locker))
762 # default to 600 seconds timeout
768 # default to 600 seconds timeout
763 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
769 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
764 releasefn, desc=desc)
770 releasefn, desc=desc)
765 if acquirefn:
771 if acquirefn:
766 acquirefn()
772 acquirefn()
767 return l
773 return l
768
774
769 def lock(self, wait=True):
775 def lock(self, wait=True):
770 '''Lock the repository store (.hg/store) and return a weak reference
776 '''Lock the repository store (.hg/store) and return a weak reference
771 to the lock. Use this before modifying the store (e.g. committing or
777 to the lock. Use this before modifying the store (e.g. committing or
772 stripping). If you are opening a transaction, get a lock as well.)'''
778 stripping). If you are opening a transaction, get a lock as well.)'''
773 l = self._lockref and self._lockref()
779 l = self._lockref and self._lockref()
774 if l is not None and l.held:
780 if l is not None and l.held:
775 l.lock()
781 l.lock()
776 return l
782 return l
777
783
778 l = self._lock(self.sjoin("lock"), wait, None, self.invalidate,
784 l = self._lock(self.sjoin("lock"), wait, None, self.invalidate,
779 _('repository %s') % self.origroot)
785 _('repository %s') % self.origroot)
780 self._lockref = weakref.ref(l)
786 self._lockref = weakref.ref(l)
781 return l
787 return l
782
788
783 def wlock(self, wait=True):
789 def wlock(self, wait=True):
784 '''Lock the non-store parts of the repository (everything under
790 '''Lock the non-store parts of the repository (everything under
785 .hg except .hg/store) and return a weak reference to the lock.
791 .hg except .hg/store) and return a weak reference to the lock.
786 Use this before modifying files in .hg.'''
792 Use this before modifying files in .hg.'''
787 l = self._wlockref and self._wlockref()
793 l = self._wlockref and self._wlockref()
788 if l is not None and l.held:
794 if l is not None and l.held:
789 l.lock()
795 l.lock()
790 return l
796 return l
791
797
792 l = self._lock(self.join("wlock"), wait, self.dirstate.write,
798 l = self._lock(self.join("wlock"), wait, self.dirstate.write,
793 self.dirstate.invalidate, _('working directory of %s') %
799 self.dirstate.invalidate, _('working directory of %s') %
794 self.origroot)
800 self.origroot)
795 self._wlockref = weakref.ref(l)
801 self._wlockref = weakref.ref(l)
796 return l
802 return l
797
803
798 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
804 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
799 """
805 """
800 commit an individual file as part of a larger transaction
806 commit an individual file as part of a larger transaction
801 """
807 """
802
808
803 fname = fctx.path()
809 fname = fctx.path()
804 text = fctx.data()
810 text = fctx.data()
805 flog = self.file(fname)
811 flog = self.file(fname)
806 fparent1 = manifest1.get(fname, nullid)
812 fparent1 = manifest1.get(fname, nullid)
807 fparent2 = fparent2o = manifest2.get(fname, nullid)
813 fparent2 = fparent2o = manifest2.get(fname, nullid)
808
814
809 meta = {}
815 meta = {}
810 copy = fctx.renamed()
816 copy = fctx.renamed()
811 if copy and copy[0] != fname:
817 if copy and copy[0] != fname:
812 # Mark the new revision of this file as a copy of another
818 # Mark the new revision of this file as a copy of another
813 # file. This copy data will effectively act as a parent
819 # file. This copy data will effectively act as a parent
814 # of this new revision. If this is a merge, the first
820 # of this new revision. If this is a merge, the first
815 # parent will be the nullid (meaning "look up the copy data")
821 # parent will be the nullid (meaning "look up the copy data")
816 # and the second one will be the other parent. For example:
822 # and the second one will be the other parent. For example:
817 #
823 #
818 # 0 --- 1 --- 3 rev1 changes file foo
824 # 0 --- 1 --- 3 rev1 changes file foo
819 # \ / rev2 renames foo to bar and changes it
825 # \ / rev2 renames foo to bar and changes it
820 # \- 2 -/ rev3 should have bar with all changes and
826 # \- 2 -/ rev3 should have bar with all changes and
821 # should record that bar descends from
827 # should record that bar descends from
822 # bar in rev2 and foo in rev1
828 # bar in rev2 and foo in rev1
823 #
829 #
824 # this allows this merge to succeed:
830 # this allows this merge to succeed:
825 #
831 #
826 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
832 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
827 # \ / merging rev3 and rev4 should use bar@rev2
833 # \ / merging rev3 and rev4 should use bar@rev2
828 # \- 2 --- 4 as the merge base
834 # \- 2 --- 4 as the merge base
829 #
835 #
830
836
831 cfname = copy[0]
837 cfname = copy[0]
832 crev = manifest1.get(cfname)
838 crev = manifest1.get(cfname)
833 newfparent = fparent2
839 newfparent = fparent2
834
840
835 if manifest2: # branch merge
841 if manifest2: # branch merge
836 if fparent2 == nullid or crev is None: # copied on remote side
842 if fparent2 == nullid or crev is None: # copied on remote side
837 if cfname in manifest2:
843 if cfname in manifest2:
838 crev = manifest2[cfname]
844 crev = manifest2[cfname]
839 newfparent = fparent1
845 newfparent = fparent1
840
846
841 # find source in nearest ancestor if we've lost track
847 # find source in nearest ancestor if we've lost track
842 if not crev:
848 if not crev:
843 self.ui.debug(" %s: searching for copy revision for %s\n" %
849 self.ui.debug(" %s: searching for copy revision for %s\n" %
844 (fname, cfname))
850 (fname, cfname))
845 for ancestor in self[None].ancestors():
851 for ancestor in self[None].ancestors():
846 if cfname in ancestor:
852 if cfname in ancestor:
847 crev = ancestor[cfname].filenode()
853 crev = ancestor[cfname].filenode()
848 break
854 break
849
855
850 if crev:
856 if crev:
851 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
857 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
852 meta["copy"] = cfname
858 meta["copy"] = cfname
853 meta["copyrev"] = hex(crev)
859 meta["copyrev"] = hex(crev)
854 fparent1, fparent2 = nullid, newfparent
860 fparent1, fparent2 = nullid, newfparent
855 else:
861 else:
856 self.ui.warn(_("warning: can't find ancestor for '%s' "
862 self.ui.warn(_("warning: can't find ancestor for '%s' "
857 "copied from '%s'!\n") % (fname, cfname))
863 "copied from '%s'!\n") % (fname, cfname))
858
864
859 elif fparent2 != nullid:
865 elif fparent2 != nullid:
860 # is one parent an ancestor of the other?
866 # is one parent an ancestor of the other?
861 fparentancestor = flog.ancestor(fparent1, fparent2)
867 fparentancestor = flog.ancestor(fparent1, fparent2)
862 if fparentancestor == fparent1:
868 if fparentancestor == fparent1:
863 fparent1, fparent2 = fparent2, nullid
869 fparent1, fparent2 = fparent2, nullid
864 elif fparentancestor == fparent2:
870 elif fparentancestor == fparent2:
865 fparent2 = nullid
871 fparent2 = nullid
866
872
867 # is the file changed?
873 # is the file changed?
868 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
874 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
869 changelist.append(fname)
875 changelist.append(fname)
870 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
876 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
871
877
872 # are just the flags changed during merge?
878 # are just the flags changed during merge?
873 if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags():
879 if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags():
874 changelist.append(fname)
880 changelist.append(fname)
875
881
876 return fparent1
882 return fparent1
877
883
878 def commit(self, text="", user=None, date=None, match=None, force=False,
884 def commit(self, text="", user=None, date=None, match=None, force=False,
879 editor=False, extra={}):
885 editor=False, extra={}):
880 """Add a new revision to current repository.
886 """Add a new revision to current repository.
881
887
882 Revision information is gathered from the working directory,
888 Revision information is gathered from the working directory,
883 match can be used to filter the committed files. If editor is
889 match can be used to filter the committed files. If editor is
884 supplied, it is called to get a commit message.
890 supplied, it is called to get a commit message.
885 """
891 """
886
892
887 def fail(f, msg):
893 def fail(f, msg):
888 raise util.Abort('%s: %s' % (f, msg))
894 raise util.Abort('%s: %s' % (f, msg))
889
895
890 if not match:
896 if not match:
891 match = matchmod.always(self.root, '')
897 match = matchmod.always(self.root, '')
892
898
893 if not force:
899 if not force:
894 vdirs = []
900 vdirs = []
895 match.dir = vdirs.append
901 match.dir = vdirs.append
896 match.bad = fail
902 match.bad = fail
897
903
898 wlock = self.wlock()
904 wlock = self.wlock()
899 try:
905 try:
900 wctx = self[None]
906 wctx = self[None]
901 merge = len(wctx.parents()) > 1
907 merge = len(wctx.parents()) > 1
902
908
903 if (not force and merge and match and
909 if (not force and merge and match and
904 (match.files() or match.anypats())):
910 (match.files() or match.anypats())):
905 raise util.Abort(_('cannot partially commit a merge '
911 raise util.Abort(_('cannot partially commit a merge '
906 '(do not specify files or patterns)'))
912 '(do not specify files or patterns)'))
907
913
908 changes = self.status(match=match, clean=force)
914 changes = self.status(match=match, clean=force)
909 if force:
915 if force:
910 changes[0].extend(changes[6]) # mq may commit unchanged files
916 changes[0].extend(changes[6]) # mq may commit unchanged files
911
917
912 # check subrepos
918 # check subrepos
913 subs = []
919 subs = []
914 removedsubs = set()
920 removedsubs = set()
915 for p in wctx.parents():
921 for p in wctx.parents():
916 removedsubs.update(s for s in p.substate if match(s))
922 removedsubs.update(s for s in p.substate if match(s))
917 for s in wctx.substate:
923 for s in wctx.substate:
918 removedsubs.discard(s)
924 removedsubs.discard(s)
919 if match(s) and wctx.sub(s).dirty():
925 if match(s) and wctx.sub(s).dirty():
920 subs.append(s)
926 subs.append(s)
921 if (subs or removedsubs):
927 if (subs or removedsubs):
922 if (not match('.hgsub') and
928 if (not match('.hgsub') and
923 '.hgsub' in (wctx.modified() + wctx.added())):
929 '.hgsub' in (wctx.modified() + wctx.added())):
924 raise util.Abort(_("can't commit subrepos without .hgsub"))
930 raise util.Abort(_("can't commit subrepos without .hgsub"))
925 if '.hgsubstate' not in changes[0]:
931 if '.hgsubstate' not in changes[0]:
926 changes[0].insert(0, '.hgsubstate')
932 changes[0].insert(0, '.hgsubstate')
927
933
928 # make sure all explicit patterns are matched
934 # make sure all explicit patterns are matched
929 if not force and match.files():
935 if not force and match.files():
930 matched = set(changes[0] + changes[1] + changes[2])
936 matched = set(changes[0] + changes[1] + changes[2])
931
937
932 for f in match.files():
938 for f in match.files():
933 if f == '.' or f in matched or f in wctx.substate:
939 if f == '.' or f in matched or f in wctx.substate:
934 continue
940 continue
935 if f in changes[3]: # missing
941 if f in changes[3]: # missing
936 fail(f, _('file not found!'))
942 fail(f, _('file not found!'))
937 if f in vdirs: # visited directory
943 if f in vdirs: # visited directory
938 d = f + '/'
944 d = f + '/'
939 for mf in matched:
945 for mf in matched:
940 if mf.startswith(d):
946 if mf.startswith(d):
941 break
947 break
942 else:
948 else:
943 fail(f, _("no match under directory!"))
949 fail(f, _("no match under directory!"))
944 elif f not in self.dirstate:
950 elif f not in self.dirstate:
945 fail(f, _("file not tracked!"))
951 fail(f, _("file not tracked!"))
946
952
947 if (not force and not extra.get("close") and not merge
953 if (not force and not extra.get("close") and not merge
948 and not (changes[0] or changes[1] or changes[2])
954 and not (changes[0] or changes[1] or changes[2])
949 and wctx.branch() == wctx.p1().branch()):
955 and wctx.branch() == wctx.p1().branch()):
950 return None
956 return None
951
957
952 ms = mergemod.mergestate(self)
958 ms = mergemod.mergestate(self)
953 for f in changes[0]:
959 for f in changes[0]:
954 if f in ms and ms[f] == 'u':
960 if f in ms and ms[f] == 'u':
955 raise util.Abort(_("unresolved merge conflicts "
961 raise util.Abort(_("unresolved merge conflicts "
956 "(see hg resolve)"))
962 "(see hg resolve)"))
957
963
958 cctx = context.workingctx(self, text, user, date, extra, changes)
964 cctx = context.workingctx(self, text, user, date, extra, changes)
959 if editor:
965 if editor:
960 cctx._text = editor(self, cctx, subs)
966 cctx._text = editor(self, cctx, subs)
961 edited = (text != cctx._text)
967 edited = (text != cctx._text)
962
968
963 # commit subs
969 # commit subs
964 if subs or removedsubs:
970 if subs or removedsubs:
965 state = wctx.substate.copy()
971 state = wctx.substate.copy()
966 for s in sorted(subs):
972 for s in sorted(subs):
967 sub = wctx.sub(s)
973 sub = wctx.sub(s)
968 self.ui.status(_('committing subrepository %s\n') %
974 self.ui.status(_('committing subrepository %s\n') %
969 subrepo.subrelpath(sub))
975 subrepo.subrelpath(sub))
970 sr = sub.commit(cctx._text, user, date)
976 sr = sub.commit(cctx._text, user, date)
971 state[s] = (state[s][0], sr)
977 state[s] = (state[s][0], sr)
972 subrepo.writestate(self, state)
978 subrepo.writestate(self, state)
973
979
974 # Save commit message in case this transaction gets rolled back
980 # Save commit message in case this transaction gets rolled back
975 # (e.g. by a pretxncommit hook). Leave the content alone on
981 # (e.g. by a pretxncommit hook). Leave the content alone on
976 # the assumption that the user will use the same editor again.
982 # the assumption that the user will use the same editor again.
977 msgfile = self.opener('last-message.txt', 'wb')
983 msgfile = self.opener('last-message.txt', 'wb')
978 msgfile.write(cctx._text)
984 msgfile.write(cctx._text)
979 msgfile.close()
985 msgfile.close()
980
986
981 p1, p2 = self.dirstate.parents()
987 p1, p2 = self.dirstate.parents()
982 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
988 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
983 try:
989 try:
984 self.hook("precommit", throw=True, parent1=hookp1, parent2=hookp2)
990 self.hook("precommit", throw=True, parent1=hookp1, parent2=hookp2)
985 ret = self.commitctx(cctx, True)
991 ret = self.commitctx(cctx, True)
986 except:
992 except:
987 if edited:
993 if edited:
988 msgfn = self.pathto(msgfile.name[len(self.root)+1:])
994 msgfn = self.pathto(msgfile.name[len(self.root)+1:])
989 self.ui.write(
995 self.ui.write(
990 _('note: commit message saved in %s\n') % msgfn)
996 _('note: commit message saved in %s\n') % msgfn)
991 raise
997 raise
992
998
993 # update bookmarks, dirstate and mergestate
999 # update bookmarks, dirstate and mergestate
994 parents = (p1, p2)
1000 parents = (p1, p2)
995 if p2 == nullid:
1001 if p2 == nullid:
996 parents = (p1,)
1002 parents = (p1,)
997 bookmarks.update(self, parents, ret)
1003 bookmarks.update(self, parents, ret)
998 for f in changes[0] + changes[1]:
1004 for f in changes[0] + changes[1]:
999 self.dirstate.normal(f)
1005 self.dirstate.normal(f)
1000 for f in changes[2]:
1006 for f in changes[2]:
1001 self.dirstate.forget(f)
1007 self.dirstate.forget(f)
1002 self.dirstate.setparents(ret)
1008 self.dirstate.setparents(ret)
1003 ms.reset()
1009 ms.reset()
1004 finally:
1010 finally:
1005 wlock.release()
1011 wlock.release()
1006
1012
1007 self.hook("commit", node=hex(ret), parent1=hookp1, parent2=hookp2)
1013 self.hook("commit", node=hex(ret), parent1=hookp1, parent2=hookp2)
1008 return ret
1014 return ret
1009
1015
1010 def commitctx(self, ctx, error=False):
1016 def commitctx(self, ctx, error=False):
1011 """Add a new revision to current repository.
1017 """Add a new revision to current repository.
1012 Revision information is passed via the context argument.
1018 Revision information is passed via the context argument.
1013 """
1019 """
1014
1020
1015 tr = lock = None
1021 tr = lock = None
1016 removed = list(ctx.removed())
1022 removed = list(ctx.removed())
1017 p1, p2 = ctx.p1(), ctx.p2()
1023 p1, p2 = ctx.p1(), ctx.p2()
1018 m1 = p1.manifest().copy()
1024 m1 = p1.manifest().copy()
1019 m2 = p2.manifest()
1025 m2 = p2.manifest()
1020 user = ctx.user()
1026 user = ctx.user()
1021
1027
1022 lock = self.lock()
1028 lock = self.lock()
1023 try:
1029 try:
1024 tr = self.transaction("commit")
1030 tr = self.transaction("commit")
1025 trp = weakref.proxy(tr)
1031 trp = weakref.proxy(tr)
1026
1032
1027 # check in files
1033 # check in files
1028 new = {}
1034 new = {}
1029 changed = []
1035 changed = []
1030 linkrev = len(self)
1036 linkrev = len(self)
1031 for f in sorted(ctx.modified() + ctx.added()):
1037 for f in sorted(ctx.modified() + ctx.added()):
1032 self.ui.note(f + "\n")
1038 self.ui.note(f + "\n")
1033 try:
1039 try:
1034 fctx = ctx[f]
1040 fctx = ctx[f]
1035 new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
1041 new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
1036 changed)
1042 changed)
1037 m1.set(f, fctx.flags())
1043 m1.set(f, fctx.flags())
1038 except OSError, inst:
1044 except OSError, inst:
1039 self.ui.warn(_("trouble committing %s!\n") % f)
1045 self.ui.warn(_("trouble committing %s!\n") % f)
1040 raise
1046 raise
1041 except IOError, inst:
1047 except IOError, inst:
1042 errcode = getattr(inst, 'errno', errno.ENOENT)
1048 errcode = getattr(inst, 'errno', errno.ENOENT)
1043 if error or errcode and errcode != errno.ENOENT:
1049 if error or errcode and errcode != errno.ENOENT:
1044 self.ui.warn(_("trouble committing %s!\n") % f)
1050 self.ui.warn(_("trouble committing %s!\n") % f)
1045 raise
1051 raise
1046 else:
1052 else:
1047 removed.append(f)
1053 removed.append(f)
1048
1054
1049 # update manifest
1055 # update manifest
1050 m1.update(new)
1056 m1.update(new)
1051 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1057 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1052 drop = [f for f in removed if f in m1]
1058 drop = [f for f in removed if f in m1]
1053 for f in drop:
1059 for f in drop:
1054 del m1[f]
1060 del m1[f]
1055 mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
1061 mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
1056 p2.manifestnode(), (new, drop))
1062 p2.manifestnode(), (new, drop))
1057
1063
1058 # update changelog
1064 # update changelog
1059 self.changelog.delayupdate()
1065 self.changelog.delayupdate()
1060 n = self.changelog.add(mn, changed + removed, ctx.description(),
1066 n = self.changelog.add(mn, changed + removed, ctx.description(),
1061 trp, p1.node(), p2.node(),
1067 trp, p1.node(), p2.node(),
1062 user, ctx.date(), ctx.extra().copy())
1068 user, ctx.date(), ctx.extra().copy())
1063 p = lambda: self.changelog.writepending() and self.root or ""
1069 p = lambda: self.changelog.writepending() and self.root or ""
1064 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1070 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1065 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1071 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1066 parent2=xp2, pending=p)
1072 parent2=xp2, pending=p)
1067 self.changelog.finalize(trp)
1073 self.changelog.finalize(trp)
1068 tr.close()
1074 tr.close()
1069
1075
1070 if self._branchcache:
1076 if self._branchcache:
1071 self.updatebranchcache()
1077 self.updatebranchcache()
1072 return n
1078 return n
1073 finally:
1079 finally:
1074 if tr:
1080 if tr:
1075 tr.release()
1081 tr.release()
1076 lock.release()
1082 lock.release()
1077
1083
1078 def destroyed(self):
1084 def destroyed(self):
1079 '''Inform the repository that nodes have been destroyed.
1085 '''Inform the repository that nodes have been destroyed.
1080 Intended for use by strip and rollback, so there's a common
1086 Intended for use by strip and rollback, so there's a common
1081 place for anything that has to be done after destroying history.'''
1087 place for anything that has to be done after destroying history.'''
1082 # XXX it might be nice if we could take the list of destroyed
1088 # XXX it might be nice if we could take the list of destroyed
1083 # nodes, but I don't see an easy way for rollback() to do that
1089 # nodes, but I don't see an easy way for rollback() to do that
1084
1090
1085 # Ensure the persistent tag cache is updated. Doing it now
1091 # Ensure the persistent tag cache is updated. Doing it now
1086 # means that the tag cache only has to worry about destroyed
1092 # means that the tag cache only has to worry about destroyed
1087 # heads immediately after a strip/rollback. That in turn
1093 # heads immediately after a strip/rollback. That in turn
1088 # guarantees that "cachetip == currenttip" (comparing both rev
1094 # guarantees that "cachetip == currenttip" (comparing both rev
1089 # and node) always means no nodes have been added or destroyed.
1095 # and node) always means no nodes have been added or destroyed.
1090
1096
1091 # XXX this is suboptimal when qrefresh'ing: we strip the current
1097 # XXX this is suboptimal when qrefresh'ing: we strip the current
1092 # head, refresh the tag cache, then immediately add a new head.
1098 # head, refresh the tag cache, then immediately add a new head.
1093 # But I think doing it this way is necessary for the "instant
1099 # But I think doing it this way is necessary for the "instant
1094 # tag cache retrieval" case to work.
1100 # tag cache retrieval" case to work.
1095 self.invalidatecaches()
1101 self.invalidatecaches()
1096
1102
1097 def walk(self, match, node=None):
1103 def walk(self, match, node=None):
1098 '''
1104 '''
1099 walk recursively through the directory tree or a given
1105 walk recursively through the directory tree or a given
1100 changeset, finding all files matched by the match
1106 changeset, finding all files matched by the match
1101 function
1107 function
1102 '''
1108 '''
1103 return self[node].walk(match)
1109 return self[node].walk(match)
1104
1110
1105 def status(self, node1='.', node2=None, match=None,
1111 def status(self, node1='.', node2=None, match=None,
1106 ignored=False, clean=False, unknown=False,
1112 ignored=False, clean=False, unknown=False,
1107 listsubrepos=False):
1113 listsubrepos=False):
1108 """return status of files between two nodes or node and working directory
1114 """return status of files between two nodes or node and working directory
1109
1115
1110 If node1 is None, use the first dirstate parent instead.
1116 If node1 is None, use the first dirstate parent instead.
1111 If node2 is None, compare node1 with working directory.
1117 If node2 is None, compare node1 with working directory.
1112 """
1118 """
1113
1119
1114 def mfmatches(ctx):
1120 def mfmatches(ctx):
1115 mf = ctx.manifest().copy()
1121 mf = ctx.manifest().copy()
1116 for fn in mf.keys():
1122 for fn in mf.keys():
1117 if not match(fn):
1123 if not match(fn):
1118 del mf[fn]
1124 del mf[fn]
1119 return mf
1125 return mf
1120
1126
1121 if isinstance(node1, context.changectx):
1127 if isinstance(node1, context.changectx):
1122 ctx1 = node1
1128 ctx1 = node1
1123 else:
1129 else:
1124 ctx1 = self[node1]
1130 ctx1 = self[node1]
1125 if isinstance(node2, context.changectx):
1131 if isinstance(node2, context.changectx):
1126 ctx2 = node2
1132 ctx2 = node2
1127 else:
1133 else:
1128 ctx2 = self[node2]
1134 ctx2 = self[node2]
1129
1135
1130 working = ctx2.rev() is None
1136 working = ctx2.rev() is None
1131 parentworking = working and ctx1 == self['.']
1137 parentworking = working and ctx1 == self['.']
1132 match = match or matchmod.always(self.root, self.getcwd())
1138 match = match or matchmod.always(self.root, self.getcwd())
1133 listignored, listclean, listunknown = ignored, clean, unknown
1139 listignored, listclean, listunknown = ignored, clean, unknown
1134
1140
1135 # load earliest manifest first for caching reasons
1141 # load earliest manifest first for caching reasons
1136 if not working and ctx2.rev() < ctx1.rev():
1142 if not working and ctx2.rev() < ctx1.rev():
1137 ctx2.manifest()
1143 ctx2.manifest()
1138
1144
1139 if not parentworking:
1145 if not parentworking:
1140 def bad(f, msg):
1146 def bad(f, msg):
1141 if f not in ctx1:
1147 if f not in ctx1:
1142 self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
1148 self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
1143 match.bad = bad
1149 match.bad = bad
1144
1150
1145 if working: # we need to scan the working dir
1151 if working: # we need to scan the working dir
1146 subrepos = []
1152 subrepos = []
1147 if '.hgsub' in self.dirstate:
1153 if '.hgsub' in self.dirstate:
1148 subrepos = ctx1.substate.keys()
1154 subrepos = ctx1.substate.keys()
1149 s = self.dirstate.status(match, subrepos, listignored,
1155 s = self.dirstate.status(match, subrepos, listignored,
1150 listclean, listunknown)
1156 listclean, listunknown)
1151 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
1157 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
1152
1158
1153 # check for any possibly clean files
1159 # check for any possibly clean files
1154 if parentworking and cmp:
1160 if parentworking and cmp:
1155 fixup = []
1161 fixup = []
1156 # do a full compare of any files that might have changed
1162 # do a full compare of any files that might have changed
1157 for f in sorted(cmp):
1163 for f in sorted(cmp):
1158 if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
1164 if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
1159 or ctx1[f].cmp(ctx2[f])):
1165 or ctx1[f].cmp(ctx2[f])):
1160 modified.append(f)
1166 modified.append(f)
1161 else:
1167 else:
1162 fixup.append(f)
1168 fixup.append(f)
1163
1169
1164 # update dirstate for files that are actually clean
1170 # update dirstate for files that are actually clean
1165 if fixup:
1171 if fixup:
1166 if listclean:
1172 if listclean:
1167 clean += fixup
1173 clean += fixup
1168
1174
1169 try:
1175 try:
1170 # updating the dirstate is optional
1176 # updating the dirstate is optional
1171 # so we don't wait on the lock
1177 # so we don't wait on the lock
1172 wlock = self.wlock(False)
1178 wlock = self.wlock(False)
1173 try:
1179 try:
1174 for f in fixup:
1180 for f in fixup:
1175 self.dirstate.normal(f)
1181 self.dirstate.normal(f)
1176 finally:
1182 finally:
1177 wlock.release()
1183 wlock.release()
1178 except error.LockError:
1184 except error.LockError:
1179 pass
1185 pass
1180
1186
1181 if not parentworking:
1187 if not parentworking:
1182 mf1 = mfmatches(ctx1)
1188 mf1 = mfmatches(ctx1)
1183 if working:
1189 if working:
1184 # we are comparing working dir against non-parent
1190 # we are comparing working dir against non-parent
1185 # generate a pseudo-manifest for the working dir
1191 # generate a pseudo-manifest for the working dir
1186 mf2 = mfmatches(self['.'])
1192 mf2 = mfmatches(self['.'])
1187 for f in cmp + modified + added:
1193 for f in cmp + modified + added:
1188 mf2[f] = None
1194 mf2[f] = None
1189 mf2.set(f, ctx2.flags(f))
1195 mf2.set(f, ctx2.flags(f))
1190 for f in removed:
1196 for f in removed:
1191 if f in mf2:
1197 if f in mf2:
1192 del mf2[f]
1198 del mf2[f]
1193 else:
1199 else:
1194 # we are comparing two revisions
1200 # we are comparing two revisions
1195 deleted, unknown, ignored = [], [], []
1201 deleted, unknown, ignored = [], [], []
1196 mf2 = mfmatches(ctx2)
1202 mf2 = mfmatches(ctx2)
1197
1203
1198 modified, added, clean = [], [], []
1204 modified, added, clean = [], [], []
1199 for fn in mf2:
1205 for fn in mf2:
1200 if fn in mf1:
1206 if fn in mf1:
1201 if (mf1.flags(fn) != mf2.flags(fn) or
1207 if (mf1.flags(fn) != mf2.flags(fn) or
1202 (mf1[fn] != mf2[fn] and
1208 (mf1[fn] != mf2[fn] and
1203 (mf2[fn] or ctx1[fn].cmp(ctx2[fn])))):
1209 (mf2[fn] or ctx1[fn].cmp(ctx2[fn])))):
1204 modified.append(fn)
1210 modified.append(fn)
1205 elif listclean:
1211 elif listclean:
1206 clean.append(fn)
1212 clean.append(fn)
1207 del mf1[fn]
1213 del mf1[fn]
1208 else:
1214 else:
1209 added.append(fn)
1215 added.append(fn)
1210 removed = mf1.keys()
1216 removed = mf1.keys()
1211
1217
1212 r = modified, added, removed, deleted, unknown, ignored, clean
1218 r = modified, added, removed, deleted, unknown, ignored, clean
1213
1219
1214 if listsubrepos:
1220 if listsubrepos:
1215 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
1221 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
1216 if working:
1222 if working:
1217 rev2 = None
1223 rev2 = None
1218 else:
1224 else:
1219 rev2 = ctx2.substate[subpath][1]
1225 rev2 = ctx2.substate[subpath][1]
1220 try:
1226 try:
1221 submatch = matchmod.narrowmatcher(subpath, match)
1227 submatch = matchmod.narrowmatcher(subpath, match)
1222 s = sub.status(rev2, match=submatch, ignored=listignored,
1228 s = sub.status(rev2, match=submatch, ignored=listignored,
1223 clean=listclean, unknown=listunknown,
1229 clean=listclean, unknown=listunknown,
1224 listsubrepos=True)
1230 listsubrepos=True)
1225 for rfiles, sfiles in zip(r, s):
1231 for rfiles, sfiles in zip(r, s):
1226 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
1232 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
1227 except error.LookupError:
1233 except error.LookupError:
1228 self.ui.status(_("skipping missing subrepository: %s\n")
1234 self.ui.status(_("skipping missing subrepository: %s\n")
1229 % subpath)
1235 % subpath)
1230
1236
1231 [l.sort() for l in r]
1237 [l.sort() for l in r]
1232 return r
1238 return r
1233
1239
1234 def heads(self, start=None):
1240 def heads(self, start=None):
1235 heads = self.changelog.heads(start)
1241 heads = self.changelog.heads(start)
1236 # sort the output in rev descending order
1242 # sort the output in rev descending order
1237 return sorted(heads, key=self.changelog.rev, reverse=True)
1243 return sorted(heads, key=self.changelog.rev, reverse=True)
1238
1244
1239 def branchheads(self, branch=None, start=None, closed=False):
1245 def branchheads(self, branch=None, start=None, closed=False):
1240 '''return a (possibly filtered) list of heads for the given branch
1246 '''return a (possibly filtered) list of heads for the given branch
1241
1247
1242 Heads are returned in topological order, from newest to oldest.
1248 Heads are returned in topological order, from newest to oldest.
1243 If branch is None, use the dirstate branch.
1249 If branch is None, use the dirstate branch.
1244 If start is not None, return only heads reachable from start.
1250 If start is not None, return only heads reachable from start.
1245 If closed is True, return heads that are marked as closed as well.
1251 If closed is True, return heads that are marked as closed as well.
1246 '''
1252 '''
1247 if branch is None:
1253 if branch is None:
1248 branch = self[None].branch()
1254 branch = self[None].branch()
1249 branches = self.branchmap()
1255 branches = self.branchmap()
1250 if branch not in branches:
1256 if branch not in branches:
1251 return []
1257 return []
1252 # the cache returns heads ordered lowest to highest
1258 # the cache returns heads ordered lowest to highest
1253 bheads = list(reversed(branches[branch]))
1259 bheads = list(reversed(branches[branch]))
1254 if start is not None:
1260 if start is not None:
1255 # filter out the heads that cannot be reached from startrev
1261 # filter out the heads that cannot be reached from startrev
1256 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1262 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1257 bheads = [h for h in bheads if h in fbheads]
1263 bheads = [h for h in bheads if h in fbheads]
1258 if not closed:
1264 if not closed:
1259 bheads = [h for h in bheads if
1265 bheads = [h for h in bheads if
1260 ('close' not in self.changelog.read(h)[5])]
1266 ('close' not in self.changelog.read(h)[5])]
1261 return bheads
1267 return bheads
1262
1268
1263 def branches(self, nodes):
1269 def branches(self, nodes):
1264 if not nodes:
1270 if not nodes:
1265 nodes = [self.changelog.tip()]
1271 nodes = [self.changelog.tip()]
1266 b = []
1272 b = []
1267 for n in nodes:
1273 for n in nodes:
1268 t = n
1274 t = n
1269 while 1:
1275 while 1:
1270 p = self.changelog.parents(n)
1276 p = self.changelog.parents(n)
1271 if p[1] != nullid or p[0] == nullid:
1277 if p[1] != nullid or p[0] == nullid:
1272 b.append((t, n, p[0], p[1]))
1278 b.append((t, n, p[0], p[1]))
1273 break
1279 break
1274 n = p[0]
1280 n = p[0]
1275 return b
1281 return b
1276
1282
1277 def between(self, pairs):
1283 def between(self, pairs):
1278 r = []
1284 r = []
1279
1285
1280 for top, bottom in pairs:
1286 for top, bottom in pairs:
1281 n, l, i = top, [], 0
1287 n, l, i = top, [], 0
1282 f = 1
1288 f = 1
1283
1289
1284 while n != bottom and n != nullid:
1290 while n != bottom and n != nullid:
1285 p = self.changelog.parents(n)[0]
1291 p = self.changelog.parents(n)[0]
1286 if i == f:
1292 if i == f:
1287 l.append(n)
1293 l.append(n)
1288 f = f * 2
1294 f = f * 2
1289 n = p
1295 n = p
1290 i += 1
1296 i += 1
1291
1297
1292 r.append(l)
1298 r.append(l)
1293
1299
1294 return r
1300 return r
1295
1301
1296 def pull(self, remote, heads=None, force=False):
1302 def pull(self, remote, heads=None, force=False):
1297 lock = self.lock()
1303 lock = self.lock()
1298 try:
1304 try:
1299 tmp = discovery.findcommonincoming(self, remote, heads=heads,
1305 tmp = discovery.findcommonincoming(self, remote, heads=heads,
1300 force=force)
1306 force=force)
1301 common, fetch, rheads = tmp
1307 common, fetch, rheads = tmp
1302 if not fetch:
1308 if not fetch:
1303 self.ui.status(_("no changes found\n"))
1309 self.ui.status(_("no changes found\n"))
1304 result = 0
1310 result = 0
1305 else:
1311 else:
1306 if heads is None and fetch == [nullid]:
1312 if heads is None and fetch == [nullid]:
1307 self.ui.status(_("requesting all changes\n"))
1313 self.ui.status(_("requesting all changes\n"))
1308 elif heads is None and remote.capable('changegroupsubset'):
1314 elif heads is None and remote.capable('changegroupsubset'):
1309 # issue1320, avoid a race if remote changed after discovery
1315 # issue1320, avoid a race if remote changed after discovery
1310 heads = rheads
1316 heads = rheads
1311
1317
1312 if heads is None:
1318 if heads is None:
1313 cg = remote.changegroup(fetch, 'pull')
1319 cg = remote.changegroup(fetch, 'pull')
1314 elif not remote.capable('changegroupsubset'):
1320 elif not remote.capable('changegroupsubset'):
1315 raise util.Abort(_("partial pull cannot be done because "
1321 raise util.Abort(_("partial pull cannot be done because "
1316 "other repository doesn't support "
1322 "other repository doesn't support "
1317 "changegroupsubset."))
1323 "changegroupsubset."))
1318 else:
1324 else:
1319 cg = remote.changegroupsubset(fetch, heads, 'pull')
1325 cg = remote.changegroupsubset(fetch, heads, 'pull')
1320 result = self.addchangegroup(cg, 'pull', remote.url(),
1326 result = self.addchangegroup(cg, 'pull', remote.url(),
1321 lock=lock)
1327 lock=lock)
1322 finally:
1328 finally:
1323 lock.release()
1329 lock.release()
1324
1330
1325 self.ui.debug("checking for updated bookmarks\n")
1331 self.ui.debug("checking for updated bookmarks\n")
1326 rb = remote.listkeys('bookmarks')
1332 rb = remote.listkeys('bookmarks')
1327 changed = False
1333 changed = False
1328 for k in rb.keys():
1334 for k in rb.keys():
1329 if k in self._bookmarks:
1335 if k in self._bookmarks:
1330 nr, nl = rb[k], self._bookmarks[k]
1336 nr, nl = rb[k], self._bookmarks[k]
1331 if nr in self:
1337 if nr in self:
1332 cr = self[nr]
1338 cr = self[nr]
1333 cl = self[nl]
1339 cl = self[nl]
1334 if cl.rev() >= cr.rev():
1340 if cl.rev() >= cr.rev():
1335 continue
1341 continue
1336 if cr in cl.descendants():
1342 if cr in cl.descendants():
1337 self._bookmarks[k] = cr.node()
1343 self._bookmarks[k] = cr.node()
1338 changed = True
1344 changed = True
1339 self.ui.status(_("updating bookmark %s\n") % k)
1345 self.ui.status(_("updating bookmark %s\n") % k)
1340 else:
1346 else:
1341 self.ui.warn(_("not updating divergent"
1347 self.ui.warn(_("not updating divergent"
1342 " bookmark %s\n") % k)
1348 " bookmark %s\n") % k)
1343 if changed:
1349 if changed:
1344 bookmarks.write(self)
1350 bookmarks.write(self)
1345
1351
1346 return result
1352 return result
1347
1353
1348 def checkpush(self, force, revs):
1354 def checkpush(self, force, revs):
1349 """Extensions can override this function if additional checks have
1355 """Extensions can override this function if additional checks have
1350 to be performed before pushing, or call it if they override push
1356 to be performed before pushing, or call it if they override push
1351 command.
1357 command.
1352 """
1358 """
1353 pass
1359 pass
1354
1360
1355 def push(self, remote, force=False, revs=None, newbranch=False):
1361 def push(self, remote, force=False, revs=None, newbranch=False):
1356 '''Push outgoing changesets (limited by revs) from the current
1362 '''Push outgoing changesets (limited by revs) from the current
1357 repository to remote. Return an integer:
1363 repository to remote. Return an integer:
1358 - 0 means HTTP error *or* nothing to push
1364 - 0 means HTTP error *or* nothing to push
1359 - 1 means we pushed and remote head count is unchanged *or*
1365 - 1 means we pushed and remote head count is unchanged *or*
1360 we have outgoing changesets but refused to push
1366 we have outgoing changesets but refused to push
1361 - other values as described by addchangegroup()
1367 - other values as described by addchangegroup()
1362 '''
1368 '''
1363 # there are two ways to push to remote repo:
1369 # there are two ways to push to remote repo:
1364 #
1370 #
1365 # addchangegroup assumes local user can lock remote
1371 # addchangegroup assumes local user can lock remote
1366 # repo (local filesystem, old ssh servers).
1372 # repo (local filesystem, old ssh servers).
1367 #
1373 #
1368 # unbundle assumes local user cannot lock remote repo (new ssh
1374 # unbundle assumes local user cannot lock remote repo (new ssh
1369 # servers, http servers).
1375 # servers, http servers).
1370
1376
1371 self.checkpush(force, revs)
1377 self.checkpush(force, revs)
1372 lock = None
1378 lock = None
1373 unbundle = remote.capable('unbundle')
1379 unbundle = remote.capable('unbundle')
1374 if not unbundle:
1380 if not unbundle:
1375 lock = remote.lock()
1381 lock = remote.lock()
1376 try:
1382 try:
1377 cg, remote_heads = discovery.prepush(self, remote, force, revs,
1383 cg, remote_heads = discovery.prepush(self, remote, force, revs,
1378 newbranch)
1384 newbranch)
1379 ret = remote_heads
1385 ret = remote_heads
1380 if cg is not None:
1386 if cg is not None:
1381 if unbundle:
1387 if unbundle:
1382 # local repo finds heads on server, finds out what
1388 # local repo finds heads on server, finds out what
1383 # revs it must push. once revs transferred, if server
1389 # revs it must push. once revs transferred, if server
1384 # finds it has different heads (someone else won
1390 # finds it has different heads (someone else won
1385 # commit/push race), server aborts.
1391 # commit/push race), server aborts.
1386 if force:
1392 if force:
1387 remote_heads = ['force']
1393 remote_heads = ['force']
1388 # ssh: return remote's addchangegroup()
1394 # ssh: return remote's addchangegroup()
1389 # http: return remote's addchangegroup() or 0 for error
1395 # http: return remote's addchangegroup() or 0 for error
1390 ret = remote.unbundle(cg, remote_heads, 'push')
1396 ret = remote.unbundle(cg, remote_heads, 'push')
1391 else:
1397 else:
1392 # we return an integer indicating remote head count change
1398 # we return an integer indicating remote head count change
1393 ret = remote.addchangegroup(cg, 'push', self.url(),
1399 ret = remote.addchangegroup(cg, 'push', self.url(),
1394 lock=lock)
1400 lock=lock)
1395 finally:
1401 finally:
1396 if lock is not None:
1402 if lock is not None:
1397 lock.release()
1403 lock.release()
1398
1404
1399 self.ui.debug("checking for updated bookmarks\n")
1405 self.ui.debug("checking for updated bookmarks\n")
1400 rb = remote.listkeys('bookmarks')
1406 rb = remote.listkeys('bookmarks')
1401 for k in rb.keys():
1407 for k in rb.keys():
1402 if k in self._bookmarks:
1408 if k in self._bookmarks:
1403 nr, nl = rb[k], hex(self._bookmarks[k])
1409 nr, nl = rb[k], hex(self._bookmarks[k])
1404 if nr in self:
1410 if nr in self:
1405 cr = self[nr]
1411 cr = self[nr]
1406 cl = self[nl]
1412 cl = self[nl]
1407 if cl in cr.descendants():
1413 if cl in cr.descendants():
1408 r = remote.pushkey('bookmarks', k, nr, nl)
1414 r = remote.pushkey('bookmarks', k, nr, nl)
1409 if r:
1415 if r:
1410 self.ui.status(_("updating bookmark %s\n") % k)
1416 self.ui.status(_("updating bookmark %s\n") % k)
1411 else:
1417 else:
1412 self.ui.warn(_('updating bookmark %s'
1418 self.ui.warn(_('updating bookmark %s'
1413 ' failed!\n') % k)
1419 ' failed!\n') % k)
1414
1420
1415 return ret
1421 return ret
1416
1422
1417 def changegroupinfo(self, nodes, source):
1423 def changegroupinfo(self, nodes, source):
1418 if self.ui.verbose or source == 'bundle':
1424 if self.ui.verbose or source == 'bundle':
1419 self.ui.status(_("%d changesets found\n") % len(nodes))
1425 self.ui.status(_("%d changesets found\n") % len(nodes))
1420 if self.ui.debugflag:
1426 if self.ui.debugflag:
1421 self.ui.debug("list of changesets:\n")
1427 self.ui.debug("list of changesets:\n")
1422 for node in nodes:
1428 for node in nodes:
1423 self.ui.debug("%s\n" % hex(node))
1429 self.ui.debug("%s\n" % hex(node))
1424
1430
1425 def changegroupsubset(self, bases, heads, source, extranodes=None):
1431 def changegroupsubset(self, bases, heads, source, extranodes=None):
1426 """Compute a changegroup consisting of all the nodes that are
1432 """Compute a changegroup consisting of all the nodes that are
1427 descendents of any of the bases and ancestors of any of the heads.
1433 descendents of any of the bases and ancestors of any of the heads.
1428 Return a chunkbuffer object whose read() method will return
1434 Return a chunkbuffer object whose read() method will return
1429 successive changegroup chunks.
1435 successive changegroup chunks.
1430
1436
1431 It is fairly complex as determining which filenodes and which
1437 It is fairly complex as determining which filenodes and which
1432 manifest nodes need to be included for the changeset to be complete
1438 manifest nodes need to be included for the changeset to be complete
1433 is non-trivial.
1439 is non-trivial.
1434
1440
1435 Another wrinkle is doing the reverse, figuring out which changeset in
1441 Another wrinkle is doing the reverse, figuring out which changeset in
1436 the changegroup a particular filenode or manifestnode belongs to.
1442 the changegroup a particular filenode or manifestnode belongs to.
1437
1443
1438 The caller can specify some nodes that must be included in the
1444 The caller can specify some nodes that must be included in the
1439 changegroup using the extranodes argument. It should be a dict
1445 changegroup using the extranodes argument. It should be a dict
1440 where the keys are the filenames (or 1 for the manifest), and the
1446 where the keys are the filenames (or 1 for the manifest), and the
1441 values are lists of (node, linknode) tuples, where node is a wanted
1447 values are lists of (node, linknode) tuples, where node is a wanted
1442 node and linknode is the changelog node that should be transmitted as
1448 node and linknode is the changelog node that should be transmitted as
1443 the linkrev.
1449 the linkrev.
1444 """
1450 """
1445
1451
1446 # Set up some initial variables
1452 # Set up some initial variables
1447 # Make it easy to refer to self.changelog
1453 # Make it easy to refer to self.changelog
1448 cl = self.changelog
1454 cl = self.changelog
1449 # Compute the list of changesets in this changegroup.
1455 # Compute the list of changesets in this changegroup.
1450 # Some bases may turn out to be superfluous, and some heads may be
1456 # Some bases may turn out to be superfluous, and some heads may be
1451 # too. nodesbetween will return the minimal set of bases and heads
1457 # too. nodesbetween will return the minimal set of bases and heads
1452 # necessary to re-create the changegroup.
1458 # necessary to re-create the changegroup.
1453 if not bases:
1459 if not bases:
1454 bases = [nullid]
1460 bases = [nullid]
1455 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
1461 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
1456
1462
1457 if extranodes is None:
1463 if extranodes is None:
1458 # can we go through the fast path ?
1464 # can we go through the fast path ?
1459 heads.sort()
1465 heads.sort()
1460 allheads = self.heads()
1466 allheads = self.heads()
1461 allheads.sort()
1467 allheads.sort()
1462 if heads == allheads:
1468 if heads == allheads:
1463 return self._changegroup(msng_cl_lst, source)
1469 return self._changegroup(msng_cl_lst, source)
1464
1470
1465 # slow path
1471 # slow path
1466 self.hook('preoutgoing', throw=True, source=source)
1472 self.hook('preoutgoing', throw=True, source=source)
1467
1473
1468 self.changegroupinfo(msng_cl_lst, source)
1474 self.changegroupinfo(msng_cl_lst, source)
1469
1475
1470 # We assume that all ancestors of bases are known
1476 # We assume that all ancestors of bases are known
1471 commonrevs = set(cl.ancestors(*[cl.rev(n) for n in bases]))
1477 commonrevs = set(cl.ancestors(*[cl.rev(n) for n in bases]))
1472
1478
1473 # Make it easy to refer to self.manifest
1479 # Make it easy to refer to self.manifest
1474 mnfst = self.manifest
1480 mnfst = self.manifest
1475 # We don't know which manifests are missing yet
1481 # We don't know which manifests are missing yet
1476 msng_mnfst_set = {}
1482 msng_mnfst_set = {}
1477 # Nor do we know which filenodes are missing.
1483 # Nor do we know which filenodes are missing.
1478 msng_filenode_set = {}
1484 msng_filenode_set = {}
1479
1485
1480 # A changeset always belongs to itself, so the changenode lookup
1486 # A changeset always belongs to itself, so the changenode lookup
1481 # function for a changenode is identity.
1487 # function for a changenode is identity.
1482 def identity(x):
1488 def identity(x):
1483 return x
1489 return x
1484
1490
1485 # A function generating function that sets up the initial environment
1491 # A function generating function that sets up the initial environment
1486 # the inner function.
1492 # the inner function.
1487 def filenode_collector(changedfiles):
1493 def filenode_collector(changedfiles):
1488 # This gathers information from each manifestnode included in the
1494 # This gathers information from each manifestnode included in the
1489 # changegroup about which filenodes the manifest node references
1495 # changegroup about which filenodes the manifest node references
1490 # so we can include those in the changegroup too.
1496 # so we can include those in the changegroup too.
1491 #
1497 #
1492 # It also remembers which changenode each filenode belongs to. It
1498 # It also remembers which changenode each filenode belongs to. It
1493 # does this by assuming the a filenode belongs to the changenode
1499 # does this by assuming the a filenode belongs to the changenode
1494 # the first manifest that references it belongs to.
1500 # the first manifest that references it belongs to.
1495 def collect_msng_filenodes(mnfstnode):
1501 def collect_msng_filenodes(mnfstnode):
1496 r = mnfst.rev(mnfstnode)
1502 r = mnfst.rev(mnfstnode)
1497 if mnfst.deltaparent(r) in mnfst.parentrevs(r):
1503 if mnfst.deltaparent(r) in mnfst.parentrevs(r):
1498 # If the previous rev is one of the parents,
1504 # If the previous rev is one of the parents,
1499 # we only need to see a diff.
1505 # we only need to see a diff.
1500 deltamf = mnfst.readdelta(mnfstnode)
1506 deltamf = mnfst.readdelta(mnfstnode)
1501 # For each line in the delta
1507 # For each line in the delta
1502 for f, fnode in deltamf.iteritems():
1508 for f, fnode in deltamf.iteritems():
1503 # And if the file is in the list of files we care
1509 # And if the file is in the list of files we care
1504 # about.
1510 # about.
1505 if f in changedfiles:
1511 if f in changedfiles:
1506 # Get the changenode this manifest belongs to
1512 # Get the changenode this manifest belongs to
1507 clnode = msng_mnfst_set[mnfstnode]
1513 clnode = msng_mnfst_set[mnfstnode]
1508 # Create the set of filenodes for the file if
1514 # Create the set of filenodes for the file if
1509 # there isn't one already.
1515 # there isn't one already.
1510 ndset = msng_filenode_set.setdefault(f, {})
1516 ndset = msng_filenode_set.setdefault(f, {})
1511 # And set the filenode's changelog node to the
1517 # And set the filenode's changelog node to the
1512 # manifest's if it hasn't been set already.
1518 # manifest's if it hasn't been set already.
1513 ndset.setdefault(fnode, clnode)
1519 ndset.setdefault(fnode, clnode)
1514 else:
1520 else:
1515 # Otherwise we need a full manifest.
1521 # Otherwise we need a full manifest.
1516 m = mnfst.read(mnfstnode)
1522 m = mnfst.read(mnfstnode)
1517 # For every file in we care about.
1523 # For every file in we care about.
1518 for f in changedfiles:
1524 for f in changedfiles:
1519 fnode = m.get(f, None)
1525 fnode = m.get(f, None)
1520 # If it's in the manifest
1526 # If it's in the manifest
1521 if fnode is not None:
1527 if fnode is not None:
1522 # See comments above.
1528 # See comments above.
1523 clnode = msng_mnfst_set[mnfstnode]
1529 clnode = msng_mnfst_set[mnfstnode]
1524 ndset = msng_filenode_set.setdefault(f, {})
1530 ndset = msng_filenode_set.setdefault(f, {})
1525 ndset.setdefault(fnode, clnode)
1531 ndset.setdefault(fnode, clnode)
1526 return collect_msng_filenodes
1532 return collect_msng_filenodes
1527
1533
1528 # If we determine that a particular file or manifest node must be a
1534 # If we determine that a particular file or manifest node must be a
1529 # node that the recipient of the changegroup will already have, we can
1535 # node that the recipient of the changegroup will already have, we can
1530 # also assume the recipient will have all the parents. This function
1536 # also assume the recipient will have all the parents. This function
1531 # prunes them from the set of missing nodes.
1537 # prunes them from the set of missing nodes.
1532 def prune(revlog, missingnodes):
1538 def prune(revlog, missingnodes):
1533 hasset = set()
1539 hasset = set()
1534 # If a 'missing' filenode thinks it belongs to a changenode we
1540 # If a 'missing' filenode thinks it belongs to a changenode we
1535 # assume the recipient must have, then the recipient must have
1541 # assume the recipient must have, then the recipient must have
1536 # that filenode.
1542 # that filenode.
1537 for n in missingnodes:
1543 for n in missingnodes:
1538 clrev = revlog.linkrev(revlog.rev(n))
1544 clrev = revlog.linkrev(revlog.rev(n))
1539 if clrev in commonrevs:
1545 if clrev in commonrevs:
1540 hasset.add(n)
1546 hasset.add(n)
1541 for n in hasset:
1547 for n in hasset:
1542 missingnodes.pop(n, None)
1548 missingnodes.pop(n, None)
1543 for r in revlog.ancestors(*[revlog.rev(n) for n in hasset]):
1549 for r in revlog.ancestors(*[revlog.rev(n) for n in hasset]):
1544 missingnodes.pop(revlog.node(r), None)
1550 missingnodes.pop(revlog.node(r), None)
1545
1551
1546 # Add the nodes that were explicitly requested.
1552 # Add the nodes that were explicitly requested.
1547 def add_extra_nodes(name, nodes):
1553 def add_extra_nodes(name, nodes):
1548 if not extranodes or name not in extranodes:
1554 if not extranodes or name not in extranodes:
1549 return
1555 return
1550
1556
1551 for node, linknode in extranodes[name]:
1557 for node, linknode in extranodes[name]:
1552 if node not in nodes:
1558 if node not in nodes:
1553 nodes[node] = linknode
1559 nodes[node] = linknode
1554
1560
1555 # Now that we have all theses utility functions to help out and
1561 # Now that we have all theses utility functions to help out and
1556 # logically divide up the task, generate the group.
1562 # logically divide up the task, generate the group.
1557 def gengroup():
1563 def gengroup():
1558 # The set of changed files starts empty.
1564 # The set of changed files starts empty.
1559 changedfiles = set()
1565 changedfiles = set()
1560 collect = changegroup.collector(cl, msng_mnfst_set, changedfiles)
1566 collect = changegroup.collector(cl, msng_mnfst_set, changedfiles)
1561
1567
1562 # Create a changenode group generator that will call our functions
1568 # Create a changenode group generator that will call our functions
1563 # back to lookup the owning changenode and collect information.
1569 # back to lookup the owning changenode and collect information.
1564 group = cl.group(msng_cl_lst, identity, collect)
1570 group = cl.group(msng_cl_lst, identity, collect)
1565 for cnt, chnk in enumerate(group):
1571 for cnt, chnk in enumerate(group):
1566 yield chnk
1572 yield chnk
1567 # revlog.group yields three entries per node, so
1573 # revlog.group yields three entries per node, so
1568 # dividing by 3 gives an approximation of how many
1574 # dividing by 3 gives an approximation of how many
1569 # nodes have been processed.
1575 # nodes have been processed.
1570 self.ui.progress(_('bundling'), cnt / 3,
1576 self.ui.progress(_('bundling'), cnt / 3,
1571 unit=_('changesets'))
1577 unit=_('changesets'))
1572 changecount = cnt / 3
1578 changecount = cnt / 3
1573 self.ui.progress(_('bundling'), None)
1579 self.ui.progress(_('bundling'), None)
1574
1580
1575 prune(mnfst, msng_mnfst_set)
1581 prune(mnfst, msng_mnfst_set)
1576 add_extra_nodes(1, msng_mnfst_set)
1582 add_extra_nodes(1, msng_mnfst_set)
1577 msng_mnfst_lst = msng_mnfst_set.keys()
1583 msng_mnfst_lst = msng_mnfst_set.keys()
1578 # Sort the manifestnodes by revision number.
1584 # Sort the manifestnodes by revision number.
1579 msng_mnfst_lst.sort(key=mnfst.rev)
1585 msng_mnfst_lst.sort(key=mnfst.rev)
1580 # Create a generator for the manifestnodes that calls our lookup
1586 # Create a generator for the manifestnodes that calls our lookup
1581 # and data collection functions back.
1587 # and data collection functions back.
1582 group = mnfst.group(msng_mnfst_lst,
1588 group = mnfst.group(msng_mnfst_lst,
1583 lambda mnode: msng_mnfst_set[mnode],
1589 lambda mnode: msng_mnfst_set[mnode],
1584 filenode_collector(changedfiles))
1590 filenode_collector(changedfiles))
1585 efiles = {}
1591 efiles = {}
1586 for cnt, chnk in enumerate(group):
1592 for cnt, chnk in enumerate(group):
1587 if cnt % 3 == 1:
1593 if cnt % 3 == 1:
1588 mnode = chnk[:20]
1594 mnode = chnk[:20]
1589 efiles.update(mnfst.readdelta(mnode))
1595 efiles.update(mnfst.readdelta(mnode))
1590 yield chnk
1596 yield chnk
1591 # see above comment for why we divide by 3
1597 # see above comment for why we divide by 3
1592 self.ui.progress(_('bundling'), cnt / 3,
1598 self.ui.progress(_('bundling'), cnt / 3,
1593 unit=_('manifests'), total=changecount)
1599 unit=_('manifests'), total=changecount)
1594 self.ui.progress(_('bundling'), None)
1600 self.ui.progress(_('bundling'), None)
1595 efiles = len(efiles)
1601 efiles = len(efiles)
1596
1602
1597 # These are no longer needed, dereference and toss the memory for
1603 # These are no longer needed, dereference and toss the memory for
1598 # them.
1604 # them.
1599 msng_mnfst_lst = None
1605 msng_mnfst_lst = None
1600 msng_mnfst_set.clear()
1606 msng_mnfst_set.clear()
1601
1607
1602 if extranodes:
1608 if extranodes:
1603 for fname in extranodes:
1609 for fname in extranodes:
1604 if isinstance(fname, int):
1610 if isinstance(fname, int):
1605 continue
1611 continue
1606 msng_filenode_set.setdefault(fname, {})
1612 msng_filenode_set.setdefault(fname, {})
1607 changedfiles.add(fname)
1613 changedfiles.add(fname)
1608 # Go through all our files in order sorted by name.
1614 # Go through all our files in order sorted by name.
1609 for idx, fname in enumerate(sorted(changedfiles)):
1615 for idx, fname in enumerate(sorted(changedfiles)):
1610 filerevlog = self.file(fname)
1616 filerevlog = self.file(fname)
1611 if not len(filerevlog):
1617 if not len(filerevlog):
1612 raise util.Abort(_("empty or missing revlog for %s") % fname)
1618 raise util.Abort(_("empty or missing revlog for %s") % fname)
1613 # Toss out the filenodes that the recipient isn't really
1619 # Toss out the filenodes that the recipient isn't really
1614 # missing.
1620 # missing.
1615 missingfnodes = msng_filenode_set.pop(fname, {})
1621 missingfnodes = msng_filenode_set.pop(fname, {})
1616 prune(filerevlog, missingfnodes)
1622 prune(filerevlog, missingfnodes)
1617 add_extra_nodes(fname, missingfnodes)
1623 add_extra_nodes(fname, missingfnodes)
1618 # If any filenodes are left, generate the group for them,
1624 # If any filenodes are left, generate the group for them,
1619 # otherwise don't bother.
1625 # otherwise don't bother.
1620 if missingfnodes:
1626 if missingfnodes:
1621 yield changegroup.chunkheader(len(fname))
1627 yield changegroup.chunkheader(len(fname))
1622 yield fname
1628 yield fname
1623 # Sort the filenodes by their revision # (topological order)
1629 # Sort the filenodes by their revision # (topological order)
1624 nodeiter = list(missingfnodes)
1630 nodeiter = list(missingfnodes)
1625 nodeiter.sort(key=filerevlog.rev)
1631 nodeiter.sort(key=filerevlog.rev)
1626 # Create a group generator and only pass in a changenode
1632 # Create a group generator and only pass in a changenode
1627 # lookup function as we need to collect no information
1633 # lookup function as we need to collect no information
1628 # from filenodes.
1634 # from filenodes.
1629 group = filerevlog.group(nodeiter,
1635 group = filerevlog.group(nodeiter,
1630 lambda fnode: missingfnodes[fnode])
1636 lambda fnode: missingfnodes[fnode])
1631 for chnk in group:
1637 for chnk in group:
1632 # even though we print the same progress on
1638 # even though we print the same progress on
1633 # most loop iterations, put the progress call
1639 # most loop iterations, put the progress call
1634 # here so that time estimates (if any) can be updated
1640 # here so that time estimates (if any) can be updated
1635 self.ui.progress(
1641 self.ui.progress(
1636 _('bundling'), idx, item=fname,
1642 _('bundling'), idx, item=fname,
1637 unit=_('files'), total=efiles)
1643 unit=_('files'), total=efiles)
1638 yield chnk
1644 yield chnk
1639 # Signal that no more groups are left.
1645 # Signal that no more groups are left.
1640 yield changegroup.closechunk()
1646 yield changegroup.closechunk()
1641 self.ui.progress(_('bundling'), None)
1647 self.ui.progress(_('bundling'), None)
1642
1648
1643 if msng_cl_lst:
1649 if msng_cl_lst:
1644 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
1650 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
1645
1651
1646 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
1652 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
1647
1653
1648 def changegroup(self, basenodes, source):
1654 def changegroup(self, basenodes, source):
1649 # to avoid a race we use changegroupsubset() (issue1320)
1655 # to avoid a race we use changegroupsubset() (issue1320)
1650 return self.changegroupsubset(basenodes, self.heads(), source)
1656 return self.changegroupsubset(basenodes, self.heads(), source)
1651
1657
1652 def _changegroup(self, nodes, source):
1658 def _changegroup(self, nodes, source):
1653 """Compute the changegroup of all nodes that we have that a recipient
1659 """Compute the changegroup of all nodes that we have that a recipient
1654 doesn't. Return a chunkbuffer object whose read() method will return
1660 doesn't. Return a chunkbuffer object whose read() method will return
1655 successive changegroup chunks.
1661 successive changegroup chunks.
1656
1662
1657 This is much easier than the previous function as we can assume that
1663 This is much easier than the previous function as we can assume that
1658 the recipient has any changenode we aren't sending them.
1664 the recipient has any changenode we aren't sending them.
1659
1665
1660 nodes is the set of nodes to send"""
1666 nodes is the set of nodes to send"""
1661
1667
1662 self.hook('preoutgoing', throw=True, source=source)
1668 self.hook('preoutgoing', throw=True, source=source)
1663
1669
1664 cl = self.changelog
1670 cl = self.changelog
1665 revset = set([cl.rev(n) for n in nodes])
1671 revset = set([cl.rev(n) for n in nodes])
1666 self.changegroupinfo(nodes, source)
1672 self.changegroupinfo(nodes, source)
1667
1673
1668 def identity(x):
1674 def identity(x):
1669 return x
1675 return x
1670
1676
1671 def gennodelst(log):
1677 def gennodelst(log):
1672 for r in log:
1678 for r in log:
1673 if log.linkrev(r) in revset:
1679 if log.linkrev(r) in revset:
1674 yield log.node(r)
1680 yield log.node(r)
1675
1681
1676 def lookuplinkrev_func(revlog):
1682 def lookuplinkrev_func(revlog):
1677 def lookuplinkrev(n):
1683 def lookuplinkrev(n):
1678 return cl.node(revlog.linkrev(revlog.rev(n)))
1684 return cl.node(revlog.linkrev(revlog.rev(n)))
1679 return lookuplinkrev
1685 return lookuplinkrev
1680
1686
1681 def gengroup():
1687 def gengroup():
1682 '''yield a sequence of changegroup chunks (strings)'''
1688 '''yield a sequence of changegroup chunks (strings)'''
1683 # construct a list of all changed files
1689 # construct a list of all changed files
1684 changedfiles = set()
1690 changedfiles = set()
1685 mmfs = {}
1691 mmfs = {}
1686 collect = changegroup.collector(cl, mmfs, changedfiles)
1692 collect = changegroup.collector(cl, mmfs, changedfiles)
1687
1693
1688 for cnt, chnk in enumerate(cl.group(nodes, identity, collect)):
1694 for cnt, chnk in enumerate(cl.group(nodes, identity, collect)):
1689 # revlog.group yields three entries per node, so
1695 # revlog.group yields three entries per node, so
1690 # dividing by 3 gives an approximation of how many
1696 # dividing by 3 gives an approximation of how many
1691 # nodes have been processed.
1697 # nodes have been processed.
1692 self.ui.progress(_('bundling'), cnt / 3, unit=_('changesets'))
1698 self.ui.progress(_('bundling'), cnt / 3, unit=_('changesets'))
1693 yield chnk
1699 yield chnk
1694 changecount = cnt / 3
1700 changecount = cnt / 3
1695 self.ui.progress(_('bundling'), None)
1701 self.ui.progress(_('bundling'), None)
1696
1702
1697 mnfst = self.manifest
1703 mnfst = self.manifest
1698 nodeiter = gennodelst(mnfst)
1704 nodeiter = gennodelst(mnfst)
1699 efiles = {}
1705 efiles = {}
1700 for cnt, chnk in enumerate(mnfst.group(nodeiter,
1706 for cnt, chnk in enumerate(mnfst.group(nodeiter,
1701 lookuplinkrev_func(mnfst))):
1707 lookuplinkrev_func(mnfst))):
1702 if cnt % 3 == 1:
1708 if cnt % 3 == 1:
1703 mnode = chnk[:20]
1709 mnode = chnk[:20]
1704 efiles.update(mnfst.readdelta(mnode))
1710 efiles.update(mnfst.readdelta(mnode))
1705 # see above comment for why we divide by 3
1711 # see above comment for why we divide by 3
1706 self.ui.progress(_('bundling'), cnt / 3,
1712 self.ui.progress(_('bundling'), cnt / 3,
1707 unit=_('manifests'), total=changecount)
1713 unit=_('manifests'), total=changecount)
1708 yield chnk
1714 yield chnk
1709 efiles = len(efiles)
1715 efiles = len(efiles)
1710 self.ui.progress(_('bundling'), None)
1716 self.ui.progress(_('bundling'), None)
1711
1717
1712 for idx, fname in enumerate(sorted(changedfiles)):
1718 for idx, fname in enumerate(sorted(changedfiles)):
1713 filerevlog = self.file(fname)
1719 filerevlog = self.file(fname)
1714 if not len(filerevlog):
1720 if not len(filerevlog):
1715 raise util.Abort(_("empty or missing revlog for %s") % fname)
1721 raise util.Abort(_("empty or missing revlog for %s") % fname)
1716 nodeiter = gennodelst(filerevlog)
1722 nodeiter = gennodelst(filerevlog)
1717 nodeiter = list(nodeiter)
1723 nodeiter = list(nodeiter)
1718 if nodeiter:
1724 if nodeiter:
1719 yield changegroup.chunkheader(len(fname))
1725 yield changegroup.chunkheader(len(fname))
1720 yield fname
1726 yield fname
1721 lookup = lookuplinkrev_func(filerevlog)
1727 lookup = lookuplinkrev_func(filerevlog)
1722 for chnk in filerevlog.group(nodeiter, lookup):
1728 for chnk in filerevlog.group(nodeiter, lookup):
1723 self.ui.progress(
1729 self.ui.progress(
1724 _('bundling'), idx, item=fname,
1730 _('bundling'), idx, item=fname,
1725 total=efiles, unit=_('files'))
1731 total=efiles, unit=_('files'))
1726 yield chnk
1732 yield chnk
1727 self.ui.progress(_('bundling'), None)
1733 self.ui.progress(_('bundling'), None)
1728
1734
1729 yield changegroup.closechunk()
1735 yield changegroup.closechunk()
1730
1736
1731 if nodes:
1737 if nodes:
1732 self.hook('outgoing', node=hex(nodes[0]), source=source)
1738 self.hook('outgoing', node=hex(nodes[0]), source=source)
1733
1739
1734 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
1740 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
1735
1741
1736 def addchangegroup(self, source, srctype, url, emptyok=False, lock=None):
1742 def addchangegroup(self, source, srctype, url, emptyok=False, lock=None):
1737 """Add the changegroup returned by source.read() to this repo.
1743 """Add the changegroup returned by source.read() to this repo.
1738 srctype is a string like 'push', 'pull', or 'unbundle'. url is
1744 srctype is a string like 'push', 'pull', or 'unbundle'. url is
1739 the URL of the repo where this changegroup is coming from.
1745 the URL of the repo where this changegroup is coming from.
1740 If lock is not None, the function takes ownership of the lock
1746 If lock is not None, the function takes ownership of the lock
1741 and releases it after the changegroup is added.
1747 and releases it after the changegroup is added.
1742
1748
1743 Return an integer summarizing the change to this repo:
1749 Return an integer summarizing the change to this repo:
1744 - nothing changed or no source: 0
1750 - nothing changed or no source: 0
1745 - more heads than before: 1+added heads (2..n)
1751 - more heads than before: 1+added heads (2..n)
1746 - fewer heads than before: -1-removed heads (-2..-n)
1752 - fewer heads than before: -1-removed heads (-2..-n)
1747 - number of heads stays the same: 1
1753 - number of heads stays the same: 1
1748 """
1754 """
1749 def csmap(x):
1755 def csmap(x):
1750 self.ui.debug("add changeset %s\n" % short(x))
1756 self.ui.debug("add changeset %s\n" % short(x))
1751 return len(cl)
1757 return len(cl)
1752
1758
1753 def revmap(x):
1759 def revmap(x):
1754 return cl.rev(x)
1760 return cl.rev(x)
1755
1761
1756 if not source:
1762 if not source:
1757 return 0
1763 return 0
1758
1764
1759 self.hook('prechangegroup', throw=True, source=srctype, url=url)
1765 self.hook('prechangegroup', throw=True, source=srctype, url=url)
1760
1766
1761 changesets = files = revisions = 0
1767 changesets = files = revisions = 0
1762 efiles = set()
1768 efiles = set()
1763
1769
1764 # write changelog data to temp files so concurrent readers will not see
1770 # write changelog data to temp files so concurrent readers will not see
1765 # inconsistent view
1771 # inconsistent view
1766 cl = self.changelog
1772 cl = self.changelog
1767 cl.delayupdate()
1773 cl.delayupdate()
1768 oldheads = len(cl.heads())
1774 oldheads = len(cl.heads())
1769
1775
1770 tr = self.transaction("\n".join([srctype, urlmod.hidepassword(url)]))
1776 tr = self.transaction("\n".join([srctype, urlmod.hidepassword(url)]))
1771 try:
1777 try:
1772 trp = weakref.proxy(tr)
1778 trp = weakref.proxy(tr)
1773 # pull off the changeset group
1779 # pull off the changeset group
1774 self.ui.status(_("adding changesets\n"))
1780 self.ui.status(_("adding changesets\n"))
1775 clstart = len(cl)
1781 clstart = len(cl)
1776 class prog(object):
1782 class prog(object):
1777 step = _('changesets')
1783 step = _('changesets')
1778 count = 1
1784 count = 1
1779 ui = self.ui
1785 ui = self.ui
1780 total = None
1786 total = None
1781 def __call__(self):
1787 def __call__(self):
1782 self.ui.progress(self.step, self.count, unit=_('chunks'),
1788 self.ui.progress(self.step, self.count, unit=_('chunks'),
1783 total=self.total)
1789 total=self.total)
1784 self.count += 1
1790 self.count += 1
1785 pr = prog()
1791 pr = prog()
1786 source.callback = pr
1792 source.callback = pr
1787
1793
1788 if (cl.addgroup(source, csmap, trp) is None
1794 if (cl.addgroup(source, csmap, trp) is None
1789 and not emptyok):
1795 and not emptyok):
1790 raise util.Abort(_("received changelog group is empty"))
1796 raise util.Abort(_("received changelog group is empty"))
1791 clend = len(cl)
1797 clend = len(cl)
1792 changesets = clend - clstart
1798 changesets = clend - clstart
1793 for c in xrange(clstart, clend):
1799 for c in xrange(clstart, clend):
1794 efiles.update(self[c].files())
1800 efiles.update(self[c].files())
1795 efiles = len(efiles)
1801 efiles = len(efiles)
1796 self.ui.progress(_('changesets'), None)
1802 self.ui.progress(_('changesets'), None)
1797
1803
1798 # pull off the manifest group
1804 # pull off the manifest group
1799 self.ui.status(_("adding manifests\n"))
1805 self.ui.status(_("adding manifests\n"))
1800 pr.step = _('manifests')
1806 pr.step = _('manifests')
1801 pr.count = 1
1807 pr.count = 1
1802 pr.total = changesets # manifests <= changesets
1808 pr.total = changesets # manifests <= changesets
1803 # no need to check for empty manifest group here:
1809 # no need to check for empty manifest group here:
1804 # if the result of the merge of 1 and 2 is the same in 3 and 4,
1810 # if the result of the merge of 1 and 2 is the same in 3 and 4,
1805 # no new manifest will be created and the manifest group will
1811 # no new manifest will be created and the manifest group will
1806 # be empty during the pull
1812 # be empty during the pull
1807 self.manifest.addgroup(source, revmap, trp)
1813 self.manifest.addgroup(source, revmap, trp)
1808 self.ui.progress(_('manifests'), None)
1814 self.ui.progress(_('manifests'), None)
1809
1815
1810 needfiles = {}
1816 needfiles = {}
1811 if self.ui.configbool('server', 'validate', default=False):
1817 if self.ui.configbool('server', 'validate', default=False):
1812 # validate incoming csets have their manifests
1818 # validate incoming csets have their manifests
1813 for cset in xrange(clstart, clend):
1819 for cset in xrange(clstart, clend):
1814 mfest = self.changelog.read(self.changelog.node(cset))[0]
1820 mfest = self.changelog.read(self.changelog.node(cset))[0]
1815 mfest = self.manifest.readdelta(mfest)
1821 mfest = self.manifest.readdelta(mfest)
1816 # store file nodes we must see
1822 # store file nodes we must see
1817 for f, n in mfest.iteritems():
1823 for f, n in mfest.iteritems():
1818 needfiles.setdefault(f, set()).add(n)
1824 needfiles.setdefault(f, set()).add(n)
1819
1825
1820 # process the files
1826 # process the files
1821 self.ui.status(_("adding file changes\n"))
1827 self.ui.status(_("adding file changes\n"))
1822 pr.step = 'files'
1828 pr.step = 'files'
1823 pr.count = 1
1829 pr.count = 1
1824 pr.total = efiles
1830 pr.total = efiles
1825 source.callback = None
1831 source.callback = None
1826
1832
1827 while 1:
1833 while 1:
1828 f = source.chunk()
1834 f = source.chunk()
1829 if not f:
1835 if not f:
1830 break
1836 break
1831 self.ui.debug("adding %s revisions\n" % f)
1837 self.ui.debug("adding %s revisions\n" % f)
1832 pr()
1838 pr()
1833 fl = self.file(f)
1839 fl = self.file(f)
1834 o = len(fl)
1840 o = len(fl)
1835 if fl.addgroup(source, revmap, trp) is None:
1841 if fl.addgroup(source, revmap, trp) is None:
1836 raise util.Abort(_("received file revlog group is empty"))
1842 raise util.Abort(_("received file revlog group is empty"))
1837 revisions += len(fl) - o
1843 revisions += len(fl) - o
1838 files += 1
1844 files += 1
1839 if f in needfiles:
1845 if f in needfiles:
1840 needs = needfiles[f]
1846 needs = needfiles[f]
1841 for new in xrange(o, len(fl)):
1847 for new in xrange(o, len(fl)):
1842 n = fl.node(new)
1848 n = fl.node(new)
1843 if n in needs:
1849 if n in needs:
1844 needs.remove(n)
1850 needs.remove(n)
1845 if not needs:
1851 if not needs:
1846 del needfiles[f]
1852 del needfiles[f]
1847 self.ui.progress(_('files'), None)
1853 self.ui.progress(_('files'), None)
1848
1854
1849 for f, needs in needfiles.iteritems():
1855 for f, needs in needfiles.iteritems():
1850 fl = self.file(f)
1856 fl = self.file(f)
1851 for n in needs:
1857 for n in needs:
1852 try:
1858 try:
1853 fl.rev(n)
1859 fl.rev(n)
1854 except error.LookupError:
1860 except error.LookupError:
1855 raise util.Abort(
1861 raise util.Abort(
1856 _('missing file data for %s:%s - run hg verify') %
1862 _('missing file data for %s:%s - run hg verify') %
1857 (f, hex(n)))
1863 (f, hex(n)))
1858
1864
1859 newheads = len(cl.heads())
1865 newheads = len(cl.heads())
1860 heads = ""
1866 heads = ""
1861 if oldheads and newheads != oldheads:
1867 if oldheads and newheads != oldheads:
1862 heads = _(" (%+d heads)") % (newheads - oldheads)
1868 heads = _(" (%+d heads)") % (newheads - oldheads)
1863
1869
1864 self.ui.status(_("added %d changesets"
1870 self.ui.status(_("added %d changesets"
1865 " with %d changes to %d files%s\n")
1871 " with %d changes to %d files%s\n")
1866 % (changesets, revisions, files, heads))
1872 % (changesets, revisions, files, heads))
1867
1873
1868 if changesets > 0:
1874 if changesets > 0:
1869 p = lambda: cl.writepending() and self.root or ""
1875 p = lambda: cl.writepending() and self.root or ""
1870 self.hook('pretxnchangegroup', throw=True,
1876 self.hook('pretxnchangegroup', throw=True,
1871 node=hex(cl.node(clstart)), source=srctype,
1877 node=hex(cl.node(clstart)), source=srctype,
1872 url=url, pending=p)
1878 url=url, pending=p)
1873
1879
1874 # make changelog see real files again
1880 # make changelog see real files again
1875 cl.finalize(trp)
1881 cl.finalize(trp)
1876
1882
1877 tr.close()
1883 tr.close()
1878 finally:
1884 finally:
1879 tr.release()
1885 tr.release()
1880 if lock:
1886 if lock:
1881 lock.release()
1887 lock.release()
1882
1888
1883 if changesets > 0:
1889 if changesets > 0:
1884 # forcefully update the on-disk branch cache
1890 # forcefully update the on-disk branch cache
1885 self.ui.debug("updating the branch cache\n")
1891 self.ui.debug("updating the branch cache\n")
1886 self.updatebranchcache()
1892 self.updatebranchcache()
1887 self.hook("changegroup", node=hex(cl.node(clstart)),
1893 self.hook("changegroup", node=hex(cl.node(clstart)),
1888 source=srctype, url=url)
1894 source=srctype, url=url)
1889
1895
1890 for i in xrange(clstart, clend):
1896 for i in xrange(clstart, clend):
1891 self.hook("incoming", node=hex(cl.node(i)),
1897 self.hook("incoming", node=hex(cl.node(i)),
1892 source=srctype, url=url)
1898 source=srctype, url=url)
1893
1899
1894 # FIXME - why does this care about tip?
1900 # FIXME - why does this care about tip?
1895 if newheads == oldheads:
1901 if newheads == oldheads:
1896 bookmarks.update(self, self.dirstate.parents(), self['tip'].node())
1902 bookmarks.update(self, self.dirstate.parents(), self['tip'].node())
1897
1903
1898 # never return 0 here:
1904 # never return 0 here:
1899 if newheads < oldheads:
1905 if newheads < oldheads:
1900 return newheads - oldheads - 1
1906 return newheads - oldheads - 1
1901 else:
1907 else:
1902 return newheads - oldheads + 1
1908 return newheads - oldheads + 1
1903
1909
1904
1910
1905 def stream_in(self, remote, requirements):
1911 def stream_in(self, remote, requirements):
1906 fp = remote.stream_out()
1912 fp = remote.stream_out()
1907 l = fp.readline()
1913 l = fp.readline()
1908 try:
1914 try:
1909 resp = int(l)
1915 resp = int(l)
1910 except ValueError:
1916 except ValueError:
1911 raise error.ResponseError(
1917 raise error.ResponseError(
1912 _('Unexpected response from remote server:'), l)
1918 _('Unexpected response from remote server:'), l)
1913 if resp == 1:
1919 if resp == 1:
1914 raise util.Abort(_('operation forbidden by server'))
1920 raise util.Abort(_('operation forbidden by server'))
1915 elif resp == 2:
1921 elif resp == 2:
1916 raise util.Abort(_('locking the remote repository failed'))
1922 raise util.Abort(_('locking the remote repository failed'))
1917 elif resp != 0:
1923 elif resp != 0:
1918 raise util.Abort(_('the server sent an unknown error code'))
1924 raise util.Abort(_('the server sent an unknown error code'))
1919 self.ui.status(_('streaming all changes\n'))
1925 self.ui.status(_('streaming all changes\n'))
1920 l = fp.readline()
1926 l = fp.readline()
1921 try:
1927 try:
1922 total_files, total_bytes = map(int, l.split(' ', 1))
1928 total_files, total_bytes = map(int, l.split(' ', 1))
1923 except (ValueError, TypeError):
1929 except (ValueError, TypeError):
1924 raise error.ResponseError(
1930 raise error.ResponseError(
1925 _('Unexpected response from remote server:'), l)
1931 _('Unexpected response from remote server:'), l)
1926 self.ui.status(_('%d files to transfer, %s of data\n') %
1932 self.ui.status(_('%d files to transfer, %s of data\n') %
1927 (total_files, util.bytecount(total_bytes)))
1933 (total_files, util.bytecount(total_bytes)))
1928 start = time.time()
1934 start = time.time()
1929 for i in xrange(total_files):
1935 for i in xrange(total_files):
1930 # XXX doesn't support '\n' or '\r' in filenames
1936 # XXX doesn't support '\n' or '\r' in filenames
1931 l = fp.readline()
1937 l = fp.readline()
1932 try:
1938 try:
1933 name, size = l.split('\0', 1)
1939 name, size = l.split('\0', 1)
1934 size = int(size)
1940 size = int(size)
1935 except (ValueError, TypeError):
1941 except (ValueError, TypeError):
1936 raise error.ResponseError(
1942 raise error.ResponseError(
1937 _('Unexpected response from remote server:'), l)
1943 _('Unexpected response from remote server:'), l)
1938 self.ui.debug('adding %s (%s)\n' % (name, util.bytecount(size)))
1944 self.ui.debug('adding %s (%s)\n' % (name, util.bytecount(size)))
1939 # for backwards compat, name was partially encoded
1945 # for backwards compat, name was partially encoded
1940 ofp = self.sopener(store.decodedir(name), 'w')
1946 ofp = self.sopener(store.decodedir(name), 'w')
1941 for chunk in util.filechunkiter(fp, limit=size):
1947 for chunk in util.filechunkiter(fp, limit=size):
1942 ofp.write(chunk)
1948 ofp.write(chunk)
1943 ofp.close()
1949 ofp.close()
1944 elapsed = time.time() - start
1950 elapsed = time.time() - start
1945 if elapsed <= 0:
1951 if elapsed <= 0:
1946 elapsed = 0.001
1952 elapsed = 0.001
1947 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
1953 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
1948 (util.bytecount(total_bytes), elapsed,
1954 (util.bytecount(total_bytes), elapsed,
1949 util.bytecount(total_bytes / elapsed)))
1955 util.bytecount(total_bytes / elapsed)))
1950
1956
1951 # new requirements = old non-format requirements + new format-related
1957 # new requirements = old non-format requirements + new format-related
1952 # requirements from the streamed-in repository
1958 # requirements from the streamed-in repository
1953 requirements.update(set(self.requirements) - self.supportedformats)
1959 requirements.update(set(self.requirements) - self.supportedformats)
1954 self._applyrequirements(requirements)
1960 self._applyrequirements(requirements)
1955 self._writerequirements()
1961 self._writerequirements()
1956
1962
1957 self.invalidate()
1963 self.invalidate()
1958 return len(self.heads()) + 1
1964 return len(self.heads()) + 1
1959
1965
1960 def clone(self, remote, heads=[], stream=False):
1966 def clone(self, remote, heads=[], stream=False):
1961 '''clone remote repository.
1967 '''clone remote repository.
1962
1968
1963 keyword arguments:
1969 keyword arguments:
1964 heads: list of revs to clone (forces use of pull)
1970 heads: list of revs to clone (forces use of pull)
1965 stream: use streaming clone if possible'''
1971 stream: use streaming clone if possible'''
1966
1972
1967 # now, all clients that can request uncompressed clones can
1973 # now, all clients that can request uncompressed clones can
1968 # read repo formats supported by all servers that can serve
1974 # read repo formats supported by all servers that can serve
1969 # them.
1975 # them.
1970
1976
1971 # if revlog format changes, client will have to check version
1977 # if revlog format changes, client will have to check version
1972 # and format flags on "stream" capability, and use
1978 # and format flags on "stream" capability, and use
1973 # uncompressed only if compatible.
1979 # uncompressed only if compatible.
1974
1980
1975 if stream and not heads:
1981 if stream and not heads:
1976 # 'stream' means remote revlog format is revlogv1 only
1982 # 'stream' means remote revlog format is revlogv1 only
1977 if remote.capable('stream'):
1983 if remote.capable('stream'):
1978 return self.stream_in(remote, set(('revlogv1',)))
1984 return self.stream_in(remote, set(('revlogv1',)))
1979 # otherwise, 'streamreqs' contains the remote revlog format
1985 # otherwise, 'streamreqs' contains the remote revlog format
1980 streamreqs = remote.capable('streamreqs')
1986 streamreqs = remote.capable('streamreqs')
1981 if streamreqs:
1987 if streamreqs:
1982 streamreqs = set(streamreqs.split(','))
1988 streamreqs = set(streamreqs.split(','))
1983 # if we support it, stream in and adjust our requirements
1989 # if we support it, stream in and adjust our requirements
1984 if not streamreqs - self.supportedformats:
1990 if not streamreqs - self.supportedformats:
1985 return self.stream_in(remote, streamreqs)
1991 return self.stream_in(remote, streamreqs)
1986 return self.pull(remote, heads)
1992 return self.pull(remote, heads)
1987
1993
1988 def pushkey(self, namespace, key, old, new):
1994 def pushkey(self, namespace, key, old, new):
1989 return pushkey.push(self, namespace, key, old, new)
1995 return pushkey.push(self, namespace, key, old, new)
1990
1996
1991 def listkeys(self, namespace):
1997 def listkeys(self, namespace):
1992 return pushkey.list(self, namespace)
1998 return pushkey.list(self, namespace)
1993
1999
1994 # used to avoid circular references so destructors work
2000 # used to avoid circular references so destructors work
1995 def aftertrans(files):
2001 def aftertrans(files):
1996 renamefiles = [tuple(t) for t in files]
2002 renamefiles = [tuple(t) for t in files]
1997 def a():
2003 def a():
1998 for src, dest in renamefiles:
2004 for src, dest in renamefiles:
1999 util.rename(src, dest)
2005 util.rename(src, dest)
2000 return a
2006 return a
2001
2007
2002 def instance(ui, path, create):
2008 def instance(ui, path, create):
2003 return localrepository(ui, util.drop_scheme('file', path), create)
2009 return localrepository(ui, util.drop_scheme('file', path), create)
2004
2010
2005 def islocal(path):
2011 def islocal(path):
2006 return True
2012 return True
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now