##// END OF EJS Templates
merge default into stable for 3.9 code freeze
Matt Mackall -
r29605:519bb4f9 merge 3.9-rc stable
parent child Browse files
Show More

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

This diff has been collapsed as it changes many lines, (506 lines changed) Show them Hide them
@@ -0,0 +1,506 b''
1 # journal.py
2 #
3 # Copyright 2014-2016 Facebook, Inc.
4 #
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
7 """Track previous positions of bookmarks (EXPERIMENTAL)
8
9 This extension adds a new command: `hg journal`, which shows you where
10 bookmarks were previously located.
11
12 """
13
14 from __future__ import absolute_import
15
16 import collections
17 import errno
18 import os
19 import weakref
20
21 from mercurial.i18n import _
22
23 from mercurial import (
24 bookmarks,
25 cmdutil,
26 commands,
27 dirstate,
28 dispatch,
29 error,
30 extensions,
31 hg,
32 localrepo,
33 lock,
34 node,
35 util,
36 )
37
38 from . import share
39
40 cmdtable = {}
41 command = cmdutil.command(cmdtable)
42
43 # Note for extension authors: ONLY specify testedwith = 'internal' for
44 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
45 # be specifying the version(s) of Mercurial they are tested with, or
46 # leave the attribute unspecified.
47 testedwith = 'internal'
48
49 # storage format version; increment when the format changes
50 storageversion = 0
51
52 # namespaces
53 bookmarktype = 'bookmark'
54 wdirparenttype = 'wdirparent'
55 # In a shared repository, what shared feature name is used
56 # to indicate this namespace is shared with the source?
57 sharednamespaces = {
58 bookmarktype: hg.sharedbookmarks,
59 }
60
61 # Journal recording, register hooks and storage object
62 def extsetup(ui):
63 extensions.wrapfunction(dispatch, 'runcommand', runcommand)
64 extensions.wrapfunction(bookmarks.bmstore, '_write', recordbookmarks)
65 extensions.wrapfunction(
66 dirstate.dirstate, '_writedirstate', recorddirstateparents)
67 extensions.wrapfunction(
68 localrepo.localrepository.dirstate, 'func', wrapdirstate)
69 extensions.wrapfunction(hg, 'postshare', wrappostshare)
70 extensions.wrapfunction(hg, 'copystore', unsharejournal)
71
72 def reposetup(ui, repo):
73 if repo.local():
74 repo.journal = journalstorage(repo)
75
76 def runcommand(orig, lui, repo, cmd, fullargs, *args):
77 """Track the command line options for recording in the journal"""
78 journalstorage.recordcommand(*fullargs)
79 return orig(lui, repo, cmd, fullargs, *args)
80
81 # hooks to record dirstate changes
82 def wrapdirstate(orig, repo):
83 """Make journal storage available to the dirstate object"""
84 dirstate = orig(repo)
85 if util.safehasattr(repo, 'journal'):
86 dirstate.journalstorage = repo.journal
87 return dirstate
88
89 def recorddirstateparents(orig, dirstate, dirstatefp):
90 """Records all dirstate parent changes in the journal."""
91 if util.safehasattr(dirstate, 'journalstorage'):
92 old = [node.nullid, node.nullid]
93 nodesize = len(node.nullid)
94 try:
95 # The only source for the old state is in the dirstate file still
96 # on disk; the in-memory dirstate object only contains the new
97 # state. dirstate._opendirstatefile() switches beteen .hg/dirstate
98 # and .hg/dirstate.pending depending on the transaction state.
99 with dirstate._opendirstatefile() as fp:
100 state = fp.read(2 * nodesize)
101 if len(state) == 2 * nodesize:
102 old = [state[:nodesize], state[nodesize:]]
103 except IOError:
104 pass
105
106 new = dirstate.parents()
107 if old != new:
108 # only record two hashes if there was a merge
109 oldhashes = old[:1] if old[1] == node.nullid else old
110 newhashes = new[:1] if new[1] == node.nullid else new
111 dirstate.journalstorage.record(
112 wdirparenttype, '.', oldhashes, newhashes)
113
114 return orig(dirstate, dirstatefp)
115
116 # hooks to record bookmark changes (both local and remote)
117 def recordbookmarks(orig, store, fp):
118 """Records all bookmark changes in the journal."""
119 repo = store._repo
120 if util.safehasattr(repo, 'journal'):
121 oldmarks = bookmarks.bmstore(repo)
122 for mark, value in store.iteritems():
123 oldvalue = oldmarks.get(mark, node.nullid)
124 if value != oldvalue:
125 repo.journal.record(bookmarktype, mark, oldvalue, value)
126 return orig(store, fp)
127
128 # shared repository support
129 def _readsharedfeatures(repo):
130 """A set of shared features for this repository"""
131 try:
132 return set(repo.vfs.read('shared').splitlines())
133 except IOError as inst:
134 if inst.errno != errno.ENOENT:
135 raise
136 return set()
137
138 def _mergeentriesiter(*iterables, **kwargs):
139 """Given a set of sorted iterables, yield the next entry in merged order
140
141 Note that by default entries go from most recent to oldest.
142 """
143 order = kwargs.pop('order', max)
144 iterables = [iter(it) for it in iterables]
145 # this tracks still active iterables; iterables are deleted as they are
146 # exhausted, which is why this is a dictionary and why each entry also
147 # stores the key. Entries are mutable so we can store the next value each
148 # time.
149 iterable_map = {}
150 for key, it in enumerate(iterables):
151 try:
152 iterable_map[key] = [next(it), key, it]
153 except StopIteration:
154 # empty entry, can be ignored
155 pass
156
157 while iterable_map:
158 value, key, it = order(iterable_map.itervalues())
159 yield value
160 try:
161 iterable_map[key][0] = next(it)
162 except StopIteration:
163 # this iterable is empty, remove it from consideration
164 del iterable_map[key]
165
166 def wrappostshare(orig, sourcerepo, destrepo, **kwargs):
167 """Mark this shared working copy as sharing journal information"""
168 orig(sourcerepo, destrepo, **kwargs)
169 with destrepo.vfs('shared', 'a') as fp:
170 fp.write('journal\n')
171
172 def unsharejournal(orig, ui, repo, repopath):
173 """Copy shared journal entries into this repo when unsharing"""
174 if (repo.path == repopath and repo.shared() and
175 util.safehasattr(repo, 'journal')):
176 sharedrepo = share._getsrcrepo(repo)
177 sharedfeatures = _readsharedfeatures(repo)
178 if sharedrepo and sharedfeatures > set(['journal']):
179 # there is a shared repository and there are shared journal entries
180 # to copy. move shared date over from source to destination but
181 # move the local file first
182 if repo.vfs.exists('journal'):
183 journalpath = repo.join('journal')
184 util.rename(journalpath, journalpath + '.bak')
185 storage = repo.journal
186 local = storage._open(
187 repo.vfs, filename='journal.bak', _newestfirst=False)
188 shared = (
189 e for e in storage._open(sharedrepo.vfs, _newestfirst=False)
190 if sharednamespaces.get(e.namespace) in sharedfeatures)
191 for entry in _mergeentriesiter(local, shared, order=min):
192 storage._write(repo.vfs, entry)
193
194 return orig(ui, repo, repopath)
195
196 class journalentry(collections.namedtuple(
197 'journalentry',
198 'timestamp user command namespace name oldhashes newhashes')):
199 """Individual journal entry
200
201 * timestamp: a mercurial (time, timezone) tuple
202 * user: the username that ran the command
203 * namespace: the entry namespace, an opaque string
204 * name: the name of the changed item, opaque string with meaning in the
205 namespace
206 * command: the hg command that triggered this record
207 * oldhashes: a tuple of one or more binary hashes for the old location
208 * newhashes: a tuple of one or more binary hashes for the new location
209
210 Handles serialisation from and to the storage format. Fields are
211 separated by newlines, hashes are written out in hex separated by commas,
212 timestamp and timezone are separated by a space.
213
214 """
215 @classmethod
216 def fromstorage(cls, line):
217 (time, user, command, namespace, name,
218 oldhashes, newhashes) = line.split('\n')
219 timestamp, tz = time.split()
220 timestamp, tz = float(timestamp), int(tz)
221 oldhashes = tuple(node.bin(hash) for hash in oldhashes.split(','))
222 newhashes = tuple(node.bin(hash) for hash in newhashes.split(','))
223 return cls(
224 (timestamp, tz), user, command, namespace, name,
225 oldhashes, newhashes)
226
227 def __str__(self):
228 """String representation for storage"""
229 time = ' '.join(map(str, self.timestamp))
230 oldhashes = ','.join([node.hex(hash) for hash in self.oldhashes])
231 newhashes = ','.join([node.hex(hash) for hash in self.newhashes])
232 return '\n'.join((
233 time, self.user, self.command, self.namespace, self.name,
234 oldhashes, newhashes))
235
236 class journalstorage(object):
237 """Storage for journal entries
238
239 Entries are divided over two files; one with entries that pertain to the
240 local working copy *only*, and one with entries that are shared across
241 multiple working copies when shared using the share extension.
242
243 Entries are stored with NUL bytes as separators. See the journalentry
244 class for the per-entry structure.
245
246 The file format starts with an integer version, delimited by a NUL.
247
248 This storage uses a dedicated lock; this makes it easier to avoid issues
249 with adding entries that added when the regular wlock is unlocked (e.g.
250 the dirstate).
251
252 """
253 _currentcommand = ()
254 _lockref = None
255
256 def __init__(self, repo):
257 self.user = util.getuser()
258 self.ui = repo.ui
259 self.vfs = repo.vfs
260
261 # is this working copy using a shared storage?
262 self.sharedfeatures = self.sharedvfs = None
263 if repo.shared():
264 features = _readsharedfeatures(repo)
265 sharedrepo = share._getsrcrepo(repo)
266 if sharedrepo is not None and 'journal' in features:
267 self.sharedvfs = sharedrepo.vfs
268 self.sharedfeatures = features
269
270 # track the current command for recording in journal entries
271 @property
272 def command(self):
273 commandstr = ' '.join(
274 map(util.shellquote, journalstorage._currentcommand))
275 if '\n' in commandstr:
276 # truncate multi-line commands
277 commandstr = commandstr.partition('\n')[0] + ' ...'
278 return commandstr
279
280 @classmethod
281 def recordcommand(cls, *fullargs):
282 """Set the current hg arguments, stored with recorded entries"""
283 # Set the current command on the class because we may have started
284 # with a non-local repo (cloning for example).
285 cls._currentcommand = fullargs
286
287 def jlock(self, vfs):
288 """Create a lock for the journal file"""
289 if self._lockref and self._lockref():
290 raise error.Abort(_('journal lock does not support nesting'))
291 desc = _('journal of %s') % vfs.base
292 try:
293 l = lock.lock(vfs, 'journal.lock', 0, desc=desc)
294 except error.LockHeld as inst:
295 self.ui.warn(
296 _("waiting for lock on %s held by %r\n") % (desc, inst.locker))
297 # default to 600 seconds timeout
298 l = lock.lock(
299 vfs, 'journal.lock',
300 int(self.ui.config("ui", "timeout", "600")), desc=desc)
301 self.ui.warn(_("got lock after %s seconds\n") % l.delay)
302 self._lockref = weakref.ref(l)
303 return l
304
305 def record(self, namespace, name, oldhashes, newhashes):
306 """Record a new journal entry
307
308 * namespace: an opaque string; this can be used to filter on the type
309 of recorded entries.
310 * name: the name defining this entry; for bookmarks, this is the
311 bookmark name. Can be filtered on when retrieving entries.
312 * oldhashes and newhashes: each a single binary hash, or a list of
313 binary hashes. These represent the old and new position of the named
314 item.
315
316 """
317 if not isinstance(oldhashes, list):
318 oldhashes = [oldhashes]
319 if not isinstance(newhashes, list):
320 newhashes = [newhashes]
321
322 entry = journalentry(
323 util.makedate(), self.user, self.command, namespace, name,
324 oldhashes, newhashes)
325
326 vfs = self.vfs
327 if self.sharedvfs is not None:
328 # write to the shared repository if this feature is being
329 # shared between working copies.
330 if sharednamespaces.get(namespace) in self.sharedfeatures:
331 vfs = self.sharedvfs
332
333 self._write(vfs, entry)
334
335 def _write(self, vfs, entry):
336 with self.jlock(vfs):
337 version = None
338 # open file in amend mode to ensure it is created if missing
339 with vfs('journal', mode='a+b', atomictemp=True) as f:
340 f.seek(0, os.SEEK_SET)
341 # Read just enough bytes to get a version number (up to 2
342 # digits plus separator)
343 version = f.read(3).partition('\0')[0]
344 if version and version != str(storageversion):
345 # different version of the storage. Exit early (and not
346 # write anything) if this is not a version we can handle or
347 # the file is corrupt. In future, perhaps rotate the file
348 # instead?
349 self.ui.warn(
350 _("unsupported journal file version '%s'\n") % version)
351 return
352 if not version:
353 # empty file, write version first
354 f.write(str(storageversion) + '\0')
355 f.seek(0, os.SEEK_END)
356 f.write(str(entry) + '\0')
357
358 def filtered(self, namespace=None, name=None):
359 """Yield all journal entries with the given namespace or name
360
361 Both the namespace and the name are optional; if neither is given all
362 entries in the journal are produced.
363
364 Matching supports regular expressions by using the `re:` prefix
365 (use `literal:` to match names or namespaces that start with `re:`)
366
367 """
368 if namespace is not None:
369 namespace = util.stringmatcher(namespace)[-1]
370 if name is not None:
371 name = util.stringmatcher(name)[-1]
372 for entry in self:
373 if namespace is not None and not namespace(entry.namespace):
374 continue
375 if name is not None and not name(entry.name):
376 continue
377 yield entry
378
379 def __iter__(self):
380 """Iterate over the storage
381
382 Yields journalentry instances for each contained journal record.
383
384 """
385 local = self._open(self.vfs)
386
387 if self.sharedvfs is None:
388 return local
389
390 # iterate over both local and shared entries, but only those
391 # shared entries that are among the currently shared features
392 shared = (
393 e for e in self._open(self.sharedvfs)
394 if sharednamespaces.get(e.namespace) in self.sharedfeatures)
395 return _mergeentriesiter(local, shared)
396
397 def _open(self, vfs, filename='journal', _newestfirst=True):
398 if not vfs.exists(filename):
399 return
400
401 with vfs(filename) as f:
402 raw = f.read()
403
404 lines = raw.split('\0')
405 version = lines and lines[0]
406 if version != str(storageversion):
407 version = version or _('not available')
408 raise error.Abort(_("unknown journal file version '%s'") % version)
409
410 # Skip the first line, it's a version number. Normally we iterate over
411 # these in reverse order to list newest first; only when copying across
412 # a shared storage do we forgo reversing.
413 lines = lines[1:]
414 if _newestfirst:
415 lines = reversed(lines)
416 for line in lines:
417 if not line:
418 continue
419 yield journalentry.fromstorage(line)
420
421 # journal reading
422 # log options that don't make sense for journal
423 _ignoreopts = ('no-merges', 'graph')
424 @command(
425 'journal', [
426 ('', 'all', None, 'show history for all names'),
427 ('c', 'commits', None, 'show commit metadata'),
428 ] + [opt for opt in commands.logopts if opt[1] not in _ignoreopts],
429 '[OPTION]... [BOOKMARKNAME]')
430 def journal(ui, repo, *args, **opts):
431 """show the previous position of bookmarks and the working copy
432
433 The journal is used to see the previous commits that bookmarks and the
434 working copy pointed to. By default the previous locations for the working
435 copy. Passing a bookmark name will show all the previous positions of
436 that bookmark. Use the --all switch to show previous locations for all
437 bookmarks and the working copy; each line will then include the bookmark
438 name, or '.' for the working copy, as well.
439
440 If `name` starts with `re:`, the remainder of the name is treated as
441 a regular expression. To match a name that actually starts with `re:`,
442 use the prefix `literal:`.
443
444 By default hg journal only shows the commit hash and the command that was
445 running at that time. -v/--verbose will show the prior hash, the user, and
446 the time at which it happened.
447
448 Use -c/--commits to output log information on each commit hash; at this
449 point you can use the usual `--patch`, `--git`, `--stat` and `--template`
450 switches to alter the log output for these.
451
452 `hg journal -T json` can be used to produce machine readable output.
453
454 """
455 name = '.'
456 if opts.get('all'):
457 if args:
458 raise error.Abort(
459 _("You can't combine --all and filtering on a name"))
460 name = None
461 if args:
462 name = args[0]
463
464 fm = ui.formatter('journal', opts)
465
466 if opts.get("template") != "json":
467 if name is None:
468 displayname = _('the working copy and bookmarks')
469 else:
470 displayname = "'%s'" % name
471 ui.status(_("previous locations of %s:\n") % displayname)
472
473 limit = cmdutil.loglimit(opts)
474 entry = None
475 for count, entry in enumerate(repo.journal.filtered(name=name)):
476 if count == limit:
477 break
478 newhashesstr = ','.join([node.short(hash) for hash in entry.newhashes])
479 oldhashesstr = ','.join([node.short(hash) for hash in entry.oldhashes])
480
481 fm.startitem()
482 fm.condwrite(ui.verbose, 'oldhashes', '%s -> ', oldhashesstr)
483 fm.write('newhashes', '%s', newhashesstr)
484 fm.condwrite(ui.verbose, 'user', ' %-8s', entry.user)
485 fm.condwrite(
486 opts.get('all') or name.startswith('re:'),
487 'name', ' %-8s', entry.name)
488
489 timestring = util.datestr(entry.timestamp, '%Y-%m-%d %H:%M %1%2')
490 fm.condwrite(ui.verbose, 'date', ' %s', timestring)
491 fm.write('command', ' %s\n', entry.command)
492
493 if opts.get("commits"):
494 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=False)
495 for hash in entry.newhashes:
496 try:
497 ctx = repo[hash]
498 displayer.show(ctx)
499 except error.RepoLookupError as e:
500 fm.write('repolookuperror', "%s\n\n", str(e))
501 displayer.close()
502
503 fm.end()
504
505 if entry is None:
506 ui.status(_("no recorded locations\n"))
@@ -0,0 +1,21 b''
1 #ifndef _HG_BDIFF_H_
2 #define _HG_BDIFF_H_
3
4 struct bdiff_line {
5 int hash, n, e;
6 ssize_t len;
7 const char *l;
8 };
9
10 struct bdiff_hunk;
11 struct bdiff_hunk {
12 int a1, a2, b1, b2;
13 struct bdiff_hunk *next;
14 };
15
16 int bdiff_splitlines(const char *a, ssize_t len, struct bdiff_line **lr);
17 int bdiff_diff(struct bdiff_line *a, int an, struct bdiff_line *b, int bn,
18 struct bdiff_hunk *base);
19 void bdiff_freehunks(struct bdiff_hunk *l);
20
21 #endif
@@ -0,0 +1,203 b''
1 /*
2 bdiff.c - efficient binary diff extension for Mercurial
3
4 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5
6 This software may be used and distributed according to the terms of
7 the GNU General Public License, incorporated herein by reference.
8
9 Based roughly on Python difflib
10 */
11
12 #define PY_SSIZE_T_CLEAN
13 #include <Python.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <limits.h>
17
18 #include "bdiff.h"
19 #include "bitmanipulation.h"
20
21
22 static PyObject *blocks(PyObject *self, PyObject *args)
23 {
24 PyObject *sa, *sb, *rl = NULL, *m;
25 struct bdiff_line *a, *b;
26 struct bdiff_hunk l, *h;
27 int an, bn, count, pos = 0;
28
29 l.next = NULL;
30
31 if (!PyArg_ParseTuple(args, "SS:bdiff", &sa, &sb))
32 return NULL;
33
34 an = bdiff_splitlines(PyBytes_AsString(sa), PyBytes_Size(sa), &a);
35 bn = bdiff_splitlines(PyBytes_AsString(sb), PyBytes_Size(sb), &b);
36
37 if (!a || !b)
38 goto nomem;
39
40 count = bdiff_diff(a, an, b, bn, &l);
41 if (count < 0)
42 goto nomem;
43
44 rl = PyList_New(count);
45 if (!rl)
46 goto nomem;
47
48 for (h = l.next; h; h = h->next) {
49 m = Py_BuildValue("iiii", h->a1, h->a2, h->b1, h->b2);
50 PyList_SetItem(rl, pos, m);
51 pos++;
52 }
53
54 nomem:
55 free(a);
56 free(b);
57 bdiff_freehunks(l.next);
58 return rl ? rl : PyErr_NoMemory();
59 }
60
61 static PyObject *bdiff(PyObject *self, PyObject *args)
62 {
63 char *sa, *sb, *rb;
64 PyObject *result = NULL;
65 struct bdiff_line *al, *bl;
66 struct bdiff_hunk l, *h;
67 int an, bn, count;
68 Py_ssize_t len = 0, la, lb;
69 PyThreadState *_save;
70
71 l.next = NULL;
72
73 if (!PyArg_ParseTuple(args, "s#s#:bdiff", &sa, &la, &sb, &lb))
74 return NULL;
75
76 if (la > UINT_MAX || lb > UINT_MAX) {
77 PyErr_SetString(PyExc_ValueError, "bdiff inputs too large");
78 return NULL;
79 }
80
81 _save = PyEval_SaveThread();
82 an = bdiff_splitlines(sa, la, &al);
83 bn = bdiff_splitlines(sb, lb, &bl);
84 if (!al || !bl)
85 goto nomem;
86
87 count = bdiff_diff(al, an, bl, bn, &l);
88 if (count < 0)
89 goto nomem;
90
91 /* calculate length of output */
92 la = lb = 0;
93 for (h = l.next; h; h = h->next) {
94 if (h->a1 != la || h->b1 != lb)
95 len += 12 + bl[h->b1].l - bl[lb].l;
96 la = h->a2;
97 lb = h->b2;
98 }
99 PyEval_RestoreThread(_save);
100 _save = NULL;
101
102 result = PyBytes_FromStringAndSize(NULL, len);
103
104 if (!result)
105 goto nomem;
106
107 /* build binary patch */
108 rb = PyBytes_AsString(result);
109 la = lb = 0;
110
111 for (h = l.next; h; h = h->next) {
112 if (h->a1 != la || h->b1 != lb) {
113 len = bl[h->b1].l - bl[lb].l;
114 putbe32((uint32_t)(al[la].l - al->l), rb);
115 putbe32((uint32_t)(al[h->a1].l - al->l), rb + 4);
116 putbe32((uint32_t)len, rb + 8);
117 memcpy(rb + 12, bl[lb].l, len);
118 rb += 12 + len;
119 }
120 la = h->a2;
121 lb = h->b2;
122 }
123
124 nomem:
125 if (_save)
126 PyEval_RestoreThread(_save);
127 free(al);
128 free(bl);
129 bdiff_freehunks(l.next);
130 return result ? result : PyErr_NoMemory();
131 }
132
133 /*
134 * If allws != 0, remove all whitespace (' ', \t and \r). Otherwise,
135 * reduce whitespace sequences to a single space and trim remaining whitespace
136 * from end of lines.
137 */
138 static PyObject *fixws(PyObject *self, PyObject *args)
139 {
140 PyObject *s, *result = NULL;
141 char allws, c;
142 const char *r;
143 Py_ssize_t i, rlen, wlen = 0;
144 char *w;
145
146 if (!PyArg_ParseTuple(args, "Sb:fixws", &s, &allws))
147 return NULL;
148 r = PyBytes_AsString(s);
149 rlen = PyBytes_Size(s);
150
151 w = (char *)malloc(rlen ? rlen : 1);
152 if (!w)
153 goto nomem;
154
155 for (i = 0; i != rlen; i++) {
156 c = r[i];
157 if (c == ' ' || c == '\t' || c == '\r') {
158 if (!allws && (wlen == 0 || w[wlen - 1] != ' '))
159 w[wlen++] = ' ';
160 } else if (c == '\n' && !allws
161 && wlen > 0 && w[wlen - 1] == ' ') {
162 w[wlen - 1] = '\n';
163 } else {
164 w[wlen++] = c;
165 }
166 }
167
168 result = PyBytes_FromStringAndSize(w, wlen);
169
170 nomem:
171 free(w);
172 return result ? result : PyErr_NoMemory();
173 }
174
175
176 static char mdiff_doc[] = "Efficient binary diff.";
177
178 static PyMethodDef methods[] = {
179 {"bdiff", bdiff, METH_VARARGS, "calculate a binary diff\n"},
180 {"blocks", blocks, METH_VARARGS, "find a list of matching lines\n"},
181 {"fixws", fixws, METH_VARARGS, "normalize diff whitespaces\n"},
182 {NULL, NULL}
183 };
184
185 #ifdef IS_PY3K
186 static struct PyModuleDef bdiff_module = {
187 PyModuleDef_HEAD_INIT,
188 "bdiff",
189 mdiff_doc,
190 -1,
191 methods
192 };
193
194 PyMODINIT_FUNC PyInit_bdiff(void)
195 {
196 return PyModule_Create(&bdiff_module);
197 }
198 #else
199 PyMODINIT_FUNC initbdiff(void)
200 {
201 Py_InitModule3("bdiff", methods, mdiff_doc);
202 }
203 #endif
@@ -0,0 +1,53 b''
1 #ifndef _HG_BITMANIPULATION_H_
2 #define _HG_BITMANIPULATION_H_
3
4 #include "compat.h"
5
6 static inline uint32_t getbe32(const char *c)
7 {
8 const unsigned char *d = (const unsigned char *)c;
9
10 return ((d[0] << 24) |
11 (d[1] << 16) |
12 (d[2] << 8) |
13 (d[3]));
14 }
15
16 static inline int16_t getbeint16(const char *c)
17 {
18 const unsigned char *d = (const unsigned char *)c;
19
20 return ((d[0] << 8) |
21 (d[1]));
22 }
23
24 static inline uint16_t getbeuint16(const char *c)
25 {
26 const unsigned char *d = (const unsigned char *)c;
27
28 return ((d[0] << 8) |
29 (d[1]));
30 }
31
32 static inline void putbe32(uint32_t x, char *c)
33 {
34 c[0] = (x >> 24) & 0xff;
35 c[1] = (x >> 16) & 0xff;
36 c[2] = (x >> 8) & 0xff;
37 c[3] = (x) & 0xff;
38 }
39
40 static inline double getbefloat64(const char *c)
41 {
42 const unsigned char *d = (const unsigned char *)c;
43 double ret;
44 int i;
45 uint64_t t = 0;
46 for (i = 0; i < 8; i++) {
47 t = (t<<8) + d[i];
48 }
49 memcpy(&ret, &t, sizeof(t));
50 return ret;
51 }
52
53 #endif
@@ -0,0 +1,43 b''
1 #ifndef _HG_COMPAT_H_
2 #define _HG_COMPAT_H_
3
4 #ifdef _WIN32
5 #ifdef _MSC_VER
6 /* msvc 6.0 has problems */
7 #define inline __inline
8 #if defined(_WIN64)
9 typedef __int64 ssize_t;
10 #else
11 typedef int ssize_t;
12 #endif
13 typedef signed char int8_t;
14 typedef short int16_t;
15 typedef long int32_t;
16 typedef __int64 int64_t;
17 typedef unsigned char uint8_t;
18 typedef unsigned short uint16_t;
19 typedef unsigned long uint32_t;
20 typedef unsigned __int64 uint64_t;
21 #else
22 #include <stdint.h>
23 #endif
24 #else
25 /* not windows */
26 #include <sys/types.h>
27 #if defined __BEOS__ && !defined __HAIKU__
28 #include <ByteOrder.h>
29 #else
30 #include <arpa/inet.h>
31 #endif
32 #include <inttypes.h>
33 #endif
34
35 #if defined __hpux || defined __SUNPRO_C || defined _AIX
36 #define inline
37 #endif
38
39 #ifdef __linux
40 #define inline __inline
41 #endif
42
43 #endif
@@ -0,0 +1,45 b''
1 # policy.py - module policy logic for Mercurial.
2 #
3 # Copyright 2015 Gregory Szorc <gregory.szorc@gmail.com>
4 #
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
7
8 from __future__ import absolute_import
9
10 import os
11 import sys
12
13 # Rules for how modules can be loaded. Values are:
14 #
15 # c - require C extensions
16 # allow - allow pure Python implementation when C loading fails
17 # cffi - required cffi versions (implemented within pure module)
18 # cffi-allow - allow pure Python implementation if cffi version is missing
19 # py - only load pure Python modules
20 #
21 # By default, require the C extensions for performance reasons.
22 policy = 'c'
23 policynoc = ('cffi', 'cffi-allow', 'py')
24 policynocffi = ('c', 'py')
25
26 try:
27 from . import __modulepolicy__
28 policy = __modulepolicy__.modulepolicy
29 except ImportError:
30 pass
31
32 # PyPy doesn't load C extensions.
33 #
34 # The canonical way to do this is to test platform.python_implementation().
35 # But we don't import platform and don't bloat for it here.
36 if '__pypy__' in sys.builtin_module_names:
37 policy = 'cffi'
38
39 # Our C extensions aren't yet compatible with Python 3. So use pure Python
40 # on Python 3 for now.
41 if sys.version_info[0] >= 3:
42 policy = 'py'
43
44 # Environment variable can always force settings.
45 policy = os.environ.get('HGMODULEPOLICY', policy)
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -156,7 +156,7 b' i18n/hg.pot: $(PYFILES) $(DOCFILES) i18n'
156 # Packaging targets
156 # Packaging targets
157
157
158 osx:
158 osx:
159 python setup.py install --optimize=1 \
159 /usr/bin/python2.7 setup.py install --optimize=1 \
160 --root=build/mercurial/ --prefix=/usr/local/ \
160 --root=build/mercurial/ --prefix=/usr/local/ \
161 --install-lib=/Library/Python/2.7/site-packages/
161 --install-lib=/Library/Python/2.7/site-packages/
162 make -C doc all install DESTDIR="$(PWD)/build/mercurial/"
162 make -C doc all install DESTDIR="$(PWD)/build/mercurial/"
@@ -184,7 +184,7 b' shopt -s extglob'
184 return
184 return
185 fi
185 fi
186
186
187 opts=$(_hg_cmd debugcomplete --options "$cmd")
187 opts=$(HGPLAINEXCEPT=alias _hg_cmd debugcomplete --options "$cmd")
188
188
189 COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$opts' -- "$cur"))
189 COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$opts' -- "$cur"))
190 _hg_fix_wordlist
190 _hg_fix_wordlist
@@ -1,7 +1,9 b''
1 # Randomized torture test generation for bdiff
1 # Randomized torture test generation for bdiff
2
2
3 from __future__ import absolute_import, print_function
3 from __future__ import absolute_import, print_function
4 import random, sys
4 import random
5 import sys
6
5 from mercurial import (
7 from mercurial import (
6 bdiff,
8 bdiff,
7 mpatch,
9 mpatch,
@@ -26,6 +26,15 b' import optparse'
26 import os
26 import os
27 import re
27 import re
28 import sys
28 import sys
29 if sys.version_info[0] < 3:
30 opentext = open
31 else:
32 def opentext(f):
33 return open(f, encoding='ascii')
34 try:
35 xrange
36 except NameError:
37 xrange = range
29 try:
38 try:
30 import re2
39 import re2
31 except ImportError:
40 except ImportError:
@@ -41,26 +50,26 b' def compilere(pat, multiline=False):'
41 pass
50 pass
42 return re.compile(pat)
51 return re.compile(pat)
43
52
53 # check "rules depending on implementation of repquote()" in each
54 # patterns (especially pypats), before changing around repquote()
55 _repquotefixedmap = {' ': ' ', '\n': '\n', '.': 'p', ':': 'q',
56 '%': '%', '\\': 'b', '*': 'A', '+': 'P', '-': 'M'}
57 def _repquoteencodechr(i):
58 if i > 255:
59 return 'u'
60 c = chr(i)
61 if c in _repquotefixedmap:
62 return _repquotefixedmap[c]
63 if c.isalpha():
64 return 'x'
65 if c.isdigit():
66 return 'n'
67 return 'o'
68 _repquotett = ''.join(_repquoteencodechr(i) for i in xrange(256))
69
44 def repquote(m):
70 def repquote(m):
45 fromc = '.:'
46 tochr = 'pq'
47 def encodechr(i):
48 if i > 255:
49 return 'u'
50 c = chr(i)
51 if c in ' \n':
52 return c
53 if c.isalpha():
54 return 'x'
55 if c.isdigit():
56 return 'n'
57 try:
58 return tochr[fromc.find(c)]
59 except (ValueError, IndexError):
60 return 'o'
61 t = m.group('text')
71 t = m.group('text')
62 tt = ''.join(encodechr(i) for i in xrange(256))
72 t = t.translate(_repquotett)
63 t = t.translate(tt)
64 return m.group('quote') + t + m.group('quote')
73 return m.group('quote') + t + m.group('quote')
65
74
66 def reppython(m):
75 def reppython(m):
@@ -103,7 +112,7 b' testpats = ['
103 (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
112 (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
104 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
113 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
105 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
114 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
106 (r'printf.*[^\\]\\([1-9]|0\d)', "don't use 'printf \NNN', use Python"),
115 (r'printf.*[^\\]\\([1-9]|0\d)', r"don't use 'printf \NNN', use Python"),
107 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
116 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
108 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
117 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
109 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
118 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
@@ -114,7 +123,7 b' testpats = ['
114 (r'export .*=', "don't export and assign at once"),
123 (r'export .*=', "don't export and assign at once"),
115 (r'^source\b', "don't use 'source', use '.'"),
124 (r'^source\b', "don't use 'source', use '.'"),
116 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
125 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
117 (r'ls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
126 (r'\bls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
118 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
127 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
119 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
128 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
120 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
129 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
@@ -133,6 +142,7 b' testpats = ['
133 (r'\|&', "don't use |&, use 2>&1"),
142 (r'\|&', "don't use |&, use 2>&1"),
134 (r'\w = +\w', "only one space after = allowed"),
143 (r'\w = +\w', "only one space after = allowed"),
135 (r'\bsed\b.*[^\\]\\n', "don't use 'sed ... \\n', use a \\ and a newline"),
144 (r'\bsed\b.*[^\\]\\n', "don't use 'sed ... \\n', use a \\ and a newline"),
145 (r'env.*-u', "don't use 'env -u VAR', use 'unset VAR'")
136 ],
146 ],
137 # warnings
147 # warnings
138 [
148 [
@@ -179,6 +189,8 b' utestpats = ['
179 (r'^ .*: largefile \S+ not available from file:.*/.*[^)]$', winglobmsg),
189 (r'^ .*: largefile \S+ not available from file:.*/.*[^)]$', winglobmsg),
180 (r'^ .*file://\$TESTTMP',
190 (r'^ .*file://\$TESTTMP',
181 'write "file:/*/$TESTTMP" + (glob) to match on windows too'),
191 'write "file:/*/$TESTTMP" + (glob) to match on windows too'),
192 (r'^ [^$>].*27\.0\.0\.1.*[^)]$',
193 'use (glob) to match localhost IP on hosts without 127.0.0.1 too'),
182 (r'^ (cat|find): .*: No such file or directory',
194 (r'^ (cat|find): .*: No such file or directory',
183 'use test -f to test for file existence'),
195 'use test -f to test for file existence'),
184 (r'^ diff -[^ -]*p',
196 (r'^ diff -[^ -]*p',
@@ -197,8 +209,8 b' utestpats = ['
197 ],
209 ],
198 # warnings
210 # warnings
199 [
211 [
200 (r'^ [^*?/\n]* \(glob\)$',
212 (r'^ (?!.*127\.0\.0\.1)[^*?/\n]* \(glob\)$',
201 "glob match with no glob character (?*/)"),
213 "glob match with no glob string (?, *, /, and 127.0.0.1)"),
202 ]
214 ]
203 ]
215 ]
204
216
@@ -214,7 +226,7 b' for i in [0, 1]:'
214
226
215 utestfilters = [
227 utestfilters = [
216 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
228 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
217 (r"( *)(#([^\n]*\S)?)", repcomment),
229 (r"( +)(#([^\n]*\S)?)", repcomment),
218 ]
230 ]
219
231
220 pypats = [
232 pypats = [
@@ -238,7 +250,6 b' pypats = ['
238 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
250 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
239 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
251 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
240 (r'.{81}', "line too long"),
252 (r'.{81}', "line too long"),
241 (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'),
242 (r'[^\n]\Z', "no trailing newline"),
253 (r'[^\n]\Z', "no trailing newline"),
243 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
254 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
244 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
255 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
@@ -305,8 +316,6 b' pypats = ['
305 (r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
316 (r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
306 'legacy exception syntax; use "as" instead of ","'),
317 'legacy exception syntax; use "as" instead of ","'),
307 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
318 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
308 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
309 "missing _() in ui message (use () to hide false-positives)"),
310 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
319 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
311 (r'\b__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
320 (r'\b__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
312 (r'os\.path\.join\(.*, *(""|\'\')\)',
321 (r'os\.path\.join\(.*, *(""|\'\')\)',
@@ -318,9 +327,37 b' pypats = ['
318 (r'^import Queue', "don't use Queue, use util.queue + util.empty"),
327 (r'^import Queue', "don't use Queue, use util.queue + util.empty"),
319 (r'^import cStringIO', "don't use cStringIO.StringIO, use util.stringio"),
328 (r'^import cStringIO', "don't use cStringIO.StringIO, use util.stringio"),
320 (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
329 (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
330 (r'^import SocketServer', "don't use SockerServer, use util.socketserver"),
331 (r'^import urlparse', "don't use urlparse, use util.urlparse"),
332 (r'^import xmlrpclib', "don't use xmlrpclib, use util.xmlrpclib"),
333 (r'^import cPickle', "don't use cPickle, use util.pickle"),
334 (r'^import pickle', "don't use pickle, use util.pickle"),
335 (r'^import httplib', "don't use httplib, use util.httplib"),
336 (r'^import BaseHTTPServer', "use util.httpserver instead"),
337 (r'\.next\(\)', "don't use .next(), use next(...)"),
338
339 # rules depending on implementation of repquote()
340 (r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
341 'string join across lines with no space'),
342 (r'''(?x)ui\.(status|progress|write|note|warn)\(
343 [ \t\n#]*
344 (?# any strings/comments might precede a string, which
345 # contains translatable message)
346 ((['"]|\'\'\'|""")[ \npq%bAPMxno]*(['"]|\'\'\'|""")[ \t\n#]+)*
347 (?# sequence consisting of below might precede translatable message
348 # - formatting string: "% 10s", "%05d", "% -3.2f", "%*s", "%%" ...
349 # - escaped character: "\\", "\n", "\0" ...
350 # - character other than '%', 'b' as '\', and 'x' as alphabet)
351 (['"]|\'\'\'|""")
352 ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
353 (?# this regexp can't use [^...] style,
354 # because _preparepats forcibly adds "\n" into [^...],
355 # even though this regexp wants match it against "\n")''',
356 "missing _() in ui message (use () to hide false-positives)"),
321 ],
357 ],
322 # warnings
358 # warnings
323 [
359 [
360 # rules depending on implementation of repquote()
324 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
361 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
325 ]
362 ]
326 ]
363 ]
@@ -365,9 +402,13 b' cpats = ['
365 (r'^\s*#import\b', "use only #include in standard C code"),
402 (r'^\s*#import\b', "use only #include in standard C code"),
366 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
403 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
367 (r'strcat\(', "don't use strcat"),
404 (r'strcat\(', "don't use strcat"),
405
406 # rules depending on implementation of repquote()
368 ],
407 ],
369 # warnings
408 # warnings
370 []
409 [
410 # rules depending on implementation of repquote()
411 ]
371 ]
412 ]
372
413
373 cfilters = [
414 cfilters = [
@@ -433,7 +474,6 b' def _preparepats():'
433 filters = c[3]
474 filters = c[3]
434 for i, flt in enumerate(filters):
475 for i, flt in enumerate(filters):
435 filters[i] = re.compile(flt[0]), flt[1]
476 filters[i] = re.compile(flt[0]), flt[1]
436 _preparepats()
437
477
438 class norepeatlogger(object):
478 class norepeatlogger(object):
439 def __init__(self):
479 def __init__(self):
@@ -486,12 +526,15 b' def checkfile(f, logfunc=_defaultlogger.'
486 result = True
526 result = True
487
527
488 try:
528 try:
489 fp = open(f)
529 with opentext(f) as fp:
530 try:
531 pre = post = fp.read()
532 except UnicodeDecodeError as e:
533 print("%s while reading %s" % (e, f))
534 return result
490 except IOError as e:
535 except IOError as e:
491 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
536 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
492 return result
537 return result
493 pre = post = fp.read()
494 fp.close()
495
538
496 for name, match, magic, filters, pats in checks:
539 for name, match, magic, filters, pats in checks:
497 if debug:
540 if debug:
@@ -578,7 +621,7 b' def checkfile(f, logfunc=_defaultlogger.'
578
621
579 return result
622 return result
580
623
581 if __name__ == "__main__":
624 def main():
582 parser = optparse.OptionParser("%prog [options] [files]")
625 parser = optparse.OptionParser("%prog [options] [files]")
583 parser.add_option("-w", "--warnings", action="store_true",
626 parser.add_option("-w", "--warnings", action="store_true",
584 help="include warning-level checks")
627 help="include warning-level checks")
@@ -600,10 +643,15 b' if __name__ == "__main__":'
600 else:
643 else:
601 check = args
644 check = args
602
645
646 _preparepats()
647
603 ret = 0
648 ret = 0
604 for f in check:
649 for f in check:
605 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
650 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
606 blame=options.blame, debug=options.debug,
651 blame=options.blame, debug=options.debug,
607 lineno=options.lineno):
652 lineno=options.lineno):
608 ret = 1
653 ret = 1
609 sys.exit(ret)
654 return ret
655
656 if __name__ == "__main__":
657 sys.exit(main())
@@ -15,7 +15,11 b''
15 #
15 #
16 # See also: https://mercurial-scm.org/wiki/ContributingChanges
16 # See also: https://mercurial-scm.org/wiki/ContributingChanges
17
17
18 import re, sys, os
18 from __future__ import absolute_import, print_function
19
20 import os
21 import re
22 import sys
19
23
20 commitheader = r"^(?:# [^\n]*\n)*"
24 commitheader = r"^(?:# [^\n]*\n)*"
21 afterheader = commitheader + r"(?!#)"
25 afterheader = commitheader + r"(?!#)"
@@ -69,9 +73,9 b' def checkcommit(commit, node=None):'
69 break
73 break
70 if not printed:
74 if not printed:
71 printed = True
75 printed = True
72 print "node: %s" % node
76 print("node: %s" % node)
73 print "%d: %s" % (n, msg)
77 print("%d: %s" % (n, msg))
74 print " %s" % nonempty(l, last)[:-1]
78 print(" %s" % nonempty(l, last)[:-1])
75 if "BYPASS" not in os.environ:
79 if "BYPASS" not in os.environ:
76 exitcode = 1
80 exitcode = 1
77 del hits[0]
81 del hits[0]
@@ -61,7 +61,20 b' def check_compat_py3(f):'
61 imp.load_module(name, fh, '', ('py', 'r', imp.PY_SOURCE))
61 imp.load_module(name, fh, '', ('py', 'r', imp.PY_SOURCE))
62 except Exception as e:
62 except Exception as e:
63 exc_type, exc_value, tb = sys.exc_info()
63 exc_type, exc_value, tb = sys.exc_info()
64 frame = traceback.extract_tb(tb)[-1]
64 # We walk the stack and ignore frames from our custom importer,
65 # import mechanisms, and stdlib modules. This kinda/sorta
66 # emulates CPython behavior in import.c while also attempting
67 # to pin blame on a Mercurial file.
68 for frame in reversed(traceback.extract_tb(tb)):
69 if frame.name == '_call_with_frames_removed':
70 continue
71 if 'importlib' in frame.filename:
72 continue
73 if 'mercurial/__init__.py' in frame.filename:
74 continue
75 if frame.filename.startswith(sys.prefix):
76 continue
77 break
65
78
66 if frame.filename:
79 if frame.filename:
67 filename = os.path.basename(frame.filename)
80 filename = os.path.basename(frame.filename)
@@ -28,3 +28,5 b' The following variables are available fo'
28
28
29 * CHGDEBUG enables debug messages.
29 * CHGDEBUG enables debug messages.
30 * CHGSOCKNAME specifies the socket path of the background cmdserver.
30 * CHGSOCKNAME specifies the socket path of the background cmdserver.
31 * CHGTIMEOUT specifies how many seconds chg will wait before giving up
32 connecting to a cmdserver. If it is 0, chg will wait forever. Default: 60
@@ -249,7 +249,13 b' static hgclient_t *retryconnectcmdserver'
249 int pst = 0;
249 int pst = 0;
250
250
251 debugmsg("try connect to %s repeatedly", opts->sockname);
251 debugmsg("try connect to %s repeatedly", opts->sockname);
252 for (unsigned int i = 0; i < 10 * 100; i++) {
252
253 unsigned int timeoutsec = 60; /* default: 60 seconds */
254 const char *timeoutenv = getenv("CHGTIMEOUT");
255 if (timeoutenv)
256 sscanf(timeoutenv, "%u", &timeoutsec);
257
258 for (unsigned int i = 0; !timeoutsec || i < timeoutsec * 100; i++) {
253 hgclient_t *hgc = hgc_open(opts->sockname);
259 hgclient_t *hgc = hgc_open(opts->sockname);
254 if (hgc)
260 if (hgc)
255 return hgc;
261 return hgc;
@@ -332,6 +338,7 b' static void killcmdserver(const struct c'
332 }
338 }
333 }
339 }
334
340
341 static pid_t pagerpid = 0;
335 static pid_t peerpid = 0;
342 static pid_t peerpid = 0;
336
343
337 static void forwardsignal(int sig)
344 static void forwardsignal(int sig)
@@ -374,6 +381,17 b' error:'
374 abortmsgerrno("failed to handle stop signal");
381 abortmsgerrno("failed to handle stop signal");
375 }
382 }
376
383
384 static void handlechildsignal(int sig UNUSED_)
385 {
386 if (peerpid == 0 || pagerpid == 0)
387 return;
388 /* if pager exits, notify the server with SIGPIPE immediately.
389 * otherwise the server won't get SIGPIPE if it does not write
390 * anything. (issue5278) */
391 if (waitpid(pagerpid, NULL, WNOHANG) == pagerpid)
392 kill(peerpid, SIGPIPE);
393 }
394
377 static void setupsignalhandler(pid_t pid)
395 static void setupsignalhandler(pid_t pid)
378 {
396 {
379 if (pid <= 0)
397 if (pid <= 0)
@@ -410,6 +428,11 b' static void setupsignalhandler(pid_t pid'
410 sa.sa_flags = SA_RESTART;
428 sa.sa_flags = SA_RESTART;
411 if (sigaction(SIGTSTP, &sa, NULL) < 0)
429 if (sigaction(SIGTSTP, &sa, NULL) < 0)
412 goto error;
430 goto error;
431 /* get notified when pager exits */
432 sa.sa_handler = handlechildsignal;
433 sa.sa_flags = SA_RESTART;
434 if (sigaction(SIGCHLD, &sa, NULL) < 0)
435 goto error;
413
436
414 return;
437 return;
415
438
@@ -417,21 +440,56 b' error:'
417 abortmsgerrno("failed to set up signal handlers");
440 abortmsgerrno("failed to set up signal handlers");
418 }
441 }
419
442
420 /* This implementation is based on hgext/pager.py (pre 369741ef7253) */
443 static void restoresignalhandler()
421 static void setuppager(hgclient_t *hgc, const char *const args[],
444 {
445 struct sigaction sa;
446 memset(&sa, 0, sizeof(sa));
447 sa.sa_handler = SIG_DFL;
448 sa.sa_flags = SA_RESTART;
449 if (sigemptyset(&sa.sa_mask) < 0)
450 goto error;
451
452 if (sigaction(SIGHUP, &sa, NULL) < 0)
453 goto error;
454 if (sigaction(SIGTERM, &sa, NULL) < 0)
455 goto error;
456 if (sigaction(SIGWINCH, &sa, NULL) < 0)
457 goto error;
458 if (sigaction(SIGCONT, &sa, NULL) < 0)
459 goto error;
460 if (sigaction(SIGTSTP, &sa, NULL) < 0)
461 goto error;
462 if (sigaction(SIGCHLD, &sa, NULL) < 0)
463 goto error;
464
465 /* ignore Ctrl+C while shutting down to make pager exits cleanly */
466 sa.sa_handler = SIG_IGN;
467 if (sigaction(SIGINT, &sa, NULL) < 0)
468 goto error;
469
470 peerpid = 0;
471 return;
472
473 error:
474 abortmsgerrno("failed to restore signal handlers");
475 }
476
477 /* This implementation is based on hgext/pager.py (post 369741ef7253)
478 * Return 0 if pager is not started, or pid of the pager */
479 static pid_t setuppager(hgclient_t *hgc, const char *const args[],
422 size_t argsize)
480 size_t argsize)
423 {
481 {
424 const char *pagercmd = hgc_getpager(hgc, args, argsize);
482 const char *pagercmd = hgc_getpager(hgc, args, argsize);
425 if (!pagercmd)
483 if (!pagercmd)
426 return;
484 return 0;
427
485
428 int pipefds[2];
486 int pipefds[2];
429 if (pipe(pipefds) < 0)
487 if (pipe(pipefds) < 0)
430 return;
488 return 0;
431 pid_t pid = fork();
489 pid_t pid = fork();
432 if (pid < 0)
490 if (pid < 0)
433 goto error;
491 goto error;
434 if (pid == 0) {
492 if (pid > 0) {
435 close(pipefds[0]);
493 close(pipefds[0]);
436 if (dup2(pipefds[1], fileno(stdout)) < 0)
494 if (dup2(pipefds[1], fileno(stdout)) < 0)
437 goto error;
495 goto error;
@@ -441,7 +499,7 b' static void setuppager(hgclient_t *hgc, '
441 }
499 }
442 close(pipefds[1]);
500 close(pipefds[1]);
443 hgc_attachio(hgc); /* reattach to pager */
501 hgc_attachio(hgc); /* reattach to pager */
444 return;
502 return pid;
445 } else {
503 } else {
446 dup2(pipefds[0], fileno(stdin));
504 dup2(pipefds[0], fileno(stdin));
447 close(pipefds[0]);
505 close(pipefds[0]);
@@ -451,13 +509,27 b' static void setuppager(hgclient_t *hgc, '
451 if (r < 0) {
509 if (r < 0) {
452 abortmsgerrno("cannot start pager '%s'", pagercmd);
510 abortmsgerrno("cannot start pager '%s'", pagercmd);
453 }
511 }
454 return;
512 return 0;
455 }
513 }
456
514
457 error:
515 error:
458 close(pipefds[0]);
516 close(pipefds[0]);
459 close(pipefds[1]);
517 close(pipefds[1]);
460 abortmsgerrno("failed to prepare pager");
518 abortmsgerrno("failed to prepare pager");
519 return 0;
520 }
521
522 static void waitpager(pid_t pid)
523 {
524 /* close output streams to notify the pager its input ends */
525 fclose(stdout);
526 fclose(stderr);
527 while (1) {
528 pid_t ret = waitpid(pid, NULL, 0);
529 if (ret == -1 && errno == EINTR)
530 continue;
531 break;
532 }
461 }
533 }
462
534
463 /* Run instructions sent from the server like unlink and set redirect path
535 /* Run instructions sent from the server like unlink and set redirect path
@@ -585,9 +657,13 b' int main(int argc, const char *argv[], c'
585 }
657 }
586
658
587 setupsignalhandler(hgc_peerpid(hgc));
659 setupsignalhandler(hgc_peerpid(hgc));
588 setuppager(hgc, argv + 1, argc - 1);
660 pagerpid = setuppager(hgc, argv + 1, argc - 1);
589 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
661 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
662 restoresignalhandler();
590 hgc_close(hgc);
663 hgc_close(hgc);
591 freecmdserveropts(&opts);
664 freecmdserveropts(&opts);
665 if (pagerpid)
666 waitpager(pagerpid);
667
592 return exitcode;
668 return exitcode;
593 }
669 }
@@ -63,6 +63,7 b' typedef struct {'
63
63
64 struct hgclient_tag_ {
64 struct hgclient_tag_ {
65 int sockfd;
65 int sockfd;
66 pid_t pgid;
66 pid_t pid;
67 pid_t pid;
67 context_t ctx;
68 context_t ctx;
68 unsigned int capflags;
69 unsigned int capflags;
@@ -125,10 +126,15 b' static void readchannel(hgclient_t *hgc)'
125 return; /* assumes input request */
126 return; /* assumes input request */
126
127
127 size_t cursize = 0;
128 size_t cursize = 0;
129 int emptycount = 0;
128 while (cursize < hgc->ctx.datasize) {
130 while (cursize < hgc->ctx.datasize) {
129 rsize = recv(hgc->sockfd, hgc->ctx.data + cursize,
131 rsize = recv(hgc->sockfd, hgc->ctx.data + cursize,
130 hgc->ctx.datasize - cursize, 0);
132 hgc->ctx.datasize - cursize, 0);
131 if (rsize < 0)
133 /* rsize == 0 normally indicates EOF, while it's also a valid
134 * packet size for unix socket. treat it as EOF and abort if
135 * we get many empty responses in a row. */
136 emptycount = (rsize == 0 ? emptycount + 1 : 0);
137 if (rsize < 0 || emptycount > 20)
132 abortmsg("failed to read data block");
138 abortmsg("failed to read data block");
133 cursize += rsize;
139 cursize += rsize;
134 }
140 }
@@ -339,6 +345,8 b' static void readhello(hgclient_t *hgc)'
339 u = dataend;
345 u = dataend;
340 if (strncmp(s, "capabilities:", t - s + 1) == 0) {
346 if (strncmp(s, "capabilities:", t - s + 1) == 0) {
341 hgc->capflags = parsecapabilities(t + 2, u);
347 hgc->capflags = parsecapabilities(t + 2, u);
348 } else if (strncmp(s, "pgid:", t - s + 1) == 0) {
349 hgc->pgid = strtol(t + 2, NULL, 10);
342 } else if (strncmp(s, "pid:", t - s + 1) == 0) {
350 } else if (strncmp(s, "pid:", t - s + 1) == 0) {
343 hgc->pid = strtol(t + 2, NULL, 10);
351 hgc->pid = strtol(t + 2, NULL, 10);
344 }
352 }
@@ -463,6 +471,12 b' void hgc_close(hgclient_t *hgc)'
463 free(hgc);
471 free(hgc);
464 }
472 }
465
473
474 pid_t hgc_peerpgid(const hgclient_t *hgc)
475 {
476 assert(hgc);
477 return hgc->pgid;
478 }
479
466 pid_t hgc_peerpid(const hgclient_t *hgc)
480 pid_t hgc_peerpid(const hgclient_t *hgc)
467 {
481 {
468 assert(hgc);
482 assert(hgc);
@@ -18,6 +18,7 b' typedef struct hgclient_tag_ hgclient_t;'
18 hgclient_t *hgc_open(const char *sockname);
18 hgclient_t *hgc_open(const char *sockname);
19 void hgc_close(hgclient_t *hgc);
19 void hgc_close(hgclient_t *hgc);
20
20
21 pid_t hgc_peerpgid(const hgclient_t *hgc);
21 pid_t hgc_peerpid(const hgclient_t *hgc);
22 pid_t hgc_peerpid(const hgclient_t *hgc);
22
23
23 const char **hgc_validate(hgclient_t *hgc, const char *const args[],
24 const char **hgc_validate(hgclient_t *hgc, const char *const args[],
@@ -12,8 +12,10 b''
12
12
13 #ifdef __GNUC__
13 #ifdef __GNUC__
14 #define PRINTF_FORMAT_ __attribute__((format(printf, 1, 2)))
14 #define PRINTF_FORMAT_ __attribute__((format(printf, 1, 2)))
15 #define UNUSED_ __attribute__((unused))
15 #else
16 #else
16 #define PRINTF_FORMAT_
17 #define PRINTF_FORMAT_
18 #define UNUSED_
17 #endif
19 #endif
18
20
19 void abortmsg(const char *fmt, ...) PRINTF_FORMAT_;
21 void abortmsg(const char *fmt, ...) PRINTF_FORMAT_;
@@ -52,7 +52,7 b' def debugshell(ui, repo, **opts):'
52 with demandimport.deactivated():
52 with demandimport.deactivated():
53 __import__(pdbmap[debugger])
53 __import__(pdbmap[debugger])
54 except ImportError:
54 except ImportError:
55 ui.warn("%s debugger specified but %s module was not found\n"
55 ui.warn(("%s debugger specified but %s module was not found\n")
56 % (debugger, pdbmap[debugger]))
56 % (debugger, pdbmap[debugger]))
57 debugger = 'pdb'
57 debugger = 'pdb'
58
58
@@ -25,10 +25,10 b' def checkconsistency(ui, orig, dmap, _no'
25 """Compute nonnormalset from dmap, check that it matches _nonnormalset"""
25 """Compute nonnormalset from dmap, check that it matches _nonnormalset"""
26 nonnormalcomputedmap = nonnormalentries(dmap)
26 nonnormalcomputedmap = nonnormalentries(dmap)
27 if _nonnormalset != nonnormalcomputedmap:
27 if _nonnormalset != nonnormalcomputedmap:
28 ui.develwarn("%s call to %s\n" % (label, orig))
28 ui.develwarn("%s call to %s\n" % (label, orig), config='dirstate')
29 ui.develwarn("inconsistency in nonnormalset\n")
29 ui.develwarn("inconsistency in nonnormalset\n", config='dirstate')
30 ui.develwarn("[nonnormalset] %s\n" % _nonnormalset)
30 ui.develwarn("[nonnormalset] %s\n" % _nonnormalset, config='dirstate')
31 ui.develwarn("[map] %s\n" % nonnormalcomputedmap)
31 ui.develwarn("[map] %s\n" % nonnormalcomputedmap, config='dirstate')
32
32
33 def _checkdirstate(orig, self, arg):
33 def _checkdirstate(orig, self, arg):
34 """Check nonnormal set consistency before and after the call to orig"""
34 """Check nonnormal set consistency before and after the call to orig"""
@@ -2,8 +2,14 b''
2 # Dump revlogs as raw data stream
2 # Dump revlogs as raw data stream
3 # $ find .hg/store/ -name "*.i" | xargs dumprevlog > repo.dump
3 # $ find .hg/store/ -name "*.i" | xargs dumprevlog > repo.dump
4
4
5 from __future__ import absolute_import, print_function
6
5 import sys
7 import sys
6 from mercurial import revlog, node, util
8 from mercurial import (
9 node,
10 revlog,
11 util,
12 )
7
13
8 for fp in (sys.stdin, sys.stdout, sys.stderr):
14 for fp in (sys.stdin, sys.stdout, sys.stderr):
9 util.setbinary(fp)
15 util.setbinary(fp)
@@ -11,15 +17,15 b' for fp in (sys.stdin, sys.stdout, sys.st'
11 for f in sys.argv[1:]:
17 for f in sys.argv[1:]:
12 binopen = lambda fn: open(fn, 'rb')
18 binopen = lambda fn: open(fn, 'rb')
13 r = revlog.revlog(binopen, f)
19 r = revlog.revlog(binopen, f)
14 print "file:", f
20 print("file:", f)
15 for i in r:
21 for i in r:
16 n = r.node(i)
22 n = r.node(i)
17 p = r.parents(n)
23 p = r.parents(n)
18 d = r.revision(n)
24 d = r.revision(n)
19 print "node:", node.hex(n)
25 print("node:", node.hex(n))
20 print "linkrev:", r.linkrev(i)
26 print("linkrev:", r.linkrev(i))
21 print "parents:", node.hex(p[0]), node.hex(p[1])
27 print("parents:", node.hex(p[0]), node.hex(p[1]))
22 print "length:", len(d)
28 print("length:", len(d))
23 print "-start-"
29 print("-start-")
24 print d
30 print(d)
25 print "-end-"
31 print("-end-")
@@ -11,8 +11,9 b' import sys'
11 # Import a minimal set of stdlib modules needed for list_stdlib_modules()
11 # Import a minimal set of stdlib modules needed for list_stdlib_modules()
12 # to work when run from a virtualenv. The modules were chosen empirically
12 # to work when run from a virtualenv. The modules were chosen empirically
13 # so that the return value matches the return value without virtualenv.
13 # so that the return value matches the return value without virtualenv.
14 import BaseHTTPServer
14 if True: # disable lexical sorting checks
15 import zlib
15 import BaseHTTPServer
16 import zlib
16
17
17 # Whitelist of modules that symbols can be directly imported from.
18 # Whitelist of modules that symbols can be directly imported from.
18 allowsymbolimports = (
19 allowsymbolimports = (
@@ -126,22 +127,32 b' def fromlocalfunc(modulename, localmods)'
126 False
127 False
127 >>> fromlocal(None, 1)
128 >>> fromlocal(None, 1)
128 ('foo', 'foo.__init__', True)
129 ('foo', 'foo.__init__', True)
130 >>> fromlocal('foo1', 1)
131 ('foo.foo1', 'foo.foo1', False)
129 >>> fromlocal2 = fromlocalfunc('foo.xxx.yyy', localmods)
132 >>> fromlocal2 = fromlocalfunc('foo.xxx.yyy', localmods)
130 >>> fromlocal2(None, 2)
133 >>> fromlocal2(None, 2)
131 ('foo', 'foo.__init__', True)
134 ('foo', 'foo.__init__', True)
135 >>> fromlocal2('bar2', 1)
136 False
137 >>> fromlocal2('bar', 2)
138 ('foo.bar', 'foo.bar.__init__', True)
132 """
139 """
133 prefix = '.'.join(modulename.split('.')[:-1])
140 prefix = '.'.join(modulename.split('.')[:-1])
134 if prefix:
141 if prefix:
135 prefix += '.'
142 prefix += '.'
136 def fromlocal(name, level=0):
143 def fromlocal(name, level=0):
137 # name is None when relative imports are used.
144 # name is false value when relative imports are used.
138 if name is None:
145 if not name:
139 # If relative imports are used, level must not be absolute.
146 # If relative imports are used, level must not be absolute.
140 assert level > 0
147 assert level > 0
141 candidates = ['.'.join(modulename.split('.')[:-level])]
148 candidates = ['.'.join(modulename.split('.')[:-level])]
142 else:
149 else:
143 # Check relative name first.
150 if not level:
144 candidates = [prefix + name, name]
151 # Check relative name first.
152 candidates = [prefix + name, name]
153 else:
154 candidates = ['.'.join(modulename.split('.')[:-level]) +
155 '.' + name]
145
156
146 for n in candidates:
157 for n in candidates:
147 if n in localmods:
158 if n in localmods:
@@ -175,6 +186,9 b' def list_stdlib_modules():'
175
186
176 >>> 'cStringIO' in mods
187 >>> 'cStringIO' in mods
177 True
188 True
189
190 >>> 'cffi' in mods
191 True
178 """
192 """
179 for m in sys.builtin_module_names:
193 for m in sys.builtin_module_names:
180 yield m
194 yield m
@@ -187,6 +201,8 b' def list_stdlib_modules():'
187 yield m
201 yield m
188 for m in 'cPickle', 'datetime': # in Python (not C) on PyPy
202 for m in 'cPickle', 'datetime': # in Python (not C) on PyPy
189 yield m
203 yield m
204 for m in ['cffi']:
205 yield m
190 stdlib_prefixes = set([sys.prefix, sys.exec_prefix])
206 stdlib_prefixes = set([sys.prefix, sys.exec_prefix])
191 # We need to supplement the list of prefixes for the search to work
207 # We need to supplement the list of prefixes for the search to work
192 # when run from within a virtualenv.
208 # when run from within a virtualenv.
@@ -360,7 +376,7 b' def verify_modern_convention(module, roo'
360 * Symbols can only be imported from specific modules (see
376 * Symbols can only be imported from specific modules (see
361 `allowsymbolimports`). For other modules, first import the module then
377 `allowsymbolimports`). For other modules, first import the module then
362 assign the symbol to a module-level variable. In addition, these imports
378 assign the symbol to a module-level variable. In addition, these imports
363 must be performed before other relative imports. This rule only
379 must be performed before other local imports. This rule only
364 applies to import statements outside of any blocks.
380 applies to import statements outside of any blocks.
365 * Relative imports from the standard library are not allowed.
381 * Relative imports from the standard library are not allowed.
366 * Certain modules must be aliased to alternate names to avoid aliasing
382 * Certain modules must be aliased to alternate names to avoid aliasing
@@ -371,8 +387,8 b' def verify_modern_convention(module, roo'
371
387
372 # Whether a local/non-stdlib import has been performed.
388 # Whether a local/non-stdlib import has been performed.
373 seenlocal = None
389 seenlocal = None
374 # Whether a relative, non-symbol import has been seen.
390 # Whether a local/non-stdlib, non-symbol import has been seen.
375 seennonsymbolrelative = False
391 seennonsymbollocal = False
376 # The last name to be imported (for sorting).
392 # The last name to be imported (for sorting).
377 lastname = None
393 lastname = None
378 # Relative import levels encountered so far.
394 # Relative import levels encountered so far.
@@ -446,26 +462,26 b' def verify_modern_convention(module, roo'
446
462
447 # Direct symbol import is only allowed from certain modules and
463 # Direct symbol import is only allowed from certain modules and
448 # must occur before non-symbol imports.
464 # must occur before non-symbol imports.
465 found = fromlocal(node.module, node.level)
466 if found and found[2]: # node.module is a package
467 prefix = found[0] + '.'
468 symbols = [n.name for n in node.names
469 if not fromlocal(prefix + n.name)]
470 else:
471 symbols = [n.name for n in node.names]
449 if node.module and node.col_offset == root_col_offset:
472 if node.module and node.col_offset == root_col_offset:
450 found = fromlocal(node.module, node.level)
451 if found and found[2]: # node.module is a package
452 prefix = found[0] + '.'
453 symbols = [n.name for n in node.names
454 if not fromlocal(prefix + n.name)]
455 else:
456 symbols = [n.name for n in node.names]
457
458 if symbols and fullname not in allowsymbolimports:
473 if symbols and fullname not in allowsymbolimports:
459 yield msg('direct symbol import %s from %s',
474 yield msg('direct symbol import %s from %s',
460 ', '.join(symbols), fullname)
475 ', '.join(symbols), fullname)
461
476
462 if symbols and seennonsymbolrelative:
477 if symbols and seennonsymbollocal:
463 yield msg('symbol import follows non-symbol import: %s',
478 yield msg('symbol import follows non-symbol import: %s',
464 fullname)
479 fullname)
480 if not symbols and fullname not in stdlib_modules:
481 seennonsymbollocal = True
465
482
466 if not node.module:
483 if not node.module:
467 assert node.level
484 assert node.level
468 seennonsymbolrelative = True
469
485
470 # Only allow 1 group per level.
486 # Only allow 1 group per level.
471 if (node.level in seenlevels
487 if (node.level in seenlevels
@@ -652,7 +668,7 b' def sources(f, modname):'
652 the input file.
668 the input file.
653 """
669 """
654 py = False
670 py = False
655 if f.endswith('.py'):
671 if not f.endswith('.t'):
656 with open(f) as src:
672 with open(f) as src:
657 yield src.read(), modname, f, 0
673 yield src.read(), modname, f, 0
658 py = True
674 py = True
@@ -1,5 +1,5 b''
1 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
1 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
2 <!-- This is the second screen displayed during the install. -->
2 <!-- This is the first screen displayed during the install. -->
3 <html>
3 <html>
4 <head>
4 <head>
5 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
5 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
@@ -1,6 +1,23 b''
1 # perf.py - performance test routines
1 # perf.py - performance test routines
2 '''helper extension to measure performance'''
2 '''helper extension to measure performance'''
3
3
4 # "historical portability" policy of perf.py:
5 #
6 # We have to do:
7 # - make perf.py "loadable" with as wide Mercurial version as possible
8 # This doesn't mean that perf commands work correctly with that Mercurial.
9 # BTW, perf.py itself has been available since 1.1 (or eb240755386d).
10 # - make historical perf command work correctly with as wide Mercurial
11 # version as possible
12 #
13 # We have to do, if possible with reasonable cost:
14 # - make recent perf command for historical feature work correctly
15 # with early Mercurial
16 #
17 # We don't have to do:
18 # - make perf command for recent feature work correctly with early
19 # Mercurial
20
4 from __future__ import absolute_import
21 from __future__ import absolute_import
5 import functools
22 import functools
6 import os
23 import os
@@ -8,25 +25,97 b' import random'
8 import sys
25 import sys
9 import time
26 import time
10 from mercurial import (
27 from mercurial import (
11 branchmap,
12 cmdutil,
28 cmdutil,
13 commands,
29 commands,
14 copies,
30 copies,
15 error,
31 error,
32 extensions,
16 mdiff,
33 mdiff,
17 merge,
34 merge,
18 obsolete,
19 repoview,
20 revlog,
35 revlog,
21 scmutil,
22 util,
36 util,
23 )
37 )
24
38
25 formatteropts = commands.formatteropts
39 # for "historical portability":
26 revlogopts = commands.debugrevlogopts
40 # try to import modules separately (in dict order), and ignore
41 # failure, because these aren't available with early Mercurial
42 try:
43 from mercurial import branchmap # since 2.5 (or bcee63733aad)
44 except ImportError:
45 pass
46 try:
47 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
48 except ImportError:
49 pass
50 try:
51 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
52 except ImportError:
53 pass
54 try:
55 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
56 except ImportError:
57 pass
58
59 # for "historical portability":
60 # define util.safehasattr forcibly, because util.safehasattr has been
61 # available since 1.9.3 (or 94b200a11cf7)
62 _undefined = object()
63 def safehasattr(thing, attr):
64 return getattr(thing, attr, _undefined) is not _undefined
65 setattr(util, 'safehasattr', safehasattr)
66
67 # for "historical portability":
68 # use locally defined empty option list, if formatteropts isn't
69 # available, because commands.formatteropts has been available since
70 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
71 # available since 2.2 (or ae5f92e154d3)
72 formatteropts = getattr(commands, "formatteropts", [])
73
74 # for "historical portability":
75 # use locally defined option list, if debugrevlogopts isn't available,
76 # because commands.debugrevlogopts has been available since 3.7 (or
77 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
78 # since 1.9 (or a79fea6b3e77).
79 revlogopts = getattr(commands, "debugrevlogopts", [
80 ('c', 'changelog', False, ('open changelog')),
81 ('m', 'manifest', False, ('open manifest')),
82 ('', 'dir', False, ('open directory manifest')),
83 ])
27
84
28 cmdtable = {}
85 cmdtable = {}
29 command = cmdutil.command(cmdtable)
86
87 # for "historical portability":
88 # define parsealiases locally, because cmdutil.parsealiases has been
89 # available since 1.5 (or 6252852b4332)
90 def parsealiases(cmd):
91 return cmd.lstrip("^").split("|")
92
93 if safehasattr(cmdutil, 'command'):
94 import inspect
95 command = cmdutil.command(cmdtable)
96 if 'norepo' not in inspect.getargspec(command)[0]:
97 # for "historical portability":
98 # wrap original cmdutil.command, because "norepo" option has
99 # been available since 3.1 (or 75a96326cecb)
100 _command = command
101 def command(name, options=(), synopsis=None, norepo=False):
102 if norepo:
103 commands.norepo += ' %s' % ' '.join(parsealiases(name))
104 return _command(name, list(options), synopsis)
105 else:
106 # for "historical portability":
107 # define "@command" annotation locally, because cmdutil.command
108 # has been available since 1.9 (or 2daa5179e73f)
109 def command(name, options=(), synopsis=None, norepo=False):
110 def decorator(func):
111 if synopsis:
112 cmdtable[name] = func, list(options), synopsis
113 else:
114 cmdtable[name] = func, list(options)
115 if norepo:
116 commands.norepo += ' %s' % ' '.join(parsealiases(name))
117 return func
118 return decorator
30
119
31 def getlen(ui):
120 def getlen(ui):
32 if ui.configbool("perf", "stub"):
121 if ui.configbool("perf", "stub"):
@@ -796,3 +885,18 b' def perflrucache(ui, size=4, gets=10000,'
796 timer, fm = gettimer(ui, opts)
885 timer, fm = gettimer(ui, opts)
797 timer(fn, title=title)
886 timer(fn, title=title)
798 fm.end()
887 fm.end()
888
889 def uisetup(ui):
890 if (util.safehasattr(cmdutil, 'openrevlog') and
891 not util.safehasattr(commands, 'debugrevlogopts')):
892 # for "historical portability":
893 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
894 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
895 # openrevlog() should cause failure, because it has been
896 # available since 3.5 (or 49c583ca48c4).
897 def openrevlog(orig, repo, cmd, file_, opts):
898 if opts.get('dir') and not util.safehasattr(repo, 'dirlog'):
899 raise error.Abort("This version doesn't support --dir option",
900 hint="use 3.5 or later")
901 return orig(repo, cmd, file_, opts)
902 extensions.wrapfunction(cmdutil, 'openrevlog', openrevlog)
@@ -10,41 +10,32 b''
10
10
11 from __future__ import absolute_import, print_function
11 from __future__ import absolute_import, print_function
12 import math
12 import math
13 import optparse # cannot use argparse, python 2.7 only
13 import os
14 import os
14 import re
15 import re
16 import subprocess
15 import sys
17 import sys
16 from subprocess import (
17 CalledProcessError,
18 check_call,
19 PIPE,
20 Popen,
21 STDOUT,
22 )
23 # cannot use argparse, python 2.7 only
24 from optparse import (
25 OptionParser,
26 )
27
18
28 DEFAULTVARIANTS = ['plain', 'min', 'max', 'first', 'last',
19 DEFAULTVARIANTS = ['plain', 'min', 'max', 'first', 'last',
29 'reverse', 'reverse+first', 'reverse+last',
20 'reverse', 'reverse+first', 'reverse+last',
30 'sort', 'sort+first', 'sort+last']
21 'sort', 'sort+first', 'sort+last']
31
22
32 def check_output(*args, **kwargs):
23 def check_output(*args, **kwargs):
33 kwargs.setdefault('stderr', PIPE)
24 kwargs.setdefault('stderr', subprocess.PIPE)
34 kwargs.setdefault('stdout', PIPE)
25 kwargs.setdefault('stdout', subprocess.PIPE)
35 proc = Popen(*args, **kwargs)
26 proc = subprocess.Popen(*args, **kwargs)
36 output, error = proc.communicate()
27 output, error = proc.communicate()
37 if proc.returncode != 0:
28 if proc.returncode != 0:
38 raise CalledProcessError(proc.returncode, ' '.join(args[0]))
29 raise subprocess.CalledProcessError(proc.returncode, ' '.join(args[0]))
39 return output
30 return output
40
31
41 def update(rev):
32 def update(rev):
42 """update the repo to a revision"""
33 """update the repo to a revision"""
43 try:
34 try:
44 check_call(['hg', 'update', '--quiet', '--check', str(rev)])
35 subprocess.check_call(['hg', 'update', '--quiet', '--check', str(rev)])
45 check_output(['make', 'local'],
36 check_output(['make', 'local'],
46 stderr=None) # suppress output except for error/warning
37 stderr=None) # suppress output except for error/warning
47 except CalledProcessError as exc:
38 except subprocess.CalledProcessError as exc:
48 print('update to revision %s failed, aborting'%rev, file=sys.stderr)
39 print('update to revision %s failed, aborting'%rev, file=sys.stderr)
49 sys.exit(exc.returncode)
40 sys.exit(exc.returncode)
50
41
@@ -60,7 +51,7 b' def hg(cmd, repo=None):'
60 fullcmd += ['--config',
51 fullcmd += ['--config',
61 'extensions.perf=' + os.path.join(contribdir, 'perf.py')]
52 'extensions.perf=' + os.path.join(contribdir, 'perf.py')]
62 fullcmd += cmd
53 fullcmd += cmd
63 return check_output(fullcmd, stderr=STDOUT)
54 return check_output(fullcmd, stderr=subprocess.STDOUT)
64
55
65 def perf(revset, target=None, contexts=False):
56 def perf(revset, target=None, contexts=False):
66 """run benchmark for this very revset"""
57 """run benchmark for this very revset"""
@@ -70,7 +61,7 b' def perf(revset, target=None, contexts=F'
70 args.append('--contexts')
61 args.append('--contexts')
71 output = hg(args, repo=target)
62 output = hg(args, repo=target)
72 return parseoutput(output)
63 return parseoutput(output)
73 except CalledProcessError as exc:
64 except subprocess.CalledProcessError as exc:
74 print('abort: cannot run revset benchmark: %s'%exc.cmd, file=sys.stderr)
65 print('abort: cannot run revset benchmark: %s'%exc.cmd, file=sys.stderr)
75 if getattr(exc, 'output', None) is None: # no output before 2.7
66 if getattr(exc, 'output', None) is None: # no output before 2.7
76 print('(no output)', file=sys.stderr)
67 print('(no output)', file=sys.stderr)
@@ -103,9 +94,9 b' def printrevision(rev):'
103 """print data about a revision"""
94 """print data about a revision"""
104 sys.stdout.write("Revision ")
95 sys.stdout.write("Revision ")
105 sys.stdout.flush()
96 sys.stdout.flush()
106 check_call(['hg', 'log', '--rev', str(rev), '--template',
97 subprocess.check_call(['hg', 'log', '--rev', str(rev), '--template',
107 '{if(tags, " ({tags})")} '
98 '{if(tags, " ({tags})")} '
108 '{rev}:{node|short}: {desc|firstline}\n'])
99 '{rev}:{node|short}: {desc|firstline}\n'])
109
100
110 def idxwidth(nbidx):
101 def idxwidth(nbidx):
111 """return the max width of number used for index
102 """return the max width of number used for index
@@ -215,7 +206,7 b' def getrevs(spec):'
215 """get the list of rev matched by a revset"""
206 """get the list of rev matched by a revset"""
216 try:
207 try:
217 out = check_output(['hg', 'log', '--template={rev}\n', '--rev', spec])
208 out = check_output(['hg', 'log', '--template={rev}\n', '--rev', spec])
218 except CalledProcessError as exc:
209 except subprocess.CalledProcessError as exc:
219 print("abort, can't get revision from %s"%spec, file=sys.stderr)
210 print("abort, can't get revision from %s"%spec, file=sys.stderr)
220 sys.exit(exc.returncode)
211 sys.exit(exc.returncode)
221 return [r for r in out.split() if r]
212 return [r for r in out.split() if r]
@@ -234,8 +225,8 b' summary output is provided. Use it to de'
234 point regressions. Revsets to run are specified in a file (or from stdin), one
225 point regressions. Revsets to run are specified in a file (or from stdin), one
235 revsets per line. Line starting with '#' will be ignored, allowing insertion of
226 revsets per line. Line starting with '#' will be ignored, allowing insertion of
236 comments."""
227 comments."""
237 parser = OptionParser(usage="usage: %prog [options] <revs>",
228 parser = optparse.OptionParser(usage="usage: %prog [options] <revs>",
238 description=helptext)
229 description=helptext)
239 parser.add_option("-f", "--file",
230 parser.add_option("-f", "--file",
240 help="read revset from FILE (stdin if omitted)",
231 help="read revset from FILE (stdin if omitted)",
241 metavar="FILE")
232 metavar="FILE")
@@ -45,6 +45,13 b' import os'
45 import random
45 import random
46 import sys
46 import sys
47 import time
47 import time
48
49 from mercurial.i18n import _
50 from mercurial.node import (
51 nullid,
52 nullrev,
53 short,
54 )
48 from mercurial import (
55 from mercurial import (
49 cmdutil,
56 cmdutil,
50 context,
57 context,
@@ -54,12 +61,6 b' from mercurial import ('
54 scmutil,
61 scmutil,
55 util,
62 util,
56 )
63 )
57 from mercurial.i18n import _
58 from mercurial.node import (
59 nullid,
60 nullrev,
61 short,
62 )
63
64
64 # Note for extension authors: ONLY specify testedwith = 'internal' for
65 # Note for extension authors: ONLY specify testedwith = 'internal' for
65 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
66 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
@@ -506,7 +507,7 b' def renamedirs(dirs, words):'
506 head = rename(head)
507 head = rename(head)
507 else:
508 else:
508 head = ''
509 head = ''
509 renamed = os.path.join(head, wordgen.next())
510 renamed = os.path.join(head, next(wordgen))
510 replacements[dirpath] = renamed
511 replacements[dirpath] = renamed
511 return renamed
512 return renamed
512 result = []
513 result = []
@@ -3,8 +3,16 b''
3 # $ hg init
3 # $ hg init
4 # $ undumprevlog < repo.dump
4 # $ undumprevlog < repo.dump
5
5
6 from __future__ import absolute_import
7
6 import sys
8 import sys
7 from mercurial import revlog, node, scmutil, util, transaction
9 from mercurial import (
10 node,
11 revlog,
12 scmutil,
13 transaction,
14 util,
15 )
8
16
9 for fp in (sys.stdin, sys.stdout, sys.stderr):
17 for fp in (sys.stdin, sys.stdout, sys.stderr):
10 util.setbinary(fp)
18 util.setbinary(fp)
@@ -79,6 +79,8 b''
79 # - Restart the web server and see if things are running.
79 # - Restart the web server and see if things are running.
80 #
80 #
81
81
82 from __future__ import absolute_import
83
82 # Configuration file location
84 # Configuration file location
83 hgweb_config = r'c:\your\directory\wsgi.config'
85 hgweb_config = r'c:\your\directory\wsgi.config'
84
86
@@ -87,7 +89,6 b' path_strip = 0 # Strip this many path '
87 path_prefix = 1 # This many path elements are prefixes (depends on the
89 path_prefix = 1 # This many path elements are prefixes (depends on the
88 # virtual path of the IIS application).
90 # virtual path of the IIS application).
89
91
90 from __future__ import absolute_import
91 import sys
92 import sys
92
93
93 # Adjust python path if this is not a system-wide install
94 # Adjust python path if this is not a system-wide install
@@ -46,7 +46,6 b' editor = notepad'
46 ;extdiff =
46 ;extdiff =
47 ;fetch =
47 ;fetch =
48 ;gpg =
48 ;gpg =
49 ;hgcia =
50 ;hgk =
49 ;hgk =
51 ;highlight =
50 ;highlight =
52 ;histedit =
51 ;histedit =
@@ -6,8 +6,11 b''
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
10 from __future__ import absolute_import, print_function
11
12 import re
9 import sys
13 import sys
10 import re
11
14
12 leadingline = re.compile(r'(^\s*)(\S.*)$')
15 leadingline = re.compile(r'(^\s*)(\S.*)$')
13
16
@@ -117,11 +117,11 b' def showdoc(ui):'
117 ui.write(_("This section contains help for extensions that are "
117 ui.write(_("This section contains help for extensions that are "
118 "distributed together with Mercurial. Help for other "
118 "distributed together with Mercurial. Help for other "
119 "extensions is available in the help system."))
119 "extensions is available in the help system."))
120 ui.write("\n\n"
120 ui.write(("\n\n"
121 ".. contents::\n"
121 ".. contents::\n"
122 " :class: htmlonly\n"
122 " :class: htmlonly\n"
123 " :local:\n"
123 " :local:\n"
124 " :depth: 1\n\n")
124 " :depth: 1\n\n"))
125
125
126 for extensionname in sorted(allextensionnames()):
126 for extensionname in sorted(allextensionnames()):
127 mod = extensions.load(ui, extensionname, None)
127 mod = extensions.load(ui, extensionname, None)
@@ -415,7 +415,7 b' class Translator(nodes.NodeVisitor):'
415 else:
415 else:
416 self._docinfo[name] = node.astext()
416 self._docinfo[name] = node.astext()
417 self._docinfo_keys.append(name)
417 self._docinfo_keys.append(name)
418 raise nodes.SkipNode
418 raise nodes.SkipNode()
419
419
420 def depart_docinfo_item(self, node):
420 def depart_docinfo_item(self, node):
421 pass
421 pass
@@ -469,7 +469,7 b' class Translator(nodes.NodeVisitor):'
469
469
470 def visit_citation_reference(self, node):
470 def visit_citation_reference(self, node):
471 self.body.append('['+node.astext()+']')
471 self.body.append('['+node.astext()+']')
472 raise nodes.SkipNode
472 raise nodes.SkipNode()
473
473
474 def visit_classifier(self, node):
474 def visit_classifier(self, node):
475 pass
475 pass
@@ -489,7 +489,7 b' class Translator(nodes.NodeVisitor):'
489 def visit_comment(self, node,
489 def visit_comment(self, node,
490 sub=re.compile('-(?=-)').sub):
490 sub=re.compile('-(?=-)').sub):
491 self.body.append(self.comment(node.astext()))
491 self.body.append(self.comment(node.astext()))
492 raise nodes.SkipNode
492 raise nodes.SkipNode()
493
493
494 def visit_contact(self, node):
494 def visit_contact(self, node):
495 self.visit_docinfo_item(node, 'contact')
495 self.visit_docinfo_item(node, 'contact')
@@ -643,7 +643,7 b' class Translator(nodes.NodeVisitor):'
643 name_normalized = self._field_name.lower().replace(" ","_")
643 name_normalized = self._field_name.lower().replace(" ","_")
644 self._docinfo_names[name_normalized] = self._field_name
644 self._docinfo_names[name_normalized] = self._field_name
645 self.visit_docinfo_item(node, name_normalized)
645 self.visit_docinfo_item(node, name_normalized)
646 raise nodes.SkipNode
646 raise nodes.SkipNode()
647
647
648 def depart_field_body(self, node):
648 def depart_field_body(self, node):
649 pass
649 pass
@@ -657,7 +657,7 b' class Translator(nodes.NodeVisitor):'
657 def visit_field_name(self, node):
657 def visit_field_name(self, node):
658 if self._in_docinfo:
658 if self._in_docinfo:
659 self._field_name = node.astext()
659 self._field_name = node.astext()
660 raise nodes.SkipNode
660 raise nodes.SkipNode()
661 else:
661 else:
662 self.body.append(self.defs['field_name'][0])
662 self.body.append(self.defs['field_name'][0])
663
663
@@ -693,7 +693,7 b' class Translator(nodes.NodeVisitor):'
693
693
694 def visit_footnote_reference(self, node):
694 def visit_footnote_reference(self, node):
695 self.body.append('['+self.deunicode(node.astext())+']')
695 self.body.append('['+self.deunicode(node.astext())+']')
696 raise nodes.SkipNode
696 raise nodes.SkipNode()
697
697
698 def depart_footnote_reference(self, node):
698 def depart_footnote_reference(self, node):
699 pass
699 pass
@@ -705,7 +705,7 b' class Translator(nodes.NodeVisitor):'
705 pass
705 pass
706
706
707 def visit_header(self, node):
707 def visit_header(self, node):
708 raise NotImplementedError, node.astext()
708 raise NotImplementedError(node.astext())
709
709
710 def depart_header(self, node):
710 def depart_header(self, node):
711 pass
711 pass
@@ -742,7 +742,7 b' class Translator(nodes.NodeVisitor):'
742 if 'uri' in node.attributes:
742 if 'uri' in node.attributes:
743 text.append(node.attributes['uri'])
743 text.append(node.attributes['uri'])
744 self.body.append('[image: %s]\n' % ('/'.join(text)))
744 self.body.append('[image: %s]\n' % ('/'.join(text)))
745 raise nodes.SkipNode
745 raise nodes.SkipNode()
746
746
747 def visit_important(self, node):
747 def visit_important(self, node):
748 self.visit_admonition(node, 'important')
748 self.visit_admonition(node, 'important')
@@ -753,7 +753,7 b' class Translator(nodes.NodeVisitor):'
753 # footnote and citation
753 # footnote and citation
754 if (isinstance(node.parent, nodes.footnote)
754 if (isinstance(node.parent, nodes.footnote)
755 or isinstance(node.parent, nodes.citation)):
755 or isinstance(node.parent, nodes.citation)):
756 raise nodes.SkipNode
756 raise nodes.SkipNode()
757 self.document.reporter.warning('"unsupported "label"',
757 self.document.reporter.warning('"unsupported "label"',
758 base_node=node)
758 base_node=node)
759 self.body.append('[')
759 self.body.append('[')
@@ -793,7 +793,7 b' class Translator(nodes.NodeVisitor):'
793 def visit_list_item(self, node):
793 def visit_list_item(self, node):
794 # man 7 man argues to use ".IP" instead of ".TP"
794 # man 7 man argues to use ".IP" instead of ".TP"
795 self.body.append('.IP %s %d\n' % (
795 self.body.append('.IP %s %d\n' % (
796 self._list_char[-1].next(),
796 next(self._list_char[-1]),
797 self._list_char[-1].get_width(),))
797 self._list_char[-1].get_width(),))
798
798
799 def depart_list_item(self, node):
799 def depart_list_item(self, node):
@@ -814,7 +814,7 b' class Translator(nodes.NodeVisitor):'
814 self.body.append(self.defs['literal_block'][1])
814 self.body.append(self.defs['literal_block'][1])
815
815
816 def visit_meta(self, node):
816 def visit_meta(self, node):
817 raise NotImplementedError, node.astext()
817 raise NotImplementedError(node.astext())
818
818
819 def depart_meta(self, node):
819 def depart_meta(self, node):
820 pass
820 pass
@@ -924,7 +924,7 b' class Translator(nodes.NodeVisitor):'
924 if node.get('format') == 'manpage':
924 if node.get('format') == 'manpage':
925 self.body.append(node.astext() + "\n")
925 self.body.append(node.astext() + "\n")
926 # Keep non-manpage raw text out of output:
926 # Keep non-manpage raw text out of output:
927 raise nodes.SkipNode
927 raise nodes.SkipNode()
928
928
929 def visit_reference(self, node):
929 def visit_reference(self, node):
930 """E.g. link or email address."""
930 """E.g. link or email address."""
@@ -963,7 +963,7 b' class Translator(nodes.NodeVisitor):'
963
963
964 def visit_substitution_definition(self, node):
964 def visit_substitution_definition(self, node):
965 """Internal only."""
965 """Internal only."""
966 raise nodes.SkipNode
966 raise nodes.SkipNode()
967
967
968 def visit_substitution_reference(self, node):
968 def visit_substitution_reference(self, node):
969 self.document.reporter.warning('"substitution_reference" not supported',
969 self.document.reporter.warning('"substitution_reference" not supported',
@@ -1009,7 +1009,7 b' class Translator(nodes.NodeVisitor):'
1009
1009
1010 def visit_target(self, node):
1010 def visit_target(self, node):
1011 # targets are in-document hyper targets, without any use for man-pages.
1011 # targets are in-document hyper targets, without any use for man-pages.
1012 raise nodes.SkipNode
1012 raise nodes.SkipNode()
1013
1013
1014 def visit_tbody(self, node):
1014 def visit_tbody(self, node):
1015 pass
1015 pass
@@ -1053,7 +1053,7 b' class Translator(nodes.NodeVisitor):'
1053 self._docinfo['title'] = node.astext()
1053 self._docinfo['title'] = node.astext()
1054 # document title for .TH
1054 # document title for .TH
1055 self._docinfo['title_upper'] = node.astext().upper()
1055 self._docinfo['title_upper'] = node.astext().upper()
1056 raise nodes.SkipNode
1056 raise nodes.SkipNode()
1057 elif self.section_level == 1:
1057 elif self.section_level == 1:
1058 self.body.append('.SH ')
1058 self.body.append('.SH ')
1059 for n in node.traverse(nodes.Text):
1059 for n in node.traverse(nodes.Text):
@@ -11,9 +11,11 b' import os'
11 import sys
11 import sys
12
12
13 if os.environ.get('HGUNICODEPEDANTRY', False):
13 if os.environ.get('HGUNICODEPEDANTRY', False):
14 reload(sys)
14 try:
15 sys.setdefaultencoding("undefined")
15 reload(sys)
16
16 sys.setdefaultencoding("undefined")
17 except NameError:
18 pass
17
19
18 libdir = '@LIBDIR@'
20 libdir = '@LIBDIR@'
19
21
@@ -26,9 +28,9 b" if libdir != '@' 'LIBDIR' '@':"
26
28
27 # enable importing on demand to reduce startup time
29 # enable importing on demand to reduce startup time
28 try:
30 try:
29 from mercurial import demandimport; demandimport.enable()
31 if sys.version_info[0] < 3:
32 from mercurial import demandimport; demandimport.enable()
30 except ImportError:
33 except ImportError:
31 import sys
32 sys.stderr.write("abort: couldn't find mercurial libraries in [%s]\n" %
34 sys.stderr.write("abort: couldn't find mercurial libraries in [%s]\n" %
33 ' '.join(sys.path))
35 ' '.join(sys.path))
34 sys.stderr.write("(check your install and PYTHONPATH)\n")
36 sys.stderr.write("(check your install and PYTHONPATH)\n")
@@ -26,6 +26,7 b' The threshold at which a file is conside'
26
26
27 from __future__ import absolute_import
27 from __future__ import absolute_import
28
28
29 from mercurial.i18n import _
29 from mercurial import (
30 from mercurial import (
30 commands,
31 commands,
31 copies,
32 copies,
@@ -34,7 +35,6 b' from mercurial import ('
34 scmutil,
35 scmutil,
35 similar
36 similar
36 )
37 )
37 from mercurial.i18n import _
38
38
39 def extsetup(ui):
39 def extsetup(ui):
40 entry = extensions.wrapcommand(
40 entry = extensions.wrapcommand(
@@ -281,8 +281,6 b' from __future__ import absolute_import'
281
281
282 import re
282 import re
283 import time
283 import time
284 import urlparse
285 import xmlrpclib
286
284
287 from mercurial.i18n import _
285 from mercurial.i18n import _
288 from mercurial.node import short
286 from mercurial.node import short
@@ -293,6 +291,9 b' from mercurial import ('
293 util,
291 util,
294 )
292 )
295
293
294 urlparse = util.urlparse
295 xmlrpclib = util.xmlrpclib
296
296 # Note for extension authors: ONLY specify testedwith = 'internal' for
297 # Note for extension authors: ONLY specify testedwith = 'internal' for
297 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
298 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
298 # be specifying the version(s) of Mercurial they are tested with, or
299 # be specifying the version(s) of Mercurial they are tested with, or
@@ -40,18 +40,15 b' Config'
40
40
41 from __future__ import absolute_import
41 from __future__ import absolute_import
42
42
43 import SocketServer
44 import errno
43 import errno
45 import gc
44 import hashlib
46 import inspect
45 import inspect
47 import os
46 import os
48 import random
49 import re
47 import re
48 import signal
50 import struct
49 import struct
51 import sys
50 import sys
52 import threading
53 import time
51 import time
54 import traceback
55
52
56 from mercurial.i18n import _
53 from mercurial.i18n import _
57
54
@@ -76,10 +73,11 b" testedwith = 'internal'"
76
73
77 def _hashlist(items):
74 def _hashlist(items):
78 """return sha1 hexdigest for a list"""
75 """return sha1 hexdigest for a list"""
79 return util.sha1(str(items)).hexdigest()
76 return hashlib.sha1(str(items)).hexdigest()
80
77
81 # sensitive config sections affecting confighash
78 # sensitive config sections affecting confighash
82 _configsections = [
79 _configsections = [
80 'alias', # affects global state commands.table
83 'extdiff', # uisetup will register new commands
81 'extdiff', # uisetup will register new commands
84 'extensions',
82 'extensions',
85 ]
83 ]
@@ -150,6 +148,10 b' def _mtimehash(paths):'
150
148
151 for chgserver, it is designed that once mtimehash changes, the server is
149 for chgserver, it is designed that once mtimehash changes, the server is
152 considered outdated immediately and should no longer provide service.
150 considered outdated immediately and should no longer provide service.
151
152 mtimehash is not included in confighash because we only know the paths of
153 extensions after importing them (there is imp.find_module but that faces
154 race conditions). We need to calculate confighash without importing.
153 """
155 """
154 def trystat(path):
156 def trystat(path):
155 try:
157 try:
@@ -213,18 +215,6 b' def _setuppagercmd(ui, options, cmd):'
213 ui.setconfig('ui', 'interactive', False, 'pager')
215 ui.setconfig('ui', 'interactive', False, 'pager')
214 return p
216 return p
215
217
216 _envvarre = re.compile(r'\$[a-zA-Z_]+')
217
218 def _clearenvaliases(cmdtable):
219 """Remove stale command aliases referencing env vars; variable expansion
220 is done at dispatch.addaliases()"""
221 for name, tab in cmdtable.items():
222 cmddef = tab[0]
223 if (isinstance(cmddef, dispatch.cmdalias) and
224 not cmddef.definition.startswith('!') and # shell alias
225 _envvarre.search(cmddef.definition)):
226 del cmdtable[name]
227
228 def _newchgui(srcui, csystem):
218 def _newchgui(srcui, csystem):
229 class chgui(srcui.__class__):
219 class chgui(srcui.__class__):
230 def __init__(self, src=None):
220 def __init__(self, src=None):
@@ -357,6 +347,7 b' class chgcmdserver(commandserver.server)'
357 self.capabilities['validate'] = chgcmdserver.validate
347 self.capabilities['validate'] = chgcmdserver.validate
358
348
359 def cleanup(self):
349 def cleanup(self):
350 super(chgcmdserver, self).cleanup()
360 # dispatch._runcatch() does not flush outputs if exception is not
351 # dispatch._runcatch() does not flush outputs if exception is not
361 # handled by dispatch._dispatch()
352 # handled by dispatch._dispatch()
362 self.ui.flush()
353 self.ui.flush()
@@ -508,6 +499,11 b' class chgcmdserver(commandserver.server)'
508
499
509 pagercmd = _setuppagercmd(self.ui, options, cmd)
500 pagercmd = _setuppagercmd(self.ui, options, cmd)
510 if pagercmd:
501 if pagercmd:
502 # Python's SIGPIPE is SIG_IGN by default. change to SIG_DFL so
503 # we can exit if the pipe to the pager is closed
504 if util.safehasattr(signal, 'SIGPIPE') and \
505 signal.getsignal(signal.SIGPIPE) == signal.SIG_IGN:
506 signal.signal(signal.SIGPIPE, signal.SIG_DFL)
511 self.cresult.write(pagercmd)
507 self.cresult.write(pagercmd)
512 else:
508 else:
513 self.cresult.write('\0')
509 self.cresult.write('\0')
@@ -525,7 +521,6 b' class chgcmdserver(commandserver.server)'
525 _log('setenv: %r\n' % sorted(newenv.keys()))
521 _log('setenv: %r\n' % sorted(newenv.keys()))
526 os.environ.clear()
522 os.environ.clear()
527 os.environ.update(newenv)
523 os.environ.update(newenv)
528 _clearenvaliases(commands.table)
529
524
530 capabilities = commandserver.server.capabilities.copy()
525 capabilities = commandserver.server.capabilities.copy()
531 capabilities.update({'attachio': attachio,
526 capabilities.update({'attachio': attachio,
@@ -534,174 +529,110 b' class chgcmdserver(commandserver.server)'
534 'setenv': setenv,
529 'setenv': setenv,
535 'setumask': setumask})
530 'setumask': setumask})
536
531
537 # copied from mercurial/commandserver.py
538 class _requesthandler(SocketServer.StreamRequestHandler):
539 def handle(self):
540 # use a different process group from the master process, making this
541 # process pass kernel "is_current_pgrp_orphaned" check so signals like
542 # SIGTSTP, SIGTTIN, SIGTTOU are not ignored.
543 os.setpgid(0, 0)
544 # change random state otherwise forked request handlers would have a
545 # same state inherited from parent.
546 random.seed()
547 ui = self.server.ui
548 repo = self.server.repo
549 sv = None
550 try:
551 sv = chgcmdserver(ui, repo, self.rfile, self.wfile, self.connection,
552 self.server.hashstate, self.server.baseaddress)
553 try:
554 sv.serve()
555 # handle exceptions that may be raised by command server. most of
556 # known exceptions are caught by dispatch.
557 except error.Abort as inst:
558 ui.warn(_('abort: %s\n') % inst)
559 except IOError as inst:
560 if inst.errno != errno.EPIPE:
561 raise
562 except KeyboardInterrupt:
563 pass
564 finally:
565 sv.cleanup()
566 except: # re-raises
567 # also write traceback to error channel. otherwise client cannot
568 # see it because it is written to server's stderr by default.
569 if sv:
570 cerr = sv.cerr
571 else:
572 cerr = commandserver.channeledoutput(self.wfile, 'e')
573 traceback.print_exc(file=cerr)
574 raise
575 finally:
576 # trigger __del__ since ForkingMixIn uses os._exit
577 gc.collect()
578
579 def _tempaddress(address):
532 def _tempaddress(address):
580 return '%s.%d.tmp' % (address, os.getpid())
533 return '%s.%d.tmp' % (address, os.getpid())
581
534
582 def _hashaddress(address, hashstr):
535 def _hashaddress(address, hashstr):
583 return '%s-%s' % (address, hashstr)
536 return '%s-%s' % (address, hashstr)
584
537
585 class AutoExitMixIn: # use old-style to comply with SocketServer design
538 class chgunixservicehandler(object):
586 lastactive = time.time()
539 """Set of operations for chg services"""
587 idletimeout = 3600 # default 1 hour
540
541 pollinterval = 1 # [sec]
588
542
589 def startautoexitthread(self):
543 def __init__(self, ui):
590 # note: the auto-exit check here is cheap enough to not use a thread,
544 self.ui = ui
591 # be done in serve_forever. however SocketServer is hook-unfriendly,
545 self._idletimeout = ui.configint('chgserver', 'idletimeout', 3600)
592 # you simply cannot hook serve_forever without copying a lot of code.
546 self._lastactive = time.time()
593 # besides, serve_forever's docstring suggests using thread.
547
594 thread = threading.Thread(target=self._autoexitloop)
548 def bindsocket(self, sock, address):
595 thread.daemon = True
549 self._inithashstate(address)
596 thread.start()
550 self._checkextensions()
551 self._bind(sock)
552 self._createsymlink()
597
553
598 def _autoexitloop(self, interval=1):
554 def _inithashstate(self, address):
599 while True:
555 self._baseaddress = address
600 time.sleep(interval)
556 if self.ui.configbool('chgserver', 'skiphash', False):
601 if not self.issocketowner():
557 self._hashstate = None
602 _log('%s is not owned, exiting.\n' % self.server_address)
558 self._realaddress = address
603 break
559 return
604 if time.time() - self.lastactive > self.idletimeout:
560 self._hashstate = hashstate.fromui(self.ui)
605 _log('being idle too long. exiting.\n')
561 self._realaddress = _hashaddress(address, self._hashstate.confighash)
606 break
607 self.shutdown()
608
562
609 def process_request(self, request, address):
563 def _checkextensions(self):
610 self.lastactive = time.time()
564 if not self._hashstate:
611 return SocketServer.ForkingMixIn.process_request(
565 return
612 self, request, address)
566 if extensions.notloaded():
567 # one or more extensions failed to load. mtimehash becomes
568 # meaningless because we do not know the paths of those extensions.
569 # set mtimehash to an illegal hash value to invalidate the server.
570 self._hashstate.mtimehash = ''
613
571
614 def server_bind(self):
572 def _bind(self, sock):
615 # use a unique temp address so we can stat the file and do ownership
573 # use a unique temp address so we can stat the file and do ownership
616 # check later
574 # check later
617 tempaddress = _tempaddress(self.server_address)
575 tempaddress = _tempaddress(self._realaddress)
618 # use relative path instead of full path at bind() if possible, since
576 util.bindunixsocket(sock, tempaddress)
619 # AF_UNIX path has very small length limit (107 chars) on common
577 self._socketstat = os.stat(tempaddress)
620 # platforms (see sys/un.h)
621 dirname, basename = os.path.split(tempaddress)
622 bakwdfd = None
623 if dirname:
624 bakwdfd = os.open('.', os.O_DIRECTORY)
625 os.chdir(dirname)
626 self.socket.bind(basename)
627 self._socketstat = os.stat(basename)
628 # rename will replace the old socket file if exists atomically. the
578 # rename will replace the old socket file if exists atomically. the
629 # old server will detect ownership change and exit.
579 # old server will detect ownership change and exit.
630 util.rename(basename, self.server_address)
580 util.rename(tempaddress, self._realaddress)
631 if bakwdfd:
632 os.fchdir(bakwdfd)
633 os.close(bakwdfd)
634
581
635 def issocketowner(self):
582 def _createsymlink(self):
583 if self._baseaddress == self._realaddress:
584 return
585 tempaddress = _tempaddress(self._baseaddress)
586 os.symlink(os.path.basename(self._realaddress), tempaddress)
587 util.rename(tempaddress, self._baseaddress)
588
589 def _issocketowner(self):
636 try:
590 try:
637 stat = os.stat(self.server_address)
591 stat = os.stat(self._realaddress)
638 return (stat.st_ino == self._socketstat.st_ino and
592 return (stat.st_ino == self._socketstat.st_ino and
639 stat.st_mtime == self._socketstat.st_mtime)
593 stat.st_mtime == self._socketstat.st_mtime)
640 except OSError:
594 except OSError:
641 return False
595 return False
642
596
643 def unlinksocketfile(self):
597 def unlinksocket(self, address):
644 if not self.issocketowner():
598 if not self._issocketowner():
645 return
599 return
646 # it is possible to have a race condition here that we may
600 # it is possible to have a race condition here that we may
647 # remove another server's socket file. but that's okay
601 # remove another server's socket file. but that's okay
648 # since that server will detect and exit automatically and
602 # since that server will detect and exit automatically and
649 # the client will start a new server on demand.
603 # the client will start a new server on demand.
650 try:
604 try:
651 os.unlink(self.server_address)
605 os.unlink(self._realaddress)
652 except OSError as exc:
606 except OSError as exc:
653 if exc.errno != errno.ENOENT:
607 if exc.errno != errno.ENOENT:
654 raise
608 raise
655
609
656 class chgunixservice(commandserver.unixservice):
610 def printbanner(self, address):
657 def init(self):
611 # no "listening at" message should be printed to simulate hg behavior
658 if self.repo:
612 pass
659 # one chgserver can serve multiple repos. drop repo infomation
613
660 self.ui.setconfig('bundle', 'mainreporoot', '', 'repo')
614 def shouldexit(self):
661 self.repo = None
615 if not self._issocketowner():
662 self._inithashstate()
616 self.ui.debug('%s is not owned, exiting.\n' % self._realaddress)
663 self._checkextensions()
617 return True
664 class cls(AutoExitMixIn, SocketServer.ForkingMixIn,
618 if time.time() - self._lastactive > self._idletimeout:
665 SocketServer.UnixStreamServer):
619 self.ui.debug('being idle too long. exiting.\n')
666 ui = self.ui
620 return True
667 repo = self.repo
621 return False
668 hashstate = self.hashstate
669 baseaddress = self.baseaddress
670 self.server = cls(self.address, _requesthandler)
671 self.server.idletimeout = self.ui.configint(
672 'chgserver', 'idletimeout', self.server.idletimeout)
673 self.server.startautoexitthread()
674 self._createsymlink()
675
622
676 def _inithashstate(self):
623 def newconnection(self):
677 self.baseaddress = self.address
624 self._lastactive = time.time()
678 if self.ui.configbool('chgserver', 'skiphash', False):
625
679 self.hashstate = None
626 def createcmdserver(self, repo, conn, fin, fout):
680 return
627 return chgcmdserver(self.ui, repo, fin, fout, conn,
681 self.hashstate = hashstate.fromui(self.ui)
628 self._hashstate, self._baseaddress)
682 self.address = _hashaddress(self.address, self.hashstate.confighash)
683
629
684 def _checkextensions(self):
630 def chgunixservice(ui, repo, opts):
685 if not self.hashstate:
631 if repo:
686 return
632 # one chgserver can serve multiple repos. drop repo infomation
687 if extensions.notloaded():
633 ui.setconfig('bundle', 'mainreporoot', '', 'repo')
688 # one or more extensions failed to load. mtimehash becomes
634 h = chgunixservicehandler(ui)
689 # meaningless because we do not know the paths of those extensions.
635 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h)
690 # set mtimehash to an illegal hash value to invalidate the server.
691 self.hashstate.mtimehash = ''
692
693 def _createsymlink(self):
694 if self.baseaddress == self.address:
695 return
696 tempaddress = _tempaddress(self.baseaddress)
697 os.symlink(os.path.basename(self.address), tempaddress)
698 util.rename(tempaddress, self.baseaddress)
699
700 def run(self):
701 try:
702 self.server.serve_forever()
703 finally:
704 self.server.unlinksocketfile()
705
636
706 def uisetup(ui):
637 def uisetup(ui):
707 commandserver._servicemap['chgunix'] = chgunixservice
638 commandserver._servicemap['chgunix'] = chgunixservice
@@ -156,6 +156,8 b' If ``pagermode`` is not defined, the ``m'
156 from __future__ import absolute_import
156 from __future__ import absolute_import
157
157
158 import os
158 import os
159
160 from mercurial.i18n import _
159 from mercurial import (
161 from mercurial import (
160 cmdutil,
162 cmdutil,
161 commands,
163 commands,
@@ -165,7 +167,6 b' from mercurial import ('
165 ui as uimod,
167 ui as uimod,
166 util,
168 util,
167 )
169 )
168 from mercurial.i18n import _
169
170
170 cmdtable = {}
171 cmdtable = {}
171 command = cmdutil.command(cmdtable)
172 command = cmdutil.command(cmdtable)
@@ -9,11 +9,11 b''
9
9
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 from mercurial.i18n import _
12 from mercurial import (
13 from mercurial import (
13 cmdutil,
14 cmdutil,
14 registrar,
15 registrar,
15 )
16 )
16 from mercurial.i18n import _
17
17
18 from . import (
18 from . import (
19 convcmd,
19 convcmd,
@@ -10,11 +10,12 b''
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 import os
12 import os
13
14 from mercurial.i18n import _
13 from mercurial import (
15 from mercurial import (
14 demandimport,
16 demandimport,
15 error
17 error
16 )
18 )
17 from mercurial.i18n import _
18 from . import common
19 from . import common
19
20
20 # these do not work with demandimport, blacklist
21 # these do not work with demandimport, blacklist
@@ -7,20 +7,20 b''
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import base64
9 import base64
10 import cPickle as pickle
11 import datetime
10 import datetime
12 import errno
11 import errno
13 import os
12 import os
14 import re
13 import re
15 import subprocess
14 import subprocess
16
15
16 from mercurial.i18n import _
17 from mercurial import (
17 from mercurial import (
18 error,
18 error,
19 phases,
19 phases,
20 util,
20 util,
21 )
21 )
22 from mercurial.i18n import _
23
22
23 pickle = util.pickle
24 propertycache = util.propertycache
24 propertycache = util.propertycache
25
25
26 def encodeargs(args):
26 def encodeargs(args):
@@ -10,13 +10,13 b' import os'
10 import shlex
10 import shlex
11 import shutil
11 import shutil
12
12
13 from mercurial.i18n import _
13 from mercurial import (
14 from mercurial import (
14 encoding,
15 encoding,
15 error,
16 error,
16 hg,
17 hg,
17 util,
18 util,
18 )
19 )
19 from mercurial.i18n import _
20
20
21 from . import (
21 from . import (
22 bzr,
22 bzr,
@@ -11,12 +11,12 b' import os'
11 import re
11 import re
12 import socket
12 import socket
13
13
14 from mercurial.i18n import _
14 from mercurial import (
15 from mercurial import (
15 encoding,
16 encoding,
16 error,
17 error,
17 util,
18 util,
18 )
19 )
19 from mercurial.i18n import _
20
20
21 from . import (
21 from . import (
22 common,
22 common,
@@ -6,15 +6,16 b''
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import cPickle as pickle
10 import os
9 import os
11 import re
10 import re
12
11
12 from mercurial.i18n import _
13 from mercurial import (
13 from mercurial import (
14 hook,
14 hook,
15 util,
15 util,
16 )
16 )
17 from mercurial.i18n import _
17
18 pickle = util.pickle
18
19
19 class logentry(object):
20 class logentry(object):
20 '''Class logentry has the following attributes:
21 '''Class logentry has the following attributes:
@@ -7,10 +7,11 b' from __future__ import absolute_import'
7
7
8 import posixpath
8 import posixpath
9 import shlex
9 import shlex
10
11 from mercurial.i18n import _
10 from mercurial import (
12 from mercurial import (
11 error,
13 error,
12 )
14 )
13 from mercurial.i18n import _
14 from . import common
15 from . import common
15 SKIPREV = common.SKIPREV
16 SKIPREV = common.SKIPREV
16
17
@@ -7,12 +7,13 b''
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import os
9 import os
10
11 from mercurial.i18n import _
10 from mercurial import (
12 from mercurial import (
11 config,
13 config,
12 error,
14 error,
13 node as nodemod,
15 node as nodemod,
14 )
16 )
15 from mercurial.i18n import _
16
17
17 from . import (
18 from . import (
18 common,
19 common,
@@ -12,12 +12,13 b' import os'
12 import shutil
12 import shutil
13 import stat
13 import stat
14 import tempfile
14 import tempfile
15
16 from mercurial.i18n import _
15 from mercurial import (
17 from mercurial import (
16 encoding,
18 encoding,
17 error,
19 error,
18 util,
20 util,
19 )
21 )
20 from mercurial.i18n import _
21 from . import common
22 from . import common
22
23
23 class gnuarch_source(common.converter_source, common.commandline):
24 class gnuarch_source(common.converter_source, common.commandline):
@@ -22,6 +22,7 b' import os'
22 import re
22 import re
23 import time
23 import time
24
24
25 from mercurial.i18n import _
25 from mercurial import (
26 from mercurial import (
26 bookmarks,
27 bookmarks,
27 context,
28 context,
@@ -37,7 +38,6 b' from mercurial import ('
37 )
38 )
38 stringio = util.stringio
39 stringio = util.stringio
39
40
40 from mercurial.i18n import _
41 from . import common
41 from . import common
42 mapfile = common.mapfile
42 mapfile = common.mapfile
43 NoRepo = common.NoRepo
43 NoRepo = common.NoRepo
@@ -10,11 +10,11 b' from __future__ import absolute_import'
10 import os
10 import os
11 import re
11 import re
12
12
13 from mercurial.i18n import _
13 from mercurial import (
14 from mercurial import (
14 error,
15 error,
15 util,
16 util,
16 )
17 )
17 from mercurial.i18n import _
18
18
19 from . import common
19 from . import common
20
20
@@ -9,11 +9,11 b' from __future__ import absolute_import'
9 import marshal
9 import marshal
10 import re
10 import re
11
11
12 from mercurial.i18n import _
12 from mercurial import (
13 from mercurial import (
13 error,
14 error,
14 util,
15 util,
15 )
16 )
16 from mercurial.i18n import _
17
17
18 from . import common
18 from . import common
19
19
@@ -3,13 +3,13 b''
3 # Copyright(C) 2007 Daniel Holth et al
3 # Copyright(C) 2007 Daniel Holth et al
4 from __future__ import absolute_import
4 from __future__ import absolute_import
5
5
6 import cPickle as pickle
7 import os
6 import os
8 import re
7 import re
9 import sys
8 import sys
10 import tempfile
9 import tempfile
11 import xml.dom.minidom
10 import xml.dom.minidom
12
11
12 from mercurial.i18n import _
13 from mercurial import (
13 from mercurial import (
14 encoding,
14 encoding,
15 error,
15 error,
@@ -17,10 +17,10 b' from mercurial import ('
17 strutil,
17 strutil,
18 util,
18 util,
19 )
19 )
20 from mercurial.i18n import _
21
20
22 from . import common
21 from . import common
23
22
23 pickle = util.pickle
24 stringio = util.stringio
24 stringio = util.stringio
25 propertycache = util.propertycache
25 propertycache = util.propertycache
26 urlerr = util.urlerr
26 urlerr = util.urlerr
@@ -102,8 +102,7 b' def monkeypatch_method(cls):'
102
102
103 @monkeypatch_method(passwordmgr)
103 @monkeypatch_method(passwordmgr)
104 def find_user_password(self, realm, authuri):
104 def find_user_password(self, realm, authuri):
105 user, passwd = urlreq.httppasswordmgrwithdefaultrealm.find_user_password(
105 user, passwd = self.passwddb.find_user_password(realm, authuri)
106 self, realm, authuri)
107 if user and passwd:
106 if user and passwd:
108 self._writedebug(user, passwd)
107 self._writedebug(user, passwd)
109 return (user, passwd)
108 return (user, passwd)
@@ -7,12 +7,23 b''
7
7
8 '''pull, update and merge in one command (DEPRECATED)'''
8 '''pull, update and merge in one command (DEPRECATED)'''
9
9
10 from __future__ import absolute_import
11
10 from mercurial.i18n import _
12 from mercurial.i18n import _
11 from mercurial.node import short
13 from mercurial.node import (
12 from mercurial import commands, cmdutil, hg, util, error
14 short,
13 from mercurial.lock import release
15 )
14 from mercurial import exchange
16 from mercurial import (
17 cmdutil,
18 commands,
19 error,
20 exchange,
21 hg,
22 lock,
23 util,
24 )
15
25
26 release = lock.release
16 cmdtable = {}
27 cmdtable = {}
17 command = cmdutil.command(cmdtable)
28 command = cmdutil.command(cmdtable)
18 # Note for extension authors: ONLY specify testedwith = 'internal' for
29 # Note for extension authors: ONLY specify testedwith = 'internal' for
@@ -91,10 +91,12 b' will disable itself if any of those are '
91
91
92 from __future__ import absolute_import
92 from __future__ import absolute_import
93
93
94 import hashlib
94 import os
95 import os
95 import stat
96 import stat
96 import sys
97 import sys
97
98
99 from mercurial.i18n import _
98 from mercurial import (
100 from mercurial import (
99 context,
101 context,
100 extensions,
102 extensions,
@@ -105,7 +107,6 b' from mercurial import ('
105 util,
107 util,
106 )
108 )
107 from mercurial import match as matchmod
109 from mercurial import match as matchmod
108 from mercurial.i18n import _
109
110
110 from . import (
111 from . import (
111 state,
112 state,
@@ -141,7 +142,7 b' def _hashignore(ignore):'
141 copy.
142 copy.
142
143
143 """
144 """
144 sha1 = util.sha1()
145 sha1 = hashlib.sha1()
145 if util.safehasattr(ignore, 'includepat'):
146 if util.safehasattr(ignore, 'includepat'):
146 sha1.update(ignore.includepat)
147 sha1.update(ignore.includepat)
147 sha1.update('\0\0')
148 sha1.update('\0\0')
@@ -12,8 +12,8 b' import os'
12 import socket
12 import socket
13 import struct
13 import struct
14
14
15 from mercurial.i18n import _
15 from mercurial import pathutil
16 from mercurial import pathutil
16 from mercurial.i18n import _
17
17
18 _version = 4
18 _version = 4
19 _versionformat = ">I"
19 _versionformat = ">I"
@@ -5,10 +5,21 b''
5
5
6 '''commands to sign and verify changesets'''
6 '''commands to sign and verify changesets'''
7
7
8 import os, tempfile, binascii
8 from __future__ import absolute_import
9 from mercurial import util, commands, match, cmdutil, error
9
10 from mercurial import node as hgnode
10 import binascii
11 import os
12 import tempfile
13
11 from mercurial.i18n import _
14 from mercurial.i18n import _
15 from mercurial import (
16 cmdutil,
17 commands,
18 error,
19 match,
20 node as hgnode,
21 util,
22 )
12
23
13 cmdtable = {}
24 cmdtable = {}
14 command = cmdutil.command(cmdtable)
25 command = cmdutil.command(cmdtable)
@@ -187,7 +198,7 b' def sigcheck(ui, repo, rev):'
187 return
198 return
188
199
189 # print summary
200 # print summary
190 ui.write("%s is signed by:\n" % hgnode.short(rev))
201 ui.write(_("%s is signed by:\n") % hgnode.short(rev))
191 for key in keys:
202 for key in keys:
192 ui.write(" %s\n" % keystr(ui, key))
203 ui.write(" %s\n" % keystr(ui, key))
193
204
@@ -15,8 +15,13 b' commands. When this options is given, an'
15 revision graph is also shown.
15 revision graph is also shown.
16 '''
16 '''
17
17
18 from __future__ import absolute_import
19
18 from mercurial.i18n import _
20 from mercurial.i18n import _
19 from mercurial import cmdutil, commands
21 from mercurial import (
22 cmdutil,
23 commands,
24 )
20
25
21 cmdtable = {}
26 cmdtable = {}
22 command = cmdutil.command(cmdtable)
27 command = cmdutil.command(cmdtable)
@@ -34,10 +34,23 b' Revisions context menu will now display '
34 vdiff on hovered and selected revisions.
34 vdiff on hovered and selected revisions.
35 '''
35 '''
36
36
37 from __future__ import absolute_import
38
37 import os
39 import os
38 from mercurial import cmdutil, commands, patch, scmutil, obsolete
40
39 from mercurial.node import nullid, nullrev, short
40 from mercurial.i18n import _
41 from mercurial.i18n import _
42 from mercurial.node import (
43 nullid,
44 nullrev,
45 short,
46 )
47 from mercurial import (
48 cmdutil,
49 commands,
50 obsolete,
51 patch,
52 scmutil,
53 )
41
54
42 cmdtable = {}
55 cmdtable = {}
43 command = cmdutil.command(cmdtable)
56 command = cmdutil.command(cmdtable)
@@ -68,13 +81,13 b' def difftree(ui, repo, node1=None, node2'
68
81
69 for f in modified:
82 for f in modified:
70 # TODO get file permissions
83 # TODO get file permissions
71 ui.write(":100664 100664 %s %s M\t%s\t%s\n" %
84 ui.write((":100664 100664 %s %s M\t%s\t%s\n") %
72 (short(mmap[f]), short(mmap2[f]), f, f))
85 (short(mmap[f]), short(mmap2[f]), f, f))
73 for f in added:
86 for f in added:
74 ui.write(":000000 100664 %s %s N\t%s\t%s\n" %
87 ui.write((":000000 100664 %s %s N\t%s\t%s\n") %
75 (empty, short(mmap2[f]), f, f))
88 (empty, short(mmap2[f]), f, f))
76 for f in removed:
89 for f in removed:
77 ui.write(":100664 000000 %s %s D\t%s\t%s\n" %
90 ui.write((":100664 000000 %s %s D\t%s\t%s\n") %
78 (short(mmap[f]), empty, f, f))
91 (short(mmap[f]), empty, f, f))
79 ##
92 ##
80
93
@@ -26,9 +26,21 b' Pygments will try very hard to identify '
26 match (even matches with a low confidence score) will be used.
26 match (even matches with a low confidence score) will be used.
27 """
27 """
28
28
29 import highlight
29 from __future__ import absolute_import
30 from mercurial.hgweb import webcommands, webutil, common
30
31 from mercurial import extensions, encoding, fileset
31 from . import highlight
32 from mercurial.hgweb import (
33 common,
34 webcommands,
35 webutil,
36 )
37
38 from mercurial import (
39 encoding,
40 extensions,
41 fileset,
42 )
43
32 # Note for extension authors: ONLY specify testedwith = 'internal' for
44 # Note for extension authors: ONLY specify testedwith = 'internal' for
33 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
45 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
34 # be specifying the version(s) of Mercurial they are tested with, or
46 # be specifying the version(s) of Mercurial they are tested with, or
@@ -8,14 +8,27 b''
8 # The original module was split in an interface and an implementation
8 # The original module was split in an interface and an implementation
9 # file to defer pygments loading and speedup extension setup.
9 # file to defer pygments loading and speedup extension setup.
10
10
11 from __future__ import absolute_import
12
13 import pygments
14 import pygments.formatters
15 import pygments.lexers
16 import pygments.util
17
11 from mercurial import demandimport
18 from mercurial import demandimport
12 demandimport.ignore.extend(['pkgutil', 'pkg_resources', '__main__'])
19 demandimport.ignore.extend(['pkgutil', 'pkg_resources', '__main__'])
13 from mercurial import util, encoding
20
21 from mercurial import (
22 encoding,
23 util,
24 )
14
25
15 from pygments import highlight
26 highlight = pygments.highlight
16 from pygments.util import ClassNotFound
27 ClassNotFound = pygments.util.ClassNotFound
17 from pygments.lexers import guess_lexer, guess_lexer_for_filename, TextLexer
28 guess_lexer = pygments.lexers.guess_lexer
18 from pygments.formatters import HtmlFormatter
29 guess_lexer_for_filename = pygments.lexers.guess_lexer_for_filename
30 TextLexer = pygments.lexers.TextLexer
31 HtmlFormatter = pygments.formatters.HtmlFormatter
19
32
20 SYNTAX_CSS = ('\n<link rel="stylesheet" href="{url}highlightcss" '
33 SYNTAX_CSS = ('\n<link rel="stylesheet" href="{url}highlightcss" '
21 'type="text/css" />')
34 'type="text/css" />')
@@ -68,7 +81,7 b' def pygmentize(field, fctx, style, tmpl,'
68 coloriter = (s.encode(encoding.encoding, 'replace')
81 coloriter = (s.encode(encoding.encoding, 'replace')
69 for s in colorized.splitlines())
82 for s in colorized.splitlines())
70
83
71 tmpl.filters['colorize'] = lambda x: coloriter.next()
84 tmpl.filters['colorize'] = lambda x: next(coloriter)
72
85
73 oldl = tmpl.cache[field]
86 oldl = tmpl.cache[field]
74 newl = oldl.replace('line|escape', 'line|colorize')
87 newl = oldl.replace('line|escape', 'line|colorize')
@@ -169,30 +169,35 b' the drop to be implicit for missing comm'
169
169
170 """
170 """
171
171
172 import pickle
172 from __future__ import absolute_import
173
173 import errno
174 import errno
174 import os
175 import os
175 import sys
176 import sys
176
177
177 from mercurial import bundle2
178 from mercurial import cmdutil
179 from mercurial import discovery
180 from mercurial import error
181 from mercurial import copies
182 from mercurial import context
183 from mercurial import destutil
184 from mercurial import exchange
185 from mercurial import extensions
186 from mercurial import hg
187 from mercurial import node
188 from mercurial import repair
189 from mercurial import scmutil
190 from mercurial import util
191 from mercurial import obsolete
192 from mercurial import merge as mergemod
193 from mercurial.lock import release
194 from mercurial.i18n import _
178 from mercurial.i18n import _
179 from mercurial import (
180 bundle2,
181 cmdutil,
182 context,
183 copies,
184 destutil,
185 discovery,
186 error,
187 exchange,
188 extensions,
189 hg,
190 lock,
191 merge as mergemod,
192 node,
193 obsolete,
194 repair,
195 scmutil,
196 util,
197 )
195
198
199 pickle = util.pickle
200 release = lock.release
196 cmdtable = {}
201 cmdtable = {}
197 command = cmdutil.command(cmdtable)
202 command = cmdutil.command(cmdtable)
198
203
@@ -415,9 +420,7 b' class histeditaction(object):'
415 <hash> <rev> <summary>
420 <hash> <rev> <summary>
416 """
421 """
417 ctx = self.repo[self.node]
422 ctx = self.repo[self.node]
418 summary = ''
423 summary = _getsummary(ctx)
419 if ctx.description():
420 summary = ctx.description().splitlines()[0]
421 line = '%s %s %d %s' % (self.verb, ctx, ctx.rev(), summary)
424 line = '%s %s %d %s' % (self.verb, ctx, ctx.rev(), summary)
422 # trim to 75 columns by default so it's not stupidly wide in my editor
425 # trim to 75 columns by default so it's not stupidly wide in my editor
423 # (the 5 more are left for verb)
426 # (the 5 more are left for verb)
@@ -1264,6 +1267,14 b' def _newhistedit(ui, repo, state, revs, '
1264 'histedit')
1267 'histedit')
1265 state.backupfile = backupfile
1268 state.backupfile = backupfile
1266
1269
1270 def _getsummary(ctx):
1271 # a common pattern is to extract the summary but default to the empty
1272 # string
1273 summary = ctx.description() or ''
1274 if summary:
1275 summary = summary.splitlines()[0]
1276 return summary
1277
1267 def bootstrapcontinue(ui, state, opts):
1278 def bootstrapcontinue(ui, state, opts):
1268 repo = state.repo
1279 repo = state.repo
1269 if state.actions:
1280 if state.actions:
@@ -1304,6 +1315,40 b' def ruleeditor(repo, ui, actions, editco'
1304
1315
1305 rules are in the format [ [act, ctx], ...] like in state.rules
1316 rules are in the format [ [act, ctx], ...] like in state.rules
1306 """
1317 """
1318 if repo.ui.configbool("experimental", "histedit.autoverb"):
1319 newact = util.sortdict()
1320 for act in actions:
1321 ctx = repo[act.node]
1322 summary = _getsummary(ctx)
1323 fword = summary.split(' ', 1)[0].lower()
1324 added = False
1325
1326 # if it doesn't end with the special character '!' just skip this
1327 if fword.endswith('!'):
1328 fword = fword[:-1]
1329 if fword in primaryactions | secondaryactions | tertiaryactions:
1330 act.verb = fword
1331 # get the target summary
1332 tsum = summary[len(fword) + 1:].lstrip()
1333 # safe but slow: reverse iterate over the actions so we
1334 # don't clash on two commits having the same summary
1335 for na, l in reversed(list(newact.iteritems())):
1336 actx = repo[na.node]
1337 asum = _getsummary(actx)
1338 if asum == tsum:
1339 added = True
1340 l.append(act)
1341 break
1342
1343 if not added:
1344 newact[act] = []
1345
1346 # copy over and flatten the new list
1347 actions = []
1348 for na, l in newact.iteritems():
1349 actions.append(na)
1350 actions += l
1351
1307 rules = '\n'.join([act.torule() for act in actions])
1352 rules = '\n'.join([act.torule() for act in actions])
1308 rules += '\n\n'
1353 rules += '\n\n'
1309 rules += editcomment
1354 rules += editcomment
@@ -89,8 +89,8 b' import os'
89 import re
89 import re
90 import tempfile
90 import tempfile
91
91
92 from mercurial.i18n import _
92 from mercurial.hgweb import webcommands
93 from mercurial.hgweb import webcommands
93 from mercurial.i18n import _
94
94
95 from mercurial import (
95 from mercurial import (
96 cmdutil,
96 cmdutil,
@@ -455,7 +455,7 b' def demo(ui, repo, *args, **opts):'
455
455
456 uisetup(ui)
456 uisetup(ui)
457 reposetup(ui, repo)
457 reposetup(ui, repo)
458 ui.write('[extensions]\nkeyword =\n')
458 ui.write(('[extensions]\nkeyword =\n'))
459 demoitems('keyword', ui.configitems('keyword'))
459 demoitems('keyword', ui.configitems('keyword'))
460 demoitems('keywordset', ui.configitems('keywordset'))
460 demoitems('keywordset', ui.configitems('keywordset'))
461 demoitems('keywordmaps', kwmaps.iteritems())
461 demoitems('keywordmaps', kwmaps.iteritems())
@@ -735,7 +735,7 b' def reposetup(ui, repo):'
735 def kwfilectx_cmp(orig, self, fctx):
735 def kwfilectx_cmp(orig, self, fctx):
736 # keyword affects data size, comparing wdir and filelog size does
736 # keyword affects data size, comparing wdir and filelog size does
737 # not make sense
737 # not make sense
738 if (fctx._filerev is None and
738 if (fctx._filenode is None and
739 (self._repo._encodefilterpats or
739 (self._repo._encodefilterpats or
740 kwt.match(fctx.path()) and 'l' not in fctx.flags() or
740 kwt.match(fctx.path()) and 'l' not in fctx.flags() or
741 self.size() - 4 == fctx.size()) or
741 self.size() - 4 == fctx.size()) or
@@ -104,14 +104,20 b' largefile. To add the first largefile to'
104 explicitly do so with the --large flag passed to the :hg:`add`
104 explicitly do so with the --large flag passed to the :hg:`add`
105 command.
105 command.
106 '''
106 '''
107 from __future__ import absolute_import
107
108
108 from mercurial import hg, localrepo
109 from mercurial import (
110 hg,
111 localrepo,
112 )
109
113
110 import lfcommands
114 from . import (
111 import proto
115 lfcommands,
112 import reposetup
116 overrides,
113 import uisetup as uisetupmod
117 proto,
114 import overrides
118 reposetup,
119 uisetup as uisetupmod,
120 )
115
121
116 # Note for extension authors: ONLY specify testedwith = 'internal' for
122 # Note for extension authors: ONLY specify testedwith = 'internal' for
117 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
123 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
@@ -7,13 +7,13 b''
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 '''base class for store implementations and store-related utility code'''
9 '''base class for store implementations and store-related utility code'''
10 from __future__ import absolute_import
10
11
11 import re
12
13 from mercurial import util, node, hg, error
14 from mercurial.i18n import _
12 from mercurial.i18n import _
15
13
16 import lfutil
14 from mercurial import node, util
15
16 from . import lfutil
17
17
18 class StoreError(Exception):
18 class StoreError(Exception):
19 '''Raised when there is a problem getting files from or putting
19 '''Raised when there is a problem getting files from or putting
@@ -116,19 +116,26 b' class basestore(object):'
116 '''Verify the existence (and, optionally, contents) of every big
116 '''Verify the existence (and, optionally, contents) of every big
117 file revision referenced by every changeset in revs.
117 file revision referenced by every changeset in revs.
118 Return 0 if all is well, non-zero on any errors.'''
118 Return 0 if all is well, non-zero on any errors.'''
119 failed = False
120
119
121 self.ui.status(_('searching %d changesets for largefiles\n') %
120 self.ui.status(_('searching %d changesets for largefiles\n') %
122 len(revs))
121 len(revs))
123 verified = set() # set of (filename, filenode) tuples
122 verified = set() # set of (filename, filenode) tuples
124
123 filestocheck = [] # list of (cset, filename, expectedhash)
125 for rev in revs:
124 for rev in revs:
126 cctx = self.repo[rev]
125 cctx = self.repo[rev]
127 cset = "%d:%s" % (cctx.rev(), node.short(cctx.node()))
126 cset = "%d:%s" % (cctx.rev(), node.short(cctx.node()))
128
127
129 for standin in cctx:
128 for standin in cctx:
130 if self._verifyfile(cctx, cset, contents, standin, verified):
129 filename = lfutil.splitstandin(standin)
131 failed = True
130 if filename:
131 fctx = cctx[standin]
132 key = (filename, fctx.filenode())
133 if key not in verified:
134 verified.add(key)
135 expectedhash = fctx.data()[0:40]
136 filestocheck.append((cset, filename, expectedhash))
137
138 failed = self._verifyfiles(contents, filestocheck)
132
139
133 numrevs = len(verified)
140 numrevs = len(verified)
134 numlfiles = len(set([fname for (fname, fnode) in verified]))
141 numlfiles = len(set([fname for (fname, fnode) in verified]))
@@ -150,72 +157,10 b' class basestore(object):'
150 exist in the store).'''
157 exist in the store).'''
151 raise NotImplementedError('abstract method')
158 raise NotImplementedError('abstract method')
152
159
153 def _verifyfile(self, cctx, cset, contents, standin, verified):
160 def _verifyfiles(self, contents, filestocheck):
154 '''Perform the actual verification of a file in the store.
161 '''Perform the actual verification of files in the store.
155 'cset' is only used in warnings.
156 'contents' controls verification of content hash.
162 'contents' controls verification of content hash.
157 'standin' is the standin path of the largefile to verify.
163 'filestocheck' is list of files to check.
158 'verified' is maintained as a set of already verified files.
164 Returns _true_ if any problems are found!
159 Returns _true_ if it is a standin and any problems are found!
160 '''
165 '''
161 raise NotImplementedError('abstract method')
166 raise NotImplementedError('abstract method')
162
163 import localstore, wirestore
164
165 _storeprovider = {
166 'file': [localstore.localstore],
167 'http': [wirestore.wirestore],
168 'https': [wirestore.wirestore],
169 'ssh': [wirestore.wirestore],
170 }
171
172 _scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
173
174 # During clone this function is passed the src's ui object
175 # but it needs the dest's ui object so it can read out of
176 # the config file. Use repo.ui instead.
177 def _openstore(repo, remote=None, put=False):
178 ui = repo.ui
179
180 if not remote:
181 lfpullsource = getattr(repo, 'lfpullsource', None)
182 if lfpullsource:
183 path = ui.expandpath(lfpullsource)
184 elif put:
185 path = ui.expandpath('default-push', 'default')
186 else:
187 path = ui.expandpath('default')
188
189 # ui.expandpath() leaves 'default-push' and 'default' alone if
190 # they cannot be expanded: fallback to the empty string,
191 # meaning the current directory.
192 if path == 'default-push' or path == 'default':
193 path = ''
194 remote = repo
195 else:
196 path, _branches = hg.parseurl(path)
197 remote = hg.peer(repo, {}, path)
198
199 # The path could be a scheme so use Mercurial's normal functionality
200 # to resolve the scheme to a repository and use its path
201 path = util.safehasattr(remote, 'url') and remote.url() or remote.path
202
203 match = _scheme_re.match(path)
204 if not match: # regular filesystem path
205 scheme = 'file'
206 else:
207 scheme = match.group(1)
208
209 try:
210 storeproviders = _storeprovider[scheme]
211 except KeyError:
212 raise error.Abort(_('unsupported URL scheme %r') % scheme)
213
214 for classobj in storeproviders:
215 try:
216 return classobj(ui, repo, remote)
217 except lfutil.storeprotonotcapable:
218 pass
219
220 raise error.Abort(_('%s does not appear to be a largefile store') %
221 util.hidepassword(path))
@@ -7,20 +7,39 b''
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 '''High-level command function for lfconvert, plus the cmdtable.'''
9 '''High-level command function for lfconvert, plus the cmdtable.'''
10 from __future__ import absolute_import
10
11
11 import os, errno
12 import errno
13 import hashlib
14 import os
12 import shutil
15 import shutil
13
16
14 from mercurial import util, match as match_, hg, node, context, error, \
15 cmdutil, scmutil, commands
16 from mercurial.i18n import _
17 from mercurial.i18n import _
17 from mercurial.lock import release
18
18
19 from hgext.convert import convcmd
19 from mercurial import (
20 from hgext.convert import filemap
20 cmdutil,
21 commands,
22 context,
23 error,
24 hg,
25 lock,
26 match as matchmod,
27 node,
28 scmutil,
29 util,
30 )
21
31
22 import lfutil
32 from ..convert import (
23 import basestore
33 convcmd,
34 filemap,
35 )
36
37 from . import (
38 lfutil,
39 storefactory
40 )
41
42 release = lock.release
24
43
25 # -- Commands ----------------------------------------------------------
44 # -- Commands ----------------------------------------------------------
26
45
@@ -92,7 +111,7 b' def lfconvert(ui, src, dest, *pats, **op'
92 if not pats:
111 if not pats:
93 pats = ui.configlist(lfutil.longname, 'patterns', default=[])
112 pats = ui.configlist(lfutil.longname, 'patterns', default=[])
94 if pats:
113 if pats:
95 matcher = match_.match(rsrc.root, '', list(pats))
114 matcher = matchmod.match(rsrc.root, '', list(pats))
96 else:
115 else:
97 matcher = None
116 matcher = None
98
117
@@ -211,7 +230,7 b' def _lfconvert_addchangeset(rsrc, rdst, '
211 raise error.Abort(_('largefile %s becomes symlink') % f)
230 raise error.Abort(_('largefile %s becomes symlink') % f)
212
231
213 # largefile was modified, update standins
232 # largefile was modified, update standins
214 m = util.sha1('')
233 m = hashlib.sha1('')
215 m.update(ctx[f].data())
234 m.update(ctx[f].data())
216 hash = m.hexdigest()
235 hash = m.hexdigest()
217 if f not in lfiletohash or lfiletohash[f] != hash:
236 if f not in lfiletohash or lfiletohash[f] != hash:
@@ -337,7 +356,7 b' def uploadlfiles(ui, rsrc, rdst, files):'
337 if not files:
356 if not files:
338 return
357 return
339
358
340 store = basestore._openstore(rsrc, rdst, put=True)
359 store = storefactory.openstore(rsrc, rdst, put=True)
341
360
342 at = 0
361 at = 0
343 ui.debug("sending statlfile command for %d largefiles\n" % len(files))
362 ui.debug("sending statlfile command for %d largefiles\n" % len(files))
@@ -368,7 +387,7 b' def verifylfiles(ui, repo, all=False, co'
368 else:
387 else:
369 revs = ['.']
388 revs = ['.']
370
389
371 store = basestore._openstore(repo)
390 store = storefactory.openstore(repo)
372 return store.verify(revs, contents=contents)
391 return store.verify(revs, contents=contents)
373
392
374 def cachelfiles(ui, repo, node, filelist=None):
393 def cachelfiles(ui, repo, node, filelist=None):
@@ -394,7 +413,7 b' def cachelfiles(ui, repo, node, filelist'
394 toget.append((lfile, expectedhash))
413 toget.append((lfile, expectedhash))
395
414
396 if toget:
415 if toget:
397 store = basestore._openstore(repo)
416 store = storefactory.openstore(repo)
398 ret = store.get(toget)
417 ret = store.get(toget)
399 return ret
418 return ret
400
419
@@ -7,21 +7,30 b''
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 '''largefiles utility code: must not import other modules in this package.'''
9 '''largefiles utility code: must not import other modules in this package.'''
10 from __future__ import absolute_import
10
11
12 import copy
13 import hashlib
11 import os
14 import os
12 import platform
15 import platform
13 import stat
16 import stat
14 import copy
17
18 from mercurial.i18n import _
15
19
16 from mercurial import dirstate, httpconnection, match as match_, util, scmutil
20 from mercurial import (
17 from mercurial.i18n import _
21 dirstate,
18 from mercurial import node, error
22 error,
23 httpconnection,
24 match as matchmod,
25 node,
26 scmutil,
27 util,
28 )
19
29
20 shortname = '.hglf'
30 shortname = '.hglf'
21 shortnameslash = shortname + '/'
31 shortnameslash = shortname + '/'
22 longname = 'largefiles'
32 longname = 'largefiles'
23
33
24
25 # -- Private worker functions ------------------------------------------
34 # -- Private worker functions ------------------------------------------
26
35
27 def getminsize(ui, assumelfiles, opt, default=10):
36 def getminsize(ui, assumelfiles, opt, default=10):
@@ -152,7 +161,7 b' def openlfdirstate(ui, repo, create=True'
152
161
153 def lfdirstatestatus(lfdirstate, repo):
162 def lfdirstatestatus(lfdirstate, repo):
154 wctx = repo['.']
163 wctx = repo['.']
155 match = match_.always(repo.root, repo.getcwd())
164 match = matchmod.always(repo.root, repo.getcwd())
156 unsure, s = lfdirstate.status(match, [], False, False, False)
165 unsure, s = lfdirstate.status(match, [], False, False, False)
157 modified, clean = s.modified, s.clean
166 modified, clean = s.modified, s.clean
158 for lfile in unsure:
167 for lfile in unsure:
@@ -180,12 +189,11 b' def listlfiles(repo, rev=None, matcher=N'
180 if rev is not None or repo.dirstate[f] != '?']
189 if rev is not None or repo.dirstate[f] != '?']
181
190
182 def instore(repo, hash, forcelocal=False):
191 def instore(repo, hash, forcelocal=False):
183 '''Return true if a largefile with the given hash exists in the user
192 '''Return true if a largefile with the given hash exists in the store'''
184 cache.'''
185 return os.path.exists(storepath(repo, hash, forcelocal))
193 return os.path.exists(storepath(repo, hash, forcelocal))
186
194
187 def storepath(repo, hash, forcelocal=False):
195 def storepath(repo, hash, forcelocal=False):
188 '''Return the correct location in the repository largefiles cache for a
196 '''Return the correct location in the repository largefiles store for a
189 file with the given hash.'''
197 file with the given hash.'''
190 if not forcelocal and repo.shared():
198 if not forcelocal and repo.shared():
191 return repo.vfs.reljoin(repo.sharedpath, longname, hash)
199 return repo.vfs.reljoin(repo.sharedpath, longname, hash)
@@ -251,7 +259,6 b' def copyalltostore(repo, node):'
251 realfile = splitstandin(filename)
259 realfile = splitstandin(filename)
252 copytostore(repo, ctx.node(), realfile)
260 copytostore(repo, ctx.node(), realfile)
253
261
254
255 def copytostoreabsolute(repo, file, hash):
262 def copytostoreabsolute(repo, file, hash):
256 if inusercache(repo.ui, hash):
263 if inusercache(repo.ui, hash):
257 link(usercachepath(repo.ui, hash), storepath(repo, hash))
264 link(usercachepath(repo.ui, hash), storepath(repo, hash))
@@ -350,7 +357,7 b' def writestandin(repo, standin, hash, ex'
350 def copyandhash(instream, outfile):
357 def copyandhash(instream, outfile):
351 '''Read bytes from instream (iterable) and write them to outfile,
358 '''Read bytes from instream (iterable) and write them to outfile,
352 computing the SHA-1 hash of the data along the way. Return the hash.'''
359 computing the SHA-1 hash of the data along the way. Return the hash.'''
353 hasher = util.sha1('')
360 hasher = hashlib.sha1('')
354 for data in instream:
361 for data in instream:
355 hasher.update(data)
362 hasher.update(data)
356 outfile.write(data)
363 outfile.write(data)
@@ -362,7 +369,7 b' def hashrepofile(repo, file):'
362 def hashfile(file):
369 def hashfile(file):
363 if not os.path.exists(file):
370 if not os.path.exists(file):
364 return ''
371 return ''
365 hasher = util.sha1('')
372 hasher = hashlib.sha1('')
366 fd = open(file, 'rb')
373 fd = open(file, 'rb')
367 for data in util.filechunkiter(fd, 128 * 1024):
374 for data in util.filechunkiter(fd, 128 * 1024):
368 hasher.update(data)
375 hasher.update(data)
@@ -391,7 +398,7 b' def urljoin(first, second, *arg):'
391 def hexsha1(data):
398 def hexsha1(data):
392 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
399 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
393 object data"""
400 object data"""
394 h = util.sha1()
401 h = hashlib.sha1()
395 for chunk in util.filechunkiter(data):
402 for chunk in util.filechunkiter(data):
396 h.update(chunk)
403 h.update(chunk)
397 return h.hexdigest()
404 return h.hexdigest()
@@ -533,7 +540,7 b' def updatestandinsbymatch(repo, match):'
533 # otherwise to update all standins if the largefiles are
540 # otherwise to update all standins if the largefiles are
534 # large.
541 # large.
535 lfdirstate = openlfdirstate(ui, repo)
542 lfdirstate = openlfdirstate(ui, repo)
536 dirtymatch = match_.always(repo.root, repo.getcwd())
543 dirtymatch = matchmod.always(repo.root, repo.getcwd())
537 unsure, s = lfdirstate.status(dirtymatch, [], False, False,
544 unsure, s = lfdirstate.status(dirtymatch, [], False, False,
538 False)
545 False)
539 modifiedfiles = unsure + s.modified + s.added + s.removed
546 modifiedfiles = unsure + s.modified + s.added + s.removed
@@ -7,11 +7,14 b''
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 '''store class for local filesystem'''
9 '''store class for local filesystem'''
10 from __future__ import absolute_import
10
11
11 from mercurial.i18n import _
12 from mercurial.i18n import _
12
13
13 import lfutil
14 from . import (
14 import basestore
15 basestore,
16 lfutil,
17 )
15
18
16 class localstore(basestore.basestore):
19 class localstore(basestore.basestore):
17 '''localstore first attempts to grab files out of the store in the remote
20 '''localstore first attempts to grab files out of the store in the remote
@@ -33,7 +36,6 b' class localstore(basestore.basestore):'
33 retval[hash] = lfutil.instore(self.remote, hash)
36 retval[hash] = lfutil.instore(self.remote, hash)
34 return retval
37 return retval
35
38
36
37 def _getfile(self, tmpfile, filename, hash):
39 def _getfile(self, tmpfile, filename, hash):
38 path = lfutil.findfile(self.remote, hash)
40 path = lfutil.findfile(self.remote, hash)
39 if not path:
41 if not path:
@@ -42,29 +44,23 b' class localstore(basestore.basestore):'
42 with open(path, 'rb') as fd:
44 with open(path, 'rb') as fd:
43 return lfutil.copyandhash(fd, tmpfile)
45 return lfutil.copyandhash(fd, tmpfile)
44
46
45 def _verifyfile(self, cctx, cset, contents, standin, verified):
47 def _verifyfiles(self, contents, filestocheck):
46 filename = lfutil.splitstandin(standin)
48 failed = False
47 if not filename:
49 for cset, filename, expectedhash in filestocheck:
48 return False
50 storepath, exists = lfutil.findstorepath(self.repo, expectedhash)
49 fctx = cctx[standin]
51 if not exists:
50 key = (filename, fctx.filenode())
52 storepath, exists = lfutil.findstorepath(
51 if key in verified:
53 self.remote, expectedhash)
52 return False
54 if not exists:
53
54 expecthash = fctx.data()[0:40]
55 storepath, exists = lfutil.findstorepath(self.remote, expecthash)
56 verified.add(key)
57 if not exists:
58 self.ui.warn(
59 _('changeset %s: %s references missing %s\n')
60 % (cset, filename, storepath))
61 return True # failed
62
63 if contents:
64 actualhash = lfutil.hashfile(storepath)
65 if actualhash != expecthash:
66 self.ui.warn(
55 self.ui.warn(
67 _('changeset %s: %s references corrupted %s\n')
56 _('changeset %s: %s references missing %s\n')
68 % (cset, filename, storepath))
57 % (cset, filename, storepath))
69 return True # failed
58 failed = True
70 return False
59 elif contents:
60 actualhash = lfutil.hashfile(storepath)
61 if actualhash != expectedhash:
62 self.ui.warn(
63 _('changeset %s: %s references corrupted %s\n')
64 % (cset, filename, storepath))
65 failed = True
66 return failed
@@ -7,17 +7,31 b''
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 '''Overridden Mercurial commands and functions for the largefiles extension'''
9 '''Overridden Mercurial commands and functions for the largefiles extension'''
10 from __future__ import absolute_import
10
11
11 import os
12 import copy
12 import copy
13 import os
13
14
14 from mercurial import hg, util, cmdutil, scmutil, match as match_, \
15 archival, pathutil, registrar, revset, error
16 from mercurial.i18n import _
15 from mercurial.i18n import _
17
16
18 import lfutil
17 from mercurial import (
19 import lfcommands
18 archival,
20 import basestore
19 cmdutil,
20 error,
21 hg,
22 match as matchmod,
23 pathutil,
24 registrar,
25 revset,
26 scmutil,
27 util,
28 )
29
30 from . import (
31 lfcommands,
32 lfutil,
33 storefactory,
34 )
21
35
22 # -- Utility functions: commonly/repeatedly needed functionality ---------------
36 # -- Utility functions: commonly/repeatedly needed functionality ---------------
23
37
@@ -99,13 +113,13 b' def addlargefiles(ui, repo, isaddremove,'
99 if lfutil.islfilesrepo(repo):
113 if lfutil.islfilesrepo(repo):
100 lfpats = ui.configlist(lfutil.longname, 'patterns', default=[])
114 lfpats = ui.configlist(lfutil.longname, 'patterns', default=[])
101 if lfpats:
115 if lfpats:
102 lfmatcher = match_.match(repo.root, '', list(lfpats))
116 lfmatcher = matchmod.match(repo.root, '', list(lfpats))
103
117
104 lfnames = []
118 lfnames = []
105 m = matcher
119 m = matcher
106
120
107 wctx = repo[None]
121 wctx = repo[None]
108 for f in repo.walk(match_.badmatch(m, lambda x, y: None)):
122 for f in repo.walk(matchmod.badmatch(m, lambda x, y: None)):
109 exact = m.exact(f)
123 exact = m.exact(f)
110 lfile = lfutil.standin(f) in wctx
124 lfile = lfutil.standin(f) in wctx
111 nfile = f in wctx
125 nfile = f in wctx
@@ -307,7 +321,7 b' def overridelog(orig, ui, repo, *pats, *'
307 if pat.startswith('set:'):
321 if pat.startswith('set:'):
308 return pat
322 return pat
309
323
310 kindpat = match_._patsplit(pat, None)
324 kindpat = matchmod._patsplit(pat, None)
311
325
312 if kindpat[0] is not None:
326 if kindpat[0] is not None:
313 return kindpat[0] + ':' + tostandin(kindpat[1])
327 return kindpat[0] + ':' + tostandin(kindpat[1])
@@ -532,7 +546,6 b' def mergerecordupdates(orig, repo, actio'
532
546
533 return orig(repo, actions, branchmerge)
547 return orig(repo, actions, branchmerge)
534
548
535
536 # Override filemerge to prompt the user about how they wish to merge
549 # Override filemerge to prompt the user about how they wish to merge
537 # largefiles. This will handle identical edits without prompting the user.
550 # largefiles. This will handle identical edits without prompting the user.
538 def overridefilemerge(origfn, premerge, repo, mynode, orig, fcd, fco, fca,
551 def overridefilemerge(origfn, premerge, repo, mynode, orig, fcd, fco, fca,
@@ -626,7 +639,7 b' def overridecopy(orig, ui, repo, pats, o'
626 # The patterns were previously mangled to add the standin
639 # The patterns were previously mangled to add the standin
627 # directory; we need to remove that now
640 # directory; we need to remove that now
628 for pat in pats:
641 for pat in pats:
629 if match_.patkind(pat) is None and lfutil.shortname in pat:
642 if matchmod.patkind(pat) is None and lfutil.shortname in pat:
630 newpats.append(pat.replace(lfutil.shortname, ''))
643 newpats.append(pat.replace(lfutil.shortname, ''))
631 else:
644 else:
632 newpats.append(pat)
645 newpats.append(pat)
@@ -644,7 +657,7 b' def overridecopy(orig, ui, repo, pats, o'
644 oldmatch = installmatchfn(overridematch)
657 oldmatch = installmatchfn(overridematch)
645 listpats = []
658 listpats = []
646 for pat in pats:
659 for pat in pats:
647 if match_.patkind(pat) is not None:
660 if matchmod.patkind(pat) is not None:
648 listpats.append(pat)
661 listpats.append(pat)
649 else:
662 else:
650 listpats.append(makestandin(pat))
663 listpats.append(makestandin(pat))
@@ -977,7 +990,7 b' def overridearchive(orig, repo, dest, no'
977 if subrepos:
990 if subrepos:
978 for subpath in sorted(ctx.substate):
991 for subpath in sorted(ctx.substate):
979 sub = ctx.workingsub(subpath)
992 sub = ctx.workingsub(subpath)
980 submatch = match_.subdirmatcher(subpath, matchfn)
993 submatch = matchmod.subdirmatcher(subpath, matchfn)
981 sub._repo.lfstatus = True
994 sub._repo.lfstatus = True
982 sub.archive(archiver, prefix, submatch)
995 sub.archive(archiver, prefix, submatch)
983
996
@@ -1025,7 +1038,7 b' def hgsubrepoarchive(orig, repo, archive'
1025
1038
1026 for subpath in sorted(ctx.substate):
1039 for subpath in sorted(ctx.substate):
1027 sub = ctx.workingsub(subpath)
1040 sub = ctx.workingsub(subpath)
1028 submatch = match_.subdirmatcher(subpath, match)
1041 submatch = matchmod.subdirmatcher(subpath, match)
1029 sub._repo.lfstatus = True
1042 sub._repo.lfstatus = True
1030 sub.archive(archiver, prefix + repo._path + '/', submatch)
1043 sub.archive(archiver, prefix + repo._path + '/', submatch)
1031
1044
@@ -1109,7 +1122,7 b' def _getoutgoings(repo, other, missing, '
1109 lfhashes.add(lfhash)
1122 lfhashes.add(lfhash)
1110 lfutil.getlfilestoupload(repo, missing, dedup)
1123 lfutil.getlfilestoupload(repo, missing, dedup)
1111 if lfhashes:
1124 if lfhashes:
1112 lfexists = basestore._openstore(repo, other).exists(lfhashes)
1125 lfexists = storefactory.openstore(repo, other).exists(lfhashes)
1113 for fn, lfhash in knowns:
1126 for fn, lfhash in knowns:
1114 if not lfexists[lfhash]: # lfhash doesn't exist on "other"
1127 if not lfexists[lfhash]: # lfhash doesn't exist on "other"
1115 addfunc(fn, lfhash)
1128 addfunc(fn, lfhash)
@@ -1190,7 +1203,7 b' def scmutiladdremove(orig, repo, matcher'
1190 return orig(repo, matcher, prefix, opts, dry_run, similarity)
1203 return orig(repo, matcher, prefix, opts, dry_run, similarity)
1191 # Get the list of missing largefiles so we can remove them
1204 # Get the list of missing largefiles so we can remove them
1192 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1205 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1193 unsure, s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [],
1206 unsure, s = lfdirstate.status(matchmod.always(repo.root, repo.getcwd()), [],
1194 False, False, False)
1207 False, False, False)
1195
1208
1196 # Call into the normal remove code, but the removing of the standin, we want
1209 # Call into the normal remove code, but the removing of the standin, we want
@@ -1338,7 +1351,7 b' def overridecat(orig, ui, repo, file1, *'
1338 else:
1351 else:
1339 hash = lfutil.readstandin(repo, lf, ctx.rev())
1352 hash = lfutil.readstandin(repo, lf, ctx.rev())
1340 if not lfutil.inusercache(repo.ui, hash):
1353 if not lfutil.inusercache(repo.ui, hash):
1341 store = basestore._openstore(repo)
1354 store = storefactory.openstore(repo)
1342 success, missing = store.get([(lf, hash)])
1355 success, missing = store.get([(lf, hash)])
1343 if len(success) != 1:
1356 if len(success) != 1:
1344 raise error.Abort(
1357 raise error.Abort(
@@ -1375,7 +1388,7 b' def mergeupdate(orig, repo, node, branch'
1375 # (*1) deprecated, but used internally (e.g: "rebase --collapse")
1388 # (*1) deprecated, but used internally (e.g: "rebase --collapse")
1376
1389
1377 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1390 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1378 unsure, s = lfdirstate.status(match_.always(repo.root,
1391 unsure, s = lfdirstate.status(matchmod.always(repo.root,
1379 repo.getcwd()),
1392 repo.getcwd()),
1380 [], False, False, False)
1393 [], False, False, False)
1381 pctx = repo['.']
1394 pctx = repo['.']
@@ -2,18 +2,27 b''
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5 from __future__ import absolute_import
5
6
6 import os
7 import os
7 import re
8 import re
8
9
9 from mercurial import error, httppeer, util, wireproto
10 from mercurial.i18n import _
10 from mercurial.i18n import _
11
11
12 from mercurial import (
13 error,
14 httppeer,
15 util,
16 wireproto,
17 )
18
19 from . import (
20 lfutil,
21 )
22
12 urlerr = util.urlerr
23 urlerr = util.urlerr
13 urlreq = util.urlreq
24 urlreq = util.urlreq
14
25
15 import lfutil
16
17 LARGEFILES_REQUIRED_MSG = ('\nThis repository uses the largefiles extension.'
26 LARGEFILES_REQUIRED_MSG = ('\nThis repository uses the largefiles extension.'
18 '\n\nPlease enable it in your Mercurial config '
27 '\n\nPlease enable it in your Mercurial config '
19 'file.\n')
28 'file.\n')
@@ -5,20 +5,30 b''
5 # GNU General Public License version 2 or any later version.
5 # GNU General Public License version 2 or any later version.
6
6
7 '''remote largefile store; the base class for wirestore'''
7 '''remote largefile store; the base class for wirestore'''
8 from __future__ import absolute_import
8
9
9 from mercurial import util, wireproto, error
10 from mercurial.i18n import _
10 from mercurial.i18n import _
11
11
12 from mercurial import (
13 error,
14 util,
15 wireproto,
16 )
17
18 from . import (
19 basestore,
20 lfutil,
21 localstore,
22 )
23
12 urlerr = util.urlerr
24 urlerr = util.urlerr
13 urlreq = util.urlreq
25 urlreq = util.urlreq
14
26
15 import lfutil
16 import basestore
17
18 class remotestore(basestore.basestore):
27 class remotestore(basestore.basestore):
19 '''a largefile store accessed over a network'''
28 '''a largefile store accessed over a network'''
20 def __init__(self, ui, repo, url):
29 def __init__(self, ui, repo, url):
21 super(remotestore, self).__init__(ui, repo, url)
30 super(remotestore, self).__init__(ui, repo, url)
31 self._lstore = localstore.localstore(self.ui, self.repo, self.repo)
22
32
23 def put(self, source, hash):
33 def put(self, source, hash):
24 if self.sendfile(source, hash):
34 if self.sendfile(source, hash):
@@ -65,34 +75,43 b' class remotestore(basestore.basestore):'
65
75
66 return lfutil.copyandhash(chunks, tmpfile)
76 return lfutil.copyandhash(chunks, tmpfile)
67
77
68 def _verifyfile(self, cctx, cset, contents, standin, verified):
78 def _hashesavailablelocally(self, hashes):
69 filename = lfutil.splitstandin(standin)
79 existslocallymap = self._lstore.exists(hashes)
70 if not filename:
80 localhashes = [hash for hash in hashes if existslocallymap[hash]]
71 return False
81 return localhashes
72 fctx = cctx[standin]
73 key = (filename, fctx.filenode())
74 if key in verified:
75 return False
76
82
77 verified.add(key)
83 def _verifyfiles(self, contents, filestocheck):
84 failed = False
85 expectedhashes = [expectedhash
86 for cset, filename, expectedhash in filestocheck]
87 localhashes = self._hashesavailablelocally(expectedhashes)
88 stats = self._stat([expectedhash for expectedhash in expectedhashes
89 if expectedhash not in localhashes])
78
90
79 expecthash = fctx.data()[0:40]
91 for cset, filename, expectedhash in filestocheck:
80 stat = self._stat([expecthash])[expecthash]
92 if expectedhash in localhashes:
81 if not stat:
93 filetocheck = (cset, filename, expectedhash)
82 return False
94 verifyresult = self._lstore._verifyfiles(contents,
83 elif stat == 1:
95 [filetocheck])
84 self.ui.warn(
96 if verifyresult:
85 _('changeset %s: %s: contents differ\n')
97 failed = True
86 % (cset, filename))
98 else:
87 return True # failed
99 stat = stats[expectedhash]
88 elif stat == 2:
100 if stat:
89 self.ui.warn(
101 if stat == 1:
90 _('changeset %s: %s missing\n')
102 self.ui.warn(
91 % (cset, filename))
103 _('changeset %s: %s: contents differ\n')
92 return True # failed
104 % (cset, filename))
93 else:
105 failed = True
94 raise RuntimeError('verify failed: unexpected response from '
106 elif stat == 2:
95 'statlfile (%r)' % stat)
107 self.ui.warn(
108 _('changeset %s: %s missing\n')
109 % (cset, filename))
110 failed = True
111 else:
112 raise RuntimeError('verify failed: unexpected response '
113 'from statlfile (%r)' % stat)
114 return failed
96
115
97 def batch(self):
116 def batch(self):
98 '''Support for remote batching.'''
117 '''Support for remote batching.'''
@@ -7,14 +7,23 b''
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 '''setup for largefiles repositories: reposetup'''
9 '''setup for largefiles repositories: reposetup'''
10 from __future__ import absolute_import
11
10 import copy
12 import copy
11
13
12 from mercurial import error, match as match_, error
13 from mercurial.i18n import _
14 from mercurial.i18n import _
14 from mercurial import scmutil, localrepo
15
15
16 import lfcommands
16 from mercurial import (
17 import lfutil
17 error,
18 localrepo,
19 match as matchmod,
20 scmutil,
21 )
22
23 from . import (
24 lfcommands,
25 lfutil,
26 )
18
27
19 def reposetup(ui, repo):
28 def reposetup(ui, repo):
20 # wire repositories should be given new wireproto functions
29 # wire repositories should be given new wireproto functions
@@ -94,7 +103,7 b' def reposetup(ui, repo):'
94 parentworking = working and ctx1 == self['.']
103 parentworking = working and ctx1 == self['.']
95
104
96 if match is None:
105 if match is None:
97 match = match_.always(self.root, self.getcwd())
106 match = matchmod.always(self.root, self.getcwd())
98
107
99 wlock = None
108 wlock = None
100 try:
109 try:
@@ -1,180 +1,28 b''
1 # Copyright 2009-2010 Gregory P. Ward
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 # Copyright 2010-2011 Fog Creek Software
4 # Copyright 2010-2011 Unity Technologies
5 #
6 # This software may be used and distributed according to the terms of the
1 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
2 # GNU General Public License version 2 or any later version.
8
3
9 '''base class for store implementations and store-related utility code'''
4 from __future__ import absolute_import
10
5
11 import re
6 import re
12
7
13 from mercurial import util, node, hg, error
14 from mercurial.i18n import _
8 from mercurial.i18n import _
15
9
16 import lfutil
10 from mercurial import (
17
11 error,
18 class StoreError(Exception):
12 hg,
19 '''Raised when there is a problem getting files from or putting
13 util,
20 files to a central store.'''
14 )
21 def __init__(self, filename, hash, url, detail):
22 self.filename = filename
23 self.hash = hash
24 self.url = url
25 self.detail = detail
26
27 def longmessage(self):
28 return (_("error getting id %s from url %s for file %s: %s\n") %
29 (self.hash, util.hidepassword(self.url), self.filename,
30 self.detail))
31
32 def __str__(self):
33 return "%s: %s" % (util.hidepassword(self.url), self.detail)
34
35 class basestore(object):
36 def __init__(self, ui, repo, url):
37 self.ui = ui
38 self.repo = repo
39 self.url = url
40
41 def put(self, source, hash):
42 '''Put source file into the store so it can be retrieved by hash.'''
43 raise NotImplementedError('abstract method')
44
45 def exists(self, hashes):
46 '''Check to see if the store contains the given hashes. Given an
47 iterable of hashes it returns a mapping from hash to bool.'''
48 raise NotImplementedError('abstract method')
49
50 def get(self, files):
51 '''Get the specified largefiles from the store and write to local
52 files under repo.root. files is a list of (filename, hash)
53 tuples. Return (success, missing), lists of files successfully
54 downloaded and those not found in the store. success is a list
55 of (filename, hash) tuples; missing is a list of filenames that
56 we could not get. (The detailed error message will already have
57 been presented to the user, so missing is just supplied as a
58 summary.)'''
59 success = []
60 missing = []
61 ui = self.ui
62
63 at = 0
64 available = self.exists(set(hash for (_filename, hash) in files))
65 for filename, hash in files:
66 ui.progress(_('getting largefiles'), at, unit=_('files'),
67 total=len(files))
68 at += 1
69 ui.note(_('getting %s:%s\n') % (filename, hash))
70
71 if not available.get(hash):
72 ui.warn(_('%s: largefile %s not available from %s\n')
73 % (filename, hash, util.hidepassword(self.url)))
74 missing.append(filename)
75 continue
76
77 if self._gethash(filename, hash):
78 success.append((filename, hash))
79 else:
80 missing.append(filename)
81
82 ui.progress(_('getting largefiles'), None)
83 return (success, missing)
84
85 def _gethash(self, filename, hash):
86 """Get file with the provided hash and store it in the local repo's
87 store and in the usercache.
88 filename is for informational messages only.
89 """
90 util.makedirs(lfutil.storepath(self.repo, ''))
91 storefilename = lfutil.storepath(self.repo, hash)
92
15
93 tmpname = storefilename + '.tmp'
16 from . import (
94 tmpfile = util.atomictempfile(tmpname,
17 lfutil,
95 createmode=self.repo.store.createmode)
18 localstore,
96
19 wirestore,
97 try:
20 )
98 gothash = self._getfile(tmpfile, filename, hash)
99 except StoreError as err:
100 self.ui.warn(err.longmessage())
101 gothash = ""
102 tmpfile.close()
103
104 if gothash != hash:
105 if gothash != "":
106 self.ui.warn(_('%s: data corruption (expected %s, got %s)\n')
107 % (filename, hash, gothash))
108 util.unlink(tmpname)
109 return False
110
111 util.rename(tmpname, storefilename)
112 lfutil.linktousercache(self.repo, hash)
113 return True
114
115 def verify(self, revs, contents=False):
116 '''Verify the existence (and, optionally, contents) of every big
117 file revision referenced by every changeset in revs.
118 Return 0 if all is well, non-zero on any errors.'''
119 failed = False
120
121 self.ui.status(_('searching %d changesets for largefiles\n') %
122 len(revs))
123 verified = set() # set of (filename, filenode) tuples
124
125 for rev in revs:
126 cctx = self.repo[rev]
127 cset = "%d:%s" % (cctx.rev(), node.short(cctx.node()))
128
129 for standin in cctx:
130 if self._verifyfile(cctx, cset, contents, standin, verified):
131 failed = True
132
133 numrevs = len(verified)
134 numlfiles = len(set([fname for (fname, fnode) in verified]))
135 if contents:
136 self.ui.status(
137 _('verified contents of %d revisions of %d largefiles\n')
138 % (numrevs, numlfiles))
139 else:
140 self.ui.status(
141 _('verified existence of %d revisions of %d largefiles\n')
142 % (numrevs, numlfiles))
143 return int(failed)
144
145 def _getfile(self, tmpfile, filename, hash):
146 '''Fetch one revision of one file from the store and write it
147 to tmpfile. Compute the hash of the file on-the-fly as it
148 downloads and return the hash. Close tmpfile. Raise
149 StoreError if unable to download the file (e.g. it does not
150 exist in the store).'''
151 raise NotImplementedError('abstract method')
152
153 def _verifyfile(self, cctx, cset, contents, standin, verified):
154 '''Perform the actual verification of a file in the store.
155 'cset' is only used in warnings.
156 'contents' controls verification of content hash.
157 'standin' is the standin path of the largefile to verify.
158 'verified' is maintained as a set of already verified files.
159 Returns _true_ if it is a standin and any problems are found!
160 '''
161 raise NotImplementedError('abstract method')
162
163 import localstore, wirestore
164
165 _storeprovider = {
166 'file': [localstore.localstore],
167 'http': [wirestore.wirestore],
168 'https': [wirestore.wirestore],
169 'ssh': [wirestore.wirestore],
170 }
171
172 _scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
173
21
174 # During clone this function is passed the src's ui object
22 # During clone this function is passed the src's ui object
175 # but it needs the dest's ui object so it can read out of
23 # but it needs the dest's ui object so it can read out of
176 # the config file. Use repo.ui instead.
24 # the config file. Use repo.ui instead.
177 def _openstore(repo, remote=None, put=False):
25 def openstore(repo, remote=None, put=False):
178 ui = repo.ui
26 ui = repo.ui
179
27
180 if not remote:
28 if not remote:
@@ -219,3 +67,12 b' def _openstore(repo, remote=None, put=Fa'
219
67
220 raise error.Abort(_('%s does not appear to be a largefile store') %
68 raise error.Abort(_('%s does not appear to be a largefile store') %
221 util.hidepassword(path))
69 util.hidepassword(path))
70
71 _storeprovider = {
72 'file': [localstore.localstore],
73 'http': [wirestore.wirestore],
74 'https': [wirestore.wirestore],
75 'ssh': [wirestore.wirestore],
76 }
77
78 _scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
@@ -7,14 +7,36 b''
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 '''setup for largefiles extension: uisetup'''
9 '''setup for largefiles extension: uisetup'''
10 from __future__ import absolute_import
10
11
11 from mercurial import archival, cmdutil, commands, extensions, filemerge, hg, \
12 httppeer, merge, scmutil, sshpeer, wireproto, subrepo, copies, exchange
13 from mercurial.i18n import _
12 from mercurial.i18n import _
14 from mercurial.hgweb import hgweb_mod, webcommands
13
14 from mercurial.hgweb import (
15 hgweb_mod,
16 webcommands,
17 )
15
18
16 import overrides
19 from mercurial import (
17 import proto
20 archival,
21 cmdutil,
22 commands,
23 copies,
24 exchange,
25 extensions,
26 filemerge,
27 hg,
28 httppeer,
29 merge,
30 scmutil,
31 sshpeer,
32 subrepo,
33 wireproto,
34 )
35
36 from . import (
37 overrides,
38 proto,
39 )
18
40
19 def uisetup(ui):
41 def uisetup(ui):
20 # Disable auto-status for some commands which assume that all
42 # Disable auto-status for some commands which assume that all
@@ -4,9 +4,12 b''
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 '''largefile store working over Mercurial's wire protocol'''
6 '''largefile store working over Mercurial's wire protocol'''
7 from __future__ import absolute_import
7
8
8 import lfutil
9 from . import (
9 import remotestore
10 lfutil,
11 remotestore,
12 )
10
13
11 class wirestore(remotestore.remotestore):
14 class wirestore(remotestore.remotestore):
12 def __init__(self, ui, repo, remote):
15 def __init__(self, ui, repo, remote):
@@ -92,7 +92,7 b' def uisetup(ui):'
92 Arguments are passed on as environment variables.
92 Arguments are passed on as environment variables.
93
93
94 """
94 """
95 script = ui.config('logtoprocess', event)
95 script = self.config('logtoprocess', event)
96 if script:
96 if script:
97 if msg:
97 if msg:
98 # try to format the log message given the remaining
98 # try to format the log message given the remaining
@@ -62,19 +62,39 b' This extension used to provide a strip c'
62 in the strip extension.
62 in the strip extension.
63 '''
63 '''
64
64
65 from __future__ import absolute_import
66
67 import errno
68 import os
69 import re
70 import shutil
65 from mercurial.i18n import _
71 from mercurial.i18n import _
66 from mercurial.node import bin, hex, short, nullid, nullrev
72 from mercurial.node import (
67 from mercurial.lock import release
73 bin,
68 from mercurial import commands, cmdutil, hg, scmutil, util, revset
74 hex,
69 from mercurial import dispatch
75 nullid,
70 from mercurial import extensions, error, phases
76 nullrev,
71 from mercurial import patch as patchmod
77 short,
72 from mercurial import lock as lockmod
78 )
73 from mercurial import localrepo
79 from mercurial import (
74 from mercurial import registrar
80 cmdutil,
75 from mercurial import subrepo
81 commands,
76 import os, re, errno, shutil
82 dispatch,
77
83 error,
84 extensions,
85 hg,
86 localrepo,
87 lock as lockmod,
88 patch as patchmod,
89 phases,
90 registrar,
91 revset,
92 scmutil,
93 subrepo,
94 util,
95 )
96
97 release = lockmod.release
78 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
98 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
79
99
80 cmdtable = {}
100 cmdtable = {}
@@ -139,6 +139,7 b' import fnmatch'
139 import socket
139 import socket
140 import time
140 import time
141
141
142 from mercurial.i18n import _
142 from mercurial import (
143 from mercurial import (
143 cmdutil,
144 cmdutil,
144 error,
145 error,
@@ -146,7 +147,6 b' from mercurial import ('
146 patch,
147 patch,
147 util,
148 util,
148 )
149 )
149 from mercurial.i18n import _
150
150
151 # Note for extension authors: ONLY specify testedwith = 'internal' for
151 # Note for extension authors: ONLY specify testedwith = 'internal' for
152 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
152 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
@@ -363,7 +363,7 b' class notifier(object):'
363 s = patch.diffstat(difflines)
363 s = patch.diffstat(difflines)
364 # s may be nil, don't include the header if it is
364 # s may be nil, don't include the header if it is
365 if s:
365 if s:
366 self.ui.write('\ndiffstat:\n\n%s' % s)
366 self.ui.write(_('\ndiffstat:\n\n%s') % s)
367
367
368 if maxdiff == 0:
368 if maxdiff == 0:
369 return
369 return
@@ -66,6 +66,7 b' import signal'
66 import subprocess
66 import subprocess
67 import sys
67 import sys
68
68
69 from mercurial.i18n import _
69 from mercurial import (
70 from mercurial import (
70 cmdutil,
71 cmdutil,
71 commands,
72 commands,
@@ -73,7 +74,6 b' from mercurial import ('
73 extensions,
74 extensions,
74 util,
75 util,
75 )
76 )
76 from mercurial.i18n import _
77
77
78 # Note for extension authors: ONLY specify testedwith = 'internal' for
78 # Note for extension authors: ONLY specify testedwith = 'internal' for
79 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
79 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
@@ -71,6 +71,7 b' import os'
71 import socket
71 import socket
72 import tempfile
72 import tempfile
73
73
74 from mercurial.i18n import _
74 from mercurial import (
75 from mercurial import (
75 cmdutil,
76 cmdutil,
76 commands,
77 commands,
@@ -83,7 +84,6 b' from mercurial import ('
83 util,
84 util,
84 )
85 )
85 stringio = util.stringio
86 stringio = util.stringio
86 from mercurial.i18n import _
87
87
88 cmdtable = {}
88 cmdtable = {}
89 command = cmdutil.command(cmdtable)
89 command = cmdutil.command(cmdtable)
@@ -708,13 +708,7 b' def email(ui, repo, *revs, **opts):'
708 fp.close()
708 fp.close()
709 else:
709 else:
710 if not sendmail:
710 if not sendmail:
711 verifycert = ui.config('smtp', 'verifycert', 'strict')
711 sendmail = mail.connect(ui, mbox=mbox)
712 if opts.get('insecure'):
713 ui.setconfig('smtp', 'verifycert', 'loose', 'patchbomb')
714 try:
715 sendmail = mail.connect(ui, mbox=mbox)
716 finally:
717 ui.setconfig('smtp', 'verifycert', verifycert, 'patchbomb')
718 ui.status(_('sending '), subj, ' ...\n')
712 ui.status(_('sending '), subj, ' ...\n')
719 ui.progress(_('sending'), i, item=subj, total=len(msgs),
713 ui.progress(_('sending'), i, item=subj, total=len(msgs),
720 unit=_('emails'))
714 unit=_('emails'))
@@ -27,6 +27,7 b' from __future__ import absolute_import'
27
27
28 import os
28 import os
29
29
30 from mercurial.i18n import _
30 from mercurial import (
31 from mercurial import (
31 cmdutil,
32 cmdutil,
32 commands,
33 commands,
@@ -34,7 +35,6 b' from mercurial import ('
34 scmutil,
35 scmutil,
35 util,
36 util,
36 )
37 )
37 from mercurial.i18n import _
38
38
39 cmdtable = {}
39 cmdtable = {}
40 command = cmdutil.command(cmdtable)
40 command = cmdutil.command(cmdtable)
@@ -84,13 +84,13 b' def purge(ui, repo, *dirs, **opts):'
84 list of files that this program would delete, use the --print
84 list of files that this program would delete, use the --print
85 option.
85 option.
86 '''
86 '''
87 act = not opts['print']
87 act = not opts.get('print')
88 eol = '\n'
88 eol = '\n'
89 if opts['print0']:
89 if opts.get('print0'):
90 eol = '\0'
90 eol = '\0'
91 act = False # --print0 implies --print
91 act = False # --print0 implies --print
92 removefiles = opts['files']
92 removefiles = opts.get('files')
93 removedirs = opts['dirs']
93 removedirs = opts.get('dirs')
94 if not removefiles and not removedirs:
94 if not removefiles and not removedirs:
95 removefiles = True
95 removefiles = True
96 removedirs = True
96 removedirs = True
@@ -101,7 +101,7 b' def purge(ui, repo, *dirs, **opts):'
101 remove_func(repo.wjoin(name))
101 remove_func(repo.wjoin(name))
102 except OSError:
102 except OSError:
103 m = _('%s cannot be removed') % name
103 m = _('%s cannot be removed') % name
104 if opts['abort_on_err']:
104 if opts.get('abort_on_err'):
105 raise error.Abort(m)
105 raise error.Abort(m)
106 ui.warn(_('warning: %s\n') % m)
106 ui.warn(_('warning: %s\n') % m)
107 else:
107 else:
@@ -111,7 +111,7 b' def purge(ui, repo, *dirs, **opts):'
111 if removedirs:
111 if removedirs:
112 directories = []
112 directories = []
113 match.explicitdir = match.traversedir = directories.append
113 match.explicitdir = match.traversedir = directories.append
114 status = repo.status(match=match, ignored=opts['all'], unknown=True)
114 status = repo.status(match=match, ignored=opts.get('all'), unknown=True)
115
115
116 if removefiles:
116 if removefiles:
117 for f in sorted(status.unknown + status.ignored):
117 for f in sorted(status.unknown + status.ignored):
This diff has been collapsed as it changes many lines, (810 lines changed) Show them Hide them
@@ -14,14 +14,42 b' For more information:'
14 https://mercurial-scm.org/wiki/RebaseExtension
14 https://mercurial-scm.org/wiki/RebaseExtension
15 '''
15 '''
16
16
17 from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks
17 from __future__ import absolute_import
18 from mercurial import extensions, patch, scmutil, phases, obsolete, error
18
19 from mercurial import copies, destutil, repoview, registrar, revset
19 import errno
20 from mercurial.commands import templateopts
20 import os
21 from mercurial.node import nullrev, nullid, hex, short
21
22 from mercurial.lock import release
23 from mercurial.i18n import _
22 from mercurial.i18n import _
24 import os, errno
23 from mercurial.node import (
24 hex,
25 nullid,
26 nullrev,
27 short,
28 )
29 from mercurial import (
30 bookmarks,
31 cmdutil,
32 commands,
33 copies,
34 destutil,
35 error,
36 extensions,
37 hg,
38 lock,
39 merge,
40 obsolete,
41 patch,
42 phases,
43 registrar,
44 repair,
45 repoview,
46 revset,
47 scmutil,
48 util,
49 )
50
51 release = lock.release
52 templateopts = commands.templateopts
25
53
26 # The following constants are used throughout the rebase module. The ordering of
54 # The following constants are used throughout the rebase module. The ordering of
27 # their values must be maintained.
55 # their values must be maintained.
@@ -91,6 +119,394 b' def _revsetdestrebase(repo, subset, x):'
91 sourceset = revset.getset(repo, revset.fullreposet(repo), x)
119 sourceset = revset.getset(repo, revset.fullreposet(repo), x)
92 return subset & revset.baseset([_destrebase(repo, sourceset)])
120 return subset & revset.baseset([_destrebase(repo, sourceset)])
93
121
122 class rebaseruntime(object):
123 """This class is a container for rebase runtime state"""
124 def __init__(self, repo, ui, opts=None):
125 if opts is None:
126 opts = {}
127
128 self.repo = repo
129 self.ui = ui
130 self.opts = opts
131 self.originalwd = None
132 self.external = nullrev
133 # Mapping between the old revision id and either what is the new rebased
134 # revision or what needs to be done with the old revision. The state
135 # dict will be what contains most of the rebase progress state.
136 self.state = {}
137 self.activebookmark = None
138 self.currentbookmarks = None
139 self.target = None
140 self.skipped = set()
141 self.targetancestors = set()
142
143 self.collapsef = opts.get('collapse', False)
144 self.collapsemsg = cmdutil.logmessage(ui, opts)
145 self.date = opts.get('date', None)
146
147 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
148 self.extrafns = [_savegraft]
149 if e:
150 self.extrafns = [e]
151
152 self.keepf = opts.get('keep', False)
153 self.keepbranchesf = opts.get('keepbranches', False)
154 # keepopen is not meant for use on the command line, but by
155 # other extensions
156 self.keepopen = opts.get('keepopen', False)
157 self.obsoletenotrebased = {}
158
159 def restorestatus(self):
160 """Restore a previously stored status"""
161 repo = self.repo
162 keepbranches = None
163 target = None
164 collapse = False
165 external = nullrev
166 activebookmark = None
167 state = {}
168
169 try:
170 f = repo.vfs("rebasestate")
171 for i, l in enumerate(f.read().splitlines()):
172 if i == 0:
173 originalwd = repo[l].rev()
174 elif i == 1:
175 target = repo[l].rev()
176 elif i == 2:
177 external = repo[l].rev()
178 elif i == 3:
179 collapse = bool(int(l))
180 elif i == 4:
181 keep = bool(int(l))
182 elif i == 5:
183 keepbranches = bool(int(l))
184 elif i == 6 and not (len(l) == 81 and ':' in l):
185 # line 6 is a recent addition, so for backwards
186 # compatibility check that the line doesn't look like the
187 # oldrev:newrev lines
188 activebookmark = l
189 else:
190 oldrev, newrev = l.split(':')
191 if newrev in (str(nullmerge), str(revignored),
192 str(revprecursor), str(revpruned)):
193 state[repo[oldrev].rev()] = int(newrev)
194 elif newrev == nullid:
195 state[repo[oldrev].rev()] = revtodo
196 # Legacy compat special case
197 else:
198 state[repo[oldrev].rev()] = repo[newrev].rev()
199
200 except IOError as err:
201 if err.errno != errno.ENOENT:
202 raise
203 cmdutil.wrongtooltocontinue(repo, _('rebase'))
204
205 if keepbranches is None:
206 raise error.Abort(_('.hg/rebasestate is incomplete'))
207
208 skipped = set()
209 # recompute the set of skipped revs
210 if not collapse:
211 seen = set([target])
212 for old, new in sorted(state.items()):
213 if new != revtodo and new in seen:
214 skipped.add(old)
215 seen.add(new)
216 repo.ui.debug('computed skipped revs: %s\n' %
217 (' '.join(str(r) for r in sorted(skipped)) or None))
218 repo.ui.debug('rebase status resumed\n')
219 _setrebasesetvisibility(repo, state.keys())
220
221 self.originalwd = originalwd
222 self.target = target
223 self.state = state
224 self.skipped = skipped
225 self.collapsef = collapse
226 self.keepf = keep
227 self.keepbranchesf = keepbranches
228 self.external = external
229 self.activebookmark = activebookmark
230
231 def _handleskippingobsolete(self, rebaserevs, obsoleterevs, target):
232 """Compute structures necessary for skipping obsolete revisions
233
234 rebaserevs: iterable of all revisions that are to be rebased
235 obsoleterevs: iterable of all obsolete revisions in rebaseset
236 target: a destination revision for the rebase operation
237 """
238 self.obsoletenotrebased = {}
239 if not self.ui.configbool('experimental', 'rebaseskipobsolete',
240 default=True):
241 return
242 rebaseset = set(rebaserevs)
243 obsoleteset = set(obsoleterevs)
244 self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
245 obsoleteset, target)
246 skippedset = set(self.obsoletenotrebased)
247 _checkobsrebase(self.repo, self.ui, obsoleteset, rebaseset, skippedset)
248
249 def _prepareabortorcontinue(self, isabort):
250 try:
251 self.restorestatus()
252 self.collapsemsg = restorecollapsemsg(self.repo)
253 except error.RepoLookupError:
254 if isabort:
255 clearstatus(self.repo)
256 clearcollapsemsg(self.repo)
257 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
258 ' only broken state is cleared)\n'))
259 return 0
260 else:
261 msg = _('cannot continue inconsistent rebase')
262 hint = _('use "hg rebase --abort" to clear broken state')
263 raise error.Abort(msg, hint=hint)
264 if isabort:
265 return abort(self.repo, self.originalwd, self.target,
266 self.state, activebookmark=self.activebookmark)
267
268 obsrevs = (r for r, st in self.state.items() if st == revprecursor)
269 self._handleskippingobsolete(self.state.keys(), obsrevs, self.target)
270
271 def _preparenewrebase(self, dest, rebaseset):
272 if dest is None:
273 return _nothingtorebase()
274
275 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
276 if (not (self.keepf or allowunstable)
277 and self.repo.revs('first(children(%ld) - %ld)',
278 rebaseset, rebaseset)):
279 raise error.Abort(
280 _("can't remove original changesets with"
281 " unrebased descendants"),
282 hint=_('use --keep to keep original changesets'))
283
284 obsrevs = _filterobsoleterevs(self.repo, rebaseset)
285 self._handleskippingobsolete(rebaseset, obsrevs, dest)
286
287 result = buildstate(self.repo, dest, rebaseset, self.collapsef,
288 self.obsoletenotrebased)
289
290 if not result:
291 # Empty state built, nothing to rebase
292 self.ui.status(_('nothing to rebase\n'))
293 return _nothingtorebase()
294
295 root = min(rebaseset)
296 if not self.keepf and not self.repo[root].mutable():
297 raise error.Abort(_("can't rebase public changeset %s")
298 % self.repo[root],
299 hint=_('see "hg help phases" for details'))
300
301 (self.originalwd, self.target, self.state) = result
302 if self.collapsef:
303 self.targetancestors = self.repo.changelog.ancestors(
304 [self.target],
305 inclusive=True)
306 self.external = externalparent(self.repo, self.state,
307 self.targetancestors)
308
309 if dest.closesbranch() and not self.keepbranchesf:
310 self.ui.status(_('reopening closed branch head %s\n') % dest)
311
312 def _performrebase(self):
313 repo, ui, opts = self.repo, self.ui, self.opts
314 if self.keepbranchesf:
315 # insert _savebranch at the start of extrafns so if
316 # there's a user-provided extrafn it can clobber branch if
317 # desired
318 self.extrafns.insert(0, _savebranch)
319 if self.collapsef:
320 branches = set()
321 for rev in self.state:
322 branches.add(repo[rev].branch())
323 if len(branches) > 1:
324 raise error.Abort(_('cannot collapse multiple named '
325 'branches'))
326
327 # Rebase
328 if not self.targetancestors:
329 self.targetancestors = repo.changelog.ancestors([self.target],
330 inclusive=True)
331
332 # Keep track of the current bookmarks in order to reset them later
333 self.currentbookmarks = repo._bookmarks.copy()
334 self.activebookmark = self.activebookmark or repo._activebookmark
335 if self.activebookmark:
336 bookmarks.deactivate(repo)
337
338 sortedrevs = sorted(self.state)
339 total = len(self.state)
340 pos = 0
341 for rev in sortedrevs:
342 ctx = repo[rev]
343 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
344 ctx.description().split('\n', 1)[0])
345 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
346 if names:
347 desc += ' (%s)' % ' '.join(names)
348 pos += 1
349 if self.state[rev] == revtodo:
350 ui.status(_('rebasing %s\n') % desc)
351 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
352 _('changesets'), total)
353 p1, p2, base = defineparents(repo, rev, self.target,
354 self.state,
355 self.targetancestors,
356 self.obsoletenotrebased)
357 storestatus(repo, self.originalwd, self.target,
358 self.state, self.collapsef, self.keepf,
359 self.keepbranchesf, self.external,
360 self.activebookmark)
361 storecollapsemsg(repo, self.collapsemsg)
362 if len(repo[None].parents()) == 2:
363 repo.ui.debug('resuming interrupted rebase\n')
364 else:
365 try:
366 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
367 'rebase')
368 stats = rebasenode(repo, rev, p1, base, self.state,
369 self.collapsef, self.target)
370 if stats and stats[3] > 0:
371 raise error.InterventionRequired(
372 _('unresolved conflicts (see hg '
373 'resolve, then hg rebase --continue)'))
374 finally:
375 ui.setconfig('ui', 'forcemerge', '', 'rebase')
376 if not self.collapsef:
377 merging = p2 != nullrev
378 editform = cmdutil.mergeeditform(merging, 'rebase')
379 editor = cmdutil.getcommiteditor(editform=editform, **opts)
380 newnode = concludenode(repo, rev, p1, p2,
381 extrafn=_makeextrafn(self.extrafns),
382 editor=editor,
383 keepbranches=self.keepbranchesf,
384 date=self.date)
385 else:
386 # Skip commit if we are collapsing
387 repo.dirstate.beginparentchange()
388 repo.setparents(repo[p1].node())
389 repo.dirstate.endparentchange()
390 newnode = None
391 # Update the state
392 if newnode is not None:
393 self.state[rev] = repo[newnode].rev()
394 ui.debug('rebased as %s\n' % short(newnode))
395 else:
396 if not self.collapsef:
397 ui.warn(_('note: rebase of %d:%s created no changes '
398 'to commit\n') % (rev, ctx))
399 self.skipped.add(rev)
400 self.state[rev] = p1
401 ui.debug('next revision set to %s\n' % p1)
402 elif self.state[rev] == nullmerge:
403 ui.debug('ignoring null merge rebase of %s\n' % rev)
404 elif self.state[rev] == revignored:
405 ui.status(_('not rebasing ignored %s\n') % desc)
406 elif self.state[rev] == revprecursor:
407 targetctx = repo[self.obsoletenotrebased[rev]]
408 desctarget = '%d:%s "%s"' % (targetctx.rev(), targetctx,
409 targetctx.description().split('\n', 1)[0])
410 msg = _('note: not rebasing %s, already in destination as %s\n')
411 ui.status(msg % (desc, desctarget))
412 elif self.state[rev] == revpruned:
413 msg = _('note: not rebasing %s, it has no successor\n')
414 ui.status(msg % desc)
415 else:
416 ui.status(_('already rebased %s as %s\n') %
417 (desc, repo[self.state[rev]]))
418
419 ui.progress(_('rebasing'), None)
420 ui.note(_('rebase merging completed\n'))
421
422 def _finishrebase(self):
423 repo, ui, opts = self.repo, self.ui, self.opts
424 if self.collapsef and not self.keepopen:
425 p1, p2, _base = defineparents(repo, min(self.state),
426 self.target, self.state,
427 self.targetancestors,
428 self.obsoletenotrebased)
429 editopt = opts.get('edit')
430 editform = 'rebase.collapse'
431 if self.collapsemsg:
432 commitmsg = self.collapsemsg
433 else:
434 commitmsg = 'Collapsed revision'
435 for rebased in self.state:
436 if rebased not in self.skipped and\
437 self.state[rebased] > nullmerge:
438 commitmsg += '\n* %s' % repo[rebased].description()
439 editopt = True
440 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
441 revtoreuse = max(self.state)
442 newnode = concludenode(repo, revtoreuse, p1, self.external,
443 commitmsg=commitmsg,
444 extrafn=_makeextrafn(self.extrafns),
445 editor=editor,
446 keepbranches=self.keepbranchesf,
447 date=self.date)
448 if newnode is None:
449 newrev = self.target
450 else:
451 newrev = repo[newnode].rev()
452 for oldrev in self.state.iterkeys():
453 if self.state[oldrev] > nullmerge:
454 self.state[oldrev] = newrev
455
456 if 'qtip' in repo.tags():
457 updatemq(repo, self.state, self.skipped, **opts)
458
459 if self.currentbookmarks:
460 # Nodeids are needed to reset bookmarks
461 nstate = {}
462 for k, v in self.state.iteritems():
463 if v > nullmerge:
464 nstate[repo[k].node()] = repo[v].node()
465 elif v == revprecursor:
466 succ = self.obsoletenotrebased[k]
467 nstate[repo[k].node()] = repo[succ].node()
468 # XXX this is the same as dest.node() for the non-continue path --
469 # this should probably be cleaned up
470 targetnode = repo[self.target].node()
471
472 # restore original working directory
473 # (we do this before stripping)
474 newwd = self.state.get(self.originalwd, self.originalwd)
475 if newwd == revprecursor:
476 newwd = self.obsoletenotrebased[self.originalwd]
477 elif newwd < 0:
478 # original directory is a parent of rebase set root or ignored
479 newwd = self.originalwd
480 if newwd not in [c.rev() for c in repo[None].parents()]:
481 ui.note(_("update back to initial working directory parent\n"))
482 hg.updaterepo(repo, newwd, False)
483
484 if not self.keepf:
485 collapsedas = None
486 if self.collapsef:
487 collapsedas = newnode
488 clearrebased(ui, repo, self.state, self.skipped, collapsedas)
489
490 with repo.transaction('bookmark') as tr:
491 if self.currentbookmarks:
492 updatebookmarks(repo, targetnode, nstate,
493 self.currentbookmarks, tr)
494 if self.activebookmark not in repo._bookmarks:
495 # active bookmark was divergent one and has been deleted
496 self.activebookmark = None
497 clearstatus(repo)
498 clearcollapsemsg(repo)
499
500 ui.note(_("rebase completed\n"))
501 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
502 if self.skipped:
503 skippedlen = len(self.skipped)
504 ui.note(_("%d revisions have been skipped\n") % skippedlen)
505
506 if (self.activebookmark and
507 repo['.'].node() == repo._bookmarks[self.activebookmark]):
508 bookmarks.activate(repo, self.activebookmark)
509
94 @command('rebase',
510 @command('rebase',
95 [('s', 'source', '',
511 [('s', 'source', '',
96 _('rebase the specified changeset and descendants'), _('REV')),
512 _('rebase the specified changeset and descendants'), _('REV')),
@@ -201,16 +617,7 b' def rebase(ui, repo, **opts):'
201 unresolved conflicts.
617 unresolved conflicts.
202
618
203 """
619 """
204 originalwd = target = None
620 rbsrt = rebaseruntime(repo, ui, opts)
205 activebookmark = None
206 external = nullrev
207 # Mapping between the old revision id and either what is the new rebased
208 # revision or what needs to be done with the old revision. The state dict
209 # will be what contains most of the rebase progress state.
210 state = {}
211 skipped = set()
212 targetancestors = set()
213
214
621
215 lock = wlock = None
622 lock = wlock = None
216 try:
623 try:
@@ -227,19 +634,6 b' def rebase(ui, repo, **opts):'
227 destspace = opts.get('_destspace')
634 destspace = opts.get('_destspace')
228 contf = opts.get('continue')
635 contf = opts.get('continue')
229 abortf = opts.get('abort')
636 abortf = opts.get('abort')
230 collapsef = opts.get('collapse', False)
231 collapsemsg = cmdutil.logmessage(ui, opts)
232 date = opts.get('date', None)
233 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
234 extrafns = [_savegraft]
235 if e:
236 extrafns = [e]
237 keepf = opts.get('keep', False)
238 keepbranchesf = opts.get('keepbranches', False)
239 # keepopen is not meant for use on the command line, but by
240 # other extensions
241 keepopen = opts.get('keepopen', False)
242
243 if opts.get('interactive'):
637 if opts.get('interactive'):
244 try:
638 try:
245 if extensions.find('histedit'):
639 if extensions.find('histedit'):
@@ -251,14 +645,14 b' def rebase(ui, repo, **opts):'
251 "'histedit' extension (see \"%s\")") % help
645 "'histedit' extension (see \"%s\")") % help
252 raise error.Abort(msg)
646 raise error.Abort(msg)
253
647
254 if collapsemsg and not collapsef:
648 if rbsrt.collapsemsg and not rbsrt.collapsef:
255 raise error.Abort(
649 raise error.Abort(
256 _('message can only be specified with collapse'))
650 _('message can only be specified with collapse'))
257
651
258 if contf or abortf:
652 if contf or abortf:
259 if contf and abortf:
653 if contf and abortf:
260 raise error.Abort(_('cannot use both abort and continue'))
654 raise error.Abort(_('cannot use both abort and continue'))
261 if collapsef:
655 if rbsrt.collapsef:
262 raise error.Abort(
656 raise error.Abort(
263 _('cannot use collapse with continue or abort'))
657 _('cannot use collapse with continue or abort'))
264 if srcf or basef or destf:
658 if srcf or basef or destf:
@@ -267,265 +661,18 b' def rebase(ui, repo, **opts):'
267 if abortf and opts.get('tool', False):
661 if abortf and opts.get('tool', False):
268 ui.warn(_('tool option will be ignored\n'))
662 ui.warn(_('tool option will be ignored\n'))
269
663
270 try:
664 retcode = rbsrt._prepareabortorcontinue(abortf)
271 (originalwd, target, state, skipped, collapsef, keepf,
665 if retcode is not None:
272 keepbranchesf, external, activebookmark) = restorestatus(repo)
666 return retcode
273 collapsemsg = restorecollapsemsg(repo)
274 except error.RepoLookupError:
275 if abortf:
276 clearstatus(repo)
277 clearcollapsemsg(repo)
278 repo.ui.warn(_('rebase aborted (no revision is removed,'
279 ' only broken state is cleared)\n'))
280 return 0
281 else:
282 msg = _('cannot continue inconsistent rebase')
283 hint = _('use "hg rebase --abort" to clear broken state')
284 raise error.Abort(msg, hint=hint)
285 if abortf:
286 return abort(repo, originalwd, target, state,
287 activebookmark=activebookmark)
288
289 obsoletenotrebased = {}
290 if ui.configbool('experimental', 'rebaseskipobsolete',
291 default=True):
292 rebaseobsrevs = set([r for r, status in state.items()
293 if status == revprecursor])
294 rebasesetrevs = set(state.keys())
295 obsoletenotrebased = _computeobsoletenotrebased(repo,
296 rebaseobsrevs,
297 target)
298 rebaseobsskipped = set(obsoletenotrebased)
299 _checkobsrebase(repo, ui, rebaseobsrevs, rebasesetrevs,
300 rebaseobsskipped)
301 else:
667 else:
302 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
668 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
303 destspace=destspace)
669 destspace=destspace)
304 if dest is None:
670 retcode = rbsrt._preparenewrebase(dest, rebaseset)
305 return _nothingtorebase()
671 if retcode is not None:
306
672 return retcode
307 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
308 if (not (keepf or allowunstable)
309 and repo.revs('first(children(%ld) - %ld)',
310 rebaseset, rebaseset)):
311 raise error.Abort(
312 _("can't remove original changesets with"
313 " unrebased descendants"),
314 hint=_('use --keep to keep original changesets'))
315
316 obsoletenotrebased = {}
317 if ui.configbool('experimental', 'rebaseskipobsolete',
318 default=True):
319 rebasesetrevs = set(rebaseset)
320 rebaseobsrevs = _filterobsoleterevs(repo, rebasesetrevs)
321 obsoletenotrebased = _computeobsoletenotrebased(repo,
322 rebaseobsrevs,
323 dest)
324 rebaseobsskipped = set(obsoletenotrebased)
325 _checkobsrebase(repo, ui, rebaseobsrevs,
326 rebasesetrevs,
327 rebaseobsskipped)
328
329 result = buildstate(repo, dest, rebaseset, collapsef,
330 obsoletenotrebased)
331
332 if not result:
333 # Empty state built, nothing to rebase
334 ui.status(_('nothing to rebase\n'))
335 return _nothingtorebase()
336
337 root = min(rebaseset)
338 if not keepf and not repo[root].mutable():
339 raise error.Abort(_("can't rebase public changeset %s")
340 % repo[root],
341 hint=_('see "hg help phases" for details'))
342
343 originalwd, target, state = result
344 if collapsef:
345 targetancestors = repo.changelog.ancestors([target],
346 inclusive=True)
347 external = externalparent(repo, state, targetancestors)
348
349 if dest.closesbranch() and not keepbranchesf:
350 ui.status(_('reopening closed branch head %s\n') % dest)
351
352 if keepbranchesf:
353 # insert _savebranch at the start of extrafns so if
354 # there's a user-provided extrafn it can clobber branch if
355 # desired
356 extrafns.insert(0, _savebranch)
357 if collapsef:
358 branches = set()
359 for rev in state:
360 branches.add(repo[rev].branch())
361 if len(branches) > 1:
362 raise error.Abort(_('cannot collapse multiple named '
363 'branches'))
364
365 # Rebase
366 if not targetancestors:
367 targetancestors = repo.changelog.ancestors([target], inclusive=True)
368
369 # Keep track of the current bookmarks in order to reset them later
370 currentbookmarks = repo._bookmarks.copy()
371 activebookmark = activebookmark or repo._activebookmark
372 if activebookmark:
373 bookmarks.deactivate(repo)
374
375 extrafn = _makeextrafn(extrafns)
376
673
377 sortedstate = sorted(state)
674 rbsrt._performrebase()
378 total = len(sortedstate)
675 rbsrt._finishrebase()
379 pos = 0
380 for rev in sortedstate:
381 ctx = repo[rev]
382 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
383 ctx.description().split('\n', 1)[0])
384 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
385 if names:
386 desc += ' (%s)' % ' '.join(names)
387 pos += 1
388 if state[rev] == revtodo:
389 ui.status(_('rebasing %s\n') % desc)
390 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
391 _('changesets'), total)
392 p1, p2, base = defineparents(repo, rev, target, state,
393 targetancestors)
394 storestatus(repo, originalwd, target, state, collapsef, keepf,
395 keepbranchesf, external, activebookmark)
396 storecollapsemsg(repo, collapsemsg)
397 if len(repo[None].parents()) == 2:
398 repo.ui.debug('resuming interrupted rebase\n')
399 else:
400 try:
401 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
402 'rebase')
403 stats = rebasenode(repo, rev, p1, base, state,
404 collapsef, target)
405 if stats and stats[3] > 0:
406 raise error.InterventionRequired(
407 _('unresolved conflicts (see hg '
408 'resolve, then hg rebase --continue)'))
409 finally:
410 ui.setconfig('ui', 'forcemerge', '', 'rebase')
411 if not collapsef:
412 merging = p2 != nullrev
413 editform = cmdutil.mergeeditform(merging, 'rebase')
414 editor = cmdutil.getcommiteditor(editform=editform, **opts)
415 newnode = concludenode(repo, rev, p1, p2, extrafn=extrafn,
416 editor=editor,
417 keepbranches=keepbranchesf,
418 date=date)
419 else:
420 # Skip commit if we are collapsing
421 repo.dirstate.beginparentchange()
422 repo.setparents(repo[p1].node())
423 repo.dirstate.endparentchange()
424 newnode = None
425 # Update the state
426 if newnode is not None:
427 state[rev] = repo[newnode].rev()
428 ui.debug('rebased as %s\n' % short(newnode))
429 else:
430 if not collapsef:
431 ui.warn(_('note: rebase of %d:%s created no changes '
432 'to commit\n') % (rev, ctx))
433 skipped.add(rev)
434 state[rev] = p1
435 ui.debug('next revision set to %s\n' % p1)
436 elif state[rev] == nullmerge:
437 ui.debug('ignoring null merge rebase of %s\n' % rev)
438 elif state[rev] == revignored:
439 ui.status(_('not rebasing ignored %s\n') % desc)
440 elif state[rev] == revprecursor:
441 targetctx = repo[obsoletenotrebased[rev]]
442 desctarget = '%d:%s "%s"' % (targetctx.rev(), targetctx,
443 targetctx.description().split('\n', 1)[0])
444 msg = _('note: not rebasing %s, already in destination as %s\n')
445 ui.status(msg % (desc, desctarget))
446 elif state[rev] == revpruned:
447 msg = _('note: not rebasing %s, it has no successor\n')
448 ui.status(msg % desc)
449 else:
450 ui.status(_('already rebased %s as %s\n') %
451 (desc, repo[state[rev]]))
452
453 ui.progress(_('rebasing'), None)
454 ui.note(_('rebase merging completed\n'))
455
456 if collapsef and not keepopen:
457 p1, p2, _base = defineparents(repo, min(state), target,
458 state, targetancestors)
459 editopt = opts.get('edit')
460 editform = 'rebase.collapse'
461 if collapsemsg:
462 commitmsg = collapsemsg
463 else:
464 commitmsg = 'Collapsed revision'
465 for rebased in state:
466 if rebased not in skipped and state[rebased] > nullmerge:
467 commitmsg += '\n* %s' % repo[rebased].description()
468 editopt = True
469 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
470 newnode = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
471 extrafn=extrafn, editor=editor,
472 keepbranches=keepbranchesf,
473 date=date)
474 if newnode is None:
475 newrev = target
476 else:
477 newrev = repo[newnode].rev()
478 for oldrev in state.iterkeys():
479 if state[oldrev] > nullmerge:
480 state[oldrev] = newrev
481
482 if 'qtip' in repo.tags():
483 updatemq(repo, state, skipped, **opts)
484
485 if currentbookmarks:
486 # Nodeids are needed to reset bookmarks
487 nstate = {}
488 for k, v in state.iteritems():
489 if v > nullmerge:
490 nstate[repo[k].node()] = repo[v].node()
491 # XXX this is the same as dest.node() for the non-continue path --
492 # this should probably be cleaned up
493 targetnode = repo[target].node()
494
495 # restore original working directory
496 # (we do this before stripping)
497 newwd = state.get(originalwd, originalwd)
498 if newwd < 0:
499 # original directory is a parent of rebase set root or ignored
500 newwd = originalwd
501 if newwd not in [c.rev() for c in repo[None].parents()]:
502 ui.note(_("update back to initial working directory parent\n"))
503 hg.updaterepo(repo, newwd, False)
504
505 if not keepf:
506 collapsedas = None
507 if collapsef:
508 collapsedas = newnode
509 clearrebased(ui, repo, state, skipped, collapsedas)
510
511 with repo.transaction('bookmark') as tr:
512 if currentbookmarks:
513 updatebookmarks(repo, targetnode, nstate, currentbookmarks, tr)
514 if activebookmark not in repo._bookmarks:
515 # active bookmark was divergent one and has been deleted
516 activebookmark = None
517 clearstatus(repo)
518 clearcollapsemsg(repo)
519
520 ui.note(_("rebase completed\n"))
521 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
522 if skipped:
523 ui.note(_("%d revisions have been skipped\n") % len(skipped))
524
525 if (activebookmark and
526 repo['.'].node() == repo._bookmarks[activebookmark]):
527 bookmarks.activate(repo, activebookmark)
528
529 finally:
676 finally:
530 release(lock, wlock)
677 release(lock, wlock)
531
678
@@ -733,21 +880,12 b' def _checkobsrebase(repo, ui,'
733 "experimental.allowdivergence=True")
880 "experimental.allowdivergence=True")
734 raise error.Abort(msg % (",".join(divhashes),), hint=h)
881 raise error.Abort(msg % (",".join(divhashes),), hint=h)
735
882
736 # - plain prune (no successor) changesets are rebased
883 def defineparents(repo, rev, target, state, targetancestors,
737 # - split changesets are not rebased if at least one of the
884 obsoletenotrebased):
738 # changeset resulting from the split is an ancestor of dest
739 rebaseset = rebasesetrevs - rebaseobsskipped
740 if rebasesetrevs and not rebaseset:
741 msg = _('all requested changesets have equivalents '
742 'or were marked as obsolete')
743 hint = _('to force the rebase, set the config '
744 'experimental.rebaseskipobsolete to False')
745 raise error.Abort(msg, hint=hint)
746
747 def defineparents(repo, rev, target, state, targetancestors):
748 'Return the new parent relationship of the revision that will be rebased'
885 'Return the new parent relationship of the revision that will be rebased'
749 parents = repo[rev].parents()
886 parents = repo[rev].parents()
750 p1 = p2 = nullrev
887 p1 = p2 = nullrev
888 rp1 = None
751
889
752 p1n = parents[0].rev()
890 p1n = parents[0].rev()
753 if p1n in targetancestors:
891 if p1n in targetancestors:
@@ -771,6 +909,8 b' def defineparents(repo, rev, target, sta'
771 if p2n in state:
909 if p2n in state:
772 if p1 == target: # p1n in targetancestors or external
910 if p1 == target: # p1n in targetancestors or external
773 p1 = state[p2n]
911 p1 = state[p2n]
912 if p1 == revprecursor:
913 rp1 = obsoletenotrebased[p2n]
774 elif state[p2n] in revskipped:
914 elif state[p2n] in revskipped:
775 p2 = nearestrebased(repo, p2n, state)
915 p2 = nearestrebased(repo, p2n, state)
776 if p2 is None:
916 if p2 is None:
@@ -784,7 +924,7 b' def defineparents(repo, rev, target, sta'
784 'would have 3 parents') % rev)
924 'would have 3 parents') % rev)
785 p2 = p2n
925 p2 = p2n
786 repo.ui.debug(" future parents are %d and %d\n" %
926 repo.ui.debug(" future parents are %d and %d\n" %
787 (repo[p1].rev(), repo[p2].rev()))
927 (repo[rp1 or p1].rev(), repo[p2].rev()))
788
928
789 if not any(p.rev() in state for p in parents):
929 if not any(p.rev() in state for p in parents):
790 # Case (1) root changeset of a non-detaching rebase set.
930 # Case (1) root changeset of a non-detaching rebase set.
@@ -828,6 +968,8 b' def defineparents(repo, rev, target, sta'
828 # make it feasible to consider different cases separately. In these
968 # make it feasible to consider different cases separately. In these
829 # other cases we currently just leave it to the user to correctly
969 # other cases we currently just leave it to the user to correctly
830 # resolve an impossible merge using a wrong ancestor.
970 # resolve an impossible merge using a wrong ancestor.
971 #
972 # xx, p1 could be -4, and both parents could probably be -4...
831 for p in repo[rev].parents():
973 for p in repo[rev].parents():
832 if state.get(p.rev()) == p1:
974 if state.get(p.rev()) == p1:
833 base = p.rev()
975 base = p.rev()
@@ -838,7 +980,7 b' def defineparents(repo, rev, target, sta'
838 # Raise because this function is called wrong (see issue 4106)
980 # Raise because this function is called wrong (see issue 4106)
839 raise AssertionError('no base found to rebase on '
981 raise AssertionError('no base found to rebase on '
840 '(defineparents called wrong)')
982 '(defineparents called wrong)')
841 return p1, p2, base
983 return rp1 or p1, p2, base
842
984
843 def isagitpatch(repo, patchname):
985 def isagitpatch(repo, patchname):
844 'Return true if the given patch is in git format'
986 'Return true if the given patch is in git format'
@@ -952,68 +1094,6 b' def clearstatus(repo):'
952 _clearrebasesetvisibiliy(repo)
1094 _clearrebasesetvisibiliy(repo)
953 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
1095 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
954
1096
955 def restorestatus(repo):
956 'Restore a previously stored status'
957 keepbranches = None
958 target = None
959 collapse = False
960 external = nullrev
961 activebookmark = None
962 state = {}
963
964 try:
965 f = repo.vfs("rebasestate")
966 for i, l in enumerate(f.read().splitlines()):
967 if i == 0:
968 originalwd = repo[l].rev()
969 elif i == 1:
970 target = repo[l].rev()
971 elif i == 2:
972 external = repo[l].rev()
973 elif i == 3:
974 collapse = bool(int(l))
975 elif i == 4:
976 keep = bool(int(l))
977 elif i == 5:
978 keepbranches = bool(int(l))
979 elif i == 6 and not (len(l) == 81 and ':' in l):
980 # line 6 is a recent addition, so for backwards compatibility
981 # check that the line doesn't look like the oldrev:newrev lines
982 activebookmark = l
983 else:
984 oldrev, newrev = l.split(':')
985 if newrev in (str(nullmerge), str(revignored),
986 str(revprecursor), str(revpruned)):
987 state[repo[oldrev].rev()] = int(newrev)
988 elif newrev == nullid:
989 state[repo[oldrev].rev()] = revtodo
990 # Legacy compat special case
991 else:
992 state[repo[oldrev].rev()] = repo[newrev].rev()
993
994 except IOError as err:
995 if err.errno != errno.ENOENT:
996 raise
997 cmdutil.wrongtooltocontinue(repo, _('rebase'))
998
999 if keepbranches is None:
1000 raise error.Abort(_('.hg/rebasestate is incomplete'))
1001
1002 skipped = set()
1003 # recompute the set of skipped revs
1004 if not collapse:
1005 seen = set([target])
1006 for old, new in sorted(state.items()):
1007 if new != revtodo and new in seen:
1008 skipped.add(old)
1009 seen.add(new)
1010 repo.ui.debug('computed skipped revs: %s\n' %
1011 (' '.join(str(r) for r in sorted(skipped)) or None))
1012 repo.ui.debug('rebase status resumed\n')
1013 _setrebasesetvisibility(repo, state.keys())
1014 return (originalwd, target, state, skipped,
1015 collapse, keep, keepbranches, external, activebookmark)
1016
1017 def needupdate(repo, state):
1097 def needupdate(repo, state):
1018 '''check whether we should `update --clean` away from a merge, or if
1098 '''check whether we should `update --clean` away from a merge, or if
1019 somehow the working dir got forcibly updated, e.g. by older hg'''
1099 somehow the working dir got forcibly updated, e.g. by older hg'''
@@ -1336,7 +1416,9 b' def summaryhook(ui, repo):'
1336 if not os.path.exists(repo.join('rebasestate')):
1416 if not os.path.exists(repo.join('rebasestate')):
1337 return
1417 return
1338 try:
1418 try:
1339 state = restorestatus(repo)[2]
1419 rbsrt = rebaseruntime(repo, ui, {})
1420 rbsrt.restorestatus()
1421 state = rbsrt.state
1340 except error.RepoLookupError:
1422 except error.RepoLookupError:
1341 # i18n: column positioning for "hg summary"
1423 # i18n: column positioning for "hg summary"
1342 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1424 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
@@ -12,13 +12,13 b' The feature provided by this extension h'
12
12
13 from __future__ import absolute_import
13 from __future__ import absolute_import
14
14
15 from mercurial.i18n import _
15 from mercurial import (
16 from mercurial import (
16 cmdutil,
17 cmdutil,
17 commands,
18 commands,
18 error,
19 error,
19 extensions,
20 extensions,
20 )
21 )
21 from mercurial.i18n import _
22
22
23 cmdtable = {}
23 cmdtable = {}
24 command = cmdutil.command(cmdtable)
24 command = cmdutil.command(cmdtable)
@@ -11,13 +11,13 b' from __future__ import absolute_import'
11 import os
11 import os
12 import stat
12 import stat
13
13
14 from mercurial.i18n import _
14 from mercurial import (
15 from mercurial import (
15 cmdutil,
16 cmdutil,
16 error,
17 error,
17 hg,
18 hg,
18 util,
19 util,
19 )
20 )
20 from mercurial.i18n import _
21
21
22 cmdtable = {}
22 cmdtable = {}
23 command = cmdutil.command(cmdtable)
23 command = cmdutil.command(cmdtable)
@@ -43,6 +43,8 b' from __future__ import absolute_import'
43
43
44 import os
44 import os
45 import re
45 import re
46
47 from mercurial.i18n import _
46 from mercurial import (
48 from mercurial import (
47 cmdutil,
49 cmdutil,
48 error,
50 error,
@@ -51,7 +53,6 b' from mercurial import ('
51 templater,
53 templater,
52 util,
54 util,
53 )
55 )
54 from mercurial.i18n import _
55
56
56 cmdtable = {}
57 cmdtable = {}
57 command = cmdutil.command(cmdtable)
58 command = cmdutil.command(cmdtable)
@@ -37,10 +37,22 b' The following ``share.`` config options '
37 The default naming mode is "identity."
37 The default naming mode is "identity."
38 '''
38 '''
39
39
40 from __future__ import absolute_import
41
42 import errno
40 from mercurial.i18n import _
43 from mercurial.i18n import _
41 from mercurial import cmdutil, commands, hg, util, extensions, bookmarks, error
44 from mercurial import (
42 from mercurial.hg import repository, parseurl
45 bookmarks,
43 import errno
46 cmdutil,
47 commands,
48 error,
49 extensions,
50 hg,
51 util,
52 )
53
54 repository = hg.repository
55 parseurl = hg.parseurl
44
56
45 cmdtable = {}
57 cmdtable = {}
46 command = cmdutil.command(cmdtable)
58 command = cmdutil.command(cmdtable)
@@ -135,7 +147,7 b' def _hassharedbookmarks(repo):'
135 if inst.errno != errno.ENOENT:
147 if inst.errno != errno.ENOENT:
136 raise
148 raise
137 return False
149 return False
138 return 'bookmarks' in shared
150 return hg.sharedbookmarks in shared
139
151
140 def _getsrcrepo(repo):
152 def _getsrcrepo(repo):
141 """
153 """
@@ -145,10 +157,15 b' def _getsrcrepo(repo):'
145 if repo.sharedpath == repo.path:
157 if repo.sharedpath == repo.path:
146 return None
158 return None
147
159
160 if util.safehasattr(repo, 'srcrepo') and repo.srcrepo:
161 return repo.srcrepo
162
148 # the sharedpath always ends in the .hg; we want the path to the repo
163 # the sharedpath always ends in the .hg; we want the path to the repo
149 source = repo.vfs.split(repo.sharedpath)[0]
164 source = repo.vfs.split(repo.sharedpath)[0]
150 srcurl, branches = parseurl(source)
165 srcurl, branches = parseurl(source)
151 return repository(repo.ui, srcurl)
166 srcrepo = repository(repo.ui, srcurl)
167 repo.srcrepo = srcrepo
168 return srcrepo
152
169
153 def getbkfile(orig, repo):
170 def getbkfile(orig, repo):
154 if _hassharedbookmarks(repo):
171 if _hassharedbookmarks(repo):
@@ -25,6 +25,8 b' from __future__ import absolute_import'
25 import collections
25 import collections
26 import errno
26 import errno
27 import itertools
27 import itertools
28
29 from mercurial.i18n import _
28 from mercurial import (
30 from mercurial import (
29 bundle2,
31 bundle2,
30 bundlerepo,
32 bundlerepo,
@@ -45,7 +47,6 b' from mercurial import ('
45 templatefilters,
47 templatefilters,
46 util,
48 util,
47 )
49 )
48 from mercurial.i18n import _
49
50
50 from . import (
51 from . import (
51 rebase,
52 rebase,
@@ -164,21 +165,26 b' class shelvedstate(object):'
164 raise error.Abort(_('this version of shelve is incompatible '
165 raise error.Abort(_('this version of shelve is incompatible '
165 'with the version used in this repo'))
166 'with the version used in this repo'))
166 name = fp.readline().strip()
167 name = fp.readline().strip()
167 wctx = fp.readline().strip()
168 wctx = nodemod.bin(fp.readline().strip())
168 pendingctx = fp.readline().strip()
169 pendingctx = nodemod.bin(fp.readline().strip())
169 parents = [nodemod.bin(h) for h in fp.readline().split()]
170 parents = [nodemod.bin(h) for h in fp.readline().split()]
170 stripnodes = [nodemod.bin(h) for h in fp.readline().split()]
171 stripnodes = [nodemod.bin(h) for h in fp.readline().split()]
171 branchtorestore = fp.readline().strip()
172 branchtorestore = fp.readline().strip()
173 except (ValueError, TypeError) as err:
174 raise error.CorruptedState(str(err))
172 finally:
175 finally:
173 fp.close()
176 fp.close()
174
177
175 obj = cls()
178 try:
176 obj.name = name
179 obj = cls()
177 obj.wctx = repo[nodemod.bin(wctx)]
180 obj.name = name
178 obj.pendingctx = repo[nodemod.bin(pendingctx)]
181 obj.wctx = repo[wctx]
179 obj.parents = parents
182 obj.pendingctx = repo[pendingctx]
180 obj.stripnodes = stripnodes
183 obj.parents = parents
181 obj.branchtorestore = branchtorestore
184 obj.stripnodes = stripnodes
185 obj.branchtorestore = branchtorestore
186 except error.RepoLookupError as err:
187 raise error.CorruptedState(str(err))
182
188
183 return obj
189 return obj
184
190
@@ -225,28 +231,10 b' def cleanupoldbackups(repo):'
225 def _aborttransaction(repo):
231 def _aborttransaction(repo):
226 '''Abort current transaction for shelve/unshelve, but keep dirstate
232 '''Abort current transaction for shelve/unshelve, but keep dirstate
227 '''
233 '''
228 backupname = 'dirstate.shelve'
234 tr = repo.currenttransaction()
229 dirstatebackup = None
235 repo.dirstate.savebackup(tr, suffix='.shelve')
230 try:
236 tr.abort()
231 # create backup of (un)shelved dirstate, because aborting transaction
237 repo.dirstate.restorebackup(None, suffix='.shelve')
232 # should restore dirstate to one at the beginning of the
233 # transaction, which doesn't include the result of (un)shelving
234 fp = repo.vfs.open(backupname, "w")
235 dirstatebackup = backupname
236 # clearing _dirty/_dirtypl of dirstate by _writedirstate below
237 # is unintentional. but it doesn't cause problem in this case,
238 # because no code path refers them until transaction is aborted.
239 repo.dirstate._writedirstate(fp) # write in-memory changes forcibly
240
241 tr = repo.currenttransaction()
242 tr.abort()
243
244 # restore to backuped dirstate
245 repo.vfs.rename(dirstatebackup, 'dirstate')
246 dirstatebackup = None
247 finally:
248 if dirstatebackup:
249 repo.vfs.unlink(dirstatebackup)
250
238
251 def createcmd(ui, repo, pats, opts):
239 def createcmd(ui, repo, pats, opts):
252 """subcommand that creates a new shelve"""
240 """subcommand that creates a new shelve"""
@@ -683,6 +671,20 b' def _dounshelve(ui, repo, *shelved, **op'
683 if err.errno != errno.ENOENT:
671 if err.errno != errno.ENOENT:
684 raise
672 raise
685 cmdutil.wrongtooltocontinue(repo, _('unshelve'))
673 cmdutil.wrongtooltocontinue(repo, _('unshelve'))
674 except error.CorruptedState as err:
675 ui.debug(str(err) + '\n')
676 if continuef:
677 msg = _('corrupted shelved state file')
678 hint = _('please run hg unshelve --abort to abort unshelve '
679 'operation')
680 raise error.Abort(msg, hint=hint)
681 elif abortf:
682 msg = _('could not read shelved state file, your working copy '
683 'may be in an unexpected state\nplease update to some '
684 'commit\n')
685 ui.warn(msg)
686 shelvedstate.clear(repo)
687 return
686
688
687 if abortf:
689 if abortf:
688 return unshelveabort(ui, repo, state, opts)
690 return unshelveabort(ui, repo, state, opts)
@@ -5,6 +5,7 b' repository. See the command help for det'
5 """
5 """
6 from __future__ import absolute_import
6 from __future__ import absolute_import
7
7
8 from mercurial.i18n import _
8 from mercurial import (
9 from mercurial import (
9 bookmarks as bookmarksmod,
10 bookmarks as bookmarksmod,
10 cmdutil,
11 cmdutil,
@@ -17,7 +18,6 b' from mercurial import ('
17 scmutil,
18 scmutil,
18 util,
19 util,
19 )
20 )
20 from mercurial.i18n import _
21 nullid = nodemod.nullid
21 nullid = nodemod.nullid
22 release = lockmod.release
22 release = lockmod.release
23
23
@@ -49,11 +49,11 b' from __future__ import absolute_import'
49 import os
49 import os
50 import sys
50 import sys
51
51
52 from mercurial.i18n import _
52 from mercurial import (
53 from mercurial import (
53 encoding,
54 encoding,
54 error,
55 error,
55 )
56 )
56 from mercurial.i18n import _
57
57
58 # Note for extension authors: ONLY specify testedwith = 'internal' for
58 # Note for extension authors: ONLY specify testedwith = 'internal' for
59 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
59 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
@@ -192,5 +192,5 b' def extsetup(ui):'
192 # command line options is not yet applied when
192 # command line options is not yet applied when
193 # extensions.loadall() is called.
193 # extensions.loadall() is called.
194 if '--debug' in sys.argv:
194 if '--debug' in sys.argv:
195 ui.write("[win32mbcs] activated with encoding: %s\n"
195 ui.write(("[win32mbcs] activated with encoding: %s\n")
196 % _encoding)
196 % _encoding)
@@ -41,10 +41,16 b' pushed or pulled::'
41 # or pretxnchangegroup.cr = python:hgext.win32text.forbidcr
41 # or pretxnchangegroup.cr = python:hgext.win32text.forbidcr
42 '''
42 '''
43
43
44 from __future__ import absolute_import
45
46 import re
44 from mercurial.i18n import _
47 from mercurial.i18n import _
45 from mercurial.node import short
48 from mercurial.node import (
46 from mercurial import util
49 short,
47 import re
50 )
51 from mercurial import (
52 util,
53 )
48
54
49 # Note for extension authors: ONLY specify testedwith = 'internal' for
55 # Note for extension authors: ONLY specify testedwith = 'internal' for
50 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
56 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
@@ -20,7 +20,11 b' Use xgettext like normal to extract stri'
20 join the message cataloges to get the final catalog.
20 join the message cataloges to get the final catalog.
21 """
21 """
22
22
23 import os, sys, inspect
23 from __future__ import absolute_import, print_function
24
25 import inspect
26 import os
27 import sys
24
28
25
29
26 def escape(s):
30 def escape(s):
@@ -95,7 +99,7 b' def docstrings(path):'
95 if mod.__doc__:
99 if mod.__doc__:
96 src = open(path).read()
100 src = open(path).read()
97 lineno = 1 + offset(src, mod.__doc__, path, 7)
101 lineno = 1 + offset(src, mod.__doc__, path, 7)
98 print poentry(path, lineno, mod.__doc__)
102 print(poentry(path, lineno, mod.__doc__))
99
103
100 functions = list(getattr(mod, 'i18nfunctions', []))
104 functions = list(getattr(mod, 'i18nfunctions', []))
101 functions = [(f, True) for f in functions]
105 functions = [(f, True) for f in functions]
@@ -115,12 +119,12 b' def docstrings(path):'
115 if rstrip:
119 if rstrip:
116 doc = doc.rstrip()
120 doc = doc.rstrip()
117 lineno += offset(src, doc, name, 1)
121 lineno += offset(src, doc, name, 1)
118 print poentry(path, lineno, doc)
122 print(poentry(path, lineno, doc))
119
123
120
124
121 def rawtext(path):
125 def rawtext(path):
122 src = open(path).read()
126 src = open(path).read()
123 print poentry(path, 1, src)
127 print(poentry(path, 1, src))
124
128
125
129
126 if __name__ == "__main__":
130 if __name__ == "__main__":
@@ -13,6 +13,8 b' modify entries, comments or metadata, et'
13 :func:`~polib.mofile` convenience functions.
13 :func:`~polib.mofile` convenience functions.
14 """
14 """
15
15
16 from __future__ import absolute_import
17
16 __author__ = 'David Jean Louis <izimobil@gmail.com>'
18 __author__ = 'David Jean Louis <izimobil@gmail.com>'
17 __version__ = '0.6.4'
19 __version__ = '0.6.4'
18 __all__ = ['pofile', 'POFile', 'POEntry', 'mofile', 'MOFile', 'MOEntry',
20 __all__ = ['pofile', 'POFile', 'POEntry', 'mofile', 'MOFile', 'MOEntry',
@@ -5,9 +5,11 b''
5 # license: MIT/X11/Expat
5 # license: MIT/X11/Expat
6 #
6 #
7
7
8 from __future__ import absolute_import, print_function
9
10 import polib
8 import re
11 import re
9 import sys
12 import sys
10 import polib
11
13
12 def addentry(po, entry, cache):
14 def addentry(po, entry, cache):
13 e = cache.get(entry.msgid)
15 e = cache.get(entry.msgid)
@@ -67,8 +69,8 b' if __name__ == "__main__":'
67 continue
69 continue
68 else:
70 else:
69 # lines following directly, unexpected
71 # lines following directly, unexpected
70 print 'Warning: text follows line with directive' \
72 print('Warning: text follows line with directive' \
71 ' %s' % directive
73 ' %s' % directive)
72 comment = 'do not translate: .. %s::' % directive
74 comment = 'do not translate: .. %s::' % directive
73 if not newentry.comment:
75 if not newentry.comment:
74 newentry.comment = comment
76 newentry.comment = comment
@@ -12,36 +12,13 b' import os'
12 import sys
12 import sys
13 import zipimport
13 import zipimport
14
14
15 from . import (
16 policy
17 )
18
15 __all__ = []
19 __all__ = []
16
20
17 # Rules for how modules can be loaded. Values are:
21 modulepolicy = policy.policy
18 #
19 # c - require C extensions
20 # allow - allow pure Python implementation when C loading fails
21 # py - only load pure Python modules
22 #
23 # By default, require the C extensions for performance reasons.
24 modulepolicy = 'c'
25 try:
26 from . import __modulepolicy__
27 modulepolicy = __modulepolicy__.modulepolicy
28 except ImportError:
29 pass
30
31 # PyPy doesn't load C extensions.
32 #
33 # The canonical way to do this is to test platform.python_implementation().
34 # But we don't import platform and don't bloat for it here.
35 if '__pypy__' in sys.builtin_module_names:
36 modulepolicy = 'py'
37
38 # Our C extensions aren't yet compatible with Python 3. So use pure Python
39 # on Python 3 for now.
40 if sys.version_info[0] >= 3:
41 modulepolicy = 'py'
42
43 # Environment variable can always force settings.
44 modulepolicy = os.environ.get('HGMODULEPOLICY', modulepolicy)
45
22
46 # Modules that have both Python and C implementations. See also the
23 # Modules that have both Python and C implementations. See also the
47 # set of .py files under mercurial/pure/.
24 # set of .py files under mercurial/pure/.
@@ -82,7 +59,7 b' class hgimporter(object):'
82 return zl
59 return zl
83
60
84 try:
61 try:
85 if modulepolicy == 'py':
62 if modulepolicy in policy.policynoc:
86 raise ImportError()
63 raise ImportError()
87
64
88 zl = ziploader('mercurial')
65 zl = ziploader('mercurial')
@@ -109,7 +86,7 b' class hgimporter(object):'
109 stem = name.split('.')[-1]
86 stem = name.split('.')[-1]
110
87
111 try:
88 try:
112 if modulepolicy == 'py':
89 if modulepolicy in policy.policynoc:
113 raise ImportError()
90 raise ImportError()
114
91
115 modinfo = imp.find_module(stem, mercurial.__path__)
92 modinfo = imp.find_module(stem, mercurial.__path__)
@@ -144,9 +121,238 b' class hgimporter(object):'
144 sys.modules[name] = mod
121 sys.modules[name] = mod
145 return mod
122 return mod
146
123
124 # Python 3 uses a custom module loader that transforms source code between
125 # source file reading and compilation. This is done by registering a custom
126 # finder that changes the spec for Mercurial modules to use a custom loader.
127 if sys.version_info[0] >= 3:
128 from . import pure
129 import importlib
130 import io
131 import token
132 import tokenize
133
134 class hgpathentryfinder(importlib.abc.MetaPathFinder):
135 """A sys.meta_path finder that uses a custom module loader."""
136 def find_spec(self, fullname, path, target=None):
137 # Only handle Mercurial-related modules.
138 if not fullname.startswith(('mercurial.', 'hgext.', 'hgext3rd.')):
139 return None
140
141 # This assumes Python 3 doesn't support loading C modules.
142 if fullname in _dualmodules:
143 stem = fullname.split('.')[-1]
144 fullname = 'mercurial.pure.%s' % stem
145 target = pure
146 assert len(path) == 1
147 path = [os.path.join(path[0], 'pure')]
148
149 # Try to find the module using other registered finders.
150 spec = None
151 for finder in sys.meta_path:
152 if finder == self:
153 continue
154
155 spec = finder.find_spec(fullname, path, target=target)
156 if spec:
157 break
158
159 # This is a Mercurial-related module but we couldn't find it
160 # using the previously-registered finders. This likely means
161 # the module doesn't exist.
162 if not spec:
163 return None
164
165 if fullname.startswith('mercurial.pure.'):
166 spec.name = spec.name.replace('.pure.', '.')
167
168 # TODO need to support loaders from alternate specs, like zip
169 # loaders.
170 spec.loader = hgloader(spec.name, spec.origin)
171 return spec
172
173 def replacetokens(tokens):
174 """Transform a stream of tokens from raw to Python 3.
175
176 It is called by the custom module loading machinery to rewrite
177 source/tokens between source decoding and compilation.
178
179 Returns a generator of possibly rewritten tokens.
180
181 The input token list may be mutated as part of processing. However,
182 its changes do not necessarily match the output token stream.
183
184 REMEMBER TO CHANGE ``BYTECODEHEADER`` WHEN CHANGING THIS FUNCTION
185 OR CACHED FILES WON'T GET INVALIDATED PROPERLY.
186 """
187 for i, t in enumerate(tokens):
188 # Convert most string literals to byte literals. String literals
189 # in Python 2 are bytes. String literals in Python 3 are unicode.
190 # Most strings in Mercurial are bytes and unicode strings are rare.
191 # Rather than rewrite all string literals to use ``b''`` to indicate
192 # byte strings, we apply this token transformer to insert the ``b``
193 # prefix nearly everywhere.
194 if t.type == token.STRING:
195 s = t.string
196
197 # Preserve docstrings as string literals. This is inconsistent
198 # with regular unprefixed strings. However, the
199 # "from __future__" parsing (which allows a module docstring to
200 # exist before it) doesn't properly handle the docstring if it
201 # is b''' prefixed, leading to a SyntaxError. We leave all
202 # docstrings as unprefixed to avoid this. This means Mercurial
203 # components touching docstrings need to handle unicode,
204 # unfortunately.
205 if s[0:3] in ("'''", '"""'):
206 yield t
207 continue
208
209 # If the first character isn't a quote, it is likely a string
210 # prefixing character (such as 'b', 'u', or 'r'. Ignore.
211 if s[0] not in ("'", '"'):
212 yield t
213 continue
214
215 # String literal. Prefix to make a b'' string.
216 yield tokenize.TokenInfo(t.type, 'b%s' % s, t.start, t.end,
217 t.line)
218 continue
219
220 try:
221 nexttoken = tokens[i + 1]
222 except IndexError:
223 nexttoken = None
224
225 try:
226 prevtoken = tokens[i - 1]
227 except IndexError:
228 prevtoken = None
229
230 # This looks like a function call.
231 if (t.type == token.NAME and nexttoken and
232 nexttoken.type == token.OP and nexttoken.string == '('):
233 fn = t.string
234
235 # *attr() builtins don't accept byte strings to 2nd argument.
236 # Rewrite the token to include the unicode literal prefix so
237 # the string transformer above doesn't add the byte prefix.
238 if fn in ('getattr', 'setattr', 'hasattr', 'safehasattr'):
239 try:
240 # (NAME, 'getattr')
241 # (OP, '(')
242 # (NAME, 'foo')
243 # (OP, ',')
244 # (NAME|STRING, foo)
245 st = tokens[i + 4]
246 if (st.type == token.STRING and
247 st.string[0] in ("'", '"')):
248 rt = tokenize.TokenInfo(st.type, 'u%s' % st.string,
249 st.start, st.end, st.line)
250 tokens[i + 4] = rt
251 except IndexError:
252 pass
253
254 # .encode() and .decode() on str/bytes/unicode don't accept
255 # byte strings on Python 3. Rewrite the token to include the
256 # unicode literal prefix so the string transformer above doesn't
257 # add the byte prefix.
258 if (fn in ('encode', 'decode') and
259 prevtoken.type == token.OP and prevtoken.string == '.'):
260 # (OP, '.')
261 # (NAME, 'encode')
262 # (OP, '(')
263 # (STRING, 'utf-8')
264 # (OP, ')')
265 try:
266 st = tokens[i + 2]
267 if (st.type == token.STRING and
268 st.string[0] in ("'", '"')):
269 rt = tokenize.TokenInfo(st.type, 'u%s' % st.string,
270 st.start, st.end, st.line)
271 tokens[i + 2] = rt
272 except IndexError:
273 pass
274
275 # Emit unmodified token.
276 yield t
277
278 # Header to add to bytecode files. This MUST be changed when
279 # ``replacetoken`` or any mechanism that changes semantics of module
280 # loading is changed. Otherwise cached bytecode may get loaded without
281 # the new transformation mechanisms applied.
282 BYTECODEHEADER = b'HG\x00\x01'
283
284 class hgloader(importlib.machinery.SourceFileLoader):
285 """Custom module loader that transforms source code.
286
287 When the source code is converted to a code object, we transform
288 certain patterns to be Python 3 compatible. This allows us to write code
289 that is natively Python 2 and compatible with Python 3 without
290 making the code excessively ugly.
291
292 We do this by transforming the token stream between parse and compile.
293
294 Implementing transformations invalidates caching assumptions made
295 by the built-in importer. The built-in importer stores a header on
296 saved bytecode files indicating the Python/bytecode version. If the
297 version changes, the cached bytecode is ignored. The Mercurial
298 transformations could change at any time. This means we need to check
299 that cached bytecode was generated with the current transformation
300 code or there could be a mismatch between cached bytecode and what
301 would be generated from this class.
302
303 We supplement the bytecode caching layer by wrapping ``get_data``
304 and ``set_data``. These functions are called when the
305 ``SourceFileLoader`` retrieves and saves bytecode cache files,
306 respectively. We simply add an additional header on the file. As
307 long as the version in this file is changed when semantics change,
308 cached bytecode should be invalidated when transformations change.
309
310 The added header has the form ``HG<VERSION>``. That is a literal
311 ``HG`` with 2 binary bytes indicating the transformation version.
312 """
313 def get_data(self, path):
314 data = super(hgloader, self).get_data(path)
315
316 if not path.endswith(tuple(importlib.machinery.BYTECODE_SUFFIXES)):
317 return data
318
319 # There should be a header indicating the Mercurial transformation
320 # version. If it doesn't exist or doesn't match the current version,
321 # we raise an OSError because that is what
322 # ``SourceFileLoader.get_code()`` expects when loading bytecode
323 # paths to indicate the cached file is "bad."
324 if data[0:2] != b'HG':
325 raise OSError('no hg header')
326 if data[0:4] != BYTECODEHEADER:
327 raise OSError('hg header version mismatch')
328
329 return data[4:]
330
331 def set_data(self, path, data, *args, **kwargs):
332 if path.endswith(tuple(importlib.machinery.BYTECODE_SUFFIXES)):
333 data = BYTECODEHEADER + data
334
335 return super(hgloader, self).set_data(path, data, *args, **kwargs)
336
337 def source_to_code(self, data, path):
338 """Perform token transformation before compilation."""
339 buf = io.BytesIO(data)
340 tokens = tokenize.tokenize(buf.readline)
341 data = tokenize.untokenize(replacetokens(list(tokens)))
342 # Python's built-in importer strips frames from exceptions raised
343 # for this code. Unfortunately, that mechanism isn't extensible
344 # and our frame will be blamed for the import failure. There
345 # are extremely hacky ways to do frame stripping. We haven't
346 # implemented them because they are very ugly.
347 return super(hgloader, self).source_to_code(data, path)
348
147 # We automagically register our custom importer as a side-effect of loading.
349 # We automagically register our custom importer as a side-effect of loading.
148 # This is necessary to ensure that any entry points are able to import
350 # This is necessary to ensure that any entry points are able to import
149 # mercurial.* modules without having to perform this registration themselves.
351 # mercurial.* modules without having to perform this registration themselves.
150 if not any(isinstance(x, hgimporter) for x in sys.meta_path):
352 if sys.version_info[0] >= 3:
353 _importercls = hgpathentryfinder
354 else:
355 _importercls = hgimporter
356 if not any(isinstance(x, _importercls) for x in sys.meta_path):
151 # meta_path is used before any implicit finders and before sys.path.
357 # meta_path is used before any implicit finders and before sys.path.
152 sys.meta_path.insert(0, hgimporter())
358 sys.meta_path.insert(0, _importercls())
@@ -291,7 +291,7 b' class lazyancestors(object):'
291 def __nonzero__(self):
291 def __nonzero__(self):
292 """False if the set is empty, True otherwise."""
292 """False if the set is empty, True otherwise."""
293 try:
293 try:
294 iter(self).next()
294 next(iter(self))
295 return True
295 return True
296 except StopIteration:
296 except StopIteration:
297 return False
297 return False
@@ -9,37 +9,25 b''
9 Based roughly on Python difflib
9 Based roughly on Python difflib
10 */
10 */
11
11
12 #define PY_SSIZE_T_CLEAN
13 #include <Python.h>
14 #include <stdlib.h>
12 #include <stdlib.h>
15 #include <string.h>
13 #include <string.h>
16 #include <limits.h>
14 #include <limits.h>
17
15
18 #include "util.h"
16 #include "compat.h"
19
17 #include "bitmanipulation.h"
20 struct line {
18 #include "bdiff.h"
21 int hash, n, e;
22 Py_ssize_t len;
23 const char *l;
24 };
25
19
26 struct pos {
20 struct pos {
27 int pos, len;
21 int pos, len;
28 };
22 };
29
23
30 struct hunk;
24 int bdiff_splitlines(const char *a, ssize_t len, struct bdiff_line **lr)
31 struct hunk {
32 int a1, a2, b1, b2;
33 struct hunk *next;
34 };
35
36 static int splitlines(const char *a, Py_ssize_t len, struct line **lr)
37 {
25 {
38 unsigned hash;
26 unsigned hash;
39 int i;
27 int i;
40 const char *p, *b = a;
28 const char *p, *b = a;
41 const char * const plast = a + len - 1;
29 const char * const plast = a + len - 1;
42 struct line *l;
30 struct bdiff_line *l;
43
31
44 /* count the lines */
32 /* count the lines */
45 i = 1; /* extra line for sentinel */
33 i = 1; /* extra line for sentinel */
@@ -47,7 +35,7 b' static int splitlines(const char *a, Py_'
47 if (*p == '\n' || p == plast)
35 if (*p == '\n' || p == plast)
48 i++;
36 i++;
49
37
50 *lr = l = (struct line *)malloc(sizeof(struct line) * i);
38 *lr = l = (struct bdiff_line *)malloc(sizeof(struct bdiff_line) * i);
51 if (!l)
39 if (!l)
52 return -1;
40 return -1;
53
41
@@ -75,12 +63,13 b' static int splitlines(const char *a, Py_'
75 return i - 1;
63 return i - 1;
76 }
64 }
77
65
78 static inline int cmp(struct line *a, struct line *b)
66 static inline int cmp(struct bdiff_line *a, struct bdiff_line *b)
79 {
67 {
80 return a->hash != b->hash || a->len != b->len || memcmp(a->l, b->l, a->len);
68 return a->hash != b->hash || a->len != b->len || memcmp(a->l, b->l, a->len);
81 }
69 }
82
70
83 static int equatelines(struct line *a, int an, struct line *b, int bn)
71 static int equatelines(struct bdiff_line *a, int an, struct bdiff_line *b,
72 int bn)
84 {
73 {
85 int i, j, buckets = 1, t, scale;
74 int i, j, buckets = 1, t, scale;
86 struct pos *h = NULL;
75 struct pos *h = NULL;
@@ -145,7 +134,8 b' static int equatelines(struct line *a, i'
145 return 1;
134 return 1;
146 }
135 }
147
136
148 static int longest_match(struct line *a, struct line *b, struct pos *pos,
137 static int longest_match(struct bdiff_line *a, struct bdiff_line *b,
138 struct pos *pos,
149 int a1, int a2, int b1, int b2, int *omi, int *omj)
139 int a1, int a2, int b1, int b2, int *omi, int *omj)
150 {
140 {
151 int mi = a1, mj = b1, mk = 0, i, j, k, half;
141 int mi = a1, mj = b1, mk = 0, i, j, k, half;
@@ -206,8 +196,9 b' static int longest_match(struct line *a,'
206 return mk;
196 return mk;
207 }
197 }
208
198
209 static struct hunk *recurse(struct line *a, struct line *b, struct pos *pos,
199 static struct bdiff_hunk *recurse(struct bdiff_line *a, struct bdiff_line *b,
210 int a1, int a2, int b1, int b2, struct hunk *l)
200 struct pos *pos,
201 int a1, int a2, int b1, int b2, struct bdiff_hunk *l)
211 {
202 {
212 int i, j, k;
203 int i, j, k;
213
204
@@ -222,7 +213,7 b' static struct hunk *recurse(struct line '
222 if (!l)
213 if (!l)
223 return NULL;
214 return NULL;
224
215
225 l->next = (struct hunk *)malloc(sizeof(struct hunk));
216 l->next = (struct bdiff_hunk *)malloc(sizeof(struct bdiff_hunk));
226 if (!l->next)
217 if (!l->next)
227 return NULL;
218 return NULL;
228
219
@@ -239,10 +230,10 b' static struct hunk *recurse(struct line '
239 }
230 }
240 }
231 }
241
232
242 static int diff(struct line *a, int an, struct line *b, int bn,
233 int bdiff_diff(struct bdiff_line *a, int an, struct bdiff_line *b,
243 struct hunk *base)
234 int bn, struct bdiff_hunk *base)
244 {
235 {
245 struct hunk *curr;
236 struct bdiff_hunk *curr;
246 struct pos *pos;
237 struct pos *pos;
247 int t, count = 0;
238 int t, count = 0;
248
239
@@ -258,7 +249,7 b' static int diff(struct line *a, int an, '
258 return -1;
249 return -1;
259
250
260 /* sentinel end hunk */
251 /* sentinel end hunk */
261 curr->next = (struct hunk *)malloc(sizeof(struct hunk));
252 curr->next = (struct bdiff_hunk *)malloc(sizeof(struct bdiff_hunk));
262 if (!curr->next)
253 if (!curr->next)
263 return -1;
254 return -1;
264 curr = curr->next;
255 curr = curr->next;
@@ -271,7 +262,7 b' static int diff(struct line *a, int an, '
271
262
272 /* normalize the hunk list, try to push each hunk towards the end */
263 /* normalize the hunk list, try to push each hunk towards the end */
273 for (curr = base->next; curr; curr = curr->next) {
264 for (curr = base->next; curr; curr = curr->next) {
274 struct hunk *next = curr->next;
265 struct bdiff_hunk *next = curr->next;
275
266
276 if (!next)
267 if (!next)
277 break;
268 break;
@@ -293,195 +284,13 b' static int diff(struct line *a, int an, '
293 return count;
284 return count;
294 }
285 }
295
286
296 static void freehunks(struct hunk *l)
287 void bdiff_freehunks(struct bdiff_hunk *l)
297 {
288 {
298 struct hunk *n;
289 struct bdiff_hunk *n;
299 for (; l; l = n) {
290 for (; l; l = n) {
300 n = l->next;
291 n = l->next;
301 free(l);
292 free(l);
302 }
293 }
303 }
294 }
304
295
305 static PyObject *blocks(PyObject *self, PyObject *args)
306 {
307 PyObject *sa, *sb, *rl = NULL, *m;
308 struct line *a, *b;
309 struct hunk l, *h;
310 int an, bn, count, pos = 0;
311
296
312 l.next = NULL;
313
314 if (!PyArg_ParseTuple(args, "SS:bdiff", &sa, &sb))
315 return NULL;
316
317 an = splitlines(PyBytes_AsString(sa), PyBytes_Size(sa), &a);
318 bn = splitlines(PyBytes_AsString(sb), PyBytes_Size(sb), &b);
319
320 if (!a || !b)
321 goto nomem;
322
323 count = diff(a, an, b, bn, &l);
324 if (count < 0)
325 goto nomem;
326
327 rl = PyList_New(count);
328 if (!rl)
329 goto nomem;
330
331 for (h = l.next; h; h = h->next) {
332 m = Py_BuildValue("iiii", h->a1, h->a2, h->b1, h->b2);
333 PyList_SetItem(rl, pos, m);
334 pos++;
335 }
336
337 nomem:
338 free(a);
339 free(b);
340 freehunks(l.next);
341 return rl ? rl : PyErr_NoMemory();
342 }
343
344 static PyObject *bdiff(PyObject *self, PyObject *args)
345 {
346 char *sa, *sb, *rb;
347 PyObject *result = NULL;
348 struct line *al, *bl;
349 struct hunk l, *h;
350 int an, bn, count;
351 Py_ssize_t len = 0, la, lb;
352 PyThreadState *_save;
353
354 l.next = NULL;
355
356 if (!PyArg_ParseTuple(args, "s#s#:bdiff", &sa, &la, &sb, &lb))
357 return NULL;
358
359 if (la > UINT_MAX || lb > UINT_MAX) {
360 PyErr_SetString(PyExc_ValueError, "bdiff inputs too large");
361 return NULL;
362 }
363
364 _save = PyEval_SaveThread();
365 an = splitlines(sa, la, &al);
366 bn = splitlines(sb, lb, &bl);
367 if (!al || !bl)
368 goto nomem;
369
370 count = diff(al, an, bl, bn, &l);
371 if (count < 0)
372 goto nomem;
373
374 /* calculate length of output */
375 la = lb = 0;
376 for (h = l.next; h; h = h->next) {
377 if (h->a1 != la || h->b1 != lb)
378 len += 12 + bl[h->b1].l - bl[lb].l;
379 la = h->a2;
380 lb = h->b2;
381 }
382 PyEval_RestoreThread(_save);
383 _save = NULL;
384
385 result = PyBytes_FromStringAndSize(NULL, len);
386
387 if (!result)
388 goto nomem;
389
390 /* build binary patch */
391 rb = PyBytes_AsString(result);
392 la = lb = 0;
393
394 for (h = l.next; h; h = h->next) {
395 if (h->a1 != la || h->b1 != lb) {
396 len = bl[h->b1].l - bl[lb].l;
397 putbe32((uint32_t)(al[la].l - al->l), rb);
398 putbe32((uint32_t)(al[h->a1].l - al->l), rb + 4);
399 putbe32((uint32_t)len, rb + 8);
400 memcpy(rb + 12, bl[lb].l, len);
401 rb += 12 + len;
402 }
403 la = h->a2;
404 lb = h->b2;
405 }
406
407 nomem:
408 if (_save)
409 PyEval_RestoreThread(_save);
410 free(al);
411 free(bl);
412 freehunks(l.next);
413 return result ? result : PyErr_NoMemory();
414 }
415
416 /*
417 * If allws != 0, remove all whitespace (' ', \t and \r). Otherwise,
418 * reduce whitespace sequences to a single space and trim remaining whitespace
419 * from end of lines.
420 */
421 static PyObject *fixws(PyObject *self, PyObject *args)
422 {
423 PyObject *s, *result = NULL;
424 char allws, c;
425 const char *r;
426 Py_ssize_t i, rlen, wlen = 0;
427 char *w;
428
429 if (!PyArg_ParseTuple(args, "Sb:fixws", &s, &allws))
430 return NULL;
431 r = PyBytes_AsString(s);
432 rlen = PyBytes_Size(s);
433
434 w = (char *)malloc(rlen ? rlen : 1);
435 if (!w)
436 goto nomem;
437
438 for (i = 0; i != rlen; i++) {
439 c = r[i];
440 if (c == ' ' || c == '\t' || c == '\r') {
441 if (!allws && (wlen == 0 || w[wlen - 1] != ' '))
442 w[wlen++] = ' ';
443 } else if (c == '\n' && !allws
444 && wlen > 0 && w[wlen - 1] == ' ') {
445 w[wlen - 1] = '\n';
446 } else {
447 w[wlen++] = c;
448 }
449 }
450
451 result = PyBytes_FromStringAndSize(w, wlen);
452
453 nomem:
454 free(w);
455 return result ? result : PyErr_NoMemory();
456 }
457
458
459 static char mdiff_doc[] = "Efficient binary diff.";
460
461 static PyMethodDef methods[] = {
462 {"bdiff", bdiff, METH_VARARGS, "calculate a binary diff\n"},
463 {"blocks", blocks, METH_VARARGS, "find a list of matching lines\n"},
464 {"fixws", fixws, METH_VARARGS, "normalize diff whitespaces\n"},
465 {NULL, NULL}
466 };
467
468 #ifdef IS_PY3K
469 static struct PyModuleDef bdiff_module = {
470 PyModuleDef_HEAD_INIT,
471 "bdiff",
472 mdiff_doc,
473 -1,
474 methods
475 };
476
477 PyMODINIT_FUNC PyInit_bdiff(void)
478 {
479 return PyModule_Create(&bdiff_module);
480 }
481 #else
482 PyMODINIT_FUNC initbdiff(void)
483 {
484 Py_InitModule3("bdiff", methods, mdiff_doc);
485 }
486 #endif
487
@@ -17,6 +17,7 b' from .node import ('
17 )
17 )
18 from . import (
18 from . import (
19 encoding,
19 encoding,
20 error,
20 lock as lockmod,
21 lock as lockmod,
21 obsolete,
22 obsolete,
22 util,
23 util,
@@ -109,39 +110,6 b' class bmstore(dict):'
109 location='plain')
110 location='plain')
110 tr.hookargs['bookmark_moved'] = '1'
111 tr.hookargs['bookmark_moved'] = '1'
111
112
112 def write(self):
113 '''Write bookmarks
114
115 Write the given bookmark => hash dictionary to the .hg/bookmarks file
116 in a format equal to those of localtags.
117
118 We also store a backup of the previous state in undo.bookmarks that
119 can be copied back on rollback.
120 '''
121 msg = 'bm.write() is deprecated, use bm.recordchange(transaction)'
122 self._repo.ui.deprecwarn(msg, '3.7')
123 # TODO: writing the active bookmark should probably also use a
124 # transaction.
125 self._writeactive()
126 if self._clean:
127 return
128 repo = self._repo
129 if (repo.ui.configbool('devel', 'all-warnings')
130 or repo.ui.configbool('devel', 'check-locks')):
131 l = repo._wlockref and repo._wlockref()
132 if l is None or not l.held:
133 repo.ui.develwarn('bookmarks write with no wlock')
134
135 tr = repo.currenttransaction()
136 if tr:
137 self.recordchange(tr)
138 # invalidatevolatilesets() is omitted because this doesn't
139 # write changes out actually
140 return
141
142 self._writerepo(repo)
143 repo.invalidatevolatilesets()
144
145 def _writerepo(self, repo):
113 def _writerepo(self, repo):
146 """Factored out for extensibility"""
114 """Factored out for extensibility"""
147 rbm = repo._bookmarks
115 rbm = repo._bookmarks
@@ -150,7 +118,8 b' class bmstore(dict):'
150 rbm._writeactive()
118 rbm._writeactive()
151
119
152 with repo.wlock():
120 with repo.wlock():
153 file_ = repo.vfs('bookmarks', 'w', atomictemp=True)
121 file_ = repo.vfs('bookmarks', 'w', atomictemp=True,
122 checkambig=True)
154 try:
123 try:
155 self._write(file_)
124 self._write(file_)
156 except: # re-raises
125 except: # re-raises
@@ -164,7 +133,8 b' class bmstore(dict):'
164 return
133 return
165 with self._repo.wlock():
134 with self._repo.wlock():
166 if self._active is not None:
135 if self._active is not None:
167 f = self._repo.vfs('bookmarks.current', 'w', atomictemp=True)
136 f = self._repo.vfs('bookmarks.current', 'w', atomictemp=True,
137 checkambig=True)
168 try:
138 try:
169 f.write(encoding.fromlocal(self._active))
139 f.write(encoding.fromlocal(self._active))
170 finally:
140 finally:
@@ -185,7 +155,10 b' class bmstore(dict):'
185
155
186 def expandname(self, bname):
156 def expandname(self, bname):
187 if bname == '.':
157 if bname == '.':
188 return self.active
158 if self.active:
159 return self.active
160 else:
161 raise error.Abort(_("no active bookmark"))
189 return bname
162 return bname
190
163
191 def _readactive(repo, marks):
164 def _readactive(repo, marks):
@@ -363,7 +363,7 b' class revbranchcache(object):'
363 bndata = repo.vfs.read(_rbcnames)
363 bndata = repo.vfs.read(_rbcnames)
364 self._rbcsnameslen = len(bndata) # for verification before writing
364 self._rbcsnameslen = len(bndata) # for verification before writing
365 self._names = [encoding.tolocal(bn) for bn in bndata.split('\0')]
365 self._names = [encoding.tolocal(bn) for bn in bndata.split('\0')]
366 except (IOError, OSError) as inst:
366 except (IOError, OSError):
367 if readonly:
367 if readonly:
368 # don't try to use cache - fall back to the slow path
368 # don't try to use cache - fall back to the slow path
369 self.branchinfo = self._branchinfo
369 self.branchinfo = self._branchinfo
@@ -402,10 +402,9 b' class revbranchcache(object):'
402 if rev == nullrev:
402 if rev == nullrev:
403 return changelog.branchinfo(rev)
403 return changelog.branchinfo(rev)
404
404
405 # if requested rev is missing, add and populate all missing revs
405 # if requested rev isn't allocated, grow and cache the rev info
406 if len(self._rbcrevs) < rbcrevidx + _rbcrecsize:
406 if len(self._rbcrevs) < rbcrevidx + _rbcrecsize:
407 self._rbcrevs.extend('\0' * (len(changelog) * _rbcrecsize -
407 return self._branchinfo(rev)
408 len(self._rbcrevs)))
409
408
410 # fast path: extract data from cache, use it if node is matching
409 # fast path: extract data from cache, use it if node is matching
411 reponode = changelog.node(rev)[:_rbcnodelen]
410 reponode = changelog.node(rev)[:_rbcnodelen]
@@ -452,6 +451,10 b' class revbranchcache(object):'
452 rbcrevidx = rev * _rbcrecsize
451 rbcrevidx = rev * _rbcrecsize
453 rec = array('c')
452 rec = array('c')
454 rec.fromstring(pack(_rbcrecfmt, node, branchidx))
453 rec.fromstring(pack(_rbcrecfmt, node, branchidx))
454 if len(self._rbcrevs) < rbcrevidx + _rbcrecsize:
455 self._rbcrevs.extend('\0' *
456 (len(self._repo.changelog) * _rbcrecsize -
457 len(self._rbcrevs)))
455 self._rbcrevs[rbcrevidx:rbcrevidx + _rbcrecsize] = rec
458 self._rbcrevs[rbcrevidx:rbcrevidx + _rbcrecsize] = rec
456 self._rbcrevslen = min(self._rbcrevslen, rev)
459 self._rbcrevslen = min(self._rbcrevslen, rev)
457
460
@@ -690,7 +690,7 b' class unbundle20(unpackermixin):'
690
690
691 def _processallparams(self, paramsblock):
691 def _processallparams(self, paramsblock):
692 """"""
692 """"""
693 params = {}
693 params = util.sortdict()
694 for p in paramsblock.split(' '):
694 for p in paramsblock.split(' '):
695 p = p.split('=', 1)
695 p = p.split('=', 1)
696 p = [urlreq.unquote(i) for i in p]
696 p = [urlreq.unquote(i) for i in p]
@@ -1115,8 +1115,8 b' class unbundlepart(unpackermixin):'
1115 self.mandatoryparams = tuple(mandatoryparams)
1115 self.mandatoryparams = tuple(mandatoryparams)
1116 self.advisoryparams = tuple(advisoryparams)
1116 self.advisoryparams = tuple(advisoryparams)
1117 # user friendly UI
1117 # user friendly UI
1118 self.params = dict(self.mandatoryparams)
1118 self.params = util.sortdict(self.mandatoryparams)
1119 self.params.update(dict(self.advisoryparams))
1119 self.params.update(self.advisoryparams)
1120 self.mandatorykeys = frozenset(p[0] for p in mandatoryparams)
1120 self.mandatorykeys = frozenset(p[0] for p in mandatoryparams)
1121
1121
1122 def _payloadchunks(self, chunknum=0):
1122 def _payloadchunks(self, chunknum=0):
@@ -1294,6 +1294,9 b' def writebundle(ui, cg, filename, bundle'
1294 bundle.setcompression(compression)
1294 bundle.setcompression(compression)
1295 part = bundle.newpart('changegroup', data=cg.getchunks())
1295 part = bundle.newpart('changegroup', data=cg.getchunks())
1296 part.addparam('version', cg.version)
1296 part.addparam('version', cg.version)
1297 if 'clcount' in cg.extras:
1298 part.addparam('nbchanges', str(cg.extras['clcount']),
1299 mandatory=False)
1297 chunkiter = bundle.getchunks()
1300 chunkiter = bundle.getchunks()
1298 else:
1301 else:
1299 # compression argument is only for the bundle2 case
1302 # compression argument is only for the bundle2 case
@@ -291,7 +291,7 b' class bundlerepository(localrepo.localre'
291 ".cg%sun" % version)
291 ".cg%sun" % version)
292
292
293 if cgstream is None:
293 if cgstream is None:
294 raise error.Abort('No changegroups found')
294 raise error.Abort(_('No changegroups found'))
295 cgstream.seek(0)
295 cgstream.seek(0)
296
296
297 self.bundle = changegroup.getunbundler(version, cgstream, 'UN')
297 self.bundle = changegroup.getunbundler(version, cgstream, 'UN')
@@ -135,7 +135,7 b' class cg1unpacker(object):'
135 version = '01'
135 version = '01'
136 _grouplistcount = 1 # One list of files after the manifests
136 _grouplistcount = 1 # One list of files after the manifests
137
137
138 def __init__(self, fh, alg):
138 def __init__(self, fh, alg, extras=None):
139 if alg == 'UN':
139 if alg == 'UN':
140 alg = None # get more modern without breaking too much
140 alg = None # get more modern without breaking too much
141 if not alg in util.decompressors:
141 if not alg in util.decompressors:
@@ -145,6 +145,7 b' class cg1unpacker(object):'
145 alg = '_truncatedBZ'
145 alg = '_truncatedBZ'
146 self._stream = util.decompressors[alg](fh)
146 self._stream = util.decompressors[alg](fh)
147 self._type = alg
147 self._type = alg
148 self.extras = extras or {}
148 self.callback = None
149 self.callback = None
149
150
150 # These methods (compressed, read, seek, tell) all appear to only
151 # These methods (compressed, read, seek, tell) all appear to only
@@ -530,6 +531,17 b' class cg1packer(object):'
530 def fileheader(self, fname):
531 def fileheader(self, fname):
531 return chunkheader(len(fname)) + fname
532 return chunkheader(len(fname)) + fname
532
533
534 # Extracted both for clarity and for overriding in extensions.
535 def _sortgroup(self, revlog, nodelist, lookup):
536 """Sort nodes for change group and turn them into revnums."""
537 # for generaldelta revlogs, we linearize the revs; this will both be
538 # much quicker and generate a much smaller bundle
539 if (revlog._generaldelta and self._reorder is None) or self._reorder:
540 dag = dagutil.revlogdag(revlog)
541 return dag.linearize(set(revlog.rev(n) for n in nodelist))
542 else:
543 return sorted([revlog.rev(n) for n in nodelist])
544
533 def group(self, nodelist, revlog, lookup, units=None):
545 def group(self, nodelist, revlog, lookup, units=None):
534 """Calculate a delta group, yielding a sequence of changegroup chunks
546 """Calculate a delta group, yielding a sequence of changegroup chunks
535 (strings).
547 (strings).
@@ -549,14 +561,7 b' class cg1packer(object):'
549 yield self.close()
561 yield self.close()
550 return
562 return
551
563
552 # for generaldelta revlogs, we linearize the revs; this will both be
564 revs = self._sortgroup(revlog, nodelist, lookup)
553 # much quicker and generate a much smaller bundle
554 if (revlog._generaldelta and self._reorder is None) or self._reorder:
555 dag = dagutil.revlogdag(revlog)
556 revs = set(revlog.rev(n) for n in nodelist)
557 revs = dag.linearize(revs)
558 else:
559 revs = sorted([revlog.rev(n) for n in nodelist])
560
565
561 # add the parent of the first rev
566 # add the parent of the first rev
562 p = revlog.parentrevs(revs[0])[0]
567 p = revlog.parentrevs(revs[0])[0]
@@ -724,10 +729,11 b' class cg1packer(object):'
724 dir = min(tmfnodes)
729 dir = min(tmfnodes)
725 nodes = tmfnodes[dir]
730 nodes = tmfnodes[dir]
726 prunednodes = self.prune(dirlog(dir), nodes, commonrevs)
731 prunednodes = self.prune(dirlog(dir), nodes, commonrevs)
727 for x in self._packmanifests(dir, prunednodes,
732 if not dir or prunednodes:
728 makelookupmflinknode(dir)):
733 for x in self._packmanifests(dir, prunednodes,
729 size += len(x)
734 makelookupmflinknode(dir)):
730 yield x
735 size += len(x)
736 yield x
731 del tmfnodes[dir]
737 del tmfnodes[dir]
732 self._verbosenote(_('%8.i (manifests)\n') % size)
738 self._verbosenote(_('%8.i (manifests)\n') % size)
733 yield self._manifestsdone()
739 yield self._manifestsdone()
@@ -895,8 +901,8 b' def getbundler(version, repo, bundlecaps'
895 assert version in supportedoutgoingversions(repo)
901 assert version in supportedoutgoingversions(repo)
896 return _packermap[version][0](repo, bundlecaps)
902 return _packermap[version][0](repo, bundlecaps)
897
903
898 def getunbundler(version, fh, alg):
904 def getunbundler(version, fh, alg, extras=None):
899 return _packermap[version][1](fh, alg)
905 return _packermap[version][1](fh, alg, extras=extras)
900
906
901 def _changegroupinfo(repo, nodes, source):
907 def _changegroupinfo(repo, nodes, source):
902 if repo.ui.verbose or source == 'bundle':
908 if repo.ui.verbose or source == 'bundle':
@@ -924,7 +930,8 b' def getsubsetraw(repo, outgoing, bundler'
924
930
925 def getsubset(repo, outgoing, bundler, source, fastpath=False):
931 def getsubset(repo, outgoing, bundler, source, fastpath=False):
926 gengroup = getsubsetraw(repo, outgoing, bundler, source, fastpath)
932 gengroup = getsubsetraw(repo, outgoing, bundler, source, fastpath)
927 return getunbundler(bundler.version, util.chunkbuffer(gengroup), None)
933 return getunbundler(bundler.version, util.chunkbuffer(gengroup), None,
934 {'clcount': len(outgoing.missing)})
928
935
929 def changegroupsubset(repo, roots, heads, source, version='01'):
936 def changegroupsubset(repo, roots, heads, source, version='01'):
930 """Compute a changegroup consisting of all the nodes that are
937 """Compute a changegroup consisting of all the nodes that are
@@ -83,7 +83,7 b' def filterchunks(ui, originalhunks, usec'
83 else:
83 else:
84 recordfn = crecordmod.chunkselector
84 recordfn = crecordmod.chunkselector
85
85
86 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
86 return crecordmod.filterpatch(ui, originalhunks, recordfn)
87
87
88 else:
88 else:
89 return patch.filterpatch(ui, originalhunks, operation)
89 return patch.filterpatch(ui, originalhunks, operation)
@@ -91,9 +91,9 b' def filterchunks(ui, originalhunks, usec'
91 def recordfilter(ui, originalhunks, operation=None):
91 def recordfilter(ui, originalhunks, operation=None):
92 """ Prompts the user to filter the originalhunks and return a list of
92 """ Prompts the user to filter the originalhunks and return a list of
93 selected hunks.
93 selected hunks.
94 *operation* is used for ui purposes to indicate the user
94 *operation* is used for to build ui messages to indicate the user what
95 what kind of filtering they are doing: reverting, committing, shelving, etc.
95 kind of filtering they are doing: reverting, committing, shelving, etc.
96 *operation* has to be a translated string.
96 (see patch.filterpatch).
97 """
97 """
98 usecurses = crecordmod.checkcurses(ui)
98 usecurses = crecordmod.checkcurses(ui)
99 testfile = ui.config('experimental', 'crecordtest', None)
99 testfile = ui.config('experimental', 'crecordtest', None)
@@ -532,7 +532,7 b' def openrevlog(repo, cmd, file_, opts):'
532 msg = _('cannot specify --changelog and --manifest at the same time')
532 msg = _('cannot specify --changelog and --manifest at the same time')
533 elif cl and dir:
533 elif cl and dir:
534 msg = _('cannot specify --changelog and --dir at the same time')
534 msg = _('cannot specify --changelog and --dir at the same time')
535 elif cl or mf:
535 elif cl or mf or dir:
536 if file_:
536 if file_:
537 msg = _('cannot specify filename with --changelog or --manifest')
537 msg = _('cannot specify filename with --changelog or --manifest')
538 elif not repo:
538 elif not repo:
@@ -549,7 +549,7 b' def openrevlog(repo, cmd, file_, opts):'
549 if 'treemanifest' not in repo.requirements:
549 if 'treemanifest' not in repo.requirements:
550 raise error.Abort(_("--dir can only be used on repos with "
550 raise error.Abort(_("--dir can only be used on repos with "
551 "treemanifest enabled"))
551 "treemanifest enabled"))
552 dirlog = repo.dirlog(file_)
552 dirlog = repo.dirlog(dir)
553 if len(dirlog):
553 if len(dirlog):
554 r = dirlog
554 r = dirlog
555 elif mf:
555 elif mf:
@@ -1405,24 +1405,24 b' class jsonchangeset(changeset_printer):'
1405 self.ui.write(",\n {")
1405 self.ui.write(",\n {")
1406
1406
1407 if self.ui.quiet:
1407 if self.ui.quiet:
1408 self.ui.write('\n "rev": %s' % jrev)
1408 self.ui.write(('\n "rev": %s') % jrev)
1409 self.ui.write(',\n "node": %s' % jnode)
1409 self.ui.write((',\n "node": %s') % jnode)
1410 self.ui.write('\n }')
1410 self.ui.write('\n }')
1411 return
1411 return
1412
1412
1413 self.ui.write('\n "rev": %s' % jrev)
1413 self.ui.write(('\n "rev": %s') % jrev)
1414 self.ui.write(',\n "node": %s' % jnode)
1414 self.ui.write((',\n "node": %s') % jnode)
1415 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1415 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1416 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1416 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1417 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1417 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1418 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1418 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1419 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1419 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1420
1420
1421 self.ui.write(',\n "bookmarks": [%s]' %
1421 self.ui.write((',\n "bookmarks": [%s]') %
1422 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1422 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1423 self.ui.write(',\n "tags": [%s]' %
1423 self.ui.write((',\n "tags": [%s]') %
1424 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1424 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1425 self.ui.write(',\n "parents": [%s]' %
1425 self.ui.write((',\n "parents": [%s]') %
1426 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1426 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1427
1427
1428 if self.ui.debugflag:
1428 if self.ui.debugflag:
@@ -1430,26 +1430,26 b' class jsonchangeset(changeset_printer):'
1430 jmanifestnode = 'null'
1430 jmanifestnode = 'null'
1431 else:
1431 else:
1432 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1432 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1433 self.ui.write(',\n "manifest": %s' % jmanifestnode)
1433 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1434
1434
1435 self.ui.write(',\n "extra": {%s}' %
1435 self.ui.write((',\n "extra": {%s}') %
1436 ", ".join('"%s": "%s"' % (j(k), j(v))
1436 ", ".join('"%s": "%s"' % (j(k), j(v))
1437 for k, v in ctx.extra().items()))
1437 for k, v in ctx.extra().items()))
1438
1438
1439 files = ctx.p1().status(ctx)
1439 files = ctx.p1().status(ctx)
1440 self.ui.write(',\n "modified": [%s]' %
1440 self.ui.write((',\n "modified": [%s]') %
1441 ", ".join('"%s"' % j(f) for f in files[0]))
1441 ", ".join('"%s"' % j(f) for f in files[0]))
1442 self.ui.write(',\n "added": [%s]' %
1442 self.ui.write((',\n "added": [%s]') %
1443 ", ".join('"%s"' % j(f) for f in files[1]))
1443 ", ".join('"%s"' % j(f) for f in files[1]))
1444 self.ui.write(',\n "removed": [%s]' %
1444 self.ui.write((',\n "removed": [%s]') %
1445 ", ".join('"%s"' % j(f) for f in files[2]))
1445 ", ".join('"%s"' % j(f) for f in files[2]))
1446
1446
1447 elif self.ui.verbose:
1447 elif self.ui.verbose:
1448 self.ui.write(',\n "files": [%s]' %
1448 self.ui.write((',\n "files": [%s]') %
1449 ", ".join('"%s"' % j(f) for f in ctx.files()))
1449 ", ".join('"%s"' % j(f) for f in ctx.files()))
1450
1450
1451 if copies:
1451 if copies:
1452 self.ui.write(',\n "copies": {%s}' %
1452 self.ui.write((',\n "copies": {%s}') %
1453 ", ".join('"%s": "%s"' % (j(k), j(v))
1453 ", ".join('"%s": "%s"' % (j(k), j(v))
1454 for k, v in copies))
1454 for k, v in copies))
1455
1455
@@ -1463,12 +1463,13 b' class jsonchangeset(changeset_printer):'
1463 self.ui.pushbuffer()
1463 self.ui.pushbuffer()
1464 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1464 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1465 match=matchfn, stat=True)
1465 match=matchfn, stat=True)
1466 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1466 self.ui.write((',\n "diffstat": "%s"')
1467 % j(self.ui.popbuffer()))
1467 if diff:
1468 if diff:
1468 self.ui.pushbuffer()
1469 self.ui.pushbuffer()
1469 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1470 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1470 match=matchfn, stat=False)
1471 match=matchfn, stat=False)
1471 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1472 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1472
1473
1473 self.ui.write("\n }")
1474 self.ui.write("\n }")
1474
1475
@@ -1998,7 +1999,7 b' def _makelogrevset(repo, pats, opts, rev'
1998 followfirst = 0
1999 followfirst = 0
1999 # --follow with FILE behavior depends on revs...
2000 # --follow with FILE behavior depends on revs...
2000 it = iter(revs)
2001 it = iter(revs)
2001 startrev = it.next()
2002 startrev = next(it)
2002 followdescendants = startrev < next(it, startrev)
2003 followdescendants = startrev < next(it, startrev)
2003
2004
2004 # branch and only_branch are really aliases and must be handled at
2005 # branch and only_branch are really aliases and must be handled at
@@ -2147,7 +2148,8 b' def getgraphlogrevs(repo, pats, opts):'
2147 if opts.get('rev'):
2148 if opts.get('rev'):
2148 # User-specified revs might be unsorted, but don't sort before
2149 # User-specified revs might be unsorted, but don't sort before
2149 # _makelogrevset because it might depend on the order of revs
2150 # _makelogrevset because it might depend on the order of revs
2150 revs.sort(reverse=True)
2151 if not (revs.isdescending() or revs.istopo()):
2152 revs.sort(reverse=True)
2151 if expr:
2153 if expr:
2152 # Revset matchers often operate faster on revisions in changelog
2154 # Revset matchers often operate faster on revisions in changelog
2153 # order, because most filters deal with the changelog.
2155 # order, because most filters deal with the changelog.
@@ -3071,7 +3073,7 b' def revert(ui, repo, ctx, parents, *pats'
3071
3073
3072 # tell newly modified apart.
3074 # tell newly modified apart.
3073 dsmodified &= modified
3075 dsmodified &= modified
3074 dsmodified |= modified & dsadded # dirstate added may needs backup
3076 dsmodified |= modified & dsadded # dirstate added may need backup
3075 modified -= dsmodified
3077 modified -= dsmodified
3076
3078
3077 # We need to wait for some post-processing to update this set
3079 # We need to wait for some post-processing to update this set
@@ -3141,11 +3143,17 b' def revert(ui, repo, ctx, parents, *pats'
3141 # All set to `discard` if `no-backup` is set do avoid checking
3143 # All set to `discard` if `no-backup` is set do avoid checking
3142 # no_backup lower in the code.
3144 # no_backup lower in the code.
3143 # These values are ordered for comparison purposes
3145 # These values are ordered for comparison purposes
3146 backupinteractive = 3 # do backup if interactively modified
3144 backup = 2 # unconditionally do backup
3147 backup = 2 # unconditionally do backup
3145 check = 1 # check if the existing file differs from target
3148 check = 1 # check if the existing file differs from target
3146 discard = 0 # never do backup
3149 discard = 0 # never do backup
3147 if opts.get('no_backup'):
3150 if opts.get('no_backup'):
3148 backup = check = discard
3151 backupinteractive = backup = check = discard
3152 if interactive:
3153 dsmodifiedbackup = backupinteractive
3154 else:
3155 dsmodifiedbackup = backup
3156 tobackup = set()
3149
3157
3150 backupanddel = actions['remove']
3158 backupanddel = actions['remove']
3151 if not opts.get('no_backup'):
3159 if not opts.get('no_backup'):
@@ -3163,7 +3171,7 b' def revert(ui, repo, ctx, parents, *pats'
3163 # Modified compared to target, but local file is deleted
3171 # Modified compared to target, but local file is deleted
3164 (deleted, actions['revert'], discard),
3172 (deleted, actions['revert'], discard),
3165 # Modified compared to target, local change
3173 # Modified compared to target, local change
3166 (dsmodified, actions['revert'], backup),
3174 (dsmodified, actions['revert'], dsmodifiedbackup),
3167 # Added since target
3175 # Added since target
3168 (added, actions['remove'], discard),
3176 (added, actions['remove'], discard),
3169 # Added in working directory
3177 # Added in working directory
@@ -3198,8 +3206,12 b' def revert(ui, repo, ctx, parents, *pats'
3198 continue
3206 continue
3199 if xlist is not None:
3207 if xlist is not None:
3200 xlist.append(abs)
3208 xlist.append(abs)
3201 if dobackup and (backup <= dobackup
3209 if dobackup:
3202 or wctx[abs].cmp(ctx[abs])):
3210 # If in interactive mode, don't automatically create
3211 # .orig files (issue4793)
3212 if dobackup == backupinteractive:
3213 tobackup.add(abs)
3214 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3203 bakname = scmutil.origpath(ui, repo, rel)
3215 bakname = scmutil.origpath(ui, repo, rel)
3204 ui.note(_('saving current version of %s as %s\n') %
3216 ui.note(_('saving current version of %s as %s\n') %
3205 (rel, bakname))
3217 (rel, bakname))
@@ -3219,7 +3231,7 b' def revert(ui, repo, ctx, parents, *pats'
3219 if not opts.get('dry_run'):
3231 if not opts.get('dry_run'):
3220 needdata = ('revert', 'add', 'undelete')
3232 needdata = ('revert', 'add', 'undelete')
3221 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3233 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3222 _performrevert(repo, parents, ctx, actions, interactive)
3234 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3223
3235
3224 if targetsubs:
3236 if targetsubs:
3225 # Revert the subrepos on the revert list
3237 # Revert the subrepos on the revert list
@@ -3234,7 +3246,8 b' def _revertprefetch(repo, ctx, *files):'
3234 """Let extension changing the storage layer prefetch content"""
3246 """Let extension changing the storage layer prefetch content"""
3235 pass
3247 pass
3236
3248
3237 def _performrevert(repo, parents, ctx, actions, interactive=False):
3249 def _performrevert(repo, parents, ctx, actions, interactive=False,
3250 tobackup=None):
3238 """function that actually perform all the actions computed for revert
3251 """function that actually perform all the actions computed for revert
3239
3252
3240 This is an independent function to let extension to plug in and react to
3253 This is an independent function to let extension to plug in and react to
@@ -3301,10 +3314,12 b' def _performrevert(repo, parents, ctx, a'
3301 else:
3314 else:
3302 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3315 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3303 originalchunks = patch.parsepatch(diff)
3316 originalchunks = patch.parsepatch(diff)
3317 operation = 'discard' if node == parent else 'revert'
3304
3318
3305 try:
3319 try:
3306
3320
3307 chunks, opts = recordfilter(repo.ui, originalchunks)
3321 chunks, opts = recordfilter(repo.ui, originalchunks,
3322 operation=operation)
3308 if reversehunks:
3323 if reversehunks:
3309 chunks = patch.reversehunks(chunks)
3324 chunks = patch.reversehunks(chunks)
3310
3325
@@ -3312,9 +3327,18 b' def _performrevert(repo, parents, ctx, a'
3312 raise error.Abort(_('error parsing patch: %s') % err)
3327 raise error.Abort(_('error parsing patch: %s') % err)
3313
3328
3314 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3329 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3330 if tobackup is None:
3331 tobackup = set()
3315 # Apply changes
3332 # Apply changes
3316 fp = stringio()
3333 fp = stringio()
3317 for c in chunks:
3334 for c in chunks:
3335 # Create a backup file only if this hunk should be backed up
3336 if ishunk(c) and c.header.filename() in tobackup:
3337 abs = c.header.filename()
3338 target = repo.wjoin(abs)
3339 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3340 util.copyfile(target, bakname)
3341 tobackup.remove(abs)
3318 c.write(fp)
3342 c.write(fp)
3319 dopatch = fp.tell()
3343 dopatch = fp.tell()
3320 fp.seek(0)
3344 fp.seek(0)
@@ -3518,7 +3542,7 b' class dirstateguard(object):'
3518 def __init__(self, repo, name):
3542 def __init__(self, repo, name):
3519 self._repo = repo
3543 self._repo = repo
3520 self._suffix = '.backup.%s.%d' % (name, id(self))
3544 self._suffix = '.backup.%s.%d' % (name, id(self))
3521 repo.dirstate._savebackup(repo.currenttransaction(), self._suffix)
3545 repo.dirstate.savebackup(repo.currenttransaction(), self._suffix)
3522 self._active = True
3546 self._active = True
3523 self._closed = False
3547 self._closed = False
3524
3548
@@ -3536,13 +3560,13 b' class dirstateguard(object):'
3536 % self._suffix)
3560 % self._suffix)
3537 raise error.Abort(msg)
3561 raise error.Abort(msg)
3538
3562
3539 self._repo.dirstate._clearbackup(self._repo.currenttransaction(),
3563 self._repo.dirstate.clearbackup(self._repo.currenttransaction(),
3540 self._suffix)
3564 self._suffix)
3541 self._active = False
3565 self._active = False
3542 self._closed = True
3566 self._closed = True
3543
3567
3544 def _abort(self):
3568 def _abort(self):
3545 self._repo.dirstate._restorebackup(self._repo.currenttransaction(),
3569 self._repo.dirstate.restorebackup(self._repo.currenttransaction(),
3546 self._suffix)
3570 self._suffix)
3547 self._active = False
3571 self._active = False
3548
3572
@@ -59,6 +59,7 b' from . import ('
59 obsolete,
59 obsolete,
60 patch,
60 patch,
61 phases,
61 phases,
62 policy,
62 pvec,
63 pvec,
63 repair,
64 repair,
64 revlog,
65 revlog,
@@ -215,7 +216,7 b' subrepoopts = ['
215 debugrevlogopts = [
216 debugrevlogopts = [
216 ('c', 'changelog', False, _('open changelog')),
217 ('c', 'changelog', False, _('open changelog')),
217 ('m', 'manifest', False, _('open manifest')),
218 ('m', 'manifest', False, _('open manifest')),
218 ('', 'dir', False, _('open directory manifest')),
219 ('', 'dir', '', _('open directory manifest')),
219 ]
220 ]
220
221
221 # Commands start here, listed alphabetically
222 # Commands start here, listed alphabetically
@@ -468,26 +469,27 b' def annotate(ui, repo, *pats, **opts):'
468
469
469 lines = fctx.annotate(follow=follow, linenumber=linenumber,
470 lines = fctx.annotate(follow=follow, linenumber=linenumber,
470 diffopts=diffopts)
471 diffopts=diffopts)
472 if not lines:
473 continue
471 formats = []
474 formats = []
472 pieces = []
475 pieces = []
473
476
474 for f, sep in funcmap:
477 for f, sep in funcmap:
475 l = [f(n) for n, dummy in lines]
478 l = [f(n) for n, dummy in lines]
476 if l:
479 if fm:
477 if fm:
480 formats.append(['%s' for x in l])
478 formats.append(['%s' for x in l])
481 else:
479 else:
482 sizes = [encoding.colwidth(x) for x in l]
480 sizes = [encoding.colwidth(x) for x in l]
483 ml = max(sizes)
481 ml = max(sizes)
484 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
482 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
485 pieces.append(l)
483 pieces.append(l)
484
486
485 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
487 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
486 fm.startitem()
488 fm.startitem()
487 fm.write(fields, "".join(f), *p)
489 fm.write(fields, "".join(f), *p)
488 fm.write('line', ": %s", l[1])
490 fm.write('line', ": %s", l[1])
489
491
490 if lines and not lines[-1][1].endswith('\n'):
492 if not lines[-1][1].endswith('\n'):
491 fm.plain('\n')
493 fm.plain('\n')
492
494
493 fm.end()
495 fm.end()
@@ -2089,51 +2091,56 b' def debugbundle(ui, bundlepath, all=None'
2089 gen = exchange.readbundle(ui, f, bundlepath)
2091 gen = exchange.readbundle(ui, f, bundlepath)
2090 if isinstance(gen, bundle2.unbundle20):
2092 if isinstance(gen, bundle2.unbundle20):
2091 return _debugbundle2(ui, gen, all=all, **opts)
2093 return _debugbundle2(ui, gen, all=all, **opts)
2092 if all:
2094 _debugchangegroup(ui, gen, all=all, **opts)
2093 ui.write(("format: id, p1, p2, cset, delta base, len(delta)\n"))
2095
2094
2096 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
2095 def showchunks(named):
2097 indent_string = ' ' * indent
2096 ui.write("\n%s\n" % named)
2098 if all:
2097 chain = None
2099 ui.write(("%sformat: id, p1, p2, cset, delta base, len(delta)\n")
2098 while True:
2100 % indent_string)
2099 chunkdata = gen.deltachunk(chain)
2101
2100 if not chunkdata:
2102 def showchunks(named):
2101 break
2103 ui.write("\n%s%s\n" % (indent_string, named))
2102 node = chunkdata['node']
2103 p1 = chunkdata['p1']
2104 p2 = chunkdata['p2']
2105 cs = chunkdata['cs']
2106 deltabase = chunkdata['deltabase']
2107 delta = chunkdata['delta']
2108 ui.write("%s %s %s %s %s %s\n" %
2109 (hex(node), hex(p1), hex(p2),
2110 hex(cs), hex(deltabase), len(delta)))
2111 chain = node
2112
2113 chunkdata = gen.changelogheader()
2114 showchunks("changelog")
2115 chunkdata = gen.manifestheader()
2116 showchunks("manifest")
2117 while True:
2118 chunkdata = gen.filelogheader()
2119 if not chunkdata:
2120 break
2121 fname = chunkdata['filename']
2122 showchunks(fname)
2123 else:
2124 if isinstance(gen, bundle2.unbundle20):
2125 raise error.Abort(_('use debugbundle2 for this file'))
2126 chunkdata = gen.changelogheader()
2127 chain = None
2104 chain = None
2128 while True:
2105 while True:
2129 chunkdata = gen.deltachunk(chain)
2106 chunkdata = gen.deltachunk(chain)
2130 if not chunkdata:
2107 if not chunkdata:
2131 break
2108 break
2132 node = chunkdata['node']
2109 node = chunkdata['node']
2133 ui.write("%s\n" % hex(node))
2110 p1 = chunkdata['p1']
2111 p2 = chunkdata['p2']
2112 cs = chunkdata['cs']
2113 deltabase = chunkdata['deltabase']
2114 delta = chunkdata['delta']
2115 ui.write("%s%s %s %s %s %s %s\n" %
2116 (indent_string, hex(node), hex(p1), hex(p2),
2117 hex(cs), hex(deltabase), len(delta)))
2134 chain = node
2118 chain = node
2135
2119
2136 def _debugbundle2(ui, gen, **opts):
2120 chunkdata = gen.changelogheader()
2121 showchunks("changelog")
2122 chunkdata = gen.manifestheader()
2123 showchunks("manifest")
2124 while True:
2125 chunkdata = gen.filelogheader()
2126 if not chunkdata:
2127 break
2128 fname = chunkdata['filename']
2129 showchunks(fname)
2130 else:
2131 if isinstance(gen, bundle2.unbundle20):
2132 raise error.Abort(_('use debugbundle2 for this file'))
2133 chunkdata = gen.changelogheader()
2134 chain = None
2135 while True:
2136 chunkdata = gen.deltachunk(chain)
2137 if not chunkdata:
2138 break
2139 node = chunkdata['node']
2140 ui.write("%s%s\n" % (indent_string, hex(node)))
2141 chain = node
2142
2143 def _debugbundle2(ui, gen, all=None, **opts):
2137 """lists the contents of a bundle2"""
2144 """lists the contents of a bundle2"""
2138 if not isinstance(gen, bundle2.unbundle20):
2145 if not isinstance(gen, bundle2.unbundle20):
2139 raise error.Abort(_('not a bundle2 file'))
2146 raise error.Abort(_('not a bundle2 file'))
@@ -2143,15 +2150,7 b' def _debugbundle2(ui, gen, **opts):'
2143 if part.type == 'changegroup':
2150 if part.type == 'changegroup':
2144 version = part.params.get('version', '01')
2151 version = part.params.get('version', '01')
2145 cg = changegroup.getunbundler(version, part, 'UN')
2152 cg = changegroup.getunbundler(version, part, 'UN')
2146 chunkdata = cg.changelogheader()
2153 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
2147 chain = None
2148 while True:
2149 chunkdata = cg.deltachunk(chain)
2150 if not chunkdata:
2151 break
2152 node = chunkdata['node']
2153 ui.write(" %s\n" % hex(node))
2154 chain = node
2155
2154
2156 @command('debugcreatestreamclonebundle', [], 'FILE')
2155 @command('debugcreatestreamclonebundle', [], 'FILE')
2157 def debugcreatestreamclonebundle(ui, repo, fname):
2156 def debugcreatestreamclonebundle(ui, repo, fname):
@@ -2301,7 +2300,9 b' def debugdag(ui, repo, file_=None, *revs'
2301 @command('debugdata', debugrevlogopts, _('-c|-m|FILE REV'))
2300 @command('debugdata', debugrevlogopts, _('-c|-m|FILE REV'))
2302 def debugdata(ui, repo, file_, rev=None, **opts):
2301 def debugdata(ui, repo, file_, rev=None, **opts):
2303 """dump the contents of a data file revision"""
2302 """dump the contents of a data file revision"""
2304 if opts.get('changelog') or opts.get('manifest'):
2303 if opts.get('changelog') or opts.get('manifest') or opts.get('dir'):
2304 if rev is not None:
2305 raise error.CommandError('debugdata', _('invalid arguments'))
2305 file_, rev = None, file_
2306 file_, rev = None, file_
2306 elif rev is None:
2307 elif rev is None:
2307 raise error.CommandError('debugdata', _('invalid arguments'))
2308 raise error.CommandError('debugdata', _('invalid arguments'))
@@ -2524,15 +2525,16 b' def debugignore(ui, repo, *files, **opts'
2524 break
2525 break
2525 if ignored:
2526 if ignored:
2526 if ignored == nf:
2527 if ignored == nf:
2527 ui.write("%s is ignored\n" % f)
2528 ui.write(_("%s is ignored\n") % f)
2528 else:
2529 else:
2529 ui.write("%s is ignored because of containing folder %s\n"
2530 ui.write(_("%s is ignored because of "
2531 "containing folder %s\n")
2530 % (f, ignored))
2532 % (f, ignored))
2531 ignorefile, lineno, line = ignoredata
2533 ignorefile, lineno, line = ignoredata
2532 ui.write("(ignore rule in %s, line %d: '%s')\n"
2534 ui.write(_("(ignore rule in %s, line %d: '%s')\n")
2533 % (ignorefile, lineno, line))
2535 % (ignorefile, lineno, line))
2534 else:
2536 else:
2535 ui.write("%s is not ignored\n" % f)
2537 ui.write(_("%s is not ignored\n") % f)
2536
2538
2537 @command('debugindex', debugrevlogopts +
2539 @command('debugindex', debugrevlogopts +
2538 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
2540 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
@@ -2563,12 +2565,12 b' def debugindex(ui, repo, file_=None, **o'
2563 break
2565 break
2564
2566
2565 if format == 0:
2567 if format == 0:
2566 ui.write(" rev offset length " + basehdr + " linkrev"
2568 ui.write((" rev offset length " + basehdr + " linkrev"
2567 " %s %s p2\n" % ("nodeid".ljust(idlen), "p1".ljust(idlen)))
2569 " %s %s p2\n") % ("nodeid".ljust(idlen), "p1".ljust(idlen)))
2568 elif format == 1:
2570 elif format == 1:
2569 ui.write(" rev flag offset length"
2571 ui.write((" rev flag offset length"
2570 " size " + basehdr + " link p1 p2"
2572 " size " + basehdr + " link p1 p2"
2571 " %s\n" % "nodeid".rjust(idlen))
2573 " %s\n") % "nodeid".rjust(idlen))
2572
2574
2573 for i in r:
2575 for i in r:
2574 node = r.node(i)
2576 node = r.node(i)
@@ -2743,7 +2745,16 b' def debuginstall(ui, **opts):'
2743 fm.write('pythonlib', _("checking Python lib (%s)...\n"),
2745 fm.write('pythonlib', _("checking Python lib (%s)...\n"),
2744 os.path.dirname(os.__file__))
2746 os.path.dirname(os.__file__))
2745
2747
2748 # hg version
2749 hgver = util.version()
2750 fm.write('hgver', _("checking Mercurial version (%s)\n"),
2751 hgver.split('+')[0])
2752 fm.write('hgverextra', _("checking Mercurial custom build (%s)\n"),
2753 '+'.join(hgver.split('+')[1:]))
2754
2746 # compiled modules
2755 # compiled modules
2756 fm.write('hgmodulepolicy', _("checking module policy (%s)\n"),
2757 policy.policy)
2747 fm.write('hgmodules', _("checking installed modules (%s)...\n"),
2758 fm.write('hgmodules', _("checking installed modules (%s)...\n"),
2748 os.path.dirname(__file__))
2759 os.path.dirname(__file__))
2749
2760
@@ -3022,13 +3033,13 b' def debuglocks(ui, repo, **opts):'
3022 else:
3033 else:
3023 locker = 'user %s, process %s, host %s' \
3034 locker = 'user %s, process %s, host %s' \
3024 % (user, pid, host)
3035 % (user, pid, host)
3025 ui.write("%-6s %s (%ds)\n" % (name + ":", locker, age))
3036 ui.write(("%-6s %s (%ds)\n") % (name + ":", locker, age))
3026 return 1
3037 return 1
3027 except OSError as e:
3038 except OSError as e:
3028 if e.errno != errno.ENOENT:
3039 if e.errno != errno.ENOENT:
3029 raise
3040 raise
3030
3041
3031 ui.write("%-6s free\n" % (name + ":"))
3042 ui.write(("%-6s free\n") % (name + ":"))
3032 return 0
3043 return 0
3033
3044
3034 held += report(repo.svfs, "lock", repo.lock)
3045 held += report(repo.svfs, "lock", repo.lock)
@@ -3321,8 +3332,8 b' def debugrevlog(ui, repo, file_=None, **'
3321
3332
3322 if opts.get("dump"):
3333 if opts.get("dump"):
3323 numrevs = len(r)
3334 numrevs = len(r)
3324 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
3335 ui.write(("# rev p1rev p2rev start end deltastart base p1 p2"
3325 " rawsize totalsize compression heads chainlen\n")
3336 " rawsize totalsize compression heads chainlen\n"))
3326 ts = 0
3337 ts = 0
3327 heads = set()
3338 heads = set()
3328
3339
@@ -3511,18 +3522,19 b' def debugrevspec(ui, repo, expr, **opts)'
3511 ui.note(revset.prettyformat(tree), "\n")
3522 ui.note(revset.prettyformat(tree), "\n")
3512 newtree = revset.expandaliases(ui, tree)
3523 newtree = revset.expandaliases(ui, tree)
3513 if newtree != tree:
3524 if newtree != tree:
3514 ui.note("* expanded:\n", revset.prettyformat(newtree), "\n")
3525 ui.note(("* expanded:\n"), revset.prettyformat(newtree), "\n")
3515 tree = newtree
3526 tree = newtree
3516 newtree = revset.foldconcat(tree)
3527 newtree = revset.foldconcat(tree)
3517 if newtree != tree:
3528 if newtree != tree:
3518 ui.note("* concatenated:\n", revset.prettyformat(newtree), "\n")
3529 ui.note(("* concatenated:\n"), revset.prettyformat(newtree), "\n")
3519 if opts["optimize"]:
3530 if opts["optimize"]:
3520 weight, optimizedtree = revset.optimize(newtree, True)
3531 optimizedtree = revset.optimize(newtree)
3521 ui.note("* optimized:\n", revset.prettyformat(optimizedtree), "\n")
3532 ui.note(("* optimized:\n"),
3533 revset.prettyformat(optimizedtree), "\n")
3522 func = revset.match(ui, expr, repo)
3534 func = revset.match(ui, expr, repo)
3523 revs = func(repo)
3535 revs = func(repo)
3524 if ui.verbose:
3536 if ui.verbose:
3525 ui.note("* set:\n", revset.prettyformatset(revs), "\n")
3537 ui.note(("* set:\n"), revset.prettyformatset(revs), "\n")
3526 for c in revs:
3538 for c in revs:
3527 ui.write("%s\n" % c)
3539 ui.write("%s\n" % c)
3528
3540
@@ -3677,7 +3689,7 b' def debugtemplate(ui, repo, tmpl, **opts'
3677 ui.note(templater.prettyformat(tree), '\n')
3689 ui.note(templater.prettyformat(tree), '\n')
3678 newtree = templater.expandaliases(tree, aliases)
3690 newtree = templater.expandaliases(tree, aliases)
3679 if newtree != tree:
3691 if newtree != tree:
3680 ui.note("* expanded:\n", templater.prettyformat(newtree), '\n')
3692 ui.note(("* expanded:\n"), templater.prettyformat(newtree), '\n')
3681
3693
3682 mapfile = None
3694 mapfile = None
3683 if revs is None:
3695 if revs is None:
@@ -4406,7 +4418,7 b' def grep(ui, repo, pattern, *pats, **opt'
4406 if not opts.get('files_with_matches'):
4418 if not opts.get('files_with_matches'):
4407 ui.write(sep, label='grep.sep')
4419 ui.write(sep, label='grep.sep')
4408 if not opts.get('text') and binary():
4420 if not opts.get('text') and binary():
4409 ui.write(" Binary file matches")
4421 ui.write(_(" Binary file matches"))
4410 else:
4422 else:
4411 for s, label in l:
4423 for s, label in l:
4412 ui.write(s, label=label)
4424 ui.write(s, label=label)
@@ -4570,7 +4582,10 b' def help_(ui, name=None, **opts):'
4570 Returns 0 if successful.
4582 Returns 0 if successful.
4571 """
4583 """
4572
4584
4573 textwidth = min(ui.termwidth(), 80) - 2
4585 textwidth = ui.configint('ui', 'textwidth', 78)
4586 termwidth = ui.termwidth() - 2
4587 if textwidth <= 0 or termwidth < textwidth:
4588 textwidth = termwidth
4574
4589
4575 keep = opts.get('system') or []
4590 keep = opts.get('system') or []
4576 if len(keep) == 0:
4591 if len(keep) == 0:
@@ -5773,6 +5788,9 b' def pull(ui, repo, source="default", **o'
5773 If SOURCE is omitted, the 'default' path will be used.
5788 If SOURCE is omitted, the 'default' path will be used.
5774 See :hg:`help urls` for more information.
5789 See :hg:`help urls` for more information.
5775
5790
5791 Specifying bookmark as ``.`` is equivalent to specifying the active
5792 bookmark's name.
5793
5776 Returns 0 on success, 1 if an update had unresolved files.
5794 Returns 0 on success, 1 if an update had unresolved files.
5777 """
5795 """
5778 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
5796 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
@@ -5794,6 +5812,7 b' def pull(ui, repo, source="default", **o'
5794 remotebookmarks = other.listkeys('bookmarks')
5812 remotebookmarks = other.listkeys('bookmarks')
5795 pullopargs['remotebookmarks'] = remotebookmarks
5813 pullopargs['remotebookmarks'] = remotebookmarks
5796 for b in opts['bookmark']:
5814 for b in opts['bookmark']:
5815 b = repo._bookmarks.expandname(b)
5797 if b not in remotebookmarks:
5816 if b not in remotebookmarks:
5798 raise error.Abort(_('remote bookmark %s not found!') % b)
5817 raise error.Abort(_('remote bookmark %s not found!') % b)
5799 revs.append(remotebookmarks[b])
5818 revs.append(remotebookmarks[b])
@@ -5926,6 +5945,15 b' def push(ui, repo, dest=None, **opts):'
5926 if not revs:
5945 if not revs:
5927 raise error.Abort(_("specified revisions evaluate to an empty set"),
5946 raise error.Abort(_("specified revisions evaluate to an empty set"),
5928 hint=_("use different revision arguments"))
5947 hint=_("use different revision arguments"))
5948 elif path.pushrev:
5949 # It doesn't make any sense to specify ancestor revisions. So limit
5950 # to DAG heads to make discovery simpler.
5951 expr = revset.formatspec('heads(%r)', path.pushrev)
5952 revs = scmutil.revrange(repo, [expr])
5953 revs = [repo[rev].node() for rev in revs]
5954 if not revs:
5955 raise error.Abort(_('default push revset for path evaluates to an '
5956 'empty set'))
5929
5957
5930 repo._subtoppath = dest
5958 repo._subtoppath = dest
5931 try:
5959 try:
@@ -6300,7 +6328,10 b' def revert(ui, repo, *pats, **opts):'
6300 related method.
6328 related method.
6301
6329
6302 Modified files are saved with a .orig suffix before reverting.
6330 Modified files are saved with a .orig suffix before reverting.
6303 To disable these backups, use --no-backup.
6331 To disable these backups, use --no-backup. It is possible to store
6332 the backup files in a custom directory relative to the root of the
6333 repository by setting the ``ui.origbackuppath`` configuration
6334 option.
6304
6335
6305 See :hg:`help dates` for a list of formats valid for -d/--date.
6336 See :hg:`help dates` for a list of formats valid for -d/--date.
6306
6337
@@ -6380,6 +6411,11 b' def rollback(ui, repo, **opts):'
6380 commit transaction if it isn't checked out. Use --force to
6411 commit transaction if it isn't checked out. Use --force to
6381 override this protection.
6412 override this protection.
6382
6413
6414 The rollback command can be entirely disabled by setting the
6415 ``ui.rollback`` configuration setting to false. If you're here
6416 because you want to use rollback and it's disabled, you can
6417 re-enable the command by setting ``ui.rollback`` to true.
6418
6383 This command is not intended for use on public repositories. Once
6419 This command is not intended for use on public repositories. Once
6384 changes are visible for pull by other users, rolling a transaction
6420 changes are visible for pull by other users, rolling a transaction
6385 back locally is ineffective (someone else may already have pulled
6421 back locally is ineffective (someone else may already have pulled
@@ -6389,6 +6425,9 b' def rollback(ui, repo, **opts):'
6389
6425
6390 Returns 0 on success, 1 if no rollback data is available.
6426 Returns 0 on success, 1 if no rollback data is available.
6391 """
6427 """
6428 if not ui.configbool('ui', 'rollback', True):
6429 raise error.Abort(_('rollback is disabled because it is unsafe'),
6430 hint=('see `hg help -v rollback` for information'))
6392 return repo.rollback(dryrun=opts.get('dry_run'),
6431 return repo.rollback(dryrun=opts.get('dry_run'),
6393 force=opts.get('force'))
6432 force=opts.get('force'))
6394
6433
@@ -7,9 +7,13 b''
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import SocketServer
11 import errno
10 import errno
11 import gc
12 import os
12 import os
13 import random
14 import select
15 import signal
16 import socket
13 import struct
17 import struct
14 import sys
18 import sys
15 import traceback
19 import traceback
@@ -178,6 +182,10 b' class server(object):'
178
182
179 self.client = fin
183 self.client = fin
180
184
185 def cleanup(self):
186 """release and restore resources taken during server session"""
187 pass
188
181 def _read(self, size):
189 def _read(self, size):
182 if not size:
190 if not size:
183 return ''
191 return ''
@@ -229,12 +237,8 b' class server(object):'
229 self.repo.ui = self.repo.dirstate._ui = repoui
237 self.repo.ui = self.repo.dirstate._ui = repoui
230 self.repo.invalidateall()
238 self.repo.invalidateall()
231
239
232 # reset last-print time of progress bar per command
233 # (progbar is singleton, we don't have to do for all uis)
234 if copiedui._progbar:
235 copiedui._progbar.resetstate()
236
237 for ui in uis:
240 for ui in uis:
241 ui.resetstate()
238 # any kind of interaction must use server channels, but chg may
242 # any kind of interaction must use server channels, but chg may
239 # replace channels by fully functional tty files. so nontty is
243 # replace channels by fully functional tty files. so nontty is
240 # enforced only if cin is a channel.
244 # enforced only if cin is a channel.
@@ -278,6 +282,9 b' class server(object):'
278 hellomsg += 'encoding: ' + encoding.encoding
282 hellomsg += 'encoding: ' + encoding.encoding
279 hellomsg += '\n'
283 hellomsg += '\n'
280 hellomsg += 'pid: %d' % util.getpid()
284 hellomsg += 'pid: %d' % util.getpid()
285 if util.safehasattr(os, 'getpgid'):
286 hellomsg += '\n'
287 hellomsg += 'pgid: %d' % os.getpgid(0)
281
288
282 # write the hello msg in -one- chunk
289 # write the hello msg in -one- chunk
283 self.cout.write(hellomsg)
290 self.cout.write(hellomsg)
@@ -332,66 +339,193 b' class pipeservice(object):'
332 sv = server(ui, self.repo, fin, fout)
339 sv = server(ui, self.repo, fin, fout)
333 return sv.serve()
340 return sv.serve()
334 finally:
341 finally:
342 sv.cleanup()
335 _restoreio(ui, fin, fout)
343 _restoreio(ui, fin, fout)
336
344
337 class _requesthandler(SocketServer.StreamRequestHandler):
345 def _initworkerprocess():
338 def handle(self):
346 # use a different process group from the master process, making this
339 ui = self.server.ui
347 # process pass kernel "is_current_pgrp_orphaned" check so signals like
340 repo = self.server.repo
348 # SIGTSTP, SIGTTIN, SIGTTOU are not ignored.
341 sv = None
349 os.setpgid(0, 0)
350 # change random state otherwise forked request handlers would have a
351 # same state inherited from parent.
352 random.seed()
353
354 def _serverequest(ui, repo, conn, createcmdserver):
355 fin = conn.makefile('rb')
356 fout = conn.makefile('wb')
357 sv = None
358 try:
359 sv = createcmdserver(repo, conn, fin, fout)
360 try:
361 sv.serve()
362 # handle exceptions that may be raised by command server. most of
363 # known exceptions are caught by dispatch.
364 except error.Abort as inst:
365 ui.warn(_('abort: %s\n') % inst)
366 except IOError as inst:
367 if inst.errno != errno.EPIPE:
368 raise
369 except KeyboardInterrupt:
370 pass
371 finally:
372 sv.cleanup()
373 except: # re-raises
374 # also write traceback to error channel. otherwise client cannot
375 # see it because it is written to server's stderr by default.
376 if sv:
377 cerr = sv.cerr
378 else:
379 cerr = channeledoutput(fout, 'e')
380 traceback.print_exc(file=cerr)
381 raise
382 finally:
383 fin.close()
342 try:
384 try:
343 sv = server(ui, repo, self.rfile, self.wfile)
385 fout.close() # implicit flush() may cause another EPIPE
344 try:
386 except IOError as inst:
345 sv.serve()
387 if inst.errno != errno.EPIPE:
346 # handle exceptions that may be raised by command server. most of
388 raise
347 # known exceptions are caught by dispatch.
389
348 except error.Abort as inst:
390 class unixservicehandler(object):
349 ui.warn(_('abort: %s\n') % inst)
391 """Set of pluggable operations for unix-mode services
350 except IOError as inst:
392
351 if inst.errno != errno.EPIPE:
393 Almost all methods except for createcmdserver() are called in the main
352 raise
394 process. You can't pass mutable resource back from createcmdserver().
353 except KeyboardInterrupt:
395 """
354 pass
396
355 except: # re-raises
397 pollinterval = None
356 # also write traceback to error channel. otherwise client cannot
398
357 # see it because it is written to server's stderr by default.
399 def __init__(self, ui):
358 if sv:
400 self.ui = ui
359 cerr = sv.cerr
401
360 else:
402 def bindsocket(self, sock, address):
361 cerr = channeledoutput(self.wfile, 'e')
403 util.bindunixsocket(sock, address)
362 traceback.print_exc(file=cerr)
363 raise
364
404
365 class unixservice(object):
405 def unlinksocket(self, address):
406 os.unlink(address)
407
408 def printbanner(self, address):
409 self.ui.status(_('listening at %s\n') % address)
410 self.ui.flush() # avoid buffering of status message
411
412 def shouldexit(self):
413 """True if server should shut down; checked per pollinterval"""
414 return False
415
416 def newconnection(self):
417 """Called when main process notices new connection"""
418 pass
419
420 def createcmdserver(self, repo, conn, fin, fout):
421 """Create new command server instance; called in the process that
422 serves for the current connection"""
423 return server(self.ui, repo, fin, fout)
424
425 class unixforkingservice(object):
366 """
426 """
367 Listens on unix domain socket and forks server per connection
427 Listens on unix domain socket and forks server per connection
368 """
428 """
369 def __init__(self, ui, repo, opts):
429
430 def __init__(self, ui, repo, opts, handler=None):
370 self.ui = ui
431 self.ui = ui
371 self.repo = repo
432 self.repo = repo
372 self.address = opts['address']
433 self.address = opts['address']
373 if not util.safehasattr(SocketServer, 'UnixStreamServer'):
434 if not util.safehasattr(socket, 'AF_UNIX'):
374 raise error.Abort(_('unsupported platform'))
435 raise error.Abort(_('unsupported platform'))
375 if not self.address:
436 if not self.address:
376 raise error.Abort(_('no socket path specified with --address'))
437 raise error.Abort(_('no socket path specified with --address'))
438 self._servicehandler = handler or unixservicehandler(ui)
439 self._sock = None
440 self._oldsigchldhandler = None
441 self._workerpids = set() # updated by signal handler; do not iterate
377
442
378 def init(self):
443 def init(self):
379 class cls(SocketServer.ForkingMixIn, SocketServer.UnixStreamServer):
444 self._sock = socket.socket(socket.AF_UNIX)
380 ui = self.ui
445 self._servicehandler.bindsocket(self._sock, self.address)
381 repo = self.repo
446 self._sock.listen(socket.SOMAXCONN)
382 self.server = cls(self.address, _requesthandler)
447 o = signal.signal(signal.SIGCHLD, self._sigchldhandler)
383 self.ui.status(_('listening at %s\n') % self.address)
448 self._oldsigchldhandler = o
384 self.ui.flush() # avoid buffering of status message
449 self._servicehandler.printbanner(self.address)
450
451 def _cleanup(self):
452 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
453 self._sock.close()
454 self._servicehandler.unlinksocket(self.address)
455 # don't kill child processes as they have active clients, just wait
456 self._reapworkers(0)
385
457
386 def run(self):
458 def run(self):
387 try:
459 try:
388 self.server.serve_forever()
460 self._mainloop()
389 finally:
461 finally:
390 os.unlink(self.address)
462 self._cleanup()
463
464 def _mainloop(self):
465 h = self._servicehandler
466 while not h.shouldexit():
467 try:
468 ready = select.select([self._sock], [], [], h.pollinterval)[0]
469 if not ready:
470 continue
471 conn, _addr = self._sock.accept()
472 except (select.error, socket.error) as inst:
473 if inst.args[0] == errno.EINTR:
474 continue
475 raise
476
477 pid = os.fork()
478 if pid:
479 try:
480 self.ui.debug('forked worker process (pid=%d)\n' % pid)
481 self._workerpids.add(pid)
482 h.newconnection()
483 finally:
484 conn.close() # release handle in parent process
485 else:
486 try:
487 self._runworker(conn)
488 conn.close()
489 os._exit(0)
490 except: # never return, hence no re-raises
491 try:
492 self.ui.traceback(force=True)
493 finally:
494 os._exit(255)
495
496 def _sigchldhandler(self, signal, frame):
497 self._reapworkers(os.WNOHANG)
498
499 def _reapworkers(self, options):
500 while self._workerpids:
501 try:
502 pid, _status = os.waitpid(-1, options)
503 except OSError as inst:
504 if inst.errno == errno.EINTR:
505 continue
506 if inst.errno != errno.ECHILD:
507 raise
508 # no child processes at all (reaped by other waitpid()?)
509 self._workerpids.clear()
510 return
511 if pid == 0:
512 # no waitable child processes
513 return
514 self.ui.debug('worker process exited (pid=%d)\n' % pid)
515 self._workerpids.discard(pid)
516
517 def _runworker(self, conn):
518 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
519 _initworkerprocess()
520 h = self._servicehandler
521 try:
522 _serverequest(self.ui, self.repo, conn, h.createcmdserver)
523 finally:
524 gc.collect() # trigger __del__ since worker process uses os._exit
391
525
392 _servicemap = {
526 _servicemap = {
393 'pipe': pipeservice,
527 'pipe': pipeservice,
394 'unix': unixservice,
528 'unix': unixforkingservice,
395 }
529 }
396
530
397 def createservice(ui, repo, opts):
531 def createservice(ui, repo, opts):
@@ -918,28 +918,25 b' class basefilectx(object):'
918 return p[1]
918 return p[1]
919 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
919 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
920
920
921 def annotate(self, follow=False, linenumber=None, diffopts=None):
921 def annotate(self, follow=False, linenumber=False, diffopts=None):
922 '''returns a list of tuples of (ctx, line) for each line
922 '''returns a list of tuples of ((ctx, number), line) for each line
923 in the file, where ctx is the filectx of the node where
923 in the file, where ctx is the filectx of the node where
924 that line was last changed.
924 that line was last changed; if linenumber parameter is true, number is
925 This returns tuples of ((ctx, linenumber), line) for each line,
925 the line number at the first appearance in the managed file, otherwise,
926 if "linenumber" parameter is NOT "None".
926 number has a fixed value of False.
927 In such tuples, linenumber means one at the first appearance
927 '''
928 in the managed file.
929 To reduce annotation cost,
930 this returns fixed value(False is used) as linenumber,
931 if "linenumber" parameter is "False".'''
932
928
933 if linenumber is None:
929 def lines(text):
930 if text.endswith("\n"):
931 return text.count("\n")
932 return text.count("\n") + 1
933
934 if linenumber:
934 def decorate(text, rev):
935 def decorate(text, rev):
935 return ([rev] * len(text.splitlines()), text)
936 return ([(rev, i) for i in xrange(1, lines(text) + 1)], text)
936 elif linenumber:
937 def decorate(text, rev):
938 size = len(text.splitlines())
939 return ([(rev, i) for i in xrange(1, size + 1)], text)
940 else:
937 else:
941 def decorate(text, rev):
938 def decorate(text, rev):
942 return ([(rev, False)] * len(text.splitlines()), text)
939 return ([(rev, False)] * lines(text), text)
943
940
944 def pair(parent, child):
941 def pair(parent, child):
945 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
942 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
@@ -484,16 +484,16 b' def checkcopies(ctx, f, m1, m2, ca, limi'
484 f1r, f2r = f1.linkrev(), f2.linkrev()
484 f1r, f2r = f1.linkrev(), f2.linkrev()
485
485
486 if f1r is None:
486 if f1r is None:
487 f1 = g1.next()
487 f1 = next(g1)
488 if f2r is None:
488 if f2r is None:
489 f2 = g2.next()
489 f2 = next(g2)
490
490
491 while True:
491 while True:
492 f1r, f2r = f1.linkrev(), f2.linkrev()
492 f1r, f2r = f1.linkrev(), f2.linkrev()
493 if f1r > f2r:
493 if f1r > f2r:
494 f1 = g1.next()
494 f1 = next(g1)
495 elif f2r > f1r:
495 elif f2r > f1r:
496 f2 = g2.next()
496 f2 = next(g2)
497 elif f1 == f2:
497 elif f1 == f2:
498 return f1 # a match
498 return f1 # a match
499 elif f1r == f2r or f1r < limit or f2r < limit:
499 elif f1r == f2r or f1r < limit or f2r < limit:
@@ -91,6 +91,7 b' class patchnode(object):'
91 def allchildren(self):
91 def allchildren(self):
92 "Return a list of all of the direct children of this node"
92 "Return a list of all of the direct children of this node"
93 raise NotImplementedError("method must be implemented by subclass")
93 raise NotImplementedError("method must be implemented by subclass")
94
94 def nextsibling(self):
95 def nextsibling(self):
95 """
96 """
96 Return the closest next item of the same type where there are no items
97 Return the closest next item of the same type where there are no items
@@ -110,18 +111,12 b' class patchnode(object):'
110 def parentitem(self):
111 def parentitem(self):
111 raise NotImplementedError("method must be implemented by subclass")
112 raise NotImplementedError("method must be implemented by subclass")
112
113
113
114 def nextitem(self, skipfolded=True):
114 def nextitem(self, constrainlevel=True, skipfolded=True):
115 """
115 """
116 If constrainLevel == True, return the closest next item
116 Try to return the next item closest to this item, regardless of item's
117 of the same type where there are no items of different types between
117 type (header, hunk, or hunkline).
118 the current item and this closest item.
119
118
120 If constrainLevel == False, then try to return the next item
119 If skipfolded == True, and the current item is folded, then the child
121 closest to this item, regardless of item's type (header, hunk, or
122 HunkLine).
123
124 If skipFolded == True, and the current item is folded, then the child
125 items that are hidden due to folding will be skipped when determining
120 items that are hidden due to folding will be skipped when determining
126 the next item.
121 the next item.
127
122
@@ -131,9 +126,7 b' class patchnode(object):'
131 itemfolded = self.folded
126 itemfolded = self.folded
132 except AttributeError:
127 except AttributeError:
133 itemfolded = False
128 itemfolded = False
134 if constrainlevel:
129 if skipfolded and itemfolded:
135 return self.nextsibling()
136 elif skipfolded and itemfolded:
137 nextitem = self.nextsibling()
130 nextitem = self.nextsibling()
138 if nextitem is None:
131 if nextitem is None:
139 try:
132 try:
@@ -164,43 +157,31 b' class patchnode(object):'
164 except AttributeError: # parent and/or grandparent was None
157 except AttributeError: # parent and/or grandparent was None
165 return None
158 return None
166
159
167 def previtem(self, constrainlevel=True, skipfolded=True):
160 def previtem(self):
168 """
161 """
169 If constrainLevel == True, return the closest previous item
162 Try to return the previous item closest to this item, regardless of
170 of the same type where there are no items of different types between
163 item's type (header, hunk, or hunkline).
171 the current item and this closest item.
172
173 If constrainLevel == False, then try to return the previous item
174 closest to this item, regardless of item's type (header, hunk, or
175 HunkLine).
176
177 If skipFolded == True, and the current item is folded, then the items
178 that are hidden due to folding will be skipped when determining the
179 next item.
180
164
181 If it is not possible to get the previous item, return None.
165 If it is not possible to get the previous item, return None.
182 """
166 """
183 if constrainlevel:
167 # try previous sibling's last child's last child,
184 return self.prevsibling()
168 # else try previous sibling's last child, else try previous sibling
185 else:
169 prevsibling = self.prevsibling()
186 # try previous sibling's last child's last child,
170 if prevsibling is not None:
187 # else try previous sibling's last child, else try previous sibling
171 prevsiblinglastchild = prevsibling.lastchild()
188 prevsibling = self.prevsibling()
172 if ((prevsiblinglastchild is not None) and
189 if prevsibling is not None:
173 not prevsibling.folded):
190 prevsiblinglastchild = prevsibling.lastchild()
174 prevsiblinglclc = prevsiblinglastchild.lastchild()
191 if ((prevsiblinglastchild is not None) and
175 if ((prevsiblinglclc is not None) and
192 not prevsibling.folded):
176 not prevsiblinglastchild.folded):
193 prevsiblinglclc = prevsiblinglastchild.lastchild()
177 return prevsiblinglclc
194 if ((prevsiblinglclc is not None) and
195 not prevsiblinglastchild.folded):
196 return prevsiblinglclc
197 else:
198 return prevsiblinglastchild
199 else:
178 else:
200 return prevsibling
179 return prevsiblinglastchild
180 else:
181 return prevsibling
201
182
202 # try parent (or None)
183 # try parent (or None)
203 return self.parentitem()
184 return self.parentitem()
204
185
205 class patch(patchnode, list): # todo: rename patchroot
186 class patch(patchnode, list): # todo: rename patchroot
206 """
187 """
@@ -236,7 +217,6 b' class uiheader(patchnode):'
236 self.neverunfolded = True
217 self.neverunfolded = True
237 self.hunks = [uihunk(h, self) for h in self.hunks]
218 self.hunks = [uihunk(h, self) for h in self.hunks]
238
219
239
240 def prettystr(self):
220 def prettystr(self):
241 x = stringio()
221 x = stringio()
242 self.pretty(x)
222 self.pretty(x)
@@ -392,6 +372,7 b' class uihunk(patchnode):'
392 def allchildren(self):
372 def allchildren(self):
393 "return a list of all of the direct children of this node"
373 "return a list of all of the direct children of this node"
394 return self.changedlines
374 return self.changedlines
375
395 def countchanges(self):
376 def countchanges(self):
396 """changedlines -> (n+,n-)"""
377 """changedlines -> (n+,n-)"""
397 add = len([l for l in self.changedlines if l.applied
378 add = len([l for l in self.changedlines if l.applied
@@ -455,14 +436,12 b' class uihunk(patchnode):'
455
436
456 def __getattr__(self, name):
437 def __getattr__(self, name):
457 return getattr(self._hunk, name)
438 return getattr(self._hunk, name)
439
458 def __repr__(self):
440 def __repr__(self):
459 return '<hunk %r@%d>' % (self.filename(), self.fromline)
441 return '<hunk %r@%d>' % (self.filename(), self.fromline)
460
442
461 def filterpatch(ui, chunks, chunkselector, operation=None):
443 def filterpatch(ui, chunks, chunkselector):
462 """interactively filter patch chunks into applied-only chunks"""
444 """interactively filter patch chunks into applied-only chunks"""
463
464 if operation is None:
465 operation = _('confirm')
466 chunks = list(chunks)
445 chunks = list(chunks)
467 # convert chunks list into structure suitable for displaying/modifying
446 # convert chunks list into structure suitable for displaying/modifying
468 # with curses. create a list of headers only.
447 # with curses. create a list of headers only.
@@ -603,13 +582,10 b' class curseschunkselector(object):'
603 the last hunkline of the hunk prior to the selected hunk. or, if
582 the last hunkline of the hunk prior to the selected hunk. or, if
604 the first hunkline of a hunk is currently selected, then select the
583 the first hunkline of a hunk is currently selected, then select the
605 hunk itself.
584 hunk itself.
606
607 if the currently selected item is already at the top of the screen,
608 scroll the screen down to show the new-selected item.
609 """
585 """
610 currentitem = self.currentselecteditem
586 currentitem = self.currentselecteditem
611
587
612 nextitem = currentitem.previtem(constrainlevel=False)
588 nextitem = currentitem.previtem()
613
589
614 if nextitem is None:
590 if nextitem is None:
615 # if no parent item (i.e. currentitem is the first header), then
591 # if no parent item (i.e. currentitem is the first header), then
@@ -623,13 +599,10 b' class curseschunkselector(object):'
623 select (if possible) the previous item on the same level as the
599 select (if possible) the previous item on the same level as the
624 currently selected item. otherwise, select (if possible) the
600 currently selected item. otherwise, select (if possible) the
625 parent-item of the currently selected item.
601 parent-item of the currently selected item.
626
627 if the currently selected item is already at the top of the screen,
628 scroll the screen down to show the new-selected item.
629 """
602 """
630 currentitem = self.currentselecteditem
603 currentitem = self.currentselecteditem
631 nextitem = currentitem.previtem()
604 nextitem = currentitem.prevsibling()
632 # if there's no previous item on this level, try choosing the parent
605 # if there's no previous sibling, try choosing the parent
633 if nextitem is None:
606 if nextitem is None:
634 nextitem = currentitem.parentitem()
607 nextitem = currentitem.parentitem()
635 if nextitem is None:
608 if nextitem is None:
@@ -646,14 +619,11 b' class curseschunkselector(object):'
646 the first hunkline of the selected hunk. or, if the last hunkline of
619 the first hunkline of the selected hunk. or, if the last hunkline of
647 a hunk is currently selected, then select the next hunk, if one exists,
620 a hunk is currently selected, then select the next hunk, if one exists,
648 or if not, the next header if one exists.
621 or if not, the next header if one exists.
649
650 if the currently selected item is already at the bottom of the screen,
651 scroll the screen up to show the new-selected item.
652 """
622 """
653 #self.startprintline += 1 #debug
623 #self.startprintline += 1 #debug
654 currentitem = self.currentselecteditem
624 currentitem = self.currentselecteditem
655
625
656 nextitem = currentitem.nextitem(constrainlevel=False)
626 nextitem = currentitem.nextitem()
657 # if there's no next item, keep the selection as-is
627 # if there's no next item, keep the selection as-is
658 if nextitem is None:
628 if nextitem is None:
659 nextitem = currentitem
629 nextitem = currentitem
@@ -662,24 +632,21 b' class curseschunkselector(object):'
662
632
663 def downarrowshiftevent(self):
633 def downarrowshiftevent(self):
664 """
634 """
665 if the cursor is already at the bottom chunk, scroll the screen up and
635 select (if possible) the next item on the same level as the currently
666 move the cursor-position to the subsequent chunk. otherwise, only move
636 selected item. otherwise, select (if possible) the next item on the
667 the cursor position down one chunk.
637 same level as the parent item of the currently selected item.
668 """
638 """
669 # todo: update docstring
670
671 currentitem = self.currentselecteditem
639 currentitem = self.currentselecteditem
672 nextitem = currentitem.nextitem()
640 nextitem = currentitem.nextsibling()
673 # if there's no previous item on this level, try choosing the parent's
641 # if there's no next sibling, try choosing the parent's nextsibling
674 # nextitem.
675 if nextitem is None:
642 if nextitem is None:
676 try:
643 try:
677 nextitem = currentitem.parentitem().nextitem()
644 nextitem = currentitem.parentitem().nextsibling()
678 except AttributeError:
645 except AttributeError:
679 # parentitem returned None, so nextitem() can't be called
646 # parentitem returned None, so nextsibling() can't be called
680 nextitem = None
647 nextitem = None
681 if nextitem is None:
648 if nextitem is None:
682 # if no next item on parent-level, then no change...
649 # if parent has no next sibling, then no change...
683 nextitem = currentitem
650 nextitem = currentitem
684
651
685 self.currentselecteditem = nextitem
652 self.currentselecteditem = nextitem
@@ -766,7 +733,6 b' class curseschunkselector(object):'
766 # negative values scroll in pgup direction
733 # negative values scroll in pgup direction
767 self.scrolllines(selstart - padstartbuffered)
734 self.scrolllines(selstart - padstartbuffered)
768
735
769
770 def scrolllines(self, numlines):
736 def scrolllines(self, numlines):
771 "scroll the screen up (down) by numlines when numlines >0 (<0)."
737 "scroll the screen up (down) by numlines when numlines >0 (<0)."
772 self.firstlineofpadtoprint += numlines
738 self.firstlineofpadtoprint += numlines
@@ -894,7 +860,6 b' class curseschunkselector(object):'
894 if isinstance(item, (uiheader, uihunk)):
860 if isinstance(item, (uiheader, uihunk)):
895 item.folded = not item.folded
861 item.folded = not item.folded
896
862
897
898 def alignstring(self, instr, window):
863 def alignstring(self, instr, window):
899 """
864 """
900 add whitespace to the end of a string in order to make it fill
865 add whitespace to the end of a string in order to make it fill
@@ -1133,7 +1098,6 b' class curseschunkselector(object):'
1133 lineprefix = " "*self.hunkindentnumchars + checkbox
1098 lineprefix = " "*self.hunkindentnumchars + checkbox
1134 frtoline = " " + hunk.getfromtoline().strip("\n")
1099 frtoline = " " + hunk.getfromtoline().strip("\n")
1135
1100
1136
1137 outstr += self.printstring(self.chunkpad, lineprefix, towin=towin,
1101 outstr += self.printstring(self.chunkpad, lineprefix, towin=towin,
1138 align=False) # add uncolored checkbox/indent
1102 align=False) # add uncolored checkbox/indent
1139 outstr += self.printstring(self.chunkpad, frtoline, pair=colorpair,
1103 outstr += self.printstring(self.chunkpad, frtoline, pair=colorpair,
@@ -1377,7 +1341,7 b' the following are valid keystrokes:'
1377 F : fold / unfold parent item and all of its ancestors
1341 F : fold / unfold parent item and all of its ancestors
1378 m : edit / resume editing the commit message
1342 m : edit / resume editing the commit message
1379 e : edit the currently selected hunk
1343 e : edit the currently selected hunk
1380 a : toggle amend mode (hg rev >= 2.2), only with commit -i
1344 a : toggle amend mode, only with commit -i
1381 c : confirm selected changes
1345 c : confirm selected changes
1382 r : review/edit and confirm selected changes
1346 r : review/edit and confirm selected changes
1383 q : quit without confirming (no changes will be made)
1347 q : quit without confirming (no changes will be made)
@@ -188,15 +188,23 b' def _demandimport(name, globals=None, lo'
188 if globalname and isinstance(symbol, _demandmod):
188 if globalname and isinstance(symbol, _demandmod):
189 symbol._addref(globalname)
189 symbol._addref(globalname)
190
190
191 def chainmodules(rootmod, modname):
192 # recurse down the module chain, and return the leaf module
193 mod = rootmod
194 for comp in modname.split('.')[1:]:
195 if getattr(mod, comp, nothing) is nothing:
196 setattr(mod, comp,
197 _demandmod(comp, mod.__dict__, mod.__dict__))
198 mod = getattr(mod, comp)
199 return mod
200
191 if level >= 0:
201 if level >= 0:
192 # The "from a import b,c,d" or "from .a import b,c,d"
193 # syntax gives errors with some modules for unknown
194 # reasons. Work around the problem.
195 if name:
202 if name:
196 return _hgextimport(_origimport, name, globals, locals,
203 # "from a import b" or "from .a import b" style
197 fromlist, level)
204 rootmod = _hgextimport(_origimport, name, globals, locals,
198
205 level=level)
199 if _pypy:
206 mod = chainmodules(rootmod, name)
207 elif _pypy:
200 # PyPy's __import__ throws an exception if invoked
208 # PyPy's __import__ throws an exception if invoked
201 # with an empty name and no fromlist. Recreate the
209 # with an empty name and no fromlist. Recreate the
202 # desired behaviour by hand.
210 # desired behaviour by hand.
@@ -220,12 +228,7 b' def _demandimport(name, globals=None, lo'
220 # But, we still need to support lazy loading of standard library and 3rd
228 # But, we still need to support lazy loading of standard library and 3rd
221 # party modules. So handle level == -1.
229 # party modules. So handle level == -1.
222 mod = _hgextimport(_origimport, name, globals, locals)
230 mod = _hgextimport(_origimport, name, globals, locals)
223 # recurse down the module chain
231 mod = chainmodules(mod, name)
224 for comp in name.split('.')[1:]:
225 if getattr(mod, comp, nothing) is nothing:
226 setattr(mod, comp,
227 _demandmod(comp, mod.__dict__, mod.__dict__))
228 mod = getattr(mod, comp)
229
232
230 for x in fromlist:
233 for x in fromlist:
231 processfromitem(mod, x)
234 processfromitem(mod, x)
@@ -95,6 +95,10 b' def _destupdatebranch(repo, clean, check'
95 wc = repo[None]
95 wc = repo[None]
96 movemark = node = None
96 movemark = node = None
97 currentbranch = wc.branch()
97 currentbranch = wc.branch()
98
99 if clean:
100 currentbranch = repo['.'].branch()
101
98 if currentbranch in repo.branchmap():
102 if currentbranch in repo.branchmap():
99 heads = repo.branchheads(currentbranch)
103 heads = repo.branchheads(currentbranch)
100 if heads:
104 if heads:
@@ -74,6 +74,8 b' def _trypending(root, vfs, filename):'
74 raise
74 raise
75 return (vfs(filename), False)
75 return (vfs(filename), False)
76
76
77 _token = object()
78
77 class dirstate(object):
79 class dirstate(object):
78
80
79 def __init__(self, opener, ui, root, validate):
81 def __init__(self, opener, ui, root, validate):
@@ -365,7 +367,7 b' class dirstate(object):'
365
367
366 def setbranch(self, branch):
368 def setbranch(self, branch):
367 self._branch = encoding.fromlocal(branch)
369 self._branch = encoding.fromlocal(branch)
368 f = self._opener('branch', 'w', atomictemp=True)
370 f = self._opener('branch', 'w', atomictemp=True, checkambig=True)
369 try:
371 try:
370 f.write(self._branch + '\n')
372 f.write(self._branch + '\n')
371 f.close()
373 f.close()
@@ -580,6 +582,8 b' class dirstate(object):'
580 del self._map[f]
582 del self._map[f]
581 if f in self._nonnormalset:
583 if f in self._nonnormalset:
582 self._nonnormalset.remove(f)
584 self._nonnormalset.remove(f)
585 if f in self._copymap:
586 del self._copymap[f]
583
587
584 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
588 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
585 if exists is None:
589 if exists is None:
@@ -688,16 +692,15 b' class dirstate(object):'
688 self._pl = (parent, nullid)
692 self._pl = (parent, nullid)
689 self._dirty = True
693 self._dirty = True
690
694
691 def write(self, tr=False):
695 def write(self, tr=_token):
692 if not self._dirty:
696 if not self._dirty:
693 return
697 return
694
698
695 filename = self._filename
699 filename = self._filename
696 if tr is False: # not explicitly specified
700 if tr is _token: # not explicitly specified
697 if (self._ui.configbool('devel', 'all-warnings')
701 self._ui.deprecwarn('use dirstate.write with '
698 or self._ui.configbool('devel', 'check-dirstate-write')):
702 'repo.currenttransaction()',
699 self._ui.develwarn('use dirstate.write with '
703 '3.9')
700 'repo.currenttransaction()')
701
704
702 if self._opener.lexists(self._pendingfilename):
705 if self._opener.lexists(self._pendingfilename):
703 # if pending file already exists, in-memory changes
706 # if pending file already exists, in-memory changes
@@ -727,7 +730,7 b' class dirstate(object):'
727 self._writedirstate, location='plain')
730 self._writedirstate, location='plain')
728 return
731 return
729
732
730 st = self._opener(filename, "w", atomictemp=True)
733 st = self._opener(filename, "w", atomictemp=True, checkambig=True)
731 self._writedirstate(st)
734 self._writedirstate(st)
732
735
733 def _writedirstate(self, st):
736 def _writedirstate(self, st):
@@ -1206,14 +1209,16 b' class dirstate(object):'
1206 else:
1209 else:
1207 return self._filename
1210 return self._filename
1208
1211
1209 def _savebackup(self, tr, suffix):
1212 def savebackup(self, tr, suffix='', prefix=''):
1210 '''Save current dirstate into backup file with suffix'''
1213 '''Save current dirstate into backup file with suffix'''
1214 assert len(suffix) > 0 or len(prefix) > 0
1211 filename = self._actualfilename(tr)
1215 filename = self._actualfilename(tr)
1212
1216
1213 # use '_writedirstate' instead of 'write' to write changes certainly,
1217 # use '_writedirstate' instead of 'write' to write changes certainly,
1214 # because the latter omits writing out if transaction is running.
1218 # because the latter omits writing out if transaction is running.
1215 # output file will be used to create backup of dirstate at this point.
1219 # output file will be used to create backup of dirstate at this point.
1216 self._writedirstate(self._opener(filename, "w", atomictemp=True))
1220 self._writedirstate(self._opener(filename, "w", atomictemp=True,
1221 checkambig=True))
1217
1222
1218 if tr:
1223 if tr:
1219 # ensure that subsequent tr.writepending returns True for
1224 # ensure that subsequent tr.writepending returns True for
@@ -1227,17 +1232,22 b' class dirstate(object):'
1227 # end of this transaction
1232 # end of this transaction
1228 tr.registertmp(filename, location='plain')
1233 tr.registertmp(filename, location='plain')
1229
1234
1230 self._opener.write(filename + suffix, self._opener.tryread(filename))
1235 self._opener.write(prefix + self._filename + suffix,
1236 self._opener.tryread(filename))
1231
1237
1232 def _restorebackup(self, tr, suffix):
1238 def restorebackup(self, tr, suffix='', prefix=''):
1233 '''Restore dirstate by backup file with suffix'''
1239 '''Restore dirstate by backup file with suffix'''
1240 assert len(suffix) > 0 or len(prefix) > 0
1234 # this "invalidate()" prevents "wlock.release()" from writing
1241 # this "invalidate()" prevents "wlock.release()" from writing
1235 # changes of dirstate out after restoring from backup file
1242 # changes of dirstate out after restoring from backup file
1236 self.invalidate()
1243 self.invalidate()
1237 filename = self._actualfilename(tr)
1244 filename = self._actualfilename(tr)
1238 self._opener.rename(filename + suffix, filename)
1245 # using self._filename to avoid having "pending" in the backup filename
1246 self._opener.rename(prefix + self._filename + suffix, filename,
1247 checkambig=True)
1239
1248
1240 def _clearbackup(self, tr, suffix):
1249 def clearbackup(self, tr, suffix='', prefix=''):
1241 '''Clear backup file with suffix'''
1250 '''Clear backup file with suffix'''
1242 filename = self._actualfilename(tr)
1251 assert len(suffix) > 0 or len(prefix) > 0
1243 self._opener.unlink(filename + suffix)
1252 # using self._filename to avoid having "pending" in the backup filename
1253 self._opener.unlink(prefix + self._filename + suffix)
@@ -384,7 +384,7 b' class cmdalias(object):'
384 self.cmdname = ''
384 self.cmdname = ''
385 self.definition = definition
385 self.definition = definition
386 self.fn = None
386 self.fn = None
387 self.args = []
387 self.givenargs = []
388 self.opts = []
388 self.opts = []
389 self.help = ''
389 self.help = ''
390 self.badalias = None
390 self.badalias = None
@@ -432,7 +432,7 b' class cmdalias(object):'
432 % (self.name, inst))
432 % (self.name, inst))
433 return
433 return
434 self.cmdname = cmd = args.pop(0)
434 self.cmdname = cmd = args.pop(0)
435 args = map(util.expandpath, args)
435 self.givenargs = args
436
436
437 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
437 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
438 if _earlygetopt([invalidarg], args):
438 if _earlygetopt([invalidarg], args):
@@ -448,7 +448,6 b' class cmdalias(object):'
448 else:
448 else:
449 self.fn, self.opts = tableentry
449 self.fn, self.opts = tableentry
450
450
451 self.args = aliasargs(self.fn, args)
452 if self.help.startswith("hg " + cmd):
451 if self.help.startswith("hg " + cmd):
453 # drop prefix in old-style help lines so hg shows the alias
452 # drop prefix in old-style help lines so hg shows the alias
454 self.help = self.help[4 + len(cmd):]
453 self.help = self.help[4 + len(cmd):]
@@ -462,6 +461,11 b' class cmdalias(object):'
462 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
461 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
463 % (self.name, cmd))
462 % (self.name, cmd))
464
463
464 @property
465 def args(self):
466 args = map(util.expandpath, self.givenargs)
467 return aliasargs(self.fn, args)
468
465 def __getattr__(self, name):
469 def __getattr__(self, name):
466 adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
470 adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
467 if name not in adefaults:
471 if name not in adefaults:
@@ -629,10 +633,16 b' def runcommand(lui, repo, cmd, fullargs,'
629 # run pre-hook, and abort if it fails
633 # run pre-hook, and abort if it fails
630 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
634 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
631 pats=cmdpats, opts=cmdoptions)
635 pats=cmdpats, opts=cmdoptions)
632 ret = _runcommand(ui, options, cmd, d)
636 try:
633 # run post-hook, passing command result
637 ret = _runcommand(ui, options, cmd, d)
634 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
638 # run post-hook, passing command result
635 result=ret, pats=cmdpats, opts=cmdoptions)
639 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
640 result=ret, pats=cmdpats, opts=cmdoptions)
641 except Exception:
642 # run failure hook and re-raise
643 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
644 pats=cmdpats, opts=cmdoptions)
645 raise
636 return ret
646 return ret
637
647
638 def _getlocal(ui, rpath, wd=None):
648 def _getlocal(ui, rpath, wd=None):
@@ -660,12 +670,8 b' def _getlocal(ui, rpath, wd=None):'
660
670
661 return path, lui
671 return path, lui
662
672
663 def _checkshellalias(lui, ui, args, precheck=True):
673 def _checkshellalias(lui, ui, args):
664 """Return the function to run the shell alias, if it is required
674 """Return the function to run the shell alias, if it is required"""
665
666 'precheck' is whether this function is invoked before adding
667 aliases or not.
668 """
669 options = {}
675 options = {}
670
676
671 try:
677 try:
@@ -676,16 +682,11 b' def _checkshellalias(lui, ui, args, prec'
676 if not args:
682 if not args:
677 return
683 return
678
684
679 if precheck:
685 cmdtable = commands.table
680 strict = True
681 cmdtable = commands.table.copy()
682 addaliases(lui, cmdtable)
683 else:
684 strict = False
685 cmdtable = commands.table
686
686
687 cmd = args[0]
687 cmd = args[0]
688 try:
688 try:
689 strict = ui.configbool("ui", "strict")
689 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
690 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
690 except (error.AmbiguousCommand, error.UnknownCommand):
691 except (error.AmbiguousCommand, error.UnknownCommand):
691 return
692 return
@@ -735,12 +736,6 b' def _dispatch(req):'
735 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
736 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
736 path, lui = _getlocal(ui, rpath)
737 path, lui = _getlocal(ui, rpath)
737
738
738 # Now that we're operating in the right directory/repository with
739 # the right config settings, check for shell aliases
740 shellaliasfn = _checkshellalias(lui, ui, args)
741 if shellaliasfn:
742 return shellaliasfn()
743
744 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
739 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
745 # reposetup. Programs like TortoiseHg will call _dispatch several
740 # reposetup. Programs like TortoiseHg will call _dispatch several
746 # times so we keep track of configured extensions in _loaded.
741 # times so we keep track of configured extensions in _loaded.
@@ -762,13 +757,11 b' def _dispatch(req):'
762
757
763 addaliases(lui, commands.table)
758 addaliases(lui, commands.table)
764
759
765 if not lui.configbool("ui", "strict"):
760 # All aliases and commands are completely defined, now.
766 # All aliases and commands are completely defined, now.
761 # Check abbreviation/ambiguity of shell alias.
767 # Check abbreviation/ambiguity of shell alias again, because shell
762 shellaliasfn = _checkshellalias(lui, ui, args)
768 # alias may cause failure of "_parse" (see issue4355)
763 if shellaliasfn:
769 shellaliasfn = _checkshellalias(lui, ui, args, precheck=False)
764 return shellaliasfn()
770 if shellaliasfn:
771 return shellaliasfn()
772
765
773 # check for fallback encoding
766 # check for fallback encoding
774 fallback = lui.config('ui', 'fallbackencoding')
767 fallback = lui.config('ui', 'fallbackencoding')
@@ -825,7 +818,7 b' def _dispatch(req):'
825
818
826 if cmdoptions.get('insecure', False):
819 if cmdoptions.get('insecure', False):
827 for ui_ in uis:
820 for ui_ in uis:
828 ui_.setconfig('web', 'cacerts', '!', '--insecure')
821 ui_.insecureconnections = True
829
822
830 if options['version']:
823 if options['version']:
831 return commands.version_(ui)
824 return commands.version_(ui)
@@ -15,12 +15,17 b' from __future__ import absolute_import'
15
15
16 # Do not import anything here, please
16 # Do not import anything here, please
17
17
18 class HintException(Exception):
18 class Hint(object):
19 """Mix-in to provide a hint of an error
20
21 This should come first in the inheritance list to consume a hint and
22 pass remaining arguments to the exception class.
23 """
19 def __init__(self, *args, **kw):
24 def __init__(self, *args, **kw):
20 Exception.__init__(self, *args)
25 self.hint = kw.pop('hint', None)
21 self.hint = kw.get('hint')
26 super(Hint, self).__init__(*args, **kw)
22
27
23 class RevlogError(HintException):
28 class RevlogError(Hint, Exception):
24 pass
29 pass
25
30
26 class FilteredIndexError(IndexError):
31 class FilteredIndexError(IndexError):
@@ -50,10 +55,10 b' class ManifestLookupError(LookupError):'
50 class CommandError(Exception):
55 class CommandError(Exception):
51 """Exception raised on errors in parsing the command line."""
56 """Exception raised on errors in parsing the command line."""
52
57
53 class InterventionRequired(HintException):
58 class InterventionRequired(Hint, Exception):
54 """Exception raised when a command requires human intervention."""
59 """Exception raised when a command requires human intervention."""
55
60
56 class Abort(HintException):
61 class Abort(Hint, Exception):
57 """Raised if a command needs to print an error and exit."""
62 """Raised if a command needs to print an error and exit."""
58
63
59 class HookLoadError(Abort):
64 class HookLoadError(Abort):
@@ -87,10 +92,10 b' class ResponseExpected(Abort):'
87 from .i18n import _
92 from .i18n import _
88 Abort.__init__(self, _('response expected'))
93 Abort.__init__(self, _('response expected'))
89
94
90 class OutOfBandError(HintException):
95 class OutOfBandError(Hint, Exception):
91 """Exception raised when a remote repo reports failure"""
96 """Exception raised when a remote repo reports failure"""
92
97
93 class ParseError(HintException):
98 class ParseError(Hint, Exception):
94 """Raised when parsing config files and {rev,file}sets (msg[, pos])"""
99 """Raised when parsing config files and {rev,file}sets (msg[, pos])"""
95
100
96 class UnknownIdentifier(ParseError):
101 class UnknownIdentifier(ParseError):
@@ -102,7 +107,7 b' class UnknownIdentifier(ParseError):'
102 self.function = function
107 self.function = function
103 self.symbols = symbols
108 self.symbols = symbols
104
109
105 class RepoError(HintException):
110 class RepoError(Hint, Exception):
106 pass
111 pass
107
112
108 class RepoLookupError(RepoError):
113 class RepoLookupError(RepoError):
@@ -235,3 +240,6 b' class InvalidBundleSpecification(Excepti'
235
240
236 class UnsupportedBundleSpecification(Exception):
241 class UnsupportedBundleSpecification(Exception):
237 """error raised when a bundle specification is not supported."""
242 """error raised when a bundle specification is not supported."""
243
244 class CorruptedState(Exception):
245 """error raised when a command is not able to read its state from file"""
@@ -8,6 +8,7 b''
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import hashlib
11
12
12 from .i18n import _
13 from .i18n import _
13 from .node import (
14 from .node import (
@@ -857,14 +858,14 b' def _pushbundle2(pushop):'
857 try:
858 try:
858 reply = pushop.remote.unbundle(stream, ['force'], 'push')
859 reply = pushop.remote.unbundle(stream, ['force'], 'push')
859 except error.BundleValueError as exc:
860 except error.BundleValueError as exc:
860 raise error.Abort('missing support for %s' % exc)
861 raise error.Abort(_('missing support for %s') % exc)
861 try:
862 try:
862 trgetter = None
863 trgetter = None
863 if pushback:
864 if pushback:
864 trgetter = pushop.trmanager.transaction
865 trgetter = pushop.trmanager.transaction
865 op = bundle2.processbundle(pushop.repo, reply, trgetter)
866 op = bundle2.processbundle(pushop.repo, reply, trgetter)
866 except error.BundleValueError as exc:
867 except error.BundleValueError as exc:
867 raise error.Abort('missing support for %s' % exc)
868 raise error.Abort(_('missing support for %s') % exc)
868 except bundle2.AbortFromPart as exc:
869 except bundle2.AbortFromPart as exc:
869 pushop.ui.status(_('remote: %s\n') % exc)
870 pushop.ui.status(_('remote: %s\n') % exc)
870 raise error.Abort(_('push failed on remote'), hint=exc.hint)
871 raise error.Abort(_('push failed on remote'), hint=exc.hint)
@@ -1055,7 +1056,8 b' class pulloperation(object):'
1055 # revision we try to pull (None is "all")
1056 # revision we try to pull (None is "all")
1056 self.heads = heads
1057 self.heads = heads
1057 # bookmark pulled explicitly
1058 # bookmark pulled explicitly
1058 self.explicitbookmarks = bookmarks
1059 self.explicitbookmarks = [repo._bookmarks.expandname(bookmark)
1060 for bookmark in bookmarks]
1059 # do we force pull?
1061 # do we force pull?
1060 self.force = force
1062 self.force = force
1061 # whether a streaming clone was requested
1063 # whether a streaming clone was requested
@@ -1323,7 +1325,7 b' def _pullbundle2(pullop):'
1323 try:
1325 try:
1324 op = bundle2.processbundle(pullop.repo, bundle, pullop.gettransaction)
1326 op = bundle2.processbundle(pullop.repo, bundle, pullop.gettransaction)
1325 except error.BundleValueError as exc:
1327 except error.BundleValueError as exc:
1326 raise error.Abort('missing support for %s' % exc)
1328 raise error.Abort(_('missing support for %s') % exc)
1327
1329
1328 if pullop.fetch:
1330 if pullop.fetch:
1329 results = [cg['return'] for cg in op.records['changegroup']]
1331 results = [cg['return'] for cg in op.records['changegroup']]
@@ -1646,7 +1648,7 b' def check_heads(repo, their_heads, conte'
1646 Used by peer for unbundling.
1648 Used by peer for unbundling.
1647 """
1649 """
1648 heads = repo.heads()
1650 heads = repo.heads()
1649 heads_hash = util.sha1(''.join(sorted(heads))).digest()
1651 heads_hash = hashlib.sha1(''.join(sorted(heads))).digest()
1650 if not (their_heads == ['force'] or their_heads == heads or
1652 if not (their_heads == ['force'] or their_heads == heads or
1651 their_heads == ['hashed', heads_hash]):
1653 their_heads == ['hashed', heads_hash]):
1652 # someone else committed/pushed/unbundled while we
1654 # someone else committed/pushed/unbundled while we
@@ -25,7 +25,7 b' from . import ('
25 _aftercallbacks = {}
25 _aftercallbacks = {}
26 _order = []
26 _order = []
27 _builtin = set(['hbisect', 'bookmarks', 'parentrevspec', 'progress', 'interhg',
27 _builtin = set(['hbisect', 'bookmarks', 'parentrevspec', 'progress', 'interhg',
28 'inotify'])
28 'inotify', 'hgcia'])
29
29
30 def extensions(ui=None):
30 def extensions(ui=None):
31 if ui:
31 if ui:
@@ -127,6 +127,21 b' def load(ui, name, path):'
127 fn(loaded=True)
127 fn(loaded=True)
128 return mod
128 return mod
129
129
130 def _runuisetup(name, ui):
131 uisetup = getattr(_extensions[name], 'uisetup', None)
132 if uisetup:
133 uisetup(ui)
134
135 def _runextsetup(name, ui):
136 extsetup = getattr(_extensions[name], 'extsetup', None)
137 if extsetup:
138 try:
139 extsetup(ui)
140 except TypeError:
141 if extsetup.func_code.co_argcount != 0:
142 raise
143 extsetup() # old extsetup with no ui argument
144
130 def loadall(ui):
145 def loadall(ui):
131 result = ui.configitems("extensions")
146 result = ui.configitems("extensions")
132 newindex = len(_order)
147 newindex = len(_order)
@@ -148,19 +163,10 b' def loadall(ui):'
148 ui.traceback()
163 ui.traceback()
149
164
150 for name in _order[newindex:]:
165 for name in _order[newindex:]:
151 uisetup = getattr(_extensions[name], 'uisetup', None)
166 _runuisetup(name, ui)
152 if uisetup:
153 uisetup(ui)
154
167
155 for name in _order[newindex:]:
168 for name in _order[newindex:]:
156 extsetup = getattr(_extensions[name], 'extsetup', None)
169 _runextsetup(name, ui)
157 if extsetup:
158 try:
159 extsetup(ui)
160 except TypeError:
161 if extsetup.func_code.co_argcount != 0:
162 raise
163 extsetup() # old extsetup with no ui argument
164
170
165 # Call aftercallbacks that were never met.
171 # Call aftercallbacks that were never met.
166 for shortname in _aftercallbacks:
172 for shortname in _aftercallbacks:
@@ -7,7 +7,6 b''
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import cPickle
11 import os
10 import os
12
11
13 from .i18n import _
12 from .i18n import _
@@ -20,8 +19,11 b' from . import ('
20 encoding,
19 encoding,
21 error,
20 error,
22 templater,
21 templater,
22 util,
23 )
23 )
24
24
25 pickle = util.pickle
26
25 class baseformatter(object):
27 class baseformatter(object):
26 def __init__(self, ui, topic, opts):
28 def __init__(self, ui, topic, opts):
27 self._ui = ui
29 self._ui = ui
@@ -107,7 +109,7 b' class pickleformatter(baseformatter):'
107 self._data.append(self._item)
109 self._data.append(self._item)
108 def end(self):
110 def end(self):
109 baseformatter.end(self)
111 baseformatter.end(self)
110 self._ui.write(cPickle.dumps(self._data))
112 self._ui.write(pickle.dumps(self._data))
111
113
112 def _jsonifyobj(v):
114 def _jsonifyobj(v):
113 if isinstance(v, tuple):
115 if isinstance(v, tuple):
@@ -19,8 +19,6 b' Data depends on type.'
19
19
20 from __future__ import absolute_import
20 from __future__ import absolute_import
21
21
22 import heapq
23
24 from .node import nullrev
22 from .node import nullrev
25 from . import (
23 from . import (
26 revset,
24 revset,
@@ -32,207 +30,11 b" PARENT = 'P'"
32 GRANDPARENT = 'G'
30 GRANDPARENT = 'G'
33 MISSINGPARENT = 'M'
31 MISSINGPARENT = 'M'
34 # Style of line to draw. None signals a line that ends and is removed at this
32 # Style of line to draw. None signals a line that ends and is removed at this
35 # point.
33 # point. A number prefix means only the last N characters of the current block
34 # will use that style, the rest will use the PARENT style. Add a - sign
35 # (so making N negative) and all but the first N characters use that style.
36 EDGES = {PARENT: '|', GRANDPARENT: ':', MISSINGPARENT: None}
36 EDGES = {PARENT: '|', GRANDPARENT: ':', MISSINGPARENT: None}
37
37
38 def groupbranchiter(revs, parentsfunc, firstbranch=()):
39 """Yield revisions from heads to roots one (topo) branch at a time.
40
41 This function aims to be used by a graph generator that wishes to minimize
42 the number of parallel branches and their interleaving.
43
44 Example iteration order (numbers show the "true" order in a changelog):
45
46 o 4
47 |
48 o 1
49 |
50 | o 3
51 | |
52 | o 2
53 |/
54 o 0
55
56 Note that the ancestors of merges are understood by the current
57 algorithm to be on the same branch. This means no reordering will
58 occur behind a merge.
59 """
60
61 ### Quick summary of the algorithm
62 #
63 # This function is based around a "retention" principle. We keep revisions
64 # in memory until we are ready to emit a whole branch that immediately
65 # "merges" into an existing one. This reduces the number of parallel
66 # branches with interleaved revisions.
67 #
68 # During iteration revs are split into two groups:
69 # A) revision already emitted
70 # B) revision in "retention". They are stored as different subgroups.
71 #
72 # for each REV, we do the following logic:
73 #
74 # 1) if REV is a parent of (A), we will emit it. If there is a
75 # retention group ((B) above) that is blocked on REV being
76 # available, we emit all the revisions out of that retention
77 # group first.
78 #
79 # 2) else, we'll search for a subgroup in (B) awaiting for REV to be
80 # available, if such subgroup exist, we add REV to it and the subgroup is
81 # now awaiting for REV.parents() to be available.
82 #
83 # 3) finally if no such group existed in (B), we create a new subgroup.
84 #
85 #
86 # To bootstrap the algorithm, we emit the tipmost revision (which
87 # puts it in group (A) from above).
88
89 revs.sort(reverse=True)
90
91 # Set of parents of revision that have been emitted. They can be considered
92 # unblocked as the graph generator is already aware of them so there is no
93 # need to delay the revisions that reference them.
94 #
95 # If someone wants to prioritize a branch over the others, pre-filling this
96 # set will force all other branches to wait until this branch is ready to be
97 # emitted.
98 unblocked = set(firstbranch)
99
100 # list of groups waiting to be displayed, each group is defined by:
101 #
102 # (revs: lists of revs waiting to be displayed,
103 # blocked: set of that cannot be displayed before those in 'revs')
104 #
105 # The second value ('blocked') correspond to parents of any revision in the
106 # group ('revs') that is not itself contained in the group. The main idea
107 # of this algorithm is to delay as much as possible the emission of any
108 # revision. This means waiting for the moment we are about to display
109 # these parents to display the revs in a group.
110 #
111 # This first implementation is smart until it encounters a merge: it will
112 # emit revs as soon as any parent is about to be emitted and can grow an
113 # arbitrary number of revs in 'blocked'. In practice this mean we properly
114 # retains new branches but gives up on any special ordering for ancestors
115 # of merges. The implementation can be improved to handle this better.
116 #
117 # The first subgroup is special. It corresponds to all the revision that
118 # were already emitted. The 'revs' lists is expected to be empty and the
119 # 'blocked' set contains the parents revisions of already emitted revision.
120 #
121 # You could pre-seed the <parents> set of groups[0] to a specific
122 # changesets to select what the first emitted branch should be.
123 groups = [([], unblocked)]
124 pendingheap = []
125 pendingset = set()
126
127 heapq.heapify(pendingheap)
128 heappop = heapq.heappop
129 heappush = heapq.heappush
130 for currentrev in revs:
131 # Heap works with smallest element, we want highest so we invert
132 if currentrev not in pendingset:
133 heappush(pendingheap, -currentrev)
134 pendingset.add(currentrev)
135 # iterates on pending rev until after the current rev have been
136 # processed.
137 rev = None
138 while rev != currentrev:
139 rev = -heappop(pendingheap)
140 pendingset.remove(rev)
141
142 # Seek for a subgroup blocked, waiting for the current revision.
143 matching = [i for i, g in enumerate(groups) if rev in g[1]]
144
145 if matching:
146 # The main idea is to gather together all sets that are blocked
147 # on the same revision.
148 #
149 # Groups are merged when a common blocking ancestor is
150 # observed. For example, given two groups:
151 #
152 # revs [5, 4] waiting for 1
153 # revs [3, 2] waiting for 1
154 #
155 # These two groups will be merged when we process
156 # 1. In theory, we could have merged the groups when
157 # we added 2 to the group it is now in (we could have
158 # noticed the groups were both blocked on 1 then), but
159 # the way it works now makes the algorithm simpler.
160 #
161 # We also always keep the oldest subgroup first. We can
162 # probably improve the behavior by having the longest set
163 # first. That way, graph algorithms could minimise the length
164 # of parallel lines their drawing. This is currently not done.
165 targetidx = matching.pop(0)
166 trevs, tparents = groups[targetidx]
167 for i in matching:
168 gr = groups[i]
169 trevs.extend(gr[0])
170 tparents |= gr[1]
171 # delete all merged subgroups (except the one we kept)
172 # (starting from the last subgroup for performance and
173 # sanity reasons)
174 for i in reversed(matching):
175 del groups[i]
176 else:
177 # This is a new head. We create a new subgroup for it.
178 targetidx = len(groups)
179 groups.append(([], set([rev])))
180
181 gr = groups[targetidx]
182
183 # We now add the current nodes to this subgroups. This is done
184 # after the subgroup merging because all elements from a subgroup
185 # that relied on this rev must precede it.
186 #
187 # we also update the <parents> set to include the parents of the
188 # new nodes.
189 if rev == currentrev: # only display stuff in rev
190 gr[0].append(rev)
191 gr[1].remove(rev)
192 parents = [p for p in parentsfunc(rev) if p > nullrev]
193 gr[1].update(parents)
194 for p in parents:
195 if p not in pendingset:
196 pendingset.add(p)
197 heappush(pendingheap, -p)
198
199 # Look for a subgroup to display
200 #
201 # When unblocked is empty (if clause), we were not waiting for any
202 # revisions during the first iteration (if no priority was given) or
203 # if we emitted a whole disconnected set of the graph (reached a
204 # root). In that case we arbitrarily take the oldest known
205 # subgroup. The heuristic could probably be better.
206 #
207 # Otherwise (elif clause) if the subgroup is blocked on
208 # a revision we just emitted, we can safely emit it as
209 # well.
210 if not unblocked:
211 if len(groups) > 1: # display other subset
212 targetidx = 1
213 gr = groups[1]
214 elif not gr[1] & unblocked:
215 gr = None
216
217 if gr is not None:
218 # update the set of awaited revisions with the one from the
219 # subgroup
220 unblocked |= gr[1]
221 # output all revisions in the subgroup
222 for r in gr[0]:
223 yield r
224 # delete the subgroup that you just output
225 # unless it is groups[0] in which case you just empty it.
226 if targetidx:
227 del groups[targetidx]
228 else:
229 gr[0][:] = []
230 # Check if we have some subgroup waiting for revisions we are not going to
231 # iterate over
232 for g in groups:
233 for r in g[0]:
234 yield r
235
236 def dagwalker(repo, revs):
38 def dagwalker(repo, revs):
237 """cset DAG generator yielding (id, CHANGESET, ctx, [parentinfo]) tuples
39 """cset DAG generator yielding (id, CHANGESET, ctx, [parentinfo]) tuples
238
40
@@ -250,16 +52,6 b' def dagwalker(repo, revs):'
250
52
251 gpcache = {}
53 gpcache = {}
252
54
253 if repo.ui.configbool('experimental', 'graph-group-branches', False):
254 firstbranch = ()
255 firstbranchrevset = repo.ui.config(
256 'experimental', 'graph-group-branches.firstbranch', '')
257 if firstbranchrevset:
258 firstbranch = repo.revs(firstbranchrevset)
259 parentrevs = repo.changelog.parentrevs
260 revs = groupbranchiter(revs, parentrevs, firstbranch)
261 revs = revset.baseset(revs)
262
263 for rev in revs:
55 for rev in revs:
264 ctx = repo[rev]
56 ctx = repo[rev]
265 # partition into parents in the rev set and missing parents, then
57 # partition into parents in the rev set and missing parents, then
@@ -653,6 +445,22 b' def ascii(ui, state, type, char, text, c'
653 while len(text) < len(lines):
445 while len(text) < len(lines):
654 text.append("")
446 text.append("")
655
447
448 if any(len(char) > 1 for char in edgemap.values()):
449 # limit drawing an edge to the first or last N lines of the current
450 # section the rest of the edge is drawn like a parent line.
451 parent = state['styles'][PARENT][-1]
452 def _drawgp(char, i):
453 # should a grandparent character be drawn for this line?
454 if len(char) < 2:
455 return True
456 num = int(char[:-1])
457 # either skip first num lines or take last num lines, based on sign
458 return -num <= i if num < 0 else (len(lines) - i) <= num
459 for i, line in enumerate(lines):
460 line[:] = [c[-1] if _drawgp(c, i) else parent for c in line]
461 edgemap.update(
462 (e, (c if len(c) < 2 else parent)) for e, c in edgemap.items())
463
656 # print lines
464 # print lines
657 indentation_level = max(ncols, ncols + coldiff)
465 indentation_level = max(ncols, ncols + coldiff)
658 for (line, logstr) in zip(lines, text):
466 for (line, logstr) in zip(lines, text):
@@ -811,6 +811,15 b' variables it is passed are listed with n'
811 dictionary of options (with unspecified options set to their defaults).
811 dictionary of options (with unspecified options set to their defaults).
812 ``$HG_PATS`` is a list of arguments. Hook failure is ignored.
812 ``$HG_PATS`` is a list of arguments. Hook failure is ignored.
813
813
814 ``fail-<command>``
815 Run after a failed invocation of an associated command. The contents
816 of the command line are passed as ``$HG_ARGS``. Parsed command line
817 arguments are passed as ``$HG_PATS`` and ``$HG_OPTS``. These contain
818 string representations of the python data internally passed to
819 <command>. ``$HG_OPTS`` is a dictionary of options (with unspecified
820 options set to their defaults). ``$HG_PATS`` is a list of arguments.
821 Hook failure is ignored.
822
814 ``pre-<command>``
823 ``pre-<command>``
815 Run before executing the associated command. The contents of the
824 Run before executing the associated command. The contents of the
816 command line are passed as ``$HG_ARGS``. Parsed command line arguments
825 command line are passed as ``$HG_ARGS``. Parsed command line arguments
@@ -967,6 +976,8 b' is treated as a failure.'
967 ``hostfingerprints``
976 ``hostfingerprints``
968 --------------------
977 --------------------
969
978
979 (Deprecated. Use ``[hostsecurity]``'s ``fingerprints`` options instead.)
980
970 Fingerprints of the certificates of known HTTPS servers.
981 Fingerprints of the certificates of known HTTPS servers.
971
982
972 A HTTPS connection to a server with a fingerprint configured here will
983 A HTTPS connection to a server with a fingerprint configured here will
@@ -986,6 +997,114 b' For example::'
986 hg.intevation.de = fc:e2:8d:d9:51:cd:cb:c1:4d:18:6b:b7:44:8d:49:72:57:e6:cd:33
997 hg.intevation.de = fc:e2:8d:d9:51:cd:cb:c1:4d:18:6b:b7:44:8d:49:72:57:e6:cd:33
987 hg.intevation.org = fc:e2:8d:d9:51:cd:cb:c1:4d:18:6b:b7:44:8d:49:72:57:e6:cd:33
998 hg.intevation.org = fc:e2:8d:d9:51:cd:cb:c1:4d:18:6b:b7:44:8d:49:72:57:e6:cd:33
988
999
1000 ``hostsecurity``
1001 ----------------
1002
1003 Used to specify global and per-host security settings for connecting to
1004 other machines.
1005
1006 The following options control default behavior for all hosts.
1007
1008 ``ciphers``
1009 Defines the cryptographic ciphers to use for connections.
1010
1011 Value must be a valid OpenSSL Cipher List Format as documented at
1012 https://www.openssl.org/docs/manmaster/apps/ciphers.html#CIPHER-LIST-FORMAT.
1013
1014 This setting is for advanced users only. Setting to incorrect values
1015 can significantly lower connection security or decrease performance.
1016 You have been warned.
1017
1018 This option requires Python 2.7.
1019
1020 ``minimumprotocol``
1021 Defines the minimum channel encryption protocol to use.
1022
1023 By default, the highest version of TLS supported by both client and server
1024 is used.
1025
1026 Allowed values are: ``tls1.0``, ``tls1.1``, ``tls1.2``.
1027
1028 When running on an old Python version, only ``tls1.0`` is allowed since
1029 old versions of Python only support up to TLS 1.0.
1030
1031 When running a Python that supports modern TLS versions, the default is
1032 ``tls1.1``. ``tls1.0`` can still be used to allow TLS 1.0. However, this
1033 weakens security and should only be used as a feature of last resort if
1034 a server does not support TLS 1.1+.
1035
1036 Options in the ``[hostsecurity]`` section can have the form
1037 ``hostname``:``setting``. This allows multiple settings to be defined on a
1038 per-host basis.
1039
1040 The following per-host settings can be defined.
1041
1042 ``ciphers``
1043 This behaves like ``ciphers`` as described above except it only applies
1044 to the host on which it is defined.
1045
1046 ``fingerprints``
1047 A list of hashes of the DER encoded peer/remote certificate. Values have
1048 the form ``algorithm``:``fingerprint``. e.g.
1049 ``sha256:c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2``.
1050
1051 The following algorithms/prefixes are supported: ``sha1``, ``sha256``,
1052 ``sha512``.
1053
1054 Use of ``sha256`` or ``sha512`` is preferred.
1055
1056 If a fingerprint is specified, the CA chain is not validated for this
1057 host and Mercurial will require the remote certificate to match one
1058 of the fingerprints specified. This means if the server updates its
1059 certificate, Mercurial will abort until a new fingerprint is defined.
1060 This can provide stronger security than traditional CA-based validation
1061 at the expense of convenience.
1062
1063 This option takes precedence over ``verifycertsfile``.
1064
1065 ``minimumprotocol``
1066 This behaves like ``minimumprotocol`` as described above except it
1067 only applies to the host on which it is defined.
1068
1069 ``verifycertsfile``
1070 Path to file a containing a list of PEM encoded certificates used to
1071 verify the server certificate. Environment variables and ``~user``
1072 constructs are expanded in the filename.
1073
1074 The server certificate or the certificate's certificate authority (CA)
1075 must match a certificate from this file or certificate verification
1076 will fail and connections to the server will be refused.
1077
1078 If defined, only certificates provided by this file will be used:
1079 ``web.cacerts`` and any system/default certificates will not be
1080 used.
1081
1082 This option has no effect if the per-host ``fingerprints`` option
1083 is set.
1084
1085 The format of the file is as follows:
1086
1087 -----BEGIN CERTIFICATE-----
1088 ... (certificate in base64 PEM encoding) ...
1089 -----END CERTIFICATE-----
1090 -----BEGIN CERTIFICATE-----
1091 ... (certificate in base64 PEM encoding) ...
1092 -----END CERTIFICATE-----
1093
1094 For example::
1095
1096 [hostsecurity]
1097 hg.example.com:fingerprints = sha256:c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2
1098 hg2.example.com:fingerprints = sha1:914f1aff87249c09b6859b88b1906d30756491ca, sha1:fc:e2:8d:d9:51:cd:cb:c1:4d:18:6b:b7:44:8d:49:72:57:e6:cd:33
1099 foo.example.com:verifycertsfile = /etc/ssl/trusted-ca-certs.pem
1100
1101 To change the default minimum protocol version to TLS 1.2 but to allow TLS 1.1
1102 when connecting to ``hg.example.com``::
1103
1104 [hostsecurity]
1105 minimumprotocol = tls1.2
1106 hg.example.com:minimumprotocol = tls1.1
1107
989 ``http_proxy``
1108 ``http_proxy``
990 --------------
1109 --------------
991
1110
@@ -1020,8 +1139,8 b' This section specifies behavior during m'
1020 file in the changeset being merged or updated to, and has different
1139 file in the changeset being merged or updated to, and has different
1021 contents. Options are ``abort``, ``warn`` and ``ignore``. With ``abort``,
1140 contents. Options are ``abort``, ``warn`` and ``ignore``. With ``abort``,
1022 abort on such files. With ``warn``, warn on such files and back them up as
1141 abort on such files. With ``warn``, warn on such files and back them up as
1023 .orig. With ``ignore``, don't print a warning and back them up as
1142 ``.orig``. With ``ignore``, don't print a warning and back them up as
1024 .orig. (default: ``abort``)
1143 ``.orig``. (default: ``abort``)
1025
1144
1026 ``checkunknown``
1145 ``checkunknown``
1027 Controls behavior when an unknown file that isn't ignored has the same name
1146 Controls behavior when an unknown file that isn't ignored has the same name
@@ -1210,6 +1329,18 b' The following sub-options can be defined'
1210 The URL to use for push operations. If not defined, the location
1329 The URL to use for push operations. If not defined, the location
1211 defined by the path's main entry is used.
1330 defined by the path's main entry is used.
1212
1331
1332 ``pushrev``
1333 A revset defining which revisions to push by default.
1334
1335 When :hg:`push` is executed without a ``-r`` argument, the revset
1336 defined by this sub-option is evaluated to determine what to push.
1337
1338 For example, a value of ``.`` will push the working directory's
1339 revision by default.
1340
1341 Revsets specifying bookmarks will not result in the bookmark being
1342 pushed.
1343
1213 The following special named paths exist:
1344 The following special named paths exist:
1214
1345
1215 ``default``
1346 ``default``
@@ -1442,16 +1573,6 b' Configuration for extensions that need t'
1442 Optional. Method to enable TLS when connecting to mail server: starttls,
1573 Optional. Method to enable TLS when connecting to mail server: starttls,
1443 smtps or none. (default: none)
1574 smtps or none. (default: none)
1444
1575
1445 ``verifycert``
1446 Optional. Verification for the certificate of mail server, when
1447 ``tls`` is starttls or smtps. "strict", "loose" or False. For
1448 "strict" or "loose", the certificate is verified as same as the
1449 verification for HTTPS connections (see ``[hostfingerprints]`` and
1450 ``[web] cacerts`` also). For "strict", sending email is also
1451 aborted, if there is no configuration for mail server in
1452 ``[hostfingerprints]`` and ``[web] cacerts``. --insecure for
1453 :hg:`email` overwrites this as "loose". (default: strict)
1454
1455 ``username``
1576 ``username``
1456 Optional. User name for authenticating with the SMTP server.
1577 Optional. User name for authenticating with the SMTP server.
1457 (default: None)
1578 (default: None)
@@ -1738,6 +1859,13 b' User interface controls.'
1738 large organisation with its own Mercurial deployment process and crash
1859 large organisation with its own Mercurial deployment process and crash
1739 reports should be addressed to your internal support.
1860 reports should be addressed to your internal support.
1740
1861
1862 ``textwidth``
1863 Maximum width of help text. A longer line generated by ``hg help`` or
1864 ``hg subcommand --help`` will be broken after white space to get this
1865 width or the terminal width, whichever comes first.
1866 A non-positive value will disable this and the terminal width will be
1867 used. (default: 78)
1868
1741 ``timeout``
1869 ``timeout``
1742 The timeout used when a lock is held (in seconds), a negative value
1870 The timeout used when a lock is held (in seconds), a negative value
1743 means no timeout. (default: 600)
1871 means no timeout. (default: 600)
@@ -1945,6 +2073,14 b' The full set of options is:'
1945 ``ipv6``
2073 ``ipv6``
1946 Whether to use IPv6. (default: False)
2074 Whether to use IPv6. (default: False)
1947
2075
2076 ``labels``
2077 List of string *labels* associated with the repository.
2078
2079 Labels are exposed as a template keyword and can be used to customize
2080 output. e.g. the ``index`` template can group or filter repositories
2081 by labels and the ``summary`` template can display additional content
2082 if a specific label is present.
2083
1948 ``logoimg``
2084 ``logoimg``
1949 File name of the logo image that some templates display on each page.
2085 File name of the logo image that some templates display on each page.
1950 The file name is relative to ``staticurl``. That is, the full path to
2086 The file name is relative to ``staticurl``. That is, the full path to
@@ -81,6 +81,10 b' Some sample command line templates:'
81
81
82 $ hg log -r 0 --template "files: {join(files, ', ')}\n"
82 $ hg log -r 0 --template "files: {join(files, ', ')}\n"
83
83
84 - Separate non-empty arguments by a " "::
85
86 $ hg log -r 0 --template "{separate(' ', node, bookmarks, tags}\n"
87
84 - Modify each line of a commit description::
88 - Modify each line of a commit description::
85
89
86 $ hg log --template "{splitlines(desc) % '**** {line}\n'}"
90 $ hg log --template "{splitlines(desc) % '**** {line}\n'}"
@@ -9,6 +9,7 b''
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import errno
11 import errno
12 import hashlib
12 import os
13 import os
13 import shutil
14 import shutil
14
15
@@ -43,6 +44,9 b' from . import ('
43
44
44 release = lock.release
45 release = lock.release
45
46
47 # shared features
48 sharedbookmarks = 'bookmarks'
49
46 def _local(path):
50 def _local(path):
47 path = util.expandpath(util.urllocalpath(path))
51 path = util.expandpath(util.urllocalpath(path))
48 return (os.path.isfile(path) and bundlerepo or localrepo)
52 return (os.path.isfile(path) and bundlerepo or localrepo)
@@ -257,7 +261,7 b' def postshare(sourcerepo, destrepo, book'
257
261
258 if bookmarks:
262 if bookmarks:
259 fp = destrepo.vfs('shared', 'w')
263 fp = destrepo.vfs('shared', 'w')
260 fp.write('bookmarks\n')
264 fp.write(sharedbookmarks + '\n')
261 fp.close()
265 fp.close()
262
266
263 def _postshareupdate(repo, update, checkout=None):
267 def _postshareupdate(repo, update, checkout=None):
@@ -480,9 +484,11 b' def clone(ui, peeropts, source, dest=Non'
480 ui.status(_('(not using pooled storage: '
484 ui.status(_('(not using pooled storage: '
481 'unable to resolve identity of remote)\n'))
485 'unable to resolve identity of remote)\n'))
482 elif sharenamemode == 'remote':
486 elif sharenamemode == 'remote':
483 sharepath = os.path.join(sharepool, util.sha1(source).hexdigest())
487 sharepath = os.path.join(
488 sharepool, hashlib.sha1(source).hexdigest())
484 else:
489 else:
485 raise error.Abort('unknown share naming mode: %s' % sharenamemode)
490 raise error.Abort(_('unknown share naming mode: %s') %
491 sharenamemode)
486
492
487 if sharepath:
493 if sharepath:
488 return clonewithshare(ui, peeropts, sharepath, source, srcpeer,
494 return clonewithshare(ui, peeropts, sharepath, source, srcpeer,
@@ -921,9 +927,7 b' def remoteui(src, opts):'
921 for key, val in src.configitems(sect):
927 for key, val in src.configitems(sect):
922 dst.setconfig(sect, key, val, 'copied')
928 dst.setconfig(sect, key, val, 'copied')
923 v = src.config('web', 'cacerts')
929 v = src.config('web', 'cacerts')
924 if v == '!':
930 if v:
925 dst.setconfig('web', 'cacerts', v, 'copied')
926 elif v:
927 dst.setconfig('web', 'cacerts', util.expandpath(v), 'copied')
931 dst.setconfig('web', 'cacerts', util.expandpath(v), 'copied')
928
932
929 return dst
933 return dst
@@ -8,11 +8,14 b''
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import BaseHTTPServer
12 import errno
11 import errno
13 import mimetypes
12 import mimetypes
14 import os
13 import os
15
14
15 from .. import util
16
17 httpserver = util.httpserver
18
16 HTTP_OK = 200
19 HTTP_OK = 200
17 HTTP_NOT_MODIFIED = 304
20 HTTP_NOT_MODIFIED = 304
18 HTTP_BAD_REQUEST = 400
21 HTTP_BAD_REQUEST = 400
@@ -107,7 +110,7 b' class continuereader(object):'
107 raise AttributeError
110 raise AttributeError
108
111
109 def _statusmessage(code):
112 def _statusmessage(code):
110 responses = BaseHTTPServer.BaseHTTPRequestHandler.responses
113 responses = httpserver.basehttprequesthandler.responses
111 return responses.get(code, ('Error', 'Unknown error'))[0]
114 return responses.get(code, ('Error', 'Unknown error'))[0]
112
115
113 def statusmessage(code, message=None):
116 def statusmessage(code, message=None):
@@ -187,7 +190,7 b' def get_contact(config):'
187 os.environ.get("EMAIL") or "")
190 os.environ.get("EMAIL") or "")
188
191
189 def caching(web, req):
192 def caching(web, req):
190 tag = str(web.mtime)
193 tag = 'W/"%s"' % web.mtime
191 if req.env.get('HTTP_IF_NONE_MATCH') == tag:
194 if req.env.get('HTTP_IF_NONE_MATCH') == tag:
192 raise ErrorResponse(HTTP_NOT_MODIFIED)
195 raise ErrorResponse(HTTP_NOT_MODIFIED)
193 req.headers.append(('ETag', tag))
196 req.headers.append(('ETag', tag))
@@ -366,7 +366,9 b' class hgwebdir(object):'
366 'lastchange': d,
366 'lastchange': d,
367 'lastchange_sort': d[1]-d[0],
367 'lastchange_sort': d[1]-d[0],
368 'archives': [],
368 'archives': [],
369 'isdirectory': True}
369 'isdirectory': True,
370 'labels': [],
371 }
370
372
371 seendirs.add(name)
373 seendirs.add(name)
372 yield row
374 yield row
@@ -416,6 +418,7 b' class hgwebdir(object):'
416 'lastchange_sort': d[1]-d[0],
418 'lastchange_sort': d[1]-d[0],
417 'archives': archivelist(u, "tip", url),
419 'archives': archivelist(u, "tip", url),
418 'isdirectory': None,
420 'isdirectory': None,
421 'labels': u.configlist('web', 'labels', untrusted=True),
419 }
422 }
420
423
421 yield row
424 yield row
@@ -8,8 +8,6 b''
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import BaseHTTPServer
12 import SocketServer
13 import errno
11 import errno
14 import os
12 import os
15 import socket
13 import socket
@@ -23,6 +21,8 b' from .. import ('
23 util,
21 util,
24 )
22 )
25
23
24 httpservermod = util.httpserver
25 socketserver = util.socketserver
26 urlerr = util.urlerr
26 urlerr = util.urlerr
27 urlreq = util.urlreq
27 urlreq = util.urlreq
28
28
@@ -53,18 +53,18 b' class _error_logger(object):'
53 for msg in seq:
53 for msg in seq:
54 self.handler.log_error("HG error: %s", msg)
54 self.handler.log_error("HG error: %s", msg)
55
55
56 class _httprequesthandler(BaseHTTPServer.BaseHTTPRequestHandler):
56 class _httprequesthandler(httpservermod.basehttprequesthandler):
57
57
58 url_scheme = 'http'
58 url_scheme = 'http'
59
59
60 @staticmethod
60 @staticmethod
61 def preparehttpserver(httpserver, ssl_cert):
61 def preparehttpserver(httpserver, ui):
62 """Prepare .socket of new HTTPServer instance"""
62 """Prepare .socket of new HTTPServer instance"""
63 pass
63 pass
64
64
65 def __init__(self, *args, **kargs):
65 def __init__(self, *args, **kargs):
66 self.protocol_version = 'HTTP/1.1'
66 self.protocol_version = 'HTTP/1.1'
67 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
67 httpservermod.basehttprequesthandler.__init__(self, *args, **kargs)
68
68
69 def _log_any(self, fp, format, *args):
69 def _log_any(self, fp, format, *args):
70 fp.write("%s - - [%s] %s\n" % (self.client_address[0],
70 fp.write("%s - - [%s] %s\n" % (self.client_address[0],
@@ -147,9 +147,9 b' class _httprequesthandler(BaseHTTPServer'
147 env['wsgi.input'] = self.rfile
147 env['wsgi.input'] = self.rfile
148 env['wsgi.errors'] = _error_logger(self)
148 env['wsgi.errors'] = _error_logger(self)
149 env['wsgi.multithread'] = isinstance(self.server,
149 env['wsgi.multithread'] = isinstance(self.server,
150 SocketServer.ThreadingMixIn)
150 socketserver.ThreadingMixIn)
151 env['wsgi.multiprocess'] = isinstance(self.server,
151 env['wsgi.multiprocess'] = isinstance(self.server,
152 SocketServer.ForkingMixIn)
152 socketserver.ForkingMixIn)
153 env['wsgi.run_once'] = 0
153 env['wsgi.run_once'] = 0
154
154
155 self.saved_status = None
155 self.saved_status = None
@@ -222,15 +222,25 b' class _httprequesthandlerssl(_httpreques'
222 url_scheme = 'https'
222 url_scheme = 'https'
223
223
224 @staticmethod
224 @staticmethod
225 def preparehttpserver(httpserver, ssl_cert):
225 def preparehttpserver(httpserver, ui):
226 try:
226 try:
227 import ssl
227 from .. import sslutil
228 ssl.wrap_socket
228 sslutil.modernssl
229 except ImportError:
229 except ImportError:
230 raise error.Abort(_("SSL support is unavailable"))
230 raise error.Abort(_("SSL support is unavailable"))
231 httpserver.socket = ssl.wrap_socket(
231
232 httpserver.socket, server_side=True,
232 certfile = ui.config('web', 'certificate')
233 certfile=ssl_cert, ssl_version=ssl.PROTOCOL_TLSv1)
233
234 # These config options are currently only meant for testing. Use
235 # at your own risk.
236 cafile = ui.config('devel', 'servercafile')
237 reqcert = ui.configbool('devel', 'serverrequirecert')
238
239 httpserver.socket = sslutil.wrapserversocket(httpserver.socket,
240 ui,
241 certfile=certfile,
242 cafile=cafile,
243 requireclientcert=reqcert)
234
244
235 def setup(self):
245 def setup(self):
236 self.connection = self.request
246 self.connection = self.request
@@ -240,10 +250,10 b' class _httprequesthandlerssl(_httpreques'
240 try:
250 try:
241 import threading
251 import threading
242 threading.activeCount() # silence pyflakes and bypass demandimport
252 threading.activeCount() # silence pyflakes and bypass demandimport
243 _mixin = SocketServer.ThreadingMixIn
253 _mixin = socketserver.ThreadingMixIn
244 except ImportError:
254 except ImportError:
245 if util.safehasattr(os, "fork"):
255 if util.safehasattr(os, "fork"):
246 _mixin = SocketServer.ForkingMixIn
256 _mixin = socketserver.ForkingMixIn
247 else:
257 else:
248 class _mixin(object):
258 class _mixin(object):
249 pass
259 pass
@@ -253,18 +263,18 b' def openlog(opt, default):'
253 return open(opt, 'a')
263 return open(opt, 'a')
254 return default
264 return default
255
265
256 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
266 class MercurialHTTPServer(object, _mixin, httpservermod.httpserver):
257
267
258 # SO_REUSEADDR has broken semantics on windows
268 # SO_REUSEADDR has broken semantics on windows
259 if os.name == 'nt':
269 if os.name == 'nt':
260 allow_reuse_address = 0
270 allow_reuse_address = 0
261
271
262 def __init__(self, ui, app, addr, handler, **kwargs):
272 def __init__(self, ui, app, addr, handler, **kwargs):
263 BaseHTTPServer.HTTPServer.__init__(self, addr, handler, **kwargs)
273 httpservermod.httpserver.__init__(self, addr, handler, **kwargs)
264 self.daemon_threads = True
274 self.daemon_threads = True
265 self.application = app
275 self.application = app
266
276
267 handler.preparehttpserver(self, ui.config('web', 'certificate'))
277 handler.preparehttpserver(self, ui)
268
278
269 prefix = ui.config('web', 'prefix', '')
279 prefix = ui.config('web', 'prefix', '')
270 if prefix:
280 if prefix:
@@ -139,7 +139,7 b' def _filerevision(web, req, tmpl, fctx):'
139 yield {"line": t,
139 yield {"line": t,
140 "lineid": "l%d" % (lineno + 1),
140 "lineid": "l%d" % (lineno + 1),
141 "linenumber": "% 6d" % (lineno + 1),
141 "linenumber": "% 6d" % (lineno + 1),
142 "parity": parity.next()}
142 "parity": next(parity)}
143
143
144 return tmpl("filerevision",
144 return tmpl("filerevision",
145 file=f,
145 file=f,
@@ -278,7 +278,7 b' def _search(web, req, tmpl):'
278 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
278 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
279
279
280 yield tmpl('searchentry',
280 yield tmpl('searchentry',
281 parity=parity.next(),
281 parity=next(parity),
282 changelogtag=showtags,
282 changelogtag=showtags,
283 files=files,
283 files=files,
284 **webutil.commonentry(web.repo, ctx))
284 **webutil.commonentry(web.repo, ctx))
@@ -375,7 +375,7 b' def changelog(web, req, tmpl, shortlog=F'
375 break
375 break
376
376
377 entry = webutil.changelistentry(web, web.repo[rev], tmpl)
377 entry = webutil.changelistentry(web, web.repo[rev], tmpl)
378 entry['parity'] = parity.next()
378 entry['parity'] = next(parity)
379 yield entry
379 yield entry
380
380
381 if shortlog:
381 if shortlog:
@@ -527,7 +527,7 b' def manifest(web, req, tmpl):'
527
527
528 fctx = ctx.filectx(full)
528 fctx = ctx.filectx(full)
529 yield {"file": full,
529 yield {"file": full,
530 "parity": parity.next(),
530 "parity": next(parity),
531 "basename": f,
531 "basename": f,
532 "date": fctx.date(),
532 "date": fctx.date(),
533 "size": fctx.size(),
533 "size": fctx.size(),
@@ -545,7 +545,7 b' def manifest(web, req, tmpl):'
545 h = v
545 h = v
546
546
547 path = "%s%s" % (abspath, d)
547 path = "%s%s" % (abspath, d)
548 yield {"parity": parity.next(),
548 yield {"parity": next(parity),
549 "path": path,
549 "path": path,
550 "emptydirs": "/".join(emptydirs),
550 "emptydirs": "/".join(emptydirs),
551 "basename": d}
551 "basename": d}
@@ -554,7 +554,7 b' def manifest(web, req, tmpl):'
554 symrev=symrev,
554 symrev=symrev,
555 path=abspath,
555 path=abspath,
556 up=webutil.up(abspath),
556 up=webutil.up(abspath),
557 upparity=parity.next(),
557 upparity=next(parity),
558 fentries=filelist,
558 fentries=filelist,
559 dentries=dirlist,
559 dentries=dirlist,
560 archives=web.archivelist(hex(node)),
560 archives=web.archivelist(hex(node)),
@@ -582,7 +582,7 b' def tags(web, req, tmpl):'
582 if latestonly:
582 if latestonly:
583 t = t[:1]
583 t = t[:1]
584 for k, n in t:
584 for k, n in t:
585 yield {"parity": parity.next(),
585 yield {"parity": next(parity),
586 "tag": k,
586 "tag": k,
587 "date": web.repo[n].date(),
587 "date": web.repo[n].date(),
588 "node": hex(n)}
588 "node": hex(n)}
@@ -615,7 +615,7 b' def bookmarks(web, req, tmpl):'
615 if latestonly:
615 if latestonly:
616 t = i[:1]
616 t = i[:1]
617 for k, n in t:
617 for k, n in t:
618 yield {"parity": parity.next(),
618 yield {"parity": next(parity),
619 "bookmark": k,
619 "bookmark": k,
620 "date": web.repo[n].date(),
620 "date": web.repo[n].date(),
621 "node": hex(n)}
621 "node": hex(n)}
@@ -677,7 +677,7 b' def summary(web, req, tmpl):'
677 break
677 break
678
678
679 yield tmpl("tagentry",
679 yield tmpl("tagentry",
680 parity=parity.next(),
680 parity=next(parity),
681 tag=k,
681 tag=k,
682 node=hex(n),
682 node=hex(n),
683 date=web.repo[n].date())
683 date=web.repo[n].date())
@@ -688,7 +688,7 b' def summary(web, req, tmpl):'
688 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
688 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
689 marks = sorted(marks, key=sortkey, reverse=True)
689 marks = sorted(marks, key=sortkey, reverse=True)
690 for k, n in marks[:10]: # limit to 10 bookmarks
690 for k, n in marks[:10]: # limit to 10 bookmarks
691 yield {'parity': parity.next(),
691 yield {'parity': next(parity),
692 'bookmark': k,
692 'bookmark': k,
693 'date': web.repo[n].date(),
693 'date': web.repo[n].date(),
694 'node': hex(n)}
694 'node': hex(n)}
@@ -704,11 +704,11 b' def summary(web, req, tmpl):'
704
704
705 l.append(tmpl(
705 l.append(tmpl(
706 'shortlogentry',
706 'shortlogentry',
707 parity=parity.next(),
707 parity=next(parity),
708 **webutil.commonentry(web.repo, ctx)))
708 **webutil.commonentry(web.repo, ctx)))
709
709
710 l.reverse()
710 for entry in reversed(l):
711 yield l
711 yield entry
712
712
713 tip = web.repo['tip']
713 tip = web.repo['tip']
714 count = len(web.repo)
714 count = len(web.repo)
@@ -725,7 +725,8 b' def summary(web, req, tmpl):'
725 shortlog=changelist,
725 shortlog=changelist,
726 node=tip.hex(),
726 node=tip.hex(),
727 symrev='tip',
727 symrev='tip',
728 archives=web.archivelist("tip"))
728 archives=web.archivelist("tip"),
729 labels=web.configlist('web', 'labels'))
729
730
730 @webcommand('filediff')
731 @webcommand('filediff')
731 def filediff(web, req, tmpl):
732 def filediff(web, req, tmpl):
@@ -863,29 +864,41 b' def annotate(web, req, tmpl):'
863 diffopts = patch.difffeatureopts(web.repo.ui, untrusted=True,
864 diffopts = patch.difffeatureopts(web.repo.ui, untrusted=True,
864 section='annotate', whitespace=True)
865 section='annotate', whitespace=True)
865
866
867 def parents(f):
868 for p in f.parents():
869 yield {
870 "node": p.hex(),
871 "rev": p.rev(),
872 }
873
866 def annotate(**map):
874 def annotate(**map):
867 last = None
868 if util.binary(fctx.data()):
875 if util.binary(fctx.data()):
869 mt = (mimetypes.guess_type(fctx.path())[0]
876 mt = (mimetypes.guess_type(fctx.path())[0]
870 or 'application/octet-stream')
877 or 'application/octet-stream')
871 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
878 lines = [((fctx.filectx(fctx.filerev()), 1), '(binary:%s)' % mt)]
872 '(binary:%s)' % mt)])
873 else:
879 else:
874 lines = enumerate(fctx.annotate(follow=True, linenumber=True,
880 lines = fctx.annotate(follow=True, linenumber=True,
875 diffopts=diffopts))
881 diffopts=diffopts)
876 for lineno, ((f, targetline), l) in lines:
882 previousrev = None
877 fnode = f.filenode()
883 blockparitygen = paritygen(1)
878
884 for lineno, ((f, targetline), l) in enumerate(lines):
879 if last != fnode:
885 rev = f.rev()
880 last = fnode
886 if rev != previousrev:
881
887 blockhead = True
882 yield {"parity": parity.next(),
888 blockparity = next(blockparitygen)
889 else:
890 blockhead = None
891 previousrev = rev
892 yield {"parity": next(parity),
883 "node": f.hex(),
893 "node": f.hex(),
884 "rev": f.rev(),
894 "rev": rev,
885 "author": f.user(),
895 "author": f.user(),
896 "parents": parents(f),
886 "desc": f.description(),
897 "desc": f.description(),
887 "extra": f.extra(),
898 "extra": f.extra(),
888 "file": f.path(),
899 "file": f.path(),
900 "blockhead": blockhead,
901 "blockparity": blockparity,
889 "targetline": targetline,
902 "targetline": targetline,
890 "line": l,
903 "line": l,
891 "lineno": lineno + 1,
904 "lineno": lineno + 1,
@@ -963,7 +976,7 b' def filelog(web, req, tmpl):'
963 iterfctx = fctx.filectx(i)
976 iterfctx = fctx.filectx(i)
964
977
965 l.append(dict(
978 l.append(dict(
966 parity=parity.next(),
979 parity=next(parity),
967 filerev=i,
980 filerev=i,
968 file=f,
981 file=f,
969 rename=webutil.renamelink(iterfctx),
982 rename=webutil.renamelink(iterfctx),
@@ -75,7 +75,7 b' class revnav(object):'
75 def _first(self):
75 def _first(self):
76 """return the minimum non-filtered changeset or None"""
76 """return the minimum non-filtered changeset or None"""
77 try:
77 try:
78 return iter(self._revlog).next()
78 return next(iter(self._revlog))
79 except StopIteration:
79 except StopIteration:
80 return None
80 return None
81
81
@@ -247,7 +247,7 b' def branchentries(repo, stripecount, lim'
247 else:
247 else:
248 status = 'open'
248 status = 'open'
249 yield {
249 yield {
250 'parity': parity.next(),
250 'parity': next(parity),
251 'branch': ctx.branch(),
251 'branch': ctx.branch(),
252 'status': status,
252 'status': status,
253 'node': ctx.hex(),
253 'node': ctx.hex(),
@@ -369,7 +369,7 b' def changesetentry(web, req, tmpl, ctx):'
369 template = f in ctx and 'filenodelink' or 'filenolink'
369 template = f in ctx and 'filenodelink' or 'filenolink'
370 files.append(tmpl(template,
370 files.append(tmpl(template,
371 node=ctx.hex(), file=f, blockno=blockno + 1,
371 node=ctx.hex(), file=f, blockno=blockno + 1,
372 parity=parity.next()))
372 parity=next(parity)))
373
373
374 basectx = basechangectx(web.repo, req)
374 basectx = basechangectx(web.repo, req)
375 if basectx is None:
375 if basectx is None:
@@ -450,15 +450,15 b' def diffs(repo, tmpl, ctx, basectx, file'
450 block = []
450 block = []
451 for chunk in patch.diff(repo, node1, node2, m, opts=diffopts):
451 for chunk in patch.diff(repo, node1, node2, m, opts=diffopts):
452 if chunk.startswith('diff') and block:
452 if chunk.startswith('diff') and block:
453 blockno = blockcount.next()
453 blockno = next(blockcount)
454 yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
454 yield tmpl('diffblock', parity=next(parity), blockno=blockno,
455 lines=prettyprintlines(''.join(block), blockno))
455 lines=prettyprintlines(''.join(block), blockno))
456 block = []
456 block = []
457 if chunk.startswith('diff') and style != 'raw':
457 if chunk.startswith('diff') and style != 'raw':
458 chunk = ''.join(chunk.splitlines(True)[1:])
458 chunk = ''.join(chunk.splitlines(True)[1:])
459 block.append(chunk)
459 block.append(chunk)
460 blockno = blockcount.next()
460 blockno = next(blockcount)
461 yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
461 yield tmpl('diffblock', parity=next(parity), blockno=blockno,
462 lines=prettyprintlines(''.join(block), blockno))
462 lines=prettyprintlines(''.join(block), blockno))
463
463
464 def compare(tmpl, context, leftlines, rightlines):
464 def compare(tmpl, context, leftlines, rightlines):
@@ -521,14 +521,14 b' def diffstatgen(ctx, basectx):'
521 def diffsummary(statgen):
521 def diffsummary(statgen):
522 '''Return a short summary of the diff.'''
522 '''Return a short summary of the diff.'''
523
523
524 stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
524 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
525 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
525 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
526 len(stats), addtotal, removetotal)
526 len(stats), addtotal, removetotal)
527
527
528 def diffstat(tmpl, ctx, statgen, parity):
528 def diffstat(tmpl, ctx, statgen, parity):
529 '''Return a diffstat template for each file in the diff.'''
529 '''Return a diffstat template for each file in the diff.'''
530
530
531 stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
531 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
532 files = ctx.files()
532 files = ctx.files()
533
533
534 def pct(i):
534 def pct(i):
@@ -543,7 +543,7 b' def diffstat(tmpl, ctx, statgen, parity)'
543 fileno += 1
543 fileno += 1
544 yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
544 yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
545 total=total, addpct=pct(adds), removepct=pct(removes),
545 total=total, addpct=pct(adds), removepct=pct(removes),
546 parity=parity.next())
546 parity=next(parity))
547
547
548 class sessionvars(object):
548 class sessionvars(object):
549 def __init__(self, vars, start='?'):
549 def __init__(self, vars, start='?'):
@@ -40,26 +40,38 b' from __future__ import absolute_import'
40
40
41 # Many functions in this file have too many arguments.
41 # Many functions in this file have too many arguments.
42 # pylint: disable=R0913
42 # pylint: disable=R0913
43
43 import email
44 import cStringIO
44 import email.message
45 import errno
45 import errno
46 import httplib
46 import inspect
47 import logging
47 import logging
48 import rfc822
49 import select
48 import select
50 import socket
49 import socket
50 import ssl
51 import sys
52
53 try:
54 import cStringIO as io
55 io.StringIO
56 except ImportError:
57 import io
58
59 try:
60 import httplib
61 httplib.HTTPException
62 except ImportError:
63 import http.client as httplib
51
64
52 from . import (
65 from . import (
53 _readers,
66 _readers,
54 socketutil,
67 )
55 )
56
68
57 logger = logging.getLogger(__name__)
69 logger = logging.getLogger(__name__)
58
70
59 __all__ = ['HTTPConnection', 'HTTPResponse']
71 __all__ = ['HTTPConnection', 'HTTPResponse']
60
72
61 HTTP_VER_1_0 = 'HTTP/1.0'
73 HTTP_VER_1_0 = b'HTTP/1.0'
62 HTTP_VER_1_1 = 'HTTP/1.1'
74 HTTP_VER_1_1 = b'HTTP/1.1'
63
75
64 OUTGOING_BUFFER_SIZE = 1 << 15
76 OUTGOING_BUFFER_SIZE = 1 << 15
65 INCOMING_BUFFER_SIZE = 1 << 20
77 INCOMING_BUFFER_SIZE = 1 << 20
@@ -73,7 +85,7 b" XFER_ENCODING_CHUNKED = 'chunked'"
73
85
74 CONNECTION_CLOSE = 'close'
86 CONNECTION_CLOSE = 'close'
75
87
76 EOL = '\r\n'
88 EOL = b'\r\n'
77 _END_HEADERS = EOL * 2
89 _END_HEADERS = EOL * 2
78
90
79 # Based on some searching around, 1 second seems like a reasonable
91 # Based on some searching around, 1 second seems like a reasonable
@@ -81,6 +93,57 b" EOL = '\\r\\n'"
81 TIMEOUT_ASSUME_CONTINUE = 1
93 TIMEOUT_ASSUME_CONTINUE = 1
82 TIMEOUT_DEFAULT = None
94 TIMEOUT_DEFAULT = None
83
95
96 if sys.version_info > (3, 0):
97 _unicode = str
98 else:
99 _unicode = unicode
100
101 def _ensurebytes(data):
102 if not isinstance(data, (_unicode, bytes)):
103 data = str(data)
104 if not isinstance(data, bytes):
105 try:
106 return data.encode('latin-1')
107 except UnicodeEncodeError as err:
108 raise UnicodeEncodeError(
109 err.encoding,
110 err.object,
111 err.start,
112 err.end,
113 '%r is not valid Latin-1 Use .encode("utf-8") '
114 'if sending as utf-8 is desired.' % (
115 data[err.start:err.end],))
116 return data
117
118 class _CompatMessage(email.message.Message):
119 """Workaround for rfc822.Message and email.message.Message API diffs."""
120
121 @classmethod
122 def from_string(cls, s):
123 if sys.version_info > (3, 0):
124 # Python 3 can't decode headers from bytes, so we have to
125 # trust RFC 2616 and decode the headers as iso-8859-1
126 # bytes.
127 s = s.decode('iso-8859-1')
128 headers = email.message_from_string(s, _class=_CompatMessage)
129 # Fix multi-line headers to match httplib's behavior from
130 # Python 2.x, since email.message.Message handles them in
131 # slightly different ways.
132 if sys.version_info < (3, 0):
133 new = []
134 for h, v in headers._headers:
135 if '\r\n' in v:
136 v = '\n'.join([' ' + x.lstrip() for x in v.split('\r\n')])[1:]
137 new.append((h, v))
138 headers._headers = new
139 return headers
140
141 def getheaders(self, key):
142 return self.get_all(key)
143
144 def getheader(self, key, default=None):
145 return self.get(key, failobj=default)
146
84
147
85 class HTTPResponse(object):
148 class HTTPResponse(object):
86 """Response from an HTTP server.
149 """Response from an HTTP server.
@@ -91,11 +154,11 b' class HTTPResponse(object):'
91 def __init__(self, sock, timeout, method):
154 def __init__(self, sock, timeout, method):
92 self.sock = sock
155 self.sock = sock
93 self.method = method
156 self.method = method
94 self.raw_response = ''
157 self.raw_response = b''
95 self._headers_len = 0
158 self._headers_len = 0
96 self.headers = None
159 self.headers = None
97 self.will_close = False
160 self.will_close = False
98 self.status_line = ''
161 self.status_line = b''
99 self.status = None
162 self.status = None
100 self.continued = False
163 self.continued = False
101 self.http_version = None
164 self.http_version = None
@@ -131,6 +194,10 b' class HTTPResponse(object):'
131 return self.headers.getheader(header, default=default)
194 return self.headers.getheader(header, default=default)
132
195
133 def getheaders(self):
196 def getheaders(self):
197 if sys.version_info < (3, 0):
198 return [(k.lower(), v) for k, v in self.headers.items()]
199 # Starting in Python 3, headers aren't lowercased before being
200 # returned here.
134 return self.headers.items()
201 return self.headers.items()
135
202
136 def readline(self):
203 def readline(self):
@@ -141,14 +208,14 b' class HTTPResponse(object):'
141 """
208 """
142 blocks = []
209 blocks = []
143 while True:
210 while True:
144 self._reader.readto('\n', blocks)
211 self._reader.readto(b'\n', blocks)
145
212
146 if blocks and blocks[-1][-1] == '\n' or self.complete():
213 if blocks and blocks[-1][-1:] == b'\n' or self.complete():
147 break
214 break
148
215
149 self._select()
216 self._select()
150
217
151 return ''.join(blocks)
218 return b''.join(blocks)
152
219
153 def read(self, length=None):
220 def read(self, length=None):
154 """Read data from the response body."""
221 """Read data from the response body."""
@@ -175,8 +242,8 b' class HTTPResponse(object):'
175 raise HTTPTimeoutException('timeout reading data')
242 raise HTTPTimeoutException('timeout reading data')
176 try:
243 try:
177 data = self.sock.recv(INCOMING_BUFFER_SIZE)
244 data = self.sock.recv(INCOMING_BUFFER_SIZE)
178 except socket.sslerror as e:
245 except ssl.SSLError as e:
179 if e.args[0] != socket.SSL_ERROR_WANT_READ:
246 if e.args[0] != ssl.SSL_ERROR_WANT_READ:
180 raise
247 raise
181 logger.debug('SSL_ERROR_WANT_READ in _select, should retry later')
248 logger.debug('SSL_ERROR_WANT_READ in _select, should retry later')
182 return True
249 return True
@@ -203,7 +270,7 b' class HTTPResponse(object):'
203 self.raw_response += data
270 self.raw_response += data
204 # This is a bogus server with bad line endings
271 # This is a bogus server with bad line endings
205 if self._eol not in self.raw_response:
272 if self._eol not in self.raw_response:
206 for bad_eol in ('\n', '\r'):
273 for bad_eol in (b'\n', b'\r'):
207 if (bad_eol in self.raw_response
274 if (bad_eol in self.raw_response
208 # verify that bad_eol is not the end of the incoming data
275 # verify that bad_eol is not the end of the incoming data
209 # as this could be a response line that just got
276 # as this could be a response line that just got
@@ -220,8 +287,8 b' class HTTPResponse(object):'
220
287
221 # handle 100-continue response
288 # handle 100-continue response
222 hdrs, body = self.raw_response.split(self._end_headers, 1)
289 hdrs, body = self.raw_response.split(self._end_headers, 1)
223 unused_http_ver, status = hdrs.split(' ', 1)
290 unused_http_ver, status = hdrs.split(b' ', 1)
224 if status.startswith('100'):
291 if status.startswith(b'100'):
225 self.raw_response = body
292 self.raw_response = body
226 self.continued = True
293 self.continued = True
227 logger.debug('continue seen, setting body to %r', body)
294 logger.debug('continue seen, setting body to %r', body)
@@ -235,14 +302,14 b' class HTTPResponse(object):'
235 self.status_line, hdrs = hdrs.split(self._eol, 1)
302 self.status_line, hdrs = hdrs.split(self._eol, 1)
236 else:
303 else:
237 self.status_line = hdrs
304 self.status_line = hdrs
238 hdrs = ''
305 hdrs = b''
239 # TODO HTTP < 1.0 support
306 # TODO HTTP < 1.0 support
240 (self.http_version, self.status,
307 (self.http_version, self.status,
241 self.reason) = self.status_line.split(' ', 2)
308 self.reason) = self.status_line.split(b' ', 2)
242 self.status = int(self.status)
309 self.status = int(self.status)
243 if self._eol != EOL:
310 if self._eol != EOL:
244 hdrs = hdrs.replace(self._eol, '\r\n')
311 hdrs = hdrs.replace(self._eol, b'\r\n')
245 headers = rfc822.Message(cStringIO.StringIO(hdrs))
312 headers = _CompatMessage.from_string(hdrs)
246 content_len = None
313 content_len = None
247 if HDR_CONTENT_LENGTH in headers:
314 if HDR_CONTENT_LENGTH in headers:
248 content_len = int(headers[HDR_CONTENT_LENGTH])
315 content_len = int(headers[HDR_CONTENT_LENGTH])
@@ -259,8 +326,8 b' class HTTPResponse(object):'
259 # HEAD responses are forbidden from returning a body, and
326 # HEAD responses are forbidden from returning a body, and
260 # it's implausible for a CONNECT response to use
327 # it's implausible for a CONNECT response to use
261 # close-is-end logic for an OK response.
328 # close-is-end logic for an OK response.
262 if (self.method == 'HEAD' or
329 if (self.method == b'HEAD' or
263 (self.method == 'CONNECT' and content_len is None)):
330 (self.method == b'CONNECT' and content_len is None)):
264 content_len = 0
331 content_len = 0
265 if content_len is not None:
332 if content_len is not None:
266 logger.debug('using a content-length reader with length %d',
333 logger.debug('using a content-length reader with length %d',
@@ -294,8 +361,48 b' def _foldheaders(headers):'
294 >>> _foldheaders({'Accept-Encoding': 'wat'})
361 >>> _foldheaders({'Accept-Encoding': 'wat'})
295 {'accept-encoding': ('Accept-Encoding', 'wat')}
362 {'accept-encoding': ('Accept-Encoding', 'wat')}
296 """
363 """
297 return dict((k.lower(), (k, v)) for k, v in headers.iteritems())
364 return dict((k.lower(), (k, v)) for k, v in headers.items())
365
366 try:
367 inspect.signature
368 def _handlesarg(func, arg):
369 """ Try to determine if func accepts arg
370
371 If it takes arg, return True
372 If it happens to take **args, then it could do anything:
373 * It could throw a different TypeError, just for fun
374 * It could throw an ArgumentError or anything else
375 * It could choose not to throw an Exception at all
376 ... return 'unknown'
298
377
378 Otherwise, return False
379 """
380 params = inspect.signature(func).parameters
381 if arg in params:
382 return True
383 for p in params:
384 if params[p].kind == inspect._ParameterKind.VAR_KEYWORD:
385 return 'unknown'
386 return False
387 except AttributeError:
388 def _handlesarg(func, arg):
389 """ Try to determine if func accepts arg
390
391 If it takes arg, return True
392 If it happens to take **args, then it could do anything:
393 * It could throw a different TypeError, just for fun
394 * It could throw an ArgumentError or anything else
395 * It could choose not to throw an Exception at all
396 ... return 'unknown'
397
398 Otherwise, return False
399 """
400 spec = inspect.getargspec(func)
401 if arg in spec.args:
402 return True
403 if spec.keywords:
404 return 'unknown'
405 return False
299
406
300 class HTTPConnection(object):
407 class HTTPConnection(object):
301 """Connection to a single http server.
408 """Connection to a single http server.
@@ -340,15 +447,38 b' class HTTPConnection(object):'
340 Any extra keyword arguments to this function will be provided
447 Any extra keyword arguments to this function will be provided
341 to the ssl_wrap_socket method. If no ssl
448 to the ssl_wrap_socket method. If no ssl
342 """
449 """
343 if port is None and host.count(':') == 1 or ']:' in host:
450 host = _ensurebytes(host)
344 host, port = host.rsplit(':', 1)
451 if port is None and host.count(b':') == 1 or b']:' in host:
452 host, port = host.rsplit(b':', 1)
345 port = int(port)
453 port = int(port)
346 if '[' in host:
454 if b'[' in host:
347 host = host[1:-1]
455 host = host[1:-1]
348 if ssl_wrap_socket is not None:
456 if ssl_wrap_socket is not None:
349 self._ssl_wrap_socket = ssl_wrap_socket
457 _wrap_socket = ssl_wrap_socket
350 else:
458 else:
351 self._ssl_wrap_socket = socketutil.wrap_socket
459 _wrap_socket = ssl.wrap_socket
460 call_wrap_socket = None
461 handlesubar = _handlesarg(_wrap_socket, 'server_hostname')
462 if handlesubar is True:
463 # supports server_hostname
464 call_wrap_socket = _wrap_socket
465 handlesnobar = _handlesarg(_wrap_socket, 'serverhostname')
466 if handlesnobar is True and handlesubar is not True:
467 # supports serverhostname
468 def call_wrap_socket(sock, server_hostname=None, **ssl_opts):
469 return _wrap_socket(sock, serverhostname=server_hostname,
470 **ssl_opts)
471 if handlesubar is False and handlesnobar is False:
472 # does not support either
473 def call_wrap_socket(sock, server_hostname=None, **ssl_opts):
474 return _wrap_socket(sock, **ssl_opts)
475 if call_wrap_socket is None:
476 # we assume it takes **args
477 def call_wrap_socket(sock, **ssl_opts):
478 if 'server_hostname' in ssl_opts:
479 ssl_opts['serverhostname'] = ssl_opts['server_hostname']
480 return _wrap_socket(sock, **ssl_opts)
481 self._ssl_wrap_socket = call_wrap_socket
352 if use_ssl is None and port is None:
482 if use_ssl is None and port is None:
353 use_ssl = False
483 use_ssl = False
354 port = 80
484 port = 80
@@ -357,8 +487,6 b' class HTTPConnection(object):'
357 elif port is None:
487 elif port is None:
358 port = (use_ssl and 443 or 80)
488 port = (use_ssl and 443 or 80)
359 self.port = port
489 self.port = port
360 if use_ssl and not socketutil.have_ssl:
361 raise Exception('ssl requested but unavailable on this Python')
362 self.ssl = use_ssl
490 self.ssl = use_ssl
363 self.ssl_opts = ssl_opts
491 self.ssl_opts = ssl_opts
364 self._ssl_validator = ssl_validator
492 self._ssl_validator = ssl_validator
@@ -388,15 +516,15 b' class HTTPConnection(object):'
388 if self._proxy_host is not None:
516 if self._proxy_host is not None:
389 logger.info('Connecting to http proxy %s:%s',
517 logger.info('Connecting to http proxy %s:%s',
390 self._proxy_host, self._proxy_port)
518 self._proxy_host, self._proxy_port)
391 sock = socketutil.create_connection((self._proxy_host,
519 sock = socket.create_connection((self._proxy_host,
392 self._proxy_port))
520 self._proxy_port))
393 if self.ssl:
521 if self.ssl:
394 data = self._buildheaders('CONNECT', '%s:%d' % (self.host,
522 data = self._buildheaders(b'CONNECT', b'%s:%d' % (self.host,
395 self.port),
523 self.port),
396 proxy_headers, HTTP_VER_1_0)
524 proxy_headers, HTTP_VER_1_0)
397 sock.send(data)
525 sock.send(data)
398 sock.setblocking(0)
526 sock.setblocking(0)
399 r = self.response_class(sock, self.timeout, 'CONNECT')
527 r = self.response_class(sock, self.timeout, b'CONNECT')
400 timeout_exc = HTTPTimeoutException(
528 timeout_exc = HTTPTimeoutException(
401 'Timed out waiting for CONNECT response from proxy')
529 'Timed out waiting for CONNECT response from proxy')
402 while not r.complete():
530 while not r.complete():
@@ -421,7 +549,7 b' class HTTPConnection(object):'
421 logger.info('CONNECT (for SSL) to %s:%s via proxy succeeded.',
549 logger.info('CONNECT (for SSL) to %s:%s via proxy succeeded.',
422 self.host, self.port)
550 self.host, self.port)
423 else:
551 else:
424 sock = socketutil.create_connection((self.host, self.port))
552 sock = socket.create_connection((self.host, self.port))
425 if self.ssl:
553 if self.ssl:
426 # This is the default, but in the case of proxied SSL
554 # This is the default, but in the case of proxied SSL
427 # requests the proxy logic above will have cleared
555 # requests the proxy logic above will have cleared
@@ -429,7 +557,8 b' class HTTPConnection(object):'
429 sock.setblocking(1)
557 sock.setblocking(1)
430 logger.debug('wrapping socket for ssl with options %r',
558 logger.debug('wrapping socket for ssl with options %r',
431 self.ssl_opts)
559 self.ssl_opts)
432 sock = self._ssl_wrap_socket(sock, **self.ssl_opts)
560 sock = self._ssl_wrap_socket(sock, server_hostname=self.host,
561 **self.ssl_opts)
433 if self._ssl_validator:
562 if self._ssl_validator:
434 self._ssl_validator(sock)
563 self._ssl_validator(sock)
435 sock.setblocking(0)
564 sock.setblocking(0)
@@ -441,25 +570,26 b' class HTTPConnection(object):'
441 hdrhost = self.host
570 hdrhost = self.host
442 else:
571 else:
443 # include nonstandard port in header
572 # include nonstandard port in header
444 if ':' in self.host: # must be IPv6
573 if b':' in self.host: # must be IPv6
445 hdrhost = '[%s]:%d' % (self.host, self.port)
574 hdrhost = b'[%s]:%d' % (self.host, self.port)
446 else:
575 else:
447 hdrhost = '%s:%d' % (self.host, self.port)
576 hdrhost = b'%s:%d' % (self.host, self.port)
448 if self._proxy_host and not self.ssl:
577 if self._proxy_host and not self.ssl:
449 # When talking to a regular http proxy we must send the
578 # When talking to a regular http proxy we must send the
450 # full URI, but in all other cases we must not (although
579 # full URI, but in all other cases we must not (although
451 # technically RFC 2616 says servers must accept our
580 # technically RFC 2616 says servers must accept our
452 # request if we screw up, experimentally few do that
581 # request if we screw up, experimentally few do that
453 # correctly.)
582 # correctly.)
454 assert path[0] == '/', 'path must start with a /'
583 assert path[0:1] == b'/', 'path must start with a /'
455 path = 'http://%s%s' % (hdrhost, path)
584 path = b'http://%s%s' % (hdrhost, path)
456 outgoing = ['%s %s %s%s' % (method, path, http_ver, EOL)]
585 outgoing = [b'%s %s %s%s' % (method, path, http_ver, EOL)]
457 headers['host'] = ('Host', hdrhost)
586 headers[b'host'] = (b'Host', hdrhost)
458 headers[HDR_ACCEPT_ENCODING] = (HDR_ACCEPT_ENCODING, 'identity')
587 headers[HDR_ACCEPT_ENCODING] = (HDR_ACCEPT_ENCODING, 'identity')
459 for hdr, val in headers.itervalues():
588 for hdr, val in sorted((_ensurebytes(h), _ensurebytes(v))
460 outgoing.append('%s: %s%s' % (hdr, val, EOL))
589 for h, v in headers.values()):
590 outgoing.append(b'%s: %s%s' % (hdr, val, EOL))
461 outgoing.append(EOL)
591 outgoing.append(EOL)
462 return ''.join(outgoing)
592 return b''.join(outgoing)
463
593
464 def close(self):
594 def close(self):
465 """Close the connection to the server.
595 """Close the connection to the server.
@@ -512,6 +642,8 b' class HTTPConnection(object):'
512 available. Use the `getresponse()` method to retrieve the
642 available. Use the `getresponse()` method to retrieve the
513 response.
643 response.
514 """
644 """
645 method = _ensurebytes(method)
646 path = _ensurebytes(path)
515 if self.busy():
647 if self.busy():
516 raise httplib.CannotSendRequest(
648 raise httplib.CannotSendRequest(
517 'Can not send another request before '
649 'Can not send another request before '
@@ -520,11 +652,26 b' class HTTPConnection(object):'
520
652
521 logger.info('sending %s request for %s to %s on port %s',
653 logger.info('sending %s request for %s to %s on port %s',
522 method, path, self.host, self.port)
654 method, path, self.host, self.port)
655
523 hdrs = _foldheaders(headers)
656 hdrs = _foldheaders(headers)
524 if hdrs.get('expect', ('', ''))[1].lower() == '100-continue':
657 # Figure out headers that have to be computed from the request
658 # body.
659 chunked = False
660 if body and HDR_CONTENT_LENGTH not in hdrs:
661 if getattr(body, '__len__', False):
662 hdrs[HDR_CONTENT_LENGTH] = (HDR_CONTENT_LENGTH,
663 b'%d' % len(body))
664 elif getattr(body, 'read', False):
665 hdrs[HDR_XFER_ENCODING] = (HDR_XFER_ENCODING,
666 XFER_ENCODING_CHUNKED)
667 chunked = True
668 else:
669 raise BadRequestData('body has no __len__() nor read()')
670 # Figure out expect-continue header
671 if hdrs.get('expect', ('', ''))[1].lower() == b'100-continue':
525 expect_continue = True
672 expect_continue = True
526 elif expect_continue:
673 elif expect_continue:
527 hdrs['expect'] = ('Expect', '100-Continue')
674 hdrs['expect'] = (b'Expect', b'100-Continue')
528 # httplib compatibility: if the user specified a
675 # httplib compatibility: if the user specified a
529 # proxy-authorization header, that's actually intended for a
676 # proxy-authorization header, that's actually intended for a
530 # proxy CONNECT action, not the real request, but only if
677 # proxy CONNECT action, not the real request, but only if
@@ -534,25 +681,15 b' class HTTPConnection(object):'
534 pa = hdrs.pop('proxy-authorization', None)
681 pa = hdrs.pop('proxy-authorization', None)
535 if pa is not None:
682 if pa is not None:
536 pheaders['proxy-authorization'] = pa
683 pheaders['proxy-authorization'] = pa
537
684 # Build header data
538 chunked = False
685 outgoing_headers = self._buildheaders(
539 if body and HDR_CONTENT_LENGTH not in hdrs:
686 method, path, hdrs, self.http_version)
540 if getattr(body, '__len__', False):
541 hdrs[HDR_CONTENT_LENGTH] = (HDR_CONTENT_LENGTH, len(body))
542 elif getattr(body, 'read', False):
543 hdrs[HDR_XFER_ENCODING] = (HDR_XFER_ENCODING,
544 XFER_ENCODING_CHUNKED)
545 chunked = True
546 else:
547 raise BadRequestData('body has no __len__() nor read()')
548
687
549 # If we're reusing the underlying socket, there are some
688 # If we're reusing the underlying socket, there are some
550 # conditions where we'll want to retry, so make a note of the
689 # conditions where we'll want to retry, so make a note of the
551 # state of self.sock
690 # state of self.sock
552 fresh_socket = self.sock is None
691 fresh_socket = self.sock is None
553 self._connect(pheaders)
692 self._connect(pheaders)
554 outgoing_headers = self._buildheaders(
555 method, path, hdrs, self.http_version)
556 response = None
693 response = None
557 first = True
694 first = True
558
695
@@ -592,8 +729,8 b' class HTTPConnection(object):'
592 try:
729 try:
593 try:
730 try:
594 data = r[0].recv(INCOMING_BUFFER_SIZE)
731 data = r[0].recv(INCOMING_BUFFER_SIZE)
595 except socket.sslerror as e:
732 except ssl.SSLError as e:
596 if e.args[0] != socket.SSL_ERROR_WANT_READ:
733 if e.args[0] != ssl.SSL_ERROR_WANT_READ:
597 raise
734 raise
598 logger.debug('SSL_ERROR_WANT_READ while sending '
735 logger.debug('SSL_ERROR_WANT_READ while sending '
599 'data, retrying...')
736 'data, retrying...')
@@ -662,16 +799,20 b' class HTTPConnection(object):'
662 continue
799 continue
663 if len(data) < OUTGOING_BUFFER_SIZE:
800 if len(data) < OUTGOING_BUFFER_SIZE:
664 if chunked:
801 if chunked:
665 body = '0' + EOL + EOL
802 body = b'0' + EOL + EOL
666 else:
803 else:
667 body = None
804 body = None
668 if chunked:
805 if chunked:
669 out = hex(len(data))[2:] + EOL + data + EOL
806 # This encode is okay because we know
807 # hex() is building us only 0-9 and a-f
808 # digits.
809 asciilen = hex(len(data))[2:].encode('ascii')
810 out = asciilen + EOL + data + EOL
670 else:
811 else:
671 out = data
812 out = data
672 amt = w[0].send(out)
813 amt = w[0].send(out)
673 except socket.error as e:
814 except socket.error as e:
674 if e[0] == socket.SSL_ERROR_WANT_WRITE and self.ssl:
815 if e[0] == ssl.SSL_ERROR_WANT_WRITE and self.ssl:
675 # This means that SSL hasn't flushed its buffer into
816 # This means that SSL hasn't flushed its buffer into
676 # the socket yet.
817 # the socket yet.
677 # TODO: find a way to block on ssl flushing its buffer
818 # TODO: find a way to block on ssl flushing its buffer
@@ -690,6 +831,7 b' class HTTPConnection(object):'
690 body = out[amt:]
831 body = out[amt:]
691 else:
832 else:
692 outgoing_headers = out[amt:]
833 outgoing_headers = out[amt:]
834 # End of request-sending loop.
693
835
694 # close if the server response said to or responded before eating
836 # close if the server response said to or responded before eating
695 # the whole request
837 # the whole request
@@ -33,7 +33,12 b' have any clients outside of httpplus.'
33 """
33 """
34 from __future__ import absolute_import
34 from __future__ import absolute_import
35
35
36 import httplib
36 try:
37 import httplib
38 httplib.HTTPException
39 except ImportError:
40 import http.client as httplib
41
37 import logging
42 import logging
38
43
39 logger = logging.getLogger(__name__)
44 logger = logging.getLogger(__name__)
@@ -93,7 +98,7 b' class AbstractReader(object):'
93 need -= len(b)
98 need -= len(b)
94 if need == 0:
99 if need == 0:
95 break
100 break
96 result = ''.join(blocks)
101 result = b''.join(blocks)
97 assert len(result) == amt or (self._finished and len(result) < amt)
102 assert len(result) == amt or (self._finished and len(result) < amt)
98
103
99 return result
104 return result
@@ -280,10 +280,9 b' class http2handler(urlreq.httphandler, u'
280 kwargs['keyfile'] = keyfile
280 kwargs['keyfile'] = keyfile
281 kwargs['certfile'] = certfile
281 kwargs['certfile'] = certfile
282
282
283 kwargs.update(sslutil.sslkwargs(self.ui, host))
284
285 con = HTTPConnection(host, port, use_ssl=True,
283 con = HTTPConnection(host, port, use_ssl=True,
286 ssl_wrap_socket=sslutil.wrapsocket,
284 ssl_wrap_socket=sslutil.wrapsocket,
287 ssl_validator=sslutil.validator(self.ui, host),
285 ssl_validator=sslutil.validatesocket,
286 ui=self.ui,
288 **kwargs)
287 **kwargs)
289 return con
288 return con
@@ -9,7 +9,6 b''
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import errno
11 import errno
12 import httplib
13 import os
12 import os
14 import socket
13 import socket
15 import tempfile
14 import tempfile
@@ -27,6 +26,7 b' from . import ('
27 wireproto,
26 wireproto,
28 )
27 )
29
28
29 httplib = util.httplib
30 urlerr = util.urlerr
30 urlerr = util.urlerr
31 urlreq = util.urlreq
31 urlreq = util.urlreq
32
32
@@ -302,7 +302,7 b' def instance(ui, path, create):'
302 except error.RepoError as httpexception:
302 except error.RepoError as httpexception:
303 try:
303 try:
304 r = statichttprepo.instance(ui, "static-" + path, create)
304 r = statichttprepo.instance(ui, "static-" + path, create)
305 ui.note('(falling back to static-http)\n')
305 ui.note(_('(falling back to static-http)\n'))
306 return r
306 return r
307 except error.RepoError:
307 except error.RepoError:
308 raise httpexception # use the original http RepoError instead
308 raise httpexception # use the original http RepoError instead
@@ -78,7 +78,7 b' def gettext(message):'
78 paragraphs = [p.decode("ascii") for p in message.split('\n\n')]
78 paragraphs = [p.decode("ascii") for p in message.split('\n\n')]
79 # Be careful not to translate the empty string -- it holds the
79 # Be careful not to translate the empty string -- it holds the
80 # meta data of the .po file.
80 # meta data of the .po file.
81 u = u'\n\n'.join([p and _ugettext(p) or '' for p in paragraphs])
81 u = u'\n\n'.join([p and _ugettext(p) or u'' for p in paragraphs])
82 try:
82 try:
83 # encoding.tolocal cannot be used since it will first try to
83 # encoding.tolocal cannot be used since it will first try to
84 # decode the Unicode string. Calling u.decode(enc) really
84 # decode the Unicode string. Calling u.decode(enc) really
@@ -110,15 +110,16 b' EXTRA ATTRIBUTES AND METHODS'
110 from __future__ import absolute_import, print_function
110 from __future__ import absolute_import, print_function
111
111
112 import errno
112 import errno
113 import httplib
113 import hashlib
114 import socket
114 import socket
115 import sys
115 import sys
116 import thread
116 import threading
117
117
118 from . import (
118 from . import (
119 util,
119 util,
120 )
120 )
121
121
122 httplib = util.httplib
122 urlerr = util.urlerr
123 urlerr = util.urlerr
123 urlreq = util.urlreq
124 urlreq = util.urlreq
124
125
@@ -134,7 +135,7 b' class ConnectionManager(object):'
134 * keep track of all existing
135 * keep track of all existing
135 """
136 """
136 def __init__(self):
137 def __init__(self):
137 self._lock = thread.allocate_lock()
138 self._lock = threading.Lock()
138 self._hostmap = {} # map hosts to a list of connections
139 self._hostmap = {} # map hosts to a list of connections
139 self._connmap = {} # map connections to host
140 self._connmap = {} # map connections to host
140 self._readymap = {} # map connection to ready state
141 self._readymap = {} # map connection to ready state
@@ -624,8 +625,7 b' def error_handler(url):'
624 keepalive_handler.close_all()
625 keepalive_handler.close_all()
625
626
626 def continuity(url):
627 def continuity(url):
627 from . import util
628 md5 = hashlib.md5
628 md5 = util.md5
629 format = '%25s: %s'
629 format = '%25s: %s'
630
630
631 # first fetch the file with the normal http handler
631 # first fetch the file with the normal http handler
@@ -8,6 +8,7 b''
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import hashlib
11 import inspect
12 import inspect
12 import os
13 import os
13 import random
14 import random
@@ -57,16 +58,16 b' from . import ('
57 )
58 )
58
59
59 release = lockmod.release
60 release = lockmod.release
60 propertycache = util.propertycache
61 urlerr = util.urlerr
61 urlerr = util.urlerr
62 urlreq = util.urlreq
62 urlreq = util.urlreq
63 filecache = scmutil.filecache
64
63
65 class repofilecache(filecache):
64 class repofilecache(scmutil.filecache):
66 """All filecache usage on repo are done for logic that should be unfiltered
65 """All filecache usage on repo are done for logic that should be unfiltered
67 """
66 """
68
67
69 def __get__(self, repo, type=None):
68 def __get__(self, repo, type=None):
69 if repo is None:
70 return self
70 return super(repofilecache, self).__get__(repo.unfiltered(), type)
71 return super(repofilecache, self).__get__(repo.unfiltered(), type)
71 def __set__(self, repo, value):
72 def __set__(self, repo, value):
72 return super(repofilecache, self).__set__(repo.unfiltered(), value)
73 return super(repofilecache, self).__set__(repo.unfiltered(), value)
@@ -78,7 +79,7 b' class storecache(repofilecache):'
78 def join(self, obj, fname):
79 def join(self, obj, fname):
79 return obj.sjoin(fname)
80 return obj.sjoin(fname)
80
81
81 class unfilteredpropertycache(propertycache):
82 class unfilteredpropertycache(util.propertycache):
82 """propertycache that apply to unfiltered repo only"""
83 """propertycache that apply to unfiltered repo only"""
83
84
84 def __get__(self, repo, type=None):
85 def __get__(self, repo, type=None):
@@ -87,7 +88,7 b' class unfilteredpropertycache(propertyca'
87 return super(unfilteredpropertycache, self).__get__(unfi)
88 return super(unfilteredpropertycache, self).__get__(unfi)
88 return getattr(unfi, self.name)
89 return getattr(unfi, self.name)
89
90
90 class filteredpropertycache(propertycache):
91 class filteredpropertycache(util.propertycache):
91 """propertycache that must take filtering in account"""
92 """propertycache that must take filtering in account"""
92
93
93 def cachevalue(self, obj, value):
94 def cachevalue(self, obj, value):
@@ -553,7 +554,10 b' class localrepository(object):'
553 The revset is specified as a string ``expr`` that may contain
554 The revset is specified as a string ``expr`` that may contain
554 %-formatting to escape certain types. See ``revset.formatspec``.
555 %-formatting to escape certain types. See ``revset.formatspec``.
555
556
556 Return a revset.abstractsmartset, which is a list-like interface
557 Revset aliases from the configuration are not expanded. To expand
558 user aliases, consider calling ``scmutil.revrange()``.
559
560 Returns a revset.abstractsmartset, which is a list-like interface
557 that contains integer revisions.
561 that contains integer revisions.
558 '''
562 '''
559 expr = revset.formatspec(expr, *args)
563 expr = revset.formatspec(expr, *args)
@@ -565,6 +569,9 b' class localrepository(object):'
565
569
566 This is a convenience wrapper around ``revs()`` that iterates the
570 This is a convenience wrapper around ``revs()`` that iterates the
567 result and is a generator of changectx instances.
571 result and is a generator of changectx instances.
572
573 Revset aliases from the configuration are not expanded. To expand
574 user aliases, consider calling ``scmutil.revrange()``.
568 '''
575 '''
569 for r in self.revs(expr, *args):
576 for r in self.revs(expr, *args):
570 yield self[r]
577 yield self[r]
@@ -881,12 +888,6 b' class localrepository(object):'
881 f = f[1:]
888 f = f[1:]
882 return filelog.filelog(self.svfs, f)
889 return filelog.filelog(self.svfs, f)
883
890
884 def parents(self, changeid=None):
885 '''get list of changectxs for parents of changeid'''
886 msg = 'repo.parents() is deprecated, use repo[%r].parents()' % changeid
887 self.ui.deprecwarn(msg, '3.7')
888 return self[changeid].parents()
889
890 def changectx(self, changeid):
891 def changectx(self, changeid):
891 return self[changeid]
892 return self[changeid]
892
893
@@ -1008,7 +1009,8 b' class localrepository(object):'
1008 or self.ui.configbool('devel', 'check-locks')):
1009 or self.ui.configbool('devel', 'check-locks')):
1009 l = self._lockref and self._lockref()
1010 l = self._lockref and self._lockref()
1010 if l is None or not l.held:
1011 if l is None or not l.held:
1011 self.ui.develwarn('transaction with no lock')
1012 raise RuntimeError('programming error: transaction requires '
1013 'locking')
1012 tr = self.currenttransaction()
1014 tr = self.currenttransaction()
1013 if tr is not None:
1015 if tr is not None:
1014 return tr.nest()
1016 return tr.nest()
@@ -1019,11 +1021,8 b' class localrepository(object):'
1019 _("abandoned transaction found"),
1021 _("abandoned transaction found"),
1020 hint=_("run 'hg recover' to clean up transaction"))
1022 hint=_("run 'hg recover' to clean up transaction"))
1021
1023
1022 # make journal.dirstate contain in-memory changes at this point
1023 self.dirstate.write(None)
1024
1025 idbase = "%.40f#%f" % (random.random(), time.time())
1024 idbase = "%.40f#%f" % (random.random(), time.time())
1026 txnid = 'TXN:' + util.sha1(idbase).hexdigest()
1025 txnid = 'TXN:' + hashlib.sha1(idbase).hexdigest()
1027 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
1026 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
1028
1027
1029 self._writejournal(desc)
1028 self._writejournal(desc)
@@ -1049,13 +1048,9 b' class localrepository(object):'
1049 # transaction running
1048 # transaction running
1050 repo.dirstate.write(None)
1049 repo.dirstate.write(None)
1051 else:
1050 else:
1052 # prevent in-memory changes from being written out at
1053 # the end of outer wlock scope or so
1054 repo.dirstate.invalidate()
1055
1056 # discard all changes (including ones already written
1051 # discard all changes (including ones already written
1057 # out) in this transaction
1052 # out) in this transaction
1058 repo.vfs.rename('journal.dirstate', 'dirstate')
1053 repo.dirstate.restorebackup(None, prefix='journal.')
1059
1054
1060 repo.invalidate(clearfilecache=True)
1055 repo.invalidate(clearfilecache=True)
1061
1056
@@ -1110,8 +1105,7 b' class localrepository(object):'
1110 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1105 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1111
1106
1112 def _writejournal(self, desc):
1107 def _writejournal(self, desc):
1113 self.vfs.write("journal.dirstate",
1108 self.dirstate.savebackup(None, prefix='journal.')
1114 self.vfs.tryread("dirstate"))
1115 self.vfs.write("journal.branch",
1109 self.vfs.write("journal.branch",
1116 encoding.fromlocal(self.dirstate.branch()))
1110 encoding.fromlocal(self.dirstate.branch()))
1117 self.vfs.write("journal.desc",
1111 self.vfs.write("journal.desc",
@@ -1186,9 +1180,9 b' class localrepository(object):'
1186 vfsmap = {'plain': self.vfs, '': self.svfs}
1180 vfsmap = {'plain': self.vfs, '': self.svfs}
1187 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn)
1181 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn)
1188 if self.vfs.exists('undo.bookmarks'):
1182 if self.vfs.exists('undo.bookmarks'):
1189 self.vfs.rename('undo.bookmarks', 'bookmarks')
1183 self.vfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
1190 if self.svfs.exists('undo.phaseroots'):
1184 if self.svfs.exists('undo.phaseroots'):
1191 self.svfs.rename('undo.phaseroots', 'phaseroots')
1185 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
1192 self.invalidate()
1186 self.invalidate()
1193
1187
1194 parentgone = (parents[0] not in self.changelog.nodemap or
1188 parentgone = (parents[0] not in self.changelog.nodemap or
@@ -1197,7 +1191,7 b' class localrepository(object):'
1197 # prevent dirstateguard from overwriting already restored one
1191 # prevent dirstateguard from overwriting already restored one
1198 dsguard.close()
1192 dsguard.close()
1199
1193
1200 self.vfs.rename('undo.dirstate', 'dirstate')
1194 self.dirstate.restorebackup(None, prefix='undo.')
1201 try:
1195 try:
1202 branch = self.vfs.read('undo.branch')
1196 branch = self.vfs.read('undo.branch')
1203 self.dirstate.setbranch(encoding.tolocal(branch))
1197 self.dirstate.setbranch(encoding.tolocal(branch))
@@ -1206,7 +1200,6 b' class localrepository(object):'
1206 'current branch is still \'%s\'\n')
1200 'current branch is still \'%s\'\n')
1207 % self.dirstate.branch())
1201 % self.dirstate.branch())
1208
1202
1209 self.dirstate.invalidate()
1210 parents = tuple([p.rev() for p in self[None].parents()])
1203 parents = tuple([p.rev() for p in self[None].parents()])
1211 if len(parents) > 1:
1204 if len(parents) > 1:
1212 ui.status(_('working directory now based on '
1205 ui.status(_('working directory now based on '
@@ -41,16 +41,16 b' def _unifiedheaderinit(self, *args, **kw'
41 kw['continuation_ws'] = ' '
41 kw['continuation_ws'] = ' '
42 _oldheaderinit(self, *args, **kw)
42 _oldheaderinit(self, *args, **kw)
43
43
44 email.Header.Header.__dict__['__init__'] = _unifiedheaderinit
44 setattr(email.header.Header, '__init__', _unifiedheaderinit)
45
45
46 class STARTTLS(smtplib.SMTP):
46 class STARTTLS(smtplib.SMTP):
47 '''Derived class to verify the peer certificate for STARTTLS.
47 '''Derived class to verify the peer certificate for STARTTLS.
48
48
49 This class allows to pass any keyword arguments to SSL socket creation.
49 This class allows to pass any keyword arguments to SSL socket creation.
50 '''
50 '''
51 def __init__(self, sslkwargs, host=None, **kwargs):
51 def __init__(self, ui, host=None, **kwargs):
52 smtplib.SMTP.__init__(self, **kwargs)
52 smtplib.SMTP.__init__(self, **kwargs)
53 self._sslkwargs = sslkwargs
53 self._ui = ui
54 self._host = host
54 self._host = host
55
55
56 def starttls(self, keyfile=None, certfile=None):
56 def starttls(self, keyfile=None, certfile=None):
@@ -60,8 +60,8 b' class STARTTLS(smtplib.SMTP):'
60 (resp, reply) = self.docmd("STARTTLS")
60 (resp, reply) = self.docmd("STARTTLS")
61 if resp == 220:
61 if resp == 220:
62 self.sock = sslutil.wrapsocket(self.sock, keyfile, certfile,
62 self.sock = sslutil.wrapsocket(self.sock, keyfile, certfile,
63 serverhostname=self._host,
63 ui=self._ui,
64 **self._sslkwargs)
64 serverhostname=self._host)
65 self.file = smtplib.SSLFakeFile(self.sock)
65 self.file = smtplib.SSLFakeFile(self.sock)
66 self.helo_resp = None
66 self.helo_resp = None
67 self.ehlo_resp = None
67 self.ehlo_resp = None
@@ -74,14 +74,14 b' class SMTPS(smtplib.SMTP):'
74
74
75 This class allows to pass any keyword arguments to SSL socket creation.
75 This class allows to pass any keyword arguments to SSL socket creation.
76 '''
76 '''
77 def __init__(self, sslkwargs, keyfile=None, certfile=None, host=None,
77 def __init__(self, ui, keyfile=None, certfile=None, host=None,
78 **kwargs):
78 **kwargs):
79 self.keyfile = keyfile
79 self.keyfile = keyfile
80 self.certfile = certfile
80 self.certfile = certfile
81 smtplib.SMTP.__init__(self, **kwargs)
81 smtplib.SMTP.__init__(self, **kwargs)
82 self._host = host
82 self._host = host
83 self.default_port = smtplib.SMTP_SSL_PORT
83 self.default_port = smtplib.SMTP_SSL_PORT
84 self._sslkwargs = sslkwargs
84 self._ui = ui
85
85
86 def _get_socket(self, host, port, timeout):
86 def _get_socket(self, host, port, timeout):
87 if self.debuglevel > 0:
87 if self.debuglevel > 0:
@@ -89,8 +89,8 b' class SMTPS(smtplib.SMTP):'
89 new_socket = socket.create_connection((host, port), timeout)
89 new_socket = socket.create_connection((host, port), timeout)
90 new_socket = sslutil.wrapsocket(new_socket,
90 new_socket = sslutil.wrapsocket(new_socket,
91 self.keyfile, self.certfile,
91 self.keyfile, self.certfile,
92 serverhostname=self._host,
92 ui=self._ui,
93 **self._sslkwargs)
93 serverhostname=self._host)
94 self.file = smtplib.SSLFakeFile(new_socket)
94 self.file = smtplib.SSLFakeFile(new_socket)
95 return new_socket
95 return new_socket
96
96
@@ -106,22 +106,11 b' def _smtp(ui):'
106 mailhost = ui.config('smtp', 'host')
106 mailhost = ui.config('smtp', 'host')
107 if not mailhost:
107 if not mailhost:
108 raise error.Abort(_('smtp.host not configured - cannot send mail'))
108 raise error.Abort(_('smtp.host not configured - cannot send mail'))
109 verifycert = ui.config('smtp', 'verifycert', 'strict')
110 if verifycert not in ['strict', 'loose']:
111 if util.parsebool(verifycert) is not False:
112 raise error.Abort(_('invalid smtp.verifycert configuration: %s')
113 % (verifycert))
114 verifycert = False
115 if (starttls or smtps) and verifycert:
116 sslkwargs = sslutil.sslkwargs(ui, mailhost)
117 else:
118 # 'ui' is required by sslutil.wrapsocket() and set by sslkwargs()
119 sslkwargs = {'ui': ui}
120 if smtps:
109 if smtps:
121 ui.note(_('(using smtps)\n'))
110 ui.note(_('(using smtps)\n'))
122 s = SMTPS(sslkwargs, local_hostname=local_hostname, host=mailhost)
111 s = SMTPS(ui, local_hostname=local_hostname, host=mailhost)
123 elif starttls:
112 elif starttls:
124 s = STARTTLS(sslkwargs, local_hostname=local_hostname, host=mailhost)
113 s = STARTTLS(ui, local_hostname=local_hostname, host=mailhost)
125 else:
114 else:
126 s = smtplib.SMTP(local_hostname=local_hostname)
115 s = smtplib.SMTP(local_hostname=local_hostname)
127 if smtps:
116 if smtps:
@@ -137,9 +126,9 b' def _smtp(ui):'
137 s.ehlo()
126 s.ehlo()
138 s.starttls()
127 s.starttls()
139 s.ehlo()
128 s.ehlo()
140 if (starttls or smtps) and verifycert:
129 if starttls or smtps:
141 ui.note(_('(verifying remote certificate)\n'))
130 ui.note(_('(verifying remote certificate)\n'))
142 sslutil.validator(ui, mailhost)(s.sock, verifycert == 'strict')
131 sslutil.validatesocket(s.sock)
143 username = ui.config('smtp', 'username')
132 username = ui.config('smtp', 'username')
144 password = ui.config('smtp', 'password')
133 password = ui.config('smtp', 'password')
145 if username and not password:
134 if username and not password:
@@ -211,8 +211,10 b' class manifestdict(object):'
211
211
212 def filesnotin(self, m2):
212 def filesnotin(self, m2):
213 '''Set of files in this manifest that are not in the other'''
213 '''Set of files in this manifest that are not in the other'''
214 files = set(self)
214 diff = self.diff(m2)
215 files.difference_update(m2)
215 files = set(filepath
216 for filepath, hashflags in diff.iteritems()
217 if hashflags[1][0] is None)
216 return files
218 return files
217
219
218 @propertycache
220 @propertycache
@@ -966,7 +968,7 b' class manifest(revlog.revlog):'
966 return self.readdelta(node)
968 return self.readdelta(node)
967 if self._usemanifestv2:
969 if self._usemanifestv2:
968 raise error.Abort(
970 raise error.Abort(
969 "readshallowdelta() not implemented for manifestv2")
971 _("readshallowdelta() not implemented for manifestv2"))
970 r = self.rev(node)
972 r = self.rev(node)
971 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
973 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
972 return manifestdict(d)
974 return manifestdict(d)
@@ -38,7 +38,7 b' def _expandsets(kindpats, ctx, listsubre'
38 for kind, pat, source in kindpats:
38 for kind, pat, source in kindpats:
39 if kind == 'set':
39 if kind == 'set':
40 if not ctx:
40 if not ctx:
41 raise error.Abort("fileset expression with no context")
41 raise error.Abort(_("fileset expression with no context"))
42 s = ctx.getfileset(pat)
42 s = ctx.getfileset(pat)
43 fset.update(s)
43 fset.update(s)
44
44
@@ -58,10 +58,8 b' class diffopts(object):'
58 'upgrade': False,
58 'upgrade': False,
59 }
59 }
60
60
61 __slots__ = defaults.keys()
62
63 def __init__(self, **opts):
61 def __init__(self, **opts):
64 for k in self.__slots__:
62 for k in self.defaults.keys():
65 v = opts.get(k)
63 v = opts.get(k)
66 if v is None:
64 if v is None:
67 v = self.defaults[k]
65 v = self.defaults[k]
@@ -8,6 +8,7 b''
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import hashlib
11 import os
12 import os
12 import shutil
13 import shutil
13 import struct
14 import struct
@@ -373,7 +374,7 b' class mergestate(object):'
373 """Write current state on disk in a version 1 file"""
374 """Write current state on disk in a version 1 file"""
374 f = self._repo.vfs(self.statepathv1, 'w')
375 f = self._repo.vfs(self.statepathv1, 'w')
375 irecords = iter(records)
376 irecords = iter(records)
376 lrecords = irecords.next()
377 lrecords = next(irecords)
377 assert lrecords[0] == 'L'
378 assert lrecords[0] == 'L'
378 f.write(hex(self._local) + '\n')
379 f.write(hex(self._local) + '\n')
379 for rtype, data in irecords:
380 for rtype, data in irecords:
@@ -408,7 +409,7 b' class mergestate(object):'
408 if fcl.isabsent():
409 if fcl.isabsent():
409 hash = nullhex
410 hash = nullhex
410 else:
411 else:
411 hash = util.sha1(fcl.path()).hexdigest()
412 hash = hashlib.sha1(fcl.path()).hexdigest()
412 self._repo.vfs.write('merge/' + hash, fcl.data())
413 self._repo.vfs.write('merge/' + hash, fcl.data())
413 self._state[fd] = ['u', hash, fcl.path(),
414 self._state[fd] = ['u', hash, fcl.path(),
414 fca.path(), hex(fca.filenode()),
415 fca.path(), hex(fca.filenode()),
@@ -989,19 +990,19 b' def calculateupdates(repo, wctx, mctx, a'
989 if len(bids) == 1: # all bids are the same kind of method
990 if len(bids) == 1: # all bids are the same kind of method
990 m, l = bids.items()[0]
991 m, l = bids.items()[0]
991 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
992 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
992 repo.ui.note(" %s: consensus for %s\n" % (f, m))
993 repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
993 actions[f] = l[0]
994 actions[f] = l[0]
994 continue
995 continue
995 # If keep is an option, just do it.
996 # If keep is an option, just do it.
996 if 'k' in bids:
997 if 'k' in bids:
997 repo.ui.note(" %s: picking 'keep' action\n" % f)
998 repo.ui.note(_(" %s: picking 'keep' action\n") % f)
998 actions[f] = bids['k'][0]
999 actions[f] = bids['k'][0]
999 continue
1000 continue
1000 # If there are gets and they all agree [how could they not?], do it.
1001 # If there are gets and they all agree [how could they not?], do it.
1001 if 'g' in bids:
1002 if 'g' in bids:
1002 ga0 = bids['g'][0]
1003 ga0 = bids['g'][0]
1003 if all(a == ga0 for a in bids['g'][1:]):
1004 if all(a == ga0 for a in bids['g'][1:]):
1004 repo.ui.note(" %s: picking 'get' action\n" % f)
1005 repo.ui.note(_(" %s: picking 'get' action\n") % f)
1005 actions[f] = ga0
1006 actions[f] = ga0
1006 continue
1007 continue
1007 # TODO: Consider other simple actions such as mode changes
1008 # TODO: Consider other simple actions such as mode changes
@@ -1075,15 +1076,14 b' def batchget(repo, mctx, actions):'
1075 absf = repo.wjoin(f)
1076 absf = repo.wjoin(f)
1076 orig = scmutil.origpath(ui, repo, absf)
1077 orig = scmutil.origpath(ui, repo, absf)
1077 try:
1078 try:
1078 # TODO Mercurial has always aborted if an untracked
1079 # directory is replaced by a tracked file, or generally
1080 # with file/directory merges. This needs to be sorted out.
1081 if repo.wvfs.isfileorlink(f):
1079 if repo.wvfs.isfileorlink(f):
1082 util.rename(absf, orig)
1080 util.rename(absf, orig)
1083 except OSError as e:
1081 except OSError as e:
1084 if e.errno != errno.ENOENT:
1082 if e.errno != errno.ENOENT:
1085 raise
1083 raise
1086
1084
1085 if repo.wvfs.isdir(f):
1086 repo.wvfs.removedirs(f)
1087 wwrite(f, fctx(f).data(), flags, backgroundclose=True)
1087 wwrite(f, fctx(f).data(), flags, backgroundclose=True)
1088 if i == 100:
1088 if i == 100:
1089 yield i, f
1089 yield i, f
@@ -1442,9 +1442,7 b' def update(repo, node, branchmerge, forc'
1442 pas = [repo[ancestor]]
1442 pas = [repo[ancestor]]
1443
1443
1444 if node is None:
1444 if node is None:
1445 if (repo.ui.configbool('devel', 'all-warnings')
1445 repo.ui.deprecwarn('update with no target', '3.9')
1446 or repo.ui.configbool('devel', 'oldapi')):
1447 repo.ui.develwarn('update with no target')
1448 rev, _mark, _act = destutil.destupdate(repo)
1446 rev, _mark, _act = destutil.destupdate(repo)
1449 node = repo[rev].node()
1447 node = repo[rev].node()
1450
1448
@@ -26,6 +26,7 b''
26 #include <string.h>
26 #include <string.h>
27
27
28 #include "util.h"
28 #include "util.h"
29 #include "bitmanipulation.h"
29
30
30 static char mpatch_doc[] = "Efficient binary patching.";
31 static char mpatch_doc[] = "Efficient binary patching.";
31 static PyObject *mpatch_Error;
32 static PyObject *mpatch_Error;
@@ -600,8 +600,8 b' class obsstore(object):'
600 Take care of filtering duplicate.
600 Take care of filtering duplicate.
601 Return the number of new marker."""
601 Return the number of new marker."""
602 if self._readonly:
602 if self._readonly:
603 raise error.Abort('creating obsolete markers is not enabled on '
603 raise error.Abort(_('creating obsolete markers is not enabled on '
604 'this repo')
604 'this repo'))
605 known = set(self._all)
605 known = set(self._all)
606 new = []
606 new = []
607 for m in markers:
607 for m in markers:
@@ -1171,7 +1171,7 b' def _computebumpedset(repo):'
1171 ignoreflags=bumpedfix):
1171 ignoreflags=bumpedfix):
1172 prev = torev(pnode) # unfiltered! but so is phasecache
1172 prev = torev(pnode) # unfiltered! but so is phasecache
1173 if (prev is not None) and (phase(repo, prev) <= public):
1173 if (prev is not None) and (phase(repo, prev) <= public):
1174 # we have a public precursors
1174 # we have a public precursor
1175 bumped.add(rev)
1175 bumped.add(rev)
1176 break # Next draft!
1176 break # Next draft!
1177 return bumped
1177 return bumped
@@ -1234,7 +1234,7 b' def createmarkers(repo, relations, flag='
1234 localmetadata.update(rel[2])
1234 localmetadata.update(rel[2])
1235
1235
1236 if not prec.mutable():
1236 if not prec.mutable():
1237 raise error.Abort("cannot obsolete public changeset: %s"
1237 raise error.Abort(_("cannot obsolete public changeset: %s")
1238 % prec,
1238 % prec,
1239 hint='see "hg help phases" for details')
1239 hint='see "hg help phases" for details')
1240 nprec = prec.node()
1240 nprec = prec.node()
@@ -1243,7 +1243,8 b' def createmarkers(repo, relations, flag='
1243 if not nsucs:
1243 if not nsucs:
1244 npare = tuple(p.node() for p in prec.parents())
1244 npare = tuple(p.node() for p in prec.parents())
1245 if nprec in nsucs:
1245 if nprec in nsucs:
1246 raise error.Abort("changeset %s cannot obsolete itself" % prec)
1246 raise error.Abort(_("changeset %s cannot obsolete itself")
1247 % prec)
1247
1248
1248 # Creating the marker causes the hidden cache to become invalid,
1249 # Creating the marker causes the hidden cache to become invalid,
1249 # which causes recomputation when we ask for prec.parents() above.
1250 # which causes recomputation when we ask for prec.parents() above.
@@ -325,13 +325,13 b' class basealiasrules(object):'
325 >>> builddecl('foo')
325 >>> builddecl('foo')
326 ('foo', None, None)
326 ('foo', None, None)
327 >>> builddecl('$foo')
327 >>> builddecl('$foo')
328 ('$foo', None, "'$' not for alias arguments")
328 ('$foo', None, "invalid symbol '$foo'")
329 >>> builddecl('foo::bar')
329 >>> builddecl('foo::bar')
330 ('foo::bar', None, 'invalid format')
330 ('foo::bar', None, 'invalid format')
331 >>> builddecl('foo()')
331 >>> builddecl('foo()')
332 ('foo', [], None)
332 ('foo', [], None)
333 >>> builddecl('$foo()')
333 >>> builddecl('$foo()')
334 ('$foo()', None, "'$' not for alias arguments")
334 ('$foo()', None, "invalid function '$foo'")
335 >>> builddecl('foo($1, $2)')
335 >>> builddecl('foo($1, $2)')
336 ('foo', ['$1', '$2'], None)
336 ('foo', ['$1', '$2'], None)
337 >>> builddecl('foo(bar_bar, baz.baz)')
337 >>> builddecl('foo(bar_bar, baz.baz)')
@@ -358,7 +358,7 b' class basealiasrules(object):'
358 # "name = ...." style
358 # "name = ...." style
359 name = tree[1]
359 name = tree[1]
360 if name.startswith('$'):
360 if name.startswith('$'):
361 return (decl, None, _("'$' not for alias arguments"))
361 return (decl, None, _("invalid symbol '%s'") % name)
362 return (name, None, None)
362 return (name, None, None)
363
363
364 func = cls._trygetfunc(tree)
364 func = cls._trygetfunc(tree)
@@ -366,7 +366,7 b' class basealiasrules(object):'
366 # "name(arg, ....) = ...." style
366 # "name(arg, ....) = ...." style
367 name, args = func
367 name, args = func
368 if name.startswith('$'):
368 if name.startswith('$'):
369 return (decl, None, _("'$' not for alias arguments"))
369 return (decl, None, _("invalid function '%s'") % name)
370 if any(t[0] != cls._symbolnode for t in args):
370 if any(t[0] != cls._symbolnode for t in args):
371 return (decl, None, _("invalid argument list"))
371 return (decl, None, _("invalid argument list"))
372 if len(args) != len(set(args)):
372 if len(args) != len(set(args)):
@@ -389,7 +389,7 b' class basealiasrules(object):'
389 if sym in args:
389 if sym in args:
390 op = '_aliasarg'
390 op = '_aliasarg'
391 elif sym.startswith('$'):
391 elif sym.startswith('$'):
392 raise error.ParseError(_("'$' not for alias arguments"))
392 raise error.ParseError(_("invalid symbol '%s'") % sym)
393 return (op, sym)
393 return (op, sym)
394
394
395 @classmethod
395 @classmethod
@@ -423,7 +423,7 b' class basealiasrules(object):'
423 ... builddefn('$1 or $bar', args)
423 ... builddefn('$1 or $bar', args)
424 ... except error.ParseError as inst:
424 ... except error.ParseError as inst:
425 ... print parseerrordetail(inst)
425 ... print parseerrordetail(inst)
426 '$' not for alias arguments
426 invalid symbol '$bar'
427 >>> args = ['$1', '$10', 'foo']
427 >>> args = ['$1', '$10', 'foo']
428 >>> pprint(builddefn('$10 or baz', args))
428 >>> pprint(builddefn('$10 or baz', args))
429 (or
429 (or
@@ -447,15 +447,13 b' class basealiasrules(object):'
447 repl = efmt = None
447 repl = efmt = None
448 name, args, err = cls._builddecl(decl)
448 name, args, err = cls._builddecl(decl)
449 if err:
449 if err:
450 efmt = _('failed to parse the declaration of %(section)s '
450 efmt = _('bad declaration of %(section)s "%(name)s": %(error)s')
451 '"%(name)s": %(error)s')
452 else:
451 else:
453 try:
452 try:
454 repl = cls._builddefn(defn, args)
453 repl = cls._builddefn(defn, args)
455 except error.ParseError as inst:
454 except error.ParseError as inst:
456 err = parseerrordetail(inst)
455 err = parseerrordetail(inst)
457 efmt = _('failed to parse the definition of %(section)s '
456 efmt = _('bad definition of %(section)s "%(name)s": %(error)s')
458 '"%(name)s": %(error)s')
459 if err:
457 if err:
460 err = efmt % {'section': cls._section, 'name': name, 'error': err}
458 err = efmt % {'section': cls._section, 'name': name, 'error': err}
461 return alias(name, args, err, repl)
459 return alias(name, args, err, repl)
@@ -13,6 +13,7 b''
13 #include <string.h>
13 #include <string.h>
14
14
15 #include "util.h"
15 #include "util.h"
16 #include "bitmanipulation.h"
16
17
17 static char *versionerrortext = "Python minor version mismatch";
18 static char *versionerrortext = "Python minor version mismatch";
18
19
@@ -12,6 +12,7 b' import collections'
12 import copy
12 import copy
13 import email
13 import email
14 import errno
14 import errno
15 import hashlib
15 import os
16 import os
16 import posixpath
17 import posixpath
17 import re
18 import re
@@ -978,7 +979,19 b' class recordhunk(object):'
978 def filterpatch(ui, headers, operation=None):
979 def filterpatch(ui, headers, operation=None):
979 """Interactively filter patch chunks into applied-only chunks"""
980 """Interactively filter patch chunks into applied-only chunks"""
980 if operation is None:
981 if operation is None:
981 operation = _('record')
982 operation = 'record'
983 messages = {
984 'multiple': {
985 'discard': _("discard change %d/%d to '%s'?"),
986 'record': _("record change %d/%d to '%s'?"),
987 'revert': _("revert change %d/%d to '%s'?"),
988 }[operation],
989 'single': {
990 'discard': _("discard this change to '%s'?"),
991 'record': _("record this change to '%s'?"),
992 'revert': _("revert this change to '%s'?"),
993 }[operation],
994 }
982
995
983 def prompt(skipfile, skipall, query, chunk):
996 def prompt(skipfile, skipall, query, chunk):
984 """prompt query, and process base inputs
997 """prompt query, and process base inputs
@@ -1109,11 +1122,10 b' the hunk is left unchanged.'
1109 if skipfile is None and skipall is None:
1122 if skipfile is None and skipall is None:
1110 chunk.pretty(ui)
1123 chunk.pretty(ui)
1111 if total == 1:
1124 if total == 1:
1112 msg = _("record this change to '%s'?") % chunk.filename()
1125 msg = messages['single'] % chunk.filename()
1113 else:
1126 else:
1114 idx = pos - len(h.hunks) + i
1127 idx = pos - len(h.hunks) + i
1115 msg = _("record change %d/%d to '%s'?") % (idx, total,
1128 msg = messages['multiple'] % (idx, total, chunk.filename())
1116 chunk.filename())
1117 r, skipfile, skipall, newpatches = prompt(skipfile,
1129 r, skipfile, skipall, newpatches = prompt(skipfile,
1118 skipall, msg, chunk)
1130 skipall, msg, chunk)
1119 if r:
1131 if r:
@@ -2172,7 +2184,7 b' def difffeatureopts(ui, opts=None, untru'
2172 return mdiff.diffopts(**buildopts)
2184 return mdiff.diffopts(**buildopts)
2173
2185
2174 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
2186 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
2175 losedatafn=None, prefix='', relroot=''):
2187 losedatafn=None, prefix='', relroot='', copy=None):
2176 '''yields diff of changes to files between two nodes, or node and
2188 '''yields diff of changes to files between two nodes, or node and
2177 working directory.
2189 working directory.
2178
2190
@@ -2191,7 +2203,10 b' def diff(repo, node1=None, node2=None, m'
2191 display (used for subrepos).
2203 display (used for subrepos).
2192
2204
2193 relroot, if not empty, must be normalized with a trailing /. Any match
2205 relroot, if not empty, must be normalized with a trailing /. Any match
2194 patterns that fall outside it will be ignored.'''
2206 patterns that fall outside it will be ignored.
2207
2208 copy, if not empty, should contain mappings {dst@y: src@x} of copy
2209 information.'''
2195
2210
2196 if opts is None:
2211 if opts is None:
2197 opts = mdiff.defaultopts
2212 opts = mdiff.defaultopts
@@ -2238,9 +2253,10 b' def diff(repo, node1=None, node2=None, m'
2238 hexfunc = short
2253 hexfunc = short
2239 revs = [hexfunc(node) for node in [ctx1.node(), ctx2.node()] if node]
2254 revs = [hexfunc(node) for node in [ctx1.node(), ctx2.node()] if node]
2240
2255
2241 copy = {}
2256 if copy is None:
2242 if opts.git or opts.upgrade:
2257 copy = {}
2243 copy = copies.pathcopies(ctx1, ctx2, match=match)
2258 if opts.git or opts.upgrade:
2259 copy = copies.pathcopies(ctx1, ctx2, match=match)
2244
2260
2245 if relroot is not None:
2261 if relroot is not None:
2246 if not relfiltered:
2262 if not relfiltered:
@@ -2401,7 +2417,7 b' def trydiff(repo, revs, ctx1, ctx2, modi'
2401 if not text:
2417 if not text:
2402 text = ""
2418 text = ""
2403 l = len(text)
2419 l = len(text)
2404 s = util.sha1('blob %d\0' % l)
2420 s = hashlib.sha1('blob %d\0' % l)
2405 s.update(text)
2421 s.update(text)
2406 return s.hexdigest()
2422 return s.hexdigest()
2407
2423
@@ -653,24 +653,24 b' static int sha1hash(char hash[20], const'
653 PyObject *shaobj, *hashobj;
653 PyObject *shaobj, *hashobj;
654
654
655 if (shafunc == NULL) {
655 if (shafunc == NULL) {
656 PyObject *util, *name = PyString_FromString("mercurial.util");
656 PyObject *hashlib, *name = PyString_FromString("hashlib");
657
657
658 if (name == NULL)
658 if (name == NULL)
659 return -1;
659 return -1;
660
660
661 util = PyImport_Import(name);
661 hashlib = PyImport_Import(name);
662 Py_DECREF(name);
662 Py_DECREF(name);
663
663
664 if (util == NULL) {
664 if (hashlib == NULL) {
665 PyErr_SetString(PyExc_ImportError, "mercurial.util");
665 PyErr_SetString(PyExc_ImportError, "hashlib");
666 return -1;
666 return -1;
667 }
667 }
668 shafunc = PyObject_GetAttrString(util, "sha1");
668 shafunc = PyObject_GetAttrString(hashlib, "sha1");
669 Py_DECREF(util);
669 Py_DECREF(hashlib);
670
670
671 if (shafunc == NULL) {
671 if (shafunc == NULL) {
672 PyErr_SetString(PyExc_AttributeError,
672 PyErr_SetString(PyExc_AttributeError,
673 "module 'mercurial.util' has no "
673 "module 'hashlib' has no "
674 "attribute 'sha1'");
674 "attribute 'sha1'");
675 return -1;
675 return -1;
676 }
676 }
@@ -98,12 +98,12 b' def batchable(f):'
98 '''
98 '''
99 def plain(*args, **opts):
99 def plain(*args, **opts):
100 batchable = f(*args, **opts)
100 batchable = f(*args, **opts)
101 encargsorres, encresref = batchable.next()
101 encargsorres, encresref = next(batchable)
102 if not encresref:
102 if not encresref:
103 return encargsorres # a local result in this case
103 return encargsorres # a local result in this case
104 self = args[0]
104 self = args[0]
105 encresref.set(self._submitone(f.func_name, encargsorres))
105 encresref.set(self._submitone(f.func_name, encargsorres))
106 return batchable.next()
106 return next(batchable)
107 setattr(plain, 'batchable', f)
107 setattr(plain, 'batchable', f)
108 return plain
108 return plain
109
109
@@ -251,7 +251,7 b' class phasecache(object):'
251 def write(self):
251 def write(self):
252 if not self.dirty:
252 if not self.dirty:
253 return
253 return
254 f = self.opener('phaseroots', 'w', atomictemp=True)
254 f = self.opener('phaseroots', 'w', atomictemp=True, checkambig=True)
255 try:
255 try:
256 self._write(f)
256 self._write(f)
257 finally:
257 finally:
@@ -598,3 +598,18 b' def readpipe(pipe):'
598 return ''.join(chunks)
598 return ''.join(chunks)
599 finally:
599 finally:
600 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
600 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
601
602 def bindunixsocket(sock, path):
603 """Bind the UNIX domain socket to the specified path"""
604 # use relative path instead of full path at bind() if possible, since
605 # AF_UNIX path has very small length limit (107 chars) on common
606 # platforms (see sys/un.h)
607 dirname, basename = os.path.split(path)
608 bakwdfd = None
609 if dirname:
610 bakwdfd = os.open('.', os.O_DIRECTORY)
611 os.chdir(dirname)
612 sock.bind(basename)
613 if bakwdfd:
614 os.fchdir(bakwdfd)
615 os.close(bakwdfd)
@@ -14,6 +14,10 b' import socket'
14 import stat as statmod
14 import stat as statmod
15 import sys
15 import sys
16
16
17 from . import policy
18 modulepolicy = policy.policy
19 policynocffi = policy.policynocffi
20
17 def _mode_to_kind(mode):
21 def _mode_to_kind(mode):
18 if statmod.S_ISREG(mode):
22 if statmod.S_ISREG(mode):
19 return statmod.S_IFREG
23 return statmod.S_IFREG
@@ -31,7 +35,7 b' def _mode_to_kind(mode):'
31 return statmod.S_IFSOCK
35 return statmod.S_IFSOCK
32 return mode
36 return mode
33
37
34 def listdir(path, stat=False, skip=None):
38 def listdirpure(path, stat=False, skip=None):
35 '''listdir(path, stat=False) -> list_of_tuples
39 '''listdir(path, stat=False) -> list_of_tuples
36
40
37 Return a sorted list containing information about the entries
41 Return a sorted list containing information about the entries
@@ -61,6 +65,95 b' def listdir(path, stat=False, skip=None)'
61 result.append((fn, _mode_to_kind(st.st_mode)))
65 result.append((fn, _mode_to_kind(st.st_mode)))
62 return result
66 return result
63
67
68 ffi = None
69 if modulepolicy not in policynocffi and sys.platform == 'darwin':
70 try:
71 from _osutil_cffi import ffi, lib
72 except ImportError:
73 if modulepolicy == 'cffi': # strict cffi import
74 raise
75
76 if sys.platform == 'darwin' and ffi is not None:
77 listdir_batch_size = 4096
78 # tweakable number, only affects performance, which chunks
79 # of bytes do we get back from getattrlistbulk
80
81 attrkinds = [None] * 20 # we need the max no for enum VXXX, 20 is plenty
82
83 attrkinds[lib.VREG] = statmod.S_IFREG
84 attrkinds[lib.VDIR] = statmod.S_IFDIR
85 attrkinds[lib.VLNK] = statmod.S_IFLNK
86 attrkinds[lib.VBLK] = statmod.S_IFBLK
87 attrkinds[lib.VCHR] = statmod.S_IFCHR
88 attrkinds[lib.VFIFO] = statmod.S_IFIFO
89 attrkinds[lib.VSOCK] = statmod.S_IFSOCK
90
91 class stat_res(object):
92 def __init__(self, st_mode, st_mtime, st_size):
93 self.st_mode = st_mode
94 self.st_mtime = st_mtime
95 self.st_size = st_size
96
97 tv_sec_ofs = ffi.offsetof("struct timespec", "tv_sec")
98 buf = ffi.new("char[]", listdir_batch_size)
99
100 def listdirinternal(dfd, req, stat, skip):
101 ret = []
102 while True:
103 r = lib.getattrlistbulk(dfd, req, buf, listdir_batch_size, 0)
104 if r == 0:
105 break
106 if r == -1:
107 raise OSError(ffi.errno, os.strerror(ffi.errno))
108 cur = ffi.cast("val_attrs_t*", buf)
109 for i in range(r):
110 lgt = cur.length
111 assert lgt == ffi.cast('uint32_t*', cur)[0]
112 ofs = cur.name_info.attr_dataoffset
113 str_lgt = cur.name_info.attr_length
114 base_ofs = ffi.offsetof('val_attrs_t', 'name_info')
115 name = str(ffi.buffer(ffi.cast("char*", cur) + base_ofs + ofs,
116 str_lgt - 1))
117 tp = attrkinds[cur.obj_type]
118 if name == "." or name == "..":
119 continue
120 if skip == name and tp == statmod.S_ISDIR:
121 return []
122 if stat:
123 mtime = cur.time.tv_sec
124 mode = (cur.accessmask & ~lib.S_IFMT)| tp
125 ret.append((name, tp, stat_res(st_mode=mode, st_mtime=mtime,
126 st_size=cur.datalength)))
127 else:
128 ret.append((name, tp))
129 cur += lgt
130 return ret
131
132 def listdir(path, stat=False, skip=None):
133 req = ffi.new("struct attrlist*")
134 req.bitmapcount = lib.ATTR_BIT_MAP_COUNT
135 req.commonattr = (lib.ATTR_CMN_RETURNED_ATTRS |
136 lib.ATTR_CMN_NAME |
137 lib.ATTR_CMN_OBJTYPE |
138 lib.ATTR_CMN_ACCESSMASK |
139 lib.ATTR_CMN_MODTIME)
140 req.fileattr = lib.ATTR_FILE_DATALENGTH
141 dfd = lib.open(path, lib.O_RDONLY, 0)
142 if dfd == -1:
143 raise OSError(ffi.errno, os.strerror(ffi.errno))
144
145 try:
146 ret = listdirinternal(dfd, req, stat, skip)
147 finally:
148 try:
149 lib.close(dfd)
150 except BaseException:
151 pass # we ignore all the errors from closing, not
152 # much we can do about that
153 return ret
154 else:
155 listdir = listdirpure
156
64 if os.name != 'nt':
157 if os.name != 'nt':
65 posixfile = open
158 posixfile = open
66
159
@@ -25,49 +25,111 b' def dirstatetuple(*x):'
25 # x is a tuple
25 # x is a tuple
26 return x
26 return x
27
27
28 def parse_index2(data, inline):
28 indexformatng = ">Qiiiiii20s12x"
29 def gettype(q):
29 indexfirst = struct.calcsize('Q')
30 return int(q & 0xFFFF)
30 sizeint = struct.calcsize('i')
31 indexsize = struct.calcsize(indexformatng)
32
33 def gettype(q):
34 return int(q & 0xFFFF)
31
35
32 def offset_type(offset, type):
36 def offset_type(offset, type):
33 return long(long(offset) << 16 | type)
37 return long(long(offset) << 16 | type)
38
39 class BaseIndexObject(object):
40 def __len__(self):
41 return self._lgt + len(self._extra) + 1
42
43 def insert(self, i, tup):
44 assert i == -1
45 self._extra.append(tup)
34
46
35 indexformatng = ">Qiiiiii20s12x"
47 def _fix_index(self, i):
48 if not isinstance(i, int):
49 raise TypeError("expecting int indexes")
50 if i < 0:
51 i = len(self) + i
52 if i < 0 or i >= len(self):
53 raise IndexError
54 return i
36
55
37 s = struct.calcsize(indexformatng)
56 def __getitem__(self, i):
38 index = []
57 i = self._fix_index(i)
39 cache = None
58 if i == len(self) - 1:
40 off = 0
59 return (0, 0, 0, -1, -1, -1, -1, nullid)
60 if i >= self._lgt:
61 return self._extra[i - self._lgt]
62 index = self._calculate_index(i)
63 r = struct.unpack(indexformatng, self._data[index:index + indexsize])
64 if i == 0:
65 e = list(r)
66 type = gettype(e[0])
67 e[0] = offset_type(0, type)
68 return tuple(e)
69 return r
70
71 class IndexObject(BaseIndexObject):
72 def __init__(self, data):
73 assert len(data) % indexsize == 0
74 self._data = data
75 self._lgt = len(data) // indexsize
76 self._extra = []
77
78 def _calculate_index(self, i):
79 return i * indexsize
41
80
42 l = len(data) - s
81 def __delitem__(self, i):
43 append = index.append
82 if not isinstance(i, slice) or not i.stop == -1 or not i.step is None:
44 if inline:
83 raise ValueError("deleting slices only supports a:-1 with step 1")
45 cache = (0, data)
84 i = self._fix_index(i.start)
46 while off <= l:
85 if i < self._lgt:
47 e = _unpack(indexformatng, data[off:off + s])
86 self._data = self._data[:i * indexsize]
48 append(e)
87 self._lgt = i
49 if e[1] < 0:
88 self._extra = []
50 break
89 else:
51 off += e[1] + s
90 self._extra = self._extra[:i - self._lgt]
52 else:
91
53 while off <= l:
92 class InlinedIndexObject(BaseIndexObject):
54 e = _unpack(indexformatng, data[off:off + s])
93 def __init__(self, data, inline=0):
55 append(e)
94 self._data = data
56 off += s
95 self._lgt = self._inline_scan(None)
96 self._inline_scan(self._lgt)
97 self._extra = []
57
98
58 if off != len(data):
99 def _inline_scan(self, lgt):
59 raise ValueError('corrupt index file')
100 off = 0
101 if lgt is not None:
102 self._offsets = [0] * lgt
103 count = 0
104 while off <= len(self._data) - indexsize:
105 s, = struct.unpack('>i',
106 self._data[off + indexfirst:off + sizeint + indexfirst])
107 if lgt is not None:
108 self._offsets[count] = off
109 count += 1
110 off += indexsize + s
111 if off != len(self._data):
112 raise ValueError("corrupted data")
113 return count
60
114
61 if index:
115 def __delitem__(self, i):
62 e = list(index[0])
116 if not isinstance(i, slice) or not i.stop == -1 or not i.step is None:
63 type = gettype(e[0])
117 raise ValueError("deleting slices only supports a:-1 with step 1")
64 e[0] = offset_type(0, type)
118 i = self._fix_index(i.start)
65 index[0] = tuple(e)
119 if i < self._lgt:
120 self._offsets = self._offsets[:i]
121 self._lgt = i
122 self._extra = []
123 else:
124 self._extra = self._extra[:i - self._lgt]
66
125
67 # add the magic null revision at -1
126 def _calculate_index(self, i):
68 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
127 return self._offsets[i]
69
128
70 return index, cache
129 def parse_index2(data, inline):
130 if not inline:
131 return IndexObject(data), None
132 return InlinedIndexObject(data, inline), (0, data)
71
133
72 def parse_dirstate(dmap, copymap, st):
134 def parse_dirstate(dmap, copymap, st):
73 parents = [st[:20], st[20: 40]]
135 parents = [st[:20], st[20: 40]]
@@ -10,18 +10,26 b' This contains aliases to hide python ver'
10
10
11 from __future__ import absolute_import
11 from __future__ import absolute_import
12
12
13 try:
13 import sys
14
15 if sys.version_info[0] < 3:
16 import cPickle as pickle
14 import cStringIO as io
17 import cStringIO as io
15 stringio = io.StringIO
18 import httplib
16 except ImportError:
19 import Queue as _queue
20 import SocketServer as socketserver
21 import urlparse
22 import xmlrpclib
23 else:
24 import http.client as httplib
17 import io
25 import io
18 stringio = io.StringIO
26 import pickle
27 import queue as _queue
28 import socketserver
29 import urllib.parse as urlparse
30 import xmlrpc.client as xmlrpclib
19
31
20 try:
32 stringio = io.StringIO
21 import Queue as _queue
22 _queue.Queue
23 except ImportError:
24 import queue as _queue
25 empty = _queue.Empty
33 empty = _queue.Empty
26 queue = _queue.Queue
34 queue = _queue.Queue
27
35
@@ -41,9 +49,13 b' def _alias(alias, origin, items):'
41 except AttributeError:
49 except AttributeError:
42 pass
50 pass
43
51
52 httpserver = _pycompatstub()
44 urlreq = _pycompatstub()
53 urlreq = _pycompatstub()
45 urlerr = _pycompatstub()
54 urlerr = _pycompatstub()
46 try:
55 try:
56 import BaseHTTPServer
57 import CGIHTTPServer
58 import SimpleHTTPServer
47 import urllib2
59 import urllib2
48 import urllib
60 import urllib
49 _alias(urlreq, urllib, (
61 _alias(urlreq, urllib, (
@@ -81,6 +93,16 b' try:'
81 "HTTPError",
93 "HTTPError",
82 "URLError",
94 "URLError",
83 ))
95 ))
96 _alias(httpserver, BaseHTTPServer, (
97 "HTTPServer",
98 "BaseHTTPRequestHandler",
99 ))
100 _alias(httpserver, SimpleHTTPServer, (
101 "SimpleHTTPRequestHandler",
102 ))
103 _alias(httpserver, CGIHTTPServer, (
104 "CGIHTTPRequestHandler",
105 ))
84
106
85 except ImportError:
107 except ImportError:
86 import urllib.request
108 import urllib.request
@@ -99,6 +121,7 b' except ImportError:'
99 "pathname2url",
121 "pathname2url",
100 "HTTPBasicAuthHandler",
122 "HTTPBasicAuthHandler",
101 "HTTPDigestAuthHandler",
123 "HTTPDigestAuthHandler",
124 "HTTPPasswordMgrWithDefaultRealm",
102 "ProxyHandler",
125 "ProxyHandler",
103 "quote",
126 "quote",
104 "Request",
127 "Request",
@@ -115,6 +138,13 b' except ImportError:'
115 "HTTPError",
138 "HTTPError",
116 "URLError",
139 "URLError",
117 ))
140 ))
141 import http.server
142 _alias(httpserver, http.server, (
143 "HTTPServer",
144 "BaseHTTPRequestHandler",
145 "SimpleHTTPRequestHandler",
146 "CGIHTTPRequestHandler",
147 ))
118
148
119 try:
149 try:
120 xrange
150 xrange
@@ -9,6 +9,7 b''
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import errno
11 import errno
12 import hashlib
12
13
13 from .i18n import _
14 from .i18n import _
14 from .node import short
15 from .node import short
@@ -35,7 +36,7 b' def _bundle(repo, bases, heads, node, su'
35 # Include a hash of all the nodes in the filename for uniqueness
36 # Include a hash of all the nodes in the filename for uniqueness
36 allcommits = repo.set('%ln::%ln', bases, heads)
37 allcommits = repo.set('%ln::%ln', bases, heads)
37 allhashes = sorted(c.hex() for c in allcommits)
38 allhashes = sorted(c.hex() for c in allcommits)
38 totalhash = util.sha1(''.join(allhashes)).hexdigest()
39 totalhash = hashlib.sha1(''.join(allhashes)).hexdigest()
39 name = "%s/%s-%s-%s.hg" % (backupdir, short(node), totalhash[:8], suffix)
40 name = "%s/%s-%s-%s.hg" % (backupdir, short(node), totalhash[:8], suffix)
40
41
41 comp = None
42 comp = None
@@ -166,6 +167,13 b' def strip(ui, repo, nodelist, backup=Tru'
166 tr.startgroup()
167 tr.startgroup()
167 cl.strip(striprev, tr)
168 cl.strip(striprev, tr)
168 mfst.strip(striprev, tr)
169 mfst.strip(striprev, tr)
170 if 'treemanifest' in repo.requirements: # safe but unnecessary
171 # otherwise
172 for unencoded, encoded, size in repo.store.datafiles():
173 if (unencoded.startswith('meta/') and
174 unencoded.endswith('00manifest.i')):
175 dir = unencoded[5:-12]
176 repo.dirlog(dir).strip(striprev, tr)
169 for fn in files:
177 for fn in files:
170 repo.file(fn).strip(striprev, tr)
178 repo.file(fn).strip(striprev, tr)
171 tr.endgroup()
179 tr.endgroup()
@@ -9,6 +9,7 b''
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import copy
11 import copy
12 import hashlib
12 import heapq
13 import heapq
13 import struct
14 import struct
14
15
@@ -18,7 +19,6 b' from . import ('
18 obsolete,
19 obsolete,
19 phases,
20 phases,
20 tags as tagsmod,
21 tags as tagsmod,
21 util,
22 )
22 )
23
23
24 def hideablerevs(repo):
24 def hideablerevs(repo):
@@ -102,7 +102,7 b' def cachehash(repo, hideable):'
102 it to the cache. Upon reading we can easily validate by checking the hash
102 it to the cache. Upon reading we can easily validate by checking the hash
103 against the stored one and discard the cache in case the hashes don't match.
103 against the stored one and discard the cache in case the hashes don't match.
104 """
104 """
105 h = util.sha1()
105 h = hashlib.sha1()
106 h.update(''.join(repo.heads()))
106 h.update(''.join(repo.heads()))
107 h.update(str(hash(frozenset(hideable))))
107 h.update(str(hash(frozenset(hideable))))
108 return h.digest()
108 return h.digest()
@@ -15,6 +15,7 b' from __future__ import absolute_import'
15
15
16 import collections
16 import collections
17 import errno
17 import errno
18 import hashlib
18 import os
19 import os
19 import struct
20 import struct
20 import zlib
21 import zlib
@@ -40,7 +41,6 b' from . import ('
40 _unpack = struct.unpack
41 _unpack = struct.unpack
41 _compress = zlib.compress
42 _compress = zlib.compress
42 _decompress = zlib.decompress
43 _decompress = zlib.decompress
43 _sha = util.sha1
44
44
45 # revlog header flags
45 # revlog header flags
46 REVLOGV0 = 0
46 REVLOGV0 = 0
@@ -74,7 +74,7 b' def gettype(q):'
74 def offset_type(offset, type):
74 def offset_type(offset, type):
75 return long(long(offset) << 16 | type)
75 return long(long(offset) << 16 | type)
76
76
77 _nullhash = _sha(nullid)
77 _nullhash = hashlib.sha1(nullid)
78
78
79 def hash(text, p1, p2):
79 def hash(text, p1, p2):
80 """generate a hash from the given text and its parent hashes
80 """generate a hash from the given text and its parent hashes
@@ -92,7 +92,7 b' def hash(text, p1, p2):'
92 # none of the parent nodes are nullid
92 # none of the parent nodes are nullid
93 l = [p1, p2]
93 l = [p1, p2]
94 l.sort()
94 l.sort()
95 s = _sha(l[0])
95 s = hashlib.sha1(l[0])
96 s.update(l[1])
96 s.update(l[1])
97 s.update(text)
97 s.update(text)
98 return s.digest()
98 return s.digest()
@@ -941,8 +941,11 b' class revlog(object):'
941 return None
941 return None
942 except RevlogError:
942 except RevlogError:
943 # parsers.c radix tree lookup gave multiple matches
943 # parsers.c radix tree lookup gave multiple matches
944 # fast path: for unfiltered changelog, radix tree is accurate
945 if not getattr(self, 'filteredrevs', None):
946 raise LookupError(id, self.indexfile,
947 _('ambiguous identifier'))
944 # fall through to slow path that filters hidden revisions
948 # fall through to slow path that filters hidden revisions
945 pass
946 except (AttributeError, ValueError):
949 except (AttributeError, ValueError):
947 # we are pure python, or key was too short to search radix tree
950 # we are pure python, or key was too short to search radix tree
948 pass
951 pass
This diff has been collapsed as it changes many lines, (564 lines changed) Show them Hide them
@@ -302,6 +302,11 b' def tokenize(program, lookup=None, symin'
302
302
303 # helpers
303 # helpers
304
304
305 def getsymbol(x):
306 if x and x[0] == 'symbol':
307 return x[1]
308 raise error.ParseError(_('not a symbol'))
309
305 def getstring(x, err):
310 def getstring(x, err):
306 if x and (x[0] == 'string' or x[0] == 'symbol'):
311 if x and (x[0] == 'string' or x[0] == 'symbol'):
307 return x[1]
312 return x[1]
@@ -330,13 +335,12 b' def getset(repo, subset, x):'
330 s = methods[x[0]](repo, subset, *x[1:])
335 s = methods[x[0]](repo, subset, *x[1:])
331 if util.safehasattr(s, 'isascending'):
336 if util.safehasattr(s, 'isascending'):
332 return s
337 return s
333 if (repo.ui.configbool('devel', 'all-warnings')
338 # else case should not happen, because all non-func are internal,
334 or repo.ui.configbool('devel', 'old-revset')):
339 # ignoring for now.
335 # else case should not happen, because all non-func are internal,
340 if x[0] == 'func' and x[1][0] == 'symbol' and x[1][1] in symbols:
336 # ignoring for now.
341 repo.ui.deprecwarn('revset "%s" uses list instead of smartset'
337 if x[0] == 'func' and x[1][0] == 'symbol' and x[1][1] in symbols:
342 % x[1][1],
338 repo.ui.develwarn('revset "%s" use list instead of smartset, '
343 '3.9')
339 '(upgrade your code)' % x[1][1])
340 return baseset(s)
344 return baseset(s)
341
345
342 def _getrevsource(repo, r):
346 def _getrevsource(repo, r):
@@ -387,9 +391,7 b' def dagrange(repo, subset, x, y):'
387 r = fullreposet(repo)
391 r = fullreposet(repo)
388 xs = reachableroots(repo, getset(repo, r, x), getset(repo, r, y),
392 xs = reachableroots(repo, getset(repo, r, x), getset(repo, r, y),
389 includepath=True)
393 includepath=True)
390 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
394 return subset & xs
391 # necessary to ensure we preserve the order in subset.
392 return xs & subset
393
395
394 def andset(repo, subset, x, y):
396 def andset(repo, subset, x, y):
395 return getset(repo, getset(repo, subset, x), y)
397 return getset(repo, getset(repo, subset, x), y)
@@ -417,13 +419,14 b' def keyvaluepair(repo, subset, k, v):'
417 raise error.ParseError(_("can't use a key-value pair in this context"))
419 raise error.ParseError(_("can't use a key-value pair in this context"))
418
420
419 def func(repo, subset, a, b):
421 def func(repo, subset, a, b):
420 if a[0] == 'symbol' and a[1] in symbols:
422 f = getsymbol(a)
421 return symbols[a[1]](repo, subset, b)
423 if f in symbols:
424 return symbols[f](repo, subset, b)
422
425
423 keep = lambda fn: getattr(fn, '__doc__', None) is not None
426 keep = lambda fn: getattr(fn, '__doc__', None) is not None
424
427
425 syms = [s for (s, fn) in symbols.items() if keep(fn)]
428 syms = [s for (s, fn) in symbols.items() if keep(fn)]
426 raise error.UnknownIdentifier(a[1], syms)
429 raise error.UnknownIdentifier(f, syms)
427
430
428 # functions
431 # functions
429
432
@@ -695,20 +698,18 b' def checkstatus(repo, subset, pat, field'
695
698
696 return subset.filter(matches, condrepr=('<status[%r] %r>', field, pat))
699 return subset.filter(matches, condrepr=('<status[%r] %r>', field, pat))
697
700
698 def _children(repo, narrow, parentset):
701 def _children(repo, subset, parentset):
699 if not parentset:
702 if not parentset:
700 return baseset()
703 return baseset()
701 cs = set()
704 cs = set()
702 pr = repo.changelog.parentrevs
705 pr = repo.changelog.parentrevs
703 minrev = parentset.min()
706 minrev = parentset.min()
704 for r in narrow:
707 for r in subset:
705 if r <= minrev:
708 if r <= minrev:
706 continue
709 continue
707 for p in pr(r):
710 for p in pr(r):
708 if p in parentset:
711 if p in parentset:
709 cs.add(r)
712 cs.add(r)
710 # XXX using a set to feed the baseset is wrong. Sets are not ordered.
711 # This does not break because of other fullreposet misbehavior.
712 return baseset(cs)
713 return baseset(cs)
713
714
714 @predicate('children(set)', safe=True)
715 @predicate('children(set)', safe=True)
@@ -1150,13 +1151,9 b' def head(repo, subset, x):'
1150 getargs(x, 0, 0, _("head takes no arguments"))
1151 getargs(x, 0, 0, _("head takes no arguments"))
1151 hs = set()
1152 hs = set()
1152 cl = repo.changelog
1153 cl = repo.changelog
1153 for b, ls in repo.branchmap().iteritems():
1154 for ls in repo.branchmap().itervalues():
1154 hs.update(cl.rev(h) for h in ls)
1155 hs.update(cl.rev(h) for h in ls)
1155 # XXX using a set to feed the baseset is wrong. Sets are not ordered.
1156 return subset & baseset(hs)
1156 # This does not break because of other fullreposet misbehavior.
1157 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
1158 # necessary to ensure we preserve the order in subset.
1159 return baseset(hs) & subset
1160
1157
1161 @predicate('heads(set)', safe=True)
1158 @predicate('heads(set)', safe=True)
1162 def heads(repo, subset, x):
1159 def heads(repo, subset, x):
@@ -1837,7 +1834,54 b' def roots(repo, subset, x):'
1837 return True
1834 return True
1838 return subset & s.filter(filter, condrepr='<roots>')
1835 return subset & s.filter(filter, condrepr='<roots>')
1839
1836
1840 @predicate('sort(set[, [-]key...])', safe=True)
1837 _sortkeyfuncs = {
1838 'rev': lambda c: c.rev(),
1839 'branch': lambda c: c.branch(),
1840 'desc': lambda c: c.description(),
1841 'user': lambda c: c.user(),
1842 'author': lambda c: c.user(),
1843 'date': lambda c: c.date()[0],
1844 }
1845
1846 def _getsortargs(x):
1847 """Parse sort options into (set, [(key, reverse)], opts)"""
1848 args = getargsdict(x, 'sort', 'set keys topo.firstbranch')
1849 if 'set' not in args:
1850 # i18n: "sort" is a keyword
1851 raise error.ParseError(_('sort requires one or two arguments'))
1852 keys = "rev"
1853 if 'keys' in args:
1854 # i18n: "sort" is a keyword
1855 keys = getstring(args['keys'], _("sort spec must be a string"))
1856
1857 keyflags = []
1858 for k in keys.split():
1859 fk = k
1860 reverse = (k[0] == '-')
1861 if reverse:
1862 k = k[1:]
1863 if k not in _sortkeyfuncs and k != 'topo':
1864 raise error.ParseError(_("unknown sort key %r") % fk)
1865 keyflags.append((k, reverse))
1866
1867 if len(keyflags) > 1 and any(k == 'topo' for k, reverse in keyflags):
1868 # i18n: "topo" is a keyword
1869 raise error.ParseError(_(
1870 'topo sort order cannot be combined with other sort keys'))
1871
1872 opts = {}
1873 if 'topo.firstbranch' in args:
1874 if any(k == 'topo' for k, reverse in keyflags):
1875 opts['topo.firstbranch'] = args['topo.firstbranch']
1876 else:
1877 # i18n: "topo" and "topo.firstbranch" are keywords
1878 raise error.ParseError(_(
1879 'topo.firstbranch can only be used when using the topo sort '
1880 'key'))
1881
1882 return args['set'], keyflags, opts
1883
1884 @predicate('sort(set[, [-]key... [, ...]])', safe=True)
1841 def sort(repo, subset, x):
1885 def sort(repo, subset, x):
1842 """Sort set by keys. The default sort order is ascending, specify a key
1886 """Sort set by keys. The default sort order is ascending, specify a key
1843 as ``-key`` to sort in descending order.
1887 as ``-key`` to sort in descending order.
@@ -1849,50 +1893,235 b' def sort(repo, subset, x):'
1849 - ``desc`` for the commit message (description),
1893 - ``desc`` for the commit message (description),
1850 - ``user`` for user name (``author`` can be used as an alias),
1894 - ``user`` for user name (``author`` can be used as an alias),
1851 - ``date`` for the commit date
1895 - ``date`` for the commit date
1896 - ``topo`` for a reverse topographical sort
1897
1898 The ``topo`` sort order cannot be combined with other sort keys. This sort
1899 takes one optional argument, ``topo.firstbranch``, which takes a revset that
1900 specifies what topographical branches to prioritize in the sort.
1901
1852 """
1902 """
1853 # i18n: "sort" is a keyword
1903 s, keyflags, opts = _getsortargs(x)
1854 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1855 keys = "rev"
1856 if len(l) == 2:
1857 # i18n: "sort" is a keyword
1858 keys = getstring(l[1], _("sort spec must be a string"))
1859
1860 s = l[0]
1861 keys = keys.split()
1862 revs = getset(repo, subset, s)
1904 revs = getset(repo, subset, s)
1863 if keys == ["rev"]:
1905
1864 revs.sort()
1906 if not keyflags:
1907 return revs
1908 if len(keyflags) == 1 and keyflags[0][0] == "rev":
1909 revs.sort(reverse=keyflags[0][1])
1865 return revs
1910 return revs
1866 elif keys == ["-rev"]:
1911 elif keyflags[0][0] == "topo":
1867 revs.sort(reverse=True)
1912 firstbranch = ()
1913 if 'topo.firstbranch' in opts:
1914 firstbranch = getset(repo, subset, opts['topo.firstbranch'])
1915 revs = baseset(_toposort(revs, repo.changelog.parentrevs, firstbranch),
1916 istopo=True)
1917 if keyflags[0][1]:
1918 revs.reverse()
1868 return revs
1919 return revs
1920
1869 # sort() is guaranteed to be stable
1921 # sort() is guaranteed to be stable
1870 ctxs = [repo[r] for r in revs]
1922 ctxs = [repo[r] for r in revs]
1871 for k in reversed(keys):
1923 for k, reverse in reversed(keyflags):
1872 if k == 'rev':
1924 ctxs.sort(key=_sortkeyfuncs[k], reverse=reverse)
1873 ctxs.sort(key=lambda c: c.rev())
1874 elif k == '-rev':
1875 ctxs.sort(key=lambda c: c.rev(), reverse=True)
1876 elif k == 'branch':
1877 ctxs.sort(key=lambda c: c.branch())
1878 elif k == '-branch':
1879 ctxs.sort(key=lambda c: c.branch(), reverse=True)
1880 elif k == 'desc':
1881 ctxs.sort(key=lambda c: c.description())
1882 elif k == '-desc':
1883 ctxs.sort(key=lambda c: c.description(), reverse=True)
1884 elif k in 'user author':
1885 ctxs.sort(key=lambda c: c.user())
1886 elif k in '-user -author':
1887 ctxs.sort(key=lambda c: c.user(), reverse=True)
1888 elif k == 'date':
1889 ctxs.sort(key=lambda c: c.date()[0])
1890 elif k == '-date':
1891 ctxs.sort(key=lambda c: c.date()[0], reverse=True)
1892 else:
1893 raise error.ParseError(_("unknown sort key %r") % k)
1894 return baseset([c.rev() for c in ctxs])
1925 return baseset([c.rev() for c in ctxs])
1895
1926
1927 def _toposort(revs, parentsfunc, firstbranch=()):
1928 """Yield revisions from heads to roots one (topo) branch at a time.
1929
1930 This function aims to be used by a graph generator that wishes to minimize
1931 the number of parallel branches and their interleaving.
1932
1933 Example iteration order (numbers show the "true" order in a changelog):
1934
1935 o 4
1936 |
1937 o 1
1938 |
1939 | o 3
1940 | |
1941 | o 2
1942 |/
1943 o 0
1944
1945 Note that the ancestors of merges are understood by the current
1946 algorithm to be on the same branch. This means no reordering will
1947 occur behind a merge.
1948 """
1949
1950 ### Quick summary of the algorithm
1951 #
1952 # This function is based around a "retention" principle. We keep revisions
1953 # in memory until we are ready to emit a whole branch that immediately
1954 # "merges" into an existing one. This reduces the number of parallel
1955 # branches with interleaved revisions.
1956 #
1957 # During iteration revs are split into two groups:
1958 # A) revision already emitted
1959 # B) revision in "retention". They are stored as different subgroups.
1960 #
1961 # for each REV, we do the following logic:
1962 #
1963 # 1) if REV is a parent of (A), we will emit it. If there is a
1964 # retention group ((B) above) that is blocked on REV being
1965 # available, we emit all the revisions out of that retention
1966 # group first.
1967 #
1968 # 2) else, we'll search for a subgroup in (B) awaiting for REV to be
1969 # available, if such subgroup exist, we add REV to it and the subgroup is
1970 # now awaiting for REV.parents() to be available.
1971 #
1972 # 3) finally if no such group existed in (B), we create a new subgroup.
1973 #
1974 #
1975 # To bootstrap the algorithm, we emit the tipmost revision (which
1976 # puts it in group (A) from above).
1977
1978 revs.sort(reverse=True)
1979
1980 # Set of parents of revision that have been emitted. They can be considered
1981 # unblocked as the graph generator is already aware of them so there is no
1982 # need to delay the revisions that reference them.
1983 #
1984 # If someone wants to prioritize a branch over the others, pre-filling this
1985 # set will force all other branches to wait until this branch is ready to be
1986 # emitted.
1987 unblocked = set(firstbranch)
1988
1989 # list of groups waiting to be displayed, each group is defined by:
1990 #
1991 # (revs: lists of revs waiting to be displayed,
1992 # blocked: set of that cannot be displayed before those in 'revs')
1993 #
1994 # The second value ('blocked') correspond to parents of any revision in the
1995 # group ('revs') that is not itself contained in the group. The main idea
1996 # of this algorithm is to delay as much as possible the emission of any
1997 # revision. This means waiting for the moment we are about to display
1998 # these parents to display the revs in a group.
1999 #
2000 # This first implementation is smart until it encounters a merge: it will
2001 # emit revs as soon as any parent is about to be emitted and can grow an
2002 # arbitrary number of revs in 'blocked'. In practice this mean we properly
2003 # retains new branches but gives up on any special ordering for ancestors
2004 # of merges. The implementation can be improved to handle this better.
2005 #
2006 # The first subgroup is special. It corresponds to all the revision that
2007 # were already emitted. The 'revs' lists is expected to be empty and the
2008 # 'blocked' set contains the parents revisions of already emitted revision.
2009 #
2010 # You could pre-seed the <parents> set of groups[0] to a specific
2011 # changesets to select what the first emitted branch should be.
2012 groups = [([], unblocked)]
2013 pendingheap = []
2014 pendingset = set()
2015
2016 heapq.heapify(pendingheap)
2017 heappop = heapq.heappop
2018 heappush = heapq.heappush
2019 for currentrev in revs:
2020 # Heap works with smallest element, we want highest so we invert
2021 if currentrev not in pendingset:
2022 heappush(pendingheap, -currentrev)
2023 pendingset.add(currentrev)
2024 # iterates on pending rev until after the current rev have been
2025 # processed.
2026 rev = None
2027 while rev != currentrev:
2028 rev = -heappop(pendingheap)
2029 pendingset.remove(rev)
2030
2031 # Seek for a subgroup blocked, waiting for the current revision.
2032 matching = [i for i, g in enumerate(groups) if rev in g[1]]
2033
2034 if matching:
2035 # The main idea is to gather together all sets that are blocked
2036 # on the same revision.
2037 #
2038 # Groups are merged when a common blocking ancestor is
2039 # observed. For example, given two groups:
2040 #
2041 # revs [5, 4] waiting for 1
2042 # revs [3, 2] waiting for 1
2043 #
2044 # These two groups will be merged when we process
2045 # 1. In theory, we could have merged the groups when
2046 # we added 2 to the group it is now in (we could have
2047 # noticed the groups were both blocked on 1 then), but
2048 # the way it works now makes the algorithm simpler.
2049 #
2050 # We also always keep the oldest subgroup first. We can
2051 # probably improve the behavior by having the longest set
2052 # first. That way, graph algorithms could minimise the length
2053 # of parallel lines their drawing. This is currently not done.
2054 targetidx = matching.pop(0)
2055 trevs, tparents = groups[targetidx]
2056 for i in matching:
2057 gr = groups[i]
2058 trevs.extend(gr[0])
2059 tparents |= gr[1]
2060 # delete all merged subgroups (except the one we kept)
2061 # (starting from the last subgroup for performance and
2062 # sanity reasons)
2063 for i in reversed(matching):
2064 del groups[i]
2065 else:
2066 # This is a new head. We create a new subgroup for it.
2067 targetidx = len(groups)
2068 groups.append(([], set([rev])))
2069
2070 gr = groups[targetidx]
2071
2072 # We now add the current nodes to this subgroups. This is done
2073 # after the subgroup merging because all elements from a subgroup
2074 # that relied on this rev must precede it.
2075 #
2076 # we also update the <parents> set to include the parents of the
2077 # new nodes.
2078 if rev == currentrev: # only display stuff in rev
2079 gr[0].append(rev)
2080 gr[1].remove(rev)
2081 parents = [p for p in parentsfunc(rev) if p > node.nullrev]
2082 gr[1].update(parents)
2083 for p in parents:
2084 if p not in pendingset:
2085 pendingset.add(p)
2086 heappush(pendingheap, -p)
2087
2088 # Look for a subgroup to display
2089 #
2090 # When unblocked is empty (if clause), we were not waiting for any
2091 # revisions during the first iteration (if no priority was given) or
2092 # if we emitted a whole disconnected set of the graph (reached a
2093 # root). In that case we arbitrarily take the oldest known
2094 # subgroup. The heuristic could probably be better.
2095 #
2096 # Otherwise (elif clause) if the subgroup is blocked on
2097 # a revision we just emitted, we can safely emit it as
2098 # well.
2099 if not unblocked:
2100 if len(groups) > 1: # display other subset
2101 targetidx = 1
2102 gr = groups[1]
2103 elif not gr[1] & unblocked:
2104 gr = None
2105
2106 if gr is not None:
2107 # update the set of awaited revisions with the one from the
2108 # subgroup
2109 unblocked |= gr[1]
2110 # output all revisions in the subgroup
2111 for r in gr[0]:
2112 yield r
2113 # delete the subgroup that you just output
2114 # unless it is groups[0] in which case you just empty it.
2115 if targetidx:
2116 del groups[targetidx]
2117 else:
2118 gr[0][:] = []
2119 # Check if we have some subgroup waiting for revisions we are not going to
2120 # iterate over
2121 for g in groups:
2122 for r in g[0]:
2123 yield r
2124
1896 @predicate('subrepo([pattern])')
2125 @predicate('subrepo([pattern])')
1897 def subrepo(repo, subset, x):
2126 def subrepo(repo, subset, x):
1898 """Changesets that add, modify or remove the given subrepo. If no subrepo
2127 """Changesets that add, modify or remove the given subrepo. If no subrepo
@@ -2073,7 +2302,22 b' methods = {'
2073 "parentpost": p1,
2302 "parentpost": p1,
2074 }
2303 }
2075
2304
2076 def optimize(x, small):
2305 def _matchonly(revs, bases):
2306 """
2307 >>> f = lambda *args: _matchonly(*map(parse, args))
2308 >>> f('ancestors(A)', 'not ancestors(B)')
2309 ('list', ('symbol', 'A'), ('symbol', 'B'))
2310 """
2311 if (revs is not None
2312 and revs[0] == 'func'
2313 and getsymbol(revs[1]) == 'ancestors'
2314 and bases is not None
2315 and bases[0] == 'not'
2316 and bases[1][0] == 'func'
2317 and getsymbol(bases[1][1]) == 'ancestors'):
2318 return ('list', revs[2], bases[1][2])
2319
2320 def _optimize(x, small):
2077 if x is None:
2321 if x is None:
2078 return 0, x
2322 return 0, x
2079
2323
@@ -2083,47 +2327,36 b' def optimize(x, small):'
2083
2327
2084 op = x[0]
2328 op = x[0]
2085 if op == 'minus':
2329 if op == 'minus':
2086 return optimize(('and', x[1], ('not', x[2])), small)
2330 return _optimize(('and', x[1], ('not', x[2])), small)
2087 elif op == 'only':
2331 elif op == 'only':
2088 return optimize(('func', ('symbol', 'only'),
2332 t = ('func', ('symbol', 'only'), ('list', x[1], x[2]))
2089 ('list', x[1], x[2])), small)
2333 return _optimize(t, small)
2090 elif op == 'onlypost':
2334 elif op == 'onlypost':
2091 return optimize(('func', ('symbol', 'only'), x[1]), small)
2335 return _optimize(('func', ('symbol', 'only'), x[1]), small)
2092 elif op == 'dagrangepre':
2336 elif op == 'dagrangepre':
2093 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
2337 return _optimize(('func', ('symbol', 'ancestors'), x[1]), small)
2094 elif op == 'dagrangepost':
2338 elif op == 'dagrangepost':
2095 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
2339 return _optimize(('func', ('symbol', 'descendants'), x[1]), small)
2096 elif op == 'rangeall':
2340 elif op == 'rangeall':
2097 return optimize(('range', ('string', '0'), ('string', 'tip')), small)
2341 return _optimize(('range', ('string', '0'), ('string', 'tip')), small)
2098 elif op == 'rangepre':
2342 elif op == 'rangepre':
2099 return optimize(('range', ('string', '0'), x[1]), small)
2343 return _optimize(('range', ('string', '0'), x[1]), small)
2100 elif op == 'rangepost':
2344 elif op == 'rangepost':
2101 return optimize(('range', x[1], ('string', 'tip')), small)
2345 return _optimize(('range', x[1], ('string', 'tip')), small)
2102 elif op == 'negate':
2346 elif op == 'negate':
2103 return optimize(('string',
2347 s = getstring(x[1], _("can't negate that"))
2104 '-' + getstring(x[1], _("can't negate that"))), small)
2348 return _optimize(('string', '-' + s), small)
2105 elif op in 'string symbol negate':
2349 elif op in 'string symbol negate':
2106 return smallbonus, x # single revisions are small
2350 return smallbonus, x # single revisions are small
2107 elif op == 'and':
2351 elif op == 'and':
2108 wa, ta = optimize(x[1], True)
2352 wa, ta = _optimize(x[1], True)
2109 wb, tb = optimize(x[2], True)
2353 wb, tb = _optimize(x[2], True)
2354 w = min(wa, wb)
2110
2355
2111 # (::x and not ::y)/(not ::y and ::x) have a fast path
2356 # (::x and not ::y)/(not ::y and ::x) have a fast path
2112 def isonly(revs, bases):
2357 tm = _matchonly(ta, tb) or _matchonly(tb, ta)
2113 return (
2358 if tm:
2114 revs is not None
2359 return w, ('func', ('symbol', 'only'), tm)
2115 and revs[0] == 'func'
2116 and getstring(revs[1], _('not a symbol')) == 'ancestors'
2117 and bases is not None
2118 and bases[0] == 'not'
2119 and bases[1][0] == 'func'
2120 and getstring(bases[1][1], _('not a symbol')) == 'ancestors')
2121
2122 w = min(wa, wb)
2123 if isonly(ta, tb):
2124 return w, ('func', ('symbol', 'only'), ('list', ta[2], tb[1][2]))
2125 if isonly(tb, ta):
2126 return w, ('func', ('symbol', 'only'), ('list', tb[2], ta[1][2]))
2127
2360
2128 if tb is not None and tb[0] == 'not':
2361 if tb is not None and tb[0] == 'not':
2129 return wa, ('difference', ta, tb[1])
2362 return wa, ('difference', ta, tb[1])
@@ -2143,12 +2376,12 b' def optimize(x, small):'
2143 else:
2376 else:
2144 s = '\0'.join(t[1] for w, t in ss)
2377 s = '\0'.join(t[1] for w, t in ss)
2145 y = ('func', ('symbol', '_list'), ('string', s))
2378 y = ('func', ('symbol', '_list'), ('string', s))
2146 w, t = optimize(y, False)
2379 w, t = _optimize(y, False)
2147 ws.append(w)
2380 ws.append(w)
2148 ts.append(t)
2381 ts.append(t)
2149 del ss[:]
2382 del ss[:]
2150 for y in x[1:]:
2383 for y in x[1:]:
2151 w, t = optimize(y, False)
2384 w, t = _optimize(y, False)
2152 if t is not None and (t[0] == 'string' or t[0] == 'symbol'):
2385 if t is not None and (t[0] == 'string' or t[0] == 'symbol'):
2153 ss.append((w, t))
2386 ss.append((w, t))
2154 continue
2387 continue
@@ -2166,34 +2399,34 b' def optimize(x, small):'
2166 # Optimize not public() to _notpublic() because we have a fast version
2399 # Optimize not public() to _notpublic() because we have a fast version
2167 if x[1] == ('func', ('symbol', 'public'), None):
2400 if x[1] == ('func', ('symbol', 'public'), None):
2168 newsym = ('func', ('symbol', '_notpublic'), None)
2401 newsym = ('func', ('symbol', '_notpublic'), None)
2169 o = optimize(newsym, not small)
2402 o = _optimize(newsym, not small)
2170 return o[0], o[1]
2403 return o[0], o[1]
2171 else:
2404 else:
2172 o = optimize(x[1], not small)
2405 o = _optimize(x[1], not small)
2173 return o[0], (op, o[1])
2406 return o[0], (op, o[1])
2174 elif op == 'parentpost':
2407 elif op == 'parentpost':
2175 o = optimize(x[1], small)
2408 o = _optimize(x[1], small)
2176 return o[0], (op, o[1])
2409 return o[0], (op, o[1])
2177 elif op == 'group':
2410 elif op == 'group':
2178 return optimize(x[1], small)
2411 return _optimize(x[1], small)
2179 elif op in 'dagrange range parent ancestorspec':
2412 elif op in 'dagrange range parent ancestorspec':
2180 if op == 'parent':
2413 if op == 'parent':
2181 # x^:y means (x^) : y, not x ^ (:y)
2414 # x^:y means (x^) : y, not x ^ (:y)
2182 post = ('parentpost', x[1])
2415 post = ('parentpost', x[1])
2183 if x[2][0] == 'dagrangepre':
2416 if x[2][0] == 'dagrangepre':
2184 return optimize(('dagrange', post, x[2][1]), small)
2417 return _optimize(('dagrange', post, x[2][1]), small)
2185 elif x[2][0] == 'rangepre':
2418 elif x[2][0] == 'rangepre':
2186 return optimize(('range', post, x[2][1]), small)
2419 return _optimize(('range', post, x[2][1]), small)
2187
2420
2188 wa, ta = optimize(x[1], small)
2421 wa, ta = _optimize(x[1], small)
2189 wb, tb = optimize(x[2], small)
2422 wb, tb = _optimize(x[2], small)
2190 return wa + wb, (op, ta, tb)
2423 return wa + wb, (op, ta, tb)
2191 elif op == 'list':
2424 elif op == 'list':
2192 ws, ts = zip(*(optimize(y, small) for y in x[1:]))
2425 ws, ts = zip(*(_optimize(y, small) for y in x[1:]))
2193 return sum(ws), (op,) + ts
2426 return sum(ws), (op,) + ts
2194 elif op == 'func':
2427 elif op == 'func':
2195 f = getstring(x[1], _("not a symbol"))
2428 f = getsymbol(x[1])
2196 wa, ta = optimize(x[2], small)
2429 wa, ta = _optimize(x[2], small)
2197 if f in ("author branch closed date desc file grep keyword "
2430 if f in ("author branch closed date desc file grep keyword "
2198 "outgoing user"):
2431 "outgoing user"):
2199 w = 10 # slow
2432 w = 10 # slow
@@ -2212,33 +2445,32 b' def optimize(x, small):'
2212 return w + wa, (op, x[1], ta)
2445 return w + wa, (op, x[1], ta)
2213 return 1, x
2446 return 1, x
2214
2447
2448 def optimize(tree):
2449 _weight, newtree = _optimize(tree, small=True)
2450 return newtree
2451
2215 # the set of valid characters for the initial letter of symbols in
2452 # the set of valid characters for the initial letter of symbols in
2216 # alias declarations and definitions
2453 # alias declarations and definitions
2217 _aliassyminitletters = set(c for c in [chr(i) for i in xrange(256)]
2454 _aliassyminitletters = set(c for c in [chr(i) for i in xrange(256)]
2218 if c.isalnum() or c in '._@$' or ord(c) > 127)
2455 if c.isalnum() or c in '._@$' or ord(c) > 127)
2219
2456
2220 def _tokenizealias(program, lookup=None):
2457 def _parsewith(spec, lookup=None, syminitletters=None):
2221 """Parse alias declaration/definition into a stream of tokens
2458 """Generate a parse tree of given spec with given tokenizing options
2222
2459
2223 This allows symbol names to use also ``$`` as an initial letter
2460 >>> _parsewith('foo($1)', syminitletters=_aliassyminitletters)
2224 (for backward compatibility), and callers of this function should
2225 examine whether ``$`` is used also for unexpected symbols or not.
2226 """
2227 return tokenize(program, lookup=lookup,
2228 syminitletters=_aliassyminitletters)
2229
2230 def _parsealias(spec):
2231 """Parse alias declaration/definition ``spec``
2232
2233 >>> _parsealias('foo($1)')
2234 ('func', ('symbol', 'foo'), ('symbol', '$1'))
2461 ('func', ('symbol', 'foo'), ('symbol', '$1'))
2235 >>> _parsealias('foo bar')
2462 >>> _parsewith('$1')
2463 Traceback (most recent call last):
2464 ...
2465 ParseError: ("syntax error in revset '$1'", 0)
2466 >>> _parsewith('foo bar')
2236 Traceback (most recent call last):
2467 Traceback (most recent call last):
2237 ...
2468 ...
2238 ParseError: ('invalid token', 4)
2469 ParseError: ('invalid token', 4)
2239 """
2470 """
2240 p = parser.parser(elements)
2471 p = parser.parser(elements)
2241 tree, pos = p.parse(_tokenizealias(spec))
2472 tree, pos = p.parse(tokenize(spec, lookup=lookup,
2473 syminitletters=syminitletters))
2242 if pos != len(spec):
2474 if pos != len(spec):
2243 raise error.ParseError(_('invalid token'), pos)
2475 raise error.ParseError(_('invalid token'), pos)
2244 return parser.simplifyinfixops(tree, ('list', 'or'))
2476 return parser.simplifyinfixops(tree, ('list', 'or'))
@@ -2246,7 +2478,16 b' def _parsealias(spec):'
2246 class _aliasrules(parser.basealiasrules):
2478 class _aliasrules(parser.basealiasrules):
2247 """Parsing and expansion rule set of revset aliases"""
2479 """Parsing and expansion rule set of revset aliases"""
2248 _section = _('revset alias')
2480 _section = _('revset alias')
2249 _parse = staticmethod(_parsealias)
2481
2482 @staticmethod
2483 def _parse(spec):
2484 """Parse alias declaration/definition ``spec``
2485
2486 This allows symbol names to use also ``$`` as an initial letter
2487 (for backward compatibility), and callers of this function should
2488 examine whether ``$`` is used also for unexpected symbols or not.
2489 """
2490 return _parsewith(spec, syminitletters=_aliassyminitletters)
2250
2491
2251 @staticmethod
2492 @staticmethod
2252 def _trygetfunc(tree):
2493 def _trygetfunc(tree):
@@ -2286,24 +2527,15 b' def foldconcat(tree):'
2286 return tuple(foldconcat(t) for t in tree)
2527 return tuple(foldconcat(t) for t in tree)
2287
2528
2288 def parse(spec, lookup=None):
2529 def parse(spec, lookup=None):
2289 p = parser.parser(elements)
2530 return _parsewith(spec, lookup=lookup)
2290 tree, pos = p.parse(tokenize(spec, lookup=lookup))
2291 if pos != len(spec):
2292 raise error.ParseError(_("invalid token"), pos)
2293 return parser.simplifyinfixops(tree, ('list', 'or'))
2294
2531
2295 def posttreebuilthook(tree, repo):
2532 def posttreebuilthook(tree, repo):
2296 # hook for extensions to execute code on the optimized tree
2533 # hook for extensions to execute code on the optimized tree
2297 pass
2534 pass
2298
2535
2299 def match(ui, spec, repo=None):
2536 def match(ui, spec, repo=None):
2300 if not spec:
2537 """Create a matcher for a single revision spec."""
2301 raise error.ParseError(_("empty query"))
2538 return matchany(ui, [spec], repo=repo)
2302 lookup = None
2303 if repo:
2304 lookup = repo.__contains__
2305 tree = parse(spec, lookup)
2306 return _makematcher(ui, tree, repo)
2307
2539
2308 def matchany(ui, specs, repo=None):
2540 def matchany(ui, specs, repo=None):
2309 """Create a matcher that will include any revisions matching one of the
2541 """Create a matcher that will include any revisions matching one of the
@@ -2327,7 +2559,7 b' def _makematcher(ui, tree, repo):'
2327 if ui:
2559 if ui:
2328 tree = expandaliases(ui, tree, showwarning=ui.warn)
2560 tree = expandaliases(ui, tree, showwarning=ui.warn)
2329 tree = foldconcat(tree)
2561 tree = foldconcat(tree)
2330 weight, tree = optimize(tree, True)
2562 tree = optimize(tree)
2331 posttreebuilthook(tree, repo)
2563 posttreebuilthook(tree, repo)
2332 def mfunc(repo, subset=None):
2564 def mfunc(repo, subset=None):
2333 if subset is None:
2565 if subset is None:
@@ -2426,7 +2658,8 b' def formatspec(expr, *args):'
2426 ret += listexp(list(args[arg]), d)
2658 ret += listexp(list(args[arg]), d)
2427 arg += 1
2659 arg += 1
2428 else:
2660 else:
2429 raise error.Abort('unexpected revspec format character %s' % d)
2661 raise error.Abort(_('unexpected revspec format character %s')
2662 % d)
2430 else:
2663 else:
2431 ret += c
2664 ret += c
2432 pos += 1
2665 pos += 1
@@ -2506,6 +2739,10 b' class abstractsmartset(object):'
2506 """True if the set will iterate in descending order"""
2739 """True if the set will iterate in descending order"""
2507 raise NotImplementedError()
2740 raise NotImplementedError()
2508
2741
2742 def istopo(self):
2743 """True if the set will iterate in topographical order"""
2744 raise NotImplementedError()
2745
2509 @util.cachefunc
2746 @util.cachefunc
2510 def min(self):
2747 def min(self):
2511 """return the minimum element in the set"""
2748 """return the minimum element in the set"""
@@ -2591,12 +2828,13 b' class baseset(abstractsmartset):'
2591
2828
2592 Every method in this class should be implemented by any smartset class.
2829 Every method in this class should be implemented by any smartset class.
2593 """
2830 """
2594 def __init__(self, data=(), datarepr=None):
2831 def __init__(self, data=(), datarepr=None, istopo=False):
2595 """
2832 """
2596 datarepr: a tuple of (format, obj, ...), a function or an object that
2833 datarepr: a tuple of (format, obj, ...), a function or an object that
2597 provides a printable representation of the given data.
2834 provides a printable representation of the given data.
2598 """
2835 """
2599 self._ascending = None
2836 self._ascending = None
2837 self._istopo = istopo
2600 if not isinstance(data, list):
2838 if not isinstance(data, list):
2601 if isinstance(data, set):
2839 if isinstance(data, set):
2602 self._set = data
2840 self._set = data
@@ -2639,12 +2877,14 b' class baseset(abstractsmartset):'
2639
2877
2640 def sort(self, reverse=False):
2878 def sort(self, reverse=False):
2641 self._ascending = not bool(reverse)
2879 self._ascending = not bool(reverse)
2880 self._istopo = False
2642
2881
2643 def reverse(self):
2882 def reverse(self):
2644 if self._ascending is None:
2883 if self._ascending is None:
2645 self._list.reverse()
2884 self._list.reverse()
2646 else:
2885 else:
2647 self._ascending = not self._ascending
2886 self._ascending = not self._ascending
2887 self._istopo = False
2648
2888
2649 def __len__(self):
2889 def __len__(self):
2650 return len(self._list)
2890 return len(self._list)
@@ -2665,6 +2905,14 b' class baseset(abstractsmartset):'
2665 return True
2905 return True
2666 return self._ascending is not None and not self._ascending
2906 return self._ascending is not None and not self._ascending
2667
2907
2908 def istopo(self):
2909 """Is the collection is in topographical order or not.
2910
2911 This is part of the mandatory API for smartset."""
2912 if len(self) <= 1:
2913 return True
2914 return self._istopo
2915
2668 def first(self):
2916 def first(self):
2669 if self:
2917 if self:
2670 if self._ascending is None:
2918 if self._ascending is None:
@@ -2741,9 +2989,16 b' class filteredset(abstractsmartset):'
2741 return lambda: self._iterfilter(it())
2989 return lambda: self._iterfilter(it())
2742
2990
2743 def __nonzero__(self):
2991 def __nonzero__(self):
2744 fast = self.fastasc
2992 fast = None
2745 if fast is None:
2993 candidates = [self.fastasc if self.isascending() else None,
2746 fast = self.fastdesc
2994 self.fastdesc if self.isdescending() else None,
2995 self.fastasc,
2996 self.fastdesc]
2997 for candidate in candidates:
2998 if candidate is not None:
2999 fast = candidate
3000 break
3001
2747 if fast is not None:
3002 if fast is not None:
2748 it = fast()
3003 it = fast()
2749 else:
3004 else:
@@ -2773,6 +3028,9 b' class filteredset(abstractsmartset):'
2773 def isdescending(self):
3028 def isdescending(self):
2774 return self._subset.isdescending()
3029 return self._subset.isdescending()
2775
3030
3031 def istopo(self):
3032 return self._subset.istopo()
3033
2776 def first(self):
3034 def first(self):
2777 for x in self:
3035 for x in self:
2778 return x
3036 return x
@@ -2816,14 +3074,14 b' def _iterordered(ascending, iter1, iter2'
2816 # Consume both iterators in an ordered way until one is empty
3074 # Consume both iterators in an ordered way until one is empty
2817 while True:
3075 while True:
2818 if val1 is None:
3076 if val1 is None:
2819 val1 = iter1.next()
3077 val1 = next(iter1)
2820 if val2 is None:
3078 if val2 is None:
2821 val2 = iter2.next()
3079 val2 = next(iter2)
2822 next = choice(val1, val2)
3080 n = choice(val1, val2)
2823 yield next
3081 yield n
2824 if val1 == next:
3082 if val1 == n:
2825 val1 = None
3083 val1 = None
2826 if val2 == next:
3084 if val2 == n:
2827 val2 = None
3085 val2 = None
2828 except StopIteration:
3086 except StopIteration:
2829 # Flush any remaining values and consume the other one
3087 # Flush any remaining values and consume the other one
@@ -3019,6 +3277,12 b' class addset(abstractsmartset):'
3019 def isdescending(self):
3277 def isdescending(self):
3020 return self._ascending is not None and not self._ascending
3278 return self._ascending is not None and not self._ascending
3021
3279
3280 def istopo(self):
3281 # not worth the trouble asserting if the two sets combined are still
3282 # in topographical order. Use the sort() predicate to explicitly sort
3283 # again instead.
3284 return False
3285
3022 def reverse(self):
3286 def reverse(self):
3023 if self._ascending is None:
3287 if self._ascending is None:
3024 self._list.reverse()
3288 self._list.reverse()
@@ -3186,6 +3450,12 b' class generatorset(abstractsmartset):'
3186 def isdescending(self):
3450 def isdescending(self):
3187 return not self._ascending
3451 return not self._ascending
3188
3452
3453 def istopo(self):
3454 # not worth the trouble asserting if the two sets combined are still
3455 # in topographical order. Use the sort() predicate to explicitly sort
3456 # again instead.
3457 return False
3458
3189 def first(self):
3459 def first(self):
3190 if self._ascending:
3460 if self._ascending:
3191 it = self.fastasc
3461 it = self.fastasc
@@ -3248,6 +3518,12 b' class spanset(abstractsmartset):'
3248 def reverse(self):
3518 def reverse(self):
3249 self._ascending = not self._ascending
3519 self._ascending = not self._ascending
3250
3520
3521 def istopo(self):
3522 # not worth the trouble asserting if the two sets combined are still
3523 # in topographical order. Use the sort() predicate to explicitly sort
3524 # again instead.
3525 return False
3526
3251 def _iterfilter(self, iterrange):
3527 def _iterfilter(self, iterrange):
3252 s = self._hiddenrevs
3528 s = self._hiddenrevs
3253 for r in iterrange:
3529 for r in iterrange:
@@ -10,6 +10,7 b' from __future__ import absolute_import'
10 import contextlib
10 import contextlib
11 import errno
11 import errno
12 import glob
12 import glob
13 import hashlib
13 import os
14 import os
14 import re
15 import re
15 import shutil
16 import shutil
@@ -224,7 +225,7 b' def filteredhash(repo, maxrev):'
224 key = None
225 key = None
225 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
226 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
226 if revs:
227 if revs:
227 s = util.sha1()
228 s = hashlib.sha1()
228 for rev in revs:
229 for rev in revs:
229 s.update('%s;' % rev)
230 s.update('%s;' % rev)
230 key = s.digest()
231 key = s.digest()
@@ -377,8 +378,24 b' class abstractvfs(object):'
377 def readlock(self, path):
378 def readlock(self, path):
378 return util.readlock(self.join(path))
379 return util.readlock(self.join(path))
379
380
380 def rename(self, src, dst):
381 def rename(self, src, dst, checkambig=False):
381 return util.rename(self.join(src), self.join(dst))
382 """Rename from src to dst
383
384 checkambig argument is used with util.filestat, and is useful
385 only if destination file is guarded by any lock
386 (e.g. repo.lock or repo.wlock).
387 """
388 dstpath = self.join(dst)
389 oldstat = checkambig and util.filestat(dstpath)
390 if oldstat and oldstat.stat:
391 ret = util.rename(self.join(src), dstpath)
392 newstat = util.filestat(dstpath)
393 if newstat.isambig(oldstat):
394 # stat of renamed file is ambiguous to original one
395 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
396 os.utime(dstpath, (advanced, advanced))
397 return ret
398 return util.rename(self.join(src), dstpath)
382
399
383 def readlink(self, path):
400 def readlink(self, path):
384 return os.readlink(self.join(path))
401 return os.readlink(self.join(path))
@@ -451,7 +468,8 b' class abstractvfs(object):'
451 # have a use case.
468 # have a use case.
452 vfs = getattr(self, 'vfs', self)
469 vfs = getattr(self, 'vfs', self)
453 if getattr(vfs, '_backgroundfilecloser', None):
470 if getattr(vfs, '_backgroundfilecloser', None):
454 raise error.Abort('can only have 1 active background file closer')
471 raise error.Abort(
472 _('can only have 1 active background file closer'))
455
473
456 with backgroundfilecloser(ui, expectedcount=expectedcount) as bfc:
474 with backgroundfilecloser(ui, expectedcount=expectedcount) as bfc:
457 try:
475 try:
@@ -502,7 +520,7 b' class vfs(abstractvfs):'
502 os.chmod(name, self.createmode & 0o666)
520 os.chmod(name, self.createmode & 0o666)
503
521
504 def __call__(self, path, mode="r", text=False, atomictemp=False,
522 def __call__(self, path, mode="r", text=False, atomictemp=False,
505 notindexed=False, backgroundclose=False):
523 notindexed=False, backgroundclose=False, checkambig=False):
506 '''Open ``path`` file, which is relative to vfs root.
524 '''Open ``path`` file, which is relative to vfs root.
507
525
508 Newly created directories are marked as "not to be indexed by
526 Newly created directories are marked as "not to be indexed by
@@ -521,6 +539,10 b' class vfs(abstractvfs):'
521 closing a file on a background thread and reopening it. (If the
539 closing a file on a background thread and reopening it. (If the
522 file were opened multiple times, there could be unflushed data
540 file were opened multiple times, there could be unflushed data
523 because the original file handle hasn't been flushed/closed yet.)
541 because the original file handle hasn't been flushed/closed yet.)
542
543 ``checkambig`` argument is passed to atomictemplfile (valid
544 only for writing), and is useful only if target file is
545 guarded by any lock (e.g. repo.lock or repo.wlock).
524 '''
546 '''
525 if self._audit:
547 if self._audit:
526 r = util.checkosfilename(path)
548 r = util.checkosfilename(path)
@@ -540,7 +562,8 b' class vfs(abstractvfs):'
540 if basename:
562 if basename:
541 if atomictemp:
563 if atomictemp:
542 util.makedirs(dirname, self.createmode, notindexed)
564 util.makedirs(dirname, self.createmode, notindexed)
543 return util.atomictempfile(f, mode, self.createmode)
565 return util.atomictempfile(f, mode, self.createmode,
566 checkambig=checkambig)
544 try:
567 try:
545 if 'w' in mode:
568 if 'w' in mode:
546 util.unlink(f)
569 util.unlink(f)
@@ -568,8 +591,9 b' class vfs(abstractvfs):'
568
591
569 if backgroundclose:
592 if backgroundclose:
570 if not self._backgroundfilecloser:
593 if not self._backgroundfilecloser:
571 raise error.Abort('backgroundclose can only be used when a '
594 raise error.Abort(_('backgroundclose can only be used when a '
572 'backgroundclosing context manager is active')
595 'backgroundclosing context manager is active')
596 )
573
597
574 fp = delayclosedfile(fp, self._backgroundfilecloser)
598 fp = delayclosedfile(fp, self._backgroundfilecloser)
575
599
@@ -640,7 +664,7 b' class readonlyvfs(abstractvfs, auditvfs)'
640
664
641 def __call__(self, path, mode='r', *args, **kw):
665 def __call__(self, path, mode='r', *args, **kw):
642 if mode not in ('r', 'rb'):
666 if mode not in ('r', 'rb'):
643 raise error.Abort('this vfs is read only')
667 raise error.Abort(_('this vfs is read only'))
644 return self.vfs(path, mode, *args, **kw)
668 return self.vfs(path, mode, *args, **kw)
645
669
646 def join(self, path, *insidef):
670 def join(self, path, *insidef):
@@ -751,7 +775,7 b" def revsingle(repo, revspec, default='.'"
751
775
752 def _pairspec(revspec):
776 def _pairspec(revspec):
753 tree = revset.parse(revspec)
777 tree = revset.parse(revspec)
754 tree = revset.optimize(tree, True)[1] # fix up "x^:y" -> "(x^):y"
778 tree = revset.optimize(tree) # fix up "x^:y" -> "(x^):y"
755 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
779 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
756
780
757 def revpair(repo, revs):
781 def revpair(repo, revs):
@@ -784,10 +808,29 b' def revpair(repo, revs):'
784
808
785 return repo.lookup(first), repo.lookup(second)
809 return repo.lookup(first), repo.lookup(second)
786
810
787 def revrange(repo, revs):
811 def revrange(repo, specs):
788 """Yield revision as strings from a list of revision specifications."""
812 """Execute 1 to many revsets and return the union.
813
814 This is the preferred mechanism for executing revsets using user-specified
815 config options, such as revset aliases.
816
817 The revsets specified by ``specs`` will be executed via a chained ``OR``
818 expression. If ``specs`` is empty, an empty result is returned.
819
820 ``specs`` can contain integers, in which case they are assumed to be
821 revision numbers.
822
823 It is assumed the revsets are already formatted. If you have arguments
824 that need to be expanded in the revset, call ``revset.formatspec()``
825 and pass the result as an element of ``specs``.
826
827 Specifying a single revset is allowed.
828
829 Returns a ``revset.abstractsmartset`` which is a list-like interface over
830 integer revisions.
831 """
789 allspecs = []
832 allspecs = []
790 for spec in revs:
833 for spec in specs:
791 if isinstance(spec, int):
834 if isinstance(spec, int):
792 spec = revset.formatspec('rev(%d)', spec)
835 spec = revset.formatspec('rev(%d)', spec)
793 allspecs.append(spec)
836 allspecs.append(spec)
@@ -1183,6 +1226,9 b' class filecache(object):'
1183 return self
1226 return self
1184
1227
1185 def __get__(self, obj, type=None):
1228 def __get__(self, obj, type=None):
1229 # if accessed on the class, return the descriptor itself.
1230 if obj is None:
1231 return self
1186 # do we need to check if the file changed?
1232 # do we need to check if the file changed?
1187 if self.name in obj.__dict__:
1233 if self.name in obj.__dict__:
1188 assert self.name in obj._filecache, self.name
1234 assert self.name in obj._filecache, self.name
@@ -1358,8 +1404,8 b' class backgroundfilecloser(object):'
1358 def close(self, fh):
1404 def close(self, fh):
1359 """Schedule a file for closing."""
1405 """Schedule a file for closing."""
1360 if not self._entered:
1406 if not self._entered:
1361 raise error.Abort('can only call close() when context manager '
1407 raise error.Abort(_('can only call close() when context manager '
1362 'active')
1408 'active'))
1363
1409
1364 # If a background thread encountered an exception, raise now so we fail
1410 # If a background thread encountered an exception, raise now so we fail
1365 # fast. Otherwise we may potentially go on for minutes until the error
1411 # fast. Otherwise we may potentially go on for minutes until the error
@@ -1375,4 +1421,3 b' class backgroundfilecloser(object):'
1375 return
1421 return
1376
1422
1377 self._queue.put(fh, block=True, timeout=None)
1423 self._queue.put(fh, block=True, timeout=None)
1378
@@ -7,6 +7,8 b''
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import hashlib
11
10 from .i18n import _
12 from .i18n import _
11 from . import (
13 from . import (
12 bdiff,
14 bdiff,
@@ -27,14 +29,14 b' def _findexactmatches(repo, added, remov'
27 for i, fctx in enumerate(removed):
29 for i, fctx in enumerate(removed):
28 repo.ui.progress(_('searching for exact renames'), i, total=numfiles,
30 repo.ui.progress(_('searching for exact renames'), i, total=numfiles,
29 unit=_('files'))
31 unit=_('files'))
30 h = util.sha1(fctx.data()).digest()
32 h = hashlib.sha1(fctx.data()).digest()
31 hashes[h] = fctx
33 hashes[h] = fctx
32
34
33 # For each added file, see if it corresponds to a removed file.
35 # For each added file, see if it corresponds to a removed file.
34 for i, fctx in enumerate(added):
36 for i, fctx in enumerate(added):
35 repo.ui.progress(_('searching for exact renames'), i + len(removed),
37 repo.ui.progress(_('searching for exact renames'), i + len(removed),
36 total=numfiles, unit=_('files'))
38 total=numfiles, unit=_('files'))
37 h = util.sha1(fctx.data()).digest()
39 h = hashlib.sha1(fctx.data()).digest()
38 if h in hashes:
40 if h in hashes:
39 yield (hashes[h], fctx)
41 yield (hashes[h], fctx)
40
42
@@ -106,4 +108,3 b' def findrenames(repo, added, removed, th'
106 for (a, b, score) in _findsimilarmatches(repo,
108 for (a, b, score) in _findsimilarmatches(repo,
107 sorted(addedfiles), sorted(removedfiles), threshold):
109 sorted(addedfiles), sorted(removedfiles), threshold):
108 yield (a.path(), b.path(), score)
110 yield (a.path(), b.path(), score)
109
@@ -307,7 +307,7 b' class sshpeer(wireproto.wirepeer):'
307 r = self._call(cmd, **args)
307 r = self._call(cmd, **args)
308 if r:
308 if r:
309 # XXX needs to be made better
309 # XXX needs to be made better
310 raise error.Abort('unexpected remote reply: %s' % r)
310 raise error.Abort(_('unexpected remote reply: %s') % r)
311 while True:
311 while True:
312 d = fp.read(4096)
312 d = fp.read(4096)
313 if not d:
313 if not d:
@@ -11,6 +11,7 b' from __future__ import absolute_import'
11 import os
11 import os
12 import sys
12 import sys
13
13
14 from .i18n import _
14 from . import (
15 from . import (
15 error,
16 error,
16 hook,
17 hook,
@@ -40,7 +41,7 b' class sshserver(wireproto.abstractserver'
40 argline = self.fin.readline()[:-1]
41 argline = self.fin.readline()[:-1]
41 arg, l = argline.split()
42 arg, l = argline.split()
42 if arg not in keys:
43 if arg not in keys:
43 raise error.Abort("unexpected parameter %r" % arg)
44 raise error.Abort(_("unexpected parameter %r") % arg)
44 if arg == '*':
45 if arg == '*':
45 star = {}
46 star = {}
46 for k in xrange(int(l)):
47 for k in xrange(int(l)):
This diff has been collapsed as it changes many lines, (638 lines changed) Show them Hide them
@@ -9,6 +9,7 b''
9
9
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 import hashlib
12 import os
13 import os
13 import re
14 import re
14 import ssl
15 import ssl
@@ -28,14 +29,21 b' from . import ('
28 # modern/secure or legacy/insecure. Many operations in this module have
29 # modern/secure or legacy/insecure. Many operations in this module have
29 # separate code paths depending on support in Python.
30 # separate code paths depending on support in Python.
30
31
32 configprotocols = set([
33 'tls1.0',
34 'tls1.1',
35 'tls1.2',
36 ])
37
31 hassni = getattr(ssl, 'HAS_SNI', False)
38 hassni = getattr(ssl, 'HAS_SNI', False)
32
39
33 try:
40 # TLS 1.1 and 1.2 may not be supported if the OpenSSL Python is compiled
34 OP_NO_SSLv2 = ssl.OP_NO_SSLv2
41 # against doesn't support them.
35 OP_NO_SSLv3 = ssl.OP_NO_SSLv3
42 supportedprotocols = set(['tls1.0'])
36 except AttributeError:
43 if util.safehasattr(ssl, 'PROTOCOL_TLSv1_1'):
37 OP_NO_SSLv2 = 0x1000000
44 supportedprotocols.add('tls1.1')
38 OP_NO_SSLv3 = 0x2000000
45 if util.safehasattr(ssl, 'PROTOCOL_TLSv1_2'):
46 supportedprotocols.add('tls1.2')
39
47
40 try:
48 try:
41 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
49 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
@@ -76,15 +84,19 b' except AttributeError:'
76
84
77 def load_verify_locations(self, cafile=None, capath=None, cadata=None):
85 def load_verify_locations(self, cafile=None, capath=None, cadata=None):
78 if capath:
86 if capath:
79 raise error.Abort('capath not supported')
87 raise error.Abort(_('capath not supported'))
80 if cadata:
88 if cadata:
81 raise error.Abort('cadata not supported')
89 raise error.Abort(_('cadata not supported'))
82
90
83 self._cacerts = cafile
91 self._cacerts = cafile
84
92
85 def set_ciphers(self, ciphers):
93 def set_ciphers(self, ciphers):
86 if not self._supportsciphers:
94 if not self._supportsciphers:
87 raise error.Abort('setting ciphers not supported')
95 raise error.Abort(_('setting ciphers in [hostsecurity] is not '
96 'supported by this version of Python'),
97 hint=_('remove the config option or run '
98 'Mercurial with a modern Python '
99 'version (preferred)'))
88
100
89 self._ciphers = ciphers
101 self._ciphers = ciphers
90
102
@@ -107,8 +119,213 b' except AttributeError:'
107
119
108 return ssl.wrap_socket(socket, **args)
120 return ssl.wrap_socket(socket, **args)
109
121
110 def wrapsocket(sock, keyfile, certfile, ui, cert_reqs=ssl.CERT_NONE,
122 def _hostsettings(ui, hostname):
111 ca_certs=None, serverhostname=None):
123 """Obtain security settings for a hostname.
124
125 Returns a dict of settings relevant to that hostname.
126 """
127 s = {
128 # Whether we should attempt to load default/available CA certs
129 # if an explicit ``cafile`` is not defined.
130 'allowloaddefaultcerts': True,
131 # List of 2-tuple of (hash algorithm, hash).
132 'certfingerprints': [],
133 # Path to file containing concatenated CA certs. Used by
134 # SSLContext.load_verify_locations().
135 'cafile': None,
136 # Whether certificate verification should be disabled.
137 'disablecertverification': False,
138 # Whether the legacy [hostfingerprints] section has data for this host.
139 'legacyfingerprint': False,
140 # PROTOCOL_* constant to use for SSLContext.__init__.
141 'protocol': None,
142 # ssl.CERT_* constant used by SSLContext.verify_mode.
143 'verifymode': None,
144 # Defines extra ssl.OP* bitwise options to set.
145 'ctxoptions': None,
146 # OpenSSL Cipher List to use (instead of default).
147 'ciphers': None,
148 }
149
150 # Allow minimum TLS protocol to be specified in the config.
151 def validateprotocol(protocol, key):
152 if protocol not in configprotocols:
153 raise error.Abort(
154 _('unsupported protocol from hostsecurity.%s: %s') %
155 (key, protocol),
156 hint=_('valid protocols: %s') %
157 ' '.join(sorted(configprotocols)))
158
159 # We default to TLS 1.1+ where we can because TLS 1.0 has known
160 # vulnerabilities (like BEAST and POODLE). We allow users to downgrade to
161 # TLS 1.0+ via config options in case a legacy server is encountered.
162 if 'tls1.1' in supportedprotocols:
163 defaultprotocol = 'tls1.1'
164 else:
165 # Let people know they are borderline secure.
166 # We don't document this config option because we want people to see
167 # the bold warnings on the web site.
168 # internal config: hostsecurity.disabletls10warning
169 if not ui.configbool('hostsecurity', 'disabletls10warning'):
170 ui.warn(_('warning: connecting to %s using legacy security '
171 'technology (TLS 1.0); see '
172 'https://mercurial-scm.org/wiki/SecureConnections for '
173 'more info\n') % hostname)
174 defaultprotocol = 'tls1.0'
175
176 key = 'minimumprotocol'
177 protocol = ui.config('hostsecurity', key, defaultprotocol)
178 validateprotocol(protocol, key)
179
180 key = '%s:minimumprotocol' % hostname
181 protocol = ui.config('hostsecurity', key, protocol)
182 validateprotocol(protocol, key)
183
184 s['protocol'], s['ctxoptions'] = protocolsettings(protocol)
185
186 ciphers = ui.config('hostsecurity', 'ciphers')
187 ciphers = ui.config('hostsecurity', '%s:ciphers' % hostname, ciphers)
188 s['ciphers'] = ciphers
189
190 # Look for fingerprints in [hostsecurity] section. Value is a list
191 # of <alg>:<fingerprint> strings.
192 fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname,
193 [])
194 for fingerprint in fingerprints:
195 if not (fingerprint.startswith(('sha1:', 'sha256:', 'sha512:'))):
196 raise error.Abort(_('invalid fingerprint for %s: %s') % (
197 hostname, fingerprint),
198 hint=_('must begin with "sha1:", "sha256:", '
199 'or "sha512:"'))
200
201 alg, fingerprint = fingerprint.split(':', 1)
202 fingerprint = fingerprint.replace(':', '').lower()
203 s['certfingerprints'].append((alg, fingerprint))
204
205 # Fingerprints from [hostfingerprints] are always SHA-1.
206 for fingerprint in ui.configlist('hostfingerprints', hostname, []):
207 fingerprint = fingerprint.replace(':', '').lower()
208 s['certfingerprints'].append(('sha1', fingerprint))
209 s['legacyfingerprint'] = True
210
211 # If a host cert fingerprint is defined, it is the only thing that
212 # matters. No need to validate CA certs.
213 if s['certfingerprints']:
214 s['verifymode'] = ssl.CERT_NONE
215 s['allowloaddefaultcerts'] = False
216
217 # If --insecure is used, don't take CAs into consideration.
218 elif ui.insecureconnections:
219 s['disablecertverification'] = True
220 s['verifymode'] = ssl.CERT_NONE
221 s['allowloaddefaultcerts'] = False
222
223 if ui.configbool('devel', 'disableloaddefaultcerts'):
224 s['allowloaddefaultcerts'] = False
225
226 # If both fingerprints and a per-host ca file are specified, issue a warning
227 # because users should not be surprised about what security is or isn't
228 # being performed.
229 cafile = ui.config('hostsecurity', '%s:verifycertsfile' % hostname)
230 if s['certfingerprints'] and cafile:
231 ui.warn(_('(hostsecurity.%s:verifycertsfile ignored when host '
232 'fingerprints defined; using host fingerprints for '
233 'verification)\n') % hostname)
234
235 # Try to hook up CA certificate validation unless something above
236 # makes it not necessary.
237 if s['verifymode'] is None:
238 # Look at per-host ca file first.
239 if cafile:
240 cafile = util.expandpath(cafile)
241 if not os.path.exists(cafile):
242 raise error.Abort(_('path specified by %s does not exist: %s') %
243 ('hostsecurity.%s:verifycertsfile' % hostname,
244 cafile))
245 s['cafile'] = cafile
246 else:
247 # Find global certificates file in config.
248 cafile = ui.config('web', 'cacerts')
249
250 if cafile:
251 cafile = util.expandpath(cafile)
252 if not os.path.exists(cafile):
253 raise error.Abort(_('could not find web.cacerts: %s') %
254 cafile)
255 elif s['allowloaddefaultcerts']:
256 # CAs not defined in config. Try to find system bundles.
257 cafile = _defaultcacerts(ui)
258 if cafile:
259 ui.debug('using %s for CA file\n' % cafile)
260
261 s['cafile'] = cafile
262
263 # Require certificate validation if CA certs are being loaded and
264 # verification hasn't been disabled above.
265 if cafile or (_canloaddefaultcerts and s['allowloaddefaultcerts']):
266 s['verifymode'] = ssl.CERT_REQUIRED
267 else:
268 # At this point we don't have a fingerprint, aren't being
269 # explicitly insecure, and can't load CA certs. Connecting
270 # is insecure. We allow the connection and abort during
271 # validation (once we have the fingerprint to print to the
272 # user).
273 s['verifymode'] = ssl.CERT_NONE
274
275 assert s['protocol'] is not None
276 assert s['ctxoptions'] is not None
277 assert s['verifymode'] is not None
278
279 return s
280
281 def protocolsettings(protocol):
282 """Resolve the protocol and context options for a config value."""
283 if protocol not in configprotocols:
284 raise ValueError('protocol value not supported: %s' % protocol)
285
286 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
287 # that both ends support, including TLS protocols. On legacy stacks,
288 # the highest it likely goes is TLS 1.0. On modern stacks, it can
289 # support TLS 1.2.
290 #
291 # The PROTOCOL_TLSv* constants select a specific TLS version
292 # only (as opposed to multiple versions). So the method for
293 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
294 # disable protocols via SSLContext.options and OP_NO_* constants.
295 # However, SSLContext.options doesn't work unless we have the
296 # full/real SSLContext available to us.
297 if supportedprotocols == set(['tls1.0']):
298 if protocol != 'tls1.0':
299 raise error.Abort(_('current Python does not support protocol '
300 'setting %s') % protocol,
301 hint=_('upgrade Python or disable setting since '
302 'only TLS 1.0 is supported'))
303
304 return ssl.PROTOCOL_TLSv1, 0
305
306 # WARNING: returned options don't work unless the modern ssl module
307 # is available. Be careful when adding options here.
308
309 # SSLv2 and SSLv3 are broken. We ban them outright.
310 options = ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3
311
312 if protocol == 'tls1.0':
313 # Defaults above are to use TLS 1.0+
314 pass
315 elif protocol == 'tls1.1':
316 options |= ssl.OP_NO_TLSv1
317 elif protocol == 'tls1.2':
318 options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
319 else:
320 raise error.Abort(_('this should not happen'))
321
322 # Prevent CRIME.
323 # There is no guarantee this attribute is defined on the module.
324 options |= getattr(ssl, 'OP_NO_COMPRESSION', 0)
325
326 return ssl.PROTOCOL_SSLv23, options
327
328 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
112 """Add SSL/TLS to a socket.
329 """Add SSL/TLS to a socket.
113
330
114 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
331 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
@@ -121,32 +338,33 b' def wrapsocket(sock, keyfile, certfile, '
121 server (and client) support SNI, this tells the server which certificate
338 server (and client) support SNI, this tells the server which certificate
122 to use.
339 to use.
123 """
340 """
124 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
341 if not serverhostname:
125 # that both ends support, including TLS protocols. On legacy stacks,
342 raise error.Abort(_('serverhostname argument is required'))
126 # the highest it likely goes in TLS 1.0. On modern stacks, it can
343
127 # support TLS 1.2.
344 settings = _hostsettings(ui, serverhostname)
128 #
129 # The PROTOCOL_TLSv* constants select a specific TLS version
130 # only (as opposed to multiple versions). So the method for
131 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
132 # disable protocols via SSLContext.options and OP_NO_* constants.
133 # However, SSLContext.options doesn't work unless we have the
134 # full/real SSLContext available to us.
135 #
136 # SSLv2 and SSLv3 are broken. We ban them outright.
137 if modernssl:
138 protocol = ssl.PROTOCOL_SSLv23
139 else:
140 protocol = ssl.PROTOCOL_TLSv1
141
345
142 # TODO use ssl.create_default_context() on modernssl.
346 # We can't use ssl.create_default_context() because it calls
143 sslcontext = SSLContext(protocol)
347 # load_default_certs() unless CA arguments are passed to it. We want to
348 # have explicit control over CA loading because implicitly loading
349 # CAs may undermine the user's intent. For example, a user may define a CA
350 # bundle with a specific CA cert removed. If the system/default CA bundle
351 # is loaded and contains that removed CA, you've just undone the user's
352 # choice.
353 sslcontext = SSLContext(settings['protocol'])
144
354
145 # This is a no-op on old Python.
355 # This is a no-op unless using modern ssl.
146 sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
356 sslcontext.options |= settings['ctxoptions']
147
357
148 # This still works on our fake SSLContext.
358 # This still works on our fake SSLContext.
149 sslcontext.verify_mode = cert_reqs
359 sslcontext.verify_mode = settings['verifymode']
360
361 if settings['ciphers']:
362 try:
363 sslcontext.set_ciphers(settings['ciphers'])
364 except ssl.SSLError as e:
365 raise error.Abort(_('could not set ciphers: %s') % e.args[0],
366 hint=_('change cipher string (%s) in config') %
367 settings['ciphers'])
150
368
151 if certfile is not None:
369 if certfile is not None:
152 def password():
370 def password():
@@ -154,20 +372,123 b' def wrapsocket(sock, keyfile, certfile, '
154 return ui.getpass(_('passphrase for %s: ') % f, '')
372 return ui.getpass(_('passphrase for %s: ') % f, '')
155 sslcontext.load_cert_chain(certfile, keyfile, password)
373 sslcontext.load_cert_chain(certfile, keyfile, password)
156
374
157 if ca_certs is not None:
375 if settings['cafile'] is not None:
158 sslcontext.load_verify_locations(cafile=ca_certs)
376 try:
159 else:
377 sslcontext.load_verify_locations(cafile=settings['cafile'])
378 except ssl.SSLError as e:
379 raise error.Abort(_('error loading CA file %s: %s') % (
380 settings['cafile'], e.args[1]),
381 hint=_('file is empty or malformed?'))
382 caloaded = True
383 elif settings['allowloaddefaultcerts']:
160 # This is a no-op on old Python.
384 # This is a no-op on old Python.
161 sslcontext.load_default_certs()
385 sslcontext.load_default_certs()
386 caloaded = True
387 else:
388 caloaded = False
162
389
163 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
390 try:
391 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
392 except ssl.SSLError as e:
393 # If we're doing certificate verification and no CA certs are loaded,
394 # that is almost certainly the reason why verification failed. Provide
395 # a hint to the user.
396 # Only modern ssl module exposes SSLContext.get_ca_certs() so we can
397 # only show this warning if modern ssl is available.
398 if (caloaded and settings['verifymode'] == ssl.CERT_REQUIRED and
399 modernssl and not sslcontext.get_ca_certs()):
400 ui.warn(_('(an attempt was made to load CA certificates but none '
401 'were loaded; see '
402 'https://mercurial-scm.org/wiki/SecureConnections for '
403 'how to configure Mercurial to avoid this error)\n'))
404 # Try to print more helpful error messages for known failures.
405 if util.safehasattr(e, 'reason'):
406 if e.reason == 'UNSUPPORTED_PROTOCOL':
407 ui.warn(_('(could not negotiate a common protocol; see '
408 'https://mercurial-scm.org/wiki/SecureConnections '
409 'for how to configure Mercurial to avoid this '
410 'error)\n'))
411 raise
412
164 # check if wrap_socket failed silently because socket had been
413 # check if wrap_socket failed silently because socket had been
165 # closed
414 # closed
166 # - see http://bugs.python.org/issue13721
415 # - see http://bugs.python.org/issue13721
167 if not sslsocket.cipher():
416 if not sslsocket.cipher():
168 raise error.Abort(_('ssl connection failed'))
417 raise error.Abort(_('ssl connection failed'))
418
419 sslsocket._hgstate = {
420 'caloaded': caloaded,
421 'hostname': serverhostname,
422 'settings': settings,
423 'ui': ui,
424 }
425
169 return sslsocket
426 return sslsocket
170
427
428 def wrapserversocket(sock, ui, certfile=None, keyfile=None, cafile=None,
429 requireclientcert=False):
430 """Wrap a socket for use by servers.
431
432 ``certfile`` and ``keyfile`` specify the files containing the certificate's
433 public and private keys, respectively. Both keys can be defined in the same
434 file via ``certfile`` (the private key must come first in the file).
435
436 ``cafile`` defines the path to certificate authorities.
437
438 ``requireclientcert`` specifies whether to require client certificates.
439
440 Typically ``cafile`` is only defined if ``requireclientcert`` is true.
441 """
442 protocol, options = protocolsettings('tls1.0')
443
444 # This config option is intended for use in tests only. It is a giant
445 # footgun to kill security. Don't define it.
446 exactprotocol = ui.config('devel', 'serverexactprotocol')
447 if exactprotocol == 'tls1.0':
448 protocol = ssl.PROTOCOL_TLSv1
449 elif exactprotocol == 'tls1.1':
450 if 'tls1.1' not in supportedprotocols:
451 raise error.Abort(_('TLS 1.1 not supported by this Python'))
452 protocol = ssl.PROTOCOL_TLSv1_1
453 elif exactprotocol == 'tls1.2':
454 if 'tls1.2' not in supportedprotocols:
455 raise error.Abort(_('TLS 1.2 not supported by this Python'))
456 protocol = ssl.PROTOCOL_TLSv1_2
457 elif exactprotocol:
458 raise error.Abort(_('invalid value for serverexactprotocol: %s') %
459 exactprotocol)
460
461 if modernssl:
462 # We /could/ use create_default_context() here since it doesn't load
463 # CAs when configured for client auth. However, it is hard-coded to
464 # use ssl.PROTOCOL_SSLv23 which may not be appropriate here.
465 sslcontext = SSLContext(protocol)
466 sslcontext.options |= options
467
468 # Improve forward secrecy.
469 sslcontext.options |= getattr(ssl, 'OP_SINGLE_DH_USE', 0)
470 sslcontext.options |= getattr(ssl, 'OP_SINGLE_ECDH_USE', 0)
471
472 # Use the list of more secure ciphers if found in the ssl module.
473 if util.safehasattr(ssl, '_RESTRICTED_SERVER_CIPHERS'):
474 sslcontext.options |= getattr(ssl, 'OP_CIPHER_SERVER_PREFERENCE', 0)
475 sslcontext.set_ciphers(ssl._RESTRICTED_SERVER_CIPHERS)
476 else:
477 sslcontext = SSLContext(ssl.PROTOCOL_TLSv1)
478
479 if requireclientcert:
480 sslcontext.verify_mode = ssl.CERT_REQUIRED
481 else:
482 sslcontext.verify_mode = ssl.CERT_NONE
483
484 if certfile or keyfile:
485 sslcontext.load_cert_chain(certfile=certfile, keyfile=keyfile)
486
487 if cafile:
488 sslcontext.load_verify_locations(cafile=cafile)
489
490 return sslcontext.wrap_socket(sock, server_side=True)
491
171 class wildcarderror(Exception):
492 class wildcarderror(Exception):
172 """Represents an error parsing wildcards in DNS name."""
493 """Represents an error parsing wildcards in DNS name."""
173
494
@@ -268,10 +589,6 b' def _verifycert(cert, hostname):'
268 else:
589 else:
269 return _('no commonName or subjectAltName found in certificate')
590 return _('no commonName or subjectAltName found in certificate')
270
591
271
272 # CERT_REQUIRED means fetch the cert from the server all the time AND
273 # validate it against the CA store provided in web.cacerts.
274
275 def _plainapplepython():
592 def _plainapplepython():
276 """return true if this seems to be a pure Apple Python that
593 """return true if this seems to be a pure Apple Python that
277 * is unfrozen and presumably has the whole mercurial module in the file
594 * is unfrozen and presumably has the whole mercurial module in the file
@@ -286,97 +603,172 b' def _plainapplepython():'
286 return (exe.startswith('/usr/bin/python') or
603 return (exe.startswith('/usr/bin/python') or
287 exe.startswith('/system/library/frameworks/python.framework/'))
604 exe.startswith('/system/library/frameworks/python.framework/'))
288
605
289 def _defaultcacerts():
606 _systemcacertpaths = [
290 """return path to CA certificates; None for system's store; ! to disable"""
607 # RHEL, CentOS, and Fedora
608 '/etc/pki/tls/certs/ca-bundle.trust.crt',
609 # Debian, Ubuntu, Gentoo
610 '/etc/ssl/certs/ca-certificates.crt',
611 ]
612
613 def _defaultcacerts(ui):
614 """return path to default CA certificates or None.
615
616 It is assumed this function is called when the returned certificates
617 file will actually be used to validate connections. Therefore this
618 function may print warnings or debug messages assuming this usage.
619
620 We don't print a message when the Python is able to load default
621 CA certs because this scenario is detected at socket connect time.
622 """
623 # The "certifi" Python package provides certificates. If it is installed,
624 # assume the user intends it to be used and use it.
625 try:
626 import certifi
627 certs = certifi.where()
628 ui.debug('using ca certificates from certifi\n')
629 return certs
630 except ImportError:
631 pass
632
633 # On Windows, only the modern ssl module is capable of loading the system
634 # CA certificates. If we're not capable of doing that, emit a warning
635 # because we'll get a certificate verification error later and the lack
636 # of loaded CA certificates will be the reason why.
637 # Assertion: this code is only called if certificates are being verified.
638 if os.name == 'nt':
639 if not _canloaddefaultcerts:
640 ui.warn(_('(unable to load Windows CA certificates; see '
641 'https://mercurial-scm.org/wiki/SecureConnections for '
642 'how to configure Mercurial to avoid this message)\n'))
643
644 return None
645
646 # Apple's OpenSSL has patches that allow a specially constructed certificate
647 # to load the system CA store. If we're running on Apple Python, use this
648 # trick.
291 if _plainapplepython():
649 if _plainapplepython():
292 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
650 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
293 if os.path.exists(dummycert):
651 if os.path.exists(dummycert):
294 return dummycert
652 return dummycert
295 if _canloaddefaultcerts:
653
654 # The Apple OpenSSL trick isn't available to us. If Python isn't able to
655 # load system certs, we're out of luck.
656 if sys.platform == 'darwin':
657 # FUTURE Consider looking for Homebrew or MacPorts installed certs
658 # files. Also consider exporting the keychain certs to a file during
659 # Mercurial install.
660 if not _canloaddefaultcerts:
661 ui.warn(_('(unable to load CA certificates; see '
662 'https://mercurial-scm.org/wiki/SecureConnections for '
663 'how to configure Mercurial to avoid this message)\n'))
296 return None
664 return None
297 return '!'
665
666 # / is writable on Windows. Out of an abundance of caution make sure
667 # we're not on Windows because paths from _systemcacerts could be installed
668 # by non-admin users.
669 assert os.name != 'nt'
298
670
299 def sslkwargs(ui, host):
671 # Try to find CA certificates in well-known locations. We print a warning
300 kws = {'ui': ui}
672 # when using a found file because we don't want too much silent magic
301 hostfingerprint = ui.config('hostfingerprints', host)
673 # for security settings. The expectation is that proper Mercurial
302 if hostfingerprint:
674 # installs will have the CA certs path defined at install time and the
303 return kws
675 # installer/packager will make an appropriate decision on the user's
304 cacerts = ui.config('web', 'cacerts')
676 # behalf. We only get here and perform this setting as a feature of
305 if cacerts == '!':
677 # last resort.
306 pass
678 if not _canloaddefaultcerts:
307 elif cacerts:
679 for path in _systemcacertpaths:
308 cacerts = util.expandpath(cacerts)
680 if os.path.isfile(path):
309 if not os.path.exists(cacerts):
681 ui.warn(_('(using CA certificates from %s; if you see this '
310 raise error.Abort(_('could not find web.cacerts: %s') % cacerts)
682 'message, your Mercurial install is not properly '
311 else:
683 'configured; see '
312 cacerts = _defaultcacerts()
684 'https://mercurial-scm.org/wiki/SecureConnections '
313 if cacerts and cacerts != '!':
685 'for how to configure Mercurial to avoid this '
314 ui.debug('using %s to enable OS X system CA\n' % cacerts)
686 'message)\n') % path)
315 ui.setconfig('web', 'cacerts', cacerts, 'defaultcacerts')
687 return path
316 if cacerts != '!':
317 kws.update({'ca_certs': cacerts,
318 'cert_reqs': ssl.CERT_REQUIRED,
319 })
320 return kws
321
688
322 class validator(object):
689 ui.warn(_('(unable to load CA certificates; see '
323 def __init__(self, ui, host):
690 'https://mercurial-scm.org/wiki/SecureConnections for '
324 self.ui = ui
691 'how to configure Mercurial to avoid this message)\n'))
325 self.host = host
326
692
327 def __call__(self, sock, strict=False):
693 return None
328 host = self.host
694
695 def validatesocket(sock):
696 """Validate a socket meets security requiremnets.
329
697
330 if not sock.cipher(): # work around http://bugs.python.org/issue13721
698 The passed socket must have been created with ``wrapsocket()``.
331 raise error.Abort(_('%s ssl connection error') % host)
699 """
332 try:
700 host = sock._hgstate['hostname']
333 peercert = sock.getpeercert(True)
701 ui = sock._hgstate['ui']
334 peercert2 = sock.getpeercert()
702 settings = sock._hgstate['settings']
335 except AttributeError:
703
336 raise error.Abort(_('%s ssl connection error') % host)
704 try:
705 peercert = sock.getpeercert(True)
706 peercert2 = sock.getpeercert()
707 except AttributeError:
708 raise error.Abort(_('%s ssl connection error') % host)
709
710 if not peercert:
711 raise error.Abort(_('%s certificate error: '
712 'no certificate received') % host)
337
713
338 if not peercert:
714 if settings['disablecertverification']:
339 raise error.Abort(_('%s certificate error: '
715 # We don't print the certificate fingerprint because it shouldn't
340 'no certificate received') % host)
716 # be necessary: if the user requested certificate verification be
717 # disabled, they presumably already saw a message about the inability
718 # to verify the certificate and this message would have printed the
719 # fingerprint. So printing the fingerprint here adds little to no
720 # value.
721 ui.warn(_('warning: connection security to %s is disabled per current '
722 'settings; communication is susceptible to eavesdropping '
723 'and tampering\n') % host)
724 return
725
726 # If a certificate fingerprint is pinned, use it and only it to
727 # validate the remote cert.
728 peerfingerprints = {
729 'sha1': hashlib.sha1(peercert).hexdigest(),
730 'sha256': hashlib.sha256(peercert).hexdigest(),
731 'sha512': hashlib.sha512(peercert).hexdigest(),
732 }
733
734 def fmtfingerprint(s):
735 return ':'.join([s[x:x + 2] for x in range(0, len(s), 2)])
736
737 nicefingerprint = 'sha256:%s' % fmtfingerprint(peerfingerprints['sha256'])
341
738
342 # If a certificate fingerprint is pinned, use it and only it to
739 if settings['certfingerprints']:
343 # validate the remote cert.
740 for hash, fingerprint in settings['certfingerprints']:
344 hostfingerprints = self.ui.configlist('hostfingerprints', host)
741 if peerfingerprints[hash].lower() == fingerprint:
345 peerfingerprint = util.sha1(peercert).hexdigest()
742 ui.debug('%s certificate matched fingerprint %s:%s\n' %
346 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
743 (host, hash, fmtfingerprint(fingerprint)))
347 for x in xrange(0, len(peerfingerprint), 2)])
744 return
348 if hostfingerprints:
745
349 fingerprintmatch = False
746 # Pinned fingerprint didn't match. This is a fatal error.
350 for hostfingerprint in hostfingerprints:
747 if settings['legacyfingerprint']:
351 if peerfingerprint.lower() == \
748 section = 'hostfingerprint'
352 hostfingerprint.replace(':', '').lower():
749 nice = fmtfingerprint(peerfingerprints['sha1'])
353 fingerprintmatch = True
750 else:
354 break
751 section = 'hostsecurity'
355 if not fingerprintmatch:
752 nice = '%s:%s' % (hash, fmtfingerprint(peerfingerprints[hash]))
356 raise error.Abort(_('certificate for %s has unexpected '
753 raise error.Abort(_('certificate for %s has unexpected '
357 'fingerprint %s') % (host, nicefingerprint),
754 'fingerprint %s') % (host, nice),
358 hint=_('check hostfingerprint configuration'))
755 hint=_('check %s configuration') % section)
359 self.ui.debug('%s certificate matched fingerprint %s\n' %
360 (host, nicefingerprint))
361 return
362
756
363 # No pinned fingerprint. Establish trust by looking at the CAs.
757 # Security is enabled but no CAs are loaded. We can't establish trust
364 cacerts = self.ui.config('web', 'cacerts')
758 # for the cert so abort.
365 if cacerts != '!':
759 if not sock._hgstate['caloaded']:
366 msg = _verifycert(peercert2, host)
760 raise error.Abort(
367 if msg:
761 _('unable to verify security of %s (no loaded CA certificates); '
368 raise error.Abort(_('%s certificate error: %s') % (host, msg),
762 'refusing to connect') % host,
369 hint=_('configure hostfingerprint %s or use '
763 hint=_('see https://mercurial-scm.org/wiki/SecureConnections for '
370 '--insecure to connect insecurely') %
764 'how to configure Mercurial to avoid this error or set '
371 nicefingerprint)
765 'hostsecurity.%s:fingerprints=%s to trust this server') %
372 self.ui.debug('%s certificate successfully verified\n' % host)
766 (host, nicefingerprint))
373 elif strict:
767
374 raise error.Abort(_('%s certificate with fingerprint %s not '
768 msg = _verifycert(peercert2, host)
375 'verified') % (host, nicefingerprint),
769 if msg:
376 hint=_('check hostfingerprints or web.cacerts '
770 raise error.Abort(_('%s certificate error: %s') % (host, msg),
377 'config setting'))
771 hint=_('set hostsecurity.%s:certfingerprints=%s '
378 else:
772 'config setting or use --insecure to connect '
379 self.ui.warn(_('warning: %s certificate with fingerprint %s not '
773 'insecurely') %
380 'verified (check hostfingerprints or web.cacerts '
774 (host, nicefingerprint))
381 'config setting)\n') %
382 (host, nicefingerprint))
@@ -8,6 +8,7 b''
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import hashlib
11 import os
12 import os
12 import stat
13 import stat
13
14
@@ -19,8 +20,6 b' from . import ('
19 util,
20 util,
20 )
21 )
21
22
22 _sha = util.sha1
23
24 # This avoids a collision between a file named foo and a dir named
23 # This avoids a collision between a file named foo and a dir named
25 # foo.i or foo.d
24 # foo.i or foo.d
26 def _encodedir(path):
25 def _encodedir(path):
@@ -57,6 +56,23 b' def decodedir(path):'
57 .replace(".i.hg/", ".i/")
56 .replace(".i.hg/", ".i/")
58 .replace(".hg.hg/", ".hg/"))
57 .replace(".hg.hg/", ".hg/"))
59
58
59 def _reserved():
60 ''' characters that are problematic for filesystems
61
62 * ascii escapes (0..31)
63 * ascii hi (126..255)
64 * windows specials
65
66 these characters will be escaped by encodefunctions
67 '''
68 winreserved = [ord(x) for x in '\\:*?"<>|']
69 for x in range(32):
70 yield x
71 for x in range(126, 256):
72 yield x
73 for x in winreserved:
74 yield x
75
60 def _buildencodefun():
76 def _buildencodefun():
61 '''
77 '''
62 >>> enc, dec = _buildencodefun()
78 >>> enc, dec = _buildencodefun()
@@ -82,11 +98,10 b' def _buildencodefun():'
82 'the\\x07quick\\xadshot'
98 'the\\x07quick\\xadshot'
83 '''
99 '''
84 e = '_'
100 e = '_'
85 winreserved = [ord(x) for x in '\\:*?"<>|']
86 cmap = dict([(chr(x), chr(x)) for x in xrange(127)])
101 cmap = dict([(chr(x), chr(x)) for x in xrange(127)])
87 for x in (range(32) + range(126, 256) + winreserved):
102 for x in _reserved():
88 cmap[chr(x)] = "~%02x" % x
103 cmap[chr(x)] = "~%02x" % x
89 for x in range(ord("A"), ord("Z") + 1) + [ord(e)]:
104 for x in list(range(ord("A"), ord("Z") + 1)) + [ord(e)]:
90 cmap[chr(x)] = e + chr(x).lower()
105 cmap[chr(x)] = e + chr(x).lower()
91 dmap = {}
106 dmap = {}
92 for k, v in cmap.iteritems():
107 for k, v in cmap.iteritems():
@@ -134,9 +149,8 b' def _buildlowerencodefun():'
134 >>> f('the\x07quick\xADshot')
149 >>> f('the\x07quick\xADshot')
135 'the~07quick~adshot'
150 'the~07quick~adshot'
136 '''
151 '''
137 winreserved = [ord(x) for x in '\\:*?"<>|']
138 cmap = dict([(chr(x), chr(x)) for x in xrange(127)])
152 cmap = dict([(chr(x), chr(x)) for x in xrange(127)])
139 for x in (range(32) + range(126, 256) + winreserved):
153 for x in _reserved():
140 cmap[chr(x)] = "~%02x" % x
154 cmap[chr(x)] = "~%02x" % x
141 for x in range(ord("A"), ord("Z") + 1):
155 for x in range(ord("A"), ord("Z") + 1):
142 cmap[chr(x)] = chr(x).lower()
156 cmap[chr(x)] = chr(x).lower()
@@ -196,7 +210,7 b' def _auxencode(path, dotencode):'
196 _maxshortdirslen = 8 * (_dirprefixlen + 1) - 4
210 _maxshortdirslen = 8 * (_dirprefixlen + 1) - 4
197
211
198 def _hashencode(path, dotencode):
212 def _hashencode(path, dotencode):
199 digest = _sha(path).hexdigest()
213 digest = hashlib.sha1(path).hexdigest()
200 le = lowerencode(path[5:]).split('/') # skips prefix 'data/' or 'meta/'
214 le = lowerencode(path[5:]).split('/') # skips prefix 'data/' or 'meta/'
201 parts = _auxencode(le, dotencode)
215 parts = _auxencode(le, dotencode)
202 basename = parts[-1]
216 basename = parts[-1]
@@ -9,6 +9,7 b' from __future__ import absolute_import'
9
9
10 import copy
10 import copy
11 import errno
11 import errno
12 import hashlib
12 import os
13 import os
13 import posixpath
14 import posixpath
14 import re
15 import re
@@ -50,14 +51,14 b' def _expandedabspath(path):'
50
51
51 def _getstorehashcachename(remotepath):
52 def _getstorehashcachename(remotepath):
52 '''get a unique filename for the store hash cache of a remote repository'''
53 '''get a unique filename for the store hash cache of a remote repository'''
53 return util.sha1(_expandedabspath(remotepath)).hexdigest()[0:12]
54 return hashlib.sha1(_expandedabspath(remotepath)).hexdigest()[0:12]
54
55
55 class SubrepoAbort(error.Abort):
56 class SubrepoAbort(error.Abort):
56 """Exception class used to avoid handling a subrepo error more than once"""
57 """Exception class used to avoid handling a subrepo error more than once"""
57 def __init__(self, *args, **kw):
58 def __init__(self, *args, **kw):
59 self.subrepo = kw.pop('subrepo', None)
60 self.cause = kw.pop('cause', None)
58 error.Abort.__init__(self, *args, **kw)
61 error.Abort.__init__(self, *args, **kw)
59 self.subrepo = kw.get('subrepo')
60 self.cause = kw.get('cause')
61
62
62 def annotatesubrepoerror(func):
63 def annotatesubrepoerror(func):
63 def decoratedmethod(self, *args, **kargs):
64 def decoratedmethod(self, *args, **kargs):
@@ -585,7 +586,7 b' class abstractsubrepo(object):'
585 return 1
586 return 1
586
587
587 def revert(self, substate, *pats, **opts):
588 def revert(self, substate, *pats, **opts):
588 self.ui.warn('%s: reverting %s subrepos is unsupported\n' \
589 self.ui.warn(_('%s: reverting %s subrepos is unsupported\n') \
589 % (substate[0], substate[2]))
590 % (substate[0], substate[2]))
590 return []
591 return []
591
592
@@ -659,7 +660,7 b' class hgsubrepo(abstractsubrepo):'
659 yield '# %s\n' % _expandedabspath(remotepath)
660 yield '# %s\n' % _expandedabspath(remotepath)
660 vfs = self._repo.vfs
661 vfs = self._repo.vfs
661 for relname in filelist:
662 for relname in filelist:
662 filehash = util.sha1(vfs.tryread(relname)).hexdigest()
663 filehash = hashlib.sha1(vfs.tryread(relname)).hexdigest()
663 yield '%s = %s\n' % (relname, filehash)
664 yield '%s = %s\n' % (relname, filehash)
664
665
665 @propertycache
666 @propertycache
@@ -1413,7 +1414,7 b' class gitsubrepo(abstractsubrepo):'
1413 if command in ('cat-file', 'symbolic-ref'):
1414 if command in ('cat-file', 'symbolic-ref'):
1414 return retdata, p.returncode
1415 return retdata, p.returncode
1415 # for all others, abort
1416 # for all others, abort
1416 raise error.Abort('git %s error %d in %s' %
1417 raise error.Abort(_('git %s error %d in %s') %
1417 (command, p.returncode, self._relpath))
1418 (command, p.returncode, self._relpath))
1418
1419
1419 return retdata, p.returncode
1420 return retdata, p.returncode
@@ -292,7 +292,7 b' def _readtagcache(ui, repo):'
292 cachehash = None
292 cachehash = None
293 if cachefile:
293 if cachefile:
294 try:
294 try:
295 validline = cachelines.next()
295 validline = next(cachelines)
296 validline = validline.split()
296 validline = validline.split()
297 cacherev = int(validline[0])
297 cacherev = int(validline[0])
298 cachenode = bin(validline[1])
298 cachenode = bin(validline[1])
@@ -724,6 +724,25 b' def rstdoc(context, mapping, args):'
724
724
725 return minirst.format(text, style=style, keep=['verbose'])
725 return minirst.format(text, style=style, keep=['verbose'])
726
726
727 @templatefunc('separate(sep, args)')
728 def separate(context, mapping, args):
729 """Add a separator between non-empty arguments."""
730 if not args:
731 # i18n: "separate" is a keyword
732 raise error.ParseError(_("separate expects at least one argument"))
733
734 sep = evalstring(context, mapping, args[0])
735 first = True
736 for arg in args[1:]:
737 argstr = evalstring(context, mapping, arg)
738 if not argstr:
739 continue
740 if first:
741 first = False
742 else:
743 yield sep
744 yield argstr
745
727 @templatefunc('shortest(node, minlength=4)')
746 @templatefunc('shortest(node, minlength=4)')
728 def shortest(context, mapping, args):
747 def shortest(context, mapping, args):
729 """Obtain the shortest representation of
748 """Obtain the shortest representation of
@@ -4,5 +4,5 b''
4 <id>{urlbase}{url|urlescape}#branch-{node}</id>
4 <id>{urlbase}{url|urlescape}#branch-{node}</id>
5 <updated>{date|rfc3339date}</updated>
5 <updated>{date|rfc3339date}</updated>
6 <published>{date|rfc3339date}</published>
6 <published>{date|rfc3339date}</published>
7 <content type="text"><![CDATA[{branch|strip|escape|addbreaks}]]></content>
7 <content type="text">{branch|strip|escape}</content>
8 </entry>
8 </entry>
@@ -9,35 +9,35 b''
9 <updated>{date|rfc3339date}</updated>
9 <updated>{date|rfc3339date}</updated>
10 <published>{date|rfc3339date}</published>
10 <published>{date|rfc3339date}</published>
11 <content type="xhtml">
11 <content type="xhtml">
12 <table xmlns="http://www.w3.org/1999/xhtml">
12 <table xmlns="http://www.w3.org/1999/xhtml">
13 <tr>
13 <tr>
14 <th style="text-align:left;">changeset</th>
14 <th style="text-align:left;">changeset</th>
15 <td>{node|short}</td>
15 <td>{node|short}</td>
16 </tr>
16 </tr>
17 <tr>
17 <tr>
18 <th style="text-align:left;">branch</th>
18 <th style="text-align:left;">branch</th>
19 <td>{inbranch%"{name|escape}"}{branches%"{name|escape}"}</td>
19 <td>{inbranch%"{name|escape}"}{branches%"{name|escape}"}</td>
20 </tr>
20 </tr>
21 <tr>
21 <tr>
22 <th style="text-align:left;">bookmark</th>
22 <th style="text-align:left;">bookmark</th>
23 <td>{bookmarks%"{name|escape}"}</td>
23 <td>{bookmarks%"{name|escape}"}</td>
24 </tr>
24 </tr>
25 <tr>
25 <tr>
26 <th style="text-align:left;">tag</th>
26 <th style="text-align:left;">tag</th>
27 <td>{tags%"{name|escape}"}</td>
27 <td>{tags%"{name|escape}"}</td>
28 </tr>
28 </tr>
29 <tr>
29 <tr>
30 <th style="text-align:left;">user</th>
30 <th style="text-align:left;">user</th>
31 <td>{author|obfuscate}</td>
31 <td>{author|obfuscate}</td>
32 </tr>
32 </tr>
33 <tr>
33 <tr>
34 <th style="text-align:left;vertical-align:top;">description</th>
34 <th style="text-align:left;vertical-align:top;">description</th>
35 <td>{desc|strip|escape|websub|addbreaks|nonempty}</td>
35 <td>{desc|strip|escape|websub|addbreaks|nonempty}</td>
36 </tr>
36 </tr>
37 <tr>
37 <tr>
38 <th style="text-align:left;vertical-align:top;">files</th>
38 <th style="text-align:left;vertical-align:top;">files</th>
39 <td>{files}</td>
39 <td>{files}</td>
40 </tr>
40 </tr>
41 </table>
41 </table>
42 </content>
42 </content>
43 </entry>
43 </entry>
@@ -5,7 +5,6 b' header = header.tmpl'
5 changelog = changelog.tmpl
5 changelog = changelog.tmpl
6 changelogentry = changelogentry.tmpl
6 changelogentry = changelogentry.tmpl
7 filelog = filelog.tmpl
7 filelog = filelog.tmpl
8 filelogentry = filelogentry.tmpl
9 tags = tags.tmpl
8 tags = tags.tmpl
10 tagentry = tagentry.tmpl
9 tagentry = tagentry.tmpl
11 bookmarks = bookmarks.tmpl
10 bookmarks = bookmarks.tmpl
@@ -95,14 +95,29 b' filelog = filelog.tmpl'
95 fileline = '
95 fileline = '
96 <a href="#{lineid}"></a><span id="{lineid}">{strip(line|escape, '\r\n')}</span>'
96 <a href="#{lineid}"></a><span id="{lineid}">{strip(line|escape, '\r\n')}</span>'
97 annotateline = '
97 annotateline = '
98 <tr id="{lineid}" style="font-family:monospace" class="parity{parity}">
98 <tr id="{lineid}" style="font-family:monospace" class="parity{parity}{ifeq(node, originalnode, ' thisrev')}">
99 <td class="linenr" style="text-align: right;">
99 <td class="annotate linenr parity{blockparity}" style="text-align: right;">
100 <a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}#l{targetline}"
100 {if(blockhead,
101 title="{node|short}: {desc|escape|firstline}">{author|user}@{rev}</a>
101 '<a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}#l{targetline}">
102 {rev}
103 </a>')}
104 <div class="annotate-info">
105 <div>
106 <a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}#l{targetline}">
107 {node|short}</a>
108 {desc|escape|firstline}
109 </div>
110 <div><em>{author|obfuscate}</em></div>
111 <div>parents: {parents%annotateparent}</div>
112 <a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a>
113 <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a>
114 </div>
102 </td>
115 </td>
103 <td><pre><a class="linenr" href="#{lineid}">{linenumber}</a></pre></td>
116 <td><pre><a class="linenr" href="#{lineid}">{linenumber}</a></pre></td>
104 <td><pre>{line|escape}</pre></td>
117 <td><pre>{line|escape}</pre></td>
105 </tr>'
118 </tr>'
119 annotateparent = '
120 <a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rev}</a>'
106 difflineplus = '
121 difflineplus = '
107 <a href="#{lineid}"></a><span id="{lineid}" class="difflineplus">{strip(line|escape, '\r\n')}</span>'
122 <a href="#{lineid}"></a><span id="{lineid}" class="difflineplus">{strip(line|escape, '\r\n')}</span>'
108 difflineminus = '
123 difflineminus = '
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from tests/test-update-renames.t to tests/test-update-names.t
NO CONTENT: file renamed from tests/test-update-renames.t to tests/test-update-names.t
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now