##// END OF EJS Templates
...
Matt Mackall -
r2298:4be9a79b merge default
parent child Browse files
Show More

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

1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,3653 +1,3654 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 proc gitdir {} {
8 proc gitdir {} {
9 global env
9 global env
10 if {[info exists env(GIT_DIR)]} {
10 if {[info exists env(GIT_DIR)]} {
11 return $env(GIT_DIR)
11 return $env(GIT_DIR)
12 } else {
12 } else {
13 return ".hg"
13 return ".hg"
14 }
14 }
15 }
15 }
16
16
17 proc getcommits {rargs} {
17 proc getcommits {rargs} {
18 global commits commfd phase canv mainfont env
18 global commits commfd phase canv mainfont env
19 global startmsecs nextupdate ncmupdate
19 global startmsecs nextupdate ncmupdate
20 global ctext maincursor textcursor leftover
20 global ctext maincursor textcursor leftover
21
21
22 # check that we can find a .git directory somewhere...
22 # check that we can find a .git directory somewhere...
23 set gitdir [gitdir]
23 set gitdir [gitdir]
24 if {![file isdirectory $gitdir]} {
24 if {![file isdirectory $gitdir]} {
25 error_popup "Cannot find the git directory \"$gitdir\"."
25 error_popup "Cannot find the git directory \"$gitdir\"."
26 exit 1
26 exit 1
27 }
27 }
28 set commits {}
28 set commits {}
29 set phase getcommits
29 set phase getcommits
30 set startmsecs [clock clicks -milliseconds]
30 set startmsecs [clock clicks -milliseconds]
31 set nextupdate [expr $startmsecs + 100]
31 set nextupdate [expr $startmsecs + 100]
32 set ncmupdate 1
32 set ncmupdate 1
33 if [catch {
33 if [catch {
34 set parse_args [concat --default HEAD $rargs]
34 set parse_args [concat --default HEAD $rargs]
35 set parsed_args [split [eval exec hg debug-rev-parse $parse_args] "\n"]
35 set parsed_args [split [eval exec hg debug-rev-parse $parse_args] "\n"]
36 }] {
36 }] {
37 # if git-rev-parse failed for some reason...
37 # if git-rev-parse failed for some reason...
38 if {$rargs == {}} {
38 if {$rargs == {}} {
39 set rargs HEAD
39 set rargs HEAD
40 }
40 }
41 set parsed_args $rargs
41 set parsed_args $rargs
42 }
42 }
43 if [catch {
43 if [catch {
44 set commfd [open "|hg debug-rev-list --header --topo-order --parents $parsed_args" r]
44 set commfd [open "|hg debug-rev-list --header --topo-order --parents $parsed_args" r]
45 } err] {
45 } err] {
46 puts stderr "Error executing hg debug-rev-list: $err"
46 puts stderr "Error executing hg debug-rev-list: $err"
47 exit 1
47 exit 1
48 }
48 }
49 set leftover {}
49 set leftover {}
50 fconfigure $commfd -blocking 0 -translation lf
50 fconfigure $commfd -blocking 0 -translation lf
51 fileevent $commfd readable [list getcommitlines $commfd]
51 fileevent $commfd readable [list getcommitlines $commfd]
52 $canv delete all
52 $canv delete all
53 $canv create text 3 3 -anchor nw -text "Reading commits..." \
53 $canv create text 3 3 -anchor nw -text "Reading commits..." \
54 -font $mainfont -tags textitems
54 -font $mainfont -tags textitems
55 . config -cursor watch
55 . config -cursor watch
56 settextcursor watch
56 settextcursor watch
57 }
57 }
58
58
59 proc getcommitlines {commfd} {
59 proc getcommitlines {commfd} {
60 global commits parents cdate children
60 global commits parents cdate children
61 global commitlisted phase commitinfo nextupdate
61 global commitlisted phase commitinfo nextupdate
62 global stopped redisplaying leftover
62 global stopped redisplaying leftover
63
63
64 set stuff [read $commfd]
64 set stuff [read $commfd]
65 if {$stuff == {}} {
65 if {$stuff == {}} {
66 if {![eof $commfd]} return
66 if {![eof $commfd]} return
67 # set it blocking so we wait for the process to terminate
67 # set it blocking so we wait for the process to terminate
68 fconfigure $commfd -blocking 1
68 fconfigure $commfd -blocking 1
69 if {![catch {close $commfd} err]} {
69 if {![catch {close $commfd} err]} {
70 after idle finishcommits
70 after idle finishcommits
71 return
71 return
72 }
72 }
73 if {[string range $err 0 4] == "usage"} {
73 if {[string range $err 0 4] == "usage"} {
74 set err \
74 set err \
75 {Gitk: error reading commits: bad arguments to git-rev-list.
75 {Gitk: error reading commits: bad arguments to git-rev-list.
76 (Note: arguments to gitk are passed to git-rev-list
76 (Note: arguments to gitk are passed to git-rev-list
77 to allow selection of commits to be displayed.)}
77 to allow selection of commits to be displayed.)}
78 } else {
78 } else {
79 set err "Error reading commits: $err"
79 set err "Error reading commits: $err"
80 }
80 }
81 error_popup $err
81 error_popup $err
82 exit 1
82 exit 1
83 }
83 }
84 set start 0
84 set start 0
85 while 1 {
85 while 1 {
86 set i [string first "\0" $stuff $start]
86 set i [string first "\0" $stuff $start]
87 if {$i < 0} {
87 if {$i < 0} {
88 append leftover [string range $stuff $start end]
88 append leftover [string range $stuff $start end]
89 return
89 return
90 }
90 }
91 set cmit [string range $stuff $start [expr {$i - 1}]]
91 set cmit [string range $stuff $start [expr {$i - 1}]]
92 if {$start == 0} {
92 if {$start == 0} {
93 set cmit "$leftover$cmit"
93 set cmit "$leftover$cmit"
94 set leftover {}
94 set leftover {}
95 }
95 }
96 set start [expr {$i + 1}]
96 set start [expr {$i + 1}]
97 set j [string first "\n" $cmit]
97 set j [string first "\n" $cmit]
98 set ok 0
98 set ok 0
99 if {$j >= 0} {
99 if {$j >= 0} {
100 set ids [string range $cmit 0 [expr {$j - 1}]]
100 set ids [string range $cmit 0 [expr {$j - 1}]]
101 set ok 1
101 set ok 1
102 foreach id $ids {
102 foreach id $ids {
103 if {![regexp {^[0-9a-f]{40}$} $id]} {
103 if {![regexp {^[0-9a-f]{40}$} $id]} {
104 set ok 0
104 set ok 0
105 break
105 break
106 }
106 }
107 }
107 }
108 }
108 }
109 if {!$ok} {
109 if {!$ok} {
110 set shortcmit $cmit
110 set shortcmit $cmit
111 if {[string length $shortcmit] > 80} {
111 if {[string length $shortcmit] > 80} {
112 set shortcmit "[string range $shortcmit 0 80]..."
112 set shortcmit "[string range $shortcmit 0 80]..."
113 }
113 }
114 error_popup "Can't parse hg debug-rev-list output: {$shortcmit}"
114 error_popup "Can't parse hg debug-rev-list output: {$shortcmit}"
115 exit 1
115 exit 1
116 }
116 }
117 set id [lindex $ids 0]
117 set id [lindex $ids 0]
118 set olds [lrange $ids 1 end]
118 set olds [lrange $ids 1 end]
119 set cmit [string range $cmit [expr {$j + 1}] end]
119 set cmit [string range $cmit [expr {$j + 1}] end]
120 lappend commits $id
120 lappend commits $id
121 set commitlisted($id) 1
121 set commitlisted($id) 1
122 parsecommit $id $cmit 1 [lrange $ids 1 end]
122 parsecommit $id $cmit 1 [lrange $ids 1 end]
123 drawcommit $id
123 drawcommit $id
124 if {[clock clicks -milliseconds] >= $nextupdate} {
124 if {[clock clicks -milliseconds] >= $nextupdate} {
125 doupdate 1
125 doupdate 1
126 }
126 }
127 while {$redisplaying} {
127 while {$redisplaying} {
128 set redisplaying 0
128 set redisplaying 0
129 if {$stopped == 1} {
129 if {$stopped == 1} {
130 set stopped 0
130 set stopped 0
131 set phase "getcommits"
131 set phase "getcommits"
132 foreach id $commits {
132 foreach id $commits {
133 drawcommit $id
133 drawcommit $id
134 if {$stopped} break
134 if {$stopped} break
135 if {[clock clicks -milliseconds] >= $nextupdate} {
135 if {[clock clicks -milliseconds] >= $nextupdate} {
136 doupdate 1
136 doupdate 1
137 }
137 }
138 }
138 }
139 }
139 }
140 }
140 }
141 }
141 }
142 }
142 }
143
143
144 proc doupdate {reading} {
144 proc doupdate {reading} {
145 global commfd nextupdate numcommits ncmupdate
145 global commfd nextupdate numcommits ncmupdate
146
146
147 if {$reading} {
147 if {$reading} {
148 fileevent $commfd readable {}
148 fileevent $commfd readable {}
149 }
149 }
150 update
150 update
151 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
151 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
152 if {$numcommits < 100} {
152 if {$numcommits < 100} {
153 set ncmupdate [expr {$numcommits + 1}]
153 set ncmupdate [expr {$numcommits + 1}]
154 } elseif {$numcommits < 10000} {
154 } elseif {$numcommits < 10000} {
155 set ncmupdate [expr {$numcommits + 10}]
155 set ncmupdate [expr {$numcommits + 10}]
156 } else {
156 } else {
157 set ncmupdate [expr {$numcommits + 100}]
157 set ncmupdate [expr {$numcommits + 100}]
158 }
158 }
159 if {$reading} {
159 if {$reading} {
160 fileevent $commfd readable [list getcommitlines $commfd]
160 fileevent $commfd readable [list getcommitlines $commfd]
161 }
161 }
162 }
162 }
163
163
164 proc readcommit {id} {
164 proc readcommit {id} {
165 if [catch {set contents [exec hg debug-cat-file commit $id]}] return
165 if [catch {set contents [exec hg debug-cat-file commit $id]}] return
166 parsecommit $id $contents 0 {}
166 parsecommit $id $contents 0 {}
167 }
167 }
168
168
169 proc parsecommit {id contents listed olds} {
169 proc parsecommit {id contents listed olds} {
170 global commitinfo children nchildren parents nparents cdate ncleft
170 global commitinfo children nchildren parents nparents cdate ncleft
171
171
172 set inhdr 1
172 set inhdr 1
173 set comment {}
173 set comment {}
174 set headline {}
174 set headline {}
175 set auname {}
175 set auname {}
176 set audate {}
176 set audate {}
177 set comname {}
177 set comname {}
178 set comdate {}
178 set comdate {}
179 if {![info exists nchildren($id)]} {
179 if {![info exists nchildren($id)]} {
180 set children($id) {}
180 set children($id) {}
181 set nchildren($id) 0
181 set nchildren($id) 0
182 set ncleft($id) 0
182 set ncleft($id) 0
183 }
183 }
184 set parents($id) $olds
184 set parents($id) $olds
185 set nparents($id) [llength $olds]
185 set nparents($id) [llength $olds]
186 foreach p $olds {
186 foreach p $olds {
187 if {![info exists nchildren($p)]} {
187 if {![info exists nchildren($p)]} {
188 set children($p) [list $id]
188 set children($p) [list $id]
189 set nchildren($p) 1
189 set nchildren($p) 1
190 set ncleft($p) 1
190 set ncleft($p) 1
191 } elseif {[lsearch -exact $children($p) $id] < 0} {
191 } elseif {[lsearch -exact $children($p) $id] < 0} {
192 lappend children($p) $id
192 lappend children($p) $id
193 incr nchildren($p)
193 incr nchildren($p)
194 incr ncleft($p)
194 incr ncleft($p)
195 }
195 }
196 }
196 }
197 foreach line [split $contents "\n"] {
197 foreach line [split $contents "\n"] {
198 if {$inhdr} {
198 if {$inhdr} {
199 set line [split $line]
199 if {$line == {}} {
200 if {$line == {}} {
200 set inhdr 0
201 set inhdr 0
201 } else {
202 } else {
202 set tag [lindex $line 0]
203 set tag [lindex $line 0]
203 if {$tag == "author"} {
204 if {$tag == "author"} {
204 set x [expr {[llength $line] - 2}]
205 set x [expr {[llength $line] - 2}]
205 set audate [lindex $line $x]
206 set audate [lindex $line $x]
206 set auname [lrange $line 1 [expr {$x - 1}]]
207 set auname [join [lrange $line 1 [expr {$x - 1}]]]
207 } elseif {$tag == "committer"} {
208 } elseif {$tag == "committer"} {
208 set x [expr {[llength $line] - 2}]
209 set x [expr {[llength $line] - 2}]
209 set comdate [lindex $line $x]
210 set comdate [lindex $line $x]
210 set comname [lrange $line 1 [expr {$x - 1}]]
211 set comname [join [lrange $line 1 [expr {$x - 1}]]]
211 }
212 }
212 }
213 }
213 } else {
214 } else {
214 if {$comment == {}} {
215 if {$comment == {}} {
215 set headline [string trim $line]
216 set headline [string trim $line]
216 } else {
217 } else {
217 append comment "\n"
218 append comment "\n"
218 }
219 }
219 if {!$listed} {
220 if {!$listed} {
220 # git-rev-list indents the comment by 4 spaces;
221 # git-rev-list indents the comment by 4 spaces;
221 # if we got this via git-cat-file, add the indentation
222 # if we got this via git-cat-file, add the indentation
222 append comment " "
223 append comment " "
223 }
224 }
224 append comment $line
225 append comment $line
225 }
226 }
226 }
227 }
227 if {$audate != {}} {
228 if {$audate != {}} {
228 set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
229 set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
229 }
230 }
230 if {$comdate != {}} {
231 if {$comdate != {}} {
231 set cdate($id) $comdate
232 set cdate($id) $comdate
232 set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
233 set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
233 }
234 }
234 set commitinfo($id) [list $headline $auname $audate \
235 set commitinfo($id) [list $headline $auname $audate \
235 $comname $comdate $comment]
236 $comname $comdate $comment]
236 }
237 }
237
238
238 proc readrefs {} {
239 proc readrefs {} {
239 global tagids idtags headids idheads tagcontents
240 global tagids idtags headids idheads tagcontents
240
241
241 set tags [exec hg tags]
242 set tags [exec hg tags]
242 set lines [split $tags '\n']
243 set lines [split $tags '\n']
243 foreach f $lines {
244 foreach f $lines {
244 set f [regexp -all -inline {\S+} $f]
245 set f [regexp -all -inline {\S+} $f]
245 set direct [lindex $f 0]
246 set direct [lindex $f 0]
246 set full [lindex $f 1]
247 set full [lindex $f 1]
247 set sha [split $full ':']
248 set sha [split $full ':']
248 set tag [lindex $sha 1]
249 set tag [lindex $sha 1]
249 lappend tagids($direct) $tag
250 lappend tagids($direct) $tag
250 lappend idtags($tag) $direct
251 lappend idtags($tag) $direct
251 }
252 }
252 }
253 }
253
254
254 proc readotherrefs {base dname excl} {
255 proc readotherrefs {base dname excl} {
255 global otherrefids idotherrefs
256 global otherrefids idotherrefs
256
257
257 set git [gitdir]
258 set git [gitdir]
258 set files [glob -nocomplain -types f [file join $git $base *]]
259 set files [glob -nocomplain -types f [file join $git $base *]]
259 foreach f $files {
260 foreach f $files {
260 catch {
261 catch {
261 set fd [open $f r]
262 set fd [open $f r]
262 set line [read $fd 40]
263 set line [read $fd 40]
263 if {[regexp {^[0-9a-f]{40}} $line id]} {
264 if {[regexp {^[0-9a-f]{40}} $line id]} {
264 set name "$dname[file tail $f]"
265 set name "$dname[file tail $f]"
265 set otherrefids($name) $id
266 set otherrefids($name) $id
266 lappend idotherrefs($id) $name
267 lappend idotherrefs($id) $name
267 }
268 }
268 close $fd
269 close $fd
269 }
270 }
270 }
271 }
271 set dirs [glob -nocomplain -types d [file join $git $base *]]
272 set dirs [glob -nocomplain -types d [file join $git $base *]]
272 foreach d $dirs {
273 foreach d $dirs {
273 set dir [file tail $d]
274 set dir [file tail $d]
274 if {[lsearch -exact $excl $dir] >= 0} continue
275 if {[lsearch -exact $excl $dir] >= 0} continue
275 readotherrefs [file join $base $dir] "$dname$dir/" {}
276 readotherrefs [file join $base $dir] "$dname$dir/" {}
276 }
277 }
277 }
278 }
278
279
279 proc error_popup msg {
280 proc error_popup msg {
280 set w .error
281 set w .error
281 toplevel $w
282 toplevel $w
282 wm transient $w .
283 wm transient $w .
283 message $w.m -text $msg -justify center -aspect 400
284 message $w.m -text $msg -justify center -aspect 400
284 pack $w.m -side top -fill x -padx 20 -pady 20
285 pack $w.m -side top -fill x -padx 20 -pady 20
285 button $w.ok -text OK -command "destroy $w"
286 button $w.ok -text OK -command "destroy $w"
286 pack $w.ok -side bottom -fill x
287 pack $w.ok -side bottom -fill x
287 bind $w <Visibility> "grab $w; focus $w"
288 bind $w <Visibility> "grab $w; focus $w"
288 tkwait window $w
289 tkwait window $w
289 }
290 }
290
291
291 proc makewindow {} {
292 proc makewindow {} {
292 global canv canv2 canv3 linespc charspc ctext cflist textfont
293 global canv canv2 canv3 linespc charspc ctext cflist textfont
293 global findtype findtypemenu findloc findstring fstring geometry
294 global findtype findtypemenu findloc findstring fstring geometry
294 global entries sha1entry sha1string sha1but
295 global entries sha1entry sha1string sha1but
295 global maincursor textcursor curtextcursor
296 global maincursor textcursor curtextcursor
296 global rowctxmenu gaudydiff mergemax
297 global rowctxmenu gaudydiff mergemax
297
298
298 menu .bar
299 menu .bar
299 .bar add cascade -label "File" -menu .bar.file
300 .bar add cascade -label "File" -menu .bar.file
300 menu .bar.file
301 menu .bar.file
301 .bar.file add command -label "Reread references" -command rereadrefs
302 .bar.file add command -label "Reread references" -command rereadrefs
302 .bar.file add command -label "Quit" -command doquit
303 .bar.file add command -label "Quit" -command doquit
303 menu .bar.help
304 menu .bar.help
304 .bar add cascade -label "Help" -menu .bar.help
305 .bar add cascade -label "Help" -menu .bar.help
305 .bar.help add command -label "About gitk" -command about
306 .bar.help add command -label "About gitk" -command about
306 . configure -menu .bar
307 . configure -menu .bar
307
308
308 if {![info exists geometry(canv1)]} {
309 if {![info exists geometry(canv1)]} {
309 set geometry(canv1) [expr 45 * $charspc]
310 set geometry(canv1) [expr 45 * $charspc]
310 set geometry(canv2) [expr 30 * $charspc]
311 set geometry(canv2) [expr 30 * $charspc]
311 set geometry(canv3) [expr 15 * $charspc]
312 set geometry(canv3) [expr 15 * $charspc]
312 set geometry(canvh) [expr 25 * $linespc + 4]
313 set geometry(canvh) [expr 25 * $linespc + 4]
313 set geometry(ctextw) 80
314 set geometry(ctextw) 80
314 set geometry(ctexth) 30
315 set geometry(ctexth) 30
315 set geometry(cflistw) 30
316 set geometry(cflistw) 30
316 }
317 }
317 panedwindow .ctop -orient vertical
318 panedwindow .ctop -orient vertical
318 if {[info exists geometry(width)]} {
319 if {[info exists geometry(width)]} {
319 .ctop conf -width $geometry(width) -height $geometry(height)
320 .ctop conf -width $geometry(width) -height $geometry(height)
320 set texth [expr {$geometry(height) - $geometry(canvh) - 56}]
321 set texth [expr {$geometry(height) - $geometry(canvh) - 56}]
321 set geometry(ctexth) [expr {($texth - 8) /
322 set geometry(ctexth) [expr {($texth - 8) /
322 [font metrics $textfont -linespace]}]
323 [font metrics $textfont -linespace]}]
323 }
324 }
324 frame .ctop.top
325 frame .ctop.top
325 frame .ctop.top.bar
326 frame .ctop.top.bar
326 pack .ctop.top.bar -side bottom -fill x
327 pack .ctop.top.bar -side bottom -fill x
327 set cscroll .ctop.top.csb
328 set cscroll .ctop.top.csb
328 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
329 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
329 pack $cscroll -side right -fill y
330 pack $cscroll -side right -fill y
330 panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
331 panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
331 pack .ctop.top.clist -side top -fill both -expand 1
332 pack .ctop.top.clist -side top -fill both -expand 1
332 .ctop add .ctop.top
333 .ctop add .ctop.top
333 set canv .ctop.top.clist.canv
334 set canv .ctop.top.clist.canv
334 canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
335 canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
335 -bg white -bd 0 \
336 -bg white -bd 0 \
336 -yscrollincr $linespc -yscrollcommand "$cscroll set"
337 -yscrollincr $linespc -yscrollcommand "$cscroll set"
337 .ctop.top.clist add $canv
338 .ctop.top.clist add $canv
338 set canv2 .ctop.top.clist.canv2
339 set canv2 .ctop.top.clist.canv2
339 canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
340 canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
340 -bg white -bd 0 -yscrollincr $linespc
341 -bg white -bd 0 -yscrollincr $linespc
341 .ctop.top.clist add $canv2
342 .ctop.top.clist add $canv2
342 set canv3 .ctop.top.clist.canv3
343 set canv3 .ctop.top.clist.canv3
343 canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
344 canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
344 -bg white -bd 0 -yscrollincr $linespc
345 -bg white -bd 0 -yscrollincr $linespc
345 .ctop.top.clist add $canv3
346 .ctop.top.clist add $canv3
346 bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
347 bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
347
348
348 set sha1entry .ctop.top.bar.sha1
349 set sha1entry .ctop.top.bar.sha1
349 set entries $sha1entry
350 set entries $sha1entry
350 set sha1but .ctop.top.bar.sha1label
351 set sha1but .ctop.top.bar.sha1label
351 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
352 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
352 -command gotocommit -width 8
353 -command gotocommit -width 8
353 $sha1but conf -disabledforeground [$sha1but cget -foreground]
354 $sha1but conf -disabledforeground [$sha1but cget -foreground]
354 pack .ctop.top.bar.sha1label -side left
355 pack .ctop.top.bar.sha1label -side left
355 entry $sha1entry -width 40 -font $textfont -textvariable sha1string
356 entry $sha1entry -width 40 -font $textfont -textvariable sha1string
356 trace add variable sha1string write sha1change
357 trace add variable sha1string write sha1change
357 pack $sha1entry -side left -pady 2
358 pack $sha1entry -side left -pady 2
358
359
359 image create bitmap bm-left -data {
360 image create bitmap bm-left -data {
360 #define left_width 16
361 #define left_width 16
361 #define left_height 16
362 #define left_height 16
362 static unsigned char left_bits[] = {
363 static unsigned char left_bits[] = {
363 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
364 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
364 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
365 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
365 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
366 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
366 }
367 }
367 image create bitmap bm-right -data {
368 image create bitmap bm-right -data {
368 #define right_width 16
369 #define right_width 16
369 #define right_height 16
370 #define right_height 16
370 static unsigned char right_bits[] = {
371 static unsigned char right_bits[] = {
371 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
372 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
372 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
373 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
373 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
374 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
374 }
375 }
375 button .ctop.top.bar.leftbut -image bm-left -command goback \
376 button .ctop.top.bar.leftbut -image bm-left -command goback \
376 -state disabled -width 26
377 -state disabled -width 26
377 pack .ctop.top.bar.leftbut -side left -fill y
378 pack .ctop.top.bar.leftbut -side left -fill y
378 button .ctop.top.bar.rightbut -image bm-right -command goforw \
379 button .ctop.top.bar.rightbut -image bm-right -command goforw \
379 -state disabled -width 26
380 -state disabled -width 26
380 pack .ctop.top.bar.rightbut -side left -fill y
381 pack .ctop.top.bar.rightbut -side left -fill y
381
382
382 button .ctop.top.bar.findbut -text "Find" -command dofind
383 button .ctop.top.bar.findbut -text "Find" -command dofind
383 pack .ctop.top.bar.findbut -side left
384 pack .ctop.top.bar.findbut -side left
384 set findstring {}
385 set findstring {}
385 set fstring .ctop.top.bar.findstring
386 set fstring .ctop.top.bar.findstring
386 lappend entries $fstring
387 lappend entries $fstring
387 entry $fstring -width 30 -font $textfont -textvariable findstring
388 entry $fstring -width 30 -font $textfont -textvariable findstring
388 pack $fstring -side left -expand 1 -fill x
389 pack $fstring -side left -expand 1 -fill x
389 set findtype Exact
390 set findtype Exact
390 set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \
391 set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \
391 findtype Exact IgnCase Regexp]
392 findtype Exact IgnCase Regexp]
392 set findloc "All fields"
393 set findloc "All fields"
393 tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
394 tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
394 Comments Author Committer Files Pickaxe
395 Comments Author Committer Files Pickaxe
395 pack .ctop.top.bar.findloc -side right
396 pack .ctop.top.bar.findloc -side right
396 pack .ctop.top.bar.findtype -side right
397 pack .ctop.top.bar.findtype -side right
397 # for making sure type==Exact whenever loc==Pickaxe
398 # for making sure type==Exact whenever loc==Pickaxe
398 trace add variable findloc write findlocchange
399 trace add variable findloc write findlocchange
399
400
400 panedwindow .ctop.cdet -orient horizontal
401 panedwindow .ctop.cdet -orient horizontal
401 .ctop add .ctop.cdet
402 .ctop add .ctop.cdet
402 frame .ctop.cdet.left
403 frame .ctop.cdet.left
403 set ctext .ctop.cdet.left.ctext
404 set ctext .ctop.cdet.left.ctext
404 text $ctext -bg white -state disabled -font $textfont \
405 text $ctext -bg white -state disabled -font $textfont \
405 -width $geometry(ctextw) -height $geometry(ctexth) \
406 -width $geometry(ctextw) -height $geometry(ctexth) \
406 -yscrollcommand ".ctop.cdet.left.sb set" \
407 -yscrollcommand ".ctop.cdet.left.sb set" \
407 -xscrollcommand ".ctop.cdet.left.hb set" -wrap none
408 -xscrollcommand ".ctop.cdet.left.hb set" -wrap none
408 scrollbar .ctop.cdet.left.sb -command "$ctext yview"
409 scrollbar .ctop.cdet.left.sb -command "$ctext yview"
409 scrollbar .ctop.cdet.left.hb -orient horizontal -command "$ctext xview"
410 scrollbar .ctop.cdet.left.hb -orient horizontal -command "$ctext xview"
410 pack .ctop.cdet.left.sb -side right -fill y
411 pack .ctop.cdet.left.sb -side right -fill y
411 pack .ctop.cdet.left.hb -side bottom -fill x
412 pack .ctop.cdet.left.hb -side bottom -fill x
412 pack $ctext -side left -fill both -expand 1
413 pack $ctext -side left -fill both -expand 1
413 .ctop.cdet add .ctop.cdet.left
414 .ctop.cdet add .ctop.cdet.left
414
415
415 $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
416 $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
416 if {$gaudydiff} {
417 if {$gaudydiff} {
417 $ctext tag conf hunksep -back blue -fore white
418 $ctext tag conf hunksep -back blue -fore white
418 $ctext tag conf d0 -back "#ff8080"
419 $ctext tag conf d0 -back "#ff8080"
419 $ctext tag conf d1 -back green
420 $ctext tag conf d1 -back green
420 } else {
421 } else {
421 $ctext tag conf hunksep -fore blue
422 $ctext tag conf hunksep -fore blue
422 $ctext tag conf d0 -fore red
423 $ctext tag conf d0 -fore red
423 $ctext tag conf d1 -fore "#00a000"
424 $ctext tag conf d1 -fore "#00a000"
424 $ctext tag conf m0 -fore red
425 $ctext tag conf m0 -fore red
425 $ctext tag conf m1 -fore blue
426 $ctext tag conf m1 -fore blue
426 $ctext tag conf m2 -fore green
427 $ctext tag conf m2 -fore green
427 $ctext tag conf m3 -fore purple
428 $ctext tag conf m3 -fore purple
428 $ctext tag conf m4 -fore brown
429 $ctext tag conf m4 -fore brown
429 $ctext tag conf mmax -fore darkgrey
430 $ctext tag conf mmax -fore darkgrey
430 set mergemax 5
431 set mergemax 5
431 $ctext tag conf mresult -font [concat $textfont bold]
432 $ctext tag conf mresult -font [concat $textfont bold]
432 $ctext tag conf msep -font [concat $textfont bold]
433 $ctext tag conf msep -font [concat $textfont bold]
433 $ctext tag conf found -back yellow
434 $ctext tag conf found -back yellow
434 }
435 }
435
436
436 frame .ctop.cdet.right
437 frame .ctop.cdet.right
437 set cflist .ctop.cdet.right.cfiles
438 set cflist .ctop.cdet.right.cfiles
438 listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
439 listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
439 -yscrollcommand ".ctop.cdet.right.sb set"
440 -yscrollcommand ".ctop.cdet.right.sb set"
440 scrollbar .ctop.cdet.right.sb -command "$cflist yview"
441 scrollbar .ctop.cdet.right.sb -command "$cflist yview"
441 pack .ctop.cdet.right.sb -side right -fill y
442 pack .ctop.cdet.right.sb -side right -fill y
442 pack $cflist -side left -fill both -expand 1
443 pack $cflist -side left -fill both -expand 1
443 .ctop.cdet add .ctop.cdet.right
444 .ctop.cdet add .ctop.cdet.right
444 bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
445 bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
445
446
446 pack .ctop -side top -fill both -expand 1
447 pack .ctop -side top -fill both -expand 1
447
448
448 bindall <1> {selcanvline %W %x %y}
449 bindall <1> {selcanvline %W %x %y}
449 #bindall <B1-Motion> {selcanvline %W %x %y}
450 #bindall <B1-Motion> {selcanvline %W %x %y}
450 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
451 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
451 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
452 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
452 bindall <2> "allcanvs scan mark 0 %y"
453 bindall <2> "allcanvs scan mark 0 %y"
453 bindall <B2-Motion> "allcanvs scan dragto 0 %y"
454 bindall <B2-Motion> "allcanvs scan dragto 0 %y"
454 bind . <Key-Up> "selnextline -1"
455 bind . <Key-Up> "selnextline -1"
455 bind . <Key-Down> "selnextline 1"
456 bind . <Key-Down> "selnextline 1"
456 bind . <Key-Prior> "allcanvs yview scroll -1 pages"
457 bind . <Key-Prior> "allcanvs yview scroll -1 pages"
457 bind . <Key-Next> "allcanvs yview scroll 1 pages"
458 bind . <Key-Next> "allcanvs yview scroll 1 pages"
458 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
459 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
459 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
460 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
460 bindkey <Key-space> "$ctext yview scroll 1 pages"
461 bindkey <Key-space> "$ctext yview scroll 1 pages"
461 bindkey p "selnextline -1"
462 bindkey p "selnextline -1"
462 bindkey n "selnextline 1"
463 bindkey n "selnextline 1"
463 bindkey b "$ctext yview scroll -1 pages"
464 bindkey b "$ctext yview scroll -1 pages"
464 bindkey d "$ctext yview scroll 18 units"
465 bindkey d "$ctext yview scroll 18 units"
465 bindkey u "$ctext yview scroll -18 units"
466 bindkey u "$ctext yview scroll -18 units"
466 bindkey / {findnext 1}
467 bindkey / {findnext 1}
467 bindkey <Key-Return> {findnext 0}
468 bindkey <Key-Return> {findnext 0}
468 bindkey ? findprev
469 bindkey ? findprev
469 bindkey f nextfile
470 bindkey f nextfile
470 bind . <Control-q> doquit
471 bind . <Control-q> doquit
471 bind . <Control-w> doquit
472 bind . <Control-w> doquit
472 bind . <Control-f> dofind
473 bind . <Control-f> dofind
473 bind . <Control-g> {findnext 0}
474 bind . <Control-g> {findnext 0}
474 bind . <Control-r> findprev
475 bind . <Control-r> findprev
475 bind . <Control-equal> {incrfont 1}
476 bind . <Control-equal> {incrfont 1}
476 bind . <Control-KP_Add> {incrfont 1}
477 bind . <Control-KP_Add> {incrfont 1}
477 bind . <Control-minus> {incrfont -1}
478 bind . <Control-minus> {incrfont -1}
478 bind . <Control-KP_Subtract> {incrfont -1}
479 bind . <Control-KP_Subtract> {incrfont -1}
479 bind $cflist <<ListboxSelect>> listboxsel
480 bind $cflist <<ListboxSelect>> listboxsel
480 bind . <Destroy> {savestuff %W}
481 bind . <Destroy> {savestuff %W}
481 bind . <Button-1> "click %W"
482 bind . <Button-1> "click %W"
482 bind $fstring <Key-Return> dofind
483 bind $fstring <Key-Return> dofind
483 bind $sha1entry <Key-Return> gotocommit
484 bind $sha1entry <Key-Return> gotocommit
484 bind $sha1entry <<PasteSelection>> clearsha1
485 bind $sha1entry <<PasteSelection>> clearsha1
485
486
486 set maincursor [. cget -cursor]
487 set maincursor [. cget -cursor]
487 set textcursor [$ctext cget -cursor]
488 set textcursor [$ctext cget -cursor]
488 set curtextcursor $textcursor
489 set curtextcursor $textcursor
489
490
490 set rowctxmenu .rowctxmenu
491 set rowctxmenu .rowctxmenu
491 menu $rowctxmenu -tearoff 0
492 menu $rowctxmenu -tearoff 0
492 $rowctxmenu add command -label "Diff this -> selected" \
493 $rowctxmenu add command -label "Diff this -> selected" \
493 -command {diffvssel 0}
494 -command {diffvssel 0}
494 $rowctxmenu add command -label "Diff selected -> this" \
495 $rowctxmenu add command -label "Diff selected -> this" \
495 -command {diffvssel 1}
496 -command {diffvssel 1}
496 $rowctxmenu add command -label "Make patch" -command mkpatch
497 $rowctxmenu add command -label "Make patch" -command mkpatch
497 $rowctxmenu add command -label "Create tag" -command mktag
498 $rowctxmenu add command -label "Create tag" -command mktag
498 $rowctxmenu add command -label "Write commit to file" -command writecommit
499 $rowctxmenu add command -label "Write commit to file" -command writecommit
499 }
500 }
500
501
501 # when we make a key binding for the toplevel, make sure
502 # when we make a key binding for the toplevel, make sure
502 # it doesn't get triggered when that key is pressed in the
503 # it doesn't get triggered when that key is pressed in the
503 # find string entry widget.
504 # find string entry widget.
504 proc bindkey {ev script} {
505 proc bindkey {ev script} {
505 global entries
506 global entries
506 bind . $ev $script
507 bind . $ev $script
507 set escript [bind Entry $ev]
508 set escript [bind Entry $ev]
508 if {$escript == {}} {
509 if {$escript == {}} {
509 set escript [bind Entry <Key>]
510 set escript [bind Entry <Key>]
510 }
511 }
511 foreach e $entries {
512 foreach e $entries {
512 bind $e $ev "$escript; break"
513 bind $e $ev "$escript; break"
513 }
514 }
514 }
515 }
515
516
516 # set the focus back to the toplevel for any click outside
517 # set the focus back to the toplevel for any click outside
517 # the entry widgets
518 # the entry widgets
518 proc click {w} {
519 proc click {w} {
519 global entries
520 global entries
520 foreach e $entries {
521 foreach e $entries {
521 if {$w == $e} return
522 if {$w == $e} return
522 }
523 }
523 focus .
524 focus .
524 }
525 }
525
526
526 proc savestuff {w} {
527 proc savestuff {w} {
527 global canv canv2 canv3 ctext cflist mainfont textfont
528 global canv canv2 canv3 ctext cflist mainfont textfont
528 global stuffsaved findmergefiles gaudydiff maxgraphpct
529 global stuffsaved findmergefiles gaudydiff maxgraphpct
529 global maxwidth
530 global maxwidth
530
531
531 if {$stuffsaved} return
532 if {$stuffsaved} return
532 if {![winfo viewable .]} return
533 if {![winfo viewable .]} return
533 catch {
534 catch {
534 set f [open "~/.gitk-new" w]
535 set f [open "~/.gitk-new" w]
535 puts $f [list set mainfont $mainfont]
536 puts $f [list set mainfont $mainfont]
536 puts $f [list set textfont $textfont]
537 puts $f [list set textfont $textfont]
537 puts $f [list set findmergefiles $findmergefiles]
538 puts $f [list set findmergefiles $findmergefiles]
538 puts $f [list set gaudydiff $gaudydiff]
539 puts $f [list set gaudydiff $gaudydiff]
539 puts $f [list set maxgraphpct $maxgraphpct]
540 puts $f [list set maxgraphpct $maxgraphpct]
540 puts $f [list set maxwidth $maxwidth]
541 puts $f [list set maxwidth $maxwidth]
541 puts $f "set geometry(width) [winfo width .ctop]"
542 puts $f "set geometry(width) [winfo width .ctop]"
542 puts $f "set geometry(height) [winfo height .ctop]"
543 puts $f "set geometry(height) [winfo height .ctop]"
543 puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
544 puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
544 puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
545 puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
545 puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
546 puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
546 puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
547 puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
547 set wid [expr {([winfo width $ctext] - 8) \
548 set wid [expr {([winfo width $ctext] - 8) \
548 / [font measure $textfont "0"]}]
549 / [font measure $textfont "0"]}]
549 puts $f "set geometry(ctextw) $wid"
550 puts $f "set geometry(ctextw) $wid"
550 set wid [expr {([winfo width $cflist] - 11) \
551 set wid [expr {([winfo width $cflist] - 11) \
551 / [font measure [$cflist cget -font] "0"]}]
552 / [font measure [$cflist cget -font] "0"]}]
552 puts $f "set geometry(cflistw) $wid"
553 puts $f "set geometry(cflistw) $wid"
553 close $f
554 close $f
554 file rename -force "~/.gitk-new" "~/.gitk"
555 file rename -force "~/.gitk-new" "~/.gitk"
555 }
556 }
556 set stuffsaved 1
557 set stuffsaved 1
557 }
558 }
558
559
559 proc resizeclistpanes {win w} {
560 proc resizeclistpanes {win w} {
560 global oldwidth
561 global oldwidth
561 if [info exists oldwidth($win)] {
562 if [info exists oldwidth($win)] {
562 set s0 [$win sash coord 0]
563 set s0 [$win sash coord 0]
563 set s1 [$win sash coord 1]
564 set s1 [$win sash coord 1]
564 if {$w < 60} {
565 if {$w < 60} {
565 set sash0 [expr {int($w/2 - 2)}]
566 set sash0 [expr {int($w/2 - 2)}]
566 set sash1 [expr {int($w*5/6 - 2)}]
567 set sash1 [expr {int($w*5/6 - 2)}]
567 } else {
568 } else {
568 set factor [expr {1.0 * $w / $oldwidth($win)}]
569 set factor [expr {1.0 * $w / $oldwidth($win)}]
569 set sash0 [expr {int($factor * [lindex $s0 0])}]
570 set sash0 [expr {int($factor * [lindex $s0 0])}]
570 set sash1 [expr {int($factor * [lindex $s1 0])}]
571 set sash1 [expr {int($factor * [lindex $s1 0])}]
571 if {$sash0 < 30} {
572 if {$sash0 < 30} {
572 set sash0 30
573 set sash0 30
573 }
574 }
574 if {$sash1 < $sash0 + 20} {
575 if {$sash1 < $sash0 + 20} {
575 set sash1 [expr $sash0 + 20]
576 set sash1 [expr $sash0 + 20]
576 }
577 }
577 if {$sash1 > $w - 10} {
578 if {$sash1 > $w - 10} {
578 set sash1 [expr $w - 10]
579 set sash1 [expr $w - 10]
579 if {$sash0 > $sash1 - 20} {
580 if {$sash0 > $sash1 - 20} {
580 set sash0 [expr $sash1 - 20]
581 set sash0 [expr $sash1 - 20]
581 }
582 }
582 }
583 }
583 }
584 }
584 $win sash place 0 $sash0 [lindex $s0 1]
585 $win sash place 0 $sash0 [lindex $s0 1]
585 $win sash place 1 $sash1 [lindex $s1 1]
586 $win sash place 1 $sash1 [lindex $s1 1]
586 }
587 }
587 set oldwidth($win) $w
588 set oldwidth($win) $w
588 }
589 }
589
590
590 proc resizecdetpanes {win w} {
591 proc resizecdetpanes {win w} {
591 global oldwidth
592 global oldwidth
592 if [info exists oldwidth($win)] {
593 if [info exists oldwidth($win)] {
593 set s0 [$win sash coord 0]
594 set s0 [$win sash coord 0]
594 if {$w < 60} {
595 if {$w < 60} {
595 set sash0 [expr {int($w*3/4 - 2)}]
596 set sash0 [expr {int($w*3/4 - 2)}]
596 } else {
597 } else {
597 set factor [expr {1.0 * $w / $oldwidth($win)}]
598 set factor [expr {1.0 * $w / $oldwidth($win)}]
598 set sash0 [expr {int($factor * [lindex $s0 0])}]
599 set sash0 [expr {int($factor * [lindex $s0 0])}]
599 if {$sash0 < 45} {
600 if {$sash0 < 45} {
600 set sash0 45
601 set sash0 45
601 }
602 }
602 if {$sash0 > $w - 15} {
603 if {$sash0 > $w - 15} {
603 set sash0 [expr $w - 15]
604 set sash0 [expr $w - 15]
604 }
605 }
605 }
606 }
606 $win sash place 0 $sash0 [lindex $s0 1]
607 $win sash place 0 $sash0 [lindex $s0 1]
607 }
608 }
608 set oldwidth($win) $w
609 set oldwidth($win) $w
609 }
610 }
610
611
611 proc allcanvs args {
612 proc allcanvs args {
612 global canv canv2 canv3
613 global canv canv2 canv3
613 eval $canv $args
614 eval $canv $args
614 eval $canv2 $args
615 eval $canv2 $args
615 eval $canv3 $args
616 eval $canv3 $args
616 }
617 }
617
618
618 proc bindall {event action} {
619 proc bindall {event action} {
619 global canv canv2 canv3
620 global canv canv2 canv3
620 bind $canv $event $action
621 bind $canv $event $action
621 bind $canv2 $event $action
622 bind $canv2 $event $action
622 bind $canv3 $event $action
623 bind $canv3 $event $action
623 }
624 }
624
625
625 proc about {} {
626 proc about {} {
626 set w .about
627 set w .about
627 if {[winfo exists $w]} {
628 if {[winfo exists $w]} {
628 raise $w
629 raise $w
629 return
630 return
630 }
631 }
631 toplevel $w
632 toplevel $w
632 wm title $w "About gitk"
633 wm title $w "About gitk"
633 message $w.m -text {
634 message $w.m -text {
634 Gitk version 1.2
635 Gitk version 1.2
635
636
636 Copyright � 2005 Paul Mackerras
637 Copyright � 2005 Paul Mackerras
637
638
638 Use and redistribute under the terms of the GNU General Public License} \
639 Use and redistribute under the terms of the GNU General Public License} \
639 -justify center -aspect 400
640 -justify center -aspect 400
640 pack $w.m -side top -fill x -padx 20 -pady 20
641 pack $w.m -side top -fill x -padx 20 -pady 20
641 button $w.ok -text Close -command "destroy $w"
642 button $w.ok -text Close -command "destroy $w"
642 pack $w.ok -side bottom
643 pack $w.ok -side bottom
643 }
644 }
644
645
645 proc assigncolor {id} {
646 proc assigncolor {id} {
646 global commitinfo colormap commcolors colors nextcolor
647 global commitinfo colormap commcolors colors nextcolor
647 global parents nparents children nchildren
648 global parents nparents children nchildren
648 global cornercrossings crossings
649 global cornercrossings crossings
649
650
650 if [info exists colormap($id)] return
651 if [info exists colormap($id)] return
651 set ncolors [llength $colors]
652 set ncolors [llength $colors]
652 if {$nparents($id) <= 1 && $nchildren($id) == 1} {
653 if {$nparents($id) <= 1 && $nchildren($id) == 1} {
653 set child [lindex $children($id) 0]
654 set child [lindex $children($id) 0]
654 if {[info exists colormap($child)]
655 if {[info exists colormap($child)]
655 && $nparents($child) == 1} {
656 && $nparents($child) == 1} {
656 set colormap($id) $colormap($child)
657 set colormap($id) $colormap($child)
657 return
658 return
658 }
659 }
659 }
660 }
660 set badcolors {}
661 set badcolors {}
661 if {[info exists cornercrossings($id)]} {
662 if {[info exists cornercrossings($id)]} {
662 foreach x $cornercrossings($id) {
663 foreach x $cornercrossings($id) {
663 if {[info exists colormap($x)]
664 if {[info exists colormap($x)]
664 && [lsearch -exact $badcolors $colormap($x)] < 0} {
665 && [lsearch -exact $badcolors $colormap($x)] < 0} {
665 lappend badcolors $colormap($x)
666 lappend badcolors $colormap($x)
666 }
667 }
667 }
668 }
668 if {[llength $badcolors] >= $ncolors} {
669 if {[llength $badcolors] >= $ncolors} {
669 set badcolors {}
670 set badcolors {}
670 }
671 }
671 }
672 }
672 set origbad $badcolors
673 set origbad $badcolors
673 if {[llength $badcolors] < $ncolors - 1} {
674 if {[llength $badcolors] < $ncolors - 1} {
674 if {[info exists crossings($id)]} {
675 if {[info exists crossings($id)]} {
675 foreach x $crossings($id) {
676 foreach x $crossings($id) {
676 if {[info exists colormap($x)]
677 if {[info exists colormap($x)]
677 && [lsearch -exact $badcolors $colormap($x)] < 0} {
678 && [lsearch -exact $badcolors $colormap($x)] < 0} {
678 lappend badcolors $colormap($x)
679 lappend badcolors $colormap($x)
679 }
680 }
680 }
681 }
681 if {[llength $badcolors] >= $ncolors} {
682 if {[llength $badcolors] >= $ncolors} {
682 set badcolors $origbad
683 set badcolors $origbad
683 }
684 }
684 }
685 }
685 set origbad $badcolors
686 set origbad $badcolors
686 }
687 }
687 if {[llength $badcolors] < $ncolors - 1} {
688 if {[llength $badcolors] < $ncolors - 1} {
688 foreach child $children($id) {
689 foreach child $children($id) {
689 if {[info exists colormap($child)]
690 if {[info exists colormap($child)]
690 && [lsearch -exact $badcolors $colormap($child)] < 0} {
691 && [lsearch -exact $badcolors $colormap($child)] < 0} {
691 lappend badcolors $colormap($child)
692 lappend badcolors $colormap($child)
692 }
693 }
693 if {[info exists parents($child)]} {
694 if {[info exists parents($child)]} {
694 foreach p $parents($child) {
695 foreach p $parents($child) {
695 if {[info exists colormap($p)]
696 if {[info exists colormap($p)]
696 && [lsearch -exact $badcolors $colormap($p)] < 0} {
697 && [lsearch -exact $badcolors $colormap($p)] < 0} {
697 lappend badcolors $colormap($p)
698 lappend badcolors $colormap($p)
698 }
699 }
699 }
700 }
700 }
701 }
701 }
702 }
702 if {[llength $badcolors] >= $ncolors} {
703 if {[llength $badcolors] >= $ncolors} {
703 set badcolors $origbad
704 set badcolors $origbad
704 }
705 }
705 }
706 }
706 for {set i 0} {$i <= $ncolors} {incr i} {
707 for {set i 0} {$i <= $ncolors} {incr i} {
707 set c [lindex $colors $nextcolor]
708 set c [lindex $colors $nextcolor]
708 if {[incr nextcolor] >= $ncolors} {
709 if {[incr nextcolor] >= $ncolors} {
709 set nextcolor 0
710 set nextcolor 0
710 }
711 }
711 if {[lsearch -exact $badcolors $c]} break
712 if {[lsearch -exact $badcolors $c]} break
712 }
713 }
713 set colormap($id) $c
714 set colormap($id) $c
714 }
715 }
715
716
716 proc initgraph {} {
717 proc initgraph {} {
717 global canvy canvy0 lineno numcommits nextcolor linespc
718 global canvy canvy0 lineno numcommits nextcolor linespc
718 global mainline mainlinearrow sidelines
719 global mainline mainlinearrow sidelines
719 global nchildren ncleft
720 global nchildren ncleft
720 global displist nhyperspace
721 global displist nhyperspace
721
722
722 allcanvs delete all
723 allcanvs delete all
723 set nextcolor 0
724 set nextcolor 0
724 set canvy $canvy0
725 set canvy $canvy0
725 set lineno -1
726 set lineno -1
726 set numcommits 0
727 set numcommits 0
727 catch {unset mainline}
728 catch {unset mainline}
728 catch {unset mainlinearrow}
729 catch {unset mainlinearrow}
729 catch {unset sidelines}
730 catch {unset sidelines}
730 foreach id [array names nchildren] {
731 foreach id [array names nchildren] {
731 set ncleft($id) $nchildren($id)
732 set ncleft($id) $nchildren($id)
732 }
733 }
733 set displist {}
734 set displist {}
734 set nhyperspace 0
735 set nhyperspace 0
735 }
736 }
736
737
737 proc bindline {t id} {
738 proc bindline {t id} {
738 global canv
739 global canv
739
740
740 $canv bind $t <Enter> "lineenter %x %y $id"
741 $canv bind $t <Enter> "lineenter %x %y $id"
741 $canv bind $t <Motion> "linemotion %x %y $id"
742 $canv bind $t <Motion> "linemotion %x %y $id"
742 $canv bind $t <Leave> "lineleave $id"
743 $canv bind $t <Leave> "lineleave $id"
743 $canv bind $t <Button-1> "lineclick %x %y $id 1"
744 $canv bind $t <Button-1> "lineclick %x %y $id 1"
744 }
745 }
745
746
746 proc drawlines {id xtra} {
747 proc drawlines {id xtra} {
747 global mainline mainlinearrow sidelines lthickness colormap canv
748 global mainline mainlinearrow sidelines lthickness colormap canv
748
749
749 $canv delete lines.$id
750 $canv delete lines.$id
750 if {[info exists mainline($id)]} {
751 if {[info exists mainline($id)]} {
751 set t [$canv create line $mainline($id) \
752 set t [$canv create line $mainline($id) \
752 -width [expr {($xtra + 1) * $lthickness}] \
753 -width [expr {($xtra + 1) * $lthickness}] \
753 -fill $colormap($id) -tags lines.$id \
754 -fill $colormap($id) -tags lines.$id \
754 -arrow $mainlinearrow($id)]
755 -arrow $mainlinearrow($id)]
755 $canv lower $t
756 $canv lower $t
756 bindline $t $id
757 bindline $t $id
757 }
758 }
758 if {[info exists sidelines($id)]} {
759 if {[info exists sidelines($id)]} {
759 foreach ls $sidelines($id) {
760 foreach ls $sidelines($id) {
760 set coords [lindex $ls 0]
761 set coords [lindex $ls 0]
761 set thick [lindex $ls 1]
762 set thick [lindex $ls 1]
762 set arrow [lindex $ls 2]
763 set arrow [lindex $ls 2]
763 set t [$canv create line $coords -fill $colormap($id) \
764 set t [$canv create line $coords -fill $colormap($id) \
764 -width [expr {($thick + $xtra) * $lthickness}] \
765 -width [expr {($thick + $xtra) * $lthickness}] \
765 -arrow $arrow -tags lines.$id]
766 -arrow $arrow -tags lines.$id]
766 $canv lower $t
767 $canv lower $t
767 bindline $t $id
768 bindline $t $id
768 }
769 }
769 }
770 }
770 }
771 }
771
772
772 # level here is an index in displist
773 # level here is an index in displist
773 proc drawcommitline {level} {
774 proc drawcommitline {level} {
774 global parents children nparents displist
775 global parents children nparents displist
775 global canv canv2 canv3 mainfont namefont canvy linespc
776 global canv canv2 canv3 mainfont namefont canvy linespc
776 global lineid linehtag linentag linedtag commitinfo
777 global lineid linehtag linentag linedtag commitinfo
777 global colormap numcommits currentparents dupparents
778 global colormap numcommits currentparents dupparents
778 global idtags idline idheads idotherrefs
779 global idtags idline idheads idotherrefs
779 global lineno lthickness mainline mainlinearrow sidelines
780 global lineno lthickness mainline mainlinearrow sidelines
780 global commitlisted rowtextx idpos lastuse displist
781 global commitlisted rowtextx idpos lastuse displist
781 global oldnlines olddlevel olddisplist
782 global oldnlines olddlevel olddisplist
782
783
783 incr numcommits
784 incr numcommits
784 incr lineno
785 incr lineno
785 set id [lindex $displist $level]
786 set id [lindex $displist $level]
786 set lastuse($id) $lineno
787 set lastuse($id) $lineno
787 set lineid($lineno) $id
788 set lineid($lineno) $id
788 set idline($id) $lineno
789 set idline($id) $lineno
789 set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
790 set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
790 if {![info exists commitinfo($id)]} {
791 if {![info exists commitinfo($id)]} {
791 readcommit $id
792 readcommit $id
792 if {![info exists commitinfo($id)]} {
793 if {![info exists commitinfo($id)]} {
793 set commitinfo($id) {"No commit information available"}
794 set commitinfo($id) {"No commit information available"}
794 set nparents($id) 0
795 set nparents($id) 0
795 }
796 }
796 }
797 }
797 assigncolor $id
798 assigncolor $id
798 set currentparents {}
799 set currentparents {}
799 set dupparents {}
800 set dupparents {}
800 if {[info exists commitlisted($id)] && [info exists parents($id)]} {
801 if {[info exists commitlisted($id)] && [info exists parents($id)]} {
801 foreach p $parents($id) {
802 foreach p $parents($id) {
802 if {[lsearch -exact $currentparents $p] < 0} {
803 if {[lsearch -exact $currentparents $p] < 0} {
803 lappend currentparents $p
804 lappend currentparents $p
804 } else {
805 } else {
805 # remember that this parent was listed twice
806 # remember that this parent was listed twice
806 lappend dupparents $p
807 lappend dupparents $p
807 }
808 }
808 }
809 }
809 }
810 }
810 set x [xcoord $level $level $lineno]
811 set x [xcoord $level $level $lineno]
811 set y1 $canvy
812 set y1 $canvy
812 set canvy [expr $canvy + $linespc]
813 set canvy [expr $canvy + $linespc]
813 allcanvs conf -scrollregion \
814 allcanvs conf -scrollregion \
814 [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
815 [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
815 if {[info exists mainline($id)]} {
816 if {[info exists mainline($id)]} {
816 lappend mainline($id) $x $y1
817 lappend mainline($id) $x $y1
817 if {$mainlinearrow($id) ne "none"} {
818 if {$mainlinearrow($id) ne "none"} {
818 set mainline($id) [trimdiagstart $mainline($id)]
819 set mainline($id) [trimdiagstart $mainline($id)]
819 }
820 }
820 }
821 }
821 drawlines $id 0
822 drawlines $id 0
822 set orad [expr {$linespc / 3}]
823 set orad [expr {$linespc / 3}]
823 set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
824 set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
824 [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
825 [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
825 -fill $ofill -outline black -width 1]
826 -fill $ofill -outline black -width 1]
826 $canv raise $t
827 $canv raise $t
827 $canv bind $t <1> {selcanvline {} %x %y}
828 $canv bind $t <1> {selcanvline {} %x %y}
828 set xt [xcoord [llength $displist] $level $lineno]
829 set xt [xcoord [llength $displist] $level $lineno]
829 if {[llength $currentparents] > 2} {
830 if {[llength $currentparents] > 2} {
830 set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
831 set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
831 }
832 }
832 set rowtextx($lineno) $xt
833 set rowtextx($lineno) $xt
833 set idpos($id) [list $x $xt $y1]
834 set idpos($id) [list $x $xt $y1]
834 if {[info exists idtags($id)] || [info exists idheads($id)]
835 if {[info exists idtags($id)] || [info exists idheads($id)]
835 || [info exists idotherrefs($id)]} {
836 || [info exists idotherrefs($id)]} {
836 set xt [drawtags $id $x $xt $y1]
837 set xt [drawtags $id $x $xt $y1]
837 }
838 }
838 set headline [lindex $commitinfo($id) 0]
839 set headline [lindex $commitinfo($id) 0]
839 set name [lindex $commitinfo($id) 1]
840 set name [lindex $commitinfo($id) 1]
840 set date [lindex $commitinfo($id) 2]
841 set date [lindex $commitinfo($id) 2]
841 set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
842 set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
842 -text $headline -font $mainfont ]
843 -text $headline -font $mainfont ]
843 $canv bind $linehtag($lineno) <Button-3> "rowmenu %X %Y $id"
844 $canv bind $linehtag($lineno) <Button-3> "rowmenu %X %Y $id"
844 set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
845 set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
845 -text $name -font $namefont]
846 -text $name -font $namefont]
846 set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
847 set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
847 -text $date -font $mainfont]
848 -text $date -font $mainfont]
848
849
849 set olddlevel $level
850 set olddlevel $level
850 set olddisplist $displist
851 set olddisplist $displist
851 set oldnlines [llength $displist]
852 set oldnlines [llength $displist]
852 }
853 }
853
854
854 proc drawtags {id x xt y1} {
855 proc drawtags {id x xt y1} {
855 global idtags idheads idotherrefs
856 global idtags idheads idotherrefs
856 global linespc lthickness
857 global linespc lthickness
857 global canv mainfont idline rowtextx
858 global canv mainfont idline rowtextx
858
859
859 set marks {}
860 set marks {}
860 set ntags 0
861 set ntags 0
861 set nheads 0
862 set nheads 0
862 if {[info exists idtags($id)]} {
863 if {[info exists idtags($id)]} {
863 set marks $idtags($id)
864 set marks $idtags($id)
864 set ntags [llength $marks]
865 set ntags [llength $marks]
865 }
866 }
866 if {[info exists idheads($id)]} {
867 if {[info exists idheads($id)]} {
867 set marks [concat $marks $idheads($id)]
868 set marks [concat $marks $idheads($id)]
868 set nheads [llength $idheads($id)]
869 set nheads [llength $idheads($id)]
869 }
870 }
870 if {[info exists idotherrefs($id)]} {
871 if {[info exists idotherrefs($id)]} {
871 set marks [concat $marks $idotherrefs($id)]
872 set marks [concat $marks $idotherrefs($id)]
872 }
873 }
873 if {$marks eq {}} {
874 if {$marks eq {}} {
874 return $xt
875 return $xt
875 }
876 }
876
877
877 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
878 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
878 set yt [expr $y1 - 0.5 * $linespc]
879 set yt [expr $y1 - 0.5 * $linespc]
879 set yb [expr $yt + $linespc - 1]
880 set yb [expr $yt + $linespc - 1]
880 set xvals {}
881 set xvals {}
881 set wvals {}
882 set wvals {}
882 foreach tag $marks {
883 foreach tag $marks {
883 set wid [font measure $mainfont $tag]
884 set wid [font measure $mainfont $tag]
884 lappend xvals $xt
885 lappend xvals $xt
885 lappend wvals $wid
886 lappend wvals $wid
886 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
887 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
887 }
888 }
888 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
889 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
889 -width $lthickness -fill black -tags tag.$id]
890 -width $lthickness -fill black -tags tag.$id]
890 $canv lower $t
891 $canv lower $t
891 foreach tag $marks x $xvals wid $wvals {
892 foreach tag $marks x $xvals wid $wvals {
892 set xl [expr $x + $delta]
893 set xl [expr $x + $delta]
893 set xr [expr $x + $delta + $wid + $lthickness]
894 set xr [expr $x + $delta + $wid + $lthickness]
894 if {[incr ntags -1] >= 0} {
895 if {[incr ntags -1] >= 0} {
895 # draw a tag
896 # draw a tag
896 set t [$canv create polygon $x [expr $yt + $delta] $xl $yt \
897 set t [$canv create polygon $x [expr $yt + $delta] $xl $yt \
897 $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
898 $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
898 -width 1 -outline black -fill yellow -tags tag.$id]
899 -width 1 -outline black -fill yellow -tags tag.$id]
899 $canv bind $t <1> [list showtag $tag 1]
900 $canv bind $t <1> [list showtag $tag 1]
900 set rowtextx($idline($id)) [expr {$xr + $linespc}]
901 set rowtextx($idline($id)) [expr {$xr + $linespc}]
901 } else {
902 } else {
902 # draw a head or other ref
903 # draw a head or other ref
903 if {[incr nheads -1] >= 0} {
904 if {[incr nheads -1] >= 0} {
904 set col green
905 set col green
905 } else {
906 } else {
906 set col "#ddddff"
907 set col "#ddddff"
907 }
908 }
908 set xl [expr $xl - $delta/2]
909 set xl [expr $xl - $delta/2]
909 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
910 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
910 -width 1 -outline black -fill $col -tags tag.$id
911 -width 1 -outline black -fill $col -tags tag.$id
911 }
912 }
912 set t [$canv create text $xl $y1 -anchor w -text $tag \
913 set t [$canv create text $xl $y1 -anchor w -text $tag \
913 -font $mainfont -tags tag.$id]
914 -font $mainfont -tags tag.$id]
914 if {$ntags >= 0} {
915 if {$ntags >= 0} {
915 $canv bind $t <1> [list showtag $tag 1]
916 $canv bind $t <1> [list showtag $tag 1]
916 }
917 }
917 }
918 }
918 return $xt
919 return $xt
919 }
920 }
920
921
921 proc notecrossings {id lo hi corner} {
922 proc notecrossings {id lo hi corner} {
922 global olddisplist crossings cornercrossings
923 global olddisplist crossings cornercrossings
923
924
924 for {set i $lo} {[incr i] < $hi} {} {
925 for {set i $lo} {[incr i] < $hi} {} {
925 set p [lindex $olddisplist $i]
926 set p [lindex $olddisplist $i]
926 if {$p == {}} continue
927 if {$p == {}} continue
927 if {$i == $corner} {
928 if {$i == $corner} {
928 if {![info exists cornercrossings($id)]
929 if {![info exists cornercrossings($id)]
929 || [lsearch -exact $cornercrossings($id) $p] < 0} {
930 || [lsearch -exact $cornercrossings($id) $p] < 0} {
930 lappend cornercrossings($id) $p
931 lappend cornercrossings($id) $p
931 }
932 }
932 if {![info exists cornercrossings($p)]
933 if {![info exists cornercrossings($p)]
933 || [lsearch -exact $cornercrossings($p) $id] < 0} {
934 || [lsearch -exact $cornercrossings($p) $id] < 0} {
934 lappend cornercrossings($p) $id
935 lappend cornercrossings($p) $id
935 }
936 }
936 } else {
937 } else {
937 if {![info exists crossings($id)]
938 if {![info exists crossings($id)]
938 || [lsearch -exact $crossings($id) $p] < 0} {
939 || [lsearch -exact $crossings($id) $p] < 0} {
939 lappend crossings($id) $p
940 lappend crossings($id) $p
940 }
941 }
941 if {![info exists crossings($p)]
942 if {![info exists crossings($p)]
942 || [lsearch -exact $crossings($p) $id] < 0} {
943 || [lsearch -exact $crossings($p) $id] < 0} {
943 lappend crossings($p) $id
944 lappend crossings($p) $id
944 }
945 }
945 }
946 }
946 }
947 }
947 }
948 }
948
949
949 proc xcoord {i level ln} {
950 proc xcoord {i level ln} {
950 global canvx0 xspc1 xspc2
951 global canvx0 xspc1 xspc2
951
952
952 set x [expr {$canvx0 + $i * $xspc1($ln)}]
953 set x [expr {$canvx0 + $i * $xspc1($ln)}]
953 if {$i > 0 && $i == $level} {
954 if {$i > 0 && $i == $level} {
954 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
955 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
955 } elseif {$i > $level} {
956 } elseif {$i > $level} {
956 set x [expr {$x + $xspc2 - $xspc1($ln)}]
957 set x [expr {$x + $xspc2 - $xspc1($ln)}]
957 }
958 }
958 return $x
959 return $x
959 }
960 }
960
961
961 # it seems Tk can't draw arrows on the end of diagonal line segments...
962 # it seems Tk can't draw arrows on the end of diagonal line segments...
962 proc trimdiagend {line} {
963 proc trimdiagend {line} {
963 while {[llength $line] > 4} {
964 while {[llength $line] > 4} {
964 set x1 [lindex $line end-3]
965 set x1 [lindex $line end-3]
965 set y1 [lindex $line end-2]
966 set y1 [lindex $line end-2]
966 set x2 [lindex $line end-1]
967 set x2 [lindex $line end-1]
967 set y2 [lindex $line end]
968 set y2 [lindex $line end]
968 if {($x1 == $x2) != ($y1 == $y2)} break
969 if {($x1 == $x2) != ($y1 == $y2)} break
969 set line [lreplace $line end-1 end]
970 set line [lreplace $line end-1 end]
970 }
971 }
971 return $line
972 return $line
972 }
973 }
973
974
974 proc trimdiagstart {line} {
975 proc trimdiagstart {line} {
975 while {[llength $line] > 4} {
976 while {[llength $line] > 4} {
976 set x1 [lindex $line 0]
977 set x1 [lindex $line 0]
977 set y1 [lindex $line 1]
978 set y1 [lindex $line 1]
978 set x2 [lindex $line 2]
979 set x2 [lindex $line 2]
979 set y2 [lindex $line 3]
980 set y2 [lindex $line 3]
980 if {($x1 == $x2) != ($y1 == $y2)} break
981 if {($x1 == $x2) != ($y1 == $y2)} break
981 set line [lreplace $line 0 1]
982 set line [lreplace $line 0 1]
982 }
983 }
983 return $line
984 return $line
984 }
985 }
985
986
986 proc drawslants {id needonscreen nohs} {
987 proc drawslants {id needonscreen nohs} {
987 global canv mainline mainlinearrow sidelines
988 global canv mainline mainlinearrow sidelines
988 global canvx0 canvy xspc1 xspc2 lthickness
989 global canvx0 canvy xspc1 xspc2 lthickness
989 global currentparents dupparents
990 global currentparents dupparents
990 global lthickness linespc canvy colormap lineno geometry
991 global lthickness linespc canvy colormap lineno geometry
991 global maxgraphpct maxwidth
992 global maxgraphpct maxwidth
992 global displist onscreen lastuse
993 global displist onscreen lastuse
993 global parents commitlisted
994 global parents commitlisted
994 global oldnlines olddlevel olddisplist
995 global oldnlines olddlevel olddisplist
995 global nhyperspace numcommits nnewparents
996 global nhyperspace numcommits nnewparents
996
997
997 if {$lineno < 0} {
998 if {$lineno < 0} {
998 lappend displist $id
999 lappend displist $id
999 set onscreen($id) 1
1000 set onscreen($id) 1
1000 return 0
1001 return 0
1001 }
1002 }
1002
1003
1003 set y1 [expr {$canvy - $linespc}]
1004 set y1 [expr {$canvy - $linespc}]
1004 set y2 $canvy
1005 set y2 $canvy
1005
1006
1006 # work out what we need to get back on screen
1007 # work out what we need to get back on screen
1007 set reins {}
1008 set reins {}
1008 if {$onscreen($id) < 0} {
1009 if {$onscreen($id) < 0} {
1009 # next to do isn't displayed, better get it on screen...
1010 # next to do isn't displayed, better get it on screen...
1010 lappend reins [list $id 0]
1011 lappend reins [list $id 0]
1011 }
1012 }
1012 # make sure all the previous commits's parents are on the screen
1013 # make sure all the previous commits's parents are on the screen
1013 foreach p $currentparents {
1014 foreach p $currentparents {
1014 if {$onscreen($p) < 0} {
1015 if {$onscreen($p) < 0} {
1015 lappend reins [list $p 0]
1016 lappend reins [list $p 0]
1016 }
1017 }
1017 }
1018 }
1018 # bring back anything requested by caller
1019 # bring back anything requested by caller
1019 if {$needonscreen ne {}} {
1020 if {$needonscreen ne {}} {
1020 lappend reins $needonscreen
1021 lappend reins $needonscreen
1021 }
1022 }
1022
1023
1023 # try the shortcut
1024 # try the shortcut
1024 if {$currentparents == $id && $onscreen($id) == 0 && $reins eq {}} {
1025 if {$currentparents == $id && $onscreen($id) == 0 && $reins eq {}} {
1025 set dlevel $olddlevel
1026 set dlevel $olddlevel
1026 set x [xcoord $dlevel $dlevel $lineno]
1027 set x [xcoord $dlevel $dlevel $lineno]
1027 set mainline($id) [list $x $y1]
1028 set mainline($id) [list $x $y1]
1028 set mainlinearrow($id) none
1029 set mainlinearrow($id) none
1029 set lastuse($id) $lineno
1030 set lastuse($id) $lineno
1030 set displist [lreplace $displist $dlevel $dlevel $id]
1031 set displist [lreplace $displist $dlevel $dlevel $id]
1031 set onscreen($id) 1
1032 set onscreen($id) 1
1032 set xspc1([expr {$lineno + 1}]) $xspc1($lineno)
1033 set xspc1([expr {$lineno + 1}]) $xspc1($lineno)
1033 return $dlevel
1034 return $dlevel
1034 }
1035 }
1035
1036
1036 # update displist
1037 # update displist
1037 set displist [lreplace $displist $olddlevel $olddlevel]
1038 set displist [lreplace $displist $olddlevel $olddlevel]
1038 set j $olddlevel
1039 set j $olddlevel
1039 foreach p $currentparents {
1040 foreach p $currentparents {
1040 set lastuse($p) $lineno
1041 set lastuse($p) $lineno
1041 if {$onscreen($p) == 0} {
1042 if {$onscreen($p) == 0} {
1042 set displist [linsert $displist $j $p]
1043 set displist [linsert $displist $j $p]
1043 set onscreen($p) 1
1044 set onscreen($p) 1
1044 incr j
1045 incr j
1045 }
1046 }
1046 }
1047 }
1047 if {$onscreen($id) == 0} {
1048 if {$onscreen($id) == 0} {
1048 lappend displist $id
1049 lappend displist $id
1049 set onscreen($id) 1
1050 set onscreen($id) 1
1050 }
1051 }
1051
1052
1052 # remove the null entry if present
1053 # remove the null entry if present
1053 set nullentry [lsearch -exact $displist {}]
1054 set nullentry [lsearch -exact $displist {}]
1054 if {$nullentry >= 0} {
1055 if {$nullentry >= 0} {
1055 set displist [lreplace $displist $nullentry $nullentry]
1056 set displist [lreplace $displist $nullentry $nullentry]
1056 }
1057 }
1057
1058
1058 # bring back the ones we need now (if we did it earlier
1059 # bring back the ones we need now (if we did it earlier
1059 # it would change displist and invalidate olddlevel)
1060 # it would change displist and invalidate olddlevel)
1060 foreach pi $reins {
1061 foreach pi $reins {
1061 # test again in case of duplicates in reins
1062 # test again in case of duplicates in reins
1062 set p [lindex $pi 0]
1063 set p [lindex $pi 0]
1063 if {$onscreen($p) < 0} {
1064 if {$onscreen($p) < 0} {
1064 set onscreen($p) 1
1065 set onscreen($p) 1
1065 set lastuse($p) $lineno
1066 set lastuse($p) $lineno
1066 set displist [linsert $displist [lindex $pi 1] $p]
1067 set displist [linsert $displist [lindex $pi 1] $p]
1067 incr nhyperspace -1
1068 incr nhyperspace -1
1068 }
1069 }
1069 }
1070 }
1070
1071
1071 set lastuse($id) $lineno
1072 set lastuse($id) $lineno
1072
1073
1073 # see if we need to make any lines jump off into hyperspace
1074 # see if we need to make any lines jump off into hyperspace
1074 set displ [llength $displist]
1075 set displ [llength $displist]
1075 if {$displ > $maxwidth} {
1076 if {$displ > $maxwidth} {
1076 set ages {}
1077 set ages {}
1077 foreach x $displist {
1078 foreach x $displist {
1078 lappend ages [list $lastuse($x) $x]
1079 lappend ages [list $lastuse($x) $x]
1079 }
1080 }
1080 set ages [lsort -integer -index 0 $ages]
1081 set ages [lsort -integer -index 0 $ages]
1081 set k 0
1082 set k 0
1082 while {$displ > $maxwidth} {
1083 while {$displ > $maxwidth} {
1083 set use [lindex $ages $k 0]
1084 set use [lindex $ages $k 0]
1084 set victim [lindex $ages $k 1]
1085 set victim [lindex $ages $k 1]
1085 if {$use >= $lineno - 5} break
1086 if {$use >= $lineno - 5} break
1086 incr k
1087 incr k
1087 if {[lsearch -exact $nohs $victim] >= 0} continue
1088 if {[lsearch -exact $nohs $victim] >= 0} continue
1088 set i [lsearch -exact $displist $victim]
1089 set i [lsearch -exact $displist $victim]
1089 set displist [lreplace $displist $i $i]
1090 set displist [lreplace $displist $i $i]
1090 set onscreen($victim) -1
1091 set onscreen($victim) -1
1091 incr nhyperspace
1092 incr nhyperspace
1092 incr displ -1
1093 incr displ -1
1093 if {$i < $nullentry} {
1094 if {$i < $nullentry} {
1094 incr nullentry -1
1095 incr nullentry -1
1095 }
1096 }
1096 set x [lindex $mainline($victim) end-1]
1097 set x [lindex $mainline($victim) end-1]
1097 lappend mainline($victim) $x $y1
1098 lappend mainline($victim) $x $y1
1098 set line [trimdiagend $mainline($victim)]
1099 set line [trimdiagend $mainline($victim)]
1099 set arrow "last"
1100 set arrow "last"
1100 if {$mainlinearrow($victim) ne "none"} {
1101 if {$mainlinearrow($victim) ne "none"} {
1101 set line [trimdiagstart $line]
1102 set line [trimdiagstart $line]
1102 set arrow "both"
1103 set arrow "both"
1103 }
1104 }
1104 lappend sidelines($victim) [list $line 1 $arrow]
1105 lappend sidelines($victim) [list $line 1 $arrow]
1105 unset mainline($victim)
1106 unset mainline($victim)
1106 }
1107 }
1107 }
1108 }
1108
1109
1109 set dlevel [lsearch -exact $displist $id]
1110 set dlevel [lsearch -exact $displist $id]
1110
1111
1111 # If we are reducing, put in a null entry
1112 # If we are reducing, put in a null entry
1112 if {$displ < $oldnlines} {
1113 if {$displ < $oldnlines} {
1113 # does the next line look like a merge?
1114 # does the next line look like a merge?
1114 # i.e. does it have > 1 new parent?
1115 # i.e. does it have > 1 new parent?
1115 if {$nnewparents($id) > 1} {
1116 if {$nnewparents($id) > 1} {
1116 set i [expr {$dlevel + 1}]
1117 set i [expr {$dlevel + 1}]
1117 } elseif {$nnewparents([lindex $olddisplist $olddlevel]) == 0} {
1118 } elseif {$nnewparents([lindex $olddisplist $olddlevel]) == 0} {
1118 set i $olddlevel
1119 set i $olddlevel
1119 if {$nullentry >= 0 && $nullentry < $i} {
1120 if {$nullentry >= 0 && $nullentry < $i} {
1120 incr i -1
1121 incr i -1
1121 }
1122 }
1122 } elseif {$nullentry >= 0} {
1123 } elseif {$nullentry >= 0} {
1123 set i $nullentry
1124 set i $nullentry
1124 while {$i < $displ
1125 while {$i < $displ
1125 && [lindex $olddisplist $i] == [lindex $displist $i]} {
1126 && [lindex $olddisplist $i] == [lindex $displist $i]} {
1126 incr i
1127 incr i
1127 }
1128 }
1128 } else {
1129 } else {
1129 set i $olddlevel
1130 set i $olddlevel
1130 if {$dlevel >= $i} {
1131 if {$dlevel >= $i} {
1131 incr i
1132 incr i
1132 }
1133 }
1133 }
1134 }
1134 if {$i < $displ} {
1135 if {$i < $displ} {
1135 set displist [linsert $displist $i {}]
1136 set displist [linsert $displist $i {}]
1136 incr displ
1137 incr displ
1137 if {$dlevel >= $i} {
1138 if {$dlevel >= $i} {
1138 incr dlevel
1139 incr dlevel
1139 }
1140 }
1140 }
1141 }
1141 }
1142 }
1142
1143
1143 # decide on the line spacing for the next line
1144 # decide on the line spacing for the next line
1144 set lj [expr {$lineno + 1}]
1145 set lj [expr {$lineno + 1}]
1145 set maxw [expr {$maxgraphpct * $geometry(canv1) / 100}]
1146 set maxw [expr {$maxgraphpct * $geometry(canv1) / 100}]
1146 if {$displ <= 1 || $canvx0 + $displ * $xspc2 <= $maxw} {
1147 if {$displ <= 1 || $canvx0 + $displ * $xspc2 <= $maxw} {
1147 set xspc1($lj) $xspc2
1148 set xspc1($lj) $xspc2
1148 } else {
1149 } else {
1149 set xspc1($lj) [expr {($maxw - $canvx0 - $xspc2) / ($displ - 1)}]
1150 set xspc1($lj) [expr {($maxw - $canvx0 - $xspc2) / ($displ - 1)}]
1150 if {$xspc1($lj) < $lthickness} {
1151 if {$xspc1($lj) < $lthickness} {
1151 set xspc1($lj) $lthickness
1152 set xspc1($lj) $lthickness
1152 }
1153 }
1153 }
1154 }
1154
1155
1155 foreach idi $reins {
1156 foreach idi $reins {
1156 set id [lindex $idi 0]
1157 set id [lindex $idi 0]
1157 set j [lsearch -exact $displist $id]
1158 set j [lsearch -exact $displist $id]
1158 set xj [xcoord $j $dlevel $lj]
1159 set xj [xcoord $j $dlevel $lj]
1159 set mainline($id) [list $xj $y2]
1160 set mainline($id) [list $xj $y2]
1160 set mainlinearrow($id) first
1161 set mainlinearrow($id) first
1161 }
1162 }
1162
1163
1163 set i -1
1164 set i -1
1164 foreach id $olddisplist {
1165 foreach id $olddisplist {
1165 incr i
1166 incr i
1166 if {$id == {}} continue
1167 if {$id == {}} continue
1167 if {$onscreen($id) <= 0} continue
1168 if {$onscreen($id) <= 0} continue
1168 set xi [xcoord $i $olddlevel $lineno]
1169 set xi [xcoord $i $olddlevel $lineno]
1169 if {$i == $olddlevel} {
1170 if {$i == $olddlevel} {
1170 foreach p $currentparents {
1171 foreach p $currentparents {
1171 set j [lsearch -exact $displist $p]
1172 set j [lsearch -exact $displist $p]
1172 set coords [list $xi $y1]
1173 set coords [list $xi $y1]
1173 set xj [xcoord $j $dlevel $lj]
1174 set xj [xcoord $j $dlevel $lj]
1174 if {$xj < $xi - $linespc} {
1175 if {$xj < $xi - $linespc} {
1175 lappend coords [expr {$xj + $linespc}] $y1
1176 lappend coords [expr {$xj + $linespc}] $y1
1176 notecrossings $p $j $i [expr {$j + 1}]
1177 notecrossings $p $j $i [expr {$j + 1}]
1177 } elseif {$xj > $xi + $linespc} {
1178 } elseif {$xj > $xi + $linespc} {
1178 lappend coords [expr {$xj - $linespc}] $y1
1179 lappend coords [expr {$xj - $linespc}] $y1
1179 notecrossings $p $i $j [expr {$j - 1}]
1180 notecrossings $p $i $j [expr {$j - 1}]
1180 }
1181 }
1181 if {[lsearch -exact $dupparents $p] >= 0} {
1182 if {[lsearch -exact $dupparents $p] >= 0} {
1182 # draw a double-width line to indicate the doubled parent
1183 # draw a double-width line to indicate the doubled parent
1183 lappend coords $xj $y2
1184 lappend coords $xj $y2
1184 lappend sidelines($p) [list $coords 2 none]
1185 lappend sidelines($p) [list $coords 2 none]
1185 if {![info exists mainline($p)]} {
1186 if {![info exists mainline($p)]} {
1186 set mainline($p) [list $xj $y2]
1187 set mainline($p) [list $xj $y2]
1187 set mainlinearrow($p) none
1188 set mainlinearrow($p) none
1188 }
1189 }
1189 } else {
1190 } else {
1190 # normal case, no parent duplicated
1191 # normal case, no parent duplicated
1191 set yb $y2
1192 set yb $y2
1192 set dx [expr {abs($xi - $xj)}]
1193 set dx [expr {abs($xi - $xj)}]
1193 if {0 && $dx < $linespc} {
1194 if {0 && $dx < $linespc} {
1194 set yb [expr {$y1 + $dx}]
1195 set yb [expr {$y1 + $dx}]
1195 }
1196 }
1196 if {![info exists mainline($p)]} {
1197 if {![info exists mainline($p)]} {
1197 if {$xi != $xj} {
1198 if {$xi != $xj} {
1198 lappend coords $xj $yb
1199 lappend coords $xj $yb
1199 }
1200 }
1200 set mainline($p) $coords
1201 set mainline($p) $coords
1201 set mainlinearrow($p) none
1202 set mainlinearrow($p) none
1202 } else {
1203 } else {
1203 lappend coords $xj $yb
1204 lappend coords $xj $yb
1204 if {$yb < $y2} {
1205 if {$yb < $y2} {
1205 lappend coords $xj $y2
1206 lappend coords $xj $y2
1206 }
1207 }
1207 lappend sidelines($p) [list $coords 1 none]
1208 lappend sidelines($p) [list $coords 1 none]
1208 }
1209 }
1209 }
1210 }
1210 }
1211 }
1211 } else {
1212 } else {
1212 set j $i
1213 set j $i
1213 if {[lindex $displist $i] != $id} {
1214 if {[lindex $displist $i] != $id} {
1214 set j [lsearch -exact $displist $id]
1215 set j [lsearch -exact $displist $id]
1215 }
1216 }
1216 if {$j != $i || $xspc1($lineno) != $xspc1($lj)
1217 if {$j != $i || $xspc1($lineno) != $xspc1($lj)
1217 || ($olddlevel < $i && $i < $dlevel)
1218 || ($olddlevel < $i && $i < $dlevel)
1218 || ($dlevel < $i && $i < $olddlevel)} {
1219 || ($dlevel < $i && $i < $olddlevel)} {
1219 set xj [xcoord $j $dlevel $lj]
1220 set xj [xcoord $j $dlevel $lj]
1220 lappend mainline($id) $xi $y1 $xj $y2
1221 lappend mainline($id) $xi $y1 $xj $y2
1221 }
1222 }
1222 }
1223 }
1223 }
1224 }
1224 return $dlevel
1225 return $dlevel
1225 }
1226 }
1226
1227
1227 # search for x in a list of lists
1228 # search for x in a list of lists
1228 proc llsearch {llist x} {
1229 proc llsearch {llist x} {
1229 set i 0
1230 set i 0
1230 foreach l $llist {
1231 foreach l $llist {
1231 if {$l == $x || [lsearch -exact $l $x] >= 0} {
1232 if {$l == $x || [lsearch -exact $l $x] >= 0} {
1232 return $i
1233 return $i
1233 }
1234 }
1234 incr i
1235 incr i
1235 }
1236 }
1236 return -1
1237 return -1
1237 }
1238 }
1238
1239
1239 proc drawmore {reading} {
1240 proc drawmore {reading} {
1240 global displayorder numcommits ncmupdate nextupdate
1241 global displayorder numcommits ncmupdate nextupdate
1241 global stopped nhyperspace parents commitlisted
1242 global stopped nhyperspace parents commitlisted
1242 global maxwidth onscreen displist currentparents olddlevel
1243 global maxwidth onscreen displist currentparents olddlevel
1243
1244
1244 set n [llength $displayorder]
1245 set n [llength $displayorder]
1245 while {$numcommits < $n} {
1246 while {$numcommits < $n} {
1246 set id [lindex $displayorder $numcommits]
1247 set id [lindex $displayorder $numcommits]
1247 set ctxend [expr {$numcommits + 10}]
1248 set ctxend [expr {$numcommits + 10}]
1248 if {!$reading && $ctxend > $n} {
1249 if {!$reading && $ctxend > $n} {
1249 set ctxend $n
1250 set ctxend $n
1250 }
1251 }
1251 set dlist {}
1252 set dlist {}
1252 if {$numcommits > 0} {
1253 if {$numcommits > 0} {
1253 set dlist [lreplace $displist $olddlevel $olddlevel]
1254 set dlist [lreplace $displist $olddlevel $olddlevel]
1254 set i $olddlevel
1255 set i $olddlevel
1255 foreach p $currentparents {
1256 foreach p $currentparents {
1256 if {$onscreen($p) == 0} {
1257 if {$onscreen($p) == 0} {
1257 set dlist [linsert $dlist $i $p]
1258 set dlist [linsert $dlist $i $p]
1258 incr i
1259 incr i
1259 }
1260 }
1260 }
1261 }
1261 }
1262 }
1262 set nohs {}
1263 set nohs {}
1263 set reins {}
1264 set reins {}
1264 set isfat [expr {[llength $dlist] > $maxwidth}]
1265 set isfat [expr {[llength $dlist] > $maxwidth}]
1265 if {$nhyperspace > 0 || $isfat} {
1266 if {$nhyperspace > 0 || $isfat} {
1266 if {$ctxend > $n} break
1267 if {$ctxend > $n} break
1267 # work out what to bring back and
1268 # work out what to bring back and
1268 # what we want to don't want to send into hyperspace
1269 # what we want to don't want to send into hyperspace
1269 set room 1
1270 set room 1
1270 for {set k $numcommits} {$k < $ctxend} {incr k} {
1271 for {set k $numcommits} {$k < $ctxend} {incr k} {
1271 set x [lindex $displayorder $k]
1272 set x [lindex $displayorder $k]
1272 set i [llsearch $dlist $x]
1273 set i [llsearch $dlist $x]
1273 if {$i < 0} {
1274 if {$i < 0} {
1274 set i [llength $dlist]
1275 set i [llength $dlist]
1275 lappend dlist $x
1276 lappend dlist $x
1276 }
1277 }
1277 if {[lsearch -exact $nohs $x] < 0} {
1278 if {[lsearch -exact $nohs $x] < 0} {
1278 lappend nohs $x
1279 lappend nohs $x
1279 }
1280 }
1280 if {$reins eq {} && $onscreen($x) < 0 && $room} {
1281 if {$reins eq {} && $onscreen($x) < 0 && $room} {
1281 set reins [list $x $i]
1282 set reins [list $x $i]
1282 }
1283 }
1283 set newp {}
1284 set newp {}
1284 if {[info exists commitlisted($x)]} {
1285 if {[info exists commitlisted($x)]} {
1285 set right 0
1286 set right 0
1286 foreach p $parents($x) {
1287 foreach p $parents($x) {
1287 if {[llsearch $dlist $p] < 0} {
1288 if {[llsearch $dlist $p] < 0} {
1288 lappend newp $p
1289 lappend newp $p
1289 if {[lsearch -exact $nohs $p] < 0} {
1290 if {[lsearch -exact $nohs $p] < 0} {
1290 lappend nohs $p
1291 lappend nohs $p
1291 }
1292 }
1292 if {$reins eq {} && $onscreen($p) < 0 && $room} {
1293 if {$reins eq {} && $onscreen($p) < 0 && $room} {
1293 set reins [list $p [expr {$i + $right}]]
1294 set reins [list $p [expr {$i + $right}]]
1294 }
1295 }
1295 }
1296 }
1296 set right 1
1297 set right 1
1297 }
1298 }
1298 }
1299 }
1299 set l [lindex $dlist $i]
1300 set l [lindex $dlist $i]
1300 if {[llength $l] == 1} {
1301 if {[llength $l] == 1} {
1301 set l $newp
1302 set l $newp
1302 } else {
1303 } else {
1303 set j [lsearch -exact $l $x]
1304 set j [lsearch -exact $l $x]
1304 set l [concat [lreplace $l $j $j] $newp]
1305 set l [concat [lreplace $l $j $j] $newp]
1305 }
1306 }
1306 set dlist [lreplace $dlist $i $i $l]
1307 set dlist [lreplace $dlist $i $i $l]
1307 if {$room && $isfat && [llength $newp] <= 1} {
1308 if {$room && $isfat && [llength $newp] <= 1} {
1308 set room 0
1309 set room 0
1309 }
1310 }
1310 }
1311 }
1311 }
1312 }
1312
1313
1313 set dlevel [drawslants $id $reins $nohs]
1314 set dlevel [drawslants $id $reins $nohs]
1314 drawcommitline $dlevel
1315 drawcommitline $dlevel
1315 if {[clock clicks -milliseconds] >= $nextupdate
1316 if {[clock clicks -milliseconds] >= $nextupdate
1316 && $numcommits >= $ncmupdate} {
1317 && $numcommits >= $ncmupdate} {
1317 doupdate $reading
1318 doupdate $reading
1318 if {$stopped} break
1319 if {$stopped} break
1319 }
1320 }
1320 }
1321 }
1321 }
1322 }
1322
1323
1323 # level here is an index in todo
1324 # level here is an index in todo
1324 proc updatetodo {level noshortcut} {
1325 proc updatetodo {level noshortcut} {
1325 global ncleft todo nnewparents
1326 global ncleft todo nnewparents
1326 global commitlisted parents onscreen
1327 global commitlisted parents onscreen
1327
1328
1328 set id [lindex $todo $level]
1329 set id [lindex $todo $level]
1329 set olds {}
1330 set olds {}
1330 if {[info exists commitlisted($id)]} {
1331 if {[info exists commitlisted($id)]} {
1331 foreach p $parents($id) {
1332 foreach p $parents($id) {
1332 if {[lsearch -exact $olds $p] < 0} {
1333 if {[lsearch -exact $olds $p] < 0} {
1333 lappend olds $p
1334 lappend olds $p
1334 }
1335 }
1335 }
1336 }
1336 }
1337 }
1337 if {!$noshortcut && [llength $olds] == 1} {
1338 if {!$noshortcut && [llength $olds] == 1} {
1338 set p [lindex $olds 0]
1339 set p [lindex $olds 0]
1339 if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
1340 if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
1340 set ncleft($p) 0
1341 set ncleft($p) 0
1341 set todo [lreplace $todo $level $level $p]
1342 set todo [lreplace $todo $level $level $p]
1342 set onscreen($p) 0
1343 set onscreen($p) 0
1343 set nnewparents($id) 1
1344 set nnewparents($id) 1
1344 return 0
1345 return 0
1345 }
1346 }
1346 }
1347 }
1347
1348
1348 set todo [lreplace $todo $level $level]
1349 set todo [lreplace $todo $level $level]
1349 set i $level
1350 set i $level
1350 set n 0
1351 set n 0
1351 foreach p $olds {
1352 foreach p $olds {
1352 incr ncleft($p) -1
1353 incr ncleft($p) -1
1353 set k [lsearch -exact $todo $p]
1354 set k [lsearch -exact $todo $p]
1354 if {$k < 0} {
1355 if {$k < 0} {
1355 set todo [linsert $todo $i $p]
1356 set todo [linsert $todo $i $p]
1356 set onscreen($p) 0
1357 set onscreen($p) 0
1357 incr i
1358 incr i
1358 incr n
1359 incr n
1359 }
1360 }
1360 }
1361 }
1361 set nnewparents($id) $n
1362 set nnewparents($id) $n
1362
1363
1363 return 1
1364 return 1
1364 }
1365 }
1365
1366
1366 proc decidenext {{noread 0}} {
1367 proc decidenext {{noread 0}} {
1367 global ncleft todo
1368 global ncleft todo
1368 global datemode cdate
1369 global datemode cdate
1369 global commitinfo
1370 global commitinfo
1370
1371
1371 # choose which one to do next time around
1372 # choose which one to do next time around
1372 set todol [llength $todo]
1373 set todol [llength $todo]
1373 set level -1
1374 set level -1
1374 set latest {}
1375 set latest {}
1375 for {set k $todol} {[incr k -1] >= 0} {} {
1376 for {set k $todol} {[incr k -1] >= 0} {} {
1376 set p [lindex $todo $k]
1377 set p [lindex $todo $k]
1377 if {$ncleft($p) == 0} {
1378 if {$ncleft($p) == 0} {
1378 if {$datemode} {
1379 if {$datemode} {
1379 if {![info exists commitinfo($p)]} {
1380 if {![info exists commitinfo($p)]} {
1380 if {$noread} {
1381 if {$noread} {
1381 return {}
1382 return {}
1382 }
1383 }
1383 readcommit $p
1384 readcommit $p
1384 }
1385 }
1385 if {$latest == {} || $cdate($p) > $latest} {
1386 if {$latest == {} || $cdate($p) > $latest} {
1386 set level $k
1387 set level $k
1387 set latest $cdate($p)
1388 set latest $cdate($p)
1388 }
1389 }
1389 } else {
1390 } else {
1390 set level $k
1391 set level $k
1391 break
1392 break
1392 }
1393 }
1393 }
1394 }
1394 }
1395 }
1395 if {$level < 0} {
1396 if {$level < 0} {
1396 if {$todo != {}} {
1397 if {$todo != {}} {
1397 puts "ERROR: none of the pending commits can be done yet:"
1398 puts "ERROR: none of the pending commits can be done yet:"
1398 foreach p $todo {
1399 foreach p $todo {
1399 puts " $p ($ncleft($p))"
1400 puts " $p ($ncleft($p))"
1400 }
1401 }
1401 }
1402 }
1402 return -1
1403 return -1
1403 }
1404 }
1404
1405
1405 return $level
1406 return $level
1406 }
1407 }
1407
1408
1408 proc drawcommit {id} {
1409 proc drawcommit {id} {
1409 global phase todo nchildren datemode nextupdate
1410 global phase todo nchildren datemode nextupdate
1410 global numcommits ncmupdate displayorder todo onscreen
1411 global numcommits ncmupdate displayorder todo onscreen
1411
1412
1412 if {$phase != "incrdraw"} {
1413 if {$phase != "incrdraw"} {
1413 set phase incrdraw
1414 set phase incrdraw
1414 set displayorder {}
1415 set displayorder {}
1415 set todo {}
1416 set todo {}
1416 initgraph
1417 initgraph
1417 }
1418 }
1418 if {$nchildren($id) == 0} {
1419 if {$nchildren($id) == 0} {
1419 lappend todo $id
1420 lappend todo $id
1420 set onscreen($id) 0
1421 set onscreen($id) 0
1421 }
1422 }
1422 set level [decidenext 1]
1423 set level [decidenext 1]
1423 if {$level == {} || $id != [lindex $todo $level]} {
1424 if {$level == {} || $id != [lindex $todo $level]} {
1424 return
1425 return
1425 }
1426 }
1426 while 1 {
1427 while 1 {
1427 lappend displayorder [lindex $todo $level]
1428 lappend displayorder [lindex $todo $level]
1428 if {[updatetodo $level $datemode]} {
1429 if {[updatetodo $level $datemode]} {
1429 set level [decidenext 1]
1430 set level [decidenext 1]
1430 if {$level == {}} break
1431 if {$level == {}} break
1431 }
1432 }
1432 set id [lindex $todo $level]
1433 set id [lindex $todo $level]
1433 if {![info exists commitlisted($id)]} {
1434 if {![info exists commitlisted($id)]} {
1434 break
1435 break
1435 }
1436 }
1436 }
1437 }
1437 drawmore 1
1438 drawmore 1
1438 }
1439 }
1439
1440
1440 proc finishcommits {} {
1441 proc finishcommits {} {
1441 global phase
1442 global phase
1442 global canv mainfont ctext maincursor textcursor
1443 global canv mainfont ctext maincursor textcursor
1443
1444
1444 if {$phase != "incrdraw"} {
1445 if {$phase != "incrdraw"} {
1445 $canv delete all
1446 $canv delete all
1446 $canv create text 3 3 -anchor nw -text "No commits selected" \
1447 $canv create text 3 3 -anchor nw -text "No commits selected" \
1447 -font $mainfont -tags textitems
1448 -font $mainfont -tags textitems
1448 set phase {}
1449 set phase {}
1449 } else {
1450 } else {
1450 drawrest
1451 drawrest
1451 }
1452 }
1452 . config -cursor $maincursor
1453 . config -cursor $maincursor
1453 settextcursor $textcursor
1454 settextcursor $textcursor
1454 }
1455 }
1455
1456
1456 # Don't change the text pane cursor if it is currently the hand cursor,
1457 # Don't change the text pane cursor if it is currently the hand cursor,
1457 # showing that we are over a sha1 ID link.
1458 # showing that we are over a sha1 ID link.
1458 proc settextcursor {c} {
1459 proc settextcursor {c} {
1459 global ctext curtextcursor
1460 global ctext curtextcursor
1460
1461
1461 if {[$ctext cget -cursor] == $curtextcursor} {
1462 if {[$ctext cget -cursor] == $curtextcursor} {
1462 $ctext config -cursor $c
1463 $ctext config -cursor $c
1463 }
1464 }
1464 set curtextcursor $c
1465 set curtextcursor $c
1465 }
1466 }
1466
1467
1467 proc drawgraph {} {
1468 proc drawgraph {} {
1468 global nextupdate startmsecs ncmupdate
1469 global nextupdate startmsecs ncmupdate
1469 global displayorder onscreen
1470 global displayorder onscreen
1470
1471
1471 if {$displayorder == {}} return
1472 if {$displayorder == {}} return
1472 set startmsecs [clock clicks -milliseconds]
1473 set startmsecs [clock clicks -milliseconds]
1473 set nextupdate [expr $startmsecs + 100]
1474 set nextupdate [expr $startmsecs + 100]
1474 set ncmupdate 1
1475 set ncmupdate 1
1475 initgraph
1476 initgraph
1476 foreach id $displayorder {
1477 foreach id $displayorder {
1477 set onscreen($id) 0
1478 set onscreen($id) 0
1478 }
1479 }
1479 drawmore 0
1480 drawmore 0
1480 }
1481 }
1481
1482
1482 proc drawrest {} {
1483 proc drawrest {} {
1483 global phase stopped redisplaying selectedline
1484 global phase stopped redisplaying selectedline
1484 global datemode todo displayorder
1485 global datemode todo displayorder
1485 global numcommits ncmupdate
1486 global numcommits ncmupdate
1486 global nextupdate startmsecs
1487 global nextupdate startmsecs
1487
1488
1488 set level [decidenext]
1489 set level [decidenext]
1489 if {$level >= 0} {
1490 if {$level >= 0} {
1490 set phase drawgraph
1491 set phase drawgraph
1491 while 1 {
1492 while 1 {
1492 lappend displayorder [lindex $todo $level]
1493 lappend displayorder [lindex $todo $level]
1493 set hard [updatetodo $level $datemode]
1494 set hard [updatetodo $level $datemode]
1494 if {$hard} {
1495 if {$hard} {
1495 set level [decidenext]
1496 set level [decidenext]
1496 if {$level < 0} break
1497 if {$level < 0} break
1497 }
1498 }
1498 }
1499 }
1499 drawmore 0
1500 drawmore 0
1500 }
1501 }
1501 set phase {}
1502 set phase {}
1502 set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
1503 set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
1503 #puts "overall $drawmsecs ms for $numcommits commits"
1504 #puts "overall $drawmsecs ms for $numcommits commits"
1504 if {$redisplaying} {
1505 if {$redisplaying} {
1505 if {$stopped == 0 && [info exists selectedline]} {
1506 if {$stopped == 0 && [info exists selectedline]} {
1506 selectline $selectedline 0
1507 selectline $selectedline 0
1507 }
1508 }
1508 if {$stopped == 1} {
1509 if {$stopped == 1} {
1509 set stopped 0
1510 set stopped 0
1510 after idle drawgraph
1511 after idle drawgraph
1511 } else {
1512 } else {
1512 set redisplaying 0
1513 set redisplaying 0
1513 }
1514 }
1514 }
1515 }
1515 }
1516 }
1516
1517
1517 proc findmatches {f} {
1518 proc findmatches {f} {
1518 global findtype foundstring foundstrlen
1519 global findtype foundstring foundstrlen
1519 if {$findtype == "Regexp"} {
1520 if {$findtype == "Regexp"} {
1520 set matches [regexp -indices -all -inline $foundstring $f]
1521 set matches [regexp -indices -all -inline $foundstring $f]
1521 } else {
1522 } else {
1522 if {$findtype == "IgnCase"} {
1523 if {$findtype == "IgnCase"} {
1523 set str [string tolower $f]
1524 set str [string tolower $f]
1524 } else {
1525 } else {
1525 set str $f
1526 set str $f
1526 }
1527 }
1527 set matches {}
1528 set matches {}
1528 set i 0
1529 set i 0
1529 while {[set j [string first $foundstring $str $i]] >= 0} {
1530 while {[set j [string first $foundstring $str $i]] >= 0} {
1530 lappend matches [list $j [expr $j+$foundstrlen-1]]
1531 lappend matches [list $j [expr $j+$foundstrlen-1]]
1531 set i [expr $j + $foundstrlen]
1532 set i [expr $j + $foundstrlen]
1532 }
1533 }
1533 }
1534 }
1534 return $matches
1535 return $matches
1535 }
1536 }
1536
1537
1537 proc dofind {} {
1538 proc dofind {} {
1538 global findtype findloc findstring markedmatches commitinfo
1539 global findtype findloc findstring markedmatches commitinfo
1539 global numcommits lineid linehtag linentag linedtag
1540 global numcommits lineid linehtag linentag linedtag
1540 global mainfont namefont canv canv2 canv3 selectedline
1541 global mainfont namefont canv canv2 canv3 selectedline
1541 global matchinglines foundstring foundstrlen
1542 global matchinglines foundstring foundstrlen
1542
1543
1543 stopfindproc
1544 stopfindproc
1544 unmarkmatches
1545 unmarkmatches
1545 focus .
1546 focus .
1546 set matchinglines {}
1547 set matchinglines {}
1547 if {$findloc == "Pickaxe"} {
1548 if {$findloc == "Pickaxe"} {
1548 findpatches
1549 findpatches
1549 return
1550 return
1550 }
1551 }
1551 if {$findtype == "IgnCase"} {
1552 if {$findtype == "IgnCase"} {
1552 set foundstring [string tolower $findstring]
1553 set foundstring [string tolower $findstring]
1553 } else {
1554 } else {
1554 set foundstring $findstring
1555 set foundstring $findstring
1555 }
1556 }
1556 set foundstrlen [string length $findstring]
1557 set foundstrlen [string length $findstring]
1557 if {$foundstrlen == 0} return
1558 if {$foundstrlen == 0} return
1558 if {$findloc == "Files"} {
1559 if {$findloc == "Files"} {
1559 findfiles
1560 findfiles
1560 return
1561 return
1561 }
1562 }
1562 if {![info exists selectedline]} {
1563 if {![info exists selectedline]} {
1563 set oldsel -1
1564 set oldsel -1
1564 } else {
1565 } else {
1565 set oldsel $selectedline
1566 set oldsel $selectedline
1566 }
1567 }
1567 set didsel 0
1568 set didsel 0
1568 set fldtypes {Headline Author Date Committer CDate Comment}
1569 set fldtypes {Headline Author Date Committer CDate Comment}
1569 for {set l 0} {$l < $numcommits} {incr l} {
1570 for {set l 0} {$l < $numcommits} {incr l} {
1570 set id $lineid($l)
1571 set id $lineid($l)
1571 set info $commitinfo($id)
1572 set info $commitinfo($id)
1572 set doesmatch 0
1573 set doesmatch 0
1573 foreach f $info ty $fldtypes {
1574 foreach f $info ty $fldtypes {
1574 if {$findloc != "All fields" && $findloc != $ty} {
1575 if {$findloc != "All fields" && $findloc != $ty} {
1575 continue
1576 continue
1576 }
1577 }
1577 set matches [findmatches $f]
1578 set matches [findmatches $f]
1578 if {$matches == {}} continue
1579 if {$matches == {}} continue
1579 set doesmatch 1
1580 set doesmatch 1
1580 if {$ty == "Headline"} {
1581 if {$ty == "Headline"} {
1581 markmatches $canv $l $f $linehtag($l) $matches $mainfont
1582 markmatches $canv $l $f $linehtag($l) $matches $mainfont
1582 } elseif {$ty == "Author"} {
1583 } elseif {$ty == "Author"} {
1583 markmatches $canv2 $l $f $linentag($l) $matches $namefont
1584 markmatches $canv2 $l $f $linentag($l) $matches $namefont
1584 } elseif {$ty == "Date"} {
1585 } elseif {$ty == "Date"} {
1585 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1586 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1586 }
1587 }
1587 }
1588 }
1588 if {$doesmatch} {
1589 if {$doesmatch} {
1589 lappend matchinglines $l
1590 lappend matchinglines $l
1590 if {!$didsel && $l > $oldsel} {
1591 if {!$didsel && $l > $oldsel} {
1591 findselectline $l
1592 findselectline $l
1592 set didsel 1
1593 set didsel 1
1593 }
1594 }
1594 }
1595 }
1595 }
1596 }
1596 if {$matchinglines == {}} {
1597 if {$matchinglines == {}} {
1597 bell
1598 bell
1598 } elseif {!$didsel} {
1599 } elseif {!$didsel} {
1599 findselectline [lindex $matchinglines 0]
1600 findselectline [lindex $matchinglines 0]
1600 }
1601 }
1601 }
1602 }
1602
1603
1603 proc findselectline {l} {
1604 proc findselectline {l} {
1604 global findloc commentend ctext
1605 global findloc commentend ctext
1605 selectline $l 1
1606 selectline $l 1
1606 if {$findloc == "All fields" || $findloc == "Comments"} {
1607 if {$findloc == "All fields" || $findloc == "Comments"} {
1607 # highlight the matches in the comments
1608 # highlight the matches in the comments
1608 set f [$ctext get 1.0 $commentend]
1609 set f [$ctext get 1.0 $commentend]
1609 set matches [findmatches $f]
1610 set matches [findmatches $f]
1610 foreach match $matches {
1611 foreach match $matches {
1611 set start [lindex $match 0]
1612 set start [lindex $match 0]
1612 set end [expr [lindex $match 1] + 1]
1613 set end [expr [lindex $match 1] + 1]
1613 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
1614 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
1614 }
1615 }
1615 }
1616 }
1616 }
1617 }
1617
1618
1618 proc findnext {restart} {
1619 proc findnext {restart} {
1619 global matchinglines selectedline
1620 global matchinglines selectedline
1620 if {![info exists matchinglines]} {
1621 if {![info exists matchinglines]} {
1621 if {$restart} {
1622 if {$restart} {
1622 dofind
1623 dofind
1623 }
1624 }
1624 return
1625 return
1625 }
1626 }
1626 if {![info exists selectedline]} return
1627 if {![info exists selectedline]} return
1627 foreach l $matchinglines {
1628 foreach l $matchinglines {
1628 if {$l > $selectedline} {
1629 if {$l > $selectedline} {
1629 findselectline $l
1630 findselectline $l
1630 return
1631 return
1631 }
1632 }
1632 }
1633 }
1633 bell
1634 bell
1634 }
1635 }
1635
1636
1636 proc findprev {} {
1637 proc findprev {} {
1637 global matchinglines selectedline
1638 global matchinglines selectedline
1638 if {![info exists matchinglines]} {
1639 if {![info exists matchinglines]} {
1639 dofind
1640 dofind
1640 return
1641 return
1641 }
1642 }
1642 if {![info exists selectedline]} return
1643 if {![info exists selectedline]} return
1643 set prev {}
1644 set prev {}
1644 foreach l $matchinglines {
1645 foreach l $matchinglines {
1645 if {$l >= $selectedline} break
1646 if {$l >= $selectedline} break
1646 set prev $l
1647 set prev $l
1647 }
1648 }
1648 if {$prev != {}} {
1649 if {$prev != {}} {
1649 findselectline $prev
1650 findselectline $prev
1650 } else {
1651 } else {
1651 bell
1652 bell
1652 }
1653 }
1653 }
1654 }
1654
1655
1655 proc findlocchange {name ix op} {
1656 proc findlocchange {name ix op} {
1656 global findloc findtype findtypemenu
1657 global findloc findtype findtypemenu
1657 if {$findloc == "Pickaxe"} {
1658 if {$findloc == "Pickaxe"} {
1658 set findtype Exact
1659 set findtype Exact
1659 set state disabled
1660 set state disabled
1660 } else {
1661 } else {
1661 set state normal
1662 set state normal
1662 }
1663 }
1663 $findtypemenu entryconf 1 -state $state
1664 $findtypemenu entryconf 1 -state $state
1664 $findtypemenu entryconf 2 -state $state
1665 $findtypemenu entryconf 2 -state $state
1665 }
1666 }
1666
1667
1667 proc stopfindproc {{done 0}} {
1668 proc stopfindproc {{done 0}} {
1668 global findprocpid findprocfile findids
1669 global findprocpid findprocfile findids
1669 global ctext findoldcursor phase maincursor textcursor
1670 global ctext findoldcursor phase maincursor textcursor
1670 global findinprogress
1671 global findinprogress
1671
1672
1672 catch {unset findids}
1673 catch {unset findids}
1673 if {[info exists findprocpid]} {
1674 if {[info exists findprocpid]} {
1674 if {!$done} {
1675 if {!$done} {
1675 catch {exec kill $findprocpid}
1676 catch {exec kill $findprocpid}
1676 }
1677 }
1677 catch {close $findprocfile}
1678 catch {close $findprocfile}
1678 unset findprocpid
1679 unset findprocpid
1679 }
1680 }
1680 if {[info exists findinprogress]} {
1681 if {[info exists findinprogress]} {
1681 unset findinprogress
1682 unset findinprogress
1682 if {$phase != "incrdraw"} {
1683 if {$phase != "incrdraw"} {
1683 . config -cursor $maincursor
1684 . config -cursor $maincursor
1684 settextcursor $textcursor
1685 settextcursor $textcursor
1685 }
1686 }
1686 }
1687 }
1687 }
1688 }
1688
1689
1689 proc findpatches {} {
1690 proc findpatches {} {
1690 global findstring selectedline numcommits
1691 global findstring selectedline numcommits
1691 global findprocpid findprocfile
1692 global findprocpid findprocfile
1692 global finddidsel ctext lineid findinprogress
1693 global finddidsel ctext lineid findinprogress
1693 global findinsertpos
1694 global findinsertpos
1694
1695
1695 if {$numcommits == 0} return
1696 if {$numcommits == 0} return
1696
1697
1697 # make a list of all the ids to search, starting at the one
1698 # make a list of all the ids to search, starting at the one
1698 # after the selected line (if any)
1699 # after the selected line (if any)
1699 if {[info exists selectedline]} {
1700 if {[info exists selectedline]} {
1700 set l $selectedline
1701 set l $selectedline
1701 } else {
1702 } else {
1702 set l -1
1703 set l -1
1703 }
1704 }
1704 set inputids {}
1705 set inputids {}
1705 for {set i 0} {$i < $numcommits} {incr i} {
1706 for {set i 0} {$i < $numcommits} {incr i} {
1706 if {[incr l] >= $numcommits} {
1707 if {[incr l] >= $numcommits} {
1707 set l 0
1708 set l 0
1708 }
1709 }
1709 append inputids $lineid($l) "\n"
1710 append inputids $lineid($l) "\n"
1710 }
1711 }
1711
1712
1712 if {[catch {
1713 if {[catch {
1713 set f [open [list | hg debug-diff-tree --stdin -s -r -S$findstring \
1714 set f [open [list | hg debug-diff-tree --stdin -s -r -S$findstring \
1714 << $inputids] r]
1715 << $inputids] r]
1715 } err]} {
1716 } err]} {
1716 error_popup "Error starting search process: $err"
1717 error_popup "Error starting search process: $err"
1717 return
1718 return
1718 }
1719 }
1719
1720
1720 set findinsertpos end
1721 set findinsertpos end
1721 set findprocfile $f
1722 set findprocfile $f
1722 set findprocpid [pid $f]
1723 set findprocpid [pid $f]
1723 fconfigure $f -blocking 0
1724 fconfigure $f -blocking 0
1724 fileevent $f readable readfindproc
1725 fileevent $f readable readfindproc
1725 set finddidsel 0
1726 set finddidsel 0
1726 . config -cursor watch
1727 . config -cursor watch
1727 settextcursor watch
1728 settextcursor watch
1728 set findinprogress 1
1729 set findinprogress 1
1729 }
1730 }
1730
1731
1731 proc readfindproc {} {
1732 proc readfindproc {} {
1732 global findprocfile finddidsel
1733 global findprocfile finddidsel
1733 global idline matchinglines findinsertpos
1734 global idline matchinglines findinsertpos
1734
1735
1735 set n [gets $findprocfile line]
1736 set n [gets $findprocfile line]
1736 if {$n < 0} {
1737 if {$n < 0} {
1737 if {[eof $findprocfile]} {
1738 if {[eof $findprocfile]} {
1738 stopfindproc 1
1739 stopfindproc 1
1739 if {!$finddidsel} {
1740 if {!$finddidsel} {
1740 bell
1741 bell
1741 }
1742 }
1742 }
1743 }
1743 return
1744 return
1744 }
1745 }
1745 if {![regexp {^[0-9a-f]{40}} $line id]} {
1746 if {![regexp {^[0-9a-f]{40}} $line id]} {
1746 error_popup "Can't parse git-diff-tree output: $line"
1747 error_popup "Can't parse git-diff-tree output: $line"
1747 stopfindproc
1748 stopfindproc
1748 return
1749 return
1749 }
1750 }
1750 if {![info exists idline($id)]} {
1751 if {![info exists idline($id)]} {
1751 puts stderr "spurious id: $id"
1752 puts stderr "spurious id: $id"
1752 return
1753 return
1753 }
1754 }
1754 set l $idline($id)
1755 set l $idline($id)
1755 insertmatch $l $id
1756 insertmatch $l $id
1756 }
1757 }
1757
1758
1758 proc insertmatch {l id} {
1759 proc insertmatch {l id} {
1759 global matchinglines findinsertpos finddidsel
1760 global matchinglines findinsertpos finddidsel
1760
1761
1761 if {$findinsertpos == "end"} {
1762 if {$findinsertpos == "end"} {
1762 if {$matchinglines != {} && $l < [lindex $matchinglines 0]} {
1763 if {$matchinglines != {} && $l < [lindex $matchinglines 0]} {
1763 set matchinglines [linsert $matchinglines 0 $l]
1764 set matchinglines [linsert $matchinglines 0 $l]
1764 set findinsertpos 1
1765 set findinsertpos 1
1765 } else {
1766 } else {
1766 lappend matchinglines $l
1767 lappend matchinglines $l
1767 }
1768 }
1768 } else {
1769 } else {
1769 set matchinglines [linsert $matchinglines $findinsertpos $l]
1770 set matchinglines [linsert $matchinglines $findinsertpos $l]
1770 incr findinsertpos
1771 incr findinsertpos
1771 }
1772 }
1772 markheadline $l $id
1773 markheadline $l $id
1773 if {!$finddidsel} {
1774 if {!$finddidsel} {
1774 findselectline $l
1775 findselectline $l
1775 set finddidsel 1
1776 set finddidsel 1
1776 }
1777 }
1777 }
1778 }
1778
1779
1779 proc findfiles {} {
1780 proc findfiles {} {
1780 global selectedline numcommits lineid ctext
1781 global selectedline numcommits lineid ctext
1781 global ffileline finddidsel parents nparents
1782 global ffileline finddidsel parents nparents
1782 global findinprogress findstartline findinsertpos
1783 global findinprogress findstartline findinsertpos
1783 global treediffs fdiffids fdiffsneeded fdiffpos
1784 global treediffs fdiffids fdiffsneeded fdiffpos
1784 global findmergefiles
1785 global findmergefiles
1785
1786
1786 if {$numcommits == 0} return
1787 if {$numcommits == 0} return
1787
1788
1788 if {[info exists selectedline]} {
1789 if {[info exists selectedline]} {
1789 set l [expr {$selectedline + 1}]
1790 set l [expr {$selectedline + 1}]
1790 } else {
1791 } else {
1791 set l 0
1792 set l 0
1792 }
1793 }
1793 set ffileline $l
1794 set ffileline $l
1794 set findstartline $l
1795 set findstartline $l
1795 set diffsneeded {}
1796 set diffsneeded {}
1796 set fdiffsneeded {}
1797 set fdiffsneeded {}
1797 while 1 {
1798 while 1 {
1798 set id $lineid($l)
1799 set id $lineid($l)
1799 if {$findmergefiles || $nparents($id) == 1} {
1800 if {$findmergefiles || $nparents($id) == 1} {
1800 foreach p $parents($id) {
1801 foreach p $parents($id) {
1801 if {![info exists treediffs([list $id $p])]} {
1802 if {![info exists treediffs([list $id $p])]} {
1802 append diffsneeded "$id $p\n"
1803 append diffsneeded "$id $p\n"
1803 lappend fdiffsneeded [list $id $p]
1804 lappend fdiffsneeded [list $id $p]
1804 }
1805 }
1805 }
1806 }
1806 }
1807 }
1807 if {[incr l] >= $numcommits} {
1808 if {[incr l] >= $numcommits} {
1808 set l 0
1809 set l 0
1809 }
1810 }
1810 if {$l == $findstartline} break
1811 if {$l == $findstartline} break
1811 }
1812 }
1812
1813
1813 # start off a git-diff-tree process if needed
1814 # start off a git-diff-tree process if needed
1814 if {$diffsneeded ne {}} {
1815 if {$diffsneeded ne {}} {
1815 if {[catch {
1816 if {[catch {
1816 set df [open [list | hg debug-diff-tree -r --stdin << $diffsneeded] r]
1817 set df [open [list | hg debug-diff-tree -r --stdin << $diffsneeded] r]
1817 } err ]} {
1818 } err ]} {
1818 error_popup "Error starting search process: $err"
1819 error_popup "Error starting search process: $err"
1819 return
1820 return
1820 }
1821 }
1821 catch {unset fdiffids}
1822 catch {unset fdiffids}
1822 set fdiffpos 0
1823 set fdiffpos 0
1823 fconfigure $df -blocking 0
1824 fconfigure $df -blocking 0
1824 fileevent $df readable [list readfilediffs $df]
1825 fileevent $df readable [list readfilediffs $df]
1825 }
1826 }
1826
1827
1827 set finddidsel 0
1828 set finddidsel 0
1828 set findinsertpos end
1829 set findinsertpos end
1829 set id $lineid($l)
1830 set id $lineid($l)
1830 set p [lindex $parents($id) 0]
1831 set p [lindex $parents($id) 0]
1831 . config -cursor watch
1832 . config -cursor watch
1832 settextcursor watch
1833 settextcursor watch
1833 set findinprogress 1
1834 set findinprogress 1
1834 findcont [list $id $p]
1835 findcont [list $id $p]
1835 update
1836 update
1836 }
1837 }
1837
1838
1838 proc readfilediffs {df} {
1839 proc readfilediffs {df} {
1839 global findids fdiffids fdiffs
1840 global findids fdiffids fdiffs
1840
1841
1841 set n [gets $df line]
1842 set n [gets $df line]
1842 if {$n < 0} {
1843 if {$n < 0} {
1843 if {[eof $df]} {
1844 if {[eof $df]} {
1844 donefilediff
1845 donefilediff
1845 if {[catch {close $df} err]} {
1846 if {[catch {close $df} err]} {
1846 stopfindproc
1847 stopfindproc
1847 bell
1848 bell
1848 error_popup "Error in hg debug-diff-tree: $err"
1849 error_popup "Error in hg debug-diff-tree: $err"
1849 } elseif {[info exists findids]} {
1850 } elseif {[info exists findids]} {
1850 set ids $findids
1851 set ids $findids
1851 stopfindproc
1852 stopfindproc
1852 bell
1853 bell
1853 error_popup "Couldn't find diffs for {$ids}"
1854 error_popup "Couldn't find diffs for {$ids}"
1854 }
1855 }
1855 }
1856 }
1856 return
1857 return
1857 }
1858 }
1858 if {[regexp {^([0-9a-f]{40}) \(from ([0-9a-f]{40})\)} $line match id p]} {
1859 if {[regexp {^([0-9a-f]{40}) \(from ([0-9a-f]{40})\)} $line match id p]} {
1859 # start of a new string of diffs
1860 # start of a new string of diffs
1860 donefilediff
1861 donefilediff
1861 set fdiffids [list $id $p]
1862 set fdiffids [list $id $p]
1862 set fdiffs {}
1863 set fdiffs {}
1863 } elseif {[string match ":*" $line]} {
1864 } elseif {[string match ":*" $line]} {
1864 lappend fdiffs [lindex $line 5]
1865 lappend fdiffs [lindex $line 5]
1865 }
1866 }
1866 }
1867 }
1867
1868
1868 proc donefilediff {} {
1869 proc donefilediff {} {
1869 global fdiffids fdiffs treediffs findids
1870 global fdiffids fdiffs treediffs findids
1870 global fdiffsneeded fdiffpos
1871 global fdiffsneeded fdiffpos
1871
1872
1872 if {[info exists fdiffids]} {
1873 if {[info exists fdiffids]} {
1873 while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids
1874 while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids
1874 && $fdiffpos < [llength $fdiffsneeded]} {
1875 && $fdiffpos < [llength $fdiffsneeded]} {
1875 # git-diff-tree doesn't output anything for a commit
1876 # git-diff-tree doesn't output anything for a commit
1876 # which doesn't change anything
1877 # which doesn't change anything
1877 set nullids [lindex $fdiffsneeded $fdiffpos]
1878 set nullids [lindex $fdiffsneeded $fdiffpos]
1878 set treediffs($nullids) {}
1879 set treediffs($nullids) {}
1879 if {[info exists findids] && $nullids eq $findids} {
1880 if {[info exists findids] && $nullids eq $findids} {
1880 unset findids
1881 unset findids
1881 findcont $nullids
1882 findcont $nullids
1882 }
1883 }
1883 incr fdiffpos
1884 incr fdiffpos
1884 }
1885 }
1885 incr fdiffpos
1886 incr fdiffpos
1886
1887
1887 if {![info exists treediffs($fdiffids)]} {
1888 if {![info exists treediffs($fdiffids)]} {
1888 set treediffs($fdiffids) $fdiffs
1889 set treediffs($fdiffids) $fdiffs
1889 }
1890 }
1890 if {[info exists findids] && $fdiffids eq $findids} {
1891 if {[info exists findids] && $fdiffids eq $findids} {
1891 unset findids
1892 unset findids
1892 findcont $fdiffids
1893 findcont $fdiffids
1893 }
1894 }
1894 }
1895 }
1895 }
1896 }
1896
1897
1897 proc findcont {ids} {
1898 proc findcont {ids} {
1898 global findids treediffs parents nparents
1899 global findids treediffs parents nparents
1899 global ffileline findstartline finddidsel
1900 global ffileline findstartline finddidsel
1900 global lineid numcommits matchinglines findinprogress
1901 global lineid numcommits matchinglines findinprogress
1901 global findmergefiles
1902 global findmergefiles
1902
1903
1903 set id [lindex $ids 0]
1904 set id [lindex $ids 0]
1904 set p [lindex $ids 1]
1905 set p [lindex $ids 1]
1905 set pi [lsearch -exact $parents($id) $p]
1906 set pi [lsearch -exact $parents($id) $p]
1906 set l $ffileline
1907 set l $ffileline
1907 while 1 {
1908 while 1 {
1908 if {$findmergefiles || $nparents($id) == 1} {
1909 if {$findmergefiles || $nparents($id) == 1} {
1909 if {![info exists treediffs($ids)]} {
1910 if {![info exists treediffs($ids)]} {
1910 set findids $ids
1911 set findids $ids
1911 set ffileline $l
1912 set ffileline $l
1912 return
1913 return
1913 }
1914 }
1914 set doesmatch 0
1915 set doesmatch 0
1915 foreach f $treediffs($ids) {
1916 foreach f $treediffs($ids) {
1916 set x [findmatches $f]
1917 set x [findmatches $f]
1917 if {$x != {}} {
1918 if {$x != {}} {
1918 set doesmatch 1
1919 set doesmatch 1
1919 break
1920 break
1920 }
1921 }
1921 }
1922 }
1922 if {$doesmatch} {
1923 if {$doesmatch} {
1923 insertmatch $l $id
1924 insertmatch $l $id
1924 set pi $nparents($id)
1925 set pi $nparents($id)
1925 }
1926 }
1926 } else {
1927 } else {
1927 set pi $nparents($id)
1928 set pi $nparents($id)
1928 }
1929 }
1929 if {[incr pi] >= $nparents($id)} {
1930 if {[incr pi] >= $nparents($id)} {
1930 set pi 0
1931 set pi 0
1931 if {[incr l] >= $numcommits} {
1932 if {[incr l] >= $numcommits} {
1932 set l 0
1933 set l 0
1933 }
1934 }
1934 if {$l == $findstartline} break
1935 if {$l == $findstartline} break
1935 set id $lineid($l)
1936 set id $lineid($l)
1936 }
1937 }
1937 set p [lindex $parents($id) $pi]
1938 set p [lindex $parents($id) $pi]
1938 set ids [list $id $p]
1939 set ids [list $id $p]
1939 }
1940 }
1940 stopfindproc
1941 stopfindproc
1941 if {!$finddidsel} {
1942 if {!$finddidsel} {
1942 bell
1943 bell
1943 }
1944 }
1944 }
1945 }
1945
1946
1946 # mark a commit as matching by putting a yellow background
1947 # mark a commit as matching by putting a yellow background
1947 # behind the headline
1948 # behind the headline
1948 proc markheadline {l id} {
1949 proc markheadline {l id} {
1949 global canv mainfont linehtag commitinfo
1950 global canv mainfont linehtag commitinfo
1950
1951
1951 set bbox [$canv bbox $linehtag($l)]
1952 set bbox [$canv bbox $linehtag($l)]
1952 set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
1953 set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
1953 $canv lower $t
1954 $canv lower $t
1954 }
1955 }
1955
1956
1956 # mark the bits of a headline, author or date that match a find string
1957 # mark the bits of a headline, author or date that match a find string
1957 proc markmatches {canv l str tag matches font} {
1958 proc markmatches {canv l str tag matches font} {
1958 set bbox [$canv bbox $tag]
1959 set bbox [$canv bbox $tag]
1959 set x0 [lindex $bbox 0]
1960 set x0 [lindex $bbox 0]
1960 set y0 [lindex $bbox 1]
1961 set y0 [lindex $bbox 1]
1961 set y1 [lindex $bbox 3]
1962 set y1 [lindex $bbox 3]
1962 foreach match $matches {
1963 foreach match $matches {
1963 set start [lindex $match 0]
1964 set start [lindex $match 0]
1964 set end [lindex $match 1]
1965 set end [lindex $match 1]
1965 if {$start > $end} continue
1966 if {$start > $end} continue
1966 set xoff [font measure $font [string range $str 0 [expr $start-1]]]
1967 set xoff [font measure $font [string range $str 0 [expr $start-1]]]
1967 set xlen [font measure $font [string range $str 0 [expr $end]]]
1968 set xlen [font measure $font [string range $str 0 [expr $end]]]
1968 set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
1969 set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
1969 -outline {} -tags matches -fill yellow]
1970 -outline {} -tags matches -fill yellow]
1970 $canv lower $t
1971 $canv lower $t
1971 }
1972 }
1972 }
1973 }
1973
1974
1974 proc unmarkmatches {} {
1975 proc unmarkmatches {} {
1975 global matchinglines findids
1976 global matchinglines findids
1976 allcanvs delete matches
1977 allcanvs delete matches
1977 catch {unset matchinglines}
1978 catch {unset matchinglines}
1978 catch {unset findids}
1979 catch {unset findids}
1979 }
1980 }
1980
1981
1981 proc selcanvline {w x y} {
1982 proc selcanvline {w x y} {
1982 global canv canvy0 ctext linespc
1983 global canv canvy0 ctext linespc
1983 global lineid linehtag linentag linedtag rowtextx
1984 global lineid linehtag linentag linedtag rowtextx
1984 set ymax [lindex [$canv cget -scrollregion] 3]
1985 set ymax [lindex [$canv cget -scrollregion] 3]
1985 if {$ymax == {}} return
1986 if {$ymax == {}} return
1986 set yfrac [lindex [$canv yview] 0]
1987 set yfrac [lindex [$canv yview] 0]
1987 set y [expr {$y + $yfrac * $ymax}]
1988 set y [expr {$y + $yfrac * $ymax}]
1988 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
1989 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
1989 if {$l < 0} {
1990 if {$l < 0} {
1990 set l 0
1991 set l 0
1991 }
1992 }
1992 if {$w eq $canv} {
1993 if {$w eq $canv} {
1993 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
1994 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
1994 }
1995 }
1995 unmarkmatches
1996 unmarkmatches
1996 selectline $l 1
1997 selectline $l 1
1997 }
1998 }
1998
1999
1999 proc commit_descriptor {p} {
2000 proc commit_descriptor {p} {
2000 global commitinfo
2001 global commitinfo
2001 set l "..."
2002 set l "..."
2002 if {[info exists commitinfo($p)]} {
2003 if {[info exists commitinfo($p)]} {
2003 set l [lindex $commitinfo($p) 0]
2004 set l [lindex $commitinfo($p) 0]
2004 }
2005 }
2005 return "$p ($l)"
2006 return "$p ($l)"
2006 }
2007 }
2007
2008
2008 # append some text to the ctext widget, and make any SHA1 ID
2009 # append some text to the ctext widget, and make any SHA1 ID
2009 # that we know about be a clickable link.
2010 # that we know about be a clickable link.
2010 proc appendwithlinks {text} {
2011 proc appendwithlinks {text} {
2011 global ctext idline linknum
2012 global ctext idline linknum
2012
2013
2013 set start [$ctext index "end - 1c"]
2014 set start [$ctext index "end - 1c"]
2014 $ctext insert end $text
2015 $ctext insert end $text
2015 $ctext insert end "\n"
2016 $ctext insert end "\n"
2016 set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
2017 set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
2017 foreach l $links {
2018 foreach l $links {
2018 set s [lindex $l 0]
2019 set s [lindex $l 0]
2019 set e [lindex $l 1]
2020 set e [lindex $l 1]
2020 set linkid [string range $text $s $e]
2021 set linkid [string range $text $s $e]
2021 if {![info exists idline($linkid)]} continue
2022 if {![info exists idline($linkid)]} continue
2022 incr e
2023 incr e
2023 $ctext tag add link "$start + $s c" "$start + $e c"
2024 $ctext tag add link "$start + $s c" "$start + $e c"
2024 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
2025 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
2025 $ctext tag bind link$linknum <1> [list selectline $idline($linkid) 1]
2026 $ctext tag bind link$linknum <1> [list selectline $idline($linkid) 1]
2026 incr linknum
2027 incr linknum
2027 }
2028 }
2028 $ctext tag conf link -foreground blue -underline 1
2029 $ctext tag conf link -foreground blue -underline 1
2029 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
2030 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
2030 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
2031 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
2031 }
2032 }
2032
2033
2033 proc selectline {l isnew} {
2034 proc selectline {l isnew} {
2034 global canv canv2 canv3 ctext commitinfo selectedline
2035 global canv canv2 canv3 ctext commitinfo selectedline
2035 global lineid linehtag linentag linedtag
2036 global lineid linehtag linentag linedtag
2036 global canvy0 linespc parents nparents children
2037 global canvy0 linespc parents nparents children
2037 global cflist currentid sha1entry
2038 global cflist currentid sha1entry
2038 global commentend idtags idline linknum
2039 global commentend idtags idline linknum
2039
2040
2040 $canv delete hover
2041 $canv delete hover
2041 normalline
2042 normalline
2042 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
2043 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
2043 $canv delete secsel
2044 $canv delete secsel
2044 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
2045 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
2045 -tags secsel -fill [$canv cget -selectbackground]]
2046 -tags secsel -fill [$canv cget -selectbackground]]
2046 $canv lower $t
2047 $canv lower $t
2047 $canv2 delete secsel
2048 $canv2 delete secsel
2048 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
2049 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
2049 -tags secsel -fill [$canv2 cget -selectbackground]]
2050 -tags secsel -fill [$canv2 cget -selectbackground]]
2050 $canv2 lower $t
2051 $canv2 lower $t
2051 $canv3 delete secsel
2052 $canv3 delete secsel
2052 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
2053 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
2053 -tags secsel -fill [$canv3 cget -selectbackground]]
2054 -tags secsel -fill [$canv3 cget -selectbackground]]
2054 $canv3 lower $t
2055 $canv3 lower $t
2055 set y [expr {$canvy0 + $l * $linespc}]
2056 set y [expr {$canvy0 + $l * $linespc}]
2056 set ymax [lindex [$canv cget -scrollregion] 3]
2057 set ymax [lindex [$canv cget -scrollregion] 3]
2057 set ytop [expr {$y - $linespc - 1}]
2058 set ytop [expr {$y - $linespc - 1}]
2058 set ybot [expr {$y + $linespc + 1}]
2059 set ybot [expr {$y + $linespc + 1}]
2059 set wnow [$canv yview]
2060 set wnow [$canv yview]
2060 set wtop [expr [lindex $wnow 0] * $ymax]
2061 set wtop [expr [lindex $wnow 0] * $ymax]
2061 set wbot [expr [lindex $wnow 1] * $ymax]
2062 set wbot [expr [lindex $wnow 1] * $ymax]
2062 set wh [expr {$wbot - $wtop}]
2063 set wh [expr {$wbot - $wtop}]
2063 set newtop $wtop
2064 set newtop $wtop
2064 if {$ytop < $wtop} {
2065 if {$ytop < $wtop} {
2065 if {$ybot < $wtop} {
2066 if {$ybot < $wtop} {
2066 set newtop [expr {$y - $wh / 2.0}]
2067 set newtop [expr {$y - $wh / 2.0}]
2067 } else {
2068 } else {
2068 set newtop $ytop
2069 set newtop $ytop
2069 if {$newtop > $wtop - $linespc} {
2070 if {$newtop > $wtop - $linespc} {
2070 set newtop [expr {$wtop - $linespc}]
2071 set newtop [expr {$wtop - $linespc}]
2071 }
2072 }
2072 }
2073 }
2073 } elseif {$ybot > $wbot} {
2074 } elseif {$ybot > $wbot} {
2074 if {$ytop > $wbot} {
2075 if {$ytop > $wbot} {
2075 set newtop [expr {$y - $wh / 2.0}]
2076 set newtop [expr {$y - $wh / 2.0}]
2076 } else {
2077 } else {
2077 set newtop [expr {$ybot - $wh}]
2078 set newtop [expr {$ybot - $wh}]
2078 if {$newtop < $wtop + $linespc} {
2079 if {$newtop < $wtop + $linespc} {
2079 set newtop [expr {$wtop + $linespc}]
2080 set newtop [expr {$wtop + $linespc}]
2080 }
2081 }
2081 }
2082 }
2082 }
2083 }
2083 if {$newtop != $wtop} {
2084 if {$newtop != $wtop} {
2084 if {$newtop < 0} {
2085 if {$newtop < 0} {
2085 set newtop 0
2086 set newtop 0
2086 }
2087 }
2087 allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
2088 allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
2088 }
2089 }
2089
2090
2090 if {$isnew} {
2091 if {$isnew} {
2091 addtohistory [list selectline $l 0]
2092 addtohistory [list selectline $l 0]
2092 }
2093 }
2093
2094
2094 set selectedline $l
2095 set selectedline $l
2095
2096
2096 set id $lineid($l)
2097 set id $lineid($l)
2097 set currentid $id
2098 set currentid $id
2098 $sha1entry delete 0 end
2099 $sha1entry delete 0 end
2099 $sha1entry insert 0 $id
2100 $sha1entry insert 0 $id
2100 $sha1entry selection from 0
2101 $sha1entry selection from 0
2101 $sha1entry selection to end
2102 $sha1entry selection to end
2102
2103
2103 $ctext conf -state normal
2104 $ctext conf -state normal
2104 $ctext delete 0.0 end
2105 $ctext delete 0.0 end
2105 set linknum 0
2106 set linknum 0
2106 $ctext mark set fmark.0 0.0
2107 $ctext mark set fmark.0 0.0
2107 $ctext mark gravity fmark.0 left
2108 $ctext mark gravity fmark.0 left
2108 set info $commitinfo($id)
2109 set info $commitinfo($id)
2109 $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
2110 $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
2110 $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
2111 $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
2111 if {[info exists idtags($id)]} {
2112 if {[info exists idtags($id)]} {
2112 $ctext insert end "Tags:"
2113 $ctext insert end "Tags:"
2113 foreach tag $idtags($id) {
2114 foreach tag $idtags($id) {
2114 $ctext insert end " $tag"
2115 $ctext insert end " $tag"
2115 }
2116 }
2116 $ctext insert end "\n"
2117 $ctext insert end "\n"
2117 }
2118 }
2118
2119
2119 set comment {}
2120 set comment {}
2120 if {[info exists parents($id)]} {
2121 if {[info exists parents($id)]} {
2121 foreach p $parents($id) {
2122 foreach p $parents($id) {
2122 append comment "Parent: [commit_descriptor $p]\n"
2123 append comment "Parent: [commit_descriptor $p]\n"
2123 }
2124 }
2124 }
2125 }
2125 if {[info exists children($id)]} {
2126 if {[info exists children($id)]} {
2126 foreach c $children($id) {
2127 foreach c $children($id) {
2127 append comment "Child: [commit_descriptor $c]\n"
2128 append comment "Child: [commit_descriptor $c]\n"
2128 }
2129 }
2129 }
2130 }
2130 append comment "\n"
2131 append comment "\n"
2131 append comment [lindex $info 5]
2132 append comment [lindex $info 5]
2132
2133
2133 # make anything that looks like a SHA1 ID be a clickable link
2134 # make anything that looks like a SHA1 ID be a clickable link
2134 appendwithlinks $comment
2135 appendwithlinks $comment
2135
2136
2136 $ctext tag delete Comments
2137 $ctext tag delete Comments
2137 $ctext tag remove found 1.0 end
2138 $ctext tag remove found 1.0 end
2138 $ctext conf -state disabled
2139 $ctext conf -state disabled
2139 set commentend [$ctext index "end - 1c"]
2140 set commentend [$ctext index "end - 1c"]
2140
2141
2141 $cflist delete 0 end
2142 $cflist delete 0 end
2142 $cflist insert end "Comments"
2143 $cflist insert end "Comments"
2143 if {$nparents($id) == 1} {
2144 if {$nparents($id) == 1} {
2144 startdiff [concat $id $parents($id)]
2145 startdiff [concat $id $parents($id)]
2145 } elseif {$nparents($id) > 1} {
2146 } elseif {$nparents($id) > 1} {
2146 mergediff $id
2147 mergediff $id
2147 }
2148 }
2148 }
2149 }
2149
2150
2150 proc selnextline {dir} {
2151 proc selnextline {dir} {
2151 global selectedline
2152 global selectedline
2152 if {![info exists selectedline]} return
2153 if {![info exists selectedline]} return
2153 set l [expr $selectedline + $dir]
2154 set l [expr $selectedline + $dir]
2154 unmarkmatches
2155 unmarkmatches
2155 selectline $l 1
2156 selectline $l 1
2156 }
2157 }
2157
2158
2158 proc unselectline {} {
2159 proc unselectline {} {
2159 global selectedline
2160 global selectedline
2160
2161
2161 catch {unset selectedline}
2162 catch {unset selectedline}
2162 allcanvs delete secsel
2163 allcanvs delete secsel
2163 }
2164 }
2164
2165
2165 proc addtohistory {cmd} {
2166 proc addtohistory {cmd} {
2166 global history historyindex
2167 global history historyindex
2167
2168
2168 if {$historyindex > 0
2169 if {$historyindex > 0
2169 && [lindex $history [expr {$historyindex - 1}]] == $cmd} {
2170 && [lindex $history [expr {$historyindex - 1}]] == $cmd} {
2170 return
2171 return
2171 }
2172 }
2172
2173
2173 if {$historyindex < [llength $history]} {
2174 if {$historyindex < [llength $history]} {
2174 set history [lreplace $history $historyindex end $cmd]
2175 set history [lreplace $history $historyindex end $cmd]
2175 } else {
2176 } else {
2176 lappend history $cmd
2177 lappend history $cmd
2177 }
2178 }
2178 incr historyindex
2179 incr historyindex
2179 if {$historyindex > 1} {
2180 if {$historyindex > 1} {
2180 .ctop.top.bar.leftbut conf -state normal
2181 .ctop.top.bar.leftbut conf -state normal
2181 } else {
2182 } else {
2182 .ctop.top.bar.leftbut conf -state disabled
2183 .ctop.top.bar.leftbut conf -state disabled
2183 }
2184 }
2184 .ctop.top.bar.rightbut conf -state disabled
2185 .ctop.top.bar.rightbut conf -state disabled
2185 }
2186 }
2186
2187
2187 proc goback {} {
2188 proc goback {} {
2188 global history historyindex
2189 global history historyindex
2189
2190
2190 if {$historyindex > 1} {
2191 if {$historyindex > 1} {
2191 incr historyindex -1
2192 incr historyindex -1
2192 set cmd [lindex $history [expr {$historyindex - 1}]]
2193 set cmd [lindex $history [expr {$historyindex - 1}]]
2193 eval $cmd
2194 eval $cmd
2194 .ctop.top.bar.rightbut conf -state normal
2195 .ctop.top.bar.rightbut conf -state normal
2195 }
2196 }
2196 if {$historyindex <= 1} {
2197 if {$historyindex <= 1} {
2197 .ctop.top.bar.leftbut conf -state disabled
2198 .ctop.top.bar.leftbut conf -state disabled
2198 }
2199 }
2199 }
2200 }
2200
2201
2201 proc goforw {} {
2202 proc goforw {} {
2202 global history historyindex
2203 global history historyindex
2203
2204
2204 if {$historyindex < [llength $history]} {
2205 if {$historyindex < [llength $history]} {
2205 set cmd [lindex $history $historyindex]
2206 set cmd [lindex $history $historyindex]
2206 incr historyindex
2207 incr historyindex
2207 eval $cmd
2208 eval $cmd
2208 .ctop.top.bar.leftbut conf -state normal
2209 .ctop.top.bar.leftbut conf -state normal
2209 }
2210 }
2210 if {$historyindex >= [llength $history]} {
2211 if {$historyindex >= [llength $history]} {
2211 .ctop.top.bar.rightbut conf -state disabled
2212 .ctop.top.bar.rightbut conf -state disabled
2212 }
2213 }
2213 }
2214 }
2214
2215
2215 proc mergediff {id} {
2216 proc mergediff {id} {
2216 global parents diffmergeid diffmergegca mergefilelist diffpindex
2217 global parents diffmergeid diffmergegca mergefilelist diffpindex
2217
2218
2218 set diffmergeid $id
2219 set diffmergeid $id
2219 set diffpindex -1
2220 set diffpindex -1
2220 set diffmergegca [findgca $parents($id)]
2221 set diffmergegca [findgca $parents($id)]
2221 if {[info exists mergefilelist($id)]} {
2222 if {[info exists mergefilelist($id)]} {
2222 if {$mergefilelist($id) ne {}} {
2223 if {$mergefilelist($id) ne {}} {
2223 showmergediff
2224 showmergediff
2224 }
2225 }
2225 } else {
2226 } else {
2226 contmergediff {}
2227 contmergediff {}
2227 }
2228 }
2228 }
2229 }
2229
2230
2230 proc findgca {ids} {
2231 proc findgca {ids} {
2231 set gca {}
2232 set gca {}
2232 foreach id $ids {
2233 foreach id $ids {
2233 if {$gca eq {}} {
2234 if {$gca eq {}} {
2234 set gca $id
2235 set gca $id
2235 } else {
2236 } else {
2236 if {[catch {
2237 if {[catch {
2237 set gca [exec hg debug-merge-base $gca $id]
2238 set gca [exec hg debug-merge-base $gca $id]
2238 } err]} {
2239 } err]} {
2239 return {}
2240 return {}
2240 }
2241 }
2241 }
2242 }
2242 }
2243 }
2243 return $gca
2244 return $gca
2244 }
2245 }
2245
2246
2246 proc contmergediff {ids} {
2247 proc contmergediff {ids} {
2247 global diffmergeid diffpindex parents nparents diffmergegca
2248 global diffmergeid diffpindex parents nparents diffmergegca
2248 global treediffs mergefilelist diffids treepending
2249 global treediffs mergefilelist diffids treepending
2249
2250
2250 # diff the child against each of the parents, and diff
2251 # diff the child against each of the parents, and diff
2251 # each of the parents against the GCA.
2252 # each of the parents against the GCA.
2252 while 1 {
2253 while 1 {
2253 if {[lindex $ids 0] == $diffmergeid && $diffmergegca ne {}} {
2254 if {[lindex $ids 0] == $diffmergeid && $diffmergegca ne {}} {
2254 set ids [list [lindex $ids 1] $diffmergegca]
2255 set ids [list [lindex $ids 1] $diffmergegca]
2255 } else {
2256 } else {
2256 if {[incr diffpindex] >= $nparents($diffmergeid)} break
2257 if {[incr diffpindex] >= $nparents($diffmergeid)} break
2257 set p [lindex $parents($diffmergeid) $diffpindex]
2258 set p [lindex $parents($diffmergeid) $diffpindex]
2258 set ids [list $diffmergeid $p]
2259 set ids [list $diffmergeid $p]
2259 }
2260 }
2260 if {![info exists treediffs($ids)]} {
2261 if {![info exists treediffs($ids)]} {
2261 set diffids $ids
2262 set diffids $ids
2262 if {![info exists treepending]} {
2263 if {![info exists treepending]} {
2263 gettreediffs $ids
2264 gettreediffs $ids
2264 }
2265 }
2265 return
2266 return
2266 }
2267 }
2267 }
2268 }
2268
2269
2269 # If a file in some parent is different from the child and also
2270 # If a file in some parent is different from the child and also
2270 # different from the GCA, then it's interesting.
2271 # different from the GCA, then it's interesting.
2271 # If we don't have a GCA, then a file is interesting if it is
2272 # If we don't have a GCA, then a file is interesting if it is
2272 # different from the child in all the parents.
2273 # different from the child in all the parents.
2273 if {$diffmergegca ne {}} {
2274 if {$diffmergegca ne {}} {
2274 set files {}
2275 set files {}
2275 foreach p $parents($diffmergeid) {
2276 foreach p $parents($diffmergeid) {
2276 set gcadiffs $treediffs([list $p $diffmergegca])
2277 set gcadiffs $treediffs([list $p $diffmergegca])
2277 foreach f $treediffs([list $diffmergeid $p]) {
2278 foreach f $treediffs([list $diffmergeid $p]) {
2278 if {[lsearch -exact $files $f] < 0
2279 if {[lsearch -exact $files $f] < 0
2279 && [lsearch -exact $gcadiffs $f] >= 0} {
2280 && [lsearch -exact $gcadiffs $f] >= 0} {
2280 lappend files $f
2281 lappend files $f
2281 }
2282 }
2282 }
2283 }
2283 }
2284 }
2284 set files [lsort $files]
2285 set files [lsort $files]
2285 } else {
2286 } else {
2286 set p [lindex $parents($diffmergeid) 0]
2287 set p [lindex $parents($diffmergeid) 0]
2287 set files $treediffs([list $diffmergeid $p])
2288 set files $treediffs([list $diffmergeid $p])
2288 for {set i 1} {$i < $nparents($diffmergeid) && $files ne {}} {incr i} {
2289 for {set i 1} {$i < $nparents($diffmergeid) && $files ne {}} {incr i} {
2289 set p [lindex $parents($diffmergeid) $i]
2290 set p [lindex $parents($diffmergeid) $i]
2290 set df $treediffs([list $diffmergeid $p])
2291 set df $treediffs([list $diffmergeid $p])
2291 set nf {}
2292 set nf {}
2292 foreach f $files {
2293 foreach f $files {
2293 if {[lsearch -exact $df $f] >= 0} {
2294 if {[lsearch -exact $df $f] >= 0} {
2294 lappend nf $f
2295 lappend nf $f
2295 }
2296 }
2296 }
2297 }
2297 set files $nf
2298 set files $nf
2298 }
2299 }
2299 }
2300 }
2300
2301
2301 set mergefilelist($diffmergeid) $files
2302 set mergefilelist($diffmergeid) $files
2302 if {$files ne {}} {
2303 if {$files ne {}} {
2303 showmergediff
2304 showmergediff
2304 }
2305 }
2305 }
2306 }
2306
2307
2307 proc showmergediff {} {
2308 proc showmergediff {} {
2308 global cflist diffmergeid mergefilelist parents
2309 global cflist diffmergeid mergefilelist parents
2309 global diffopts diffinhunk currentfile currenthunk filelines
2310 global diffopts diffinhunk currentfile currenthunk filelines
2310 global diffblocked groupfilelast mergefds groupfilenum grouphunks
2311 global diffblocked groupfilelast mergefds groupfilenum grouphunks
2311
2312
2312 set files $mergefilelist($diffmergeid)
2313 set files $mergefilelist($diffmergeid)
2313 foreach f $files {
2314 foreach f $files {
2314 $cflist insert end $f
2315 $cflist insert end $f
2315 }
2316 }
2316 set env(GIT_DIFF_OPTS) $diffopts
2317 set env(GIT_DIFF_OPTS) $diffopts
2317 set flist {}
2318 set flist {}
2318 catch {unset currentfile}
2319 catch {unset currentfile}
2319 catch {unset currenthunk}
2320 catch {unset currenthunk}
2320 catch {unset filelines}
2321 catch {unset filelines}
2321 catch {unset groupfilenum}
2322 catch {unset groupfilenum}
2322 catch {unset grouphunks}
2323 catch {unset grouphunks}
2323 set groupfilelast -1
2324 set groupfilelast -1
2324 foreach p $parents($diffmergeid) {
2325 foreach p $parents($diffmergeid) {
2325 set cmd [list | hg debug-diff-tree -p $p $diffmergeid]
2326 set cmd [list | hg debug-diff-tree -p $p $diffmergeid]
2326 set cmd [concat $cmd $mergefilelist($diffmergeid)]
2327 set cmd [concat $cmd $mergefilelist($diffmergeid)]
2327 if {[catch {set f [open $cmd r]} err]} {
2328 if {[catch {set f [open $cmd r]} err]} {
2328 error_popup "Error getting diffs: $err"
2329 error_popup "Error getting diffs: $err"
2329 foreach f $flist {
2330 foreach f $flist {
2330 catch {close $f}
2331 catch {close $f}
2331 }
2332 }
2332 return
2333 return
2333 }
2334 }
2334 lappend flist $f
2335 lappend flist $f
2335 set ids [list $diffmergeid $p]
2336 set ids [list $diffmergeid $p]
2336 set mergefds($ids) $f
2337 set mergefds($ids) $f
2337 set diffinhunk($ids) 0
2338 set diffinhunk($ids) 0
2338 set diffblocked($ids) 0
2339 set diffblocked($ids) 0
2339 fconfigure $f -blocking 0
2340 fconfigure $f -blocking 0
2340 fileevent $f readable [list getmergediffline $f $ids $diffmergeid]
2341 fileevent $f readable [list getmergediffline $f $ids $diffmergeid]
2341 }
2342 }
2342 }
2343 }
2343
2344
2344 proc getmergediffline {f ids id} {
2345 proc getmergediffline {f ids id} {
2345 global diffmergeid diffinhunk diffoldlines diffnewlines
2346 global diffmergeid diffinhunk diffoldlines diffnewlines
2346 global currentfile currenthunk
2347 global currentfile currenthunk
2347 global diffoldstart diffnewstart diffoldlno diffnewlno
2348 global diffoldstart diffnewstart diffoldlno diffnewlno
2348 global diffblocked mergefilelist
2349 global diffblocked mergefilelist
2349 global noldlines nnewlines difflcounts filelines
2350 global noldlines nnewlines difflcounts filelines
2350
2351
2351 set n [gets $f line]
2352 set n [gets $f line]
2352 if {$n < 0} {
2353 if {$n < 0} {
2353 if {![eof $f]} return
2354 if {![eof $f]} return
2354 }
2355 }
2355
2356
2356 if {!([info exists diffmergeid] && $diffmergeid == $id)} {
2357 if {!([info exists diffmergeid] && $diffmergeid == $id)} {
2357 if {$n < 0} {
2358 if {$n < 0} {
2358 close $f
2359 close $f
2359 }
2360 }
2360 return
2361 return
2361 }
2362 }
2362
2363
2363 if {$diffinhunk($ids) != 0} {
2364 if {$diffinhunk($ids) != 0} {
2364 set fi $currentfile($ids)
2365 set fi $currentfile($ids)
2365 if {$n > 0 && [regexp {^[-+ \\]} $line match]} {
2366 if {$n > 0 && [regexp {^[-+ \\]} $line match]} {
2366 # continuing an existing hunk
2367 # continuing an existing hunk
2367 set line [string range $line 1 end]
2368 set line [string range $line 1 end]
2368 set p [lindex $ids 1]
2369 set p [lindex $ids 1]
2369 if {$match eq "-" || $match eq " "} {
2370 if {$match eq "-" || $match eq " "} {
2370 set filelines($p,$fi,$diffoldlno($ids)) $line
2371 set filelines($p,$fi,$diffoldlno($ids)) $line
2371 incr diffoldlno($ids)
2372 incr diffoldlno($ids)
2372 }
2373 }
2373 if {$match eq "+" || $match eq " "} {
2374 if {$match eq "+" || $match eq " "} {
2374 set filelines($id,$fi,$diffnewlno($ids)) $line
2375 set filelines($id,$fi,$diffnewlno($ids)) $line
2375 incr diffnewlno($ids)
2376 incr diffnewlno($ids)
2376 }
2377 }
2377 if {$match eq " "} {
2378 if {$match eq " "} {
2378 if {$diffinhunk($ids) == 2} {
2379 if {$diffinhunk($ids) == 2} {
2379 lappend difflcounts($ids) \
2380 lappend difflcounts($ids) \
2380 [list $noldlines($ids) $nnewlines($ids)]
2381 [list $noldlines($ids) $nnewlines($ids)]
2381 set noldlines($ids) 0
2382 set noldlines($ids) 0
2382 set diffinhunk($ids) 1
2383 set diffinhunk($ids) 1
2383 }
2384 }
2384 incr noldlines($ids)
2385 incr noldlines($ids)
2385 } elseif {$match eq "-" || $match eq "+"} {
2386 } elseif {$match eq "-" || $match eq "+"} {
2386 if {$diffinhunk($ids) == 1} {
2387 if {$diffinhunk($ids) == 1} {
2387 lappend difflcounts($ids) [list $noldlines($ids)]
2388 lappend difflcounts($ids) [list $noldlines($ids)]
2388 set noldlines($ids) 0
2389 set noldlines($ids) 0
2389 set nnewlines($ids) 0
2390 set nnewlines($ids) 0
2390 set diffinhunk($ids) 2
2391 set diffinhunk($ids) 2
2391 }
2392 }
2392 if {$match eq "-"} {
2393 if {$match eq "-"} {
2393 incr noldlines($ids)
2394 incr noldlines($ids)
2394 } else {
2395 } else {
2395 incr nnewlines($ids)
2396 incr nnewlines($ids)
2396 }
2397 }
2397 }
2398 }
2398 # and if it's \ No newline at end of line, then what?
2399 # and if it's \ No newline at end of line, then what?
2399 return
2400 return
2400 }
2401 }
2401 # end of a hunk
2402 # end of a hunk
2402 if {$diffinhunk($ids) == 1 && $noldlines($ids) != 0} {
2403 if {$diffinhunk($ids) == 1 && $noldlines($ids) != 0} {
2403 lappend difflcounts($ids) [list $noldlines($ids)]
2404 lappend difflcounts($ids) [list $noldlines($ids)]
2404 } elseif {$diffinhunk($ids) == 2
2405 } elseif {$diffinhunk($ids) == 2
2405 && ($noldlines($ids) != 0 || $nnewlines($ids) != 0)} {
2406 && ($noldlines($ids) != 0 || $nnewlines($ids) != 0)} {
2406 lappend difflcounts($ids) [list $noldlines($ids) $nnewlines($ids)]
2407 lappend difflcounts($ids) [list $noldlines($ids) $nnewlines($ids)]
2407 }
2408 }
2408 set currenthunk($ids) [list $currentfile($ids) \
2409 set currenthunk($ids) [list $currentfile($ids) \
2409 $diffoldstart($ids) $diffnewstart($ids) \
2410 $diffoldstart($ids) $diffnewstart($ids) \
2410 $diffoldlno($ids) $diffnewlno($ids) \
2411 $diffoldlno($ids) $diffnewlno($ids) \
2411 $difflcounts($ids)]
2412 $difflcounts($ids)]
2412 set diffinhunk($ids) 0
2413 set diffinhunk($ids) 0
2413 # -1 = need to block, 0 = unblocked, 1 = is blocked
2414 # -1 = need to block, 0 = unblocked, 1 = is blocked
2414 set diffblocked($ids) -1
2415 set diffblocked($ids) -1
2415 processhunks
2416 processhunks
2416 if {$diffblocked($ids) == -1} {
2417 if {$diffblocked($ids) == -1} {
2417 fileevent $f readable {}
2418 fileevent $f readable {}
2418 set diffblocked($ids) 1
2419 set diffblocked($ids) 1
2419 }
2420 }
2420 }
2421 }
2421
2422
2422 if {$n < 0} {
2423 if {$n < 0} {
2423 # eof
2424 # eof
2424 if {!$diffblocked($ids)} {
2425 if {!$diffblocked($ids)} {
2425 close $f
2426 close $f
2426 set currentfile($ids) [llength $mergefilelist($diffmergeid)]
2427 set currentfile($ids) [llength $mergefilelist($diffmergeid)]
2427 set currenthunk($ids) [list $currentfile($ids) 0 0 0 0 {}]
2428 set currenthunk($ids) [list $currentfile($ids) 0 0 0 0 {}]
2428 processhunks
2429 processhunks
2429 }
2430 }
2430 } elseif {[regexp {^diff --git a/(.*) b/} $line match fname]} {
2431 } elseif {[regexp {^diff --git a/(.*) b/} $line match fname]} {
2431 # start of a new file
2432 # start of a new file
2432 set currentfile($ids) \
2433 set currentfile($ids) \
2433 [lsearch -exact $mergefilelist($diffmergeid) $fname]
2434 [lsearch -exact $mergefilelist($diffmergeid) $fname]
2434 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2435 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2435 $line match f1l f1c f2l f2c rest]} {
2436 $line match f1l f1c f2l f2c rest]} {
2436 if {[info exists currentfile($ids)] && $currentfile($ids) >= 0} {
2437 if {[info exists currentfile($ids)] && $currentfile($ids) >= 0} {
2437 # start of a new hunk
2438 # start of a new hunk
2438 if {$f1l == 0 && $f1c == 0} {
2439 if {$f1l == 0 && $f1c == 0} {
2439 set f1l 1
2440 set f1l 1
2440 }
2441 }
2441 if {$f2l == 0 && $f2c == 0} {
2442 if {$f2l == 0 && $f2c == 0} {
2442 set f2l 1
2443 set f2l 1
2443 }
2444 }
2444 set diffinhunk($ids) 1
2445 set diffinhunk($ids) 1
2445 set diffoldstart($ids) $f1l
2446 set diffoldstart($ids) $f1l
2446 set diffnewstart($ids) $f2l
2447 set diffnewstart($ids) $f2l
2447 set diffoldlno($ids) $f1l
2448 set diffoldlno($ids) $f1l
2448 set diffnewlno($ids) $f2l
2449 set diffnewlno($ids) $f2l
2449 set difflcounts($ids) {}
2450 set difflcounts($ids) {}
2450 set noldlines($ids) 0
2451 set noldlines($ids) 0
2451 set nnewlines($ids) 0
2452 set nnewlines($ids) 0
2452 }
2453 }
2453 }
2454 }
2454 }
2455 }
2455
2456
2456 proc processhunks {} {
2457 proc processhunks {} {
2457 global diffmergeid parents nparents currenthunk
2458 global diffmergeid parents nparents currenthunk
2458 global mergefilelist diffblocked mergefds
2459 global mergefilelist diffblocked mergefds
2459 global grouphunks grouplinestart grouplineend groupfilenum
2460 global grouphunks grouplinestart grouplineend groupfilenum
2460
2461
2461 set nfiles [llength $mergefilelist($diffmergeid)]
2462 set nfiles [llength $mergefilelist($diffmergeid)]
2462 while 1 {
2463 while 1 {
2463 set fi $nfiles
2464 set fi $nfiles
2464 set lno 0
2465 set lno 0
2465 # look for the earliest hunk
2466 # look for the earliest hunk
2466 foreach p $parents($diffmergeid) {
2467 foreach p $parents($diffmergeid) {
2467 set ids [list $diffmergeid $p]
2468 set ids [list $diffmergeid $p]
2468 if {![info exists currenthunk($ids)]} return
2469 if {![info exists currenthunk($ids)]} return
2469 set i [lindex $currenthunk($ids) 0]
2470 set i [lindex $currenthunk($ids) 0]
2470 set l [lindex $currenthunk($ids) 2]
2471 set l [lindex $currenthunk($ids) 2]
2471 if {$i < $fi || ($i == $fi && $l < $lno)} {
2472 if {$i < $fi || ($i == $fi && $l < $lno)} {
2472 set fi $i
2473 set fi $i
2473 set lno $l
2474 set lno $l
2474 set pi $p
2475 set pi $p
2475 }
2476 }
2476 }
2477 }
2477
2478
2478 if {$fi < $nfiles} {
2479 if {$fi < $nfiles} {
2479 set ids [list $diffmergeid $pi]
2480 set ids [list $diffmergeid $pi]
2480 set hunk $currenthunk($ids)
2481 set hunk $currenthunk($ids)
2481 unset currenthunk($ids)
2482 unset currenthunk($ids)
2482 if {$diffblocked($ids) > 0} {
2483 if {$diffblocked($ids) > 0} {
2483 fileevent $mergefds($ids) readable \
2484 fileevent $mergefds($ids) readable \
2484 [list getmergediffline $mergefds($ids) $ids $diffmergeid]
2485 [list getmergediffline $mergefds($ids) $ids $diffmergeid]
2485 }
2486 }
2486 set diffblocked($ids) 0
2487 set diffblocked($ids) 0
2487
2488
2488 if {[info exists groupfilenum] && $groupfilenum == $fi
2489 if {[info exists groupfilenum] && $groupfilenum == $fi
2489 && $lno <= $grouplineend} {
2490 && $lno <= $grouplineend} {
2490 # add this hunk to the pending group
2491 # add this hunk to the pending group
2491 lappend grouphunks($pi) $hunk
2492 lappend grouphunks($pi) $hunk
2492 set endln [lindex $hunk 4]
2493 set endln [lindex $hunk 4]
2493 if {$endln > $grouplineend} {
2494 if {$endln > $grouplineend} {
2494 set grouplineend $endln
2495 set grouplineend $endln
2495 }
2496 }
2496 continue
2497 continue
2497 }
2498 }
2498 }
2499 }
2499
2500
2500 # succeeding stuff doesn't belong in this group, so
2501 # succeeding stuff doesn't belong in this group, so
2501 # process the group now
2502 # process the group now
2502 if {[info exists groupfilenum]} {
2503 if {[info exists groupfilenum]} {
2503 processgroup
2504 processgroup
2504 unset groupfilenum
2505 unset groupfilenum
2505 unset grouphunks
2506 unset grouphunks
2506 }
2507 }
2507
2508
2508 if {$fi >= $nfiles} break
2509 if {$fi >= $nfiles} break
2509
2510
2510 # start a new group
2511 # start a new group
2511 set groupfilenum $fi
2512 set groupfilenum $fi
2512 set grouphunks($pi) [list $hunk]
2513 set grouphunks($pi) [list $hunk]
2513 set grouplinestart $lno
2514 set grouplinestart $lno
2514 set grouplineend [lindex $hunk 4]
2515 set grouplineend [lindex $hunk 4]
2515 }
2516 }
2516 }
2517 }
2517
2518
2518 proc processgroup {} {
2519 proc processgroup {} {
2519 global groupfilelast groupfilenum difffilestart
2520 global groupfilelast groupfilenum difffilestart
2520 global mergefilelist diffmergeid ctext filelines
2521 global mergefilelist diffmergeid ctext filelines
2521 global parents diffmergeid diffoffset
2522 global parents diffmergeid diffoffset
2522 global grouphunks grouplinestart grouplineend nparents
2523 global grouphunks grouplinestart grouplineend nparents
2523 global mergemax
2524 global mergemax
2524
2525
2525 $ctext conf -state normal
2526 $ctext conf -state normal
2526 set id $diffmergeid
2527 set id $diffmergeid
2527 set f $groupfilenum
2528 set f $groupfilenum
2528 if {$groupfilelast != $f} {
2529 if {$groupfilelast != $f} {
2529 $ctext insert end "\n"
2530 $ctext insert end "\n"
2530 set here [$ctext index "end - 1c"]
2531 set here [$ctext index "end - 1c"]
2531 set difffilestart($f) $here
2532 set difffilestart($f) $here
2532 set mark fmark.[expr {$f + 1}]
2533 set mark fmark.[expr {$f + 1}]
2533 $ctext mark set $mark $here
2534 $ctext mark set $mark $here
2534 $ctext mark gravity $mark left
2535 $ctext mark gravity $mark left
2535 set header [lindex $mergefilelist($id) $f]
2536 set header [lindex $mergefilelist($id) $f]
2536 set l [expr {(78 - [string length $header]) / 2}]
2537 set l [expr {(78 - [string length $header]) / 2}]
2537 set pad [string range "----------------------------------------" 1 $l]
2538 set pad [string range "----------------------------------------" 1 $l]
2538 $ctext insert end "$pad $header $pad\n" filesep
2539 $ctext insert end "$pad $header $pad\n" filesep
2539 set groupfilelast $f
2540 set groupfilelast $f
2540 foreach p $parents($id) {
2541 foreach p $parents($id) {
2541 set diffoffset($p) 0
2542 set diffoffset($p) 0
2542 }
2543 }
2543 }
2544 }
2544
2545
2545 $ctext insert end "@@" msep
2546 $ctext insert end "@@" msep
2546 set nlines [expr {$grouplineend - $grouplinestart}]
2547 set nlines [expr {$grouplineend - $grouplinestart}]
2547 set events {}
2548 set events {}
2548 set pnum 0
2549 set pnum 0
2549 foreach p $parents($id) {
2550 foreach p $parents($id) {
2550 set startline [expr {$grouplinestart + $diffoffset($p)}]
2551 set startline [expr {$grouplinestart + $diffoffset($p)}]
2551 set ol $startline
2552 set ol $startline
2552 set nl $grouplinestart
2553 set nl $grouplinestart
2553 if {[info exists grouphunks($p)]} {
2554 if {[info exists grouphunks($p)]} {
2554 foreach h $grouphunks($p) {
2555 foreach h $grouphunks($p) {
2555 set l [lindex $h 2]
2556 set l [lindex $h 2]
2556 if {$nl < $l} {
2557 if {$nl < $l} {
2557 for {} {$nl < $l} {incr nl} {
2558 for {} {$nl < $l} {incr nl} {
2558 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2559 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2559 incr ol
2560 incr ol
2560 }
2561 }
2561 }
2562 }
2562 foreach chunk [lindex $h 5] {
2563 foreach chunk [lindex $h 5] {
2563 if {[llength $chunk] == 2} {
2564 if {[llength $chunk] == 2} {
2564 set olc [lindex $chunk 0]
2565 set olc [lindex $chunk 0]
2565 set nlc [lindex $chunk 1]
2566 set nlc [lindex $chunk 1]
2566 set nnl [expr {$nl + $nlc}]
2567 set nnl [expr {$nl + $nlc}]
2567 lappend events [list $nl $nnl $pnum $olc $nlc]
2568 lappend events [list $nl $nnl $pnum $olc $nlc]
2568 incr ol $olc
2569 incr ol $olc
2569 set nl $nnl
2570 set nl $nnl
2570 } else {
2571 } else {
2571 incr ol [lindex $chunk 0]
2572 incr ol [lindex $chunk 0]
2572 incr nl [lindex $chunk 0]
2573 incr nl [lindex $chunk 0]
2573 }
2574 }
2574 }
2575 }
2575 }
2576 }
2576 }
2577 }
2577 if {$nl < $grouplineend} {
2578 if {$nl < $grouplineend} {
2578 for {} {$nl < $grouplineend} {incr nl} {
2579 for {} {$nl < $grouplineend} {incr nl} {
2579 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2580 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2580 incr ol
2581 incr ol
2581 }
2582 }
2582 }
2583 }
2583 set nlines [expr {$ol - $startline}]
2584 set nlines [expr {$ol - $startline}]
2584 $ctext insert end " -$startline,$nlines" msep
2585 $ctext insert end " -$startline,$nlines" msep
2585 incr pnum
2586 incr pnum
2586 }
2587 }
2587
2588
2588 set nlines [expr {$grouplineend - $grouplinestart}]
2589 set nlines [expr {$grouplineend - $grouplinestart}]
2589 $ctext insert end " +$grouplinestart,$nlines @@\n" msep
2590 $ctext insert end " +$grouplinestart,$nlines @@\n" msep
2590
2591
2591 set events [lsort -integer -index 0 $events]
2592 set events [lsort -integer -index 0 $events]
2592 set nevents [llength $events]
2593 set nevents [llength $events]
2593 set nmerge $nparents($diffmergeid)
2594 set nmerge $nparents($diffmergeid)
2594 set l $grouplinestart
2595 set l $grouplinestart
2595 for {set i 0} {$i < $nevents} {set i $j} {
2596 for {set i 0} {$i < $nevents} {set i $j} {
2596 set nl [lindex $events $i 0]
2597 set nl [lindex $events $i 0]
2597 while {$l < $nl} {
2598 while {$l < $nl} {
2598 $ctext insert end " $filelines($id,$f,$l)\n"
2599 $ctext insert end " $filelines($id,$f,$l)\n"
2599 incr l
2600 incr l
2600 }
2601 }
2601 set e [lindex $events $i]
2602 set e [lindex $events $i]
2602 set enl [lindex $e 1]
2603 set enl [lindex $e 1]
2603 set j $i
2604 set j $i
2604 set active {}
2605 set active {}
2605 while 1 {
2606 while 1 {
2606 set pnum [lindex $e 2]
2607 set pnum [lindex $e 2]
2607 set olc [lindex $e 3]
2608 set olc [lindex $e 3]
2608 set nlc [lindex $e 4]
2609 set nlc [lindex $e 4]
2609 if {![info exists delta($pnum)]} {
2610 if {![info exists delta($pnum)]} {
2610 set delta($pnum) [expr {$olc - $nlc}]
2611 set delta($pnum) [expr {$olc - $nlc}]
2611 lappend active $pnum
2612 lappend active $pnum
2612 } else {
2613 } else {
2613 incr delta($pnum) [expr {$olc - $nlc}]
2614 incr delta($pnum) [expr {$olc - $nlc}]
2614 }
2615 }
2615 if {[incr j] >= $nevents} break
2616 if {[incr j] >= $nevents} break
2616 set e [lindex $events $j]
2617 set e [lindex $events $j]
2617 if {[lindex $e 0] >= $enl} break
2618 if {[lindex $e 0] >= $enl} break
2618 if {[lindex $e 1] > $enl} {
2619 if {[lindex $e 1] > $enl} {
2619 set enl [lindex $e 1]
2620 set enl [lindex $e 1]
2620 }
2621 }
2621 }
2622 }
2622 set nlc [expr {$enl - $l}]
2623 set nlc [expr {$enl - $l}]
2623 set ncol mresult
2624 set ncol mresult
2624 set bestpn -1
2625 set bestpn -1
2625 if {[llength $active] == $nmerge - 1} {
2626 if {[llength $active] == $nmerge - 1} {
2626 # no diff for one of the parents, i.e. it's identical
2627 # no diff for one of the parents, i.e. it's identical
2627 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2628 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2628 if {![info exists delta($pnum)]} {
2629 if {![info exists delta($pnum)]} {
2629 if {$pnum < $mergemax} {
2630 if {$pnum < $mergemax} {
2630 lappend ncol m$pnum
2631 lappend ncol m$pnum
2631 } else {
2632 } else {
2632 lappend ncol mmax
2633 lappend ncol mmax
2633 }
2634 }
2634 break
2635 break
2635 }
2636 }
2636 }
2637 }
2637 } elseif {[llength $active] == $nmerge} {
2638 } elseif {[llength $active] == $nmerge} {
2638 # all parents are different, see if one is very similar
2639 # all parents are different, see if one is very similar
2639 set bestsim 30
2640 set bestsim 30
2640 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2641 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2641 set sim [similarity $pnum $l $nlc $f \
2642 set sim [similarity $pnum $l $nlc $f \
2642 [lrange $events $i [expr {$j-1}]]]
2643 [lrange $events $i [expr {$j-1}]]]
2643 if {$sim > $bestsim} {
2644 if {$sim > $bestsim} {
2644 set bestsim $sim
2645 set bestsim $sim
2645 set bestpn $pnum
2646 set bestpn $pnum
2646 }
2647 }
2647 }
2648 }
2648 if {$bestpn >= 0} {
2649 if {$bestpn >= 0} {
2649 lappend ncol m$bestpn
2650 lappend ncol m$bestpn
2650 }
2651 }
2651 }
2652 }
2652 set pnum -1
2653 set pnum -1
2653 foreach p $parents($id) {
2654 foreach p $parents($id) {
2654 incr pnum
2655 incr pnum
2655 if {![info exists delta($pnum)] || $pnum == $bestpn} continue
2656 if {![info exists delta($pnum)] || $pnum == $bestpn} continue
2656 set olc [expr {$nlc + $delta($pnum)}]
2657 set olc [expr {$nlc + $delta($pnum)}]
2657 set ol [expr {$l + $diffoffset($p)}]
2658 set ol [expr {$l + $diffoffset($p)}]
2658 incr diffoffset($p) $delta($pnum)
2659 incr diffoffset($p) $delta($pnum)
2659 unset delta($pnum)
2660 unset delta($pnum)
2660 for {} {$olc > 0} {incr olc -1} {
2661 for {} {$olc > 0} {incr olc -1} {
2661 $ctext insert end "-$filelines($p,$f,$ol)\n" m$pnum
2662 $ctext insert end "-$filelines($p,$f,$ol)\n" m$pnum
2662 incr ol
2663 incr ol
2663 }
2664 }
2664 }
2665 }
2665 set endl [expr {$l + $nlc}]
2666 set endl [expr {$l + $nlc}]
2666 if {$bestpn >= 0} {
2667 if {$bestpn >= 0} {
2667 # show this pretty much as a normal diff
2668 # show this pretty much as a normal diff
2668 set p [lindex $parents($id) $bestpn]
2669 set p [lindex $parents($id) $bestpn]
2669 set ol [expr {$l + $diffoffset($p)}]
2670 set ol [expr {$l + $diffoffset($p)}]
2670 incr diffoffset($p) $delta($bestpn)
2671 incr diffoffset($p) $delta($bestpn)
2671 unset delta($bestpn)
2672 unset delta($bestpn)
2672 for {set k $i} {$k < $j} {incr k} {
2673 for {set k $i} {$k < $j} {incr k} {
2673 set e [lindex $events $k]
2674 set e [lindex $events $k]
2674 if {[lindex $e 2] != $bestpn} continue
2675 if {[lindex $e 2] != $bestpn} continue
2675 set nl [lindex $e 0]
2676 set nl [lindex $e 0]
2676 set ol [expr {$ol + $nl - $l}]
2677 set ol [expr {$ol + $nl - $l}]
2677 for {} {$l < $nl} {incr l} {
2678 for {} {$l < $nl} {incr l} {
2678 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2679 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2679 }
2680 }
2680 set c [lindex $e 3]
2681 set c [lindex $e 3]
2681 for {} {$c > 0} {incr c -1} {
2682 for {} {$c > 0} {incr c -1} {
2682 $ctext insert end "-$filelines($p,$f,$ol)\n" m$bestpn
2683 $ctext insert end "-$filelines($p,$f,$ol)\n" m$bestpn
2683 incr ol
2684 incr ol
2684 }
2685 }
2685 set nl [lindex $e 1]
2686 set nl [lindex $e 1]
2686 for {} {$l < $nl} {incr l} {
2687 for {} {$l < $nl} {incr l} {
2687 $ctext insert end "+$filelines($id,$f,$l)\n" mresult
2688 $ctext insert end "+$filelines($id,$f,$l)\n" mresult
2688 }
2689 }
2689 }
2690 }
2690 }
2691 }
2691 for {} {$l < $endl} {incr l} {
2692 for {} {$l < $endl} {incr l} {
2692 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2693 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2693 }
2694 }
2694 }
2695 }
2695 while {$l < $grouplineend} {
2696 while {$l < $grouplineend} {
2696 $ctext insert end " $filelines($id,$f,$l)\n"
2697 $ctext insert end " $filelines($id,$f,$l)\n"
2697 incr l
2698 incr l
2698 }
2699 }
2699 $ctext conf -state disabled
2700 $ctext conf -state disabled
2700 }
2701 }
2701
2702
2702 proc similarity {pnum l nlc f events} {
2703 proc similarity {pnum l nlc f events} {
2703 global diffmergeid parents diffoffset filelines
2704 global diffmergeid parents diffoffset filelines
2704
2705
2705 set id $diffmergeid
2706 set id $diffmergeid
2706 set p [lindex $parents($id) $pnum]
2707 set p [lindex $parents($id) $pnum]
2707 set ol [expr {$l + $diffoffset($p)}]
2708 set ol [expr {$l + $diffoffset($p)}]
2708 set endl [expr {$l + $nlc}]
2709 set endl [expr {$l + $nlc}]
2709 set same 0
2710 set same 0
2710 set diff 0
2711 set diff 0
2711 foreach e $events {
2712 foreach e $events {
2712 if {[lindex $e 2] != $pnum} continue
2713 if {[lindex $e 2] != $pnum} continue
2713 set nl [lindex $e 0]
2714 set nl [lindex $e 0]
2714 set ol [expr {$ol + $nl - $l}]
2715 set ol [expr {$ol + $nl - $l}]
2715 for {} {$l < $nl} {incr l} {
2716 for {} {$l < $nl} {incr l} {
2716 incr same [string length $filelines($id,$f,$l)]
2717 incr same [string length $filelines($id,$f,$l)]
2717 incr same
2718 incr same
2718 }
2719 }
2719 set oc [lindex $e 3]
2720 set oc [lindex $e 3]
2720 for {} {$oc > 0} {incr oc -1} {
2721 for {} {$oc > 0} {incr oc -1} {
2721 incr diff [string length $filelines($p,$f,$ol)]
2722 incr diff [string length $filelines($p,$f,$ol)]
2722 incr diff
2723 incr diff
2723 incr ol
2724 incr ol
2724 }
2725 }
2725 set nl [lindex $e 1]
2726 set nl [lindex $e 1]
2726 for {} {$l < $nl} {incr l} {
2727 for {} {$l < $nl} {incr l} {
2727 incr diff [string length $filelines($id,$f,$l)]
2728 incr diff [string length $filelines($id,$f,$l)]
2728 incr diff
2729 incr diff
2729 }
2730 }
2730 }
2731 }
2731 for {} {$l < $endl} {incr l} {
2732 for {} {$l < $endl} {incr l} {
2732 incr same [string length $filelines($id,$f,$l)]
2733 incr same [string length $filelines($id,$f,$l)]
2733 incr same
2734 incr same
2734 }
2735 }
2735 if {$same == 0} {
2736 if {$same == 0} {
2736 return 0
2737 return 0
2737 }
2738 }
2738 return [expr {200 * $same / (2 * $same + $diff)}]
2739 return [expr {200 * $same / (2 * $same + $diff)}]
2739 }
2740 }
2740
2741
2741 proc startdiff {ids} {
2742 proc startdiff {ids} {
2742 global treediffs diffids treepending diffmergeid
2743 global treediffs diffids treepending diffmergeid
2743
2744
2744 set diffids $ids
2745 set diffids $ids
2745 catch {unset diffmergeid}
2746 catch {unset diffmergeid}
2746 if {![info exists treediffs($ids)]} {
2747 if {![info exists treediffs($ids)]} {
2747 if {![info exists treepending]} {
2748 if {![info exists treepending]} {
2748 gettreediffs $ids
2749 gettreediffs $ids
2749 }
2750 }
2750 } else {
2751 } else {
2751 addtocflist $ids
2752 addtocflist $ids
2752 }
2753 }
2753 }
2754 }
2754
2755
2755 proc addtocflist {ids} {
2756 proc addtocflist {ids} {
2756 global treediffs cflist
2757 global treediffs cflist
2757 foreach f $treediffs($ids) {
2758 foreach f $treediffs($ids) {
2758 $cflist insert end $f
2759 $cflist insert end $f
2759 }
2760 }
2760 getblobdiffs $ids
2761 getblobdiffs $ids
2761 }
2762 }
2762
2763
2763 proc gettreediffs {ids} {
2764 proc gettreediffs {ids} {
2764 global treediff parents treepending
2765 global treediff parents treepending
2765 set treepending $ids
2766 set treepending $ids
2766 set treediff {}
2767 set treediff {}
2767 set id [lindex $ids 0]
2768 set id [lindex $ids 0]
2768 set p [lindex $ids 1]
2769 set p [lindex $ids 1]
2769 if [catch {set gdtf [open "|hg debug-diff-tree -r $p $id" r]}] return
2770 if [catch {set gdtf [open "|hg debug-diff-tree -r $p $id" r]}] return
2770 fconfigure $gdtf -blocking 0
2771 fconfigure $gdtf -blocking 0
2771 fileevent $gdtf readable [list gettreediffline $gdtf $ids]
2772 fileevent $gdtf readable [list gettreediffline $gdtf $ids]
2772 }
2773 }
2773
2774
2774 proc gettreediffline {gdtf ids} {
2775 proc gettreediffline {gdtf ids} {
2775 global treediff treediffs treepending diffids diffmergeid
2776 global treediff treediffs treepending diffids diffmergeid
2776
2777
2777 set n [gets $gdtf line]
2778 set n [gets $gdtf line]
2778 if {$n < 0} {
2779 if {$n < 0} {
2779 if {![eof $gdtf]} return
2780 if {![eof $gdtf]} return
2780 close $gdtf
2781 close $gdtf
2781 set treediffs($ids) $treediff
2782 set treediffs($ids) $treediff
2782 unset treepending
2783 unset treepending
2783 if {$ids != $diffids} {
2784 if {$ids != $diffids} {
2784 gettreediffs $diffids
2785 gettreediffs $diffids
2785 } else {
2786 } else {
2786 if {[info exists diffmergeid]} {
2787 if {[info exists diffmergeid]} {
2787 contmergediff $ids
2788 contmergediff $ids
2788 } else {
2789 } else {
2789 addtocflist $ids
2790 addtocflist $ids
2790 }
2791 }
2791 }
2792 }
2792 return
2793 return
2793 }
2794 }
2794 set file [lindex $line 5]
2795 set file [lindex $line 5]
2795 lappend treediff $file
2796 lappend treediff $file
2796 }
2797 }
2797
2798
2798 proc getblobdiffs {ids} {
2799 proc getblobdiffs {ids} {
2799 global diffopts blobdifffd diffids env curdifftag curtagstart
2800 global diffopts blobdifffd diffids env curdifftag curtagstart
2800 global difffilestart nextupdate diffinhdr treediffs
2801 global difffilestart nextupdate diffinhdr treediffs
2801
2802
2802 set id [lindex $ids 0]
2803 set id [lindex $ids 0]
2803 set p [lindex $ids 1]
2804 set p [lindex $ids 1]
2804 set env(GIT_DIFF_OPTS) $diffopts
2805 set env(GIT_DIFF_OPTS) $diffopts
2805 set cmd [list | hg debug-diff-tree -r -p -C $p $id]
2806 set cmd [list | hg debug-diff-tree -r -p -C $p $id]
2806 if {[catch {set bdf [open $cmd r]} err]} {
2807 if {[catch {set bdf [open $cmd r]} err]} {
2807 puts "error getting diffs: $err"
2808 puts "error getting diffs: $err"
2808 return
2809 return
2809 }
2810 }
2810 set diffinhdr 0
2811 set diffinhdr 0
2811 fconfigure $bdf -blocking 0
2812 fconfigure $bdf -blocking 0
2812 set blobdifffd($ids) $bdf
2813 set blobdifffd($ids) $bdf
2813 set curdifftag Comments
2814 set curdifftag Comments
2814 set curtagstart 0.0
2815 set curtagstart 0.0
2815 catch {unset difffilestart}
2816 catch {unset difffilestart}
2816 fileevent $bdf readable [list getblobdiffline $bdf $diffids]
2817 fileevent $bdf readable [list getblobdiffline $bdf $diffids]
2817 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
2818 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
2818 }
2819 }
2819
2820
2820 proc getblobdiffline {bdf ids} {
2821 proc getblobdiffline {bdf ids} {
2821 global diffids blobdifffd ctext curdifftag curtagstart
2822 global diffids blobdifffd ctext curdifftag curtagstart
2822 global diffnexthead diffnextnote difffilestart
2823 global diffnexthead diffnextnote difffilestart
2823 global nextupdate diffinhdr treediffs
2824 global nextupdate diffinhdr treediffs
2824 global gaudydiff
2825 global gaudydiff
2825
2826
2826 set n [gets $bdf line]
2827 set n [gets $bdf line]
2827 if {$n < 0} {
2828 if {$n < 0} {
2828 if {[eof $bdf]} {
2829 if {[eof $bdf]} {
2829 close $bdf
2830 close $bdf
2830 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
2831 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
2831 $ctext tag add $curdifftag $curtagstart end
2832 $ctext tag add $curdifftag $curtagstart end
2832 }
2833 }
2833 }
2834 }
2834 return
2835 return
2835 }
2836 }
2836 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
2837 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
2837 return
2838 return
2838 }
2839 }
2839 $ctext conf -state normal
2840 $ctext conf -state normal
2840 if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
2841 if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
2841 # start of a new file
2842 # start of a new file
2842 $ctext insert end "\n"
2843 $ctext insert end "\n"
2843 $ctext tag add $curdifftag $curtagstart end
2844 $ctext tag add $curdifftag $curtagstart end
2844 set curtagstart [$ctext index "end - 1c"]
2845 set curtagstart [$ctext index "end - 1c"]
2845 set header $newname
2846 set header $newname
2846 set here [$ctext index "end - 1c"]
2847 set here [$ctext index "end - 1c"]
2847 set i [lsearch -exact $treediffs($diffids) $fname]
2848 set i [lsearch -exact $treediffs($diffids) $fname]
2848 if {$i >= 0} {
2849 if {$i >= 0} {
2849 set difffilestart($i) $here
2850 set difffilestart($i) $here
2850 incr i
2851 incr i
2851 $ctext mark set fmark.$i $here
2852 $ctext mark set fmark.$i $here
2852 $ctext mark gravity fmark.$i left
2853 $ctext mark gravity fmark.$i left
2853 }
2854 }
2854 if {$newname != $fname} {
2855 if {$newname != $fname} {
2855 set i [lsearch -exact $treediffs($diffids) $newname]
2856 set i [lsearch -exact $treediffs($diffids) $newname]
2856 if {$i >= 0} {
2857 if {$i >= 0} {
2857 set difffilestart($i) $here
2858 set difffilestart($i) $here
2858 incr i
2859 incr i
2859 $ctext mark set fmark.$i $here
2860 $ctext mark set fmark.$i $here
2860 $ctext mark gravity fmark.$i left
2861 $ctext mark gravity fmark.$i left
2861 }
2862 }
2862 }
2863 }
2863 set curdifftag "f:$fname"
2864 set curdifftag "f:$fname"
2864 $ctext tag delete $curdifftag
2865 $ctext tag delete $curdifftag
2865 set l [expr {(78 - [string length $header]) / 2}]
2866 set l [expr {(78 - [string length $header]) / 2}]
2866 set pad [string range "----------------------------------------" 1 $l]
2867 set pad [string range "----------------------------------------" 1 $l]
2867 $ctext insert end "$pad $header $pad\n" filesep
2868 $ctext insert end "$pad $header $pad\n" filesep
2868 set diffinhdr 1
2869 set diffinhdr 1
2869 } elseif {[regexp {^(---|\+\+\+)} $line]} {
2870 } elseif {[regexp {^(---|\+\+\+)} $line]} {
2870 set diffinhdr 0
2871 set diffinhdr 0
2871 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2872 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2872 $line match f1l f1c f2l f2c rest]} {
2873 $line match f1l f1c f2l f2c rest]} {
2873 if {$gaudydiff} {
2874 if {$gaudydiff} {
2874 $ctext insert end "\t" hunksep
2875 $ctext insert end "\t" hunksep
2875 $ctext insert end " $f1l " d0 " $f2l " d1
2876 $ctext insert end " $f1l " d0 " $f2l " d1
2876 $ctext insert end " $rest \n" hunksep
2877 $ctext insert end " $rest \n" hunksep
2877 } else {
2878 } else {
2878 $ctext insert end "$line\n" hunksep
2879 $ctext insert end "$line\n" hunksep
2879 }
2880 }
2880 set diffinhdr 0
2881 set diffinhdr 0
2881 } else {
2882 } else {
2882 set x [string range $line 0 0]
2883 set x [string range $line 0 0]
2883 if {$x == "-" || $x == "+"} {
2884 if {$x == "-" || $x == "+"} {
2884 set tag [expr {$x == "+"}]
2885 set tag [expr {$x == "+"}]
2885 if {$gaudydiff} {
2886 if {$gaudydiff} {
2886 set line [string range $line 1 end]
2887 set line [string range $line 1 end]
2887 }
2888 }
2888 $ctext insert end "$line\n" d$tag
2889 $ctext insert end "$line\n" d$tag
2889 } elseif {$x == " "} {
2890 } elseif {$x == " "} {
2890 if {$gaudydiff} {
2891 if {$gaudydiff} {
2891 set line [string range $line 1 end]
2892 set line [string range $line 1 end]
2892 }
2893 }
2893 $ctext insert end "$line\n"
2894 $ctext insert end "$line\n"
2894 } elseif {$diffinhdr || $x == "\\"} {
2895 } elseif {$diffinhdr || $x == "\\"} {
2895 # e.g. "\ No newline at end of file"
2896 # e.g. "\ No newline at end of file"
2896 $ctext insert end "$line\n" filesep
2897 $ctext insert end "$line\n" filesep
2897 } else {
2898 } else {
2898 # Something else we don't recognize
2899 # Something else we don't recognize
2899 if {$curdifftag != "Comments"} {
2900 if {$curdifftag != "Comments"} {
2900 $ctext insert end "\n"
2901 $ctext insert end "\n"
2901 $ctext tag add $curdifftag $curtagstart end
2902 $ctext tag add $curdifftag $curtagstart end
2902 set curtagstart [$ctext index "end - 1c"]
2903 set curtagstart [$ctext index "end - 1c"]
2903 set curdifftag Comments
2904 set curdifftag Comments
2904 }
2905 }
2905 $ctext insert end "$line\n" filesep
2906 $ctext insert end "$line\n" filesep
2906 }
2907 }
2907 }
2908 }
2908 $ctext conf -state disabled
2909 $ctext conf -state disabled
2909 if {[clock clicks -milliseconds] >= $nextupdate} {
2910 if {[clock clicks -milliseconds] >= $nextupdate} {
2910 incr nextupdate 100
2911 incr nextupdate 100
2911 fileevent $bdf readable {}
2912 fileevent $bdf readable {}
2912 update
2913 update
2913 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
2914 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
2914 }
2915 }
2915 }
2916 }
2916
2917
2917 proc nextfile {} {
2918 proc nextfile {} {
2918 global difffilestart ctext
2919 global difffilestart ctext
2919 set here [$ctext index @0,0]
2920 set here [$ctext index @0,0]
2920 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
2921 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
2921 if {[$ctext compare $difffilestart($i) > $here]} {
2922 if {[$ctext compare $difffilestart($i) > $here]} {
2922 if {![info exists pos]
2923 if {![info exists pos]
2923 || [$ctext compare $difffilestart($i) < $pos]} {
2924 || [$ctext compare $difffilestart($i) < $pos]} {
2924 set pos $difffilestart($i)
2925 set pos $difffilestart($i)
2925 }
2926 }
2926 }
2927 }
2927 }
2928 }
2928 if {[info exists pos]} {
2929 if {[info exists pos]} {
2929 $ctext yview $pos
2930 $ctext yview $pos
2930 }
2931 }
2931 }
2932 }
2932
2933
2933 proc listboxsel {} {
2934 proc listboxsel {} {
2934 global ctext cflist currentid
2935 global ctext cflist currentid
2935 if {![info exists currentid]} return
2936 if {![info exists currentid]} return
2936 set sel [lsort [$cflist curselection]]
2937 set sel [lsort [$cflist curselection]]
2937 if {$sel eq {}} return
2938 if {$sel eq {}} return
2938 set first [lindex $sel 0]
2939 set first [lindex $sel 0]
2939 catch {$ctext yview fmark.$first}
2940 catch {$ctext yview fmark.$first}
2940 }
2941 }
2941
2942
2942 proc setcoords {} {
2943 proc setcoords {} {
2943 global linespc charspc canvx0 canvy0 mainfont
2944 global linespc charspc canvx0 canvy0 mainfont
2944 global xspc1 xspc2 lthickness
2945 global xspc1 xspc2 lthickness
2945
2946
2946 set linespc [font metrics $mainfont -linespace]
2947 set linespc [font metrics $mainfont -linespace]
2947 set charspc [font measure $mainfont "m"]
2948 set charspc [font measure $mainfont "m"]
2948 set canvy0 [expr 3 + 0.5 * $linespc]
2949 set canvy0 [expr 3 + 0.5 * $linespc]
2949 set canvx0 [expr 3 + 0.5 * $linespc]
2950 set canvx0 [expr 3 + 0.5 * $linespc]
2950 set lthickness [expr {int($linespc / 9) + 1}]
2951 set lthickness [expr {int($linespc / 9) + 1}]
2951 set xspc1(0) $linespc
2952 set xspc1(0) $linespc
2952 set xspc2 $linespc
2953 set xspc2 $linespc
2953 }
2954 }
2954
2955
2955 proc redisplay {} {
2956 proc redisplay {} {
2956 global stopped redisplaying phase
2957 global stopped redisplaying phase
2957 if {$stopped > 1} return
2958 if {$stopped > 1} return
2958 if {$phase == "getcommits"} return
2959 if {$phase == "getcommits"} return
2959 set redisplaying 1
2960 set redisplaying 1
2960 if {$phase == "drawgraph" || $phase == "incrdraw"} {
2961 if {$phase == "drawgraph" || $phase == "incrdraw"} {
2961 set stopped 1
2962 set stopped 1
2962 } else {
2963 } else {
2963 drawgraph
2964 drawgraph
2964 }
2965 }
2965 }
2966 }
2966
2967
2967 proc incrfont {inc} {
2968 proc incrfont {inc} {
2968 global mainfont namefont textfont ctext canv phase
2969 global mainfont namefont textfont ctext canv phase
2969 global stopped entries
2970 global stopped entries
2970 unmarkmatches
2971 unmarkmatches
2971 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
2972 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
2972 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
2973 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
2973 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
2974 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
2974 setcoords
2975 setcoords
2975 $ctext conf -font $textfont
2976 $ctext conf -font $textfont
2976 $ctext tag conf filesep -font [concat $textfont bold]
2977 $ctext tag conf filesep -font [concat $textfont bold]
2977 foreach e $entries {
2978 foreach e $entries {
2978 $e conf -font $mainfont
2979 $e conf -font $mainfont
2979 }
2980 }
2980 if {$phase == "getcommits"} {
2981 if {$phase == "getcommits"} {
2981 $canv itemconf textitems -font $mainfont
2982 $canv itemconf textitems -font $mainfont
2982 }
2983 }
2983 redisplay
2984 redisplay
2984 }
2985 }
2985
2986
2986 proc clearsha1 {} {
2987 proc clearsha1 {} {
2987 global sha1entry sha1string
2988 global sha1entry sha1string
2988 if {[string length $sha1string] == 40} {
2989 if {[string length $sha1string] == 40} {
2989 $sha1entry delete 0 end
2990 $sha1entry delete 0 end
2990 }
2991 }
2991 }
2992 }
2992
2993
2993 proc sha1change {n1 n2 op} {
2994 proc sha1change {n1 n2 op} {
2994 global sha1string currentid sha1but
2995 global sha1string currentid sha1but
2995 if {$sha1string == {}
2996 if {$sha1string == {}
2996 || ([info exists currentid] && $sha1string == $currentid)} {
2997 || ([info exists currentid] && $sha1string == $currentid)} {
2997 set state disabled
2998 set state disabled
2998 } else {
2999 } else {
2999 set state normal
3000 set state normal
3000 }
3001 }
3001 if {[$sha1but cget -state] == $state} return
3002 if {[$sha1but cget -state] == $state} return
3002 if {$state == "normal"} {
3003 if {$state == "normal"} {
3003 $sha1but conf -state normal -relief raised -text "Goto: "
3004 $sha1but conf -state normal -relief raised -text "Goto: "
3004 } else {
3005 } else {
3005 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
3006 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
3006 }
3007 }
3007 }
3008 }
3008
3009
3009 proc gotocommit {} {
3010 proc gotocommit {} {
3010 global sha1string currentid idline tagids
3011 global sha1string currentid idline tagids
3011 global lineid numcommits
3012 global lineid numcommits
3012
3013
3013 if {$sha1string == {}
3014 if {$sha1string == {}
3014 || ([info exists currentid] && $sha1string == $currentid)} return
3015 || ([info exists currentid] && $sha1string == $currentid)} return
3015 if {[info exists tagids($sha1string)]} {
3016 if {[info exists tagids($sha1string)]} {
3016 set id $tagids($sha1string)
3017 set id $tagids($sha1string)
3017 } else {
3018 } else {
3018 set id [string tolower $sha1string]
3019 set id [string tolower $sha1string]
3019 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
3020 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
3020 set matches {}
3021 set matches {}
3021 for {set l 0} {$l < $numcommits} {incr l} {
3022 for {set l 0} {$l < $numcommits} {incr l} {
3022 if {[string match $id* $lineid($l)]} {
3023 if {[string match $id* $lineid($l)]} {
3023 lappend matches $lineid($l)
3024 lappend matches $lineid($l)
3024 }
3025 }
3025 }
3026 }
3026 if {$matches ne {}} {
3027 if {$matches ne {}} {
3027 if {[llength $matches] > 1} {
3028 if {[llength $matches] > 1} {
3028 error_popup "Short SHA1 id $id is ambiguous"
3029 error_popup "Short SHA1 id $id is ambiguous"
3029 return
3030 return
3030 }
3031 }
3031 set id [lindex $matches 0]
3032 set id [lindex $matches 0]
3032 }
3033 }
3033 }
3034 }
3034 }
3035 }
3035 if {[info exists idline($id)]} {
3036 if {[info exists idline($id)]} {
3036 selectline $idline($id) 1
3037 selectline $idline($id) 1
3037 return
3038 return
3038 }
3039 }
3039 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
3040 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
3040 set type "SHA1 id"
3041 set type "SHA1 id"
3041 } else {
3042 } else {
3042 set type "Tag"
3043 set type "Tag"
3043 }
3044 }
3044 error_popup "$type $sha1string is not known"
3045 error_popup "$type $sha1string is not known"
3045 }
3046 }
3046
3047
3047 proc lineenter {x y id} {
3048 proc lineenter {x y id} {
3048 global hoverx hovery hoverid hovertimer
3049 global hoverx hovery hoverid hovertimer
3049 global commitinfo canv
3050 global commitinfo canv
3050
3051
3051 if {![info exists commitinfo($id)]} return
3052 if {![info exists commitinfo($id)]} return
3052 set hoverx $x
3053 set hoverx $x
3053 set hovery $y
3054 set hovery $y
3054 set hoverid $id
3055 set hoverid $id
3055 if {[info exists hovertimer]} {
3056 if {[info exists hovertimer]} {
3056 after cancel $hovertimer
3057 after cancel $hovertimer
3057 }
3058 }
3058 set hovertimer [after 500 linehover]
3059 set hovertimer [after 500 linehover]
3059 $canv delete hover
3060 $canv delete hover
3060 }
3061 }
3061
3062
3062 proc linemotion {x y id} {
3063 proc linemotion {x y id} {
3063 global hoverx hovery hoverid hovertimer
3064 global hoverx hovery hoverid hovertimer
3064
3065
3065 if {[info exists hoverid] && $id == $hoverid} {
3066 if {[info exists hoverid] && $id == $hoverid} {
3066 set hoverx $x
3067 set hoverx $x
3067 set hovery $y
3068 set hovery $y
3068 if {[info exists hovertimer]} {
3069 if {[info exists hovertimer]} {
3069 after cancel $hovertimer
3070 after cancel $hovertimer
3070 }
3071 }
3071 set hovertimer [after 500 linehover]
3072 set hovertimer [after 500 linehover]
3072 }
3073 }
3073 }
3074 }
3074
3075
3075 proc lineleave {id} {
3076 proc lineleave {id} {
3076 global hoverid hovertimer canv
3077 global hoverid hovertimer canv
3077
3078
3078 if {[info exists hoverid] && $id == $hoverid} {
3079 if {[info exists hoverid] && $id == $hoverid} {
3079 $canv delete hover
3080 $canv delete hover
3080 if {[info exists hovertimer]} {
3081 if {[info exists hovertimer]} {
3081 after cancel $hovertimer
3082 after cancel $hovertimer
3082 unset hovertimer
3083 unset hovertimer
3083 }
3084 }
3084 unset hoverid
3085 unset hoverid
3085 }
3086 }
3086 }
3087 }
3087
3088
3088 proc linehover {} {
3089 proc linehover {} {
3089 global hoverx hovery hoverid hovertimer
3090 global hoverx hovery hoverid hovertimer
3090 global canv linespc lthickness
3091 global canv linespc lthickness
3091 global commitinfo mainfont
3092 global commitinfo mainfont
3092
3093
3093 set text [lindex $commitinfo($hoverid) 0]
3094 set text [lindex $commitinfo($hoverid) 0]
3094 set ymax [lindex [$canv cget -scrollregion] 3]
3095 set ymax [lindex [$canv cget -scrollregion] 3]
3095 if {$ymax == {}} return
3096 if {$ymax == {}} return
3096 set yfrac [lindex [$canv yview] 0]
3097 set yfrac [lindex [$canv yview] 0]
3097 set x [expr {$hoverx + 2 * $linespc}]
3098 set x [expr {$hoverx + 2 * $linespc}]
3098 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
3099 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
3099 set x0 [expr {$x - 2 * $lthickness}]
3100 set x0 [expr {$x - 2 * $lthickness}]
3100 set y0 [expr {$y - 2 * $lthickness}]
3101 set y0 [expr {$y - 2 * $lthickness}]
3101 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
3102 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
3102 set y1 [expr {$y + $linespc + 2 * $lthickness}]
3103 set y1 [expr {$y + $linespc + 2 * $lthickness}]
3103 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
3104 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
3104 -fill \#ffff80 -outline black -width 1 -tags hover]
3105 -fill \#ffff80 -outline black -width 1 -tags hover]
3105 $canv raise $t
3106 $canv raise $t
3106 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
3107 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
3107 $canv raise $t
3108 $canv raise $t
3108 }
3109 }
3109
3110
3110 proc clickisonarrow {id y} {
3111 proc clickisonarrow {id y} {
3111 global mainline mainlinearrow sidelines lthickness
3112 global mainline mainlinearrow sidelines lthickness
3112
3113
3113 set thresh [expr {2 * $lthickness + 6}]
3114 set thresh [expr {2 * $lthickness + 6}]
3114 if {[info exists mainline($id)]} {
3115 if {[info exists mainline($id)]} {
3115 if {$mainlinearrow($id) ne "none"} {
3116 if {$mainlinearrow($id) ne "none"} {
3116 if {abs([lindex $mainline($id) 1] - $y) < $thresh} {
3117 if {abs([lindex $mainline($id) 1] - $y) < $thresh} {
3117 return "up"
3118 return "up"
3118 }
3119 }
3119 }
3120 }
3120 }
3121 }
3121 if {[info exists sidelines($id)]} {
3122 if {[info exists sidelines($id)]} {
3122 foreach ls $sidelines($id) {
3123 foreach ls $sidelines($id) {
3123 set coords [lindex $ls 0]
3124 set coords [lindex $ls 0]
3124 set arrow [lindex $ls 2]
3125 set arrow [lindex $ls 2]
3125 if {$arrow eq "first" || $arrow eq "both"} {
3126 if {$arrow eq "first" || $arrow eq "both"} {
3126 if {abs([lindex $coords 1] - $y) < $thresh} {
3127 if {abs([lindex $coords 1] - $y) < $thresh} {
3127 return "up"
3128 return "up"
3128 }
3129 }
3129 }
3130 }
3130 if {$arrow eq "last" || $arrow eq "both"} {
3131 if {$arrow eq "last" || $arrow eq "both"} {
3131 if {abs([lindex $coords end] - $y) < $thresh} {
3132 if {abs([lindex $coords end] - $y) < $thresh} {
3132 return "down"
3133 return "down"
3133 }
3134 }
3134 }
3135 }
3135 }
3136 }
3136 }
3137 }
3137 return {}
3138 return {}
3138 }
3139 }
3139
3140
3140 proc arrowjump {id dirn y} {
3141 proc arrowjump {id dirn y} {
3141 global mainline sidelines canv
3142 global mainline sidelines canv
3142
3143
3143 set yt {}
3144 set yt {}
3144 if {$dirn eq "down"} {
3145 if {$dirn eq "down"} {
3145 if {[info exists mainline($id)]} {
3146 if {[info exists mainline($id)]} {
3146 set y1 [lindex $mainline($id) 1]
3147 set y1 [lindex $mainline($id) 1]
3147 if {$y1 > $y} {
3148 if {$y1 > $y} {
3148 set yt $y1
3149 set yt $y1
3149 }
3150 }
3150 }
3151 }
3151 if {[info exists sidelines($id)]} {
3152 if {[info exists sidelines($id)]} {
3152 foreach ls $sidelines($id) {
3153 foreach ls $sidelines($id) {
3153 set y1 [lindex $ls 0 1]
3154 set y1 [lindex $ls 0 1]
3154 if {$y1 > $y && ($yt eq {} || $y1 < $yt)} {
3155 if {$y1 > $y && ($yt eq {} || $y1 < $yt)} {
3155 set yt $y1
3156 set yt $y1
3156 }
3157 }
3157 }
3158 }
3158 }
3159 }
3159 } else {
3160 } else {
3160 if {[info exists sidelines($id)]} {
3161 if {[info exists sidelines($id)]} {
3161 foreach ls $sidelines($id) {
3162 foreach ls $sidelines($id) {
3162 set y1 [lindex $ls 0 end]
3163 set y1 [lindex $ls 0 end]
3163 if {$y1 < $y && ($yt eq {} || $y1 > $yt)} {
3164 if {$y1 < $y && ($yt eq {} || $y1 > $yt)} {
3164 set yt $y1
3165 set yt $y1
3165 }
3166 }
3166 }
3167 }
3167 }
3168 }
3168 }
3169 }
3169 if {$yt eq {}} return
3170 if {$yt eq {}} return
3170 set ymax [lindex [$canv cget -scrollregion] 3]
3171 set ymax [lindex [$canv cget -scrollregion] 3]
3171 if {$ymax eq {} || $ymax <= 0} return
3172 if {$ymax eq {} || $ymax <= 0} return
3172 set view [$canv yview]
3173 set view [$canv yview]
3173 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
3174 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
3174 set yfrac [expr {$yt / $ymax - $yspan / 2}]
3175 set yfrac [expr {$yt / $ymax - $yspan / 2}]
3175 if {$yfrac < 0} {
3176 if {$yfrac < 0} {
3176 set yfrac 0
3177 set yfrac 0
3177 }
3178 }
3178 $canv yview moveto $yfrac
3179 $canv yview moveto $yfrac
3179 }
3180 }
3180
3181
3181 proc lineclick {x y id isnew} {
3182 proc lineclick {x y id isnew} {
3182 global ctext commitinfo children cflist canv thickerline
3183 global ctext commitinfo children cflist canv thickerline
3183
3184
3184 unmarkmatches
3185 unmarkmatches
3185 unselectline
3186 unselectline
3186 normalline
3187 normalline
3187 $canv delete hover
3188 $canv delete hover
3188 # draw this line thicker than normal
3189 # draw this line thicker than normal
3189 drawlines $id 1
3190 drawlines $id 1
3190 set thickerline $id
3191 set thickerline $id
3191 if {$isnew} {
3192 if {$isnew} {
3192 set ymax [lindex [$canv cget -scrollregion] 3]
3193 set ymax [lindex [$canv cget -scrollregion] 3]
3193 if {$ymax eq {}} return
3194 if {$ymax eq {}} return
3194 set yfrac [lindex [$canv yview] 0]
3195 set yfrac [lindex [$canv yview] 0]
3195 set y [expr {$y + $yfrac * $ymax}]
3196 set y [expr {$y + $yfrac * $ymax}]
3196 }
3197 }
3197 set dirn [clickisonarrow $id $y]
3198 set dirn [clickisonarrow $id $y]
3198 if {$dirn ne {}} {
3199 if {$dirn ne {}} {
3199 arrowjump $id $dirn $y
3200 arrowjump $id $dirn $y
3200 return
3201 return
3201 }
3202 }
3202
3203
3203 if {$isnew} {
3204 if {$isnew} {
3204 addtohistory [list lineclick $x $y $id 0]
3205 addtohistory [list lineclick $x $y $id 0]
3205 }
3206 }
3206 # fill the details pane with info about this line
3207 # fill the details pane with info about this line
3207 $ctext conf -state normal
3208 $ctext conf -state normal
3208 $ctext delete 0.0 end
3209 $ctext delete 0.0 end
3209 $ctext tag conf link -foreground blue -underline 1
3210 $ctext tag conf link -foreground blue -underline 1
3210 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3211 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3211 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3212 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3212 $ctext insert end "Parent:\t"
3213 $ctext insert end "Parent:\t"
3213 $ctext insert end $id [list link link0]
3214 $ctext insert end $id [list link link0]
3214 $ctext tag bind link0 <1> [list selbyid $id]
3215 $ctext tag bind link0 <1> [list selbyid $id]
3215 set info $commitinfo($id)
3216 set info $commitinfo($id)
3216 $ctext insert end "\n\t[lindex $info 0]\n"
3217 $ctext insert end "\n\t[lindex $info 0]\n"
3217 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
3218 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
3218 $ctext insert end "\tDate:\t[lindex $info 2]\n"
3219 $ctext insert end "\tDate:\t[lindex $info 2]\n"
3219 if {[info exists children($id)]} {
3220 if {[info exists children($id)]} {
3220 $ctext insert end "\nChildren:"
3221 $ctext insert end "\nChildren:"
3221 set i 0
3222 set i 0
3222 foreach child $children($id) {
3223 foreach child $children($id) {
3223 incr i
3224 incr i
3224 set info $commitinfo($child)
3225 set info $commitinfo($child)
3225 $ctext insert end "\n\t"
3226 $ctext insert end "\n\t"
3226 $ctext insert end $child [list link link$i]
3227 $ctext insert end $child [list link link$i]
3227 $ctext tag bind link$i <1> [list selbyid $child]
3228 $ctext tag bind link$i <1> [list selbyid $child]
3228 $ctext insert end "\n\t[lindex $info 0]"
3229 $ctext insert end "\n\t[lindex $info 0]"
3229 $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
3230 $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
3230 $ctext insert end "\n\tDate:\t[lindex $info 2]\n"
3231 $ctext insert end "\n\tDate:\t[lindex $info 2]\n"
3231 }
3232 }
3232 }
3233 }
3233 $ctext conf -state disabled
3234 $ctext conf -state disabled
3234
3235
3235 $cflist delete 0 end
3236 $cflist delete 0 end
3236 }
3237 }
3237
3238
3238 proc normalline {} {
3239 proc normalline {} {
3239 global thickerline
3240 global thickerline
3240 if {[info exists thickerline]} {
3241 if {[info exists thickerline]} {
3241 drawlines $thickerline 0
3242 drawlines $thickerline 0
3242 unset thickerline
3243 unset thickerline
3243 }
3244 }
3244 }
3245 }
3245
3246
3246 proc selbyid {id} {
3247 proc selbyid {id} {
3247 global idline
3248 global idline
3248 if {[info exists idline($id)]} {
3249 if {[info exists idline($id)]} {
3249 selectline $idline($id) 1
3250 selectline $idline($id) 1
3250 }
3251 }
3251 }
3252 }
3252
3253
3253 proc mstime {} {
3254 proc mstime {} {
3254 global startmstime
3255 global startmstime
3255 if {![info exists startmstime]} {
3256 if {![info exists startmstime]} {
3256 set startmstime [clock clicks -milliseconds]
3257 set startmstime [clock clicks -milliseconds]
3257 }
3258 }
3258 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
3259 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
3259 }
3260 }
3260
3261
3261 proc rowmenu {x y id} {
3262 proc rowmenu {x y id} {
3262 global rowctxmenu idline selectedline rowmenuid
3263 global rowctxmenu idline selectedline rowmenuid
3263
3264
3264 if {![info exists selectedline] || $idline($id) eq $selectedline} {
3265 if {![info exists selectedline] || $idline($id) eq $selectedline} {
3265 set state disabled
3266 set state disabled
3266 } else {
3267 } else {
3267 set state normal
3268 set state normal
3268 }
3269 }
3269 $rowctxmenu entryconfigure 0 -state $state
3270 $rowctxmenu entryconfigure 0 -state $state
3270 $rowctxmenu entryconfigure 1 -state $state
3271 $rowctxmenu entryconfigure 1 -state $state
3271 $rowctxmenu entryconfigure 2 -state $state
3272 $rowctxmenu entryconfigure 2 -state $state
3272 set rowmenuid $id
3273 set rowmenuid $id
3273 tk_popup $rowctxmenu $x $y
3274 tk_popup $rowctxmenu $x $y
3274 }
3275 }
3275
3276
3276 proc diffvssel {dirn} {
3277 proc diffvssel {dirn} {
3277 global rowmenuid selectedline lineid
3278 global rowmenuid selectedline lineid
3278
3279
3279 if {![info exists selectedline]} return
3280 if {![info exists selectedline]} return
3280 if {$dirn} {
3281 if {$dirn} {
3281 set oldid $lineid($selectedline)
3282 set oldid $lineid($selectedline)
3282 set newid $rowmenuid
3283 set newid $rowmenuid
3283 } else {
3284 } else {
3284 set oldid $rowmenuid
3285 set oldid $rowmenuid
3285 set newid $lineid($selectedline)
3286 set newid $lineid($selectedline)
3286 }
3287 }
3287 addtohistory [list doseldiff $oldid $newid]
3288 addtohistory [list doseldiff $oldid $newid]
3288 doseldiff $oldid $newid
3289 doseldiff $oldid $newid
3289 }
3290 }
3290
3291
3291 proc doseldiff {oldid newid} {
3292 proc doseldiff {oldid newid} {
3292 global ctext cflist
3293 global ctext cflist
3293 global commitinfo
3294 global commitinfo
3294
3295
3295 $ctext conf -state normal
3296 $ctext conf -state normal
3296 $ctext delete 0.0 end
3297 $ctext delete 0.0 end
3297 $ctext mark set fmark.0 0.0
3298 $ctext mark set fmark.0 0.0
3298 $ctext mark gravity fmark.0 left
3299 $ctext mark gravity fmark.0 left
3299 $cflist delete 0 end
3300 $cflist delete 0 end
3300 $cflist insert end "Top"
3301 $cflist insert end "Top"
3301 $ctext insert end "From "
3302 $ctext insert end "From "
3302 $ctext tag conf link -foreground blue -underline 1
3303 $ctext tag conf link -foreground blue -underline 1
3303 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3304 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3304 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3305 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3305 $ctext tag bind link0 <1> [list selbyid $oldid]
3306 $ctext tag bind link0 <1> [list selbyid $oldid]
3306 $ctext insert end $oldid [list link link0]
3307 $ctext insert end $oldid [list link link0]
3307 $ctext insert end "\n "
3308 $ctext insert end "\n "
3308 $ctext insert end [lindex $commitinfo($oldid) 0]
3309 $ctext insert end [lindex $commitinfo($oldid) 0]
3309 $ctext insert end "\n\nTo "
3310 $ctext insert end "\n\nTo "
3310 $ctext tag bind link1 <1> [list selbyid $newid]
3311 $ctext tag bind link1 <1> [list selbyid $newid]
3311 $ctext insert end $newid [list link link1]
3312 $ctext insert end $newid [list link link1]
3312 $ctext insert end "\n "
3313 $ctext insert end "\n "
3313 $ctext insert end [lindex $commitinfo($newid) 0]
3314 $ctext insert end [lindex $commitinfo($newid) 0]
3314 $ctext insert end "\n"
3315 $ctext insert end "\n"
3315 $ctext conf -state disabled
3316 $ctext conf -state disabled
3316 $ctext tag delete Comments
3317 $ctext tag delete Comments
3317 $ctext tag remove found 1.0 end
3318 $ctext tag remove found 1.0 end
3318 startdiff [list $newid $oldid]
3319 startdiff [list $newid $oldid]
3319 }
3320 }
3320
3321
3321 proc mkpatch {} {
3322 proc mkpatch {} {
3322 global rowmenuid currentid commitinfo patchtop patchnum
3323 global rowmenuid currentid commitinfo patchtop patchnum
3323
3324
3324 if {![info exists currentid]} return
3325 if {![info exists currentid]} return
3325 set oldid $currentid
3326 set oldid $currentid
3326 set oldhead [lindex $commitinfo($oldid) 0]
3327 set oldhead [lindex $commitinfo($oldid) 0]
3327 set newid $rowmenuid
3328 set newid $rowmenuid
3328 set newhead [lindex $commitinfo($newid) 0]
3329 set newhead [lindex $commitinfo($newid) 0]
3329 set top .patch
3330 set top .patch
3330 set patchtop $top
3331 set patchtop $top
3331 catch {destroy $top}
3332 catch {destroy $top}
3332 toplevel $top
3333 toplevel $top
3333 label $top.title -text "Generate patch"
3334 label $top.title -text "Generate patch"
3334 grid $top.title - -pady 10
3335 grid $top.title - -pady 10
3335 label $top.from -text "From:"
3336 label $top.from -text "From:"
3336 entry $top.fromsha1 -width 40 -relief flat
3337 entry $top.fromsha1 -width 40 -relief flat
3337 $top.fromsha1 insert 0 $oldid
3338 $top.fromsha1 insert 0 $oldid
3338 $top.fromsha1 conf -state readonly
3339 $top.fromsha1 conf -state readonly
3339 grid $top.from $top.fromsha1 -sticky w
3340 grid $top.from $top.fromsha1 -sticky w
3340 entry $top.fromhead -width 60 -relief flat
3341 entry $top.fromhead -width 60 -relief flat
3341 $top.fromhead insert 0 $oldhead
3342 $top.fromhead insert 0 $oldhead
3342 $top.fromhead conf -state readonly
3343 $top.fromhead conf -state readonly
3343 grid x $top.fromhead -sticky w
3344 grid x $top.fromhead -sticky w
3344 label $top.to -text "To:"
3345 label $top.to -text "To:"
3345 entry $top.tosha1 -width 40 -relief flat
3346 entry $top.tosha1 -width 40 -relief flat
3346 $top.tosha1 insert 0 $newid
3347 $top.tosha1 insert 0 $newid
3347 $top.tosha1 conf -state readonly
3348 $top.tosha1 conf -state readonly
3348 grid $top.to $top.tosha1 -sticky w
3349 grid $top.to $top.tosha1 -sticky w
3349 entry $top.tohead -width 60 -relief flat
3350 entry $top.tohead -width 60 -relief flat
3350 $top.tohead insert 0 $newhead
3351 $top.tohead insert 0 $newhead
3351 $top.tohead conf -state readonly
3352 $top.tohead conf -state readonly
3352 grid x $top.tohead -sticky w
3353 grid x $top.tohead -sticky w
3353 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
3354 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
3354 grid $top.rev x -pady 10
3355 grid $top.rev x -pady 10
3355 label $top.flab -text "Output file:"
3356 label $top.flab -text "Output file:"
3356 entry $top.fname -width 60
3357 entry $top.fname -width 60
3357 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
3358 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
3358 incr patchnum
3359 incr patchnum
3359 grid $top.flab $top.fname -sticky w
3360 grid $top.flab $top.fname -sticky w
3360 frame $top.buts
3361 frame $top.buts
3361 button $top.buts.gen -text "Generate" -command mkpatchgo
3362 button $top.buts.gen -text "Generate" -command mkpatchgo
3362 button $top.buts.can -text "Cancel" -command mkpatchcan
3363 button $top.buts.can -text "Cancel" -command mkpatchcan
3363 grid $top.buts.gen $top.buts.can
3364 grid $top.buts.gen $top.buts.can
3364 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3365 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3365 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3366 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3366 grid $top.buts - -pady 10 -sticky ew
3367 grid $top.buts - -pady 10 -sticky ew
3367 focus $top.fname
3368 focus $top.fname
3368 }
3369 }
3369
3370
3370 proc mkpatchrev {} {
3371 proc mkpatchrev {} {
3371 global patchtop
3372 global patchtop
3372
3373
3373 set oldid [$patchtop.fromsha1 get]
3374 set oldid [$patchtop.fromsha1 get]
3374 set oldhead [$patchtop.fromhead get]
3375 set oldhead [$patchtop.fromhead get]
3375 set newid [$patchtop.tosha1 get]
3376 set newid [$patchtop.tosha1 get]
3376 set newhead [$patchtop.tohead get]
3377 set newhead [$patchtop.tohead get]
3377 foreach e [list fromsha1 fromhead tosha1 tohead] \
3378 foreach e [list fromsha1 fromhead tosha1 tohead] \
3378 v [list $newid $newhead $oldid $oldhead] {
3379 v [list $newid $newhead $oldid $oldhead] {
3379 $patchtop.$e conf -state normal
3380 $patchtop.$e conf -state normal
3380 $patchtop.$e delete 0 end
3381 $patchtop.$e delete 0 end
3381 $patchtop.$e insert 0 $v
3382 $patchtop.$e insert 0 $v
3382 $patchtop.$e conf -state readonly
3383 $patchtop.$e conf -state readonly
3383 }
3384 }
3384 }
3385 }
3385
3386
3386 proc mkpatchgo {} {
3387 proc mkpatchgo {} {
3387 global patchtop
3388 global patchtop
3388
3389
3389 set oldid [$patchtop.fromsha1 get]
3390 set oldid [$patchtop.fromsha1 get]
3390 set newid [$patchtop.tosha1 get]
3391 set newid [$patchtop.tosha1 get]
3391 set fname [$patchtop.fname get]
3392 set fname [$patchtop.fname get]
3392 if {[catch {exec hg debug-diff-tree -p $oldid $newid >$fname &} err]} {
3393 if {[catch {exec hg debug-diff-tree -p $oldid $newid >$fname &} err]} {
3393 error_popup "Error creating patch: $err"
3394 error_popup "Error creating patch: $err"
3394 }
3395 }
3395 catch {destroy $patchtop}
3396 catch {destroy $patchtop}
3396 unset patchtop
3397 unset patchtop
3397 }
3398 }
3398
3399
3399 proc mkpatchcan {} {
3400 proc mkpatchcan {} {
3400 global patchtop
3401 global patchtop
3401
3402
3402 catch {destroy $patchtop}
3403 catch {destroy $patchtop}
3403 unset patchtop
3404 unset patchtop
3404 }
3405 }
3405
3406
3406 proc mktag {} {
3407 proc mktag {} {
3407 global rowmenuid mktagtop commitinfo
3408 global rowmenuid mktagtop commitinfo
3408
3409
3409 set top .maketag
3410 set top .maketag
3410 set mktagtop $top
3411 set mktagtop $top
3411 catch {destroy $top}
3412 catch {destroy $top}
3412 toplevel $top
3413 toplevel $top
3413 label $top.title -text "Create tag"
3414 label $top.title -text "Create tag"
3414 grid $top.title - -pady 10
3415 grid $top.title - -pady 10
3415 label $top.id -text "ID:"
3416 label $top.id -text "ID:"
3416 entry $top.sha1 -width 40 -relief flat
3417 entry $top.sha1 -width 40 -relief flat
3417 $top.sha1 insert 0 $rowmenuid
3418 $top.sha1 insert 0 $rowmenuid
3418 $top.sha1 conf -state readonly
3419 $top.sha1 conf -state readonly
3419 grid $top.id $top.sha1 -sticky w
3420 grid $top.id $top.sha1 -sticky w
3420 entry $top.head -width 60 -relief flat
3421 entry $top.head -width 60 -relief flat
3421 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3422 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3422 $top.head conf -state readonly
3423 $top.head conf -state readonly
3423 grid x $top.head -sticky w
3424 grid x $top.head -sticky w
3424 label $top.tlab -text "Tag name:"
3425 label $top.tlab -text "Tag name:"
3425 entry $top.tag -width 60
3426 entry $top.tag -width 60
3426 grid $top.tlab $top.tag -sticky w
3427 grid $top.tlab $top.tag -sticky w
3427 frame $top.buts
3428 frame $top.buts
3428 button $top.buts.gen -text "Create" -command mktaggo
3429 button $top.buts.gen -text "Create" -command mktaggo
3429 button $top.buts.can -text "Cancel" -command mktagcan
3430 button $top.buts.can -text "Cancel" -command mktagcan
3430 grid $top.buts.gen $top.buts.can
3431 grid $top.buts.gen $top.buts.can
3431 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3432 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3432 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3433 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3433 grid $top.buts - -pady 10 -sticky ew
3434 grid $top.buts - -pady 10 -sticky ew
3434 focus $top.tag
3435 focus $top.tag
3435 }
3436 }
3436
3437
3437 proc domktag {} {
3438 proc domktag {} {
3438 global mktagtop env tagids idtags
3439 global mktagtop env tagids idtags
3439
3440
3440 set id [$mktagtop.sha1 get]
3441 set id [$mktagtop.sha1 get]
3441 set tag [$mktagtop.tag get]
3442 set tag [$mktagtop.tag get]
3442 if {$tag == {}} {
3443 if {$tag == {}} {
3443 error_popup "No tag name specified"
3444 error_popup "No tag name specified"
3444 return
3445 return
3445 }
3446 }
3446 if {[info exists tagids($tag)]} {
3447 if {[info exists tagids($tag)]} {
3447 error_popup "Tag \"$tag\" already exists"
3448 error_popup "Tag \"$tag\" already exists"
3448 return
3449 return
3449 }
3450 }
3450 if {[catch {
3451 if {[catch {
3451 set out [exec hg tag -r $id $tag]
3452 set out [exec hg tag -r $id $tag]
3452 } err]} {
3453 } err]} {
3453 error_popup "Error creating tag: $err"
3454 error_popup "Error creating tag: $err"
3454 return
3455 return
3455 }
3456 }
3456
3457
3457 set tagids($tag) $id
3458 set tagids($tag) $id
3458 lappend idtags($id) $tag
3459 lappend idtags($id) $tag
3459 redrawtags $id
3460 redrawtags $id
3460 }
3461 }
3461
3462
3462 proc redrawtags {id} {
3463 proc redrawtags {id} {
3463 global canv linehtag idline idpos selectedline
3464 global canv linehtag idline idpos selectedline
3464
3465
3465 if {![info exists idline($id)]} return
3466 if {![info exists idline($id)]} return
3466 $canv delete tag.$id
3467 $canv delete tag.$id
3467 set xt [eval drawtags $id $idpos($id)]
3468 set xt [eval drawtags $id $idpos($id)]
3468 $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
3469 $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
3469 if {[info exists selectedline] && $selectedline == $idline($id)} {
3470 if {[info exists selectedline] && $selectedline == $idline($id)} {
3470 selectline $selectedline 0
3471 selectline $selectedline 0
3471 }
3472 }
3472 }
3473 }
3473
3474
3474 proc mktagcan {} {
3475 proc mktagcan {} {
3475 global mktagtop
3476 global mktagtop
3476
3477
3477 catch {destroy $mktagtop}
3478 catch {destroy $mktagtop}
3478 unset mktagtop
3479 unset mktagtop
3479 }
3480 }
3480
3481
3481 proc mktaggo {} {
3482 proc mktaggo {} {
3482 domktag
3483 domktag
3483 mktagcan
3484 mktagcan
3484 }
3485 }
3485
3486
3486 proc writecommit {} {
3487 proc writecommit {} {
3487 global rowmenuid wrcomtop commitinfo wrcomcmd
3488 global rowmenuid wrcomtop commitinfo wrcomcmd
3488
3489
3489 set top .writecommit
3490 set top .writecommit
3490 set wrcomtop $top
3491 set wrcomtop $top
3491 catch {destroy $top}
3492 catch {destroy $top}
3492 toplevel $top
3493 toplevel $top
3493 label $top.title -text "Write commit to file"
3494 label $top.title -text "Write commit to file"
3494 grid $top.title - -pady 10
3495 grid $top.title - -pady 10
3495 label $top.id -text "ID:"
3496 label $top.id -text "ID:"
3496 entry $top.sha1 -width 40 -relief flat
3497 entry $top.sha1 -width 40 -relief flat
3497 $top.sha1 insert 0 $rowmenuid
3498 $top.sha1 insert 0 $rowmenuid
3498 $top.sha1 conf -state readonly
3499 $top.sha1 conf -state readonly
3499 grid $top.id $top.sha1 -sticky w
3500 grid $top.id $top.sha1 -sticky w
3500 entry $top.head -width 60 -relief flat
3501 entry $top.head -width 60 -relief flat
3501 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3502 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3502 $top.head conf -state readonly
3503 $top.head conf -state readonly
3503 grid x $top.head -sticky w
3504 grid x $top.head -sticky w
3504 label $top.clab -text "Command:"
3505 label $top.clab -text "Command:"
3505 entry $top.cmd -width 60 -textvariable wrcomcmd
3506 entry $top.cmd -width 60 -textvariable wrcomcmd
3506 grid $top.clab $top.cmd -sticky w -pady 10
3507 grid $top.clab $top.cmd -sticky w -pady 10
3507 label $top.flab -text "Output file:"
3508 label $top.flab -text "Output file:"
3508 entry $top.fname -width 60
3509 entry $top.fname -width 60
3509 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
3510 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
3510 grid $top.flab $top.fname -sticky w
3511 grid $top.flab $top.fname -sticky w
3511 frame $top.buts
3512 frame $top.buts
3512 button $top.buts.gen -text "Write" -command wrcomgo
3513 button $top.buts.gen -text "Write" -command wrcomgo
3513 button $top.buts.can -text "Cancel" -command wrcomcan
3514 button $top.buts.can -text "Cancel" -command wrcomcan
3514 grid $top.buts.gen $top.buts.can
3515 grid $top.buts.gen $top.buts.can
3515 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3516 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3516 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3517 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3517 grid $top.buts - -pady 10 -sticky ew
3518 grid $top.buts - -pady 10 -sticky ew
3518 focus $top.fname
3519 focus $top.fname
3519 }
3520 }
3520
3521
3521 proc wrcomgo {} {
3522 proc wrcomgo {} {
3522 global wrcomtop
3523 global wrcomtop
3523
3524
3524 set id [$wrcomtop.sha1 get]
3525 set id [$wrcomtop.sha1 get]
3525 set cmd "echo $id | [$wrcomtop.cmd get]"
3526 set cmd "echo $id | [$wrcomtop.cmd get]"
3526 set fname [$wrcomtop.fname get]
3527 set fname [$wrcomtop.fname get]
3527 if {[catch {exec sh -c $cmd >$fname &} err]} {
3528 if {[catch {exec sh -c $cmd >$fname &} err]} {
3528 error_popup "Error writing commit: $err"
3529 error_popup "Error writing commit: $err"
3529 }
3530 }
3530 catch {destroy $wrcomtop}
3531 catch {destroy $wrcomtop}
3531 unset wrcomtop
3532 unset wrcomtop
3532 }
3533 }
3533
3534
3534 proc wrcomcan {} {
3535 proc wrcomcan {} {
3535 global wrcomtop
3536 global wrcomtop
3536
3537
3537 catch {destroy $wrcomtop}
3538 catch {destroy $wrcomtop}
3538 unset wrcomtop
3539 unset wrcomtop
3539 }
3540 }
3540
3541
3541 proc listrefs {id} {
3542 proc listrefs {id} {
3542 global idtags idheads idotherrefs
3543 global idtags idheads idotherrefs
3543
3544
3544 set x {}
3545 set x {}
3545 if {[info exists idtags($id)]} {
3546 if {[info exists idtags($id)]} {
3546 set x $idtags($id)
3547 set x $idtags($id)
3547 }
3548 }
3548 set y {}
3549 set y {}
3549 if {[info exists idheads($id)]} {
3550 if {[info exists idheads($id)]} {
3550 set y $idheads($id)
3551 set y $idheads($id)
3551 }
3552 }
3552 set z {}
3553 set z {}
3553 if {[info exists idotherrefs($id)]} {
3554 if {[info exists idotherrefs($id)]} {
3554 set z $idotherrefs($id)
3555 set z $idotherrefs($id)
3555 }
3556 }
3556 return [list $x $y $z]
3557 return [list $x $y $z]
3557 }
3558 }
3558
3559
3559 proc rereadrefs {} {
3560 proc rereadrefs {} {
3560 global idtags idheads idotherrefs
3561 global idtags idheads idotherrefs
3561 global tagids headids otherrefids
3562 global tagids headids otherrefids
3562
3563
3563 set refids [concat [array names idtags] \
3564 set refids [concat [array names idtags] \
3564 [array names idheads] [array names idotherrefs]]
3565 [array names idheads] [array names idotherrefs]]
3565 foreach id $refids {
3566 foreach id $refids {
3566 if {![info exists ref($id)]} {
3567 if {![info exists ref($id)]} {
3567 set ref($id) [listrefs $id]
3568 set ref($id) [listrefs $id]
3568 }
3569 }
3569 }
3570 }
3570 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
3571 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
3571 catch {unset $v}
3572 catch {unset $v}
3572 }
3573 }
3573 readrefs
3574 readrefs
3574 set refids [lsort -unique [concat $refids [array names idtags] \
3575 set refids [lsort -unique [concat $refids [array names idtags] \
3575 [array names idheads] [array names idotherrefs]]]
3576 [array names idheads] [array names idotherrefs]]]
3576 foreach id $refids {
3577 foreach id $refids {
3577 set v [listrefs $id]
3578 set v [listrefs $id]
3578 if {![info exists ref($id)] || $ref($id) != $v} {
3579 if {![info exists ref($id)] || $ref($id) != $v} {
3579 redrawtags $id
3580 redrawtags $id
3580 }
3581 }
3581 }
3582 }
3582 }
3583 }
3583
3584
3584 proc showtag {tag isnew} {
3585 proc showtag {tag isnew} {
3585 global ctext cflist tagcontents tagids linknum
3586 global ctext cflist tagcontents tagids linknum
3586
3587
3587 if {$isnew} {
3588 if {$isnew} {
3588 addtohistory [list showtag $tag 0]
3589 addtohistory [list showtag $tag 0]
3589 }
3590 }
3590 $ctext conf -state normal
3591 $ctext conf -state normal
3591 $ctext delete 0.0 end
3592 $ctext delete 0.0 end
3592 set linknum 0
3593 set linknum 0
3593 if {[info exists tagcontents($tag)]} {
3594 if {[info exists tagcontents($tag)]} {
3594 set text $tagcontents($tag)
3595 set text $tagcontents($tag)
3595 } else {
3596 } else {
3596 set text "Tag: $tag\nId: $tagids($tag)"
3597 set text "Tag: $tag\nId: $tagids($tag)"
3597 }
3598 }
3598 appendwithlinks $text
3599 appendwithlinks $text
3599 $ctext conf -state disabled
3600 $ctext conf -state disabled
3600 $cflist delete 0 end
3601 $cflist delete 0 end
3601 }
3602 }
3602
3603
3603 proc doquit {} {
3604 proc doquit {} {
3604 global stopped
3605 global stopped
3605 set stopped 100
3606 set stopped 100
3606 destroy .
3607 destroy .
3607 }
3608 }
3608
3609
3609 # defaults...
3610 # defaults...
3610 set datemode 0
3611 set datemode 0
3611 set boldnames 0
3612 set boldnames 0
3612 set diffopts "-U 5 -p"
3613 set diffopts "-U 5 -p"
3613 set wrcomcmd "hg debug-diff-tree --stdin -p --pretty"
3614 set wrcomcmd "hg debug-diff-tree --stdin -p --pretty"
3614
3615
3615 set mainfont {Helvetica 9}
3616 set mainfont {Helvetica 9}
3616 set textfont {Courier 9}
3617 set textfont {Courier 9}
3617 set findmergefiles 0
3618 set findmergefiles 0
3618 set gaudydiff 0
3619 set gaudydiff 0
3619 set maxgraphpct 50
3620 set maxgraphpct 50
3620 set maxwidth 16
3621 set maxwidth 16
3621
3622
3622 set colors {green red blue magenta darkgrey brown orange}
3623 set colors {green red blue magenta darkgrey brown orange}
3623
3624
3624 catch {source ~/.gitk}
3625 catch {source ~/.gitk}
3625
3626
3626 set namefont $mainfont
3627 set namefont $mainfont
3627 if {$boldnames} {
3628 if {$boldnames} {
3628 lappend namefont bold
3629 lappend namefont bold
3629 }
3630 }
3630
3631
3631 set revtreeargs {}
3632 set revtreeargs {}
3632 foreach arg $argv {
3633 foreach arg $argv {
3633 switch -regexp -- $arg {
3634 switch -regexp -- $arg {
3634 "^$" { }
3635 "^$" { }
3635 "^-b" { set boldnames 1 }
3636 "^-b" { set boldnames 1 }
3636 "^-d" { set datemode 1 }
3637 "^-d" { set datemode 1 }
3637 default {
3638 default {
3638 lappend revtreeargs $arg
3639 lappend revtreeargs $arg
3639 }
3640 }
3640 }
3641 }
3641 }
3642 }
3642
3643
3643 set history {}
3644 set history {}
3644 set historyindex 0
3645 set historyindex 0
3645
3646
3646 set stopped 0
3647 set stopped 0
3647 set redisplaying 0
3648 set redisplaying 0
3648 set stuffsaved 0
3649 set stuffsaved 0
3649 set patchnum 0
3650 set patchnum 0
3650 setcoords
3651 setcoords
3651 makewindow
3652 makewindow
3652 readrefs
3653 readrefs
3653 getcommits $revtreeargs
3654 getcommits $revtreeargs
@@ -1,136 +1,136 b''
1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
2 <html>
2 <html>
3 <head>
3 <head>
4 <title>Mercurial for Windows</title>
4 <title>Mercurial for Windows</title>
5 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
5 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
6 <style type="text/css">
6 <style type="text/css">
7 <!--
7 <!--
8 .indented
8 .indented
9 {
9 {
10 padding-left: 10pt;
10 padding-left: 10pt;
11 }
11 }
12 -->
12 -->
13 </style>
13 </style>
14 </head>
14 </head>
15
15
16 <body>
16 <body>
17 <h1>Mercurial version 0.8.1 for Windows</h1>
17 <h1>Mercurial version 0.9 for Windows</h1>
18
18
19 <p>Welcome to Mercurial for Windows!</p>
19 <p>Welcome to Mercurial for Windows!</p>
20
20
21 <p>Mercurial is a command-line application. You must run it from
21 <p>Mercurial is a command-line application. You must run it from
22 the Windows command prompt (or if you're hard core, a <a
22 the Windows command prompt (or if you're hard core, a <a
23 href="http://www.mingw.org/">MinGW</a> shell).</p>
23 href="http://www.mingw.org/">MinGW</a> shell).</p>
24
24
25 <p><div class="indented"><i>Note: the standard <a
25 <p><div class="indented"><i>Note: the standard <a
26 href="http://www.mingw.org/">MinGW</a> msys startup script uses
26 href="http://www.mingw.org/">MinGW</a> msys startup script uses
27 rxvt which has problems setting up standard input and output.
27 rxvt which has problems setting up standard input and output.
28 Running bash directly works correctly.</i></div>
28 Running bash directly works correctly.</i></div>
29
29
30 <p>For documentation, please visit the <a
30 <p>For documentation, please visit the <a
31 href="http://www.selenic.com/mercurial">Mercurial web site</a>.</p>
31 href="http://www.selenic.com/mercurial">Mercurial web site</a>.</p>
32
32
33 <p>By default, Mercurial installs to <tt>C:\Mercurial</tt>. The
33 <p>By default, Mercurial installs to <tt>C:\Mercurial</tt>. The
34 Mercurial command is called <tt>hg.exe</tt>. To run this
34 Mercurial command is called <tt>hg.exe</tt>. To run this
35 command, the install directory must be in your search path.</p>
35 command, the install directory must be in your search path.</p>
36
36
37 <h2>Setting your search path temporarily</h2>
37 <h2>Setting your search path temporarily</h2>
38
38
39 <p>To set your search path temporarily, type the following into a
39 <p>To set your search path temporarily, type the following into a
40 command prompt window:</p>
40 command prompt window:</p>
41
41
42 <pre>
42 <pre>
43 set PATH=C:\Mercurial;%PATH%
43 set PATH=C:\Mercurial;%PATH%
44 </pre>
44 </pre>
45
45
46 <h2>Setting your search path permanently</h2>
46 <h2>Setting your search path permanently</h2>
47
47
48 <p>To set your search path permanently, perform the following
48 <p>To set your search path permanently, perform the following
49 steps. These instructions are for Windows NT, 2000 and XP.</p>
49 steps. These instructions are for Windows NT, 2000 and XP.</p>
50
50
51 <ol>
51 <ol>
52 <li>Open the Control Panel. Under Windows XP, select the
52 <li>Open the Control Panel. Under Windows XP, select the
53 "Classic View".</li>
53 "Classic View".</li>
54
54
55 <li>Double-click on the "System" control panel.</li>
55 <li>Double-click on the "System" control panel.</li>
56
56
57 <li>Click on the "Advanced" tab.</li>
57 <li>Click on the "Advanced" tab.</li>
58
58
59 <li>Click on "Environment Variables". You'll find this near the
59 <li>Click on "Environment Variables". You'll find this near the
60 bottom of the window.</li>
60 bottom of the window.</li>
61
61
62 <li>Under "System variables", you will see "Path". Double-click
62 <li>Under "System variables", you will see "Path". Double-click
63 it.</li>
63 it.</li>
64
64
65 <li>Edit "Variable value". Each path element is separated by a
65 <li>Edit "Variable value". Each path element is separated by a
66 semicolon (";") character. Append a semicolon to the end of the
66 semicolon (";") character. Append a semicolon to the end of the
67 list, followed by the path where you installed Mercurial
67 list, followed by the path where you installed Mercurial
68 (e.g. <tt>C:\Mercurial</tt>).</li>
68 (e.g. <tt>C:\Mercurial</tt>).</li>
69
69
70 <li>Click on the various "OK" buttons until you've completely
70 <li>Click on the various "OK" buttons until you've completely
71 exited from the System control panel.</li>
71 exited from the System control panel.</li>
72
72
73 <li>Log out and log back in, or restart your system.</li>
73 <li>Log out and log back in, or restart your system.</li>
74
74
75 <li>The next time you run the Windows command prompt, you will be
75 <li>The next time you run the Windows command prompt, you will be
76 able to run the <tt>hg</tt> command without any special
76 able to run the <tt>hg</tt> command without any special
77 help.</li>
77 help.</li>
78 </ol>
78 </ol>
79
79
80 <h1>Testing Mercurial after you've installed it</h1>
80 <h1>Testing Mercurial after you've installed it</h1>
81
81
82 <p>The easiest way to check that Mercurial is installed properly is to
82 <p>The easiest way to check that Mercurial is installed properly is to
83 just type the following at the command prompt:</p>
83 just type the following at the command prompt:</p>
84
84
85 <pre>
85 <pre>
86 hg
86 hg
87 </pre>
87 </pre>
88
88
89 <p>This command should print a useful help message. If it does,
89 <p>This command should print a useful help message. If it does,
90 other Mercurial commands should work fine for you.</p>
90 other Mercurial commands should work fine for you.</p>
91
91
92 <h1>Reporting problems</h1>
92 <h1>Reporting problems</h1>
93
93
94 <p>Before you report any problems, please consult the <a
94 <p>Before you report any problems, please consult the <a
95 href="http://www.selenic.com/mercurial">Mercurial web site</a> and
95 href="http://www.selenic.com/mercurial">Mercurial web site</a> and
96 see if your question is already in our list of <a
96 see if your question is already in our list of <a
97 href="http://www.selenic.com/mercurial/wiki/index.cgi/FAQ">Frequently
97 href="http://www.selenic.com/mercurial/wiki/index.cgi/FAQ">Frequently
98 Answered Questions</a> (the "FAQ").
98 Answered Questions</a> (the "FAQ").
99
99
100 <p>If you cannot find an answer to your question, please feel
100 <p>If you cannot find an answer to your question, please feel
101 free to send mail to the Mercurial mailing list, at <a
101 free to send mail to the Mercurial mailing list, at <a
102 href="mailto:mercurial@selenic.com">mercurial@selenic.com</a>.
102 href="mailto:mercurial@selenic.com">mercurial@selenic.com</a>.
103 <b>Remember</b>, the more useful information you include in your
103 <b>Remember</b>, the more useful information you include in your
104 report, the easier it will be for us to help you!</p>
104 report, the easier it will be for us to help you!</p>
105
105
106 <p>If you are IRC-savvy, that's usually the fastest way to get
106 <p>If you are IRC-savvy, that's usually the fastest way to get
107 help. Go to <tt>#mercurial</tt> on
107 help. Go to <tt>#mercurial</tt> on
108 <tt>irc.freenode.net</tt>.</p>
108 <tt>irc.freenode.net</tt>.</p>
109
109
110 <h1>Author and copyright information</h1>
110 <h1>Author and copyright information</h1>
111
111
112 <p>Mercurial was written by <a href="http://www.selenic.com">Matt
112 <p>Mercurial was written by <a href="http://www.selenic.com">Matt
113 Mackall</a>, and is maintained by Matt and a team of
113 Mackall</a>, and is maintained by Matt and a team of
114 volunteers.</p>
114 volunteers.</p>
115
115
116 <p>The Windows installer was written by <a
116 <p>The Windows installer was written by <a
117 href="http://www.serpentine.com/blog">Bryan
117 href="http://www.serpentine.com/blog">Bryan
118 O'Sullivan</a>.</p>
118 O'Sullivan</a>.</p>
119
119
120 <p>Mercurial is Copyright 2005, 2006 Matt Mackall and others. See the
120 <p>Mercurial is Copyright 2005, 2006 Matt Mackall and others. See the
121 <tt>Contributors.txt</tt> file for a list of contributors.</p>
121 <tt>Contributors.txt</tt> file for a list of contributors.</p>
122
122
123 <p>Mercurial is free software; you can redistribute it and/or
123 <p>Mercurial is free software; you can redistribute it and/or
124 modify it under the terms of the <a
124 modify it under the terms of the <a
125 href="http://www.gnu.org/copyleft/gpl.html">GNU General Public
125 href="http://www.gnu.org/copyleft/gpl.html">GNU General Public
126 License</a> as published by the Free Software Foundation; either
126 License</a> as published by the Free Software Foundation; either
127 version 2 of the License, or (at your option) any later
127 version 2 of the License, or (at your option) any later
128 version.</p>
128 version.</p>
129
129
130 <p>Mercurial is distributed in the hope that it will be useful,
130 <p>Mercurial is distributed in the hope that it will be useful,
131 but <b>without any warranty</b>; without even the implied
131 but <b>without any warranty</b>; without even the implied
132 warranty of <b>merchantability</b> or <b>fitness for a
132 warranty of <b>merchantability</b> or <b>fitness for a
133 particular purpose</b>. See the GNU General Public License for
133 particular purpose</b>. See the GNU General Public License for
134 more details.</p>
134 more details.</p>
135 </body>
135 </body>
136 </html>
136 </html>
@@ -1,57 +1,57 b''
1 ; Script generated by the Inno Setup Script Wizard.
1 ; Script generated by the Inno Setup Script Wizard.
2 ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
2 ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
3
3
4 [Setup]
4 [Setup]
5 AppCopyright=Copyright 2005, 2006 Matt Mackall and others
5 AppCopyright=Copyright 2005, 2006 Matt Mackall and others
6 AppName=Mercurial
6 AppName=Mercurial
7 AppVerName=Mercurial version 0.8.1
7 AppVerName=Mercurial version 0.9
8 InfoAfterFile=contrib/win32/postinstall.txt
8 InfoAfterFile=contrib/win32/postinstall.txt
9 LicenseFile=COPYING
9 LicenseFile=COPYING
10 ShowLanguageDialog=yes
10 ShowLanguageDialog=yes
11 AppPublisher=Matt Mackall and others
11 AppPublisher=Matt Mackall and others
12 AppPublisherURL=http://www.selenic.com/mercurial
12 AppPublisherURL=http://www.selenic.com/mercurial
13 AppSupportURL=http://www.selenic.com/mercurial
13 AppSupportURL=http://www.selenic.com/mercurial
14 AppUpdatesURL=http://www.selenic.com/mercurial
14 AppUpdatesURL=http://www.selenic.com/mercurial
15 AppID={{4B95A5F1-EF59-4B08-BED8-C891C46121B3}
15 AppID={{4B95A5F1-EF59-4B08-BED8-C891C46121B3}
16 AppContact=mercurial@selenic.com
16 AppContact=mercurial@selenic.com
17 OutputBaseFilename=Mercurial-0.8.1
17 OutputBaseFilename=Mercurial-0.9
18 DefaultDirName={sd}\Mercurial
18 DefaultDirName={sd}\Mercurial
19 SourceDir=C:\hg\hg-release
19 SourceDir=C:\hg\hg-release
20 VersionInfoVersion=0.8.1
20 VersionInfoVersion=0.9
21 VersionInfoDescription=Mercurial distributed SCM
21 VersionInfoDescription=Mercurial distributed SCM
22 VersionInfoCopyright=Copyright 2005, 2006 Matt Mackall and others
22 VersionInfoCopyright=Copyright 2005, 2006 Matt Mackall and others
23 VersionInfoCompany=Matt Mackall and others
23 VersionInfoCompany=Matt Mackall and others
24 InternalCompressLevel=max
24 InternalCompressLevel=max
25 SolidCompression=true
25 SolidCompression=true
26 SetupIconFile=contrib\favicon.ico
26 SetupIconFile=contrib\favicon.ico
27 AllowNoIcons=true
27 AllowNoIcons=true
28 DefaultGroupName=Mercurial
28 DefaultGroupName=Mercurial
29
29
30 [Files]
30 [Files]
31 Source: ..\..\msys\1.0\bin\patch.exe; DestDir: {app}
31 Source: ..\..\msys\1.0\bin\patch.exe; DestDir: {app}
32 Source: contrib\mercurial.el; DestDir: {app}/Contrib
32 Source: contrib\mercurial.el; DestDir: {app}/Contrib
33 Source: contrib\win32\ReadMe.html; DestDir: {app}; Flags: isreadme
33 Source: contrib\win32\ReadMe.html; DestDir: {app}; Flags: isreadme
34 Source: contrib\win32\mercurial.ini; DestDir: {app}; DestName: Mercurial.ini; Flags: confirmoverwrite
34 Source: contrib\win32\mercurial.ini; DestDir: {app}; DestName: Mercurial.ini; Flags: confirmoverwrite
35 Source: contrib\win32\postinstall.txt; DestDir: {app}; DestName: ReleaseNotes.txt
35 Source: contrib\win32\postinstall.txt; DestDir: {app}; DestName: ReleaseNotes.txt
36 Source: dist\hg.exe; DestDir: {app}
36 Source: dist\hg.exe; DestDir: {app}
37 Source: dist\library.zip; DestDir: {app}
37 Source: dist\library.zip; DestDir: {app}
38 Source: dist\mfc71.dll; DestDir: {sys}; Flags: sharedfile uninsnosharedfileprompt
38 Source: dist\mfc71.dll; DestDir: {sys}; Flags: sharedfile uninsnosharedfileprompt
39 Source: dist\msvcr71.dll; DestDir: {sys}; Flags: sharedfile uninsnosharedfileprompt
39 Source: dist\msvcr71.dll; DestDir: {sys}; Flags: sharedfile uninsnosharedfileprompt
40 Source: dist\w9xpopen.exe; DestDir: {app}
40 Source: dist\w9xpopen.exe; DestDir: {app}
41 Source: doc\*.txt; DestDir: {app}\Docs
41 Source: doc\*.txt; DestDir: {app}\Docs
42 Source: templates\*.*; DestDir: {app}\Templates; Flags: recursesubdirs createallsubdirs
42 Source: templates\*.*; DestDir: {app}\Templates; Flags: recursesubdirs createallsubdirs
43 Source: CONTRIBUTORS; DestDir: {app}; DestName: Contributors.txt
43 Source: CONTRIBUTORS; DestDir: {app}; DestName: Contributors.txt
44 Source: COPYING; DestDir: {app}; DestName: Copying.txt
44 Source: COPYING; DestDir: {app}; DestName: Copying.txt
45 Source: comparison.txt; DestDir: {app}\Docs; DestName: Comparison.txt
45 Source: comparison.txt; DestDir: {app}\Docs; DestName: Comparison.txt
46 Source: notes.txt; DestDir: {app}\Docs; DestName: DesignNotes.txt
46 Source: notes.txt; DestDir: {app}\Docs; DestName: DesignNotes.txt
47
47
48 [INI]
48 [INI]
49 Filename: {app}\Mercurial.url; Section: InternetShortcut; Key: URL; String: http://www.selenic.com/mercurial/
49 Filename: {app}\Mercurial.url; Section: InternetShortcut; Key: URL; String: http://www.selenic.com/mercurial/
50
50
51 [UninstallDelete]
51 [UninstallDelete]
52 Type: files; Name: {app}\Mercurial.url
52 Type: files; Name: {app}\Mercurial.url
53
53
54 [Icons]
54 [Icons]
55 Name: {group}\Uninstall Mercurial; Filename: {uninstallexe}
55 Name: {group}\Uninstall Mercurial; Filename: {uninstallexe}
56 Name: {group}\Mercurial Command Reference; Filename: {app}\Docs\hg.1.txt
56 Name: {group}\Mercurial Command Reference; Filename: {app}\Docs\hg.1.txt
57 Name: {group}\Mercurial Web Site; Filename: {app}\Mercurial.url
57 Name: {group}\Mercurial Web Site; Filename: {app}\Mercurial.url
@@ -1,75 +1,112 b''
1 Welcome to Mercurial for Windows!
1 Welcome to Mercurial for Windows!
2 ---------------------------------
2 ---------------------------------
3
3
4 For configuration and usage directions, please read the ReadMe.html
4 For configuration and usage directions, please read the ReadMe.html
5 file that comes with this package.
5 file that comes with this package.
6
6
7
8 Release Notes
7 Release Notes
9 -------------
8 -------------
10
9
10 2006-05-10 v0.9
11
12 * Major changes between Mercurial 0.8.1 and 0.9:
13
14 - The repository file format has been improved.
15 - This has resulted in an average 40% reduction in disk space usage.
16 - The new format (called RevlogNG) is now the default.
17 - Mercurial works perfectly with both the old and new repository
18 file formats. It can transfer changes transparently between
19 repositories of either format.
20 - To use the new repository format, simply use `hg clone --pull` to
21 clone an existing repository.
22 - Note: Versions 0.8.1 and earlier of Mercurial cannot read
23 RevlogNG repositories directly, but they can `clone`, `pull`
24 from, and `push` to servers that are serving RevlogNG
25 repositories.
26 - Memory usage has been improved by over 50% for many common operations.
27 - Substantial performance improvements on large repositories.
28 - New commands:
29 - 'archive' - generate a directory tree snapshot, tarball, or zip
30 file of a revision
31 - Deprecated commands:
32 - 'addremove' - replaced by 'add' and 'remove --after'
33 - 'forget' - replaced by 'revert'
34 - 'undo' - replaced by 'rollback'
35 - New extensions:
36 - Bugzilla integration hook
37 - Email notification hook
38 - Nested repositories are now supported. Mercurial will not recurse
39 into a subdirectory that contains a '.hg' directory. It is treated
40 as a separate repository.
41 - The standalone web server, 'hg serve', is now threaded, so it can
42 talk to multiple clients at a time.
43 - The web server can now display a "message of the day".
44 - Support added for hooks written in Python.
45 - Many improvements and clarifications to built-in help.
46
47
11 2006-04-07 v0.8.1
48 2006-04-07 v0.8.1
12
49
13 * Major changes from 0.8 to 0.8.1:
50 * Major changes from 0.8 to 0.8.1:
14
51
15 - new extensions:
52 - new extensions:
16 mq (manage a queue of patches, like quilt only better)
53 mq (manage a queue of patches, like quilt only better)
17 email (send changes as series of email patches)
54 email (send changes as series of email patches)
18 - new command: merge (replaces "update -m")
55 - new command: merge (replaces "update -m")
19 - improved commands: log (--limit option added), pull/push ("-r" works
56 - improved commands: log (--limit option added), pull/push ("-r" works
20 on specific revisions), revert (rewritten, much better)
57 on specific revisions), revert (rewritten, much better)
21 - comprehensive hook support
58 - comprehensive hook support
22 - output templating added, supporting e.g. GNU changelog style
59 - output templating added, supporting e.g. GNU changelog style
23 - Windows, Mac OS X: prebuilt binary packages, better support
60 - Windows, Mac OS X: prebuilt binary packages, better support
24 - many reliability, performance, and memory usage improvements
61 - many reliability, performance, and memory usage improvements
25
62
26
63
27 2006-01-29 v0.8
64 2006-01-29 v0.8
28
65
29 * Upgrade notes:
66 * Upgrade notes:
30
67
31 - diff and status command are now repo-wide by default
68 - diff and status command are now repo-wide by default
32 (use 'hg diff .' for the old behavior)
69 (use 'hg diff .' for the old behavior)
33 - GPG signing is now done with the gpg extension
70 - GPG signing is now done with the gpg extension
34 - the --text option for commit, rawcommit, and tag has been removed
71 - the --text option for commit, rawcommit, and tag has been removed
35 - the copy/rename --parents option has been removed
72 - the copy/rename --parents option has been removed
36
73
37 * Major changes from 0.7 to 0.8:
74 * Major changes from 0.7 to 0.8:
38
75
39 - faster status, diff, and commit
76 - faster status, diff, and commit
40 - reduced memory usage for push and pull
77 - reduced memory usage for push and pull
41 - improved extension API
78 - improved extension API
42 - new bisect, gpg, hgk, and win32text extensions
79 - new bisect, gpg, hgk, and win32text extensions
43 - short URLs, binary file handling, and optional gitweb skin for hgweb
80 - short URLs, binary file handling, and optional gitweb skin for hgweb
44 - numerous new command options including log --keyword and pull --rev
81 - numerous new command options including log --keyword and pull --rev
45 - improved hooks and file filtering
82 - improved hooks and file filtering
46
83
47
84
48 2005-09-21 v0.7 with modifications
85 2005-09-21 v0.7 with modifications
49
86
50 * New INI files have been added to control Mercurial's behaviour:
87 * New INI files have been added to control Mercurial's behaviour:
51
88
52 System-wide - C:\Mercurial\Mercurial.ini
89 System-wide - C:\Mercurial\Mercurial.ini
53 Per-user - C:\Documents and Settings\USERNAME\Mercurial.ini
90 Per-user - C:\Documents and Settings\USERNAME\Mercurial.ini
54
91
55 A default version of the system-wide INI file is installed with
92 A default version of the system-wide INI file is installed with
56 Mercurial. No per-user INI file is installed, but it will be
93 Mercurial. No per-user INI file is installed, but it will be
57 honoured if you create one.
94 honoured if you create one.
58
95
59 * Windows line endings are now handled automatically and correctly by
96 * Windows line endings are now handled automatically and correctly by
60 the update and commit commands. See the INI file for how to
97 the update and commit commands. See the INI file for how to
61 customise this behaviour.
98 customise this behaviour.
62
99
63 * NOTE: Much of the rest of the Mercurial code does not handle Windows
100 * NOTE: Much of the rest of the Mercurial code does not handle Windows
64 line endings properly. Accordingly, the output of the diff command,
101 line endings properly. Accordingly, the output of the diff command,
65 for example, will appear huge until I fix this.
102 for example, will appear huge until I fix this.
66
103
67 * Packaged text files now have correct Windows line endings.
104 * Packaged text files now have correct Windows line endings.
68
105
69
106
70 2005-09-21 v0.7 with modifications
107 2005-09-21 v0.7 with modifications
71
108
72 * This is the first standalone release of Mercurial for Windows.
109 * This is the first standalone release of Mercurial for Windows.
73
110
74 * I believe it to be mostly functional, with one exception: there is
111 * I believe it to be mostly functional, with one exception: there is
75 no support yet for DOS <-> Unix line ending conversion.
112 no support yet for DOS <-> Unix line ending conversion.
@@ -1,381 +1,412 b''
1 HGRC(5)
1 HGRC(5)
2 =======
2 =======
3 Bryan O'Sullivan <bos@serpentine.com>
3 Bryan O'Sullivan <bos@serpentine.com>
4
4
5 NAME
5 NAME
6 ----
6 ----
7 hgrc - configuration files for Mercurial
7 hgrc - configuration files for Mercurial
8
8
9 SYNOPSIS
9 SYNOPSIS
10 --------
10 --------
11
11
12 The Mercurial system uses a set of configuration files to control
12 The Mercurial system uses a set of configuration files to control
13 aspects of its behaviour.
13 aspects of its behaviour.
14
14
15 FILES
15 FILES
16 -----
16 -----
17
17
18 Mercurial reads configuration data from several files, if they exist.
18 Mercurial reads configuration data from several files, if they exist.
19 The names of these files depend on the system on which Mercurial is
19 The names of these files depend on the system on which Mercurial is
20 installed.
20 installed.
21
21
22 (Unix) <install-root>/etc/mercurial/hgrc.d/*.rc::
22 (Unix) <install-root>/etc/mercurial/hgrc.d/*.rc::
23 (Unix) <install-root>/etc/mercurial/hgrc::
23 (Unix) <install-root>/etc/mercurial/hgrc::
24 Per-installation configuration files, searched for in the
24 Per-installation configuration files, searched for in the
25 directory where Mercurial is installed. For example, if installed
25 directory where Mercurial is installed. For example, if installed
26 in /shared/tools, Mercurial will look in
26 in /shared/tools, Mercurial will look in
27 /shared/tools/etc/mercurial/hgrc. Options in these files apply to
27 /shared/tools/etc/mercurial/hgrc. Options in these files apply to
28 all Mercurial commands executed by any user in any directory.
28 all Mercurial commands executed by any user in any directory.
29
29
30 (Unix) /etc/mercurial/hgrc.d/*.rc::
30 (Unix) /etc/mercurial/hgrc.d/*.rc::
31 (Unix) /etc/mercurial/hgrc::
31 (Unix) /etc/mercurial/hgrc::
32 (Windows) C:\Mercurial\Mercurial.ini::
32 (Windows) C:\Mercurial\Mercurial.ini::
33 Per-system configuration files, for the system on which Mercurial
33 Per-system configuration files, for the system on which Mercurial
34 is running. Options in these files apply to all Mercurial
34 is running. Options in these files apply to all Mercurial
35 commands executed by any user in any directory. Options in these
35 commands executed by any user in any directory. Options in these
36 files override per-installation options.
36 files override per-installation options.
37
37
38 (Unix) $HOME/.hgrc::
38 (Unix) $HOME/.hgrc::
39 (Windows) C:\Documents and Settings\USERNAME\Mercurial.ini
39 (Windows) C:\Documents and Settings\USERNAME\Mercurial.ini
40 Per-user configuration file, for the user running Mercurial.
40 Per-user configuration file, for the user running Mercurial.
41 Options in this file apply to all Mercurial commands executed by
41 Options in this file apply to all Mercurial commands executed by
42 any user in any directory. Options in this file override
42 any user in any directory. Options in this file override
43 per-installation and per-system options.
43 per-installation and per-system options.
44
44
45 (Unix, Windows) <repo>/.hg/hgrc::
45 (Unix, Windows) <repo>/.hg/hgrc::
46 Per-repository configuration options that only apply in a
46 Per-repository configuration options that only apply in a
47 particular repository. This file is not version-controlled, and
47 particular repository. This file is not version-controlled, and
48 will not get transferred during a "clone" operation. Options in
48 will not get transferred during a "clone" operation. Options in
49 this file override options in all other configuration files.
49 this file override options in all other configuration files.
50
50
51 SYNTAX
51 SYNTAX
52 ------
52 ------
53
53
54 A configuration file consists of sections, led by a "[section]" header
54 A configuration file consists of sections, led by a "[section]" header
55 and followed by "name: value" entries; "name=value" is also accepted.
55 and followed by "name: value" entries; "name=value" is also accepted.
56
56
57 [spam]
57 [spam]
58 eggs=ham
58 eggs=ham
59 green=
59 green=
60 eggs
60 eggs
61
61
62 Each line contains one entry. If the lines that follow are indented,
62 Each line contains one entry. If the lines that follow are indented,
63 they are treated as continuations of that entry.
63 they are treated as continuations of that entry.
64
64
65 Leading whitespace is removed from values. Empty lines are skipped.
65 Leading whitespace is removed from values. Empty lines are skipped.
66
66
67 The optional values can contain format strings which refer to other
67 The optional values can contain format strings which refer to other
68 values in the same section, or values in a special DEFAULT section.
68 values in the same section, or values in a special DEFAULT section.
69
69
70 Lines beginning with "#" or ";" are ignored and may be used to provide
70 Lines beginning with "#" or ";" are ignored and may be used to provide
71 comments.
71 comments.
72
72
73 SECTIONS
73 SECTIONS
74 --------
74 --------
75
75
76 This section describes the different sections that may appear in a
76 This section describes the different sections that may appear in a
77 Mercurial "hgrc" file, the purpose of each section, its possible
77 Mercurial "hgrc" file, the purpose of each section, its possible
78 keys, and their possible values.
78 keys, and their possible values.
79
79
80 decode/encode::
80 decode/encode::
81 Filters for transforming files on checkout/checkin. This would
81 Filters for transforming files on checkout/checkin. This would
82 typically be used for newline processing or other
82 typically be used for newline processing or other
83 localization/canonicalization of files.
83 localization/canonicalization of files.
84
84
85 Filters consist of a filter pattern followed by a filter command.
85 Filters consist of a filter pattern followed by a filter command.
86 Filter patterns are globs by default, rooted at the repository
86 Filter patterns are globs by default, rooted at the repository
87 root. For example, to match any file ending in ".txt" in the root
87 root. For example, to match any file ending in ".txt" in the root
88 directory only, use the pattern "*.txt". To match any file ending
88 directory only, use the pattern "*.txt". To match any file ending
89 in ".c" anywhere in the repository, use the pattern "**.c".
89 in ".c" anywhere in the repository, use the pattern "**.c".
90
90
91 The filter command can start with a specifier, either "pipe:" or
91 The filter command can start with a specifier, either "pipe:" or
92 "tempfile:". If no specifier is given, "pipe:" is used by default.
92 "tempfile:". If no specifier is given, "pipe:" is used by default.
93
93
94 A "pipe:" command must accept data on stdin and return the
94 A "pipe:" command must accept data on stdin and return the
95 transformed data on stdout.
95 transformed data on stdout.
96
96
97 Pipe example:
97 Pipe example:
98
98
99 [encode]
99 [encode]
100 # uncompress gzip files on checkin to improve delta compression
100 # uncompress gzip files on checkin to improve delta compression
101 # note: not necessarily a good idea, just an example
101 # note: not necessarily a good idea, just an example
102 *.gz = pipe: gunzip
102 *.gz = pipe: gunzip
103
103
104 [decode]
104 [decode]
105 # recompress gzip files when writing them to the working dir (we
105 # recompress gzip files when writing them to the working dir (we
106 # can safely omit "pipe:", because it's the default)
106 # can safely omit "pipe:", because it's the default)
107 *.gz = gzip
107 *.gz = gzip
108
108
109 A "tempfile:" command is a template. The string INFILE is replaced
109 A "tempfile:" command is a template. The string INFILE is replaced
110 with the name of a temporary file that contains the data to be
110 with the name of a temporary file that contains the data to be
111 filtered by the command. The string OUTFILE is replaced with the
111 filtered by the command. The string OUTFILE is replaced with the
112 name of an empty temporary file, where the filtered data must be
112 name of an empty temporary file, where the filtered data must be
113 written by the command.
113 written by the command.
114
114
115 NOTE: the tempfile mechanism is recommended for Windows systems,
115 NOTE: the tempfile mechanism is recommended for Windows systems,
116 where the standard shell I/O redirection operators often have
116 where the standard shell I/O redirection operators often have
117 strange effects. In particular, if you are doing line ending
117 strange effects. In particular, if you are doing line ending
118 conversion on Windows using the popular dos2unix and unix2dos
118 conversion on Windows using the popular dos2unix and unix2dos
119 programs, you *must* use the tempfile mechanism, as using pipes will
119 programs, you *must* use the tempfile mechanism, as using pipes will
120 corrupt the contents of your files.
120 corrupt the contents of your files.
121
121
122 Tempfile example:
122 Tempfile example:
123
123
124 [encode]
124 [encode]
125 # convert files to unix line ending conventions on checkin
125 # convert files to unix line ending conventions on checkin
126 **.txt = tempfile: dos2unix -n INFILE OUTFILE
126 **.txt = tempfile: dos2unix -n INFILE OUTFILE
127
127
128 [decode]
128 [decode]
129 # convert files to windows line ending conventions when writing
129 # convert files to windows line ending conventions when writing
130 # them to the working dir
130 # them to the working dir
131 **.txt = tempfile: unix2dos -n INFILE OUTFILE
131 **.txt = tempfile: unix2dos -n INFILE OUTFILE
132
132
133 email::
133 email::
134 Settings for extensions that send email messages.
134 Settings for extensions that send email messages.
135 from;;
135 from;;
136 Optional. Email address to use in "From" header and SMTP envelope
136 Optional. Email address to use in "From" header and SMTP envelope
137 of outgoing messages.
137 of outgoing messages.
138 method;;
139 Optional. Method to use to send email messages. If value is
140 "smtp" (default), use SMTP (see section "[mail]" for
141 configuration). Otherwise, use as name of program to run that
142 acts like sendmail (takes "-f" option for sender, list of
143 recipients on command line, message on stdin). Normally, setting
144 this to "sendmail" or "/usr/sbin/sendmail" is enough to use
145 sendmail to send messages.
146
147 Email example:
148
149 [email]
150 from = Joseph User <joe.user@example.com>
151 method = /usr/sbin/sendmail
138
152
139 extensions::
153 extensions::
140 Mercurial has an extension mechanism for adding new features. To
154 Mercurial has an extension mechanism for adding new features. To
141 enable an extension, create an entry for it in this section.
155 enable an extension, create an entry for it in this section.
142
156
143 If you know that the extension is already in Python's search path,
157 If you know that the extension is already in Python's search path,
144 you can give the name of the module, followed by "=", with nothing
158 you can give the name of the module, followed by "=", with nothing
145 after the "=".
159 after the "=".
146
160
147 Otherwise, give a name that you choose, followed by "=", followed by
161 Otherwise, give a name that you choose, followed by "=", followed by
148 the path to the ".py" file (including the file name extension) that
162 the path to the ".py" file (including the file name extension) that
149 defines the extension.
163 defines the extension.
150
164
151 hooks::
165 hooks::
152 Commands or Python functions that get automatically executed by
166 Commands or Python functions that get automatically executed by
153 various actions such as starting or finishing a commit. Multiple
167 various actions such as starting or finishing a commit. Multiple
154 hooks can be run for the same action by appending a suffix to the
168 hooks can be run for the same action by appending a suffix to the
155 action. Overriding a site-wide hook can be done by changing its
169 action. Overriding a site-wide hook can be done by changing its
156 value or setting it to an empty string.
170 value or setting it to an empty string.
157
171
158 Example .hg/hgrc:
172 Example .hg/hgrc:
159
173
160 [hooks]
174 [hooks]
161 # do not use the site-wide hook
175 # do not use the site-wide hook
162 incoming =
176 incoming =
163 incoming.email = /my/email/hook
177 incoming.email = /my/email/hook
164 incoming.autobuild = /my/build/hook
178 incoming.autobuild = /my/build/hook
165
179
166 Most hooks are run with environment variables set that give added
180 Most hooks are run with environment variables set that give added
167 useful information. For each hook below, the environment variables
181 useful information. For each hook below, the environment variables
168 it is passed are listed with names of the form "$HG_foo".
182 it is passed are listed with names of the form "$HG_foo".
169
183
170 changegroup;;
184 changegroup;;
171 Run after a changegroup has been added via push, pull or
185 Run after a changegroup has been added via push, pull or
172 unbundle. ID of the first new changeset is in $HG_NODE.
186 unbundle. ID of the first new changeset is in $HG_NODE.
173 commit;;
187 commit;;
174 Run after a changeset has been created in the local repository.
188 Run after a changeset has been created in the local repository.
175 ID of the newly created changeset is in $HG_NODE. Parent
189 ID of the newly created changeset is in $HG_NODE. Parent
176 changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
190 changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
177 incoming;;
191 incoming;;
178 Run after a changeset has been pulled, pushed, or unbundled into
192 Run after a changeset has been pulled, pushed, or unbundled into
179 the local repository. The ID of the newly arrived changeset is in
193 the local repository. The ID of the newly arrived changeset is in
180 $HG_NODE.
194 $HG_NODE.
181 outgoing;;
195 outgoing;;
182 Run after sending changes from local repository to another. ID of
196 Run after sending changes from local repository to another. ID of
183 first changeset sent is in $HG_NODE. Source of operation is in
197 first changeset sent is in $HG_NODE. Source of operation is in
184 $HG_SOURCE; see "preoutgoing" hook for description.
198 $HG_SOURCE; see "preoutgoing" hook for description.
185 prechangegroup;;
199 prechangegroup;;
186 Run before a changegroup is added via push, pull or unbundle.
200 Run before a changegroup is added via push, pull or unbundle.
187 Exit status 0 allows the changegroup to proceed. Non-zero status
201 Exit status 0 allows the changegroup to proceed. Non-zero status
188 will cause the push, pull or unbundle to fail.
202 will cause the push, pull or unbundle to fail.
189 precommit;;
203 precommit;;
190 Run before starting a local commit. Exit status 0 allows the
204 Run before starting a local commit. Exit status 0 allows the
191 commit to proceed. Non-zero status will cause the commit to fail.
205 commit to proceed. Non-zero status will cause the commit to fail.
192 Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
206 Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
193 preoutgoing;;
207 preoutgoing;;
194 Run before computing changes to send from the local repository to
208 Run before computing changes to send from the local repository to
195 another. Non-zero status will cause failure. This lets you
209 another. Non-zero status will cause failure. This lets you
196 prevent pull over http or ssh. Also prevents against local pull,
210 prevent pull over http or ssh. Also prevents against local pull,
197 push (outbound) or bundle commands, but not effective, since you
211 push (outbound) or bundle commands, but not effective, since you
198 can just copy files instead then. Source of operation is in
212 can just copy files instead then. Source of operation is in
199 $HG_SOURCE. If "serve", operation is happening on behalf of
213 $HG_SOURCE. If "serve", operation is happening on behalf of
200 remote ssh or http repository. If "push", "pull" or "bundle",
214 remote ssh or http repository. If "push", "pull" or "bundle",
201 operation is happening on behalf of repository on same system.
215 operation is happening on behalf of repository on same system.
202 pretag;;
216 pretag;;
203 Run before creating a tag. Exit status 0 allows the tag to be
217 Run before creating a tag. Exit status 0 allows the tag to be
204 created. Non-zero status will cause the tag to fail. ID of
218 created. Non-zero status will cause the tag to fail. ID of
205 changeset to tag is in $HG_NODE. Name of tag is in $HG_TAG. Tag
219 changeset to tag is in $HG_NODE. Name of tag is in $HG_TAG. Tag
206 is local if $HG_LOCAL=1, in repo if $HG_LOCAL=0.
220 is local if $HG_LOCAL=1, in repo if $HG_LOCAL=0.
207 pretxnchangegroup;;
221 pretxnchangegroup;;
208 Run after a changegroup has been added via push, pull or unbundle,
222 Run after a changegroup has been added via push, pull or unbundle,
209 but before the transaction has been committed. Changegroup is
223 but before the transaction has been committed. Changegroup is
210 visible to hook program. This lets you validate incoming changes
224 visible to hook program. This lets you validate incoming changes
211 before accepting them. Passed the ID of the first new changeset
225 before accepting them. Passed the ID of the first new changeset
212 in $HG_NODE. Exit status 0 allows the transaction to commit.
226 in $HG_NODE. Exit status 0 allows the transaction to commit.
213 Non-zero status will cause the transaction to be rolled back and
227 Non-zero status will cause the transaction to be rolled back and
214 the push, pull or unbundle will fail.
228 the push, pull or unbundle will fail.
215 pretxncommit;;
229 pretxncommit;;
216 Run after a changeset has been created but the transaction not yet
230 Run after a changeset has been created but the transaction not yet
217 committed. Changeset is visible to hook program. This lets you
231 committed. Changeset is visible to hook program. This lets you
218 validate commit message and changes. Exit status 0 allows the
232 validate commit message and changes. Exit status 0 allows the
219 commit to proceed. Non-zero status will cause the transaction to
233 commit to proceed. Non-zero status will cause the transaction to
220 be rolled back. ID of changeset is in $HG_NODE. Parent changeset
234 be rolled back. ID of changeset is in $HG_NODE. Parent changeset
221 IDs are in $HG_PARENT1 and $HG_PARENT2.
235 IDs are in $HG_PARENT1 and $HG_PARENT2.
236 preupdate;;
237 Run before updating the working directory. Exit status 0 allows
238 the update to proceed. Non-zero status will prevent the update.
239 Changeset ID of first new parent is in $HG_PARENT1. If merge, ID
240 of second new parent is in $HG_PARENT2.
222 tag;;
241 tag;;
223 Run after a tag is created. ID of tagged changeset is in
242 Run after a tag is created. ID of tagged changeset is in
224 $HG_NODE. Name of tag is in $HG_TAG. Tag is local if
243 $HG_NODE. Name of tag is in $HG_TAG. Tag is local if
225 $HG_LOCAL=1, in repo if $HG_LOCAL=0.
244 $HG_LOCAL=1, in repo if $HG_LOCAL=0.
245 update;;
246 Run after updating the working directory. Changeset ID of first
247 new parent is in $HG_PARENT1. If merge, ID of second new parent
248 is in $HG_PARENT2. If update succeeded, $HG_ERROR=0. If update
249 failed (e.g. because conflicts not resolved), $HG_ERROR=1.
226
250
227 In earlier releases, the names of hook environment variables did not
251 Note: In earlier releases, the names of hook environment variables
228 have a "HG_" prefix. These unprefixed names are still provided in
252 did not have a "HG_" prefix. The old unprefixed names are no longer
229 the environment for backwards compatibility, but their use is
253 provided in the environment.
230 deprecated, and they will be removed in a future release.
231
254
232 The syntax for Python hooks is as follows:
255 The syntax for Python hooks is as follows:
233
256
234 hookname = python:modulename.submodule.callable
257 hookname = python:modulename.submodule.callable
235
258
236 Python hooks are run within the Mercurial process. Each hook is
259 Python hooks are run within the Mercurial process. Each hook is
237 called with at least three keyword arguments: a ui object (keyword
260 called with at least three keyword arguments: a ui object (keyword
238 "ui"), a repository object (keyword "repo"), and a "hooktype"
261 "ui"), a repository object (keyword "repo"), and a "hooktype"
239 keyword that tells what kind of hook is used. Arguments listed as
262 keyword that tells what kind of hook is used. Arguments listed as
240 environment variables above are passed as keyword arguments, with no
263 environment variables above are passed as keyword arguments, with no
241 "HG_" prefix, and names in lower case.
264 "HG_" prefix, and names in lower case.
242
265
243 A Python hook must return a "true" value to succeed. Returning a
266 A Python hook must return a "true" value to succeed. Returning a
244 "false" value or raising an exception is treated as failure of the
267 "false" value or raising an exception is treated as failure of the
245 hook.
268 hook.
246
269
247 http_proxy::
270 http_proxy::
248 Used to access web-based Mercurial repositories through a HTTP
271 Used to access web-based Mercurial repositories through a HTTP
249 proxy.
272 proxy.
250 host;;
273 host;;
251 Host name and (optional) port of the proxy server, for example
274 Host name and (optional) port of the proxy server, for example
252 "myproxy:8000".
275 "myproxy:8000".
253 no;;
276 no;;
254 Optional. Comma-separated list of host names that should bypass
277 Optional. Comma-separated list of host names that should bypass
255 the proxy.
278 the proxy.
256 passwd;;
279 passwd;;
257 Optional. Password to authenticate with at the proxy server.
280 Optional. Password to authenticate with at the proxy server.
258 user;;
281 user;;
259 Optional. User name to authenticate with at the proxy server.
282 Optional. User name to authenticate with at the proxy server.
260
283
261 smtp::
284 smtp::
262 Configuration for extensions that need to send email messages.
285 Configuration for extensions that need to send email messages.
263 host;;
286 host;;
264 Optional. Host name of mail server. Default: "mail".
287 Optional. Host name of mail server. Default: "mail".
265 port;;
288 port;;
266 Optional. Port to connect to on mail server. Default: 25.
289 Optional. Port to connect to on mail server. Default: 25.
267 tls;;
290 tls;;
268 Optional. Whether to connect to mail server using TLS. True or
291 Optional. Whether to connect to mail server using TLS. True or
269 False. Default: False.
292 False. Default: False.
270 username;;
293 username;;
271 Optional. User name to authenticate to SMTP server with.
294 Optional. User name to authenticate to SMTP server with.
272 If username is specified, password must also be specified.
295 If username is specified, password must also be specified.
273 Default: none.
296 Default: none.
274 password;;
297 password;;
275 Optional. Password to authenticate to SMTP server with.
298 Optional. Password to authenticate to SMTP server with.
276 If username is specified, password must also be specified.
299 If username is specified, password must also be specified.
277 Default: none.
300 Default: none.
278
301
279 paths::
302 paths::
280 Assigns symbolic names to repositories. The left side is the
303 Assigns symbolic names to repositories. The left side is the
281 symbolic name, and the right gives the directory or URL that is the
304 symbolic name, and the right gives the directory or URL that is the
282 location of the repository.
305 location of the repository. Default paths can be declared by
306 setting the following entries.
307 default;;
308 Directory or URL to use when pulling if no source is specified.
309 Default is set to repository from which the current repository
310 was cloned.
311 default-push;;
312 Optional. Directory or URL to use when pushing if no destination
313 is specified.
283
314
284 ui::
315 ui::
285 User interface controls.
316 User interface controls.
286 debug;;
317 debug;;
287 Print debugging information. True or False. Default is False.
318 Print debugging information. True or False. Default is False.
288 editor;;
319 editor;;
289 The editor to use during a commit. Default is $EDITOR or "vi".
320 The editor to use during a commit. Default is $EDITOR or "vi".
290 ignore;;
321 ignore;;
291 A file to read per-user ignore patterns from. This file should be in
322 A file to read per-user ignore patterns from. This file should be in
292 the same format as a repository-wide .hgignore file. This option
323 the same format as a repository-wide .hgignore file. This option
293 supports hook syntax, so if you want to specify multiple ignore
324 supports hook syntax, so if you want to specify multiple ignore
294 files, you can do so by setting something like
325 files, you can do so by setting something like
295 "ignore.other = ~/.hgignore2". For details of the ignore file
326 "ignore.other = ~/.hgignore2". For details of the ignore file
296 format, see the hgignore(5) man page.
327 format, see the hgignore(5) man page.
297 interactive;;
328 interactive;;
298 Allow to prompt the user. True or False. Default is True.
329 Allow to prompt the user. True or False. Default is True.
299 logtemplate;;
330 logtemplate;;
300 Template string for commands that print changesets.
331 Template string for commands that print changesets.
301 style;;
332 style;;
302 Name of style to use for command output.
333 Name of style to use for command output.
303 merge;;
334 merge;;
304 The conflict resolution program to use during a manual merge.
335 The conflict resolution program to use during a manual merge.
305 Default is "hgmerge".
336 Default is "hgmerge".
306 quiet;;
337 quiet;;
307 Reduce the amount of output printed. True or False. Default is False.
338 Reduce the amount of output printed. True or False. Default is False.
308 remotecmd;;
339 remotecmd;;
309 remote command to use for clone/push/pull operations. Default is 'hg'.
340 remote command to use for clone/push/pull operations. Default is 'hg'.
310 ssh;;
341 ssh;;
311 command to use for SSH connections. Default is 'ssh'.
342 command to use for SSH connections. Default is 'ssh'.
312 timeout;;
343 timeout;;
313 The timeout used when a lock is held (in seconds), a negative value
344 The timeout used when a lock is held (in seconds), a negative value
314 means no timeout. Default is 600.
345 means no timeout. Default is 600.
315 username;;
346 username;;
316 The committer of a changeset created when running "commit".
347 The committer of a changeset created when running "commit".
317 Typically a person's name and email address, e.g. "Fred Widget
348 Typically a person's name and email address, e.g. "Fred Widget
318 <fred@example.com>". Default is $EMAIL or username@hostname, unless
349 <fred@example.com>". Default is $EMAIL or username@hostname, unless
319 username is set to an empty string, which enforces specifying the
350 username is set to an empty string, which enforces specifying the
320 username manually.
351 username manually.
321 verbose;;
352 verbose;;
322 Increase the amount of output printed. True or False. Default is False.
353 Increase the amount of output printed. True or False. Default is False.
323
354
324
355
325 web::
356 web::
326 Web interface configuration.
357 Web interface configuration.
327 accesslog;;
358 accesslog;;
328 Where to output the access log. Default is stdout.
359 Where to output the access log. Default is stdout.
329 address;;
360 address;;
330 Interface address to bind to. Default is all.
361 Interface address to bind to. Default is all.
331 allowbz2;;
362 allowbz2;;
332 Whether to allow .tar.bz2 downloading of repo revisions. Default is false.
363 Whether to allow .tar.bz2 downloading of repo revisions. Default is false.
333 allowgz;;
364 allowgz;;
334 Whether to allow .tar.gz downloading of repo revisions. Default is false.
365 Whether to allow .tar.gz downloading of repo revisions. Default is false.
335 allowpull;;
366 allowpull;;
336 Whether to allow pulling from the repository. Default is true.
367 Whether to allow pulling from the repository. Default is true.
337 allowzip;;
368 allowzip;;
338 Whether to allow .zip downloading of repo revisions. Default is false.
369 Whether to allow .zip downloading of repo revisions. Default is false.
339 This feature creates temporary files.
370 This feature creates temporary files.
340 baseurl;;
371 baseurl;;
341 Base URL to use when publishing URLs in other locations, so
372 Base URL to use when publishing URLs in other locations, so
342 third-party tools like email notification hooks can construct URLs.
373 third-party tools like email notification hooks can construct URLs.
343 Example: "http://hgserver/repos/"
374 Example: "http://hgserver/repos/"
344 description;;
375 description;;
345 Textual description of the repository's purpose or contents.
376 Textual description of the repository's purpose or contents.
346 Default is "unknown".
377 Default is "unknown".
347 errorlog;;
378 errorlog;;
348 Where to output the error log. Default is stderr.
379 Where to output the error log. Default is stderr.
349 ipv6;;
380 ipv6;;
350 Whether to use IPv6. Default is false.
381 Whether to use IPv6. Default is false.
351 name;;
382 name;;
352 Repository name to use in the web interface. Default is current
383 Repository name to use in the web interface. Default is current
353 working directory.
384 working directory.
354 maxchanges;;
385 maxchanges;;
355 Maximum number of changes to list on the changelog. Default is 10.
386 Maximum number of changes to list on the changelog. Default is 10.
356 maxfiles;;
387 maxfiles;;
357 Maximum number of files to list per changeset. Default is 10.
388 Maximum number of files to list per changeset. Default is 10.
358 port;;
389 port;;
359 Port to listen on. Default is 8000.
390 Port to listen on. Default is 8000.
360 style;;
391 style;;
361 Which template map style to use.
392 Which template map style to use.
362 templates;;
393 templates;;
363 Where to find the HTML templates. Default is install path.
394 Where to find the HTML templates. Default is install path.
364
395
365
396
366 AUTHOR
397 AUTHOR
367 ------
398 ------
368 Bryan O'Sullivan <bos@serpentine.com>.
399 Bryan O'Sullivan <bos@serpentine.com>.
369
400
370 Mercurial was written by Matt Mackall <mpm@selenic.com>.
401 Mercurial was written by Matt Mackall <mpm@selenic.com>.
371
402
372 SEE ALSO
403 SEE ALSO
373 --------
404 --------
374 hg(1), hgignore(5)
405 hg(1), hgignore(5)
375
406
376 COPYING
407 COPYING
377 -------
408 -------
378 This manual page is copyright 2005 Bryan O'Sullivan.
409 This manual page is copyright 2005 Bryan O'Sullivan.
379 Mercurial is copyright 2005, 2006 Matt Mackall.
410 Mercurial is copyright 2005, 2006 Matt Mackall.
380 Free use of this software is granted under the terms of the GNU General
411 Free use of this software is granted under the terms of the GNU General
381 Public License (GPL).
412 Public License (GPL).
@@ -1,1305 +1,1306 b''
1 # queue.py - patch queues for mercurial
1 # queue.py - patch queues for mercurial
2 #
2 #
3 # Copyright 2005 Chris Mason <mason@suse.com>
3 # Copyright 2005 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from mercurial.demandload import *
8 from mercurial.demandload import *
9 demandload(globals(), "os sys re struct traceback errno bz2")
9 demandload(globals(), "os sys re struct traceback errno bz2")
10 from mercurial.i18n import gettext as _
10 from mercurial.i18n import gettext as _
11 from mercurial import ui, hg, revlog, commands, util
11 from mercurial import ui, hg, revlog, commands, util
12
12
13 versionstr = "0.45"
13 versionstr = "0.45"
14
14
15 repomap = {}
15 repomap = {}
16
16
17 commands.norepo += " qversion"
17 commands.norepo += " qversion"
18 class queue:
18 class queue:
19 def __init__(self, ui, path, patchdir=None):
19 def __init__(self, ui, path, patchdir=None):
20 self.basepath = path
20 self.basepath = path
21 if patchdir:
21 if patchdir:
22 self.path = patchdir
22 self.path = patchdir
23 else:
23 else:
24 self.path = os.path.join(path, "patches")
24 self.path = os.path.join(path, "patches")
25 self.opener = util.opener(self.path)
25 self.opener = util.opener(self.path)
26 self.ui = ui
26 self.ui = ui
27 self.applied = []
27 self.applied = []
28 self.full_series = []
28 self.full_series = []
29 self.applied_dirty = 0
29 self.applied_dirty = 0
30 self.series_dirty = 0
30 self.series_dirty = 0
31 self.series_path = "series"
31 self.series_path = "series"
32 self.status_path = "status"
32 self.status_path = "status"
33
33
34 if os.path.exists(os.path.join(self.path, self.series_path)):
34 if os.path.exists(os.path.join(self.path, self.series_path)):
35 self.full_series = self.opener(self.series_path).read().splitlines()
35 self.full_series = self.opener(self.series_path).read().splitlines()
36 self.read_series(self.full_series)
36 self.read_series(self.full_series)
37
37
38 if os.path.exists(os.path.join(self.path, self.status_path)):
38 if os.path.exists(os.path.join(self.path, self.status_path)):
39 self.applied = self.opener(self.status_path).read().splitlines()
39 self.applied = self.opener(self.status_path).read().splitlines()
40
40
41 def find_series(self, patch):
41 def find_series(self, patch):
42 pre = re.compile("(\s*)([^#]+)")
42 pre = re.compile("(\s*)([^#]+)")
43 index = 0
43 index = 0
44 for l in self.full_series:
44 for l in self.full_series:
45 m = pre.match(l)
45 m = pre.match(l)
46 if m:
46 if m:
47 s = m.group(2)
47 s = m.group(2)
48 s = s.rstrip()
48 s = s.rstrip()
49 if s == patch:
49 if s == patch:
50 return index
50 return index
51 index += 1
51 index += 1
52 return None
52 return None
53
53
54 def read_series(self, list):
54 def read_series(self, list):
55 def matcher(list):
55 def matcher(list):
56 pre = re.compile("(\s*)([^#]+)")
56 pre = re.compile("(\s*)([^#]+)")
57 for l in list:
57 for l in list:
58 m = pre.match(l)
58 m = pre.match(l)
59 if m:
59 if m:
60 s = m.group(2)
60 s = m.group(2)
61 s = s.rstrip()
61 s = s.rstrip()
62 if len(s) > 0:
62 if len(s) > 0:
63 yield s
63 yield s
64 self.series = []
64 self.series = []
65 self.series = [ x for x in matcher(list) ]
65 self.series = [ x for x in matcher(list) ]
66
66
67 def save_dirty(self):
67 def save_dirty(self):
68 if self.applied_dirty:
68 if self.applied_dirty:
69 if len(self.applied) > 0:
69 if len(self.applied) > 0:
70 nl = "\n"
70 nl = "\n"
71 else:
71 else:
72 nl = ""
72 nl = ""
73 f = self.opener(self.status_path, "w")
73 f = self.opener(self.status_path, "w")
74 f.write("\n".join(self.applied) + nl)
74 f.write("\n".join(self.applied) + nl)
75 if self.series_dirty:
75 if self.series_dirty:
76 if len(self.full_series) > 0:
76 if len(self.full_series) > 0:
77 nl = "\n"
77 nl = "\n"
78 else:
78 else:
79 nl = ""
79 nl = ""
80 f = self.opener(self.series_path, "w")
80 f = self.opener(self.series_path, "w")
81 f.write("\n".join(self.full_series) + nl)
81 f.write("\n".join(self.full_series) + nl)
82
82
83 def readheaders(self, patch):
83 def readheaders(self, patch):
84 def eatdiff(lines):
84 def eatdiff(lines):
85 while lines:
85 while lines:
86 l = lines[-1]
86 l = lines[-1]
87 if (l.startswith("diff -") or
87 if (l.startswith("diff -") or
88 l.startswith("Index:") or
88 l.startswith("Index:") or
89 l.startswith("===========")):
89 l.startswith("===========")):
90 del lines[-1]
90 del lines[-1]
91 else:
91 else:
92 break
92 break
93 def eatempty(lines):
93 def eatempty(lines):
94 while lines:
94 while lines:
95 l = lines[-1]
95 l = lines[-1]
96 if re.match('\s*$', l):
96 if re.match('\s*$', l):
97 del lines[-1]
97 del lines[-1]
98 else:
98 else:
99 break
99 break
100
100
101 pf = os.path.join(self.path, patch)
101 pf = os.path.join(self.path, patch)
102 message = []
102 message = []
103 comments = []
103 comments = []
104 user = None
104 user = None
105 format = None
105 format = None
106 subject = None
106 subject = None
107 diffstart = 0
107 diffstart = 0
108
108
109 for line in file(pf):
109 for line in file(pf):
110 line = line.rstrip()
110 line = line.rstrip()
111 if diffstart:
111 if diffstart:
112 if line.startswith('+++ '):
112 if line.startswith('+++ '):
113 diffstart = 2
113 diffstart = 2
114 break
114 break
115 if line.startswith("--- "):
115 if line.startswith("--- "):
116 diffstart = 1
116 diffstart = 1
117 continue
117 continue
118 elif format == "hgpatch":
118 elif format == "hgpatch":
119 # parse values when importing the result of an hg export
119 # parse values when importing the result of an hg export
120 if line.startswith("# User "):
120 if line.startswith("# User "):
121 user = line[7:]
121 user = line[7:]
122 elif not line.startswith("# ") and line:
122 elif not line.startswith("# ") and line:
123 message.append(line)
123 message.append(line)
124 format = None
124 format = None
125 elif line == '# HG changeset patch':
125 elif line == '# HG changeset patch':
126 format = "hgpatch"
126 format = "hgpatch"
127 elif (format != "tagdone" and (line.startswith("Subject: ") or
127 elif (format != "tagdone" and (line.startswith("Subject: ") or
128 line.startswith("subject: "))):
128 line.startswith("subject: "))):
129 subject = line[9:]
129 subject = line[9:]
130 format = "tag"
130 format = "tag"
131 elif (format != "tagdone" and (line.startswith("From: ") or
131 elif (format != "tagdone" and (line.startswith("From: ") or
132 line.startswith("from: "))):
132 line.startswith("from: "))):
133 user = line[6:]
133 user = line[6:]
134 format = "tag"
134 format = "tag"
135 elif format == "tag" and line == "":
135 elif format == "tag" and line == "":
136 # when looking for tags (subject: from: etc) they
136 # when looking for tags (subject: from: etc) they
137 # end once you find a blank line in the source
137 # end once you find a blank line in the source
138 format = "tagdone"
138 format = "tagdone"
139 else:
139 else:
140 message.append(line)
140 message.append(line)
141 comments.append(line)
141 comments.append(line)
142
142
143 eatdiff(message)
143 eatdiff(message)
144 eatdiff(comments)
144 eatdiff(comments)
145 eatempty(message)
145 eatempty(message)
146 eatempty(comments)
146 eatempty(comments)
147
147
148 # make sure message isn't empty
148 # make sure message isn't empty
149 if format and format.startswith("tag") and subject:
149 if format and format.startswith("tag") and subject:
150 message.insert(0, "")
150 message.insert(0, "")
151 message.insert(0, subject)
151 message.insert(0, subject)
152 return (message, comments, user, diffstart > 1)
152 return (message, comments, user, diffstart > 1)
153
153
154 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
154 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
155 # first try just applying the patch
155 # first try just applying the patch
156 (err, n) = self.apply(repo, [ patch ], update_status=False,
156 (err, n) = self.apply(repo, [ patch ], update_status=False,
157 strict=True, merge=rev, wlock=wlock)
157 strict=True, merge=rev, wlock=wlock)
158
158
159 if err == 0:
159 if err == 0:
160 return (err, n)
160 return (err, n)
161
161
162 if n is None:
162 if n is None:
163 self.ui.warn("apply failed for patch %s\n" % patch)
163 self.ui.warn("apply failed for patch %s\n" % patch)
164 sys.exit(1)
164 sys.exit(1)
165
165
166 self.ui.warn("patch didn't work out, merging %s\n" % patch)
166 self.ui.warn("patch didn't work out, merging %s\n" % patch)
167
167
168 # apply failed, strip away that rev and merge.
168 # apply failed, strip away that rev and merge.
169 repo.update(head, allow=False, force=True, wlock=wlock)
169 repo.update(head, allow=False, force=True, wlock=wlock)
170 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
170 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
171
171
172 c = repo.changelog.read(rev)
172 c = repo.changelog.read(rev)
173 ret = repo.update(rev, allow=True, wlock=wlock)
173 ret = repo.update(rev, allow=True, wlock=wlock)
174 if ret:
174 if ret:
175 self.ui.warn("update returned %d\n" % ret)
175 self.ui.warn("update returned %d\n" % ret)
176 sys.exit(1)
176 sys.exit(1)
177 n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
177 n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
178 if n == None:
178 if n == None:
179 self.ui.warn("repo commit failed\n")
179 self.ui.warn("repo commit failed\n")
180 sys.exit(1)
180 sys.exit(1)
181 try:
181 try:
182 message, comments, user, patchfound = mergeq.readheaders(patch)
182 message, comments, user, patchfound = mergeq.readheaders(patch)
183 except:
183 except:
184 self.ui.warn("Unable to read %s\n" % patch)
184 self.ui.warn("Unable to read %s\n" % patch)
185 sys.exit(1)
185 sys.exit(1)
186
186
187 patchf = self.opener(patch, "w")
187 patchf = self.opener(patch, "w")
188 if comments:
188 if comments:
189 comments = "\n".join(comments) + '\n\n'
189 comments = "\n".join(comments) + '\n\n'
190 patchf.write(comments)
190 patchf.write(comments)
191 commands.dodiff(patchf, self.ui, repo, head, n)
191 commands.dodiff(patchf, self.ui, repo, head, n)
192 patchf.close()
192 patchf.close()
193 return (0, n)
193 return (0, n)
194
194
195 def qparents(self, repo, rev=None):
195 def qparents(self, repo, rev=None):
196 if rev is None:
196 if rev is None:
197 (p1, p2) = repo.dirstate.parents()
197 (p1, p2) = repo.dirstate.parents()
198 if p2 == revlog.nullid:
198 if p2 == revlog.nullid:
199 return p1
199 return p1
200 if len(self.applied) == 0:
200 if len(self.applied) == 0:
201 return None
201 return None
202 (top, patch) = self.applied[-1].split(':')
202 (top, patch) = self.applied[-1].split(':')
203 top = revlog.bin(top)
203 top = revlog.bin(top)
204 return top
204 return top
205 pp = repo.changelog.parents(rev)
205 pp = repo.changelog.parents(rev)
206 if pp[1] != revlog.nullid:
206 if pp[1] != revlog.nullid:
207 arevs = [ x.split(':')[0] for x in self.applied ]
207 arevs = [ x.split(':')[0] for x in self.applied ]
208 p0 = revlog.hex(pp[0])
208 p0 = revlog.hex(pp[0])
209 p1 = revlog.hex(pp[1])
209 p1 = revlog.hex(pp[1])
210 if p0 in arevs:
210 if p0 in arevs:
211 return pp[0]
211 return pp[0]
212 if p1 in arevs:
212 if p1 in arevs:
213 return pp[1]
213 return pp[1]
214 return None
214 return None
215 return pp[0]
215 return pp[0]
216
216
217 def mergepatch(self, repo, mergeq, series, wlock):
217 def mergepatch(self, repo, mergeq, series, wlock):
218 if len(self.applied) == 0:
218 if len(self.applied) == 0:
219 # each of the patches merged in will have two parents. This
219 # each of the patches merged in will have two parents. This
220 # can confuse the qrefresh, qdiff, and strip code because it
220 # can confuse the qrefresh, qdiff, and strip code because it
221 # needs to know which parent is actually in the patch queue.
221 # needs to know which parent is actually in the patch queue.
222 # so, we insert a merge marker with only one parent. This way
222 # so, we insert a merge marker with only one parent. This way
223 # the first patch in the queue is never a merge patch
223 # the first patch in the queue is never a merge patch
224 #
224 #
225 pname = ".hg.patches.merge.marker"
225 pname = ".hg.patches.merge.marker"
226 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
226 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
227 wlock=wlock)
227 wlock=wlock)
228 self.applied.append(revlog.hex(n) + ":" + pname)
228 self.applied.append(revlog.hex(n) + ":" + pname)
229 self.applied_dirty = 1
229 self.applied_dirty = 1
230
230
231 head = self.qparents(repo)
231 head = self.qparents(repo)
232
232
233 for patch in series:
233 for patch in series:
234 patch = mergeq.lookup(patch)
234 patch = mergeq.lookup(patch)
235 if not patch:
235 if not patch:
236 self.ui.warn("patch %s does not exist\n" % patch)
236 self.ui.warn("patch %s does not exist\n" % patch)
237 return (1, None)
237 return (1, None)
238
238
239 info = mergeq.isapplied(patch)
239 info = mergeq.isapplied(patch)
240 if not info:
240 if not info:
241 self.ui.warn("patch %s is not applied\n" % patch)
241 self.ui.warn("patch %s is not applied\n" % patch)
242 return (1, None)
242 return (1, None)
243 rev = revlog.bin(info[1])
243 rev = revlog.bin(info[1])
244 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
244 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
245 if head:
245 if head:
246 self.applied.append(revlog.hex(head) + ":" + patch)
246 self.applied.append(revlog.hex(head) + ":" + patch)
247 self.applied_dirty = 1
247 self.applied_dirty = 1
248 if err:
248 if err:
249 return (err, head)
249 return (err, head)
250 return (0, head)
250 return (0, head)
251
251
252 def apply(self, repo, series, list=False, update_status=True,
252 def apply(self, repo, series, list=False, update_status=True,
253 strict=False, patchdir=None, merge=None, wlock=None):
253 strict=False, patchdir=None, merge=None, wlock=None):
254 # TODO unify with commands.py
254 # TODO unify with commands.py
255 if not patchdir:
255 if not patchdir:
256 patchdir = self.path
256 patchdir = self.path
257 pwd = os.getcwd()
257 pwd = os.getcwd()
258 os.chdir(repo.root)
258 os.chdir(repo.root)
259 err = 0
259 err = 0
260 if not wlock:
260 if not wlock:
261 wlock = repo.wlock()
261 wlock = repo.wlock()
262 lock = repo.lock()
262 lock = repo.lock()
263 tr = repo.transaction()
263 tr = repo.transaction()
264 n = None
264 n = None
265 for patch in series:
265 for patch in series:
266 self.ui.warn("applying %s\n" % patch)
266 self.ui.warn("applying %s\n" % patch)
267 pf = os.path.join(patchdir, patch)
267 pf = os.path.join(patchdir, patch)
268
268
269 try:
269 try:
270 message, comments, user, patchfound = self.readheaders(patch)
270 message, comments, user, patchfound = self.readheaders(patch)
271 except:
271 except:
272 self.ui.warn("Unable to read %s\n" % pf)
272 self.ui.warn("Unable to read %s\n" % pf)
273 err = 1
273 err = 1
274 break
274 break
275
275
276 if not message:
276 if not message:
277 message = "imported patch %s\n" % patch
277 message = "imported patch %s\n" % patch
278 else:
278 else:
279 if list:
279 if list:
280 message.append("\nimported patch %s" % patch)
280 message.append("\nimported patch %s" % patch)
281 message = '\n'.join(message)
281 message = '\n'.join(message)
282
282
283 try:
283 try:
284 f = os.popen("patch -p1 --no-backup-if-mismatch < '%s'" % (pf))
284 pp = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
285 f = os.popen("%s -p1 --no-backup-if-mismatch < '%s'" % (pp, pf))
285 except:
286 except:
286 self.ui.warn("patch failed, unable to continue (try -v)\n")
287 self.ui.warn("patch failed, unable to continue (try -v)\n")
287 err = 1
288 err = 1
288 break
289 break
289 files = []
290 files = []
290 fuzz = False
291 fuzz = False
291 for l in f:
292 for l in f:
292 l = l.rstrip('\r\n');
293 l = l.rstrip('\r\n');
293 if self.ui.verbose:
294 if self.ui.verbose:
294 self.ui.warn(l + "\n")
295 self.ui.warn(l + "\n")
295 if l[:14] == 'patching file ':
296 if l[:14] == 'patching file ':
296 pf = os.path.normpath(l[14:])
297 pf = os.path.normpath(l[14:])
297 # when patch finds a space in the file name, it puts
298 # when patch finds a space in the file name, it puts
298 # single quotes around the filename. strip them off
299 # single quotes around the filename. strip them off
299 if pf[0] == "'" and pf[-1] == "'":
300 if pf[0] == "'" and pf[-1] == "'":
300 pf = pf[1:-1]
301 pf = pf[1:-1]
301 if pf not in files:
302 if pf not in files:
302 files.append(pf)
303 files.append(pf)
303 printed_file = False
304 printed_file = False
304 file_str = l
305 file_str = l
305 elif l.find('with fuzz') >= 0:
306 elif l.find('with fuzz') >= 0:
306 if not printed_file:
307 if not printed_file:
307 self.ui.warn(file_str + '\n')
308 self.ui.warn(file_str + '\n')
308 printed_file = True
309 printed_file = True
309 self.ui.warn(l + '\n')
310 self.ui.warn(l + '\n')
310 fuzz = True
311 fuzz = True
311 elif l.find('saving rejects to file') >= 0:
312 elif l.find('saving rejects to file') >= 0:
312 self.ui.warn(l + '\n')
313 self.ui.warn(l + '\n')
313 elif l.find('FAILED') >= 0:
314 elif l.find('FAILED') >= 0:
314 if not printed_file:
315 if not printed_file:
315 self.ui.warn(file_str + '\n')
316 self.ui.warn(file_str + '\n')
316 printed_file = True
317 printed_file = True
317 self.ui.warn(l + '\n')
318 self.ui.warn(l + '\n')
318 patcherr = f.close()
319 patcherr = f.close()
319
320
320 if merge and len(files) > 0:
321 if merge and len(files) > 0:
321 # Mark as merged and update dirstate parent info
322 # Mark as merged and update dirstate parent info
322 repo.dirstate.update(repo.dirstate.filterfiles(files), 'm')
323 repo.dirstate.update(repo.dirstate.filterfiles(files), 'm')
323 p1, p2 = repo.dirstate.parents()
324 p1, p2 = repo.dirstate.parents()
324 repo.dirstate.setparents(p1, merge)
325 repo.dirstate.setparents(p1, merge)
325 if len(files) > 0:
326 if len(files) > 0:
326 commands.addremove_lock(self.ui, repo, files,
327 commands.addremove_lock(self.ui, repo, files,
327 opts={}, wlock=wlock)
328 opts={}, wlock=wlock)
328 n = repo.commit(files, message, user, force=1, lock=lock,
329 n = repo.commit(files, message, user, force=1, lock=lock,
329 wlock=wlock)
330 wlock=wlock)
330
331
331 if n == None:
332 if n == None:
332 self.ui.warn("repo commit failed\n")
333 self.ui.warn("repo commit failed\n")
333 sys.exit(1)
334 sys.exit(1)
334
335
335 if update_status:
336 if update_status:
336 self.applied.append(revlog.hex(n) + ":" + patch)
337 self.applied.append(revlog.hex(n) + ":" + patch)
337
338
338 if patcherr:
339 if patcherr:
339 if not patchfound:
340 if not patchfound:
340 self.ui.warn("patch %s is empty\n" % patch)
341 self.ui.warn("patch %s is empty\n" % patch)
341 err = 0
342 err = 0
342 else:
343 else:
343 self.ui.warn("patch failed, rejects left in working dir\n")
344 self.ui.warn("patch failed, rejects left in working dir\n")
344 err = 1
345 err = 1
345 break
346 break
346
347
347 if fuzz and strict:
348 if fuzz and strict:
348 self.ui.warn("fuzz found when applying patch, stopping\n")
349 self.ui.warn("fuzz found when applying patch, stopping\n")
349 err = 1
350 err = 1
350 break
351 break
351 tr.close()
352 tr.close()
352 os.chdir(pwd)
353 os.chdir(pwd)
353 return (err, n)
354 return (err, n)
354
355
355 def delete(self, repo, patch):
356 def delete(self, repo, patch):
356 patch = self.lookup(patch)
357 patch = self.lookup(patch)
357 info = self.isapplied(patch)
358 info = self.isapplied(patch)
358 if info:
359 if info:
359 self.ui.warn("cannot delete applied patch %s\n" % patch)
360 self.ui.warn("cannot delete applied patch %s\n" % patch)
360 sys.exit(1)
361 sys.exit(1)
361 if patch not in self.series:
362 if patch not in self.series:
362 self.ui.warn("patch %s not in series file\n" % patch)
363 self.ui.warn("patch %s not in series file\n" % patch)
363 sys.exit(1)
364 sys.exit(1)
364 i = self.find_series(patch)
365 i = self.find_series(patch)
365 del self.full_series[i]
366 del self.full_series[i]
366 self.read_series(self.full_series)
367 self.read_series(self.full_series)
367 self.series_dirty = 1
368 self.series_dirty = 1
368
369
369 def check_toppatch(self, repo):
370 def check_toppatch(self, repo):
370 if len(self.applied) > 0:
371 if len(self.applied) > 0:
371 (top, patch) = self.applied[-1].split(':')
372 (top, patch) = self.applied[-1].split(':')
372 top = revlog.bin(top)
373 top = revlog.bin(top)
373 pp = repo.dirstate.parents()
374 pp = repo.dirstate.parents()
374 if top not in pp:
375 if top not in pp:
375 self.ui.warn("queue top not at dirstate parents. top %s dirstate %s %s\n" %( revlog.short(top), revlog.short(pp[0]), revlog.short(pp[1])))
376 self.ui.warn("queue top not at dirstate parents. top %s dirstate %s %s\n" %( revlog.short(top), revlog.short(pp[0]), revlog.short(pp[1])))
376 sys.exit(1)
377 sys.exit(1)
377 return top
378 return top
378 return None
379 return None
379 def check_localchanges(self, repo):
380 def check_localchanges(self, repo):
380 (c, a, r, d, u) = repo.changes(None, None)
381 (c, a, r, d, u) = repo.changes(None, None)
381 if c or a or d or r:
382 if c or a or d or r:
382 self.ui.write("Local changes found, refresh first\n")
383 self.ui.write("Local changes found, refresh first\n")
383 sys.exit(1)
384 sys.exit(1)
384 def new(self, repo, patch, msg=None, force=None):
385 def new(self, repo, patch, msg=None, force=None):
385 if not force:
386 if not force:
386 self.check_localchanges(repo)
387 self.check_localchanges(repo)
387 self.check_toppatch(repo)
388 self.check_toppatch(repo)
388 wlock = repo.wlock()
389 wlock = repo.wlock()
389 insert = self.series_end()
390 insert = self.series_end()
390 if msg:
391 if msg:
391 n = repo.commit([], "[mq]: %s" % msg, force=True, wlock=wlock)
392 n = repo.commit([], "[mq]: %s" % msg, force=True, wlock=wlock)
392 else:
393 else:
393 n = repo.commit([],
394 n = repo.commit([],
394 "New patch: %s" % patch, force=True, wlock=wlock)
395 "New patch: %s" % patch, force=True, wlock=wlock)
395 if n == None:
396 if n == None:
396 self.ui.warn("repo commit failed\n")
397 self.ui.warn("repo commit failed\n")
397 sys.exit(1)
398 sys.exit(1)
398 self.full_series[insert:insert] = [patch]
399 self.full_series[insert:insert] = [patch]
399 self.applied.append(revlog.hex(n) + ":" + patch)
400 self.applied.append(revlog.hex(n) + ":" + patch)
400 self.read_series(self.full_series)
401 self.read_series(self.full_series)
401 self.series_dirty = 1
402 self.series_dirty = 1
402 self.applied_dirty = 1
403 self.applied_dirty = 1
403 p = self.opener(patch, "w")
404 p = self.opener(patch, "w")
404 if msg:
405 if msg:
405 msg = msg + "\n"
406 msg = msg + "\n"
406 p.write(msg)
407 p.write(msg)
407 p.close()
408 p.close()
408 wlock = None
409 wlock = None
409 r = self.qrepo()
410 r = self.qrepo()
410 if r: r.add([patch])
411 if r: r.add([patch])
411
412
412 def strip(self, repo, rev, update=True, backup="all", wlock=None):
413 def strip(self, repo, rev, update=True, backup="all", wlock=None):
413 def limitheads(chlog, stop):
414 def limitheads(chlog, stop):
414 """return the list of all nodes that have no children"""
415 """return the list of all nodes that have no children"""
415 p = {}
416 p = {}
416 h = []
417 h = []
417 stoprev = 0
418 stoprev = 0
418 if stop in chlog.nodemap:
419 if stop in chlog.nodemap:
419 stoprev = chlog.rev(stop)
420 stoprev = chlog.rev(stop)
420
421
421 for r in range(chlog.count() - 1, -1, -1):
422 for r in range(chlog.count() - 1, -1, -1):
422 n = chlog.node(r)
423 n = chlog.node(r)
423 if n not in p:
424 if n not in p:
424 h.append(n)
425 h.append(n)
425 if n == stop:
426 if n == stop:
426 break
427 break
427 if r < stoprev:
428 if r < stoprev:
428 break
429 break
429 for pn in chlog.parents(n):
430 for pn in chlog.parents(n):
430 p[pn] = 1
431 p[pn] = 1
431 return h
432 return h
432
433
433 def bundle(cg):
434 def bundle(cg):
434 backupdir = repo.join("strip-backup")
435 backupdir = repo.join("strip-backup")
435 if not os.path.isdir(backupdir):
436 if not os.path.isdir(backupdir):
436 os.mkdir(backupdir)
437 os.mkdir(backupdir)
437 name = os.path.join(backupdir, "%s" % revlog.short(rev))
438 name = os.path.join(backupdir, "%s" % revlog.short(rev))
438 name = savename(name)
439 name = savename(name)
439 self.ui.warn("saving bundle to %s\n" % name)
440 self.ui.warn("saving bundle to %s\n" % name)
440 # TODO, exclusive open
441 # TODO, exclusive open
441 f = open(name, "wb")
442 f = open(name, "wb")
442 try:
443 try:
443 f.write("HG10")
444 f.write("HG10")
444 z = bz2.BZ2Compressor(9)
445 z = bz2.BZ2Compressor(9)
445 while 1:
446 while 1:
446 chunk = cg.read(4096)
447 chunk = cg.read(4096)
447 if not chunk:
448 if not chunk:
448 break
449 break
449 f.write(z.compress(chunk))
450 f.write(z.compress(chunk))
450 f.write(z.flush())
451 f.write(z.flush())
451 except:
452 except:
452 os.unlink(name)
453 os.unlink(name)
453 raise
454 raise
454 f.close()
455 f.close()
455 return name
456 return name
456
457
457 def stripall(rev, revnum):
458 def stripall(rev, revnum):
458 cl = repo.changelog
459 cl = repo.changelog
459 c = cl.read(rev)
460 c = cl.read(rev)
460 mm = repo.manifest.read(c[0])
461 mm = repo.manifest.read(c[0])
461 seen = {}
462 seen = {}
462
463
463 for x in xrange(revnum, cl.count()):
464 for x in xrange(revnum, cl.count()):
464 c = cl.read(cl.node(x))
465 c = cl.read(cl.node(x))
465 for f in c[3]:
466 for f in c[3]:
466 if f in seen:
467 if f in seen:
467 continue
468 continue
468 seen[f] = 1
469 seen[f] = 1
469 if f in mm:
470 if f in mm:
470 filerev = mm[f]
471 filerev = mm[f]
471 else:
472 else:
472 filerev = 0
473 filerev = 0
473 seen[f] = filerev
474 seen[f] = filerev
474 # we go in two steps here so the strip loop happens in a
475 # we go in two steps here so the strip loop happens in a
475 # sensible order. When stripping many files, this helps keep
476 # sensible order. When stripping many files, this helps keep
476 # our disk access patterns under control.
477 # our disk access patterns under control.
477 list = seen.keys()
478 list = seen.keys()
478 list.sort()
479 list.sort()
479 for f in list:
480 for f in list:
480 ff = repo.file(f)
481 ff = repo.file(f)
481 filerev = seen[f]
482 filerev = seen[f]
482 if filerev != 0:
483 if filerev != 0:
483 if filerev in ff.nodemap:
484 if filerev in ff.nodemap:
484 filerev = ff.rev(filerev)
485 filerev = ff.rev(filerev)
485 else:
486 else:
486 filerev = 0
487 filerev = 0
487 ff.strip(filerev, revnum)
488 ff.strip(filerev, revnum)
488
489
489 if not wlock:
490 if not wlock:
490 wlock = repo.wlock()
491 wlock = repo.wlock()
491 lock = repo.lock()
492 lock = repo.lock()
492 chlog = repo.changelog
493 chlog = repo.changelog
493 # TODO delete the undo files, and handle undo of merge sets
494 # TODO delete the undo files, and handle undo of merge sets
494 pp = chlog.parents(rev)
495 pp = chlog.parents(rev)
495 revnum = chlog.rev(rev)
496 revnum = chlog.rev(rev)
496
497
497 if update:
498 if update:
498 urev = self.qparents(repo, rev)
499 urev = self.qparents(repo, rev)
499 repo.update(urev, allow=False, force=True, wlock=wlock)
500 repo.update(urev, allow=False, force=True, wlock=wlock)
500 repo.dirstate.write()
501 repo.dirstate.write()
501
502
502 # save is a list of all the branches we are truncating away
503 # save is a list of all the branches we are truncating away
503 # that we actually want to keep. changegroup will be used
504 # that we actually want to keep. changegroup will be used
504 # to preserve them and add them back after the truncate
505 # to preserve them and add them back after the truncate
505 saveheads = []
506 saveheads = []
506 savebases = {}
507 savebases = {}
507
508
508 tip = chlog.tip()
509 tip = chlog.tip()
509 heads = limitheads(chlog, rev)
510 heads = limitheads(chlog, rev)
510 seen = {}
511 seen = {}
511
512
512 # search through all the heads, finding those where the revision
513 # search through all the heads, finding those where the revision
513 # we want to strip away is an ancestor. Also look for merges
514 # we want to strip away is an ancestor. Also look for merges
514 # that might be turned into new heads by the strip.
515 # that might be turned into new heads by the strip.
515 while heads:
516 while heads:
516 h = heads.pop()
517 h = heads.pop()
517 n = h
518 n = h
518 while True:
519 while True:
519 seen[n] = 1
520 seen[n] = 1
520 pp = chlog.parents(n)
521 pp = chlog.parents(n)
521 if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum:
522 if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum:
522 if pp[1] not in seen:
523 if pp[1] not in seen:
523 heads.append(pp[1])
524 heads.append(pp[1])
524 if pp[0] == revlog.nullid:
525 if pp[0] == revlog.nullid:
525 break
526 break
526 if chlog.rev(pp[0]) < revnum:
527 if chlog.rev(pp[0]) < revnum:
527 break
528 break
528 n = pp[0]
529 n = pp[0]
529 if n == rev:
530 if n == rev:
530 break
531 break
531 r = chlog.reachable(h, rev)
532 r = chlog.reachable(h, rev)
532 if rev not in r:
533 if rev not in r:
533 saveheads.append(h)
534 saveheads.append(h)
534 for x in r:
535 for x in r:
535 if chlog.rev(x) > revnum:
536 if chlog.rev(x) > revnum:
536 savebases[x] = 1
537 savebases[x] = 1
537
538
538 # create a changegroup for all the branches we need to keep
539 # create a changegroup for all the branches we need to keep
539 if backup is "all":
540 if backup is "all":
540 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
541 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
541 bundle(backupch)
542 bundle(backupch)
542 if saveheads:
543 if saveheads:
543 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
544 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
544 chgrpfile = bundle(backupch)
545 chgrpfile = bundle(backupch)
545
546
546 stripall(rev, revnum)
547 stripall(rev, revnum)
547
548
548 change = chlog.read(rev)
549 change = chlog.read(rev)
549 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
550 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
550 chlog.strip(revnum, revnum)
551 chlog.strip(revnum, revnum)
551 if saveheads:
552 if saveheads:
552 self.ui.status("adding branch\n")
553 self.ui.status("adding branch\n")
553 commands.unbundle(self.ui, repo, chgrpfile, update=False)
554 commands.unbundle(self.ui, repo, chgrpfile, update=False)
554 if backup is not "strip":
555 if backup is not "strip":
555 os.unlink(chgrpfile)
556 os.unlink(chgrpfile)
556
557
557 def isapplied(self, patch):
558 def isapplied(self, patch):
558 """returns (index, rev, patch)"""
559 """returns (index, rev, patch)"""
559 for i in xrange(len(self.applied)):
560 for i in xrange(len(self.applied)):
560 p = self.applied[i]
561 p = self.applied[i]
561 a = p.split(':')
562 a = p.split(':')
562 if a[1] == patch:
563 if a[1] == patch:
563 return (i, a[0], a[1])
564 return (i, a[0], a[1])
564 return None
565 return None
565
566
566 def lookup(self, patch):
567 def lookup(self, patch):
567 if patch == None:
568 if patch == None:
568 return None
569 return None
569 if patch in self.series:
570 if patch in self.series:
570 return patch
571 return patch
571 if not os.path.isfile(os.path.join(self.path, patch)):
572 if not os.path.isfile(os.path.join(self.path, patch)):
572 try:
573 try:
573 sno = int(patch)
574 sno = int(patch)
574 except(ValueError, OverflowError):
575 except(ValueError, OverflowError):
575 self.ui.warn("patch %s not in series\n" % patch)
576 self.ui.warn("patch %s not in series\n" % patch)
576 sys.exit(1)
577 sys.exit(1)
577 if sno >= len(self.series):
578 if sno >= len(self.series):
578 self.ui.warn("patch number %d is out of range\n" % sno)
579 self.ui.warn("patch number %d is out of range\n" % sno)
579 sys.exit(1)
580 sys.exit(1)
580 patch = self.series[sno]
581 patch = self.series[sno]
581 else:
582 else:
582 self.ui.warn("patch %s not in series\n" % patch)
583 self.ui.warn("patch %s not in series\n" % patch)
583 sys.exit(1)
584 sys.exit(1)
584 return patch
585 return patch
585
586
586 def push(self, repo, patch=None, force=False, list=False,
587 def push(self, repo, patch=None, force=False, list=False,
587 mergeq=None, wlock=None):
588 mergeq=None, wlock=None):
588 if not wlock:
589 if not wlock:
589 wlock = repo.wlock()
590 wlock = repo.wlock()
590 patch = self.lookup(patch)
591 patch = self.lookup(patch)
591 if patch and self.isapplied(patch):
592 if patch and self.isapplied(patch):
592 self.ui.warn("patch %s is already applied\n" % patch)
593 self.ui.warn("patch %s is already applied\n" % patch)
593 sys.exit(1)
594 sys.exit(1)
594 if self.series_end() == len(self.series):
595 if self.series_end() == len(self.series):
595 self.ui.warn("File series fully applied\n")
596 self.ui.warn("File series fully applied\n")
596 sys.exit(1)
597 sys.exit(1)
597 if not force:
598 if not force:
598 self.check_localchanges(repo)
599 self.check_localchanges(repo)
599
600
600 self.applied_dirty = 1;
601 self.applied_dirty = 1;
601 start = self.series_end()
602 start = self.series_end()
602 if start > 0:
603 if start > 0:
603 self.check_toppatch(repo)
604 self.check_toppatch(repo)
604 if not patch:
605 if not patch:
605 patch = self.series[start]
606 patch = self.series[start]
606 end = start + 1
607 end = start + 1
607 else:
608 else:
608 end = self.series.index(patch, start) + 1
609 end = self.series.index(patch, start) + 1
609 s = self.series[start:end]
610 s = self.series[start:end]
610 if mergeq:
611 if mergeq:
611 ret = self.mergepatch(repo, mergeq, s, wlock)
612 ret = self.mergepatch(repo, mergeq, s, wlock)
612 else:
613 else:
613 ret = self.apply(repo, s, list, wlock=wlock)
614 ret = self.apply(repo, s, list, wlock=wlock)
614 top = self.applied[-1].split(':')[1]
615 top = self.applied[-1].split(':')[1]
615 if ret[0]:
616 if ret[0]:
616 self.ui.write("Errors during apply, please fix and refresh %s\n" %
617 self.ui.write("Errors during apply, please fix and refresh %s\n" %
617 top)
618 top)
618 else:
619 else:
619 self.ui.write("Now at: %s\n" % top)
620 self.ui.write("Now at: %s\n" % top)
620 return ret[0]
621 return ret[0]
621
622
622 def pop(self, repo, patch=None, force=False, update=True, wlock=None):
623 def pop(self, repo, patch=None, force=False, update=True, wlock=None):
623 def getfile(f, rev):
624 def getfile(f, rev):
624 t = repo.file(f).read(rev)
625 t = repo.file(f).read(rev)
625 try:
626 try:
626 repo.wfile(f, "w").write(t)
627 repo.wfile(f, "w").write(t)
627 except IOError:
628 except IOError:
628 try:
629 try:
629 os.makedirs(os.path.dirname(repo.wjoin(f)))
630 os.makedirs(os.path.dirname(repo.wjoin(f)))
630 except OSError, err:
631 except OSError, err:
631 if err.errno != errno.EEXIST: raise
632 if err.errno != errno.EEXIST: raise
632 repo.wfile(f, "w").write(t)
633 repo.wfile(f, "w").write(t)
633
634
634 if not wlock:
635 if not wlock:
635 wlock = repo.wlock()
636 wlock = repo.wlock()
636 if patch:
637 if patch:
637 # index, rev, patch
638 # index, rev, patch
638 info = self.isapplied(patch)
639 info = self.isapplied(patch)
639 if not info:
640 if not info:
640 patch = self.lookup(patch)
641 patch = self.lookup(patch)
641 info = self.isapplied(patch)
642 info = self.isapplied(patch)
642 if not info:
643 if not info:
643 self.ui.warn("patch %s is not applied\n" % patch)
644 self.ui.warn("patch %s is not applied\n" % patch)
644 sys.exit(1)
645 sys.exit(1)
645 if len(self.applied) == 0:
646 if len(self.applied) == 0:
646 self.ui.warn("No patches applied\n")
647 self.ui.warn("No patches applied\n")
647 sys.exit(1)
648 sys.exit(1)
648
649
649 if not update:
650 if not update:
650 parents = repo.dirstate.parents()
651 parents = repo.dirstate.parents()
651 rr = [ revlog.bin(x.split(':')[0]) for x in self.applied ]
652 rr = [ revlog.bin(x.split(':')[0]) for x in self.applied ]
652 for p in parents:
653 for p in parents:
653 if p in rr:
654 if p in rr:
654 self.ui.warn("qpop: forcing dirstate update\n")
655 self.ui.warn("qpop: forcing dirstate update\n")
655 update = True
656 update = True
656
657
657 if not force and update:
658 if not force and update:
658 self.check_localchanges(repo)
659 self.check_localchanges(repo)
659
660
660 self.applied_dirty = 1;
661 self.applied_dirty = 1;
661 end = len(self.applied)
662 end = len(self.applied)
662 if not patch:
663 if not patch:
663 info = [len(self.applied) - 1] + self.applied[-1].split(':')
664 info = [len(self.applied) - 1] + self.applied[-1].split(':')
664 start = info[0]
665 start = info[0]
665 rev = revlog.bin(info[1])
666 rev = revlog.bin(info[1])
666
667
667 # we know there are no local changes, so we can make a simplified
668 # we know there are no local changes, so we can make a simplified
668 # form of hg.update.
669 # form of hg.update.
669 if update:
670 if update:
670 top = self.check_toppatch(repo)
671 top = self.check_toppatch(repo)
671 qp = self.qparents(repo, rev)
672 qp = self.qparents(repo, rev)
672 changes = repo.changelog.read(qp)
673 changes = repo.changelog.read(qp)
673 mf1 = repo.manifest.readflags(changes[0])
674 mf1 = repo.manifest.readflags(changes[0])
674 mmap = repo.manifest.read(changes[0])
675 mmap = repo.manifest.read(changes[0])
675 (c, a, r, d, u) = repo.changes(qp, top)
676 (c, a, r, d, u) = repo.changes(qp, top)
676 if d:
677 if d:
677 raise util.Abort("deletions found between repo revs")
678 raise util.Abort("deletions found between repo revs")
678 for f in c:
679 for f in c:
679 getfile(f, mmap[f])
680 getfile(f, mmap[f])
680 for f in r:
681 for f in r:
681 getfile(f, mmap[f])
682 getfile(f, mmap[f])
682 util.set_exec(repo.wjoin(f), mf1[f])
683 util.set_exec(repo.wjoin(f), mf1[f])
683 repo.dirstate.update(c + r, 'n')
684 repo.dirstate.update(c + r, 'n')
684 for f in a:
685 for f in a:
685 try: os.unlink(repo.wjoin(f))
686 try: os.unlink(repo.wjoin(f))
686 except: raise
687 except: raise
687 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
688 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
688 except: pass
689 except: pass
689 if a:
690 if a:
690 repo.dirstate.forget(a)
691 repo.dirstate.forget(a)
691 repo.dirstate.setparents(qp, revlog.nullid)
692 repo.dirstate.setparents(qp, revlog.nullid)
692 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
693 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
693 del self.applied[start:end]
694 del self.applied[start:end]
694 if len(self.applied):
695 if len(self.applied):
695 self.ui.write("Now at: %s\n" % self.applied[-1].split(':')[1])
696 self.ui.write("Now at: %s\n" % self.applied[-1].split(':')[1])
696 else:
697 else:
697 self.ui.write("Patch queue now empty\n")
698 self.ui.write("Patch queue now empty\n")
698
699
699 def diff(self, repo, files):
700 def diff(self, repo, files):
700 top = self.check_toppatch(repo)
701 top = self.check_toppatch(repo)
701 if not top:
702 if not top:
702 self.ui.write("No patches applied\n")
703 self.ui.write("No patches applied\n")
703 return
704 return
704 qp = self.qparents(repo, top)
705 qp = self.qparents(repo, top)
705 commands.dodiff(sys.stdout, self.ui, repo, qp, None, files)
706 commands.dodiff(sys.stdout, self.ui, repo, qp, None, files)
706
707
707 def refresh(self, repo, short=False):
708 def refresh(self, repo, short=False):
708 if len(self.applied) == 0:
709 if len(self.applied) == 0:
709 self.ui.write("No patches applied\n")
710 self.ui.write("No patches applied\n")
710 return
711 return
711 wlock = repo.wlock()
712 wlock = repo.wlock()
712 self.check_toppatch(repo)
713 self.check_toppatch(repo)
713 qp = self.qparents(repo)
714 qp = self.qparents(repo)
714 (top, patch) = self.applied[-1].split(':')
715 (top, patch) = self.applied[-1].split(':')
715 top = revlog.bin(top)
716 top = revlog.bin(top)
716 cparents = repo.changelog.parents(top)
717 cparents = repo.changelog.parents(top)
717 patchparent = self.qparents(repo, top)
718 patchparent = self.qparents(repo, top)
718 message, comments, user, patchfound = self.readheaders(patch)
719 message, comments, user, patchfound = self.readheaders(patch)
719
720
720 patchf = self.opener(patch, "w")
721 patchf = self.opener(patch, "w")
721 if comments:
722 if comments:
722 comments = "\n".join(comments) + '\n\n'
723 comments = "\n".join(comments) + '\n\n'
723 patchf.write(comments)
724 patchf.write(comments)
724
725
725 tip = repo.changelog.tip()
726 tip = repo.changelog.tip()
726 if top == tip:
727 if top == tip:
727 # if the top of our patch queue is also the tip, there is an
728 # if the top of our patch queue is also the tip, there is an
728 # optimization here. We update the dirstate in place and strip
729 # optimization here. We update the dirstate in place and strip
729 # off the tip commit. Then just commit the current directory
730 # off the tip commit. Then just commit the current directory
730 # tree. We can also send repo.commit the list of files
731 # tree. We can also send repo.commit the list of files
731 # changed to speed up the diff
732 # changed to speed up the diff
732 #
733 #
733 # in short mode, we only diff the files included in the
734 # in short mode, we only diff the files included in the
734 # patch already
735 # patch already
735 #
736 #
736 # this should really read:
737 # this should really read:
737 #(cc, dd, aa, aa2, uu) = repo.changes(tip, patchparent)
738 #(cc, dd, aa, aa2, uu) = repo.changes(tip, patchparent)
738 # but we do it backwards to take advantage of manifest/chlog
739 # but we do it backwards to take advantage of manifest/chlog
739 # caching against the next repo.changes call
740 # caching against the next repo.changes call
740 #
741 #
741 (cc, aa, dd, aa2, uu) = repo.changes(patchparent, tip)
742 (cc, aa, dd, aa2, uu) = repo.changes(patchparent, tip)
742 if short:
743 if short:
743 filelist = cc + aa + dd
744 filelist = cc + aa + dd
744 else:
745 else:
745 filelist = None
746 filelist = None
746 (c, a, r, d, u) = repo.changes(None, None, filelist)
747 (c, a, r, d, u) = repo.changes(None, None, filelist)
747
748
748 # we might end up with files that were added between tip and
749 # we might end up with files that were added between tip and
749 # the dirstate parent, but then changed in the local dirstate.
750 # the dirstate parent, but then changed in the local dirstate.
750 # in this case, we want them to only show up in the added section
751 # in this case, we want them to only show up in the added section
751 for x in c:
752 for x in c:
752 if x not in aa:
753 if x not in aa:
753 cc.append(x)
754 cc.append(x)
754 # we might end up with files added by the local dirstate that
755 # we might end up with files added by the local dirstate that
755 # were deleted by the patch. In this case, they should only
756 # were deleted by the patch. In this case, they should only
756 # show up in the changed section.
757 # show up in the changed section.
757 for x in a:
758 for x in a:
758 if x in dd:
759 if x in dd:
759 del dd[dd.index(x)]
760 del dd[dd.index(x)]
760 cc.append(x)
761 cc.append(x)
761 else:
762 else:
762 aa.append(x)
763 aa.append(x)
763 # make sure any files deleted in the local dirstate
764 # make sure any files deleted in the local dirstate
764 # are not in the add or change column of the patch
765 # are not in the add or change column of the patch
765 forget = []
766 forget = []
766 for x in d + r:
767 for x in d + r:
767 if x in aa:
768 if x in aa:
768 del aa[aa.index(x)]
769 del aa[aa.index(x)]
769 forget.append(x)
770 forget.append(x)
770 continue
771 continue
771 elif x in cc:
772 elif x in cc:
772 del cc[cc.index(x)]
773 del cc[cc.index(x)]
773 dd.append(x)
774 dd.append(x)
774
775
775 c = list(util.unique(cc))
776 c = list(util.unique(cc))
776 r = list(util.unique(dd))
777 r = list(util.unique(dd))
777 a = list(util.unique(aa))
778 a = list(util.unique(aa))
778 filelist = list(util.unique(c + r + a ))
779 filelist = list(util.unique(c + r + a ))
779 commands.dodiff(patchf, self.ui, repo, patchparent, None,
780 commands.dodiff(patchf, self.ui, repo, patchparent, None,
780 filelist, changes=(c, a, r, [], u))
781 filelist, changes=(c, a, r, [], u))
781 patchf.close()
782 patchf.close()
782
783
783 changes = repo.changelog.read(tip)
784 changes = repo.changelog.read(tip)
784 repo.dirstate.setparents(*cparents)
785 repo.dirstate.setparents(*cparents)
785 repo.dirstate.update(a, 'a')
786 repo.dirstate.update(a, 'a')
786 repo.dirstate.update(r, 'r')
787 repo.dirstate.update(r, 'r')
787 repo.dirstate.update(c, 'n')
788 repo.dirstate.update(c, 'n')
788 repo.dirstate.forget(forget)
789 repo.dirstate.forget(forget)
789
790
790 if not message:
791 if not message:
791 message = "patch queue: %s\n" % patch
792 message = "patch queue: %s\n" % patch
792 else:
793 else:
793 message = "\n".join(message)
794 message = "\n".join(message)
794 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
795 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
795 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
796 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
796 self.applied[-1] = revlog.hex(n) + ':' + patch
797 self.applied[-1] = revlog.hex(n) + ':' + patch
797 self.applied_dirty = 1
798 self.applied_dirty = 1
798 else:
799 else:
799 commands.dodiff(patchf, self.ui, repo, patchparent, None)
800 commands.dodiff(patchf, self.ui, repo, patchparent, None)
800 patchf.close()
801 patchf.close()
801 self.pop(repo, force=True, wlock=wlock)
802 self.pop(repo, force=True, wlock=wlock)
802 self.push(repo, force=True, wlock=wlock)
803 self.push(repo, force=True, wlock=wlock)
803
804
804 def init(self, repo, create=False):
805 def init(self, repo, create=False):
805 if os.path.isdir(self.path):
806 if os.path.isdir(self.path):
806 raise util.Abort("patch queue directory already exists")
807 raise util.Abort("patch queue directory already exists")
807 os.mkdir(self.path)
808 os.mkdir(self.path)
808 if create:
809 if create:
809 return self.qrepo(create=True)
810 return self.qrepo(create=True)
810
811
811 def unapplied(self, repo, patch=None):
812 def unapplied(self, repo, patch=None):
812 if patch and patch not in self.series:
813 if patch and patch not in self.series:
813 self.ui.warn("%s not in the series file\n" % patch)
814 self.ui.warn("%s not in the series file\n" % patch)
814 sys.exit(1)
815 sys.exit(1)
815 if not patch:
816 if not patch:
816 start = self.series_end()
817 start = self.series_end()
817 else:
818 else:
818 start = self.series.index(patch) + 1
819 start = self.series.index(patch) + 1
819 for p in self.series[start:]:
820 for p in self.series[start:]:
820 self.ui.write("%s\n" % p)
821 self.ui.write("%s\n" % p)
821
822
822 def qseries(self, repo, missing=None):
823 def qseries(self, repo, missing=None):
823 start = self.series_end()
824 start = self.series_end()
824 if not missing:
825 if not missing:
825 for p in self.series[:start]:
826 for p in self.series[:start]:
826 if self.ui.verbose:
827 if self.ui.verbose:
827 self.ui.write("%d A " % self.series.index(p))
828 self.ui.write("%d A " % self.series.index(p))
828 self.ui.write("%s\n" % p)
829 self.ui.write("%s\n" % p)
829 for p in self.series[start:]:
830 for p in self.series[start:]:
830 if self.ui.verbose:
831 if self.ui.verbose:
831 self.ui.write("%d U " % self.series.index(p))
832 self.ui.write("%d U " % self.series.index(p))
832 self.ui.write("%s\n" % p)
833 self.ui.write("%s\n" % p)
833 else:
834 else:
834 list = []
835 list = []
835 for root, dirs, files in os.walk(self.path):
836 for root, dirs, files in os.walk(self.path):
836 d = root[len(self.path) + 1:]
837 d = root[len(self.path) + 1:]
837 for f in files:
838 for f in files:
838 fl = os.path.join(d, f)
839 fl = os.path.join(d, f)
839 if (fl not in self.series and
840 if (fl not in self.series and
840 fl not in (self.status_path, self.series_path)
841 fl not in (self.status_path, self.series_path)
841 and not fl.startswith('.')):
842 and not fl.startswith('.')):
842 list.append(fl)
843 list.append(fl)
843 list.sort()
844 list.sort()
844 if list:
845 if list:
845 for x in list:
846 for x in list:
846 if self.ui.verbose:
847 if self.ui.verbose:
847 self.ui.write("D ")
848 self.ui.write("D ")
848 self.ui.write("%s\n" % x)
849 self.ui.write("%s\n" % x)
849
850
850 def issaveline(self, l):
851 def issaveline(self, l):
851 name = l.split(':')[1]
852 name = l.split(':')[1]
852 if name == '.hg.patches.save.line':
853 if name == '.hg.patches.save.line':
853 return True
854 return True
854
855
855 def qrepo(self, create=False):
856 def qrepo(self, create=False):
856 if create or os.path.isdir(os.path.join(self.path, ".hg")):
857 if create or os.path.isdir(os.path.join(self.path, ".hg")):
857 return hg.repository(self.ui, path=self.path, create=create)
858 return hg.repository(self.ui, path=self.path, create=create)
858
859
859 def restore(self, repo, rev, delete=None, qupdate=None):
860 def restore(self, repo, rev, delete=None, qupdate=None):
860 c = repo.changelog.read(rev)
861 c = repo.changelog.read(rev)
861 desc = c[4].strip()
862 desc = c[4].strip()
862 lines = desc.splitlines()
863 lines = desc.splitlines()
863 i = 0
864 i = 0
864 datastart = None
865 datastart = None
865 series = []
866 series = []
866 applied = []
867 applied = []
867 qpp = None
868 qpp = None
868 for i in xrange(0, len(lines)):
869 for i in xrange(0, len(lines)):
869 if lines[i] == 'Patch Data:':
870 if lines[i] == 'Patch Data:':
870 datastart = i + 1
871 datastart = i + 1
871 elif lines[i].startswith('Dirstate:'):
872 elif lines[i].startswith('Dirstate:'):
872 l = lines[i].rstrip()
873 l = lines[i].rstrip()
873 l = l[10:].split(' ')
874 l = l[10:].split(' ')
874 qpp = [ hg.bin(x) for x in l ]
875 qpp = [ hg.bin(x) for x in l ]
875 elif datastart != None:
876 elif datastart != None:
876 l = lines[i].rstrip()
877 l = lines[i].rstrip()
877 index = l.index(':')
878 index = l.index(':')
878 id = l[:index]
879 id = l[:index]
879 file = l[index + 1:]
880 file = l[index + 1:]
880 if id:
881 if id:
881 applied.append(l)
882 applied.append(l)
882 series.append(file)
883 series.append(file)
883 if datastart == None:
884 if datastart == None:
884 self.ui.warn("No saved patch data found\n")
885 self.ui.warn("No saved patch data found\n")
885 return 1
886 return 1
886 self.ui.warn("restoring status: %s\n" % lines[0])
887 self.ui.warn("restoring status: %s\n" % lines[0])
887 self.full_series = series
888 self.full_series = series
888 self.applied = applied
889 self.applied = applied
889 self.read_series(self.full_series)
890 self.read_series(self.full_series)
890 self.series_dirty = 1
891 self.series_dirty = 1
891 self.applied_dirty = 1
892 self.applied_dirty = 1
892 heads = repo.changelog.heads()
893 heads = repo.changelog.heads()
893 if delete:
894 if delete:
894 if rev not in heads:
895 if rev not in heads:
895 self.ui.warn("save entry has children, leaving it alone\n")
896 self.ui.warn("save entry has children, leaving it alone\n")
896 else:
897 else:
897 self.ui.warn("removing save entry %s\n" % hg.short(rev))
898 self.ui.warn("removing save entry %s\n" % hg.short(rev))
898 pp = repo.dirstate.parents()
899 pp = repo.dirstate.parents()
899 if rev in pp:
900 if rev in pp:
900 update = True
901 update = True
901 else:
902 else:
902 update = False
903 update = False
903 self.strip(repo, rev, update=update, backup='strip')
904 self.strip(repo, rev, update=update, backup='strip')
904 if qpp:
905 if qpp:
905 self.ui.warn("saved queue repository parents: %s %s\n" %
906 self.ui.warn("saved queue repository parents: %s %s\n" %
906 (hg.short(qpp[0]), hg.short(qpp[1])))
907 (hg.short(qpp[0]), hg.short(qpp[1])))
907 if qupdate:
908 if qupdate:
908 print "queue directory updating"
909 print "queue directory updating"
909 r = self.qrepo()
910 r = self.qrepo()
910 if not r:
911 if not r:
911 self.ui.warn("Unable to load queue repository\n")
912 self.ui.warn("Unable to load queue repository\n")
912 return 1
913 return 1
913 r.update(qpp[0], allow=False, force=True)
914 r.update(qpp[0], allow=False, force=True)
914
915
915 def save(self, repo, msg=None):
916 def save(self, repo, msg=None):
916 if len(self.applied) == 0:
917 if len(self.applied) == 0:
917 self.ui.warn("save: no patches applied, exiting\n")
918 self.ui.warn("save: no patches applied, exiting\n")
918 return 1
919 return 1
919 if self.issaveline(self.applied[-1]):
920 if self.issaveline(self.applied[-1]):
920 self.ui.warn("status is already saved\n")
921 self.ui.warn("status is already saved\n")
921 return 1
922 return 1
922
923
923 ar = [ ':' + x for x in self.full_series ]
924 ar = [ ':' + x for x in self.full_series ]
924 if not msg:
925 if not msg:
925 msg = "hg patches saved state"
926 msg = "hg patches saved state"
926 else:
927 else:
927 msg = "hg patches: " + msg.rstrip('\r\n')
928 msg = "hg patches: " + msg.rstrip('\r\n')
928 r = self.qrepo()
929 r = self.qrepo()
929 if r:
930 if r:
930 pp = r.dirstate.parents()
931 pp = r.dirstate.parents()
931 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
932 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
932 msg += "\n\nPatch Data:\n"
933 msg += "\n\nPatch Data:\n"
933 text = msg + "\n".join(self.applied) + '\n' + (ar and "\n".join(ar)
934 text = msg + "\n".join(self.applied) + '\n' + (ar and "\n".join(ar)
934 + '\n' or "")
935 + '\n' or "")
935 n = repo.commit(None, text, user=None, force=1)
936 n = repo.commit(None, text, user=None, force=1)
936 if not n:
937 if not n:
937 self.ui.warn("repo commit failed\n")
938 self.ui.warn("repo commit failed\n")
938 return 1
939 return 1
939 self.applied.append(revlog.hex(n) + ":" + '.hg.patches.save.line')
940 self.applied.append(revlog.hex(n) + ":" + '.hg.patches.save.line')
940 self.applied_dirty = 1
941 self.applied_dirty = 1
941
942
942 def series_end(self):
943 def series_end(self):
943 end = 0
944 end = 0
944 if len(self.applied) > 0:
945 if len(self.applied) > 0:
945 (top, p) = self.applied[-1].split(':')
946 (top, p) = self.applied[-1].split(':')
946 try:
947 try:
947 end = self.series.index(p)
948 end = self.series.index(p)
948 except ValueError:
949 except ValueError:
949 return 0
950 return 0
950 return end + 1
951 return end + 1
951 return end
952 return end
952
953
953 def qapplied(self, repo, patch=None):
954 def qapplied(self, repo, patch=None):
954 if patch and patch not in self.series:
955 if patch and patch not in self.series:
955 self.ui.warn("%s not in the series file\n" % patch)
956 self.ui.warn("%s not in the series file\n" % patch)
956 sys.exit(1)
957 sys.exit(1)
957 if not patch:
958 if not patch:
958 end = len(self.applied)
959 end = len(self.applied)
959 else:
960 else:
960 end = self.series.index(patch) + 1
961 end = self.series.index(patch) + 1
961 for x in xrange(end):
962 for x in xrange(end):
962 p = self.appliedname(x)
963 p = self.appliedname(x)
963 self.ui.write("%s\n" % p)
964 self.ui.write("%s\n" % p)
964
965
965 def appliedname(self, index):
966 def appliedname(self, index):
966 p = self.applied[index]
967 p = self.applied[index]
967 if not self.ui.verbose:
968 if not self.ui.verbose:
968 p = p.split(':')[1]
969 p = p.split(':')[1]
969 return p
970 return p
970
971
971 def top(self, repo):
972 def top(self, repo):
972 if len(self.applied):
973 if len(self.applied):
973 p = self.appliedname(-1)
974 p = self.appliedname(-1)
974 self.ui.write(p + '\n')
975 self.ui.write(p + '\n')
975 else:
976 else:
976 self.ui.write("No patches applied\n")
977 self.ui.write("No patches applied\n")
977
978
978 def next(self, repo):
979 def next(self, repo):
979 end = self.series_end()
980 end = self.series_end()
980 if end == len(self.series):
981 if end == len(self.series):
981 self.ui.write("All patches applied\n")
982 self.ui.write("All patches applied\n")
982 else:
983 else:
983 self.ui.write(self.series[end] + '\n')
984 self.ui.write(self.series[end] + '\n')
984
985
985 def prev(self, repo):
986 def prev(self, repo):
986 if len(self.applied) > 1:
987 if len(self.applied) > 1:
987 p = self.appliedname(-2)
988 p = self.appliedname(-2)
988 self.ui.write(p + '\n')
989 self.ui.write(p + '\n')
989 elif len(self.applied) == 1:
990 elif len(self.applied) == 1:
990 self.ui.write("Only one patch applied\n")
991 self.ui.write("Only one patch applied\n")
991 else:
992 else:
992 self.ui.write("No patches applied\n")
993 self.ui.write("No patches applied\n")
993
994
994 def qimport(self, repo, files, patch=None, existing=None, force=None):
995 def qimport(self, repo, files, patch=None, existing=None, force=None):
995 if len(files) > 1 and patch:
996 if len(files) > 1 and patch:
996 self.ui.warn("-n option not valid when importing multiple files\n")
997 self.ui.warn("-n option not valid when importing multiple files\n")
997 sys.exit(1)
998 sys.exit(1)
998 i = 0
999 i = 0
999 for filename in files:
1000 for filename in files:
1000 if existing:
1001 if existing:
1001 if not patch:
1002 if not patch:
1002 patch = filename
1003 patch = filename
1003 if not os.path.isfile(os.path.join(self.path, patch)):
1004 if not os.path.isfile(os.path.join(self.path, patch)):
1004 self.ui.warn("patch %s does not exist\n" % patch)
1005 self.ui.warn("patch %s does not exist\n" % patch)
1005 sys.exit(1)
1006 sys.exit(1)
1006 else:
1007 else:
1007 try:
1008 try:
1008 text = file(filename).read()
1009 text = file(filename).read()
1009 except IOError:
1010 except IOError:
1010 self.ui.warn("Unable to read %s\n" % patch)
1011 self.ui.warn("Unable to read %s\n" % patch)
1011 sys.exit(1)
1012 sys.exit(1)
1012 if not patch:
1013 if not patch:
1013 patch = os.path.split(filename)[1]
1014 patch = os.path.split(filename)[1]
1014 if not force and os.path.isfile(os.path.join(self.path, patch)):
1015 if not force and os.path.isfile(os.path.join(self.path, patch)):
1015 self.ui.warn("patch %s already exists\n" % patch)
1016 self.ui.warn("patch %s already exists\n" % patch)
1016 sys.exit(1)
1017 sys.exit(1)
1017 patchf = self.opener(patch, "w")
1018 patchf = self.opener(patch, "w")
1018 patchf.write(text)
1019 patchf.write(text)
1019 if patch in self.series:
1020 if patch in self.series:
1020 self.ui.warn("patch %s is already in the series file\n" % patch)
1021 self.ui.warn("patch %s is already in the series file\n" % patch)
1021 sys.exit(1)
1022 sys.exit(1)
1022 index = self.series_end() + i
1023 index = self.series_end() + i
1023 self.full_series[index:index] = [patch]
1024 self.full_series[index:index] = [patch]
1024 self.read_series(self.full_series)
1025 self.read_series(self.full_series)
1025 self.ui.warn("adding %s to series file\n" % patch)
1026 self.ui.warn("adding %s to series file\n" % patch)
1026 i += 1
1027 i += 1
1027 patch = None
1028 patch = None
1028 self.series_dirty = 1
1029 self.series_dirty = 1
1029
1030
1030 def delete(ui, repo, patch, **opts):
1031 def delete(ui, repo, patch, **opts):
1031 """remove a patch from the series file"""
1032 """remove a patch from the series file"""
1032 q = repomap[repo]
1033 q = repomap[repo]
1033 q.delete(repo, patch)
1034 q.delete(repo, patch)
1034 q.save_dirty()
1035 q.save_dirty()
1035 return 0
1036 return 0
1036
1037
1037 def applied(ui, repo, patch=None, **opts):
1038 def applied(ui, repo, patch=None, **opts):
1038 """print the patches already applied"""
1039 """print the patches already applied"""
1039 repomap[repo].qapplied(repo, patch)
1040 repomap[repo].qapplied(repo, patch)
1040 return 0
1041 return 0
1041
1042
1042 def unapplied(ui, repo, patch=None, **opts):
1043 def unapplied(ui, repo, patch=None, **opts):
1043 """print the patches not yet applied"""
1044 """print the patches not yet applied"""
1044 repomap[repo].unapplied(repo, patch)
1045 repomap[repo].unapplied(repo, patch)
1045 return 0
1046 return 0
1046
1047
1047 def qimport(ui, repo, *filename, **opts):
1048 def qimport(ui, repo, *filename, **opts):
1048 """import a patch"""
1049 """import a patch"""
1049 q = repomap[repo]
1050 q = repomap[repo]
1050 q.qimport(repo, filename, patch=opts['name'],
1051 q.qimport(repo, filename, patch=opts['name'],
1051 existing=opts['existing'], force=opts['force'])
1052 existing=opts['existing'], force=opts['force'])
1052 q.save_dirty()
1053 q.save_dirty()
1053 return 0
1054 return 0
1054
1055
1055 def init(ui, repo, **opts):
1056 def init(ui, repo, **opts):
1056 """init a new queue repository"""
1057 """init a new queue repository"""
1057 q = repomap[repo]
1058 q = repomap[repo]
1058 r = q.init(repo, create=opts['create_repo'])
1059 r = q.init(repo, create=opts['create_repo'])
1059 q.save_dirty()
1060 q.save_dirty()
1060 if r:
1061 if r:
1061 fp = r.wopener('.hgignore', 'w')
1062 fp = r.wopener('.hgignore', 'w')
1062 print >> fp, 'syntax: glob'
1063 print >> fp, 'syntax: glob'
1063 print >> fp, 'status'
1064 print >> fp, 'status'
1064 fp.close()
1065 fp.close()
1065 r.wopener('series', 'w').close()
1066 r.wopener('series', 'w').close()
1066 r.add(['.hgignore', 'series'])
1067 r.add(['.hgignore', 'series'])
1067 return 0
1068 return 0
1068
1069
1069 def commit(ui, repo, *pats, **opts):
1070 def commit(ui, repo, *pats, **opts):
1070 q = repomap[repo]
1071 q = repomap[repo]
1071 r = q.qrepo()
1072 r = q.qrepo()
1072 if not r: raise util.Abort('no queue repository')
1073 if not r: raise util.Abort('no queue repository')
1073 commands.commit(r.ui, r, *pats, **opts)
1074 commands.commit(r.ui, r, *pats, **opts)
1074
1075
1075 def series(ui, repo, **opts):
1076 def series(ui, repo, **opts):
1076 """print the entire series file"""
1077 """print the entire series file"""
1077 repomap[repo].qseries(repo, missing=opts['missing'])
1078 repomap[repo].qseries(repo, missing=opts['missing'])
1078 return 0
1079 return 0
1079
1080
1080 def top(ui, repo, **opts):
1081 def top(ui, repo, **opts):
1081 """print the name of the current patch"""
1082 """print the name of the current patch"""
1082 repomap[repo].top(repo)
1083 repomap[repo].top(repo)
1083 return 0
1084 return 0
1084
1085
1085 def next(ui, repo, **opts):
1086 def next(ui, repo, **opts):
1086 """print the name of the next patch"""
1087 """print the name of the next patch"""
1087 repomap[repo].next(repo)
1088 repomap[repo].next(repo)
1088 return 0
1089 return 0
1089
1090
1090 def prev(ui, repo, **opts):
1091 def prev(ui, repo, **opts):
1091 """print the name of the previous patch"""
1092 """print the name of the previous patch"""
1092 repomap[repo].prev(repo)
1093 repomap[repo].prev(repo)
1093 return 0
1094 return 0
1094
1095
1095 def new(ui, repo, patch, **opts):
1096 def new(ui, repo, patch, **opts):
1096 """create a new patch"""
1097 """create a new patch"""
1097 q = repomap[repo]
1098 q = repomap[repo]
1098 q.new(repo, patch, msg=opts['message'], force=opts['force'])
1099 q.new(repo, patch, msg=opts['message'], force=opts['force'])
1099 q.save_dirty()
1100 q.save_dirty()
1100 return 0
1101 return 0
1101
1102
1102 def refresh(ui, repo, **opts):
1103 def refresh(ui, repo, **opts):
1103 """update the current patch"""
1104 """update the current patch"""
1104 q = repomap[repo]
1105 q = repomap[repo]
1105 q.refresh(repo, short=opts['short'])
1106 q.refresh(repo, short=opts['short'])
1106 q.save_dirty()
1107 q.save_dirty()
1107 return 0
1108 return 0
1108
1109
1109 def diff(ui, repo, *files, **opts):
1110 def diff(ui, repo, *files, **opts):
1110 """diff of the current patch"""
1111 """diff of the current patch"""
1111 # deep in the dirstate code, the walkhelper method wants a list, not a tuple
1112 # deep in the dirstate code, the walkhelper method wants a list, not a tuple
1112 repomap[repo].diff(repo, list(files))
1113 repomap[repo].diff(repo, list(files))
1113 return 0
1114 return 0
1114
1115
1115 def lastsavename(path):
1116 def lastsavename(path):
1116 (dir, base) = os.path.split(path)
1117 (dir, base) = os.path.split(path)
1117 names = os.listdir(dir)
1118 names = os.listdir(dir)
1118 namere = re.compile("%s.([0-9]+)" % base)
1119 namere = re.compile("%s.([0-9]+)" % base)
1119 max = None
1120 max = None
1120 maxname = None
1121 maxname = None
1121 for f in names:
1122 for f in names:
1122 m = namere.match(f)
1123 m = namere.match(f)
1123 if m:
1124 if m:
1124 index = int(m.group(1))
1125 index = int(m.group(1))
1125 if max == None or index > max:
1126 if max == None or index > max:
1126 max = index
1127 max = index
1127 maxname = f
1128 maxname = f
1128 if maxname:
1129 if maxname:
1129 return (os.path.join(dir, maxname), max)
1130 return (os.path.join(dir, maxname), max)
1130 return (None, None)
1131 return (None, None)
1131
1132
1132 def savename(path):
1133 def savename(path):
1133 (last, index) = lastsavename(path)
1134 (last, index) = lastsavename(path)
1134 if last is None:
1135 if last is None:
1135 index = 0
1136 index = 0
1136 newpath = path + ".%d" % (index + 1)
1137 newpath = path + ".%d" % (index + 1)
1137 return newpath
1138 return newpath
1138
1139
1139 def push(ui, repo, patch=None, **opts):
1140 def push(ui, repo, patch=None, **opts):
1140 """push the next patch onto the stack"""
1141 """push the next patch onto the stack"""
1141 q = repomap[repo]
1142 q = repomap[repo]
1142 mergeq = None
1143 mergeq = None
1143
1144
1144 if opts['all']:
1145 if opts['all']:
1145 patch = q.series[-1]
1146 patch = q.series[-1]
1146 if opts['merge']:
1147 if opts['merge']:
1147 if opts['name']:
1148 if opts['name']:
1148 newpath = opts['name']
1149 newpath = opts['name']
1149 else:
1150 else:
1150 newpath, i = lastsavename(q.path)
1151 newpath, i = lastsavename(q.path)
1151 if not newpath:
1152 if not newpath:
1152 ui.warn("no saved queues found, please use -n\n")
1153 ui.warn("no saved queues found, please use -n\n")
1153 return 1
1154 return 1
1154 mergeq = queue(ui, repo.join(""), newpath)
1155 mergeq = queue(ui, repo.join(""), newpath)
1155 ui.warn("merging with queue at: %s\n" % mergeq.path)
1156 ui.warn("merging with queue at: %s\n" % mergeq.path)
1156 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1157 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1157 mergeq=mergeq)
1158 mergeq=mergeq)
1158 q.save_dirty()
1159 q.save_dirty()
1159 return ret
1160 return ret
1160
1161
1161 def pop(ui, repo, patch=None, **opts):
1162 def pop(ui, repo, patch=None, **opts):
1162 """pop the current patch off the stack"""
1163 """pop the current patch off the stack"""
1163 localupdate = True
1164 localupdate = True
1164 if opts['name']:
1165 if opts['name']:
1165 q = queue(ui, repo.join(""), repo.join(opts['name']))
1166 q = queue(ui, repo.join(""), repo.join(opts['name']))
1166 ui.warn('using patch queue: %s\n' % q.path)
1167 ui.warn('using patch queue: %s\n' % q.path)
1167 localupdate = False
1168 localupdate = False
1168 else:
1169 else:
1169 q = repomap[repo]
1170 q = repomap[repo]
1170 if opts['all'] and len(q.applied) > 0:
1171 if opts['all'] and len(q.applied) > 0:
1171 patch = q.applied[0].split(':')[1]
1172 patch = q.applied[0].split(':')[1]
1172 q.pop(repo, patch, force=opts['force'], update=localupdate)
1173 q.pop(repo, patch, force=opts['force'], update=localupdate)
1173 q.save_dirty()
1174 q.save_dirty()
1174 return 0
1175 return 0
1175
1176
1176 def restore(ui, repo, rev, **opts):
1177 def restore(ui, repo, rev, **opts):
1177 """restore the queue state saved by a rev"""
1178 """restore the queue state saved by a rev"""
1178 rev = repo.lookup(rev)
1179 rev = repo.lookup(rev)
1179 q = repomap[repo]
1180 q = repomap[repo]
1180 q.restore(repo, rev, delete=opts['delete'],
1181 q.restore(repo, rev, delete=opts['delete'],
1181 qupdate=opts['update'])
1182 qupdate=opts['update'])
1182 q.save_dirty()
1183 q.save_dirty()
1183 return 0
1184 return 0
1184
1185
1185 def save(ui, repo, **opts):
1186 def save(ui, repo, **opts):
1186 """save current queue state"""
1187 """save current queue state"""
1187 q = repomap[repo]
1188 q = repomap[repo]
1188 ret = q.save(repo, msg=opts['message'])
1189 ret = q.save(repo, msg=opts['message'])
1189 if ret:
1190 if ret:
1190 return ret
1191 return ret
1191 q.save_dirty()
1192 q.save_dirty()
1192 if opts['copy']:
1193 if opts['copy']:
1193 path = q.path
1194 path = q.path
1194 if opts['name']:
1195 if opts['name']:
1195 newpath = os.path.join(q.basepath, opts['name'])
1196 newpath = os.path.join(q.basepath, opts['name'])
1196 if os.path.exists(newpath):
1197 if os.path.exists(newpath):
1197 if not os.path.isdir(newpath):
1198 if not os.path.isdir(newpath):
1198 ui.warn("destination %s exists and is not a directory\n" %
1199 ui.warn("destination %s exists and is not a directory\n" %
1199 newpath)
1200 newpath)
1200 sys.exit(1)
1201 sys.exit(1)
1201 if not opts['force']:
1202 if not opts['force']:
1202 ui.warn("destination %s exists, use -f to force\n" %
1203 ui.warn("destination %s exists, use -f to force\n" %
1203 newpath)
1204 newpath)
1204 sys.exit(1)
1205 sys.exit(1)
1205 else:
1206 else:
1206 newpath = savename(path)
1207 newpath = savename(path)
1207 ui.warn("copy %s to %s\n" % (path, newpath))
1208 ui.warn("copy %s to %s\n" % (path, newpath))
1208 util.copyfiles(path, newpath)
1209 util.copyfiles(path, newpath)
1209 if opts['empty']:
1210 if opts['empty']:
1210 try:
1211 try:
1211 os.unlink(os.path.join(q.path, q.status_path))
1212 os.unlink(os.path.join(q.path, q.status_path))
1212 except:
1213 except:
1213 pass
1214 pass
1214 return 0
1215 return 0
1215
1216
1216 def strip(ui, repo, rev, **opts):
1217 def strip(ui, repo, rev, **opts):
1217 """strip a revision and all later revs on the same branch"""
1218 """strip a revision and all later revs on the same branch"""
1218 rev = repo.lookup(rev)
1219 rev = repo.lookup(rev)
1219 backup = 'all'
1220 backup = 'all'
1220 if opts['backup']:
1221 if opts['backup']:
1221 backup = 'strip'
1222 backup = 'strip'
1222 elif opts['nobackup']:
1223 elif opts['nobackup']:
1223 backup = 'none'
1224 backup = 'none'
1224 repomap[repo].strip(repo, rev, backup=backup)
1225 repomap[repo].strip(repo, rev, backup=backup)
1225 return 0
1226 return 0
1226
1227
1227 def version(ui, q=None):
1228 def version(ui, q=None):
1228 """print the version number"""
1229 """print the version number"""
1229 ui.write("mq version %s\n" % versionstr)
1230 ui.write("mq version %s\n" % versionstr)
1230 return 0
1231 return 0
1231
1232
1232 def reposetup(ui, repo):
1233 def reposetup(ui, repo):
1233 repomap[repo] = queue(ui, repo.join(""))
1234 repomap[repo] = queue(ui, repo.join(""))
1234
1235
1235 cmdtable = {
1236 cmdtable = {
1236 "qapplied": (applied, [], 'hg qapplied [PATCH]'),
1237 "qapplied": (applied, [], 'hg qapplied [PATCH]'),
1237 "qcommit|qci":
1238 "qcommit|qci":
1238 (commit,
1239 (commit,
1239 commands.table["^commit|ci"][1],
1240 commands.table["^commit|ci"][1],
1240 'hg qcommit [OPTION]... [FILE]...'),
1241 'hg qcommit [OPTION]... [FILE]...'),
1241 "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
1242 "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
1242 "qdelete": (delete, [], 'hg qdelete PATCH'),
1243 "qdelete": (delete, [], 'hg qdelete PATCH'),
1243 "^qimport":
1244 "^qimport":
1244 (qimport,
1245 (qimport,
1245 [('e', 'existing', None, 'import file in patch dir'),
1246 [('e', 'existing', None, 'import file in patch dir'),
1246 ('n', 'name', '', 'patch file name'),
1247 ('n', 'name', '', 'patch file name'),
1247 ('f', 'force', None, 'overwrite existing files')],
1248 ('f', 'force', None, 'overwrite existing files')],
1248 'hg qimport [-e] [-n NAME] [-f] FILE...'),
1249 'hg qimport [-e] [-n NAME] [-f] FILE...'),
1249 "^qinit":
1250 "^qinit":
1250 (init,
1251 (init,
1251 [('c', 'create-repo', None, 'create patch repository')],
1252 [('c', 'create-repo', None, 'create patch repository')],
1252 'hg qinit [-c]'),
1253 'hg qinit [-c]'),
1253 "qnew":
1254 "qnew":
1254 (new,
1255 (new,
1255 [('m', 'message', '', 'commit message'),
1256 [('m', 'message', '', 'commit message'),
1256 ('f', 'force', None, 'force')],
1257 ('f', 'force', None, 'force')],
1257 'hg qnew [-m TEXT] [-f] PATCH'),
1258 'hg qnew [-m TEXT] [-f] PATCH'),
1258 "qnext": (next, [], 'hg qnext'),
1259 "qnext": (next, [], 'hg qnext'),
1259 "qprev": (prev, [], 'hg qprev'),
1260 "qprev": (prev, [], 'hg qprev'),
1260 "^qpop":
1261 "^qpop":
1261 (pop,
1262 (pop,
1262 [('a', 'all', None, 'pop all patches'),
1263 [('a', 'all', None, 'pop all patches'),
1263 ('n', 'name', '', 'queue name to pop'),
1264 ('n', 'name', '', 'queue name to pop'),
1264 ('f', 'force', None, 'forget any local changes')],
1265 ('f', 'force', None, 'forget any local changes')],
1265 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
1266 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
1266 "^qpush":
1267 "^qpush":
1267 (push,
1268 (push,
1268 [('f', 'force', None, 'apply if the patch has rejects'),
1269 [('f', 'force', None, 'apply if the patch has rejects'),
1269 ('l', 'list', None, 'list patch name in commit text'),
1270 ('l', 'list', None, 'list patch name in commit text'),
1270 ('a', 'all', None, 'apply all patches'),
1271 ('a', 'all', None, 'apply all patches'),
1271 ('m', 'merge', None, 'merge from another queue'),
1272 ('m', 'merge', None, 'merge from another queue'),
1272 ('n', 'name', '', 'merge queue name')],
1273 ('n', 'name', '', 'merge queue name')],
1273 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
1274 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
1274 "^qrefresh":
1275 "^qrefresh":
1275 (refresh,
1276 (refresh,
1276 [('s', 'short', None, 'short refresh')],
1277 [('s', 'short', None, 'short refresh')],
1277 'hg qrefresh [-s]'),
1278 'hg qrefresh [-s]'),
1278 "qrestore":
1279 "qrestore":
1279 (restore,
1280 (restore,
1280 [('d', 'delete', None, 'delete save entry'),
1281 [('d', 'delete', None, 'delete save entry'),
1281 ('u', 'update', None, 'update queue working dir')],
1282 ('u', 'update', None, 'update queue working dir')],
1282 'hg qrestore [-d] [-u] REV'),
1283 'hg qrestore [-d] [-u] REV'),
1283 "qsave":
1284 "qsave":
1284 (save,
1285 (save,
1285 [('m', 'message', '', 'commit message'),
1286 [('m', 'message', '', 'commit message'),
1286 ('c', 'copy', None, 'copy patch directory'),
1287 ('c', 'copy', None, 'copy patch directory'),
1287 ('n', 'name', '', 'copy directory name'),
1288 ('n', 'name', '', 'copy directory name'),
1288 ('e', 'empty', None, 'clear queue status file'),
1289 ('e', 'empty', None, 'clear queue status file'),
1289 ('f', 'force', None, 'force copy')],
1290 ('f', 'force', None, 'force copy')],
1290 'hg qsave [-m TEXT] [-c] [-n NAME] [-e] [-f]'),
1291 'hg qsave [-m TEXT] [-c] [-n NAME] [-e] [-f]'),
1291 "qseries":
1292 "qseries":
1292 (series,
1293 (series,
1293 [('m', 'missing', None, 'print patches not in series')],
1294 [('m', 'missing', None, 'print patches not in series')],
1294 'hg qseries [-m]'),
1295 'hg qseries [-m]'),
1295 "^strip":
1296 "^strip":
1296 (strip,
1297 (strip,
1297 [('f', 'force', None, 'force multi-head removal'),
1298 [('f', 'force', None, 'force multi-head removal'),
1298 ('b', 'backup', None, 'bundle unrelated changesets'),
1299 ('b', 'backup', None, 'bundle unrelated changesets'),
1299 ('n', 'nobackup', None, 'no backups')],
1300 ('n', 'nobackup', None, 'no backups')],
1300 'hg strip [-f] [-b] [-n] REV'),
1301 'hg strip [-f] [-b] [-n] REV'),
1301 "qtop": (top, [], 'hg qtop'),
1302 "qtop": (top, [], 'hg qtop'),
1302 "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
1303 "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
1303 "qversion": (version, [], 'hg qversion')
1304 "qversion": (version, [], 'hg qversion')
1304 }
1305 }
1305
1306
@@ -1,267 +1,267 b''
1 # notify.py - email notifications for mercurial
1 # notify.py - email notifications for mercurial
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7 #
7 #
8 # hook extension to email notifications to people when changesets are
8 # hook extension to email notifications to people when changesets are
9 # committed to a repo they subscribe to.
9 # committed to a repo they subscribe to.
10 #
10 #
11 # default mode is to print messages to stdout, for testing and
11 # default mode is to print messages to stdout, for testing and
12 # configuring.
12 # configuring.
13 #
13 #
14 # to use, configure notify extension and enable in hgrc like this:
14 # to use, configure notify extension and enable in hgrc like this:
15 #
15 #
16 # [extensions]
16 # [extensions]
17 # hgext.notify =
17 # hgext.notify =
18 #
18 #
19 # [hooks]
19 # [hooks]
20 # # one email for each incoming changeset
20 # # one email for each incoming changeset
21 # incoming.notify = python:hgext.notify.hook
21 # incoming.notify = python:hgext.notify.hook
22 # # batch emails when many changesets incoming at one time
22 # # batch emails when many changesets incoming at one time
23 # changegroup.notify = python:hgext.notify.hook
23 # changegroup.notify = python:hgext.notify.hook
24 #
24 #
25 # [notify]
25 # [notify]
26 # # config items go in here
26 # # config items go in here
27 #
27 #
28 # config items:
28 # config items:
29 #
29 #
30 # REQUIRED:
30 # REQUIRED:
31 # config = /path/to/file # file containing subscriptions
31 # config = /path/to/file # file containing subscriptions
32 #
32 #
33 # OPTIONAL:
33 # OPTIONAL:
34 # test = True # print messages to stdout for testing
34 # test = True # print messages to stdout for testing
35 # strip = 3 # number of slashes to strip for url paths
35 # strip = 3 # number of slashes to strip for url paths
36 # domain = example.com # domain to use if committer missing domain
36 # domain = example.com # domain to use if committer missing domain
37 # style = ... # style file to use when formatting email
37 # style = ... # style file to use when formatting email
38 # template = ... # template to use when formatting email
38 # template = ... # template to use when formatting email
39 # incoming = ... # template to use when run as incoming hook
39 # incoming = ... # template to use when run as incoming hook
40 # changegroup = ... # template when run as changegroup hook
40 # changegroup = ... # template when run as changegroup hook
41 # maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
41 # maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
42 # maxsubject = 67 # truncate subject line longer than this
42 # maxsubject = 67 # truncate subject line longer than this
43 # sources = serve # notify if source of incoming changes in this list
43 # sources = serve # notify if source of incoming changes in this list
44 # # (serve == ssh or http, push, pull, bundle)
44 # # (serve == ssh or http, push, pull, bundle)
45 # [email]
45 # [email]
46 # from = user@host.com # email address to send as if none given
46 # from = user@host.com # email address to send as if none given
47 # [web]
47 # [web]
48 # baseurl = http://hgserver/... # root of hg web site for browsing commits
48 # baseurl = http://hgserver/... # root of hg web site for browsing commits
49 #
49 #
50 # notify config file has same format as regular hgrc. it has two
50 # notify config file has same format as regular hgrc. it has two
51 # sections so you can express subscriptions in whatever way is handier
51 # sections so you can express subscriptions in whatever way is handier
52 # for you.
52 # for you.
53 #
53 #
54 # [usersubs]
54 # [usersubs]
55 # # key is subscriber email, value is ","-separated list of glob patterns
55 # # key is subscriber email, value is ","-separated list of glob patterns
56 # user@host = pattern
56 # user@host = pattern
57 #
57 #
58 # [reposubs]
58 # [reposubs]
59 # # key is glob pattern, value is ","-separated list of subscriber emails
59 # # key is glob pattern, value is ","-separated list of subscriber emails
60 # pattern = user@host
60 # pattern = user@host
61 #
61 #
62 # glob patterns are matched against path to repo root.
62 # glob patterns are matched against path to repo root.
63 #
63 #
64 # if you like, you can put notify config file in repo that users can
64 # if you like, you can put notify config file in repo that users can
65 # push changes to, they can manage their own subscriptions.
65 # push changes to, they can manage their own subscriptions.
66
66
67 from mercurial.demandload import *
67 from mercurial.demandload import *
68 from mercurial.i18n import gettext as _
68 from mercurial.i18n import gettext as _
69 from mercurial.node import *
69 from mercurial.node import *
70 demandload(globals(), 'email.Parser mercurial:commands,templater,util')
70 demandload(globals(), 'email.Parser mercurial:commands,templater,util')
71 demandload(globals(), 'fnmatch socket time')
71 demandload(globals(), 'fnmatch socket time')
72
72
73 # template for single changeset can include email headers.
73 # template for single changeset can include email headers.
74 single_template = '''
74 single_template = '''
75 Subject: changeset in {webroot}: {desc|firstline|strip}
75 Subject: changeset in {webroot}: {desc|firstline|strip}
76 From: {author}
76 From: {author}
77
77
78 changeset {node|short} in {root}
78 changeset {node|short} in {root}
79 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
79 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
80 description:
80 description:
81 \t{desc|tabindent|strip}
81 \t{desc|tabindent|strip}
82 '''.lstrip()
82 '''.lstrip()
83
83
84 # template for multiple changesets should not contain email headers,
84 # template for multiple changesets should not contain email headers,
85 # because only first set of headers will be used and result will look
85 # because only first set of headers will be used and result will look
86 # strange.
86 # strange.
87 multiple_template = '''
87 multiple_template = '''
88 changeset {node|short} in {root}
88 changeset {node|short} in {root}
89 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
89 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
90 summary: {desc|firstline}
90 summary: {desc|firstline}
91 '''
91 '''
92
92
93 deftemplates = {
93 deftemplates = {
94 'changegroup': multiple_template,
94 'changegroup': multiple_template,
95 }
95 }
96
96
97 class notifier(object):
97 class notifier(object):
98 '''email notification class.'''
98 '''email notification class.'''
99
99
100 def __init__(self, ui, repo, hooktype):
100 def __init__(self, ui, repo, hooktype):
101 self.ui = ui
101 self.ui = ui
102 self.ui.readconfig(self.ui.config('notify', 'config'))
102 self.ui.readconfig(self.ui.config('notify', 'config'))
103 self.repo = repo
103 self.repo = repo
104 self.stripcount = int(self.ui.config('notify', 'strip', 0))
104 self.stripcount = int(self.ui.config('notify', 'strip', 0))
105 self.root = self.strip(self.repo.root)
105 self.root = self.strip(self.repo.root)
106 self.domain = self.ui.config('notify', 'domain')
106 self.domain = self.ui.config('notify', 'domain')
107 self.sio = templater.stringio()
107 self.sio = templater.stringio()
108 self.subs = self.subscribers()
108 self.subs = self.subscribers()
109
109
110 mapfile = self.ui.config('notify', 'style')
110 mapfile = self.ui.config('notify', 'style')
111 template = (self.ui.config('notify', hooktype) or
111 template = (self.ui.config('notify', hooktype) or
112 self.ui.config('notify', 'template'))
112 self.ui.config('notify', 'template'))
113 self.t = templater.changeset_templater(self.ui, self.repo, mapfile,
113 self.t = templater.changeset_templater(self.ui, self.repo, mapfile,
114 self.sio)
114 self.sio)
115 if not mapfile and not template:
115 if not mapfile and not template:
116 template = deftemplates.get(hooktype) or single_template
116 template = deftemplates.get(hooktype) or single_template
117 if template:
117 if template:
118 template = templater.parsestring(template, quoted=False)
118 template = templater.parsestring(template, quoted=False)
119 self.t.use_template(template)
119 self.t.use_template(template)
120
120
121 def strip(self, path):
121 def strip(self, path):
122 '''strip leading slashes from local path, turn into web-safe path.'''
122 '''strip leading slashes from local path, turn into web-safe path.'''
123
123
124 path = util.pconvert(path)
124 path = util.pconvert(path)
125 count = self.stripcount
125 count = self.stripcount
126 while path and count >= 0:
126 while path and count >= 0:
127 c = path.find('/')
127 c = path.find('/')
128 if c == -1:
128 if c == -1:
129 break
129 break
130 path = path[c+1:]
130 path = path[c+1:]
131 count -= 1
131 count -= 1
132 return path
132 return path
133
133
134 def fixmail(self, addr):
134 def fixmail(self, addr):
135 '''try to clean up email addresses.'''
135 '''try to clean up email addresses.'''
136
136
137 addr = templater.email(addr.strip())
137 addr = templater.email(addr.strip())
138 a = addr.find('@localhost')
138 a = addr.find('@localhost')
139 if a != -1:
139 if a != -1:
140 addr = addr[:a]
140 addr = addr[:a]
141 if '@' not in addr:
141 if '@' not in addr:
142 return addr + '@' + self.domain
142 return addr + '@' + self.domain
143 return addr
143 return addr
144
144
145 def subscribers(self):
145 def subscribers(self):
146 '''return list of email addresses of subscribers to this repo.'''
146 '''return list of email addresses of subscribers to this repo.'''
147
147
148 subs = {}
148 subs = {}
149 for user, pats in self.ui.configitems('usersubs'):
149 for user, pats in self.ui.configitems('usersubs'):
150 for pat in pats.split(','):
150 for pat in pats.split(','):
151 if fnmatch.fnmatch(self.repo.root, pat.strip()):
151 if fnmatch.fnmatch(self.repo.root, pat.strip()):
152 subs[self.fixmail(user)] = 1
152 subs[self.fixmail(user)] = 1
153 for pat, users in self.ui.configitems('reposubs'):
153 for pat, users in self.ui.configitems('reposubs'):
154 if fnmatch.fnmatch(self.repo.root, pat):
154 if fnmatch.fnmatch(self.repo.root, pat):
155 for user in users.split(','):
155 for user in users.split(','):
156 subs[self.fixmail(user)] = 1
156 subs[self.fixmail(user)] = 1
157 subs = subs.keys()
157 subs = subs.keys()
158 subs.sort()
158 subs.sort()
159 return subs
159 return subs
160
160
161 def url(self, path=None):
161 def url(self, path=None):
162 return self.ui.config('web', 'baseurl') + (path or self.root)
162 return self.ui.config('web', 'baseurl') + (path or self.root)
163
163
164 def node(self, node):
164 def node(self, node):
165 '''format one changeset.'''
165 '''format one changeset.'''
166
166
167 self.t.show(changenode=node, changes=self.repo.changelog.read(node),
167 self.t.show(changenode=node, changes=self.repo.changelog.read(node),
168 baseurl=self.ui.config('web', 'baseurl'),
168 baseurl=self.ui.config('web', 'baseurl'),
169 root=self.repo.root,
169 root=self.repo.root,
170 webroot=self.root)
170 webroot=self.root)
171
171
172 def skipsource(self, source):
172 def skipsource(self, source):
173 '''true if incoming changes from this source should be skipped.'''
173 '''true if incoming changes from this source should be skipped.'''
174 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
174 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
175 return source not in ok_sources
175 return source not in ok_sources
176
176
177 def send(self, node, count):
177 def send(self, node, count):
178 '''send message.'''
178 '''send message.'''
179
179
180 p = email.Parser.Parser()
180 p = email.Parser.Parser()
181 self.sio.seek(0)
181 self.sio.seek(0)
182 msg = p.parse(self.sio)
182 msg = p.parse(self.sio)
183
183
184 def fix_subject():
184 def fix_subject():
185 '''try to make subject line exist and be useful.'''
185 '''try to make subject line exist and be useful.'''
186
186
187 subject = msg['Subject']
187 subject = msg['Subject']
188 if not subject:
188 if not subject:
189 if count > 1:
189 if count > 1:
190 subject = _('%s: %d new changesets') % (self.root, count)
190 subject = _('%s: %d new changesets') % (self.root, count)
191 else:
191 else:
192 changes = self.repo.changelog.read(node)
192 changes = self.repo.changelog.read(node)
193 s = changes[4].lstrip().split('\n', 1)[0].rstrip()
193 s = changes[4].lstrip().split('\n', 1)[0].rstrip()
194 subject = '%s: %s' % (self.root, s)
194 subject = '%s: %s' % (self.root, s)
195 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
195 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
196 if maxsubject and len(subject) > maxsubject:
196 if maxsubject and len(subject) > maxsubject:
197 subject = subject[:maxsubject-3] + '...'
197 subject = subject[:maxsubject-3] + '...'
198 del msg['Subject']
198 del msg['Subject']
199 msg['Subject'] = subject
199 msg['Subject'] = subject
200
200
201 def fix_sender():
201 def fix_sender():
202 '''try to make message have proper sender.'''
202 '''try to make message have proper sender.'''
203
203
204 sender = msg['From']
204 sender = msg['From']
205 if not sender:
205 if not sender:
206 sender = self.ui.config('email', 'from') or self.ui.username()
206 sender = self.ui.config('email', 'from') or self.ui.username()
207 if '@' not in sender or '@localhost' in sender:
207 if '@' not in sender or '@localhost' in sender:
208 sender = self.fixmail(sender)
208 sender = self.fixmail(sender)
209 del msg['From']
209 del msg['From']
210 msg['From'] = sender
210 msg['From'] = sender
211
211
212 fix_subject()
212 fix_subject()
213 fix_sender()
213 fix_sender()
214
214
215 msg['X-Hg-Notification'] = 'changeset ' + short(node)
215 msg['X-Hg-Notification'] = 'changeset ' + short(node)
216 if not msg['Message-Id']:
216 if not msg['Message-Id']:
217 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
217 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
218 (short(node), int(time.time()),
218 (short(node), int(time.time()),
219 hash(self.repo.root), socket.getfqdn()))
219 hash(self.repo.root), socket.getfqdn()))
220 msg['To'] = ', '.join(self.subs)
220 msg['To'] = ', '.join(self.subs)
221
221
222 msgtext = msg.as_string(0)
222 msgtext = msg.as_string(0)
223 if self.ui.configbool('notify', 'test', True):
223 if self.ui.configbool('notify', 'test', True):
224 self.ui.write(msgtext)
224 self.ui.write(msgtext)
225 if not msgtext.endswith('\n'):
225 if not msgtext.endswith('\n'):
226 self.ui.write('\n')
226 self.ui.write('\n')
227 else:
227 else:
228 mail = self.ui.sendmail()
228 mail = self.ui.sendmail()
229 mail.sendmail(templater.email(msg['From']), self.subs, msgtext)
229 mail.sendmail(templater.email(msg['From']), self.subs, msgtext)
230
230
231 def diff(self, node):
231 def diff(self, node, ref):
232 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
232 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
233 if maxdiff == 0:
233 if maxdiff == 0:
234 return
234 return
235 fp = templater.stringio()
235 fp = templater.stringio()
236 prev = self.repo.changelog.parents(node)[0]
236 prev = self.repo.changelog.parents(node)[0]
237 commands.dodiff(fp, self.ui, self.repo, prev,
237 commands.dodiff(fp, self.ui, self.repo, prev, ref)
238 self.repo.changelog.tip())
239 difflines = fp.getvalue().splitlines(1)
238 difflines = fp.getvalue().splitlines(1)
240 if maxdiff > 0 and len(difflines) > maxdiff:
239 if maxdiff > 0 and len(difflines) > maxdiff:
241 self.sio.write(_('\ndiffs (truncated from %d to %d lines):\n\n') %
240 self.sio.write(_('\ndiffs (truncated from %d to %d lines):\n\n') %
242 (len(difflines), maxdiff))
241 (len(difflines), maxdiff))
243 difflines = difflines[:maxdiff]
242 difflines = difflines[:maxdiff]
244 elif difflines:
243 elif difflines:
245 self.sio.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
244 self.sio.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
246 self.sio.write(*difflines)
245 self.sio.write(*difflines)
247
246
248 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
247 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
249 '''send email notifications to interested subscribers.
248 '''send email notifications to interested subscribers.
250
249
251 if used as changegroup hook, send one email for all changesets in
250 if used as changegroup hook, send one email for all changesets in
252 changegroup. else send one email per changeset.'''
251 changegroup. else send one email per changeset.'''
253 n = notifier(ui, repo, hooktype)
252 n = notifier(ui, repo, hooktype)
254 if not n.subs or n.skipsource(source):
253 if not n.subs or n.skipsource(source):
255 return
254 return
256 node = bin(node)
255 node = bin(node)
257 if hooktype == 'changegroup':
256 if hooktype == 'changegroup':
258 start = repo.changelog.rev(node)
257 start = repo.changelog.rev(node)
259 end = repo.changelog.count()
258 end = repo.changelog.count()
260 count = end - start
259 count = end - start
261 for rev in xrange(start, end):
260 for rev in xrange(start, end):
262 n.node(repo.changelog.node(rev))
261 n.node(repo.changelog.node(rev))
262 n.diff(node, repo.changelog.tip())
263 else:
263 else:
264 count = 1
264 count = 1
265 n.node(node)
265 n.node(node)
266 n.diff(node)
266 n.diff(node, node)
267 n.send(node, count)
267 n.send(node, count)
@@ -1,272 +1,270 b''
1 # Command for sending a collection of Mercurial changesets as a series
1 # Command for sending a collection of Mercurial changesets as a series
2 # of patch emails.
2 # of patch emails.
3 #
3 #
4 # The series is started off with a "[PATCH 0 of N]" introduction,
4 # The series is started off with a "[PATCH 0 of N]" introduction,
5 # which describes the series as a whole.
5 # which describes the series as a whole.
6 #
6 #
7 # Each patch email has a Subject line of "[PATCH M of N] ...", using
7 # Each patch email has a Subject line of "[PATCH M of N] ...", using
8 # the first line of the changeset description as the subject text.
8 # the first line of the changeset description as the subject text.
9 # The message contains two or three body parts:
9 # The message contains two or three body parts:
10 #
10 #
11 # The remainder of the changeset description.
11 # The remainder of the changeset description.
12 #
12 #
13 # [Optional] If the diffstat program is installed, the result of
13 # [Optional] If the diffstat program is installed, the result of
14 # running diffstat on the patch.
14 # running diffstat on the patch.
15 #
15 #
16 # The patch itself, as generated by "hg export".
16 # The patch itself, as generated by "hg export".
17 #
17 #
18 # Each message refers to all of its predecessors using the In-Reply-To
18 # Each message refers to all of its predecessors using the In-Reply-To
19 # and References headers, so they will show up as a sequence in
19 # and References headers, so they will show up as a sequence in
20 # threaded mail and news readers, and in mail archives.
20 # threaded mail and news readers, and in mail archives.
21 #
21 #
22 # For each changeset, you will be prompted with a diffstat summary and
22 # For each changeset, you will be prompted with a diffstat summary and
23 # the changeset summary, so you can be sure you are sending the right
23 # the changeset summary, so you can be sure you are sending the right
24 # changes.
24 # changes.
25 #
25 #
26 # It is best to run this script with the "-n" (test only) flag before
26 # It is best to run this script with the "-n" (test only) flag before
27 # firing it up "for real", in which case it will use your pager to
27 # firing it up "for real", in which case it will use your pager to
28 # display each of the messages that it would send.
28 # display each of the messages that it would send.
29 #
29 #
30 # The "-m" (mbox) option will create an mbox file instead of sending
30 # The "-m" (mbox) option will create an mbox file instead of sending
31 # the messages directly. This can be reviewed e.g. with "mutt -R -f mbox",
31 # the messages directly. This can be reviewed e.g. with "mutt -R -f mbox",
32 # and finally sent with "formail -s sendmail -bm -t < mbox".
32 # and finally sent with "formail -s sendmail -bm -t < mbox".
33 #
33 #
34 # To configure other defaults, add a section like this to your hgrc
34 # To configure other defaults, add a section like this to your hgrc
35 # file:
35 # file:
36 #
36 #
37 # [email]
37 # [email]
38 # from = My Name <my@email>
38 # from = My Name <my@email>
39 # to = recipient1, recipient2, ...
39 # to = recipient1, recipient2, ...
40 # cc = cc1, cc2, ...
40 # cc = cc1, cc2, ...
41
41
42 from mercurial.demandload import *
42 from mercurial.demandload import *
43 demandload(globals(), '''email.MIMEMultipart email.MIMEText email.Utils
43 demandload(globals(), '''email.MIMEMultipart email.MIMEText email.Utils
44 mercurial:commands,hg,ui
44 mercurial:commands,hg,ui
45 os errno popen2 socket sys tempfile time''')
45 os errno popen2 socket sys tempfile time''')
46 from mercurial.i18n import gettext as _
46 from mercurial.i18n import gettext as _
47
47
48 try:
48 try:
49 # readline gives raw_input editing capabilities, but is not
49 # readline gives raw_input editing capabilities, but is not
50 # present on windows
50 # present on windows
51 import readline
51 import readline
52 except ImportError: pass
52 except ImportError: pass
53
53
54 def diffstat(patch):
54 def diffstat(patch):
55 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
55 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
56 try:
56 try:
57 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
57 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
58 try:
58 try:
59 for line in patch: print >> p.tochild, line
59 for line in patch: print >> p.tochild, line
60 p.tochild.close()
60 p.tochild.close()
61 if p.wait(): return
61 if p.wait(): return
62 fp = os.fdopen(fd, 'r')
62 fp = os.fdopen(fd, 'r')
63 stat = []
63 stat = []
64 for line in fp: stat.append(line.lstrip())
64 for line in fp: stat.append(line.lstrip())
65 last = stat.pop()
65 last = stat.pop()
66 stat.insert(0, last)
66 stat.insert(0, last)
67 stat = ''.join(stat)
67 stat = ''.join(stat)
68 if stat.startswith('0 files'): raise ValueError
68 if stat.startswith('0 files'): raise ValueError
69 return stat
69 return stat
70 except: raise
70 except: raise
71 finally:
71 finally:
72 try: os.unlink(name)
72 try: os.unlink(name)
73 except: pass
73 except: pass
74
74
75 def patchbomb(ui, repo, *revs, **opts):
75 def patchbomb(ui, repo, *revs, **opts):
76 '''send changesets as a series of patch emails
76 '''send changesets as a series of patch emails
77
77
78 The series starts with a "[PATCH 0 of N]" introduction, which
78 The series starts with a "[PATCH 0 of N]" introduction, which
79 describes the series as a whole.
79 describes the series as a whole.
80
80
81 Each patch email has a Subject line of "[PATCH M of N] ...", using
81 Each patch email has a Subject line of "[PATCH M of N] ...", using
82 the first line of the changeset description as the subject text.
82 the first line of the changeset description as the subject text.
83 The message contains two or three body parts. First, the rest of
83 The message contains two or three body parts. First, the rest of
84 the changeset description. Next, (optionally) if the diffstat
84 the changeset description. Next, (optionally) if the diffstat
85 program is installed, the result of running diffstat on the patch.
85 program is installed, the result of running diffstat on the patch.
86 Finally, the patch itself, as generated by "hg export".'''
86 Finally, the patch itself, as generated by "hg export".'''
87 def prompt(prompt, default = None, rest = ': ', empty_ok = False):
87 def prompt(prompt, default = None, rest = ': ', empty_ok = False):
88 if default: prompt += ' [%s]' % default
88 if default: prompt += ' [%s]' % default
89 prompt += rest
89 prompt += rest
90 while True:
90 while True:
91 r = raw_input(prompt)
91 r = raw_input(prompt)
92 if r: return r
92 if r: return r
93 if default is not None: return default
93 if default is not None: return default
94 if empty_ok: return r
94 if empty_ok: return r
95 ui.warn(_('Please enter a valid value.\n'))
95 ui.warn(_('Please enter a valid value.\n'))
96
96
97 def confirm(s):
97 def confirm(s):
98 if not prompt(s, default = 'y', rest = '? ').lower().startswith('y'):
98 if not prompt(s, default = 'y', rest = '? ').lower().startswith('y'):
99 raise ValueError
99 raise ValueError
100
100
101 def cdiffstat(summary, patch):
101 def cdiffstat(summary, patch):
102 s = diffstat(patch)
102 s = diffstat(patch)
103 if s:
103 if s:
104 if summary:
104 if summary:
105 ui.write(summary, '\n')
105 ui.write(summary, '\n')
106 ui.write(s, '\n')
106 ui.write(s, '\n')
107 confirm(_('Does the diffstat above look okay'))
107 confirm(_('Does the diffstat above look okay'))
108 return s
108 return s
109
109
110 def makepatch(patch, idx, total):
110 def makepatch(patch, idx, total):
111 desc = []
111 desc = []
112 node = None
112 node = None
113 body = ''
113 body = ''
114 for line in patch:
114 for line in patch:
115 if line.startswith('#'):
115 if line.startswith('#'):
116 if line.startswith('# Node ID'): node = line.split()[-1]
116 if line.startswith('# Node ID'): node = line.split()[-1]
117 continue
117 continue
118 if line.startswith('diff -r'): break
118 if line.startswith('diff -r'): break
119 desc.append(line)
119 desc.append(line)
120 if not node: raise ValueError
120 if not node: raise ValueError
121
121
122 #body = ('\n'.join(desc[1:]).strip() or
122 #body = ('\n'.join(desc[1:]).strip() or
123 # 'Patch subject is complete summary.')
123 # 'Patch subject is complete summary.')
124 #body += '\n\n\n'
124 #body += '\n\n\n'
125
125
126 if opts['plain']:
126 if opts['plain']:
127 while patch and patch[0].startswith('# '): patch.pop(0)
127 while patch and patch[0].startswith('# '): patch.pop(0)
128 if patch: patch.pop(0)
128 if patch: patch.pop(0)
129 while patch and not patch[0].strip(): patch.pop(0)
129 while patch and not patch[0].strip(): patch.pop(0)
130 if opts['diffstat']:
130 if opts['diffstat']:
131 body += cdiffstat('\n'.join(desc), patch) + '\n\n'
131 body += cdiffstat('\n'.join(desc), patch) + '\n\n'
132 body += '\n'.join(patch)
132 body += '\n'.join(patch)
133 msg = email.MIMEText.MIMEText(body)
133 msg = email.MIMEText.MIMEText(body)
134 if total == 1:
134 if total == 1:
135 subj = '[PATCH] ' + desc[0].strip()
135 subj = '[PATCH] ' + desc[0].strip()
136 else:
136 else:
137 subj = '[PATCH %d of %d] %s' % (idx, total, desc[0].strip())
137 subj = '[PATCH %d of %d] %s' % (idx, total, desc[0].strip())
138 if subj.endswith('.'): subj = subj[:-1]
138 if subj.endswith('.'): subj = subj[:-1]
139 msg['Subject'] = subj
139 msg['Subject'] = subj
140 msg['X-Mercurial-Node'] = node
140 msg['X-Mercurial-Node'] = node
141 return msg
141 return msg
142
142
143 start_time = int(time.time())
143 start_time = int(time.time())
144
144
145 def genmsgid(id):
145 def genmsgid(id):
146 return '<%s.%s@%s>' % (id[:20], start_time, socket.getfqdn())
146 return '<%s.%s@%s>' % (id[:20], start_time, socket.getfqdn())
147
147
148 patches = []
148 patches = []
149
149
150 class exportee:
150 class exportee:
151 def __init__(self, container):
151 def __init__(self, container):
152 self.lines = []
152 self.lines = []
153 self.container = container
153 self.container = container
154 self.name = 'email'
154 self.name = 'email'
155
155
156 def write(self, data):
156 def write(self, data):
157 self.lines.append(data)
157 self.lines.append(data)
158
158
159 def close(self):
159 def close(self):
160 self.container.append(''.join(self.lines).split('\n'))
160 self.container.append(''.join(self.lines).split('\n'))
161 self.lines = []
161 self.lines = []
162
162
163 commands.export(ui, repo, *revs, **{'output': exportee(patches),
163 commands.export(ui, repo, *revs, **{'output': exportee(patches),
164 'switch_parent': False,
164 'switch_parent': False,
165 'text': None})
165 'text': None})
166
166
167 jumbo = []
167 jumbo = []
168 msgs = []
168 msgs = []
169
169
170 ui.write(_('This patch series consists of %d patches.\n\n') % len(patches))
170 ui.write(_('This patch series consists of %d patches.\n\n') % len(patches))
171
171
172 for p, i in zip(patches, range(len(patches))):
172 for p, i in zip(patches, range(len(patches))):
173 jumbo.extend(p)
173 jumbo.extend(p)
174 msgs.append(makepatch(p, i + 1, len(patches)))
174 msgs.append(makepatch(p, i + 1, len(patches)))
175
175
176 sender = (opts['from'] or ui.config('email', 'from') or
176 sender = (opts['from'] or ui.config('email', 'from') or
177 ui.config('patchbomb', 'from') or
177 ui.config('patchbomb', 'from') or
178 prompt('From', ui.username()))
178 prompt('From', ui.username()))
179
179
180 def getaddrs(opt, prpt, default = None):
180 def getaddrs(opt, prpt, default = None):
181 addrs = opts[opt] or (ui.config('email', opt) or
181 addrs = opts[opt] or (ui.config('email', opt) or
182 ui.config('patchbomb', opt) or
182 ui.config('patchbomb', opt) or
183 prompt(prpt, default = default)).split(',')
183 prompt(prpt, default = default)).split(',')
184 return [a.strip() for a in addrs if a.strip()]
184 return [a.strip() for a in addrs if a.strip()]
185 to = getaddrs('to', 'To')
185 to = getaddrs('to', 'To')
186 cc = getaddrs('cc', 'Cc', '')
186 cc = getaddrs('cc', 'Cc', '')
187
187
188 if len(patches) > 1:
188 if len(patches) > 1:
189 ui.write(_('\nWrite the introductory message for the patch series.\n\n'))
189 ui.write(_('\nWrite the introductory message for the patch series.\n\n'))
190
190
191 msg = email.MIMEMultipart.MIMEMultipart()
191 msg = email.MIMEMultipart.MIMEMultipart()
192 msg['Subject'] = '[PATCH 0 of %d] %s' % (
192 msg['Subject'] = '[PATCH 0 of %d] %s' % (
193 len(patches),
193 len(patches),
194 opts['subject'] or
194 opts['subject'] or
195 prompt('Subject:', rest = ' [PATCH 0 of %d] ' % len(patches)))
195 prompt('Subject:', rest = ' [PATCH 0 of %d] ' % len(patches)))
196
196
197 ui.write(_('Finish with ^D or a dot on a line by itself.\n\n'))
197 ui.write(_('Finish with ^D or a dot on a line by itself.\n\n'))
198
198
199 body = []
199 body = []
200
200
201 while True:
201 while True:
202 try: l = raw_input()
202 try: l = raw_input()
203 except EOFError: break
203 except EOFError: break
204 if l == '.': break
204 if l == '.': break
205 body.append(l)
205 body.append(l)
206
206
207 msg.attach(email.MIMEText.MIMEText('\n'.join(body) + '\n'))
207 msg.attach(email.MIMEText.MIMEText('\n'.join(body) + '\n'))
208
208
209 if opts['diffstat']:
209 if opts['diffstat']:
210 d = cdiffstat(_('Final summary:\n'), jumbo)
210 d = cdiffstat(_('Final summary:\n'), jumbo)
211 if d: msg.attach(email.MIMEText.MIMEText(d))
211 if d: msg.attach(email.MIMEText.MIMEText(d))
212
212
213 msgs.insert(0, msg)
213 msgs.insert(0, msg)
214
214
215 ui.write('\n')
215 ui.write('\n')
216
216
217 if not opts['test'] and not opts['mbox']:
217 if not opts['test'] and not opts['mbox']:
218 mail = ui.sendmail()
218 mail = ui.sendmail()
219 parent = None
219 parent = None
220 tz = time.strftime('%z')
220 tz = time.strftime('%z')
221 sender_addr = email.Utils.parseaddr(sender)[1]
221 sender_addr = email.Utils.parseaddr(sender)[1]
222 for m in msgs:
222 for m in msgs:
223 try:
223 try:
224 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
224 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
225 except TypeError:
225 except TypeError:
226 m['Message-Id'] = genmsgid('patchbomb')
226 m['Message-Id'] = genmsgid('patchbomb')
227 if parent:
227 if parent:
228 m['In-Reply-To'] = parent
228 m['In-Reply-To'] = parent
229 else:
229 else:
230 parent = m['Message-Id']
230 parent = m['Message-Id']
231 m['Date'] = time.strftime('%a, %e %b %Y %T ', time.localtime(start_time)) + tz
231 m['Date'] = time.strftime('%a, %e %b %Y %T ', time.localtime(start_time)) + tz
232 start_time += 1
232 start_time += 1
233 m['From'] = sender
233 m['From'] = sender
234 m['To'] = ', '.join(to)
234 m['To'] = ', '.join(to)
235 if cc: m['Cc'] = ', '.join(cc)
235 if cc: m['Cc'] = ', '.join(cc)
236 if opts['test']:
236 if opts['test']:
237 ui.status('Displaying ', m['Subject'], ' ...\n')
237 ui.status('Displaying ', m['Subject'], ' ...\n')
238 fp = os.popen(os.getenv('PAGER', 'more'), 'w')
238 fp = os.popen(os.getenv('PAGER', 'more'), 'w')
239 try:
239 try:
240 fp.write(m.as_string(0))
240 fp.write(m.as_string(0))
241 fp.write('\n')
241 fp.write('\n')
242 except IOError, inst:
242 except IOError, inst:
243 if inst.errno != errno.EPIPE:
243 if inst.errno != errno.EPIPE:
244 raise
244 raise
245 fp.close()
245 fp.close()
246 elif opts['mbox']:
246 elif opts['mbox']:
247 ui.status('Writing ', m['Subject'], ' ...\n')
247 ui.status('Writing ', m['Subject'], ' ...\n')
248 fp = open(opts['mbox'], m.has_key('In-Reply-To') and 'ab+' or 'wb+')
248 fp = open(opts['mbox'], m.has_key('In-Reply-To') and 'ab+' or 'wb+')
249 date = time.asctime(time.localtime(start_time))
249 date = time.asctime(time.localtime(start_time))
250 fp.write('From %s %s\n' % (sender_addr, date))
250 fp.write('From %s %s\n' % (sender_addr, date))
251 fp.write(m.as_string(0))
251 fp.write(m.as_string(0))
252 fp.write('\n\n')
252 fp.write('\n\n')
253 fp.close()
253 fp.close()
254 else:
254 else:
255 ui.status('Sending ', m['Subject'], ' ...\n')
255 ui.status('Sending ', m['Subject'], ' ...\n')
256 mail.sendmail(sender, to + cc, m.as_string(0))
256 mail.sendmail(sender, to + cc, m.as_string(0))
257 if not opts['test'] and not opts['mbox']:
258 mail.close()
259
257
260 cmdtable = {
258 cmdtable = {
261 'email':
259 'email':
262 (patchbomb,
260 (patchbomb,
263 [('c', 'cc', [], 'email addresses of copy recipients'),
261 [('c', 'cc', [], 'email addresses of copy recipients'),
264 ('d', 'diffstat', None, 'add diffstat output to messages'),
262 ('d', 'diffstat', None, 'add diffstat output to messages'),
265 ('f', 'from', '', 'email address of sender'),
263 ('f', 'from', '', 'email address of sender'),
266 ('', 'plain', None, 'omit hg patch header'),
264 ('', 'plain', None, 'omit hg patch header'),
267 ('n', 'test', None, 'print messages that would be sent'),
265 ('n', 'test', None, 'print messages that would be sent'),
268 ('m', 'mbox', '', 'write messages to mbox file instead of sending them'),
266 ('m', 'mbox', '', 'write messages to mbox file instead of sending them'),
269 ('s', 'subject', '', 'subject of introductory message'),
267 ('s', 'subject', '', 'subject of introductory message'),
270 ('t', 'to', [], 'email addresses of recipients')],
268 ('t', 'to', [], 'email addresses of recipients')],
271 "hg email [OPTION]... [REV]...")
269 "hg email [OPTION]... [REV]...")
272 }
270 }
@@ -1,207 +1,232 b''
1 """
1 """
2 bundlerepo.py - repository class for viewing uncompressed bundles
2 bundlerepo.py - repository class for viewing uncompressed bundles
3
3
4 This provides a read-only repository interface to bundles as if
4 This provides a read-only repository interface to bundles as if
5 they were part of the actual repository.
5 they were part of the actual repository.
6
6
7 Copyright 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
7 Copyright 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
8
8
9 This software may be used and distributed according to the terms
9 This software may be used and distributed according to the terms
10 of the GNU General Public License, incorporated herein by reference.
10 of the GNU General Public License, incorporated herein by reference.
11 """
11 """
12
12
13 from node import *
13 from node import *
14 from i18n import gettext as _
14 from i18n import gettext as _
15 from demandload import demandload
15 from demandload import demandload
16 demandload(globals(), "changegroup util os struct")
16 demandload(globals(), "changegroup util os struct bz2 tempfile")
17
17
18 import localrepo, changelog, manifest, filelog, revlog
18 import localrepo, changelog, manifest, filelog, revlog
19
19
20 class bundlerevlog(revlog.revlog):
20 class bundlerevlog(revlog.revlog):
21 def __init__(self, opener, indexfile, datafile, bundlefile,
21 def __init__(self, opener, indexfile, datafile, bundlefile,
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 bundlefile (an opened file).
25 # the revision in the bundlefile (an opened file).
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, datafile)
32 revlog.revlog.__init__(self, opener, indexfile, datafile)
33 self.bundlefile = bundlefile
33 self.bundlefile = bundlefile
34 self.basemap = {}
34 self.basemap = {}
35 def chunkpositer():
35 def chunkpositer():
36 for chunk in changegroup.chunkiter(bundlefile):
36 for chunk in changegroup.chunkiter(bundlefile):
37 pos = bundlefile.tell()
37 pos = bundlefile.tell()
38 yield chunk, pos - len(chunk)
38 yield chunk, pos - len(chunk)
39 n = self.count()
39 n = self.count()
40 prev = None
40 prev = None
41 for chunk, start in chunkpositer():
41 for chunk, start in chunkpositer():
42 size = len(chunk)
42 size = len(chunk)
43 if size < 80:
43 if size < 80:
44 raise util.Abort("invalid changegroup")
44 raise util.Abort("invalid changegroup")
45 start += 80
45 start += 80
46 size -= 80
46 size -= 80
47 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
47 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
48 if node in self.nodemap:
48 if node in self.nodemap:
49 prev = node
49 prev = node
50 continue
50 continue
51 for p in (p1, p2):
51 for p in (p1, p2):
52 if not p in self.nodemap:
52 if not p in self.nodemap:
53 raise RevlogError(_("unknown parent %s") % short(p1))
53 raise revlog.RevlogError(_("unknown parent %s") % short(p1))
54 if linkmapper is None:
54 if linkmapper is None:
55 link = n
55 link = n
56 else:
56 else:
57 link = linkmapper(cs)
57 link = linkmapper(cs)
58
58
59 if not prev:
59 if not prev:
60 prev = p1
60 prev = p1
61 # start, size, base is not used, link, p1, p2, delta ref
61 # start, size, base is not used, link, p1, p2, delta ref
62 if self.version == 0:
62 if self.version == 0:
63 e = (start, size, None, link, p1, p2, node)
63 e = (start, size, None, link, p1, p2, node)
64 else:
64 else:
65 e = (self.offset_type(start, 0), size, -1, None, link,
65 e = (self.offset_type(start, 0), size, -1, None, link,
66 self.rev(p1), self.rev(p2), node)
66 self.rev(p1), self.rev(p2), node)
67 self.basemap[n] = prev
67 self.basemap[n] = prev
68 self.index.append(e)
68 self.index.append(e)
69 self.nodemap[node] = n
69 self.nodemap[node] = n
70 prev = node
70 prev = node
71 n += 1
71 n += 1
72
72
73 def bundle(self, rev):
73 def bundle(self, rev):
74 """is rev from the bundle"""
74 """is rev from the bundle"""
75 if rev < 0:
75 if rev < 0:
76 return False
76 return False
77 return rev in self.basemap
77 return rev in self.basemap
78 def bundlebase(self, rev): return self.basemap[rev]
78 def bundlebase(self, rev): return self.basemap[rev]
79 def chunk(self, rev, df=None):
79 def chunk(self, rev, df=None, cachelen=4096):
80 # Warning: in case of bundle, the diff is against bundlebase,
80 # Warning: in case of bundle, the diff is against bundlebase,
81 # not against rev - 1
81 # not against rev - 1
82 # XXX: could use some caching
82 # XXX: could use some caching
83 if not self.bundle(rev):
83 if not self.bundle(rev):
84 return revlog.revlog.chunk(self, rev)
84 return revlog.revlog.chunk(self, rev, df, cachelen)
85 self.bundlefile.seek(self.start(rev))
85 self.bundlefile.seek(self.start(rev))
86 return self.bundlefile.read(self.length(rev))
86 return self.bundlefile.read(self.length(rev))
87
87
88 def revdiff(self, rev1, rev2):
88 def revdiff(self, rev1, rev2):
89 """return or calculate a delta between two revisions"""
89 """return or calculate a delta between two revisions"""
90 if self.bundle(rev1) and self.bundle(rev2):
90 if self.bundle(rev1) and self.bundle(rev2):
91 # hot path for bundle
91 # hot path for bundle
92 revb = self.rev(self.bundlebase(rev2))
92 revb = self.rev(self.bundlebase(rev2))
93 if revb == rev1:
93 if revb == rev1:
94 return self.chunk(rev2)
94 return self.chunk(rev2)
95 elif not self.bundle(rev1) and not self.bundle(rev2):
95 elif not self.bundle(rev1) and not self.bundle(rev2):
96 return revlog.revlog.chunk(self, rev1, rev2)
96 return revlog.revlog.chunk(self, rev1, rev2)
97
97
98 return self.diff(self.revision(self.node(rev1)),
98 return self.diff(self.revision(self.node(rev1)),
99 self.revision(self.node(rev2)))
99 self.revision(self.node(rev2)))
100
100
101 def revision(self, node):
101 def revision(self, node):
102 """return an uncompressed revision of a given"""
102 """return an uncompressed revision of a given"""
103 if node == nullid: return ""
103 if node == nullid: return ""
104
104
105 text = None
105 text = None
106 chain = []
106 chain = []
107 iter_node = node
107 iter_node = node
108 rev = self.rev(iter_node)
108 rev = self.rev(iter_node)
109 # reconstruct the revision if it is from a changegroup
109 # reconstruct the revision if it is from a changegroup
110 while self.bundle(rev):
110 while self.bundle(rev):
111 if self.cache and self.cache[0] == iter_node:
111 if self.cache and self.cache[0] == iter_node:
112 text = self.cache[2]
112 text = self.cache[2]
113 break
113 break
114 chain.append(rev)
114 chain.append(rev)
115 iter_node = self.bundlebase(rev)
115 iter_node = self.bundlebase(rev)
116 rev = self.rev(iter_node)
116 rev = self.rev(iter_node)
117 if text is None:
117 if text is None:
118 text = revlog.revlog.revision(self, iter_node)
118 text = revlog.revlog.revision(self, iter_node)
119
119
120 while chain:
120 while chain:
121 delta = self.chunk(chain.pop())
121 delta = self.chunk(chain.pop())
122 text = self.patches(text, [delta])
122 text = self.patches(text, [delta])
123
123
124 p1, p2 = self.parents(node)
124 p1, p2 = self.parents(node)
125 if node != revlog.hash(text, p1, p2):
125 if node != revlog.hash(text, p1, p2):
126 raise RevlogError(_("integrity check failed on %s:%d")
126 raise revlog.RevlogError(_("integrity check failed on %s:%d")
127 % (self.datafile, self.rev(node)))
127 % (self.datafile, self.rev(node)))
128
128
129 self.cache = (node, self.rev(node), text)
129 self.cache = (node, self.rev(node), text)
130 return text
130 return text
131
131
132 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
132 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
133 raise NotImplementedError
133 raise NotImplementedError
134 def addgroup(self, revs, linkmapper, transaction, unique=0):
134 def addgroup(self, revs, linkmapper, transaction, unique=0):
135 raise NotImplementedError
135 raise NotImplementedError
136 def strip(self, rev, minlink):
136 def strip(self, rev, minlink):
137 raise NotImplementedError
137 raise NotImplementedError
138 def checksize(self):
138 def checksize(self):
139 raise NotImplementedError
139 raise NotImplementedError
140
140
141 class bundlechangelog(bundlerevlog, changelog.changelog):
141 class bundlechangelog(bundlerevlog, changelog.changelog):
142 def __init__(self, opener, bundlefile):
142 def __init__(self, opener, bundlefile):
143 changelog.changelog.__init__(self, opener)
143 changelog.changelog.__init__(self, opener)
144 bundlerevlog.__init__(self, opener, "00changelog.i", "00changelog.d",
144 bundlerevlog.__init__(self, opener, "00changelog.i", "00changelog.d",
145 bundlefile)
145 bundlefile)
146
146
147 class bundlemanifest(bundlerevlog, manifest.manifest):
147 class bundlemanifest(bundlerevlog, manifest.manifest):
148 def __init__(self, opener, bundlefile, linkmapper):
148 def __init__(self, opener, bundlefile, linkmapper):
149 manifest.manifest.__init__(self, opener)
149 manifest.manifest.__init__(self, opener)
150 bundlerevlog.__init__(self, opener, self.indexfile, self.datafile,
150 bundlerevlog.__init__(self, opener, self.indexfile, self.datafile,
151 bundlefile, linkmapper)
151 bundlefile, linkmapper)
152
152
153 class bundlefilelog(bundlerevlog, filelog.filelog):
153 class bundlefilelog(bundlerevlog, filelog.filelog):
154 def __init__(self, opener, path, bundlefile, linkmapper):
154 def __init__(self, opener, path, bundlefile, linkmapper):
155 filelog.filelog.__init__(self, opener, path)
155 filelog.filelog.__init__(self, opener, path)
156 bundlerevlog.__init__(self, opener, self.indexfile, self.datafile,
156 bundlerevlog.__init__(self, opener, self.indexfile, self.datafile,
157 bundlefile, linkmapper)
157 bundlefile, linkmapper)
158
158
159 class bundlerepository(localrepo.localrepository):
159 class bundlerepository(localrepo.localrepository):
160 def __init__(self, ui, path, bundlename):
160 def __init__(self, ui, path, bundlename):
161 localrepo.localrepository.__init__(self, ui, path)
161 localrepo.localrepository.__init__(self, ui, path)
162 f = open(bundlename, "rb")
162 self.tempfile = None
163 s = util.fstat(f)
163 self.bundlefile = open(bundlename, "rb")
164 self.bundlefile = f
165 header = self.bundlefile.read(6)
164 header = self.bundlefile.read(6)
166 if not header.startswith("HG"):
165 if not header.startswith("HG"):
167 raise util.Abort(_("%s: not a Mercurial bundle file") % bundlename)
166 raise util.Abort(_("%s: not a Mercurial bundle file") % bundlename)
168 elif not header.startswith("HG10"):
167 elif not header.startswith("HG10"):
169 raise util.Abort(_("%s: unknown bundle version") % bundlename)
168 raise util.Abort(_("%s: unknown bundle version") % bundlename)
170 elif header == "HG10BZ":
169 elif header == "HG10BZ":
171 raise util.Abort(_("%s: compressed bundle not supported")
170 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
172 % bundlename)
171 suffix=".hg10un", dir=self.path)
172 self.tempfile = temp
173 fptemp = os.fdopen(fdtemp, 'wb')
174 def generator(f):
175 zd = bz2.BZ2Decompressor()
176 zd.decompress("BZ")
177 for chunk in f:
178 yield zd.decompress(chunk)
179 gen = generator(util.filechunkiter(self.bundlefile, 4096))
180
181 try:
182 fptemp.write("HG10UN")
183 for chunk in gen:
184 fptemp.write(chunk)
185 finally:
186 fptemp.close()
187 self.bundlefile.close()
188
189 self.bundlefile = open(self.tempfile, "rb")
190 # seek right after the header
191 self.bundlefile.seek(6)
173 elif header == "HG10UN":
192 elif header == "HG10UN":
174 # uncompressed bundle supported
193 # nothing to do
175 pass
194 pass
176 else:
195 else:
177 raise util.Abort(_("%s: unknown bundle compression type")
196 raise util.Abort(_("%s: unknown bundle compression type")
178 % bundlename)
197 % bundlename)
179 self.changelog = bundlechangelog(self.opener, self.bundlefile)
198 self.changelog = bundlechangelog(self.opener, self.bundlefile)
180 self.manifest = bundlemanifest(self.opener, self.bundlefile,
199 self.manifest = bundlemanifest(self.opener, self.bundlefile,
181 self.changelog.rev)
200 self.changelog.rev)
182 # dict with the mapping 'filename' -> position in the bundle
201 # dict with the mapping 'filename' -> position in the bundle
183 self.bundlefilespos = {}
202 self.bundlefilespos = {}
184 while 1:
203 while 1:
185 f = changegroup.getchunk(self.bundlefile)
204 f = changegroup.getchunk(self.bundlefile)
186 if not f:
205 if not f:
187 break
206 break
188 self.bundlefilespos[f] = self.bundlefile.tell()
207 self.bundlefilespos[f] = self.bundlefile.tell()
189 for c in changegroup.chunkiter(self.bundlefile):
208 for c in changegroup.chunkiter(self.bundlefile):
190 pass
209 pass
191
210
192 def dev(self):
211 def dev(self):
193 return -1
212 return -1
194
213
195 def file(self, f):
214 def file(self, f):
196 if f[0] == '/':
215 if f[0] == '/':
197 f = f[1:]
216 f = f[1:]
198 if f in self.bundlefilespos:
217 if f in self.bundlefilespos:
199 self.bundlefile.seek(self.bundlefilespos[f])
218 self.bundlefile.seek(self.bundlefilespos[f])
200 return bundlefilelog(self.opener, f, self.bundlefile,
219 return bundlefilelog(self.opener, f, self.bundlefile,
201 self.changelog.rev)
220 self.changelog.rev)
202 else:
221 else:
203 return filelog.filelog(self.opener, f)
222 return filelog.filelog(self.opener, f)
204
223
205 def close(self):
224 def close(self):
206 """Close assigned bundle file immediately."""
225 """Close assigned bundle file immediately."""
207 self.bundlefile.close()
226 self.bundlefile.close()
227
228 def __del__(self):
229 if not self.bundlefile.closed:
230 self.bundlefile.close()
231 if self.tempfile is not None:
232 os.unlink(self.tempfile)
@@ -1,3431 +1,3453 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from demandload import demandload
8 from demandload import demandload
9 from node import *
9 from node import *
10 from i18n import gettext as _
10 from i18n import gettext as _
11 demandload(globals(), "os re sys signal shutil imp urllib pdb")
11 demandload(globals(), "os re sys signal shutil imp urllib pdb")
12 demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo")
12 demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo")
13 demandload(globals(), "fnmatch hgweb mdiff random signal tempfile time")
13 demandload(globals(), "fnmatch hgweb mdiff random signal tempfile time")
14 demandload(globals(), "traceback errno socket version struct atexit sets bz2")
14 demandload(globals(), "traceback errno socket version struct atexit sets bz2")
15 demandload(globals(), "archival changegroup")
15 demandload(globals(), "archival changegroup")
16
16
17 class UnknownCommand(Exception):
17 class UnknownCommand(Exception):
18 """Exception raised if command is not in the command table."""
18 """Exception raised if command is not in the command table."""
19 class AmbiguousCommand(Exception):
19 class AmbiguousCommand(Exception):
20 """Exception raised if command shortcut matches more than one command."""
20 """Exception raised if command shortcut matches more than one command."""
21
21
22 def bail_if_changed(repo):
22 def bail_if_changed(repo):
23 modified, added, removed, deleted, unknown = repo.changes()
23 modified, added, removed, deleted, unknown = repo.changes()
24 if modified or added or removed or deleted:
24 if modified or added or removed or deleted:
25 raise util.Abort(_("outstanding uncommitted changes"))
25 raise util.Abort(_("outstanding uncommitted changes"))
26
26
27 def filterfiles(filters, files):
27 def filterfiles(filters, files):
28 l = [x for x in files if x in filters]
28 l = [x for x in files if x in filters]
29
29
30 for t in filters:
30 for t in filters:
31 if t and t[-1] != "/":
31 if t and t[-1] != "/":
32 t += "/"
32 t += "/"
33 l += [x for x in files if x.startswith(t)]
33 l += [x for x in files if x.startswith(t)]
34 return l
34 return l
35
35
36 def relpath(repo, args):
36 def relpath(repo, args):
37 cwd = repo.getcwd()
37 cwd = repo.getcwd()
38 if cwd:
38 if cwd:
39 return [util.normpath(os.path.join(cwd, x)) for x in args]
39 return [util.normpath(os.path.join(cwd, x)) for x in args]
40 return args
40 return args
41
41
42 def matchpats(repo, pats=[], opts={}, head=''):
42 def matchpats(repo, pats=[], opts={}, head=''):
43 cwd = repo.getcwd()
43 cwd = repo.getcwd()
44 if not pats and cwd:
44 if not pats and cwd:
45 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
45 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
46 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
46 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
47 cwd = ''
47 cwd = ''
48 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
48 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
49 opts.get('exclude'), head)
49 opts.get('exclude'), head)
50
50
51 def makewalk(repo, pats, opts, node=None, head='', badmatch=None):
51 def makewalk(repo, pats, opts, node=None, head='', badmatch=None):
52 files, matchfn, anypats = matchpats(repo, pats, opts, head)
52 files, matchfn, anypats = matchpats(repo, pats, opts, head)
53 exact = dict(zip(files, files))
53 exact = dict(zip(files, files))
54 def walk():
54 def walk():
55 for src, fn in repo.walk(node=node, files=files, match=matchfn,
55 for src, fn in repo.walk(node=node, files=files, match=matchfn,
56 badmatch=badmatch):
56 badmatch=badmatch):
57 yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
57 yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
58 return files, matchfn, walk()
58 return files, matchfn, walk()
59
59
60 def walk(repo, pats, opts, node=None, head='', badmatch=None):
60 def walk(repo, pats, opts, node=None, head='', badmatch=None):
61 files, matchfn, results = makewalk(repo, pats, opts, node, head, badmatch)
61 files, matchfn, results = makewalk(repo, pats, opts, node, head, badmatch)
62 for r in results:
62 for r in results:
63 yield r
63 yield r
64
64
65 def walkchangerevs(ui, repo, pats, opts):
65 def walkchangerevs(ui, repo, pats, opts):
66 '''Iterate over files and the revs they changed in.
66 '''Iterate over files and the revs they changed in.
67
67
68 Callers most commonly need to iterate backwards over the history
68 Callers most commonly need to iterate backwards over the history
69 it is interested in. Doing so has awful (quadratic-looking)
69 it is interested in. Doing so has awful (quadratic-looking)
70 performance, so we use iterators in a "windowed" way.
70 performance, so we use iterators in a "windowed" way.
71
71
72 We walk a window of revisions in the desired order. Within the
72 We walk a window of revisions in the desired order. Within the
73 window, we first walk forwards to gather data, then in the desired
73 window, we first walk forwards to gather data, then in the desired
74 order (usually backwards) to display it.
74 order (usually backwards) to display it.
75
75
76 This function returns an (iterator, getchange, matchfn) tuple. The
76 This function returns an (iterator, getchange, matchfn) tuple. The
77 getchange function returns the changelog entry for a numeric
77 getchange function returns the changelog entry for a numeric
78 revision. The iterator yields 3-tuples. They will be of one of
78 revision. The iterator yields 3-tuples. They will be of one of
79 the following forms:
79 the following forms:
80
80
81 "window", incrementing, lastrev: stepping through a window,
81 "window", incrementing, lastrev: stepping through a window,
82 positive if walking forwards through revs, last rev in the
82 positive if walking forwards through revs, last rev in the
83 sequence iterated over - use to reset state for the current window
83 sequence iterated over - use to reset state for the current window
84
84
85 "add", rev, fns: out-of-order traversal of the given file names
85 "add", rev, fns: out-of-order traversal of the given file names
86 fns, which changed during revision rev - use to gather data for
86 fns, which changed during revision rev - use to gather data for
87 possible display
87 possible display
88
88
89 "iter", rev, None: in-order traversal of the revs earlier iterated
89 "iter", rev, None: in-order traversal of the revs earlier iterated
90 over with "add" - use to display data'''
90 over with "add" - use to display data'''
91
91
92 def increasing_windows(start, end, windowsize=8, sizelimit=512):
92 def increasing_windows(start, end, windowsize=8, sizelimit=512):
93 if start < end:
93 if start < end:
94 while start < end:
94 while start < end:
95 yield start, min(windowsize, end-start)
95 yield start, min(windowsize, end-start)
96 start += windowsize
96 start += windowsize
97 if windowsize < sizelimit:
97 if windowsize < sizelimit:
98 windowsize *= 2
98 windowsize *= 2
99 else:
99 else:
100 while start > end:
100 while start > end:
101 yield start, min(windowsize, start-end-1)
101 yield start, min(windowsize, start-end-1)
102 start -= windowsize
102 start -= windowsize
103 if windowsize < sizelimit:
103 if windowsize < sizelimit:
104 windowsize *= 2
104 windowsize *= 2
105
105
106
106
107 files, matchfn, anypats = matchpats(repo, pats, opts)
107 files, matchfn, anypats = matchpats(repo, pats, opts)
108
108
109 if repo.changelog.count() == 0:
109 if repo.changelog.count() == 0:
110 return [], False, matchfn
110 return [], False, matchfn
111
111
112 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
112 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
113 wanted = {}
113 wanted = {}
114 slowpath = anypats
114 slowpath = anypats
115 fncache = {}
115 fncache = {}
116
116
117 chcache = {}
117 chcache = {}
118 def getchange(rev):
118 def getchange(rev):
119 ch = chcache.get(rev)
119 ch = chcache.get(rev)
120 if ch is None:
120 if ch is None:
121 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
121 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
122 return ch
122 return ch
123
123
124 if not slowpath and not files:
124 if not slowpath and not files:
125 # No files, no patterns. Display all revs.
125 # No files, no patterns. Display all revs.
126 wanted = dict(zip(revs, revs))
126 wanted = dict(zip(revs, revs))
127 if not slowpath:
127 if not slowpath:
128 # Only files, no patterns. Check the history of each file.
128 # Only files, no patterns. Check the history of each file.
129 def filerevgen(filelog):
129 def filerevgen(filelog):
130 for i, window in increasing_windows(filelog.count()-1, -1):
130 for i, window in increasing_windows(filelog.count()-1, -1):
131 revs = []
131 revs = []
132 for j in xrange(i - window, i + 1):
132 for j in xrange(i - window, i + 1):
133 revs.append(filelog.linkrev(filelog.node(j)))
133 revs.append(filelog.linkrev(filelog.node(j)))
134 revs.reverse()
134 revs.reverse()
135 for rev in revs:
135 for rev in revs:
136 yield rev
136 yield rev
137
137
138 minrev, maxrev = min(revs), max(revs)
138 minrev, maxrev = min(revs), max(revs)
139 for file_ in files:
139 for file_ in files:
140 filelog = repo.file(file_)
140 filelog = repo.file(file_)
141 # A zero count may be a directory or deleted file, so
141 # A zero count may be a directory or deleted file, so
142 # try to find matching entries on the slow path.
142 # try to find matching entries on the slow path.
143 if filelog.count() == 0:
143 if filelog.count() == 0:
144 slowpath = True
144 slowpath = True
145 break
145 break
146 for rev in filerevgen(filelog):
146 for rev in filerevgen(filelog):
147 if rev <= maxrev:
147 if rev <= maxrev:
148 if rev < minrev:
148 if rev < minrev:
149 break
149 break
150 fncache.setdefault(rev, [])
150 fncache.setdefault(rev, [])
151 fncache[rev].append(file_)
151 fncache[rev].append(file_)
152 wanted[rev] = 1
152 wanted[rev] = 1
153 if slowpath:
153 if slowpath:
154 # The slow path checks files modified in every changeset.
154 # The slow path checks files modified in every changeset.
155 def changerevgen():
155 def changerevgen():
156 for i, window in increasing_windows(repo.changelog.count()-1, -1):
156 for i, window in increasing_windows(repo.changelog.count()-1, -1):
157 for j in xrange(i - window, i + 1):
157 for j in xrange(i - window, i + 1):
158 yield j, getchange(j)[3]
158 yield j, getchange(j)[3]
159
159
160 for rev, changefiles in changerevgen():
160 for rev, changefiles in changerevgen():
161 matches = filter(matchfn, changefiles)
161 matches = filter(matchfn, changefiles)
162 if matches:
162 if matches:
163 fncache[rev] = matches
163 fncache[rev] = matches
164 wanted[rev] = 1
164 wanted[rev] = 1
165
165
166 def iterate():
166 def iterate():
167 for i, window in increasing_windows(0, len(revs)):
167 for i, window in increasing_windows(0, len(revs)):
168 yield 'window', revs[0] < revs[-1], revs[-1]
168 yield 'window', revs[0] < revs[-1], revs[-1]
169 nrevs = [rev for rev in revs[i:i+window]
169 nrevs = [rev for rev in revs[i:i+window]
170 if rev in wanted]
170 if rev in wanted]
171 srevs = list(nrevs)
171 srevs = list(nrevs)
172 srevs.sort()
172 srevs.sort()
173 for rev in srevs:
173 for rev in srevs:
174 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
174 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
175 yield 'add', rev, fns
175 yield 'add', rev, fns
176 for rev in nrevs:
176 for rev in nrevs:
177 yield 'iter', rev, None
177 yield 'iter', rev, None
178 return iterate(), getchange, matchfn
178 return iterate(), getchange, matchfn
179
179
180 revrangesep = ':'
180 revrangesep = ':'
181
181
182 def revrange(ui, repo, revs, revlog=None):
182 def revrange(ui, repo, revs, revlog=None):
183 """Yield revision as strings from a list of revision specifications."""
183 """Yield revision as strings from a list of revision specifications."""
184 if revlog is None:
184 if revlog is None:
185 revlog = repo.changelog
185 revlog = repo.changelog
186 revcount = revlog.count()
186 revcount = revlog.count()
187 def fix(val, defval):
187 def fix(val, defval):
188 if not val:
188 if not val:
189 return defval
189 return defval
190 try:
190 try:
191 num = int(val)
191 num = int(val)
192 if str(num) != val:
192 if str(num) != val:
193 raise ValueError
193 raise ValueError
194 if num < 0:
194 if num < 0:
195 num += revcount
195 num += revcount
196 if num < 0:
196 if num < 0:
197 num = 0
197 num = 0
198 elif num >= revcount:
198 elif num >= revcount:
199 raise ValueError
199 raise ValueError
200 except ValueError:
200 except ValueError:
201 try:
201 try:
202 num = repo.changelog.rev(repo.lookup(val))
202 num = repo.changelog.rev(repo.lookup(val))
203 except KeyError:
203 except KeyError:
204 try:
204 try:
205 num = revlog.rev(revlog.lookup(val))
205 num = revlog.rev(revlog.lookup(val))
206 except KeyError:
206 except KeyError:
207 raise util.Abort(_('invalid revision identifier %s'), val)
207 raise util.Abort(_('invalid revision identifier %s'), val)
208 return num
208 return num
209 seen = {}
209 seen = {}
210 for spec in revs:
210 for spec in revs:
211 if spec.find(revrangesep) >= 0:
211 if spec.find(revrangesep) >= 0:
212 start, end = spec.split(revrangesep, 1)
212 start, end = spec.split(revrangesep, 1)
213 start = fix(start, 0)
213 start = fix(start, 0)
214 end = fix(end, revcount - 1)
214 end = fix(end, revcount - 1)
215 step = start > end and -1 or 1
215 step = start > end and -1 or 1
216 for rev in xrange(start, end+step, step):
216 for rev in xrange(start, end+step, step):
217 if rev in seen:
217 if rev in seen:
218 continue
218 continue
219 seen[rev] = 1
219 seen[rev] = 1
220 yield str(rev)
220 yield str(rev)
221 else:
221 else:
222 rev = fix(spec, None)
222 rev = fix(spec, None)
223 if rev in seen:
223 if rev in seen:
224 continue
224 continue
225 seen[rev] = 1
225 seen[rev] = 1
226 yield str(rev)
226 yield str(rev)
227
227
228 def make_filename(repo, r, pat, node=None,
228 def make_filename(repo, r, pat, node=None,
229 total=None, seqno=None, revwidth=None, pathname=None):
229 total=None, seqno=None, revwidth=None, pathname=None):
230 node_expander = {
230 node_expander = {
231 'H': lambda: hex(node),
231 'H': lambda: hex(node),
232 'R': lambda: str(r.rev(node)),
232 'R': lambda: str(r.rev(node)),
233 'h': lambda: short(node),
233 'h': lambda: short(node),
234 }
234 }
235 expander = {
235 expander = {
236 '%': lambda: '%',
236 '%': lambda: '%',
237 'b': lambda: os.path.basename(repo.root),
237 'b': lambda: os.path.basename(repo.root),
238 }
238 }
239
239
240 try:
240 try:
241 if node:
241 if node:
242 expander.update(node_expander)
242 expander.update(node_expander)
243 if node and revwidth is not None:
243 if node and revwidth is not None:
244 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
244 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
245 if total is not None:
245 if total is not None:
246 expander['N'] = lambda: str(total)
246 expander['N'] = lambda: str(total)
247 if seqno is not None:
247 if seqno is not None:
248 expander['n'] = lambda: str(seqno)
248 expander['n'] = lambda: str(seqno)
249 if total is not None and seqno is not None:
249 if total is not None and seqno is not None:
250 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
250 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
251 if pathname is not None:
251 if pathname is not None:
252 expander['s'] = lambda: os.path.basename(pathname)
252 expander['s'] = lambda: os.path.basename(pathname)
253 expander['d'] = lambda: os.path.dirname(pathname) or '.'
253 expander['d'] = lambda: os.path.dirname(pathname) or '.'
254 expander['p'] = lambda: pathname
254 expander['p'] = lambda: pathname
255
255
256 newname = []
256 newname = []
257 patlen = len(pat)
257 patlen = len(pat)
258 i = 0
258 i = 0
259 while i < patlen:
259 while i < patlen:
260 c = pat[i]
260 c = pat[i]
261 if c == '%':
261 if c == '%':
262 i += 1
262 i += 1
263 c = pat[i]
263 c = pat[i]
264 c = expander[c]()
264 c = expander[c]()
265 newname.append(c)
265 newname.append(c)
266 i += 1
266 i += 1
267 return ''.join(newname)
267 return ''.join(newname)
268 except KeyError, inst:
268 except KeyError, inst:
269 raise util.Abort(_("invalid format spec '%%%s' in output file name"),
269 raise util.Abort(_("invalid format spec '%%%s' in output file name"),
270 inst.args[0])
270 inst.args[0])
271
271
272 def make_file(repo, r, pat, node=None,
272 def make_file(repo, r, pat, node=None,
273 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
273 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
274 if not pat or pat == '-':
274 if not pat or pat == '-':
275 return 'w' in mode and sys.stdout or sys.stdin
275 return 'w' in mode and sys.stdout or sys.stdin
276 if hasattr(pat, 'write') and 'w' in mode:
276 if hasattr(pat, 'write') and 'w' in mode:
277 return pat
277 return pat
278 if hasattr(pat, 'read') and 'r' in mode:
278 if hasattr(pat, 'read') and 'r' in mode:
279 return pat
279 return pat
280 return open(make_filename(repo, r, pat, node, total, seqno, revwidth,
280 return open(make_filename(repo, r, pat, node, total, seqno, revwidth,
281 pathname),
281 pathname),
282 mode)
282 mode)
283
283
284 def write_bundle(cg, filename=None, compress=True):
284 def write_bundle(cg, filename=None, compress=True):
285 """Write a bundle file and return its filename.
285 """Write a bundle file and return its filename.
286
286
287 Existing files will not be overwritten.
287 Existing files will not be overwritten.
288 If no filename is specified, a temporary file is created.
288 If no filename is specified, a temporary file is created.
289 bz2 compression can be turned off.
289 bz2 compression can be turned off.
290 The bundle file will be deleted in case of errors.
290 The bundle file will be deleted in case of errors.
291 """
291 """
292 class nocompress(object):
292 class nocompress(object):
293 def compress(self, x):
293 def compress(self, x):
294 return x
294 return x
295 def flush(self):
295 def flush(self):
296 return ""
296 return ""
297
297
298 fh = None
298 fh = None
299 cleanup = None
299 cleanup = None
300 try:
300 try:
301 if filename:
301 if filename:
302 if os.path.exists(filename):
302 if os.path.exists(filename):
303 raise util.Abort(_("file '%s' already exists"), filename)
303 raise util.Abort(_("file '%s' already exists"), filename)
304 fh = open(filename, "wb")
304 fh = open(filename, "wb")
305 else:
305 else:
306 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
306 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
307 fh = os.fdopen(fd, "wb")
307 fh = os.fdopen(fd, "wb")
308 cleanup = filename
308 cleanup = filename
309
309
310 if compress:
310 if compress:
311 fh.write("HG10")
311 fh.write("HG10")
312 z = bz2.BZ2Compressor(9)
312 z = bz2.BZ2Compressor(9)
313 else:
313 else:
314 fh.write("HG10UN")
314 fh.write("HG10UN")
315 z = nocompress()
315 z = nocompress()
316 # parse the changegroup data, otherwise we will block
316 # parse the changegroup data, otherwise we will block
317 # in case of sshrepo because we don't know the end of the stream
317 # in case of sshrepo because we don't know the end of the stream
318
318
319 # an empty chunkiter is the end of the changegroup
319 # an empty chunkiter is the end of the changegroup
320 empty = False
320 empty = False
321 while not empty:
321 while not empty:
322 empty = True
322 empty = True
323 for chunk in changegroup.chunkiter(cg):
323 for chunk in changegroup.chunkiter(cg):
324 empty = False
324 empty = False
325 fh.write(z.compress(changegroup.genchunk(chunk)))
325 fh.write(z.compress(changegroup.genchunk(chunk)))
326 fh.write(z.compress(changegroup.closechunk()))
326 fh.write(z.compress(changegroup.closechunk()))
327 fh.write(z.flush())
327 fh.write(z.flush())
328 cleanup = None
328 cleanup = None
329 return filename
329 return filename
330 finally:
330 finally:
331 if fh is not None:
331 if fh is not None:
332 fh.close()
332 fh.close()
333 if cleanup is not None:
333 if cleanup is not None:
334 os.unlink(cleanup)
334 os.unlink(cleanup)
335
335
336 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
336 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
337 changes=None, text=False, opts={}):
337 changes=None, text=False, opts={}):
338 if not node1:
338 if not node1:
339 node1 = repo.dirstate.parents()[0]
339 node1 = repo.dirstate.parents()[0]
340 # reading the data for node1 early allows it to play nicely
340 # reading the data for node1 early allows it to play nicely
341 # with repo.changes and the revlog cache.
341 # with repo.changes and the revlog cache.
342 change = repo.changelog.read(node1)
342 change = repo.changelog.read(node1)
343 mmap = repo.manifest.read(change[0])
343 mmap = repo.manifest.read(change[0])
344 date1 = util.datestr(change[2])
344 date1 = util.datestr(change[2])
345
345
346 if not changes:
346 if not changes:
347 changes = repo.changes(node1, node2, files, match=match)
347 changes = repo.changes(node1, node2, files, match=match)
348 modified, added, removed, deleted, unknown = changes
348 modified, added, removed, deleted, unknown = changes
349 if files:
349 if files:
350 modified, added, removed = map(lambda x: filterfiles(files, x),
350 modified, added, removed = map(lambda x: filterfiles(files, x),
351 (modified, added, removed))
351 (modified, added, removed))
352
352
353 if not modified and not added and not removed:
353 if not modified and not added and not removed:
354 return
354 return
355
355
356 if node2:
356 if node2:
357 change = repo.changelog.read(node2)
357 change = repo.changelog.read(node2)
358 mmap2 = repo.manifest.read(change[0])
358 mmap2 = repo.manifest.read(change[0])
359 date2 = util.datestr(change[2])
359 date2 = util.datestr(change[2])
360 def read(f):
360 def read(f):
361 return repo.file(f).read(mmap2[f])
361 return repo.file(f).read(mmap2[f])
362 else:
362 else:
363 date2 = util.datestr()
363 date2 = util.datestr()
364 def read(f):
364 def read(f):
365 return repo.wread(f)
365 return repo.wread(f)
366
366
367 if ui.quiet:
367 if ui.quiet:
368 r = None
368 r = None
369 else:
369 else:
370 hexfunc = ui.verbose and hex or short
370 hexfunc = ui.verbose and hex or short
371 r = [hexfunc(node) for node in [node1, node2] if node]
371 r = [hexfunc(node) for node in [node1, node2] if node]
372
372
373 diffopts = ui.diffopts()
373 diffopts = ui.diffopts()
374 showfunc = opts.get('show_function') or diffopts['showfunc']
374 showfunc = opts.get('show_function') or diffopts['showfunc']
375 ignorews = opts.get('ignore_all_space') or diffopts['ignorews']
375 ignorews = opts.get('ignore_all_space') or diffopts['ignorews']
376 for f in modified:
376 for f in modified:
377 to = None
377 to = None
378 if f in mmap:
378 if f in mmap:
379 to = repo.file(f).read(mmap[f])
379 to = repo.file(f).read(mmap[f])
380 tn = read(f)
380 tn = read(f)
381 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
381 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
382 showfunc=showfunc, ignorews=ignorews))
382 showfunc=showfunc, ignorews=ignorews))
383 for f in added:
383 for f in added:
384 to = None
384 to = None
385 tn = read(f)
385 tn = read(f)
386 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
386 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
387 showfunc=showfunc, ignorews=ignorews))
387 showfunc=showfunc, ignorews=ignorews))
388 for f in removed:
388 for f in removed:
389 to = repo.file(f).read(mmap[f])
389 to = repo.file(f).read(mmap[f])
390 tn = None
390 tn = None
391 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
391 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
392 showfunc=showfunc, ignorews=ignorews))
392 showfunc=showfunc, ignorews=ignorews))
393
393
394 def trimuser(ui, name, rev, revcache):
394 def trimuser(ui, name, rev, revcache):
395 """trim the name of the user who committed a change"""
395 """trim the name of the user who committed a change"""
396 user = revcache.get(rev)
396 user = revcache.get(rev)
397 if user is None:
397 if user is None:
398 user = revcache[rev] = ui.shortuser(name)
398 user = revcache[rev] = ui.shortuser(name)
399 return user
399 return user
400
400
401 class changeset_printer(object):
401 class changeset_printer(object):
402 '''show changeset information when templating not requested.'''
402 '''show changeset information when templating not requested.'''
403
403
404 def __init__(self, ui, repo):
404 def __init__(self, ui, repo):
405 self.ui = ui
405 self.ui = ui
406 self.repo = repo
406 self.repo = repo
407
407
408 def show(self, rev=0, changenode=None, brinfo=None):
408 def show(self, rev=0, changenode=None, brinfo=None):
409 '''show a single changeset or file revision'''
409 '''show a single changeset or file revision'''
410 log = self.repo.changelog
410 log = self.repo.changelog
411 if changenode is None:
411 if changenode is None:
412 changenode = log.node(rev)
412 changenode = log.node(rev)
413 elif not rev:
413 elif not rev:
414 rev = log.rev(changenode)
414 rev = log.rev(changenode)
415
415
416 if self.ui.quiet:
416 if self.ui.quiet:
417 self.ui.write("%d:%s\n" % (rev, short(changenode)))
417 self.ui.write("%d:%s\n" % (rev, short(changenode)))
418 return
418 return
419
419
420 changes = log.read(changenode)
420 changes = log.read(changenode)
421 date = util.datestr(changes[2])
421 date = util.datestr(changes[2])
422
422
423 parents = [(log.rev(p), self.ui.verbose and hex(p) or short(p))
423 parents = [(log.rev(p), self.ui.verbose and hex(p) or short(p))
424 for p in log.parents(changenode)
424 for p in log.parents(changenode)
425 if self.ui.debugflag or p != nullid]
425 if self.ui.debugflag or p != nullid]
426 if (not self.ui.debugflag and len(parents) == 1 and
426 if (not self.ui.debugflag and len(parents) == 1 and
427 parents[0][0] == rev-1):
427 parents[0][0] == rev-1):
428 parents = []
428 parents = []
429
429
430 if self.ui.verbose:
430 if self.ui.verbose:
431 self.ui.write(_("changeset: %d:%s\n") % (rev, hex(changenode)))
431 self.ui.write(_("changeset: %d:%s\n") % (rev, hex(changenode)))
432 else:
432 else:
433 self.ui.write(_("changeset: %d:%s\n") % (rev, short(changenode)))
433 self.ui.write(_("changeset: %d:%s\n") % (rev, short(changenode)))
434
434
435 for tag in self.repo.nodetags(changenode):
435 for tag in self.repo.nodetags(changenode):
436 self.ui.status(_("tag: %s\n") % tag)
436 self.ui.status(_("tag: %s\n") % tag)
437 for parent in parents:
437 for parent in parents:
438 self.ui.write(_("parent: %d:%s\n") % parent)
438 self.ui.write(_("parent: %d:%s\n") % parent)
439
439
440 if brinfo and changenode in brinfo:
440 if brinfo and changenode in brinfo:
441 br = brinfo[changenode]
441 br = brinfo[changenode]
442 self.ui.write(_("branch: %s\n") % " ".join(br))
442 self.ui.write(_("branch: %s\n") % " ".join(br))
443
443
444 self.ui.debug(_("manifest: %d:%s\n") %
444 self.ui.debug(_("manifest: %d:%s\n") %
445 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
445 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
446 self.ui.status(_("user: %s\n") % changes[1])
446 self.ui.status(_("user: %s\n") % changes[1])
447 self.ui.status(_("date: %s\n") % date)
447 self.ui.status(_("date: %s\n") % date)
448
448
449 if self.ui.debugflag:
449 if self.ui.debugflag:
450 files = self.repo.changes(log.parents(changenode)[0], changenode)
450 files = self.repo.changes(log.parents(changenode)[0], changenode)
451 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
451 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
452 files):
452 files):
453 if value:
453 if value:
454 self.ui.note("%-12s %s\n" % (key, " ".join(value)))
454 self.ui.note("%-12s %s\n" % (key, " ".join(value)))
455 else:
455 else:
456 self.ui.note(_("files: %s\n") % " ".join(changes[3]))
456 self.ui.note(_("files: %s\n") % " ".join(changes[3]))
457
457
458 description = changes[4].strip()
458 description = changes[4].strip()
459 if description:
459 if description:
460 if self.ui.verbose:
460 if self.ui.verbose:
461 self.ui.status(_("description:\n"))
461 self.ui.status(_("description:\n"))
462 self.ui.status(description)
462 self.ui.status(description)
463 self.ui.status("\n\n")
463 self.ui.status("\n\n")
464 else:
464 else:
465 self.ui.status(_("summary: %s\n") %
465 self.ui.status(_("summary: %s\n") %
466 description.splitlines()[0])
466 description.splitlines()[0])
467 self.ui.status("\n")
467 self.ui.status("\n")
468
468
469 def show_changeset(ui, repo, opts):
469 def show_changeset(ui, repo, opts):
470 '''show one changeset. uses template or regular display. caller
470 '''show one changeset. uses template or regular display. caller
471 can pass in 'style' and 'template' options in opts.'''
471 can pass in 'style' and 'template' options in opts.'''
472
472
473 tmpl = opts.get('template')
473 tmpl = opts.get('template')
474 if tmpl:
474 if tmpl:
475 tmpl = templater.parsestring(tmpl, quoted=False)
475 tmpl = templater.parsestring(tmpl, quoted=False)
476 else:
476 else:
477 tmpl = ui.config('ui', 'logtemplate')
477 tmpl = ui.config('ui', 'logtemplate')
478 if tmpl: tmpl = templater.parsestring(tmpl)
478 if tmpl: tmpl = templater.parsestring(tmpl)
479 mapfile = opts.get('style') or ui.config('ui', 'style')
479 mapfile = opts.get('style') or ui.config('ui', 'style')
480 if tmpl or mapfile:
480 if tmpl or mapfile:
481 if mapfile:
481 if mapfile:
482 if not os.path.isfile(mapfile):
482 if not os.path.isfile(mapfile):
483 mapname = templater.templatepath('map-cmdline.' + mapfile)
483 mapname = templater.templatepath('map-cmdline.' + mapfile)
484 if not mapname: mapname = templater.templatepath(mapfile)
484 if not mapname: mapname = templater.templatepath(mapfile)
485 if mapname: mapfile = mapname
485 if mapname: mapfile = mapname
486 try:
486 try:
487 t = templater.changeset_templater(ui, repo, mapfile)
487 t = templater.changeset_templater(ui, repo, mapfile)
488 except SyntaxError, inst:
488 except SyntaxError, inst:
489 raise util.Abort(inst.args[0])
489 raise util.Abort(inst.args[0])
490 if tmpl: t.use_template(tmpl)
490 if tmpl: t.use_template(tmpl)
491 return t
491 return t
492 return changeset_printer(ui, repo)
492 return changeset_printer(ui, repo)
493
493
494 def show_version(ui):
494 def show_version(ui):
495 """output version and copyright information"""
495 """output version and copyright information"""
496 ui.write(_("Mercurial Distributed SCM (version %s)\n")
496 ui.write(_("Mercurial Distributed SCM (version %s)\n")
497 % version.get_version())
497 % version.get_version())
498 ui.status(_(
498 ui.status(_(
499 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
499 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
500 "This is free software; see the source for copying conditions. "
500 "This is free software; see the source for copying conditions. "
501 "There is NO\nwarranty; "
501 "There is NO\nwarranty; "
502 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
502 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
503 ))
503 ))
504
504
505 def help_(ui, cmd=None, with_version=False):
505 def help_(ui, cmd=None, with_version=False):
506 """show help for a given command or all commands"""
506 """show help for a given command or all commands"""
507 option_lists = []
507 option_lists = []
508 if cmd and cmd != 'shortlist':
508 if cmd and cmd != 'shortlist':
509 if with_version:
509 if with_version:
510 show_version(ui)
510 show_version(ui)
511 ui.write('\n')
511 ui.write('\n')
512 aliases, i = find(cmd)
512 aliases, i = find(cmd)
513 # synopsis
513 # synopsis
514 ui.write("%s\n\n" % i[2])
514 ui.write("%s\n\n" % i[2])
515
515
516 # description
516 # description
517 doc = i[0].__doc__
517 doc = i[0].__doc__
518 if not doc:
518 if not doc:
519 doc = _("(No help text available)")
519 doc = _("(No help text available)")
520 if ui.quiet:
520 if ui.quiet:
521 doc = doc.splitlines(0)[0]
521 doc = doc.splitlines(0)[0]
522 ui.write("%s\n" % doc.rstrip())
522 ui.write("%s\n" % doc.rstrip())
523
523
524 if not ui.quiet:
524 if not ui.quiet:
525 # aliases
525 # aliases
526 if len(aliases) > 1:
526 if len(aliases) > 1:
527 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
527 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
528
528
529 # options
529 # options
530 if i[1]:
530 if i[1]:
531 option_lists.append(("options", i[1]))
531 option_lists.append(("options", i[1]))
532
532
533 else:
533 else:
534 # program name
534 # program name
535 if ui.verbose or with_version:
535 if ui.verbose or with_version:
536 show_version(ui)
536 show_version(ui)
537 else:
537 else:
538 ui.status(_("Mercurial Distributed SCM\n"))
538 ui.status(_("Mercurial Distributed SCM\n"))
539 ui.status('\n')
539 ui.status('\n')
540
540
541 # list of commands
541 # list of commands
542 if cmd == "shortlist":
542 if cmd == "shortlist":
543 ui.status(_('basic commands (use "hg help" '
543 ui.status(_('basic commands (use "hg help" '
544 'for the full list or option "-v" for details):\n\n'))
544 'for the full list or option "-v" for details):\n\n'))
545 elif ui.verbose:
545 elif ui.verbose:
546 ui.status(_('list of commands:\n\n'))
546 ui.status(_('list of commands:\n\n'))
547 else:
547 else:
548 ui.status(_('list of commands (use "hg help -v" '
548 ui.status(_('list of commands (use "hg help -v" '
549 'to show aliases and global options):\n\n'))
549 'to show aliases and global options):\n\n'))
550
550
551 h = {}
551 h = {}
552 cmds = {}
552 cmds = {}
553 for c, e in table.items():
553 for c, e in table.items():
554 f = c.split("|")[0]
554 f = c.split("|")[0]
555 if cmd == "shortlist" and not f.startswith("^"):
555 if cmd == "shortlist" and not f.startswith("^"):
556 continue
556 continue
557 f = f.lstrip("^")
557 f = f.lstrip("^")
558 if not ui.debugflag and f.startswith("debug"):
558 if not ui.debugflag and f.startswith("debug"):
559 continue
559 continue
560 doc = e[0].__doc__
560 doc = e[0].__doc__
561 if not doc:
561 if not doc:
562 doc = _("(No help text available)")
562 doc = _("(No help text available)")
563 h[f] = doc.splitlines(0)[0].rstrip()
563 h[f] = doc.splitlines(0)[0].rstrip()
564 cmds[f] = c.lstrip("^")
564 cmds[f] = c.lstrip("^")
565
565
566 fns = h.keys()
566 fns = h.keys()
567 fns.sort()
567 fns.sort()
568 m = max(map(len, fns))
568 m = max(map(len, fns))
569 for f in fns:
569 for f in fns:
570 if ui.verbose:
570 if ui.verbose:
571 commands = cmds[f].replace("|",", ")
571 commands = cmds[f].replace("|",", ")
572 ui.write(" %s:\n %s\n"%(commands, h[f]))
572 ui.write(" %s:\n %s\n"%(commands, h[f]))
573 else:
573 else:
574 ui.write(' %-*s %s\n' % (m, f, h[f]))
574 ui.write(' %-*s %s\n' % (m, f, h[f]))
575
575
576 # global options
576 # global options
577 if ui.verbose:
577 if ui.verbose:
578 option_lists.append(("global options", globalopts))
578 option_lists.append(("global options", globalopts))
579
579
580 # list all option lists
580 # list all option lists
581 opt_output = []
581 opt_output = []
582 for title, options in option_lists:
582 for title, options in option_lists:
583 opt_output.append(("\n%s:\n" % title, None))
583 opt_output.append(("\n%s:\n" % title, None))
584 for shortopt, longopt, default, desc in options:
584 for shortopt, longopt, default, desc in options:
585 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
585 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
586 longopt and " --%s" % longopt),
586 longopt and " --%s" % longopt),
587 "%s%s" % (desc,
587 "%s%s" % (desc,
588 default
588 default
589 and _(" (default: %s)") % default
589 and _(" (default: %s)") % default
590 or "")))
590 or "")))
591
591
592 if opt_output:
592 if opt_output:
593 opts_len = max([len(line[0]) for line in opt_output if line[1]])
593 opts_len = max([len(line[0]) for line in opt_output if line[1]])
594 for first, second in opt_output:
594 for first, second in opt_output:
595 if second:
595 if second:
596 ui.write(" %-*s %s\n" % (opts_len, first, second))
596 ui.write(" %-*s %s\n" % (opts_len, first, second))
597 else:
597 else:
598 ui.write("%s\n" % first)
598 ui.write("%s\n" % first)
599
599
600 # Commands start here, listed alphabetically
600 # Commands start here, listed alphabetically
601
601
602 def add(ui, repo, *pats, **opts):
602 def add(ui, repo, *pats, **opts):
603 """add the specified files on the next commit
603 """add the specified files on the next commit
604
604
605 Schedule files to be version controlled and added to the repository.
605 Schedule files to be version controlled and added to the repository.
606
606
607 The files will be added to the repository at the next commit.
607 The files will be added to the repository at the next commit.
608
608
609 If no names are given, add all files in the repository.
609 If no names are given, add all files in the repository.
610 """
610 """
611
611
612 names = []
612 names = []
613 for src, abs, rel, exact in walk(repo, pats, opts):
613 for src, abs, rel, exact in walk(repo, pats, opts):
614 if exact:
614 if exact:
615 if ui.verbose:
615 if ui.verbose:
616 ui.status(_('adding %s\n') % rel)
616 ui.status(_('adding %s\n') % rel)
617 names.append(abs)
617 names.append(abs)
618 elif repo.dirstate.state(abs) == '?':
618 elif repo.dirstate.state(abs) == '?':
619 ui.status(_('adding %s\n') % rel)
619 ui.status(_('adding %s\n') % rel)
620 names.append(abs)
620 names.append(abs)
621 repo.add(names)
621 repo.add(names)
622
622
623 def addremove(ui, repo, *pats, **opts):
623 def addremove(ui, repo, *pats, **opts):
624 """add all new files, delete all missing files (DEPRECATED)
624 """add all new files, delete all missing files (DEPRECATED)
625
625
626 (DEPRECATED)
626 (DEPRECATED)
627 Add all new files and remove all missing files from the repository.
627 Add all new files and remove all missing files from the repository.
628
628
629 New files are ignored if they match any of the patterns in .hgignore. As
629 New files are ignored if they match any of the patterns in .hgignore. As
630 with add, these changes take effect at the next commit.
630 with add, these changes take effect at the next commit.
631
631
632 This command is now deprecated and will be removed in a future
632 This command is now deprecated and will be removed in a future
633 release. Please use add and remove --after instead.
633 release. Please use add and remove --after instead.
634 """
634 """
635 ui.warn(_('(the addremove command is deprecated; use add and remove '
635 ui.warn(_('(the addremove command is deprecated; use add and remove '
636 '--after instead)\n'))
636 '--after instead)\n'))
637 return addremove_lock(ui, repo, pats, opts)
637 return addremove_lock(ui, repo, pats, opts)
638
638
639 def addremove_lock(ui, repo, pats, opts, wlock=None):
639 def addremove_lock(ui, repo, pats, opts, wlock=None):
640 add, remove = [], []
640 add, remove = [], []
641 for src, abs, rel, exact in walk(repo, pats, opts):
641 for src, abs, rel, exact in walk(repo, pats, opts):
642 if src == 'f' and repo.dirstate.state(abs) == '?':
642 if src == 'f' and repo.dirstate.state(abs) == '?':
643 add.append(abs)
643 add.append(abs)
644 if ui.verbose or not exact:
644 if ui.verbose or not exact:
645 ui.status(_('adding %s\n') % ((pats and rel) or abs))
645 ui.status(_('adding %s\n') % ((pats and rel) or abs))
646 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
646 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
647 remove.append(abs)
647 remove.append(abs)
648 if ui.verbose or not exact:
648 if ui.verbose or not exact:
649 ui.status(_('removing %s\n') % ((pats and rel) or abs))
649 ui.status(_('removing %s\n') % ((pats and rel) or abs))
650 repo.add(add, wlock=wlock)
650 repo.add(add, wlock=wlock)
651 repo.remove(remove, wlock=wlock)
651 repo.remove(remove, wlock=wlock)
652
652
653 def annotate(ui, repo, *pats, **opts):
653 def annotate(ui, repo, *pats, **opts):
654 """show changeset information per file line
654 """show changeset information per file line
655
655
656 List changes in files, showing the revision id responsible for each line
656 List changes in files, showing the revision id responsible for each line
657
657
658 This command is useful to discover who did a change or when a change took
658 This command is useful to discover who did a change or when a change took
659 place.
659 place.
660
660
661 Without the -a option, annotate will avoid processing files it
661 Without the -a option, annotate will avoid processing files it
662 detects as binary. With -a, annotate will generate an annotation
662 detects as binary. With -a, annotate will generate an annotation
663 anyway, probably with undesirable results.
663 anyway, probably with undesirable results.
664 """
664 """
665 def getnode(rev):
665 def getnode(rev):
666 return short(repo.changelog.node(rev))
666 return short(repo.changelog.node(rev))
667
667
668 ucache = {}
668 ucache = {}
669 def getname(rev):
669 def getname(rev):
670 cl = repo.changelog.read(repo.changelog.node(rev))
670 cl = repo.changelog.read(repo.changelog.node(rev))
671 return trimuser(ui, cl[1], rev, ucache)
671 return trimuser(ui, cl[1], rev, ucache)
672
672
673 dcache = {}
673 dcache = {}
674 def getdate(rev):
674 def getdate(rev):
675 datestr = dcache.get(rev)
675 datestr = dcache.get(rev)
676 if datestr is None:
676 if datestr is None:
677 cl = repo.changelog.read(repo.changelog.node(rev))
677 cl = repo.changelog.read(repo.changelog.node(rev))
678 datestr = dcache[rev] = util.datestr(cl[2])
678 datestr = dcache[rev] = util.datestr(cl[2])
679 return datestr
679 return datestr
680
680
681 if not pats:
681 if not pats:
682 raise util.Abort(_('at least one file name or pattern required'))
682 raise util.Abort(_('at least one file name or pattern required'))
683
683
684 opmap = [['user', getname], ['number', str], ['changeset', getnode],
684 opmap = [['user', getname], ['number', str], ['changeset', getnode],
685 ['date', getdate]]
685 ['date', getdate]]
686 if not opts['user'] and not opts['changeset'] and not opts['date']:
686 if not opts['user'] and not opts['changeset'] and not opts['date']:
687 opts['number'] = 1
687 opts['number'] = 1
688
688
689 if opts['rev']:
689 if opts['rev']:
690 node = repo.changelog.lookup(opts['rev'])
690 node = repo.changelog.lookup(opts['rev'])
691 else:
691 else:
692 node = repo.dirstate.parents()[0]
692 node = repo.dirstate.parents()[0]
693 change = repo.changelog.read(node)
693 change = repo.changelog.read(node)
694 mmap = repo.manifest.read(change[0])
694 mmap = repo.manifest.read(change[0])
695
695
696 for src, abs, rel, exact in walk(repo, pats, opts, node=node):
696 for src, abs, rel, exact in walk(repo, pats, opts, node=node):
697 f = repo.file(abs)
697 f = repo.file(abs)
698 if not opts['text'] and util.binary(f.read(mmap[abs])):
698 if not opts['text'] and util.binary(f.read(mmap[abs])):
699 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
699 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
700 continue
700 continue
701
701
702 lines = f.annotate(mmap[abs])
702 lines = f.annotate(mmap[abs])
703 pieces = []
703 pieces = []
704
704
705 for o, f in opmap:
705 for o, f in opmap:
706 if opts[o]:
706 if opts[o]:
707 l = [f(n) for n, dummy in lines]
707 l = [f(n) for n, dummy in lines]
708 if l:
708 if l:
709 m = max(map(len, l))
709 m = max(map(len, l))
710 pieces.append(["%*s" % (m, x) for x in l])
710 pieces.append(["%*s" % (m, x) for x in l])
711
711
712 if pieces:
712 if pieces:
713 for p, l in zip(zip(*pieces), lines):
713 for p, l in zip(zip(*pieces), lines):
714 ui.write("%s: %s" % (" ".join(p), l[1]))
714 ui.write("%s: %s" % (" ".join(p), l[1]))
715
715
716 def archive(ui, repo, dest, **opts):
716 def archive(ui, repo, dest, **opts):
717 '''create unversioned archive of a repository revision
717 '''create unversioned archive of a repository revision
718
718
719 By default, the revision used is the parent of the working
719 By default, the revision used is the parent of the working
720 directory; use "-r" to specify a different revision.
720 directory; use "-r" to specify a different revision.
721
721
722 To specify the type of archive to create, use "-t". Valid
722 To specify the type of archive to create, use "-t". Valid
723 types are:
723 types are:
724
724
725 "files" (default): a directory full of files
725 "files" (default): a directory full of files
726 "tar": tar archive, uncompressed
726 "tar": tar archive, uncompressed
727 "tbz2": tar archive, compressed using bzip2
727 "tbz2": tar archive, compressed using bzip2
728 "tgz": tar archive, compressed using gzip
728 "tgz": tar archive, compressed using gzip
729 "uzip": zip archive, uncompressed
729 "uzip": zip archive, uncompressed
730 "zip": zip archive, compressed using deflate
730 "zip": zip archive, compressed using deflate
731
731
732 The exact name of the destination archive or directory is given
732 The exact name of the destination archive or directory is given
733 using a format string; see "hg help export" for details.
733 using a format string; see "hg help export" for details.
734
734
735 Each member added to an archive file has a directory prefix
735 Each member added to an archive file has a directory prefix
736 prepended. Use "-p" to specify a format string for the prefix.
736 prepended. Use "-p" to specify a format string for the prefix.
737 The default is the basename of the archive, with suffixes removed.
737 The default is the basename of the archive, with suffixes removed.
738 '''
738 '''
739
739
740 if opts['rev']:
740 if opts['rev']:
741 node = repo.lookup(opts['rev'])
741 node = repo.lookup(opts['rev'])
742 else:
742 else:
743 node, p2 = repo.dirstate.parents()
743 node, p2 = repo.dirstate.parents()
744 if p2 != nullid:
744 if p2 != nullid:
745 raise util.Abort(_('uncommitted merge - please provide a '
745 raise util.Abort(_('uncommitted merge - please provide a '
746 'specific revision'))
746 'specific revision'))
747
747
748 dest = make_filename(repo, repo.changelog, dest, node)
748 dest = make_filename(repo, repo.changelog, dest, node)
749 prefix = make_filename(repo, repo.changelog, opts['prefix'], node)
749 prefix = make_filename(repo, repo.changelog, opts['prefix'], node)
750 if os.path.realpath(dest) == repo.root:
750 if os.path.realpath(dest) == repo.root:
751 raise util.Abort(_('repository root cannot be destination'))
751 raise util.Abort(_('repository root cannot be destination'))
752 dummy, matchfn, dummy = matchpats(repo, [], opts)
752 dummy, matchfn, dummy = matchpats(repo, [], opts)
753 archival.archive(repo, dest, node, opts.get('type') or 'files',
753 archival.archive(repo, dest, node, opts.get('type') or 'files',
754 not opts['no_decode'], matchfn, prefix)
754 not opts['no_decode'], matchfn, prefix)
755
755
756 def backout(ui, repo, rev, **opts):
756 def backout(ui, repo, rev, **opts):
757 '''reverse effect of earlier changeset
757 '''reverse effect of earlier changeset
758
758
759 Commit the backed out changes as a new changeset.
759 Commit the backed out changes as a new changeset. The new
760 changeset is a child of the backed out changeset.
760
761
761 If you back out a changeset other than the tip, a new head is
762 If you back out a changeset other than the tip, a new head is
762 created. The --merge option remembers the parent of the working
763 created. This head is the parent of the working directory. If
763 directory before starting the backout, then merges the new head
764 you back out an old changeset, your working directory will appear
764 with it afterwards, to save you from doing this by hand. The
765 old after the backout. You should merge the backout changeset
765 result of this merge is not committed, as for a normal merge.'''
766 with another head.
767
768 The --merge option remembers the parent of the working directory
769 before starting the backout, then merges the new head with that
770 changeset afterwards. This saves you from doing the merge by
771 hand. The result of this merge is not committed, as for a normal
772 merge.'''
766
773
767 bail_if_changed(repo)
774 bail_if_changed(repo)
768 op1, op2 = repo.dirstate.parents()
775 op1, op2 = repo.dirstate.parents()
769 if op2 != nullid:
776 if op2 != nullid:
770 raise util.Abort(_('outstanding uncommitted merge'))
777 raise util.Abort(_('outstanding uncommitted merge'))
771 node = repo.lookup(rev)
778 node = repo.lookup(rev)
772 parent, p2 = repo.changelog.parents(node)
779 parent, p2 = repo.changelog.parents(node)
773 if parent == nullid:
780 if parent == nullid:
774 raise util.Abort(_('cannot back out a change with no parents'))
781 raise util.Abort(_('cannot back out a change with no parents'))
775 if p2 != nullid:
782 if p2 != nullid:
776 raise util.Abort(_('cannot back out a merge'))
783 raise util.Abort(_('cannot back out a merge'))
777 repo.update(node, force=True, show_stats=False)
784 repo.update(node, force=True, show_stats=False)
778 revert_opts = opts.copy()
785 revert_opts = opts.copy()
779 revert_opts['rev'] = hex(parent)
786 revert_opts['rev'] = hex(parent)
780 revert(ui, repo, **revert_opts)
787 revert(ui, repo, **revert_opts)
781 commit_opts = opts.copy()
788 commit_opts = opts.copy()
782 commit_opts['addremove'] = False
789 commit_opts['addremove'] = False
783 if not commit_opts['message'] and not commit_opts['logfile']:
790 if not commit_opts['message'] and not commit_opts['logfile']:
784 commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
791 commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
792 commit_opts['force_editor'] = True
785 commit(ui, repo, **commit_opts)
793 commit(ui, repo, **commit_opts)
786 def nice(node):
794 def nice(node):
787 return '%d:%s' % (repo.changelog.rev(node), short(node))
795 return '%d:%s' % (repo.changelog.rev(node), short(node))
788 ui.status(_('changeset %s backs out changeset %s\n') %
796 ui.status(_('changeset %s backs out changeset %s\n') %
789 (nice(repo.changelog.tip()), nice(node)))
797 (nice(repo.changelog.tip()), nice(node)))
790 if opts['merge'] and op1 != node:
798 if opts['merge'] and op1 != node:
791 ui.status(_('merging with changeset %s\n') % nice(op1))
799 ui.status(_('merging with changeset %s\n') % nice(op1))
792 update(ui, repo, hex(op1), **opts)
800 doupdate(ui, repo, hex(op1), **opts)
793
801
794 def bundle(ui, repo, fname, dest="default-push", **opts):
802 def bundle(ui, repo, fname, dest="default-push", **opts):
795 """create a changegroup file
803 """create a changegroup file
796
804
797 Generate a compressed changegroup file collecting all changesets
805 Generate a compressed changegroup file collecting all changesets
798 not found in the other repository.
806 not found in the other repository.
799
807
800 This file can then be transferred using conventional means and
808 This file can then be transferred using conventional means and
801 applied to another repository with the unbundle command. This is
809 applied to another repository with the unbundle command. This is
802 useful when native push and pull are not available or when
810 useful when native push and pull are not available or when
803 exporting an entire repository is undesirable. The standard file
811 exporting an entire repository is undesirable. The standard file
804 extension is ".hg".
812 extension is ".hg".
805
813
806 Unlike import/export, this exactly preserves all changeset
814 Unlike import/export, this exactly preserves all changeset
807 contents including permissions, rename data, and revision history.
815 contents including permissions, rename data, and revision history.
808 """
816 """
809 dest = ui.expandpath(dest)
817 dest = ui.expandpath(dest)
810 other = hg.repository(ui, dest)
818 other = hg.repository(ui, dest)
811 o = repo.findoutgoing(other, force=opts['force'])
819 o = repo.findoutgoing(other, force=opts['force'])
812 cg = repo.changegroup(o, 'bundle')
820 cg = repo.changegroup(o, 'bundle')
813 write_bundle(cg, fname)
821 write_bundle(cg, fname)
814
822
815 def cat(ui, repo, file1, *pats, **opts):
823 def cat(ui, repo, file1, *pats, **opts):
816 """output the latest or given revisions of files
824 """output the latest or given revisions of files
817
825
818 Print the specified files as they were at the given revision.
826 Print the specified files as they were at the given revision.
819 If no revision is given then the tip is used.
827 If no revision is given then the tip is used.
820
828
821 Output may be to a file, in which case the name of the file is
829 Output may be to a file, in which case the name of the file is
822 given using a format string. The formatting rules are the same as
830 given using a format string. The formatting rules are the same as
823 for the export command, with the following additions:
831 for the export command, with the following additions:
824
832
825 %s basename of file being printed
833 %s basename of file being printed
826 %d dirname of file being printed, or '.' if in repo root
834 %d dirname of file being printed, or '.' if in repo root
827 %p root-relative path name of file being printed
835 %p root-relative path name of file being printed
828 """
836 """
829 mf = {}
837 mf = {}
830 rev = opts['rev']
838 rev = opts['rev']
831 if rev:
839 if rev:
832 node = repo.lookup(rev)
840 node = repo.lookup(rev)
833 else:
841 else:
834 node = repo.changelog.tip()
842 node = repo.changelog.tip()
835 change = repo.changelog.read(node)
843 change = repo.changelog.read(node)
836 mf = repo.manifest.read(change[0])
844 mf = repo.manifest.read(change[0])
837 for src, abs, rel, exact in walk(repo, (file1,) + pats, opts, node):
845 for src, abs, rel, exact in walk(repo, (file1,) + pats, opts, node):
838 r = repo.file(abs)
846 r = repo.file(abs)
839 n = mf[abs]
847 n = mf[abs]
840 fp = make_file(repo, r, opts['output'], node=n, pathname=abs)
848 fp = make_file(repo, r, opts['output'], node=n, pathname=abs)
841 fp.write(r.read(n))
849 fp.write(r.read(n))
842
850
843 def clone(ui, source, dest=None, **opts):
851 def clone(ui, source, dest=None, **opts):
844 """make a copy of an existing repository
852 """make a copy of an existing repository
845
853
846 Create a copy of an existing repository in a new directory.
854 Create a copy of an existing repository in a new directory.
847
855
848 If no destination directory name is specified, it defaults to the
856 If no destination directory name is specified, it defaults to the
849 basename of the source.
857 basename of the source.
850
858
851 The location of the source is added to the new repository's
859 The location of the source is added to the new repository's
852 .hg/hgrc file, as the default to be used for future pulls.
860 .hg/hgrc file, as the default to be used for future pulls.
853
861
854 For efficiency, hardlinks are used for cloning whenever the source
862 For efficiency, hardlinks are used for cloning whenever the source
855 and destination are on the same filesystem. Some filesystems,
863 and destination are on the same filesystem. Some filesystems,
856 such as AFS, implement hardlinking incorrectly, but do not report
864 such as AFS, implement hardlinking incorrectly, but do not report
857 errors. In these cases, use the --pull option to avoid
865 errors. In these cases, use the --pull option to avoid
858 hardlinking.
866 hardlinking.
859
867
860 See pull for valid source format details.
868 See pull for valid source format details.
861 """
869 """
862 if dest is None:
870 if dest is None:
863 dest = os.path.basename(os.path.normpath(source))
871 dest = os.path.basename(os.path.normpath(source))
864
872
865 if os.path.exists(dest):
873 if os.path.exists(dest):
866 raise util.Abort(_("destination '%s' already exists"), dest)
874 raise util.Abort(_("destination '%s' already exists"), dest)
867
875
868 dest = os.path.realpath(dest)
876 dest = os.path.realpath(dest)
869
877
870 class Dircleanup(object):
878 class Dircleanup(object):
871 def __init__(self, dir_):
879 def __init__(self, dir_):
872 self.rmtree = shutil.rmtree
880 self.rmtree = shutil.rmtree
873 self.dir_ = dir_
881 self.dir_ = dir_
874 os.mkdir(dir_)
882 os.mkdir(dir_)
875 def close(self):
883 def close(self):
876 self.dir_ = None
884 self.dir_ = None
877 def __del__(self):
885 def __del__(self):
878 if self.dir_:
886 if self.dir_:
879 self.rmtree(self.dir_, True)
887 self.rmtree(self.dir_, True)
880
888
881 if opts['ssh']:
889 if opts['ssh']:
882 ui.setconfig("ui", "ssh", opts['ssh'])
890 ui.setconfig("ui", "ssh", opts['ssh'])
883 if opts['remotecmd']:
891 if opts['remotecmd']:
884 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
892 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
885
893
886 source = ui.expandpath(source)
894 source = ui.expandpath(source)
887
895
888 d = Dircleanup(dest)
896 d = Dircleanup(dest)
889 abspath = source
897 abspath = source
890 other = hg.repository(ui, source)
898 other = hg.repository(ui, source)
891
899
892 copy = False
900 copy = False
893 if other.dev() != -1:
901 if other.dev() != -1:
894 abspath = os.path.abspath(source)
902 abspath = os.path.abspath(source)
895 if not opts['pull'] and not opts['rev']:
903 if not opts['pull'] and not opts['rev']:
896 copy = True
904 copy = True
897
905
898 if copy:
906 if copy:
899 try:
907 try:
900 # we use a lock here because if we race with commit, we
908 # we use a lock here because if we race with commit, we
901 # can end up with extra data in the cloned revlogs that's
909 # can end up with extra data in the cloned revlogs that's
902 # not pointed to by changesets, thus causing verify to
910 # not pointed to by changesets, thus causing verify to
903 # fail
911 # fail
904 l1 = other.lock()
912 l1 = other.lock()
905 except lock.LockException:
913 except lock.LockException:
906 copy = False
914 copy = False
907
915
908 if copy:
916 if copy:
909 # we lock here to avoid premature writing to the target
917 # we lock here to avoid premature writing to the target
910 os.mkdir(os.path.join(dest, ".hg"))
918 os.mkdir(os.path.join(dest, ".hg"))
911 l2 = lock.lock(os.path.join(dest, ".hg", "lock"))
919 l2 = lock.lock(os.path.join(dest, ".hg", "lock"))
912
920
913 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
921 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
914 for f in files.split():
922 for f in files.split():
915 src = os.path.join(source, ".hg", f)
923 src = os.path.join(source, ".hg", f)
916 dst = os.path.join(dest, ".hg", f)
924 dst = os.path.join(dest, ".hg", f)
917 try:
925 try:
918 util.copyfiles(src, dst)
926 util.copyfiles(src, dst)
919 except OSError, inst:
927 except OSError, inst:
920 if inst.errno != errno.ENOENT:
928 if inst.errno != errno.ENOENT:
921 raise
929 raise
922
930
923 repo = hg.repository(ui, dest)
931 repo = hg.repository(ui, dest)
924
932
925 else:
933 else:
926 revs = None
934 revs = None
927 if opts['rev']:
935 if opts['rev']:
928 if not other.local():
936 if not other.local():
929 error = _("clone -r not supported yet for remote repositories.")
937 error = _("clone -r not supported yet for remote repositories.")
930 raise util.Abort(error)
938 raise util.Abort(error)
931 else:
939 else:
932 revs = [other.lookup(rev) for rev in opts['rev']]
940 revs = [other.lookup(rev) for rev in opts['rev']]
933 repo = hg.repository(ui, dest, create=1)
941 repo = hg.repository(ui, dest, create=1)
934 repo.pull(other, heads = revs)
942 repo.pull(other, heads = revs)
935
943
936 f = repo.opener("hgrc", "w", text=True)
944 f = repo.opener("hgrc", "w", text=True)
937 f.write("[paths]\n")
945 f.write("[paths]\n")
938 f.write("default = %s\n" % abspath)
946 f.write("default = %s\n" % abspath)
939 f.close()
947 f.close()
940
948
941 if not opts['noupdate']:
949 if not opts['noupdate']:
942 update(repo.ui, repo)
950 doupdate(repo.ui, repo)
943
951
944 d.close()
952 d.close()
945
953
946 def commit(ui, repo, *pats, **opts):
954 def commit(ui, repo, *pats, **opts):
947 """commit the specified files or all outstanding changes
955 """commit the specified files or all outstanding changes
948
956
949 Commit changes to the given files into the repository.
957 Commit changes to the given files into the repository.
950
958
951 If a list of files is omitted, all changes reported by "hg status"
959 If a list of files is omitted, all changes reported by "hg status"
952 will be committed.
960 will be committed.
953
961
954 If no commit message is specified, the editor configured in your hgrc
962 If no commit message is specified, the editor configured in your hgrc
955 or in the EDITOR environment variable is started to enter a message.
963 or in the EDITOR environment variable is started to enter a message.
956 """
964 """
957 message = opts['message']
965 message = opts['message']
958 logfile = opts['logfile']
966 logfile = opts['logfile']
959
967
960 if message and logfile:
968 if message and logfile:
961 raise util.Abort(_('options --message and --logfile are mutually '
969 raise util.Abort(_('options --message and --logfile are mutually '
962 'exclusive'))
970 'exclusive'))
963 if not message and logfile:
971 if not message and logfile:
964 try:
972 try:
965 if logfile == '-':
973 if logfile == '-':
966 message = sys.stdin.read()
974 message = sys.stdin.read()
967 else:
975 else:
968 message = open(logfile).read()
976 message = open(logfile).read()
969 except IOError, inst:
977 except IOError, inst:
970 raise util.Abort(_("can't read commit message '%s': %s") %
978 raise util.Abort(_("can't read commit message '%s': %s") %
971 (logfile, inst.strerror))
979 (logfile, inst.strerror))
972
980
973 if opts['addremove']:
981 if opts['addremove']:
974 addremove_lock(ui, repo, pats, opts)
982 addremove_lock(ui, repo, pats, opts)
975 fns, match, anypats = matchpats(repo, pats, opts)
983 fns, match, anypats = matchpats(repo, pats, opts)
976 if pats:
984 if pats:
977 modified, added, removed, deleted, unknown = (
985 modified, added, removed, deleted, unknown = (
978 repo.changes(files=fns, match=match))
986 repo.changes(files=fns, match=match))
979 files = modified + added + removed
987 files = modified + added + removed
980 else:
988 else:
981 files = []
989 files = []
982 try:
990 try:
983 repo.commit(files, message, opts['user'], opts['date'], match)
991 repo.commit(files, message, opts['user'], opts['date'], match,
992 force_editor=opts.get('force_editor'))
984 except ValueError, inst:
993 except ValueError, inst:
985 raise util.Abort(str(inst))
994 raise util.Abort(str(inst))
986
995
987 def docopy(ui, repo, pats, opts, wlock):
996 def docopy(ui, repo, pats, opts, wlock):
988 # called with the repo lock held
997 # called with the repo lock held
989 cwd = repo.getcwd()
998 cwd = repo.getcwd()
990 errors = 0
999 errors = 0
991 copied = []
1000 copied = []
992 targets = {}
1001 targets = {}
993
1002
994 def okaytocopy(abs, rel, exact):
1003 def okaytocopy(abs, rel, exact):
995 reasons = {'?': _('is not managed'),
1004 reasons = {'?': _('is not managed'),
996 'a': _('has been marked for add'),
1005 'a': _('has been marked for add'),
997 'r': _('has been marked for remove')}
1006 'r': _('has been marked for remove')}
998 state = repo.dirstate.state(abs)
1007 state = repo.dirstate.state(abs)
999 reason = reasons.get(state)
1008 reason = reasons.get(state)
1000 if reason:
1009 if reason:
1001 if state == 'a':
1010 if state == 'a':
1002 origsrc = repo.dirstate.copied(abs)
1011 origsrc = repo.dirstate.copied(abs)
1003 if origsrc is not None:
1012 if origsrc is not None:
1004 return origsrc
1013 return origsrc
1005 if exact:
1014 if exact:
1006 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
1015 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
1007 else:
1016 else:
1008 return abs
1017 return abs
1009
1018
1010 def copy(origsrc, abssrc, relsrc, target, exact):
1019 def copy(origsrc, abssrc, relsrc, target, exact):
1011 abstarget = util.canonpath(repo.root, cwd, target)
1020 abstarget = util.canonpath(repo.root, cwd, target)
1012 reltarget = util.pathto(cwd, abstarget)
1021 reltarget = util.pathto(cwd, abstarget)
1013 prevsrc = targets.get(abstarget)
1022 prevsrc = targets.get(abstarget)
1014 if prevsrc is not None:
1023 if prevsrc is not None:
1015 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1024 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1016 (reltarget, abssrc, prevsrc))
1025 (reltarget, abssrc, prevsrc))
1017 return
1026 return
1018 if (not opts['after'] and os.path.exists(reltarget) or
1027 if (not opts['after'] and os.path.exists(reltarget) or
1019 opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
1028 opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
1020 if not opts['force']:
1029 if not opts['force']:
1021 ui.warn(_('%s: not overwriting - file exists\n') %
1030 ui.warn(_('%s: not overwriting - file exists\n') %
1022 reltarget)
1031 reltarget)
1023 return
1032 return
1024 if not opts['after']:
1033 if not opts['after']:
1025 os.unlink(reltarget)
1034 os.unlink(reltarget)
1026 if opts['after']:
1035 if opts['after']:
1027 if not os.path.exists(reltarget):
1036 if not os.path.exists(reltarget):
1028 return
1037 return
1029 else:
1038 else:
1030 targetdir = os.path.dirname(reltarget) or '.'
1039 targetdir = os.path.dirname(reltarget) or '.'
1031 if not os.path.isdir(targetdir):
1040 if not os.path.isdir(targetdir):
1032 os.makedirs(targetdir)
1041 os.makedirs(targetdir)
1033 try:
1042 try:
1034 restore = repo.dirstate.state(abstarget) == 'r'
1043 restore = repo.dirstate.state(abstarget) == 'r'
1035 if restore:
1044 if restore:
1036 repo.undelete([abstarget], wlock)
1045 repo.undelete([abstarget], wlock)
1037 try:
1046 try:
1038 shutil.copyfile(relsrc, reltarget)
1047 shutil.copyfile(relsrc, reltarget)
1039 shutil.copymode(relsrc, reltarget)
1048 shutil.copymode(relsrc, reltarget)
1040 restore = False
1049 restore = False
1041 finally:
1050 finally:
1042 if restore:
1051 if restore:
1043 repo.remove([abstarget], wlock)
1052 repo.remove([abstarget], wlock)
1044 except shutil.Error, inst:
1053 except shutil.Error, inst:
1045 raise util.Abort(str(inst))
1054 raise util.Abort(str(inst))
1046 except IOError, inst:
1055 except IOError, inst:
1047 if inst.errno == errno.ENOENT:
1056 if inst.errno == errno.ENOENT:
1048 ui.warn(_('%s: deleted in working copy\n') % relsrc)
1057 ui.warn(_('%s: deleted in working copy\n') % relsrc)
1049 else:
1058 else:
1050 ui.warn(_('%s: cannot copy - %s\n') %
1059 ui.warn(_('%s: cannot copy - %s\n') %
1051 (relsrc, inst.strerror))
1060 (relsrc, inst.strerror))
1052 errors += 1
1061 errors += 1
1053 return
1062 return
1054 if ui.verbose or not exact:
1063 if ui.verbose or not exact:
1055 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1064 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1056 targets[abstarget] = abssrc
1065 targets[abstarget] = abssrc
1057 if abstarget != origsrc:
1066 if abstarget != origsrc:
1058 repo.copy(origsrc, abstarget, wlock)
1067 repo.copy(origsrc, abstarget, wlock)
1059 copied.append((abssrc, relsrc, exact))
1068 copied.append((abssrc, relsrc, exact))
1060
1069
1061 def targetpathfn(pat, dest, srcs):
1070 def targetpathfn(pat, dest, srcs):
1062 if os.path.isdir(pat):
1071 if os.path.isdir(pat):
1063 abspfx = util.canonpath(repo.root, cwd, pat)
1072 abspfx = util.canonpath(repo.root, cwd, pat)
1064 if destdirexists:
1073 if destdirexists:
1065 striplen = len(os.path.split(abspfx)[0])
1074 striplen = len(os.path.split(abspfx)[0])
1066 else:
1075 else:
1067 striplen = len(abspfx)
1076 striplen = len(abspfx)
1068 if striplen:
1077 if striplen:
1069 striplen += len(os.sep)
1078 striplen += len(os.sep)
1070 res = lambda p: os.path.join(dest, p[striplen:])
1079 res = lambda p: os.path.join(dest, p[striplen:])
1071 elif destdirexists:
1080 elif destdirexists:
1072 res = lambda p: os.path.join(dest, os.path.basename(p))
1081 res = lambda p: os.path.join(dest, os.path.basename(p))
1073 else:
1082 else:
1074 res = lambda p: dest
1083 res = lambda p: dest
1075 return res
1084 return res
1076
1085
1077 def targetpathafterfn(pat, dest, srcs):
1086 def targetpathafterfn(pat, dest, srcs):
1078 if util.patkind(pat, None)[0]:
1087 if util.patkind(pat, None)[0]:
1079 # a mercurial pattern
1088 # a mercurial pattern
1080 res = lambda p: os.path.join(dest, os.path.basename(p))
1089 res = lambda p: os.path.join(dest, os.path.basename(p))
1081 else:
1090 else:
1082 abspfx = util.canonpath(repo.root, cwd, pat)
1091 abspfx = util.canonpath(repo.root, cwd, pat)
1083 if len(abspfx) < len(srcs[0][0]):
1092 if len(abspfx) < len(srcs[0][0]):
1084 # A directory. Either the target path contains the last
1093 # A directory. Either the target path contains the last
1085 # component of the source path or it does not.
1094 # component of the source path or it does not.
1086 def evalpath(striplen):
1095 def evalpath(striplen):
1087 score = 0
1096 score = 0
1088 for s in srcs:
1097 for s in srcs:
1089 t = os.path.join(dest, s[0][striplen:])
1098 t = os.path.join(dest, s[0][striplen:])
1090 if os.path.exists(t):
1099 if os.path.exists(t):
1091 score += 1
1100 score += 1
1092 return score
1101 return score
1093
1102
1094 striplen = len(abspfx)
1103 striplen = len(abspfx)
1095 if striplen:
1104 if striplen:
1096 striplen += len(os.sep)
1105 striplen += len(os.sep)
1097 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1106 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1098 score = evalpath(striplen)
1107 score = evalpath(striplen)
1099 striplen1 = len(os.path.split(abspfx)[0])
1108 striplen1 = len(os.path.split(abspfx)[0])
1100 if striplen1:
1109 if striplen1:
1101 striplen1 += len(os.sep)
1110 striplen1 += len(os.sep)
1102 if evalpath(striplen1) > score:
1111 if evalpath(striplen1) > score:
1103 striplen = striplen1
1112 striplen = striplen1
1104 res = lambda p: os.path.join(dest, p[striplen:])
1113 res = lambda p: os.path.join(dest, p[striplen:])
1105 else:
1114 else:
1106 # a file
1115 # a file
1107 if destdirexists:
1116 if destdirexists:
1108 res = lambda p: os.path.join(dest, os.path.basename(p))
1117 res = lambda p: os.path.join(dest, os.path.basename(p))
1109 else:
1118 else:
1110 res = lambda p: dest
1119 res = lambda p: dest
1111 return res
1120 return res
1112
1121
1113
1122
1114 pats = list(pats)
1123 pats = list(pats)
1115 if not pats:
1124 if not pats:
1116 raise util.Abort(_('no source or destination specified'))
1125 raise util.Abort(_('no source or destination specified'))
1117 if len(pats) == 1:
1126 if len(pats) == 1:
1118 raise util.Abort(_('no destination specified'))
1127 raise util.Abort(_('no destination specified'))
1119 dest = pats.pop()
1128 dest = pats.pop()
1120 destdirexists = os.path.isdir(dest)
1129 destdirexists = os.path.isdir(dest)
1121 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
1130 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
1122 raise util.Abort(_('with multiple sources, destination must be an '
1131 raise util.Abort(_('with multiple sources, destination must be an '
1123 'existing directory'))
1132 'existing directory'))
1124 if opts['after']:
1133 if opts['after']:
1125 tfn = targetpathafterfn
1134 tfn = targetpathafterfn
1126 else:
1135 else:
1127 tfn = targetpathfn
1136 tfn = targetpathfn
1128 copylist = []
1137 copylist = []
1129 for pat in pats:
1138 for pat in pats:
1130 srcs = []
1139 srcs = []
1131 for tag, abssrc, relsrc, exact in walk(repo, [pat], opts):
1140 for tag, abssrc, relsrc, exact in walk(repo, [pat], opts):
1132 origsrc = okaytocopy(abssrc, relsrc, exact)
1141 origsrc = okaytocopy(abssrc, relsrc, exact)
1133 if origsrc:
1142 if origsrc:
1134 srcs.append((origsrc, abssrc, relsrc, exact))
1143 srcs.append((origsrc, abssrc, relsrc, exact))
1135 if not srcs:
1144 if not srcs:
1136 continue
1145 continue
1137 copylist.append((tfn(pat, dest, srcs), srcs))
1146 copylist.append((tfn(pat, dest, srcs), srcs))
1138 if not copylist:
1147 if not copylist:
1139 raise util.Abort(_('no files to copy'))
1148 raise util.Abort(_('no files to copy'))
1140
1149
1141 for targetpath, srcs in copylist:
1150 for targetpath, srcs in copylist:
1142 for origsrc, abssrc, relsrc, exact in srcs:
1151 for origsrc, abssrc, relsrc, exact in srcs:
1143 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
1152 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
1144
1153
1145 if errors:
1154 if errors:
1146 ui.warn(_('(consider using --after)\n'))
1155 ui.warn(_('(consider using --after)\n'))
1147 return errors, copied
1156 return errors, copied
1148
1157
1149 def copy(ui, repo, *pats, **opts):
1158 def copy(ui, repo, *pats, **opts):
1150 """mark files as copied for the next commit
1159 """mark files as copied for the next commit
1151
1160
1152 Mark dest as having copies of source files. If dest is a
1161 Mark dest as having copies of source files. If dest is a
1153 directory, copies are put in that directory. If dest is a file,
1162 directory, copies are put in that directory. If dest is a file,
1154 there can only be one source.
1163 there can only be one source.
1155
1164
1156 By default, this command copies the contents of files as they
1165 By default, this command copies the contents of files as they
1157 stand in the working directory. If invoked with --after, the
1166 stand in the working directory. If invoked with --after, the
1158 operation is recorded, but no copying is performed.
1167 operation is recorded, but no copying is performed.
1159
1168
1160 This command takes effect in the next commit.
1169 This command takes effect in the next commit.
1161
1170
1162 NOTE: This command should be treated as experimental. While it
1171 NOTE: This command should be treated as experimental. While it
1163 should properly record copied files, this information is not yet
1172 should properly record copied files, this information is not yet
1164 fully used by merge, nor fully reported by log.
1173 fully used by merge, nor fully reported by log.
1165 """
1174 """
1166 wlock = repo.wlock(0)
1175 wlock = repo.wlock(0)
1167 errs, copied = docopy(ui, repo, pats, opts, wlock)
1176 errs, copied = docopy(ui, repo, pats, opts, wlock)
1168 return errs
1177 return errs
1169
1178
1170 def debugancestor(ui, index, rev1, rev2):
1179 def debugancestor(ui, index, rev1, rev2):
1171 """find the ancestor revision of two revisions in a given index"""
1180 """find the ancestor revision of two revisions in a given index"""
1172 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index, "", 0)
1181 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index, "", 0)
1173 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
1182 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
1174 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1183 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1175
1184
1176 def debugcomplete(ui, cmd='', **opts):
1185 def debugcomplete(ui, cmd='', **opts):
1177 """returns the completion list associated with the given command"""
1186 """returns the completion list associated with the given command"""
1178
1187
1179 if opts['options']:
1188 if opts['options']:
1180 options = []
1189 options = []
1181 otables = [globalopts]
1190 otables = [globalopts]
1182 if cmd:
1191 if cmd:
1183 aliases, entry = find(cmd)
1192 aliases, entry = find(cmd)
1184 otables.append(entry[1])
1193 otables.append(entry[1])
1185 for t in otables:
1194 for t in otables:
1186 for o in t:
1195 for o in t:
1187 if o[0]:
1196 if o[0]:
1188 options.append('-%s' % o[0])
1197 options.append('-%s' % o[0])
1189 options.append('--%s' % o[1])
1198 options.append('--%s' % o[1])
1190 ui.write("%s\n" % "\n".join(options))
1199 ui.write("%s\n" % "\n".join(options))
1191 return
1200 return
1192
1201
1193 clist = findpossible(cmd).keys()
1202 clist = findpossible(cmd).keys()
1194 clist.sort()
1203 clist.sort()
1195 ui.write("%s\n" % "\n".join(clist))
1204 ui.write("%s\n" % "\n".join(clist))
1196
1205
1197 def debugrebuildstate(ui, repo, rev=None):
1206 def debugrebuildstate(ui, repo, rev=None):
1198 """rebuild the dirstate as it would look like for the given revision"""
1207 """rebuild the dirstate as it would look like for the given revision"""
1199 if not rev:
1208 if not rev:
1200 rev = repo.changelog.tip()
1209 rev = repo.changelog.tip()
1201 else:
1210 else:
1202 rev = repo.lookup(rev)
1211 rev = repo.lookup(rev)
1203 change = repo.changelog.read(rev)
1212 change = repo.changelog.read(rev)
1204 n = change[0]
1213 n = change[0]
1205 files = repo.manifest.readflags(n)
1214 files = repo.manifest.readflags(n)
1206 wlock = repo.wlock()
1215 wlock = repo.wlock()
1207 repo.dirstate.rebuild(rev, files.iteritems())
1216 repo.dirstate.rebuild(rev, files.iteritems())
1208
1217
1209 def debugcheckstate(ui, repo):
1218 def debugcheckstate(ui, repo):
1210 """validate the correctness of the current dirstate"""
1219 """validate the correctness of the current dirstate"""
1211 parent1, parent2 = repo.dirstate.parents()
1220 parent1, parent2 = repo.dirstate.parents()
1212 repo.dirstate.read()
1221 repo.dirstate.read()
1213 dc = repo.dirstate.map
1222 dc = repo.dirstate.map
1214 keys = dc.keys()
1223 keys = dc.keys()
1215 keys.sort()
1224 keys.sort()
1216 m1n = repo.changelog.read(parent1)[0]
1225 m1n = repo.changelog.read(parent1)[0]
1217 m2n = repo.changelog.read(parent2)[0]
1226 m2n = repo.changelog.read(parent2)[0]
1218 m1 = repo.manifest.read(m1n)
1227 m1 = repo.manifest.read(m1n)
1219 m2 = repo.manifest.read(m2n)
1228 m2 = repo.manifest.read(m2n)
1220 errors = 0
1229 errors = 0
1221 for f in dc:
1230 for f in dc:
1222 state = repo.dirstate.state(f)
1231 state = repo.dirstate.state(f)
1223 if state in "nr" and f not in m1:
1232 if state in "nr" and f not in m1:
1224 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1233 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1225 errors += 1
1234 errors += 1
1226 if state in "a" and f in m1:
1235 if state in "a" and f in m1:
1227 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1236 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1228 errors += 1
1237 errors += 1
1229 if state in "m" and f not in m1 and f not in m2:
1238 if state in "m" and f not in m1 and f not in m2:
1230 ui.warn(_("%s in state %s, but not in either manifest\n") %
1239 ui.warn(_("%s in state %s, but not in either manifest\n") %
1231 (f, state))
1240 (f, state))
1232 errors += 1
1241 errors += 1
1233 for f in m1:
1242 for f in m1:
1234 state = repo.dirstate.state(f)
1243 state = repo.dirstate.state(f)
1235 if state not in "nrm":
1244 if state not in "nrm":
1236 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1245 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1237 errors += 1
1246 errors += 1
1238 if errors:
1247 if errors:
1239 error = _(".hg/dirstate inconsistent with current parent's manifest")
1248 error = _(".hg/dirstate inconsistent with current parent's manifest")
1240 raise util.Abort(error)
1249 raise util.Abort(error)
1241
1250
1242 def debugconfig(ui, repo):
1251 def debugconfig(ui, repo):
1243 """show combined config settings from all hgrc files"""
1252 """show combined config settings from all hgrc files"""
1244 for section, name, value in ui.walkconfig():
1253 for section, name, value in ui.walkconfig():
1245 ui.write('%s.%s=%s\n' % (section, name, value))
1254 ui.write('%s.%s=%s\n' % (section, name, value))
1246
1255
1247 def debugsetparents(ui, repo, rev1, rev2=None):
1256 def debugsetparents(ui, repo, rev1, rev2=None):
1248 """manually set the parents of the current working directory
1257 """manually set the parents of the current working directory
1249
1258
1250 This is useful for writing repository conversion tools, but should
1259 This is useful for writing repository conversion tools, but should
1251 be used with care.
1260 be used with care.
1252 """
1261 """
1253
1262
1254 if not rev2:
1263 if not rev2:
1255 rev2 = hex(nullid)
1264 rev2 = hex(nullid)
1256
1265
1257 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1266 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1258
1267
1259 def debugstate(ui, repo):
1268 def debugstate(ui, repo):
1260 """show the contents of the current dirstate"""
1269 """show the contents of the current dirstate"""
1261 repo.dirstate.read()
1270 repo.dirstate.read()
1262 dc = repo.dirstate.map
1271 dc = repo.dirstate.map
1263 keys = dc.keys()
1272 keys = dc.keys()
1264 keys.sort()
1273 keys.sort()
1265 for file_ in keys:
1274 for file_ in keys:
1266 ui.write("%c %3o %10d %s %s\n"
1275 ui.write("%c %3o %10d %s %s\n"
1267 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
1276 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
1268 time.strftime("%x %X",
1277 time.strftime("%x %X",
1269 time.localtime(dc[file_][3])), file_))
1278 time.localtime(dc[file_][3])), file_))
1270 for f in repo.dirstate.copies:
1279 for f in repo.dirstate.copies:
1271 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copies[f], f))
1280 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copies[f], f))
1272
1281
1273 def debugdata(ui, file_, rev):
1282 def debugdata(ui, file_, rev):
1274 """dump the contents of an data file revision"""
1283 """dump the contents of an data file revision"""
1275 r = revlog.revlog(util.opener(os.getcwd(), audit=False),
1284 r = revlog.revlog(util.opener(os.getcwd(), audit=False),
1276 file_[:-2] + ".i", file_, 0)
1285 file_[:-2] + ".i", file_, 0)
1277 try:
1286 try:
1278 ui.write(r.revision(r.lookup(rev)))
1287 ui.write(r.revision(r.lookup(rev)))
1279 except KeyError:
1288 except KeyError:
1280 raise util.Abort(_('invalid revision identifier %s'), rev)
1289 raise util.Abort(_('invalid revision identifier %s'), rev)
1281
1290
1282 def debugindex(ui, file_):
1291 def debugindex(ui, file_):
1283 """dump the contents of an index file"""
1292 """dump the contents of an index file"""
1284 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1293 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1285 ui.write(" rev offset length base linkrev" +
1294 ui.write(" rev offset length base linkrev" +
1286 " nodeid p1 p2\n")
1295 " nodeid p1 p2\n")
1287 for i in range(r.count()):
1296 for i in range(r.count()):
1288 node = r.node(i)
1297 node = r.node(i)
1289 pp = r.parents(node)
1298 pp = r.parents(node)
1290 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1299 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1291 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
1300 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
1292 short(node), short(pp[0]), short(pp[1])))
1301 short(node), short(pp[0]), short(pp[1])))
1293
1302
1294 def debugindexdot(ui, file_):
1303 def debugindexdot(ui, file_):
1295 """dump an index DAG as a .dot file"""
1304 """dump an index DAG as a .dot file"""
1296 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1305 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1297 ui.write("digraph G {\n")
1306 ui.write("digraph G {\n")
1298 for i in range(r.count()):
1307 for i in range(r.count()):
1299 e = r.index[i]
1308 node = r.node(i)
1300 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
1309 pp = r.parents(node)
1301 if e[5] != nullid:
1310 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1302 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
1311 if pp[1] != nullid:
1312 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1303 ui.write("}\n")
1313 ui.write("}\n")
1304
1314
1305 def debugrename(ui, repo, file, rev=None):
1315 def debugrename(ui, repo, file, rev=None):
1306 """dump rename information"""
1316 """dump rename information"""
1307 r = repo.file(relpath(repo, [file])[0])
1317 r = repo.file(relpath(repo, [file])[0])
1308 if rev:
1318 if rev:
1309 try:
1319 try:
1310 # assume all revision numbers are for changesets
1320 # assume all revision numbers are for changesets
1311 n = repo.lookup(rev)
1321 n = repo.lookup(rev)
1312 change = repo.changelog.read(n)
1322 change = repo.changelog.read(n)
1313 m = repo.manifest.read(change[0])
1323 m = repo.manifest.read(change[0])
1314 n = m[relpath(repo, [file])[0]]
1324 n = m[relpath(repo, [file])[0]]
1315 except (hg.RepoError, KeyError):
1325 except (hg.RepoError, KeyError):
1316 n = r.lookup(rev)
1326 n = r.lookup(rev)
1317 else:
1327 else:
1318 n = r.tip()
1328 n = r.tip()
1319 m = r.renamed(n)
1329 m = r.renamed(n)
1320 if m:
1330 if m:
1321 ui.write(_("renamed from %s:%s\n") % (m[0], hex(m[1])))
1331 ui.write(_("renamed from %s:%s\n") % (m[0], hex(m[1])))
1322 else:
1332 else:
1323 ui.write(_("not renamed\n"))
1333 ui.write(_("not renamed\n"))
1324
1334
1325 def debugwalk(ui, repo, *pats, **opts):
1335 def debugwalk(ui, repo, *pats, **opts):
1326 """show how files match on given patterns"""
1336 """show how files match on given patterns"""
1327 items = list(walk(repo, pats, opts))
1337 items = list(walk(repo, pats, opts))
1328 if not items:
1338 if not items:
1329 return
1339 return
1330 fmt = '%%s %%-%ds %%-%ds %%s' % (
1340 fmt = '%%s %%-%ds %%-%ds %%s' % (
1331 max([len(abs) for (src, abs, rel, exact) in items]),
1341 max([len(abs) for (src, abs, rel, exact) in items]),
1332 max([len(rel) for (src, abs, rel, exact) in items]))
1342 max([len(rel) for (src, abs, rel, exact) in items]))
1333 for src, abs, rel, exact in items:
1343 for src, abs, rel, exact in items:
1334 line = fmt % (src, abs, rel, exact and 'exact' or '')
1344 line = fmt % (src, abs, rel, exact and 'exact' or '')
1335 ui.write("%s\n" % line.rstrip())
1345 ui.write("%s\n" % line.rstrip())
1336
1346
1337 def diff(ui, repo, *pats, **opts):
1347 def diff(ui, repo, *pats, **opts):
1338 """diff repository (or selected files)
1348 """diff repository (or selected files)
1339
1349
1340 Show differences between revisions for the specified files.
1350 Show differences between revisions for the specified files.
1341
1351
1342 Differences between files are shown using the unified diff format.
1352 Differences between files are shown using the unified diff format.
1343
1353
1344 When two revision arguments are given, then changes are shown
1354 When two revision arguments are given, then changes are shown
1345 between those revisions. If only one revision is specified then
1355 between those revisions. If only one revision is specified then
1346 that revision is compared to the working directory, and, when no
1356 that revision is compared to the working directory, and, when no
1347 revisions are specified, the working directory files are compared
1357 revisions are specified, the working directory files are compared
1348 to its parent.
1358 to its parent.
1349
1359
1350 Without the -a option, diff will avoid generating diffs of files
1360 Without the -a option, diff will avoid generating diffs of files
1351 it detects as binary. With -a, diff will generate a diff anyway,
1361 it detects as binary. With -a, diff will generate a diff anyway,
1352 probably with undesirable results.
1362 probably with undesirable results.
1353 """
1363 """
1354 node1, node2 = None, None
1364 node1, node2 = None, None
1355 revs = [repo.lookup(x) for x in opts['rev']]
1365 revs = [repo.lookup(x) for x in opts['rev']]
1356
1366
1357 if len(revs) > 0:
1367 if len(revs) > 0:
1358 node1 = revs[0]
1368 node1 = revs[0]
1359 if len(revs) > 1:
1369 if len(revs) > 1:
1360 node2 = revs[1]
1370 node2 = revs[1]
1361 if len(revs) > 2:
1371 if len(revs) > 2:
1362 raise util.Abort(_("too many revisions to diff"))
1372 raise util.Abort(_("too many revisions to diff"))
1363
1373
1364 fns, matchfn, anypats = matchpats(repo, pats, opts)
1374 fns, matchfn, anypats = matchpats(repo, pats, opts)
1365
1375
1366 dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn,
1376 dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn,
1367 text=opts['text'], opts=opts)
1377 text=opts['text'], opts=opts)
1368
1378
1369 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
1379 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
1370 node = repo.lookup(changeset)
1380 node = repo.lookup(changeset)
1371 parents = [p for p in repo.changelog.parents(node) if p != nullid]
1381 parents = [p for p in repo.changelog.parents(node) if p != nullid]
1372 if opts['switch_parent']:
1382 if opts['switch_parent']:
1373 parents.reverse()
1383 parents.reverse()
1374 prev = (parents and parents[0]) or nullid
1384 prev = (parents and parents[0]) or nullid
1375 change = repo.changelog.read(node)
1385 change = repo.changelog.read(node)
1376
1386
1377 fp = make_file(repo, repo.changelog, opts['output'],
1387 fp = make_file(repo, repo.changelog, opts['output'],
1378 node=node, total=total, seqno=seqno,
1388 node=node, total=total, seqno=seqno,
1379 revwidth=revwidth)
1389 revwidth=revwidth)
1380 if fp != sys.stdout:
1390 if fp != sys.stdout:
1381 ui.note("%s\n" % fp.name)
1391 ui.note("%s\n" % fp.name)
1382
1392
1383 fp.write("# HG changeset patch\n")
1393 fp.write("# HG changeset patch\n")
1384 fp.write("# User %s\n" % change[1])
1394 fp.write("# User %s\n" % change[1])
1385 fp.write("# Node ID %s\n" % hex(node))
1395 fp.write("# Node ID %s\n" % hex(node))
1386 fp.write("# Parent %s\n" % hex(prev))
1396 fp.write("# Parent %s\n" % hex(prev))
1387 if len(parents) > 1:
1397 if len(parents) > 1:
1388 fp.write("# Parent %s\n" % hex(parents[1]))
1398 fp.write("# Parent %s\n" % hex(parents[1]))
1389 fp.write(change[4].rstrip())
1399 fp.write(change[4].rstrip())
1390 fp.write("\n\n")
1400 fp.write("\n\n")
1391
1401
1392 dodiff(fp, ui, repo, prev, node, text=opts['text'])
1402 dodiff(fp, ui, repo, prev, node, text=opts['text'])
1393 if fp != sys.stdout:
1403 if fp != sys.stdout:
1394 fp.close()
1404 fp.close()
1395
1405
1396 def export(ui, repo, *changesets, **opts):
1406 def export(ui, repo, *changesets, **opts):
1397 """dump the header and diffs for one or more changesets
1407 """dump the header and diffs for one or more changesets
1398
1408
1399 Print the changeset header and diffs for one or more revisions.
1409 Print the changeset header and diffs for one or more revisions.
1400
1410
1401 The information shown in the changeset header is: author,
1411 The information shown in the changeset header is: author,
1402 changeset hash, parent and commit comment.
1412 changeset hash, parent and commit comment.
1403
1413
1404 Output may be to a file, in which case the name of the file is
1414 Output may be to a file, in which case the name of the file is
1405 given using a format string. The formatting rules are as follows:
1415 given using a format string. The formatting rules are as follows:
1406
1416
1407 %% literal "%" character
1417 %% literal "%" character
1408 %H changeset hash (40 bytes of hexadecimal)
1418 %H changeset hash (40 bytes of hexadecimal)
1409 %N number of patches being generated
1419 %N number of patches being generated
1410 %R changeset revision number
1420 %R changeset revision number
1411 %b basename of the exporting repository
1421 %b basename of the exporting repository
1412 %h short-form changeset hash (12 bytes of hexadecimal)
1422 %h short-form changeset hash (12 bytes of hexadecimal)
1413 %n zero-padded sequence number, starting at 1
1423 %n zero-padded sequence number, starting at 1
1414 %r zero-padded changeset revision number
1424 %r zero-padded changeset revision number
1415
1425
1416 Without the -a option, export will avoid generating diffs of files
1426 Without the -a option, export will avoid generating diffs of files
1417 it detects as binary. With -a, export will generate a diff anyway,
1427 it detects as binary. With -a, export will generate a diff anyway,
1418 probably with undesirable results.
1428 probably with undesirable results.
1419
1429
1420 With the --switch-parent option, the diff will be against the second
1430 With the --switch-parent option, the diff will be against the second
1421 parent. It can be useful to review a merge.
1431 parent. It can be useful to review a merge.
1422 """
1432 """
1423 if not changesets:
1433 if not changesets:
1424 raise util.Abort(_("export requires at least one changeset"))
1434 raise util.Abort(_("export requires at least one changeset"))
1425 seqno = 0
1435 seqno = 0
1426 revs = list(revrange(ui, repo, changesets))
1436 revs = list(revrange(ui, repo, changesets))
1427 total = len(revs)
1437 total = len(revs)
1428 revwidth = max(map(len, revs))
1438 revwidth = max(map(len, revs))
1429 msg = len(revs) > 1 and _("Exporting patches:\n") or _("Exporting patch:\n")
1439 msg = len(revs) > 1 and _("Exporting patches:\n") or _("Exporting patch:\n")
1430 ui.note(msg)
1440 ui.note(msg)
1431 for cset in revs:
1441 for cset in revs:
1432 seqno += 1
1442 seqno += 1
1433 doexport(ui, repo, cset, seqno, total, revwidth, opts)
1443 doexport(ui, repo, cset, seqno, total, revwidth, opts)
1434
1444
1435 def forget(ui, repo, *pats, **opts):
1445 def forget(ui, repo, *pats, **opts):
1436 """don't add the specified files on the next commit (DEPRECATED)
1446 """don't add the specified files on the next commit (DEPRECATED)
1437
1447
1438 (DEPRECATED)
1448 (DEPRECATED)
1439 Undo an 'hg add' scheduled for the next commit.
1449 Undo an 'hg add' scheduled for the next commit.
1440
1450
1441 This command is now deprecated and will be removed in a future
1451 This command is now deprecated and will be removed in a future
1442 release. Please use revert instead.
1452 release. Please use revert instead.
1443 """
1453 """
1444 ui.warn(_("(the forget command is deprecated; use revert instead)\n"))
1454 ui.warn(_("(the forget command is deprecated; use revert instead)\n"))
1445 forget = []
1455 forget = []
1446 for src, abs, rel, exact in walk(repo, pats, opts):
1456 for src, abs, rel, exact in walk(repo, pats, opts):
1447 if repo.dirstate.state(abs) == 'a':
1457 if repo.dirstate.state(abs) == 'a':
1448 forget.append(abs)
1458 forget.append(abs)
1449 if ui.verbose or not exact:
1459 if ui.verbose or not exact:
1450 ui.status(_('forgetting %s\n') % ((pats and rel) or abs))
1460 ui.status(_('forgetting %s\n') % ((pats and rel) or abs))
1451 repo.forget(forget)
1461 repo.forget(forget)
1452
1462
1453 def grep(ui, repo, pattern, *pats, **opts):
1463 def grep(ui, repo, pattern, *pats, **opts):
1454 """search for a pattern in specified files and revisions
1464 """search for a pattern in specified files and revisions
1455
1465
1456 Search revisions of files for a regular expression.
1466 Search revisions of files for a regular expression.
1457
1467
1458 This command behaves differently than Unix grep. It only accepts
1468 This command behaves differently than Unix grep. It only accepts
1459 Python/Perl regexps. It searches repository history, not the
1469 Python/Perl regexps. It searches repository history, not the
1460 working directory. It always prints the revision number in which
1470 working directory. It always prints the revision number in which
1461 a match appears.
1471 a match appears.
1462
1472
1463 By default, grep only prints output for the first revision of a
1473 By default, grep only prints output for the first revision of a
1464 file in which it finds a match. To get it to print every revision
1474 file in which it finds a match. To get it to print every revision
1465 that contains a change in match status ("-" for a match that
1475 that contains a change in match status ("-" for a match that
1466 becomes a non-match, or "+" for a non-match that becomes a match),
1476 becomes a non-match, or "+" for a non-match that becomes a match),
1467 use the --all flag.
1477 use the --all flag.
1468 """
1478 """
1469 reflags = 0
1479 reflags = 0
1470 if opts['ignore_case']:
1480 if opts['ignore_case']:
1471 reflags |= re.I
1481 reflags |= re.I
1472 regexp = re.compile(pattern, reflags)
1482 regexp = re.compile(pattern, reflags)
1473 sep, eol = ':', '\n'
1483 sep, eol = ':', '\n'
1474 if opts['print0']:
1484 if opts['print0']:
1475 sep = eol = '\0'
1485 sep = eol = '\0'
1476
1486
1477 fcache = {}
1487 fcache = {}
1478 def getfile(fn):
1488 def getfile(fn):
1479 if fn not in fcache:
1489 if fn not in fcache:
1480 fcache[fn] = repo.file(fn)
1490 fcache[fn] = repo.file(fn)
1481 return fcache[fn]
1491 return fcache[fn]
1482
1492
1483 def matchlines(body):
1493 def matchlines(body):
1484 begin = 0
1494 begin = 0
1485 linenum = 0
1495 linenum = 0
1486 while True:
1496 while True:
1487 match = regexp.search(body, begin)
1497 match = regexp.search(body, begin)
1488 if not match:
1498 if not match:
1489 break
1499 break
1490 mstart, mend = match.span()
1500 mstart, mend = match.span()
1491 linenum += body.count('\n', begin, mstart) + 1
1501 linenum += body.count('\n', begin, mstart) + 1
1492 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1502 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1493 lend = body.find('\n', mend)
1503 lend = body.find('\n', mend)
1494 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1504 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1495 begin = lend + 1
1505 begin = lend + 1
1496
1506
1497 class linestate(object):
1507 class linestate(object):
1498 def __init__(self, line, linenum, colstart, colend):
1508 def __init__(self, line, linenum, colstart, colend):
1499 self.line = line
1509 self.line = line
1500 self.linenum = linenum
1510 self.linenum = linenum
1501 self.colstart = colstart
1511 self.colstart = colstart
1502 self.colend = colend
1512 self.colend = colend
1503 def __eq__(self, other):
1513 def __eq__(self, other):
1504 return self.line == other.line
1514 return self.line == other.line
1505 def __hash__(self):
1515 def __hash__(self):
1506 return hash(self.line)
1516 return hash(self.line)
1507
1517
1508 matches = {}
1518 matches = {}
1509 def grepbody(fn, rev, body):
1519 def grepbody(fn, rev, body):
1510 matches[rev].setdefault(fn, {})
1520 matches[rev].setdefault(fn, {})
1511 m = matches[rev][fn]
1521 m = matches[rev][fn]
1512 for lnum, cstart, cend, line in matchlines(body):
1522 for lnum, cstart, cend, line in matchlines(body):
1513 s = linestate(line, lnum, cstart, cend)
1523 s = linestate(line, lnum, cstart, cend)
1514 m[s] = s
1524 m[s] = s
1515
1525
1516 # FIXME: prev isn't used, why ?
1526 # FIXME: prev isn't used, why ?
1517 prev = {}
1527 prev = {}
1518 ucache = {}
1528 ucache = {}
1519 def display(fn, rev, states, prevstates):
1529 def display(fn, rev, states, prevstates):
1520 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
1530 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
1521 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
1531 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
1522 counts = {'-': 0, '+': 0}
1532 counts = {'-': 0, '+': 0}
1523 filerevmatches = {}
1533 filerevmatches = {}
1524 for l in diff:
1534 for l in diff:
1525 if incrementing or not opts['all']:
1535 if incrementing or not opts['all']:
1526 change = ((l in prevstates) and '-') or '+'
1536 change = ((l in prevstates) and '-') or '+'
1527 r = rev
1537 r = rev
1528 else:
1538 else:
1529 change = ((l in states) and '-') or '+'
1539 change = ((l in states) and '-') or '+'
1530 r = prev[fn]
1540 r = prev[fn]
1531 cols = [fn, str(rev)]
1541 cols = [fn, str(rev)]
1532 if opts['line_number']:
1542 if opts['line_number']:
1533 cols.append(str(l.linenum))
1543 cols.append(str(l.linenum))
1534 if opts['all']:
1544 if opts['all']:
1535 cols.append(change)
1545 cols.append(change)
1536 if opts['user']:
1546 if opts['user']:
1537 cols.append(trimuser(ui, getchange(rev)[1], rev,
1547 cols.append(trimuser(ui, getchange(rev)[1], rev,
1538 ucache))
1548 ucache))
1539 if opts['files_with_matches']:
1549 if opts['files_with_matches']:
1540 c = (fn, rev)
1550 c = (fn, rev)
1541 if c in filerevmatches:
1551 if c in filerevmatches:
1542 continue
1552 continue
1543 filerevmatches[c] = 1
1553 filerevmatches[c] = 1
1544 else:
1554 else:
1545 cols.append(l.line)
1555 cols.append(l.line)
1546 ui.write(sep.join(cols), eol)
1556 ui.write(sep.join(cols), eol)
1547 counts[change] += 1
1557 counts[change] += 1
1548 return counts['+'], counts['-']
1558 return counts['+'], counts['-']
1549
1559
1550 fstate = {}
1560 fstate = {}
1551 skip = {}
1561 skip = {}
1552 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1562 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1553 count = 0
1563 count = 0
1554 incrementing = False
1564 incrementing = False
1555 for st, rev, fns in changeiter:
1565 for st, rev, fns in changeiter:
1556 if st == 'window':
1566 if st == 'window':
1557 incrementing = rev
1567 incrementing = rev
1558 matches.clear()
1568 matches.clear()
1559 elif st == 'add':
1569 elif st == 'add':
1560 change = repo.changelog.read(repo.lookup(str(rev)))
1570 change = repo.changelog.read(repo.lookup(str(rev)))
1561 mf = repo.manifest.read(change[0])
1571 mf = repo.manifest.read(change[0])
1562 matches[rev] = {}
1572 matches[rev] = {}
1563 for fn in fns:
1573 for fn in fns:
1564 if fn in skip:
1574 if fn in skip:
1565 continue
1575 continue
1566 fstate.setdefault(fn, {})
1576 fstate.setdefault(fn, {})
1567 try:
1577 try:
1568 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1578 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1569 except KeyError:
1579 except KeyError:
1570 pass
1580 pass
1571 elif st == 'iter':
1581 elif st == 'iter':
1572 states = matches[rev].items()
1582 states = matches[rev].items()
1573 states.sort()
1583 states.sort()
1574 for fn, m in states:
1584 for fn, m in states:
1575 if fn in skip:
1585 if fn in skip:
1576 continue
1586 continue
1577 if incrementing or not opts['all'] or fstate[fn]:
1587 if incrementing or not opts['all'] or fstate[fn]:
1578 pos, neg = display(fn, rev, m, fstate[fn])
1588 pos, neg = display(fn, rev, m, fstate[fn])
1579 count += pos + neg
1589 count += pos + neg
1580 if pos and not opts['all']:
1590 if pos and not opts['all']:
1581 skip[fn] = True
1591 skip[fn] = True
1582 fstate[fn] = m
1592 fstate[fn] = m
1583 prev[fn] = rev
1593 prev[fn] = rev
1584
1594
1585 if not incrementing:
1595 if not incrementing:
1586 fstate = fstate.items()
1596 fstate = fstate.items()
1587 fstate.sort()
1597 fstate.sort()
1588 for fn, state in fstate:
1598 for fn, state in fstate:
1589 if fn in skip:
1599 if fn in skip:
1590 continue
1600 continue
1591 display(fn, rev, {}, state)
1601 display(fn, rev, {}, state)
1592 return (count == 0 and 1) or 0
1602 return (count == 0 and 1) or 0
1593
1603
1594 def heads(ui, repo, **opts):
1604 def heads(ui, repo, **opts):
1595 """show current repository heads
1605 """show current repository heads
1596
1606
1597 Show all repository head changesets.
1607 Show all repository head changesets.
1598
1608
1599 Repository "heads" are changesets that don't have children
1609 Repository "heads" are changesets that don't have children
1600 changesets. They are where development generally takes place and
1610 changesets. They are where development generally takes place and
1601 are the usual targets for update and merge operations.
1611 are the usual targets for update and merge operations.
1602 """
1612 """
1603 if opts['rev']:
1613 if opts['rev']:
1604 heads = repo.heads(repo.lookup(opts['rev']))
1614 heads = repo.heads(repo.lookup(opts['rev']))
1605 else:
1615 else:
1606 heads = repo.heads()
1616 heads = repo.heads()
1607 br = None
1617 br = None
1608 if opts['branches']:
1618 if opts['branches']:
1609 br = repo.branchlookup(heads)
1619 br = repo.branchlookup(heads)
1610 displayer = show_changeset(ui, repo, opts)
1620 displayer = show_changeset(ui, repo, opts)
1611 for n in heads:
1621 for n in heads:
1612 displayer.show(changenode=n, brinfo=br)
1622 displayer.show(changenode=n, brinfo=br)
1613
1623
1614 def identify(ui, repo):
1624 def identify(ui, repo):
1615 """print information about the working copy
1625 """print information about the working copy
1616
1626
1617 Print a short summary of the current state of the repo.
1627 Print a short summary of the current state of the repo.
1618
1628
1619 This summary identifies the repository state using one or two parent
1629 This summary identifies the repository state using one or two parent
1620 hash identifiers, followed by a "+" if there are uncommitted changes
1630 hash identifiers, followed by a "+" if there are uncommitted changes
1621 in the working directory, followed by a list of tags for this revision.
1631 in the working directory, followed by a list of tags for this revision.
1622 """
1632 """
1623 parents = [p for p in repo.dirstate.parents() if p != nullid]
1633 parents = [p for p in repo.dirstate.parents() if p != nullid]
1624 if not parents:
1634 if not parents:
1625 ui.write(_("unknown\n"))
1635 ui.write(_("unknown\n"))
1626 return
1636 return
1627
1637
1628 hexfunc = ui.verbose and hex or short
1638 hexfunc = ui.verbose and hex or short
1629 modified, added, removed, deleted, unknown = repo.changes()
1639 modified, added, removed, deleted, unknown = repo.changes()
1630 output = ["%s%s" %
1640 output = ["%s%s" %
1631 ('+'.join([hexfunc(parent) for parent in parents]),
1641 ('+'.join([hexfunc(parent) for parent in parents]),
1632 (modified or added or removed or deleted) and "+" or "")]
1642 (modified or added or removed or deleted) and "+" or "")]
1633
1643
1634 if not ui.quiet:
1644 if not ui.quiet:
1635 # multiple tags for a single parent separated by '/'
1645 # multiple tags for a single parent separated by '/'
1636 parenttags = ['/'.join(tags)
1646 parenttags = ['/'.join(tags)
1637 for tags in map(repo.nodetags, parents) if tags]
1647 for tags in map(repo.nodetags, parents) if tags]
1638 # tags for multiple parents separated by ' + '
1648 # tags for multiple parents separated by ' + '
1639 if parenttags:
1649 if parenttags:
1640 output.append(' + '.join(parenttags))
1650 output.append(' + '.join(parenttags))
1641
1651
1642 ui.write("%s\n" % ' '.join(output))
1652 ui.write("%s\n" % ' '.join(output))
1643
1653
1644 def import_(ui, repo, patch1, *patches, **opts):
1654 def import_(ui, repo, patch1, *patches, **opts):
1645 """import an ordered set of patches
1655 """import an ordered set of patches
1646
1656
1647 Import a list of patches and commit them individually.
1657 Import a list of patches and commit them individually.
1648
1658
1649 If there are outstanding changes in the working directory, import
1659 If there are outstanding changes in the working directory, import
1650 will abort unless given the -f flag.
1660 will abort unless given the -f flag.
1651
1661
1652 If a patch looks like a mail message (its first line starts with
1662 If a patch looks like a mail message (its first line starts with
1653 "From " or looks like an RFC822 header), it will not be applied
1663 "From " or looks like an RFC822 header), it will not be applied
1654 unless the -f option is used. The importer neither parses nor
1664 unless the -f option is used. The importer neither parses nor
1655 discards mail headers, so use -f only to override the "mailness"
1665 discards mail headers, so use -f only to override the "mailness"
1656 safety check, not to import a real mail message.
1666 safety check, not to import a real mail message.
1657 """
1667 """
1658 patches = (patch1,) + patches
1668 patches = (patch1,) + patches
1659
1669
1660 if not opts['force']:
1670 if not opts['force']:
1661 bail_if_changed(repo)
1671 bail_if_changed(repo)
1662
1672
1663 d = opts["base"]
1673 d = opts["base"]
1664 strip = opts["strip"]
1674 strip = opts["strip"]
1665
1675
1666 mailre = re.compile(r'(?:From |[\w-]+:)')
1676 mailre = re.compile(r'(?:From |[\w-]+:)')
1667
1677
1668 # attempt to detect the start of a patch
1678 # attempt to detect the start of a patch
1669 # (this heuristic is borrowed from quilt)
1679 # (this heuristic is borrowed from quilt)
1670 diffre = re.compile(r'(?:Index:[ \t]|diff[ \t]|RCS file: |' +
1680 diffre = re.compile(r'(?:Index:[ \t]|diff[ \t]|RCS file: |' +
1671 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
1681 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
1672 '(---|\*\*\*)[ \t])')
1682 '(---|\*\*\*)[ \t])')
1673
1683
1674 for patch in patches:
1684 for patch in patches:
1675 ui.status(_("applying %s\n") % patch)
1685 ui.status(_("applying %s\n") % patch)
1676 pf = os.path.join(d, patch)
1686 pf = os.path.join(d, patch)
1677
1687
1678 message = []
1688 message = []
1679 user = None
1689 user = None
1680 hgpatch = False
1690 hgpatch = False
1681 for line in file(pf):
1691 for line in file(pf):
1682 line = line.rstrip()
1692 line = line.rstrip()
1683 if (not message and not hgpatch and
1693 if (not message and not hgpatch and
1684 mailre.match(line) and not opts['force']):
1694 mailre.match(line) and not opts['force']):
1685 if len(line) > 35:
1695 if len(line) > 35:
1686 line = line[:32] + '...'
1696 line = line[:32] + '...'
1687 raise util.Abort(_('first line looks like a '
1697 raise util.Abort(_('first line looks like a '
1688 'mail header: ') + line)
1698 'mail header: ') + line)
1689 if diffre.match(line):
1699 if diffre.match(line):
1690 break
1700 break
1691 elif hgpatch:
1701 elif hgpatch:
1692 # parse values when importing the result of an hg export
1702 # parse values when importing the result of an hg export
1693 if line.startswith("# User "):
1703 if line.startswith("# User "):
1694 user = line[7:]
1704 user = line[7:]
1695 ui.debug(_('User: %s\n') % user)
1705 ui.debug(_('User: %s\n') % user)
1696 elif not line.startswith("# ") and line:
1706 elif not line.startswith("# ") and line:
1697 message.append(line)
1707 message.append(line)
1698 hgpatch = False
1708 hgpatch = False
1699 elif line == '# HG changeset patch':
1709 elif line == '# HG changeset patch':
1700 hgpatch = True
1710 hgpatch = True
1701 message = [] # We may have collected garbage
1711 message = [] # We may have collected garbage
1702 else:
1712 else:
1703 message.append(line)
1713 message.append(line)
1704
1714
1705 # make sure message isn't empty
1715 # make sure message isn't empty
1706 if not message:
1716 if not message:
1707 message = _("imported patch %s\n") % patch
1717 message = _("imported patch %s\n") % patch
1708 else:
1718 else:
1709 message = "%s\n" % '\n'.join(message)
1719 message = "%s\n" % '\n'.join(message)
1710 ui.debug(_('message:\n%s\n') % message)
1720 ui.debug(_('message:\n%s\n') % message)
1711
1721
1712 files = util.patch(strip, pf, ui)
1722 files = util.patch(strip, pf, ui)
1713
1723
1714 if len(files) > 0:
1724 if len(files) > 0:
1715 addremove_lock(ui, repo, files, {})
1725 addremove_lock(ui, repo, files, {})
1716 repo.commit(files, message, user)
1726 repo.commit(files, message, user)
1717
1727
1718 def incoming(ui, repo, source="default", **opts):
1728 def incoming(ui, repo, source="default", **opts):
1719 """show new changesets found in source
1729 """show new changesets found in source
1720
1730
1721 Show new changesets found in the specified path/URL or the default
1731 Show new changesets found in the specified path/URL or the default
1722 pull location. These are the changesets that would be pulled if a pull
1732 pull location. These are the changesets that would be pulled if a pull
1723 was requested.
1733 was requested.
1724
1734
1725 For remote repository, using --bundle avoids downloading the changesets
1735 For remote repository, using --bundle avoids downloading the changesets
1726 twice if the incoming is followed by a pull.
1736 twice if the incoming is followed by a pull.
1727
1737
1728 See pull for valid source format details.
1738 See pull for valid source format details.
1729 """
1739 """
1730 source = ui.expandpath(source)
1740 source = ui.expandpath(source)
1731 if opts['ssh']:
1741 if opts['ssh']:
1732 ui.setconfig("ui", "ssh", opts['ssh'])
1742 ui.setconfig("ui", "ssh", opts['ssh'])
1733 if opts['remotecmd']:
1743 if opts['remotecmd']:
1734 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1744 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1735
1745
1736 other = hg.repository(ui, source)
1746 other = hg.repository(ui, source)
1737 incoming = repo.findincoming(other, force=opts["force"])
1747 incoming = repo.findincoming(other, force=opts["force"])
1738 if not incoming:
1748 if not incoming:
1739 ui.status(_("no changes found\n"))
1749 ui.status(_("no changes found\n"))
1740 return
1750 return
1741
1751
1742 cleanup = None
1752 cleanup = None
1743 try:
1753 try:
1744 fname = opts["bundle"]
1754 fname = opts["bundle"]
1745 if fname or not other.local():
1755 if fname or not other.local():
1746 # create a bundle (uncompressed if other repo is not local)
1756 # create a bundle (uncompressed if other repo is not local)
1747 cg = other.changegroup(incoming, "incoming")
1757 cg = other.changegroup(incoming, "incoming")
1748 fname = cleanup = write_bundle(cg, fname, compress=other.local())
1758 fname = cleanup = write_bundle(cg, fname, compress=other.local())
1749 # keep written bundle?
1759 # keep written bundle?
1750 if opts["bundle"]:
1760 if opts["bundle"]:
1751 cleanup = None
1761 cleanup = None
1752 if not other.local():
1762 if not other.local():
1753 # use the created uncompressed bundlerepo
1763 # use the created uncompressed bundlerepo
1754 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1764 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1755
1765
1756 o = other.changelog.nodesbetween(incoming)[0]
1766 o = other.changelog.nodesbetween(incoming)[0]
1757 if opts['newest_first']:
1767 if opts['newest_first']:
1758 o.reverse()
1768 o.reverse()
1759 displayer = show_changeset(ui, other, opts)
1769 displayer = show_changeset(ui, other, opts)
1760 for n in o:
1770 for n in o:
1761 parents = [p for p in other.changelog.parents(n) if p != nullid]
1771 parents = [p for p in other.changelog.parents(n) if p != nullid]
1762 if opts['no_merges'] and len(parents) == 2:
1772 if opts['no_merges'] and len(parents) == 2:
1763 continue
1773 continue
1764 displayer.show(changenode=n)
1774 displayer.show(changenode=n)
1765 if opts['patch']:
1775 if opts['patch']:
1766 prev = (parents and parents[0]) or nullid
1776 prev = (parents and parents[0]) or nullid
1767 dodiff(ui, ui, other, prev, n)
1777 dodiff(ui, ui, other, prev, n)
1768 ui.write("\n")
1778 ui.write("\n")
1769 finally:
1779 finally:
1770 if hasattr(other, 'close'):
1780 if hasattr(other, 'close'):
1771 other.close()
1781 other.close()
1772 if cleanup:
1782 if cleanup:
1773 os.unlink(cleanup)
1783 os.unlink(cleanup)
1774
1784
1775 def init(ui, dest="."):
1785 def init(ui, dest="."):
1776 """create a new repository in the given directory
1786 """create a new repository in the given directory
1777
1787
1778 Initialize a new repository in the given directory. If the given
1788 Initialize a new repository in the given directory. If the given
1779 directory does not exist, it is created.
1789 directory does not exist, it is created.
1780
1790
1781 If no directory is given, the current directory is used.
1791 If no directory is given, the current directory is used.
1782 """
1792 """
1783 if not os.path.exists(dest):
1793 if not os.path.exists(dest):
1784 os.mkdir(dest)
1794 os.mkdir(dest)
1785 hg.repository(ui, dest, create=1)
1795 hg.repository(ui, dest, create=1)
1786
1796
1787 def locate(ui, repo, *pats, **opts):
1797 def locate(ui, repo, *pats, **opts):
1788 """locate files matching specific patterns
1798 """locate files matching specific patterns
1789
1799
1790 Print all files under Mercurial control whose names match the
1800 Print all files under Mercurial control whose names match the
1791 given patterns.
1801 given patterns.
1792
1802
1793 This command searches the current directory and its
1803 This command searches the current directory and its
1794 subdirectories. To search an entire repository, move to the root
1804 subdirectories. To search an entire repository, move to the root
1795 of the repository.
1805 of the repository.
1796
1806
1797 If no patterns are given to match, this command prints all file
1807 If no patterns are given to match, this command prints all file
1798 names.
1808 names.
1799
1809
1800 If you want to feed the output of this command into the "xargs"
1810 If you want to feed the output of this command into the "xargs"
1801 command, use the "-0" option to both this command and "xargs".
1811 command, use the "-0" option to both this command and "xargs".
1802 This will avoid the problem of "xargs" treating single filenames
1812 This will avoid the problem of "xargs" treating single filenames
1803 that contain white space as multiple filenames.
1813 that contain white space as multiple filenames.
1804 """
1814 """
1805 end = opts['print0'] and '\0' or '\n'
1815 end = opts['print0'] and '\0' or '\n'
1806 rev = opts['rev']
1816 rev = opts['rev']
1807 if rev:
1817 if rev:
1808 node = repo.lookup(rev)
1818 node = repo.lookup(rev)
1809 else:
1819 else:
1810 node = None
1820 node = None
1811
1821
1812 for src, abs, rel, exact in walk(repo, pats, opts, node=node,
1822 for src, abs, rel, exact in walk(repo, pats, opts, node=node,
1813 head='(?:.*/|)'):
1823 head='(?:.*/|)'):
1814 if not node and repo.dirstate.state(abs) == '?':
1824 if not node and repo.dirstate.state(abs) == '?':
1815 continue
1825 continue
1816 if opts['fullpath']:
1826 if opts['fullpath']:
1817 ui.write(os.path.join(repo.root, abs), end)
1827 ui.write(os.path.join(repo.root, abs), end)
1818 else:
1828 else:
1819 ui.write(((pats and rel) or abs), end)
1829 ui.write(((pats and rel) or abs), end)
1820
1830
1821 def log(ui, repo, *pats, **opts):
1831 def log(ui, repo, *pats, **opts):
1822 """show revision history of entire repository or files
1832 """show revision history of entire repository or files
1823
1833
1824 Print the revision history of the specified files or the entire project.
1834 Print the revision history of the specified files or the entire project.
1825
1835
1826 By default this command outputs: changeset id and hash, tags,
1836 By default this command outputs: changeset id and hash, tags,
1827 non-trivial parents, user, date and time, and a summary for each
1837 non-trivial parents, user, date and time, and a summary for each
1828 commit. When the -v/--verbose switch is used, the list of changed
1838 commit. When the -v/--verbose switch is used, the list of changed
1829 files and full commit message is shown.
1839 files and full commit message is shown.
1830 """
1840 """
1831 class dui(object):
1841 class dui(object):
1832 # Implement and delegate some ui protocol. Save hunks of
1842 # Implement and delegate some ui protocol. Save hunks of
1833 # output for later display in the desired order.
1843 # output for later display in the desired order.
1834 def __init__(self, ui):
1844 def __init__(self, ui):
1835 self.ui = ui
1845 self.ui = ui
1836 self.hunk = {}
1846 self.hunk = {}
1837 self.header = {}
1847 self.header = {}
1838 def bump(self, rev):
1848 def bump(self, rev):
1839 self.rev = rev
1849 self.rev = rev
1840 self.hunk[rev] = []
1850 self.hunk[rev] = []
1841 self.header[rev] = []
1851 self.header[rev] = []
1842 def note(self, *args):
1852 def note(self, *args):
1843 if self.verbose:
1853 if self.verbose:
1844 self.write(*args)
1854 self.write(*args)
1845 def status(self, *args):
1855 def status(self, *args):
1846 if not self.quiet:
1856 if not self.quiet:
1847 self.write(*args)
1857 self.write(*args)
1848 def write(self, *args):
1858 def write(self, *args):
1849 self.hunk[self.rev].append(args)
1859 self.hunk[self.rev].append(args)
1850 def write_header(self, *args):
1860 def write_header(self, *args):
1851 self.header[self.rev].append(args)
1861 self.header[self.rev].append(args)
1852 def debug(self, *args):
1862 def debug(self, *args):
1853 if self.debugflag:
1863 if self.debugflag:
1854 self.write(*args)
1864 self.write(*args)
1855 def __getattr__(self, key):
1865 def __getattr__(self, key):
1856 return getattr(self.ui, key)
1866 return getattr(self.ui, key)
1857
1867
1858 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1868 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1859
1869
1860 if opts['limit']:
1870 if opts['limit']:
1861 try:
1871 try:
1862 limit = int(opts['limit'])
1872 limit = int(opts['limit'])
1863 except ValueError:
1873 except ValueError:
1864 raise util.Abort(_('limit must be a positive integer'))
1874 raise util.Abort(_('limit must be a positive integer'))
1865 if limit <= 0: raise util.Abort(_('limit must be positive'))
1875 if limit <= 0: raise util.Abort(_('limit must be positive'))
1866 else:
1876 else:
1867 limit = sys.maxint
1877 limit = sys.maxint
1868 count = 0
1878 count = 0
1869
1879
1870 displayer = show_changeset(ui, repo, opts)
1880 displayer = show_changeset(ui, repo, opts)
1871 for st, rev, fns in changeiter:
1881 for st, rev, fns in changeiter:
1872 if st == 'window':
1882 if st == 'window':
1873 du = dui(ui)
1883 du = dui(ui)
1874 displayer.ui = du
1884 displayer.ui = du
1875 elif st == 'add':
1885 elif st == 'add':
1876 du.bump(rev)
1886 du.bump(rev)
1877 changenode = repo.changelog.node(rev)
1887 changenode = repo.changelog.node(rev)
1878 parents = [p for p in repo.changelog.parents(changenode)
1888 parents = [p for p in repo.changelog.parents(changenode)
1879 if p != nullid]
1889 if p != nullid]
1880 if opts['no_merges'] and len(parents) == 2:
1890 if opts['no_merges'] and len(parents) == 2:
1881 continue
1891 continue
1882 if opts['only_merges'] and len(parents) != 2:
1892 if opts['only_merges'] and len(parents) != 2:
1883 continue
1893 continue
1884
1894
1885 if opts['keyword']:
1895 if opts['keyword']:
1886 changes = getchange(rev)
1896 changes = getchange(rev)
1887 miss = 0
1897 miss = 0
1888 for k in [kw.lower() for kw in opts['keyword']]:
1898 for k in [kw.lower() for kw in opts['keyword']]:
1889 if not (k in changes[1].lower() or
1899 if not (k in changes[1].lower() or
1890 k in changes[4].lower() or
1900 k in changes[4].lower() or
1891 k in " ".join(changes[3][:20]).lower()):
1901 k in " ".join(changes[3][:20]).lower()):
1892 miss = 1
1902 miss = 1
1893 break
1903 break
1894 if miss:
1904 if miss:
1895 continue
1905 continue
1896
1906
1897 br = None
1907 br = None
1898 if opts['branches']:
1908 if opts['branches']:
1899 br = repo.branchlookup([repo.changelog.node(rev)])
1909 br = repo.branchlookup([repo.changelog.node(rev)])
1900
1910
1901 displayer.show(rev, brinfo=br)
1911 displayer.show(rev, brinfo=br)
1902 if opts['patch']:
1912 if opts['patch']:
1903 prev = (parents and parents[0]) or nullid
1913 prev = (parents and parents[0]) or nullid
1904 dodiff(du, du, repo, prev, changenode, match=matchfn)
1914 dodiff(du, du, repo, prev, changenode, match=matchfn)
1905 du.write("\n\n")
1915 du.write("\n\n")
1906 elif st == 'iter':
1916 elif st == 'iter':
1907 if count == limit: break
1917 if count == limit: break
1908 if du.header[rev]:
1918 if du.header[rev]:
1909 for args in du.header[rev]:
1919 for args in du.header[rev]:
1910 ui.write_header(*args)
1920 ui.write_header(*args)
1911 if du.hunk[rev]:
1921 if du.hunk[rev]:
1912 count += 1
1922 count += 1
1913 for args in du.hunk[rev]:
1923 for args in du.hunk[rev]:
1914 ui.write(*args)
1924 ui.write(*args)
1915
1925
1916 def manifest(ui, repo, rev=None):
1926 def manifest(ui, repo, rev=None):
1917 """output the latest or given revision of the project manifest
1927 """output the latest or given revision of the project manifest
1918
1928
1919 Print a list of version controlled files for the given revision.
1929 Print a list of version controlled files for the given revision.
1920
1930
1921 The manifest is the list of files being version controlled. If no revision
1931 The manifest is the list of files being version controlled. If no revision
1922 is given then the tip is used.
1932 is given then the tip is used.
1923 """
1933 """
1924 if rev:
1934 if rev:
1925 try:
1935 try:
1926 # assume all revision numbers are for changesets
1936 # assume all revision numbers are for changesets
1927 n = repo.lookup(rev)
1937 n = repo.lookup(rev)
1928 change = repo.changelog.read(n)
1938 change = repo.changelog.read(n)
1929 n = change[0]
1939 n = change[0]
1930 except hg.RepoError:
1940 except hg.RepoError:
1931 n = repo.manifest.lookup(rev)
1941 n = repo.manifest.lookup(rev)
1932 else:
1942 else:
1933 n = repo.manifest.tip()
1943 n = repo.manifest.tip()
1934 m = repo.manifest.read(n)
1944 m = repo.manifest.read(n)
1935 mf = repo.manifest.readflags(n)
1945 mf = repo.manifest.readflags(n)
1936 files = m.keys()
1946 files = m.keys()
1937 files.sort()
1947 files.sort()
1938
1948
1939 for f in files:
1949 for f in files:
1940 ui.write("%40s %3s %s\n" % (hex(m[f]), mf[f] and "755" or "644", f))
1950 ui.write("%40s %3s %s\n" % (hex(m[f]), mf[f] and "755" or "644", f))
1941
1951
1942 def merge(ui, repo, node=None, **opts):
1952 def merge(ui, repo, node=None, **opts):
1943 """Merge working directory with another revision
1953 """Merge working directory with another revision
1944
1954
1945 Merge the contents of the current working directory and the
1955 Merge the contents of the current working directory and the
1946 requested revision. Files that changed between either parent are
1956 requested revision. Files that changed between either parent are
1947 marked as changed for the next commit and a commit must be
1957 marked as changed for the next commit and a commit must be
1948 performed before any further updates are allowed.
1958 performed before any further updates are allowed.
1949 """
1959 """
1950 return update(ui, repo, node=node, merge=True, **opts)
1960 return doupdate(ui, repo, node=node, merge=True, **opts)
1951
1961
1952 def outgoing(ui, repo, dest="default-push", **opts):
1962 def outgoing(ui, repo, dest="default-push", **opts):
1953 """show changesets not found in destination
1963 """show changesets not found in destination
1954
1964
1955 Show changesets not found in the specified destination repository or
1965 Show changesets not found in the specified destination repository or
1956 the default push location. These are the changesets that would be pushed
1966 the default push location. These are the changesets that would be pushed
1957 if a push was requested.
1967 if a push was requested.
1958
1968
1959 See pull for valid destination format details.
1969 See pull for valid destination format details.
1960 """
1970 """
1961 dest = ui.expandpath(dest)
1971 dest = ui.expandpath(dest)
1962 if opts['ssh']:
1972 if opts['ssh']:
1963 ui.setconfig("ui", "ssh", opts['ssh'])
1973 ui.setconfig("ui", "ssh", opts['ssh'])
1964 if opts['remotecmd']:
1974 if opts['remotecmd']:
1965 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1975 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1966
1976
1967 other = hg.repository(ui, dest)
1977 other = hg.repository(ui, dest)
1968 o = repo.findoutgoing(other, force=opts['force'])
1978 o = repo.findoutgoing(other, force=opts['force'])
1969 if not o:
1979 if not o:
1970 ui.status(_("no changes found\n"))
1980 ui.status(_("no changes found\n"))
1971 return
1981 return
1972 o = repo.changelog.nodesbetween(o)[0]
1982 o = repo.changelog.nodesbetween(o)[0]
1973 if opts['newest_first']:
1983 if opts['newest_first']:
1974 o.reverse()
1984 o.reverse()
1975 displayer = show_changeset(ui, repo, opts)
1985 displayer = show_changeset(ui, repo, opts)
1976 for n in o:
1986 for n in o:
1977 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1987 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1978 if opts['no_merges'] and len(parents) == 2:
1988 if opts['no_merges'] and len(parents) == 2:
1979 continue
1989 continue
1980 displayer.show(changenode=n)
1990 displayer.show(changenode=n)
1981 if opts['patch']:
1991 if opts['patch']:
1982 prev = (parents and parents[0]) or nullid
1992 prev = (parents and parents[0]) or nullid
1983 dodiff(ui, ui, repo, prev, n)
1993 dodiff(ui, ui, repo, prev, n)
1984 ui.write("\n")
1994 ui.write("\n")
1985
1995
1986 def parents(ui, repo, rev=None, branches=None, **opts):
1996 def parents(ui, repo, rev=None, branches=None, **opts):
1987 """show the parents of the working dir or revision
1997 """show the parents of the working dir or revision
1988
1998
1989 Print the working directory's parent revisions.
1999 Print the working directory's parent revisions.
1990 """
2000 """
1991 if rev:
2001 if rev:
1992 p = repo.changelog.parents(repo.lookup(rev))
2002 p = repo.changelog.parents(repo.lookup(rev))
1993 else:
2003 else:
1994 p = repo.dirstate.parents()
2004 p = repo.dirstate.parents()
1995
2005
1996 br = None
2006 br = None
1997 if branches is not None:
2007 if branches is not None:
1998 br = repo.branchlookup(p)
2008 br = repo.branchlookup(p)
1999 displayer = show_changeset(ui, repo, opts)
2009 displayer = show_changeset(ui, repo, opts)
2000 for n in p:
2010 for n in p:
2001 if n != nullid:
2011 if n != nullid:
2002 displayer.show(changenode=n, brinfo=br)
2012 displayer.show(changenode=n, brinfo=br)
2003
2013
2004 def paths(ui, repo, search=None):
2014 def paths(ui, repo, search=None):
2005 """show definition of symbolic path names
2015 """show definition of symbolic path names
2006
2016
2007 Show definition of symbolic path name NAME. If no name is given, show
2017 Show definition of symbolic path name NAME. If no name is given, show
2008 definition of available names.
2018 definition of available names.
2009
2019
2010 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2020 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2011 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2021 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2012 """
2022 """
2013 if search:
2023 if search:
2014 for name, path in ui.configitems("paths"):
2024 for name, path in ui.configitems("paths"):
2015 if name == search:
2025 if name == search:
2016 ui.write("%s\n" % path)
2026 ui.write("%s\n" % path)
2017 return
2027 return
2018 ui.warn(_("not found!\n"))
2028 ui.warn(_("not found!\n"))
2019 return 1
2029 return 1
2020 else:
2030 else:
2021 for name, path in ui.configitems("paths"):
2031 for name, path in ui.configitems("paths"):
2022 ui.write("%s = %s\n" % (name, path))
2032 ui.write("%s = %s\n" % (name, path))
2023
2033
2024 def postincoming(ui, repo, modheads, optupdate):
2034 def postincoming(ui, repo, modheads, optupdate):
2025 if modheads == 0:
2035 if modheads == 0:
2026 return
2036 return
2027 if optupdate:
2037 if optupdate:
2028 if modheads == 1:
2038 if modheads == 1:
2029 return update(ui, repo)
2039 return doupdate(ui, repo)
2030 else:
2040 else:
2031 ui.status(_("not updating, since new heads added\n"))
2041 ui.status(_("not updating, since new heads added\n"))
2032 if modheads > 1:
2042 if modheads > 1:
2033 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2043 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2034 else:
2044 else:
2035 ui.status(_("(run 'hg update' to get a working copy)\n"))
2045 ui.status(_("(run 'hg update' to get a working copy)\n"))
2036
2046
2037 def pull(ui, repo, source="default", **opts):
2047 def pull(ui, repo, source="default", **opts):
2038 """pull changes from the specified source
2048 """pull changes from the specified source
2039
2049
2040 Pull changes from a remote repository to a local one.
2050 Pull changes from a remote repository to a local one.
2041
2051
2042 This finds all changes from the repository at the specified path
2052 This finds all changes from the repository at the specified path
2043 or URL and adds them to the local repository. By default, this
2053 or URL and adds them to the local repository. By default, this
2044 does not update the copy of the project in the working directory.
2054 does not update the copy of the project in the working directory.
2045
2055
2046 Valid URLs are of the form:
2056 Valid URLs are of the form:
2047
2057
2048 local/filesystem/path
2058 local/filesystem/path
2049 http://[user@]host[:port][/path]
2059 http://[user@]host[:port][/path]
2050 https://[user@]host[:port][/path]
2060 https://[user@]host[:port][/path]
2051 ssh://[user@]host[:port][/path]
2061 ssh://[user@]host[:port][/path]
2052
2062
2053 Some notes about using SSH with Mercurial:
2063 Some notes about using SSH with Mercurial:
2054 - SSH requires an accessible shell account on the destination machine
2064 - SSH requires an accessible shell account on the destination machine
2055 and a copy of hg in the remote path or specified with as remotecmd.
2065 and a copy of hg in the remote path or specified with as remotecmd.
2056 - /path is relative to the remote user's home directory by default.
2066 - /path is relative to the remote user's home directory by default.
2057 Use two slashes at the start of a path to specify an absolute path.
2067 Use two slashes at the start of a path to specify an absolute path.
2058 - Mercurial doesn't use its own compression via SSH; the right thing
2068 - Mercurial doesn't use its own compression via SSH; the right thing
2059 to do is to configure it in your ~/.ssh/ssh_config, e.g.:
2069 to do is to configure it in your ~/.ssh/ssh_config, e.g.:
2060 Host *.mylocalnetwork.example.com
2070 Host *.mylocalnetwork.example.com
2061 Compression off
2071 Compression off
2062 Host *
2072 Host *
2063 Compression on
2073 Compression on
2064 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2074 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2065 with the --ssh command line option.
2075 with the --ssh command line option.
2066 """
2076 """
2067 source = ui.expandpath(source)
2077 source = ui.expandpath(source)
2068 ui.status(_('pulling from %s\n') % (source))
2078 ui.status(_('pulling from %s\n') % (source))
2069
2079
2070 if opts['ssh']:
2080 if opts['ssh']:
2071 ui.setconfig("ui", "ssh", opts['ssh'])
2081 ui.setconfig("ui", "ssh", opts['ssh'])
2072 if opts['remotecmd']:
2082 if opts['remotecmd']:
2073 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
2083 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
2074
2084
2075 other = hg.repository(ui, source)
2085 other = hg.repository(ui, source)
2076 revs = None
2086 revs = None
2077 if opts['rev'] and not other.local():
2087 if opts['rev'] and not other.local():
2078 raise util.Abort(_("pull -r doesn't work for remote repositories yet"))
2088 raise util.Abort(_("pull -r doesn't work for remote repositories yet"))
2079 elif opts['rev']:
2089 elif opts['rev']:
2080 revs = [other.lookup(rev) for rev in opts['rev']]
2090 revs = [other.lookup(rev) for rev in opts['rev']]
2081 modheads = repo.pull(other, heads=revs, force=opts['force'])
2091 modheads = repo.pull(other, heads=revs, force=opts['force'])
2082 return postincoming(ui, repo, modheads, opts['update'])
2092 return postincoming(ui, repo, modheads, opts['update'])
2083
2093
2084 def push(ui, repo, dest="default-push", **opts):
2094 def push(ui, repo, dest="default-push", **opts):
2085 """push changes to the specified destination
2095 """push changes to the specified destination
2086
2096
2087 Push changes from the local repository to the given destination.
2097 Push changes from the local repository to the given destination.
2088
2098
2089 This is the symmetrical operation for pull. It helps to move
2099 This is the symmetrical operation for pull. It helps to move
2090 changes from the current repository to a different one. If the
2100 changes from the current repository to a different one. If the
2091 destination is local this is identical to a pull in that directory
2101 destination is local this is identical to a pull in that directory
2092 from the current one.
2102 from the current one.
2093
2103
2094 By default, push will refuse to run if it detects the result would
2104 By default, push will refuse to run if it detects the result would
2095 increase the number of remote heads. This generally indicates the
2105 increase the number of remote heads. This generally indicates the
2096 the client has forgotten to sync and merge before pushing.
2106 the client has forgotten to sync and merge before pushing.
2097
2107
2098 Valid URLs are of the form:
2108 Valid URLs are of the form:
2099
2109
2100 local/filesystem/path
2110 local/filesystem/path
2101 ssh://[user@]host[:port][/path]
2111 ssh://[user@]host[:port][/path]
2102
2112
2103 Look at the help text for the pull command for important details
2113 Look at the help text for the pull command for important details
2104 about ssh:// URLs.
2114 about ssh:// URLs.
2105 """
2115 """
2106 dest = ui.expandpath(dest)
2116 dest = ui.expandpath(dest)
2107 ui.status('pushing to %s\n' % (dest))
2117 ui.status('pushing to %s\n' % (dest))
2108
2118
2109 if opts['ssh']:
2119 if opts['ssh']:
2110 ui.setconfig("ui", "ssh", opts['ssh'])
2120 ui.setconfig("ui", "ssh", opts['ssh'])
2111 if opts['remotecmd']:
2121 if opts['remotecmd']:
2112 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
2122 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
2113
2123
2114 other = hg.repository(ui, dest)
2124 other = hg.repository(ui, dest)
2115 revs = None
2125 revs = None
2116 if opts['rev']:
2126 if opts['rev']:
2117 revs = [repo.lookup(rev) for rev in opts['rev']]
2127 revs = [repo.lookup(rev) for rev in opts['rev']]
2118 r = repo.push(other, opts['force'], revs=revs)
2128 r = repo.push(other, opts['force'], revs=revs)
2119 return r == 0
2129 return r == 0
2120
2130
2121 def rawcommit(ui, repo, *flist, **rc):
2131 def rawcommit(ui, repo, *flist, **rc):
2122 """raw commit interface (DEPRECATED)
2132 """raw commit interface (DEPRECATED)
2123
2133
2124 (DEPRECATED)
2134 (DEPRECATED)
2125 Lowlevel commit, for use in helper scripts.
2135 Lowlevel commit, for use in helper scripts.
2126
2136
2127 This command is not intended to be used by normal users, as it is
2137 This command is not intended to be used by normal users, as it is
2128 primarily useful for importing from other SCMs.
2138 primarily useful for importing from other SCMs.
2129
2139
2130 This command is now deprecated and will be removed in a future
2140 This command is now deprecated and will be removed in a future
2131 release, please use debugsetparents and commit instead.
2141 release, please use debugsetparents and commit instead.
2132 """
2142 """
2133
2143
2134 ui.warn(_("(the rawcommit command is deprecated)\n"))
2144 ui.warn(_("(the rawcommit command is deprecated)\n"))
2135
2145
2136 message = rc['message']
2146 message = rc['message']
2137 if not message and rc['logfile']:
2147 if not message and rc['logfile']:
2138 try:
2148 try:
2139 message = open(rc['logfile']).read()
2149 message = open(rc['logfile']).read()
2140 except IOError:
2150 except IOError:
2141 pass
2151 pass
2142 if not message and not rc['logfile']:
2152 if not message and not rc['logfile']:
2143 raise util.Abort(_("missing commit message"))
2153 raise util.Abort(_("missing commit message"))
2144
2154
2145 files = relpath(repo, list(flist))
2155 files = relpath(repo, list(flist))
2146 if rc['files']:
2156 if rc['files']:
2147 files += open(rc['files']).read().splitlines()
2157 files += open(rc['files']).read().splitlines()
2148
2158
2149 rc['parent'] = map(repo.lookup, rc['parent'])
2159 rc['parent'] = map(repo.lookup, rc['parent'])
2150
2160
2151 try:
2161 try:
2152 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
2162 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
2153 except ValueError, inst:
2163 except ValueError, inst:
2154 raise util.Abort(str(inst))
2164 raise util.Abort(str(inst))
2155
2165
2156 def recover(ui, repo):
2166 def recover(ui, repo):
2157 """roll back an interrupted transaction
2167 """roll back an interrupted transaction
2158
2168
2159 Recover from an interrupted commit or pull.
2169 Recover from an interrupted commit or pull.
2160
2170
2161 This command tries to fix the repository status after an interrupted
2171 This command tries to fix the repository status after an interrupted
2162 operation. It should only be necessary when Mercurial suggests it.
2172 operation. It should only be necessary when Mercurial suggests it.
2163 """
2173 """
2164 if repo.recover():
2174 if repo.recover():
2165 return repo.verify()
2175 return repo.verify()
2166 return 1
2176 return 1
2167
2177
2168 def remove(ui, repo, *pats, **opts):
2178 def remove(ui, repo, *pats, **opts):
2169 """remove the specified files on the next commit
2179 """remove the specified files on the next commit
2170
2180
2171 Schedule the indicated files for removal from the repository.
2181 Schedule the indicated files for removal from the repository.
2172
2182
2173 This command schedules the files to be removed at the next commit.
2183 This command schedules the files to be removed at the next commit.
2174 This only removes files from the current branch, not from the
2184 This only removes files from the current branch, not from the
2175 entire project history. If the files still exist in the working
2185 entire project history. If the files still exist in the working
2176 directory, they will be deleted from it. If invoked with --after,
2186 directory, they will be deleted from it. If invoked with --after,
2177 files that have been manually deleted are marked as removed.
2187 files that have been manually deleted are marked as removed.
2178 """
2188 """
2179 names = []
2189 names = []
2180 if not opts['after'] and not pats:
2190 if not opts['after'] and not pats:
2181 raise util.Abort(_('no files specified'))
2191 raise util.Abort(_('no files specified'))
2182 def okaytoremove(abs, rel, exact):
2192 def okaytoremove(abs, rel, exact):
2183 modified, added, removed, deleted, unknown = repo.changes(files=[abs])
2193 modified, added, removed, deleted, unknown = repo.changes(files=[abs])
2184 reason = None
2194 reason = None
2185 if not deleted and opts['after']:
2195 if not deleted and opts['after']:
2186 reason = _('is still present')
2196 reason = _('is still present')
2187 elif modified and not opts['force']:
2197 elif modified and not opts['force']:
2188 reason = _('is modified')
2198 reason = _('is modified')
2189 elif added:
2199 elif added:
2190 reason = _('has been marked for add')
2200 reason = _('has been marked for add')
2191 elif unknown:
2201 elif unknown:
2192 reason = _('is not managed')
2202 reason = _('is not managed')
2193 elif removed:
2203 elif removed:
2194 return False
2204 return False
2195 if reason:
2205 if reason:
2196 if exact:
2206 if exact:
2197 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2207 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2198 else:
2208 else:
2199 return True
2209 return True
2200 for src, abs, rel, exact in walk(repo, pats, opts):
2210 for src, abs, rel, exact in walk(repo, pats, opts):
2201 if okaytoremove(abs, rel, exact):
2211 if okaytoremove(abs, rel, exact):
2202 if ui.verbose or not exact:
2212 if ui.verbose or not exact:
2203 ui.status(_('removing %s\n') % rel)
2213 ui.status(_('removing %s\n') % rel)
2204 names.append(abs)
2214 names.append(abs)
2205 repo.remove(names, unlink=not opts['after'])
2215 repo.remove(names, unlink=not opts['after'])
2206
2216
2207 def rename(ui, repo, *pats, **opts):
2217 def rename(ui, repo, *pats, **opts):
2208 """rename files; equivalent of copy + remove
2218 """rename files; equivalent of copy + remove
2209
2219
2210 Mark dest as copies of sources; mark sources for deletion. If
2220 Mark dest as copies of sources; mark sources for deletion. If
2211 dest is a directory, copies are put in that directory. If dest is
2221 dest is a directory, copies are put in that directory. If dest is
2212 a file, there can only be one source.
2222 a file, there can only be one source.
2213
2223
2214 By default, this command copies the contents of files as they
2224 By default, this command copies the contents of files as they
2215 stand in the working directory. If invoked with --after, the
2225 stand in the working directory. If invoked with --after, the
2216 operation is recorded, but no copying is performed.
2226 operation is recorded, but no copying is performed.
2217
2227
2218 This command takes effect in the next commit.
2228 This command takes effect in the next commit.
2219
2229
2220 NOTE: This command should be treated as experimental. While it
2230 NOTE: This command should be treated as experimental. While it
2221 should properly record rename files, this information is not yet
2231 should properly record rename files, this information is not yet
2222 fully used by merge, nor fully reported by log.
2232 fully used by merge, nor fully reported by log.
2223 """
2233 """
2224 wlock = repo.wlock(0)
2234 wlock = repo.wlock(0)
2225 errs, copied = docopy(ui, repo, pats, opts, wlock)
2235 errs, copied = docopy(ui, repo, pats, opts, wlock)
2226 names = []
2236 names = []
2227 for abs, rel, exact in copied:
2237 for abs, rel, exact in copied:
2228 if ui.verbose or not exact:
2238 if ui.verbose or not exact:
2229 ui.status(_('removing %s\n') % rel)
2239 ui.status(_('removing %s\n') % rel)
2230 names.append(abs)
2240 names.append(abs)
2231 repo.remove(names, True, wlock)
2241 repo.remove(names, True, wlock)
2232 return errs
2242 return errs
2233
2243
2234 def revert(ui, repo, *pats, **opts):
2244 def revert(ui, repo, *pats, **opts):
2235 """revert files or dirs to their states as of some revision
2245 """revert files or dirs to their states as of some revision
2236
2246
2237 With no revision specified, revert the named files or directories
2247 With no revision specified, revert the named files or directories
2238 to the contents they had in the parent of the working directory.
2248 to the contents they had in the parent of the working directory.
2239 This restores the contents of the affected files to an unmodified
2249 This restores the contents of the affected files to an unmodified
2240 state. If the working directory has two parents, you must
2250 state. If the working directory has two parents, you must
2241 explicitly specify the revision to revert to.
2251 explicitly specify the revision to revert to.
2242
2252
2243 Modified files are saved with a .orig suffix before reverting.
2253 Modified files are saved with a .orig suffix before reverting.
2244 To disable these backups, use --no-backup.
2254 To disable these backups, use --no-backup.
2245
2255
2246 Using the -r option, revert the given files or directories to
2256 Using the -r option, revert the given files or directories to
2247 their contents as of a specific revision. This can be helpful to"roll
2257 their contents as of a specific revision. This can be helpful to"roll
2248 back" some or all of a change that should not have been committed.
2258 back" some or all of a change that should not have been committed.
2249
2259
2250 Revert modifies the working directory. It does not commit any
2260 Revert modifies the working directory. It does not commit any
2251 changes, or change the parent of the working directory. If you
2261 changes, or change the parent of the working directory. If you
2252 revert to a revision other than the parent of the working
2262 revert to a revision other than the parent of the working
2253 directory, the reverted files will thus appear modified
2263 directory, the reverted files will thus appear modified
2254 afterwards.
2264 afterwards.
2255
2265
2256 If a file has been deleted, it is recreated. If the executable
2266 If a file has been deleted, it is recreated. If the executable
2257 mode of a file was changed, it is reset.
2267 mode of a file was changed, it is reset.
2258
2268
2259 If names are given, all files matching the names are reverted.
2269 If names are given, all files matching the names are reverted.
2260
2270
2261 If no arguments are given, all files in the repository are reverted.
2271 If no arguments are given, all files in the repository are reverted.
2262 """
2272 """
2263 parent, p2 = repo.dirstate.parents()
2273 parent, p2 = repo.dirstate.parents()
2264 if opts['rev']:
2274 if opts['rev']:
2265 node = repo.lookup(opts['rev'])
2275 node = repo.lookup(opts['rev'])
2266 elif p2 != nullid:
2276 elif p2 != nullid:
2267 raise util.Abort(_('working dir has two parents; '
2277 raise util.Abort(_('working dir has two parents; '
2268 'you must specify the revision to revert to'))
2278 'you must specify the revision to revert to'))
2269 else:
2279 else:
2270 node = parent
2280 node = parent
2281 pmf = None
2271 mf = repo.manifest.read(repo.changelog.read(node)[0])
2282 mf = repo.manifest.read(repo.changelog.read(node)[0])
2272
2283
2273 wlock = repo.wlock()
2284 wlock = repo.wlock()
2274
2285
2275 # need all matching names in dirstate and manifest of target rev,
2286 # need all matching names in dirstate and manifest of target rev,
2276 # so have to walk both. do not print errors if files exist in one
2287 # so have to walk both. do not print errors if files exist in one
2277 # but not other.
2288 # but not other.
2278
2289
2279 names = {}
2290 names = {}
2280 target_only = {}
2291 target_only = {}
2281
2292
2282 # walk dirstate.
2293 # walk dirstate.
2283
2294
2284 for src, abs, rel, exact in walk(repo, pats, opts, badmatch=mf.has_key):
2295 for src, abs, rel, exact in walk(repo, pats, opts, badmatch=mf.has_key):
2285 names[abs] = (rel, exact)
2296 names[abs] = (rel, exact)
2286 if src == 'b':
2297 if src == 'b':
2287 target_only[abs] = True
2298 target_only[abs] = True
2288
2299
2289 # walk target manifest.
2300 # walk target manifest.
2290
2301
2291 for src, abs, rel, exact in walk(repo, pats, opts, node=node,
2302 for src, abs, rel, exact in walk(repo, pats, opts, node=node,
2292 badmatch=names.has_key):
2303 badmatch=names.has_key):
2293 if abs in names: continue
2304 if abs in names: continue
2294 names[abs] = (rel, exact)
2305 names[abs] = (rel, exact)
2295 target_only[abs] = True
2306 target_only[abs] = True
2296
2307
2297 changes = repo.changes(match=names.has_key, wlock=wlock)
2308 changes = repo.changes(match=names.has_key, wlock=wlock)
2298 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2309 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2299
2310
2300 revert = ([], _('reverting %s\n'))
2311 revert = ([], _('reverting %s\n'))
2301 add = ([], _('adding %s\n'))
2312 add = ([], _('adding %s\n'))
2302 remove = ([], _('removing %s\n'))
2313 remove = ([], _('removing %s\n'))
2303 forget = ([], _('forgetting %s\n'))
2314 forget = ([], _('forgetting %s\n'))
2304 undelete = ([], _('undeleting %s\n'))
2315 undelete = ([], _('undeleting %s\n'))
2305 update = {}
2316 update = {}
2306
2317
2307 disptable = (
2318 disptable = (
2308 # dispatch table:
2319 # dispatch table:
2309 # file state
2320 # file state
2310 # action if in target manifest
2321 # action if in target manifest
2311 # action if not in target manifest
2322 # action if not in target manifest
2312 # make backup if in target manifest
2323 # make backup if in target manifest
2313 # make backup if not in target manifest
2324 # make backup if not in target manifest
2314 (modified, revert, remove, True, True),
2325 (modified, revert, remove, True, True),
2315 (added, revert, forget, True, False),
2326 (added, revert, forget, True, False),
2316 (removed, undelete, None, False, False),
2327 (removed, undelete, None, False, False),
2317 (deleted, revert, remove, False, False),
2328 (deleted, revert, remove, False, False),
2318 (unknown, add, None, True, False),
2329 (unknown, add, None, True, False),
2319 (target_only, add, None, False, False),
2330 (target_only, add, None, False, False),
2320 )
2331 )
2321
2332
2322 entries = names.items()
2333 entries = names.items()
2323 entries.sort()
2334 entries.sort()
2324
2335
2325 for abs, (rel, exact) in entries:
2336 for abs, (rel, exact) in entries:
2326 in_mf = abs in mf
2337 in_mf = abs in mf
2327 def handle(xlist, dobackup):
2338 def handle(xlist, dobackup):
2328 xlist[0].append(abs)
2339 xlist[0].append(abs)
2329 if dobackup and not opts['no_backup'] and os.path.exists(rel):
2340 if dobackup and not opts['no_backup'] and os.path.exists(rel):
2330 bakname = "%s.orig" % rel
2341 bakname = "%s.orig" % rel
2331 ui.note(_('saving current version of %s as %s\n') %
2342 ui.note(_('saving current version of %s as %s\n') %
2332 (rel, bakname))
2343 (rel, bakname))
2333 shutil.copyfile(rel, bakname)
2344 shutil.copyfile(rel, bakname)
2334 shutil.copymode(rel, bakname)
2345 shutil.copymode(rel, bakname)
2335 if ui.verbose or not exact:
2346 if ui.verbose or not exact:
2336 ui.status(xlist[1] % rel)
2347 ui.status(xlist[1] % rel)
2337 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2348 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2338 if abs not in table: continue
2349 if abs not in table: continue
2339 # file has changed in dirstate
2350 # file has changed in dirstate
2340 if in_mf:
2351 if in_mf:
2341 handle(hitlist, backuphit)
2352 handle(hitlist, backuphit)
2342 elif misslist is not None:
2353 elif misslist is not None:
2343 handle(misslist, backupmiss)
2354 handle(misslist, backupmiss)
2344 else:
2355 else:
2345 if exact: ui.warn(_('file not managed: %s\n' % rel))
2356 if exact: ui.warn(_('file not managed: %s\n' % rel))
2346 break
2357 break
2347 else:
2358 else:
2348 # file has not changed in dirstate
2359 # file has not changed in dirstate
2349 if node == parent:
2360 if node == parent:
2350 if exact: ui.warn(_('no changes needed to %s\n' % rel))
2361 if exact: ui.warn(_('no changes needed to %s\n' % rel))
2351 continue
2362 continue
2352 if not in_mf:
2363 if not in_mf:
2353 handle(remove, False)
2364 if pmf is None:
2365 # only need parent manifest in this unlikely case,
2366 # so do not read by default
2367 pmf = repo.manifest.read(repo.changelog.read(parent)[0])
2368 if abs in pmf:
2369 handle(remove, False)
2354 update[abs] = True
2370 update[abs] = True
2355
2371
2356 repo.dirstate.forget(forget[0])
2372 repo.dirstate.forget(forget[0])
2357 r = repo.update(node, False, True, update.has_key, False, wlock=wlock,
2373 r = repo.update(node, False, True, update.has_key, False, wlock=wlock,
2358 show_stats=False)
2374 show_stats=False)
2359 repo.dirstate.update(add[0], 'a')
2375 repo.dirstate.update(add[0], 'a')
2360 repo.dirstate.update(undelete[0], 'n')
2376 repo.dirstate.update(undelete[0], 'n')
2361 repo.dirstate.update(remove[0], 'r')
2377 repo.dirstate.update(remove[0], 'r')
2362 return r
2378 return r
2363
2379
2364 def rollback(ui, repo):
2380 def rollback(ui, repo):
2365 """roll back the last transaction in this repository
2381 """roll back the last transaction in this repository
2366
2382
2367 Roll back the last transaction in this repository, restoring the
2383 Roll back the last transaction in this repository, restoring the
2368 project to its state prior to the transaction.
2384 project to its state prior to the transaction.
2369
2385
2370 Transactions are used to encapsulate the effects of all commands
2386 Transactions are used to encapsulate the effects of all commands
2371 that create new changesets or propagate existing changesets into a
2387 that create new changesets or propagate existing changesets into a
2372 repository. For example, the following commands are transactional,
2388 repository. For example, the following commands are transactional,
2373 and their effects can be rolled back:
2389 and their effects can be rolled back:
2374
2390
2375 commit
2391 commit
2376 import
2392 import
2377 pull
2393 pull
2378 push (with this repository as destination)
2394 push (with this repository as destination)
2379 unbundle
2395 unbundle
2380
2396
2381 This command should be used with care. There is only one level of
2397 This command should be used with care. There is only one level of
2382 rollback, and there is no way to undo a rollback.
2398 rollback, and there is no way to undo a rollback.
2383
2399
2384 This command is not intended for use on public repositories. Once
2400 This command is not intended for use on public repositories. Once
2385 changes are visible for pull by other users, rolling a transaction
2401 changes are visible for pull by other users, rolling a transaction
2386 back locally is ineffective (someone else may already have pulled
2402 back locally is ineffective (someone else may already have pulled
2387 the changes). Furthermore, a race is possible with readers of the
2403 the changes). Furthermore, a race is possible with readers of the
2388 repository; for example an in-progress pull from the repository
2404 repository; for example an in-progress pull from the repository
2389 may fail if a rollback is performed.
2405 may fail if a rollback is performed.
2390 """
2406 """
2391 repo.undo()
2407 repo.undo()
2392
2408
2393 def root(ui, repo):
2409 def root(ui, repo):
2394 """print the root (top) of the current working dir
2410 """print the root (top) of the current working dir
2395
2411
2396 Print the root directory of the current repository.
2412 Print the root directory of the current repository.
2397 """
2413 """
2398 ui.write(repo.root + "\n")
2414 ui.write(repo.root + "\n")
2399
2415
2400 def serve(ui, repo, **opts):
2416 def serve(ui, repo, **opts):
2401 """export the repository via HTTP
2417 """export the repository via HTTP
2402
2418
2403 Start a local HTTP repository browser and pull server.
2419 Start a local HTTP repository browser and pull server.
2404
2420
2405 By default, the server logs accesses to stdout and errors to
2421 By default, the server logs accesses to stdout and errors to
2406 stderr. Use the "-A" and "-E" options to log to files.
2422 stderr. Use the "-A" and "-E" options to log to files.
2407 """
2423 """
2408
2424
2409 if opts["stdio"]:
2425 if opts["stdio"]:
2410 if repo is None:
2426 if repo is None:
2411 raise hg.RepoError(_('no repo found'))
2427 raise hg.RepoError(_('no repo found'))
2412 fin, fout = sys.stdin, sys.stdout
2428 fin, fout = sys.stdin, sys.stdout
2413 sys.stdout = sys.stderr
2429 sys.stdout = sys.stderr
2414
2430
2415 # Prevent insertion/deletion of CRs
2431 # Prevent insertion/deletion of CRs
2416 util.set_binary(fin)
2432 util.set_binary(fin)
2417 util.set_binary(fout)
2433 util.set_binary(fout)
2418
2434
2419 def getarg():
2435 def getarg():
2420 argline = fin.readline()[:-1]
2436 argline = fin.readline()[:-1]
2421 arg, l = argline.split()
2437 arg, l = argline.split()
2422 val = fin.read(int(l))
2438 val = fin.read(int(l))
2423 return arg, val
2439 return arg, val
2424 def respond(v):
2440 def respond(v):
2425 fout.write("%d\n" % len(v))
2441 fout.write("%d\n" % len(v))
2426 fout.write(v)
2442 fout.write(v)
2427 fout.flush()
2443 fout.flush()
2428
2444
2429 lock = None
2445 lock = None
2430
2446
2431 while 1:
2447 while 1:
2432 cmd = fin.readline()[:-1]
2448 cmd = fin.readline()[:-1]
2433 if cmd == '':
2449 if cmd == '':
2434 return
2450 return
2435 if cmd == "heads":
2451 if cmd == "heads":
2436 h = repo.heads()
2452 h = repo.heads()
2437 respond(" ".join(map(hex, h)) + "\n")
2453 respond(" ".join(map(hex, h)) + "\n")
2438 if cmd == "lock":
2454 if cmd == "lock":
2439 lock = repo.lock()
2455 lock = repo.lock()
2440 respond("")
2456 respond("")
2441 if cmd == "unlock":
2457 if cmd == "unlock":
2442 if lock:
2458 if lock:
2443 lock.release()
2459 lock.release()
2444 lock = None
2460 lock = None
2445 respond("")
2461 respond("")
2446 elif cmd == "branches":
2462 elif cmd == "branches":
2447 arg, nodes = getarg()
2463 arg, nodes = getarg()
2448 nodes = map(bin, nodes.split(" "))
2464 nodes = map(bin, nodes.split(" "))
2449 r = []
2465 r = []
2450 for b in repo.branches(nodes):
2466 for b in repo.branches(nodes):
2451 r.append(" ".join(map(hex, b)) + "\n")
2467 r.append(" ".join(map(hex, b)) + "\n")
2452 respond("".join(r))
2468 respond("".join(r))
2453 elif cmd == "between":
2469 elif cmd == "between":
2454 arg, pairs = getarg()
2470 arg, pairs = getarg()
2455 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
2471 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
2456 r = []
2472 r = []
2457 for b in repo.between(pairs):
2473 for b in repo.between(pairs):
2458 r.append(" ".join(map(hex, b)) + "\n")
2474 r.append(" ".join(map(hex, b)) + "\n")
2459 respond("".join(r))
2475 respond("".join(r))
2460 elif cmd == "changegroup":
2476 elif cmd == "changegroup":
2461 nodes = []
2477 nodes = []
2462 arg, roots = getarg()
2478 arg, roots = getarg()
2463 nodes = map(bin, roots.split(" "))
2479 nodes = map(bin, roots.split(" "))
2464
2480
2465 cg = repo.changegroup(nodes, 'serve')
2481 cg = repo.changegroup(nodes, 'serve')
2466 while 1:
2482 while 1:
2467 d = cg.read(4096)
2483 d = cg.read(4096)
2468 if not d:
2484 if not d:
2469 break
2485 break
2470 fout.write(d)
2486 fout.write(d)
2471
2487
2472 fout.flush()
2488 fout.flush()
2473
2489
2474 elif cmd == "addchangegroup":
2490 elif cmd == "addchangegroup":
2475 if not lock:
2491 if not lock:
2476 respond("not locked")
2492 respond("not locked")
2477 continue
2493 continue
2478 respond("")
2494 respond("")
2479
2495
2480 r = repo.addchangegroup(fin, 'serve')
2496 r = repo.addchangegroup(fin, 'serve')
2481 respond(str(r))
2497 respond(str(r))
2482
2498
2483 optlist = ("name templates style address port ipv6"
2499 optlist = ("name templates style address port ipv6"
2484 " accesslog errorlog webdir_conf")
2500 " accesslog errorlog webdir_conf")
2485 for o in optlist.split():
2501 for o in optlist.split():
2486 if opts[o]:
2502 if opts[o]:
2487 ui.setconfig("web", o, opts[o])
2503 ui.setconfig("web", o, opts[o])
2488
2504
2489 if repo is None and not ui.config("web", "webdir_conf"):
2505 if repo is None and not ui.config("web", "webdir_conf"):
2490 raise hg.RepoError(_('no repo found'))
2506 raise hg.RepoError(_('no repo found'))
2491
2507
2492 if opts['daemon'] and not opts['daemon_pipefds']:
2508 if opts['daemon'] and not opts['daemon_pipefds']:
2493 rfd, wfd = os.pipe()
2509 rfd, wfd = os.pipe()
2494 args = sys.argv[:]
2510 args = sys.argv[:]
2495 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
2511 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
2496 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
2512 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
2497 args[0], args)
2513 args[0], args)
2498 os.close(wfd)
2514 os.close(wfd)
2499 os.read(rfd, 1)
2515 os.read(rfd, 1)
2500 os._exit(0)
2516 os._exit(0)
2501
2517
2502 try:
2518 try:
2503 httpd = hgweb.create_server(ui, repo)
2519 httpd = hgweb.create_server(ui, repo)
2504 except socket.error, inst:
2520 except socket.error, inst:
2505 raise util.Abort(_('cannot start server: ') + inst.args[1])
2521 raise util.Abort(_('cannot start server: ') + inst.args[1])
2506
2522
2507 if ui.verbose:
2523 if ui.verbose:
2508 addr, port = httpd.socket.getsockname()
2524 addr, port = httpd.socket.getsockname()
2509 if addr == '0.0.0.0':
2525 if addr == '0.0.0.0':
2510 addr = socket.gethostname()
2526 addr = socket.gethostname()
2511 else:
2527 else:
2512 try:
2528 try:
2513 addr = socket.gethostbyaddr(addr)[0]
2529 addr = socket.gethostbyaddr(addr)[0]
2514 except socket.error:
2530 except socket.error:
2515 pass
2531 pass
2516 if port != 80:
2532 if port != 80:
2517 ui.status(_('listening at http://%s:%d/\n') % (addr, port))
2533 ui.status(_('listening at http://%s:%d/\n') % (addr, port))
2518 else:
2534 else:
2519 ui.status(_('listening at http://%s/\n') % addr)
2535 ui.status(_('listening at http://%s/\n') % addr)
2520
2536
2521 if opts['pid_file']:
2537 if opts['pid_file']:
2522 fp = open(opts['pid_file'], 'w')
2538 fp = open(opts['pid_file'], 'w')
2523 fp.write(str(os.getpid()))
2539 fp.write(str(os.getpid()))
2524 fp.close()
2540 fp.close()
2525
2541
2526 if opts['daemon_pipefds']:
2542 if opts['daemon_pipefds']:
2527 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
2543 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
2528 os.close(rfd)
2544 os.close(rfd)
2529 os.write(wfd, 'y')
2545 os.write(wfd, 'y')
2530 os.close(wfd)
2546 os.close(wfd)
2531 sys.stdout.flush()
2547 sys.stdout.flush()
2532 sys.stderr.flush()
2548 sys.stderr.flush()
2533 fd = os.open(util.nulldev, os.O_RDWR)
2549 fd = os.open(util.nulldev, os.O_RDWR)
2534 if fd != 0: os.dup2(fd, 0)
2550 if fd != 0: os.dup2(fd, 0)
2535 if fd != 1: os.dup2(fd, 1)
2551 if fd != 1: os.dup2(fd, 1)
2536 if fd != 2: os.dup2(fd, 2)
2552 if fd != 2: os.dup2(fd, 2)
2537 if fd not in (0, 1, 2): os.close(fd)
2553 if fd not in (0, 1, 2): os.close(fd)
2538
2554
2539 httpd.serve_forever()
2555 httpd.serve_forever()
2540
2556
2541 def status(ui, repo, *pats, **opts):
2557 def status(ui, repo, *pats, **opts):
2542 """show changed files in the working directory
2558 """show changed files in the working directory
2543
2559
2544 Show changed files in the repository. If names are
2560 Show changed files in the repository. If names are
2545 given, only files that match are shown.
2561 given, only files that match are shown.
2546
2562
2547 The codes used to show the status of files are:
2563 The codes used to show the status of files are:
2548 M = modified
2564 M = modified
2549 A = added
2565 A = added
2550 R = removed
2566 R = removed
2551 ! = deleted, but still tracked
2567 ! = deleted, but still tracked
2552 ? = not tracked
2568 ? = not tracked
2553 I = ignored (not shown by default)
2569 I = ignored (not shown by default)
2554 """
2570 """
2555
2571
2556 show_ignored = opts['ignored'] and True or False
2572 show_ignored = opts['ignored'] and True or False
2557 files, matchfn, anypats = matchpats(repo, pats, opts)
2573 files, matchfn, anypats = matchpats(repo, pats, opts)
2558 cwd = (pats and repo.getcwd()) or ''
2574 cwd = (pats and repo.getcwd()) or ''
2559 modified, added, removed, deleted, unknown, ignored = [
2575 modified, added, removed, deleted, unknown, ignored = [
2560 [util.pathto(cwd, x) for x in n]
2576 [util.pathto(cwd, x) for x in n]
2561 for n in repo.changes(files=files, match=matchfn,
2577 for n in repo.changes(files=files, match=matchfn,
2562 show_ignored=show_ignored)]
2578 show_ignored=show_ignored)]
2563
2579
2564 changetypes = [('modified', 'M', modified),
2580 changetypes = [('modified', 'M', modified),
2565 ('added', 'A', added),
2581 ('added', 'A', added),
2566 ('removed', 'R', removed),
2582 ('removed', 'R', removed),
2567 ('deleted', '!', deleted),
2583 ('deleted', '!', deleted),
2568 ('unknown', '?', unknown),
2584 ('unknown', '?', unknown),
2569 ('ignored', 'I', ignored)]
2585 ('ignored', 'I', ignored)]
2570
2586
2571 end = opts['print0'] and '\0' or '\n'
2587 end = opts['print0'] and '\0' or '\n'
2572
2588
2573 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
2589 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
2574 or changetypes):
2590 or changetypes):
2575 if opts['no_status']:
2591 if opts['no_status']:
2576 format = "%%s%s" % end
2592 format = "%%s%s" % end
2577 else:
2593 else:
2578 format = "%s %%s%s" % (char, end)
2594 format = "%s %%s%s" % (char, end)
2579
2595
2580 for f in changes:
2596 for f in changes:
2581 ui.write(format % f)
2597 ui.write(format % f)
2582
2598
2583 def tag(ui, repo, name, rev_=None, **opts):
2599 def tag(ui, repo, name, rev_=None, **opts):
2584 """add a tag for the current tip or a given revision
2600 """add a tag for the current tip or a given revision
2585
2601
2586 Name a particular revision using <name>.
2602 Name a particular revision using <name>.
2587
2603
2588 Tags are used to name particular revisions of the repository and are
2604 Tags are used to name particular revisions of the repository and are
2589 very useful to compare different revision, to go back to significant
2605 very useful to compare different revision, to go back to significant
2590 earlier versions or to mark branch points as releases, etc.
2606 earlier versions or to mark branch points as releases, etc.
2591
2607
2592 If no revision is given, the tip is used.
2608 If no revision is given, the tip is used.
2593
2609
2594 To facilitate version control, distribution, and merging of tags,
2610 To facilitate version control, distribution, and merging of tags,
2595 they are stored as a file named ".hgtags" which is managed
2611 they are stored as a file named ".hgtags" which is managed
2596 similarly to other project files and can be hand-edited if
2612 similarly to other project files and can be hand-edited if
2597 necessary. The file '.hg/localtags' is used for local tags (not
2613 necessary. The file '.hg/localtags' is used for local tags (not
2598 shared among repositories).
2614 shared among repositories).
2599 """
2615 """
2600 if name == "tip":
2616 if name == "tip":
2601 raise util.Abort(_("the name 'tip' is reserved"))
2617 raise util.Abort(_("the name 'tip' is reserved"))
2602 if rev_ is not None:
2618 if rev_ is not None:
2603 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2619 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2604 "please use 'hg tag [-r REV] NAME' instead\n"))
2620 "please use 'hg tag [-r REV] NAME' instead\n"))
2605 if opts['rev']:
2621 if opts['rev']:
2606 raise util.Abort(_("use only one form to specify the revision"))
2622 raise util.Abort(_("use only one form to specify the revision"))
2607 if opts['rev']:
2623 if opts['rev']:
2608 rev_ = opts['rev']
2624 rev_ = opts['rev']
2609 if rev_:
2625 if rev_:
2610 r = hex(repo.lookup(rev_))
2626 r = hex(repo.lookup(rev_))
2611 else:
2627 else:
2612 r = hex(repo.changelog.tip())
2628 r = hex(repo.changelog.tip())
2613
2629
2614 disallowed = (revrangesep, '\r', '\n')
2630 disallowed = (revrangesep, '\r', '\n')
2615 for c in disallowed:
2631 for c in disallowed:
2616 if name.find(c) >= 0:
2632 if name.find(c) >= 0:
2617 raise util.Abort(_("%s cannot be used in a tag name") % repr(c))
2633 raise util.Abort(_("%s cannot be used in a tag name") % repr(c))
2618
2634
2619 repo.hook('pretag', throw=True, node=r, tag=name,
2635 repo.hook('pretag', throw=True, node=r, tag=name,
2620 local=int(not not opts['local']))
2636 local=int(not not opts['local']))
2621
2637
2622 if opts['local']:
2638 if opts['local']:
2623 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
2639 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
2624 repo.hook('tag', node=r, tag=name, local=1)
2640 repo.hook('tag', node=r, tag=name, local=1)
2625 return
2641 return
2626
2642
2627 for x in repo.changes():
2643 for x in repo.changes():
2628 if ".hgtags" in x:
2644 if ".hgtags" in x:
2629 raise util.Abort(_("working copy of .hgtags is changed "
2645 raise util.Abort(_("working copy of .hgtags is changed "
2630 "(please commit .hgtags manually)"))
2646 "(please commit .hgtags manually)"))
2631
2647
2632 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
2648 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
2633 if repo.dirstate.state(".hgtags") == '?':
2649 if repo.dirstate.state(".hgtags") == '?':
2634 repo.add([".hgtags"])
2650 repo.add([".hgtags"])
2635
2651
2636 message = (opts['message'] or
2652 message = (opts['message'] or
2637 _("Added tag %s for changeset %s") % (name, r))
2653 _("Added tag %s for changeset %s") % (name, r))
2638 try:
2654 try:
2639 repo.commit([".hgtags"], message, opts['user'], opts['date'])
2655 repo.commit([".hgtags"], message, opts['user'], opts['date'])
2640 repo.hook('tag', node=r, tag=name, local=0)
2656 repo.hook('tag', node=r, tag=name, local=0)
2641 except ValueError, inst:
2657 except ValueError, inst:
2642 raise util.Abort(str(inst))
2658 raise util.Abort(str(inst))
2643
2659
2644 def tags(ui, repo):
2660 def tags(ui, repo):
2645 """list repository tags
2661 """list repository tags
2646
2662
2647 List the repository tags.
2663 List the repository tags.
2648
2664
2649 This lists both regular and local tags.
2665 This lists both regular and local tags.
2650 """
2666 """
2651
2667
2652 l = repo.tagslist()
2668 l = repo.tagslist()
2653 l.reverse()
2669 l.reverse()
2654 for t, n in l:
2670 for t, n in l:
2655 try:
2671 try:
2656 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
2672 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
2657 except KeyError:
2673 except KeyError:
2658 r = " ?:?"
2674 r = " ?:?"
2659 if ui.quiet:
2675 if ui.quiet:
2660 ui.write("%s\n" % t)
2676 ui.write("%s\n" % t)
2661 else:
2677 else:
2662 ui.write("%-30s %s\n" % (t, r))
2678 ui.write("%-30s %s\n" % (t, r))
2663
2679
2664 def tip(ui, repo, **opts):
2680 def tip(ui, repo, **opts):
2665 """show the tip revision
2681 """show the tip revision
2666
2682
2667 Show the tip revision.
2683 Show the tip revision.
2668 """
2684 """
2669 n = repo.changelog.tip()
2685 n = repo.changelog.tip()
2670 br = None
2686 br = None
2671 if opts['branches']:
2687 if opts['branches']:
2672 br = repo.branchlookup([n])
2688 br = repo.branchlookup([n])
2673 show_changeset(ui, repo, opts).show(changenode=n, brinfo=br)
2689 show_changeset(ui, repo, opts).show(changenode=n, brinfo=br)
2674 if opts['patch']:
2690 if opts['patch']:
2675 dodiff(ui, ui, repo, repo.changelog.parents(n)[0], n)
2691 dodiff(ui, ui, repo, repo.changelog.parents(n)[0], n)
2676
2692
2677 def unbundle(ui, repo, fname, **opts):
2693 def unbundle(ui, repo, fname, **opts):
2678 """apply a changegroup file
2694 """apply a changegroup file
2679
2695
2680 Apply a compressed changegroup file generated by the bundle
2696 Apply a compressed changegroup file generated by the bundle
2681 command.
2697 command.
2682 """
2698 """
2683 f = urllib.urlopen(fname)
2699 f = urllib.urlopen(fname)
2684
2700
2685 header = f.read(6)
2701 header = f.read(6)
2686 if not header.startswith("HG"):
2702 if not header.startswith("HG"):
2687 raise util.Abort(_("%s: not a Mercurial bundle file") % fname)
2703 raise util.Abort(_("%s: not a Mercurial bundle file") % fname)
2688 elif not header.startswith("HG10"):
2704 elif not header.startswith("HG10"):
2689 raise util.Abort(_("%s: unknown bundle version") % fname)
2705 raise util.Abort(_("%s: unknown bundle version") % fname)
2690 elif header == "HG10BZ":
2706 elif header == "HG10BZ":
2691 def generator(f):
2707 def generator(f):
2692 zd = bz2.BZ2Decompressor()
2708 zd = bz2.BZ2Decompressor()
2693 zd.decompress("BZ")
2709 zd.decompress("BZ")
2694 for chunk in f:
2710 for chunk in f:
2695 yield zd.decompress(chunk)
2711 yield zd.decompress(chunk)
2696 elif header == "HG10UN":
2712 elif header == "HG10UN":
2697 def generator(f):
2713 def generator(f):
2698 for chunk in f:
2714 for chunk in f:
2699 yield chunk
2715 yield chunk
2700 else:
2716 else:
2701 raise util.Abort(_("%s: unknown bundle compression type")
2717 raise util.Abort(_("%s: unknown bundle compression type")
2702 % fname)
2718 % fname)
2703 gen = generator(util.filechunkiter(f, 4096))
2719 gen = generator(util.filechunkiter(f, 4096))
2704 modheads = repo.addchangegroup(util.chunkbuffer(gen), 'unbundle')
2720 modheads = repo.addchangegroup(util.chunkbuffer(gen), 'unbundle')
2705 return postincoming(ui, repo, modheads, opts['update'])
2721 return postincoming(ui, repo, modheads, opts['update'])
2706
2722
2707 def undo(ui, repo):
2723 def undo(ui, repo):
2708 """undo the last commit or pull (DEPRECATED)
2724 """undo the last commit or pull (DEPRECATED)
2709
2725
2710 (DEPRECATED)
2726 (DEPRECATED)
2711 This command is now deprecated and will be removed in a future
2727 This command is now deprecated and will be removed in a future
2712 release. Please use the rollback command instead. For usage
2728 release. Please use the rollback command instead. For usage
2713 instructions, see the rollback command.
2729 instructions, see the rollback command.
2714 """
2730 """
2715 ui.warn(_('(the undo command is deprecated; use rollback instead)\n'))
2731 ui.warn(_('(the undo command is deprecated; use rollback instead)\n'))
2716 repo.undo()
2732 repo.undo()
2717
2733
2718 def update(ui, repo, node=None, merge=False, clean=False, force=None,
2734 def update(ui, repo, node=None, merge=False, clean=False, force=None,
2719 branch=None, **opts):
2735 branch=None, **opts):
2720 """update or merge working directory
2736 """update or merge working directory
2721
2737
2722 Update the working directory to the specified revision.
2738 Update the working directory to the specified revision.
2723
2739
2724 If there are no outstanding changes in the working directory and
2740 If there are no outstanding changes in the working directory and
2725 there is a linear relationship between the current version and the
2741 there is a linear relationship between the current version and the
2726 requested version, the result is the requested version.
2742 requested version, the result is the requested version.
2727
2743
2728 Otherwise the result is a merge between the contents of the
2744 To merge the working directory with another revision, use the
2729 current working directory and the requested version. Files that
2745 merge command.
2730 changed between either parent are marked as changed for the next
2731 commit and a commit must be performed before any further updates
2732 are allowed.
2733
2746
2734 By default, update will refuse to run if doing so would require
2747 By default, update will refuse to run if doing so would require
2735 merging or discarding local changes.
2748 merging or discarding local changes.
2736 """
2749 """
2750 if merge:
2751 ui.warn(_('(the -m/--merge option is deprecated; '
2752 'use the merge command instead)\n'))
2753 return doupdate(ui, repo, node, merge, clean, force, branch, **opts)
2754
2755 def doupdate(ui, repo, node=None, merge=False, clean=False, force=None,
2756 branch=None, **opts):
2737 if branch:
2757 if branch:
2738 br = repo.branchlookup(branch=branch)
2758 br = repo.branchlookup(branch=branch)
2739 found = []
2759 found = []
2740 for x in br:
2760 for x in br:
2741 if branch in br[x]:
2761 if branch in br[x]:
2742 found.append(x)
2762 found.append(x)
2743 if len(found) > 1:
2763 if len(found) > 1:
2744 ui.warn(_("Found multiple heads for %s\n") % branch)
2764 ui.warn(_("Found multiple heads for %s\n") % branch)
2745 for x in found:
2765 for x in found:
2746 show_changeset(ui, repo, opts).show(changenode=x, brinfo=br)
2766 show_changeset(ui, repo, opts).show(changenode=x, brinfo=br)
2747 return 1
2767 return 1
2748 if len(found) == 1:
2768 if len(found) == 1:
2749 node = found[0]
2769 node = found[0]
2750 ui.warn(_("Using head %s for branch %s\n") % (short(node), branch))
2770 ui.warn(_("Using head %s for branch %s\n") % (short(node), branch))
2751 else:
2771 else:
2752 ui.warn(_("branch %s not found\n") % (branch))
2772 ui.warn(_("branch %s not found\n") % (branch))
2753 return 1
2773 return 1
2754 else:
2774 else:
2755 node = node and repo.lookup(node) or repo.changelog.tip()
2775 node = node and repo.lookup(node) or repo.changelog.tip()
2756 return repo.update(node, allow=merge, force=clean, forcemerge=force)
2776 return repo.update(node, allow=merge, force=clean, forcemerge=force)
2757
2777
2758 def verify(ui, repo):
2778 def verify(ui, repo):
2759 """verify the integrity of the repository
2779 """verify the integrity of the repository
2760
2780
2761 Verify the integrity of the current repository.
2781 Verify the integrity of the current repository.
2762
2782
2763 This will perform an extensive check of the repository's
2783 This will perform an extensive check of the repository's
2764 integrity, validating the hashes and checksums of each entry in
2784 integrity, validating the hashes and checksums of each entry in
2765 the changelog, manifest, and tracked files, as well as the
2785 the changelog, manifest, and tracked files, as well as the
2766 integrity of their crosslinks and indices.
2786 integrity of their crosslinks and indices.
2767 """
2787 """
2768 return repo.verify()
2788 return repo.verify()
2769
2789
2770 # Command options and aliases are listed here, alphabetically
2790 # Command options and aliases are listed here, alphabetically
2771
2791
2772 table = {
2792 table = {
2773 "^add":
2793 "^add":
2774 (add,
2794 (add,
2775 [('I', 'include', [], _('include names matching the given patterns')),
2795 [('I', 'include', [], _('include names matching the given patterns')),
2776 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2796 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2777 _('hg add [OPTION]... [FILE]...')),
2797 _('hg add [OPTION]... [FILE]...')),
2778 "debugaddremove|addremove":
2798 "debugaddremove|addremove":
2779 (addremove,
2799 (addremove,
2780 [('I', 'include', [], _('include names matching the given patterns')),
2800 [('I', 'include', [], _('include names matching the given patterns')),
2781 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2801 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2782 _('hg addremove [OPTION]... [FILE]...')),
2802 _('hg addremove [OPTION]... [FILE]...')),
2783 "^annotate":
2803 "^annotate":
2784 (annotate,
2804 (annotate,
2785 [('r', 'rev', '', _('annotate the specified revision')),
2805 [('r', 'rev', '', _('annotate the specified revision')),
2786 ('a', 'text', None, _('treat all files as text')),
2806 ('a', 'text', None, _('treat all files as text')),
2787 ('u', 'user', None, _('list the author')),
2807 ('u', 'user', None, _('list the author')),
2788 ('d', 'date', None, _('list the date')),
2808 ('d', 'date', None, _('list the date')),
2789 ('n', 'number', None, _('list the revision number (default)')),
2809 ('n', 'number', None, _('list the revision number (default)')),
2790 ('c', 'changeset', None, _('list the changeset')),
2810 ('c', 'changeset', None, _('list the changeset')),
2791 ('I', 'include', [], _('include names matching the given patterns')),
2811 ('I', 'include', [], _('include names matching the given patterns')),
2792 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2812 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2793 _('hg annotate [-r REV] [-a] [-u] [-d] [-n] [-c] FILE...')),
2813 _('hg annotate [-r REV] [-a] [-u] [-d] [-n] [-c] FILE...')),
2794 "archive":
2814 "archive":
2795 (archive,
2815 (archive,
2796 [('', 'no-decode', None, _('do not pass files through decoders')),
2816 [('', 'no-decode', None, _('do not pass files through decoders')),
2797 ('p', 'prefix', '', _('directory prefix for files in archive')),
2817 ('p', 'prefix', '', _('directory prefix for files in archive')),
2798 ('r', 'rev', '', _('revision to distribute')),
2818 ('r', 'rev', '', _('revision to distribute')),
2799 ('t', 'type', '', _('type of distribution to create')),
2819 ('t', 'type', '', _('type of distribution to create')),
2800 ('I', 'include', [], _('include names matching the given patterns')),
2820 ('I', 'include', [], _('include names matching the given patterns')),
2801 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2821 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2802 _('hg archive [OPTION]... DEST')),
2822 _('hg archive [OPTION]... DEST')),
2803 "backout":
2823 "backout":
2804 (backout,
2824 (backout,
2805 [('', 'merge', None,
2825 [('', 'merge', None,
2806 _('merge with old dirstate parent after backout')),
2826 _('merge with old dirstate parent after backout')),
2807 ('m', 'message', '', _('use <text> as commit message')),
2827 ('m', 'message', '', _('use <text> as commit message')),
2808 ('l', 'logfile', '', _('read commit message from <file>')),
2828 ('l', 'logfile', '', _('read commit message from <file>')),
2809 ('d', 'date', '', _('record datecode as commit date')),
2829 ('d', 'date', '', _('record datecode as commit date')),
2810 ('u', 'user', '', _('record user as committer')),
2830 ('u', 'user', '', _('record user as committer')),
2811 ('I', 'include', [], _('include names matching the given patterns')),
2831 ('I', 'include', [], _('include names matching the given patterns')),
2812 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2832 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2813 _('hg backout [OPTION]... REV')),
2833 _('hg backout [OPTION]... REV')),
2814 "bundle":
2834 "bundle":
2815 (bundle,
2835 (bundle,
2816 [('f', 'force', None,
2836 [('f', 'force', None,
2817 _('run even when remote repository is unrelated'))],
2837 _('run even when remote repository is unrelated'))],
2818 _('hg bundle FILE DEST')),
2838 _('hg bundle FILE DEST')),
2819 "cat":
2839 "cat":
2820 (cat,
2840 (cat,
2821 [('o', 'output', '', _('print output to file with formatted name')),
2841 [('o', 'output', '', _('print output to file with formatted name')),
2822 ('r', 'rev', '', _('print the given revision')),
2842 ('r', 'rev', '', _('print the given revision')),
2823 ('I', 'include', [], _('include names matching the given patterns')),
2843 ('I', 'include', [], _('include names matching the given patterns')),
2824 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2844 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2825 _('hg cat [OPTION]... FILE...')),
2845 _('hg cat [OPTION]... FILE...')),
2826 "^clone":
2846 "^clone":
2827 (clone,
2847 (clone,
2828 [('U', 'noupdate', None, _('do not update the new working directory')),
2848 [('U', 'noupdate', None, _('do not update the new working directory')),
2829 ('r', 'rev', [],
2849 ('r', 'rev', [],
2830 _('a changeset you would like to have after cloning')),
2850 _('a changeset you would like to have after cloning')),
2831 ('', 'pull', None, _('use pull protocol to copy metadata')),
2851 ('', 'pull', None, _('use pull protocol to copy metadata')),
2832 ('e', 'ssh', '', _('specify ssh command to use')),
2852 ('e', 'ssh', '', _('specify ssh command to use')),
2833 ('', 'remotecmd', '',
2853 ('', 'remotecmd', '',
2834 _('specify hg command to run on the remote side'))],
2854 _('specify hg command to run on the remote side'))],
2835 _('hg clone [OPTION]... SOURCE [DEST]')),
2855 _('hg clone [OPTION]... SOURCE [DEST]')),
2836 "^commit|ci":
2856 "^commit|ci":
2837 (commit,
2857 (commit,
2838 [('A', 'addremove', None,
2858 [('A', 'addremove', None,
2839 _('mark new/missing files as added/removed before committing')),
2859 _('mark new/missing files as added/removed before committing')),
2840 ('m', 'message', '', _('use <text> as commit message')),
2860 ('m', 'message', '', _('use <text> as commit message')),
2841 ('l', 'logfile', '', _('read the commit message from <file>')),
2861 ('l', 'logfile', '', _('read the commit message from <file>')),
2842 ('d', 'date', '', _('record datecode as commit date')),
2862 ('d', 'date', '', _('record datecode as commit date')),
2843 ('u', 'user', '', _('record user as commiter')),
2863 ('u', 'user', '', _('record user as commiter')),
2844 ('I', 'include', [], _('include names matching the given patterns')),
2864 ('I', 'include', [], _('include names matching the given patterns')),
2845 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2865 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2846 _('hg commit [OPTION]... [FILE]...')),
2866 _('hg commit [OPTION]... [FILE]...')),
2847 "copy|cp":
2867 "copy|cp":
2848 (copy,
2868 (copy,
2849 [('A', 'after', None, _('record a copy that has already occurred')),
2869 [('A', 'after', None, _('record a copy that has already occurred')),
2850 ('f', 'force', None,
2870 ('f', 'force', None,
2851 _('forcibly copy over an existing managed file')),
2871 _('forcibly copy over an existing managed file')),
2852 ('I', 'include', [], _('include names matching the given patterns')),
2872 ('I', 'include', [], _('include names matching the given patterns')),
2853 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2873 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2854 _('hg copy [OPTION]... [SOURCE]... DEST')),
2874 _('hg copy [OPTION]... [SOURCE]... DEST')),
2855 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2875 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2856 "debugcomplete":
2876 "debugcomplete":
2857 (debugcomplete,
2877 (debugcomplete,
2858 [('o', 'options', None, _('show the command options'))],
2878 [('o', 'options', None, _('show the command options'))],
2859 _('debugcomplete [-o] CMD')),
2879 _('debugcomplete [-o] CMD')),
2860 "debugrebuildstate":
2880 "debugrebuildstate":
2861 (debugrebuildstate,
2881 (debugrebuildstate,
2862 [('r', 'rev', '', _('revision to rebuild to'))],
2882 [('r', 'rev', '', _('revision to rebuild to'))],
2863 _('debugrebuildstate [-r REV] [REV]')),
2883 _('debugrebuildstate [-r REV] [REV]')),
2864 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2884 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2865 "debugconfig": (debugconfig, [], _('debugconfig')),
2885 "debugconfig": (debugconfig, [], _('debugconfig')),
2866 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2886 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2867 "debugstate": (debugstate, [], _('debugstate')),
2887 "debugstate": (debugstate, [], _('debugstate')),
2868 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2888 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2869 "debugindex": (debugindex, [], _('debugindex FILE')),
2889 "debugindex": (debugindex, [], _('debugindex FILE')),
2870 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2890 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2871 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
2891 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
2872 "debugwalk":
2892 "debugwalk":
2873 (debugwalk,
2893 (debugwalk,
2874 [('I', 'include', [], _('include names matching the given patterns')),
2894 [('I', 'include', [], _('include names matching the given patterns')),
2875 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2895 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2876 _('debugwalk [OPTION]... [FILE]...')),
2896 _('debugwalk [OPTION]... [FILE]...')),
2877 "^diff":
2897 "^diff":
2878 (diff,
2898 (diff,
2879 [('r', 'rev', [], _('revision')),
2899 [('r', 'rev', [], _('revision')),
2880 ('a', 'text', None, _('treat all files as text')),
2900 ('a', 'text', None, _('treat all files as text')),
2881 ('p', 'show-function', None,
2901 ('p', 'show-function', None,
2882 _('show which function each change is in')),
2902 _('show which function each change is in')),
2883 ('w', 'ignore-all-space', None,
2903 ('w', 'ignore-all-space', None,
2884 _('ignore white space when comparing lines')),
2904 _('ignore white space when comparing lines')),
2885 ('I', 'include', [], _('include names matching the given patterns')),
2905 ('I', 'include', [], _('include names matching the given patterns')),
2886 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2906 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2887 _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
2907 _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
2888 "^export":
2908 "^export":
2889 (export,
2909 (export,
2890 [('o', 'output', '', _('print output to file with formatted name')),
2910 [('o', 'output', '', _('print output to file with formatted name')),
2891 ('a', 'text', None, _('treat all files as text')),
2911 ('a', 'text', None, _('treat all files as text')),
2892 ('', 'switch-parent', None, _('diff against the second parent'))],
2912 ('', 'switch-parent', None, _('diff against the second parent'))],
2893 _('hg export [-a] [-o OUTFILESPEC] REV...')),
2913 _('hg export [-a] [-o OUTFILESPEC] REV...')),
2894 "debugforget|forget":
2914 "debugforget|forget":
2895 (forget,
2915 (forget,
2896 [('I', 'include', [], _('include names matching the given patterns')),
2916 [('I', 'include', [], _('include names matching the given patterns')),
2897 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2917 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2898 _('hg forget [OPTION]... FILE...')),
2918 _('hg forget [OPTION]... FILE...')),
2899 "grep":
2919 "grep":
2900 (grep,
2920 (grep,
2901 [('0', 'print0', None, _('end fields with NUL')),
2921 [('0', 'print0', None, _('end fields with NUL')),
2902 ('', 'all', None, _('print all revisions that match')),
2922 ('', 'all', None, _('print all revisions that match')),
2903 ('i', 'ignore-case', None, _('ignore case when matching')),
2923 ('i', 'ignore-case', None, _('ignore case when matching')),
2904 ('l', 'files-with-matches', None,
2924 ('l', 'files-with-matches', None,
2905 _('print only filenames and revs that match')),
2925 _('print only filenames and revs that match')),
2906 ('n', 'line-number', None, _('print matching line numbers')),
2926 ('n', 'line-number', None, _('print matching line numbers')),
2907 ('r', 'rev', [], _('search in given revision range')),
2927 ('r', 'rev', [], _('search in given revision range')),
2908 ('u', 'user', None, _('print user who committed change')),
2928 ('u', 'user', None, _('print user who committed change')),
2909 ('I', 'include', [], _('include names matching the given patterns')),
2929 ('I', 'include', [], _('include names matching the given patterns')),
2910 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2930 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2911 _('hg grep [OPTION]... PATTERN [FILE]...')),
2931 _('hg grep [OPTION]... PATTERN [FILE]...')),
2912 "heads":
2932 "heads":
2913 (heads,
2933 (heads,
2914 [('b', 'branches', None, _('show branches')),
2934 [('b', 'branches', None, _('show branches')),
2915 ('', 'style', '', _('display using template map file')),
2935 ('', 'style', '', _('display using template map file')),
2916 ('r', 'rev', '', _('show only heads which are descendants of rev')),
2936 ('r', 'rev', '', _('show only heads which are descendants of rev')),
2917 ('', 'template', '', _('display with template'))],
2937 ('', 'template', '', _('display with template'))],
2918 _('hg heads [-b] [-r <rev>]')),
2938 _('hg heads [-b] [-r <rev>]')),
2919 "help": (help_, [], _('hg help [COMMAND]')),
2939 "help": (help_, [], _('hg help [COMMAND]')),
2920 "identify|id": (identify, [], _('hg identify')),
2940 "identify|id": (identify, [], _('hg identify')),
2921 "import|patch":
2941 "import|patch":
2922 (import_,
2942 (import_,
2923 [('p', 'strip', 1,
2943 [('p', 'strip', 1,
2924 _('directory strip option for patch. This has the same\n'
2944 _('directory strip option for patch. This has the same\n'
2925 'meaning as the corresponding patch option')),
2945 'meaning as the corresponding patch option')),
2926 ('b', 'base', '', _('base path')),
2946 ('b', 'base', '', _('base path')),
2927 ('f', 'force', None,
2947 ('f', 'force', None,
2928 _('skip check for outstanding uncommitted changes'))],
2948 _('skip check for outstanding uncommitted changes'))],
2929 _('hg import [-p NUM] [-b BASE] [-f] PATCH...')),
2949 _('hg import [-p NUM] [-b BASE] [-f] PATCH...')),
2930 "incoming|in": (incoming,
2950 "incoming|in": (incoming,
2931 [('M', 'no-merges', None, _('do not show merges')),
2951 [('M', 'no-merges', None, _('do not show merges')),
2932 ('f', 'force', None,
2952 ('f', 'force', None,
2933 _('run even when remote repository is unrelated')),
2953 _('run even when remote repository is unrelated')),
2934 ('', 'style', '', _('display using template map file')),
2954 ('', 'style', '', _('display using template map file')),
2935 ('n', 'newest-first', None, _('show newest record first')),
2955 ('n', 'newest-first', None, _('show newest record first')),
2936 ('', 'bundle', '', _('file to store the bundles into')),
2956 ('', 'bundle', '', _('file to store the bundles into')),
2937 ('p', 'patch', None, _('show patch')),
2957 ('p', 'patch', None, _('show patch')),
2938 ('', 'template', '', _('display with template')),
2958 ('', 'template', '', _('display with template')),
2939 ('e', 'ssh', '', _('specify ssh command to use')),
2959 ('e', 'ssh', '', _('specify ssh command to use')),
2940 ('', 'remotecmd', '',
2960 ('', 'remotecmd', '',
2941 _('specify hg command to run on the remote side'))],
2961 _('specify hg command to run on the remote side'))],
2942 _('hg incoming [-p] [-n] [-M] [--bundle FILENAME] [SOURCE]')),
2962 _('hg incoming [-p] [-n] [-M] [--bundle FILENAME] [SOURCE]')),
2943 "^init": (init, [], _('hg init [DEST]')),
2963 "^init": (init, [], _('hg init [DEST]')),
2944 "locate":
2964 "locate":
2945 (locate,
2965 (locate,
2946 [('r', 'rev', '', _('search the repository as it stood at rev')),
2966 [('r', 'rev', '', _('search the repository as it stood at rev')),
2947 ('0', 'print0', None,
2967 ('0', 'print0', None,
2948 _('end filenames with NUL, for use with xargs')),
2968 _('end filenames with NUL, for use with xargs')),
2949 ('f', 'fullpath', None,
2969 ('f', 'fullpath', None,
2950 _('print complete paths from the filesystem root')),
2970 _('print complete paths from the filesystem root')),
2951 ('I', 'include', [], _('include names matching the given patterns')),
2971 ('I', 'include', [], _('include names matching the given patterns')),
2952 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2972 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2953 _('hg locate [OPTION]... [PATTERN]...')),
2973 _('hg locate [OPTION]... [PATTERN]...')),
2954 "^log|history":
2974 "^log|history":
2955 (log,
2975 (log,
2956 [('b', 'branches', None, _('show branches')),
2976 [('b', 'branches', None, _('show branches')),
2957 ('k', 'keyword', [], _('search for a keyword')),
2977 ('k', 'keyword', [], _('search for a keyword')),
2958 ('l', 'limit', '', _('limit number of changes displayed')),
2978 ('l', 'limit', '', _('limit number of changes displayed')),
2959 ('r', 'rev', [], _('show the specified revision or range')),
2979 ('r', 'rev', [], _('show the specified revision or range')),
2960 ('M', 'no-merges', None, _('do not show merges')),
2980 ('M', 'no-merges', None, _('do not show merges')),
2961 ('', 'style', '', _('display using template map file')),
2981 ('', 'style', '', _('display using template map file')),
2962 ('m', 'only-merges', None, _('show only merges')),
2982 ('m', 'only-merges', None, _('show only merges')),
2963 ('p', 'patch', None, _('show patch')),
2983 ('p', 'patch', None, _('show patch')),
2964 ('', 'template', '', _('display with template')),
2984 ('', 'template', '', _('display with template')),
2965 ('I', 'include', [], _('include names matching the given patterns')),
2985 ('I', 'include', [], _('include names matching the given patterns')),
2966 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2986 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2967 _('hg log [OPTION]... [FILE]')),
2987 _('hg log [OPTION]... [FILE]')),
2968 "manifest": (manifest, [], _('hg manifest [REV]')),
2988 "manifest": (manifest, [], _('hg manifest [REV]')),
2969 "merge":
2989 "merge":
2970 (merge,
2990 (merge,
2971 [('b', 'branch', '', _('merge with head of a specific branch')),
2991 [('b', 'branch', '', _('merge with head of a specific branch')),
2972 ('f', 'force', None, _('force a merge with outstanding changes'))],
2992 ('f', 'force', None, _('force a merge with outstanding changes'))],
2973 _('hg merge [-b TAG] [-f] [REV]')),
2993 _('hg merge [-b TAG] [-f] [REV]')),
2974 "outgoing|out": (outgoing,
2994 "outgoing|out": (outgoing,
2975 [('M', 'no-merges', None, _('do not show merges')),
2995 [('M', 'no-merges', None, _('do not show merges')),
2976 ('f', 'force', None,
2996 ('f', 'force', None,
2977 _('run even when remote repository is unrelated')),
2997 _('run even when remote repository is unrelated')),
2978 ('p', 'patch', None, _('show patch')),
2998 ('p', 'patch', None, _('show patch')),
2979 ('', 'style', '', _('display using template map file')),
2999 ('', 'style', '', _('display using template map file')),
2980 ('n', 'newest-first', None, _('show newest record first')),
3000 ('n', 'newest-first', None, _('show newest record first')),
2981 ('', 'template', '', _('display with template')),
3001 ('', 'template', '', _('display with template')),
2982 ('e', 'ssh', '', _('specify ssh command to use')),
3002 ('e', 'ssh', '', _('specify ssh command to use')),
2983 ('', 'remotecmd', '',
3003 ('', 'remotecmd', '',
2984 _('specify hg command to run on the remote side'))],
3004 _('specify hg command to run on the remote side'))],
2985 _('hg outgoing [-M] [-p] [-n] [DEST]')),
3005 _('hg outgoing [-M] [-p] [-n] [DEST]')),
2986 "^parents":
3006 "^parents":
2987 (parents,
3007 (parents,
2988 [('b', 'branches', None, _('show branches')),
3008 [('b', 'branches', None, _('show branches')),
2989 ('', 'style', '', _('display using template map file')),
3009 ('', 'style', '', _('display using template map file')),
2990 ('', 'template', '', _('display with template'))],
3010 ('', 'template', '', _('display with template'))],
2991 _('hg parents [-b] [REV]')),
3011 _('hg parents [-b] [REV]')),
2992 "paths": (paths, [], _('hg paths [NAME]')),
3012 "paths": (paths, [], _('hg paths [NAME]')),
2993 "^pull":
3013 "^pull":
2994 (pull,
3014 (pull,
2995 [('u', 'update', None,
3015 [('u', 'update', None,
2996 _('update the working directory to tip after pull')),
3016 _('update the working directory to tip after pull')),
2997 ('e', 'ssh', '', _('specify ssh command to use')),
3017 ('e', 'ssh', '', _('specify ssh command to use')),
2998 ('f', 'force', None,
3018 ('f', 'force', None,
2999 _('run even when remote repository is unrelated')),
3019 _('run even when remote repository is unrelated')),
3000 ('r', 'rev', [], _('a specific revision you would like to pull')),
3020 ('r', 'rev', [], _('a specific revision you would like to pull')),
3001 ('', 'remotecmd', '',
3021 ('', 'remotecmd', '',
3002 _('specify hg command to run on the remote side'))],
3022 _('specify hg command to run on the remote side'))],
3003 _('hg pull [-u] [-e FILE] [-r REV]... [--remotecmd FILE] [SOURCE]')),
3023 _('hg pull [-u] [-e FILE] [-r REV]... [--remotecmd FILE] [SOURCE]')),
3004 "^push":
3024 "^push":
3005 (push,
3025 (push,
3006 [('f', 'force', None, _('force push')),
3026 [('f', 'force', None, _('force push')),
3007 ('e', 'ssh', '', _('specify ssh command to use')),
3027 ('e', 'ssh', '', _('specify ssh command to use')),
3008 ('r', 'rev', [], _('a specific revision you would like to push')),
3028 ('r', 'rev', [], _('a specific revision you would like to push')),
3009 ('', 'remotecmd', '',
3029 ('', 'remotecmd', '',
3010 _('specify hg command to run on the remote side'))],
3030 _('specify hg command to run on the remote side'))],
3011 _('hg push [-f] [-e FILE] [-r REV]... [--remotecmd FILE] [DEST]')),
3031 _('hg push [-f] [-e FILE] [-r REV]... [--remotecmd FILE] [DEST]')),
3012 "debugrawcommit|rawcommit":
3032 "debugrawcommit|rawcommit":
3013 (rawcommit,
3033 (rawcommit,
3014 [('p', 'parent', [], _('parent')),
3034 [('p', 'parent', [], _('parent')),
3015 ('d', 'date', '', _('date code')),
3035 ('d', 'date', '', _('date code')),
3016 ('u', 'user', '', _('user')),
3036 ('u', 'user', '', _('user')),
3017 ('F', 'files', '', _('file list')),
3037 ('F', 'files', '', _('file list')),
3018 ('m', 'message', '', _('commit message')),
3038 ('m', 'message', '', _('commit message')),
3019 ('l', 'logfile', '', _('commit message file'))],
3039 ('l', 'logfile', '', _('commit message file'))],
3020 _('hg debugrawcommit [OPTION]... [FILE]...')),
3040 _('hg debugrawcommit [OPTION]... [FILE]...')),
3021 "recover": (recover, [], _('hg recover')),
3041 "recover": (recover, [], _('hg recover')),
3022 "^remove|rm":
3042 "^remove|rm":
3023 (remove,
3043 (remove,
3024 [('A', 'after', None, _('record remove that has already occurred')),
3044 [('A', 'after', None, _('record remove that has already occurred')),
3025 ('f', 'force', None, _('remove file even if modified')),
3045 ('f', 'force', None, _('remove file even if modified')),
3026 ('I', 'include', [], _('include names matching the given patterns')),
3046 ('I', 'include', [], _('include names matching the given patterns')),
3027 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3047 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3028 _('hg remove [OPTION]... FILE...')),
3048 _('hg remove [OPTION]... FILE...')),
3029 "rename|mv":
3049 "rename|mv":
3030 (rename,
3050 (rename,
3031 [('A', 'after', None, _('record a rename that has already occurred')),
3051 [('A', 'after', None, _('record a rename that has already occurred')),
3032 ('f', 'force', None,
3052 ('f', 'force', None,
3033 _('forcibly copy over an existing managed file')),
3053 _('forcibly copy over an existing managed file')),
3034 ('I', 'include', [], _('include names matching the given patterns')),
3054 ('I', 'include', [], _('include names matching the given patterns')),
3035 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3055 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3036 _('hg rename [OPTION]... SOURCE... DEST')),
3056 _('hg rename [OPTION]... SOURCE... DEST')),
3037 "^revert":
3057 "^revert":
3038 (revert,
3058 (revert,
3039 [('r', 'rev', '', _('revision to revert to')),
3059 [('r', 'rev', '', _('revision to revert to')),
3040 ('', 'no-backup', None, _('do not save backup copies of files')),
3060 ('', 'no-backup', None, _('do not save backup copies of files')),
3041 ('I', 'include', [], _('include names matching given patterns')),
3061 ('I', 'include', [], _('include names matching given patterns')),
3042 ('X', 'exclude', [], _('exclude names matching given patterns'))],
3062 ('X', 'exclude', [], _('exclude names matching given patterns'))],
3043 _('hg revert [-r REV] [NAME]...')),
3063 _('hg revert [-r REV] [NAME]...')),
3044 "rollback": (rollback, [], _('hg rollback')),
3064 "rollback": (rollback, [], _('hg rollback')),
3045 "root": (root, [], _('hg root')),
3065 "root": (root, [], _('hg root')),
3046 "^serve":
3066 "^serve":
3047 (serve,
3067 (serve,
3048 [('A', 'accesslog', '', _('name of access log file to write to')),
3068 [('A', 'accesslog', '', _('name of access log file to write to')),
3049 ('d', 'daemon', None, _('run server in background')),
3069 ('d', 'daemon', None, _('run server in background')),
3050 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3070 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3051 ('E', 'errorlog', '', _('name of error log file to write to')),
3071 ('E', 'errorlog', '', _('name of error log file to write to')),
3052 ('p', 'port', 0, _('port to use (default: 8000)')),
3072 ('p', 'port', 0, _('port to use (default: 8000)')),
3053 ('a', 'address', '', _('address to use')),
3073 ('a', 'address', '', _('address to use')),
3054 ('n', 'name', '',
3074 ('n', 'name', '',
3055 _('name to show in web pages (default: working dir)')),
3075 _('name to show in web pages (default: working dir)')),
3056 ('', 'webdir-conf', '', _('name of the webdir config file'
3076 ('', 'webdir-conf', '', _('name of the webdir config file'
3057 ' (serve more than one repo)')),
3077 ' (serve more than one repo)')),
3058 ('', 'pid-file', '', _('name of file to write process ID to')),
3078 ('', 'pid-file', '', _('name of file to write process ID to')),
3059 ('', 'stdio', None, _('for remote clients')),
3079 ('', 'stdio', None, _('for remote clients')),
3060 ('t', 'templates', '', _('web templates to use')),
3080 ('t', 'templates', '', _('web templates to use')),
3061 ('', 'style', '', _('template style to use')),
3081 ('', 'style', '', _('template style to use')),
3062 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
3082 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
3063 _('hg serve [OPTION]...')),
3083 _('hg serve [OPTION]...')),
3064 "^status|st":
3084 "^status|st":
3065 (status,
3085 (status,
3066 [('m', 'modified', None, _('show only modified files')),
3086 [('m', 'modified', None, _('show only modified files')),
3067 ('a', 'added', None, _('show only added files')),
3087 ('a', 'added', None, _('show only added files')),
3068 ('r', 'removed', None, _('show only removed files')),
3088 ('r', 'removed', None, _('show only removed files')),
3069 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3089 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3070 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3090 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3071 ('i', 'ignored', None, _('show ignored files')),
3091 ('i', 'ignored', None, _('show ignored files')),
3072 ('n', 'no-status', None, _('hide status prefix')),
3092 ('n', 'no-status', None, _('hide status prefix')),
3073 ('0', 'print0', None,
3093 ('0', 'print0', None,
3074 _('end filenames with NUL, for use with xargs')),
3094 _('end filenames with NUL, for use with xargs')),
3075 ('I', 'include', [], _('include names matching the given patterns')),
3095 ('I', 'include', [], _('include names matching the given patterns')),
3076 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3096 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3077 _('hg status [OPTION]... [FILE]...')),
3097 _('hg status [OPTION]... [FILE]...')),
3078 "tag":
3098 "tag":
3079 (tag,
3099 (tag,
3080 [('l', 'local', None, _('make the tag local')),
3100 [('l', 'local', None, _('make the tag local')),
3081 ('m', 'message', '', _('message for tag commit log entry')),
3101 ('m', 'message', '', _('message for tag commit log entry')),
3082 ('d', 'date', '', _('record datecode as commit date')),
3102 ('d', 'date', '', _('record datecode as commit date')),
3083 ('u', 'user', '', _('record user as commiter')),
3103 ('u', 'user', '', _('record user as commiter')),
3084 ('r', 'rev', '', _('revision to tag'))],
3104 ('r', 'rev', '', _('revision to tag'))],
3085 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
3105 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
3086 "tags": (tags, [], _('hg tags')),
3106 "tags": (tags, [], _('hg tags')),
3087 "tip":
3107 "tip":
3088 (tip,
3108 (tip,
3089 [('b', 'branches', None, _('show branches')),
3109 [('b', 'branches', None, _('show branches')),
3090 ('', 'style', '', _('display using template map file')),
3110 ('', 'style', '', _('display using template map file')),
3091 ('p', 'patch', None, _('show patch')),
3111 ('p', 'patch', None, _('show patch')),
3092 ('', 'template', '', _('display with template'))],
3112 ('', 'template', '', _('display with template'))],
3093 _('hg tip [-b] [-p]')),
3113 _('hg tip [-b] [-p]')),
3094 "unbundle":
3114 "unbundle":
3095 (unbundle,
3115 (unbundle,
3096 [('u', 'update', None,
3116 [('u', 'update', None,
3097 _('update the working directory to tip after unbundle'))],
3117 _('update the working directory to tip after unbundle'))],
3098 _('hg unbundle [-u] FILE')),
3118 _('hg unbundle [-u] FILE')),
3099 "debugundo|undo": (undo, [], _('hg undo')),
3119 "debugundo|undo": (undo, [], _('hg undo')),
3100 "^update|up|checkout|co":
3120 "^update|up|checkout|co":
3101 (update,
3121 (update,
3102 [('b', 'branch', '', _('checkout the head of a specific branch')),
3122 [('b', 'branch', '', _('checkout the head of a specific branch')),
3103 ('m', 'merge', None, _('allow merging of branches')),
3123 ('m', 'merge', None, _('allow merging of branches (DEPRECATED)')),
3104 ('C', 'clean', None, _('overwrite locally modified files')),
3124 ('C', 'clean', None, _('overwrite locally modified files')),
3105 ('f', 'force', None, _('force a merge with outstanding changes'))],
3125 ('f', 'force', None, _('force a merge with outstanding changes'))],
3106 _('hg update [-b TAG] [-m] [-C] [-f] [REV]')),
3126 _('hg update [-b TAG] [-m] [-C] [-f] [REV]')),
3107 "verify": (verify, [], _('hg verify')),
3127 "verify": (verify, [], _('hg verify')),
3108 "version": (show_version, [], _('hg version')),
3128 "version": (show_version, [], _('hg version')),
3109 }
3129 }
3110
3130
3111 globalopts = [
3131 globalopts = [
3112 ('R', 'repository', '',
3132 ('R', 'repository', '',
3113 _('repository root directory or symbolic path name')),
3133 _('repository root directory or symbolic path name')),
3114 ('', 'cwd', '', _('change working directory')),
3134 ('', 'cwd', '', _('change working directory')),
3115 ('y', 'noninteractive', None,
3135 ('y', 'noninteractive', None,
3116 _('do not prompt, assume \'yes\' for any required answers')),
3136 _('do not prompt, assume \'yes\' for any required answers')),
3117 ('q', 'quiet', None, _('suppress output')),
3137 ('q', 'quiet', None, _('suppress output')),
3118 ('v', 'verbose', None, _('enable additional output')),
3138 ('v', 'verbose', None, _('enable additional output')),
3139 ('', 'config', [], _('set/override config option')),
3119 ('', 'debug', None, _('enable debugging output')),
3140 ('', 'debug', None, _('enable debugging output')),
3120 ('', 'debugger', None, _('start debugger')),
3141 ('', 'debugger', None, _('start debugger')),
3121 ('', 'traceback', None, _('print traceback on exception')),
3142 ('', 'traceback', None, _('print traceback on exception')),
3122 ('', 'time', None, _('time how long the command takes')),
3143 ('', 'time', None, _('time how long the command takes')),
3123 ('', 'profile', None, _('print command execution profile')),
3144 ('', 'profile', None, _('print command execution profile')),
3124 ('', 'version', None, _('output version information and exit')),
3145 ('', 'version', None, _('output version information and exit')),
3125 ('h', 'help', None, _('display help and exit')),
3146 ('h', 'help', None, _('display help and exit')),
3126 ]
3147 ]
3127
3148
3128 norepo = ("clone init version help debugancestor debugcomplete debugdata"
3149 norepo = ("clone init version help debugancestor debugcomplete debugdata"
3129 " debugindex debugindexdot")
3150 " debugindex debugindexdot")
3130 optionalrepo = ("paths serve debugconfig")
3151 optionalrepo = ("paths serve debugconfig")
3131
3152
3132 def findpossible(cmd):
3153 def findpossible(cmd):
3133 """
3154 """
3134 Return cmd -> (aliases, command table entry)
3155 Return cmd -> (aliases, command table entry)
3135 for each matching command.
3156 for each matching command.
3136 Return debug commands (or their aliases) only if no normal command matches.
3157 Return debug commands (or their aliases) only if no normal command matches.
3137 """
3158 """
3138 choice = {}
3159 choice = {}
3139 debugchoice = {}
3160 debugchoice = {}
3140 for e in table.keys():
3161 for e in table.keys():
3141 aliases = e.lstrip("^").split("|")
3162 aliases = e.lstrip("^").split("|")
3142 found = None
3163 found = None
3143 if cmd in aliases:
3164 if cmd in aliases:
3144 found = cmd
3165 found = cmd
3145 else:
3166 else:
3146 for a in aliases:
3167 for a in aliases:
3147 if a.startswith(cmd):
3168 if a.startswith(cmd):
3148 found = a
3169 found = a
3149 break
3170 break
3150 if found is not None:
3171 if found is not None:
3151 if aliases[0].startswith("debug"):
3172 if aliases[0].startswith("debug"):
3152 debugchoice[found] = (aliases, table[e])
3173 debugchoice[found] = (aliases, table[e])
3153 else:
3174 else:
3154 choice[found] = (aliases, table[e])
3175 choice[found] = (aliases, table[e])
3155
3176
3156 if not choice and debugchoice:
3177 if not choice and debugchoice:
3157 choice = debugchoice
3178 choice = debugchoice
3158
3179
3159 return choice
3180 return choice
3160
3181
3161 def find(cmd):
3182 def find(cmd):
3162 """Return (aliases, command table entry) for command string."""
3183 """Return (aliases, command table entry) for command string."""
3163 choice = findpossible(cmd)
3184 choice = findpossible(cmd)
3164
3185
3165 if choice.has_key(cmd):
3186 if choice.has_key(cmd):
3166 return choice[cmd]
3187 return choice[cmd]
3167
3188
3168 if len(choice) > 1:
3189 if len(choice) > 1:
3169 clist = choice.keys()
3190 clist = choice.keys()
3170 clist.sort()
3191 clist.sort()
3171 raise AmbiguousCommand(cmd, clist)
3192 raise AmbiguousCommand(cmd, clist)
3172
3193
3173 if choice:
3194 if choice:
3174 return choice.values()[0]
3195 return choice.values()[0]
3175
3196
3176 raise UnknownCommand(cmd)
3197 raise UnknownCommand(cmd)
3177
3198
3178 def catchterm(*args):
3199 def catchterm(*args):
3179 raise util.SignalInterrupt
3200 raise util.SignalInterrupt
3180
3201
3181 def run():
3202 def run():
3182 sys.exit(dispatch(sys.argv[1:]))
3203 sys.exit(dispatch(sys.argv[1:]))
3183
3204
3184 class ParseError(Exception):
3205 class ParseError(Exception):
3185 """Exception raised on errors in parsing the command line."""
3206 """Exception raised on errors in parsing the command line."""
3186
3207
3187 def parse(ui, args):
3208 def parse(ui, args):
3188 options = {}
3209 options = {}
3189 cmdoptions = {}
3210 cmdoptions = {}
3190
3211
3191 try:
3212 try:
3192 args = fancyopts.fancyopts(args, globalopts, options)
3213 args = fancyopts.fancyopts(args, globalopts, options)
3193 except fancyopts.getopt.GetoptError, inst:
3214 except fancyopts.getopt.GetoptError, inst:
3194 raise ParseError(None, inst)
3215 raise ParseError(None, inst)
3195
3216
3196 if args:
3217 if args:
3197 cmd, args = args[0], args[1:]
3218 cmd, args = args[0], args[1:]
3198 aliases, i = find(cmd)
3219 aliases, i = find(cmd)
3199 cmd = aliases[0]
3220 cmd = aliases[0]
3200 defaults = ui.config("defaults", cmd)
3221 defaults = ui.config("defaults", cmd)
3201 if defaults:
3222 if defaults:
3202 args = defaults.split() + args
3223 args = defaults.split() + args
3203 c = list(i[1])
3224 c = list(i[1])
3204 else:
3225 else:
3205 cmd = None
3226 cmd = None
3206 c = []
3227 c = []
3207
3228
3208 # combine global options into local
3229 # combine global options into local
3209 for o in globalopts:
3230 for o in globalopts:
3210 c.append((o[0], o[1], options[o[1]], o[3]))
3231 c.append((o[0], o[1], options[o[1]], o[3]))
3211
3232
3212 try:
3233 try:
3213 args = fancyopts.fancyopts(args, c, cmdoptions)
3234 args = fancyopts.fancyopts(args, c, cmdoptions)
3214 except fancyopts.getopt.GetoptError, inst:
3235 except fancyopts.getopt.GetoptError, inst:
3215 raise ParseError(cmd, inst)
3236 raise ParseError(cmd, inst)
3216
3237
3217 # separate global options back out
3238 # separate global options back out
3218 for o in globalopts:
3239 for o in globalopts:
3219 n = o[1]
3240 n = o[1]
3220 options[n] = cmdoptions[n]
3241 options[n] = cmdoptions[n]
3221 del cmdoptions[n]
3242 del cmdoptions[n]
3222
3243
3223 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
3244 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
3224
3245
3225 def dispatch(args):
3246 def dispatch(args):
3226 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
3247 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
3227 num = getattr(signal, name, None)
3248 num = getattr(signal, name, None)
3228 if num: signal.signal(num, catchterm)
3249 if num: signal.signal(num, catchterm)
3229
3250
3230 try:
3251 try:
3231 u = ui.ui(traceback='--traceback' in sys.argv[1:])
3252 u = ui.ui(traceback='--traceback' in sys.argv[1:])
3232 except util.Abort, inst:
3253 except util.Abort, inst:
3233 sys.stderr.write(_("abort: %s\n") % inst)
3254 sys.stderr.write(_("abort: %s\n") % inst)
3234 return -1
3255 return -1
3235
3256
3236 external = []
3257 external = []
3237 for x in u.extensions():
3258 for x in u.extensions():
3238 try:
3259 try:
3239 if x[1]:
3260 if x[1]:
3240 mod = imp.load_source(x[0], x[1])
3261 mod = imp.load_source(x[0], x[1])
3241 else:
3262 else:
3242 def importh(name):
3263 def importh(name):
3243 mod = __import__(name)
3264 mod = __import__(name)
3244 components = name.split('.')
3265 components = name.split('.')
3245 for comp in components[1:]:
3266 for comp in components[1:]:
3246 mod = getattr(mod, comp)
3267 mod = getattr(mod, comp)
3247 return mod
3268 return mod
3248 try:
3269 try:
3249 mod = importh("hgext." + x[0])
3270 mod = importh("hgext." + x[0])
3250 except ImportError:
3271 except ImportError:
3251 mod = importh(x[0])
3272 mod = importh(x[0])
3252 external.append(mod)
3273 external.append(mod)
3253 except Exception, inst:
3274 except Exception, inst:
3254 u.warn(_("*** failed to import extension %s: %s\n") % (x[0], inst))
3275 u.warn(_("*** failed to import extension %s: %s\n") % (x[0], inst))
3255 if u.traceback:
3276 if u.traceback:
3256 traceback.print_exc()
3277 traceback.print_exc()
3257 return 1
3278 return 1
3258 continue
3279 continue
3259
3280
3260 for x in external:
3281 for x in external:
3261 cmdtable = getattr(x, 'cmdtable', {})
3282 cmdtable = getattr(x, 'cmdtable', {})
3262 for t in cmdtable:
3283 for t in cmdtable:
3263 if t in table:
3284 if t in table:
3264 u.warn(_("module %s overrides %s\n") % (x.__name__, t))
3285 u.warn(_("module %s overrides %s\n") % (x.__name__, t))
3265 table.update(cmdtable)
3286 table.update(cmdtable)
3266
3287
3267 try:
3288 try:
3268 cmd, func, args, options, cmdoptions = parse(u, args)
3289 cmd, func, args, options, cmdoptions = parse(u, args)
3269 if options["time"]:
3290 if options["time"]:
3270 def get_times():
3291 def get_times():
3271 t = os.times()
3292 t = os.times()
3272 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
3293 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
3273 t = (t[0], t[1], t[2], t[3], time.clock())
3294 t = (t[0], t[1], t[2], t[3], time.clock())
3274 return t
3295 return t
3275 s = get_times()
3296 s = get_times()
3276 def print_time():
3297 def print_time():
3277 t = get_times()
3298 t = get_times()
3278 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
3299 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
3279 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
3300 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
3280 atexit.register(print_time)
3301 atexit.register(print_time)
3281
3302
3282 u.updateopts(options["verbose"], options["debug"], options["quiet"],
3303 u.updateopts(options["verbose"], options["debug"], options["quiet"],
3283 not options["noninteractive"], options["traceback"])
3304 not options["noninteractive"], options["traceback"],
3305 options["config"])
3284
3306
3285 # enter the debugger before command execution
3307 # enter the debugger before command execution
3286 if options['debugger']:
3308 if options['debugger']:
3287 pdb.set_trace()
3309 pdb.set_trace()
3288
3310
3289 try:
3311 try:
3290 if options['cwd']:
3312 if options['cwd']:
3291 try:
3313 try:
3292 os.chdir(options['cwd'])
3314 os.chdir(options['cwd'])
3293 except OSError, inst:
3315 except OSError, inst:
3294 raise util.Abort('%s: %s' %
3316 raise util.Abort('%s: %s' %
3295 (options['cwd'], inst.strerror))
3317 (options['cwd'], inst.strerror))
3296
3318
3297 path = u.expandpath(options["repository"]) or ""
3319 path = u.expandpath(options["repository"]) or ""
3298 repo = path and hg.repository(u, path=path) or None
3320 repo = path and hg.repository(u, path=path) or None
3299
3321
3300 if options['help']:
3322 if options['help']:
3301 return help_(u, cmd, options['version'])
3323 return help_(u, cmd, options['version'])
3302 elif options['version']:
3324 elif options['version']:
3303 return show_version(u)
3325 return show_version(u)
3304 elif not cmd:
3326 elif not cmd:
3305 return help_(u, 'shortlist')
3327 return help_(u, 'shortlist')
3306
3328
3307 if cmd not in norepo.split():
3329 if cmd not in norepo.split():
3308 try:
3330 try:
3309 if not repo:
3331 if not repo:
3310 repo = hg.repository(u, path=path)
3332 repo = hg.repository(u, path=path)
3311 u = repo.ui
3333 u = repo.ui
3312 for x in external:
3334 for x in external:
3313 if hasattr(x, 'reposetup'):
3335 if hasattr(x, 'reposetup'):
3314 x.reposetup(u, repo)
3336 x.reposetup(u, repo)
3315 except hg.RepoError:
3337 except hg.RepoError:
3316 if cmd not in optionalrepo.split():
3338 if cmd not in optionalrepo.split():
3317 raise
3339 raise
3318 d = lambda: func(u, repo, *args, **cmdoptions)
3340 d = lambda: func(u, repo, *args, **cmdoptions)
3319 else:
3341 else:
3320 d = lambda: func(u, *args, **cmdoptions)
3342 d = lambda: func(u, *args, **cmdoptions)
3321
3343
3322 try:
3344 try:
3323 if options['profile']:
3345 if options['profile']:
3324 import hotshot, hotshot.stats
3346 import hotshot, hotshot.stats
3325 prof = hotshot.Profile("hg.prof")
3347 prof = hotshot.Profile("hg.prof")
3326 try:
3348 try:
3327 try:
3349 try:
3328 return prof.runcall(d)
3350 return prof.runcall(d)
3329 except:
3351 except:
3330 try:
3352 try:
3331 u.warn(_('exception raised - generating '
3353 u.warn(_('exception raised - generating '
3332 'profile anyway\n'))
3354 'profile anyway\n'))
3333 except:
3355 except:
3334 pass
3356 pass
3335 raise
3357 raise
3336 finally:
3358 finally:
3337 prof.close()
3359 prof.close()
3338 stats = hotshot.stats.load("hg.prof")
3360 stats = hotshot.stats.load("hg.prof")
3339 stats.strip_dirs()
3361 stats.strip_dirs()
3340 stats.sort_stats('time', 'calls')
3362 stats.sort_stats('time', 'calls')
3341 stats.print_stats(40)
3363 stats.print_stats(40)
3342 else:
3364 else:
3343 return d()
3365 return d()
3344 finally:
3366 finally:
3345 u.flush()
3367 u.flush()
3346 except:
3368 except:
3347 # enter the debugger when we hit an exception
3369 # enter the debugger when we hit an exception
3348 if options['debugger']:
3370 if options['debugger']:
3349 pdb.post_mortem(sys.exc_info()[2])
3371 pdb.post_mortem(sys.exc_info()[2])
3350 if u.traceback:
3372 if u.traceback:
3351 traceback.print_exc()
3373 traceback.print_exc()
3352 raise
3374 raise
3353 except ParseError, inst:
3375 except ParseError, inst:
3354 if inst.args[0]:
3376 if inst.args[0]:
3355 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
3377 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
3356 help_(u, inst.args[0])
3378 help_(u, inst.args[0])
3357 else:
3379 else:
3358 u.warn(_("hg: %s\n") % inst.args[1])
3380 u.warn(_("hg: %s\n") % inst.args[1])
3359 help_(u, 'shortlist')
3381 help_(u, 'shortlist')
3360 except AmbiguousCommand, inst:
3382 except AmbiguousCommand, inst:
3361 u.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
3383 u.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
3362 (inst.args[0], " ".join(inst.args[1])))
3384 (inst.args[0], " ".join(inst.args[1])))
3363 except UnknownCommand, inst:
3385 except UnknownCommand, inst:
3364 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
3386 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
3365 help_(u, 'shortlist')
3387 help_(u, 'shortlist')
3366 except hg.RepoError, inst:
3388 except hg.RepoError, inst:
3367 u.warn(_("abort: %s!\n") % inst)
3389 u.warn(_("abort: %s!\n") % inst)
3368 except lock.LockHeld, inst:
3390 except lock.LockHeld, inst:
3369 if inst.errno == errno.ETIMEDOUT:
3391 if inst.errno == errno.ETIMEDOUT:
3370 reason = _('timed out waiting for lock held by %s') % inst.locker
3392 reason = _('timed out waiting for lock held by %s') % inst.locker
3371 else:
3393 else:
3372 reason = _('lock held by %s') % inst.locker
3394 reason = _('lock held by %s') % inst.locker
3373 u.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
3395 u.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
3374 except lock.LockUnavailable, inst:
3396 except lock.LockUnavailable, inst:
3375 u.warn(_("abort: could not lock %s: %s\n") %
3397 u.warn(_("abort: could not lock %s: %s\n") %
3376 (inst.desc or inst.filename, inst.strerror))
3398 (inst.desc or inst.filename, inst.strerror))
3377 except revlog.RevlogError, inst:
3399 except revlog.RevlogError, inst:
3378 u.warn(_("abort: "), inst, "!\n")
3400 u.warn(_("abort: "), inst, "!\n")
3379 except util.SignalInterrupt:
3401 except util.SignalInterrupt:
3380 u.warn(_("killed!\n"))
3402 u.warn(_("killed!\n"))
3381 except KeyboardInterrupt:
3403 except KeyboardInterrupt:
3382 try:
3404 try:
3383 u.warn(_("interrupted!\n"))
3405 u.warn(_("interrupted!\n"))
3384 except IOError, inst:
3406 except IOError, inst:
3385 if inst.errno == errno.EPIPE:
3407 if inst.errno == errno.EPIPE:
3386 if u.debugflag:
3408 if u.debugflag:
3387 u.warn(_("\nbroken pipe\n"))
3409 u.warn(_("\nbroken pipe\n"))
3388 else:
3410 else:
3389 raise
3411 raise
3390 except IOError, inst:
3412 except IOError, inst:
3391 if hasattr(inst, "code"):
3413 if hasattr(inst, "code"):
3392 u.warn(_("abort: %s\n") % inst)
3414 u.warn(_("abort: %s\n") % inst)
3393 elif hasattr(inst, "reason"):
3415 elif hasattr(inst, "reason"):
3394 u.warn(_("abort: error: %s\n") % inst.reason[1])
3416 u.warn(_("abort: error: %s\n") % inst.reason[1])
3395 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
3417 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
3396 if u.debugflag:
3418 if u.debugflag:
3397 u.warn(_("broken pipe\n"))
3419 u.warn(_("broken pipe\n"))
3398 elif getattr(inst, "strerror", None):
3420 elif getattr(inst, "strerror", None):
3399 if getattr(inst, "filename", None):
3421 if getattr(inst, "filename", None):
3400 u.warn(_("abort: %s - %s\n") % (inst.strerror, inst.filename))
3422 u.warn(_("abort: %s - %s\n") % (inst.strerror, inst.filename))
3401 else:
3423 else:
3402 u.warn(_("abort: %s\n") % inst.strerror)
3424 u.warn(_("abort: %s\n") % inst.strerror)
3403 else:
3425 else:
3404 raise
3426 raise
3405 except OSError, inst:
3427 except OSError, inst:
3406 if hasattr(inst, "filename"):
3428 if hasattr(inst, "filename"):
3407 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
3429 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
3408 else:
3430 else:
3409 u.warn(_("abort: %s\n") % inst.strerror)
3431 u.warn(_("abort: %s\n") % inst.strerror)
3410 except util.Abort, inst:
3432 except util.Abort, inst:
3411 u.warn(_('abort: '), inst.args[0] % inst.args[1:], '\n')
3433 u.warn(_('abort: '), inst.args[0] % inst.args[1:], '\n')
3412 except TypeError, inst:
3434 except TypeError, inst:
3413 # was this an argument error?
3435 # was this an argument error?
3414 tb = traceback.extract_tb(sys.exc_info()[2])
3436 tb = traceback.extract_tb(sys.exc_info()[2])
3415 if len(tb) > 2: # no
3437 if len(tb) > 2: # no
3416 raise
3438 raise
3417 u.debug(inst, "\n")
3439 u.debug(inst, "\n")
3418 u.warn(_("%s: invalid arguments\n") % cmd)
3440 u.warn(_("%s: invalid arguments\n") % cmd)
3419 help_(u, cmd)
3441 help_(u, cmd)
3420 except SystemExit, inst:
3442 except SystemExit, inst:
3421 # Commands shouldn't sys.exit directly, but give a return code.
3443 # Commands shouldn't sys.exit directly, but give a return code.
3422 # Just in case catch this and and pass exit code to caller.
3444 # Just in case catch this and and pass exit code to caller.
3423 return inst.code
3445 return inst.code
3424 except:
3446 except:
3425 u.warn(_("** unknown exception encountered, details follow\n"))
3447 u.warn(_("** unknown exception encountered, details follow\n"))
3426 u.warn(_("** report bug details to mercurial@selenic.com\n"))
3448 u.warn(_("** report bug details to mercurial@selenic.com\n"))
3427 u.warn(_("** Mercurial Distributed SCM (version %s)\n")
3449 u.warn(_("** Mercurial Distributed SCM (version %s)\n")
3428 % version.get_version())
3450 % version.get_version())
3429 raise
3451 raise
3430
3452
3431 return -1
3453 return -1
@@ -1,1142 +1,1142 b''
1 # hgweb.py - web interface to a mercurial repository
1 # hgweb.py - web interface to a mercurial repository
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os, cgi, sys
9 import os, cgi, sys
10 import mimetypes
10 import mimetypes
11 from demandload import demandload
11 from demandload import demandload
12 demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser")
12 demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser")
13 demandload(globals(), "tempfile StringIO BaseHTTPServer util SocketServer")
13 demandload(globals(), "tempfile StringIO BaseHTTPServer util SocketServer")
14 demandload(globals(), "archival mimetypes templater urllib")
14 demandload(globals(), "archival mimetypes templater urllib")
15 from node import *
15 from node import *
16 from i18n import gettext as _
16 from i18n import gettext as _
17
17
18 def splitURI(uri):
18 def splitURI(uri):
19 """ Return path and query splited from uri
19 """ Return path and query splited from uri
20
20
21 Just like CGI environment, the path is unquoted, the query is
21 Just like CGI environment, the path is unquoted, the query is
22 not.
22 not.
23 """
23 """
24 if '?' in uri:
24 if '?' in uri:
25 path, query = uri.split('?', 1)
25 path, query = uri.split('?', 1)
26 else:
26 else:
27 path, query = uri, ''
27 path, query = uri, ''
28 return urllib.unquote(path), query
28 return urllib.unquote(path), query
29
29
30 def up(p):
30 def up(p):
31 if p[0] != "/":
31 if p[0] != "/":
32 p = "/" + p
32 p = "/" + p
33 if p[-1] == "/":
33 if p[-1] == "/":
34 p = p[:-1]
34 p = p[:-1]
35 up = os.path.dirname(p)
35 up = os.path.dirname(p)
36 if up == "/":
36 if up == "/":
37 return "/"
37 return "/"
38 return up + "/"
38 return up + "/"
39
39
40 def get_mtime(repo_path):
40 def get_mtime(repo_path):
41 hg_path = os.path.join(repo_path, ".hg")
41 hg_path = os.path.join(repo_path, ".hg")
42 cl_path = os.path.join(hg_path, "00changelog.i")
42 cl_path = os.path.join(hg_path, "00changelog.i")
43 if os.path.exists(os.path.join(cl_path)):
43 if os.path.exists(os.path.join(cl_path)):
44 return os.stat(cl_path).st_mtime
44 return os.stat(cl_path).st_mtime
45 else:
45 else:
46 return os.stat(hg_path).st_mtime
46 return os.stat(hg_path).st_mtime
47
47
48 def staticfile(directory, fname):
48 def staticfile(directory, fname):
49 """return a file inside directory with guessed content-type header
49 """return a file inside directory with guessed content-type header
50
50
51 fname always uses '/' as directory separator and isn't allowed to
51 fname always uses '/' as directory separator and isn't allowed to
52 contain unusual path components.
52 contain unusual path components.
53 Content-type is guessed using the mimetypes module.
53 Content-type is guessed using the mimetypes module.
54 Return an empty string if fname is illegal or file not found.
54 Return an empty string if fname is illegal or file not found.
55
55
56 """
56 """
57 parts = fname.split('/')
57 parts = fname.split('/')
58 path = directory
58 path = directory
59 for part in parts:
59 for part in parts:
60 if (part in ('', os.curdir, os.pardir) or
60 if (part in ('', os.curdir, os.pardir) or
61 os.sep in part or os.altsep is not None and os.altsep in part):
61 os.sep in part or os.altsep is not None and os.altsep in part):
62 return ""
62 return ""
63 path = os.path.join(path, part)
63 path = os.path.join(path, part)
64 try:
64 try:
65 os.stat(path)
65 os.stat(path)
66 ct = mimetypes.guess_type(path)[0] or "text/plain"
66 ct = mimetypes.guess_type(path)[0] or "text/plain"
67 return "Content-type: %s\n\n%s" % (ct, file(path).read())
67 return "Content-type: %s\n\n%s" % (ct, file(path).read())
68 except (TypeError, OSError):
68 except (TypeError, OSError):
69 # illegal fname or unreadable file
69 # illegal fname or unreadable file
70 return ""
70 return ""
71
71
72 class hgrequest(object):
72 class hgrequest(object):
73 def __init__(self, inp=None, out=None, env=None):
73 def __init__(self, inp=None, out=None, env=None):
74 self.inp = inp or sys.stdin
74 self.inp = inp or sys.stdin
75 self.out = out or sys.stdout
75 self.out = out or sys.stdout
76 self.env = env or os.environ
76 self.env = env or os.environ
77 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
77 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
78
78
79 def write(self, *things):
79 def write(self, *things):
80 for thing in things:
80 for thing in things:
81 if hasattr(thing, "__iter__"):
81 if hasattr(thing, "__iter__"):
82 for part in thing:
82 for part in thing:
83 self.write(part)
83 self.write(part)
84 else:
84 else:
85 try:
85 try:
86 self.out.write(str(thing))
86 self.out.write(str(thing))
87 except socket.error, inst:
87 except socket.error, inst:
88 if inst[0] != errno.ECONNRESET:
88 if inst[0] != errno.ECONNRESET:
89 raise
89 raise
90
90
91 def header(self, headers=[('Content-type','text/html')]):
91 def header(self, headers=[('Content-type','text/html')]):
92 for header in headers:
92 for header in headers:
93 self.out.write("%s: %s\r\n" % header)
93 self.out.write("%s: %s\r\n" % header)
94 self.out.write("\r\n")
94 self.out.write("\r\n")
95
95
96 def httphdr(self, type, file="", size=0):
96 def httphdr(self, type, file="", size=0):
97
97
98 headers = [('Content-type', type)]
98 headers = [('Content-type', type)]
99 if file:
99 if file:
100 headers.append(('Content-disposition', 'attachment; filename=%s' % file))
100 headers.append(('Content-disposition', 'attachment; filename=%s' % file))
101 if size > 0:
101 if size > 0:
102 headers.append(('Content-length', str(size)))
102 headers.append(('Content-length', str(size)))
103 self.header(headers)
103 self.header(headers)
104
104
105 class hgweb(object):
105 class hgweb(object):
106 def __init__(self, repo, name=None):
106 def __init__(self, repo, name=None):
107 if type(repo) == type(""):
107 if type(repo) == type(""):
108 self.repo = hg.repository(ui.ui(), repo)
108 self.repo = hg.repository(ui.ui(), repo)
109 else:
109 else:
110 self.repo = repo
110 self.repo = repo
111
111
112 self.mtime = -1
112 self.mtime = -1
113 self.reponame = name
113 self.reponame = name
114 self.archives = 'zip', 'gz', 'bz2'
114 self.archives = 'zip', 'gz', 'bz2'
115
115
116 def refresh(self):
116 def refresh(self):
117 mtime = get_mtime(self.repo.root)
117 mtime = get_mtime(self.repo.root)
118 if mtime != self.mtime:
118 if mtime != self.mtime:
119 self.mtime = mtime
119 self.mtime = mtime
120 self.repo = hg.repository(self.repo.ui, self.repo.root)
120 self.repo = hg.repository(self.repo.ui, self.repo.root)
121 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
121 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
122 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
122 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
123 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
123 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
124
124
125 def archivelist(self, nodeid):
125 def archivelist(self, nodeid):
126 for i in self.archives:
126 for i in self.archives:
127 if self.repo.ui.configbool("web", "allow" + i, False):
127 if self.repo.ui.configbool("web", "allow" + i, False):
128 yield {"type" : i, "node" : nodeid, "url": ""}
128 yield {"type" : i, "node" : nodeid, "url": ""}
129
129
130 def listfiles(self, files, mf):
130 def listfiles(self, files, mf):
131 for f in files[:self.maxfiles]:
131 for f in files[:self.maxfiles]:
132 yield self.t("filenodelink", node=hex(mf[f]), file=f)
132 yield self.t("filenodelink", node=hex(mf[f]), file=f)
133 if len(files) > self.maxfiles:
133 if len(files) > self.maxfiles:
134 yield self.t("fileellipses")
134 yield self.t("fileellipses")
135
135
136 def listfilediffs(self, files, changeset):
136 def listfilediffs(self, files, changeset):
137 for f in files[:self.maxfiles]:
137 for f in files[:self.maxfiles]:
138 yield self.t("filedifflink", node=hex(changeset), file=f)
138 yield self.t("filedifflink", node=hex(changeset), file=f)
139 if len(files) > self.maxfiles:
139 if len(files) > self.maxfiles:
140 yield self.t("fileellipses")
140 yield self.t("fileellipses")
141
141
142 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
142 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
143 if not rev:
143 if not rev:
144 rev = lambda x: ""
144 rev = lambda x: ""
145 siblings = [s for s in siblings if s != nullid]
145 siblings = [s for s in siblings if s != nullid]
146 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
146 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
147 return
147 return
148 for s in siblings:
148 for s in siblings:
149 yield dict(node=hex(s), rev=rev(s), **args)
149 yield dict(node=hex(s), rev=rev(s), **args)
150
150
151 def renamelink(self, fl, node):
151 def renamelink(self, fl, node):
152 r = fl.renamed(node)
152 r = fl.renamed(node)
153 if r:
153 if r:
154 return [dict(file=r[0], node=hex(r[1]))]
154 return [dict(file=r[0], node=hex(r[1]))]
155 return []
155 return []
156
156
157 def showtag(self, t1, node=nullid, **args):
157 def showtag(self, t1, node=nullid, **args):
158 for t in self.repo.nodetags(node):
158 for t in self.repo.nodetags(node):
159 yield self.t(t1, tag=t, **args)
159 yield self.t(t1, tag=t, **args)
160
160
161 def diff(self, node1, node2, files):
161 def diff(self, node1, node2, files):
162 def filterfiles(filters, files):
162 def filterfiles(filters, files):
163 l = [x for x in files if x in filters]
163 l = [x for x in files if x in filters]
164
164
165 for t in filters:
165 for t in filters:
166 if t and t[-1] != os.sep:
166 if t and t[-1] != os.sep:
167 t += os.sep
167 t += os.sep
168 l += [x for x in files if x.startswith(t)]
168 l += [x for x in files if x.startswith(t)]
169 return l
169 return l
170
170
171 parity = [0]
171 parity = [0]
172 def diffblock(diff, f, fn):
172 def diffblock(diff, f, fn):
173 yield self.t("diffblock",
173 yield self.t("diffblock",
174 lines=prettyprintlines(diff),
174 lines=prettyprintlines(diff),
175 parity=parity[0],
175 parity=parity[0],
176 file=f,
176 file=f,
177 filenode=hex(fn or nullid))
177 filenode=hex(fn or nullid))
178 parity[0] = 1 - parity[0]
178 parity[0] = 1 - parity[0]
179
179
180 def prettyprintlines(diff):
180 def prettyprintlines(diff):
181 for l in diff.splitlines(1):
181 for l in diff.splitlines(1):
182 if l.startswith('+'):
182 if l.startswith('+'):
183 yield self.t("difflineplus", line=l)
183 yield self.t("difflineplus", line=l)
184 elif l.startswith('-'):
184 elif l.startswith('-'):
185 yield self.t("difflineminus", line=l)
185 yield self.t("difflineminus", line=l)
186 elif l.startswith('@'):
186 elif l.startswith('@'):
187 yield self.t("difflineat", line=l)
187 yield self.t("difflineat", line=l)
188 else:
188 else:
189 yield self.t("diffline", line=l)
189 yield self.t("diffline", line=l)
190
190
191 r = self.repo
191 r = self.repo
192 cl = r.changelog
192 cl = r.changelog
193 mf = r.manifest
193 mf = r.manifest
194 change1 = cl.read(node1)
194 change1 = cl.read(node1)
195 change2 = cl.read(node2)
195 change2 = cl.read(node2)
196 mmap1 = mf.read(change1[0])
196 mmap1 = mf.read(change1[0])
197 mmap2 = mf.read(change2[0])
197 mmap2 = mf.read(change2[0])
198 date1 = util.datestr(change1[2])
198 date1 = util.datestr(change1[2])
199 date2 = util.datestr(change2[2])
199 date2 = util.datestr(change2[2])
200
200
201 modified, added, removed, deleted, unknown = r.changes(node1, node2)
201 modified, added, removed, deleted, unknown = r.changes(node1, node2)
202 if files:
202 if files:
203 modified, added, removed = map(lambda x: filterfiles(files, x),
203 modified, added, removed = map(lambda x: filterfiles(files, x),
204 (modified, added, removed))
204 (modified, added, removed))
205
205
206 diffopts = self.repo.ui.diffopts()
206 diffopts = self.repo.ui.diffopts()
207 showfunc = diffopts['showfunc']
207 showfunc = diffopts['showfunc']
208 ignorews = diffopts['ignorews']
208 ignorews = diffopts['ignorews']
209 for f in modified:
209 for f in modified:
210 to = r.file(f).read(mmap1[f])
210 to = r.file(f).read(mmap1[f])
211 tn = r.file(f).read(mmap2[f])
211 tn = r.file(f).read(mmap2[f])
212 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
212 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
213 showfunc=showfunc, ignorews=ignorews), f, tn)
213 showfunc=showfunc, ignorews=ignorews), f, tn)
214 for f in added:
214 for f in added:
215 to = None
215 to = None
216 tn = r.file(f).read(mmap2[f])
216 tn = r.file(f).read(mmap2[f])
217 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
217 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
218 showfunc=showfunc, ignorews=ignorews), f, tn)
218 showfunc=showfunc, ignorews=ignorews), f, tn)
219 for f in removed:
219 for f in removed:
220 to = r.file(f).read(mmap1[f])
220 to = r.file(f).read(mmap1[f])
221 tn = None
221 tn = None
222 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
222 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
223 showfunc=showfunc, ignorews=ignorews), f, tn)
223 showfunc=showfunc, ignorews=ignorews), f, tn)
224
224
225 def changelog(self, pos):
225 def changelog(self, pos):
226 def changenav(**map):
226 def changenav(**map):
227 def seq(factor, maxchanges=None):
227 def seq(factor, maxchanges=None):
228 if maxchanges:
228 if maxchanges:
229 yield maxchanges
229 yield maxchanges
230 if maxchanges >= 20 and maxchanges <= 40:
230 if maxchanges >= 20 and maxchanges <= 40:
231 yield 50
231 yield 50
232 else:
232 else:
233 yield 1 * factor
233 yield 1 * factor
234 yield 3 * factor
234 yield 3 * factor
235 for f in seq(factor * 10):
235 for f in seq(factor * 10):
236 yield f
236 yield f
237
237
238 l = []
238 l = []
239 last = 0
239 last = 0
240 for f in seq(1, self.maxchanges):
240 for f in seq(1, self.maxchanges):
241 if f < self.maxchanges or f <= last:
241 if f < self.maxchanges or f <= last:
242 continue
242 continue
243 if f > count:
243 if f > count:
244 break
244 break
245 last = f
245 last = f
246 r = "%d" % f
246 r = "%d" % f
247 if pos + f < count:
247 if pos + f < count:
248 l.append(("+" + r, pos + f))
248 l.append(("+" + r, pos + f))
249 if pos - f >= 0:
249 if pos - f >= 0:
250 l.insert(0, ("-" + r, pos - f))
250 l.insert(0, ("-" + r, pos - f))
251
251
252 yield {"rev": 0, "label": "(0)"}
252 yield {"rev": 0, "label": "(0)"}
253
253
254 for label, rev in l:
254 for label, rev in l:
255 yield {"label": label, "rev": rev}
255 yield {"label": label, "rev": rev}
256
256
257 yield {"label": "tip", "rev": "tip"}
257 yield {"label": "tip", "rev": "tip"}
258
258
259 def changelist(**map):
259 def changelist(**map):
260 parity = (start - end) & 1
260 parity = (start - end) & 1
261 cl = self.repo.changelog
261 cl = self.repo.changelog
262 l = [] # build a list in forward order for efficiency
262 l = [] # build a list in forward order for efficiency
263 for i in range(start, end):
263 for i in range(start, end):
264 n = cl.node(i)
264 n = cl.node(i)
265 changes = cl.read(n)
265 changes = cl.read(n)
266 hn = hex(n)
266 hn = hex(n)
267
267
268 l.insert(0, {"parity": parity,
268 l.insert(0, {"parity": parity,
269 "author": changes[1],
269 "author": changes[1],
270 "parent": self.siblings(cl.parents(n), cl.rev,
270 "parent": self.siblings(cl.parents(n), cl.rev,
271 cl.rev(n) - 1),
271 cl.rev(n) - 1),
272 "child": self.siblings(cl.children(n), cl.rev,
272 "child": self.siblings(cl.children(n), cl.rev,
273 cl.rev(n) + 1),
273 cl.rev(n) + 1),
274 "changelogtag": self.showtag("changelogtag",n),
274 "changelogtag": self.showtag("changelogtag",n),
275 "manifest": hex(changes[0]),
275 "manifest": hex(changes[0]),
276 "desc": changes[4],
276 "desc": changes[4],
277 "date": changes[2],
277 "date": changes[2],
278 "files": self.listfilediffs(changes[3], n),
278 "files": self.listfilediffs(changes[3], n),
279 "rev": i,
279 "rev": i,
280 "node": hn})
280 "node": hn})
281 parity = 1 - parity
281 parity = 1 - parity
282
282
283 for e in l:
283 for e in l:
284 yield e
284 yield e
285
285
286 cl = self.repo.changelog
286 cl = self.repo.changelog
287 mf = cl.read(cl.tip())[0]
287 mf = cl.read(cl.tip())[0]
288 count = cl.count()
288 count = cl.count()
289 start = max(0, pos - self.maxchanges + 1)
289 start = max(0, pos - self.maxchanges + 1)
290 end = min(count, start + self.maxchanges)
290 end = min(count, start + self.maxchanges)
291 pos = end - 1
291 pos = end - 1
292
292
293 yield self.t('changelog',
293 yield self.t('changelog',
294 changenav=changenav,
294 changenav=changenav,
295 manifest=hex(mf),
295 manifest=hex(mf),
296 rev=pos, changesets=count, entries=changelist,
296 rev=pos, changesets=count, entries=changelist,
297 archives=self.archivelist("tip"))
297 archives=self.archivelist("tip"))
298
298
299 def search(self, query):
299 def search(self, query):
300
300
301 def changelist(**map):
301 def changelist(**map):
302 cl = self.repo.changelog
302 cl = self.repo.changelog
303 count = 0
303 count = 0
304 qw = query.lower().split()
304 qw = query.lower().split()
305
305
306 def revgen():
306 def revgen():
307 for i in range(cl.count() - 1, 0, -100):
307 for i in range(cl.count() - 1, 0, -100):
308 l = []
308 l = []
309 for j in range(max(0, i - 100), i):
309 for j in range(max(0, i - 100), i):
310 n = cl.node(j)
310 n = cl.node(j)
311 changes = cl.read(n)
311 changes = cl.read(n)
312 l.append((n, j, changes))
312 l.append((n, j, changes))
313 l.reverse()
313 l.reverse()
314 for e in l:
314 for e in l:
315 yield e
315 yield e
316
316
317 for n, i, changes in revgen():
317 for n, i, changes in revgen():
318 miss = 0
318 miss = 0
319 for q in qw:
319 for q in qw:
320 if not (q in changes[1].lower() or
320 if not (q in changes[1].lower() or
321 q in changes[4].lower() or
321 q in changes[4].lower() or
322 q in " ".join(changes[3][:20]).lower()):
322 q in " ".join(changes[3][:20]).lower()):
323 miss = 1
323 miss = 1
324 break
324 break
325 if miss:
325 if miss:
326 continue
326 continue
327
327
328 count += 1
328 count += 1
329 hn = hex(n)
329 hn = hex(n)
330
330
331 yield self.t('searchentry',
331 yield self.t('searchentry',
332 parity=count & 1,
332 parity=count & 1,
333 author=changes[1],
333 author=changes[1],
334 parent=self.siblings(cl.parents(n), cl.rev),
334 parent=self.siblings(cl.parents(n), cl.rev),
335 child=self.siblings(cl.children(n), cl.rev),
335 child=self.siblings(cl.children(n), cl.rev),
336 changelogtag=self.showtag("changelogtag",n),
336 changelogtag=self.showtag("changelogtag",n),
337 manifest=hex(changes[0]),
337 manifest=hex(changes[0]),
338 desc=changes[4],
338 desc=changes[4],
339 date=changes[2],
339 date=changes[2],
340 files=self.listfilediffs(changes[3], n),
340 files=self.listfilediffs(changes[3], n),
341 rev=i,
341 rev=i,
342 node=hn)
342 node=hn)
343
343
344 if count >= self.maxchanges:
344 if count >= self.maxchanges:
345 break
345 break
346
346
347 cl = self.repo.changelog
347 cl = self.repo.changelog
348 mf = cl.read(cl.tip())[0]
348 mf = cl.read(cl.tip())[0]
349
349
350 yield self.t('search',
350 yield self.t('search',
351 query=query,
351 query=query,
352 manifest=hex(mf),
352 manifest=hex(mf),
353 entries=changelist)
353 entries=changelist)
354
354
355 def changeset(self, nodeid):
355 def changeset(self, nodeid):
356 cl = self.repo.changelog
356 cl = self.repo.changelog
357 n = self.repo.lookup(nodeid)
357 n = self.repo.lookup(nodeid)
358 nodeid = hex(n)
358 nodeid = hex(n)
359 changes = cl.read(n)
359 changes = cl.read(n)
360 p1 = cl.parents(n)[0]
360 p1 = cl.parents(n)[0]
361
361
362 files = []
362 files = []
363 mf = self.repo.manifest.read(changes[0])
363 mf = self.repo.manifest.read(changes[0])
364 for f in changes[3]:
364 for f in changes[3]:
365 files.append(self.t("filenodelink",
365 files.append(self.t("filenodelink",
366 filenode=hex(mf.get(f, nullid)), file=f))
366 filenode=hex(mf.get(f, nullid)), file=f))
367
367
368 def diff(**map):
368 def diff(**map):
369 yield self.diff(p1, n, None)
369 yield self.diff(p1, n, None)
370
370
371 yield self.t('changeset',
371 yield self.t('changeset',
372 diff=diff,
372 diff=diff,
373 rev=cl.rev(n),
373 rev=cl.rev(n),
374 node=nodeid,
374 node=nodeid,
375 parent=self.siblings(cl.parents(n), cl.rev),
375 parent=self.siblings(cl.parents(n), cl.rev),
376 child=self.siblings(cl.children(n), cl.rev),
376 child=self.siblings(cl.children(n), cl.rev),
377 changesettag=self.showtag("changesettag",n),
377 changesettag=self.showtag("changesettag",n),
378 manifest=hex(changes[0]),
378 manifest=hex(changes[0]),
379 author=changes[1],
379 author=changes[1],
380 desc=changes[4],
380 desc=changes[4],
381 date=changes[2],
381 date=changes[2],
382 files=files,
382 files=files,
383 archives=self.archivelist(nodeid))
383 archives=self.archivelist(nodeid))
384
384
385 def filelog(self, f, filenode):
385 def filelog(self, f, filenode):
386 cl = self.repo.changelog
386 cl = self.repo.changelog
387 fl = self.repo.file(f)
387 fl = self.repo.file(f)
388 filenode = hex(fl.lookup(filenode))
388 filenode = hex(fl.lookup(filenode))
389 count = fl.count()
389 count = fl.count()
390
390
391 def entries(**map):
391 def entries(**map):
392 l = []
392 l = []
393 parity = (count - 1) & 1
393 parity = (count - 1) & 1
394
394
395 for i in range(count):
395 for i in range(count):
396 n = fl.node(i)
396 n = fl.node(i)
397 lr = fl.linkrev(n)
397 lr = fl.linkrev(n)
398 cn = cl.node(lr)
398 cn = cl.node(lr)
399 cs = cl.read(cl.node(lr))
399 cs = cl.read(cl.node(lr))
400
400
401 l.insert(0, {"parity": parity,
401 l.insert(0, {"parity": parity,
402 "filenode": hex(n),
402 "filenode": hex(n),
403 "filerev": i,
403 "filerev": i,
404 "file": f,
404 "file": f,
405 "node": hex(cn),
405 "node": hex(cn),
406 "author": cs[1],
406 "author": cs[1],
407 "date": cs[2],
407 "date": cs[2],
408 "rename": self.renamelink(fl, n),
408 "rename": self.renamelink(fl, n),
409 "parent": self.siblings(fl.parents(n),
409 "parent": self.siblings(fl.parents(n),
410 fl.rev, file=f),
410 fl.rev, file=f),
411 "child": self.siblings(fl.children(n),
411 "child": self.siblings(fl.children(n),
412 fl.rev, file=f),
412 fl.rev, file=f),
413 "desc": cs[4]})
413 "desc": cs[4]})
414 parity = 1 - parity
414 parity = 1 - parity
415
415
416 for e in l:
416 for e in l:
417 yield e
417 yield e
418
418
419 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
419 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
420
420
421 def filerevision(self, f, node):
421 def filerevision(self, f, node):
422 fl = self.repo.file(f)
422 fl = self.repo.file(f)
423 n = fl.lookup(node)
423 n = fl.lookup(node)
424 node = hex(n)
424 node = hex(n)
425 text = fl.read(n)
425 text = fl.read(n)
426 changerev = fl.linkrev(n)
426 changerev = fl.linkrev(n)
427 cl = self.repo.changelog
427 cl = self.repo.changelog
428 cn = cl.node(changerev)
428 cn = cl.node(changerev)
429 cs = cl.read(cn)
429 cs = cl.read(cn)
430 mfn = cs[0]
430 mfn = cs[0]
431
431
432 mt = mimetypes.guess_type(f)[0]
432 mt = mimetypes.guess_type(f)[0]
433 rawtext = text
433 rawtext = text
434 if util.binary(text):
434 if util.binary(text):
435 mt = mt or 'application/octet-stream'
435 mt = mt or 'application/octet-stream'
436 text = "(binary:%s)" % mt
436 text = "(binary:%s)" % mt
437 mt = mt or 'text/plain'
437 mt = mt or 'text/plain'
438
438
439 def lines():
439 def lines():
440 for l, t in enumerate(text.splitlines(1)):
440 for l, t in enumerate(text.splitlines(1)):
441 yield {"line": t,
441 yield {"line": t,
442 "linenumber": "% 6d" % (l + 1),
442 "linenumber": "% 6d" % (l + 1),
443 "parity": l & 1}
443 "parity": l & 1}
444
444
445 yield self.t("filerevision",
445 yield self.t("filerevision",
446 file=f,
446 file=f,
447 filenode=node,
447 filenode=node,
448 path=up(f),
448 path=up(f),
449 text=lines(),
449 text=lines(),
450 raw=rawtext,
450 raw=rawtext,
451 mimetype=mt,
451 mimetype=mt,
452 rev=changerev,
452 rev=changerev,
453 node=hex(cn),
453 node=hex(cn),
454 manifest=hex(mfn),
454 manifest=hex(mfn),
455 author=cs[1],
455 author=cs[1],
456 date=cs[2],
456 date=cs[2],
457 parent=self.siblings(fl.parents(n), fl.rev, file=f),
457 parent=self.siblings(fl.parents(n), fl.rev, file=f),
458 child=self.siblings(fl.children(n), fl.rev, file=f),
458 child=self.siblings(fl.children(n), fl.rev, file=f),
459 rename=self.renamelink(fl, n),
459 rename=self.renamelink(fl, n),
460 permissions=self.repo.manifest.readflags(mfn)[f])
460 permissions=self.repo.manifest.readflags(mfn)[f])
461
461
462 def fileannotate(self, f, node):
462 def fileannotate(self, f, node):
463 bcache = {}
463 bcache = {}
464 ncache = {}
464 ncache = {}
465 fl = self.repo.file(f)
465 fl = self.repo.file(f)
466 n = fl.lookup(node)
466 n = fl.lookup(node)
467 node = hex(n)
467 node = hex(n)
468 changerev = fl.linkrev(n)
468 changerev = fl.linkrev(n)
469
469
470 cl = self.repo.changelog
470 cl = self.repo.changelog
471 cn = cl.node(changerev)
471 cn = cl.node(changerev)
472 cs = cl.read(cn)
472 cs = cl.read(cn)
473 mfn = cs[0]
473 mfn = cs[0]
474
474
475 def annotate(**map):
475 def annotate(**map):
476 parity = 1
476 parity = 1
477 last = None
477 last = None
478 for r, l in fl.annotate(n):
478 for r, l in fl.annotate(n):
479 try:
479 try:
480 cnode = ncache[r]
480 cnode = ncache[r]
481 except KeyError:
481 except KeyError:
482 cnode = ncache[r] = self.repo.changelog.node(r)
482 cnode = ncache[r] = self.repo.changelog.node(r)
483
483
484 try:
484 try:
485 name = bcache[r]
485 name = bcache[r]
486 except KeyError:
486 except KeyError:
487 cl = self.repo.changelog.read(cnode)
487 cl = self.repo.changelog.read(cnode)
488 bcache[r] = name = self.repo.ui.shortuser(cl[1])
488 bcache[r] = name = self.repo.ui.shortuser(cl[1])
489
489
490 if last != cnode:
490 if last != cnode:
491 parity = 1 - parity
491 parity = 1 - parity
492 last = cnode
492 last = cnode
493
493
494 yield {"parity": parity,
494 yield {"parity": parity,
495 "node": hex(cnode),
495 "node": hex(cnode),
496 "rev": r,
496 "rev": r,
497 "author": name,
497 "author": name,
498 "file": f,
498 "file": f,
499 "line": l}
499 "line": l}
500
500
501 yield self.t("fileannotate",
501 yield self.t("fileannotate",
502 file=f,
502 file=f,
503 filenode=node,
503 filenode=node,
504 annotate=annotate,
504 annotate=annotate,
505 path=up(f),
505 path=up(f),
506 rev=changerev,
506 rev=changerev,
507 node=hex(cn),
507 node=hex(cn),
508 manifest=hex(mfn),
508 manifest=hex(mfn),
509 author=cs[1],
509 author=cs[1],
510 date=cs[2],
510 date=cs[2],
511 rename=self.renamelink(fl, n),
511 rename=self.renamelink(fl, n),
512 parent=self.siblings(fl.parents(n), fl.rev, file=f),
512 parent=self.siblings(fl.parents(n), fl.rev, file=f),
513 child=self.siblings(fl.children(n), fl.rev, file=f),
513 child=self.siblings(fl.children(n), fl.rev, file=f),
514 permissions=self.repo.manifest.readflags(mfn)[f])
514 permissions=self.repo.manifest.readflags(mfn)[f])
515
515
516 def manifest(self, mnode, path):
516 def manifest(self, mnode, path):
517 man = self.repo.manifest
517 man = self.repo.manifest
518 mn = man.lookup(mnode)
518 mn = man.lookup(mnode)
519 mnode = hex(mn)
519 mnode = hex(mn)
520 mf = man.read(mn)
520 mf = man.read(mn)
521 rev = man.rev(mn)
521 rev = man.rev(mn)
522 node = self.repo.changelog.node(rev)
522 node = self.repo.changelog.node(rev)
523 mff = man.readflags(mn)
523 mff = man.readflags(mn)
524
524
525 files = {}
525 files = {}
526
526
527 p = path[1:]
527 p = path[1:]
528 if p and p[-1] != "/":
528 if p and p[-1] != "/":
529 p += "/"
529 p += "/"
530 l = len(p)
530 l = len(p)
531
531
532 for f,n in mf.items():
532 for f,n in mf.items():
533 if f[:l] != p:
533 if f[:l] != p:
534 continue
534 continue
535 remain = f[l:]
535 remain = f[l:]
536 if "/" in remain:
536 if "/" in remain:
537 short = remain[:remain.find("/") + 1] # bleah
537 short = remain[:remain.find("/") + 1] # bleah
538 files[short] = (f, None)
538 files[short] = (f, None)
539 else:
539 else:
540 short = os.path.basename(remain)
540 short = os.path.basename(remain)
541 files[short] = (f, n)
541 files[short] = (f, n)
542
542
543 def filelist(**map):
543 def filelist(**map):
544 parity = 0
544 parity = 0
545 fl = files.keys()
545 fl = files.keys()
546 fl.sort()
546 fl.sort()
547 for f in fl:
547 for f in fl:
548 full, fnode = files[f]
548 full, fnode = files[f]
549 if not fnode:
549 if not fnode:
550 continue
550 continue
551
551
552 yield {"file": full,
552 yield {"file": full,
553 "manifest": mnode,
553 "manifest": mnode,
554 "filenode": hex(fnode),
554 "filenode": hex(fnode),
555 "parity": parity,
555 "parity": parity,
556 "basename": f,
556 "basename": f,
557 "permissions": mff[full]}
557 "permissions": mff[full]}
558 parity = 1 - parity
558 parity = 1 - parity
559
559
560 def dirlist(**map):
560 def dirlist(**map):
561 parity = 0
561 parity = 0
562 fl = files.keys()
562 fl = files.keys()
563 fl.sort()
563 fl.sort()
564 for f in fl:
564 for f in fl:
565 full, fnode = files[f]
565 full, fnode = files[f]
566 if fnode:
566 if fnode:
567 continue
567 continue
568
568
569 yield {"parity": parity,
569 yield {"parity": parity,
570 "path": os.path.join(path, f),
570 "path": os.path.join(path, f),
571 "manifest": mnode,
571 "manifest": mnode,
572 "basename": f[:-1]}
572 "basename": f[:-1]}
573 parity = 1 - parity
573 parity = 1 - parity
574
574
575 yield self.t("manifest",
575 yield self.t("manifest",
576 manifest=mnode,
576 manifest=mnode,
577 rev=rev,
577 rev=rev,
578 node=hex(node),
578 node=hex(node),
579 path=path,
579 path=path,
580 up=up(path),
580 up=up(path),
581 fentries=filelist,
581 fentries=filelist,
582 dentries=dirlist,
582 dentries=dirlist,
583 archives=self.archivelist(hex(node)))
583 archives=self.archivelist(hex(node)))
584
584
585 def tags(self):
585 def tags(self):
586 cl = self.repo.changelog
586 cl = self.repo.changelog
587 mf = cl.read(cl.tip())[0]
587 mf = cl.read(cl.tip())[0]
588
588
589 i = self.repo.tagslist()
589 i = self.repo.tagslist()
590 i.reverse()
590 i.reverse()
591
591
592 def entries(notip=False, **map):
592 def entries(notip=False, **map):
593 parity = 0
593 parity = 0
594 for k,n in i:
594 for k,n in i:
595 if notip and k == "tip": continue
595 if notip and k == "tip": continue
596 yield {"parity": parity,
596 yield {"parity": parity,
597 "tag": k,
597 "tag": k,
598 "tagmanifest": hex(cl.read(n)[0]),
598 "tagmanifest": hex(cl.read(n)[0]),
599 "date": cl.read(n)[2],
599 "date": cl.read(n)[2],
600 "node": hex(n)}
600 "node": hex(n)}
601 parity = 1 - parity
601 parity = 1 - parity
602
602
603 yield self.t("tags",
603 yield self.t("tags",
604 manifest=hex(mf),
604 manifest=hex(mf),
605 entries=lambda **x: entries(False, **x),
605 entries=lambda **x: entries(False, **x),
606 entriesnotip=lambda **x: entries(True, **x))
606 entriesnotip=lambda **x: entries(True, **x))
607
607
608 def summary(self):
608 def summary(self):
609 cl = self.repo.changelog
609 cl = self.repo.changelog
610 mf = cl.read(cl.tip())[0]
610 mf = cl.read(cl.tip())[0]
611
611
612 i = self.repo.tagslist()
612 i = self.repo.tagslist()
613 i.reverse()
613 i.reverse()
614
614
615 def tagentries(**map):
615 def tagentries(**map):
616 parity = 0
616 parity = 0
617 count = 0
617 count = 0
618 for k,n in i:
618 for k,n in i:
619 if k == "tip": # skip tip
619 if k == "tip": # skip tip
620 continue;
620 continue;
621
621
622 count += 1
622 count += 1
623 if count > 10: # limit to 10 tags
623 if count > 10: # limit to 10 tags
624 break;
624 break;
625
625
626 c = cl.read(n)
626 c = cl.read(n)
627 m = c[0]
627 m = c[0]
628 t = c[2]
628 t = c[2]
629
629
630 yield self.t("tagentry",
630 yield self.t("tagentry",
631 parity = parity,
631 parity = parity,
632 tag = k,
632 tag = k,
633 node = hex(n),
633 node = hex(n),
634 date = t,
634 date = t,
635 tagmanifest = hex(m))
635 tagmanifest = hex(m))
636 parity = 1 - parity
636 parity = 1 - parity
637
637
638 def changelist(**map):
638 def changelist(**map):
639 parity = 0
639 parity = 0
640 cl = self.repo.changelog
640 cl = self.repo.changelog
641 l = [] # build a list in forward order for efficiency
641 l = [] # build a list in forward order for efficiency
642 for i in range(start, end):
642 for i in range(start, end):
643 n = cl.node(i)
643 n = cl.node(i)
644 changes = cl.read(n)
644 changes = cl.read(n)
645 hn = hex(n)
645 hn = hex(n)
646 t = changes[2]
646 t = changes[2]
647
647
648 l.insert(0, self.t(
648 l.insert(0, self.t(
649 'shortlogentry',
649 'shortlogentry',
650 parity = parity,
650 parity = parity,
651 author = changes[1],
651 author = changes[1],
652 manifest = hex(changes[0]),
652 manifest = hex(changes[0]),
653 desc = changes[4],
653 desc = changes[4],
654 date = t,
654 date = t,
655 rev = i,
655 rev = i,
656 node = hn))
656 node = hn))
657 parity = 1 - parity
657 parity = 1 - parity
658
658
659 yield l
659 yield l
660
660
661 cl = self.repo.changelog
661 cl = self.repo.changelog
662 mf = cl.read(cl.tip())[0]
662 mf = cl.read(cl.tip())[0]
663 count = cl.count()
663 count = cl.count()
664 start = max(0, count - self.maxchanges)
664 start = max(0, count - self.maxchanges)
665 end = min(count, start + self.maxchanges)
665 end = min(count, start + self.maxchanges)
666 pos = end - 1
666 pos = end - 1
667
667
668 yield self.t("summary",
668 yield self.t("summary",
669 desc = self.repo.ui.config("web", "description", "unknown"),
669 desc = self.repo.ui.config("web", "description", "unknown"),
670 owner = (self.repo.ui.config("ui", "username") or # preferred
670 owner = (self.repo.ui.config("ui", "username") or # preferred
671 self.repo.ui.config("web", "contact") or # deprecated
671 self.repo.ui.config("web", "contact") or # deprecated
672 self.repo.ui.config("web", "author", "unknown")), # also
672 self.repo.ui.config("web", "author", "unknown")), # also
673 lastchange = (0, 0), # FIXME
673 lastchange = (0, 0), # FIXME
674 manifest = hex(mf),
674 manifest = hex(mf),
675 tags = tagentries,
675 tags = tagentries,
676 shortlog = changelist)
676 shortlog = changelist)
677
677
678 def filediff(self, file, changeset):
678 def filediff(self, file, changeset):
679 cl = self.repo.changelog
679 cl = self.repo.changelog
680 n = self.repo.lookup(changeset)
680 n = self.repo.lookup(changeset)
681 changeset = hex(n)
681 changeset = hex(n)
682 p1 = cl.parents(n)[0]
682 p1 = cl.parents(n)[0]
683 cs = cl.read(n)
683 cs = cl.read(n)
684 mf = self.repo.manifest.read(cs[0])
684 mf = self.repo.manifest.read(cs[0])
685
685
686 def diff(**map):
686 def diff(**map):
687 yield self.diff(p1, n, file)
687 yield self.diff(p1, n, [file])
688
688
689 yield self.t("filediff",
689 yield self.t("filediff",
690 file=file,
690 file=file,
691 filenode=hex(mf.get(file, nullid)),
691 filenode=hex(mf.get(file, nullid)),
692 node=changeset,
692 node=changeset,
693 rev=self.repo.changelog.rev(n),
693 rev=self.repo.changelog.rev(n),
694 parent=self.siblings(cl.parents(n), cl.rev),
694 parent=self.siblings(cl.parents(n), cl.rev),
695 child=self.siblings(cl.children(n), cl.rev),
695 child=self.siblings(cl.children(n), cl.rev),
696 diff=diff)
696 diff=diff)
697
697
698 archive_specs = {
698 archive_specs = {
699 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', 'x-bzip2'),
699 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', 'x-bzip2'),
700 'gz': ('application/x-tar', 'tgz', '.tar.gz', 'x-gzip'),
700 'gz': ('application/x-tar', 'tgz', '.tar.gz', 'x-gzip'),
701 'zip': ('application/zip', 'zip', '.zip', None),
701 'zip': ('application/zip', 'zip', '.zip', None),
702 }
702 }
703
703
704 def archive(self, req, cnode, type):
704 def archive(self, req, cnode, type):
705 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
705 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
706 name = "%s-%s" % (reponame, short(cnode))
706 name = "%s-%s" % (reponame, short(cnode))
707 mimetype, artype, extension, encoding = self.archive_specs[type]
707 mimetype, artype, extension, encoding = self.archive_specs[type]
708 headers = [('Content-type', mimetype),
708 headers = [('Content-type', mimetype),
709 ('Content-disposition', 'attachment; filename=%s%s' %
709 ('Content-disposition', 'attachment; filename=%s%s' %
710 (name, extension))]
710 (name, extension))]
711 if encoding:
711 if encoding:
712 headers.append(('Content-encoding', encoding))
712 headers.append(('Content-encoding', encoding))
713 req.header(headers)
713 req.header(headers)
714 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
714 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
715
715
716 # add tags to things
716 # add tags to things
717 # tags -> list of changesets corresponding to tags
717 # tags -> list of changesets corresponding to tags
718 # find tag, changeset, file
718 # find tag, changeset, file
719
719
720 def run(self, req=hgrequest()):
720 def run(self, req=hgrequest()):
721 def clean(path):
721 def clean(path):
722 p = util.normpath(path)
722 p = util.normpath(path)
723 if p[:2] == "..":
723 if p[:2] == "..":
724 raise "suspicious path"
724 raise "suspicious path"
725 return p
725 return p
726
726
727 def header(**map):
727 def header(**map):
728 yield self.t("header", **map)
728 yield self.t("header", **map)
729
729
730 def footer(**map):
730 def footer(**map):
731 yield self.t("footer",
731 yield self.t("footer",
732 motd=self.repo.ui.config("web", "motd", ""),
732 motd=self.repo.ui.config("web", "motd", ""),
733 **map)
733 **map)
734
734
735 def expand_form(form):
735 def expand_form(form):
736 shortcuts = {
736 shortcuts = {
737 'cl': [('cmd', ['changelog']), ('rev', None)],
737 'cl': [('cmd', ['changelog']), ('rev', None)],
738 'cs': [('cmd', ['changeset']), ('node', None)],
738 'cs': [('cmd', ['changeset']), ('node', None)],
739 'f': [('cmd', ['file']), ('filenode', None)],
739 'f': [('cmd', ['file']), ('filenode', None)],
740 'fl': [('cmd', ['filelog']), ('filenode', None)],
740 'fl': [('cmd', ['filelog']), ('filenode', None)],
741 'fd': [('cmd', ['filediff']), ('node', None)],
741 'fd': [('cmd', ['filediff']), ('node', None)],
742 'fa': [('cmd', ['annotate']), ('filenode', None)],
742 'fa': [('cmd', ['annotate']), ('filenode', None)],
743 'mf': [('cmd', ['manifest']), ('manifest', None)],
743 'mf': [('cmd', ['manifest']), ('manifest', None)],
744 'ca': [('cmd', ['archive']), ('node', None)],
744 'ca': [('cmd', ['archive']), ('node', None)],
745 'tags': [('cmd', ['tags'])],
745 'tags': [('cmd', ['tags'])],
746 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
746 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
747 'static': [('cmd', ['static']), ('file', None)]
747 'static': [('cmd', ['static']), ('file', None)]
748 }
748 }
749
749
750 for k in shortcuts.iterkeys():
750 for k in shortcuts.iterkeys():
751 if form.has_key(k):
751 if form.has_key(k):
752 for name, value in shortcuts[k]:
752 for name, value in shortcuts[k]:
753 if value is None:
753 if value is None:
754 value = form[k]
754 value = form[k]
755 form[name] = value
755 form[name] = value
756 del form[k]
756 del form[k]
757
757
758 self.refresh()
758 self.refresh()
759
759
760 expand_form(req.form)
760 expand_form(req.form)
761
761
762 t = self.repo.ui.config("web", "templates", templater.templatepath())
762 t = self.repo.ui.config("web", "templates", templater.templatepath())
763 static = self.repo.ui.config("web", "static", os.path.join(t,"static"))
763 static = self.repo.ui.config("web", "static", os.path.join(t,"static"))
764 m = os.path.join(t, "map")
764 m = os.path.join(t, "map")
765 style = self.repo.ui.config("web", "style", "")
765 style = self.repo.ui.config("web", "style", "")
766 if req.form.has_key('style'):
766 if req.form.has_key('style'):
767 style = req.form['style'][0]
767 style = req.form['style'][0]
768 if style:
768 if style:
769 b = os.path.basename("map-" + style)
769 b = os.path.basename("map-" + style)
770 p = os.path.join(t, b)
770 p = os.path.join(t, b)
771 if os.path.isfile(p):
771 if os.path.isfile(p):
772 m = p
772 m = p
773
773
774 port = req.env["SERVER_PORT"]
774 port = req.env["SERVER_PORT"]
775 port = port != "80" and (":" + port) or ""
775 port = port != "80" and (":" + port) or ""
776 uri = req.env["REQUEST_URI"]
776 uri = req.env["REQUEST_URI"]
777 if "?" in uri:
777 if "?" in uri:
778 uri = uri.split("?")[0]
778 uri = uri.split("?")[0]
779 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
779 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
780 if not self.reponame:
780 if not self.reponame:
781 self.reponame = (self.repo.ui.config("web", "name")
781 self.reponame = (self.repo.ui.config("web", "name")
782 or uri.strip('/') or self.repo.root)
782 or uri.strip('/') or self.repo.root)
783
783
784 self.t = templater.templater(m, templater.common_filters,
784 self.t = templater.templater(m, templater.common_filters,
785 defaults={"url": url,
785 defaults={"url": url,
786 "repo": self.reponame,
786 "repo": self.reponame,
787 "header": header,
787 "header": header,
788 "footer": footer,
788 "footer": footer,
789 })
789 })
790
790
791 if not req.form.has_key('cmd'):
791 if not req.form.has_key('cmd'):
792 req.form['cmd'] = [self.t.cache['default'],]
792 req.form['cmd'] = [self.t.cache['default'],]
793
793
794 cmd = req.form['cmd'][0]
794 cmd = req.form['cmd'][0]
795 if cmd == 'changelog':
795 if cmd == 'changelog':
796 hi = self.repo.changelog.count() - 1
796 hi = self.repo.changelog.count() - 1
797 if req.form.has_key('rev'):
797 if req.form.has_key('rev'):
798 hi = req.form['rev'][0]
798 hi = req.form['rev'][0]
799 try:
799 try:
800 hi = self.repo.changelog.rev(self.repo.lookup(hi))
800 hi = self.repo.changelog.rev(self.repo.lookup(hi))
801 except hg.RepoError:
801 except hg.RepoError:
802 req.write(self.search(hi)) # XXX redirect to 404 page?
802 req.write(self.search(hi)) # XXX redirect to 404 page?
803 return
803 return
804
804
805 req.write(self.changelog(hi))
805 req.write(self.changelog(hi))
806
806
807 elif cmd == 'changeset':
807 elif cmd == 'changeset':
808 req.write(self.changeset(req.form['node'][0]))
808 req.write(self.changeset(req.form['node'][0]))
809
809
810 elif cmd == 'manifest':
810 elif cmd == 'manifest':
811 req.write(self.manifest(req.form['manifest'][0],
811 req.write(self.manifest(req.form['manifest'][0],
812 clean(req.form['path'][0])))
812 clean(req.form['path'][0])))
813
813
814 elif cmd == 'tags':
814 elif cmd == 'tags':
815 req.write(self.tags())
815 req.write(self.tags())
816
816
817 elif cmd == 'summary':
817 elif cmd == 'summary':
818 req.write(self.summary())
818 req.write(self.summary())
819
819
820 elif cmd == 'filediff':
820 elif cmd == 'filediff':
821 req.write(self.filediff(clean(req.form['file'][0]),
821 req.write(self.filediff(clean(req.form['file'][0]),
822 req.form['node'][0]))
822 req.form['node'][0]))
823
823
824 elif cmd == 'file':
824 elif cmd == 'file':
825 req.write(self.filerevision(clean(req.form['file'][0]),
825 req.write(self.filerevision(clean(req.form['file'][0]),
826 req.form['filenode'][0]))
826 req.form['filenode'][0]))
827
827
828 elif cmd == 'annotate':
828 elif cmd == 'annotate':
829 req.write(self.fileannotate(clean(req.form['file'][0]),
829 req.write(self.fileannotate(clean(req.form['file'][0]),
830 req.form['filenode'][0]))
830 req.form['filenode'][0]))
831
831
832 elif cmd == 'filelog':
832 elif cmd == 'filelog':
833 req.write(self.filelog(clean(req.form['file'][0]),
833 req.write(self.filelog(clean(req.form['file'][0]),
834 req.form['filenode'][0]))
834 req.form['filenode'][0]))
835
835
836 elif cmd == 'heads':
836 elif cmd == 'heads':
837 req.httphdr("application/mercurial-0.1")
837 req.httphdr("application/mercurial-0.1")
838 h = self.repo.heads()
838 h = self.repo.heads()
839 req.write(" ".join(map(hex, h)) + "\n")
839 req.write(" ".join(map(hex, h)) + "\n")
840
840
841 elif cmd == 'branches':
841 elif cmd == 'branches':
842 req.httphdr("application/mercurial-0.1")
842 req.httphdr("application/mercurial-0.1")
843 nodes = []
843 nodes = []
844 if req.form.has_key('nodes'):
844 if req.form.has_key('nodes'):
845 nodes = map(bin, req.form['nodes'][0].split(" "))
845 nodes = map(bin, req.form['nodes'][0].split(" "))
846 for b in self.repo.branches(nodes):
846 for b in self.repo.branches(nodes):
847 req.write(" ".join(map(hex, b)) + "\n")
847 req.write(" ".join(map(hex, b)) + "\n")
848
848
849 elif cmd == 'between':
849 elif cmd == 'between':
850 req.httphdr("application/mercurial-0.1")
850 req.httphdr("application/mercurial-0.1")
851 nodes = []
851 nodes = []
852 if req.form.has_key('pairs'):
852 if req.form.has_key('pairs'):
853 pairs = [map(bin, p.split("-"))
853 pairs = [map(bin, p.split("-"))
854 for p in req.form['pairs'][0].split(" ")]
854 for p in req.form['pairs'][0].split(" ")]
855 for b in self.repo.between(pairs):
855 for b in self.repo.between(pairs):
856 req.write(" ".join(map(hex, b)) + "\n")
856 req.write(" ".join(map(hex, b)) + "\n")
857
857
858 elif cmd == 'changegroup':
858 elif cmd == 'changegroup':
859 req.httphdr("application/mercurial-0.1")
859 req.httphdr("application/mercurial-0.1")
860 nodes = []
860 nodes = []
861 if not self.allowpull:
861 if not self.allowpull:
862 return
862 return
863
863
864 if req.form.has_key('roots'):
864 if req.form.has_key('roots'):
865 nodes = map(bin, req.form['roots'][0].split(" "))
865 nodes = map(bin, req.form['roots'][0].split(" "))
866
866
867 z = zlib.compressobj()
867 z = zlib.compressobj()
868 f = self.repo.changegroup(nodes, 'serve')
868 f = self.repo.changegroup(nodes, 'serve')
869 while 1:
869 while 1:
870 chunk = f.read(4096)
870 chunk = f.read(4096)
871 if not chunk:
871 if not chunk:
872 break
872 break
873 req.write(z.compress(chunk))
873 req.write(z.compress(chunk))
874
874
875 req.write(z.flush())
875 req.write(z.flush())
876
876
877 elif cmd == 'archive':
877 elif cmd == 'archive':
878 changeset = self.repo.lookup(req.form['node'][0])
878 changeset = self.repo.lookup(req.form['node'][0])
879 type = req.form['type'][0]
879 type = req.form['type'][0]
880 if (type in self.archives and
880 if (type in self.archives and
881 self.repo.ui.configbool("web", "allow" + type, False)):
881 self.repo.ui.configbool("web", "allow" + type, False)):
882 self.archive(req, changeset, type)
882 self.archive(req, changeset, type)
883 return
883 return
884
884
885 req.write(self.t("error"))
885 req.write(self.t("error"))
886
886
887 elif cmd == 'static':
887 elif cmd == 'static':
888 fname = req.form['file'][0]
888 fname = req.form['file'][0]
889 req.write(staticfile(static, fname)
889 req.write(staticfile(static, fname)
890 or self.t("error", error="%r not found" % fname))
890 or self.t("error", error="%r not found" % fname))
891
891
892 else:
892 else:
893 req.write(self.t("error"))
893 req.write(self.t("error"))
894
894
895 def create_server(ui, repo):
895 def create_server(ui, repo):
896 use_threads = True
896 use_threads = True
897
897
898 def openlog(opt, default):
898 def openlog(opt, default):
899 if opt and opt != '-':
899 if opt and opt != '-':
900 return open(opt, 'w')
900 return open(opt, 'w')
901 return default
901 return default
902
902
903 address = ui.config("web", "address", "")
903 address = ui.config("web", "address", "")
904 port = int(ui.config("web", "port", 8000))
904 port = int(ui.config("web", "port", 8000))
905 use_ipv6 = ui.configbool("web", "ipv6")
905 use_ipv6 = ui.configbool("web", "ipv6")
906 webdir_conf = ui.config("web", "webdir_conf")
906 webdir_conf = ui.config("web", "webdir_conf")
907 accesslog = openlog(ui.config("web", "accesslog", "-"), sys.stdout)
907 accesslog = openlog(ui.config("web", "accesslog", "-"), sys.stdout)
908 errorlog = openlog(ui.config("web", "errorlog", "-"), sys.stderr)
908 errorlog = openlog(ui.config("web", "errorlog", "-"), sys.stderr)
909
909
910 if use_threads:
910 if use_threads:
911 try:
911 try:
912 from threading import activeCount
912 from threading import activeCount
913 except ImportError:
913 except ImportError:
914 use_threads = False
914 use_threads = False
915
915
916 if use_threads:
916 if use_threads:
917 _mixin = SocketServer.ThreadingMixIn
917 _mixin = SocketServer.ThreadingMixIn
918 else:
918 else:
919 if hasattr(os, "fork"):
919 if hasattr(os, "fork"):
920 _mixin = SocketServer.ForkingMixIn
920 _mixin = SocketServer.ForkingMixIn
921 else:
921 else:
922 class _mixin: pass
922 class _mixin: pass
923
923
924 class MercurialHTTPServer(_mixin, BaseHTTPServer.HTTPServer):
924 class MercurialHTTPServer(_mixin, BaseHTTPServer.HTTPServer):
925 pass
925 pass
926
926
927 class IPv6HTTPServer(MercurialHTTPServer):
927 class IPv6HTTPServer(MercurialHTTPServer):
928 address_family = getattr(socket, 'AF_INET6', None)
928 address_family = getattr(socket, 'AF_INET6', None)
929
929
930 def __init__(self, *args, **kwargs):
930 def __init__(self, *args, **kwargs):
931 if self.address_family is None:
931 if self.address_family is None:
932 raise hg.RepoError(_('IPv6 not available on this system'))
932 raise hg.RepoError(_('IPv6 not available on this system'))
933 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
933 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
934
934
935 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
935 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
936
936
937 def log_error(self, format, *args):
937 def log_error(self, format, *args):
938 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
938 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
939 self.log_date_time_string(),
939 self.log_date_time_string(),
940 format % args))
940 format % args))
941
941
942 def log_message(self, format, *args):
942 def log_message(self, format, *args):
943 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
943 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
944 self.log_date_time_string(),
944 self.log_date_time_string(),
945 format % args))
945 format % args))
946
946
947 def do_POST(self):
947 def do_POST(self):
948 try:
948 try:
949 self.do_hgweb()
949 self.do_hgweb()
950 except socket.error, inst:
950 except socket.error, inst:
951 if inst[0] != errno.EPIPE:
951 if inst[0] != errno.EPIPE:
952 raise
952 raise
953
953
954 def do_GET(self):
954 def do_GET(self):
955 self.do_POST()
955 self.do_POST()
956
956
957 def do_hgweb(self):
957 def do_hgweb(self):
958 path_info, query = splitURI(self.path)
958 path_info, query = splitURI(self.path)
959
959
960 env = {}
960 env = {}
961 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
961 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
962 env['REQUEST_METHOD'] = self.command
962 env['REQUEST_METHOD'] = self.command
963 env['SERVER_NAME'] = self.server.server_name
963 env['SERVER_NAME'] = self.server.server_name
964 env['SERVER_PORT'] = str(self.server.server_port)
964 env['SERVER_PORT'] = str(self.server.server_port)
965 env['REQUEST_URI'] = "/"
965 env['REQUEST_URI'] = "/"
966 env['PATH_INFO'] = path_info
966 env['PATH_INFO'] = path_info
967 if query:
967 if query:
968 env['QUERY_STRING'] = query
968 env['QUERY_STRING'] = query
969 host = self.address_string()
969 host = self.address_string()
970 if host != self.client_address[0]:
970 if host != self.client_address[0]:
971 env['REMOTE_HOST'] = host
971 env['REMOTE_HOST'] = host
972 env['REMOTE_ADDR'] = self.client_address[0]
972 env['REMOTE_ADDR'] = self.client_address[0]
973
973
974 if self.headers.typeheader is None:
974 if self.headers.typeheader is None:
975 env['CONTENT_TYPE'] = self.headers.type
975 env['CONTENT_TYPE'] = self.headers.type
976 else:
976 else:
977 env['CONTENT_TYPE'] = self.headers.typeheader
977 env['CONTENT_TYPE'] = self.headers.typeheader
978 length = self.headers.getheader('content-length')
978 length = self.headers.getheader('content-length')
979 if length:
979 if length:
980 env['CONTENT_LENGTH'] = length
980 env['CONTENT_LENGTH'] = length
981 accept = []
981 accept = []
982 for line in self.headers.getallmatchingheaders('accept'):
982 for line in self.headers.getallmatchingheaders('accept'):
983 if line[:1] in "\t\n\r ":
983 if line[:1] in "\t\n\r ":
984 accept.append(line.strip())
984 accept.append(line.strip())
985 else:
985 else:
986 accept = accept + line[7:].split(',')
986 accept = accept + line[7:].split(',')
987 env['HTTP_ACCEPT'] = ','.join(accept)
987 env['HTTP_ACCEPT'] = ','.join(accept)
988
988
989 req = hgrequest(self.rfile, self.wfile, env)
989 req = hgrequest(self.rfile, self.wfile, env)
990 self.send_response(200, "Script output follows")
990 self.send_response(200, "Script output follows")
991
991
992 if webdir_conf:
992 if webdir_conf:
993 hgwebobj = hgwebdir(webdir_conf)
993 hgwebobj = hgwebdir(webdir_conf)
994 elif repo is not None:
994 elif repo is not None:
995 hgwebobj = hgweb(repo.__class__(repo.ui, repo.origroot))
995 hgwebobj = hgweb(repo.__class__(repo.ui, repo.origroot))
996 else:
996 else:
997 raise hg.RepoError(_('no repo found'))
997 raise hg.RepoError(_('no repo found'))
998 hgwebobj.run(req)
998 hgwebobj.run(req)
999
999
1000
1000
1001 if use_ipv6:
1001 if use_ipv6:
1002 return IPv6HTTPServer((address, port), hgwebhandler)
1002 return IPv6HTTPServer((address, port), hgwebhandler)
1003 else:
1003 else:
1004 return MercurialHTTPServer((address, port), hgwebhandler)
1004 return MercurialHTTPServer((address, port), hgwebhandler)
1005
1005
1006 # This is a stopgap
1006 # This is a stopgap
1007 class hgwebdir(object):
1007 class hgwebdir(object):
1008 def __init__(self, config):
1008 def __init__(self, config):
1009 def cleannames(items):
1009 def cleannames(items):
1010 return [(name.strip(os.sep), path) for name, path in items]
1010 return [(name.strip(os.sep), path) for name, path in items]
1011
1011
1012 self.motd = ""
1012 self.motd = ""
1013 self.repos_sorted = ('name', False)
1013 self.repos_sorted = ('name', False)
1014 if isinstance(config, (list, tuple)):
1014 if isinstance(config, (list, tuple)):
1015 self.repos = cleannames(config)
1015 self.repos = cleannames(config)
1016 self.repos_sorted = ('', False)
1016 self.repos_sorted = ('', False)
1017 elif isinstance(config, dict):
1017 elif isinstance(config, dict):
1018 self.repos = cleannames(config.items())
1018 self.repos = cleannames(config.items())
1019 self.repos.sort()
1019 self.repos.sort()
1020 else:
1020 else:
1021 cp = ConfigParser.SafeConfigParser()
1021 cp = ConfigParser.SafeConfigParser()
1022 cp.read(config)
1022 cp.read(config)
1023 self.repos = []
1023 self.repos = []
1024 if cp.has_section('web') and cp.has_option('web', 'motd'):
1024 if cp.has_section('web') and cp.has_option('web', 'motd'):
1025 self.motd = cp.get('web', 'motd')
1025 self.motd = cp.get('web', 'motd')
1026 if cp.has_section('paths'):
1026 if cp.has_section('paths'):
1027 self.repos.extend(cleannames(cp.items('paths')))
1027 self.repos.extend(cleannames(cp.items('paths')))
1028 if cp.has_section('collections'):
1028 if cp.has_section('collections'):
1029 for prefix, root in cp.items('collections'):
1029 for prefix, root in cp.items('collections'):
1030 for path in util.walkrepos(root):
1030 for path in util.walkrepos(root):
1031 repo = os.path.normpath(path)
1031 repo = os.path.normpath(path)
1032 name = repo
1032 name = repo
1033 if name.startswith(prefix):
1033 if name.startswith(prefix):
1034 name = name[len(prefix):]
1034 name = name[len(prefix):]
1035 self.repos.append((name.lstrip(os.sep), repo))
1035 self.repos.append((name.lstrip(os.sep), repo))
1036 self.repos.sort()
1036 self.repos.sort()
1037
1037
1038 def run(self, req=hgrequest()):
1038 def run(self, req=hgrequest()):
1039 def header(**map):
1039 def header(**map):
1040 yield tmpl("header", **map)
1040 yield tmpl("header", **map)
1041
1041
1042 def footer(**map):
1042 def footer(**map):
1043 yield tmpl("footer", motd=self.motd, **map)
1043 yield tmpl("footer", motd=self.motd, **map)
1044
1044
1045 m = os.path.join(templater.templatepath(), "map")
1045 m = os.path.join(templater.templatepath(), "map")
1046 tmpl = templater.templater(m, templater.common_filters,
1046 tmpl = templater.templater(m, templater.common_filters,
1047 defaults={"header": header,
1047 defaults={"header": header,
1048 "footer": footer})
1048 "footer": footer})
1049
1049
1050 def archivelist(ui, nodeid, url):
1050 def archivelist(ui, nodeid, url):
1051 for i in ['zip', 'gz', 'bz2']:
1051 for i in ['zip', 'gz', 'bz2']:
1052 if ui.configbool("web", "allow" + i, False):
1052 if ui.configbool("web", "allow" + i, False):
1053 yield {"type" : i, "node": nodeid, "url": url}
1053 yield {"type" : i, "node": nodeid, "url": url}
1054
1054
1055 def entries(sortcolumn="", descending=False, **map):
1055 def entries(sortcolumn="", descending=False, **map):
1056 rows = []
1056 rows = []
1057 parity = 0
1057 parity = 0
1058 for name, path in self.repos:
1058 for name, path in self.repos:
1059 u = ui.ui()
1059 u = ui.ui()
1060 try:
1060 try:
1061 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
1061 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
1062 except IOError:
1062 except IOError:
1063 pass
1063 pass
1064 get = u.config
1064 get = u.config
1065
1065
1066 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
1066 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
1067 .replace("//", "/"))
1067 .replace("//", "/"))
1068
1068
1069 # update time with local timezone
1069 # update time with local timezone
1070 try:
1070 try:
1071 d = (get_mtime(path), util.makedate()[1])
1071 d = (get_mtime(path), util.makedate()[1])
1072 except OSError:
1072 except OSError:
1073 continue
1073 continue
1074
1074
1075 contact = (get("ui", "username") or # preferred
1075 contact = (get("ui", "username") or # preferred
1076 get("web", "contact") or # deprecated
1076 get("web", "contact") or # deprecated
1077 get("web", "author", "")) # also
1077 get("web", "author", "")) # also
1078 description = get("web", "description", "")
1078 description = get("web", "description", "")
1079 name = get("web", "name", name)
1079 name = get("web", "name", name)
1080 row = dict(contact=contact or "unknown",
1080 row = dict(contact=contact or "unknown",
1081 contact_sort=contact.upper() or "unknown",
1081 contact_sort=contact.upper() or "unknown",
1082 name=name,
1082 name=name,
1083 name_sort=name,
1083 name_sort=name,
1084 url=url,
1084 url=url,
1085 description=description or "unknown",
1085 description=description or "unknown",
1086 description_sort=description.upper() or "unknown",
1086 description_sort=description.upper() or "unknown",
1087 lastchange=d,
1087 lastchange=d,
1088 lastchange_sort=d[1]-d[0],
1088 lastchange_sort=d[1]-d[0],
1089 archives=archivelist(u, "tip", url))
1089 archives=archivelist(u, "tip", url))
1090 if (not sortcolumn
1090 if (not sortcolumn
1091 or (sortcolumn, descending) == self.repos_sorted):
1091 or (sortcolumn, descending) == self.repos_sorted):
1092 # fast path for unsorted output
1092 # fast path for unsorted output
1093 row['parity'] = parity
1093 row['parity'] = parity
1094 parity = 1 - parity
1094 parity = 1 - parity
1095 yield row
1095 yield row
1096 else:
1096 else:
1097 rows.append((row["%s_sort" % sortcolumn], row))
1097 rows.append((row["%s_sort" % sortcolumn], row))
1098 if rows:
1098 if rows:
1099 rows.sort()
1099 rows.sort()
1100 if descending:
1100 if descending:
1101 rows.reverse()
1101 rows.reverse()
1102 for key, row in rows:
1102 for key, row in rows:
1103 row['parity'] = parity
1103 row['parity'] = parity
1104 parity = 1 - parity
1104 parity = 1 - parity
1105 yield row
1105 yield row
1106
1106
1107 virtual = req.env.get("PATH_INFO", "").strip('/')
1107 virtual = req.env.get("PATH_INFO", "").strip('/')
1108 if virtual:
1108 if virtual:
1109 real = dict(self.repos).get(virtual)
1109 real = dict(self.repos).get(virtual)
1110 if real:
1110 if real:
1111 try:
1111 try:
1112 hgweb(real).run(req)
1112 hgweb(real).run(req)
1113 except IOError, inst:
1113 except IOError, inst:
1114 req.write(tmpl("error", error=inst.strerror))
1114 req.write(tmpl("error", error=inst.strerror))
1115 except hg.RepoError, inst:
1115 except hg.RepoError, inst:
1116 req.write(tmpl("error", error=str(inst)))
1116 req.write(tmpl("error", error=str(inst)))
1117 else:
1117 else:
1118 req.write(tmpl("notfound", repo=virtual))
1118 req.write(tmpl("notfound", repo=virtual))
1119 else:
1119 else:
1120 if req.form.has_key('static'):
1120 if req.form.has_key('static'):
1121 static = os.path.join(templater.templatepath(), "static")
1121 static = os.path.join(templater.templatepath(), "static")
1122 fname = req.form['static'][0]
1122 fname = req.form['static'][0]
1123 req.write(staticfile(static, fname)
1123 req.write(staticfile(static, fname)
1124 or tmpl("error", error="%r not found" % fname))
1124 or tmpl("error", error="%r not found" % fname))
1125 else:
1125 else:
1126 sortable = ["name", "description", "contact", "lastchange"]
1126 sortable = ["name", "description", "contact", "lastchange"]
1127 sortcolumn, descending = self.repos_sorted
1127 sortcolumn, descending = self.repos_sorted
1128 if req.form.has_key('sort'):
1128 if req.form.has_key('sort'):
1129 sortcolumn = req.form['sort'][0]
1129 sortcolumn = req.form['sort'][0]
1130 descending = sortcolumn.startswith('-')
1130 descending = sortcolumn.startswith('-')
1131 if descending:
1131 if descending:
1132 sortcolumn = sortcolumn[1:]
1132 sortcolumn = sortcolumn[1:]
1133 if sortcolumn not in sortable:
1133 if sortcolumn not in sortable:
1134 sortcolumn = ""
1134 sortcolumn = ""
1135
1135
1136 sort = [("sort_%s" % column,
1136 sort = [("sort_%s" % column,
1137 "%s%s" % ((not descending and column == sortcolumn)
1137 "%s%s" % ((not descending and column == sortcolumn)
1138 and "-" or "", column))
1138 and "-" or "", column))
1139 for column in sortable]
1139 for column in sortable]
1140 req.write(tmpl("index", entries=entries,
1140 req.write(tmpl("index", entries=entries,
1141 sortcolumn=sortcolumn, descending=descending,
1141 sortcolumn=sortcolumn, descending=descending,
1142 **dict(sort)))
1142 **dict(sort)))
@@ -1,142 +1,172 b''
1 # httprepo.py - HTTP repository proxy classes for mercurial
1 # httprepo.py - HTTP repository proxy classes for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from node import *
8 from node import *
9 from remoterepo import *
9 from remoterepo import *
10 from i18n import gettext as _
10 from i18n import gettext as _
11 from demandload import *
11 from demandload import *
12 demandload(globals(), "hg os urllib urllib2 urlparse zlib util httplib")
12 demandload(globals(), "hg os urllib urllib2 urlparse zlib util httplib")
13
13
14 class passwordmgr(urllib2.HTTPPasswordMgr):
15 def __init__(self, ui):
16 urllib2.HTTPPasswordMgr.__init__(self)
17 self.ui = ui
18
19 def find_user_password(self, realm, authuri):
20 authinfo = urllib2.HTTPPasswordMgr.find_user_password(
21 self, realm, authuri)
22 if authinfo != (None, None):
23 return authinfo
24
25 self.ui.write(_("http authorization required\n"))
26 self.ui.status(_("realm: %s\n") % realm)
27 user = self.ui.prompt(_("user:"), default=None)
28 passwd = self.ui.getpass()
29
30 self.add_password(realm, authuri, user, passwd)
31 return (user, passwd)
32
14 class httprepository(remoterepository):
33 class httprepository(remoterepository):
15 def __init__(self, ui, path):
34 def __init__(self, ui, path):
16 # fix missing / after hostname
35 # fix missing / after hostname
17 s = urlparse.urlsplit(path)
36 s = urlparse.urlsplit(path)
18 partial = s[2]
37 partial = s[2]
19 if not partial: partial = "/"
38 if not partial: partial = "/"
20 self.url = urlparse.urlunsplit((s[0], s[1], partial, '', ''))
39 self.url = urlparse.urlunsplit((s[0], s[1], partial, '', ''))
21 self.ui = ui
40 self.ui = ui
22 no_list = [ "localhost", "127.0.0.1" ]
41 no_list = [ "localhost", "127.0.0.1" ]
23 host = ui.config("http_proxy", "host")
42 host = ui.config("http_proxy", "host")
24 if host is None:
43 if host is None:
25 host = os.environ.get("http_proxy")
44 host = os.environ.get("http_proxy")
26 if host and host.startswith('http://'):
45 if host and host.startswith('http://'):
27 host = host[7:]
46 host = host[7:]
28 user = ui.config("http_proxy", "user")
47 user = ui.config("http_proxy", "user")
29 passwd = ui.config("http_proxy", "passwd")
48 passwd = ui.config("http_proxy", "passwd")
30 no = ui.config("http_proxy", "no")
49 no = ui.config("http_proxy", "no")
31 if no is None:
50 if no is None:
32 no = os.environ.get("no_proxy")
51 no = os.environ.get("no_proxy")
33 if no:
52 if no:
34 no_list = no_list + no.split(",")
53 no_list = no_list + no.split(",")
35
54
36 no_proxy = 0
55 no_proxy = 0
37 for h in no_list:
56 for h in no_list:
38 if (path.startswith("http://" + h + "/") or
57 if (path.startswith("http://" + h + "/") or
39 path.startswith("http://" + h + ":") or
58 path.startswith("http://" + h + ":") or
40 path == "http://" + h):
59 path == "http://" + h):
41 no_proxy = 1
60 no_proxy = 1
42
61
43 # Note: urllib2 takes proxy values from the environment and those will
62 # Note: urllib2 takes proxy values from the environment and those will
44 # take precedence
63 # take precedence
45 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
64 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
46 try:
65 try:
47 if os.environ.has_key(env):
66 if os.environ.has_key(env):
48 del os.environ[env]
67 del os.environ[env]
49 except OSError:
68 except OSError:
50 pass
69 pass
51
70
52 proxy_handler = urllib2.BaseHandler()
71 proxy_handler = urllib2.BaseHandler()
53 if host and not no_proxy:
72 if host and not no_proxy:
54 proxy_handler = urllib2.ProxyHandler({"http" : "http://" + host})
73 proxy_handler = urllib2.ProxyHandler({"http" : "http://" + host})
55
74
56 authinfo = None
75 proxyauthinfo = None
57 if user and passwd:
76 if user and passwd:
58 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
77 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
59 passmgr.add_password(None, host, user, passwd)
78 passmgr.add_password(None, host, user, passwd)
60 authinfo = urllib2.ProxyBasicAuthHandler(passmgr)
79 proxyauthinfo = urllib2.ProxyBasicAuthHandler(passmgr)
61
80
62 opener = urllib2.build_opener(proxy_handler, authinfo)
81 if ui.interactive:
82 passmgr = passwordmgr(ui)
83 opener = urllib2.build_opener(
84 proxy_handler, proxyauthinfo,
85 urllib2.HTTPBasicAuthHandler(passmgr),
86 urllib2.HTTPDigestAuthHandler(passmgr))
87 else:
88 opener = urllib2.build_opener(proxy_handler, proxyauthinfo)
89
63 # 1.0 here is the _protocol_ version
90 # 1.0 here is the _protocol_ version
64 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
91 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
65 urllib2.install_opener(opener)
92 urllib2.install_opener(opener)
66
93
67 def dev(self):
94 def dev(self):
68 return -1
95 return -1
69
96
70 def lock(self):
97 def lock(self):
71 raise util.Abort(_('operation not supported over http'))
98 raise util.Abort(_('operation not supported over http'))
72
99
73 def do_cmd(self, cmd, **args):
100 def do_cmd(self, cmd, **args):
74 self.ui.debug(_("sending %s command\n") % cmd)
101 self.ui.debug(_("sending %s command\n") % cmd)
75 q = {"cmd": cmd}
102 q = {"cmd": cmd}
76 q.update(args)
103 q.update(args)
77 qs = urllib.urlencode(q)
104 qs = urllib.urlencode(q)
78 cu = "%s?%s" % (self.url, qs)
105 cu = "%s?%s" % (self.url, qs)
79 resp = urllib2.urlopen(cu)
106 try:
107 resp = urllib2.urlopen(cu)
108 except httplib.HTTPException, inst:
109 raise IOError(None, _('http error while sending %s command') % cmd)
80 proto = resp.headers['content-type']
110 proto = resp.headers['content-type']
81
111
82 # accept old "text/plain" and "application/hg-changegroup" for now
112 # accept old "text/plain" and "application/hg-changegroup" for now
83 if not proto.startswith('application/mercurial') and \
113 if not proto.startswith('application/mercurial') and \
84 not proto.startswith('text/plain') and \
114 not proto.startswith('text/plain') and \
85 not proto.startswith('application/hg-changegroup'):
115 not proto.startswith('application/hg-changegroup'):
86 raise hg.RepoError(_("'%s' does not appear to be an hg repository") %
116 raise hg.RepoError(_("'%s' does not appear to be an hg repository") %
87 self.url)
117 self.url)
88
118
89 if proto.startswith('application/mercurial'):
119 if proto.startswith('application/mercurial'):
90 version = proto[22:]
120 version = proto[22:]
91 if float(version) > 0.1:
121 if float(version) > 0.1:
92 raise hg.RepoError(_("'%s' uses newer protocol %s") %
122 raise hg.RepoError(_("'%s' uses newer protocol %s") %
93 (self.url, version))
123 (self.url, version))
94
124
95 return resp
125 return resp
96
126
97 def heads(self):
127 def heads(self):
98 d = self.do_cmd("heads").read()
128 d = self.do_cmd("heads").read()
99 try:
129 try:
100 return map(bin, d[:-1].split(" "))
130 return map(bin, d[:-1].split(" "))
101 except:
131 except:
102 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
132 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
103 raise
133 raise
104
134
105 def branches(self, nodes):
135 def branches(self, nodes):
106 n = " ".join(map(hex, nodes))
136 n = " ".join(map(hex, nodes))
107 d = self.do_cmd("branches", nodes=n).read()
137 d = self.do_cmd("branches", nodes=n).read()
108 try:
138 try:
109 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
139 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
110 return br
140 return br
111 except:
141 except:
112 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
142 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
113 raise
143 raise
114
144
115 def between(self, pairs):
145 def between(self, pairs):
116 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
146 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
117 d = self.do_cmd("between", pairs=n).read()
147 d = self.do_cmd("between", pairs=n).read()
118 try:
148 try:
119 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
149 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
120 return p
150 return p
121 except:
151 except:
122 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
152 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
123 raise
153 raise
124
154
125 def changegroup(self, nodes, kind):
155 def changegroup(self, nodes, kind):
126 n = " ".join(map(hex, nodes))
156 n = " ".join(map(hex, nodes))
127 f = self.do_cmd("changegroup", roots=n)
157 f = self.do_cmd("changegroup", roots=n)
128 bytes = 0
158 bytes = 0
129
159
130 def zgenerator(f):
160 def zgenerator(f):
131 zd = zlib.decompressobj()
161 zd = zlib.decompressobj()
132 try:
162 try:
133 for chnk in f:
163 for chnk in f:
134 yield zd.decompress(chnk)
164 yield zd.decompress(chnk)
135 except httplib.HTTPException, inst:
165 except httplib.HTTPException, inst:
136 raise IOError(None, _('connection ended unexpectedly'))
166 raise IOError(None, _('connection ended unexpectedly'))
137 yield zd.flush()
167 yield zd.flush()
138
168
139 return util.chunkbuffer(zgenerator(util.filechunkiter(f)))
169 return util.chunkbuffer(zgenerator(util.filechunkiter(f)))
140
170
141 class httpsrepository(httprepository):
171 class httpsrepository(httprepository):
142 pass
172 pass
@@ -1,2089 +1,2099 b''
1 # localrepo.py - read/write repository class for mercurial
1 # localrepo.py - read/write repository class for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 import os, util
8 import os, util
9 import filelog, manifest, changelog, dirstate, repo
9 import filelog, manifest, changelog, dirstate, repo
10 from node import *
10 from node import *
11 from i18n import gettext as _
11 from i18n import gettext as _
12 from demandload import *
12 from demandload import *
13 demandload(globals(), "appendfile changegroup")
13 demandload(globals(), "appendfile changegroup")
14 demandload(globals(), "re lock transaction tempfile stat mdiff errno ui")
14 demandload(globals(), "re lock transaction tempfile stat mdiff errno ui")
15 demandload(globals(), "revlog traceback")
15 demandload(globals(), "revlog traceback")
16
16
17 class localrepository(object):
17 class localrepository(object):
18 def __del__(self):
18 def __del__(self):
19 self.transhandle = None
19 self.transhandle = None
20 def __init__(self, parentui, path=None, create=0):
20 def __init__(self, parentui, path=None, create=0):
21 if not path:
21 if not path:
22 p = os.getcwd()
22 p = os.getcwd()
23 while not os.path.isdir(os.path.join(p, ".hg")):
23 while not os.path.isdir(os.path.join(p, ".hg")):
24 oldp = p
24 oldp = p
25 p = os.path.dirname(p)
25 p = os.path.dirname(p)
26 if p == oldp:
26 if p == oldp:
27 raise repo.RepoError(_("no repo found"))
27 raise repo.RepoError(_("no repo found"))
28 path = p
28 path = p
29 self.path = os.path.join(path, ".hg")
29 self.path = os.path.join(path, ".hg")
30
30
31 if not create and not os.path.isdir(self.path):
31 if not create and not os.path.isdir(self.path):
32 raise repo.RepoError(_("repository %s not found") % path)
32 raise repo.RepoError(_("repository %s not found") % path)
33
33
34 self.root = os.path.abspath(path)
34 self.root = os.path.abspath(path)
35 self.origroot = path
35 self.origroot = path
36 self.ui = ui.ui(parentui=parentui)
36 self.ui = ui.ui(parentui=parentui)
37 self.opener = util.opener(self.path)
37 self.opener = util.opener(self.path)
38 self.wopener = util.opener(self.root)
38 self.wopener = util.opener(self.root)
39
39
40 try:
40 try:
41 self.ui.readconfig(self.join("hgrc"), self.root)
41 self.ui.readconfig(self.join("hgrc"), self.root)
42 except IOError:
42 except IOError:
43 pass
43 pass
44
44
45 v = self.ui.revlogopts
45 v = self.ui.revlogopts
46 self.revlogversion = int(v.get('format', revlog.REVLOG_DEFAULT_FORMAT))
46 self.revlogversion = int(v.get('format', revlog.REVLOG_DEFAULT_FORMAT))
47 self.revlogv1 = self.revlogversion != revlog.REVLOGV0
47 self.revlogv1 = self.revlogversion != revlog.REVLOGV0
48 fl = v.get('flags', None)
48 fl = v.get('flags', None)
49 flags = 0
49 flags = 0
50 if fl != None:
50 if fl != None:
51 for x in fl.split():
51 for x in fl.split():
52 flags |= revlog.flagstr(x)
52 flags |= revlog.flagstr(x)
53 elif self.revlogv1:
53 elif self.revlogv1:
54 flags = revlog.REVLOG_DEFAULT_FLAGS
54 flags = revlog.REVLOG_DEFAULT_FLAGS
55
55
56 v = self.revlogversion | flags
56 v = self.revlogversion | flags
57 self.manifest = manifest.manifest(self.opener, v)
57 self.manifest = manifest.manifest(self.opener, v)
58 self.changelog = changelog.changelog(self.opener, v)
58 self.changelog = changelog.changelog(self.opener, v)
59
59
60 # the changelog might not have the inline index flag
60 # the changelog might not have the inline index flag
61 # on. If the format of the changelog is the same as found in
61 # on. If the format of the changelog is the same as found in
62 # .hgrc, apply any flags found in the .hgrc as well.
62 # .hgrc, apply any flags found in the .hgrc as well.
63 # Otherwise, just version from the changelog
63 # Otherwise, just version from the changelog
64 v = self.changelog.version
64 v = self.changelog.version
65 if v == self.revlogversion:
65 if v == self.revlogversion:
66 v |= flags
66 v |= flags
67 self.revlogversion = v
67 self.revlogversion = v
68
68
69 self.tagscache = None
69 self.tagscache = None
70 self.nodetagscache = None
70 self.nodetagscache = None
71 self.encodepats = None
71 self.encodepats = None
72 self.decodepats = None
72 self.decodepats = None
73 self.transhandle = None
73 self.transhandle = None
74
74
75 if create:
75 if create:
76 os.mkdir(self.path)
76 os.mkdir(self.path)
77 os.mkdir(self.join("data"))
77 os.mkdir(self.join("data"))
78
78
79 self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
79 self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
80
80
81 def hook(self, name, throw=False, **args):
81 def hook(self, name, throw=False, **args):
82 def callhook(hname, funcname):
82 def callhook(hname, funcname):
83 '''call python hook. hook is callable object, looked up as
83 '''call python hook. hook is callable object, looked up as
84 name in python module. if callable returns "true", hook
84 name in python module. if callable returns "true", hook
85 fails, else passes. if hook raises exception, treated as
85 fails, else passes. if hook raises exception, treated as
86 hook failure. exception propagates if throw is "true".
86 hook failure. exception propagates if throw is "true".
87
87
88 reason for "true" meaning "hook failed" is so that
88 reason for "true" meaning "hook failed" is so that
89 unmodified commands (e.g. mercurial.commands.update) can
89 unmodified commands (e.g. mercurial.commands.update) can
90 be run as hooks without wrappers to convert return values.'''
90 be run as hooks without wrappers to convert return values.'''
91
91
92 self.ui.note(_("calling hook %s: %s\n") % (hname, funcname))
92 self.ui.note(_("calling hook %s: %s\n") % (hname, funcname))
93 d = funcname.rfind('.')
93 d = funcname.rfind('.')
94 if d == -1:
94 if d == -1:
95 raise util.Abort(_('%s hook is invalid ("%s" not in a module)')
95 raise util.Abort(_('%s hook is invalid ("%s" not in a module)')
96 % (hname, funcname))
96 % (hname, funcname))
97 modname = funcname[:d]
97 modname = funcname[:d]
98 try:
98 try:
99 obj = __import__(modname)
99 obj = __import__(modname)
100 except ImportError:
100 except ImportError:
101 raise util.Abort(_('%s hook is invalid '
101 raise util.Abort(_('%s hook is invalid '
102 '(import of "%s" failed)') %
102 '(import of "%s" failed)') %
103 (hname, modname))
103 (hname, modname))
104 try:
104 try:
105 for p in funcname.split('.')[1:]:
105 for p in funcname.split('.')[1:]:
106 obj = getattr(obj, p)
106 obj = getattr(obj, p)
107 except AttributeError, err:
107 except AttributeError, err:
108 raise util.Abort(_('%s hook is invalid '
108 raise util.Abort(_('%s hook is invalid '
109 '("%s" is not defined)') %
109 '("%s" is not defined)') %
110 (hname, funcname))
110 (hname, funcname))
111 if not callable(obj):
111 if not callable(obj):
112 raise util.Abort(_('%s hook is invalid '
112 raise util.Abort(_('%s hook is invalid '
113 '("%s" is not callable)') %
113 '("%s" is not callable)') %
114 (hname, funcname))
114 (hname, funcname))
115 try:
115 try:
116 r = obj(ui=self.ui, repo=self, hooktype=name, **args)
116 r = obj(ui=self.ui, repo=self, hooktype=name, **args)
117 except (KeyboardInterrupt, util.SignalInterrupt):
117 except (KeyboardInterrupt, util.SignalInterrupt):
118 raise
118 raise
119 except Exception, exc:
119 except Exception, exc:
120 if isinstance(exc, util.Abort):
120 if isinstance(exc, util.Abort):
121 self.ui.warn(_('error: %s hook failed: %s\n') %
121 self.ui.warn(_('error: %s hook failed: %s\n') %
122 (hname, exc.args[0] % exc.args[1:]))
122 (hname, exc.args[0] % exc.args[1:]))
123 else:
123 else:
124 self.ui.warn(_('error: %s hook raised an exception: '
124 self.ui.warn(_('error: %s hook raised an exception: '
125 '%s\n') % (hname, exc))
125 '%s\n') % (hname, exc))
126 if throw:
126 if throw:
127 raise
127 raise
128 if self.ui.traceback:
128 if self.ui.traceback:
129 traceback.print_exc()
129 traceback.print_exc()
130 return True
130 return True
131 if r:
131 if r:
132 if throw:
132 if throw:
133 raise util.Abort(_('%s hook failed') % hname)
133 raise util.Abort(_('%s hook failed') % hname)
134 self.ui.warn(_('warning: %s hook failed\n') % hname)
134 self.ui.warn(_('warning: %s hook failed\n') % hname)
135 return r
135 return r
136
136
137 def runhook(name, cmd):
137 def runhook(name, cmd):
138 self.ui.note(_("running hook %s: %s\n") % (name, cmd))
138 self.ui.note(_("running hook %s: %s\n") % (name, cmd))
139 env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()] +
139 env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()])
140 [(k.upper(), v) for k, v in args.iteritems()])
141 r = util.system(cmd, environ=env, cwd=self.root)
140 r = util.system(cmd, environ=env, cwd=self.root)
142 if r:
141 if r:
143 desc, r = util.explain_exit(r)
142 desc, r = util.explain_exit(r)
144 if throw:
143 if throw:
145 raise util.Abort(_('%s hook %s') % (name, desc))
144 raise util.Abort(_('%s hook %s') % (name, desc))
146 self.ui.warn(_('warning: %s hook %s\n') % (name, desc))
145 self.ui.warn(_('warning: %s hook %s\n') % (name, desc))
147 return r
146 return r
148
147
149 r = False
148 r = False
150 hooks = [(hname, cmd) for hname, cmd in self.ui.configitems("hooks")
149 hooks = [(hname, cmd) for hname, cmd in self.ui.configitems("hooks")
151 if hname.split(".", 1)[0] == name and cmd]
150 if hname.split(".", 1)[0] == name and cmd]
152 hooks.sort()
151 hooks.sort()
153 for hname, cmd in hooks:
152 for hname, cmd in hooks:
154 if cmd.startswith('python:'):
153 if cmd.startswith('python:'):
155 r = callhook(hname, cmd[7:].strip()) or r
154 r = callhook(hname, cmd[7:].strip()) or r
156 else:
155 else:
157 r = runhook(hname, cmd) or r
156 r = runhook(hname, cmd) or r
158 return r
157 return r
159
158
160 def tags(self):
159 def tags(self):
161 '''return a mapping of tag to node'''
160 '''return a mapping of tag to node'''
162 if not self.tagscache:
161 if not self.tagscache:
163 self.tagscache = {}
162 self.tagscache = {}
164
163
165 def parsetag(line, context):
164 def parsetag(line, context):
166 if not line:
165 if not line:
167 return
166 return
168 s = l.split(" ", 1)
167 s = l.split(" ", 1)
169 if len(s) != 2:
168 if len(s) != 2:
170 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
169 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
171 return
170 return
172 node, key = s
171 node, key = s
173 try:
172 try:
174 bin_n = bin(node)
173 bin_n = bin(node)
175 except TypeError:
174 except TypeError:
176 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
175 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
177 return
176 return
178 if bin_n not in self.changelog.nodemap:
177 if bin_n not in self.changelog.nodemap:
179 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
178 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
180 return
179 return
181 self.tagscache[key.strip()] = bin_n
180 self.tagscache[key.strip()] = bin_n
182
181
183 # read each head of the tags file, ending with the tip
182 # read each head of the tags file, ending with the tip
184 # and add each tag found to the map, with "newer" ones
183 # and add each tag found to the map, with "newer" ones
185 # taking precedence
184 # taking precedence
186 fl = self.file(".hgtags")
185 fl = self.file(".hgtags")
187 h = fl.heads()
186 h = fl.heads()
188 h.reverse()
187 h.reverse()
189 for r in h:
188 for r in h:
190 count = 0
189 count = 0
191 for l in fl.read(r).splitlines():
190 for l in fl.read(r).splitlines():
192 count += 1
191 count += 1
193 parsetag(l, ".hgtags:%d" % count)
192 parsetag(l, ".hgtags:%d" % count)
194
193
195 try:
194 try:
196 f = self.opener("localtags")
195 f = self.opener("localtags")
197 count = 0
196 count = 0
198 for l in f:
197 for l in f:
199 count += 1
198 count += 1
200 parsetag(l, "localtags:%d" % count)
199 parsetag(l, "localtags:%d" % count)
201 except IOError:
200 except IOError:
202 pass
201 pass
203
202
204 self.tagscache['tip'] = self.changelog.tip()
203 self.tagscache['tip'] = self.changelog.tip()
205
204
206 return self.tagscache
205 return self.tagscache
207
206
208 def tagslist(self):
207 def tagslist(self):
209 '''return a list of tags ordered by revision'''
208 '''return a list of tags ordered by revision'''
210 l = []
209 l = []
211 for t, n in self.tags().items():
210 for t, n in self.tags().items():
212 try:
211 try:
213 r = self.changelog.rev(n)
212 r = self.changelog.rev(n)
214 except:
213 except:
215 r = -2 # sort to the beginning of the list if unknown
214 r = -2 # sort to the beginning of the list if unknown
216 l.append((r, t, n))
215 l.append((r, t, n))
217 l.sort()
216 l.sort()
218 return [(t, n) for r, t, n in l]
217 return [(t, n) for r, t, n in l]
219
218
220 def nodetags(self, node):
219 def nodetags(self, node):
221 '''return the tags associated with a node'''
220 '''return the tags associated with a node'''
222 if not self.nodetagscache:
221 if not self.nodetagscache:
223 self.nodetagscache = {}
222 self.nodetagscache = {}
224 for t, n in self.tags().items():
223 for t, n in self.tags().items():
225 self.nodetagscache.setdefault(n, []).append(t)
224 self.nodetagscache.setdefault(n, []).append(t)
226 return self.nodetagscache.get(node, [])
225 return self.nodetagscache.get(node, [])
227
226
228 def lookup(self, key):
227 def lookup(self, key):
229 try:
228 try:
230 return self.tags()[key]
229 return self.tags()[key]
231 except KeyError:
230 except KeyError:
232 try:
231 try:
233 return self.changelog.lookup(key)
232 return self.changelog.lookup(key)
234 except:
233 except:
235 raise repo.RepoError(_("unknown revision '%s'") % key)
234 raise repo.RepoError(_("unknown revision '%s'") % key)
236
235
237 def dev(self):
236 def dev(self):
238 return os.stat(self.path).st_dev
237 return os.stat(self.path).st_dev
239
238
240 def local(self):
239 def local(self):
241 return True
240 return True
242
241
243 def join(self, f):
242 def join(self, f):
244 return os.path.join(self.path, f)
243 return os.path.join(self.path, f)
245
244
246 def wjoin(self, f):
245 def wjoin(self, f):
247 return os.path.join(self.root, f)
246 return os.path.join(self.root, f)
248
247
249 def file(self, f):
248 def file(self, f):
250 if f[0] == '/':
249 if f[0] == '/':
251 f = f[1:]
250 f = f[1:]
252 return filelog.filelog(self.opener, f, self.revlogversion)
251 return filelog.filelog(self.opener, f, self.revlogversion)
253
252
254 def getcwd(self):
253 def getcwd(self):
255 return self.dirstate.getcwd()
254 return self.dirstate.getcwd()
256
255
257 def wfile(self, f, mode='r'):
256 def wfile(self, f, mode='r'):
258 return self.wopener(f, mode)
257 return self.wopener(f, mode)
259
258
260 def wread(self, filename):
259 def wread(self, filename):
261 if self.encodepats == None:
260 if self.encodepats == None:
262 l = []
261 l = []
263 for pat, cmd in self.ui.configitems("encode"):
262 for pat, cmd in self.ui.configitems("encode"):
264 mf = util.matcher(self.root, "", [pat], [], [])[1]
263 mf = util.matcher(self.root, "", [pat], [], [])[1]
265 l.append((mf, cmd))
264 l.append((mf, cmd))
266 self.encodepats = l
265 self.encodepats = l
267
266
268 data = self.wopener(filename, 'r').read()
267 data = self.wopener(filename, 'r').read()
269
268
270 for mf, cmd in self.encodepats:
269 for mf, cmd in self.encodepats:
271 if mf(filename):
270 if mf(filename):
272 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
271 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
273 data = util.filter(data, cmd)
272 data = util.filter(data, cmd)
274 break
273 break
275
274
276 return data
275 return data
277
276
278 def wwrite(self, filename, data, fd=None):
277 def wwrite(self, filename, data, fd=None):
279 if self.decodepats == None:
278 if self.decodepats == None:
280 l = []
279 l = []
281 for pat, cmd in self.ui.configitems("decode"):
280 for pat, cmd in self.ui.configitems("decode"):
282 mf = util.matcher(self.root, "", [pat], [], [])[1]
281 mf = util.matcher(self.root, "", [pat], [], [])[1]
283 l.append((mf, cmd))
282 l.append((mf, cmd))
284 self.decodepats = l
283 self.decodepats = l
285
284
286 for mf, cmd in self.decodepats:
285 for mf, cmd in self.decodepats:
287 if mf(filename):
286 if mf(filename):
288 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
287 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
289 data = util.filter(data, cmd)
288 data = util.filter(data, cmd)
290 break
289 break
291
290
292 if fd:
291 if fd:
293 return fd.write(data)
292 return fd.write(data)
294 return self.wopener(filename, 'w').write(data)
293 return self.wopener(filename, 'w').write(data)
295
294
296 def transaction(self):
295 def transaction(self):
297 tr = self.transhandle
296 tr = self.transhandle
298 if tr != None and tr.running():
297 if tr != None and tr.running():
299 return tr.nest()
298 return tr.nest()
300
299
301 # save dirstate for undo
300 # save dirstate for undo
302 try:
301 try:
303 ds = self.opener("dirstate").read()
302 ds = self.opener("dirstate").read()
304 except IOError:
303 except IOError:
305 ds = ""
304 ds = ""
306 self.opener("journal.dirstate", "w").write(ds)
305 self.opener("journal.dirstate", "w").write(ds)
307
306
308 tr = transaction.transaction(self.ui.warn, self.opener,
307 tr = transaction.transaction(self.ui.warn, self.opener,
309 self.join("journal"),
308 self.join("journal"),
310 aftertrans(self.path))
309 aftertrans(self.path))
311 self.transhandle = tr
310 self.transhandle = tr
312 return tr
311 return tr
313
312
314 def recover(self):
313 def recover(self):
315 l = self.lock()
314 l = self.lock()
316 if os.path.exists(self.join("journal")):
315 if os.path.exists(self.join("journal")):
317 self.ui.status(_("rolling back interrupted transaction\n"))
316 self.ui.status(_("rolling back interrupted transaction\n"))
318 transaction.rollback(self.opener, self.join("journal"))
317 transaction.rollback(self.opener, self.join("journal"))
319 self.reload()
318 self.reload()
320 return True
319 return True
321 else:
320 else:
322 self.ui.warn(_("no interrupted transaction available\n"))
321 self.ui.warn(_("no interrupted transaction available\n"))
323 return False
322 return False
324
323
325 def undo(self, wlock=None):
324 def undo(self, wlock=None):
326 if not wlock:
325 if not wlock:
327 wlock = self.wlock()
326 wlock = self.wlock()
328 l = self.lock()
327 l = self.lock()
329 if os.path.exists(self.join("undo")):
328 if os.path.exists(self.join("undo")):
330 self.ui.status(_("rolling back last transaction\n"))
329 self.ui.status(_("rolling back last transaction\n"))
331 transaction.rollback(self.opener, self.join("undo"))
330 transaction.rollback(self.opener, self.join("undo"))
332 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
331 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
333 self.reload()
332 self.reload()
334 self.wreload()
333 self.wreload()
335 else:
334 else:
336 self.ui.warn(_("no undo information available\n"))
335 self.ui.warn(_("no undo information available\n"))
337
336
338 def wreload(self):
337 def wreload(self):
339 self.dirstate.read()
338 self.dirstate.read()
340
339
341 def reload(self):
340 def reload(self):
342 self.changelog.load()
341 self.changelog.load()
343 self.manifest.load()
342 self.manifest.load()
344 self.tagscache = None
343 self.tagscache = None
345 self.nodetagscache = None
344 self.nodetagscache = None
346
345
347 def do_lock(self, lockname, wait, releasefn=None, acquirefn=None,
346 def do_lock(self, lockname, wait, releasefn=None, acquirefn=None,
348 desc=None):
347 desc=None):
349 try:
348 try:
350 l = lock.lock(self.join(lockname), 0, releasefn, desc=desc)
349 l = lock.lock(self.join(lockname), 0, releasefn, desc=desc)
351 except lock.LockHeld, inst:
350 except lock.LockHeld, inst:
352 if not wait:
351 if not wait:
353 raise
352 raise
354 self.ui.warn(_("waiting for lock on %s held by %s\n") %
353 self.ui.warn(_("waiting for lock on %s held by %s\n") %
355 (desc, inst.args[0]))
354 (desc, inst.args[0]))
356 # default to 600 seconds timeout
355 # default to 600 seconds timeout
357 l = lock.lock(self.join(lockname),
356 l = lock.lock(self.join(lockname),
358 int(self.ui.config("ui", "timeout") or 600),
357 int(self.ui.config("ui", "timeout") or 600),
359 releasefn, desc=desc)
358 releasefn, desc=desc)
360 if acquirefn:
359 if acquirefn:
361 acquirefn()
360 acquirefn()
362 return l
361 return l
363
362
364 def lock(self, wait=1):
363 def lock(self, wait=1):
365 return self.do_lock("lock", wait, acquirefn=self.reload,
364 return self.do_lock("lock", wait, acquirefn=self.reload,
366 desc=_('repository %s') % self.origroot)
365 desc=_('repository %s') % self.origroot)
367
366
368 def wlock(self, wait=1):
367 def wlock(self, wait=1):
369 return self.do_lock("wlock", wait, self.dirstate.write,
368 return self.do_lock("wlock", wait, self.dirstate.write,
370 self.wreload,
369 self.wreload,
371 desc=_('working directory of %s') % self.origroot)
370 desc=_('working directory of %s') % self.origroot)
372
371
373 def checkfilemerge(self, filename, text, filelog, manifest1, manifest2):
372 def checkfilemerge(self, filename, text, filelog, manifest1, manifest2):
374 "determine whether a new filenode is needed"
373 "determine whether a new filenode is needed"
375 fp1 = manifest1.get(filename, nullid)
374 fp1 = manifest1.get(filename, nullid)
376 fp2 = manifest2.get(filename, nullid)
375 fp2 = manifest2.get(filename, nullid)
377
376
378 if fp2 != nullid:
377 if fp2 != nullid:
379 # is one parent an ancestor of the other?
378 # is one parent an ancestor of the other?
380 fpa = filelog.ancestor(fp1, fp2)
379 fpa = filelog.ancestor(fp1, fp2)
381 if fpa == fp1:
380 if fpa == fp1:
382 fp1, fp2 = fp2, nullid
381 fp1, fp2 = fp2, nullid
383 elif fpa == fp2:
382 elif fpa == fp2:
384 fp2 = nullid
383 fp2 = nullid
385
384
386 # is the file unmodified from the parent? report existing entry
385 # is the file unmodified from the parent? report existing entry
387 if fp2 == nullid and text == filelog.read(fp1):
386 if fp2 == nullid and text == filelog.read(fp1):
388 return (fp1, None, None)
387 return (fp1, None, None)
389
388
390 return (None, fp1, fp2)
389 return (None, fp1, fp2)
391
390
392 def rawcommit(self, files, text, user, date, p1=None, p2=None, wlock=None):
391 def rawcommit(self, files, text, user, date, p1=None, p2=None, wlock=None):
393 orig_parent = self.dirstate.parents()[0] or nullid
392 orig_parent = self.dirstate.parents()[0] or nullid
394 p1 = p1 or self.dirstate.parents()[0] or nullid
393 p1 = p1 or self.dirstate.parents()[0] or nullid
395 p2 = p2 or self.dirstate.parents()[1] or nullid
394 p2 = p2 or self.dirstate.parents()[1] or nullid
396 c1 = self.changelog.read(p1)
395 c1 = self.changelog.read(p1)
397 c2 = self.changelog.read(p2)
396 c2 = self.changelog.read(p2)
398 m1 = self.manifest.read(c1[0])
397 m1 = self.manifest.read(c1[0])
399 mf1 = self.manifest.readflags(c1[0])
398 mf1 = self.manifest.readflags(c1[0])
400 m2 = self.manifest.read(c2[0])
399 m2 = self.manifest.read(c2[0])
401 changed = []
400 changed = []
402
401
403 if orig_parent == p1:
402 if orig_parent == p1:
404 update_dirstate = 1
403 update_dirstate = 1
405 else:
404 else:
406 update_dirstate = 0
405 update_dirstate = 0
407
406
408 if not wlock:
407 if not wlock:
409 wlock = self.wlock()
408 wlock = self.wlock()
410 l = self.lock()
409 l = self.lock()
411 tr = self.transaction()
410 tr = self.transaction()
412 mm = m1.copy()
411 mm = m1.copy()
413 mfm = mf1.copy()
412 mfm = mf1.copy()
414 linkrev = self.changelog.count()
413 linkrev = self.changelog.count()
415 for f in files:
414 for f in files:
416 try:
415 try:
417 t = self.wread(f)
416 t = self.wread(f)
418 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
417 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
419 r = self.file(f)
418 r = self.file(f)
420 mfm[f] = tm
419 mfm[f] = tm
421
420
422 (entry, fp1, fp2) = self.checkfilemerge(f, t, r, m1, m2)
421 (entry, fp1, fp2) = self.checkfilemerge(f, t, r, m1, m2)
423 if entry:
422 if entry:
424 mm[f] = entry
423 mm[f] = entry
425 continue
424 continue
426
425
427 mm[f] = r.add(t, {}, tr, linkrev, fp1, fp2)
426 mm[f] = r.add(t, {}, tr, linkrev, fp1, fp2)
428 changed.append(f)
427 changed.append(f)
429 if update_dirstate:
428 if update_dirstate:
430 self.dirstate.update([f], "n")
429 self.dirstate.update([f], "n")
431 except IOError:
430 except IOError:
432 try:
431 try:
433 del mm[f]
432 del mm[f]
434 del mfm[f]
433 del mfm[f]
435 if update_dirstate:
434 if update_dirstate:
436 self.dirstate.forget([f])
435 self.dirstate.forget([f])
437 except:
436 except:
438 # deleted from p2?
437 # deleted from p2?
439 pass
438 pass
440
439
441 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
440 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
442 user = user or self.ui.username()
441 user = user or self.ui.username()
443 n = self.changelog.add(mnode, changed, text, tr, p1, p2, user, date)
442 n = self.changelog.add(mnode, changed, text, tr, p1, p2, user, date)
444 tr.close()
443 tr.close()
445 if update_dirstate:
444 if update_dirstate:
446 self.dirstate.setparents(n, nullid)
445 self.dirstate.setparents(n, nullid)
447
446
448 def commit(self, files=None, text="", user=None, date=None,
447 def commit(self, files=None, text="", user=None, date=None,
449 match=util.always, force=False, lock=None, wlock=None):
448 match=util.always, force=False, lock=None, wlock=None,
449 force_editor=False):
450 commit = []
450 commit = []
451 remove = []
451 remove = []
452 changed = []
452 changed = []
453
453
454 if files:
454 if files:
455 for f in files:
455 for f in files:
456 s = self.dirstate.state(f)
456 s = self.dirstate.state(f)
457 if s in 'nmai':
457 if s in 'nmai':
458 commit.append(f)
458 commit.append(f)
459 elif s == 'r':
459 elif s == 'r':
460 remove.append(f)
460 remove.append(f)
461 else:
461 else:
462 self.ui.warn(_("%s not tracked!\n") % f)
462 self.ui.warn(_("%s not tracked!\n") % f)
463 else:
463 else:
464 modified, added, removed, deleted, unknown = self.changes(match=match)
464 modified, added, removed, deleted, unknown = self.changes(match=match)
465 commit = modified + added
465 commit = modified + added
466 remove = removed
466 remove = removed
467
467
468 p1, p2 = self.dirstate.parents()
468 p1, p2 = self.dirstate.parents()
469 c1 = self.changelog.read(p1)
469 c1 = self.changelog.read(p1)
470 c2 = self.changelog.read(p2)
470 c2 = self.changelog.read(p2)
471 m1 = self.manifest.read(c1[0])
471 m1 = self.manifest.read(c1[0])
472 mf1 = self.manifest.readflags(c1[0])
472 mf1 = self.manifest.readflags(c1[0])
473 m2 = self.manifest.read(c2[0])
473 m2 = self.manifest.read(c2[0])
474
474
475 if not commit and not remove and not force and p2 == nullid:
475 if not commit and not remove and not force and p2 == nullid:
476 self.ui.status(_("nothing changed\n"))
476 self.ui.status(_("nothing changed\n"))
477 return None
477 return None
478
478
479 xp1 = hex(p1)
479 xp1 = hex(p1)
480 if p2 == nullid: xp2 = ''
480 if p2 == nullid: xp2 = ''
481 else: xp2 = hex(p2)
481 else: xp2 = hex(p2)
482
482
483 self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
483 self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
484
484
485 if not wlock:
485 if not wlock:
486 wlock = self.wlock()
486 wlock = self.wlock()
487 if not lock:
487 if not lock:
488 lock = self.lock()
488 lock = self.lock()
489 tr = self.transaction()
489 tr = self.transaction()
490
490
491 # check in files
491 # check in files
492 new = {}
492 new = {}
493 linkrev = self.changelog.count()
493 linkrev = self.changelog.count()
494 commit.sort()
494 commit.sort()
495 for f in commit:
495 for f in commit:
496 self.ui.note(f + "\n")
496 self.ui.note(f + "\n")
497 try:
497 try:
498 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
498 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
499 t = self.wread(f)
499 t = self.wread(f)
500 except IOError:
500 except IOError:
501 self.ui.warn(_("trouble committing %s!\n") % f)
501 self.ui.warn(_("trouble committing %s!\n") % f)
502 raise
502 raise
503
503
504 r = self.file(f)
504 r = self.file(f)
505
505
506 meta = {}
506 meta = {}
507 cp = self.dirstate.copied(f)
507 cp = self.dirstate.copied(f)
508 if cp:
508 if cp:
509 meta["copy"] = cp
509 meta["copy"] = cp
510 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
510 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
511 self.ui.debug(_(" %s: copy %s:%s\n") % (f, cp, meta["copyrev"]))
511 self.ui.debug(_(" %s: copy %s:%s\n") % (f, cp, meta["copyrev"]))
512 fp1, fp2 = nullid, nullid
512 fp1, fp2 = nullid, nullid
513 else:
513 else:
514 entry, fp1, fp2 = self.checkfilemerge(f, t, r, m1, m2)
514 entry, fp1, fp2 = self.checkfilemerge(f, t, r, m1, m2)
515 if entry:
515 if entry:
516 new[f] = entry
516 new[f] = entry
517 continue
517 continue
518
518
519 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
519 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
520 # remember what we've added so that we can later calculate
520 # remember what we've added so that we can later calculate
521 # the files to pull from a set of changesets
521 # the files to pull from a set of changesets
522 changed.append(f)
522 changed.append(f)
523
523
524 # update manifest
524 # update manifest
525 m1 = m1.copy()
525 m1 = m1.copy()
526 m1.update(new)
526 m1.update(new)
527 for f in remove:
527 for f in remove:
528 if f in m1:
528 if f in m1:
529 del m1[f]
529 del m1[f]
530 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0],
530 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0],
531 (new, remove))
531 (new, remove))
532
532
533 # add changeset
533 # add changeset
534 new = new.keys()
534 new = new.keys()
535 new.sort()
535 new.sort()
536
536
537 user = user or self.ui.username()
537 user = user or self.ui.username()
538 if not text:
538 if not text or force_editor:
539 edittext = [""]
539 edittext = []
540 if text:
541 edittext.append(text)
542 edittext.append("")
540 if p2 != nullid:
543 if p2 != nullid:
541 edittext.append("HG: branch merge")
544 edittext.append("HG: branch merge")
542 edittext.extend(["HG: changed %s" % f for f in changed])
545 edittext.extend(["HG: changed %s" % f for f in changed])
543 edittext.extend(["HG: removed %s" % f for f in remove])
546 edittext.extend(["HG: removed %s" % f for f in remove])
544 if not changed and not remove:
547 if not changed and not remove:
545 edittext.append("HG: no files changed")
548 edittext.append("HG: no files changed")
546 edittext.append("")
549 edittext.append("")
547 # run editor in the repository root
550 # run editor in the repository root
548 olddir = os.getcwd()
551 olddir = os.getcwd()
549 os.chdir(self.root)
552 os.chdir(self.root)
550 edittext = self.ui.edit("\n".join(edittext), user)
553 edittext = self.ui.edit("\n".join(edittext), user)
551 os.chdir(olddir)
554 os.chdir(olddir)
552 if not edittext.rstrip():
555 if not edittext.rstrip():
553 return None
556 return None
554 text = edittext
557 text = edittext
555
558
556 n = self.changelog.add(mn, changed + remove, text, tr, p1, p2, user, date)
559 n = self.changelog.add(mn, changed + remove, text, tr, p1, p2, user, date)
557 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
560 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
558 parent2=xp2)
561 parent2=xp2)
559 tr.close()
562 tr.close()
560
563
561 self.dirstate.setparents(n)
564 self.dirstate.setparents(n)
562 self.dirstate.update(new, "n")
565 self.dirstate.update(new, "n")
563 self.dirstate.forget(remove)
566 self.dirstate.forget(remove)
564
567
565 self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
568 self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
566 return n
569 return n
567
570
568 def walk(self, node=None, files=[], match=util.always, badmatch=None):
571 def walk(self, node=None, files=[], match=util.always, badmatch=None):
569 if node:
572 if node:
570 fdict = dict.fromkeys(files)
573 fdict = dict.fromkeys(files)
571 for fn in self.manifest.read(self.changelog.read(node)[0]):
574 for fn in self.manifest.read(self.changelog.read(node)[0]):
572 fdict.pop(fn, None)
575 fdict.pop(fn, None)
573 if match(fn):
576 if match(fn):
574 yield 'm', fn
577 yield 'm', fn
575 for fn in fdict:
578 for fn in fdict:
576 if badmatch and badmatch(fn):
579 if badmatch and badmatch(fn):
577 if match(fn):
580 if match(fn):
578 yield 'b', fn
581 yield 'b', fn
579 else:
582 else:
580 self.ui.warn(_('%s: No such file in rev %s\n') % (
583 self.ui.warn(_('%s: No such file in rev %s\n') % (
581 util.pathto(self.getcwd(), fn), short(node)))
584 util.pathto(self.getcwd(), fn), short(node)))
582 else:
585 else:
583 for src, fn in self.dirstate.walk(files, match, badmatch=badmatch):
586 for src, fn in self.dirstate.walk(files, match, badmatch=badmatch):
584 yield src, fn
587 yield src, fn
585
588
586 def changes(self, node1=None, node2=None, files=[], match=util.always,
589 def changes(self, node1=None, node2=None, files=[], match=util.always,
587 wlock=None, show_ignored=None):
590 wlock=None, show_ignored=None):
588 """return changes between two nodes or node and working directory
591 """return changes between two nodes or node and working directory
589
592
590 If node1 is None, use the first dirstate parent instead.
593 If node1 is None, use the first dirstate parent instead.
591 If node2 is None, compare node1 with working directory.
594 If node2 is None, compare node1 with working directory.
592 """
595 """
593
596
594 def fcmp(fn, mf):
597 def fcmp(fn, mf):
595 t1 = self.wread(fn)
598 t1 = self.wread(fn)
596 t2 = self.file(fn).read(mf.get(fn, nullid))
599 t2 = self.file(fn).read(mf.get(fn, nullid))
597 return cmp(t1, t2)
600 return cmp(t1, t2)
598
601
599 def mfmatches(node):
602 def mfmatches(node):
600 change = self.changelog.read(node)
603 change = self.changelog.read(node)
601 mf = dict(self.manifest.read(change[0]))
604 mf = dict(self.manifest.read(change[0]))
602 for fn in mf.keys():
605 for fn in mf.keys():
603 if not match(fn):
606 if not match(fn):
604 del mf[fn]
607 del mf[fn]
605 return mf
608 return mf
606
609
607 if node1:
610 if node1:
608 # read the manifest from node1 before the manifest from node2,
611 # read the manifest from node1 before the manifest from node2,
609 # so that we'll hit the manifest cache if we're going through
612 # so that we'll hit the manifest cache if we're going through
610 # all the revisions in parent->child order.
613 # all the revisions in parent->child order.
611 mf1 = mfmatches(node1)
614 mf1 = mfmatches(node1)
612
615
613 # are we comparing the working directory?
616 # are we comparing the working directory?
614 if not node2:
617 if not node2:
615 if not wlock:
618 if not wlock:
616 try:
619 try:
617 wlock = self.wlock(wait=0)
620 wlock = self.wlock(wait=0)
618 except lock.LockException:
621 except lock.LockException:
619 wlock = None
622 wlock = None
620 lookup, modified, added, removed, deleted, unknown, ignored = (
623 lookup, modified, added, removed, deleted, unknown, ignored = (
621 self.dirstate.changes(files, match, show_ignored))
624 self.dirstate.changes(files, match, show_ignored))
622
625
623 # are we comparing working dir against its parent?
626 # are we comparing working dir against its parent?
624 if not node1:
627 if not node1:
625 if lookup:
628 if lookup:
626 # do a full compare of any files that might have changed
629 # do a full compare of any files that might have changed
627 mf2 = mfmatches(self.dirstate.parents()[0])
630 mf2 = mfmatches(self.dirstate.parents()[0])
628 for f in lookup:
631 for f in lookup:
629 if fcmp(f, mf2):
632 if fcmp(f, mf2):
630 modified.append(f)
633 modified.append(f)
631 elif wlock is not None:
634 elif wlock is not None:
632 self.dirstate.update([f], "n")
635 self.dirstate.update([f], "n")
633 else:
636 else:
634 # we are comparing working dir against non-parent
637 # we are comparing working dir against non-parent
635 # generate a pseudo-manifest for the working dir
638 # generate a pseudo-manifest for the working dir
636 mf2 = mfmatches(self.dirstate.parents()[0])
639 mf2 = mfmatches(self.dirstate.parents()[0])
637 for f in lookup + modified + added:
640 for f in lookup + modified + added:
638 mf2[f] = ""
641 mf2[f] = ""
639 for f in removed:
642 for f in removed:
640 if f in mf2:
643 if f in mf2:
641 del mf2[f]
644 del mf2[f]
642 else:
645 else:
643 # we are comparing two revisions
646 # we are comparing two revisions
644 deleted, unknown, ignored = [], [], []
647 deleted, unknown, ignored = [], [], []
645 mf2 = mfmatches(node2)
648 mf2 = mfmatches(node2)
646
649
647 if node1:
650 if node1:
648 # flush lists from dirstate before comparing manifests
651 # flush lists from dirstate before comparing manifests
649 modified, added = [], []
652 modified, added = [], []
650
653
651 for fn in mf2:
654 for fn in mf2:
652 if mf1.has_key(fn):
655 if mf1.has_key(fn):
653 if mf1[fn] != mf2[fn] and (mf2[fn] != "" or fcmp(fn, mf1)):
656 if mf1[fn] != mf2[fn] and (mf2[fn] != "" or fcmp(fn, mf1)):
654 modified.append(fn)
657 modified.append(fn)
655 del mf1[fn]
658 del mf1[fn]
656 else:
659 else:
657 added.append(fn)
660 added.append(fn)
658
661
659 removed = mf1.keys()
662 removed = mf1.keys()
660
663
661 # sort and return results:
664 # sort and return results:
662 for l in modified, added, removed, deleted, unknown, ignored:
665 for l in modified, added, removed, deleted, unknown, ignored:
663 l.sort()
666 l.sort()
664 if show_ignored is None:
667 if show_ignored is None:
665 return (modified, added, removed, deleted, unknown)
668 return (modified, added, removed, deleted, unknown)
666 else:
669 else:
667 return (modified, added, removed, deleted, unknown, ignored)
670 return (modified, added, removed, deleted, unknown, ignored)
668
671
669 def add(self, list, wlock=None):
672 def add(self, list, wlock=None):
670 if not wlock:
673 if not wlock:
671 wlock = self.wlock()
674 wlock = self.wlock()
672 for f in list:
675 for f in list:
673 p = self.wjoin(f)
676 p = self.wjoin(f)
674 if not os.path.exists(p):
677 if not os.path.exists(p):
675 self.ui.warn(_("%s does not exist!\n") % f)
678 self.ui.warn(_("%s does not exist!\n") % f)
676 elif not os.path.isfile(p):
679 elif not os.path.isfile(p):
677 self.ui.warn(_("%s not added: only files supported currently\n")
680 self.ui.warn(_("%s not added: only files supported currently\n")
678 % f)
681 % f)
679 elif self.dirstate.state(f) in 'an':
682 elif self.dirstate.state(f) in 'an':
680 self.ui.warn(_("%s already tracked!\n") % f)
683 self.ui.warn(_("%s already tracked!\n") % f)
681 else:
684 else:
682 self.dirstate.update([f], "a")
685 self.dirstate.update([f], "a")
683
686
684 def forget(self, list, wlock=None):
687 def forget(self, list, wlock=None):
685 if not wlock:
688 if not wlock:
686 wlock = self.wlock()
689 wlock = self.wlock()
687 for f in list:
690 for f in list:
688 if self.dirstate.state(f) not in 'ai':
691 if self.dirstate.state(f) not in 'ai':
689 self.ui.warn(_("%s not added!\n") % f)
692 self.ui.warn(_("%s not added!\n") % f)
690 else:
693 else:
691 self.dirstate.forget([f])
694 self.dirstate.forget([f])
692
695
693 def remove(self, list, unlink=False, wlock=None):
696 def remove(self, list, unlink=False, wlock=None):
694 if unlink:
697 if unlink:
695 for f in list:
698 for f in list:
696 try:
699 try:
697 util.unlink(self.wjoin(f))
700 util.unlink(self.wjoin(f))
698 except OSError, inst:
701 except OSError, inst:
699 if inst.errno != errno.ENOENT:
702 if inst.errno != errno.ENOENT:
700 raise
703 raise
701 if not wlock:
704 if not wlock:
702 wlock = self.wlock()
705 wlock = self.wlock()
703 for f in list:
706 for f in list:
704 p = self.wjoin(f)
707 p = self.wjoin(f)
705 if os.path.exists(p):
708 if os.path.exists(p):
706 self.ui.warn(_("%s still exists!\n") % f)
709 self.ui.warn(_("%s still exists!\n") % f)
707 elif self.dirstate.state(f) == 'a':
710 elif self.dirstate.state(f) == 'a':
708 self.dirstate.forget([f])
711 self.dirstate.forget([f])
709 elif f not in self.dirstate:
712 elif f not in self.dirstate:
710 self.ui.warn(_("%s not tracked!\n") % f)
713 self.ui.warn(_("%s not tracked!\n") % f)
711 else:
714 else:
712 self.dirstate.update([f], "r")
715 self.dirstate.update([f], "r")
713
716
714 def undelete(self, list, wlock=None):
717 def undelete(self, list, wlock=None):
715 p = self.dirstate.parents()[0]
718 p = self.dirstate.parents()[0]
716 mn = self.changelog.read(p)[0]
719 mn = self.changelog.read(p)[0]
717 mf = self.manifest.readflags(mn)
720 mf = self.manifest.readflags(mn)
718 m = self.manifest.read(mn)
721 m = self.manifest.read(mn)
719 if not wlock:
722 if not wlock:
720 wlock = self.wlock()
723 wlock = self.wlock()
721 for f in list:
724 for f in list:
722 if self.dirstate.state(f) not in "r":
725 if self.dirstate.state(f) not in "r":
723 self.ui.warn("%s not removed!\n" % f)
726 self.ui.warn("%s not removed!\n" % f)
724 else:
727 else:
725 t = self.file(f).read(m[f])
728 t = self.file(f).read(m[f])
726 self.wwrite(f, t)
729 self.wwrite(f, t)
727 util.set_exec(self.wjoin(f), mf[f])
730 util.set_exec(self.wjoin(f), mf[f])
728 self.dirstate.update([f], "n")
731 self.dirstate.update([f], "n")
729
732
730 def copy(self, source, dest, wlock=None):
733 def copy(self, source, dest, wlock=None):
731 p = self.wjoin(dest)
734 p = self.wjoin(dest)
732 if not os.path.exists(p):
735 if not os.path.exists(p):
733 self.ui.warn(_("%s does not exist!\n") % dest)
736 self.ui.warn(_("%s does not exist!\n") % dest)
734 elif not os.path.isfile(p):
737 elif not os.path.isfile(p):
735 self.ui.warn(_("copy failed: %s is not a file\n") % dest)
738 self.ui.warn(_("copy failed: %s is not a file\n") % dest)
736 else:
739 else:
737 if not wlock:
740 if not wlock:
738 wlock = self.wlock()
741 wlock = self.wlock()
739 if self.dirstate.state(dest) == '?':
742 if self.dirstate.state(dest) == '?':
740 self.dirstate.update([dest], "a")
743 self.dirstate.update([dest], "a")
741 self.dirstate.copy(source, dest)
744 self.dirstate.copy(source, dest)
742
745
743 def heads(self, start=None):
746 def heads(self, start=None):
744 heads = self.changelog.heads(start)
747 heads = self.changelog.heads(start)
745 # sort the output in rev descending order
748 # sort the output in rev descending order
746 heads = [(-self.changelog.rev(h), h) for h in heads]
749 heads = [(-self.changelog.rev(h), h) for h in heads]
747 heads.sort()
750 heads.sort()
748 return [n for (r, n) in heads]
751 return [n for (r, n) in heads]
749
752
750 # branchlookup returns a dict giving a list of branches for
753 # branchlookup returns a dict giving a list of branches for
751 # each head. A branch is defined as the tag of a node or
754 # each head. A branch is defined as the tag of a node or
752 # the branch of the node's parents. If a node has multiple
755 # the branch of the node's parents. If a node has multiple
753 # branch tags, tags are eliminated if they are visible from other
756 # branch tags, tags are eliminated if they are visible from other
754 # branch tags.
757 # branch tags.
755 #
758 #
756 # So, for this graph: a->b->c->d->e
759 # So, for this graph: a->b->c->d->e
757 # \ /
760 # \ /
758 # aa -----/
761 # aa -----/
759 # a has tag 2.6.12
762 # a has tag 2.6.12
760 # d has tag 2.6.13
763 # d has tag 2.6.13
761 # e would have branch tags for 2.6.12 and 2.6.13. Because the node
764 # e would have branch tags for 2.6.12 and 2.6.13. Because the node
762 # for 2.6.12 can be reached from the node 2.6.13, that is eliminated
765 # for 2.6.12 can be reached from the node 2.6.13, that is eliminated
763 # from the list.
766 # from the list.
764 #
767 #
765 # It is possible that more than one head will have the same branch tag.
768 # It is possible that more than one head will have the same branch tag.
766 # callers need to check the result for multiple heads under the same
769 # callers need to check the result for multiple heads under the same
767 # branch tag if that is a problem for them (ie checkout of a specific
770 # branch tag if that is a problem for them (ie checkout of a specific
768 # branch).
771 # branch).
769 #
772 #
770 # passing in a specific branch will limit the depth of the search
773 # passing in a specific branch will limit the depth of the search
771 # through the parents. It won't limit the branches returned in the
774 # through the parents. It won't limit the branches returned in the
772 # result though.
775 # result though.
773 def branchlookup(self, heads=None, branch=None):
776 def branchlookup(self, heads=None, branch=None):
774 if not heads:
777 if not heads:
775 heads = self.heads()
778 heads = self.heads()
776 headt = [ h for h in heads ]
779 headt = [ h for h in heads ]
777 chlog = self.changelog
780 chlog = self.changelog
778 branches = {}
781 branches = {}
779 merges = []
782 merges = []
780 seenmerge = {}
783 seenmerge = {}
781
784
782 # traverse the tree once for each head, recording in the branches
785 # traverse the tree once for each head, recording in the branches
783 # dict which tags are visible from this head. The branches
786 # dict which tags are visible from this head. The branches
784 # dict also records which tags are visible from each tag
787 # dict also records which tags are visible from each tag
785 # while we traverse.
788 # while we traverse.
786 while headt or merges:
789 while headt or merges:
787 if merges:
790 if merges:
788 n, found = merges.pop()
791 n, found = merges.pop()
789 visit = [n]
792 visit = [n]
790 else:
793 else:
791 h = headt.pop()
794 h = headt.pop()
792 visit = [h]
795 visit = [h]
793 found = [h]
796 found = [h]
794 seen = {}
797 seen = {}
795 while visit:
798 while visit:
796 n = visit.pop()
799 n = visit.pop()
797 if n in seen:
800 if n in seen:
798 continue
801 continue
799 pp = chlog.parents(n)
802 pp = chlog.parents(n)
800 tags = self.nodetags(n)
803 tags = self.nodetags(n)
801 if tags:
804 if tags:
802 for x in tags:
805 for x in tags:
803 if x == 'tip':
806 if x == 'tip':
804 continue
807 continue
805 for f in found:
808 for f in found:
806 branches.setdefault(f, {})[n] = 1
809 branches.setdefault(f, {})[n] = 1
807 branches.setdefault(n, {})[n] = 1
810 branches.setdefault(n, {})[n] = 1
808 break
811 break
809 if n not in found:
812 if n not in found:
810 found.append(n)
813 found.append(n)
811 if branch in tags:
814 if branch in tags:
812 continue
815 continue
813 seen[n] = 1
816 seen[n] = 1
814 if pp[1] != nullid and n not in seenmerge:
817 if pp[1] != nullid and n not in seenmerge:
815 merges.append((pp[1], [x for x in found]))
818 merges.append((pp[1], [x for x in found]))
816 seenmerge[n] = 1
819 seenmerge[n] = 1
817 if pp[0] != nullid:
820 if pp[0] != nullid:
818 visit.append(pp[0])
821 visit.append(pp[0])
819 # traverse the branches dict, eliminating branch tags from each
822 # traverse the branches dict, eliminating branch tags from each
820 # head that are visible from another branch tag for that head.
823 # head that are visible from another branch tag for that head.
821 out = {}
824 out = {}
822 viscache = {}
825 viscache = {}
823 for h in heads:
826 for h in heads:
824 def visible(node):
827 def visible(node):
825 if node in viscache:
828 if node in viscache:
826 return viscache[node]
829 return viscache[node]
827 ret = {}
830 ret = {}
828 visit = [node]
831 visit = [node]
829 while visit:
832 while visit:
830 x = visit.pop()
833 x = visit.pop()
831 if x in viscache:
834 if x in viscache:
832 ret.update(viscache[x])
835 ret.update(viscache[x])
833 elif x not in ret:
836 elif x not in ret:
834 ret[x] = 1
837 ret[x] = 1
835 if x in branches:
838 if x in branches:
836 visit[len(visit):] = branches[x].keys()
839 visit[len(visit):] = branches[x].keys()
837 viscache[node] = ret
840 viscache[node] = ret
838 return ret
841 return ret
839 if h not in branches:
842 if h not in branches:
840 continue
843 continue
841 # O(n^2), but somewhat limited. This only searches the
844 # O(n^2), but somewhat limited. This only searches the
842 # tags visible from a specific head, not all the tags in the
845 # tags visible from a specific head, not all the tags in the
843 # whole repo.
846 # whole repo.
844 for b in branches[h]:
847 for b in branches[h]:
845 vis = False
848 vis = False
846 for bb in branches[h].keys():
849 for bb in branches[h].keys():
847 if b != bb:
850 if b != bb:
848 if b in visible(bb):
851 if b in visible(bb):
849 vis = True
852 vis = True
850 break
853 break
851 if not vis:
854 if not vis:
852 l = out.setdefault(h, [])
855 l = out.setdefault(h, [])
853 l[len(l):] = self.nodetags(b)
856 l[len(l):] = self.nodetags(b)
854 return out
857 return out
855
858
856 def branches(self, nodes):
859 def branches(self, nodes):
857 if not nodes:
860 if not nodes:
858 nodes = [self.changelog.tip()]
861 nodes = [self.changelog.tip()]
859 b = []
862 b = []
860 for n in nodes:
863 for n in nodes:
861 t = n
864 t = n
862 while n:
865 while n:
863 p = self.changelog.parents(n)
866 p = self.changelog.parents(n)
864 if p[1] != nullid or p[0] == nullid:
867 if p[1] != nullid or p[0] == nullid:
865 b.append((t, n, p[0], p[1]))
868 b.append((t, n, p[0], p[1]))
866 break
869 break
867 n = p[0]
870 n = p[0]
868 return b
871 return b
869
872
870 def between(self, pairs):
873 def between(self, pairs):
871 r = []
874 r = []
872
875
873 for top, bottom in pairs:
876 for top, bottom in pairs:
874 n, l, i = top, [], 0
877 n, l, i = top, [], 0
875 f = 1
878 f = 1
876
879
877 while n != bottom:
880 while n != bottom:
878 p = self.changelog.parents(n)[0]
881 p = self.changelog.parents(n)[0]
879 if i == f:
882 if i == f:
880 l.append(n)
883 l.append(n)
881 f = f * 2
884 f = f * 2
882 n = p
885 n = p
883 i += 1
886 i += 1
884
887
885 r.append(l)
888 r.append(l)
886
889
887 return r
890 return r
888
891
889 def findincoming(self, remote, base=None, heads=None, force=False):
892 def findincoming(self, remote, base=None, heads=None, force=False):
890 m = self.changelog.nodemap
893 m = self.changelog.nodemap
891 search = []
894 search = []
892 fetch = {}
895 fetch = {}
893 seen = {}
896 seen = {}
894 seenbranch = {}
897 seenbranch = {}
895 if base == None:
898 if base == None:
896 base = {}
899 base = {}
897
900
898 if not heads:
901 if not heads:
899 heads = remote.heads()
902 heads = remote.heads()
900
903
901 if self.changelog.tip() == nullid:
904 if self.changelog.tip() == nullid:
902 if heads != [nullid]:
905 if heads != [nullid]:
903 return [nullid]
906 return [nullid]
904 return []
907 return []
905
908
906 # assume we're closer to the tip than the root
909 # assume we're closer to the tip than the root
907 # and start by examining the heads
910 # and start by examining the heads
908 self.ui.status(_("searching for changes\n"))
911 self.ui.status(_("searching for changes\n"))
909
912
910 unknown = []
913 unknown = []
911 for h in heads:
914 for h in heads:
912 if h not in m:
915 if h not in m:
913 unknown.append(h)
916 unknown.append(h)
914 else:
917 else:
915 base[h] = 1
918 base[h] = 1
916
919
917 if not unknown:
920 if not unknown:
918 return []
921 return []
919
922
920 rep = {}
923 rep = {}
921 reqcnt = 0
924 reqcnt = 0
922
925
923 # search through remote branches
926 # search through remote branches
924 # a 'branch' here is a linear segment of history, with four parts:
927 # a 'branch' here is a linear segment of history, with four parts:
925 # head, root, first parent, second parent
928 # head, root, first parent, second parent
926 # (a branch always has two parents (or none) by definition)
929 # (a branch always has two parents (or none) by definition)
927 unknown = remote.branches(unknown)
930 unknown = remote.branches(unknown)
928 while unknown:
931 while unknown:
929 r = []
932 r = []
930 while unknown:
933 while unknown:
931 n = unknown.pop(0)
934 n = unknown.pop(0)
932 if n[0] in seen:
935 if n[0] in seen:
933 continue
936 continue
934
937
935 self.ui.debug(_("examining %s:%s\n")
938 self.ui.debug(_("examining %s:%s\n")
936 % (short(n[0]), short(n[1])))
939 % (short(n[0]), short(n[1])))
937 if n[0] == nullid:
940 if n[0] == nullid:
938 break
941 break
939 if n in seenbranch:
942 if n in seenbranch:
940 self.ui.debug(_("branch already found\n"))
943 self.ui.debug(_("branch already found\n"))
941 continue
944 continue
942 if n[1] and n[1] in m: # do we know the base?
945 if n[1] and n[1] in m: # do we know the base?
943 self.ui.debug(_("found incomplete branch %s:%s\n")
946 self.ui.debug(_("found incomplete branch %s:%s\n")
944 % (short(n[0]), short(n[1])))
947 % (short(n[0]), short(n[1])))
945 search.append(n) # schedule branch range for scanning
948 search.append(n) # schedule branch range for scanning
946 seenbranch[n] = 1
949 seenbranch[n] = 1
947 else:
950 else:
948 if n[1] not in seen and n[1] not in fetch:
951 if n[1] not in seen and n[1] not in fetch:
949 if n[2] in m and n[3] in m:
952 if n[2] in m and n[3] in m:
950 self.ui.debug(_("found new changeset %s\n") %
953 self.ui.debug(_("found new changeset %s\n") %
951 short(n[1]))
954 short(n[1]))
952 fetch[n[1]] = 1 # earliest unknown
955 fetch[n[1]] = 1 # earliest unknown
953 base[n[2]] = 1 # latest known
956 base[n[2]] = 1 # latest known
954 continue
957 continue
955
958
956 for a in n[2:4]:
959 for a in n[2:4]:
957 if a not in rep:
960 if a not in rep:
958 r.append(a)
961 r.append(a)
959 rep[a] = 1
962 rep[a] = 1
960
963
961 seen[n[0]] = 1
964 seen[n[0]] = 1
962
965
963 if r:
966 if r:
964 reqcnt += 1
967 reqcnt += 1
965 self.ui.debug(_("request %d: %s\n") %
968 self.ui.debug(_("request %d: %s\n") %
966 (reqcnt, " ".join(map(short, r))))
969 (reqcnt, " ".join(map(short, r))))
967 for p in range(0, len(r), 10):
970 for p in range(0, len(r), 10):
968 for b in remote.branches(r[p:p+10]):
971 for b in remote.branches(r[p:p+10]):
969 self.ui.debug(_("received %s:%s\n") %
972 self.ui.debug(_("received %s:%s\n") %
970 (short(b[0]), short(b[1])))
973 (short(b[0]), short(b[1])))
971 if b[0] in m:
974 if b[0] in m:
972 self.ui.debug(_("found base node %s\n")
975 self.ui.debug(_("found base node %s\n")
973 % short(b[0]))
976 % short(b[0]))
974 base[b[0]] = 1
977 base[b[0]] = 1
975 elif b[0] not in seen:
978 elif b[0] not in seen:
976 unknown.append(b)
979 unknown.append(b)
977
980
978 # do binary search on the branches we found
981 # do binary search on the branches we found
979 while search:
982 while search:
980 n = search.pop(0)
983 n = search.pop(0)
981 reqcnt += 1
984 reqcnt += 1
982 l = remote.between([(n[0], n[1])])[0]
985 l = remote.between([(n[0], n[1])])[0]
983 l.append(n[1])
986 l.append(n[1])
984 p = n[0]
987 p = n[0]
985 f = 1
988 f = 1
986 for i in l:
989 for i in l:
987 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
990 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
988 if i in m:
991 if i in m:
989 if f <= 2:
992 if f <= 2:
990 self.ui.debug(_("found new branch changeset %s\n") %
993 self.ui.debug(_("found new branch changeset %s\n") %
991 short(p))
994 short(p))
992 fetch[p] = 1
995 fetch[p] = 1
993 base[i] = 1
996 base[i] = 1
994 else:
997 else:
995 self.ui.debug(_("narrowed branch search to %s:%s\n")
998 self.ui.debug(_("narrowed branch search to %s:%s\n")
996 % (short(p), short(i)))
999 % (short(p), short(i)))
997 search.append((p, i))
1000 search.append((p, i))
998 break
1001 break
999 p, f = i, f * 2
1002 p, f = i, f * 2
1000
1003
1001 # sanity check our fetch list
1004 # sanity check our fetch list
1002 for f in fetch.keys():
1005 for f in fetch.keys():
1003 if f in m:
1006 if f in m:
1004 raise repo.RepoError(_("already have changeset ") + short(f[:4]))
1007 raise repo.RepoError(_("already have changeset ") + short(f[:4]))
1005
1008
1006 if base.keys() == [nullid]:
1009 if base.keys() == [nullid]:
1007 if force:
1010 if force:
1008 self.ui.warn(_("warning: repository is unrelated\n"))
1011 self.ui.warn(_("warning: repository is unrelated\n"))
1009 else:
1012 else:
1010 raise util.Abort(_("repository is unrelated"))
1013 raise util.Abort(_("repository is unrelated"))
1011
1014
1012 self.ui.note(_("found new changesets starting at ") +
1015 self.ui.note(_("found new changesets starting at ") +
1013 " ".join([short(f) for f in fetch]) + "\n")
1016 " ".join([short(f) for f in fetch]) + "\n")
1014
1017
1015 self.ui.debug(_("%d total queries\n") % reqcnt)
1018 self.ui.debug(_("%d total queries\n") % reqcnt)
1016
1019
1017 return fetch.keys()
1020 return fetch.keys()
1018
1021
1019 def findoutgoing(self, remote, base=None, heads=None, force=False):
1022 def findoutgoing(self, remote, base=None, heads=None, force=False):
1020 """Return list of nodes that are roots of subsets not in remote
1023 """Return list of nodes that are roots of subsets not in remote
1021
1024
1022 If base dict is specified, assume that these nodes and their parents
1025 If base dict is specified, assume that these nodes and their parents
1023 exist on the remote side.
1026 exist on the remote side.
1024 If a list of heads is specified, return only nodes which are heads
1027 If a list of heads is specified, return only nodes which are heads
1025 or ancestors of these heads, and return a second element which
1028 or ancestors of these heads, and return a second element which
1026 contains all remote heads which get new children.
1029 contains all remote heads which get new children.
1027 """
1030 """
1028 if base == None:
1031 if base == None:
1029 base = {}
1032 base = {}
1030 self.findincoming(remote, base, heads, force=force)
1033 self.findincoming(remote, base, heads, force=force)
1031
1034
1032 self.ui.debug(_("common changesets up to ")
1035 self.ui.debug(_("common changesets up to ")
1033 + " ".join(map(short, base.keys())) + "\n")
1036 + " ".join(map(short, base.keys())) + "\n")
1034
1037
1035 remain = dict.fromkeys(self.changelog.nodemap)
1038 remain = dict.fromkeys(self.changelog.nodemap)
1036
1039
1037 # prune everything remote has from the tree
1040 # prune everything remote has from the tree
1038 del remain[nullid]
1041 del remain[nullid]
1039 remove = base.keys()
1042 remove = base.keys()
1040 while remove:
1043 while remove:
1041 n = remove.pop(0)
1044 n = remove.pop(0)
1042 if n in remain:
1045 if n in remain:
1043 del remain[n]
1046 del remain[n]
1044 for p in self.changelog.parents(n):
1047 for p in self.changelog.parents(n):
1045 remove.append(p)
1048 remove.append(p)
1046
1049
1047 # find every node whose parents have been pruned
1050 # find every node whose parents have been pruned
1048 subset = []
1051 subset = []
1049 # find every remote head that will get new children
1052 # find every remote head that will get new children
1050 updated_heads = {}
1053 updated_heads = {}
1051 for n in remain:
1054 for n in remain:
1052 p1, p2 = self.changelog.parents(n)
1055 p1, p2 = self.changelog.parents(n)
1053 if p1 not in remain and p2 not in remain:
1056 if p1 not in remain and p2 not in remain:
1054 subset.append(n)
1057 subset.append(n)
1055 if heads:
1058 if heads:
1056 if p1 in heads:
1059 if p1 in heads:
1057 updated_heads[p1] = True
1060 updated_heads[p1] = True
1058 if p2 in heads:
1061 if p2 in heads:
1059 updated_heads[p2] = True
1062 updated_heads[p2] = True
1060
1063
1061 # this is the set of all roots we have to push
1064 # this is the set of all roots we have to push
1062 if heads:
1065 if heads:
1063 return subset, updated_heads.keys()
1066 return subset, updated_heads.keys()
1064 else:
1067 else:
1065 return subset
1068 return subset
1066
1069
1067 def pull(self, remote, heads=None, force=False):
1070 def pull(self, remote, heads=None, force=False):
1068 l = self.lock()
1071 l = self.lock()
1069
1072
1070 fetch = self.findincoming(remote, force=force)
1073 fetch = self.findincoming(remote, force=force)
1071 if fetch == [nullid]:
1074 if fetch == [nullid]:
1072 self.ui.status(_("requesting all changes\n"))
1075 self.ui.status(_("requesting all changes\n"))
1073
1076
1074 if not fetch:
1077 if not fetch:
1075 self.ui.status(_("no changes found\n"))
1078 self.ui.status(_("no changes found\n"))
1076 return 0
1079 return 0
1077
1080
1078 if heads is None:
1081 if heads is None:
1079 cg = remote.changegroup(fetch, 'pull')
1082 cg = remote.changegroup(fetch, 'pull')
1080 else:
1083 else:
1081 cg = remote.changegroupsubset(fetch, heads, 'pull')
1084 cg = remote.changegroupsubset(fetch, heads, 'pull')
1082 return self.addchangegroup(cg, 'pull')
1085 return self.addchangegroup(cg, 'pull')
1083
1086
1084 def push(self, remote, force=False, revs=None):
1087 def push(self, remote, force=False, revs=None):
1085 lock = remote.lock()
1088 lock = remote.lock()
1086
1089
1087 base = {}
1090 base = {}
1088 remote_heads = remote.heads()
1091 remote_heads = remote.heads()
1089 inc = self.findincoming(remote, base, remote_heads, force=force)
1092 inc = self.findincoming(remote, base, remote_heads, force=force)
1090 if not force and inc:
1093 if not force and inc:
1091 self.ui.warn(_("abort: unsynced remote changes!\n"))
1094 self.ui.warn(_("abort: unsynced remote changes!\n"))
1092 self.ui.status(_("(did you forget to sync?"
1095 self.ui.status(_("(did you forget to sync?"
1093 " use push -f to force)\n"))
1096 " use push -f to force)\n"))
1094 return 1
1097 return 1
1095
1098
1096 update, updated_heads = self.findoutgoing(remote, base, remote_heads)
1099 update, updated_heads = self.findoutgoing(remote, base, remote_heads)
1097 if revs is not None:
1100 if revs is not None:
1098 msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
1101 msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
1099 else:
1102 else:
1100 bases, heads = update, self.changelog.heads()
1103 bases, heads = update, self.changelog.heads()
1101
1104
1102 if not bases:
1105 if not bases:
1103 self.ui.status(_("no changes found\n"))
1106 self.ui.status(_("no changes found\n"))
1104 return 1
1107 return 1
1105 elif not force:
1108 elif not force:
1106 # FIXME we don't properly detect creation of new heads
1109 # FIXME we don't properly detect creation of new heads
1107 # in the push -r case, assume the user knows what he's doing
1110 # in the push -r case, assume the user knows what he's doing
1108 if not revs and len(remote_heads) < len(heads) \
1111 if not revs and len(remote_heads) < len(heads) \
1109 and remote_heads != [nullid]:
1112 and remote_heads != [nullid]:
1110 self.ui.warn(_("abort: push creates new remote branches!\n"))
1113 self.ui.warn(_("abort: push creates new remote branches!\n"))
1111 self.ui.status(_("(did you forget to merge?"
1114 self.ui.status(_("(did you forget to merge?"
1112 " use push -f to force)\n"))
1115 " use push -f to force)\n"))
1113 return 1
1116 return 1
1114
1117
1115 if revs is None:
1118 if revs is None:
1116 cg = self.changegroup(update, 'push')
1119 cg = self.changegroup(update, 'push')
1117 else:
1120 else:
1118 cg = self.changegroupsubset(update, revs, 'push')
1121 cg = self.changegroupsubset(update, revs, 'push')
1119 return remote.addchangegroup(cg, 'push')
1122 return remote.addchangegroup(cg, 'push')
1120
1123
1121 def changegroupsubset(self, bases, heads, source):
1124 def changegroupsubset(self, bases, heads, source):
1122 """This function generates a changegroup consisting of all the nodes
1125 """This function generates a changegroup consisting of all the nodes
1123 that are descendents of any of the bases, and ancestors of any of
1126 that are descendents of any of the bases, and ancestors of any of
1124 the heads.
1127 the heads.
1125
1128
1126 It is fairly complex as determining which filenodes and which
1129 It is fairly complex as determining which filenodes and which
1127 manifest nodes need to be included for the changeset to be complete
1130 manifest nodes need to be included for the changeset to be complete
1128 is non-trivial.
1131 is non-trivial.
1129
1132
1130 Another wrinkle is doing the reverse, figuring out which changeset in
1133 Another wrinkle is doing the reverse, figuring out which changeset in
1131 the changegroup a particular filenode or manifestnode belongs to."""
1134 the changegroup a particular filenode or manifestnode belongs to."""
1132
1135
1133 self.hook('preoutgoing', throw=True, source=source)
1136 self.hook('preoutgoing', throw=True, source=source)
1134
1137
1135 # Set up some initial variables
1138 # Set up some initial variables
1136 # Make it easy to refer to self.changelog
1139 # Make it easy to refer to self.changelog
1137 cl = self.changelog
1140 cl = self.changelog
1138 # msng is short for missing - compute the list of changesets in this
1141 # msng is short for missing - compute the list of changesets in this
1139 # changegroup.
1142 # changegroup.
1140 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
1143 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
1141 # Some bases may turn out to be superfluous, and some heads may be
1144 # Some bases may turn out to be superfluous, and some heads may be
1142 # too. nodesbetween will return the minimal set of bases and heads
1145 # too. nodesbetween will return the minimal set of bases and heads
1143 # necessary to re-create the changegroup.
1146 # necessary to re-create the changegroup.
1144
1147
1145 # Known heads are the list of heads that it is assumed the recipient
1148 # Known heads are the list of heads that it is assumed the recipient
1146 # of this changegroup will know about.
1149 # of this changegroup will know about.
1147 knownheads = {}
1150 knownheads = {}
1148 # We assume that all parents of bases are known heads.
1151 # We assume that all parents of bases are known heads.
1149 for n in bases:
1152 for n in bases:
1150 for p in cl.parents(n):
1153 for p in cl.parents(n):
1151 if p != nullid:
1154 if p != nullid:
1152 knownheads[p] = 1
1155 knownheads[p] = 1
1153 knownheads = knownheads.keys()
1156 knownheads = knownheads.keys()
1154 if knownheads:
1157 if knownheads:
1155 # Now that we know what heads are known, we can compute which
1158 # Now that we know what heads are known, we can compute which
1156 # changesets are known. The recipient must know about all
1159 # changesets are known. The recipient must know about all
1157 # changesets required to reach the known heads from the null
1160 # changesets required to reach the known heads from the null
1158 # changeset.
1161 # changeset.
1159 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
1162 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
1160 junk = None
1163 junk = None
1161 # Transform the list into an ersatz set.
1164 # Transform the list into an ersatz set.
1162 has_cl_set = dict.fromkeys(has_cl_set)
1165 has_cl_set = dict.fromkeys(has_cl_set)
1163 else:
1166 else:
1164 # If there were no known heads, the recipient cannot be assumed to
1167 # If there were no known heads, the recipient cannot be assumed to
1165 # know about any changesets.
1168 # know about any changesets.
1166 has_cl_set = {}
1169 has_cl_set = {}
1167
1170
1168 # Make it easy to refer to self.manifest
1171 # Make it easy to refer to self.manifest
1169 mnfst = self.manifest
1172 mnfst = self.manifest
1170 # We don't know which manifests are missing yet
1173 # We don't know which manifests are missing yet
1171 msng_mnfst_set = {}
1174 msng_mnfst_set = {}
1172 # Nor do we know which filenodes are missing.
1175 # Nor do we know which filenodes are missing.
1173 msng_filenode_set = {}
1176 msng_filenode_set = {}
1174
1177
1175 junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex
1178 junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex
1176 junk = None
1179 junk = None
1177
1180
1178 # A changeset always belongs to itself, so the changenode lookup
1181 # A changeset always belongs to itself, so the changenode lookup
1179 # function for a changenode is identity.
1182 # function for a changenode is identity.
1180 def identity(x):
1183 def identity(x):
1181 return x
1184 return x
1182
1185
1183 # A function generating function. Sets up an environment for the
1186 # A function generating function. Sets up an environment for the
1184 # inner function.
1187 # inner function.
1185 def cmp_by_rev_func(revlog):
1188 def cmp_by_rev_func(revlog):
1186 # Compare two nodes by their revision number in the environment's
1189 # Compare two nodes by their revision number in the environment's
1187 # revision history. Since the revision number both represents the
1190 # revision history. Since the revision number both represents the
1188 # most efficient order to read the nodes in, and represents a
1191 # most efficient order to read the nodes in, and represents a
1189 # topological sorting of the nodes, this function is often useful.
1192 # topological sorting of the nodes, this function is often useful.
1190 def cmp_by_rev(a, b):
1193 def cmp_by_rev(a, b):
1191 return cmp(revlog.rev(a), revlog.rev(b))
1194 return cmp(revlog.rev(a), revlog.rev(b))
1192 return cmp_by_rev
1195 return cmp_by_rev
1193
1196
1194 # If we determine that a particular file or manifest node must be a
1197 # If we determine that a particular file or manifest node must be a
1195 # node that the recipient of the changegroup will already have, we can
1198 # node that the recipient of the changegroup will already have, we can
1196 # also assume the recipient will have all the parents. This function
1199 # also assume the recipient will have all the parents. This function
1197 # prunes them from the set of missing nodes.
1200 # prunes them from the set of missing nodes.
1198 def prune_parents(revlog, hasset, msngset):
1201 def prune_parents(revlog, hasset, msngset):
1199 haslst = hasset.keys()
1202 haslst = hasset.keys()
1200 haslst.sort(cmp_by_rev_func(revlog))
1203 haslst.sort(cmp_by_rev_func(revlog))
1201 for node in haslst:
1204 for node in haslst:
1202 parentlst = [p for p in revlog.parents(node) if p != nullid]
1205 parentlst = [p for p in revlog.parents(node) if p != nullid]
1203 while parentlst:
1206 while parentlst:
1204 n = parentlst.pop()
1207 n = parentlst.pop()
1205 if n not in hasset:
1208 if n not in hasset:
1206 hasset[n] = 1
1209 hasset[n] = 1
1207 p = [p for p in revlog.parents(n) if p != nullid]
1210 p = [p for p in revlog.parents(n) if p != nullid]
1208 parentlst.extend(p)
1211 parentlst.extend(p)
1209 for n in hasset:
1212 for n in hasset:
1210 msngset.pop(n, None)
1213 msngset.pop(n, None)
1211
1214
1212 # This is a function generating function used to set up an environment
1215 # This is a function generating function used to set up an environment
1213 # for the inner function to execute in.
1216 # for the inner function to execute in.
1214 def manifest_and_file_collector(changedfileset):
1217 def manifest_and_file_collector(changedfileset):
1215 # This is an information gathering function that gathers
1218 # This is an information gathering function that gathers
1216 # information from each changeset node that goes out as part of
1219 # information from each changeset node that goes out as part of
1217 # the changegroup. The information gathered is a list of which
1220 # the changegroup. The information gathered is a list of which
1218 # manifest nodes are potentially required (the recipient may
1221 # manifest nodes are potentially required (the recipient may
1219 # already have them) and total list of all files which were
1222 # already have them) and total list of all files which were
1220 # changed in any changeset in the changegroup.
1223 # changed in any changeset in the changegroup.
1221 #
1224 #
1222 # We also remember the first changenode we saw any manifest
1225 # We also remember the first changenode we saw any manifest
1223 # referenced by so we can later determine which changenode 'owns'
1226 # referenced by so we can later determine which changenode 'owns'
1224 # the manifest.
1227 # the manifest.
1225 def collect_manifests_and_files(clnode):
1228 def collect_manifests_and_files(clnode):
1226 c = cl.read(clnode)
1229 c = cl.read(clnode)
1227 for f in c[3]:
1230 for f in c[3]:
1228 # This is to make sure we only have one instance of each
1231 # This is to make sure we only have one instance of each
1229 # filename string for each filename.
1232 # filename string for each filename.
1230 changedfileset.setdefault(f, f)
1233 changedfileset.setdefault(f, f)
1231 msng_mnfst_set.setdefault(c[0], clnode)
1234 msng_mnfst_set.setdefault(c[0], clnode)
1232 return collect_manifests_and_files
1235 return collect_manifests_and_files
1233
1236
1234 # Figure out which manifest nodes (of the ones we think might be part
1237 # Figure out which manifest nodes (of the ones we think might be part
1235 # of the changegroup) the recipient must know about and remove them
1238 # of the changegroup) the recipient must know about and remove them
1236 # from the changegroup.
1239 # from the changegroup.
1237 def prune_manifests():
1240 def prune_manifests():
1238 has_mnfst_set = {}
1241 has_mnfst_set = {}
1239 for n in msng_mnfst_set:
1242 for n in msng_mnfst_set:
1240 # If a 'missing' manifest thinks it belongs to a changenode
1243 # If a 'missing' manifest thinks it belongs to a changenode
1241 # the recipient is assumed to have, obviously the recipient
1244 # the recipient is assumed to have, obviously the recipient
1242 # must have that manifest.
1245 # must have that manifest.
1243 linknode = cl.node(mnfst.linkrev(n))
1246 linknode = cl.node(mnfst.linkrev(n))
1244 if linknode in has_cl_set:
1247 if linknode in has_cl_set:
1245 has_mnfst_set[n] = 1
1248 has_mnfst_set[n] = 1
1246 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1249 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1247
1250
1248 # Use the information collected in collect_manifests_and_files to say
1251 # Use the information collected in collect_manifests_and_files to say
1249 # which changenode any manifestnode belongs to.
1252 # which changenode any manifestnode belongs to.
1250 def lookup_manifest_link(mnfstnode):
1253 def lookup_manifest_link(mnfstnode):
1251 return msng_mnfst_set[mnfstnode]
1254 return msng_mnfst_set[mnfstnode]
1252
1255
1253 # A function generating function that sets up the initial environment
1256 # A function generating function that sets up the initial environment
1254 # the inner function.
1257 # the inner function.
1255 def filenode_collector(changedfiles):
1258 def filenode_collector(changedfiles):
1256 next_rev = [0]
1259 next_rev = [0]
1257 # This gathers information from each manifestnode included in the
1260 # This gathers information from each manifestnode included in the
1258 # changegroup about which filenodes the manifest node references
1261 # changegroup about which filenodes the manifest node references
1259 # so we can include those in the changegroup too.
1262 # so we can include those in the changegroup too.
1260 #
1263 #
1261 # It also remembers which changenode each filenode belongs to. It
1264 # It also remembers which changenode each filenode belongs to. It
1262 # does this by assuming the a filenode belongs to the changenode
1265 # does this by assuming the a filenode belongs to the changenode
1263 # the first manifest that references it belongs to.
1266 # the first manifest that references it belongs to.
1264 def collect_msng_filenodes(mnfstnode):
1267 def collect_msng_filenodes(mnfstnode):
1265 r = mnfst.rev(mnfstnode)
1268 r = mnfst.rev(mnfstnode)
1266 if r == next_rev[0]:
1269 if r == next_rev[0]:
1267 # If the last rev we looked at was the one just previous,
1270 # If the last rev we looked at was the one just previous,
1268 # we only need to see a diff.
1271 # we only need to see a diff.
1269 delta = mdiff.patchtext(mnfst.delta(mnfstnode))
1272 delta = mdiff.patchtext(mnfst.delta(mnfstnode))
1270 # For each line in the delta
1273 # For each line in the delta
1271 for dline in delta.splitlines():
1274 for dline in delta.splitlines():
1272 # get the filename and filenode for that line
1275 # get the filename and filenode for that line
1273 f, fnode = dline.split('\0')
1276 f, fnode = dline.split('\0')
1274 fnode = bin(fnode[:40])
1277 fnode = bin(fnode[:40])
1275 f = changedfiles.get(f, None)
1278 f = changedfiles.get(f, None)
1276 # And if the file is in the list of files we care
1279 # And if the file is in the list of files we care
1277 # about.
1280 # about.
1278 if f is not None:
1281 if f is not None:
1279 # Get the changenode this manifest belongs to
1282 # Get the changenode this manifest belongs to
1280 clnode = msng_mnfst_set[mnfstnode]
1283 clnode = msng_mnfst_set[mnfstnode]
1281 # Create the set of filenodes for the file if
1284 # Create the set of filenodes for the file if
1282 # there isn't one already.
1285 # there isn't one already.
1283 ndset = msng_filenode_set.setdefault(f, {})
1286 ndset = msng_filenode_set.setdefault(f, {})
1284 # And set the filenode's changelog node to the
1287 # And set the filenode's changelog node to the
1285 # manifest's if it hasn't been set already.
1288 # manifest's if it hasn't been set already.
1286 ndset.setdefault(fnode, clnode)
1289 ndset.setdefault(fnode, clnode)
1287 else:
1290 else:
1288 # Otherwise we need a full manifest.
1291 # Otherwise we need a full manifest.
1289 m = mnfst.read(mnfstnode)
1292 m = mnfst.read(mnfstnode)
1290 # For every file in we care about.
1293 # For every file in we care about.
1291 for f in changedfiles:
1294 for f in changedfiles:
1292 fnode = m.get(f, None)
1295 fnode = m.get(f, None)
1293 # If it's in the manifest
1296 # If it's in the manifest
1294 if fnode is not None:
1297 if fnode is not None:
1295 # See comments above.
1298 # See comments above.
1296 clnode = msng_mnfst_set[mnfstnode]
1299 clnode = msng_mnfst_set[mnfstnode]
1297 ndset = msng_filenode_set.setdefault(f, {})
1300 ndset = msng_filenode_set.setdefault(f, {})
1298 ndset.setdefault(fnode, clnode)
1301 ndset.setdefault(fnode, clnode)
1299 # Remember the revision we hope to see next.
1302 # Remember the revision we hope to see next.
1300 next_rev[0] = r + 1
1303 next_rev[0] = r + 1
1301 return collect_msng_filenodes
1304 return collect_msng_filenodes
1302
1305
1303 # We have a list of filenodes we think we need for a file, lets remove
1306 # We have a list of filenodes we think we need for a file, lets remove
1304 # all those we now the recipient must have.
1307 # all those we now the recipient must have.
1305 def prune_filenodes(f, filerevlog):
1308 def prune_filenodes(f, filerevlog):
1306 msngset = msng_filenode_set[f]
1309 msngset = msng_filenode_set[f]
1307 hasset = {}
1310 hasset = {}
1308 # If a 'missing' filenode thinks it belongs to a changenode we
1311 # If a 'missing' filenode thinks it belongs to a changenode we
1309 # assume the recipient must have, then the recipient must have
1312 # assume the recipient must have, then the recipient must have
1310 # that filenode.
1313 # that filenode.
1311 for n in msngset:
1314 for n in msngset:
1312 clnode = cl.node(filerevlog.linkrev(n))
1315 clnode = cl.node(filerevlog.linkrev(n))
1313 if clnode in has_cl_set:
1316 if clnode in has_cl_set:
1314 hasset[n] = 1
1317 hasset[n] = 1
1315 prune_parents(filerevlog, hasset, msngset)
1318 prune_parents(filerevlog, hasset, msngset)
1316
1319
1317 # A function generator function that sets up the a context for the
1320 # A function generator function that sets up the a context for the
1318 # inner function.
1321 # inner function.
1319 def lookup_filenode_link_func(fname):
1322 def lookup_filenode_link_func(fname):
1320 msngset = msng_filenode_set[fname]
1323 msngset = msng_filenode_set[fname]
1321 # Lookup the changenode the filenode belongs to.
1324 # Lookup the changenode the filenode belongs to.
1322 def lookup_filenode_link(fnode):
1325 def lookup_filenode_link(fnode):
1323 return msngset[fnode]
1326 return msngset[fnode]
1324 return lookup_filenode_link
1327 return lookup_filenode_link
1325
1328
1326 # Now that we have all theses utility functions to help out and
1329 # Now that we have all theses utility functions to help out and
1327 # logically divide up the task, generate the group.
1330 # logically divide up the task, generate the group.
1328 def gengroup():
1331 def gengroup():
1329 # The set of changed files starts empty.
1332 # The set of changed files starts empty.
1330 changedfiles = {}
1333 changedfiles = {}
1331 # Create a changenode group generator that will call our functions
1334 # Create a changenode group generator that will call our functions
1332 # back to lookup the owning changenode and collect information.
1335 # back to lookup the owning changenode and collect information.
1333 group = cl.group(msng_cl_lst, identity,
1336 group = cl.group(msng_cl_lst, identity,
1334 manifest_and_file_collector(changedfiles))
1337 manifest_and_file_collector(changedfiles))
1335 for chnk in group:
1338 for chnk in group:
1336 yield chnk
1339 yield chnk
1337
1340
1338 # The list of manifests has been collected by the generator
1341 # The list of manifests has been collected by the generator
1339 # calling our functions back.
1342 # calling our functions back.
1340 prune_manifests()
1343 prune_manifests()
1341 msng_mnfst_lst = msng_mnfst_set.keys()
1344 msng_mnfst_lst = msng_mnfst_set.keys()
1342 # Sort the manifestnodes by revision number.
1345 # Sort the manifestnodes by revision number.
1343 msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
1346 msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
1344 # Create a generator for the manifestnodes that calls our lookup
1347 # Create a generator for the manifestnodes that calls our lookup
1345 # and data collection functions back.
1348 # and data collection functions back.
1346 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1349 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1347 filenode_collector(changedfiles))
1350 filenode_collector(changedfiles))
1348 for chnk in group:
1351 for chnk in group:
1349 yield chnk
1352 yield chnk
1350
1353
1351 # These are no longer needed, dereference and toss the memory for
1354 # These are no longer needed, dereference and toss the memory for
1352 # them.
1355 # them.
1353 msng_mnfst_lst = None
1356 msng_mnfst_lst = None
1354 msng_mnfst_set.clear()
1357 msng_mnfst_set.clear()
1355
1358
1356 changedfiles = changedfiles.keys()
1359 changedfiles = changedfiles.keys()
1357 changedfiles.sort()
1360 changedfiles.sort()
1358 # Go through all our files in order sorted by name.
1361 # Go through all our files in order sorted by name.
1359 for fname in changedfiles:
1362 for fname in changedfiles:
1360 filerevlog = self.file(fname)
1363 filerevlog = self.file(fname)
1361 # Toss out the filenodes that the recipient isn't really
1364 # Toss out the filenodes that the recipient isn't really
1362 # missing.
1365 # missing.
1363 if msng_filenode_set.has_key(fname):
1366 if msng_filenode_set.has_key(fname):
1364 prune_filenodes(fname, filerevlog)
1367 prune_filenodes(fname, filerevlog)
1365 msng_filenode_lst = msng_filenode_set[fname].keys()
1368 msng_filenode_lst = msng_filenode_set[fname].keys()
1366 else:
1369 else:
1367 msng_filenode_lst = []
1370 msng_filenode_lst = []
1368 # If any filenodes are left, generate the group for them,
1371 # If any filenodes are left, generate the group for them,
1369 # otherwise don't bother.
1372 # otherwise don't bother.
1370 if len(msng_filenode_lst) > 0:
1373 if len(msng_filenode_lst) > 0:
1371 yield changegroup.genchunk(fname)
1374 yield changegroup.genchunk(fname)
1372 # Sort the filenodes by their revision #
1375 # Sort the filenodes by their revision #
1373 msng_filenode_lst.sort(cmp_by_rev_func(filerevlog))
1376 msng_filenode_lst.sort(cmp_by_rev_func(filerevlog))
1374 # Create a group generator and only pass in a changenode
1377 # Create a group generator and only pass in a changenode
1375 # lookup function as we need to collect no information
1378 # lookup function as we need to collect no information
1376 # from filenodes.
1379 # from filenodes.
1377 group = filerevlog.group(msng_filenode_lst,
1380 group = filerevlog.group(msng_filenode_lst,
1378 lookup_filenode_link_func(fname))
1381 lookup_filenode_link_func(fname))
1379 for chnk in group:
1382 for chnk in group:
1380 yield chnk
1383 yield chnk
1381 if msng_filenode_set.has_key(fname):
1384 if msng_filenode_set.has_key(fname):
1382 # Don't need this anymore, toss it to free memory.
1385 # Don't need this anymore, toss it to free memory.
1383 del msng_filenode_set[fname]
1386 del msng_filenode_set[fname]
1384 # Signal that no more groups are left.
1387 # Signal that no more groups are left.
1385 yield changegroup.closechunk()
1388 yield changegroup.closechunk()
1386
1389
1387 if msng_cl_lst:
1390 if msng_cl_lst:
1388 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
1391 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
1389
1392
1390 return util.chunkbuffer(gengroup())
1393 return util.chunkbuffer(gengroup())
1391
1394
1392 def changegroup(self, basenodes, source):
1395 def changegroup(self, basenodes, source):
1393 """Generate a changegroup of all nodes that we have that a recipient
1396 """Generate a changegroup of all nodes that we have that a recipient
1394 doesn't.
1397 doesn't.
1395
1398
1396 This is much easier than the previous function as we can assume that
1399 This is much easier than the previous function as we can assume that
1397 the recipient has any changenode we aren't sending them."""
1400 the recipient has any changenode we aren't sending them."""
1398
1401
1399 self.hook('preoutgoing', throw=True, source=source)
1402 self.hook('preoutgoing', throw=True, source=source)
1400
1403
1401 cl = self.changelog
1404 cl = self.changelog
1402 nodes = cl.nodesbetween(basenodes, None)[0]
1405 nodes = cl.nodesbetween(basenodes, None)[0]
1403 revset = dict.fromkeys([cl.rev(n) for n in nodes])
1406 revset = dict.fromkeys([cl.rev(n) for n in nodes])
1404
1407
1405 def identity(x):
1408 def identity(x):
1406 return x
1409 return x
1407
1410
1408 def gennodelst(revlog):
1411 def gennodelst(revlog):
1409 for r in xrange(0, revlog.count()):
1412 for r in xrange(0, revlog.count()):
1410 n = revlog.node(r)
1413 n = revlog.node(r)
1411 if revlog.linkrev(n) in revset:
1414 if revlog.linkrev(n) in revset:
1412 yield n
1415 yield n
1413
1416
1414 def changed_file_collector(changedfileset):
1417 def changed_file_collector(changedfileset):
1415 def collect_changed_files(clnode):
1418 def collect_changed_files(clnode):
1416 c = cl.read(clnode)
1419 c = cl.read(clnode)
1417 for fname in c[3]:
1420 for fname in c[3]:
1418 changedfileset[fname] = 1
1421 changedfileset[fname] = 1
1419 return collect_changed_files
1422 return collect_changed_files
1420
1423
1421 def lookuprevlink_func(revlog):
1424 def lookuprevlink_func(revlog):
1422 def lookuprevlink(n):
1425 def lookuprevlink(n):
1423 return cl.node(revlog.linkrev(n))
1426 return cl.node(revlog.linkrev(n))
1424 return lookuprevlink
1427 return lookuprevlink
1425
1428
1426 def gengroup():
1429 def gengroup():
1427 # construct a list of all changed files
1430 # construct a list of all changed files
1428 changedfiles = {}
1431 changedfiles = {}
1429
1432
1430 for chnk in cl.group(nodes, identity,
1433 for chnk in cl.group(nodes, identity,
1431 changed_file_collector(changedfiles)):
1434 changed_file_collector(changedfiles)):
1432 yield chnk
1435 yield chnk
1433 changedfiles = changedfiles.keys()
1436 changedfiles = changedfiles.keys()
1434 changedfiles.sort()
1437 changedfiles.sort()
1435
1438
1436 mnfst = self.manifest
1439 mnfst = self.manifest
1437 nodeiter = gennodelst(mnfst)
1440 nodeiter = gennodelst(mnfst)
1438 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1441 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1439 yield chnk
1442 yield chnk
1440
1443
1441 for fname in changedfiles:
1444 for fname in changedfiles:
1442 filerevlog = self.file(fname)
1445 filerevlog = self.file(fname)
1443 nodeiter = gennodelst(filerevlog)
1446 nodeiter = gennodelst(filerevlog)
1444 nodeiter = list(nodeiter)
1447 nodeiter = list(nodeiter)
1445 if nodeiter:
1448 if nodeiter:
1446 yield changegroup.genchunk(fname)
1449 yield changegroup.genchunk(fname)
1447 lookup = lookuprevlink_func(filerevlog)
1450 lookup = lookuprevlink_func(filerevlog)
1448 for chnk in filerevlog.group(nodeiter, lookup):
1451 for chnk in filerevlog.group(nodeiter, lookup):
1449 yield chnk
1452 yield chnk
1450
1453
1451 yield changegroup.closechunk()
1454 yield changegroup.closechunk()
1452
1455
1453 if nodes:
1456 if nodes:
1454 self.hook('outgoing', node=hex(nodes[0]), source=source)
1457 self.hook('outgoing', node=hex(nodes[0]), source=source)
1455
1458
1456 return util.chunkbuffer(gengroup())
1459 return util.chunkbuffer(gengroup())
1457
1460
1458 def addchangegroup(self, source, srctype):
1461 def addchangegroup(self, source, srctype):
1459 """add changegroup to repo.
1462 """add changegroup to repo.
1460 returns number of heads modified or added + 1."""
1463 returns number of heads modified or added + 1."""
1461
1464
1462 def csmap(x):
1465 def csmap(x):
1463 self.ui.debug(_("add changeset %s\n") % short(x))
1466 self.ui.debug(_("add changeset %s\n") % short(x))
1464 return cl.count()
1467 return cl.count()
1465
1468
1466 def revmap(x):
1469 def revmap(x):
1467 return cl.rev(x)
1470 return cl.rev(x)
1468
1471
1469 if not source:
1472 if not source:
1470 return 0
1473 return 0
1471
1474
1472 self.hook('prechangegroup', throw=True, source=srctype)
1475 self.hook('prechangegroup', throw=True, source=srctype)
1473
1476
1474 changesets = files = revisions = 0
1477 changesets = files = revisions = 0
1475
1478
1476 tr = self.transaction()
1479 tr = self.transaction()
1477
1480
1478 # write changelog and manifest data to temp files so
1481 # write changelog and manifest data to temp files so
1479 # concurrent readers will not see inconsistent view
1482 # concurrent readers will not see inconsistent view
1480 cl = None
1483 cl = None
1481 try:
1484 try:
1482 cl = appendfile.appendchangelog(self.opener, self.changelog.version)
1485 cl = appendfile.appendchangelog(self.opener, self.changelog.version)
1483
1486
1484 oldheads = len(cl.heads())
1487 oldheads = len(cl.heads())
1485
1488
1486 # pull off the changeset group
1489 # pull off the changeset group
1487 self.ui.status(_("adding changesets\n"))
1490 self.ui.status(_("adding changesets\n"))
1488 co = cl.tip()
1491 co = cl.tip()
1489 chunkiter = changegroup.chunkiter(source)
1492 chunkiter = changegroup.chunkiter(source)
1490 cn = cl.addgroup(chunkiter, csmap, tr, 1) # unique
1493 cn = cl.addgroup(chunkiter, csmap, tr, 1) # unique
1491 cnr, cor = map(cl.rev, (cn, co))
1494 cnr, cor = map(cl.rev, (cn, co))
1492 if cn == nullid:
1495 if cn == nullid:
1493 cnr = cor
1496 cnr = cor
1494 changesets = cnr - cor
1497 changesets = cnr - cor
1495
1498
1496 mf = None
1499 mf = None
1497 try:
1500 try:
1498 mf = appendfile.appendmanifest(self.opener,
1501 mf = appendfile.appendmanifest(self.opener,
1499 self.manifest.version)
1502 self.manifest.version)
1500
1503
1501 # pull off the manifest group
1504 # pull off the manifest group
1502 self.ui.status(_("adding manifests\n"))
1505 self.ui.status(_("adding manifests\n"))
1503 mm = mf.tip()
1506 mm = mf.tip()
1504 chunkiter = changegroup.chunkiter(source)
1507 chunkiter = changegroup.chunkiter(source)
1505 mo = mf.addgroup(chunkiter, revmap, tr)
1508 mo = mf.addgroup(chunkiter, revmap, tr)
1506
1509
1507 # process the files
1510 # process the files
1508 self.ui.status(_("adding file changes\n"))
1511 self.ui.status(_("adding file changes\n"))
1509 while 1:
1512 while 1:
1510 f = changegroup.getchunk(source)
1513 f = changegroup.getchunk(source)
1511 if not f:
1514 if not f:
1512 break
1515 break
1513 self.ui.debug(_("adding %s revisions\n") % f)
1516 self.ui.debug(_("adding %s revisions\n") % f)
1514 fl = self.file(f)
1517 fl = self.file(f)
1515 o = fl.count()
1518 o = fl.count()
1516 chunkiter = changegroup.chunkiter(source)
1519 chunkiter = changegroup.chunkiter(source)
1517 n = fl.addgroup(chunkiter, revmap, tr)
1520 n = fl.addgroup(chunkiter, revmap, tr)
1518 revisions += fl.count() - o
1521 revisions += fl.count() - o
1519 files += 1
1522 files += 1
1520
1523
1521 # write order here is important so concurrent readers will see
1524 # write order here is important so concurrent readers will see
1522 # consistent view of repo
1525 # consistent view of repo
1523 mf.writedata()
1526 mf.writedata()
1524 finally:
1527 finally:
1525 if mf:
1528 if mf:
1526 mf.cleanup()
1529 mf.cleanup()
1527 cl.writedata()
1530 cl.writedata()
1528 finally:
1531 finally:
1529 if cl:
1532 if cl:
1530 cl.cleanup()
1533 cl.cleanup()
1531
1534
1532 # make changelog and manifest see real files again
1535 # make changelog and manifest see real files again
1533 self.changelog = changelog.changelog(self.opener, self.changelog.version)
1536 self.changelog = changelog.changelog(self.opener, self.changelog.version)
1534 self.manifest = manifest.manifest(self.opener, self.manifest.version)
1537 self.manifest = manifest.manifest(self.opener, self.manifest.version)
1535 self.changelog.checkinlinesize(tr)
1538 self.changelog.checkinlinesize(tr)
1536 self.manifest.checkinlinesize(tr)
1539 self.manifest.checkinlinesize(tr)
1537
1540
1538 newheads = len(self.changelog.heads())
1541 newheads = len(self.changelog.heads())
1539 heads = ""
1542 heads = ""
1540 if oldheads and newheads > oldheads:
1543 if oldheads and newheads > oldheads:
1541 heads = _(" (+%d heads)") % (newheads - oldheads)
1544 heads = _(" (+%d heads)") % (newheads - oldheads)
1542
1545
1543 self.ui.status(_("added %d changesets"
1546 self.ui.status(_("added %d changesets"
1544 " with %d changes to %d files%s\n")
1547 " with %d changes to %d files%s\n")
1545 % (changesets, revisions, files, heads))
1548 % (changesets, revisions, files, heads))
1546
1549
1547 self.hook('pretxnchangegroup', throw=True,
1550 if changesets > 0:
1548 node=hex(self.changelog.node(cor+1)), source=srctype)
1551 self.hook('pretxnchangegroup', throw=True,
1552 node=hex(self.changelog.node(cor+1)), source=srctype)
1549
1553
1550 tr.close()
1554 tr.close()
1551
1555
1552 if changesets > 0:
1556 if changesets > 0:
1553 self.hook("changegroup", node=hex(self.changelog.node(cor+1)),
1557 self.hook("changegroup", node=hex(self.changelog.node(cor+1)),
1554 source=srctype)
1558 source=srctype)
1555
1559
1556 for i in range(cor + 1, cnr + 1):
1560 for i in range(cor + 1, cnr + 1):
1557 self.hook("incoming", node=hex(self.changelog.node(i)),
1561 self.hook("incoming", node=hex(self.changelog.node(i)),
1558 source=srctype)
1562 source=srctype)
1559
1563
1560 return newheads - oldheads + 1
1564 return newheads - oldheads + 1
1561
1565
1562 def update(self, node, allow=False, force=False, choose=None,
1566 def update(self, node, allow=False, force=False, choose=None,
1563 moddirstate=True, forcemerge=False, wlock=None, show_stats=True):
1567 moddirstate=True, forcemerge=False, wlock=None, show_stats=True):
1564 pl = self.dirstate.parents()
1568 pl = self.dirstate.parents()
1565 if not force and pl[1] != nullid:
1569 if not force and pl[1] != nullid:
1566 self.ui.warn(_("aborting: outstanding uncommitted merges\n"))
1570 raise util.Abort(_("outstanding uncommitted merges"))
1567 return 1
1568
1571
1569 err = False
1572 err = False
1570
1573
1571 p1, p2 = pl[0], node
1574 p1, p2 = pl[0], node
1572 pa = self.changelog.ancestor(p1, p2)
1575 pa = self.changelog.ancestor(p1, p2)
1573 m1n = self.changelog.read(p1)[0]
1576 m1n = self.changelog.read(p1)[0]
1574 m2n = self.changelog.read(p2)[0]
1577 m2n = self.changelog.read(p2)[0]
1575 man = self.manifest.ancestor(m1n, m2n)
1578 man = self.manifest.ancestor(m1n, m2n)
1576 m1 = self.manifest.read(m1n)
1579 m1 = self.manifest.read(m1n)
1577 mf1 = self.manifest.readflags(m1n)
1580 mf1 = self.manifest.readflags(m1n)
1578 m2 = self.manifest.read(m2n).copy()
1581 m2 = self.manifest.read(m2n).copy()
1579 mf2 = self.manifest.readflags(m2n)
1582 mf2 = self.manifest.readflags(m2n)
1580 ma = self.manifest.read(man)
1583 ma = self.manifest.read(man)
1581 mfa = self.manifest.readflags(man)
1584 mfa = self.manifest.readflags(man)
1582
1585
1583 modified, added, removed, deleted, unknown = self.changes()
1586 modified, added, removed, deleted, unknown = self.changes()
1584
1587
1585 # is this a jump, or a merge? i.e. is there a linear path
1588 # is this a jump, or a merge? i.e. is there a linear path
1586 # from p1 to p2?
1589 # from p1 to p2?
1587 linear_path = (pa == p1 or pa == p2)
1590 linear_path = (pa == p1 or pa == p2)
1588
1591
1589 if allow and linear_path:
1592 if allow and linear_path:
1590 raise util.Abort(_("there is nothing to merge, "
1593 raise util.Abort(_("there is nothing to merge, "
1591 "just use 'hg update'"))
1594 "just use 'hg update'"))
1592 if allow and not forcemerge:
1595 if allow and not forcemerge:
1593 if modified or added or removed:
1596 if modified or added or removed:
1594 raise util.Abort(_("outstanding uncommitted changes"))
1597 raise util.Abort(_("outstanding uncommitted changes"))
1598
1595 if not forcemerge and not force:
1599 if not forcemerge and not force:
1596 for f in unknown:
1600 for f in unknown:
1597 if f in m2:
1601 if f in m2:
1598 t1 = self.wread(f)
1602 t1 = self.wread(f)
1599 t2 = self.file(f).read(m2[f])
1603 t2 = self.file(f).read(m2[f])
1600 if cmp(t1, t2) != 0:
1604 if cmp(t1, t2) != 0:
1601 raise util.Abort(_("'%s' already exists in the working"
1605 raise util.Abort(_("'%s' already exists in the working"
1602 " dir and differs from remote") % f)
1606 " dir and differs from remote") % f)
1603
1607
1604 # resolve the manifest to determine which files
1608 # resolve the manifest to determine which files
1605 # we care about merging
1609 # we care about merging
1606 self.ui.note(_("resolving manifests\n"))
1610 self.ui.note(_("resolving manifests\n"))
1607 self.ui.debug(_(" force %s allow %s moddirstate %s linear %s\n") %
1611 self.ui.debug(_(" force %s allow %s moddirstate %s linear %s\n") %
1608 (force, allow, moddirstate, linear_path))
1612 (force, allow, moddirstate, linear_path))
1609 self.ui.debug(_(" ancestor %s local %s remote %s\n") %
1613 self.ui.debug(_(" ancestor %s local %s remote %s\n") %
1610 (short(man), short(m1n), short(m2n)))
1614 (short(man), short(m1n), short(m2n)))
1611
1615
1612 merge = {}
1616 merge = {}
1613 get = {}
1617 get = {}
1614 remove = []
1618 remove = []
1615
1619
1616 # construct a working dir manifest
1620 # construct a working dir manifest
1617 mw = m1.copy()
1621 mw = m1.copy()
1618 mfw = mf1.copy()
1622 mfw = mf1.copy()
1619 umap = dict.fromkeys(unknown)
1623 umap = dict.fromkeys(unknown)
1620
1624
1621 for f in added + modified + unknown:
1625 for f in added + modified + unknown:
1622 mw[f] = ""
1626 mw[f] = ""
1623 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1627 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1624
1628
1625 if moddirstate and not wlock:
1629 if moddirstate and not wlock:
1626 wlock = self.wlock()
1630 wlock = self.wlock()
1627
1631
1628 for f in deleted + removed:
1632 for f in deleted + removed:
1629 if f in mw:
1633 if f in mw:
1630 del mw[f]
1634 del mw[f]
1631
1635
1632 # If we're jumping between revisions (as opposed to merging),
1636 # If we're jumping between revisions (as opposed to merging),
1633 # and if neither the working directory nor the target rev has
1637 # and if neither the working directory nor the target rev has
1634 # the file, then we need to remove it from the dirstate, to
1638 # the file, then we need to remove it from the dirstate, to
1635 # prevent the dirstate from listing the file when it is no
1639 # prevent the dirstate from listing the file when it is no
1636 # longer in the manifest.
1640 # longer in the manifest.
1637 if moddirstate and linear_path and f not in m2:
1641 if moddirstate and linear_path and f not in m2:
1638 self.dirstate.forget((f,))
1642 self.dirstate.forget((f,))
1639
1643
1640 # Compare manifests
1644 # Compare manifests
1641 for f, n in mw.iteritems():
1645 for f, n in mw.iteritems():
1642 if choose and not choose(f):
1646 if choose and not choose(f):
1643 continue
1647 continue
1644 if f in m2:
1648 if f in m2:
1645 s = 0
1649 s = 0
1646
1650
1647 # is the wfile new since m1, and match m2?
1651 # is the wfile new since m1, and match m2?
1648 if f not in m1:
1652 if f not in m1:
1649 t1 = self.wread(f)
1653 t1 = self.wread(f)
1650 t2 = self.file(f).read(m2[f])
1654 t2 = self.file(f).read(m2[f])
1651 if cmp(t1, t2) == 0:
1655 if cmp(t1, t2) == 0:
1652 n = m2[f]
1656 n = m2[f]
1653 del t1, t2
1657 del t1, t2
1654
1658
1655 # are files different?
1659 # are files different?
1656 if n != m2[f]:
1660 if n != m2[f]:
1657 a = ma.get(f, nullid)
1661 a = ma.get(f, nullid)
1658 # are both different from the ancestor?
1662 # are both different from the ancestor?
1659 if n != a and m2[f] != a:
1663 if n != a and m2[f] != a:
1660 self.ui.debug(_(" %s versions differ, resolve\n") % f)
1664 self.ui.debug(_(" %s versions differ, resolve\n") % f)
1661 # merge executable bits
1665 # merge executable bits
1662 # "if we changed or they changed, change in merge"
1666 # "if we changed or they changed, change in merge"
1663 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1667 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1664 mode = ((a^b) | (a^c)) ^ a
1668 mode = ((a^b) | (a^c)) ^ a
1665 merge[f] = (m1.get(f, nullid), m2[f], mode)
1669 merge[f] = (m1.get(f, nullid), m2[f], mode)
1666 s = 1
1670 s = 1
1667 # are we clobbering?
1671 # are we clobbering?
1668 # is remote's version newer?
1672 # is remote's version newer?
1669 # or are we going back in time?
1673 # or are we going back in time?
1670 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1674 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1671 self.ui.debug(_(" remote %s is newer, get\n") % f)
1675 self.ui.debug(_(" remote %s is newer, get\n") % f)
1672 get[f] = m2[f]
1676 get[f] = m2[f]
1673 s = 1
1677 s = 1
1674 elif f in umap or f in added:
1678 elif f in umap or f in added:
1675 # this unknown file is the same as the checkout
1679 # this unknown file is the same as the checkout
1676 # we need to reset the dirstate if the file was added
1680 # we need to reset the dirstate if the file was added
1677 get[f] = m2[f]
1681 get[f] = m2[f]
1678
1682
1679 if not s and mfw[f] != mf2[f]:
1683 if not s and mfw[f] != mf2[f]:
1680 if force:
1684 if force:
1681 self.ui.debug(_(" updating permissions for %s\n") % f)
1685 self.ui.debug(_(" updating permissions for %s\n") % f)
1682 util.set_exec(self.wjoin(f), mf2[f])
1686 util.set_exec(self.wjoin(f), mf2[f])
1683 else:
1687 else:
1684 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1688 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1685 mode = ((a^b) | (a^c)) ^ a
1689 mode = ((a^b) | (a^c)) ^ a
1686 if mode != b:
1690 if mode != b:
1687 self.ui.debug(_(" updating permissions for %s\n")
1691 self.ui.debug(_(" updating permissions for %s\n")
1688 % f)
1692 % f)
1689 util.set_exec(self.wjoin(f), mode)
1693 util.set_exec(self.wjoin(f), mode)
1690 del m2[f]
1694 del m2[f]
1691 elif f in ma:
1695 elif f in ma:
1692 if n != ma[f]:
1696 if n != ma[f]:
1693 r = _("d")
1697 r = _("d")
1694 if not force and (linear_path or allow):
1698 if not force and (linear_path or allow):
1695 r = self.ui.prompt(
1699 r = self.ui.prompt(
1696 (_(" local changed %s which remote deleted\n") % f) +
1700 (_(" local changed %s which remote deleted\n") % f) +
1697 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1701 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1698 if r == _("d"):
1702 if r == _("d"):
1699 remove.append(f)
1703 remove.append(f)
1700 else:
1704 else:
1701 self.ui.debug(_("other deleted %s\n") % f)
1705 self.ui.debug(_("other deleted %s\n") % f)
1702 remove.append(f) # other deleted it
1706 remove.append(f) # other deleted it
1703 else:
1707 else:
1704 # file is created on branch or in working directory
1708 # file is created on branch or in working directory
1705 if force and f not in umap:
1709 if force and f not in umap:
1706 self.ui.debug(_("remote deleted %s, clobbering\n") % f)
1710 self.ui.debug(_("remote deleted %s, clobbering\n") % f)
1707 remove.append(f)
1711 remove.append(f)
1708 elif n == m1.get(f, nullid): # same as parent
1712 elif n == m1.get(f, nullid): # same as parent
1709 if p2 == pa: # going backwards?
1713 if p2 == pa: # going backwards?
1710 self.ui.debug(_("remote deleted %s\n") % f)
1714 self.ui.debug(_("remote deleted %s\n") % f)
1711 remove.append(f)
1715 remove.append(f)
1712 else:
1716 else:
1713 self.ui.debug(_("local modified %s, keeping\n") % f)
1717 self.ui.debug(_("local modified %s, keeping\n") % f)
1714 else:
1718 else:
1715 self.ui.debug(_("working dir created %s, keeping\n") % f)
1719 self.ui.debug(_("working dir created %s, keeping\n") % f)
1716
1720
1717 for f, n in m2.iteritems():
1721 for f, n in m2.iteritems():
1718 if choose and not choose(f):
1722 if choose and not choose(f):
1719 continue
1723 continue
1720 if f[0] == "/":
1724 if f[0] == "/":
1721 continue
1725 continue
1722 if f in ma and n != ma[f]:
1726 if f in ma and n != ma[f]:
1723 r = _("k")
1727 r = _("k")
1724 if not force and (linear_path or allow):
1728 if not force and (linear_path or allow):
1725 r = self.ui.prompt(
1729 r = self.ui.prompt(
1726 (_("remote changed %s which local deleted\n") % f) +
1730 (_("remote changed %s which local deleted\n") % f) +
1727 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1731 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1728 if r == _("k"):
1732 if r == _("k"):
1729 get[f] = n
1733 get[f] = n
1730 elif f not in ma:
1734 elif f not in ma:
1731 self.ui.debug(_("remote created %s\n") % f)
1735 self.ui.debug(_("remote created %s\n") % f)
1732 get[f] = n
1736 get[f] = n
1733 else:
1737 else:
1734 if force or p2 == pa: # going backwards?
1738 if force or p2 == pa: # going backwards?
1735 self.ui.debug(_("local deleted %s, recreating\n") % f)
1739 self.ui.debug(_("local deleted %s, recreating\n") % f)
1736 get[f] = n
1740 get[f] = n
1737 else:
1741 else:
1738 self.ui.debug(_("local deleted %s\n") % f)
1742 self.ui.debug(_("local deleted %s\n") % f)
1739
1743
1740 del mw, m1, m2, ma
1744 del mw, m1, m2, ma
1741
1745
1742 if force:
1746 if force:
1743 for f in merge:
1747 for f in merge:
1744 get[f] = merge[f][1]
1748 get[f] = merge[f][1]
1745 merge = {}
1749 merge = {}
1746
1750
1747 if linear_path or force:
1751 if linear_path or force:
1748 # we don't need to do any magic, just jump to the new rev
1752 # we don't need to do any magic, just jump to the new rev
1749 branch_merge = False
1753 branch_merge = False
1750 p1, p2 = p2, nullid
1754 p1, p2 = p2, nullid
1751 else:
1755 else:
1752 if not allow:
1756 if not allow:
1753 self.ui.status(_("this update spans a branch"
1757 self.ui.status(_("this update spans a branch"
1754 " affecting the following files:\n"))
1758 " affecting the following files:\n"))
1755 fl = merge.keys() + get.keys()
1759 fl = merge.keys() + get.keys()
1756 fl.sort()
1760 fl.sort()
1757 for f in fl:
1761 for f in fl:
1758 cf = ""
1762 cf = ""
1759 if f in merge:
1763 if f in merge:
1760 cf = _(" (resolve)")
1764 cf = _(" (resolve)")
1761 self.ui.status(" %s%s\n" % (f, cf))
1765 self.ui.status(" %s%s\n" % (f, cf))
1762 self.ui.warn(_("aborting update spanning branches!\n"))
1766 self.ui.warn(_("aborting update spanning branches!\n"))
1763 self.ui.status(_("(use 'hg merge' to merge across branches"
1767 self.ui.status(_("(use 'hg merge' to merge across branches"
1764 " or 'hg update -C' to lose changes)\n"))
1768 " or 'hg update -C' to lose changes)\n"))
1765 return 1
1769 return 1
1766 branch_merge = True
1770 branch_merge = True
1767
1771
1772 xp1 = hex(p1)
1773 xp2 = hex(p2)
1774 if p2 == nullid: xxp2 = ''
1775 else: xxp2 = xp2
1776
1777 self.hook('preupdate', throw=True, parent1=xp1, parent2=xxp2)
1778
1768 # get the files we don't need to change
1779 # get the files we don't need to change
1769 files = get.keys()
1780 files = get.keys()
1770 files.sort()
1781 files.sort()
1771 for f in files:
1782 for f in files:
1772 if f[0] == "/":
1783 if f[0] == "/":
1773 continue
1784 continue
1774 self.ui.note(_("getting %s\n") % f)
1785 self.ui.note(_("getting %s\n") % f)
1775 t = self.file(f).read(get[f])
1786 t = self.file(f).read(get[f])
1776 self.wwrite(f, t)
1787 self.wwrite(f, t)
1777 util.set_exec(self.wjoin(f), mf2[f])
1788 util.set_exec(self.wjoin(f), mf2[f])
1778 if moddirstate:
1789 if moddirstate:
1779 if branch_merge:
1790 if branch_merge:
1780 self.dirstate.update([f], 'n', st_mtime=-1)
1791 self.dirstate.update([f], 'n', st_mtime=-1)
1781 else:
1792 else:
1782 self.dirstate.update([f], 'n')
1793 self.dirstate.update([f], 'n')
1783
1794
1784 # merge the tricky bits
1795 # merge the tricky bits
1785 failedmerge = []
1796 failedmerge = []
1786 files = merge.keys()
1797 files = merge.keys()
1787 files.sort()
1798 files.sort()
1788 xp1 = hex(p1)
1789 xp2 = hex(p2)
1790 for f in files:
1799 for f in files:
1791 self.ui.status(_("merging %s\n") % f)
1800 self.ui.status(_("merging %s\n") % f)
1792 my, other, flag = merge[f]
1801 my, other, flag = merge[f]
1793 ret = self.merge3(f, my, other, xp1, xp2)
1802 ret = self.merge3(f, my, other, xp1, xp2)
1794 if ret:
1803 if ret:
1795 err = True
1804 err = True
1796 failedmerge.append(f)
1805 failedmerge.append(f)
1797 util.set_exec(self.wjoin(f), flag)
1806 util.set_exec(self.wjoin(f), flag)
1798 if moddirstate:
1807 if moddirstate:
1799 if branch_merge:
1808 if branch_merge:
1800 # We've done a branch merge, mark this file as merged
1809 # We've done a branch merge, mark this file as merged
1801 # so that we properly record the merger later
1810 # so that we properly record the merger later
1802 self.dirstate.update([f], 'm')
1811 self.dirstate.update([f], 'm')
1803 else:
1812 else:
1804 # We've update-merged a locally modified file, so
1813 # We've update-merged a locally modified file, so
1805 # we set the dirstate to emulate a normal checkout
1814 # we set the dirstate to emulate a normal checkout
1806 # of that file some time in the past. Thus our
1815 # of that file some time in the past. Thus our
1807 # merge will appear as a normal local file
1816 # merge will appear as a normal local file
1808 # modification.
1817 # modification.
1809 f_len = len(self.file(f).read(other))
1818 f_len = len(self.file(f).read(other))
1810 self.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
1819 self.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
1811
1820
1812 remove.sort()
1821 remove.sort()
1813 for f in remove:
1822 for f in remove:
1814 self.ui.note(_("removing %s\n") % f)
1823 self.ui.note(_("removing %s\n") % f)
1815 util.audit_path(f)
1824 util.audit_path(f)
1816 try:
1825 try:
1817 util.unlink(self.wjoin(f))
1826 util.unlink(self.wjoin(f))
1818 except OSError, inst:
1827 except OSError, inst:
1819 if inst.errno != errno.ENOENT:
1828 if inst.errno != errno.ENOENT:
1820 self.ui.warn(_("update failed to remove %s: %s!\n") %
1829 self.ui.warn(_("update failed to remove %s: %s!\n") %
1821 (f, inst.strerror))
1830 (f, inst.strerror))
1822 if moddirstate:
1831 if moddirstate:
1823 if branch_merge:
1832 if branch_merge:
1824 self.dirstate.update(remove, 'r')
1833 self.dirstate.update(remove, 'r')
1825 else:
1834 else:
1826 self.dirstate.forget(remove)
1835 self.dirstate.forget(remove)
1827
1836
1828 if moddirstate:
1837 if moddirstate:
1829 self.dirstate.setparents(p1, p2)
1838 self.dirstate.setparents(p1, p2)
1830
1839
1831 if show_stats:
1840 if show_stats:
1832 stats = ((len(get), _("updated")),
1841 stats = ((len(get), _("updated")),
1833 (len(merge) - len(failedmerge), _("merged")),
1842 (len(merge) - len(failedmerge), _("merged")),
1834 (len(remove), _("removed")),
1843 (len(remove), _("removed")),
1835 (len(failedmerge), _("unresolved")))
1844 (len(failedmerge), _("unresolved")))
1836 note = ", ".join([_("%d files %s") % s for s in stats])
1845 note = ", ".join([_("%d files %s") % s for s in stats])
1837 self.ui.status("%s\n" % note)
1846 self.ui.status("%s\n" % note)
1838 if moddirstate:
1847 if moddirstate:
1839 if branch_merge:
1848 if branch_merge:
1840 if failedmerge:
1849 if failedmerge:
1841 self.ui.status(_("There are unresolved merges,"
1850 self.ui.status(_("There are unresolved merges,"
1842 " you can redo the full merge using:\n"
1851 " you can redo the full merge using:\n"
1843 " hg update -C %s\n"
1852 " hg update -C %s\n"
1844 " hg merge %s\n"
1853 " hg merge %s\n"
1845 % (self.changelog.rev(p1),
1854 % (self.changelog.rev(p1),
1846 self.changelog.rev(p2))))
1855 self.changelog.rev(p2))))
1847 else:
1856 else:
1848 self.ui.status(_("(branch merge, don't forget to commit)\n"))
1857 self.ui.status(_("(branch merge, don't forget to commit)\n"))
1849 elif failedmerge:
1858 elif failedmerge:
1850 self.ui.status(_("There are unresolved merges with"
1859 self.ui.status(_("There are unresolved merges with"
1851 " locally modified files.\n"))
1860 " locally modified files.\n"))
1852
1861
1862 self.hook('update', parent1=xp1, parent2=xxp2, error=int(err))
1853 return err
1863 return err
1854
1864
1855 def merge3(self, fn, my, other, p1, p2):
1865 def merge3(self, fn, my, other, p1, p2):
1856 """perform a 3-way merge in the working directory"""
1866 """perform a 3-way merge in the working directory"""
1857
1867
1858 def temp(prefix, node):
1868 def temp(prefix, node):
1859 pre = "%s~%s." % (os.path.basename(fn), prefix)
1869 pre = "%s~%s." % (os.path.basename(fn), prefix)
1860 (fd, name) = tempfile.mkstemp(prefix=pre)
1870 (fd, name) = tempfile.mkstemp(prefix=pre)
1861 f = os.fdopen(fd, "wb")
1871 f = os.fdopen(fd, "wb")
1862 self.wwrite(fn, fl.read(node), f)
1872 self.wwrite(fn, fl.read(node), f)
1863 f.close()
1873 f.close()
1864 return name
1874 return name
1865
1875
1866 fl = self.file(fn)
1876 fl = self.file(fn)
1867 base = fl.ancestor(my, other)
1877 base = fl.ancestor(my, other)
1868 a = self.wjoin(fn)
1878 a = self.wjoin(fn)
1869 b = temp("base", base)
1879 b = temp("base", base)
1870 c = temp("other", other)
1880 c = temp("other", other)
1871
1881
1872 self.ui.note(_("resolving %s\n") % fn)
1882 self.ui.note(_("resolving %s\n") % fn)
1873 self.ui.debug(_("file %s: my %s other %s ancestor %s\n") %
1883 self.ui.debug(_("file %s: my %s other %s ancestor %s\n") %
1874 (fn, short(my), short(other), short(base)))
1884 (fn, short(my), short(other), short(base)))
1875
1885
1876 cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge")
1886 cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge")
1877 or "hgmerge")
1887 or "hgmerge")
1878 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=self.root,
1888 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=self.root,
1879 environ={'HG_FILE': fn,
1889 environ={'HG_FILE': fn,
1880 'HG_MY_NODE': p1,
1890 'HG_MY_NODE': p1,
1881 'HG_OTHER_NODE': p2,
1891 'HG_OTHER_NODE': p2,
1882 'HG_FILE_MY_NODE': hex(my),
1892 'HG_FILE_MY_NODE': hex(my),
1883 'HG_FILE_OTHER_NODE': hex(other),
1893 'HG_FILE_OTHER_NODE': hex(other),
1884 'HG_FILE_BASE_NODE': hex(base)})
1894 'HG_FILE_BASE_NODE': hex(base)})
1885 if r:
1895 if r:
1886 self.ui.warn(_("merging %s failed!\n") % fn)
1896 self.ui.warn(_("merging %s failed!\n") % fn)
1887
1897
1888 os.unlink(b)
1898 os.unlink(b)
1889 os.unlink(c)
1899 os.unlink(c)
1890 return r
1900 return r
1891
1901
1892 def verify(self):
1902 def verify(self):
1893 filelinkrevs = {}
1903 filelinkrevs = {}
1894 filenodes = {}
1904 filenodes = {}
1895 changesets = revisions = files = 0
1905 changesets = revisions = files = 0
1896 errors = [0]
1906 errors = [0]
1897 warnings = [0]
1907 warnings = [0]
1898 neededmanifests = {}
1908 neededmanifests = {}
1899
1909
1900 def err(msg):
1910 def err(msg):
1901 self.ui.warn(msg + "\n")
1911 self.ui.warn(msg + "\n")
1902 errors[0] += 1
1912 errors[0] += 1
1903
1913
1904 def warn(msg):
1914 def warn(msg):
1905 self.ui.warn(msg + "\n")
1915 self.ui.warn(msg + "\n")
1906 warnings[0] += 1
1916 warnings[0] += 1
1907
1917
1908 def checksize(obj, name):
1918 def checksize(obj, name):
1909 d = obj.checksize()
1919 d = obj.checksize()
1910 if d[0]:
1920 if d[0]:
1911 err(_("%s data length off by %d bytes") % (name, d[0]))
1921 err(_("%s data length off by %d bytes") % (name, d[0]))
1912 if d[1]:
1922 if d[1]:
1913 err(_("%s index contains %d extra bytes") % (name, d[1]))
1923 err(_("%s index contains %d extra bytes") % (name, d[1]))
1914
1924
1915 def checkversion(obj, name):
1925 def checkversion(obj, name):
1916 if obj.version != revlog.REVLOGV0:
1926 if obj.version != revlog.REVLOGV0:
1917 if not revlogv1:
1927 if not revlogv1:
1918 warn(_("warning: `%s' uses revlog format 1") % name)
1928 warn(_("warning: `%s' uses revlog format 1") % name)
1919 elif revlogv1:
1929 elif revlogv1:
1920 warn(_("warning: `%s' uses revlog format 0") % name)
1930 warn(_("warning: `%s' uses revlog format 0") % name)
1921
1931
1922 revlogv1 = self.revlogversion != revlog.REVLOGV0
1932 revlogv1 = self.revlogversion != revlog.REVLOGV0
1923 if self.ui.verbose or revlogv1 != self.revlogv1:
1933 if self.ui.verbose or revlogv1 != self.revlogv1:
1924 self.ui.status(_("repository uses revlog format %d\n") %
1934 self.ui.status(_("repository uses revlog format %d\n") %
1925 (revlogv1 and 1 or 0))
1935 (revlogv1 and 1 or 0))
1926
1936
1927 seen = {}
1937 seen = {}
1928 self.ui.status(_("checking changesets\n"))
1938 self.ui.status(_("checking changesets\n"))
1929 checksize(self.changelog, "changelog")
1939 checksize(self.changelog, "changelog")
1930
1940
1931 for i in range(self.changelog.count()):
1941 for i in range(self.changelog.count()):
1932 changesets += 1
1942 changesets += 1
1933 n = self.changelog.node(i)
1943 n = self.changelog.node(i)
1934 l = self.changelog.linkrev(n)
1944 l = self.changelog.linkrev(n)
1935 if l != i:
1945 if l != i:
1936 err(_("incorrect link (%d) for changeset revision %d") %(l, i))
1946 err(_("incorrect link (%d) for changeset revision %d") %(l, i))
1937 if n in seen:
1947 if n in seen:
1938 err(_("duplicate changeset at revision %d") % i)
1948 err(_("duplicate changeset at revision %d") % i)
1939 seen[n] = 1
1949 seen[n] = 1
1940
1950
1941 for p in self.changelog.parents(n):
1951 for p in self.changelog.parents(n):
1942 if p not in self.changelog.nodemap:
1952 if p not in self.changelog.nodemap:
1943 err(_("changeset %s has unknown parent %s") %
1953 err(_("changeset %s has unknown parent %s") %
1944 (short(n), short(p)))
1954 (short(n), short(p)))
1945 try:
1955 try:
1946 changes = self.changelog.read(n)
1956 changes = self.changelog.read(n)
1947 except KeyboardInterrupt:
1957 except KeyboardInterrupt:
1948 self.ui.warn(_("interrupted"))
1958 self.ui.warn(_("interrupted"))
1949 raise
1959 raise
1950 except Exception, inst:
1960 except Exception, inst:
1951 err(_("unpacking changeset %s: %s") % (short(n), inst))
1961 err(_("unpacking changeset %s: %s") % (short(n), inst))
1952 continue
1962 continue
1953
1963
1954 neededmanifests[changes[0]] = n
1964 neededmanifests[changes[0]] = n
1955
1965
1956 for f in changes[3]:
1966 for f in changes[3]:
1957 filelinkrevs.setdefault(f, []).append(i)
1967 filelinkrevs.setdefault(f, []).append(i)
1958
1968
1959 seen = {}
1969 seen = {}
1960 self.ui.status(_("checking manifests\n"))
1970 self.ui.status(_("checking manifests\n"))
1961 checkversion(self.manifest, "manifest")
1971 checkversion(self.manifest, "manifest")
1962 checksize(self.manifest, "manifest")
1972 checksize(self.manifest, "manifest")
1963
1973
1964 for i in range(self.manifest.count()):
1974 for i in range(self.manifest.count()):
1965 n = self.manifest.node(i)
1975 n = self.manifest.node(i)
1966 l = self.manifest.linkrev(n)
1976 l = self.manifest.linkrev(n)
1967
1977
1968 if l < 0 or l >= self.changelog.count():
1978 if l < 0 or l >= self.changelog.count():
1969 err(_("bad manifest link (%d) at revision %d") % (l, i))
1979 err(_("bad manifest link (%d) at revision %d") % (l, i))
1970
1980
1971 if n in neededmanifests:
1981 if n in neededmanifests:
1972 del neededmanifests[n]
1982 del neededmanifests[n]
1973
1983
1974 if n in seen:
1984 if n in seen:
1975 err(_("duplicate manifest at revision %d") % i)
1985 err(_("duplicate manifest at revision %d") % i)
1976
1986
1977 seen[n] = 1
1987 seen[n] = 1
1978
1988
1979 for p in self.manifest.parents(n):
1989 for p in self.manifest.parents(n):
1980 if p not in self.manifest.nodemap:
1990 if p not in self.manifest.nodemap:
1981 err(_("manifest %s has unknown parent %s") %
1991 err(_("manifest %s has unknown parent %s") %
1982 (short(n), short(p)))
1992 (short(n), short(p)))
1983
1993
1984 try:
1994 try:
1985 delta = mdiff.patchtext(self.manifest.delta(n))
1995 delta = mdiff.patchtext(self.manifest.delta(n))
1986 except KeyboardInterrupt:
1996 except KeyboardInterrupt:
1987 self.ui.warn(_("interrupted"))
1997 self.ui.warn(_("interrupted"))
1988 raise
1998 raise
1989 except Exception, inst:
1999 except Exception, inst:
1990 err(_("unpacking manifest %s: %s") % (short(n), inst))
2000 err(_("unpacking manifest %s: %s") % (short(n), inst))
1991 continue
2001 continue
1992
2002
1993 try:
2003 try:
1994 ff = [ l.split('\0') for l in delta.splitlines() ]
2004 ff = [ l.split('\0') for l in delta.splitlines() ]
1995 for f, fn in ff:
2005 for f, fn in ff:
1996 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
2006 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1997 except (ValueError, TypeError), inst:
2007 except (ValueError, TypeError), inst:
1998 err(_("broken delta in manifest %s: %s") % (short(n), inst))
2008 err(_("broken delta in manifest %s: %s") % (short(n), inst))
1999
2009
2000 self.ui.status(_("crosschecking files in changesets and manifests\n"))
2010 self.ui.status(_("crosschecking files in changesets and manifests\n"))
2001
2011
2002 for m, c in neededmanifests.items():
2012 for m, c in neededmanifests.items():
2003 err(_("Changeset %s refers to unknown manifest %s") %
2013 err(_("Changeset %s refers to unknown manifest %s") %
2004 (short(m), short(c)))
2014 (short(m), short(c)))
2005 del neededmanifests
2015 del neededmanifests
2006
2016
2007 for f in filenodes:
2017 for f in filenodes:
2008 if f not in filelinkrevs:
2018 if f not in filelinkrevs:
2009 err(_("file %s in manifest but not in changesets") % f)
2019 err(_("file %s in manifest but not in changesets") % f)
2010
2020
2011 for f in filelinkrevs:
2021 for f in filelinkrevs:
2012 if f not in filenodes:
2022 if f not in filenodes:
2013 err(_("file %s in changeset but not in manifest") % f)
2023 err(_("file %s in changeset but not in manifest") % f)
2014
2024
2015 self.ui.status(_("checking files\n"))
2025 self.ui.status(_("checking files\n"))
2016 ff = filenodes.keys()
2026 ff = filenodes.keys()
2017 ff.sort()
2027 ff.sort()
2018 for f in ff:
2028 for f in ff:
2019 if f == "/dev/null":
2029 if f == "/dev/null":
2020 continue
2030 continue
2021 files += 1
2031 files += 1
2022 if not f:
2032 if not f:
2023 err(_("file without name in manifest %s") % short(n))
2033 err(_("file without name in manifest %s") % short(n))
2024 continue
2034 continue
2025 fl = self.file(f)
2035 fl = self.file(f)
2026 checkversion(fl, f)
2036 checkversion(fl, f)
2027 checksize(fl, f)
2037 checksize(fl, f)
2028
2038
2029 nodes = {nullid: 1}
2039 nodes = {nullid: 1}
2030 seen = {}
2040 seen = {}
2031 for i in range(fl.count()):
2041 for i in range(fl.count()):
2032 revisions += 1
2042 revisions += 1
2033 n = fl.node(i)
2043 n = fl.node(i)
2034
2044
2035 if n in seen:
2045 if n in seen:
2036 err(_("%s: duplicate revision %d") % (f, i))
2046 err(_("%s: duplicate revision %d") % (f, i))
2037 if n not in filenodes[f]:
2047 if n not in filenodes[f]:
2038 err(_("%s: %d:%s not in manifests") % (f, i, short(n)))
2048 err(_("%s: %d:%s not in manifests") % (f, i, short(n)))
2039 else:
2049 else:
2040 del filenodes[f][n]
2050 del filenodes[f][n]
2041
2051
2042 flr = fl.linkrev(n)
2052 flr = fl.linkrev(n)
2043 if flr not in filelinkrevs.get(f, []):
2053 if flr not in filelinkrevs.get(f, []):
2044 err(_("%s:%s points to unexpected changeset %d")
2054 err(_("%s:%s points to unexpected changeset %d")
2045 % (f, short(n), flr))
2055 % (f, short(n), flr))
2046 else:
2056 else:
2047 filelinkrevs[f].remove(flr)
2057 filelinkrevs[f].remove(flr)
2048
2058
2049 # verify contents
2059 # verify contents
2050 try:
2060 try:
2051 t = fl.read(n)
2061 t = fl.read(n)
2052 except KeyboardInterrupt:
2062 except KeyboardInterrupt:
2053 self.ui.warn(_("interrupted"))
2063 self.ui.warn(_("interrupted"))
2054 raise
2064 raise
2055 except Exception, inst:
2065 except Exception, inst:
2056 err(_("unpacking file %s %s: %s") % (f, short(n), inst))
2066 err(_("unpacking file %s %s: %s") % (f, short(n), inst))
2057
2067
2058 # verify parents
2068 # verify parents
2059 (p1, p2) = fl.parents(n)
2069 (p1, p2) = fl.parents(n)
2060 if p1 not in nodes:
2070 if p1 not in nodes:
2061 err(_("file %s:%s unknown parent 1 %s") %
2071 err(_("file %s:%s unknown parent 1 %s") %
2062 (f, short(n), short(p1)))
2072 (f, short(n), short(p1)))
2063 if p2 not in nodes:
2073 if p2 not in nodes:
2064 err(_("file %s:%s unknown parent 2 %s") %
2074 err(_("file %s:%s unknown parent 2 %s") %
2065 (f, short(n), short(p1)))
2075 (f, short(n), short(p1)))
2066 nodes[n] = 1
2076 nodes[n] = 1
2067
2077
2068 # cross-check
2078 # cross-check
2069 for node in filenodes[f]:
2079 for node in filenodes[f]:
2070 err(_("node %s in manifests not in %s") % (hex(node), f))
2080 err(_("node %s in manifests not in %s") % (hex(node), f))
2071
2081
2072 self.ui.status(_("%d files, %d changesets, %d total revisions\n") %
2082 self.ui.status(_("%d files, %d changesets, %d total revisions\n") %
2073 (files, changesets, revisions))
2083 (files, changesets, revisions))
2074
2084
2075 if warnings[0]:
2085 if warnings[0]:
2076 self.ui.warn(_("%d warnings encountered!\n") % warnings[0])
2086 self.ui.warn(_("%d warnings encountered!\n") % warnings[0])
2077 if errors[0]:
2087 if errors[0]:
2078 self.ui.warn(_("%d integrity errors encountered!\n") % errors[0])
2088 self.ui.warn(_("%d integrity errors encountered!\n") % errors[0])
2079 return 1
2089 return 1
2080
2090
2081 # used to avoid circular references so destructors work
2091 # used to avoid circular references so destructors work
2082 def aftertrans(base):
2092 def aftertrans(base):
2083 p = base
2093 p = base
2084 def a():
2094 def a():
2085 util.rename(os.path.join(p, "journal"), os.path.join(p, "undo"))
2095 util.rename(os.path.join(p, "journal"), os.path.join(p, "undo"))
2086 util.rename(os.path.join(p, "journal.dirstate"),
2096 util.rename(os.path.join(p, "journal.dirstate"),
2087 os.path.join(p, "undo.dirstate"))
2097 os.path.join(p, "undo.dirstate"))
2088 return a
2098 return a
2089
2099
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
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
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