##// END OF EJS Templates
bookmarks: separate bookmarks update code from localrepo's pull....
David Soria Parra -
r13646:31eac42d default
parent child Browse files
Show More
@@ -1,179 +1,201 b''
1 1 # Mercurial bookmark support code
2 2 #
3 3 # Copyright 2008 David Soria Parra <dsp@php.net>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from mercurial.i18n import _
9 9 from mercurial.node import nullid, nullrev, bin, hex, short
10 10 from mercurial import encoding, util
11 11 import os
12 12
13 13 def valid(mark):
14 14 for c in (':', '\0', '\n', '\r'):
15 15 if c in mark:
16 16 return False
17 17 return True
18 18
19 19 def read(repo):
20 20 '''Parse .hg/bookmarks file and return a dictionary
21 21
22 22 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
23 23 in the .hg/bookmarks file.
24 24 Read the file and return a (name=>nodeid) dictionary
25 25 '''
26 26 try:
27 27 bookmarks = {}
28 28 for line in repo.opener('bookmarks'):
29 29 sha, refspec = line.strip().split(' ', 1)
30 30 refspec = encoding.tolocal(refspec)
31 31 bookmarks[refspec] = repo.changelog.lookup(sha)
32 32 except:
33 33 pass
34 34 return bookmarks
35 35
36 36 def readcurrent(repo):
37 37 '''Get the current bookmark
38 38
39 39 If we use gittishsh branches we have a current bookmark that
40 40 we are on. This function returns the name of the bookmark. It
41 41 is stored in .hg/bookmarks.current
42 42 '''
43 43 mark = None
44 44 if os.path.exists(repo.join('bookmarks.current')):
45 45 file = repo.opener('bookmarks.current')
46 46 # No readline() in posixfile_nt, reading everything is cheap
47 47 mark = encoding.tolocal((file.readlines() or [''])[0])
48 48 if mark == '' or mark not in repo._bookmarks:
49 49 mark = None
50 50 file.close()
51 51 return mark
52 52
53 53 def write(repo):
54 54 '''Write bookmarks
55 55
56 56 Write the given bookmark => hash dictionary to the .hg/bookmarks file
57 57 in a format equal to those of localtags.
58 58
59 59 We also store a backup of the previous state in undo.bookmarks that
60 60 can be copied back on rollback.
61 61 '''
62 62 refs = repo._bookmarks
63 63
64 64 try:
65 65 bms = repo.opener('bookmarks').read()
66 66 except IOError:
67 67 bms = ''
68 68 repo.opener('undo.bookmarks', 'w').write(bms)
69 69
70 70 if repo._bookmarkcurrent not in refs:
71 71 setcurrent(repo, None)
72 72 for mark in refs.keys():
73 73 if not valid(mark):
74 74 raise util.Abort(_("bookmark '%s' contains illegal "
75 75 "character" % mark))
76 76
77 77 wlock = repo.wlock()
78 78 try:
79 79
80 80 file = repo.opener('bookmarks', 'w', atomictemp=True)
81 81 for refspec, node in refs.iteritems():
82 82 file.write("%s %s\n" % (hex(node), encoding.fromlocal(refspec)))
83 83 file.rename()
84 84
85 85 # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
86 86 try:
87 87 os.utime(repo.sjoin('00changelog.i'), None)
88 88 except OSError:
89 89 pass
90 90
91 91 finally:
92 92 wlock.release()
93 93
94 94 def setcurrent(repo, mark):
95 95 '''Set the name of the bookmark that we are currently on
96 96
97 97 Set the name of the bookmark that we are on (hg update <bookmark>).
98 98 The name is recorded in .hg/bookmarks.current
99 99 '''
100 100 current = repo._bookmarkcurrent
101 101 if current == mark:
102 102 return
103 103
104 104 refs = repo._bookmarks
105 105
106 106 # do not update if we do update to a rev equal to the current bookmark
107 107 if (mark and mark not in refs and
108 108 current and refs[current] == repo.changectx('.').node()):
109 109 return
110 110 if mark not in refs:
111 111 mark = ''
112 112 if not valid(mark):
113 113 raise util.Abort(_("bookmark '%s' contains illegal "
114 114 "character" % mark))
115 115
116 116 wlock = repo.wlock()
117 117 try:
118 118 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
119 119 file.write(mark)
120 120 file.rename()
121 121 finally:
122 122 wlock.release()
123 123 repo._bookmarkcurrent = mark
124 124
125 125 def update(repo, parents, node):
126 126 marks = repo._bookmarks
127 127 update = False
128 128 mark = repo._bookmarkcurrent
129 129 if mark and marks[mark] in parents:
130 130 old = repo[marks[mark]]
131 131 new = repo[node]
132 132 if new in old.descendants():
133 133 marks[mark] = new.node()
134 134 update = True
135 135 if update:
136 136 write(repo)
137 137
138 138 def listbookmarks(repo):
139 139 # We may try to list bookmarks on a repo type that does not
140 140 # support it (e.g., statichttprepository).
141 141 if not hasattr(repo, '_bookmarks'):
142 142 return {}
143 143
144 144 d = {}
145 145 for k, v in repo._bookmarks.iteritems():
146 146 d[k] = hex(v)
147 147 return d
148 148
149 149 def pushbookmark(repo, key, old, new):
150 150 w = repo.wlock()
151 151 try:
152 152 marks = repo._bookmarks
153 153 if hex(marks.get(key, '')) != old:
154 154 return False
155 155 if new == '':
156 156 del marks[key]
157 157 else:
158 158 if new not in repo:
159 159 return False
160 160 marks[key] = repo[new].node()
161 161 write(repo)
162 162 return True
163 163 finally:
164 164 w.release()
165 165
166 def updatefromremote(ui, repo, remote):
167 ui.debug("checking for updated bookmarks\n")
168 rb = remote.listkeys('bookmarks')
169 changed = False
170 for k in rb.keys():
171 if k in repo._bookmarks:
172 nr, nl = rb[k], repo._bookmarks[k]
173 if nr in repo:
174 cr = repo[nr]
175 cl = repo[nl]
176 if cl.rev() >= cr.rev():
177 continue
178 if cr in cl.descendants():
179 repo._bookmarks[k] = cr.node()
180 changed = True
181 ui.status(_("updating bookmark %s\n") % k)
182 else:
183 ui.warn(_("not updating divergent"
184 " bookmark %s\n") % k)
185 if changed:
186 write(repo)
187
166 188 def diff(ui, repo, remote):
167 189 ui.status(_("searching for changed bookmarks\n"))
168 190
169 191 lmarks = repo.listkeys('bookmarks')
170 192 rmarks = remote.listkeys('bookmarks')
171 193
172 194 diff = sorted(set(rmarks) - set(lmarks))
173 195 for k in diff:
174 196 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
175 197
176 198 if len(diff) <= 0:
177 199 ui.status(_("no changed bookmarks found\n"))
178 200 return 1
179 201 return 0
@@ -1,4788 +1,4789 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import hex, nullid, nullrev, short
9 9 from lock import release
10 10 from i18n import _, gettext
11 11 import os, re, sys, difflib, time, tempfile
12 12 import hg, util, revlog, extensions, copies, error, bookmarks
13 13 import patch, help, mdiff, url, encoding, templatekw, discovery
14 14 import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server
15 15 import merge as mergemod
16 16 import minirst, revset, templatefilters
17 17 import dagparser
18 18
19 19 # Commands start here, listed alphabetically
20 20
21 21 def add(ui, repo, *pats, **opts):
22 22 """add the specified files on the next commit
23 23
24 24 Schedule files to be version controlled and added to the
25 25 repository.
26 26
27 27 The files will be added to the repository at the next commit. To
28 28 undo an add before that, see :hg:`forget`.
29 29
30 30 If no names are given, add all files to the repository.
31 31
32 32 .. container:: verbose
33 33
34 34 An example showing how new (unknown) files are added
35 35 automatically by :hg:`add`::
36 36
37 37 $ ls
38 38 foo.c
39 39 $ hg status
40 40 ? foo.c
41 41 $ hg add
42 42 adding foo.c
43 43 $ hg status
44 44 A foo.c
45 45
46 46 Returns 0 if all files are successfully added.
47 47 """
48 48
49 49 m = cmdutil.match(repo, pats, opts)
50 50 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
51 51 opts.get('subrepos'), prefix="")
52 52 return rejected and 1 or 0
53 53
54 54 def addremove(ui, repo, *pats, **opts):
55 55 """add all new files, delete all missing files
56 56
57 57 Add all new files and remove all missing files from the
58 58 repository.
59 59
60 60 New files are ignored if they match any of the patterns in
61 61 ``.hgignore``. As with add, these changes take effect at the next
62 62 commit.
63 63
64 64 Use the -s/--similarity option to detect renamed files. With a
65 65 parameter greater than 0, this compares every removed file with
66 66 every added file and records those similar enough as renames. This
67 67 option takes a percentage between 0 (disabled) and 100 (files must
68 68 be identical) as its parameter. Detecting renamed files this way
69 69 can be expensive. After using this option, :hg:`status -C` can be
70 70 used to check which files were identified as moved or renamed.
71 71
72 72 Returns 0 if all files are successfully added.
73 73 """
74 74 try:
75 75 sim = float(opts.get('similarity') or 100)
76 76 except ValueError:
77 77 raise util.Abort(_('similarity must be a number'))
78 78 if sim < 0 or sim > 100:
79 79 raise util.Abort(_('similarity must be between 0 and 100'))
80 80 return cmdutil.addremove(repo, pats, opts, similarity=sim / 100.0)
81 81
82 82 def annotate(ui, repo, *pats, **opts):
83 83 """show changeset information by line for each file
84 84
85 85 List changes in files, showing the revision id responsible for
86 86 each line
87 87
88 88 This command is useful for discovering when a change was made and
89 89 by whom.
90 90
91 91 Without the -a/--text option, annotate will avoid processing files
92 92 it detects as binary. With -a, annotate will annotate the file
93 93 anyway, although the results will probably be neither useful
94 94 nor desirable.
95 95
96 96 Returns 0 on success.
97 97 """
98 98 if opts.get('follow'):
99 99 # --follow is deprecated and now just an alias for -f/--file
100 100 # to mimic the behavior of Mercurial before version 1.5
101 101 opts['file'] = 1
102 102
103 103 datefunc = ui.quiet and util.shortdate or util.datestr
104 104 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
105 105
106 106 if not pats:
107 107 raise util.Abort(_('at least one filename or pattern is required'))
108 108
109 109 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
110 110 ('number', lambda x: str(x[0].rev())),
111 111 ('changeset', lambda x: short(x[0].node())),
112 112 ('date', getdate),
113 113 ('file', lambda x: x[0].path()),
114 114 ]
115 115
116 116 if (not opts.get('user') and not opts.get('changeset')
117 117 and not opts.get('date') and not opts.get('file')):
118 118 opts['number'] = 1
119 119
120 120 linenumber = opts.get('line_number') is not None
121 121 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
122 122 raise util.Abort(_('at least one of -n/-c is required for -l'))
123 123
124 124 funcmap = [func for op, func in opmap if opts.get(op)]
125 125 if linenumber:
126 126 lastfunc = funcmap[-1]
127 127 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
128 128
129 129 ctx = cmdutil.revsingle(repo, opts.get('rev'))
130 130 m = cmdutil.match(repo, pats, opts)
131 131 follow = not opts.get('no_follow')
132 132 for abs in ctx.walk(m):
133 133 fctx = ctx[abs]
134 134 if not opts.get('text') and util.binary(fctx.data()):
135 135 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
136 136 continue
137 137
138 138 lines = fctx.annotate(follow=follow, linenumber=linenumber)
139 139 pieces = []
140 140
141 141 for f in funcmap:
142 142 l = [f(n) for n, dummy in lines]
143 143 if l:
144 144 sized = [(x, encoding.colwidth(x)) for x in l]
145 145 ml = max([w for x, w in sized])
146 146 pieces.append(["%s%s" % (' ' * (ml - w), x) for x, w in sized])
147 147
148 148 if pieces:
149 149 for p, l in zip(zip(*pieces), lines):
150 150 ui.write("%s: %s" % (" ".join(p), l[1]))
151 151
152 152 def archive(ui, repo, dest, **opts):
153 153 '''create an unversioned archive of a repository revision
154 154
155 155 By default, the revision used is the parent of the working
156 156 directory; use -r/--rev to specify a different revision.
157 157
158 158 The archive type is automatically detected based on file
159 159 extension (or override using -t/--type).
160 160
161 161 Valid types are:
162 162
163 163 :``files``: a directory full of files (default)
164 164 :``tar``: tar archive, uncompressed
165 165 :``tbz2``: tar archive, compressed using bzip2
166 166 :``tgz``: tar archive, compressed using gzip
167 167 :``uzip``: zip archive, uncompressed
168 168 :``zip``: zip archive, compressed using deflate
169 169
170 170 The exact name of the destination archive or directory is given
171 171 using a format string; see :hg:`help export` for details.
172 172
173 173 Each member added to an archive file has a directory prefix
174 174 prepended. Use -p/--prefix to specify a format string for the
175 175 prefix. The default is the basename of the archive, with suffixes
176 176 removed.
177 177
178 178 Returns 0 on success.
179 179 '''
180 180
181 181 ctx = cmdutil.revsingle(repo, opts.get('rev'))
182 182 if not ctx:
183 183 raise util.Abort(_('no working directory: please specify a revision'))
184 184 node = ctx.node()
185 185 dest = cmdutil.make_filename(repo, dest, node)
186 186 if os.path.realpath(dest) == repo.root:
187 187 raise util.Abort(_('repository root cannot be destination'))
188 188
189 189 kind = opts.get('type') or archival.guesskind(dest) or 'files'
190 190 prefix = opts.get('prefix')
191 191
192 192 if dest == '-':
193 193 if kind == 'files':
194 194 raise util.Abort(_('cannot archive plain files to stdout'))
195 195 dest = sys.stdout
196 196 if not prefix:
197 197 prefix = os.path.basename(repo.root) + '-%h'
198 198
199 199 prefix = cmdutil.make_filename(repo, prefix, node)
200 200 matchfn = cmdutil.match(repo, [], opts)
201 201 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
202 202 matchfn, prefix, subrepos=opts.get('subrepos'))
203 203
204 204 def backout(ui, repo, node=None, rev=None, **opts):
205 205 '''reverse effect of earlier changeset
206 206
207 207 Prepare a new changeset with the effect of REV undone in the
208 208 current working directory.
209 209
210 210 If REV is the parent of the working directory, then this new changeset
211 211 is committed automatically. Otherwise, hg needs to merge the
212 212 changes and the merged result is left uncommitted.
213 213
214 214 By default, the pending changeset will have one parent,
215 215 maintaining a linear history. With --merge, the pending changeset
216 216 will instead have two parents: the old parent of the working
217 217 directory and a new child of REV that simply undoes REV.
218 218
219 219 Before version 1.7, the behavior without --merge was equivalent to
220 220 specifying --merge followed by :hg:`update --clean .` to cancel
221 221 the merge and leave the child of REV as a head to be merged
222 222 separately.
223 223
224 224 See :hg:`help dates` for a list of formats valid for -d/--date.
225 225
226 226 Returns 0 on success.
227 227 '''
228 228 if rev and node:
229 229 raise util.Abort(_("please specify just one revision"))
230 230
231 231 if not rev:
232 232 rev = node
233 233
234 234 if not rev:
235 235 raise util.Abort(_("please specify a revision to backout"))
236 236
237 237 date = opts.get('date')
238 238 if date:
239 239 opts['date'] = util.parsedate(date)
240 240
241 241 cmdutil.bail_if_changed(repo)
242 242 node = cmdutil.revsingle(repo, rev).node()
243 243
244 244 op1, op2 = repo.dirstate.parents()
245 245 a = repo.changelog.ancestor(op1, node)
246 246 if a != node:
247 247 raise util.Abort(_('cannot backout change on a different branch'))
248 248
249 249 p1, p2 = repo.changelog.parents(node)
250 250 if p1 == nullid:
251 251 raise util.Abort(_('cannot backout a change with no parents'))
252 252 if p2 != nullid:
253 253 if not opts.get('parent'):
254 254 raise util.Abort(_('cannot backout a merge changeset without '
255 255 '--parent'))
256 256 p = repo.lookup(opts['parent'])
257 257 if p not in (p1, p2):
258 258 raise util.Abort(_('%s is not a parent of %s') %
259 259 (short(p), short(node)))
260 260 parent = p
261 261 else:
262 262 if opts.get('parent'):
263 263 raise util.Abort(_('cannot use --parent on non-merge changeset'))
264 264 parent = p1
265 265
266 266 # the backout should appear on the same branch
267 267 branch = repo.dirstate.branch()
268 268 hg.clean(repo, node, show_stats=False)
269 269 repo.dirstate.setbranch(branch)
270 270 revert_opts = opts.copy()
271 271 revert_opts['date'] = None
272 272 revert_opts['all'] = True
273 273 revert_opts['rev'] = hex(parent)
274 274 revert_opts['no_backup'] = None
275 275 revert(ui, repo, **revert_opts)
276 276 if not opts.get('merge') and op1 != node:
277 277 try:
278 278 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
279 279 return hg.update(repo, op1)
280 280 finally:
281 281 ui.setconfig('ui', 'forcemerge', '')
282 282
283 283 commit_opts = opts.copy()
284 284 commit_opts['addremove'] = False
285 285 if not commit_opts['message'] and not commit_opts['logfile']:
286 286 # we don't translate commit messages
287 287 commit_opts['message'] = "Backed out changeset %s" % short(node)
288 288 commit_opts['force_editor'] = True
289 289 commit(ui, repo, **commit_opts)
290 290 def nice(node):
291 291 return '%d:%s' % (repo.changelog.rev(node), short(node))
292 292 ui.status(_('changeset %s backs out changeset %s\n') %
293 293 (nice(repo.changelog.tip()), nice(node)))
294 294 if opts.get('merge') and op1 != node:
295 295 hg.clean(repo, op1, show_stats=False)
296 296 ui.status(_('merging with changeset %s\n')
297 297 % nice(repo.changelog.tip()))
298 298 try:
299 299 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
300 300 return hg.merge(repo, hex(repo.changelog.tip()))
301 301 finally:
302 302 ui.setconfig('ui', 'forcemerge', '')
303 303 return 0
304 304
305 305 def bisect(ui, repo, rev=None, extra=None, command=None,
306 306 reset=None, good=None, bad=None, skip=None, extend=None,
307 307 noupdate=None):
308 308 """subdivision search of changesets
309 309
310 310 This command helps to find changesets which introduce problems. To
311 311 use, mark the earliest changeset you know exhibits the problem as
312 312 bad, then mark the latest changeset which is free from the problem
313 313 as good. Bisect will update your working directory to a revision
314 314 for testing (unless the -U/--noupdate option is specified). Once
315 315 you have performed tests, mark the working directory as good or
316 316 bad, and bisect will either update to another candidate changeset
317 317 or announce that it has found the bad revision.
318 318
319 319 As a shortcut, you can also use the revision argument to mark a
320 320 revision as good or bad without checking it out first.
321 321
322 322 If you supply a command, it will be used for automatic bisection.
323 323 Its exit status will be used to mark revisions as good or bad:
324 324 status 0 means good, 125 means to skip the revision, 127
325 325 (command not found) will abort the bisection, and any other
326 326 non-zero exit status means the revision is bad.
327 327
328 328 Returns 0 on success.
329 329 """
330 330 def extendbisectrange(nodes, good):
331 331 # bisect is incomplete when it ends on a merge node and
332 332 # one of the parent was not checked.
333 333 parents = repo[nodes[0]].parents()
334 334 if len(parents) > 1:
335 335 side = good and state['bad'] or state['good']
336 336 num = len(set(i.node() for i in parents) & set(side))
337 337 if num == 1:
338 338 return parents[0].ancestor(parents[1])
339 339 return None
340 340
341 341 def print_result(nodes, good):
342 342 displayer = cmdutil.show_changeset(ui, repo, {})
343 343 if len(nodes) == 1:
344 344 # narrowed it down to a single revision
345 345 if good:
346 346 ui.write(_("The first good revision is:\n"))
347 347 else:
348 348 ui.write(_("The first bad revision is:\n"))
349 349 displayer.show(repo[nodes[0]])
350 350 parents = repo[nodes[0]].parents()
351 351 extendnode = extendbisectrange(nodes, good)
352 352 if extendnode is not None:
353 353 ui.write(_('Not all ancestors of this changeset have been'
354 354 ' checked.\nUse bisect --extend to continue the '
355 355 'bisection from\nthe common ancestor, %s.\n')
356 356 % short(extendnode.node()))
357 357 else:
358 358 # multiple possible revisions
359 359 if good:
360 360 ui.write(_("Due to skipped revisions, the first "
361 361 "good revision could be any of:\n"))
362 362 else:
363 363 ui.write(_("Due to skipped revisions, the first "
364 364 "bad revision could be any of:\n"))
365 365 for n in nodes:
366 366 displayer.show(repo[n])
367 367 displayer.close()
368 368
369 369 def check_state(state, interactive=True):
370 370 if not state['good'] or not state['bad']:
371 371 if (good or bad or skip or reset) and interactive:
372 372 return
373 373 if not state['good']:
374 374 raise util.Abort(_('cannot bisect (no known good revisions)'))
375 375 else:
376 376 raise util.Abort(_('cannot bisect (no known bad revisions)'))
377 377 return True
378 378
379 379 # backward compatibility
380 380 if rev in "good bad reset init".split():
381 381 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
382 382 cmd, rev, extra = rev, extra, None
383 383 if cmd == "good":
384 384 good = True
385 385 elif cmd == "bad":
386 386 bad = True
387 387 else:
388 388 reset = True
389 389 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
390 390 raise util.Abort(_('incompatible arguments'))
391 391
392 392 if reset:
393 393 p = repo.join("bisect.state")
394 394 if os.path.exists(p):
395 395 os.unlink(p)
396 396 return
397 397
398 398 state = hbisect.load_state(repo)
399 399
400 400 if command:
401 401 changesets = 1
402 402 try:
403 403 while changesets:
404 404 # update state
405 405 status = util.system(command)
406 406 if status == 125:
407 407 transition = "skip"
408 408 elif status == 0:
409 409 transition = "good"
410 410 # status < 0 means process was killed
411 411 elif status == 127:
412 412 raise util.Abort(_("failed to execute %s") % command)
413 413 elif status < 0:
414 414 raise util.Abort(_("%s killed") % command)
415 415 else:
416 416 transition = "bad"
417 417 ctx = cmdutil.revsingle(repo, rev)
418 418 rev = None # clear for future iterations
419 419 state[transition].append(ctx.node())
420 420 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
421 421 check_state(state, interactive=False)
422 422 # bisect
423 423 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
424 424 # update to next check
425 425 cmdutil.bail_if_changed(repo)
426 426 hg.clean(repo, nodes[0], show_stats=False)
427 427 finally:
428 428 hbisect.save_state(repo, state)
429 429 print_result(nodes, good)
430 430 return
431 431
432 432 # update state
433 433
434 434 if rev:
435 435 nodes = [repo.lookup(i) for i in cmdutil.revrange(repo, [rev])]
436 436 else:
437 437 nodes = [repo.lookup('.')]
438 438
439 439 if good or bad or skip:
440 440 if good:
441 441 state['good'] += nodes
442 442 elif bad:
443 443 state['bad'] += nodes
444 444 elif skip:
445 445 state['skip'] += nodes
446 446 hbisect.save_state(repo, state)
447 447
448 448 if not check_state(state):
449 449 return
450 450
451 451 # actually bisect
452 452 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
453 453 if extend:
454 454 if not changesets:
455 455 extendnode = extendbisectrange(nodes, good)
456 456 if extendnode is not None:
457 457 ui.write(_("Extending search to changeset %d:%s\n"
458 458 % (extendnode.rev(), short(extendnode.node()))))
459 459 if noupdate:
460 460 return
461 461 cmdutil.bail_if_changed(repo)
462 462 return hg.clean(repo, extendnode.node())
463 463 raise util.Abort(_("nothing to extend"))
464 464
465 465 if changesets == 0:
466 466 print_result(nodes, good)
467 467 else:
468 468 assert len(nodes) == 1 # only a single node can be tested next
469 469 node = nodes[0]
470 470 # compute the approximate number of remaining tests
471 471 tests, size = 0, 2
472 472 while size <= changesets:
473 473 tests, size = tests + 1, size * 2
474 474 rev = repo.changelog.rev(node)
475 475 ui.write(_("Testing changeset %d:%s "
476 476 "(%d changesets remaining, ~%d tests)\n")
477 477 % (rev, short(node), changesets, tests))
478 478 if not noupdate:
479 479 cmdutil.bail_if_changed(repo)
480 480 return hg.clean(repo, node)
481 481
482 482 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
483 483 '''track a line of development with movable markers
484 484
485 485 Bookmarks are pointers to certain commits that move when
486 486 committing. Bookmarks are local. They can be renamed, copied and
487 487 deleted. It is possible to use bookmark names in :hg:`merge` and
488 488 :hg:`update` to merge and update respectively to a given bookmark.
489 489
490 490 You can use :hg:`bookmark NAME` to set a bookmark on the working
491 491 directory's parent revision with the given name. If you specify
492 492 a revision using -r REV (where REV may be an existing bookmark),
493 493 the bookmark is assigned to that revision.
494 494
495 495 Bookmarks can be pushed and pulled between repositories (see :hg:`help
496 496 push` and :hg:`help pull`). This requires both the local and remote
497 497 repositories to support bookmarks. For versions prior to 1.8, this means
498 498 the bookmarks extension must be enabled.
499 499 '''
500 500 hexfn = ui.debugflag and hex or short
501 501 marks = repo._bookmarks
502 502 cur = repo.changectx('.').node()
503 503
504 504 if rename:
505 505 if rename not in marks:
506 506 raise util.Abort(_("a bookmark of this name does not exist"))
507 507 if mark in marks and not force:
508 508 raise util.Abort(_("a bookmark of the same name already exists"))
509 509 if mark is None:
510 510 raise util.Abort(_("new bookmark name required"))
511 511 marks[mark] = marks[rename]
512 512 if repo._bookmarkcurrent == rename:
513 513 bookmarks.setcurrent(repo, mark)
514 514 del marks[rename]
515 515 bookmarks.write(repo)
516 516 return
517 517
518 518 if delete:
519 519 if mark is None:
520 520 raise util.Abort(_("bookmark name required"))
521 521 if mark not in marks:
522 522 raise util.Abort(_("a bookmark of this name does not exist"))
523 523 if mark == repo._bookmarkcurrent:
524 524 bookmarks.setcurrent(repo, None)
525 525 del marks[mark]
526 526 bookmarks.write(repo)
527 527 return
528 528
529 529 if mark is not None:
530 530 if "\n" in mark:
531 531 raise util.Abort(_("bookmark name cannot contain newlines"))
532 532 mark = mark.strip()
533 533 if not mark:
534 534 raise util.Abort(_("bookmark names cannot consist entirely of "
535 535 "whitespace"))
536 536 if mark in marks and not force:
537 537 raise util.Abort(_("a bookmark of the same name already exists"))
538 538 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
539 539 and not force):
540 540 raise util.Abort(
541 541 _("a bookmark cannot have the name of an existing branch"))
542 542 if rev:
543 543 marks[mark] = repo.lookup(rev)
544 544 else:
545 545 marks[mark] = repo.changectx('.').node()
546 546 if repo.changectx('.').node() == marks[mark]:
547 547 bookmarks.setcurrent(repo, mark)
548 548 bookmarks.write(repo)
549 549 return
550 550
551 551 if mark is None:
552 552 if rev:
553 553 raise util.Abort(_("bookmark name required"))
554 554 if len(marks) == 0:
555 555 ui.status(_("no bookmarks set\n"))
556 556 else:
557 557 for bmark, n in sorted(marks.iteritems()):
558 558 current = repo._bookmarkcurrent
559 559 if bmark == current and n == cur:
560 560 prefix, label = '*', 'bookmarks.current'
561 561 else:
562 562 prefix, label = ' ', ''
563 563
564 564 if ui.quiet:
565 565 ui.write("%s\n" % bmark, label=label)
566 566 else:
567 567 ui.write(" %s %-25s %d:%s\n" % (
568 568 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
569 569 label=label)
570 570 return
571 571
572 572 def branch(ui, repo, label=None, **opts):
573 573 """set or show the current branch name
574 574
575 575 With no argument, show the current branch name. With one argument,
576 576 set the working directory branch name (the branch will not exist
577 577 in the repository until the next commit). Standard practice
578 578 recommends that primary development take place on the 'default'
579 579 branch.
580 580
581 581 Unless -f/--force is specified, branch will not let you set a
582 582 branch name that already exists, even if it's inactive.
583 583
584 584 Use -C/--clean to reset the working directory branch to that of
585 585 the parent of the working directory, negating a previous branch
586 586 change.
587 587
588 588 Use the command :hg:`update` to switch to an existing branch. Use
589 589 :hg:`commit --close-branch` to mark this branch as closed.
590 590
591 591 Returns 0 on success.
592 592 """
593 593
594 594 if opts.get('clean'):
595 595 label = repo[None].parents()[0].branch()
596 596 repo.dirstate.setbranch(label)
597 597 ui.status(_('reset working directory to branch %s\n') % label)
598 598 elif label:
599 599 if not opts.get('force') and label in repo.branchtags():
600 600 if label not in [p.branch() for p in repo.parents()]:
601 601 raise util.Abort(_('a branch of the same name already exists'
602 602 " (use 'hg update' to switch to it)"))
603 603 repo.dirstate.setbranch(label)
604 604 ui.status(_('marked working directory as branch %s\n') % label)
605 605 else:
606 606 ui.write("%s\n" % repo.dirstate.branch())
607 607
608 608 def branches(ui, repo, active=False, closed=False):
609 609 """list repository named branches
610 610
611 611 List the repository's named branches, indicating which ones are
612 612 inactive. If -c/--closed is specified, also list branches which have
613 613 been marked closed (see :hg:`commit --close-branch`).
614 614
615 615 If -a/--active is specified, only show active branches. A branch
616 616 is considered active if it contains repository heads.
617 617
618 618 Use the command :hg:`update` to switch to an existing branch.
619 619
620 620 Returns 0.
621 621 """
622 622
623 623 hexfunc = ui.debugflag and hex or short
624 624 activebranches = [repo[n].branch() for n in repo.heads()]
625 625 def testactive(tag, node):
626 626 realhead = tag in activebranches
627 627 open = node in repo.branchheads(tag, closed=False)
628 628 return realhead and open
629 629 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
630 630 for tag, node in repo.branchtags().items()],
631 631 reverse=True)
632 632
633 633 for isactive, node, tag in branches:
634 634 if (not active) or isactive:
635 635 if ui.quiet:
636 636 ui.write("%s\n" % tag)
637 637 else:
638 638 hn = repo.lookup(node)
639 639 if isactive:
640 640 label = 'branches.active'
641 641 notice = ''
642 642 elif hn not in repo.branchheads(tag, closed=False):
643 643 if not closed:
644 644 continue
645 645 label = 'branches.closed'
646 646 notice = _(' (closed)')
647 647 else:
648 648 label = 'branches.inactive'
649 649 notice = _(' (inactive)')
650 650 if tag == repo.dirstate.branch():
651 651 label = 'branches.current'
652 652 rev = str(node).rjust(31 - encoding.colwidth(tag))
653 653 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
654 654 tag = ui.label(tag, label)
655 655 ui.write("%s %s%s\n" % (tag, rev, notice))
656 656
657 657 def bundle(ui, repo, fname, dest=None, **opts):
658 658 """create a changegroup file
659 659
660 660 Generate a compressed changegroup file collecting changesets not
661 661 known to be in another repository.
662 662
663 663 If you omit the destination repository, then hg assumes the
664 664 destination will have all the nodes you specify with --base
665 665 parameters. To create a bundle containing all changesets, use
666 666 -a/--all (or --base null).
667 667
668 668 You can change compression method with the -t/--type option.
669 669 The available compression methods are: none, bzip2, and
670 670 gzip (by default, bundles are compressed using bzip2).
671 671
672 672 The bundle file can then be transferred using conventional means
673 673 and applied to another repository with the unbundle or pull
674 674 command. This is useful when direct push and pull are not
675 675 available or when exporting an entire repository is undesirable.
676 676
677 677 Applying bundles preserves all changeset contents including
678 678 permissions, copy/rename information, and revision history.
679 679
680 680 Returns 0 on success, 1 if no changes found.
681 681 """
682 682 revs = None
683 683 if 'rev' in opts:
684 684 revs = cmdutil.revrange(repo, opts['rev'])
685 685
686 686 if opts.get('all'):
687 687 base = ['null']
688 688 else:
689 689 base = cmdutil.revrange(repo, opts.get('base'))
690 690 if base:
691 691 if dest:
692 692 raise util.Abort(_("--base is incompatible with specifying "
693 693 "a destination"))
694 694 base = [repo.lookup(rev) for rev in base]
695 695 # create the right base
696 696 # XXX: nodesbetween / changegroup* should be "fixed" instead
697 697 o = []
698 698 has = set((nullid,))
699 699 for n in base:
700 700 has.update(repo.changelog.reachable(n))
701 701 if revs:
702 702 revs = [repo.lookup(rev) for rev in revs]
703 703 visit = revs[:]
704 704 has.difference_update(visit)
705 705 else:
706 706 visit = repo.changelog.heads()
707 707 seen = {}
708 708 while visit:
709 709 n = visit.pop(0)
710 710 parents = [p for p in repo.changelog.parents(n) if p not in has]
711 711 if len(parents) == 0:
712 712 if n not in has:
713 713 o.append(n)
714 714 else:
715 715 for p in parents:
716 716 if p not in seen:
717 717 seen[p] = 1
718 718 visit.append(p)
719 719 else:
720 720 dest = ui.expandpath(dest or 'default-push', dest or 'default')
721 721 dest, branches = hg.parseurl(dest, opts.get('branch'))
722 722 other = hg.repository(hg.remoteui(repo, opts), dest)
723 723 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
724 724 if revs:
725 725 revs = [repo.lookup(rev) for rev in revs]
726 726 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
727 727
728 728 if not o:
729 729 ui.status(_("no changes found\n"))
730 730 return 1
731 731
732 732 if revs:
733 733 cg = repo.changegroupsubset(o, revs, 'bundle')
734 734 else:
735 735 cg = repo.changegroup(o, 'bundle')
736 736
737 737 bundletype = opts.get('type', 'bzip2').lower()
738 738 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
739 739 bundletype = btypes.get(bundletype)
740 740 if bundletype not in changegroup.bundletypes:
741 741 raise util.Abort(_('unknown bundle type specified with --type'))
742 742
743 743 changegroup.writebundle(cg, fname, bundletype)
744 744
745 745 def cat(ui, repo, file1, *pats, **opts):
746 746 """output the current or given revision of files
747 747
748 748 Print the specified files as they were at the given revision. If
749 749 no revision is given, the parent of the working directory is used,
750 750 or tip if no revision is checked out.
751 751
752 752 Output may be to a file, in which case the name of the file is
753 753 given using a format string. The formatting rules are the same as
754 754 for the export command, with the following additions:
755 755
756 756 :``%s``: basename of file being printed
757 757 :``%d``: dirname of file being printed, or '.' if in repository root
758 758 :``%p``: root-relative path name of file being printed
759 759
760 760 Returns 0 on success.
761 761 """
762 762 ctx = cmdutil.revsingle(repo, opts.get('rev'))
763 763 err = 1
764 764 m = cmdutil.match(repo, (file1,) + pats, opts)
765 765 for abs in ctx.walk(m):
766 766 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
767 767 data = ctx[abs].data()
768 768 if opts.get('decode'):
769 769 data = repo.wwritedata(abs, data)
770 770 fp.write(data)
771 771 fp.close()
772 772 err = 0
773 773 return err
774 774
775 775 def clone(ui, source, dest=None, **opts):
776 776 """make a copy of an existing repository
777 777
778 778 Create a copy of an existing repository in a new directory.
779 779
780 780 If no destination directory name is specified, it defaults to the
781 781 basename of the source.
782 782
783 783 The location of the source is added to the new repository's
784 784 ``.hg/hgrc`` file, as the default to be used for future pulls.
785 785
786 786 See :hg:`help urls` for valid source format details.
787 787
788 788 It is possible to specify an ``ssh://`` URL as the destination, but no
789 789 ``.hg/hgrc`` and working directory will be created on the remote side.
790 790 Please see :hg:`help urls` for important details about ``ssh://`` URLs.
791 791
792 792 A set of changesets (tags, or branch names) to pull may be specified
793 793 by listing each changeset (tag, or branch name) with -r/--rev.
794 794 If -r/--rev is used, the cloned repository will contain only a subset
795 795 of the changesets of the source repository. Only the set of changesets
796 796 defined by all -r/--rev options (including all their ancestors)
797 797 will be pulled into the destination repository.
798 798 No subsequent changesets (including subsequent tags) will be present
799 799 in the destination.
800 800
801 801 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
802 802 local source repositories.
803 803
804 804 For efficiency, hardlinks are used for cloning whenever the source
805 805 and destination are on the same filesystem (note this applies only
806 806 to the repository data, not to the working directory). Some
807 807 filesystems, such as AFS, implement hardlinking incorrectly, but
808 808 do not report errors. In these cases, use the --pull option to
809 809 avoid hardlinking.
810 810
811 811 In some cases, you can clone repositories and the working directory
812 812 using full hardlinks with ::
813 813
814 814 $ cp -al REPO REPOCLONE
815 815
816 816 This is the fastest way to clone, but it is not always safe. The
817 817 operation is not atomic (making sure REPO is not modified during
818 818 the operation is up to you) and you have to make sure your editor
819 819 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
820 820 this is not compatible with certain extensions that place their
821 821 metadata under the .hg directory, such as mq.
822 822
823 823 Mercurial will update the working directory to the first applicable
824 824 revision from this list:
825 825
826 826 a) null if -U or the source repository has no changesets
827 827 b) if -u . and the source repository is local, the first parent of
828 828 the source repository's working directory
829 829 c) the changeset specified with -u (if a branch name, this means the
830 830 latest head of that branch)
831 831 d) the changeset specified with -r
832 832 e) the tipmost head specified with -b
833 833 f) the tipmost head specified with the url#branch source syntax
834 834 g) the tipmost head of the default branch
835 835 h) tip
836 836
837 837 Returns 0 on success.
838 838 """
839 839 if opts.get('noupdate') and opts.get('updaterev'):
840 840 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
841 841
842 842 r = hg.clone(hg.remoteui(ui, opts), source, dest,
843 843 pull=opts.get('pull'),
844 844 stream=opts.get('uncompressed'),
845 845 rev=opts.get('rev'),
846 846 update=opts.get('updaterev') or not opts.get('noupdate'),
847 847 branch=opts.get('branch'))
848 848
849 849 return r is None
850 850
851 851 def commit(ui, repo, *pats, **opts):
852 852 """commit the specified files or all outstanding changes
853 853
854 854 Commit changes to the given files into the repository. Unlike a
855 855 centralized SCM, this operation is a local operation. See
856 856 :hg:`push` for a way to actively distribute your changes.
857 857
858 858 If a list of files is omitted, all changes reported by :hg:`status`
859 859 will be committed.
860 860
861 861 If you are committing the result of a merge, do not provide any
862 862 filenames or -I/-X filters.
863 863
864 864 If no commit message is specified, Mercurial starts your
865 865 configured editor where you can enter a message. In case your
866 866 commit fails, you will find a backup of your message in
867 867 ``.hg/last-message.txt``.
868 868
869 869 See :hg:`help dates` for a list of formats valid for -d/--date.
870 870
871 871 Returns 0 on success, 1 if nothing changed.
872 872 """
873 873 extra = {}
874 874 if opts.get('close_branch'):
875 875 if repo['.'].node() not in repo.branchheads():
876 876 # The topo heads set is included in the branch heads set of the
877 877 # current branch, so it's sufficient to test branchheads
878 878 raise util.Abort(_('can only close branch heads'))
879 879 extra['close'] = 1
880 880 e = cmdutil.commiteditor
881 881 if opts.get('force_editor'):
882 882 e = cmdutil.commitforceeditor
883 883
884 884 def commitfunc(ui, repo, message, match, opts):
885 885 return repo.commit(message, opts.get('user'), opts.get('date'), match,
886 886 editor=e, extra=extra)
887 887
888 888 branch = repo[None].branch()
889 889 bheads = repo.branchheads(branch)
890 890
891 891 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
892 892 if not node:
893 893 ui.status(_("nothing changed\n"))
894 894 return 1
895 895
896 896 ctx = repo[node]
897 897 parents = ctx.parents()
898 898
899 899 if bheads and not [x for x in parents
900 900 if x.node() in bheads and x.branch() == branch]:
901 901 ui.status(_('created new head\n'))
902 902 # The message is not printed for initial roots. For the other
903 903 # changesets, it is printed in the following situations:
904 904 #
905 905 # Par column: for the 2 parents with ...
906 906 # N: null or no parent
907 907 # B: parent is on another named branch
908 908 # C: parent is a regular non head changeset
909 909 # H: parent was a branch head of the current branch
910 910 # Msg column: whether we print "created new head" message
911 911 # In the following, it is assumed that there already exists some
912 912 # initial branch heads of the current branch, otherwise nothing is
913 913 # printed anyway.
914 914 #
915 915 # Par Msg Comment
916 916 # NN y additional topo root
917 917 #
918 918 # BN y additional branch root
919 919 # CN y additional topo head
920 920 # HN n usual case
921 921 #
922 922 # BB y weird additional branch root
923 923 # CB y branch merge
924 924 # HB n merge with named branch
925 925 #
926 926 # CC y additional head from merge
927 927 # CH n merge with a head
928 928 #
929 929 # HH n head merge: head count decreases
930 930
931 931 if not opts.get('close_branch'):
932 932 for r in parents:
933 933 if r.extra().get('close') and r.branch() == branch:
934 934 ui.status(_('reopening closed branch head %d\n') % r)
935 935
936 936 if ui.debugflag:
937 937 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
938 938 elif ui.verbose:
939 939 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
940 940
941 941 def copy(ui, repo, *pats, **opts):
942 942 """mark files as copied for the next commit
943 943
944 944 Mark dest as having copies of source files. If dest is a
945 945 directory, copies are put in that directory. If dest is a file,
946 946 the source must be a single file.
947 947
948 948 By default, this command copies the contents of files as they
949 949 exist in the working directory. If invoked with -A/--after, the
950 950 operation is recorded, but no copying is performed.
951 951
952 952 This command takes effect with the next commit. To undo a copy
953 953 before that, see :hg:`revert`.
954 954
955 955 Returns 0 on success, 1 if errors are encountered.
956 956 """
957 957 wlock = repo.wlock(False)
958 958 try:
959 959 return cmdutil.copy(ui, repo, pats, opts)
960 960 finally:
961 961 wlock.release()
962 962
963 963 def debugancestor(ui, repo, *args):
964 964 """find the ancestor revision of two revisions in a given index"""
965 965 if len(args) == 3:
966 966 index, rev1, rev2 = args
967 967 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
968 968 lookup = r.lookup
969 969 elif len(args) == 2:
970 970 if not repo:
971 971 raise util.Abort(_("there is no Mercurial repository here "
972 972 "(.hg not found)"))
973 973 rev1, rev2 = args
974 974 r = repo.changelog
975 975 lookup = repo.lookup
976 976 else:
977 977 raise util.Abort(_('either two or three arguments required'))
978 978 a = r.ancestor(lookup(rev1), lookup(rev2))
979 979 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
980 980
981 981 def debugbuilddag(ui, repo, text,
982 982 mergeable_file=False,
983 983 appended_file=False,
984 984 overwritten_file=False,
985 985 new_file=False):
986 986 """builds a repo with a given dag from scratch in the current empty repo
987 987
988 988 Elements:
989 989
990 990 - "+n" is a linear run of n nodes based on the current default parent
991 991 - "." is a single node based on the current default parent
992 992 - "$" resets the default parent to null (implied at the start);
993 993 otherwise the default parent is always the last node created
994 994 - "<p" sets the default parent to the backref p
995 995 - "*p" is a fork at parent p, which is a backref
996 996 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
997 997 - "/p2" is a merge of the preceding node and p2
998 998 - ":tag" defines a local tag for the preceding node
999 999 - "@branch" sets the named branch for subsequent nodes
1000 1000 - "!command" runs the command using your shell
1001 1001 - "!!my command\\n" is like "!", but to the end of the line
1002 1002 - "#...\\n" is a comment up to the end of the line
1003 1003
1004 1004 Whitespace between the above elements is ignored.
1005 1005
1006 1006 A backref is either
1007 1007
1008 1008 - a number n, which references the node curr-n, where curr is the current
1009 1009 node, or
1010 1010 - the name of a local tag you placed earlier using ":tag", or
1011 1011 - empty to denote the default parent.
1012 1012
1013 1013 All string valued-elements are either strictly alphanumeric, or must
1014 1014 be enclosed in double quotes ("..."), with "\\" as escape character.
1015 1015
1016 1016 Note that the --overwritten-file and --appended-file options imply the
1017 1017 use of "HGMERGE=internal:local" during DAG buildup.
1018 1018 """
1019 1019
1020 1020 if not (mergeable_file or appended_file or overwritten_file or new_file):
1021 1021 raise util.Abort(_('need at least one of -m, -a, -o, -n'))
1022 1022
1023 1023 if len(repo.changelog) > 0:
1024 1024 raise util.Abort(_('repository is not empty'))
1025 1025
1026 1026 if overwritten_file or appended_file:
1027 1027 # we don't want to fail in merges during buildup
1028 1028 os.environ['HGMERGE'] = 'internal:local'
1029 1029
1030 1030 def writefile(fname, text, fmode="wb"):
1031 1031 f = open(fname, fmode)
1032 1032 try:
1033 1033 f.write(text)
1034 1034 finally:
1035 1035 f.close()
1036 1036
1037 1037 if mergeable_file:
1038 1038 linesperrev = 2
1039 1039 # determine number of revs in DAG
1040 1040 n = 0
1041 1041 for type, data in dagparser.parsedag(text):
1042 1042 if type == 'n':
1043 1043 n += 1
1044 1044 # make a file with k lines per rev
1045 1045 writefile("mf", "\n".join(str(i) for i in xrange(0, n * linesperrev))
1046 1046 + "\n")
1047 1047
1048 1048 at = -1
1049 1049 atbranch = 'default'
1050 1050 for type, data in dagparser.parsedag(text):
1051 1051 if type == 'n':
1052 1052 ui.status('node %s\n' % str(data))
1053 1053 id, ps = data
1054 1054 p1 = ps[0]
1055 1055 if p1 != at:
1056 1056 update(ui, repo, node=str(p1), clean=True)
1057 1057 at = p1
1058 1058 if repo.dirstate.branch() != atbranch:
1059 1059 branch(ui, repo, atbranch, force=True)
1060 1060 if len(ps) > 1:
1061 1061 p2 = ps[1]
1062 1062 merge(ui, repo, node=p2)
1063 1063
1064 1064 if mergeable_file:
1065 1065 f = open("mf", "rb+")
1066 1066 try:
1067 1067 lines = f.read().split("\n")
1068 1068 lines[id * linesperrev] += " r%i" % id
1069 1069 f.seek(0)
1070 1070 f.write("\n".join(lines))
1071 1071 finally:
1072 1072 f.close()
1073 1073
1074 1074 if appended_file:
1075 1075 writefile("af", "r%i\n" % id, "ab")
1076 1076
1077 1077 if overwritten_file:
1078 1078 writefile("of", "r%i\n" % id)
1079 1079
1080 1080 if new_file:
1081 1081 writefile("nf%i" % id, "r%i\n" % id)
1082 1082
1083 1083 commit(ui, repo, addremove=True, message="r%i" % id, date=(id, 0))
1084 1084 at = id
1085 1085 elif type == 'l':
1086 1086 id, name = data
1087 1087 ui.status('tag %s\n' % name)
1088 1088 tag(ui, repo, name, local=True)
1089 1089 elif type == 'a':
1090 1090 ui.status('branch %s\n' % data)
1091 1091 atbranch = data
1092 1092 elif type in 'cC':
1093 1093 r = util.system(data, cwd=repo.root)
1094 1094 if r:
1095 1095 desc, r = util.explain_exit(r)
1096 1096 raise util.Abort(_('%s command %s') % (data, desc))
1097 1097
1098 1098 def debugcommands(ui, cmd='', *args):
1099 1099 """list all available commands and options"""
1100 1100 for cmd, vals in sorted(table.iteritems()):
1101 1101 cmd = cmd.split('|')[0].strip('^')
1102 1102 opts = ', '.join([i[1] for i in vals[1]])
1103 1103 ui.write('%s: %s\n' % (cmd, opts))
1104 1104
1105 1105 def debugcomplete(ui, cmd='', **opts):
1106 1106 """returns the completion list associated with the given command"""
1107 1107
1108 1108 if opts.get('options'):
1109 1109 options = []
1110 1110 otables = [globalopts]
1111 1111 if cmd:
1112 1112 aliases, entry = cmdutil.findcmd(cmd, table, False)
1113 1113 otables.append(entry[1])
1114 1114 for t in otables:
1115 1115 for o in t:
1116 1116 if "(DEPRECATED)" in o[3]:
1117 1117 continue
1118 1118 if o[0]:
1119 1119 options.append('-%s' % o[0])
1120 1120 options.append('--%s' % o[1])
1121 1121 ui.write("%s\n" % "\n".join(options))
1122 1122 return
1123 1123
1124 1124 cmdlist = cmdutil.findpossible(cmd, table)
1125 1125 if ui.verbose:
1126 1126 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1127 1127 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1128 1128
1129 1129 def debugfsinfo(ui, path = "."):
1130 1130 """show information detected about current filesystem"""
1131 1131 open('.debugfsinfo', 'w').write('')
1132 1132 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1133 1133 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1134 1134 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1135 1135 and 'yes' or 'no'))
1136 1136 os.unlink('.debugfsinfo')
1137 1137
1138 1138 def debugrebuildstate(ui, repo, rev="tip"):
1139 1139 """rebuild the dirstate as it would look like for the given revision"""
1140 1140 ctx = cmdutil.revsingle(repo, rev)
1141 1141 wlock = repo.wlock()
1142 1142 try:
1143 1143 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1144 1144 finally:
1145 1145 wlock.release()
1146 1146
1147 1147 def debugcheckstate(ui, repo):
1148 1148 """validate the correctness of the current dirstate"""
1149 1149 parent1, parent2 = repo.dirstate.parents()
1150 1150 m1 = repo[parent1].manifest()
1151 1151 m2 = repo[parent2].manifest()
1152 1152 errors = 0
1153 1153 for f in repo.dirstate:
1154 1154 state = repo.dirstate[f]
1155 1155 if state in "nr" and f not in m1:
1156 1156 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1157 1157 errors += 1
1158 1158 if state in "a" and f in m1:
1159 1159 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1160 1160 errors += 1
1161 1161 if state in "m" and f not in m1 and f not in m2:
1162 1162 ui.warn(_("%s in state %s, but not in either manifest\n") %
1163 1163 (f, state))
1164 1164 errors += 1
1165 1165 for f in m1:
1166 1166 state = repo.dirstate[f]
1167 1167 if state not in "nrm":
1168 1168 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1169 1169 errors += 1
1170 1170 if errors:
1171 1171 error = _(".hg/dirstate inconsistent with current parent's manifest")
1172 1172 raise util.Abort(error)
1173 1173
1174 1174 def showconfig(ui, repo, *values, **opts):
1175 1175 """show combined config settings from all hgrc files
1176 1176
1177 1177 With no arguments, print names and values of all config items.
1178 1178
1179 1179 With one argument of the form section.name, print just the value
1180 1180 of that config item.
1181 1181
1182 1182 With multiple arguments, print names and values of all config
1183 1183 items with matching section names.
1184 1184
1185 1185 With --debug, the source (filename and line number) is printed
1186 1186 for each config item.
1187 1187
1188 1188 Returns 0 on success.
1189 1189 """
1190 1190
1191 1191 for f in util.rcpath():
1192 1192 ui.debug(_('read config from: %s\n') % f)
1193 1193 untrusted = bool(opts.get('untrusted'))
1194 1194 if values:
1195 1195 sections = [v for v in values if '.' not in v]
1196 1196 items = [v for v in values if '.' in v]
1197 1197 if len(items) > 1 or items and sections:
1198 1198 raise util.Abort(_('only one config item permitted'))
1199 1199 for section, name, value in ui.walkconfig(untrusted=untrusted):
1200 1200 value = str(value).replace('\n', '\\n')
1201 1201 sectname = section + '.' + name
1202 1202 if values:
1203 1203 for v in values:
1204 1204 if v == section:
1205 1205 ui.debug('%s: ' %
1206 1206 ui.configsource(section, name, untrusted))
1207 1207 ui.write('%s=%s\n' % (sectname, value))
1208 1208 elif v == sectname:
1209 1209 ui.debug('%s: ' %
1210 1210 ui.configsource(section, name, untrusted))
1211 1211 ui.write(value, '\n')
1212 1212 else:
1213 1213 ui.debug('%s: ' %
1214 1214 ui.configsource(section, name, untrusted))
1215 1215 ui.write('%s=%s\n' % (sectname, value))
1216 1216
1217 1217 def debugpushkey(ui, repopath, namespace, *keyinfo):
1218 1218 '''access the pushkey key/value protocol
1219 1219
1220 1220 With two args, list the keys in the given namespace.
1221 1221
1222 1222 With five args, set a key to new if it currently is set to old.
1223 1223 Reports success or failure.
1224 1224 '''
1225 1225
1226 1226 target = hg.repository(ui, repopath)
1227 1227 if keyinfo:
1228 1228 key, old, new = keyinfo
1229 1229 r = target.pushkey(namespace, key, old, new)
1230 1230 ui.status(str(r) + '\n')
1231 1231 return not r
1232 1232 else:
1233 1233 for k, v in target.listkeys(namespace).iteritems():
1234 1234 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1235 1235 v.encode('string-escape')))
1236 1236
1237 1237 def debugrevspec(ui, repo, expr):
1238 1238 '''parse and apply a revision specification'''
1239 1239 if ui.verbose:
1240 1240 tree = revset.parse(expr)
1241 1241 ui.note(tree, "\n")
1242 1242 func = revset.match(expr)
1243 1243 for c in func(repo, range(len(repo))):
1244 1244 ui.write("%s\n" % c)
1245 1245
1246 1246 def debugsetparents(ui, repo, rev1, rev2=None):
1247 1247 """manually set the parents of the current working directory
1248 1248
1249 1249 This is useful for writing repository conversion tools, but should
1250 1250 be used with care.
1251 1251
1252 1252 Returns 0 on success.
1253 1253 """
1254 1254
1255 1255 r1 = cmdutil.revsingle(repo, rev1).node()
1256 1256 r2 = cmdutil.revsingle(repo, rev2, 'null').node()
1257 1257
1258 1258 wlock = repo.wlock()
1259 1259 try:
1260 1260 repo.dirstate.setparents(r1, r2)
1261 1261 finally:
1262 1262 wlock.release()
1263 1263
1264 1264 def debugstate(ui, repo, nodates=None):
1265 1265 """show the contents of the current dirstate"""
1266 1266 timestr = ""
1267 1267 showdate = not nodates
1268 1268 for file_, ent in sorted(repo.dirstate._map.iteritems()):
1269 1269 if showdate:
1270 1270 if ent[3] == -1:
1271 1271 # Pad or slice to locale representation
1272 1272 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
1273 1273 time.localtime(0)))
1274 1274 timestr = 'unset'
1275 1275 timestr = (timestr[:locale_len] +
1276 1276 ' ' * (locale_len - len(timestr)))
1277 1277 else:
1278 1278 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
1279 1279 time.localtime(ent[3]))
1280 1280 if ent[1] & 020000:
1281 1281 mode = 'lnk'
1282 1282 else:
1283 1283 mode = '%3o' % (ent[1] & 0777)
1284 1284 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
1285 1285 for f in repo.dirstate.copies():
1286 1286 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
1287 1287
1288 1288 def debugsub(ui, repo, rev=None):
1289 1289 ctx = cmdutil.revsingle(repo, rev, None)
1290 1290 for k, v in sorted(ctx.substate.items()):
1291 1291 ui.write('path %s\n' % k)
1292 1292 ui.write(' source %s\n' % v[0])
1293 1293 ui.write(' revision %s\n' % v[1])
1294 1294
1295 1295 def debugdag(ui, repo, file_=None, *revs, **opts):
1296 1296 """format the changelog or an index DAG as a concise textual description
1297 1297
1298 1298 If you pass a revlog index, the revlog's DAG is emitted. If you list
1299 1299 revision numbers, they get labelled in the output as rN.
1300 1300
1301 1301 Otherwise, the changelog DAG of the current repo is emitted.
1302 1302 """
1303 1303 spaces = opts.get('spaces')
1304 1304 dots = opts.get('dots')
1305 1305 if file_:
1306 1306 rlog = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1307 1307 revs = set((int(r) for r in revs))
1308 1308 def events():
1309 1309 for r in rlog:
1310 1310 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1311 1311 if r in revs:
1312 1312 yield 'l', (r, "r%i" % r)
1313 1313 elif repo:
1314 1314 cl = repo.changelog
1315 1315 tags = opts.get('tags')
1316 1316 branches = opts.get('branches')
1317 1317 if tags:
1318 1318 labels = {}
1319 1319 for l, n in repo.tags().items():
1320 1320 labels.setdefault(cl.rev(n), []).append(l)
1321 1321 def events():
1322 1322 b = "default"
1323 1323 for r in cl:
1324 1324 if branches:
1325 1325 newb = cl.read(cl.node(r))[5]['branch']
1326 1326 if newb != b:
1327 1327 yield 'a', newb
1328 1328 b = newb
1329 1329 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1330 1330 if tags:
1331 1331 ls = labels.get(r)
1332 1332 if ls:
1333 1333 for l in ls:
1334 1334 yield 'l', (r, l)
1335 1335 else:
1336 1336 raise util.Abort(_('need repo for changelog dag'))
1337 1337
1338 1338 for line in dagparser.dagtextlines(events(),
1339 1339 addspaces=spaces,
1340 1340 wraplabels=True,
1341 1341 wrapannotations=True,
1342 1342 wrapnonlinear=dots,
1343 1343 usedots=dots,
1344 1344 maxlinewidth=70):
1345 1345 ui.write(line)
1346 1346 ui.write("\n")
1347 1347
1348 1348 def debugdata(ui, repo, file_, rev):
1349 1349 """dump the contents of a data file revision"""
1350 1350 r = None
1351 1351 if repo:
1352 1352 filelog = repo.file(file_)
1353 1353 if len(filelog):
1354 1354 r = filelog
1355 1355 if not r:
1356 1356 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
1357 1357 try:
1358 1358 ui.write(r.revision(r.lookup(rev)))
1359 1359 except KeyError:
1360 1360 raise util.Abort(_('invalid revision identifier %s') % rev)
1361 1361
1362 1362 def debugdate(ui, date, range=None, **opts):
1363 1363 """parse and display a date"""
1364 1364 if opts["extended"]:
1365 1365 d = util.parsedate(date, util.extendeddateformats)
1366 1366 else:
1367 1367 d = util.parsedate(date)
1368 1368 ui.write("internal: %s %s\n" % d)
1369 1369 ui.write("standard: %s\n" % util.datestr(d))
1370 1370 if range:
1371 1371 m = util.matchdate(range)
1372 1372 ui.write("match: %s\n" % m(d[0]))
1373 1373
1374 1374 def debugignore(ui, repo, *values, **opts):
1375 1375 """display the combined ignore pattern"""
1376 1376 ignore = repo.dirstate._ignore
1377 1377 if hasattr(ignore, 'includepat'):
1378 1378 ui.write("%s\n" % ignore.includepat)
1379 1379 else:
1380 1380 raise util.Abort(_("no ignore patterns found"))
1381 1381
1382 1382 def debugindex(ui, repo, file_, **opts):
1383 1383 """dump the contents of an index file"""
1384 1384 r = None
1385 1385 if repo:
1386 1386 filelog = repo.file(file_)
1387 1387 if len(filelog):
1388 1388 r = filelog
1389 1389
1390 1390 format = opts.get('format', 0)
1391 1391 if format not in (0, 1):
1392 1392 raise util.Abort(_("unknown format %d") % format)
1393 1393
1394 1394 if not r:
1395 1395 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1396 1396
1397 1397 if format == 0:
1398 1398 ui.write(" rev offset length base linkrev"
1399 1399 " nodeid p1 p2\n")
1400 1400 elif format == 1:
1401 1401 ui.write(" rev flag offset length"
1402 1402 " size base link p1 p2 nodeid\n")
1403 1403
1404 1404 for i in r:
1405 1405 node = r.node(i)
1406 1406 if format == 0:
1407 1407 try:
1408 1408 pp = r.parents(node)
1409 1409 except:
1410 1410 pp = [nullid, nullid]
1411 1411 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1412 1412 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
1413 1413 short(node), short(pp[0]), short(pp[1])))
1414 1414 elif format == 1:
1415 1415 pr = r.parentrevs(i)
1416 1416 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1417 1417 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1418 1418 r.base(i), r.linkrev(i), pr[0], pr[1], short(node)))
1419 1419
1420 1420 def debugindexdot(ui, repo, file_):
1421 1421 """dump an index DAG as a graphviz dot file"""
1422 1422 r = None
1423 1423 if repo:
1424 1424 filelog = repo.file(file_)
1425 1425 if len(filelog):
1426 1426 r = filelog
1427 1427 if not r:
1428 1428 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1429 1429 ui.write("digraph G {\n")
1430 1430 for i in r:
1431 1431 node = r.node(i)
1432 1432 pp = r.parents(node)
1433 1433 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1434 1434 if pp[1] != nullid:
1435 1435 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1436 1436 ui.write("}\n")
1437 1437
1438 1438 def debuginstall(ui):
1439 1439 '''test Mercurial installation
1440 1440
1441 1441 Returns 0 on success.
1442 1442 '''
1443 1443
1444 1444 def writetemp(contents):
1445 1445 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1446 1446 f = os.fdopen(fd, "wb")
1447 1447 f.write(contents)
1448 1448 f.close()
1449 1449 return name
1450 1450
1451 1451 problems = 0
1452 1452
1453 1453 # encoding
1454 1454 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1455 1455 try:
1456 1456 encoding.fromlocal("test")
1457 1457 except util.Abort, inst:
1458 1458 ui.write(" %s\n" % inst)
1459 1459 ui.write(_(" (check that your locale is properly set)\n"))
1460 1460 problems += 1
1461 1461
1462 1462 # compiled modules
1463 1463 ui.status(_("Checking installed modules (%s)...\n")
1464 1464 % os.path.dirname(__file__))
1465 1465 try:
1466 1466 import bdiff, mpatch, base85, osutil
1467 1467 except Exception, inst:
1468 1468 ui.write(" %s\n" % inst)
1469 1469 ui.write(_(" One or more extensions could not be found"))
1470 1470 ui.write(_(" (check that you compiled the extensions)\n"))
1471 1471 problems += 1
1472 1472
1473 1473 # templates
1474 1474 ui.status(_("Checking templates...\n"))
1475 1475 try:
1476 1476 import templater
1477 1477 templater.templater(templater.templatepath("map-cmdline.default"))
1478 1478 except Exception, inst:
1479 1479 ui.write(" %s\n" % inst)
1480 1480 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1481 1481 problems += 1
1482 1482
1483 1483 # patch
1484 1484 ui.status(_("Checking patch...\n"))
1485 1485 patchproblems = 0
1486 1486 a = "1\n2\n3\n4\n"
1487 1487 b = "1\n2\n3\ninsert\n4\n"
1488 1488 fa = writetemp(a)
1489 1489 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
1490 1490 os.path.basename(fa))
1491 1491 fd = writetemp(d)
1492 1492
1493 1493 files = {}
1494 1494 try:
1495 1495 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
1496 1496 except util.Abort, e:
1497 1497 ui.write(_(" patch call failed:\n"))
1498 1498 ui.write(" " + str(e) + "\n")
1499 1499 patchproblems += 1
1500 1500 else:
1501 1501 if list(files) != [os.path.basename(fa)]:
1502 1502 ui.write(_(" unexpected patch output!\n"))
1503 1503 patchproblems += 1
1504 1504 a = open(fa).read()
1505 1505 if a != b:
1506 1506 ui.write(_(" patch test failed!\n"))
1507 1507 patchproblems += 1
1508 1508
1509 1509 if patchproblems:
1510 1510 if ui.config('ui', 'patch'):
1511 1511 ui.write(_(" (Current patch tool may be incompatible with patch,"
1512 1512 " or misconfigured. Please check your configuration"
1513 1513 " file)\n"))
1514 1514 else:
1515 1515 ui.write(_(" Internal patcher failure, please report this error"
1516 1516 " to http://mercurial.selenic.com/wiki/BugTracker\n"))
1517 1517 problems += patchproblems
1518 1518
1519 1519 os.unlink(fa)
1520 1520 os.unlink(fd)
1521 1521
1522 1522 # editor
1523 1523 ui.status(_("Checking commit editor...\n"))
1524 1524 editor = ui.geteditor()
1525 1525 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
1526 1526 if not cmdpath:
1527 1527 if editor == 'vi':
1528 1528 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1529 1529 ui.write(_(" (specify a commit editor in your configuration"
1530 1530 " file)\n"))
1531 1531 else:
1532 1532 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1533 1533 ui.write(_(" (specify a commit editor in your configuration"
1534 1534 " file)\n"))
1535 1535 problems += 1
1536 1536
1537 1537 # check username
1538 1538 ui.status(_("Checking username...\n"))
1539 1539 try:
1540 1540 ui.username()
1541 1541 except util.Abort, e:
1542 1542 ui.write(" %s\n" % e)
1543 1543 ui.write(_(" (specify a username in your configuration file)\n"))
1544 1544 problems += 1
1545 1545
1546 1546 if not problems:
1547 1547 ui.status(_("No problems detected\n"))
1548 1548 else:
1549 1549 ui.write(_("%s problems detected,"
1550 1550 " please check your install!\n") % problems)
1551 1551
1552 1552 return problems
1553 1553
1554 1554 def debugrename(ui, repo, file1, *pats, **opts):
1555 1555 """dump rename information"""
1556 1556
1557 1557 ctx = cmdutil.revsingle(repo, opts.get('rev'))
1558 1558 m = cmdutil.match(repo, (file1,) + pats, opts)
1559 1559 for abs in ctx.walk(m):
1560 1560 fctx = ctx[abs]
1561 1561 o = fctx.filelog().renamed(fctx.filenode())
1562 1562 rel = m.rel(abs)
1563 1563 if o:
1564 1564 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1565 1565 else:
1566 1566 ui.write(_("%s not renamed\n") % rel)
1567 1567
1568 1568 def debugwalk(ui, repo, *pats, **opts):
1569 1569 """show how files match on given patterns"""
1570 1570 m = cmdutil.match(repo, pats, opts)
1571 1571 items = list(repo.walk(m))
1572 1572 if not items:
1573 1573 return
1574 1574 fmt = 'f %%-%ds %%-%ds %%s' % (
1575 1575 max([len(abs) for abs in items]),
1576 1576 max([len(m.rel(abs)) for abs in items]))
1577 1577 for abs in items:
1578 1578 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1579 1579 ui.write("%s\n" % line.rstrip())
1580 1580
1581 1581 def diff(ui, repo, *pats, **opts):
1582 1582 """diff repository (or selected files)
1583 1583
1584 1584 Show differences between revisions for the specified files.
1585 1585
1586 1586 Differences between files are shown using the unified diff format.
1587 1587
1588 1588 .. note::
1589 1589 diff may generate unexpected results for merges, as it will
1590 1590 default to comparing against the working directory's first
1591 1591 parent changeset if no revisions are specified.
1592 1592
1593 1593 When two revision arguments are given, then changes are shown
1594 1594 between those revisions. If only one revision is specified then
1595 1595 that revision is compared to the working directory, and, when no
1596 1596 revisions are specified, the working directory files are compared
1597 1597 to its parent.
1598 1598
1599 1599 Alternatively you can specify -c/--change with a revision to see
1600 1600 the changes in that changeset relative to its first parent.
1601 1601
1602 1602 Without the -a/--text option, diff will avoid generating diffs of
1603 1603 files it detects as binary. With -a, diff will generate a diff
1604 1604 anyway, probably with undesirable results.
1605 1605
1606 1606 Use the -g/--git option to generate diffs in the git extended diff
1607 1607 format. For more information, read :hg:`help diffs`.
1608 1608
1609 1609 Returns 0 on success.
1610 1610 """
1611 1611
1612 1612 revs = opts.get('rev')
1613 1613 change = opts.get('change')
1614 1614 stat = opts.get('stat')
1615 1615 reverse = opts.get('reverse')
1616 1616
1617 1617 if revs and change:
1618 1618 msg = _('cannot specify --rev and --change at the same time')
1619 1619 raise util.Abort(msg)
1620 1620 elif change:
1621 1621 node2 = cmdutil.revsingle(repo, change, None).node()
1622 1622 node1 = repo[node2].parents()[0].node()
1623 1623 else:
1624 1624 node1, node2 = cmdutil.revpair(repo, revs)
1625 1625
1626 1626 if reverse:
1627 1627 node1, node2 = node2, node1
1628 1628
1629 1629 diffopts = patch.diffopts(ui, opts)
1630 1630 m = cmdutil.match(repo, pats, opts)
1631 1631 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1632 1632 listsubrepos=opts.get('subrepos'))
1633 1633
1634 1634 def export(ui, repo, *changesets, **opts):
1635 1635 """dump the header and diffs for one or more changesets
1636 1636
1637 1637 Print the changeset header and diffs for one or more revisions.
1638 1638
1639 1639 The information shown in the changeset header is: author, date,
1640 1640 branch name (if non-default), changeset hash, parent(s) and commit
1641 1641 comment.
1642 1642
1643 1643 .. note::
1644 1644 export may generate unexpected diff output for merge
1645 1645 changesets, as it will compare the merge changeset against its
1646 1646 first parent only.
1647 1647
1648 1648 Output may be to a file, in which case the name of the file is
1649 1649 given using a format string. The formatting rules are as follows:
1650 1650
1651 1651 :``%%``: literal "%" character
1652 1652 :``%H``: changeset hash (40 hexadecimal digits)
1653 1653 :``%N``: number of patches being generated
1654 1654 :``%R``: changeset revision number
1655 1655 :``%b``: basename of the exporting repository
1656 1656 :``%h``: short-form changeset hash (12 hexadecimal digits)
1657 1657 :``%n``: zero-padded sequence number, starting at 1
1658 1658 :``%r``: zero-padded changeset revision number
1659 1659
1660 1660 Without the -a/--text option, export will avoid generating diffs
1661 1661 of files it detects as binary. With -a, export will generate a
1662 1662 diff anyway, probably with undesirable results.
1663 1663
1664 1664 Use the -g/--git option to generate diffs in the git extended diff
1665 1665 format. See :hg:`help diffs` for more information.
1666 1666
1667 1667 With the --switch-parent option, the diff will be against the
1668 1668 second parent. It can be useful to review a merge.
1669 1669
1670 1670 Returns 0 on success.
1671 1671 """
1672 1672 changesets += tuple(opts.get('rev', []))
1673 1673 if not changesets:
1674 1674 raise util.Abort(_("export requires at least one changeset"))
1675 1675 revs = cmdutil.revrange(repo, changesets)
1676 1676 if len(revs) > 1:
1677 1677 ui.note(_('exporting patches:\n'))
1678 1678 else:
1679 1679 ui.note(_('exporting patch:\n'))
1680 1680 cmdutil.export(repo, revs, template=opts.get('output'),
1681 1681 switch_parent=opts.get('switch_parent'),
1682 1682 opts=patch.diffopts(ui, opts))
1683 1683
1684 1684 def forget(ui, repo, *pats, **opts):
1685 1685 """forget the specified files on the next commit
1686 1686
1687 1687 Mark the specified files so they will no longer be tracked
1688 1688 after the next commit.
1689 1689
1690 1690 This only removes files from the current branch, not from the
1691 1691 entire project history, and it does not delete them from the
1692 1692 working directory.
1693 1693
1694 1694 To undo a forget before the next commit, see :hg:`add`.
1695 1695
1696 1696 Returns 0 on success.
1697 1697 """
1698 1698
1699 1699 if not pats:
1700 1700 raise util.Abort(_('no files specified'))
1701 1701
1702 1702 m = cmdutil.match(repo, pats, opts)
1703 1703 s = repo.status(match=m, clean=True)
1704 1704 forget = sorted(s[0] + s[1] + s[3] + s[6])
1705 1705 errs = 0
1706 1706
1707 1707 for f in m.files():
1708 1708 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1709 1709 ui.warn(_('not removing %s: file is already untracked\n')
1710 1710 % m.rel(f))
1711 1711 errs = 1
1712 1712
1713 1713 for f in forget:
1714 1714 if ui.verbose or not m.exact(f):
1715 1715 ui.status(_('removing %s\n') % m.rel(f))
1716 1716
1717 1717 repo[None].remove(forget, unlink=False)
1718 1718 return errs
1719 1719
1720 1720 def grep(ui, repo, pattern, *pats, **opts):
1721 1721 """search for a pattern in specified files and revisions
1722 1722
1723 1723 Search revisions of files for a regular expression.
1724 1724
1725 1725 This command behaves differently than Unix grep. It only accepts
1726 1726 Python/Perl regexps. It searches repository history, not the
1727 1727 working directory. It always prints the revision number in which a
1728 1728 match appears.
1729 1729
1730 1730 By default, grep only prints output for the first revision of a
1731 1731 file in which it finds a match. To get it to print every revision
1732 1732 that contains a change in match status ("-" for a match that
1733 1733 becomes a non-match, or "+" for a non-match that becomes a match),
1734 1734 use the --all flag.
1735 1735
1736 1736 Returns 0 if a match is found, 1 otherwise.
1737 1737 """
1738 1738 reflags = 0
1739 1739 if opts.get('ignore_case'):
1740 1740 reflags |= re.I
1741 1741 try:
1742 1742 regexp = re.compile(pattern, reflags)
1743 1743 except re.error, inst:
1744 1744 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1745 1745 return 1
1746 1746 sep, eol = ':', '\n'
1747 1747 if opts.get('print0'):
1748 1748 sep = eol = '\0'
1749 1749
1750 1750 getfile = util.lrucachefunc(repo.file)
1751 1751
1752 1752 def matchlines(body):
1753 1753 begin = 0
1754 1754 linenum = 0
1755 1755 while True:
1756 1756 match = regexp.search(body, begin)
1757 1757 if not match:
1758 1758 break
1759 1759 mstart, mend = match.span()
1760 1760 linenum += body.count('\n', begin, mstart) + 1
1761 1761 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1762 1762 begin = body.find('\n', mend) + 1 or len(body)
1763 1763 lend = begin - 1
1764 1764 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1765 1765
1766 1766 class linestate(object):
1767 1767 def __init__(self, line, linenum, colstart, colend):
1768 1768 self.line = line
1769 1769 self.linenum = linenum
1770 1770 self.colstart = colstart
1771 1771 self.colend = colend
1772 1772
1773 1773 def __hash__(self):
1774 1774 return hash((self.linenum, self.line))
1775 1775
1776 1776 def __eq__(self, other):
1777 1777 return self.line == other.line
1778 1778
1779 1779 matches = {}
1780 1780 copies = {}
1781 1781 def grepbody(fn, rev, body):
1782 1782 matches[rev].setdefault(fn, [])
1783 1783 m = matches[rev][fn]
1784 1784 for lnum, cstart, cend, line in matchlines(body):
1785 1785 s = linestate(line, lnum, cstart, cend)
1786 1786 m.append(s)
1787 1787
1788 1788 def difflinestates(a, b):
1789 1789 sm = difflib.SequenceMatcher(None, a, b)
1790 1790 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1791 1791 if tag == 'insert':
1792 1792 for i in xrange(blo, bhi):
1793 1793 yield ('+', b[i])
1794 1794 elif tag == 'delete':
1795 1795 for i in xrange(alo, ahi):
1796 1796 yield ('-', a[i])
1797 1797 elif tag == 'replace':
1798 1798 for i in xrange(alo, ahi):
1799 1799 yield ('-', a[i])
1800 1800 for i in xrange(blo, bhi):
1801 1801 yield ('+', b[i])
1802 1802
1803 1803 def display(fn, ctx, pstates, states):
1804 1804 rev = ctx.rev()
1805 1805 datefunc = ui.quiet and util.shortdate or util.datestr
1806 1806 found = False
1807 1807 filerevmatches = {}
1808 1808 if opts.get('all'):
1809 1809 iter = difflinestates(pstates, states)
1810 1810 else:
1811 1811 iter = [('', l) for l in states]
1812 1812 for change, l in iter:
1813 1813 cols = [fn, str(rev)]
1814 1814 before, match, after = None, None, None
1815 1815 if opts.get('line_number'):
1816 1816 cols.append(str(l.linenum))
1817 1817 if opts.get('all'):
1818 1818 cols.append(change)
1819 1819 if opts.get('user'):
1820 1820 cols.append(ui.shortuser(ctx.user()))
1821 1821 if opts.get('date'):
1822 1822 cols.append(datefunc(ctx.date()))
1823 1823 if opts.get('files_with_matches'):
1824 1824 c = (fn, rev)
1825 1825 if c in filerevmatches:
1826 1826 continue
1827 1827 filerevmatches[c] = 1
1828 1828 else:
1829 1829 before = l.line[:l.colstart]
1830 1830 match = l.line[l.colstart:l.colend]
1831 1831 after = l.line[l.colend:]
1832 1832 ui.write(sep.join(cols))
1833 1833 if before is not None:
1834 1834 ui.write(sep + before)
1835 1835 ui.write(match, label='grep.match')
1836 1836 ui.write(after)
1837 1837 ui.write(eol)
1838 1838 found = True
1839 1839 return found
1840 1840
1841 1841 skip = {}
1842 1842 revfiles = {}
1843 1843 matchfn = cmdutil.match(repo, pats, opts)
1844 1844 found = False
1845 1845 follow = opts.get('follow')
1846 1846
1847 1847 def prep(ctx, fns):
1848 1848 rev = ctx.rev()
1849 1849 pctx = ctx.parents()[0]
1850 1850 parent = pctx.rev()
1851 1851 matches.setdefault(rev, {})
1852 1852 matches.setdefault(parent, {})
1853 1853 files = revfiles.setdefault(rev, [])
1854 1854 for fn in fns:
1855 1855 flog = getfile(fn)
1856 1856 try:
1857 1857 fnode = ctx.filenode(fn)
1858 1858 except error.LookupError:
1859 1859 continue
1860 1860
1861 1861 copied = flog.renamed(fnode)
1862 1862 copy = follow and copied and copied[0]
1863 1863 if copy:
1864 1864 copies.setdefault(rev, {})[fn] = copy
1865 1865 if fn in skip:
1866 1866 if copy:
1867 1867 skip[copy] = True
1868 1868 continue
1869 1869 files.append(fn)
1870 1870
1871 1871 if fn not in matches[rev]:
1872 1872 grepbody(fn, rev, flog.read(fnode))
1873 1873
1874 1874 pfn = copy or fn
1875 1875 if pfn not in matches[parent]:
1876 1876 try:
1877 1877 fnode = pctx.filenode(pfn)
1878 1878 grepbody(pfn, parent, flog.read(fnode))
1879 1879 except error.LookupError:
1880 1880 pass
1881 1881
1882 1882 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
1883 1883 rev = ctx.rev()
1884 1884 parent = ctx.parents()[0].rev()
1885 1885 for fn in sorted(revfiles.get(rev, [])):
1886 1886 states = matches[rev][fn]
1887 1887 copy = copies.get(rev, {}).get(fn)
1888 1888 if fn in skip:
1889 1889 if copy:
1890 1890 skip[copy] = True
1891 1891 continue
1892 1892 pstates = matches.get(parent, {}).get(copy or fn, [])
1893 1893 if pstates or states:
1894 1894 r = display(fn, ctx, pstates, states)
1895 1895 found = found or r
1896 1896 if r and not opts.get('all'):
1897 1897 skip[fn] = True
1898 1898 if copy:
1899 1899 skip[copy] = True
1900 1900 del matches[rev]
1901 1901 del revfiles[rev]
1902 1902
1903 1903 return not found
1904 1904
1905 1905 def heads(ui, repo, *branchrevs, **opts):
1906 1906 """show current repository heads or show branch heads
1907 1907
1908 1908 With no arguments, show all repository branch heads.
1909 1909
1910 1910 Repository "heads" are changesets with no child changesets. They are
1911 1911 where development generally takes place and are the usual targets
1912 1912 for update and merge operations. Branch heads are changesets that have
1913 1913 no child changeset on the same branch.
1914 1914
1915 1915 If one or more REVs are given, only branch heads on the branches
1916 1916 associated with the specified changesets are shown.
1917 1917
1918 1918 If -c/--closed is specified, also show branch heads marked closed
1919 1919 (see :hg:`commit --close-branch`).
1920 1920
1921 1921 If STARTREV is specified, only those heads that are descendants of
1922 1922 STARTREV will be displayed.
1923 1923
1924 1924 If -t/--topo is specified, named branch mechanics will be ignored and only
1925 1925 changesets without children will be shown.
1926 1926
1927 1927 Returns 0 if matching heads are found, 1 if not.
1928 1928 """
1929 1929
1930 1930 start = None
1931 1931 if 'rev' in opts:
1932 1932 start = cmdutil.revsingle(repo, opts['rev'], None).node()
1933 1933
1934 1934 if opts.get('topo'):
1935 1935 heads = [repo[h] for h in repo.heads(start)]
1936 1936 else:
1937 1937 heads = []
1938 1938 for b, ls in repo.branchmap().iteritems():
1939 1939 if start is None:
1940 1940 heads += [repo[h] for h in ls]
1941 1941 continue
1942 1942 startrev = repo.changelog.rev(start)
1943 1943 descendants = set(repo.changelog.descendants(startrev))
1944 1944 descendants.add(startrev)
1945 1945 rev = repo.changelog.rev
1946 1946 heads += [repo[h] for h in ls if rev(h) in descendants]
1947 1947
1948 1948 if branchrevs:
1949 1949 branches = set(repo[br].branch() for br in branchrevs)
1950 1950 heads = [h for h in heads if h.branch() in branches]
1951 1951
1952 1952 if not opts.get('closed'):
1953 1953 heads = [h for h in heads if not h.extra().get('close')]
1954 1954
1955 1955 if opts.get('active') and branchrevs:
1956 1956 dagheads = repo.heads(start)
1957 1957 heads = [h for h in heads if h.node() in dagheads]
1958 1958
1959 1959 if branchrevs:
1960 1960 haveheads = set(h.branch() for h in heads)
1961 1961 if branches - haveheads:
1962 1962 headless = ', '.join(b for b in branches - haveheads)
1963 1963 msg = _('no open branch heads found on branches %s')
1964 1964 if opts.get('rev'):
1965 1965 msg += _(' (started at %s)' % opts['rev'])
1966 1966 ui.warn((msg + '\n') % headless)
1967 1967
1968 1968 if not heads:
1969 1969 return 1
1970 1970
1971 1971 heads = sorted(heads, key=lambda x: -x.rev())
1972 1972 displayer = cmdutil.show_changeset(ui, repo, opts)
1973 1973 for ctx in heads:
1974 1974 displayer.show(ctx)
1975 1975 displayer.close()
1976 1976
1977 1977 def help_(ui, name=None, with_version=False, unknowncmd=False):
1978 1978 """show help for a given topic or a help overview
1979 1979
1980 1980 With no arguments, print a list of commands with short help messages.
1981 1981
1982 1982 Given a topic, extension, or command name, print help for that
1983 1983 topic.
1984 1984
1985 1985 Returns 0 if successful.
1986 1986 """
1987 1987 option_lists = []
1988 1988 textwidth = min(ui.termwidth(), 80) - 2
1989 1989
1990 1990 def addglobalopts(aliases):
1991 1991 if ui.verbose:
1992 1992 option_lists.append((_("global options:"), globalopts))
1993 1993 if name == 'shortlist':
1994 1994 option_lists.append((_('use "hg help" for the full list '
1995 1995 'of commands'), ()))
1996 1996 else:
1997 1997 if name == 'shortlist':
1998 1998 msg = _('use "hg help" for the full list of commands '
1999 1999 'or "hg -v" for details')
2000 2000 elif aliases:
2001 2001 msg = _('use "hg -v help%s" to show builtin aliases and '
2002 2002 'global options') % (name and " " + name or "")
2003 2003 else:
2004 2004 msg = _('use "hg -v help %s" to show global options') % name
2005 2005 option_lists.append((msg, ()))
2006 2006
2007 2007 def helpcmd(name):
2008 2008 if with_version:
2009 2009 version_(ui)
2010 2010 ui.write('\n')
2011 2011
2012 2012 try:
2013 2013 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
2014 2014 except error.AmbiguousCommand, inst:
2015 2015 # py3k fix: except vars can't be used outside the scope of the
2016 2016 # except block, nor can be used inside a lambda. python issue4617
2017 2017 prefix = inst.args[0]
2018 2018 select = lambda c: c.lstrip('^').startswith(prefix)
2019 2019 helplist(_('list of commands:\n\n'), select)
2020 2020 return
2021 2021
2022 2022 # check if it's an invalid alias and display its error if it is
2023 2023 if getattr(entry[0], 'badalias', False):
2024 2024 if not unknowncmd:
2025 2025 entry[0](ui)
2026 2026 return
2027 2027
2028 2028 # synopsis
2029 2029 if len(entry) > 2:
2030 2030 if entry[2].startswith('hg'):
2031 2031 ui.write("%s\n" % entry[2])
2032 2032 else:
2033 2033 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
2034 2034 else:
2035 2035 ui.write('hg %s\n' % aliases[0])
2036 2036
2037 2037 # aliases
2038 2038 if not ui.quiet and len(aliases) > 1:
2039 2039 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
2040 2040
2041 2041 # description
2042 2042 doc = gettext(entry[0].__doc__)
2043 2043 if not doc:
2044 2044 doc = _("(no help text available)")
2045 2045 if hasattr(entry[0], 'definition'): # aliased command
2046 2046 if entry[0].definition.startswith('!'): # shell alias
2047 2047 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
2048 2048 else:
2049 2049 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
2050 2050 if ui.quiet:
2051 2051 doc = doc.splitlines()[0]
2052 2052 keep = ui.verbose and ['verbose'] or []
2053 2053 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
2054 2054 ui.write("\n%s\n" % formatted)
2055 2055 if pruned:
2056 2056 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
2057 2057
2058 2058 if not ui.quiet:
2059 2059 # options
2060 2060 if entry[1]:
2061 2061 option_lists.append((_("options:\n"), entry[1]))
2062 2062
2063 2063 addglobalopts(False)
2064 2064
2065 2065 def helplist(header, select=None):
2066 2066 h = {}
2067 2067 cmds = {}
2068 2068 for c, e in table.iteritems():
2069 2069 f = c.split("|", 1)[0]
2070 2070 if select and not select(f):
2071 2071 continue
2072 2072 if (not select and name != 'shortlist' and
2073 2073 e[0].__module__ != __name__):
2074 2074 continue
2075 2075 if name == "shortlist" and not f.startswith("^"):
2076 2076 continue
2077 2077 f = f.lstrip("^")
2078 2078 if not ui.debugflag and f.startswith("debug"):
2079 2079 continue
2080 2080 doc = e[0].__doc__
2081 2081 if doc and 'DEPRECATED' in doc and not ui.verbose:
2082 2082 continue
2083 2083 doc = gettext(doc)
2084 2084 if not doc:
2085 2085 doc = _("(no help text available)")
2086 2086 h[f] = doc.splitlines()[0].rstrip()
2087 2087 cmds[f] = c.lstrip("^")
2088 2088
2089 2089 if not h:
2090 2090 ui.status(_('no commands defined\n'))
2091 2091 return
2092 2092
2093 2093 ui.status(header)
2094 2094 fns = sorted(h)
2095 2095 m = max(map(len, fns))
2096 2096 for f in fns:
2097 2097 if ui.verbose:
2098 2098 commands = cmds[f].replace("|",", ")
2099 2099 ui.write(" %s:\n %s\n"%(commands, h[f]))
2100 2100 else:
2101 2101 ui.write('%s\n' % (util.wrap(h[f], textwidth,
2102 2102 initindent=' %-*s ' % (m, f),
2103 2103 hangindent=' ' * (m + 4))))
2104 2104
2105 2105 if not ui.quiet:
2106 2106 addglobalopts(True)
2107 2107
2108 2108 def helptopic(name):
2109 2109 for names, header, doc in help.helptable:
2110 2110 if name in names:
2111 2111 break
2112 2112 else:
2113 2113 raise error.UnknownCommand(name)
2114 2114
2115 2115 # description
2116 2116 if not doc:
2117 2117 doc = _("(no help text available)")
2118 2118 if hasattr(doc, '__call__'):
2119 2119 doc = doc()
2120 2120
2121 2121 ui.write("%s\n\n" % header)
2122 2122 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
2123 2123
2124 2124 def helpext(name):
2125 2125 try:
2126 2126 mod = extensions.find(name)
2127 2127 doc = gettext(mod.__doc__) or _('no help text available')
2128 2128 except KeyError:
2129 2129 mod = None
2130 2130 doc = extensions.disabledext(name)
2131 2131 if not doc:
2132 2132 raise error.UnknownCommand(name)
2133 2133
2134 2134 if '\n' not in doc:
2135 2135 head, tail = doc, ""
2136 2136 else:
2137 2137 head, tail = doc.split('\n', 1)
2138 2138 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
2139 2139 if tail:
2140 2140 ui.write(minirst.format(tail, textwidth))
2141 2141 ui.status('\n\n')
2142 2142
2143 2143 if mod:
2144 2144 try:
2145 2145 ct = mod.cmdtable
2146 2146 except AttributeError:
2147 2147 ct = {}
2148 2148 modcmds = set([c.split('|', 1)[0] for c in ct])
2149 2149 helplist(_('list of commands:\n\n'), modcmds.__contains__)
2150 2150 else:
2151 2151 ui.write(_('use "hg help extensions" for information on enabling '
2152 2152 'extensions\n'))
2153 2153
2154 2154 def helpextcmd(name):
2155 2155 cmd, ext, mod = extensions.disabledcmd(ui, name, ui.config('ui', 'strict'))
2156 2156 doc = gettext(mod.__doc__).splitlines()[0]
2157 2157
2158 2158 msg = help.listexts(_("'%s' is provided by the following "
2159 2159 "extension:") % cmd, {ext: doc}, len(ext),
2160 2160 indent=4)
2161 2161 ui.write(minirst.format(msg, textwidth))
2162 2162 ui.write('\n\n')
2163 2163 ui.write(_('use "hg help extensions" for information on enabling '
2164 2164 'extensions\n'))
2165 2165
2166 2166 help.addtopichook('revsets', revset.makedoc)
2167 2167 help.addtopichook('templates', templatekw.makedoc)
2168 2168 help.addtopichook('templates', templatefilters.makedoc)
2169 2169
2170 2170 if name and name != 'shortlist':
2171 2171 i = None
2172 2172 if unknowncmd:
2173 2173 queries = (helpextcmd,)
2174 2174 else:
2175 2175 queries = (helptopic, helpcmd, helpext, helpextcmd)
2176 2176 for f in queries:
2177 2177 try:
2178 2178 f(name)
2179 2179 i = None
2180 2180 break
2181 2181 except error.UnknownCommand, inst:
2182 2182 i = inst
2183 2183 if i:
2184 2184 raise i
2185 2185
2186 2186 else:
2187 2187 # program name
2188 2188 if ui.verbose or with_version:
2189 2189 version_(ui)
2190 2190 else:
2191 2191 ui.status(_("Mercurial Distributed SCM\n"))
2192 2192 ui.status('\n')
2193 2193
2194 2194 # list of commands
2195 2195 if name == "shortlist":
2196 2196 header = _('basic commands:\n\n')
2197 2197 else:
2198 2198 header = _('list of commands:\n\n')
2199 2199
2200 2200 helplist(header)
2201 2201 if name != 'shortlist':
2202 2202 exts, maxlength = extensions.enabled()
2203 2203 text = help.listexts(_('enabled extensions:'), exts, maxlength)
2204 2204 if text:
2205 2205 ui.write("\n%s\n" % minirst.format(text, textwidth))
2206 2206
2207 2207 # list all option lists
2208 2208 opt_output = []
2209 2209 multioccur = False
2210 2210 for title, options in option_lists:
2211 2211 opt_output.append(("\n%s" % title, None))
2212 2212 for option in options:
2213 2213 if len(option) == 5:
2214 2214 shortopt, longopt, default, desc, optlabel = option
2215 2215 else:
2216 2216 shortopt, longopt, default, desc = option
2217 2217 optlabel = _("VALUE") # default label
2218 2218
2219 2219 if _("DEPRECATED") in desc and not ui.verbose:
2220 2220 continue
2221 2221 if isinstance(default, list):
2222 2222 numqualifier = " %s [+]" % optlabel
2223 2223 multioccur = True
2224 2224 elif (default is not None) and not isinstance(default, bool):
2225 2225 numqualifier = " %s" % optlabel
2226 2226 else:
2227 2227 numqualifier = ""
2228 2228 opt_output.append(("%2s%s" %
2229 2229 (shortopt and "-%s" % shortopt,
2230 2230 longopt and " --%s%s" %
2231 2231 (longopt, numqualifier)),
2232 2232 "%s%s" % (desc,
2233 2233 default
2234 2234 and _(" (default: %s)") % default
2235 2235 or "")))
2236 2236 if multioccur:
2237 2237 msg = _("\n[+] marked option can be specified multiple times")
2238 2238 if ui.verbose and name != 'shortlist':
2239 2239 opt_output.append((msg, None))
2240 2240 else:
2241 2241 opt_output.insert(-1, (msg, None))
2242 2242
2243 2243 if not name:
2244 2244 ui.write(_("\nadditional help topics:\n\n"))
2245 2245 topics = []
2246 2246 for names, header, doc in help.helptable:
2247 2247 topics.append((sorted(names, key=len, reverse=True)[0], header))
2248 2248 topics_len = max([len(s[0]) for s in topics])
2249 2249 for t, desc in topics:
2250 2250 ui.write(" %-*s %s\n" % (topics_len, t, desc))
2251 2251
2252 2252 if opt_output:
2253 2253 colwidth = encoding.colwidth
2254 2254 # normalize: (opt or message, desc or None, width of opt)
2255 2255 entries = [desc and (opt, desc, colwidth(opt)) or (opt, None, 0)
2256 2256 for opt, desc in opt_output]
2257 2257 hanging = max([e[2] for e in entries])
2258 2258 for opt, desc, width in entries:
2259 2259 if desc:
2260 2260 initindent = ' %s%s ' % (opt, ' ' * (hanging - width))
2261 2261 hangindent = ' ' * (hanging + 3)
2262 2262 ui.write('%s\n' % (util.wrap(desc, textwidth,
2263 2263 initindent=initindent,
2264 2264 hangindent=hangindent)))
2265 2265 else:
2266 2266 ui.write("%s\n" % opt)
2267 2267
2268 2268 def identify(ui, repo, source=None, rev=None,
2269 2269 num=None, id=None, branch=None, tags=None, bookmarks=None):
2270 2270 """identify the working copy or specified revision
2271 2271
2272 2272 With no revision, print a summary of the current state of the
2273 2273 repository.
2274 2274
2275 2275 Specifying a path to a repository root or Mercurial bundle will
2276 2276 cause lookup to operate on that repository/bundle.
2277 2277
2278 2278 This summary identifies the repository state using one or two
2279 2279 parent hash identifiers, followed by a "+" if there are
2280 2280 uncommitted changes in the working directory, a list of tags for
2281 2281 this revision and a branch name for non-default branches.
2282 2282
2283 2283 Returns 0 if successful.
2284 2284 """
2285 2285
2286 2286 if not repo and not source:
2287 2287 raise util.Abort(_("there is no Mercurial repository here "
2288 2288 "(.hg not found)"))
2289 2289
2290 2290 hexfunc = ui.debugflag and hex or short
2291 2291 default = not (num or id or branch or tags or bookmarks)
2292 2292 output = []
2293 2293
2294 2294 revs = []
2295 2295 bms = []
2296 2296 if source:
2297 2297 source, branches = hg.parseurl(ui.expandpath(source))
2298 2298 repo = hg.repository(ui, source)
2299 2299 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
2300 2300
2301 2301 if not repo.local():
2302 2302 if not rev and revs:
2303 2303 rev = revs[0]
2304 2304 if not rev:
2305 2305 rev = "tip"
2306 2306 if num or branch or tags:
2307 2307 raise util.Abort(
2308 2308 _("can't query remote revision number, branch, or tags"))
2309 2309
2310 2310 remoterev = repo.lookup(rev)
2311 2311 if default or id:
2312 2312 output = [hexfunc(remoterev)]
2313 2313
2314 2314 if 'bookmarks' in repo.listkeys('namespaces'):
2315 2315 hexremoterev = hex(remoterev)
2316 2316 bms = [bm for bm, bmrev in repo.listkeys('bookmarks').iteritems()
2317 2317 if bmrev == hexremoterev]
2318 2318
2319 2319 elif not rev:
2320 2320 ctx = repo[None]
2321 2321 parents = ctx.parents()
2322 2322 changed = False
2323 2323 if default or id or num:
2324 2324 changed = util.any(repo.status())
2325 2325 if default or id:
2326 2326 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
2327 2327 (changed) and "+" or "")]
2328 2328 if num:
2329 2329 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
2330 2330 (changed) and "+" or ""))
2331 2331 else:
2332 2332 ctx = cmdutil.revsingle(repo, rev)
2333 2333 if default or id:
2334 2334 output = [hexfunc(ctx.node())]
2335 2335 if num:
2336 2336 output.append(str(ctx.rev()))
2337 2337
2338 2338 if repo.local():
2339 2339 bms = ctx.bookmarks()
2340 2340
2341 2341 if repo.local() and default and not ui.quiet:
2342 2342 b = ctx.branch()
2343 2343 if b != 'default':
2344 2344 output.append("(%s)" % b)
2345 2345
2346 2346 # multiple tags for a single parent separated by '/'
2347 2347 t = "/".join(ctx.tags())
2348 2348 if t:
2349 2349 output.append(t)
2350 2350
2351 2351 if default and not ui.quiet:
2352 2352 # multiple bookmarks for a single parent separated by '/'
2353 2353 bm = '/'.join(bms)
2354 2354 if bm:
2355 2355 output.append(bm)
2356 2356
2357 2357 if branch:
2358 2358 output.append(ctx.branch())
2359 2359
2360 2360 if tags:
2361 2361 output.extend(ctx.tags())
2362 2362
2363 2363 if bookmarks:
2364 2364 output.extend(bms)
2365 2365
2366 2366 ui.write("%s\n" % ' '.join(output))
2367 2367
2368 2368 def import_(ui, repo, patch1, *patches, **opts):
2369 2369 """import an ordered set of patches
2370 2370
2371 2371 Import a list of patches and commit them individually (unless
2372 2372 --no-commit is specified).
2373 2373
2374 2374 If there are outstanding changes in the working directory, import
2375 2375 will abort unless given the -f/--force flag.
2376 2376
2377 2377 You can import a patch straight from a mail message. Even patches
2378 2378 as attachments work (to use the body part, it must have type
2379 2379 text/plain or text/x-patch). From and Subject headers of email
2380 2380 message are used as default committer and commit message. All
2381 2381 text/plain body parts before first diff are added to commit
2382 2382 message.
2383 2383
2384 2384 If the imported patch was generated by :hg:`export`, user and
2385 2385 description from patch override values from message headers and
2386 2386 body. Values given on command line with -m/--message and -u/--user
2387 2387 override these.
2388 2388
2389 2389 If --exact is specified, import will set the working directory to
2390 2390 the parent of each patch before applying it, and will abort if the
2391 2391 resulting changeset has a different ID than the one recorded in
2392 2392 the patch. This may happen due to character set problems or other
2393 2393 deficiencies in the text patch format.
2394 2394
2395 2395 With -s/--similarity, hg will attempt to discover renames and
2396 2396 copies in the patch in the same way as 'addremove'.
2397 2397
2398 2398 To read a patch from standard input, use "-" as the patch name. If
2399 2399 a URL is specified, the patch will be downloaded from it.
2400 2400 See :hg:`help dates` for a list of formats valid for -d/--date.
2401 2401
2402 2402 Returns 0 on success.
2403 2403 """
2404 2404 patches = (patch1,) + patches
2405 2405
2406 2406 date = opts.get('date')
2407 2407 if date:
2408 2408 opts['date'] = util.parsedate(date)
2409 2409
2410 2410 try:
2411 2411 sim = float(opts.get('similarity') or 0)
2412 2412 except ValueError:
2413 2413 raise util.Abort(_('similarity must be a number'))
2414 2414 if sim < 0 or sim > 100:
2415 2415 raise util.Abort(_('similarity must be between 0 and 100'))
2416 2416
2417 2417 if opts.get('exact') or not opts.get('force'):
2418 2418 cmdutil.bail_if_changed(repo)
2419 2419
2420 2420 d = opts["base"]
2421 2421 strip = opts["strip"]
2422 2422 wlock = lock = None
2423 2423 msgs = []
2424 2424
2425 2425 def tryone(ui, hunk):
2426 2426 tmpname, message, user, date, branch, nodeid, p1, p2 = \
2427 2427 patch.extract(ui, hunk)
2428 2428
2429 2429 if not tmpname:
2430 2430 return None
2431 2431 commitid = _('to working directory')
2432 2432
2433 2433 try:
2434 2434 cmdline_message = cmdutil.logmessage(opts)
2435 2435 if cmdline_message:
2436 2436 # pickup the cmdline msg
2437 2437 message = cmdline_message
2438 2438 elif message:
2439 2439 # pickup the patch msg
2440 2440 message = message.strip()
2441 2441 else:
2442 2442 # launch the editor
2443 2443 message = None
2444 2444 ui.debug('message:\n%s\n' % message)
2445 2445
2446 2446 wp = repo.parents()
2447 2447 if opts.get('exact'):
2448 2448 if not nodeid or not p1:
2449 2449 raise util.Abort(_('not a Mercurial patch'))
2450 2450 p1 = repo.lookup(p1)
2451 2451 p2 = repo.lookup(p2 or hex(nullid))
2452 2452
2453 2453 if p1 != wp[0].node():
2454 2454 hg.clean(repo, p1)
2455 2455 repo.dirstate.setparents(p1, p2)
2456 2456 elif p2:
2457 2457 try:
2458 2458 p1 = repo.lookup(p1)
2459 2459 p2 = repo.lookup(p2)
2460 2460 if p1 == wp[0].node():
2461 2461 repo.dirstate.setparents(p1, p2)
2462 2462 except error.RepoError:
2463 2463 pass
2464 2464 if opts.get('exact') or opts.get('import_branch'):
2465 2465 repo.dirstate.setbranch(branch or 'default')
2466 2466
2467 2467 files = {}
2468 2468 try:
2469 2469 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
2470 2470 files=files, eolmode=None)
2471 2471 finally:
2472 2472 files = cmdutil.updatedir(ui, repo, files,
2473 2473 similarity=sim / 100.0)
2474 2474 if opts.get('no_commit'):
2475 2475 if message:
2476 2476 msgs.append(message)
2477 2477 else:
2478 2478 if opts.get('exact'):
2479 2479 m = None
2480 2480 else:
2481 2481 m = cmdutil.matchfiles(repo, files or [])
2482 2482 n = repo.commit(message, opts.get('user') or user,
2483 2483 opts.get('date') or date, match=m,
2484 2484 editor=cmdutil.commiteditor)
2485 2485 if opts.get('exact'):
2486 2486 if hex(n) != nodeid:
2487 2487 repo.rollback()
2488 2488 raise util.Abort(_('patch is damaged'
2489 2489 ' or loses information'))
2490 2490 # Force a dirstate write so that the next transaction
2491 2491 # backups an up-do-date file.
2492 2492 repo.dirstate.write()
2493 2493 if n:
2494 2494 commitid = short(n)
2495 2495
2496 2496 return commitid
2497 2497 finally:
2498 2498 os.unlink(tmpname)
2499 2499
2500 2500 try:
2501 2501 wlock = repo.wlock()
2502 2502 lock = repo.lock()
2503 2503 lastcommit = None
2504 2504 for p in patches:
2505 2505 pf = os.path.join(d, p)
2506 2506
2507 2507 if pf == '-':
2508 2508 ui.status(_("applying patch from stdin\n"))
2509 2509 pf = sys.stdin
2510 2510 else:
2511 2511 ui.status(_("applying %s\n") % p)
2512 2512 pf = url.open(ui, pf)
2513 2513
2514 2514 haspatch = False
2515 2515 for hunk in patch.split(pf):
2516 2516 commitid = tryone(ui, hunk)
2517 2517 if commitid:
2518 2518 haspatch = True
2519 2519 if lastcommit:
2520 2520 ui.status(_('applied %s\n') % lastcommit)
2521 2521 lastcommit = commitid
2522 2522
2523 2523 if not haspatch:
2524 2524 raise util.Abort(_('no diffs found'))
2525 2525
2526 2526 if msgs:
2527 2527 repo.opener('last-message.txt', 'wb').write('\n* * *\n'.join(msgs))
2528 2528 finally:
2529 2529 release(lock, wlock)
2530 2530
2531 2531 def incoming(ui, repo, source="default", **opts):
2532 2532 """show new changesets found in source
2533 2533
2534 2534 Show new changesets found in the specified path/URL or the default
2535 2535 pull location. These are the changesets that would have been pulled
2536 2536 if a pull at the time you issued this command.
2537 2537
2538 2538 For remote repository, using --bundle avoids downloading the
2539 2539 changesets twice if the incoming is followed by a pull.
2540 2540
2541 2541 See pull for valid source format details.
2542 2542
2543 2543 Returns 0 if there are incoming changes, 1 otherwise.
2544 2544 """
2545 2545 if opts.get('bundle') and opts.get('subrepos'):
2546 2546 raise util.Abort(_('cannot combine --bundle and --subrepos'))
2547 2547
2548 2548 if opts.get('bookmarks'):
2549 2549 source, branches = hg.parseurl(ui.expandpath(source),
2550 2550 opts.get('branch'))
2551 2551 other = hg.repository(hg.remoteui(repo, opts), source)
2552 2552 if 'bookmarks' not in other.listkeys('namespaces'):
2553 2553 ui.warn(_("remote doesn't support bookmarks\n"))
2554 2554 return 0
2555 2555 ui.status(_('comparing with %s\n') % url.hidepassword(source))
2556 2556 return bookmarks.diff(ui, repo, other)
2557 2557
2558 2558 ret = hg.incoming(ui, repo, source, opts)
2559 2559 return ret
2560 2560
2561 2561 def init(ui, dest=".", **opts):
2562 2562 """create a new repository in the given directory
2563 2563
2564 2564 Initialize a new repository in the given directory. If the given
2565 2565 directory does not exist, it will be created.
2566 2566
2567 2567 If no directory is given, the current directory is used.
2568 2568
2569 2569 It is possible to specify an ``ssh://`` URL as the destination.
2570 2570 See :hg:`help urls` for more information.
2571 2571
2572 2572 Returns 0 on success.
2573 2573 """
2574 2574 hg.repository(hg.remoteui(ui, opts), ui.expandpath(dest), create=1)
2575 2575
2576 2576 def locate(ui, repo, *pats, **opts):
2577 2577 """locate files matching specific patterns
2578 2578
2579 2579 Print files under Mercurial control in the working directory whose
2580 2580 names match the given patterns.
2581 2581
2582 2582 By default, this command searches all directories in the working
2583 2583 directory. To search just the current directory and its
2584 2584 subdirectories, use "--include .".
2585 2585
2586 2586 If no patterns are given to match, this command prints the names
2587 2587 of all files under Mercurial control in the working directory.
2588 2588
2589 2589 If you want to feed the output of this command into the "xargs"
2590 2590 command, use the -0 option to both this command and "xargs". This
2591 2591 will avoid the problem of "xargs" treating single filenames that
2592 2592 contain whitespace as multiple filenames.
2593 2593
2594 2594 Returns 0 if a match is found, 1 otherwise.
2595 2595 """
2596 2596 end = opts.get('print0') and '\0' or '\n'
2597 2597 rev = cmdutil.revsingle(repo, opts.get('rev'), None).node()
2598 2598
2599 2599 ret = 1
2600 2600 m = cmdutil.match(repo, pats, opts, default='relglob')
2601 2601 m.bad = lambda x, y: False
2602 2602 for abs in repo[rev].walk(m):
2603 2603 if not rev and abs not in repo.dirstate:
2604 2604 continue
2605 2605 if opts.get('fullpath'):
2606 2606 ui.write(repo.wjoin(abs), end)
2607 2607 else:
2608 2608 ui.write(((pats and m.rel(abs)) or abs), end)
2609 2609 ret = 0
2610 2610
2611 2611 return ret
2612 2612
2613 2613 def log(ui, repo, *pats, **opts):
2614 2614 """show revision history of entire repository or files
2615 2615
2616 2616 Print the revision history of the specified files or the entire
2617 2617 project.
2618 2618
2619 2619 File history is shown without following rename or copy history of
2620 2620 files. Use -f/--follow with a filename to follow history across
2621 2621 renames and copies. --follow without a filename will only show
2622 2622 ancestors or descendants of the starting revision. --follow-first
2623 2623 only follows the first parent of merge revisions.
2624 2624
2625 2625 If no revision range is specified, the default is ``tip:0`` unless
2626 2626 --follow is set, in which case the working directory parent is
2627 2627 used as the starting revision. You can specify a revision set for
2628 2628 log, see :hg:`help revsets` for more information.
2629 2629
2630 2630 See :hg:`help dates` for a list of formats valid for -d/--date.
2631 2631
2632 2632 By default this command prints revision number and changeset id,
2633 2633 tags, non-trivial parents, user, date and time, and a summary for
2634 2634 each commit. When the -v/--verbose switch is used, the list of
2635 2635 changed files and full commit message are shown.
2636 2636
2637 2637 .. note::
2638 2638 log -p/--patch may generate unexpected diff output for merge
2639 2639 changesets, as it will only compare the merge changeset against
2640 2640 its first parent. Also, only files different from BOTH parents
2641 2641 will appear in files:.
2642 2642
2643 2643 Returns 0 on success.
2644 2644 """
2645 2645
2646 2646 matchfn = cmdutil.match(repo, pats, opts)
2647 2647 limit = cmdutil.loglimit(opts)
2648 2648 count = 0
2649 2649
2650 2650 endrev = None
2651 2651 if opts.get('copies') and opts.get('rev'):
2652 2652 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
2653 2653
2654 2654 df = False
2655 2655 if opts["date"]:
2656 2656 df = util.matchdate(opts["date"])
2657 2657
2658 2658 branches = opts.get('branch', []) + opts.get('only_branch', [])
2659 2659 opts['branch'] = [repo.lookupbranch(b) for b in branches]
2660 2660
2661 2661 displayer = cmdutil.show_changeset(ui, repo, opts, True)
2662 2662 def prep(ctx, fns):
2663 2663 rev = ctx.rev()
2664 2664 parents = [p for p in repo.changelog.parentrevs(rev)
2665 2665 if p != nullrev]
2666 2666 if opts.get('no_merges') and len(parents) == 2:
2667 2667 return
2668 2668 if opts.get('only_merges') and len(parents) != 2:
2669 2669 return
2670 2670 if opts.get('branch') and ctx.branch() not in opts['branch']:
2671 2671 return
2672 2672 if df and not df(ctx.date()[0]):
2673 2673 return
2674 2674 if opts['user'] and not [k for k in opts['user']
2675 2675 if k.lower() in ctx.user().lower()]:
2676 2676 return
2677 2677 if opts.get('keyword'):
2678 2678 for k in [kw.lower() for kw in opts['keyword']]:
2679 2679 if (k in ctx.user().lower() or
2680 2680 k in ctx.description().lower() or
2681 2681 k in " ".join(ctx.files()).lower()):
2682 2682 break
2683 2683 else:
2684 2684 return
2685 2685
2686 2686 copies = None
2687 2687 if opts.get('copies') and rev:
2688 2688 copies = []
2689 2689 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2690 2690 for fn in ctx.files():
2691 2691 rename = getrenamed(fn, rev)
2692 2692 if rename:
2693 2693 copies.append((fn, rename[0]))
2694 2694
2695 2695 revmatchfn = None
2696 2696 if opts.get('patch') or opts.get('stat'):
2697 2697 if opts.get('follow') or opts.get('follow_first'):
2698 2698 # note: this might be wrong when following through merges
2699 2699 revmatchfn = cmdutil.match(repo, fns, default='path')
2700 2700 else:
2701 2701 revmatchfn = matchfn
2702 2702
2703 2703 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2704 2704
2705 2705 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2706 2706 if count == limit:
2707 2707 break
2708 2708 if displayer.flush(ctx.rev()):
2709 2709 count += 1
2710 2710 displayer.close()
2711 2711
2712 2712 def manifest(ui, repo, node=None, rev=None):
2713 2713 """output the current or given revision of the project manifest
2714 2714
2715 2715 Print a list of version controlled files for the given revision.
2716 2716 If no revision is given, the first parent of the working directory
2717 2717 is used, or the null revision if no revision is checked out.
2718 2718
2719 2719 With -v, print file permissions, symlink and executable bits.
2720 2720 With --debug, print file revision hashes.
2721 2721
2722 2722 Returns 0 on success.
2723 2723 """
2724 2724
2725 2725 if rev and node:
2726 2726 raise util.Abort(_("please specify just one revision"))
2727 2727
2728 2728 if not node:
2729 2729 node = rev
2730 2730
2731 2731 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2732 2732 ctx = cmdutil.revsingle(repo, node)
2733 2733 for f in ctx:
2734 2734 if ui.debugflag:
2735 2735 ui.write("%40s " % hex(ctx.manifest()[f]))
2736 2736 if ui.verbose:
2737 2737 ui.write(decor[ctx.flags(f)])
2738 2738 ui.write("%s\n" % f)
2739 2739
2740 2740 def merge(ui, repo, node=None, **opts):
2741 2741 """merge working directory with another revision
2742 2742
2743 2743 The current working directory is updated with all changes made in
2744 2744 the requested revision since the last common predecessor revision.
2745 2745
2746 2746 Files that changed between either parent are marked as changed for
2747 2747 the next commit and a commit must be performed before any further
2748 2748 updates to the repository are allowed. The next commit will have
2749 2749 two parents.
2750 2750
2751 2751 ``--tool`` can be used to specify the merge tool used for file
2752 2752 merges. It overrides the HGMERGE environment variable and your
2753 2753 configuration files.
2754 2754
2755 2755 If no revision is specified, the working directory's parent is a
2756 2756 head revision, and the current branch contains exactly one other
2757 2757 head, the other head is merged with by default. Otherwise, an
2758 2758 explicit revision with which to merge with must be provided.
2759 2759
2760 2760 :hg:`resolve` must be used to resolve unresolved files.
2761 2761
2762 2762 To undo an uncommitted merge, use :hg:`update --clean .` which
2763 2763 will check out a clean copy of the original merge parent, losing
2764 2764 all changes.
2765 2765
2766 2766 Returns 0 on success, 1 if there are unresolved files.
2767 2767 """
2768 2768
2769 2769 if opts.get('rev') and node:
2770 2770 raise util.Abort(_("please specify just one revision"))
2771 2771 if not node:
2772 2772 node = opts.get('rev')
2773 2773
2774 2774 if not node:
2775 2775 branch = repo[None].branch()
2776 2776 bheads = repo.branchheads(branch)
2777 2777 if len(bheads) > 2:
2778 2778 raise util.Abort(_(
2779 2779 'branch \'%s\' has %d heads - '
2780 2780 'please merge with an explicit rev\n'
2781 2781 '(run \'hg heads .\' to see heads)')
2782 2782 % (branch, len(bheads)))
2783 2783
2784 2784 parent = repo.dirstate.parents()[0]
2785 2785 if len(bheads) == 1:
2786 2786 if len(repo.heads()) > 1:
2787 2787 raise util.Abort(_(
2788 2788 'branch \'%s\' has one head - '
2789 2789 'please merge with an explicit rev\n'
2790 2790 '(run \'hg heads\' to see all heads)')
2791 2791 % branch)
2792 2792 msg = _('there is nothing to merge')
2793 2793 if parent != repo.lookup(repo[None].branch()):
2794 2794 msg = _('%s - use "hg update" instead') % msg
2795 2795 raise util.Abort(msg)
2796 2796
2797 2797 if parent not in bheads:
2798 2798 raise util.Abort(_('working dir not at a head rev - '
2799 2799 'use "hg update" or merge with an explicit rev'))
2800 2800 node = parent == bheads[0] and bheads[-1] or bheads[0]
2801 2801 else:
2802 2802 node = cmdutil.revsingle(repo, node).node()
2803 2803
2804 2804 if opts.get('preview'):
2805 2805 # find nodes that are ancestors of p2 but not of p1
2806 2806 p1 = repo.lookup('.')
2807 2807 p2 = repo.lookup(node)
2808 2808 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
2809 2809
2810 2810 displayer = cmdutil.show_changeset(ui, repo, opts)
2811 2811 for node in nodes:
2812 2812 displayer.show(repo[node])
2813 2813 displayer.close()
2814 2814 return 0
2815 2815
2816 2816 try:
2817 2817 # ui.forcemerge is an internal variable, do not document
2818 2818 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2819 2819 return hg.merge(repo, node, force=opts.get('force'))
2820 2820 finally:
2821 2821 ui.setconfig('ui', 'forcemerge', '')
2822 2822
2823 2823 def outgoing(ui, repo, dest=None, **opts):
2824 2824 """show changesets not found in the destination
2825 2825
2826 2826 Show changesets not found in the specified destination repository
2827 2827 or the default push location. These are the changesets that would
2828 2828 be pushed if a push was requested.
2829 2829
2830 2830 See pull for details of valid destination formats.
2831 2831
2832 2832 Returns 0 if there are outgoing changes, 1 otherwise.
2833 2833 """
2834 2834
2835 2835 if opts.get('bookmarks'):
2836 2836 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2837 2837 dest, branches = hg.parseurl(dest, opts.get('branch'))
2838 2838 other = hg.repository(hg.remoteui(repo, opts), dest)
2839 2839 if 'bookmarks' not in other.listkeys('namespaces'):
2840 2840 ui.warn(_("remote doesn't support bookmarks\n"))
2841 2841 return 0
2842 2842 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
2843 2843 return bookmarks.diff(ui, other, repo)
2844 2844
2845 2845 ret = hg.outgoing(ui, repo, dest, opts)
2846 2846 return ret
2847 2847
2848 2848 def parents(ui, repo, file_=None, **opts):
2849 2849 """show the parents of the working directory or revision
2850 2850
2851 2851 Print the working directory's parent revisions. If a revision is
2852 2852 given via -r/--rev, the parent of that revision will be printed.
2853 2853 If a file argument is given, the revision in which the file was
2854 2854 last changed (before the working directory revision or the
2855 2855 argument to --rev if given) is printed.
2856 2856
2857 2857 Returns 0 on success.
2858 2858 """
2859 2859
2860 2860 ctx = cmdutil.revsingle(repo, opts.get('rev'), None)
2861 2861
2862 2862 if file_:
2863 2863 m = cmdutil.match(repo, (file_,), opts)
2864 2864 if m.anypats() or len(m.files()) != 1:
2865 2865 raise util.Abort(_('can only specify an explicit filename'))
2866 2866 file_ = m.files()[0]
2867 2867 filenodes = []
2868 2868 for cp in ctx.parents():
2869 2869 if not cp:
2870 2870 continue
2871 2871 try:
2872 2872 filenodes.append(cp.filenode(file_))
2873 2873 except error.LookupError:
2874 2874 pass
2875 2875 if not filenodes:
2876 2876 raise util.Abort(_("'%s' not found in manifest!") % file_)
2877 2877 fl = repo.file(file_)
2878 2878 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2879 2879 else:
2880 2880 p = [cp.node() for cp in ctx.parents()]
2881 2881
2882 2882 displayer = cmdutil.show_changeset(ui, repo, opts)
2883 2883 for n in p:
2884 2884 if n != nullid:
2885 2885 displayer.show(repo[n])
2886 2886 displayer.close()
2887 2887
2888 2888 def paths(ui, repo, search=None):
2889 2889 """show aliases for remote repositories
2890 2890
2891 2891 Show definition of symbolic path name NAME. If no name is given,
2892 2892 show definition of all available names.
2893 2893
2894 2894 Path names are defined in the [paths] section of your
2895 2895 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
2896 2896 repository, ``.hg/hgrc`` is used, too.
2897 2897
2898 2898 The path names ``default`` and ``default-push`` have a special
2899 2899 meaning. When performing a push or pull operation, they are used
2900 2900 as fallbacks if no location is specified on the command-line.
2901 2901 When ``default-push`` is set, it will be used for push and
2902 2902 ``default`` will be used for pull; otherwise ``default`` is used
2903 2903 as the fallback for both. When cloning a repository, the clone
2904 2904 source is written as ``default`` in ``.hg/hgrc``. Note that
2905 2905 ``default`` and ``default-push`` apply to all inbound (e.g.
2906 2906 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
2907 2907 :hg:`bundle`) operations.
2908 2908
2909 2909 See :hg:`help urls` for more information.
2910 2910
2911 2911 Returns 0 on success.
2912 2912 """
2913 2913 if search:
2914 2914 for name, path in ui.configitems("paths"):
2915 2915 if name == search:
2916 2916 ui.write("%s\n" % url.hidepassword(path))
2917 2917 return
2918 2918 ui.warn(_("not found!\n"))
2919 2919 return 1
2920 2920 else:
2921 2921 for name, path in ui.configitems("paths"):
2922 2922 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2923 2923
2924 2924 def postincoming(ui, repo, modheads, optupdate, checkout):
2925 2925 if modheads == 0:
2926 2926 return
2927 2927 if optupdate:
2928 2928 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2929 2929 return hg.update(repo, checkout)
2930 2930 else:
2931 2931 ui.status(_("not updating, since new heads added\n"))
2932 2932 if modheads > 1:
2933 2933 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2934 2934 else:
2935 2935 ui.status(_("(run 'hg update' to get a working copy)\n"))
2936 2936
2937 2937 def pull(ui, repo, source="default", **opts):
2938 2938 """pull changes from the specified source
2939 2939
2940 2940 Pull changes from a remote repository to a local one.
2941 2941
2942 2942 This finds all changes from the repository at the specified path
2943 2943 or URL and adds them to a local repository (the current one unless
2944 2944 -R is specified). By default, this does not update the copy of the
2945 2945 project in the working directory.
2946 2946
2947 2947 Use :hg:`incoming` if you want to see what would have been added
2948 2948 by a pull at the time you issued this command. If you then decide
2949 2949 to add those changes to the repository, you should use :hg:`pull
2950 2950 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
2951 2951
2952 2952 If SOURCE is omitted, the 'default' path will be used.
2953 2953 See :hg:`help urls` for more information.
2954 2954
2955 2955 Returns 0 on success, 1 if an update had unresolved files.
2956 2956 """
2957 2957 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
2958 2958 other = hg.repository(hg.remoteui(repo, opts), source)
2959 2959 ui.status(_('pulling from %s\n') % url.hidepassword(source))
2960 2960 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
2961 2961
2962 2962 if opts.get('bookmark'):
2963 2963 if not revs:
2964 2964 revs = []
2965 2965 rb = other.listkeys('bookmarks')
2966 2966 for b in opts['bookmark']:
2967 2967 if b not in rb:
2968 2968 raise util.Abort(_('remote bookmark %s not found!') % b)
2969 2969 revs.append(rb[b])
2970 2970
2971 2971 if revs:
2972 2972 try:
2973 2973 revs = [other.lookup(rev) for rev in revs]
2974 2974 except error.CapabilityError:
2975 2975 err = _("other repository doesn't support revision lookup, "
2976 2976 "so a rev cannot be specified.")
2977 2977 raise util.Abort(err)
2978 2978
2979 2979 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
2980 bookmarks.updatefromremote(ui, repo, other)
2980 2981 if checkout:
2981 2982 checkout = str(repo.changelog.rev(other.lookup(checkout)))
2982 2983 repo._subtoppath = source
2983 2984 try:
2984 2985 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
2985 2986
2986 2987 finally:
2987 2988 del repo._subtoppath
2988 2989
2989 2990 # update specified bookmarks
2990 2991 if opts.get('bookmark'):
2991 2992 for b in opts['bookmark']:
2992 2993 # explicit pull overrides local bookmark if any
2993 2994 ui.status(_("importing bookmark %s\n") % b)
2994 2995 repo._bookmarks[b] = repo[rb[b]].node()
2995 2996 bookmarks.write(repo)
2996 2997
2997 2998 return ret
2998 2999
2999 3000 def push(ui, repo, dest=None, **opts):
3000 3001 """push changes to the specified destination
3001 3002
3002 3003 Push changesets from the local repository to the specified
3003 3004 destination.
3004 3005
3005 3006 This operation is symmetrical to pull: it is identical to a pull
3006 3007 in the destination repository from the current one.
3007 3008
3008 3009 By default, push will not allow creation of new heads at the
3009 3010 destination, since multiple heads would make it unclear which head
3010 3011 to use. In this situation, it is recommended to pull and merge
3011 3012 before pushing.
3012 3013
3013 3014 Use --new-branch if you want to allow push to create a new named
3014 3015 branch that is not present at the destination. This allows you to
3015 3016 only create a new branch without forcing other changes.
3016 3017
3017 3018 Use -f/--force to override the default behavior and push all
3018 3019 changesets on all branches.
3019 3020
3020 3021 If -r/--rev is used, the specified revision and all its ancestors
3021 3022 will be pushed to the remote repository.
3022 3023
3023 3024 Please see :hg:`help urls` for important details about ``ssh://``
3024 3025 URLs. If DESTINATION is omitted, a default path will be used.
3025 3026
3026 3027 Returns 0 if push was successful, 1 if nothing to push.
3027 3028 """
3028 3029
3029 3030 if opts.get('bookmark'):
3030 3031 for b in opts['bookmark']:
3031 3032 # translate -B options to -r so changesets get pushed
3032 3033 if b in repo._bookmarks:
3033 3034 opts.setdefault('rev', []).append(b)
3034 3035 else:
3035 3036 # if we try to push a deleted bookmark, translate it to null
3036 3037 # this lets simultaneous -r, -b options continue working
3037 3038 opts.setdefault('rev', []).append("null")
3038 3039
3039 3040 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3040 3041 dest, branches = hg.parseurl(dest, opts.get('branch'))
3041 3042 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
3042 3043 other = hg.repository(hg.remoteui(repo, opts), dest)
3043 3044 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
3044 3045 if revs:
3045 3046 revs = [repo.lookup(rev) for rev in revs]
3046 3047
3047 3048 repo._subtoppath = dest
3048 3049 try:
3049 3050 # push subrepos depth-first for coherent ordering
3050 3051 c = repo['']
3051 3052 subs = c.substate # only repos that are committed
3052 3053 for s in sorted(subs):
3053 3054 if not c.sub(s).push(opts.get('force')):
3054 3055 return False
3055 3056 finally:
3056 3057 del repo._subtoppath
3057 3058 result = repo.push(other, opts.get('force'), revs=revs,
3058 3059 newbranch=opts.get('new_branch'))
3059 3060
3060 3061 result = (result == 0)
3061 3062
3062 3063 if opts.get('bookmark'):
3063 3064 rb = other.listkeys('bookmarks')
3064 3065 for b in opts['bookmark']:
3065 3066 # explicit push overrides remote bookmark if any
3066 3067 if b in repo._bookmarks:
3067 3068 ui.status(_("exporting bookmark %s\n") % b)
3068 3069 new = repo[b].hex()
3069 3070 elif b in rb:
3070 3071 ui.status(_("deleting remote bookmark %s\n") % b)
3071 3072 new = '' # delete
3072 3073 else:
3073 3074 ui.warn(_('bookmark %s does not exist on the local '
3074 3075 'or remote repository!\n') % b)
3075 3076 return 2
3076 3077 old = rb.get(b, '')
3077 3078 r = other.pushkey('bookmarks', b, old, new)
3078 3079 if not r:
3079 3080 ui.warn(_('updating bookmark %s failed!\n') % b)
3080 3081 if not result:
3081 3082 result = 2
3082 3083
3083 3084 return result
3084 3085
3085 3086 def recover(ui, repo):
3086 3087 """roll back an interrupted transaction
3087 3088
3088 3089 Recover from an interrupted commit or pull.
3089 3090
3090 3091 This command tries to fix the repository status after an
3091 3092 interrupted operation. It should only be necessary when Mercurial
3092 3093 suggests it.
3093 3094
3094 3095 Returns 0 if successful, 1 if nothing to recover or verify fails.
3095 3096 """
3096 3097 if repo.recover():
3097 3098 return hg.verify(repo)
3098 3099 return 1
3099 3100
3100 3101 def remove(ui, repo, *pats, **opts):
3101 3102 """remove the specified files on the next commit
3102 3103
3103 3104 Schedule the indicated files for removal from the repository.
3104 3105
3105 3106 This only removes files from the current branch, not from the
3106 3107 entire project history. -A/--after can be used to remove only
3107 3108 files that have already been deleted, -f/--force can be used to
3108 3109 force deletion, and -Af can be used to remove files from the next
3109 3110 revision without deleting them from the working directory.
3110 3111
3111 3112 The following table details the behavior of remove for different
3112 3113 file states (columns) and option combinations (rows). The file
3113 3114 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
3114 3115 reported by :hg:`status`). The actions are Warn, Remove (from
3115 3116 branch) and Delete (from disk)::
3116 3117
3117 3118 A C M !
3118 3119 none W RD W R
3119 3120 -f R RD RD R
3120 3121 -A W W W R
3121 3122 -Af R R R R
3122 3123
3123 3124 This command schedules the files to be removed at the next commit.
3124 3125 To undo a remove before that, see :hg:`revert`.
3125 3126
3126 3127 Returns 0 on success, 1 if any warnings encountered.
3127 3128 """
3128 3129
3129 3130 ret = 0
3130 3131 after, force = opts.get('after'), opts.get('force')
3131 3132 if not pats and not after:
3132 3133 raise util.Abort(_('no files specified'))
3133 3134
3134 3135 m = cmdutil.match(repo, pats, opts)
3135 3136 s = repo.status(match=m, clean=True)
3136 3137 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
3137 3138
3138 3139 for f in m.files():
3139 3140 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
3140 3141 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
3141 3142 ret = 1
3142 3143
3143 3144 if force:
3144 3145 remove, forget = modified + deleted + clean, added
3145 3146 elif after:
3146 3147 remove, forget = deleted, []
3147 3148 for f in modified + added + clean:
3148 3149 ui.warn(_('not removing %s: file still exists (use -f'
3149 3150 ' to force removal)\n') % m.rel(f))
3150 3151 ret = 1
3151 3152 else:
3152 3153 remove, forget = deleted + clean, []
3153 3154 for f in modified:
3154 3155 ui.warn(_('not removing %s: file is modified (use -f'
3155 3156 ' to force removal)\n') % m.rel(f))
3156 3157 ret = 1
3157 3158 for f in added:
3158 3159 ui.warn(_('not removing %s: file has been marked for add (use -f'
3159 3160 ' to force removal)\n') % m.rel(f))
3160 3161 ret = 1
3161 3162
3162 3163 for f in sorted(remove + forget):
3163 3164 if ui.verbose or not m.exact(f):
3164 3165 ui.status(_('removing %s\n') % m.rel(f))
3165 3166
3166 3167 repo[None].forget(forget)
3167 3168 repo[None].remove(remove, unlink=not after)
3168 3169 return ret
3169 3170
3170 3171 def rename(ui, repo, *pats, **opts):
3171 3172 """rename files; equivalent of copy + remove
3172 3173
3173 3174 Mark dest as copies of sources; mark sources for deletion. If dest
3174 3175 is a directory, copies are put in that directory. If dest is a
3175 3176 file, there can only be one source.
3176 3177
3177 3178 By default, this command copies the contents of files as they
3178 3179 exist in the working directory. If invoked with -A/--after, the
3179 3180 operation is recorded, but no copying is performed.
3180 3181
3181 3182 This command takes effect at the next commit. To undo a rename
3182 3183 before that, see :hg:`revert`.
3183 3184
3184 3185 Returns 0 on success, 1 if errors are encountered.
3185 3186 """
3186 3187 wlock = repo.wlock(False)
3187 3188 try:
3188 3189 return cmdutil.copy(ui, repo, pats, opts, rename=True)
3189 3190 finally:
3190 3191 wlock.release()
3191 3192
3192 3193 def resolve(ui, repo, *pats, **opts):
3193 3194 """redo merges or set/view the merge status of files
3194 3195
3195 3196 Merges with unresolved conflicts are often the result of
3196 3197 non-interactive merging using the ``internal:merge`` configuration
3197 3198 setting, or a command-line merge tool like ``diff3``. The resolve
3198 3199 command is used to manage the files involved in a merge, after
3199 3200 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
3200 3201 working directory must have two parents).
3201 3202
3202 3203 The resolve command can be used in the following ways:
3203 3204
3204 3205 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
3205 3206 files, discarding any previous merge attempts. Re-merging is not
3206 3207 performed for files already marked as resolved. Use ``--all/-a``
3207 3208 to selects all unresolved files. ``--tool`` can be used to specify
3208 3209 the merge tool used for the given files. It overrides the HGMERGE
3209 3210 environment variable and your configuration files.
3210 3211
3211 3212 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
3212 3213 (e.g. after having manually fixed-up the files). The default is
3213 3214 to mark all unresolved files.
3214 3215
3215 3216 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
3216 3217 default is to mark all resolved files.
3217 3218
3218 3219 - :hg:`resolve -l`: list files which had or still have conflicts.
3219 3220 In the printed list, ``U`` = unresolved and ``R`` = resolved.
3220 3221
3221 3222 Note that Mercurial will not let you commit files with unresolved
3222 3223 merge conflicts. You must use :hg:`resolve -m ...` before you can
3223 3224 commit after a conflicting merge.
3224 3225
3225 3226 Returns 0 on success, 1 if any files fail a resolve attempt.
3226 3227 """
3227 3228
3228 3229 all, mark, unmark, show, nostatus = \
3229 3230 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
3230 3231
3231 3232 if (show and (mark or unmark)) or (mark and unmark):
3232 3233 raise util.Abort(_("too many options specified"))
3233 3234 if pats and all:
3234 3235 raise util.Abort(_("can't specify --all and patterns"))
3235 3236 if not (all or pats or show or mark or unmark):
3236 3237 raise util.Abort(_('no files or directories specified; '
3237 3238 'use --all to remerge all files'))
3238 3239
3239 3240 ms = mergemod.mergestate(repo)
3240 3241 m = cmdutil.match(repo, pats, opts)
3241 3242 ret = 0
3242 3243
3243 3244 for f in ms:
3244 3245 if m(f):
3245 3246 if show:
3246 3247 if nostatus:
3247 3248 ui.write("%s\n" % f)
3248 3249 else:
3249 3250 ui.write("%s %s\n" % (ms[f].upper(), f),
3250 3251 label='resolve.' +
3251 3252 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
3252 3253 elif mark:
3253 3254 ms.mark(f, "r")
3254 3255 elif unmark:
3255 3256 ms.mark(f, "u")
3256 3257 else:
3257 3258 wctx = repo[None]
3258 3259 mctx = wctx.parents()[-1]
3259 3260
3260 3261 # backup pre-resolve (merge uses .orig for its own purposes)
3261 3262 a = repo.wjoin(f)
3262 3263 util.copyfile(a, a + ".resolve")
3263 3264
3264 3265 try:
3265 3266 # resolve file
3266 3267 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
3267 3268 if ms.resolve(f, wctx, mctx):
3268 3269 ret = 1
3269 3270 finally:
3270 3271 ui.setconfig('ui', 'forcemerge', '')
3271 3272
3272 3273 # replace filemerge's .orig file with our resolve file
3273 3274 util.rename(a + ".resolve", a + ".orig")
3274 3275
3275 3276 ms.commit()
3276 3277 return ret
3277 3278
3278 3279 def revert(ui, repo, *pats, **opts):
3279 3280 """restore individual files or directories to an earlier state
3280 3281
3281 3282 .. note::
3282 3283 This command is most likely not what you are looking for.
3283 3284 Revert will partially overwrite content in the working
3284 3285 directory without changing the working directory parents. Use
3285 3286 :hg:`update -r rev` to check out earlier revisions, or
3286 3287 :hg:`update --clean .` to undo a merge which has added another
3287 3288 parent.
3288 3289
3289 3290 With no revision specified, revert the named files or directories
3290 3291 to the contents they had in the parent of the working directory.
3291 3292 This restores the contents of the affected files to an unmodified
3292 3293 state and unschedules adds, removes, copies, and renames. If the
3293 3294 working directory has two parents, you must explicitly specify a
3294 3295 revision.
3295 3296
3296 3297 Using the -r/--rev option, revert the given files or directories
3297 3298 to their contents as of a specific revision. This can be helpful
3298 3299 to "roll back" some or all of an earlier change. See :hg:`help
3299 3300 dates` for a list of formats valid for -d/--date.
3300 3301
3301 3302 Revert modifies the working directory. It does not commit any
3302 3303 changes, or change the parent of the working directory. If you
3303 3304 revert to a revision other than the parent of the working
3304 3305 directory, the reverted files will thus appear modified
3305 3306 afterwards.
3306 3307
3307 3308 If a file has been deleted, it is restored. If the executable mode
3308 3309 of a file was changed, it is reset.
3309 3310
3310 3311 If names are given, all files matching the names are reverted.
3311 3312 If no arguments are given, no files are reverted.
3312 3313
3313 3314 Modified files are saved with a .orig suffix before reverting.
3314 3315 To disable these backups, use --no-backup.
3315 3316
3316 3317 Returns 0 on success.
3317 3318 """
3318 3319
3319 3320 if opts.get("date"):
3320 3321 if opts.get("rev"):
3321 3322 raise util.Abort(_("you can't specify a revision and a date"))
3322 3323 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
3323 3324
3324 3325 parent, p2 = repo.dirstate.parents()
3325 3326 if not opts.get('rev') and p2 != nullid:
3326 3327 raise util.Abort(_('uncommitted merge - '
3327 3328 'use "hg update", see "hg help revert"'))
3328 3329
3329 3330 if not pats and not opts.get('all'):
3330 3331 raise util.Abort(_('no files or directories specified; '
3331 3332 'use --all to revert the whole repo'))
3332 3333
3333 3334 ctx = cmdutil.revsingle(repo, opts.get('rev'))
3334 3335 node = ctx.node()
3335 3336 mf = ctx.manifest()
3336 3337 if node == parent:
3337 3338 pmf = mf
3338 3339 else:
3339 3340 pmf = None
3340 3341
3341 3342 # need all matching names in dirstate and manifest of target rev,
3342 3343 # so have to walk both. do not print errors if files exist in one
3343 3344 # but not other.
3344 3345
3345 3346 names = {}
3346 3347
3347 3348 wlock = repo.wlock()
3348 3349 try:
3349 3350 # walk dirstate.
3350 3351
3351 3352 m = cmdutil.match(repo, pats, opts)
3352 3353 m.bad = lambda x, y: False
3353 3354 for abs in repo.walk(m):
3354 3355 names[abs] = m.rel(abs), m.exact(abs)
3355 3356
3356 3357 # walk target manifest.
3357 3358
3358 3359 def badfn(path, msg):
3359 3360 if path in names:
3360 3361 return
3361 3362 path_ = path + '/'
3362 3363 for f in names:
3363 3364 if f.startswith(path_):
3364 3365 return
3365 3366 ui.warn("%s: %s\n" % (m.rel(path), msg))
3366 3367
3367 3368 m = cmdutil.match(repo, pats, opts)
3368 3369 m.bad = badfn
3369 3370 for abs in repo[node].walk(m):
3370 3371 if abs not in names:
3371 3372 names[abs] = m.rel(abs), m.exact(abs)
3372 3373
3373 3374 m = cmdutil.matchfiles(repo, names)
3374 3375 changes = repo.status(match=m)[:4]
3375 3376 modified, added, removed, deleted = map(set, changes)
3376 3377
3377 3378 # if f is a rename, also revert the source
3378 3379 cwd = repo.getcwd()
3379 3380 for f in added:
3380 3381 src = repo.dirstate.copied(f)
3381 3382 if src and src not in names and repo.dirstate[src] == 'r':
3382 3383 removed.add(src)
3383 3384 names[src] = (repo.pathto(src, cwd), True)
3384 3385
3385 3386 def removeforget(abs):
3386 3387 if repo.dirstate[abs] == 'a':
3387 3388 return _('forgetting %s\n')
3388 3389 return _('removing %s\n')
3389 3390
3390 3391 revert = ([], _('reverting %s\n'))
3391 3392 add = ([], _('adding %s\n'))
3392 3393 remove = ([], removeforget)
3393 3394 undelete = ([], _('undeleting %s\n'))
3394 3395
3395 3396 disptable = (
3396 3397 # dispatch table:
3397 3398 # file state
3398 3399 # action if in target manifest
3399 3400 # action if not in target manifest
3400 3401 # make backup if in target manifest
3401 3402 # make backup if not in target manifest
3402 3403 (modified, revert, remove, True, True),
3403 3404 (added, revert, remove, True, False),
3404 3405 (removed, undelete, None, False, False),
3405 3406 (deleted, revert, remove, False, False),
3406 3407 )
3407 3408
3408 3409 for abs, (rel, exact) in sorted(names.items()):
3409 3410 mfentry = mf.get(abs)
3410 3411 target = repo.wjoin(abs)
3411 3412 def handle(xlist, dobackup):
3412 3413 xlist[0].append(abs)
3413 3414 if (dobackup and not opts.get('no_backup') and
3414 3415 os.path.lexists(target)):
3415 3416 bakname = "%s.orig" % rel
3416 3417 ui.note(_('saving current version of %s as %s\n') %
3417 3418 (rel, bakname))
3418 3419 if not opts.get('dry_run'):
3419 3420 util.rename(target, bakname)
3420 3421 if ui.verbose or not exact:
3421 3422 msg = xlist[1]
3422 3423 if not isinstance(msg, basestring):
3423 3424 msg = msg(abs)
3424 3425 ui.status(msg % rel)
3425 3426 for table, hitlist, misslist, backuphit, backupmiss in disptable:
3426 3427 if abs not in table:
3427 3428 continue
3428 3429 # file has changed in dirstate
3429 3430 if mfentry:
3430 3431 handle(hitlist, backuphit)
3431 3432 elif misslist is not None:
3432 3433 handle(misslist, backupmiss)
3433 3434 break
3434 3435 else:
3435 3436 if abs not in repo.dirstate:
3436 3437 if mfentry:
3437 3438 handle(add, True)
3438 3439 elif exact:
3439 3440 ui.warn(_('file not managed: %s\n') % rel)
3440 3441 continue
3441 3442 # file has not changed in dirstate
3442 3443 if node == parent:
3443 3444 if exact:
3444 3445 ui.warn(_('no changes needed to %s\n') % rel)
3445 3446 continue
3446 3447 if pmf is None:
3447 3448 # only need parent manifest in this unlikely case,
3448 3449 # so do not read by default
3449 3450 pmf = repo[parent].manifest()
3450 3451 if abs in pmf:
3451 3452 if mfentry:
3452 3453 # if version of file is same in parent and target
3453 3454 # manifests, do nothing
3454 3455 if (pmf[abs] != mfentry or
3455 3456 pmf.flags(abs) != mf.flags(abs)):
3456 3457 handle(revert, False)
3457 3458 else:
3458 3459 handle(remove, False)
3459 3460
3460 3461 if not opts.get('dry_run'):
3461 3462 def checkout(f):
3462 3463 fc = ctx[f]
3463 3464 repo.wwrite(f, fc.data(), fc.flags())
3464 3465
3465 3466 audit_path = util.path_auditor(repo.root)
3466 3467 for f in remove[0]:
3467 3468 if repo.dirstate[f] == 'a':
3468 3469 repo.dirstate.forget(f)
3469 3470 continue
3470 3471 audit_path(f)
3471 3472 try:
3472 3473 util.unlinkpath(repo.wjoin(f))
3473 3474 except OSError:
3474 3475 pass
3475 3476 repo.dirstate.remove(f)
3476 3477
3477 3478 normal = None
3478 3479 if node == parent:
3479 3480 # We're reverting to our parent. If possible, we'd like status
3480 3481 # to report the file as clean. We have to use normallookup for
3481 3482 # merges to avoid losing information about merged/dirty files.
3482 3483 if p2 != nullid:
3483 3484 normal = repo.dirstate.normallookup
3484 3485 else:
3485 3486 normal = repo.dirstate.normal
3486 3487 for f in revert[0]:
3487 3488 checkout(f)
3488 3489 if normal:
3489 3490 normal(f)
3490 3491
3491 3492 for f in add[0]:
3492 3493 checkout(f)
3493 3494 repo.dirstate.add(f)
3494 3495
3495 3496 normal = repo.dirstate.normallookup
3496 3497 if node == parent and p2 == nullid:
3497 3498 normal = repo.dirstate.normal
3498 3499 for f in undelete[0]:
3499 3500 checkout(f)
3500 3501 normal(f)
3501 3502
3502 3503 finally:
3503 3504 wlock.release()
3504 3505
3505 3506 def rollback(ui, repo, **opts):
3506 3507 """roll back the last transaction (dangerous)
3507 3508
3508 3509 This command should be used with care. There is only one level of
3509 3510 rollback, and there is no way to undo a rollback. It will also
3510 3511 restore the dirstate at the time of the last transaction, losing
3511 3512 any dirstate changes since that time. This command does not alter
3512 3513 the working directory.
3513 3514
3514 3515 Transactions are used to encapsulate the effects of all commands
3515 3516 that create new changesets or propagate existing changesets into a
3516 3517 repository. For example, the following commands are transactional,
3517 3518 and their effects can be rolled back:
3518 3519
3519 3520 - commit
3520 3521 - import
3521 3522 - pull
3522 3523 - push (with this repository as the destination)
3523 3524 - unbundle
3524 3525
3525 3526 This command is not intended for use on public repositories. Once
3526 3527 changes are visible for pull by other users, rolling a transaction
3527 3528 back locally is ineffective (someone else may already have pulled
3528 3529 the changes). Furthermore, a race is possible with readers of the
3529 3530 repository; for example an in-progress pull from the repository
3530 3531 may fail if a rollback is performed.
3531 3532
3532 3533 Returns 0 on success, 1 if no rollback data is available.
3533 3534 """
3534 3535 return repo.rollback(opts.get('dry_run'))
3535 3536
3536 3537 def root(ui, repo):
3537 3538 """print the root (top) of the current working directory
3538 3539
3539 3540 Print the root directory of the current repository.
3540 3541
3541 3542 Returns 0 on success.
3542 3543 """
3543 3544 ui.write(repo.root + "\n")
3544 3545
3545 3546 def serve(ui, repo, **opts):
3546 3547 """start stand-alone webserver
3547 3548
3548 3549 Start a local HTTP repository browser and pull server. You can use
3549 3550 this for ad-hoc sharing and browsing of repositories. It is
3550 3551 recommended to use a real web server to serve a repository for
3551 3552 longer periods of time.
3552 3553
3553 3554 Please note that the server does not implement access control.
3554 3555 This means that, by default, anybody can read from the server and
3555 3556 nobody can write to it by default. Set the ``web.allow_push``
3556 3557 option to ``*`` to allow everybody to push to the server. You
3557 3558 should use a real web server if you need to authenticate users.
3558 3559
3559 3560 By default, the server logs accesses to stdout and errors to
3560 3561 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
3561 3562 files.
3562 3563
3563 3564 To have the server choose a free port number to listen on, specify
3564 3565 a port number of 0; in this case, the server will print the port
3565 3566 number it uses.
3566 3567
3567 3568 Returns 0 on success.
3568 3569 """
3569 3570
3570 3571 if opts["stdio"]:
3571 3572 if repo is None:
3572 3573 raise error.RepoError(_("There is no Mercurial repository here"
3573 3574 " (.hg not found)"))
3574 3575 s = sshserver.sshserver(ui, repo)
3575 3576 s.serve_forever()
3576 3577
3577 3578 # this way we can check if something was given in the command-line
3578 3579 if opts.get('port'):
3579 3580 opts['port'] = util.getport(opts.get('port'))
3580 3581
3581 3582 baseui = repo and repo.baseui or ui
3582 3583 optlist = ("name templates style address port prefix ipv6"
3583 3584 " accesslog errorlog certificate encoding")
3584 3585 for o in optlist.split():
3585 3586 val = opts.get(o, '')
3586 3587 if val in (None, ''): # should check against default options instead
3587 3588 continue
3588 3589 baseui.setconfig("web", o, val)
3589 3590 if repo and repo.ui != baseui:
3590 3591 repo.ui.setconfig("web", o, val)
3591 3592
3592 3593 o = opts.get('web_conf') or opts.get('webdir_conf')
3593 3594 if not o:
3594 3595 if not repo:
3595 3596 raise error.RepoError(_("There is no Mercurial repository"
3596 3597 " here (.hg not found)"))
3597 3598 o = repo.root
3598 3599
3599 3600 app = hgweb.hgweb(o, baseui=ui)
3600 3601
3601 3602 class service(object):
3602 3603 def init(self):
3603 3604 util.set_signal_handler()
3604 3605 self.httpd = hgweb.server.create_server(ui, app)
3605 3606
3606 3607 if opts['port'] and not ui.verbose:
3607 3608 return
3608 3609
3609 3610 if self.httpd.prefix:
3610 3611 prefix = self.httpd.prefix.strip('/') + '/'
3611 3612 else:
3612 3613 prefix = ''
3613 3614
3614 3615 port = ':%d' % self.httpd.port
3615 3616 if port == ':80':
3616 3617 port = ''
3617 3618
3618 3619 bindaddr = self.httpd.addr
3619 3620 if bindaddr == '0.0.0.0':
3620 3621 bindaddr = '*'
3621 3622 elif ':' in bindaddr: # IPv6
3622 3623 bindaddr = '[%s]' % bindaddr
3623 3624
3624 3625 fqaddr = self.httpd.fqaddr
3625 3626 if ':' in fqaddr:
3626 3627 fqaddr = '[%s]' % fqaddr
3627 3628 if opts['port']:
3628 3629 write = ui.status
3629 3630 else:
3630 3631 write = ui.write
3631 3632 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
3632 3633 (fqaddr, port, prefix, bindaddr, self.httpd.port))
3633 3634
3634 3635 def run(self):
3635 3636 self.httpd.serve_forever()
3636 3637
3637 3638 service = service()
3638 3639
3639 3640 cmdutil.service(opts, initfn=service.init, runfn=service.run)
3640 3641
3641 3642 def status(ui, repo, *pats, **opts):
3642 3643 """show changed files in the working directory
3643 3644
3644 3645 Show status of files in the repository. If names are given, only
3645 3646 files that match are shown. Files that are clean or ignored or
3646 3647 the source of a copy/move operation, are not listed unless
3647 3648 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
3648 3649 Unless options described with "show only ..." are given, the
3649 3650 options -mardu are used.
3650 3651
3651 3652 Option -q/--quiet hides untracked (unknown and ignored) files
3652 3653 unless explicitly requested with -u/--unknown or -i/--ignored.
3653 3654
3654 3655 .. note::
3655 3656 status may appear to disagree with diff if permissions have
3656 3657 changed or a merge has occurred. The standard diff format does
3657 3658 not report permission changes and diff only reports changes
3658 3659 relative to one merge parent.
3659 3660
3660 3661 If one revision is given, it is used as the base revision.
3661 3662 If two revisions are given, the differences between them are
3662 3663 shown. The --change option can also be used as a shortcut to list
3663 3664 the changed files of a revision from its first parent.
3664 3665
3665 3666 The codes used to show the status of files are::
3666 3667
3667 3668 M = modified
3668 3669 A = added
3669 3670 R = removed
3670 3671 C = clean
3671 3672 ! = missing (deleted by non-hg command, but still tracked)
3672 3673 ? = not tracked
3673 3674 I = ignored
3674 3675 = origin of the previous file listed as A (added)
3675 3676
3676 3677 Returns 0 on success.
3677 3678 """
3678 3679
3679 3680 revs = opts.get('rev')
3680 3681 change = opts.get('change')
3681 3682
3682 3683 if revs and change:
3683 3684 msg = _('cannot specify --rev and --change at the same time')
3684 3685 raise util.Abort(msg)
3685 3686 elif change:
3686 3687 node2 = repo.lookup(change)
3687 3688 node1 = repo[node2].parents()[0].node()
3688 3689 else:
3689 3690 node1, node2 = cmdutil.revpair(repo, revs)
3690 3691
3691 3692 cwd = (pats and repo.getcwd()) or ''
3692 3693 end = opts.get('print0') and '\0' or '\n'
3693 3694 copy = {}
3694 3695 states = 'modified added removed deleted unknown ignored clean'.split()
3695 3696 show = [k for k in states if opts.get(k)]
3696 3697 if opts.get('all'):
3697 3698 show += ui.quiet and (states[:4] + ['clean']) or states
3698 3699 if not show:
3699 3700 show = ui.quiet and states[:4] or states[:5]
3700 3701
3701 3702 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
3702 3703 'ignored' in show, 'clean' in show, 'unknown' in show,
3703 3704 opts.get('subrepos'))
3704 3705 changestates = zip(states, 'MAR!?IC', stat)
3705 3706
3706 3707 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
3707 3708 ctxn = repo[nullid]
3708 3709 ctx1 = repo[node1]
3709 3710 ctx2 = repo[node2]
3710 3711 added = stat[1]
3711 3712 if node2 is None:
3712 3713 added = stat[0] + stat[1] # merged?
3713 3714
3714 3715 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
3715 3716 if k in added:
3716 3717 copy[k] = v
3717 3718 elif v in added:
3718 3719 copy[v] = k
3719 3720
3720 3721 for state, char, files in changestates:
3721 3722 if state in show:
3722 3723 format = "%s %%s%s" % (char, end)
3723 3724 if opts.get('no_status'):
3724 3725 format = "%%s%s" % end
3725 3726
3726 3727 for f in files:
3727 3728 ui.write(format % repo.pathto(f, cwd),
3728 3729 label='status.' + state)
3729 3730 if f in copy:
3730 3731 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
3731 3732 label='status.copied')
3732 3733
3733 3734 def summary(ui, repo, **opts):
3734 3735 """summarize working directory state
3735 3736
3736 3737 This generates a brief summary of the working directory state,
3737 3738 including parents, branch, commit status, and available updates.
3738 3739
3739 3740 With the --remote option, this will check the default paths for
3740 3741 incoming and outgoing changes. This can be time-consuming.
3741 3742
3742 3743 Returns 0 on success.
3743 3744 """
3744 3745
3745 3746 ctx = repo[None]
3746 3747 parents = ctx.parents()
3747 3748 pnode = parents[0].node()
3748 3749
3749 3750 for p in parents:
3750 3751 # label with log.changeset (instead of log.parent) since this
3751 3752 # shows a working directory parent *changeset*:
3752 3753 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
3753 3754 label='log.changeset')
3754 3755 ui.write(' '.join(p.tags()), label='log.tag')
3755 3756 if p.bookmarks():
3756 3757 ui.write(' ' + ' '.join(p.bookmarks()), label='log.bookmark')
3757 3758 if p.rev() == -1:
3758 3759 if not len(repo):
3759 3760 ui.write(_(' (empty repository)'))
3760 3761 else:
3761 3762 ui.write(_(' (no revision checked out)'))
3762 3763 ui.write('\n')
3763 3764 if p.description():
3764 3765 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
3765 3766 label='log.summary')
3766 3767
3767 3768 branch = ctx.branch()
3768 3769 bheads = repo.branchheads(branch)
3769 3770 m = _('branch: %s\n') % branch
3770 3771 if branch != 'default':
3771 3772 ui.write(m, label='log.branch')
3772 3773 else:
3773 3774 ui.status(m, label='log.branch')
3774 3775
3775 3776 st = list(repo.status(unknown=True))[:6]
3776 3777
3777 3778 c = repo.dirstate.copies()
3778 3779 copied, renamed = [], []
3779 3780 for d, s in c.iteritems():
3780 3781 if s in st[2]:
3781 3782 st[2].remove(s)
3782 3783 renamed.append(d)
3783 3784 else:
3784 3785 copied.append(d)
3785 3786 if d in st[1]:
3786 3787 st[1].remove(d)
3787 3788 st.insert(3, renamed)
3788 3789 st.insert(4, copied)
3789 3790
3790 3791 ms = mergemod.mergestate(repo)
3791 3792 st.append([f for f in ms if ms[f] == 'u'])
3792 3793
3793 3794 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
3794 3795 st.append(subs)
3795 3796
3796 3797 labels = [ui.label(_('%d modified'), 'status.modified'),
3797 3798 ui.label(_('%d added'), 'status.added'),
3798 3799 ui.label(_('%d removed'), 'status.removed'),
3799 3800 ui.label(_('%d renamed'), 'status.copied'),
3800 3801 ui.label(_('%d copied'), 'status.copied'),
3801 3802 ui.label(_('%d deleted'), 'status.deleted'),
3802 3803 ui.label(_('%d unknown'), 'status.unknown'),
3803 3804 ui.label(_('%d ignored'), 'status.ignored'),
3804 3805 ui.label(_('%d unresolved'), 'resolve.unresolved'),
3805 3806 ui.label(_('%d subrepos'), 'status.modified')]
3806 3807 t = []
3807 3808 for s, l in zip(st, labels):
3808 3809 if s:
3809 3810 t.append(l % len(s))
3810 3811
3811 3812 t = ', '.join(t)
3812 3813 cleanworkdir = False
3813 3814
3814 3815 if len(parents) > 1:
3815 3816 t += _(' (merge)')
3816 3817 elif branch != parents[0].branch():
3817 3818 t += _(' (new branch)')
3818 3819 elif (parents[0].extra().get('close') and
3819 3820 pnode in repo.branchheads(branch, closed=True)):
3820 3821 t += _(' (head closed)')
3821 3822 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
3822 3823 t += _(' (clean)')
3823 3824 cleanworkdir = True
3824 3825 elif pnode not in bheads:
3825 3826 t += _(' (new branch head)')
3826 3827
3827 3828 if cleanworkdir:
3828 3829 ui.status(_('commit: %s\n') % t.strip())
3829 3830 else:
3830 3831 ui.write(_('commit: %s\n') % t.strip())
3831 3832
3832 3833 # all ancestors of branch heads - all ancestors of parent = new csets
3833 3834 new = [0] * len(repo)
3834 3835 cl = repo.changelog
3835 3836 for a in [cl.rev(n) for n in bheads]:
3836 3837 new[a] = 1
3837 3838 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
3838 3839 new[a] = 1
3839 3840 for a in [p.rev() for p in parents]:
3840 3841 if a >= 0:
3841 3842 new[a] = 0
3842 3843 for a in cl.ancestors(*[p.rev() for p in parents]):
3843 3844 new[a] = 0
3844 3845 new = sum(new)
3845 3846
3846 3847 if new == 0:
3847 3848 ui.status(_('update: (current)\n'))
3848 3849 elif pnode not in bheads:
3849 3850 ui.write(_('update: %d new changesets (update)\n') % new)
3850 3851 else:
3851 3852 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
3852 3853 (new, len(bheads)))
3853 3854
3854 3855 if opts.get('remote'):
3855 3856 t = []
3856 3857 source, branches = hg.parseurl(ui.expandpath('default'))
3857 3858 other = hg.repository(hg.remoteui(repo, {}), source)
3858 3859 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3859 3860 ui.debug('comparing with %s\n' % url.hidepassword(source))
3860 3861 repo.ui.pushbuffer()
3861 3862 common, incoming, rheads = discovery.findcommonincoming(repo, other)
3862 3863 repo.ui.popbuffer()
3863 3864 if incoming:
3864 3865 t.append(_('1 or more incoming'))
3865 3866
3866 3867 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
3867 3868 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3868 3869 other = hg.repository(hg.remoteui(repo, {}), dest)
3869 3870 ui.debug('comparing with %s\n' % url.hidepassword(dest))
3870 3871 repo.ui.pushbuffer()
3871 3872 o = discovery.findoutgoing(repo, other)
3872 3873 repo.ui.popbuffer()
3873 3874 o = repo.changelog.nodesbetween(o, None)[0]
3874 3875 if o:
3875 3876 t.append(_('%d outgoing') % len(o))
3876 3877 if 'bookmarks' in other.listkeys('namespaces'):
3877 3878 lmarks = repo.listkeys('bookmarks')
3878 3879 rmarks = other.listkeys('bookmarks')
3879 3880 diff = set(rmarks) - set(lmarks)
3880 3881 if len(diff) > 0:
3881 3882 t.append(_('%d incoming bookmarks') % len(diff))
3882 3883 diff = set(lmarks) - set(rmarks)
3883 3884 if len(diff) > 0:
3884 3885 t.append(_('%d outgoing bookmarks') % len(diff))
3885 3886
3886 3887 if t:
3887 3888 ui.write(_('remote: %s\n') % (', '.join(t)))
3888 3889 else:
3889 3890 ui.status(_('remote: (synced)\n'))
3890 3891
3891 3892 def tag(ui, repo, name1, *names, **opts):
3892 3893 """add one or more tags for the current or given revision
3893 3894
3894 3895 Name a particular revision using <name>.
3895 3896
3896 3897 Tags are used to name particular revisions of the repository and are
3897 3898 very useful to compare different revisions, to go back to significant
3898 3899 earlier versions or to mark branch points as releases, etc. Changing
3899 3900 an existing tag is normally disallowed; use -f/--force to override.
3900 3901
3901 3902 If no revision is given, the parent of the working directory is
3902 3903 used, or tip if no revision is checked out.
3903 3904
3904 3905 To facilitate version control, distribution, and merging of tags,
3905 3906 they are stored as a file named ".hgtags" which is managed similarly
3906 3907 to other project files and can be hand-edited if necessary. This
3907 3908 also means that tagging creates a new commit. The file
3908 3909 ".hg/localtags" is used for local tags (not shared among
3909 3910 repositories).
3910 3911
3911 3912 Tag commits are usually made at the head of a branch. If the parent
3912 3913 of the working directory is not a branch head, :hg:`tag` aborts; use
3913 3914 -f/--force to force the tag commit to be based on a non-head
3914 3915 changeset.
3915 3916
3916 3917 See :hg:`help dates` for a list of formats valid for -d/--date.
3917 3918
3918 3919 Since tag names have priority over branch names during revision
3919 3920 lookup, using an existing branch name as a tag name is discouraged.
3920 3921
3921 3922 Returns 0 on success.
3922 3923 """
3923 3924
3924 3925 rev_ = "."
3925 3926 names = [t.strip() for t in (name1,) + names]
3926 3927 if len(names) != len(set(names)):
3927 3928 raise util.Abort(_('tag names must be unique'))
3928 3929 for n in names:
3929 3930 if n in ['tip', '.', 'null']:
3930 3931 raise util.Abort(_('the name \'%s\' is reserved') % n)
3931 3932 if not n:
3932 3933 raise util.Abort(_('tag names cannot consist entirely of whitespace'))
3933 3934 if opts.get('rev') and opts.get('remove'):
3934 3935 raise util.Abort(_("--rev and --remove are incompatible"))
3935 3936 if opts.get('rev'):
3936 3937 rev_ = opts['rev']
3937 3938 message = opts.get('message')
3938 3939 if opts.get('remove'):
3939 3940 expectedtype = opts.get('local') and 'local' or 'global'
3940 3941 for n in names:
3941 3942 if not repo.tagtype(n):
3942 3943 raise util.Abort(_('tag \'%s\' does not exist') % n)
3943 3944 if repo.tagtype(n) != expectedtype:
3944 3945 if expectedtype == 'global':
3945 3946 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
3946 3947 else:
3947 3948 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
3948 3949 rev_ = nullid
3949 3950 if not message:
3950 3951 # we don't translate commit messages
3951 3952 message = 'Removed tag %s' % ', '.join(names)
3952 3953 elif not opts.get('force'):
3953 3954 for n in names:
3954 3955 if n in repo.tags():
3955 3956 raise util.Abort(_('tag \'%s\' already exists '
3956 3957 '(use -f to force)') % n)
3957 3958 if not opts.get('local'):
3958 3959 p1, p2 = repo.dirstate.parents()
3959 3960 if p2 != nullid:
3960 3961 raise util.Abort(_('uncommitted merge'))
3961 3962 bheads = repo.branchheads()
3962 3963 if not opts.get('force') and bheads and p1 not in bheads:
3963 3964 raise util.Abort(_('not at a branch head (use -f to force)'))
3964 3965 r = cmdutil.revsingle(repo, rev_).node()
3965 3966
3966 3967 if not message:
3967 3968 # we don't translate commit messages
3968 3969 message = ('Added tag %s for changeset %s' %
3969 3970 (', '.join(names), short(r)))
3970 3971
3971 3972 date = opts.get('date')
3972 3973 if date:
3973 3974 date = util.parsedate(date)
3974 3975
3975 3976 if opts.get('edit'):
3976 3977 message = ui.edit(message, ui.username())
3977 3978
3978 3979 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
3979 3980
3980 3981 def tags(ui, repo):
3981 3982 """list repository tags
3982 3983
3983 3984 This lists both regular and local tags. When the -v/--verbose
3984 3985 switch is used, a third column "local" is printed for local tags.
3985 3986
3986 3987 Returns 0 on success.
3987 3988 """
3988 3989
3989 3990 hexfunc = ui.debugflag and hex or short
3990 3991 tagtype = ""
3991 3992
3992 3993 for t, n in reversed(repo.tagslist()):
3993 3994 if ui.quiet:
3994 3995 ui.write("%s\n" % t)
3995 3996 continue
3996 3997
3997 3998 try:
3998 3999 hn = hexfunc(n)
3999 4000 r = "%5d:%s" % (repo.changelog.rev(n), hn)
4000 4001 except error.LookupError:
4001 4002 r = " ?:%s" % hn
4002 4003 else:
4003 4004 spaces = " " * (30 - encoding.colwidth(t))
4004 4005 if ui.verbose:
4005 4006 if repo.tagtype(t) == 'local':
4006 4007 tagtype = " local"
4007 4008 else:
4008 4009 tagtype = ""
4009 4010 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
4010 4011
4011 4012 def tip(ui, repo, **opts):
4012 4013 """show the tip revision
4013 4014
4014 4015 The tip revision (usually just called the tip) is the changeset
4015 4016 most recently added to the repository (and therefore the most
4016 4017 recently changed head).
4017 4018
4018 4019 If you have just made a commit, that commit will be the tip. If
4019 4020 you have just pulled changes from another repository, the tip of
4020 4021 that repository becomes the current tip. The "tip" tag is special
4021 4022 and cannot be renamed or assigned to a different changeset.
4022 4023
4023 4024 Returns 0 on success.
4024 4025 """
4025 4026 displayer = cmdutil.show_changeset(ui, repo, opts)
4026 4027 displayer.show(repo[len(repo) - 1])
4027 4028 displayer.close()
4028 4029
4029 4030 def unbundle(ui, repo, fname1, *fnames, **opts):
4030 4031 """apply one or more changegroup files
4031 4032
4032 4033 Apply one or more compressed changegroup files generated by the
4033 4034 bundle command.
4034 4035
4035 4036 Returns 0 on success, 1 if an update has unresolved files.
4036 4037 """
4037 4038 fnames = (fname1,) + fnames
4038 4039
4039 4040 lock = repo.lock()
4040 4041 try:
4041 4042 for fname in fnames:
4042 4043 f = url.open(ui, fname)
4043 4044 gen = changegroup.readbundle(f, fname)
4044 4045 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname,
4045 4046 lock=lock)
4046 4047 finally:
4047 4048 lock.release()
4048 4049
4049 4050 return postincoming(ui, repo, modheads, opts.get('update'), None)
4050 4051
4051 4052 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
4052 4053 """update working directory (or switch revisions)
4053 4054
4054 4055 Update the repository's working directory to the specified
4055 4056 changeset. If no changeset is specified, update to the tip of the
4056 4057 current named branch.
4057 4058
4058 4059 If the changeset is not a descendant of the working directory's
4059 4060 parent, the update is aborted. With the -c/--check option, the
4060 4061 working directory is checked for uncommitted changes; if none are
4061 4062 found, the working directory is updated to the specified
4062 4063 changeset.
4063 4064
4064 4065 The following rules apply when the working directory contains
4065 4066 uncommitted changes:
4066 4067
4067 4068 1. If neither -c/--check nor -C/--clean is specified, and if
4068 4069 the requested changeset is an ancestor or descendant of
4069 4070 the working directory's parent, the uncommitted changes
4070 4071 are merged into the requested changeset and the merged
4071 4072 result is left uncommitted. If the requested changeset is
4072 4073 not an ancestor or descendant (that is, it is on another
4073 4074 branch), the update is aborted and the uncommitted changes
4074 4075 are preserved.
4075 4076
4076 4077 2. With the -c/--check option, the update is aborted and the
4077 4078 uncommitted changes are preserved.
4078 4079
4079 4080 3. With the -C/--clean option, uncommitted changes are discarded and
4080 4081 the working directory is updated to the requested changeset.
4081 4082
4082 4083 Use null as the changeset to remove the working directory (like
4083 4084 :hg:`clone -U`).
4084 4085
4085 4086 If you want to update just one file to an older changeset, use
4086 4087 :hg:`revert`.
4087 4088
4088 4089 See :hg:`help dates` for a list of formats valid for -d/--date.
4089 4090
4090 4091 Returns 0 on success, 1 if there are unresolved files.
4091 4092 """
4092 4093 if rev and node:
4093 4094 raise util.Abort(_("please specify just one revision"))
4094 4095
4095 4096 if rev is None or rev == '':
4096 4097 rev = node
4097 4098
4098 4099 # if we defined a bookmark, we have to remember the original bookmark name
4099 4100 brev = rev
4100 4101 rev = cmdutil.revsingle(repo, rev, rev).rev()
4101 4102
4102 4103 if check and clean:
4103 4104 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
4104 4105
4105 4106 if check:
4106 4107 # we could use dirty() but we can ignore merge and branch trivia
4107 4108 c = repo[None]
4108 4109 if c.modified() or c.added() or c.removed():
4109 4110 raise util.Abort(_("uncommitted local changes"))
4110 4111
4111 4112 if date:
4112 4113 if rev:
4113 4114 raise util.Abort(_("you can't specify a revision and a date"))
4114 4115 rev = cmdutil.finddate(ui, repo, date)
4115 4116
4116 4117 if clean or check:
4117 4118 ret = hg.clean(repo, rev)
4118 4119 else:
4119 4120 ret = hg.update(repo, rev)
4120 4121
4121 4122 if brev in repo._bookmarks:
4122 4123 bookmarks.setcurrent(repo, brev)
4123 4124
4124 4125 return ret
4125 4126
4126 4127 def verify(ui, repo):
4127 4128 """verify the integrity of the repository
4128 4129
4129 4130 Verify the integrity of the current repository.
4130 4131
4131 4132 This will perform an extensive check of the repository's
4132 4133 integrity, validating the hashes and checksums of each entry in
4133 4134 the changelog, manifest, and tracked files, as well as the
4134 4135 integrity of their crosslinks and indices.
4135 4136
4136 4137 Returns 0 on success, 1 if errors are encountered.
4137 4138 """
4138 4139 return hg.verify(repo)
4139 4140
4140 4141 def version_(ui):
4141 4142 """output version and copyright information"""
4142 4143 ui.write(_("Mercurial Distributed SCM (version %s)\n")
4143 4144 % util.version())
4144 4145 ui.status(_(
4145 4146 "(see http://mercurial.selenic.com for more information)\n"
4146 4147 "\nCopyright (C) 2005-2011 Matt Mackall and others\n"
4147 4148 "This is free software; see the source for copying conditions. "
4148 4149 "There is NO\nwarranty; "
4149 4150 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
4150 4151 ))
4151 4152
4152 4153 # Command options and aliases are listed here, alphabetically
4153 4154
4154 4155 globalopts = [
4155 4156 ('R', 'repository', '',
4156 4157 _('repository root directory or name of overlay bundle file'),
4157 4158 _('REPO')),
4158 4159 ('', 'cwd', '',
4159 4160 _('change working directory'), _('DIR')),
4160 4161 ('y', 'noninteractive', None,
4161 4162 _('do not prompt, assume \'yes\' for any required answers')),
4162 4163 ('q', 'quiet', None, _('suppress output')),
4163 4164 ('v', 'verbose', None, _('enable additional output')),
4164 4165 ('', 'config', [],
4165 4166 _('set/override config option (use \'section.name=value\')'),
4166 4167 _('CONFIG')),
4167 4168 ('', 'debug', None, _('enable debugging output')),
4168 4169 ('', 'debugger', None, _('start debugger')),
4169 4170 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
4170 4171 _('ENCODE')),
4171 4172 ('', 'encodingmode', encoding.encodingmode,
4172 4173 _('set the charset encoding mode'), _('MODE')),
4173 4174 ('', 'traceback', None, _('always print a traceback on exception')),
4174 4175 ('', 'time', None, _('time how long the command takes')),
4175 4176 ('', 'profile', None, _('print command execution profile')),
4176 4177 ('', 'version', None, _('output version information and exit')),
4177 4178 ('h', 'help', None, _('display help and exit')),
4178 4179 ]
4179 4180
4180 4181 dryrunopts = [('n', 'dry-run', None,
4181 4182 _('do not perform actions, just print output'))]
4182 4183
4183 4184 remoteopts = [
4184 4185 ('e', 'ssh', '',
4185 4186 _('specify ssh command to use'), _('CMD')),
4186 4187 ('', 'remotecmd', '',
4187 4188 _('specify hg command to run on the remote side'), _('CMD')),
4188 4189 ('', 'insecure', None,
4189 4190 _('do not verify server certificate (ignoring web.cacerts config)')),
4190 4191 ]
4191 4192
4192 4193 walkopts = [
4193 4194 ('I', 'include', [],
4194 4195 _('include names matching the given patterns'), _('PATTERN')),
4195 4196 ('X', 'exclude', [],
4196 4197 _('exclude names matching the given patterns'), _('PATTERN')),
4197 4198 ]
4198 4199
4199 4200 commitopts = [
4200 4201 ('m', 'message', '',
4201 4202 _('use text as commit message'), _('TEXT')),
4202 4203 ('l', 'logfile', '',
4203 4204 _('read commit message from file'), _('FILE')),
4204 4205 ]
4205 4206
4206 4207 commitopts2 = [
4207 4208 ('d', 'date', '',
4208 4209 _('record datecode as commit date'), _('DATE')),
4209 4210 ('u', 'user', '',
4210 4211 _('record the specified user as committer'), _('USER')),
4211 4212 ]
4212 4213
4213 4214 templateopts = [
4214 4215 ('', 'style', '',
4215 4216 _('display using template map file'), _('STYLE')),
4216 4217 ('', 'template', '',
4217 4218 _('display with template'), _('TEMPLATE')),
4218 4219 ]
4219 4220
4220 4221 logopts = [
4221 4222 ('p', 'patch', None, _('show patch')),
4222 4223 ('g', 'git', None, _('use git extended diff format')),
4223 4224 ('l', 'limit', '',
4224 4225 _('limit number of changes displayed'), _('NUM')),
4225 4226 ('M', 'no-merges', None, _('do not show merges')),
4226 4227 ('', 'stat', None, _('output diffstat-style summary of changes')),
4227 4228 ] + templateopts
4228 4229
4229 4230 diffopts = [
4230 4231 ('a', 'text', None, _('treat all files as text')),
4231 4232 ('g', 'git', None, _('use git extended diff format')),
4232 4233 ('', 'nodates', None, _('omit dates from diff headers'))
4233 4234 ]
4234 4235
4235 4236 diffopts2 = [
4236 4237 ('p', 'show-function', None, _('show which function each change is in')),
4237 4238 ('', 'reverse', None, _('produce a diff that undoes the changes')),
4238 4239 ('w', 'ignore-all-space', None,
4239 4240 _('ignore white space when comparing lines')),
4240 4241 ('b', 'ignore-space-change', None,
4241 4242 _('ignore changes in the amount of white space')),
4242 4243 ('B', 'ignore-blank-lines', None,
4243 4244 _('ignore changes whose lines are all blank')),
4244 4245 ('U', 'unified', '',
4245 4246 _('number of lines of context to show'), _('NUM')),
4246 4247 ('', 'stat', None, _('output diffstat-style summary of changes')),
4247 4248 ]
4248 4249
4249 4250 similarityopts = [
4250 4251 ('s', 'similarity', '',
4251 4252 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
4252 4253 ]
4253 4254
4254 4255 subrepoopts = [
4255 4256 ('S', 'subrepos', None,
4256 4257 _('recurse into subrepositories'))
4257 4258 ]
4258 4259
4259 4260 table = {
4260 4261 "^add": (add, walkopts + subrepoopts + dryrunopts,
4261 4262 _('[OPTION]... [FILE]...')),
4262 4263 "addremove":
4263 4264 (addremove, similarityopts + walkopts + dryrunopts,
4264 4265 _('[OPTION]... [FILE]...')),
4265 4266 "^annotate|blame":
4266 4267 (annotate,
4267 4268 [('r', 'rev', '',
4268 4269 _('annotate the specified revision'), _('REV')),
4269 4270 ('', 'follow', None,
4270 4271 _('follow copies/renames and list the filename (DEPRECATED)')),
4271 4272 ('', 'no-follow', None, _("don't follow copies and renames")),
4272 4273 ('a', 'text', None, _('treat all files as text')),
4273 4274 ('u', 'user', None, _('list the author (long with -v)')),
4274 4275 ('f', 'file', None, _('list the filename')),
4275 4276 ('d', 'date', None, _('list the date (short with -q)')),
4276 4277 ('n', 'number', None, _('list the revision number (default)')),
4277 4278 ('c', 'changeset', None, _('list the changeset')),
4278 4279 ('l', 'line-number', None,
4279 4280 _('show line number at the first appearance'))
4280 4281 ] + walkopts,
4281 4282 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
4282 4283 "archive":
4283 4284 (archive,
4284 4285 [('', 'no-decode', None, _('do not pass files through decoders')),
4285 4286 ('p', 'prefix', '',
4286 4287 _('directory prefix for files in archive'), _('PREFIX')),
4287 4288 ('r', 'rev', '',
4288 4289 _('revision to distribute'), _('REV')),
4289 4290 ('t', 'type', '',
4290 4291 _('type of distribution to create'), _('TYPE')),
4291 4292 ] + subrepoopts + walkopts,
4292 4293 _('[OPTION]... DEST')),
4293 4294 "backout":
4294 4295 (backout,
4295 4296 [('', 'merge', None,
4296 4297 _('merge with old dirstate parent after backout')),
4297 4298 ('', 'parent', '',
4298 4299 _('parent to choose when backing out merge'), _('REV')),
4299 4300 ('t', 'tool', '',
4300 4301 _('specify merge tool')),
4301 4302 ('r', 'rev', '',
4302 4303 _('revision to backout'), _('REV')),
4303 4304 ] + walkopts + commitopts + commitopts2,
4304 4305 _('[OPTION]... [-r] REV')),
4305 4306 "bisect":
4306 4307 (bisect,
4307 4308 [('r', 'reset', False, _('reset bisect state')),
4308 4309 ('g', 'good', False, _('mark changeset good')),
4309 4310 ('b', 'bad', False, _('mark changeset bad')),
4310 4311 ('s', 'skip', False, _('skip testing changeset')),
4311 4312 ('e', 'extend', False, _('extend the bisect range')),
4312 4313 ('c', 'command', '',
4313 4314 _('use command to check changeset state'), _('CMD')),
4314 4315 ('U', 'noupdate', False, _('do not update to target'))],
4315 4316 _("[-gbsr] [-U] [-c CMD] [REV]")),
4316 4317 "bookmarks":
4317 4318 (bookmark,
4318 4319 [('f', 'force', False, _('force')),
4319 4320 ('r', 'rev', '', _('revision'), _('REV')),
4320 4321 ('d', 'delete', False, _('delete a given bookmark')),
4321 4322 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
4322 4323 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
4323 4324 "branch":
4324 4325 (branch,
4325 4326 [('f', 'force', None,
4326 4327 _('set branch name even if it shadows an existing branch')),
4327 4328 ('C', 'clean', None, _('reset branch name to parent branch name'))],
4328 4329 _('[-fC] [NAME]')),
4329 4330 "branches":
4330 4331 (branches,
4331 4332 [('a', 'active', False,
4332 4333 _('show only branches that have unmerged heads')),
4333 4334 ('c', 'closed', False,
4334 4335 _('show normal and closed branches'))],
4335 4336 _('[-ac]')),
4336 4337 "bundle":
4337 4338 (bundle,
4338 4339 [('f', 'force', None,
4339 4340 _('run even when the destination is unrelated')),
4340 4341 ('r', 'rev', [],
4341 4342 _('a changeset intended to be added to the destination'),
4342 4343 _('REV')),
4343 4344 ('b', 'branch', [],
4344 4345 _('a specific branch you would like to bundle'),
4345 4346 _('BRANCH')),
4346 4347 ('', 'base', [],
4347 4348 _('a base changeset assumed to be available at the destination'),
4348 4349 _('REV')),
4349 4350 ('a', 'all', None, _('bundle all changesets in the repository')),
4350 4351 ('t', 'type', 'bzip2',
4351 4352 _('bundle compression type to use'), _('TYPE')),
4352 4353 ] + remoteopts,
4353 4354 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
4354 4355 "cat":
4355 4356 (cat,
4356 4357 [('o', 'output', '',
4357 4358 _('print output to file with formatted name'), _('FORMAT')),
4358 4359 ('r', 'rev', '',
4359 4360 _('print the given revision'), _('REV')),
4360 4361 ('', 'decode', None, _('apply any matching decode filter')),
4361 4362 ] + walkopts,
4362 4363 _('[OPTION]... FILE...')),
4363 4364 "^clone":
4364 4365 (clone,
4365 4366 [('U', 'noupdate', None,
4366 4367 _('the clone will include an empty working copy (only a repository)')),
4367 4368 ('u', 'updaterev', '',
4368 4369 _('revision, tag or branch to check out'), _('REV')),
4369 4370 ('r', 'rev', [],
4370 4371 _('include the specified changeset'), _('REV')),
4371 4372 ('b', 'branch', [],
4372 4373 _('clone only the specified branch'), _('BRANCH')),
4373 4374 ('', 'pull', None, _('use pull protocol to copy metadata')),
4374 4375 ('', 'uncompressed', None,
4375 4376 _('use uncompressed transfer (fast over LAN)')),
4376 4377 ] + remoteopts,
4377 4378 _('[OPTION]... SOURCE [DEST]')),
4378 4379 "^commit|ci":
4379 4380 (commit,
4380 4381 [('A', 'addremove', None,
4381 4382 _('mark new/missing files as added/removed before committing')),
4382 4383 ('', 'close-branch', None,
4383 4384 _('mark a branch as closed, hiding it from the branch list')),
4384 4385 ] + walkopts + commitopts + commitopts2,
4385 4386 _('[OPTION]... [FILE]...')),
4386 4387 "copy|cp":
4387 4388 (copy,
4388 4389 [('A', 'after', None, _('record a copy that has already occurred')),
4389 4390 ('f', 'force', None,
4390 4391 _('forcibly copy over an existing managed file')),
4391 4392 ] + walkopts + dryrunopts,
4392 4393 _('[OPTION]... [SOURCE]... DEST')),
4393 4394 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
4394 4395 "debugbuilddag":
4395 4396 (debugbuilddag,
4396 4397 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
4397 4398 ('a', 'appended-file', None, _('add single file all revs append to')),
4398 4399 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
4399 4400 ('n', 'new-file', None, _('add new file at each rev')),
4400 4401 ],
4401 4402 _('[OPTION]... TEXT')),
4402 4403 "debugcheckstate": (debugcheckstate, [], ''),
4403 4404 "debugcommands": (debugcommands, [], _('[COMMAND]')),
4404 4405 "debugcomplete":
4405 4406 (debugcomplete,
4406 4407 [('o', 'options', None, _('show the command options'))],
4407 4408 _('[-o] CMD')),
4408 4409 "debugdag":
4409 4410 (debugdag,
4410 4411 [('t', 'tags', None, _('use tags as labels')),
4411 4412 ('b', 'branches', None, _('annotate with branch names')),
4412 4413 ('', 'dots', None, _('use dots for runs')),
4413 4414 ('s', 'spaces', None, _('separate elements by spaces')),
4414 4415 ],
4415 4416 _('[OPTION]... [FILE [REV]...]')),
4416 4417 "debugdate":
4417 4418 (debugdate,
4418 4419 [('e', 'extended', None, _('try extended date formats'))],
4419 4420 _('[-e] DATE [RANGE]')),
4420 4421 "debugdata": (debugdata, [], _('FILE REV')),
4421 4422 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
4422 4423 "debugignore": (debugignore, [], ''),
4423 4424 "debugindex": (debugindex,
4424 4425 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
4425 4426 _('FILE')),
4426 4427 "debugindexdot": (debugindexdot, [], _('FILE')),
4427 4428 "debuginstall": (debuginstall, [], ''),
4428 4429 "debugpushkey": (debugpushkey, [], _('REPO NAMESPACE [KEY OLD NEW]')),
4429 4430 "debugrebuildstate":
4430 4431 (debugrebuildstate,
4431 4432 [('r', 'rev', '',
4432 4433 _('revision to rebuild to'), _('REV'))],
4433 4434 _('[-r REV] [REV]')),
4434 4435 "debugrename":
4435 4436 (debugrename,
4436 4437 [('r', 'rev', '',
4437 4438 _('revision to debug'), _('REV'))],
4438 4439 _('[-r REV] FILE')),
4439 4440 "debugrevspec":
4440 4441 (debugrevspec, [], ('REVSPEC')),
4441 4442 "debugsetparents":
4442 4443 (debugsetparents, [], _('REV1 [REV2]')),
4443 4444 "debugstate":
4444 4445 (debugstate,
4445 4446 [('', 'nodates', None, _('do not display the saved mtime'))],
4446 4447 _('[OPTION]...')),
4447 4448 "debugsub":
4448 4449 (debugsub,
4449 4450 [('r', 'rev', '',
4450 4451 _('revision to check'), _('REV'))],
4451 4452 _('[-r REV] [REV]')),
4452 4453 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
4453 4454 "^diff":
4454 4455 (diff,
4455 4456 [('r', 'rev', [],
4456 4457 _('revision'), _('REV')),
4457 4458 ('c', 'change', '',
4458 4459 _('change made by revision'), _('REV'))
4459 4460 ] + diffopts + diffopts2 + walkopts + subrepoopts,
4460 4461 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...')),
4461 4462 "^export":
4462 4463 (export,
4463 4464 [('o', 'output', '',
4464 4465 _('print output to file with formatted name'), _('FORMAT')),
4465 4466 ('', 'switch-parent', None, _('diff against the second parent')),
4466 4467 ('r', 'rev', [],
4467 4468 _('revisions to export'), _('REV')),
4468 4469 ] + diffopts,
4469 4470 _('[OPTION]... [-o OUTFILESPEC] REV...')),
4470 4471 "^forget":
4471 4472 (forget,
4472 4473 [] + walkopts,
4473 4474 _('[OPTION]... FILE...')),
4474 4475 "grep":
4475 4476 (grep,
4476 4477 [('0', 'print0', None, _('end fields with NUL')),
4477 4478 ('', 'all', None, _('print all revisions that match')),
4478 4479 ('f', 'follow', None,
4479 4480 _('follow changeset history,'
4480 4481 ' or file history across copies and renames')),
4481 4482 ('i', 'ignore-case', None, _('ignore case when matching')),
4482 4483 ('l', 'files-with-matches', None,
4483 4484 _('print only filenames and revisions that match')),
4484 4485 ('n', 'line-number', None, _('print matching line numbers')),
4485 4486 ('r', 'rev', [],
4486 4487 _('only search files changed within revision range'), _('REV')),
4487 4488 ('u', 'user', None, _('list the author (long with -v)')),
4488 4489 ('d', 'date', None, _('list the date (short with -q)')),
4489 4490 ] + walkopts,
4490 4491 _('[OPTION]... PATTERN [FILE]...')),
4491 4492 "heads":
4492 4493 (heads,
4493 4494 [('r', 'rev', '',
4494 4495 _('show only heads which are descendants of STARTREV'),
4495 4496 _('STARTREV')),
4496 4497 ('t', 'topo', False, _('show topological heads only')),
4497 4498 ('a', 'active', False,
4498 4499 _('show active branchheads only (DEPRECATED)')),
4499 4500 ('c', 'closed', False,
4500 4501 _('show normal and closed branch heads')),
4501 4502 ] + templateopts,
4502 4503 _('[-ac] [-r STARTREV] [REV]...')),
4503 4504 "help": (help_, [], _('[TOPIC]')),
4504 4505 "identify|id":
4505 4506 (identify,
4506 4507 [('r', 'rev', '',
4507 4508 _('identify the specified revision'), _('REV')),
4508 4509 ('n', 'num', None, _('show local revision number')),
4509 4510 ('i', 'id', None, _('show global revision id')),
4510 4511 ('b', 'branch', None, _('show branch')),
4511 4512 ('t', 'tags', None, _('show tags')),
4512 4513 ('B', 'bookmarks', None, _('show bookmarks'))],
4513 4514 _('[-nibtB] [-r REV] [SOURCE]')),
4514 4515 "import|patch":
4515 4516 (import_,
4516 4517 [('p', 'strip', 1,
4517 4518 _('directory strip option for patch. This has the same '
4518 4519 'meaning as the corresponding patch option'),
4519 4520 _('NUM')),
4520 4521 ('b', 'base', '',
4521 4522 _('base path'), _('PATH')),
4522 4523 ('f', 'force', None,
4523 4524 _('skip check for outstanding uncommitted changes')),
4524 4525 ('', 'no-commit', None,
4525 4526 _("don't commit, just update the working directory")),
4526 4527 ('', 'exact', None,
4527 4528 _('apply patch to the nodes from which it was generated')),
4528 4529 ('', 'import-branch', None,
4529 4530 _('use any branch information in patch (implied by --exact)'))] +
4530 4531 commitopts + commitopts2 + similarityopts,
4531 4532 _('[OPTION]... PATCH...')),
4532 4533 "incoming|in":
4533 4534 (incoming,
4534 4535 [('f', 'force', None,
4535 4536 _('run even if remote repository is unrelated')),
4536 4537 ('n', 'newest-first', None, _('show newest record first')),
4537 4538 ('', 'bundle', '',
4538 4539 _('file to store the bundles into'), _('FILE')),
4539 4540 ('r', 'rev', [],
4540 4541 _('a remote changeset intended to be added'), _('REV')),
4541 4542 ('B', 'bookmarks', False, _("compare bookmarks")),
4542 4543 ('b', 'branch', [],
4543 4544 _('a specific branch you would like to pull'), _('BRANCH')),
4544 4545 ] + logopts + remoteopts + subrepoopts,
4545 4546 _('[-p] [-n] [-M] [-f] [-r REV]...'
4546 4547 ' [--bundle FILENAME] [SOURCE]')),
4547 4548 "^init":
4548 4549 (init,
4549 4550 remoteopts,
4550 4551 _('[-e CMD] [--remotecmd CMD] [DEST]')),
4551 4552 "locate":
4552 4553 (locate,
4553 4554 [('r', 'rev', '',
4554 4555 _('search the repository as it is in REV'), _('REV')),
4555 4556 ('0', 'print0', None,
4556 4557 _('end filenames with NUL, for use with xargs')),
4557 4558 ('f', 'fullpath', None,
4558 4559 _('print complete paths from the filesystem root')),
4559 4560 ] + walkopts,
4560 4561 _('[OPTION]... [PATTERN]...')),
4561 4562 "^log|history":
4562 4563 (log,
4563 4564 [('f', 'follow', None,
4564 4565 _('follow changeset history,'
4565 4566 ' or file history across copies and renames')),
4566 4567 ('', 'follow-first', None,
4567 4568 _('only follow the first parent of merge changesets')),
4568 4569 ('d', 'date', '',
4569 4570 _('show revisions matching date spec'), _('DATE')),
4570 4571 ('C', 'copies', None, _('show copied files')),
4571 4572 ('k', 'keyword', [],
4572 4573 _('do case-insensitive search for a given text'), _('TEXT')),
4573 4574 ('r', 'rev', [],
4574 4575 _('show the specified revision or range'), _('REV')),
4575 4576 ('', 'removed', None, _('include revisions where files were removed')),
4576 4577 ('m', 'only-merges', None, _('show only merges')),
4577 4578 ('u', 'user', [],
4578 4579 _('revisions committed by user'), _('USER')),
4579 4580 ('', 'only-branch', [],
4580 4581 _('show only changesets within the given named branch (DEPRECATED)'),
4581 4582 _('BRANCH')),
4582 4583 ('b', 'branch', [],
4583 4584 _('show changesets within the given named branch'), _('BRANCH')),
4584 4585 ('P', 'prune', [],
4585 4586 _('do not display revision or any of its ancestors'), _('REV')),
4586 4587 ] + logopts + walkopts,
4587 4588 _('[OPTION]... [FILE]')),
4588 4589 "manifest":
4589 4590 (manifest,
4590 4591 [('r', 'rev', '',
4591 4592 _('revision to display'), _('REV'))],
4592 4593 _('[-r REV]')),
4593 4594 "^merge":
4594 4595 (merge,
4595 4596 [('f', 'force', None, _('force a merge with outstanding changes')),
4596 4597 ('t', 'tool', '', _('specify merge tool')),
4597 4598 ('r', 'rev', '',
4598 4599 _('revision to merge'), _('REV')),
4599 4600 ('P', 'preview', None,
4600 4601 _('review revisions to merge (no merge is performed)'))],
4601 4602 _('[-P] [-f] [[-r] REV]')),
4602 4603 "outgoing|out":
4603 4604 (outgoing,
4604 4605 [('f', 'force', None,
4605 4606 _('run even when the destination is unrelated')),
4606 4607 ('r', 'rev', [],
4607 4608 _('a changeset intended to be included in the destination'),
4608 4609 _('REV')),
4609 4610 ('n', 'newest-first', None, _('show newest record first')),
4610 4611 ('B', 'bookmarks', False, _("compare bookmarks")),
4611 4612 ('b', 'branch', [],
4612 4613 _('a specific branch you would like to push'), _('BRANCH')),
4613 4614 ] + logopts + remoteopts + subrepoopts,
4614 4615 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
4615 4616 "parents":
4616 4617 (parents,
4617 4618 [('r', 'rev', '',
4618 4619 _('show parents of the specified revision'), _('REV')),
4619 4620 ] + templateopts,
4620 4621 _('[-r REV] [FILE]')),
4621 4622 "paths": (paths, [], _('[NAME]')),
4622 4623 "^pull":
4623 4624 (pull,
4624 4625 [('u', 'update', None,
4625 4626 _('update to new branch head if changesets were pulled')),
4626 4627 ('f', 'force', None,
4627 4628 _('run even when remote repository is unrelated')),
4628 4629 ('r', 'rev', [],
4629 4630 _('a remote changeset intended to be added'), _('REV')),
4630 4631 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4631 4632 ('b', 'branch', [],
4632 4633 _('a specific branch you would like to pull'), _('BRANCH')),
4633 4634 ] + remoteopts,
4634 4635 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
4635 4636 "^push":
4636 4637 (push,
4637 4638 [('f', 'force', None, _('force push')),
4638 4639 ('r', 'rev', [],
4639 4640 _('a changeset intended to be included in the destination'),
4640 4641 _('REV')),
4641 4642 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4642 4643 ('b', 'branch', [],
4643 4644 _('a specific branch you would like to push'), _('BRANCH')),
4644 4645 ('', 'new-branch', False, _('allow pushing a new branch')),
4645 4646 ] + remoteopts,
4646 4647 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
4647 4648 "recover": (recover, []),
4648 4649 "^remove|rm":
4649 4650 (remove,
4650 4651 [('A', 'after', None, _('record delete for missing files')),
4651 4652 ('f', 'force', None,
4652 4653 _('remove (and delete) file even if added or modified')),
4653 4654 ] + walkopts,
4654 4655 _('[OPTION]... FILE...')),
4655 4656 "rename|move|mv":
4656 4657 (rename,
4657 4658 [('A', 'after', None, _('record a rename that has already occurred')),
4658 4659 ('f', 'force', None,
4659 4660 _('forcibly copy over an existing managed file')),
4660 4661 ] + walkopts + dryrunopts,
4661 4662 _('[OPTION]... SOURCE... DEST')),
4662 4663 "resolve":
4663 4664 (resolve,
4664 4665 [('a', 'all', None, _('select all unresolved files')),
4665 4666 ('l', 'list', None, _('list state of files needing merge')),
4666 4667 ('m', 'mark', None, _('mark files as resolved')),
4667 4668 ('u', 'unmark', None, _('mark files as unresolved')),
4668 4669 ('t', 'tool', '', _('specify merge tool')),
4669 4670 ('n', 'no-status', None, _('hide status prefix'))]
4670 4671 + walkopts,
4671 4672 _('[OPTION]... [FILE]...')),
4672 4673 "revert":
4673 4674 (revert,
4674 4675 [('a', 'all', None, _('revert all changes when no arguments given')),
4675 4676 ('d', 'date', '',
4676 4677 _('tipmost revision matching date'), _('DATE')),
4677 4678 ('r', 'rev', '',
4678 4679 _('revert to the specified revision'), _('REV')),
4679 4680 ('', 'no-backup', None, _('do not save backup copies of files')),
4680 4681 ] + walkopts + dryrunopts,
4681 4682 _('[OPTION]... [-r REV] [NAME]...')),
4682 4683 "rollback": (rollback, dryrunopts),
4683 4684 "root": (root, []),
4684 4685 "^serve":
4685 4686 (serve,
4686 4687 [('A', 'accesslog', '',
4687 4688 _('name of access log file to write to'), _('FILE')),
4688 4689 ('d', 'daemon', None, _('run server in background')),
4689 4690 ('', 'daemon-pipefds', '',
4690 4691 _('used internally by daemon mode'), _('NUM')),
4691 4692 ('E', 'errorlog', '',
4692 4693 _('name of error log file to write to'), _('FILE')),
4693 4694 # use string type, then we can check if something was passed
4694 4695 ('p', 'port', '',
4695 4696 _('port to listen on (default: 8000)'), _('PORT')),
4696 4697 ('a', 'address', '',
4697 4698 _('address to listen on (default: all interfaces)'), _('ADDR')),
4698 4699 ('', 'prefix', '',
4699 4700 _('prefix path to serve from (default: server root)'), _('PREFIX')),
4700 4701 ('n', 'name', '',
4701 4702 _('name to show in web pages (default: working directory)'),
4702 4703 _('NAME')),
4703 4704 ('', 'web-conf', '',
4704 4705 _('name of the hgweb config file (see "hg help hgweb")'),
4705 4706 _('FILE')),
4706 4707 ('', 'webdir-conf', '',
4707 4708 _('name of the hgweb config file (DEPRECATED)'), _('FILE')),
4708 4709 ('', 'pid-file', '',
4709 4710 _('name of file to write process ID to'), _('FILE')),
4710 4711 ('', 'stdio', None, _('for remote clients')),
4711 4712 ('t', 'templates', '',
4712 4713 _('web templates to use'), _('TEMPLATE')),
4713 4714 ('', 'style', '',
4714 4715 _('template style to use'), _('STYLE')),
4715 4716 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4716 4717 ('', 'certificate', '',
4717 4718 _('SSL certificate file'), _('FILE'))],
4718 4719 _('[OPTION]...')),
4719 4720 "showconfig|debugconfig":
4720 4721 (showconfig,
4721 4722 [('u', 'untrusted', None, _('show untrusted configuration options'))],
4722 4723 _('[-u] [NAME]...')),
4723 4724 "^summary|sum":
4724 4725 (summary,
4725 4726 [('', 'remote', None, _('check for push and pull'))], '[--remote]'),
4726 4727 "^status|st":
4727 4728 (status,
4728 4729 [('A', 'all', None, _('show status of all files')),
4729 4730 ('m', 'modified', None, _('show only modified files')),
4730 4731 ('a', 'added', None, _('show only added files')),
4731 4732 ('r', 'removed', None, _('show only removed files')),
4732 4733 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4733 4734 ('c', 'clean', None, _('show only files without changes')),
4734 4735 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4735 4736 ('i', 'ignored', None, _('show only ignored files')),
4736 4737 ('n', 'no-status', None, _('hide status prefix')),
4737 4738 ('C', 'copies', None, _('show source of copied files')),
4738 4739 ('0', 'print0', None,
4739 4740 _('end filenames with NUL, for use with xargs')),
4740 4741 ('', 'rev', [],
4741 4742 _('show difference from revision'), _('REV')),
4742 4743 ('', 'change', '',
4743 4744 _('list the changed files of a revision'), _('REV')),
4744 4745 ] + walkopts + subrepoopts,
4745 4746 _('[OPTION]... [FILE]...')),
4746 4747 "tag":
4747 4748 (tag,
4748 4749 [('f', 'force', None, _('force tag')),
4749 4750 ('l', 'local', None, _('make the tag local')),
4750 4751 ('r', 'rev', '',
4751 4752 _('revision to tag'), _('REV')),
4752 4753 ('', 'remove', None, _('remove a tag')),
4753 4754 # -l/--local is already there, commitopts cannot be used
4754 4755 ('e', 'edit', None, _('edit commit message')),
4755 4756 ('m', 'message', '',
4756 4757 _('use <text> as commit message'), _('TEXT')),
4757 4758 ] + commitopts2,
4758 4759 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
4759 4760 "tags": (tags, [], ''),
4760 4761 "tip":
4761 4762 (tip,
4762 4763 [('p', 'patch', None, _('show patch')),
4763 4764 ('g', 'git', None, _('use git extended diff format')),
4764 4765 ] + templateopts,
4765 4766 _('[-p] [-g]')),
4766 4767 "unbundle":
4767 4768 (unbundle,
4768 4769 [('u', 'update', None,
4769 4770 _('update to new branch head if changesets were unbundled'))],
4770 4771 _('[-u] FILE...')),
4771 4772 "^update|up|checkout|co":
4772 4773 (update,
4773 4774 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
4774 4775 ('c', 'check', None,
4775 4776 _('update across branches if no uncommitted changes')),
4776 4777 ('d', 'date', '',
4777 4778 _('tipmost revision matching date'), _('DATE')),
4778 4779 ('r', 'rev', '',
4779 4780 _('revision'), _('REV'))],
4780 4781 _('[-c] [-C] [-d DATE] [[-r] REV]')),
4781 4782 "verify": (verify, []),
4782 4783 "version": (version_, []),
4783 4784 }
4784 4785
4785 4786 norepo = ("clone init version help debugcommands debugcomplete"
4786 4787 " debugdate debuginstall debugfsinfo debugpushkey")
4787 4788 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
4788 4789 " debugdata debugindex debugindexdot")
@@ -1,2034 +1,2013 b''
1 1 # localrepo.py - read/write repository class for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import bin, hex, nullid, nullrev, short
9 9 from i18n import _
10 10 import repo, changegroup, subrepo, discovery, pushkey
11 11 import changelog, dirstate, filelog, manifest, context, bookmarks
12 12 import lock, transaction, store, encoding
13 13 import util, extensions, hook, error
14 14 import match as matchmod
15 15 import merge as mergemod
16 16 import tags as tagsmod
17 17 import url as urlmod
18 18 from lock import release
19 19 import weakref, errno, os, time, inspect
20 20 propertycache = util.propertycache
21 21
22 22 class localrepository(repo.repository):
23 23 capabilities = set(('lookup', 'changegroupsubset', 'branchmap', 'pushkey'))
24 24 supportedformats = set(('revlogv1', 'parentdelta'))
25 25 supported = supportedformats | set(('store', 'fncache', 'shared',
26 26 'dotencode'))
27 27
28 28 def __init__(self, baseui, path=None, create=0):
29 29 repo.repository.__init__(self)
30 30 self.root = os.path.realpath(util.expandpath(path))
31 31 self.path = os.path.join(self.root, ".hg")
32 32 self.origroot = path
33 33 self.auditor = util.path_auditor(self.root, self._checknested)
34 34 self.opener = util.opener(self.path)
35 35 self.wopener = util.opener(self.root)
36 36 self.baseui = baseui
37 37 self.ui = baseui.copy()
38 38
39 39 try:
40 40 self.ui.readconfig(self.join("hgrc"), self.root)
41 41 extensions.loadall(self.ui)
42 42 except IOError:
43 43 pass
44 44
45 45 if not os.path.isdir(self.path):
46 46 if create:
47 47 if not os.path.exists(path):
48 48 util.makedirs(path)
49 49 os.mkdir(self.path)
50 50 requirements = ["revlogv1"]
51 51 if self.ui.configbool('format', 'usestore', True):
52 52 os.mkdir(os.path.join(self.path, "store"))
53 53 requirements.append("store")
54 54 if self.ui.configbool('format', 'usefncache', True):
55 55 requirements.append("fncache")
56 56 if self.ui.configbool('format', 'dotencode', True):
57 57 requirements.append('dotencode')
58 58 # create an invalid changelog
59 59 self.opener("00changelog.i", "a").write(
60 60 '\0\0\0\2' # represents revlogv2
61 61 ' dummy changelog to prevent using the old repo layout'
62 62 )
63 63 if self.ui.configbool('format', 'parentdelta', False):
64 64 requirements.append("parentdelta")
65 65 else:
66 66 raise error.RepoError(_("repository %s not found") % path)
67 67 elif create:
68 68 raise error.RepoError(_("repository %s already exists") % path)
69 69 else:
70 70 # find requirements
71 71 requirements = set()
72 72 try:
73 73 requirements = set(self.opener("requires").read().splitlines())
74 74 except IOError, inst:
75 75 if inst.errno != errno.ENOENT:
76 76 raise
77 77 for r in requirements - self.supported:
78 78 raise error.RequirementError(
79 79 _("requirement '%s' not supported") % r)
80 80
81 81 self.sharedpath = self.path
82 82 try:
83 83 s = os.path.realpath(self.opener("sharedpath").read())
84 84 if not os.path.exists(s):
85 85 raise error.RepoError(
86 86 _('.hg/sharedpath points to nonexistent directory %s') % s)
87 87 self.sharedpath = s
88 88 except IOError, inst:
89 89 if inst.errno != errno.ENOENT:
90 90 raise
91 91
92 92 self.store = store.store(requirements, self.sharedpath, util.opener)
93 93 self.spath = self.store.path
94 94 self.sopener = self.store.opener
95 95 self.sjoin = self.store.join
96 96 self.opener.createmode = self.store.createmode
97 97 self._applyrequirements(requirements)
98 98 if create:
99 99 self._writerequirements()
100 100
101 101 # These two define the set of tags for this repository. _tags
102 102 # maps tag name to node; _tagtypes maps tag name to 'global' or
103 103 # 'local'. (Global tags are defined by .hgtags across all
104 104 # heads, and local tags are defined in .hg/localtags.) They
105 105 # constitute the in-memory cache of tags.
106 106 self._tags = None
107 107 self._tagtypes = None
108 108
109 109 self._branchcache = None
110 110 self._branchcachetip = None
111 111 self.nodetagscache = None
112 112 self.filterpats = {}
113 113 self._datafilters = {}
114 114 self._transref = self._lockref = self._wlockref = None
115 115
116 116 def _applyrequirements(self, requirements):
117 117 self.requirements = requirements
118 118 self.sopener.options = {}
119 119 if 'parentdelta' in requirements:
120 120 self.sopener.options['parentdelta'] = 1
121 121
122 122 def _writerequirements(self):
123 123 reqfile = self.opener("requires", "w")
124 124 for r in self.requirements:
125 125 reqfile.write("%s\n" % r)
126 126 reqfile.close()
127 127
128 128 def _checknested(self, path):
129 129 """Determine if path is a legal nested repository."""
130 130 if not path.startswith(self.root):
131 131 return False
132 132 subpath = path[len(self.root) + 1:]
133 133
134 134 # XXX: Checking against the current working copy is wrong in
135 135 # the sense that it can reject things like
136 136 #
137 137 # $ hg cat -r 10 sub/x.txt
138 138 #
139 139 # if sub/ is no longer a subrepository in the working copy
140 140 # parent revision.
141 141 #
142 142 # However, it can of course also allow things that would have
143 143 # been rejected before, such as the above cat command if sub/
144 144 # is a subrepository now, but was a normal directory before.
145 145 # The old path auditor would have rejected by mistake since it
146 146 # panics when it sees sub/.hg/.
147 147 #
148 148 # All in all, checking against the working copy seems sensible
149 149 # since we want to prevent access to nested repositories on
150 150 # the filesystem *now*.
151 151 ctx = self[None]
152 152 parts = util.splitpath(subpath)
153 153 while parts:
154 154 prefix = os.sep.join(parts)
155 155 if prefix in ctx.substate:
156 156 if prefix == subpath:
157 157 return True
158 158 else:
159 159 sub = ctx.sub(prefix)
160 160 return sub.checknested(subpath[len(prefix) + 1:])
161 161 else:
162 162 parts.pop()
163 163 return False
164 164
165 165 @util.propertycache
166 166 def _bookmarks(self):
167 167 return bookmarks.read(self)
168 168
169 169 @util.propertycache
170 170 def _bookmarkcurrent(self):
171 171 return bookmarks.readcurrent(self)
172 172
173 173 @propertycache
174 174 def changelog(self):
175 175 c = changelog.changelog(self.sopener)
176 176 if 'HG_PENDING' in os.environ:
177 177 p = os.environ['HG_PENDING']
178 178 if p.startswith(self.root):
179 179 c.readpending('00changelog.i.a')
180 180 self.sopener.options['defversion'] = c.version
181 181 return c
182 182
183 183 @propertycache
184 184 def manifest(self):
185 185 return manifest.manifest(self.sopener)
186 186
187 187 @propertycache
188 188 def dirstate(self):
189 189 warned = [0]
190 190 def validate(node):
191 191 try:
192 192 r = self.changelog.rev(node)
193 193 return node
194 194 except error.LookupError:
195 195 if not warned[0]:
196 196 warned[0] = True
197 197 self.ui.warn(_("warning: ignoring unknown"
198 198 " working parent %s!\n") % short(node))
199 199 return nullid
200 200
201 201 return dirstate.dirstate(self.opener, self.ui, self.root, validate)
202 202
203 203 def __getitem__(self, changeid):
204 204 if changeid is None:
205 205 return context.workingctx(self)
206 206 return context.changectx(self, changeid)
207 207
208 208 def __contains__(self, changeid):
209 209 try:
210 210 return bool(self.lookup(changeid))
211 211 except error.RepoLookupError:
212 212 return False
213 213
214 214 def __nonzero__(self):
215 215 return True
216 216
217 217 def __len__(self):
218 218 return len(self.changelog)
219 219
220 220 def __iter__(self):
221 221 for i in xrange(len(self)):
222 222 yield i
223 223
224 224 def url(self):
225 225 return 'file:' + self.root
226 226
227 227 def hook(self, name, throw=False, **args):
228 228 return hook.hook(self.ui, self, name, throw, **args)
229 229
230 230 tag_disallowed = ':\r\n'
231 231
232 232 def _tag(self, names, node, message, local, user, date, extra={}):
233 233 if isinstance(names, str):
234 234 allchars = names
235 235 names = (names,)
236 236 else:
237 237 allchars = ''.join(names)
238 238 for c in self.tag_disallowed:
239 239 if c in allchars:
240 240 raise util.Abort(_('%r cannot be used in a tag name') % c)
241 241
242 242 branches = self.branchmap()
243 243 for name in names:
244 244 self.hook('pretag', throw=True, node=hex(node), tag=name,
245 245 local=local)
246 246 if name in branches:
247 247 self.ui.warn(_("warning: tag %s conflicts with existing"
248 248 " branch name\n") % name)
249 249
250 250 def writetags(fp, names, munge, prevtags):
251 251 fp.seek(0, 2)
252 252 if prevtags and prevtags[-1] != '\n':
253 253 fp.write('\n')
254 254 for name in names:
255 255 m = munge and munge(name) or name
256 256 if self._tagtypes and name in self._tagtypes:
257 257 old = self._tags.get(name, nullid)
258 258 fp.write('%s %s\n' % (hex(old), m))
259 259 fp.write('%s %s\n' % (hex(node), m))
260 260 fp.close()
261 261
262 262 prevtags = ''
263 263 if local:
264 264 try:
265 265 fp = self.opener('localtags', 'r+')
266 266 except IOError:
267 267 fp = self.opener('localtags', 'a')
268 268 else:
269 269 prevtags = fp.read()
270 270
271 271 # local tags are stored in the current charset
272 272 writetags(fp, names, None, prevtags)
273 273 for name in names:
274 274 self.hook('tag', node=hex(node), tag=name, local=local)
275 275 return
276 276
277 277 try:
278 278 fp = self.wfile('.hgtags', 'rb+')
279 279 except IOError:
280 280 fp = self.wfile('.hgtags', 'ab')
281 281 else:
282 282 prevtags = fp.read()
283 283
284 284 # committed tags are stored in UTF-8
285 285 writetags(fp, names, encoding.fromlocal, prevtags)
286 286
287 287 fp.close()
288 288
289 289 if '.hgtags' not in self.dirstate:
290 290 self[None].add(['.hgtags'])
291 291
292 292 m = matchmod.exact(self.root, '', ['.hgtags'])
293 293 tagnode = self.commit(message, user, date, extra=extra, match=m)
294 294
295 295 for name in names:
296 296 self.hook('tag', node=hex(node), tag=name, local=local)
297 297
298 298 return tagnode
299 299
300 300 def tag(self, names, node, message, local, user, date):
301 301 '''tag a revision with one or more symbolic names.
302 302
303 303 names is a list of strings or, when adding a single tag, names may be a
304 304 string.
305 305
306 306 if local is True, the tags are stored in a per-repository file.
307 307 otherwise, they are stored in the .hgtags file, and a new
308 308 changeset is committed with the change.
309 309
310 310 keyword arguments:
311 311
312 312 local: whether to store tags in non-version-controlled file
313 313 (default False)
314 314
315 315 message: commit message to use if committing
316 316
317 317 user: name of user to use if committing
318 318
319 319 date: date tuple to use if committing'''
320 320
321 321 if not local:
322 322 for x in self.status()[:5]:
323 323 if '.hgtags' in x:
324 324 raise util.Abort(_('working copy of .hgtags is changed '
325 325 '(please commit .hgtags manually)'))
326 326
327 327 self.tags() # instantiate the cache
328 328 self._tag(names, node, message, local, user, date)
329 329
330 330 def tags(self):
331 331 '''return a mapping of tag to node'''
332 332 if self._tags is None:
333 333 (self._tags, self._tagtypes) = self._findtags()
334 334
335 335 return self._tags
336 336
337 337 def _findtags(self):
338 338 '''Do the hard work of finding tags. Return a pair of dicts
339 339 (tags, tagtypes) where tags maps tag name to node, and tagtypes
340 340 maps tag name to a string like \'global\' or \'local\'.
341 341 Subclasses or extensions are free to add their own tags, but
342 342 should be aware that the returned dicts will be retained for the
343 343 duration of the localrepo object.'''
344 344
345 345 # XXX what tagtype should subclasses/extensions use? Currently
346 346 # mq and bookmarks add tags, but do not set the tagtype at all.
347 347 # Should each extension invent its own tag type? Should there
348 348 # be one tagtype for all such "virtual" tags? Or is the status
349 349 # quo fine?
350 350
351 351 alltags = {} # map tag name to (node, hist)
352 352 tagtypes = {}
353 353
354 354 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
355 355 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
356 356
357 357 # Build the return dicts. Have to re-encode tag names because
358 358 # the tags module always uses UTF-8 (in order not to lose info
359 359 # writing to the cache), but the rest of Mercurial wants them in
360 360 # local encoding.
361 361 tags = {}
362 362 for (name, (node, hist)) in alltags.iteritems():
363 363 if node != nullid:
364 364 tags[encoding.tolocal(name)] = node
365 365 tags['tip'] = self.changelog.tip()
366 366 tagtypes = dict([(encoding.tolocal(name), value)
367 367 for (name, value) in tagtypes.iteritems()])
368 368 return (tags, tagtypes)
369 369
370 370 def tagtype(self, tagname):
371 371 '''
372 372 return the type of the given tag. result can be:
373 373
374 374 'local' : a local tag
375 375 'global' : a global tag
376 376 None : tag does not exist
377 377 '''
378 378
379 379 self.tags()
380 380
381 381 return self._tagtypes.get(tagname)
382 382
383 383 def tagslist(self):
384 384 '''return a list of tags ordered by revision'''
385 385 l = []
386 386 for t, n in self.tags().iteritems():
387 387 try:
388 388 r = self.changelog.rev(n)
389 389 except:
390 390 r = -2 # sort to the beginning of the list if unknown
391 391 l.append((r, t, n))
392 392 return [(t, n) for r, t, n in sorted(l)]
393 393
394 394 def nodetags(self, node):
395 395 '''return the tags associated with a node'''
396 396 if not self.nodetagscache:
397 397 self.nodetagscache = {}
398 398 for t, n in self.tags().iteritems():
399 399 self.nodetagscache.setdefault(n, []).append(t)
400 400 for tags in self.nodetagscache.itervalues():
401 401 tags.sort()
402 402 return self.nodetagscache.get(node, [])
403 403
404 404 def nodebookmarks(self, node):
405 405 marks = []
406 406 for bookmark, n in self._bookmarks.iteritems():
407 407 if n == node:
408 408 marks.append(bookmark)
409 409 return sorted(marks)
410 410
411 411 def _branchtags(self, partial, lrev):
412 412 # TODO: rename this function?
413 413 tiprev = len(self) - 1
414 414 if lrev != tiprev:
415 415 ctxgen = (self[r] for r in xrange(lrev + 1, tiprev + 1))
416 416 self._updatebranchcache(partial, ctxgen)
417 417 self._writebranchcache(partial, self.changelog.tip(), tiprev)
418 418
419 419 return partial
420 420
421 421 def updatebranchcache(self):
422 422 tip = self.changelog.tip()
423 423 if self._branchcache is not None and self._branchcachetip == tip:
424 424 return self._branchcache
425 425
426 426 oldtip = self._branchcachetip
427 427 self._branchcachetip = tip
428 428 if oldtip is None or oldtip not in self.changelog.nodemap:
429 429 partial, last, lrev = self._readbranchcache()
430 430 else:
431 431 lrev = self.changelog.rev(oldtip)
432 432 partial = self._branchcache
433 433
434 434 self._branchtags(partial, lrev)
435 435 # this private cache holds all heads (not just tips)
436 436 self._branchcache = partial
437 437
438 438 def branchmap(self):
439 439 '''returns a dictionary {branch: [branchheads]}'''
440 440 self.updatebranchcache()
441 441 return self._branchcache
442 442
443 443 def branchtags(self):
444 444 '''return a dict where branch names map to the tipmost head of
445 445 the branch, open heads come before closed'''
446 446 bt = {}
447 447 for bn, heads in self.branchmap().iteritems():
448 448 tip = heads[-1]
449 449 for h in reversed(heads):
450 450 if 'close' not in self.changelog.read(h)[5]:
451 451 tip = h
452 452 break
453 453 bt[bn] = tip
454 454 return bt
455 455
456 456 def _readbranchcache(self):
457 457 partial = {}
458 458 try:
459 459 f = self.opener("cache/branchheads")
460 460 lines = f.read().split('\n')
461 461 f.close()
462 462 except (IOError, OSError):
463 463 return {}, nullid, nullrev
464 464
465 465 try:
466 466 last, lrev = lines.pop(0).split(" ", 1)
467 467 last, lrev = bin(last), int(lrev)
468 468 if lrev >= len(self) or self[lrev].node() != last:
469 469 # invalidate the cache
470 470 raise ValueError('invalidating branch cache (tip differs)')
471 471 for l in lines:
472 472 if not l:
473 473 continue
474 474 node, label = l.split(" ", 1)
475 475 label = encoding.tolocal(label.strip())
476 476 partial.setdefault(label, []).append(bin(node))
477 477 except KeyboardInterrupt:
478 478 raise
479 479 except Exception, inst:
480 480 if self.ui.debugflag:
481 481 self.ui.warn(str(inst), '\n')
482 482 partial, last, lrev = {}, nullid, nullrev
483 483 return partial, last, lrev
484 484
485 485 def _writebranchcache(self, branches, tip, tiprev):
486 486 try:
487 487 f = self.opener("cache/branchheads", "w", atomictemp=True)
488 488 f.write("%s %s\n" % (hex(tip), tiprev))
489 489 for label, nodes in branches.iteritems():
490 490 for node in nodes:
491 491 f.write("%s %s\n" % (hex(node), encoding.fromlocal(label)))
492 492 f.rename()
493 493 except (IOError, OSError):
494 494 pass
495 495
496 496 def _updatebranchcache(self, partial, ctxgen):
497 497 # collect new branch entries
498 498 newbranches = {}
499 499 for c in ctxgen:
500 500 newbranches.setdefault(c.branch(), []).append(c.node())
501 501 # if older branchheads are reachable from new ones, they aren't
502 502 # really branchheads. Note checking parents is insufficient:
503 503 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
504 504 for branch, newnodes in newbranches.iteritems():
505 505 bheads = partial.setdefault(branch, [])
506 506 bheads.extend(newnodes)
507 507 if len(bheads) <= 1:
508 508 continue
509 509 # starting from tip means fewer passes over reachable
510 510 while newnodes:
511 511 latest = newnodes.pop()
512 512 if latest not in bheads:
513 513 continue
514 514 minbhrev = self[min([self[bh].rev() for bh in bheads])].node()
515 515 reachable = self.changelog.reachable(latest, minbhrev)
516 516 reachable.remove(latest)
517 517 bheads = [b for b in bheads if b not in reachable]
518 518 partial[branch] = bheads
519 519
520 520 def lookup(self, key):
521 521 if isinstance(key, int):
522 522 return self.changelog.node(key)
523 523 elif key == '.':
524 524 return self.dirstate.parents()[0]
525 525 elif key == 'null':
526 526 return nullid
527 527 elif key == 'tip':
528 528 return self.changelog.tip()
529 529 n = self.changelog._match(key)
530 530 if n:
531 531 return n
532 532 if key in self._bookmarks:
533 533 return self._bookmarks[key]
534 534 if key in self.tags():
535 535 return self.tags()[key]
536 536 if key in self.branchtags():
537 537 return self.branchtags()[key]
538 538 n = self.changelog._partialmatch(key)
539 539 if n:
540 540 return n
541 541
542 542 # can't find key, check if it might have come from damaged dirstate
543 543 if key in self.dirstate.parents():
544 544 raise error.Abort(_("working directory has unknown parent '%s'!")
545 545 % short(key))
546 546 try:
547 547 if len(key) == 20:
548 548 key = hex(key)
549 549 except:
550 550 pass
551 551 raise error.RepoLookupError(_("unknown revision '%s'") % key)
552 552
553 553 def lookupbranch(self, key, remote=None):
554 554 repo = remote or self
555 555 if key in repo.branchmap():
556 556 return key
557 557
558 558 repo = (remote and remote.local()) and remote or self
559 559 return repo[key].branch()
560 560
561 561 def local(self):
562 562 return True
563 563
564 564 def join(self, f):
565 565 return os.path.join(self.path, f)
566 566
567 567 def wjoin(self, f):
568 568 return os.path.join(self.root, f)
569 569
570 570 def file(self, f):
571 571 if f[0] == '/':
572 572 f = f[1:]
573 573 return filelog.filelog(self.sopener, f)
574 574
575 575 def changectx(self, changeid):
576 576 return self[changeid]
577 577
578 578 def parents(self, changeid=None):
579 579 '''get list of changectxs for parents of changeid'''
580 580 return self[changeid].parents()
581 581
582 582 def filectx(self, path, changeid=None, fileid=None):
583 583 """changeid can be a changeset revision, node, or tag.
584 584 fileid can be a file revision or node."""
585 585 return context.filectx(self, path, changeid, fileid)
586 586
587 587 def getcwd(self):
588 588 return self.dirstate.getcwd()
589 589
590 590 def pathto(self, f, cwd=None):
591 591 return self.dirstate.pathto(f, cwd)
592 592
593 593 def wfile(self, f, mode='r'):
594 594 return self.wopener(f, mode)
595 595
596 596 def _link(self, f):
597 597 return os.path.islink(self.wjoin(f))
598 598
599 599 def _loadfilter(self, filter):
600 600 if filter not in self.filterpats:
601 601 l = []
602 602 for pat, cmd in self.ui.configitems(filter):
603 603 if cmd == '!':
604 604 continue
605 605 mf = matchmod.match(self.root, '', [pat])
606 606 fn = None
607 607 params = cmd
608 608 for name, filterfn in self._datafilters.iteritems():
609 609 if cmd.startswith(name):
610 610 fn = filterfn
611 611 params = cmd[len(name):].lstrip()
612 612 break
613 613 if not fn:
614 614 fn = lambda s, c, **kwargs: util.filter(s, c)
615 615 # Wrap old filters not supporting keyword arguments
616 616 if not inspect.getargspec(fn)[2]:
617 617 oldfn = fn
618 618 fn = lambda s, c, **kwargs: oldfn(s, c)
619 619 l.append((mf, fn, params))
620 620 self.filterpats[filter] = l
621 621 return self.filterpats[filter]
622 622
623 623 def _filter(self, filterpats, filename, data):
624 624 for mf, fn, cmd in filterpats:
625 625 if mf(filename):
626 626 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
627 627 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
628 628 break
629 629
630 630 return data
631 631
632 632 @propertycache
633 633 def _encodefilterpats(self):
634 634 return self._loadfilter('encode')
635 635
636 636 @propertycache
637 637 def _decodefilterpats(self):
638 638 return self._loadfilter('decode')
639 639
640 640 def adddatafilter(self, name, filter):
641 641 self._datafilters[name] = filter
642 642
643 643 def wread(self, filename):
644 644 if self._link(filename):
645 645 data = os.readlink(self.wjoin(filename))
646 646 else:
647 647 data = self.wopener(filename, 'r').read()
648 648 return self._filter(self._encodefilterpats, filename, data)
649 649
650 650 def wwrite(self, filename, data, flags):
651 651 data = self._filter(self._decodefilterpats, filename, data)
652 652 if 'l' in flags:
653 653 self.wopener.symlink(data, filename)
654 654 else:
655 655 self.wopener(filename, 'w').write(data)
656 656 if 'x' in flags:
657 657 util.set_flags(self.wjoin(filename), False, True)
658 658
659 659 def wwritedata(self, filename, data):
660 660 return self._filter(self._decodefilterpats, filename, data)
661 661
662 662 def transaction(self, desc):
663 663 tr = self._transref and self._transref() or None
664 664 if tr and tr.running():
665 665 return tr.nest()
666 666
667 667 # abort here if the journal already exists
668 668 if os.path.exists(self.sjoin("journal")):
669 669 raise error.RepoError(
670 670 _("abandoned transaction found - run hg recover"))
671 671
672 672 # save dirstate for rollback
673 673 try:
674 674 ds = self.opener("dirstate").read()
675 675 except IOError:
676 676 ds = ""
677 677 self.opener("journal.dirstate", "w").write(ds)
678 678 self.opener("journal.branch", "w").write(
679 679 encoding.fromlocal(self.dirstate.branch()))
680 680 self.opener("journal.desc", "w").write("%d\n%s\n" % (len(self), desc))
681 681
682 682 renames = [(self.sjoin("journal"), self.sjoin("undo")),
683 683 (self.join("journal.dirstate"), self.join("undo.dirstate")),
684 684 (self.join("journal.branch"), self.join("undo.branch")),
685 685 (self.join("journal.desc"), self.join("undo.desc"))]
686 686 tr = transaction.transaction(self.ui.warn, self.sopener,
687 687 self.sjoin("journal"),
688 688 aftertrans(renames),
689 689 self.store.createmode)
690 690 self._transref = weakref.ref(tr)
691 691 return tr
692 692
693 693 def recover(self):
694 694 lock = self.lock()
695 695 try:
696 696 if os.path.exists(self.sjoin("journal")):
697 697 self.ui.status(_("rolling back interrupted transaction\n"))
698 698 transaction.rollback(self.sopener, self.sjoin("journal"),
699 699 self.ui.warn)
700 700 self.invalidate()
701 701 return True
702 702 else:
703 703 self.ui.warn(_("no interrupted transaction available\n"))
704 704 return False
705 705 finally:
706 706 lock.release()
707 707
708 708 def rollback(self, dryrun=False):
709 709 wlock = lock = None
710 710 try:
711 711 wlock = self.wlock()
712 712 lock = self.lock()
713 713 if os.path.exists(self.sjoin("undo")):
714 714 try:
715 715 args = self.opener("undo.desc", "r").read().splitlines()
716 716 if len(args) >= 3 and self.ui.verbose:
717 717 desc = _("repository tip rolled back to revision %s"
718 718 " (undo %s: %s)\n") % (
719 719 int(args[0]) - 1, args[1], args[2])
720 720 elif len(args) >= 2:
721 721 desc = _("repository tip rolled back to revision %s"
722 722 " (undo %s)\n") % (
723 723 int(args[0]) - 1, args[1])
724 724 except IOError:
725 725 desc = _("rolling back unknown transaction\n")
726 726 self.ui.status(desc)
727 727 if dryrun:
728 728 return
729 729 transaction.rollback(self.sopener, self.sjoin("undo"),
730 730 self.ui.warn)
731 731 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
732 732 if os.path.exists(self.join('undo.bookmarks')):
733 733 util.rename(self.join('undo.bookmarks'),
734 734 self.join('bookmarks'))
735 735 try:
736 736 branch = self.opener("undo.branch").read()
737 737 self.dirstate.setbranch(branch)
738 738 except IOError:
739 739 self.ui.warn(_("Named branch could not be reset, "
740 740 "current branch still is: %s\n")
741 741 % self.dirstate.branch())
742 742 self.invalidate()
743 743 self.dirstate.invalidate()
744 744 self.destroyed()
745 745 parents = tuple([p.rev() for p in self.parents()])
746 746 if len(parents) > 1:
747 747 self.ui.status(_("working directory now based on "
748 748 "revisions %d and %d\n") % parents)
749 749 else:
750 750 self.ui.status(_("working directory now based on "
751 751 "revision %d\n") % parents)
752 752 else:
753 753 self.ui.warn(_("no rollback information available\n"))
754 754 return 1
755 755 finally:
756 756 release(lock, wlock)
757 757
758 758 def invalidatecaches(self):
759 759 self._tags = None
760 760 self._tagtypes = None
761 761 self.nodetagscache = None
762 762 self._branchcache = None # in UTF-8
763 763 self._branchcachetip = None
764 764
765 765 def invalidate(self):
766 766 for a in ("changelog", "manifest", "_bookmarks", "_bookmarkcurrent"):
767 767 if a in self.__dict__:
768 768 delattr(self, a)
769 769 self.invalidatecaches()
770 770
771 771 def _lock(self, lockname, wait, releasefn, acquirefn, desc):
772 772 try:
773 773 l = lock.lock(lockname, 0, releasefn, desc=desc)
774 774 except error.LockHeld, inst:
775 775 if not wait:
776 776 raise
777 777 self.ui.warn(_("waiting for lock on %s held by %r\n") %
778 778 (desc, inst.locker))
779 779 # default to 600 seconds timeout
780 780 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
781 781 releasefn, desc=desc)
782 782 if acquirefn:
783 783 acquirefn()
784 784 return l
785 785
786 786 def lock(self, wait=True):
787 787 '''Lock the repository store (.hg/store) and return a weak reference
788 788 to the lock. Use this before modifying the store (e.g. committing or
789 789 stripping). If you are opening a transaction, get a lock as well.)'''
790 790 l = self._lockref and self._lockref()
791 791 if l is not None and l.held:
792 792 l.lock()
793 793 return l
794 794
795 795 l = self._lock(self.sjoin("lock"), wait, self.store.write,
796 796 self.invalidate, _('repository %s') % self.origroot)
797 797 self._lockref = weakref.ref(l)
798 798 return l
799 799
800 800 def wlock(self, wait=True):
801 801 '''Lock the non-store parts of the repository (everything under
802 802 .hg except .hg/store) and return a weak reference to the lock.
803 803 Use this before modifying files in .hg.'''
804 804 l = self._wlockref and self._wlockref()
805 805 if l is not None and l.held:
806 806 l.lock()
807 807 return l
808 808
809 809 l = self._lock(self.join("wlock"), wait, self.dirstate.write,
810 810 self.dirstate.invalidate, _('working directory of %s') %
811 811 self.origroot)
812 812 self._wlockref = weakref.ref(l)
813 813 return l
814 814
815 815 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
816 816 """
817 817 commit an individual file as part of a larger transaction
818 818 """
819 819
820 820 fname = fctx.path()
821 821 text = fctx.data()
822 822 flog = self.file(fname)
823 823 fparent1 = manifest1.get(fname, nullid)
824 824 fparent2 = fparent2o = manifest2.get(fname, nullid)
825 825
826 826 meta = {}
827 827 copy = fctx.renamed()
828 828 if copy and copy[0] != fname:
829 829 # Mark the new revision of this file as a copy of another
830 830 # file. This copy data will effectively act as a parent
831 831 # of this new revision. If this is a merge, the first
832 832 # parent will be the nullid (meaning "look up the copy data")
833 833 # and the second one will be the other parent. For example:
834 834 #
835 835 # 0 --- 1 --- 3 rev1 changes file foo
836 836 # \ / rev2 renames foo to bar and changes it
837 837 # \- 2 -/ rev3 should have bar with all changes and
838 838 # should record that bar descends from
839 839 # bar in rev2 and foo in rev1
840 840 #
841 841 # this allows this merge to succeed:
842 842 #
843 843 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
844 844 # \ / merging rev3 and rev4 should use bar@rev2
845 845 # \- 2 --- 4 as the merge base
846 846 #
847 847
848 848 cfname = copy[0]
849 849 crev = manifest1.get(cfname)
850 850 newfparent = fparent2
851 851
852 852 if manifest2: # branch merge
853 853 if fparent2 == nullid or crev is None: # copied on remote side
854 854 if cfname in manifest2:
855 855 crev = manifest2[cfname]
856 856 newfparent = fparent1
857 857
858 858 # find source in nearest ancestor if we've lost track
859 859 if not crev:
860 860 self.ui.debug(" %s: searching for copy revision for %s\n" %
861 861 (fname, cfname))
862 862 for ancestor in self[None].ancestors():
863 863 if cfname in ancestor:
864 864 crev = ancestor[cfname].filenode()
865 865 break
866 866
867 867 if crev:
868 868 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
869 869 meta["copy"] = cfname
870 870 meta["copyrev"] = hex(crev)
871 871 fparent1, fparent2 = nullid, newfparent
872 872 else:
873 873 self.ui.warn(_("warning: can't find ancestor for '%s' "
874 874 "copied from '%s'!\n") % (fname, cfname))
875 875
876 876 elif fparent2 != nullid:
877 877 # is one parent an ancestor of the other?
878 878 fparentancestor = flog.ancestor(fparent1, fparent2)
879 879 if fparentancestor == fparent1:
880 880 fparent1, fparent2 = fparent2, nullid
881 881 elif fparentancestor == fparent2:
882 882 fparent2 = nullid
883 883
884 884 # is the file changed?
885 885 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
886 886 changelist.append(fname)
887 887 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
888 888
889 889 # are just the flags changed during merge?
890 890 if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags():
891 891 changelist.append(fname)
892 892
893 893 return fparent1
894 894
895 895 def commit(self, text="", user=None, date=None, match=None, force=False,
896 896 editor=False, extra={}):
897 897 """Add a new revision to current repository.
898 898
899 899 Revision information is gathered from the working directory,
900 900 match can be used to filter the committed files. If editor is
901 901 supplied, it is called to get a commit message.
902 902 """
903 903
904 904 def fail(f, msg):
905 905 raise util.Abort('%s: %s' % (f, msg))
906 906
907 907 if not match:
908 908 match = matchmod.always(self.root, '')
909 909
910 910 if not force:
911 911 vdirs = []
912 912 match.dir = vdirs.append
913 913 match.bad = fail
914 914
915 915 wlock = self.wlock()
916 916 try:
917 917 wctx = self[None]
918 918 merge = len(wctx.parents()) > 1
919 919
920 920 if (not force and merge and match and
921 921 (match.files() or match.anypats())):
922 922 raise util.Abort(_('cannot partially commit a merge '
923 923 '(do not specify files or patterns)'))
924 924
925 925 changes = self.status(match=match, clean=force)
926 926 if force:
927 927 changes[0].extend(changes[6]) # mq may commit unchanged files
928 928
929 929 # check subrepos
930 930 subs = []
931 931 removedsubs = set()
932 932 for p in wctx.parents():
933 933 removedsubs.update(s for s in p.substate if match(s))
934 934 for s in wctx.substate:
935 935 removedsubs.discard(s)
936 936 if match(s) and wctx.sub(s).dirty():
937 937 subs.append(s)
938 938 if (subs or removedsubs):
939 939 if (not match('.hgsub') and
940 940 '.hgsub' in (wctx.modified() + wctx.added())):
941 941 raise util.Abort(_("can't commit subrepos without .hgsub"))
942 942 if '.hgsubstate' not in changes[0]:
943 943 changes[0].insert(0, '.hgsubstate')
944 944
945 945 if subs and not self.ui.configbool('ui', 'commitsubrepos', True):
946 946 changedsubs = [s for s in subs if wctx.sub(s).dirty(True)]
947 947 if changedsubs:
948 948 raise util.Abort(_("uncommitted changes in subrepo %s")
949 949 % changedsubs[0])
950 950
951 951 # make sure all explicit patterns are matched
952 952 if not force and match.files():
953 953 matched = set(changes[0] + changes[1] + changes[2])
954 954
955 955 for f in match.files():
956 956 if f == '.' or f in matched or f in wctx.substate:
957 957 continue
958 958 if f in changes[3]: # missing
959 959 fail(f, _('file not found!'))
960 960 if f in vdirs: # visited directory
961 961 d = f + '/'
962 962 for mf in matched:
963 963 if mf.startswith(d):
964 964 break
965 965 else:
966 966 fail(f, _("no match under directory!"))
967 967 elif f not in self.dirstate:
968 968 fail(f, _("file not tracked!"))
969 969
970 970 if (not force and not extra.get("close") and not merge
971 971 and not (changes[0] or changes[1] or changes[2])
972 972 and wctx.branch() == wctx.p1().branch()):
973 973 return None
974 974
975 975 ms = mergemod.mergestate(self)
976 976 for f in changes[0]:
977 977 if f in ms and ms[f] == 'u':
978 978 raise util.Abort(_("unresolved merge conflicts "
979 979 "(see hg help resolve)"))
980 980
981 981 cctx = context.workingctx(self, text, user, date, extra, changes)
982 982 if editor:
983 983 cctx._text = editor(self, cctx, subs)
984 984 edited = (text != cctx._text)
985 985
986 986 # commit subs
987 987 if subs or removedsubs:
988 988 state = wctx.substate.copy()
989 989 for s in sorted(subs):
990 990 sub = wctx.sub(s)
991 991 self.ui.status(_('committing subrepository %s\n') %
992 992 subrepo.subrelpath(sub))
993 993 sr = sub.commit(cctx._text, user, date)
994 994 state[s] = (state[s][0], sr)
995 995 subrepo.writestate(self, state)
996 996
997 997 # Save commit message in case this transaction gets rolled back
998 998 # (e.g. by a pretxncommit hook). Leave the content alone on
999 999 # the assumption that the user will use the same editor again.
1000 1000 msgfile = self.opener('last-message.txt', 'wb')
1001 1001 msgfile.write(cctx._text)
1002 1002 msgfile.close()
1003 1003
1004 1004 p1, p2 = self.dirstate.parents()
1005 1005 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1006 1006 try:
1007 1007 self.hook("precommit", throw=True, parent1=hookp1, parent2=hookp2)
1008 1008 ret = self.commitctx(cctx, True)
1009 1009 except:
1010 1010 if edited:
1011 1011 msgfn = self.pathto(msgfile.name[len(self.root)+1:])
1012 1012 self.ui.write(
1013 1013 _('note: commit message saved in %s\n') % msgfn)
1014 1014 raise
1015 1015
1016 1016 # update bookmarks, dirstate and mergestate
1017 1017 parents = (p1, p2)
1018 1018 if p2 == nullid:
1019 1019 parents = (p1,)
1020 1020 bookmarks.update(self, parents, ret)
1021 1021 for f in changes[0] + changes[1]:
1022 1022 self.dirstate.normal(f)
1023 1023 for f in changes[2]:
1024 1024 self.dirstate.forget(f)
1025 1025 self.dirstate.setparents(ret)
1026 1026 ms.reset()
1027 1027 finally:
1028 1028 wlock.release()
1029 1029
1030 1030 self.hook("commit", node=hex(ret), parent1=hookp1, parent2=hookp2)
1031 1031 return ret
1032 1032
1033 1033 def commitctx(self, ctx, error=False):
1034 1034 """Add a new revision to current repository.
1035 1035 Revision information is passed via the context argument.
1036 1036 """
1037 1037
1038 1038 tr = lock = None
1039 1039 removed = list(ctx.removed())
1040 1040 p1, p2 = ctx.p1(), ctx.p2()
1041 1041 m1 = p1.manifest().copy()
1042 1042 m2 = p2.manifest()
1043 1043 user = ctx.user()
1044 1044
1045 1045 lock = self.lock()
1046 1046 try:
1047 1047 tr = self.transaction("commit")
1048 1048 trp = weakref.proxy(tr)
1049 1049
1050 1050 # check in files
1051 1051 new = {}
1052 1052 changed = []
1053 1053 linkrev = len(self)
1054 1054 for f in sorted(ctx.modified() + ctx.added()):
1055 1055 self.ui.note(f + "\n")
1056 1056 try:
1057 1057 fctx = ctx[f]
1058 1058 new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
1059 1059 changed)
1060 1060 m1.set(f, fctx.flags())
1061 1061 except OSError, inst:
1062 1062 self.ui.warn(_("trouble committing %s!\n") % f)
1063 1063 raise
1064 1064 except IOError, inst:
1065 1065 errcode = getattr(inst, 'errno', errno.ENOENT)
1066 1066 if error or errcode and errcode != errno.ENOENT:
1067 1067 self.ui.warn(_("trouble committing %s!\n") % f)
1068 1068 raise
1069 1069 else:
1070 1070 removed.append(f)
1071 1071
1072 1072 # update manifest
1073 1073 m1.update(new)
1074 1074 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1075 1075 drop = [f for f in removed if f in m1]
1076 1076 for f in drop:
1077 1077 del m1[f]
1078 1078 mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
1079 1079 p2.manifestnode(), (new, drop))
1080 1080
1081 1081 # update changelog
1082 1082 self.changelog.delayupdate()
1083 1083 n = self.changelog.add(mn, changed + removed, ctx.description(),
1084 1084 trp, p1.node(), p2.node(),
1085 1085 user, ctx.date(), ctx.extra().copy())
1086 1086 p = lambda: self.changelog.writepending() and self.root or ""
1087 1087 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1088 1088 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1089 1089 parent2=xp2, pending=p)
1090 1090 self.changelog.finalize(trp)
1091 1091 tr.close()
1092 1092
1093 1093 if self._branchcache:
1094 1094 self.updatebranchcache()
1095 1095 return n
1096 1096 finally:
1097 1097 if tr:
1098 1098 tr.release()
1099 1099 lock.release()
1100 1100
1101 1101 def destroyed(self):
1102 1102 '''Inform the repository that nodes have been destroyed.
1103 1103 Intended for use by strip and rollback, so there's a common
1104 1104 place for anything that has to be done after destroying history.'''
1105 1105 # XXX it might be nice if we could take the list of destroyed
1106 1106 # nodes, but I don't see an easy way for rollback() to do that
1107 1107
1108 1108 # Ensure the persistent tag cache is updated. Doing it now
1109 1109 # means that the tag cache only has to worry about destroyed
1110 1110 # heads immediately after a strip/rollback. That in turn
1111 1111 # guarantees that "cachetip == currenttip" (comparing both rev
1112 1112 # and node) always means no nodes have been added or destroyed.
1113 1113
1114 1114 # XXX this is suboptimal when qrefresh'ing: we strip the current
1115 1115 # head, refresh the tag cache, then immediately add a new head.
1116 1116 # But I think doing it this way is necessary for the "instant
1117 1117 # tag cache retrieval" case to work.
1118 1118 self.invalidatecaches()
1119 1119
1120 1120 def walk(self, match, node=None):
1121 1121 '''
1122 1122 walk recursively through the directory tree or a given
1123 1123 changeset, finding all files matched by the match
1124 1124 function
1125 1125 '''
1126 1126 return self[node].walk(match)
1127 1127
1128 1128 def status(self, node1='.', node2=None, match=None,
1129 1129 ignored=False, clean=False, unknown=False,
1130 1130 listsubrepos=False):
1131 1131 """return status of files between two nodes or node and working directory
1132 1132
1133 1133 If node1 is None, use the first dirstate parent instead.
1134 1134 If node2 is None, compare node1 with working directory.
1135 1135 """
1136 1136
1137 1137 def mfmatches(ctx):
1138 1138 mf = ctx.manifest().copy()
1139 1139 for fn in mf.keys():
1140 1140 if not match(fn):
1141 1141 del mf[fn]
1142 1142 return mf
1143 1143
1144 1144 if isinstance(node1, context.changectx):
1145 1145 ctx1 = node1
1146 1146 else:
1147 1147 ctx1 = self[node1]
1148 1148 if isinstance(node2, context.changectx):
1149 1149 ctx2 = node2
1150 1150 else:
1151 1151 ctx2 = self[node2]
1152 1152
1153 1153 working = ctx2.rev() is None
1154 1154 parentworking = working and ctx1 == self['.']
1155 1155 match = match or matchmod.always(self.root, self.getcwd())
1156 1156 listignored, listclean, listunknown = ignored, clean, unknown
1157 1157
1158 1158 # load earliest manifest first for caching reasons
1159 1159 if not working and ctx2.rev() < ctx1.rev():
1160 1160 ctx2.manifest()
1161 1161
1162 1162 if not parentworking:
1163 1163 def bad(f, msg):
1164 1164 if f not in ctx1:
1165 1165 self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
1166 1166 match.bad = bad
1167 1167
1168 1168 if working: # we need to scan the working dir
1169 1169 subrepos = []
1170 1170 if '.hgsub' in self.dirstate:
1171 1171 subrepos = ctx1.substate.keys()
1172 1172 s = self.dirstate.status(match, subrepos, listignored,
1173 1173 listclean, listunknown)
1174 1174 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
1175 1175
1176 1176 # check for any possibly clean files
1177 1177 if parentworking and cmp:
1178 1178 fixup = []
1179 1179 # do a full compare of any files that might have changed
1180 1180 for f in sorted(cmp):
1181 1181 if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
1182 1182 or ctx1[f].cmp(ctx2[f])):
1183 1183 modified.append(f)
1184 1184 else:
1185 1185 fixup.append(f)
1186 1186
1187 1187 # update dirstate for files that are actually clean
1188 1188 if fixup:
1189 1189 if listclean:
1190 1190 clean += fixup
1191 1191
1192 1192 try:
1193 1193 # updating the dirstate is optional
1194 1194 # so we don't wait on the lock
1195 1195 wlock = self.wlock(False)
1196 1196 try:
1197 1197 for f in fixup:
1198 1198 self.dirstate.normal(f)
1199 1199 finally:
1200 1200 wlock.release()
1201 1201 except error.LockError:
1202 1202 pass
1203 1203
1204 1204 if not parentworking:
1205 1205 mf1 = mfmatches(ctx1)
1206 1206 if working:
1207 1207 # we are comparing working dir against non-parent
1208 1208 # generate a pseudo-manifest for the working dir
1209 1209 mf2 = mfmatches(self['.'])
1210 1210 for f in cmp + modified + added:
1211 1211 mf2[f] = None
1212 1212 mf2.set(f, ctx2.flags(f))
1213 1213 for f in removed:
1214 1214 if f in mf2:
1215 1215 del mf2[f]
1216 1216 else:
1217 1217 # we are comparing two revisions
1218 1218 deleted, unknown, ignored = [], [], []
1219 1219 mf2 = mfmatches(ctx2)
1220 1220
1221 1221 modified, added, clean = [], [], []
1222 1222 for fn in mf2:
1223 1223 if fn in mf1:
1224 1224 if (mf1.flags(fn) != mf2.flags(fn) or
1225 1225 (mf1[fn] != mf2[fn] and
1226 1226 (mf2[fn] or ctx1[fn].cmp(ctx2[fn])))):
1227 1227 modified.append(fn)
1228 1228 elif listclean:
1229 1229 clean.append(fn)
1230 1230 del mf1[fn]
1231 1231 else:
1232 1232 added.append(fn)
1233 1233 removed = mf1.keys()
1234 1234
1235 1235 r = modified, added, removed, deleted, unknown, ignored, clean
1236 1236
1237 1237 if listsubrepos:
1238 1238 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
1239 1239 if working:
1240 1240 rev2 = None
1241 1241 else:
1242 1242 rev2 = ctx2.substate[subpath][1]
1243 1243 try:
1244 1244 submatch = matchmod.narrowmatcher(subpath, match)
1245 1245 s = sub.status(rev2, match=submatch, ignored=listignored,
1246 1246 clean=listclean, unknown=listunknown,
1247 1247 listsubrepos=True)
1248 1248 for rfiles, sfiles in zip(r, s):
1249 1249 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
1250 1250 except error.LookupError:
1251 1251 self.ui.status(_("skipping missing subrepository: %s\n")
1252 1252 % subpath)
1253 1253
1254 1254 for l in r:
1255 1255 l.sort()
1256 1256 return r
1257 1257
1258 1258 def heads(self, start=None):
1259 1259 heads = self.changelog.heads(start)
1260 1260 # sort the output in rev descending order
1261 1261 return sorted(heads, key=self.changelog.rev, reverse=True)
1262 1262
1263 1263 def branchheads(self, branch=None, start=None, closed=False):
1264 1264 '''return a (possibly filtered) list of heads for the given branch
1265 1265
1266 1266 Heads are returned in topological order, from newest to oldest.
1267 1267 If branch is None, use the dirstate branch.
1268 1268 If start is not None, return only heads reachable from start.
1269 1269 If closed is True, return heads that are marked as closed as well.
1270 1270 '''
1271 1271 if branch is None:
1272 1272 branch = self[None].branch()
1273 1273 branches = self.branchmap()
1274 1274 if branch not in branches:
1275 1275 return []
1276 1276 # the cache returns heads ordered lowest to highest
1277 1277 bheads = list(reversed(branches[branch]))
1278 1278 if start is not None:
1279 1279 # filter out the heads that cannot be reached from startrev
1280 1280 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1281 1281 bheads = [h for h in bheads if h in fbheads]
1282 1282 if not closed:
1283 1283 bheads = [h for h in bheads if
1284 1284 ('close' not in self.changelog.read(h)[5])]
1285 1285 return bheads
1286 1286
1287 1287 def branches(self, nodes):
1288 1288 if not nodes:
1289 1289 nodes = [self.changelog.tip()]
1290 1290 b = []
1291 1291 for n in nodes:
1292 1292 t = n
1293 1293 while 1:
1294 1294 p = self.changelog.parents(n)
1295 1295 if p[1] != nullid or p[0] == nullid:
1296 1296 b.append((t, n, p[0], p[1]))
1297 1297 break
1298 1298 n = p[0]
1299 1299 return b
1300 1300
1301 1301 def between(self, pairs):
1302 1302 r = []
1303 1303
1304 1304 for top, bottom in pairs:
1305 1305 n, l, i = top, [], 0
1306 1306 f = 1
1307 1307
1308 1308 while n != bottom and n != nullid:
1309 1309 p = self.changelog.parents(n)[0]
1310 1310 if i == f:
1311 1311 l.append(n)
1312 1312 f = f * 2
1313 1313 n = p
1314 1314 i += 1
1315 1315
1316 1316 r.append(l)
1317 1317
1318 1318 return r
1319 1319
1320 1320 def pull(self, remote, heads=None, force=False):
1321 1321 lock = self.lock()
1322 1322 try:
1323 1323 tmp = discovery.findcommonincoming(self, remote, heads=heads,
1324 1324 force=force)
1325 1325 common, fetch, rheads = tmp
1326 1326 if not fetch:
1327 1327 self.ui.status(_("no changes found\n"))
1328 1328 result = 0
1329 1329 else:
1330 1330 if heads is None and fetch == [nullid]:
1331 1331 self.ui.status(_("requesting all changes\n"))
1332 1332 elif heads is None and remote.capable('changegroupsubset'):
1333 1333 # issue1320, avoid a race if remote changed after discovery
1334 1334 heads = rheads
1335 1335
1336 1336 if heads is None:
1337 1337 cg = remote.changegroup(fetch, 'pull')
1338 1338 elif not remote.capable('changegroupsubset'):
1339 1339 raise util.Abort(_("partial pull cannot be done because "
1340 1340 "other repository doesn't support "
1341 1341 "changegroupsubset."))
1342 1342 else:
1343 1343 cg = remote.changegroupsubset(fetch, heads, 'pull')
1344 1344 result = self.addchangegroup(cg, 'pull', remote.url(),
1345 1345 lock=lock)
1346 1346 finally:
1347 1347 lock.release()
1348 1348
1349 self.ui.debug("checking for updated bookmarks\n")
1350 rb = remote.listkeys('bookmarks')
1351 changed = False
1352 for k in rb.keys():
1353 if k in self._bookmarks:
1354 nr, nl = rb[k], self._bookmarks[k]
1355 if nr in self:
1356 cr = self[nr]
1357 cl = self[nl]
1358 if cl.rev() >= cr.rev():
1359 continue
1360 if cr in cl.descendants():
1361 self._bookmarks[k] = cr.node()
1362 changed = True
1363 self.ui.status(_("updating bookmark %s\n") % k)
1364 else:
1365 self.ui.warn(_("not updating divergent"
1366 " bookmark %s\n") % k)
1367 if changed:
1368 bookmarks.write(self)
1369
1370 1349 return result
1371 1350
1372 1351 def checkpush(self, force, revs):
1373 1352 """Extensions can override this function if additional checks have
1374 1353 to be performed before pushing, or call it if they override push
1375 1354 command.
1376 1355 """
1377 1356 pass
1378 1357
1379 1358 def push(self, remote, force=False, revs=None, newbranch=False):
1380 1359 '''Push outgoing changesets (limited by revs) from the current
1381 1360 repository to remote. Return an integer:
1382 1361 - 0 means HTTP error *or* nothing to push
1383 1362 - 1 means we pushed and remote head count is unchanged *or*
1384 1363 we have outgoing changesets but refused to push
1385 1364 - other values as described by addchangegroup()
1386 1365 '''
1387 1366 # there are two ways to push to remote repo:
1388 1367 #
1389 1368 # addchangegroup assumes local user can lock remote
1390 1369 # repo (local filesystem, old ssh servers).
1391 1370 #
1392 1371 # unbundle assumes local user cannot lock remote repo (new ssh
1393 1372 # servers, http servers).
1394 1373
1395 1374 self.checkpush(force, revs)
1396 1375 lock = None
1397 1376 unbundle = remote.capable('unbundle')
1398 1377 if not unbundle:
1399 1378 lock = remote.lock()
1400 1379 try:
1401 1380 cg, remote_heads = discovery.prepush(self, remote, force, revs,
1402 1381 newbranch)
1403 1382 ret = remote_heads
1404 1383 if cg is not None:
1405 1384 if unbundle:
1406 1385 # local repo finds heads on server, finds out what
1407 1386 # revs it must push. once revs transferred, if server
1408 1387 # finds it has different heads (someone else won
1409 1388 # commit/push race), server aborts.
1410 1389 if force:
1411 1390 remote_heads = ['force']
1412 1391 # ssh: return remote's addchangegroup()
1413 1392 # http: return remote's addchangegroup() or 0 for error
1414 1393 ret = remote.unbundle(cg, remote_heads, 'push')
1415 1394 else:
1416 1395 # we return an integer indicating remote head count change
1417 1396 ret = remote.addchangegroup(cg, 'push', self.url(),
1418 1397 lock=lock)
1419 1398 finally:
1420 1399 if lock is not None:
1421 1400 lock.release()
1422 1401
1423 1402 self.ui.debug("checking for updated bookmarks\n")
1424 1403 rb = remote.listkeys('bookmarks')
1425 1404 for k in rb.keys():
1426 1405 if k in self._bookmarks:
1427 1406 nr, nl = rb[k], hex(self._bookmarks[k])
1428 1407 if nr in self:
1429 1408 cr = self[nr]
1430 1409 cl = self[nl]
1431 1410 if cl in cr.descendants():
1432 1411 r = remote.pushkey('bookmarks', k, nr, nl)
1433 1412 if r:
1434 1413 self.ui.status(_("updating bookmark %s\n") % k)
1435 1414 else:
1436 1415 self.ui.warn(_('updating bookmark %s'
1437 1416 ' failed!\n') % k)
1438 1417
1439 1418 return ret
1440 1419
1441 1420 def changegroupinfo(self, nodes, source):
1442 1421 if self.ui.verbose or source == 'bundle':
1443 1422 self.ui.status(_("%d changesets found\n") % len(nodes))
1444 1423 if self.ui.debugflag:
1445 1424 self.ui.debug("list of changesets:\n")
1446 1425 for node in nodes:
1447 1426 self.ui.debug("%s\n" % hex(node))
1448 1427
1449 1428 def changegroupsubset(self, bases, heads, source, extranodes=None):
1450 1429 """Compute a changegroup consisting of all the nodes that are
1451 1430 descendents of any of the bases and ancestors of any of the heads.
1452 1431 Return a chunkbuffer object whose read() method will return
1453 1432 successive changegroup chunks.
1454 1433
1455 1434 It is fairly complex as determining which filenodes and which
1456 1435 manifest nodes need to be included for the changeset to be complete
1457 1436 is non-trivial.
1458 1437
1459 1438 Another wrinkle is doing the reverse, figuring out which changeset in
1460 1439 the changegroup a particular filenode or manifestnode belongs to.
1461 1440
1462 1441 The caller can specify some nodes that must be included in the
1463 1442 changegroup using the extranodes argument. It should be a dict
1464 1443 where the keys are the filenames (or 1 for the manifest), and the
1465 1444 values are lists of (node, linknode) tuples, where node is a wanted
1466 1445 node and linknode is the changelog node that should be transmitted as
1467 1446 the linkrev.
1468 1447 """
1469 1448
1470 1449 # Set up some initial variables
1471 1450 # Make it easy to refer to self.changelog
1472 1451 cl = self.changelog
1473 1452 # Compute the list of changesets in this changegroup.
1474 1453 # Some bases may turn out to be superfluous, and some heads may be
1475 1454 # too. nodesbetween will return the minimal set of bases and heads
1476 1455 # necessary to re-create the changegroup.
1477 1456 if not bases:
1478 1457 bases = [nullid]
1479 1458 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
1480 1459
1481 1460 if extranodes is None:
1482 1461 # can we go through the fast path ?
1483 1462 heads.sort()
1484 1463 allheads = self.heads()
1485 1464 allheads.sort()
1486 1465 if heads == allheads:
1487 1466 return self._changegroup(msng_cl_lst, source)
1488 1467
1489 1468 # slow path
1490 1469 self.hook('preoutgoing', throw=True, source=source)
1491 1470
1492 1471 self.changegroupinfo(msng_cl_lst, source)
1493 1472
1494 1473 # We assume that all ancestors of bases are known
1495 1474 commonrevs = set(cl.ancestors(*[cl.rev(n) for n in bases]))
1496 1475
1497 1476 # Make it easy to refer to self.manifest
1498 1477 mnfst = self.manifest
1499 1478 # We don't know which manifests are missing yet
1500 1479 msng_mnfst_set = {}
1501 1480 # Nor do we know which filenodes are missing.
1502 1481 msng_filenode_set = {}
1503 1482
1504 1483 # A changeset always belongs to itself, so the changenode lookup
1505 1484 # function for a changenode is identity.
1506 1485 def identity(x):
1507 1486 return x
1508 1487
1509 1488 # A function generating function that sets up the initial environment
1510 1489 # the inner function.
1511 1490 def filenode_collector(changedfiles):
1512 1491 # This gathers information from each manifestnode included in the
1513 1492 # changegroup about which filenodes the manifest node references
1514 1493 # so we can include those in the changegroup too.
1515 1494 #
1516 1495 # It also remembers which changenode each filenode belongs to. It
1517 1496 # does this by assuming the a filenode belongs to the changenode
1518 1497 # the first manifest that references it belongs to.
1519 1498 def collect_msng_filenodes(mnfstnode):
1520 1499 r = mnfst.rev(mnfstnode)
1521 1500 if mnfst.deltaparent(r) in mnfst.parentrevs(r):
1522 1501 # If the previous rev is one of the parents,
1523 1502 # we only need to see a diff.
1524 1503 deltamf = mnfst.readdelta(mnfstnode)
1525 1504 # For each line in the delta
1526 1505 for f, fnode in deltamf.iteritems():
1527 1506 # And if the file is in the list of files we care
1528 1507 # about.
1529 1508 if f in changedfiles:
1530 1509 # Get the changenode this manifest belongs to
1531 1510 clnode = msng_mnfst_set[mnfstnode]
1532 1511 # Create the set of filenodes for the file if
1533 1512 # there isn't one already.
1534 1513 ndset = msng_filenode_set.setdefault(f, {})
1535 1514 # And set the filenode's changelog node to the
1536 1515 # manifest's if it hasn't been set already.
1537 1516 ndset.setdefault(fnode, clnode)
1538 1517 else:
1539 1518 # Otherwise we need a full manifest.
1540 1519 m = mnfst.read(mnfstnode)
1541 1520 # For every file in we care about.
1542 1521 for f in changedfiles:
1543 1522 fnode = m.get(f, None)
1544 1523 # If it's in the manifest
1545 1524 if fnode is not None:
1546 1525 # See comments above.
1547 1526 clnode = msng_mnfst_set[mnfstnode]
1548 1527 ndset = msng_filenode_set.setdefault(f, {})
1549 1528 ndset.setdefault(fnode, clnode)
1550 1529 return collect_msng_filenodes
1551 1530
1552 1531 # If we determine that a particular file or manifest node must be a
1553 1532 # node that the recipient of the changegroup will already have, we can
1554 1533 # also assume the recipient will have all the parents. This function
1555 1534 # prunes them from the set of missing nodes.
1556 1535 def prune(revlog, missingnodes):
1557 1536 hasset = set()
1558 1537 # If a 'missing' filenode thinks it belongs to a changenode we
1559 1538 # assume the recipient must have, then the recipient must have
1560 1539 # that filenode.
1561 1540 for n in missingnodes:
1562 1541 clrev = revlog.linkrev(revlog.rev(n))
1563 1542 if clrev in commonrevs:
1564 1543 hasset.add(n)
1565 1544 for n in hasset:
1566 1545 missingnodes.pop(n, None)
1567 1546 for r in revlog.ancestors(*[revlog.rev(n) for n in hasset]):
1568 1547 missingnodes.pop(revlog.node(r), None)
1569 1548
1570 1549 # Add the nodes that were explicitly requested.
1571 1550 def add_extra_nodes(name, nodes):
1572 1551 if not extranodes or name not in extranodes:
1573 1552 return
1574 1553
1575 1554 for node, linknode in extranodes[name]:
1576 1555 if node not in nodes:
1577 1556 nodes[node] = linknode
1578 1557
1579 1558 # Now that we have all theses utility functions to help out and
1580 1559 # logically divide up the task, generate the group.
1581 1560 def gengroup():
1582 1561 # The set of changed files starts empty.
1583 1562 changedfiles = set()
1584 1563 collect = changegroup.collector(cl, msng_mnfst_set, changedfiles)
1585 1564
1586 1565 # Create a changenode group generator that will call our functions
1587 1566 # back to lookup the owning changenode and collect information.
1588 1567 group = cl.group(msng_cl_lst, identity, collect)
1589 1568 for cnt, chnk in enumerate(group):
1590 1569 yield chnk
1591 1570 # revlog.group yields three entries per node, so
1592 1571 # dividing by 3 gives an approximation of how many
1593 1572 # nodes have been processed.
1594 1573 self.ui.progress(_('bundling'), cnt / 3,
1595 1574 unit=_('changesets'))
1596 1575 changecount = cnt / 3
1597 1576 self.ui.progress(_('bundling'), None)
1598 1577
1599 1578 prune(mnfst, msng_mnfst_set)
1600 1579 add_extra_nodes(1, msng_mnfst_set)
1601 1580 msng_mnfst_lst = msng_mnfst_set.keys()
1602 1581 # Sort the manifestnodes by revision number.
1603 1582 msng_mnfst_lst.sort(key=mnfst.rev)
1604 1583 # Create a generator for the manifestnodes that calls our lookup
1605 1584 # and data collection functions back.
1606 1585 group = mnfst.group(msng_mnfst_lst,
1607 1586 lambda mnode: msng_mnfst_set[mnode],
1608 1587 filenode_collector(changedfiles))
1609 1588 efiles = {}
1610 1589 for cnt, chnk in enumerate(group):
1611 1590 if cnt % 3 == 1:
1612 1591 mnode = chnk[:20]
1613 1592 efiles.update(mnfst.readdelta(mnode))
1614 1593 yield chnk
1615 1594 # see above comment for why we divide by 3
1616 1595 self.ui.progress(_('bundling'), cnt / 3,
1617 1596 unit=_('manifests'), total=changecount)
1618 1597 self.ui.progress(_('bundling'), None)
1619 1598 efiles = len(efiles)
1620 1599
1621 1600 # These are no longer needed, dereference and toss the memory for
1622 1601 # them.
1623 1602 msng_mnfst_lst = None
1624 1603 msng_mnfst_set.clear()
1625 1604
1626 1605 if extranodes:
1627 1606 for fname in extranodes:
1628 1607 if isinstance(fname, int):
1629 1608 continue
1630 1609 msng_filenode_set.setdefault(fname, {})
1631 1610 changedfiles.add(fname)
1632 1611 # Go through all our files in order sorted by name.
1633 1612 for idx, fname in enumerate(sorted(changedfiles)):
1634 1613 filerevlog = self.file(fname)
1635 1614 if not len(filerevlog):
1636 1615 raise util.Abort(_("empty or missing revlog for %s") % fname)
1637 1616 # Toss out the filenodes that the recipient isn't really
1638 1617 # missing.
1639 1618 missingfnodes = msng_filenode_set.pop(fname, {})
1640 1619 prune(filerevlog, missingfnodes)
1641 1620 add_extra_nodes(fname, missingfnodes)
1642 1621 # If any filenodes are left, generate the group for them,
1643 1622 # otherwise don't bother.
1644 1623 if missingfnodes:
1645 1624 yield changegroup.chunkheader(len(fname))
1646 1625 yield fname
1647 1626 # Sort the filenodes by their revision # (topological order)
1648 1627 nodeiter = list(missingfnodes)
1649 1628 nodeiter.sort(key=filerevlog.rev)
1650 1629 # Create a group generator and only pass in a changenode
1651 1630 # lookup function as we need to collect no information
1652 1631 # from filenodes.
1653 1632 group = filerevlog.group(nodeiter,
1654 1633 lambda fnode: missingfnodes[fnode])
1655 1634 for chnk in group:
1656 1635 # even though we print the same progress on
1657 1636 # most loop iterations, put the progress call
1658 1637 # here so that time estimates (if any) can be updated
1659 1638 self.ui.progress(
1660 1639 _('bundling'), idx, item=fname,
1661 1640 unit=_('files'), total=efiles)
1662 1641 yield chnk
1663 1642 # Signal that no more groups are left.
1664 1643 yield changegroup.closechunk()
1665 1644 self.ui.progress(_('bundling'), None)
1666 1645
1667 1646 if msng_cl_lst:
1668 1647 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
1669 1648
1670 1649 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
1671 1650
1672 1651 def changegroup(self, basenodes, source):
1673 1652 # to avoid a race we use changegroupsubset() (issue1320)
1674 1653 return self.changegroupsubset(basenodes, self.heads(), source)
1675 1654
1676 1655 def _changegroup(self, nodes, source):
1677 1656 """Compute the changegroup of all nodes that we have that a recipient
1678 1657 doesn't. Return a chunkbuffer object whose read() method will return
1679 1658 successive changegroup chunks.
1680 1659
1681 1660 This is much easier than the previous function as we can assume that
1682 1661 the recipient has any changenode we aren't sending them.
1683 1662
1684 1663 nodes is the set of nodes to send"""
1685 1664
1686 1665 self.hook('preoutgoing', throw=True, source=source)
1687 1666
1688 1667 cl = self.changelog
1689 1668 revset = set([cl.rev(n) for n in nodes])
1690 1669 self.changegroupinfo(nodes, source)
1691 1670
1692 1671 def identity(x):
1693 1672 return x
1694 1673
1695 1674 def gennodelst(log):
1696 1675 for r in log:
1697 1676 if log.linkrev(r) in revset:
1698 1677 yield log.node(r)
1699 1678
1700 1679 def lookuplinkrev_func(revlog):
1701 1680 def lookuplinkrev(n):
1702 1681 return cl.node(revlog.linkrev(revlog.rev(n)))
1703 1682 return lookuplinkrev
1704 1683
1705 1684 def gengroup():
1706 1685 '''yield a sequence of changegroup chunks (strings)'''
1707 1686 # construct a list of all changed files
1708 1687 changedfiles = set()
1709 1688 mmfs = {}
1710 1689 collect = changegroup.collector(cl, mmfs, changedfiles)
1711 1690
1712 1691 for cnt, chnk in enumerate(cl.group(nodes, identity, collect)):
1713 1692 # revlog.group yields three entries per node, so
1714 1693 # dividing by 3 gives an approximation of how many
1715 1694 # nodes have been processed.
1716 1695 self.ui.progress(_('bundling'), cnt / 3, unit=_('changesets'))
1717 1696 yield chnk
1718 1697 changecount = cnt / 3
1719 1698 self.ui.progress(_('bundling'), None)
1720 1699
1721 1700 mnfst = self.manifest
1722 1701 nodeiter = gennodelst(mnfst)
1723 1702 efiles = {}
1724 1703 for cnt, chnk in enumerate(mnfst.group(nodeiter,
1725 1704 lookuplinkrev_func(mnfst))):
1726 1705 if cnt % 3 == 1:
1727 1706 mnode = chnk[:20]
1728 1707 efiles.update(mnfst.readdelta(mnode))
1729 1708 # see above comment for why we divide by 3
1730 1709 self.ui.progress(_('bundling'), cnt / 3,
1731 1710 unit=_('manifests'), total=changecount)
1732 1711 yield chnk
1733 1712 efiles = len(efiles)
1734 1713 self.ui.progress(_('bundling'), None)
1735 1714
1736 1715 for idx, fname in enumerate(sorted(changedfiles)):
1737 1716 filerevlog = self.file(fname)
1738 1717 if not len(filerevlog):
1739 1718 raise util.Abort(_("empty or missing revlog for %s") % fname)
1740 1719 nodeiter = gennodelst(filerevlog)
1741 1720 nodeiter = list(nodeiter)
1742 1721 if nodeiter:
1743 1722 yield changegroup.chunkheader(len(fname))
1744 1723 yield fname
1745 1724 lookup = lookuplinkrev_func(filerevlog)
1746 1725 for chnk in filerevlog.group(nodeiter, lookup):
1747 1726 self.ui.progress(
1748 1727 _('bundling'), idx, item=fname,
1749 1728 total=efiles, unit=_('files'))
1750 1729 yield chnk
1751 1730 self.ui.progress(_('bundling'), None)
1752 1731
1753 1732 yield changegroup.closechunk()
1754 1733
1755 1734 if nodes:
1756 1735 self.hook('outgoing', node=hex(nodes[0]), source=source)
1757 1736
1758 1737 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
1759 1738
1760 1739 def addchangegroup(self, source, srctype, url, emptyok=False, lock=None):
1761 1740 """Add the changegroup returned by source.read() to this repo.
1762 1741 srctype is a string like 'push', 'pull', or 'unbundle'. url is
1763 1742 the URL of the repo where this changegroup is coming from.
1764 1743 If lock is not None, the function takes ownership of the lock
1765 1744 and releases it after the changegroup is added.
1766 1745
1767 1746 Return an integer summarizing the change to this repo:
1768 1747 - nothing changed or no source: 0
1769 1748 - more heads than before: 1+added heads (2..n)
1770 1749 - fewer heads than before: -1-removed heads (-2..-n)
1771 1750 - number of heads stays the same: 1
1772 1751 """
1773 1752 def csmap(x):
1774 1753 self.ui.debug("add changeset %s\n" % short(x))
1775 1754 return len(cl)
1776 1755
1777 1756 def revmap(x):
1778 1757 return cl.rev(x)
1779 1758
1780 1759 if not source:
1781 1760 return 0
1782 1761
1783 1762 self.hook('prechangegroup', throw=True, source=srctype, url=url)
1784 1763
1785 1764 changesets = files = revisions = 0
1786 1765 efiles = set()
1787 1766
1788 1767 # write changelog data to temp files so concurrent readers will not see
1789 1768 # inconsistent view
1790 1769 cl = self.changelog
1791 1770 cl.delayupdate()
1792 1771 oldheads = len(cl.heads())
1793 1772
1794 1773 tr = self.transaction("\n".join([srctype, urlmod.hidepassword(url)]))
1795 1774 try:
1796 1775 trp = weakref.proxy(tr)
1797 1776 # pull off the changeset group
1798 1777 self.ui.status(_("adding changesets\n"))
1799 1778 clstart = len(cl)
1800 1779 class prog(object):
1801 1780 step = _('changesets')
1802 1781 count = 1
1803 1782 ui = self.ui
1804 1783 total = None
1805 1784 def __call__(self):
1806 1785 self.ui.progress(self.step, self.count, unit=_('chunks'),
1807 1786 total=self.total)
1808 1787 self.count += 1
1809 1788 pr = prog()
1810 1789 source.callback = pr
1811 1790
1812 1791 if (cl.addgroup(source, csmap, trp) is None
1813 1792 and not emptyok):
1814 1793 raise util.Abort(_("received changelog group is empty"))
1815 1794 clend = len(cl)
1816 1795 changesets = clend - clstart
1817 1796 for c in xrange(clstart, clend):
1818 1797 efiles.update(self[c].files())
1819 1798 efiles = len(efiles)
1820 1799 self.ui.progress(_('changesets'), None)
1821 1800
1822 1801 # pull off the manifest group
1823 1802 self.ui.status(_("adding manifests\n"))
1824 1803 pr.step = _('manifests')
1825 1804 pr.count = 1
1826 1805 pr.total = changesets # manifests <= changesets
1827 1806 # no need to check for empty manifest group here:
1828 1807 # if the result of the merge of 1 and 2 is the same in 3 and 4,
1829 1808 # no new manifest will be created and the manifest group will
1830 1809 # be empty during the pull
1831 1810 self.manifest.addgroup(source, revmap, trp)
1832 1811 self.ui.progress(_('manifests'), None)
1833 1812
1834 1813 needfiles = {}
1835 1814 if self.ui.configbool('server', 'validate', default=False):
1836 1815 # validate incoming csets have their manifests
1837 1816 for cset in xrange(clstart, clend):
1838 1817 mfest = self.changelog.read(self.changelog.node(cset))[0]
1839 1818 mfest = self.manifest.readdelta(mfest)
1840 1819 # store file nodes we must see
1841 1820 for f, n in mfest.iteritems():
1842 1821 needfiles.setdefault(f, set()).add(n)
1843 1822
1844 1823 # process the files
1845 1824 self.ui.status(_("adding file changes\n"))
1846 1825 pr.step = 'files'
1847 1826 pr.count = 1
1848 1827 pr.total = efiles
1849 1828 source.callback = None
1850 1829
1851 1830 while 1:
1852 1831 f = source.chunk()
1853 1832 if not f:
1854 1833 break
1855 1834 self.ui.debug("adding %s revisions\n" % f)
1856 1835 pr()
1857 1836 fl = self.file(f)
1858 1837 o = len(fl)
1859 1838 if fl.addgroup(source, revmap, trp) is None:
1860 1839 raise util.Abort(_("received file revlog group is empty"))
1861 1840 revisions += len(fl) - o
1862 1841 files += 1
1863 1842 if f in needfiles:
1864 1843 needs = needfiles[f]
1865 1844 for new in xrange(o, len(fl)):
1866 1845 n = fl.node(new)
1867 1846 if n in needs:
1868 1847 needs.remove(n)
1869 1848 if not needs:
1870 1849 del needfiles[f]
1871 1850 self.ui.progress(_('files'), None)
1872 1851
1873 1852 for f, needs in needfiles.iteritems():
1874 1853 fl = self.file(f)
1875 1854 for n in needs:
1876 1855 try:
1877 1856 fl.rev(n)
1878 1857 except error.LookupError:
1879 1858 raise util.Abort(
1880 1859 _('missing file data for %s:%s - run hg verify') %
1881 1860 (f, hex(n)))
1882 1861
1883 1862 newheads = len(cl.heads())
1884 1863 heads = ""
1885 1864 if oldheads and newheads != oldheads:
1886 1865 heads = _(" (%+d heads)") % (newheads - oldheads)
1887 1866
1888 1867 self.ui.status(_("added %d changesets"
1889 1868 " with %d changes to %d files%s\n")
1890 1869 % (changesets, revisions, files, heads))
1891 1870
1892 1871 if changesets > 0:
1893 1872 p = lambda: cl.writepending() and self.root or ""
1894 1873 self.hook('pretxnchangegroup', throw=True,
1895 1874 node=hex(cl.node(clstart)), source=srctype,
1896 1875 url=url, pending=p)
1897 1876
1898 1877 # make changelog see real files again
1899 1878 cl.finalize(trp)
1900 1879
1901 1880 tr.close()
1902 1881 finally:
1903 1882 tr.release()
1904 1883 if lock:
1905 1884 lock.release()
1906 1885
1907 1886 if changesets > 0:
1908 1887 # forcefully update the on-disk branch cache
1909 1888 self.ui.debug("updating the branch cache\n")
1910 1889 self.updatebranchcache()
1911 1890 self.hook("changegroup", node=hex(cl.node(clstart)),
1912 1891 source=srctype, url=url)
1913 1892
1914 1893 for i in xrange(clstart, clend):
1915 1894 self.hook("incoming", node=hex(cl.node(i)),
1916 1895 source=srctype, url=url)
1917 1896
1918 1897 # FIXME - why does this care about tip?
1919 1898 if newheads == oldheads:
1920 1899 bookmarks.update(self, self.dirstate.parents(), self['tip'].node())
1921 1900
1922 1901 # never return 0 here:
1923 1902 if newheads < oldheads:
1924 1903 return newheads - oldheads - 1
1925 1904 else:
1926 1905 return newheads - oldheads + 1
1927 1906
1928 1907
1929 1908 def stream_in(self, remote, requirements):
1930 1909 lock = self.lock()
1931 1910 try:
1932 1911 fp = remote.stream_out()
1933 1912 l = fp.readline()
1934 1913 try:
1935 1914 resp = int(l)
1936 1915 except ValueError:
1937 1916 raise error.ResponseError(
1938 1917 _('Unexpected response from remote server:'), l)
1939 1918 if resp == 1:
1940 1919 raise util.Abort(_('operation forbidden by server'))
1941 1920 elif resp == 2:
1942 1921 raise util.Abort(_('locking the remote repository failed'))
1943 1922 elif resp != 0:
1944 1923 raise util.Abort(_('the server sent an unknown error code'))
1945 1924 self.ui.status(_('streaming all changes\n'))
1946 1925 l = fp.readline()
1947 1926 try:
1948 1927 total_files, total_bytes = map(int, l.split(' ', 1))
1949 1928 except (ValueError, TypeError):
1950 1929 raise error.ResponseError(
1951 1930 _('Unexpected response from remote server:'), l)
1952 1931 self.ui.status(_('%d files to transfer, %s of data\n') %
1953 1932 (total_files, util.bytecount(total_bytes)))
1954 1933 start = time.time()
1955 1934 for i in xrange(total_files):
1956 1935 # XXX doesn't support '\n' or '\r' in filenames
1957 1936 l = fp.readline()
1958 1937 try:
1959 1938 name, size = l.split('\0', 1)
1960 1939 size = int(size)
1961 1940 except (ValueError, TypeError):
1962 1941 raise error.ResponseError(
1963 1942 _('Unexpected response from remote server:'), l)
1964 1943 self.ui.debug('adding %s (%s)\n' % (name, util.bytecount(size)))
1965 1944 # for backwards compat, name was partially encoded
1966 1945 ofp = self.sopener(store.decodedir(name), 'w')
1967 1946 for chunk in util.filechunkiter(fp, limit=size):
1968 1947 ofp.write(chunk)
1969 1948 ofp.close()
1970 1949 elapsed = time.time() - start
1971 1950 if elapsed <= 0:
1972 1951 elapsed = 0.001
1973 1952 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
1974 1953 (util.bytecount(total_bytes), elapsed,
1975 1954 util.bytecount(total_bytes / elapsed)))
1976 1955
1977 1956 # new requirements = old non-format requirements + new format-related
1978 1957 # requirements from the streamed-in repository
1979 1958 requirements.update(set(self.requirements) - self.supportedformats)
1980 1959 self._applyrequirements(requirements)
1981 1960 self._writerequirements()
1982 1961
1983 1962 self.invalidate()
1984 1963 return len(self.heads()) + 1
1985 1964 finally:
1986 1965 lock.release()
1987 1966
1988 1967 def clone(self, remote, heads=[], stream=False):
1989 1968 '''clone remote repository.
1990 1969
1991 1970 keyword arguments:
1992 1971 heads: list of revs to clone (forces use of pull)
1993 1972 stream: use streaming clone if possible'''
1994 1973
1995 1974 # now, all clients that can request uncompressed clones can
1996 1975 # read repo formats supported by all servers that can serve
1997 1976 # them.
1998 1977
1999 1978 # if revlog format changes, client will have to check version
2000 1979 # and format flags on "stream" capability, and use
2001 1980 # uncompressed only if compatible.
2002 1981
2003 1982 if stream and not heads:
2004 1983 # 'stream' means remote revlog format is revlogv1 only
2005 1984 if remote.capable('stream'):
2006 1985 return self.stream_in(remote, set(('revlogv1',)))
2007 1986 # otherwise, 'streamreqs' contains the remote revlog format
2008 1987 streamreqs = remote.capable('streamreqs')
2009 1988 if streamreqs:
2010 1989 streamreqs = set(streamreqs.split(','))
2011 1990 # if we support it, stream in and adjust our requirements
2012 1991 if not streamreqs - self.supportedformats:
2013 1992 return self.stream_in(remote, streamreqs)
2014 1993 return self.pull(remote, heads)
2015 1994
2016 1995 def pushkey(self, namespace, key, old, new):
2017 1996 return pushkey.push(self, namespace, key, old, new)
2018 1997
2019 1998 def listkeys(self, namespace):
2020 1999 return pushkey.list(self, namespace)
2021 2000
2022 2001 # used to avoid circular references so destructors work
2023 2002 def aftertrans(files):
2024 2003 renamefiles = [tuple(t) for t in files]
2025 2004 def a():
2026 2005 for src, dest in renamefiles:
2027 2006 util.rename(src, dest)
2028 2007 return a
2029 2008
2030 2009 def instance(ui, path, create):
2031 2010 return localrepository(ui, util.drop_scheme('file', path), create)
2032 2011
2033 2012 def islocal(path):
2034 2013 return True
@@ -1,1023 +1,1024 b''
1 1 # subrepo.py - sub-repository handling for Mercurial
2 2 #
3 3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import errno, os, re, xml.dom.minidom, shutil, urlparse, posixpath
9 9 import stat, subprocess, tarfile
10 10 from i18n import _
11 import config, util, node, error, cmdutil
11 import config, util, node, error, cmdutil, bookmarks
12 12 hg = None
13 13
14 14 nullstate = ('', '', 'empty')
15 15
16 16 def state(ctx, ui):
17 17 """return a state dict, mapping subrepo paths configured in .hgsub
18 18 to tuple: (source from .hgsub, revision from .hgsubstate, kind
19 19 (key in types dict))
20 20 """
21 21 p = config.config()
22 22 def read(f, sections=None, remap=None):
23 23 if f in ctx:
24 24 try:
25 25 data = ctx[f].data()
26 26 except IOError, err:
27 27 if err.errno != errno.ENOENT:
28 28 raise
29 29 # handle missing subrepo spec files as removed
30 30 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
31 31 return
32 32 p.parse(f, data, sections, remap, read)
33 33 else:
34 34 raise util.Abort(_("subrepo spec file %s not found") % f)
35 35
36 36 if '.hgsub' in ctx:
37 37 read('.hgsub')
38 38
39 39 for path, src in ui.configitems('subpaths'):
40 40 p.set('subpaths', path, src, ui.configsource('subpaths', path))
41 41
42 42 rev = {}
43 43 if '.hgsubstate' in ctx:
44 44 try:
45 45 for l in ctx['.hgsubstate'].data().splitlines():
46 46 revision, path = l.split(" ", 1)
47 47 rev[path] = revision
48 48 except IOError, err:
49 49 if err.errno != errno.ENOENT:
50 50 raise
51 51
52 52 state = {}
53 53 for path, src in p[''].items():
54 54 kind = 'hg'
55 55 if src.startswith('['):
56 56 if ']' not in src:
57 57 raise util.Abort(_('missing ] in subrepo source'))
58 58 kind, src = src.split(']', 1)
59 59 kind = kind[1:]
60 60
61 61 for pattern, repl in p.items('subpaths'):
62 62 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
63 63 # does a string decode.
64 64 repl = repl.encode('string-escape')
65 65 # However, we still want to allow back references to go
66 66 # through unharmed, so we turn r'\\1' into r'\1'. Again,
67 67 # extra escapes are needed because re.sub string decodes.
68 68 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
69 69 try:
70 70 src = re.sub(pattern, repl, src, 1)
71 71 except re.error, e:
72 72 raise util.Abort(_("bad subrepository pattern in %s: %s")
73 73 % (p.source('subpaths', pattern), e))
74 74
75 75 state[path] = (src.strip(), rev.get(path, ''), kind)
76 76
77 77 return state
78 78
79 79 def writestate(repo, state):
80 80 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
81 81 repo.wwrite('.hgsubstate',
82 82 ''.join(['%s %s\n' % (state[s][1], s)
83 83 for s in sorted(state)]), '')
84 84
85 85 def submerge(repo, wctx, mctx, actx, overwrite):
86 86 """delegated from merge.applyupdates: merging of .hgsubstate file
87 87 in working context, merging context and ancestor context"""
88 88 if mctx == actx: # backwards?
89 89 actx = wctx.p1()
90 90 s1 = wctx.substate
91 91 s2 = mctx.substate
92 92 sa = actx.substate
93 93 sm = {}
94 94
95 95 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
96 96
97 97 def debug(s, msg, r=""):
98 98 if r:
99 99 r = "%s:%s:%s" % r
100 100 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
101 101
102 102 for s, l in s1.items():
103 103 a = sa.get(s, nullstate)
104 104 ld = l # local state with possible dirty flag for compares
105 105 if wctx.sub(s).dirty():
106 106 ld = (l[0], l[1] + "+")
107 107 if wctx == actx: # overwrite
108 108 a = ld
109 109
110 110 if s in s2:
111 111 r = s2[s]
112 112 if ld == r or r == a: # no change or local is newer
113 113 sm[s] = l
114 114 continue
115 115 elif ld == a: # other side changed
116 116 debug(s, "other changed, get", r)
117 117 wctx.sub(s).get(r, overwrite)
118 118 sm[s] = r
119 119 elif ld[0] != r[0]: # sources differ
120 120 if repo.ui.promptchoice(
121 121 _(' subrepository sources for %s differ\n'
122 122 'use (l)ocal source (%s) or (r)emote source (%s)?')
123 123 % (s, l[0], r[0]),
124 124 (_('&Local'), _('&Remote')), 0):
125 125 debug(s, "prompt changed, get", r)
126 126 wctx.sub(s).get(r, overwrite)
127 127 sm[s] = r
128 128 elif ld[1] == a[1]: # local side is unchanged
129 129 debug(s, "other side changed, get", r)
130 130 wctx.sub(s).get(r, overwrite)
131 131 sm[s] = r
132 132 else:
133 133 debug(s, "both sides changed, merge with", r)
134 134 wctx.sub(s).merge(r)
135 135 sm[s] = l
136 136 elif ld == a: # remote removed, local unchanged
137 137 debug(s, "remote removed, remove")
138 138 wctx.sub(s).remove()
139 139 else:
140 140 if repo.ui.promptchoice(
141 141 _(' local changed subrepository %s which remote removed\n'
142 142 'use (c)hanged version or (d)elete?') % s,
143 143 (_('&Changed'), _('&Delete')), 0):
144 144 debug(s, "prompt remove")
145 145 wctx.sub(s).remove()
146 146
147 147 for s, r in s2.items():
148 148 if s in s1:
149 149 continue
150 150 elif s not in sa:
151 151 debug(s, "remote added, get", r)
152 152 mctx.sub(s).get(r)
153 153 sm[s] = r
154 154 elif r != sa[s]:
155 155 if repo.ui.promptchoice(
156 156 _(' remote changed subrepository %s which local removed\n'
157 157 'use (c)hanged version or (d)elete?') % s,
158 158 (_('&Changed'), _('&Delete')), 0) == 0:
159 159 debug(s, "prompt recreate", r)
160 160 wctx.sub(s).get(r)
161 161 sm[s] = r
162 162
163 163 # record merged .hgsubstate
164 164 writestate(repo, sm)
165 165
166 166 def _updateprompt(ui, sub, dirty, local, remote):
167 167 if dirty:
168 168 msg = (_(' subrepository sources for %s differ\n'
169 169 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
170 170 % (subrelpath(sub), local, remote))
171 171 else:
172 172 msg = (_(' subrepository sources for %s differ (in checked out version)\n'
173 173 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
174 174 % (subrelpath(sub), local, remote))
175 175 return ui.promptchoice(msg, (_('&Local'), _('&Remote')), 0)
176 176
177 177 def reporelpath(repo):
178 178 """return path to this (sub)repo as seen from outermost repo"""
179 179 parent = repo
180 180 while hasattr(parent, '_subparent'):
181 181 parent = parent._subparent
182 182 return repo.root[len(parent.root)+1:]
183 183
184 184 def subrelpath(sub):
185 185 """return path to this subrepo as seen from outermost repo"""
186 186 if hasattr(sub, '_relpath'):
187 187 return sub._relpath
188 188 if not hasattr(sub, '_repo'):
189 189 return sub._path
190 190 return reporelpath(sub._repo)
191 191
192 192 def _abssource(repo, push=False, abort=True):
193 193 """return pull/push path of repo - either based on parent repo .hgsub info
194 194 or on the top repo config. Abort or return None if no source found."""
195 195 if hasattr(repo, '_subparent'):
196 196 source = repo._subsource
197 197 if source.startswith('/') or '://' in source:
198 198 return source
199 199 parent = _abssource(repo._subparent, push, abort=False)
200 200 if parent:
201 201 if '://' in parent:
202 202 if parent[-1] == '/':
203 203 parent = parent[:-1]
204 204 r = urlparse.urlparse(parent + '/' + source)
205 205 r = urlparse.urlunparse((r[0], r[1],
206 206 posixpath.normpath(r[2]),
207 207 r[3], r[4], r[5]))
208 208 return r
209 209 else: # plain file system path
210 210 return posixpath.normpath(os.path.join(parent, repo._subsource))
211 211 else: # recursion reached top repo
212 212 if hasattr(repo, '_subtoppath'):
213 213 return repo._subtoppath
214 214 if push and repo.ui.config('paths', 'default-push'):
215 215 return repo.ui.config('paths', 'default-push')
216 216 if repo.ui.config('paths', 'default'):
217 217 return repo.ui.config('paths', 'default')
218 218 if abort:
219 219 raise util.Abort(_("default path for subrepository %s not found") %
220 220 reporelpath(repo))
221 221
222 222 def itersubrepos(ctx1, ctx2):
223 223 """find subrepos in ctx1 or ctx2"""
224 224 # Create a (subpath, ctx) mapping where we prefer subpaths from
225 225 # ctx1. The subpaths from ctx2 are important when the .hgsub file
226 226 # has been modified (in ctx2) but not yet committed (in ctx1).
227 227 subpaths = dict.fromkeys(ctx2.substate, ctx2)
228 228 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
229 229 for subpath, ctx in sorted(subpaths.iteritems()):
230 230 yield subpath, ctx.sub(subpath)
231 231
232 232 def subrepo(ctx, path):
233 233 """return instance of the right subrepo class for subrepo in path"""
234 234 # subrepo inherently violates our import layering rules
235 235 # because it wants to make repo objects from deep inside the stack
236 236 # so we manually delay the circular imports to not break
237 237 # scripts that don't use our demand-loading
238 238 global hg
239 239 import hg as h
240 240 hg = h
241 241
242 242 util.path_auditor(ctx._repo.root)(path)
243 243 state = ctx.substate.get(path, nullstate)
244 244 if state[2] not in types:
245 245 raise util.Abort(_('unknown subrepo type %s') % state[2])
246 246 return types[state[2]](ctx, path, state[:2])
247 247
248 248 # subrepo classes need to implement the following abstract class:
249 249
250 250 class abstractsubrepo(object):
251 251
252 252 def dirty(self, ignoreupdate=False):
253 253 """returns true if the dirstate of the subrepo is dirty or does not
254 254 match current stored state. If ignoreupdate is true, only check
255 255 whether the subrepo has uncommitted changes in its dirstate.
256 256 """
257 257 raise NotImplementedError
258 258
259 259 def checknested(self, path):
260 260 """check if path is a subrepository within this repository"""
261 261 return False
262 262
263 263 def commit(self, text, user, date):
264 264 """commit the current changes to the subrepo with the given
265 265 log message. Use given user and date if possible. Return the
266 266 new state of the subrepo.
267 267 """
268 268 raise NotImplementedError
269 269
270 270 def remove(self):
271 271 """remove the subrepo
272 272
273 273 (should verify the dirstate is not dirty first)
274 274 """
275 275 raise NotImplementedError
276 276
277 277 def get(self, state, overwrite=False):
278 278 """run whatever commands are needed to put the subrepo into
279 279 this state
280 280 """
281 281 raise NotImplementedError
282 282
283 283 def merge(self, state):
284 284 """merge currently-saved state with the new state."""
285 285 raise NotImplementedError
286 286
287 287 def push(self, force):
288 288 """perform whatever action is analogous to 'hg push'
289 289
290 290 This may be a no-op on some systems.
291 291 """
292 292 raise NotImplementedError
293 293
294 294 def add(self, ui, match, dryrun, prefix):
295 295 return []
296 296
297 297 def status(self, rev2, **opts):
298 298 return [], [], [], [], [], [], []
299 299
300 300 def diff(self, diffopts, node2, match, prefix, **opts):
301 301 pass
302 302
303 303 def outgoing(self, ui, dest, opts):
304 304 return 1
305 305
306 306 def incoming(self, ui, source, opts):
307 307 return 1
308 308
309 309 def files(self):
310 310 """return filename iterator"""
311 311 raise NotImplementedError
312 312
313 313 def filedata(self, name):
314 314 """return file data"""
315 315 raise NotImplementedError
316 316
317 317 def fileflags(self, name):
318 318 """return file flags"""
319 319 return ''
320 320
321 321 def archive(self, ui, archiver, prefix):
322 322 files = self.files()
323 323 total = len(files)
324 324 relpath = subrelpath(self)
325 325 ui.progress(_('archiving (%s)') % relpath, 0,
326 326 unit=_('files'), total=total)
327 327 for i, name in enumerate(files):
328 328 flags = self.fileflags(name)
329 329 mode = 'x' in flags and 0755 or 0644
330 330 symlink = 'l' in flags
331 331 archiver.addfile(os.path.join(prefix, self._path, name),
332 332 mode, symlink, self.filedata(name))
333 333 ui.progress(_('archiving (%s)') % relpath, i + 1,
334 334 unit=_('files'), total=total)
335 335 ui.progress(_('archiving (%s)') % relpath, None)
336 336
337 337
338 338 class hgsubrepo(abstractsubrepo):
339 339 def __init__(self, ctx, path, state):
340 340 self._path = path
341 341 self._state = state
342 342 r = ctx._repo
343 343 root = r.wjoin(path)
344 344 create = False
345 345 if not os.path.exists(os.path.join(root, '.hg')):
346 346 create = True
347 347 util.makedirs(root)
348 348 self._repo = hg.repository(r.ui, root, create=create)
349 349 self._repo._subparent = r
350 350 self._repo._subsource = state[0]
351 351
352 352 if create:
353 353 fp = self._repo.opener("hgrc", "w", text=True)
354 354 fp.write('[paths]\n')
355 355
356 356 def addpathconfig(key, value):
357 357 if value:
358 358 fp.write('%s = %s\n' % (key, value))
359 359 self._repo.ui.setconfig('paths', key, value)
360 360
361 361 defpath = _abssource(self._repo, abort=False)
362 362 defpushpath = _abssource(self._repo, True, abort=False)
363 363 addpathconfig('default', defpath)
364 364 if defpath != defpushpath:
365 365 addpathconfig('default-push', defpushpath)
366 366 fp.close()
367 367
368 368 def add(self, ui, match, dryrun, prefix):
369 369 return cmdutil.add(ui, self._repo, match, dryrun, True,
370 370 os.path.join(prefix, self._path))
371 371
372 372 def status(self, rev2, **opts):
373 373 try:
374 374 rev1 = self._state[1]
375 375 ctx1 = self._repo[rev1]
376 376 ctx2 = self._repo[rev2]
377 377 return self._repo.status(ctx1, ctx2, **opts)
378 378 except error.RepoLookupError, inst:
379 379 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
380 380 % (inst, subrelpath(self)))
381 381 return [], [], [], [], [], [], []
382 382
383 383 def diff(self, diffopts, node2, match, prefix, **opts):
384 384 try:
385 385 node1 = node.bin(self._state[1])
386 386 # We currently expect node2 to come from substate and be
387 387 # in hex format
388 388 if node2 is not None:
389 389 node2 = node.bin(node2)
390 390 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
391 391 node1, node2, match,
392 392 prefix=os.path.join(prefix, self._path),
393 393 listsubrepos=True, **opts)
394 394 except error.RepoLookupError, inst:
395 395 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
396 396 % (inst, subrelpath(self)))
397 397
398 398 def archive(self, ui, archiver, prefix):
399 399 abstractsubrepo.archive(self, ui, archiver, prefix)
400 400
401 401 rev = self._state[1]
402 402 ctx = self._repo[rev]
403 403 for subpath in ctx.substate:
404 404 s = subrepo(ctx, subpath)
405 405 s.archive(ui, archiver, os.path.join(prefix, self._path))
406 406
407 407 def dirty(self, ignoreupdate=False):
408 408 r = self._state[1]
409 409 if r == '' and not ignoreupdate: # no state recorded
410 410 return True
411 411 w = self._repo[None]
412 412 if w.p1() != self._repo[r] and not ignoreupdate:
413 413 # different version checked out
414 414 return True
415 415 return w.dirty() # working directory changed
416 416
417 417 def checknested(self, path):
418 418 return self._repo._checknested(self._repo.wjoin(path))
419 419
420 420 def commit(self, text, user, date):
421 421 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
422 422 n = self._repo.commit(text, user, date)
423 423 if not n:
424 424 return self._repo['.'].hex() # different version checked out
425 425 return node.hex(n)
426 426
427 427 def remove(self):
428 428 # we can't fully delete the repository as it may contain
429 429 # local-only history
430 430 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
431 431 hg.clean(self._repo, node.nullid, False)
432 432
433 433 def _get(self, state):
434 434 source, revision, kind = state
435 435 try:
436 436 self._repo.lookup(revision)
437 437 except error.RepoError:
438 438 self._repo._subsource = source
439 439 srcurl = _abssource(self._repo)
440 440 self._repo.ui.status(_('pulling subrepo %s from %s\n')
441 441 % (subrelpath(self), srcurl))
442 442 other = hg.repository(self._repo.ui, srcurl)
443 443 self._repo.pull(other)
444 bookmarks.updatefromremote(self._repo.ui, self._repo, other)
444 445
445 446 def get(self, state, overwrite=False):
446 447 self._get(state)
447 448 source, revision, kind = state
448 449 self._repo.ui.debug("getting subrepo %s\n" % self._path)
449 450 hg.clean(self._repo, revision, False)
450 451
451 452 def merge(self, state):
452 453 self._get(state)
453 454 cur = self._repo['.']
454 455 dst = self._repo[state[1]]
455 456 anc = dst.ancestor(cur)
456 457
457 458 def mergefunc():
458 459 if anc == cur:
459 460 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
460 461 hg.update(self._repo, state[1])
461 462 elif anc == dst:
462 463 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
463 464 else:
464 465 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
465 466 hg.merge(self._repo, state[1], remind=False)
466 467
467 468 wctx = self._repo[None]
468 469 if self.dirty():
469 470 if anc != dst:
470 471 if _updateprompt(self._repo.ui, self, wctx.dirty(), cur, dst):
471 472 mergefunc()
472 473 else:
473 474 mergefunc()
474 475 else:
475 476 mergefunc()
476 477
477 478 def push(self, force):
478 479 # push subrepos depth-first for coherent ordering
479 480 c = self._repo['']
480 481 subs = c.substate # only repos that are committed
481 482 for s in sorted(subs):
482 483 if not c.sub(s).push(force):
483 484 return False
484 485
485 486 dsturl = _abssource(self._repo, True)
486 487 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
487 488 (subrelpath(self), dsturl))
488 489 other = hg.repository(self._repo.ui, dsturl)
489 490 return self._repo.push(other, force)
490 491
491 492 def outgoing(self, ui, dest, opts):
492 493 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
493 494
494 495 def incoming(self, ui, source, opts):
495 496 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
496 497
497 498 def files(self):
498 499 rev = self._state[1]
499 500 ctx = self._repo[rev]
500 501 return ctx.manifest()
501 502
502 503 def filedata(self, name):
503 504 rev = self._state[1]
504 505 return self._repo[rev][name].data()
505 506
506 507 def fileflags(self, name):
507 508 rev = self._state[1]
508 509 ctx = self._repo[rev]
509 510 return ctx.flags(name)
510 511
511 512
512 513 class svnsubrepo(abstractsubrepo):
513 514 def __init__(self, ctx, path, state):
514 515 self._path = path
515 516 self._state = state
516 517 self._ctx = ctx
517 518 self._ui = ctx._repo.ui
518 519
519 520 def _svncommand(self, commands, filename=''):
520 521 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
521 522 cmd = ['svn'] + commands + [path]
522 523 env = dict(os.environ)
523 524 # Avoid localized output, preserve current locale for everything else.
524 525 env['LC_MESSAGES'] = 'C'
525 526 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
526 527 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
527 528 universal_newlines=True, env=env)
528 529 stdout, stderr = p.communicate()
529 530 stderr = stderr.strip()
530 531 if stderr:
531 532 raise util.Abort(stderr)
532 533 return stdout
533 534
534 535 def _wcrevs(self):
535 536 # Get the working directory revision as well as the last
536 537 # commit revision so we can compare the subrepo state with
537 538 # both. We used to store the working directory one.
538 539 output = self._svncommand(['info', '--xml'])
539 540 doc = xml.dom.minidom.parseString(output)
540 541 entries = doc.getElementsByTagName('entry')
541 542 lastrev, rev = '0', '0'
542 543 if entries:
543 544 rev = str(entries[0].getAttribute('revision')) or '0'
544 545 commits = entries[0].getElementsByTagName('commit')
545 546 if commits:
546 547 lastrev = str(commits[0].getAttribute('revision')) or '0'
547 548 return (lastrev, rev)
548 549
549 550 def _wcrev(self):
550 551 return self._wcrevs()[0]
551 552
552 553 def _wcchanged(self):
553 554 """Return (changes, extchanges) where changes is True
554 555 if the working directory was changed, and extchanges is
555 556 True if any of these changes concern an external entry.
556 557 """
557 558 output = self._svncommand(['status', '--xml'])
558 559 externals, changes = [], []
559 560 doc = xml.dom.minidom.parseString(output)
560 561 for e in doc.getElementsByTagName('entry'):
561 562 s = e.getElementsByTagName('wc-status')
562 563 if not s:
563 564 continue
564 565 item = s[0].getAttribute('item')
565 566 props = s[0].getAttribute('props')
566 567 path = e.getAttribute('path')
567 568 if item == 'external':
568 569 externals.append(path)
569 570 if (item not in ('', 'normal', 'unversioned', 'external')
570 571 or props not in ('', 'none')):
571 572 changes.append(path)
572 573 for path in changes:
573 574 for ext in externals:
574 575 if path == ext or path.startswith(ext + os.sep):
575 576 return True, True
576 577 return bool(changes), False
577 578
578 579 def dirty(self, ignoreupdate=False):
579 580 if not self._wcchanged()[0]:
580 581 if self._state[1] in self._wcrevs() or ignoreupdate:
581 582 return False
582 583 return True
583 584
584 585 def commit(self, text, user, date):
585 586 # user and date are out of our hands since svn is centralized
586 587 changed, extchanged = self._wcchanged()
587 588 if not changed:
588 589 return self._wcrev()
589 590 if extchanged:
590 591 # Do not try to commit externals
591 592 raise util.Abort(_('cannot commit svn externals'))
592 593 commitinfo = self._svncommand(['commit', '-m', text])
593 594 self._ui.status(commitinfo)
594 595 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
595 596 if not newrev:
596 597 raise util.Abort(commitinfo.splitlines()[-1])
597 598 newrev = newrev.groups()[0]
598 599 self._ui.status(self._svncommand(['update', '-r', newrev]))
599 600 return newrev
600 601
601 602 def remove(self):
602 603 if self.dirty():
603 604 self._ui.warn(_('not removing repo %s because '
604 605 'it has changes.\n' % self._path))
605 606 return
606 607 self._ui.note(_('removing subrepo %s\n') % self._path)
607 608
608 609 def onerror(function, path, excinfo):
609 610 if function is not os.remove:
610 611 raise
611 612 # read-only files cannot be unlinked under Windows
612 613 s = os.stat(path)
613 614 if (s.st_mode & stat.S_IWRITE) != 0:
614 615 raise
615 616 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
616 617 os.remove(path)
617 618
618 619 path = self._ctx._repo.wjoin(self._path)
619 620 shutil.rmtree(path, onerror=onerror)
620 621 try:
621 622 os.removedirs(os.path.dirname(path))
622 623 except OSError:
623 624 pass
624 625
625 626 def get(self, state, overwrite=False):
626 627 if overwrite:
627 628 self._svncommand(['revert', '--recursive'])
628 629 status = self._svncommand(['checkout', state[0], '--revision', state[1]])
629 630 if not re.search('Checked out revision [0-9]+.', status):
630 631 raise util.Abort(status.splitlines()[-1])
631 632 self._ui.status(status)
632 633
633 634 def merge(self, state):
634 635 old = self._state[1]
635 636 new = state[1]
636 637 if new != self._wcrev():
637 638 dirty = old == self._wcrev() or self._wcchanged()[0]
638 639 if _updateprompt(self._ui, self, dirty, self._wcrev(), new):
639 640 self.get(state, False)
640 641
641 642 def push(self, force):
642 643 # push is a no-op for SVN
643 644 return True
644 645
645 646 def files(self):
646 647 output = self._svncommand(['list'])
647 648 # This works because svn forbids \n in filenames.
648 649 return output.splitlines()
649 650
650 651 def filedata(self, name):
651 652 return self._svncommand(['cat'], name)
652 653
653 654
654 655 class gitsubrepo(abstractsubrepo):
655 656 def __init__(self, ctx, path, state):
656 657 # TODO add git version check.
657 658 self._state = state
658 659 self._ctx = ctx
659 660 self._path = path
660 661 self._relpath = os.path.join(reporelpath(ctx._repo), path)
661 662 self._abspath = ctx._repo.wjoin(path)
662 663 self._subparent = ctx._repo
663 664 self._ui = ctx._repo.ui
664 665
665 666 def _gitcommand(self, commands, env=None, stream=False):
666 667 return self._gitdir(commands, env=env, stream=stream)[0]
667 668
668 669 def _gitdir(self, commands, env=None, stream=False):
669 670 return self._gitnodir(commands, env=env, stream=stream,
670 671 cwd=self._abspath)
671 672
672 673 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
673 674 """Calls the git command
674 675
675 676 The methods tries to call the git command. versions previor to 1.6.0
676 677 are not supported and very probably fail.
677 678 """
678 679 self._ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
679 680 # unless ui.quiet is set, print git's stderr,
680 681 # which is mostly progress and useful info
681 682 errpipe = None
682 683 if self._ui.quiet:
683 684 errpipe = open(os.devnull, 'w')
684 685 p = subprocess.Popen(['git'] + commands, bufsize=-1, cwd=cwd, env=env,
685 686 close_fds=util.closefds,
686 687 stdout=subprocess.PIPE, stderr=errpipe)
687 688 if stream:
688 689 return p.stdout, None
689 690
690 691 retdata = p.stdout.read().strip()
691 692 # wait for the child to exit to avoid race condition.
692 693 p.wait()
693 694
694 695 if p.returncode != 0 and p.returncode != 1:
695 696 # there are certain error codes that are ok
696 697 command = commands[0]
697 698 if command in ('cat-file', 'symbolic-ref'):
698 699 return retdata, p.returncode
699 700 # for all others, abort
700 701 raise util.Abort('git %s error %d in %s' %
701 702 (command, p.returncode, self._relpath))
702 703
703 704 return retdata, p.returncode
704 705
705 706 def _gitmissing(self):
706 707 return not os.path.exists(os.path.join(self._abspath, '.git'))
707 708
708 709 def _gitstate(self):
709 710 return self._gitcommand(['rev-parse', 'HEAD'])
710 711
711 712 def _gitcurrentbranch(self):
712 713 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
713 714 if err:
714 715 current = None
715 716 return current
716 717
717 718 def _gitremote(self, remote):
718 719 out = self._gitcommand(['remote', 'show', '-n', remote])
719 720 line = out.split('\n')[1]
720 721 i = line.index('URL: ') + len('URL: ')
721 722 return line[i:]
722 723
723 724 def _githavelocally(self, revision):
724 725 out, code = self._gitdir(['cat-file', '-e', revision])
725 726 return code == 0
726 727
727 728 def _gitisancestor(self, r1, r2):
728 729 base = self._gitcommand(['merge-base', r1, r2])
729 730 return base == r1
730 731
731 732 def _gitbranchmap(self):
732 733 '''returns 2 things:
733 734 a map from git branch to revision
734 735 a map from revision to branches'''
735 736 branch2rev = {}
736 737 rev2branch = {}
737 738
738 739 out = self._gitcommand(['for-each-ref', '--format',
739 740 '%(objectname) %(refname)'])
740 741 for line in out.split('\n'):
741 742 revision, ref = line.split(' ')
742 743 if (not ref.startswith('refs/heads/') and
743 744 not ref.startswith('refs/remotes/')):
744 745 continue
745 746 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
746 747 continue # ignore remote/HEAD redirects
747 748 branch2rev[ref] = revision
748 749 rev2branch.setdefault(revision, []).append(ref)
749 750 return branch2rev, rev2branch
750 751
751 752 def _gittracking(self, branches):
752 753 'return map of remote branch to local tracking branch'
753 754 # assumes no more than one local tracking branch for each remote
754 755 tracking = {}
755 756 for b in branches:
756 757 if b.startswith('refs/remotes/'):
757 758 continue
758 759 remote = self._gitcommand(['config', 'branch.%s.remote' % b])
759 760 if remote:
760 761 ref = self._gitcommand(['config', 'branch.%s.merge' % b])
761 762 tracking['refs/remotes/%s/%s' %
762 763 (remote, ref.split('/', 2)[2])] = b
763 764 return tracking
764 765
765 766 def _abssource(self, source):
766 767 self._subsource = source
767 768 return _abssource(self)
768 769
769 770 def _fetch(self, source, revision):
770 771 if self._gitmissing():
771 772 source = self._abssource(source)
772 773 self._ui.status(_('cloning subrepo %s from %s\n') %
773 774 (self._relpath, source))
774 775 self._gitnodir(['clone', source, self._abspath])
775 776 if self._githavelocally(revision):
776 777 return
777 778 self._ui.status(_('pulling subrepo %s from %s\n') %
778 779 (self._relpath, self._gitremote('origin')))
779 780 # try only origin: the originally cloned repo
780 781 self._gitcommand(['fetch'])
781 782 if not self._githavelocally(revision):
782 783 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
783 784 (revision, self._relpath))
784 785
785 786 def dirty(self, ignoreupdate=False):
786 787 if self._gitmissing():
787 788 return True
788 789 if not ignoreupdate and self._state[1] != self._gitstate():
789 790 # different version checked out
790 791 return True
791 792 # check for staged changes or modified files; ignore untracked files
792 793 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
793 794 return code == 1
794 795
795 796 def get(self, state, overwrite=False):
796 797 source, revision, kind = state
797 798 self._fetch(source, revision)
798 799 # if the repo was set to be bare, unbare it
799 800 if self._gitcommand(['config', '--bool', 'core.bare']) == 'true':
800 801 self._gitcommand(['config', 'core.bare', 'false'])
801 802 if self._gitstate() == revision:
802 803 self._gitcommand(['reset', '--hard', 'HEAD'])
803 804 return
804 805 elif self._gitstate() == revision:
805 806 if overwrite:
806 807 # first reset the index to unmark new files for commit, because
807 808 # reset --hard will otherwise throw away files added for commit,
808 809 # not just unmark them.
809 810 self._gitcommand(['reset', 'HEAD'])
810 811 self._gitcommand(['reset', '--hard', 'HEAD'])
811 812 return
812 813 branch2rev, rev2branch = self._gitbranchmap()
813 814
814 815 def checkout(args):
815 816 cmd = ['checkout']
816 817 if overwrite:
817 818 # first reset the index to unmark new files for commit, because
818 819 # the -f option will otherwise throw away files added for
819 820 # commit, not just unmark them.
820 821 self._gitcommand(['reset', 'HEAD'])
821 822 cmd.append('-f')
822 823 self._gitcommand(cmd + args)
823 824
824 825 def rawcheckout():
825 826 # no branch to checkout, check it out with no branch
826 827 self._ui.warn(_('checking out detached HEAD in subrepo %s\n') %
827 828 self._relpath)
828 829 self._ui.warn(_('check out a git branch if you intend '
829 830 'to make changes\n'))
830 831 checkout(['-q', revision])
831 832
832 833 if revision not in rev2branch:
833 834 rawcheckout()
834 835 return
835 836 branches = rev2branch[revision]
836 837 firstlocalbranch = None
837 838 for b in branches:
838 839 if b == 'refs/heads/master':
839 840 # master trumps all other branches
840 841 checkout(['refs/heads/master'])
841 842 return
842 843 if not firstlocalbranch and not b.startswith('refs/remotes/'):
843 844 firstlocalbranch = b
844 845 if firstlocalbranch:
845 846 checkout([firstlocalbranch])
846 847 return
847 848
848 849 tracking = self._gittracking(branch2rev.keys())
849 850 # choose a remote branch already tracked if possible
850 851 remote = branches[0]
851 852 if remote not in tracking:
852 853 for b in branches:
853 854 if b in tracking:
854 855 remote = b
855 856 break
856 857
857 858 if remote not in tracking:
858 859 # create a new local tracking branch
859 860 local = remote.split('/', 2)[2]
860 861 checkout(['-b', local, remote])
861 862 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
862 863 # When updating to a tracked remote branch,
863 864 # if the local tracking branch is downstream of it,
864 865 # a normal `git pull` would have performed a "fast-forward merge"
865 866 # which is equivalent to updating the local branch to the remote.
866 867 # Since we are only looking at branching at update, we need to
867 868 # detect this situation and perform this action lazily.
868 869 if tracking[remote] != self._gitcurrentbranch():
869 870 checkout([tracking[remote]])
870 871 self._gitcommand(['merge', '--ff', remote])
871 872 else:
872 873 # a real merge would be required, just checkout the revision
873 874 rawcheckout()
874 875
875 876 def commit(self, text, user, date):
876 877 if self._gitmissing():
877 878 raise util.Abort(_("subrepo %s is missing") % self._relpath)
878 879 cmd = ['commit', '-a', '-m', text]
879 880 env = os.environ.copy()
880 881 if user:
881 882 cmd += ['--author', user]
882 883 if date:
883 884 # git's date parser silently ignores when seconds < 1e9
884 885 # convert to ISO8601
885 886 env['GIT_AUTHOR_DATE'] = util.datestr(date,
886 887 '%Y-%m-%dT%H:%M:%S %1%2')
887 888 self._gitcommand(cmd, env=env)
888 889 # make sure commit works otherwise HEAD might not exist under certain
889 890 # circumstances
890 891 return self._gitstate()
891 892
892 893 def merge(self, state):
893 894 source, revision, kind = state
894 895 self._fetch(source, revision)
895 896 base = self._gitcommand(['merge-base', revision, self._state[1]])
896 897 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
897 898
898 899 def mergefunc():
899 900 if base == revision:
900 901 self.get(state) # fast forward merge
901 902 elif base != self._state[1]:
902 903 self._gitcommand(['merge', '--no-commit', revision])
903 904
904 905 if self.dirty():
905 906 if self._gitstate() != revision:
906 907 dirty = self._gitstate() == self._state[1] or code != 0
907 908 if _updateprompt(self._ui, self, dirty,
908 909 self._state[1][:7], revision[:7]):
909 910 mergefunc()
910 911 else:
911 912 mergefunc()
912 913
913 914 def push(self, force):
914 915 if self._gitmissing():
915 916 raise util.Abort(_("subrepo %s is missing") % self._relpath)
916 917 # if a branch in origin contains the revision, nothing to do
917 918 branch2rev, rev2branch = self._gitbranchmap()
918 919 if self._state[1] in rev2branch:
919 920 for b in rev2branch[self._state[1]]:
920 921 if b.startswith('refs/remotes/origin/'):
921 922 return True
922 923 for b, revision in branch2rev.iteritems():
923 924 if b.startswith('refs/remotes/origin/'):
924 925 if self._gitisancestor(self._state[1], revision):
925 926 return True
926 927 # otherwise, try to push the currently checked out branch
927 928 cmd = ['push']
928 929 if force:
929 930 cmd.append('--force')
930 931
931 932 current = self._gitcurrentbranch()
932 933 if current:
933 934 # determine if the current branch is even useful
934 935 if not self._gitisancestor(self._state[1], current):
935 936 self._ui.warn(_('unrelated git branch checked out '
936 937 'in subrepo %s\n') % self._relpath)
937 938 return False
938 939 self._ui.status(_('pushing branch %s of subrepo %s\n') %
939 940 (current.split('/', 2)[2], self._relpath))
940 941 self._gitcommand(cmd + ['origin', current])
941 942 return True
942 943 else:
943 944 self._ui.warn(_('no branch checked out in subrepo %s\n'
944 945 'cannot push revision %s') %
945 946 (self._relpath, self._state[1]))
946 947 return False
947 948
948 949 def remove(self):
949 950 if self._gitmissing():
950 951 return
951 952 if self.dirty():
952 953 self._ui.warn(_('not removing repo %s because '
953 954 'it has changes.\n') % self._relpath)
954 955 return
955 956 # we can't fully delete the repository as it may contain
956 957 # local-only history
957 958 self._ui.note(_('removing subrepo %s\n') % self._relpath)
958 959 self._gitcommand(['config', 'core.bare', 'true'])
959 960 for f in os.listdir(self._abspath):
960 961 if f == '.git':
961 962 continue
962 963 path = os.path.join(self._abspath, f)
963 964 if os.path.isdir(path) and not os.path.islink(path):
964 965 shutil.rmtree(path)
965 966 else:
966 967 os.remove(path)
967 968
968 969 def archive(self, ui, archiver, prefix):
969 970 source, revision = self._state
970 971 self._fetch(source, revision)
971 972
972 973 # Parse git's native archive command.
973 974 # This should be much faster than manually traversing the trees
974 975 # and objects with many subprocess calls.
975 976 tarstream = self._gitcommand(['archive', revision], stream=True)
976 977 tar = tarfile.open(fileobj=tarstream, mode='r|')
977 978 relpath = subrelpath(self)
978 979 ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
979 980 for i, info in enumerate(tar):
980 981 if info.isdir():
981 982 continue
982 983 if info.issym():
983 984 data = info.linkname
984 985 else:
985 986 data = tar.extractfile(info).read()
986 987 archiver.addfile(os.path.join(prefix, self._path, info.name),
987 988 info.mode, info.issym(), data)
988 989 ui.progress(_('archiving (%s)') % relpath, i + 1,
989 990 unit=_('files'))
990 991 ui.progress(_('archiving (%s)') % relpath, None)
991 992
992 993
993 994 def status(self, rev2, **opts):
994 995 if self._gitmissing():
995 996 # if the repo is missing, return no results
996 997 return [], [], [], [], [], [], []
997 998 rev1 = self._state[1]
998 999 modified, added, removed = [], [], []
999 1000 if rev2:
1000 1001 command = ['diff-tree', rev1, rev2]
1001 1002 else:
1002 1003 command = ['diff-index', rev1]
1003 1004 out = self._gitcommand(command)
1004 1005 for line in out.split('\n'):
1005 1006 tab = line.find('\t')
1006 1007 if tab == -1:
1007 1008 continue
1008 1009 status, f = line[tab - 1], line[tab + 1:]
1009 1010 if status == 'M':
1010 1011 modified.append(f)
1011 1012 elif status == 'A':
1012 1013 added.append(f)
1013 1014 elif status == 'D':
1014 1015 removed.append(f)
1015 1016
1016 1017 deleted = unknown = ignored = clean = []
1017 1018 return modified, added, removed, deleted, unknown, ignored, clean
1018 1019
1019 1020 types = {
1020 1021 'hg': hgsubrepo,
1021 1022 'svn': svnsubrepo,
1022 1023 'git': gitsubrepo,
1023 1024 }
@@ -1,124 +1,120 b''
1 1
2 2 $ hg init a
3 3 $ cd a
4 4 $ echo a > a
5 5 $ hg ci -Ama -d '1123456789 0'
6 6 adding a
7 7 $ hg --config server.uncompressed=True serve -p $HGPORT -d --pid-file=hg.pid
8 8 $ cat hg.pid >> $DAEMON_PIDS
9 9 $ cd ..
10 10 $ ("$TESTDIR/tinyproxy.py" $HGPORT1 localhost >proxy.log 2>&1 </dev/null &
11 11 $ echo $! > proxy.pid)
12 12 $ cat proxy.pid >> $DAEMON_PIDS
13 13 $ sleep 2
14 14
15 15 url for proxy, stream
16 16
17 17 $ http_proxy=http://localhost:$HGPORT1/ hg --config http_proxy.always=True clone --uncompressed http://localhost:$HGPORT/ b
18 18 streaming all changes
19 19 3 files to transfer, 303 bytes of data
20 20 transferred * bytes in * seconds (*B/sec) (glob)
21 21 updating to branch default
22 22 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
23 23 $ cd b
24 24 $ hg verify
25 25 checking changesets
26 26 checking manifests
27 27 crosschecking files in changesets and manifests
28 28 checking files
29 29 1 files, 1 changesets, 1 total revisions
30 30 $ cd ..
31 31
32 32 url for proxy, pull
33 33
34 34 $ http_proxy=http://localhost:$HGPORT1/ hg --config http_proxy.always=True clone http://localhost:$HGPORT/ b-pull
35 35 requesting all changes
36 36 adding changesets
37 37 adding manifests
38 38 adding file changes
39 39 added 1 changesets with 1 changes to 1 files
40 40 updating to branch default
41 41 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
42 42 $ cd b-pull
43 43 $ hg verify
44 44 checking changesets
45 45 checking manifests
46 46 crosschecking files in changesets and manifests
47 47 checking files
48 48 1 files, 1 changesets, 1 total revisions
49 49 $ cd ..
50 50
51 51 host:port for proxy
52 52
53 53 $ http_proxy=localhost:$HGPORT1 hg clone --config http_proxy.always=True http://localhost:$HGPORT/ c
54 54 requesting all changes
55 55 adding changesets
56 56 adding manifests
57 57 adding file changes
58 58 added 1 changesets with 1 changes to 1 files
59 59 updating to branch default
60 60 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
61 61
62 62 proxy url with user name and password
63 63
64 64 $ http_proxy=http://user:passwd@localhost:$HGPORT1 hg clone --config http_proxy.always=True http://localhost:$HGPORT/ d
65 65 requesting all changes
66 66 adding changesets
67 67 adding manifests
68 68 adding file changes
69 69 added 1 changesets with 1 changes to 1 files
70 70 updating to branch default
71 71 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
72 72
73 73 url with user name and password
74 74
75 75 $ http_proxy=http://user:passwd@localhost:$HGPORT1 hg clone --config http_proxy.always=True http://user:passwd@localhost:$HGPORT/ e
76 76 requesting all changes
77 77 adding changesets
78 78 adding manifests
79 79 adding file changes
80 80 added 1 changesets with 1 changes to 1 files
81 81 updating to branch default
82 82 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
83 83
84 84 bad host:port for proxy
85 85
86 86 $ http_proxy=localhost:$HGPORT2 hg clone --config http_proxy.always=True http://localhost:$HGPORT/ f
87 87 abort: error: Connection refused
88 88 [255]
89 89
90 90 do not use the proxy if it is in the no list
91 91
92 92 $ http_proxy=localhost:$HGPORT1 hg clone --config http_proxy.no=localhost http://localhost:$HGPORT/ g
93 93 requesting all changes
94 94 adding changesets
95 95 adding manifests
96 96 adding file changes
97 97 added 1 changesets with 1 changes to 1 files
98 98 updating to branch default
99 99 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
100 100 $ cat proxy.log
101 101 * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
102 102 * - - [*] "GET http://localhost:$HGPORT/?cmd=stream_out HTTP/1.1" - - (glob)
103 103 * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
104 104 * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
105 105 * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
106 106 * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
107 107 * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
108 * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
109 * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
110 * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
108 111 * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
109 112 * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
110 113 * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
111 114 * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
112 115 * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
113 * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
114 116 * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
115 117 * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
116 118 * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
117 119 * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
118 * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
119 * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
120 * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
121 * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
122 * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
123 * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
124 120
General Comments 0
You need to be logged in to leave comments. Login now