Show More
The requested changes are too big and content was truncated. Show full diff
@@ -0,0 +1,55 b'' | |||||
|
1 | #!/usr/bin/env python | |||
|
2 | # | |||
|
3 | # Copyright 2014 Matt Mackall <mpm@selenic.com> | |||
|
4 | # | |||
|
5 | # A tool/hook to run basic sanity checks on commits/patches for | |||
|
6 | # submission to Mercurial. Install by adding the following to your | |||
|
7 | # .hg/hgrc: | |||
|
8 | # | |||
|
9 | # [hooks] | |||
|
10 | # pretxncommit = contrib/check-commit | |||
|
11 | # | |||
|
12 | # The hook can be temporarily bypassed with: | |||
|
13 | # | |||
|
14 | # $ BYPASS= hg commit | |||
|
15 | # | |||
|
16 | # See also: http://mercurial.selenic.com/wiki/ContributingChanges | |||
|
17 | ||||
|
18 | import re, sys, os | |||
|
19 | ||||
|
20 | errors = [ | |||
|
21 | (r"[(]bc[)]", "(BC) needs to be uppercase"), | |||
|
22 | (r"[(]issue \d\d\d", "no space allowed between issue and number"), | |||
|
23 | (r"[(]bug", "use (issueDDDD) instead of bug"), | |||
|
24 | (r"^# User [^@\n]+$", "username is not an email address"), | |||
|
25 | (r"^# .*\n(?!merge with )[^#]\S+[^:] ", | |||
|
26 | "summary line doesn't start with 'topic: '"), | |||
|
27 | (r"^# .*\n[A-Z][a-z]\S+", "don't capitalize summary lines"), | |||
|
28 | (r"^# .*\n.*\.\s+$", "don't add trailing period on summary line"), | |||
|
29 | (r"^# .*\n.{78,}", "summary line too long"), | |||
|
30 | (r"^\+\n \n", "adds double empty line"), | |||
|
31 | (r"\+\s+def [a-z]+_[a-z]", "adds a function with foo_bar naming"), | |||
|
32 | ] | |||
|
33 | ||||
|
34 | node = os.environ.get("HG_NODE") | |||
|
35 | ||||
|
36 | if node: | |||
|
37 | commit = os.popen("hg export %s" % node).read() | |||
|
38 | else: | |||
|
39 | commit = sys.stdin.read() | |||
|
40 | ||||
|
41 | exitcode = 0 | |||
|
42 | for exp, msg in errors: | |||
|
43 | m = re.search(exp, commit, re.MULTILINE) | |||
|
44 | if m: | |||
|
45 | pos = 0 | |||
|
46 | for n, l in enumerate(commit.splitlines(True)): | |||
|
47 | pos += len(l) | |||
|
48 | if pos >= m.end(): | |||
|
49 | print "%d: %s" % (n, msg) | |||
|
50 | print " %s" % l[:-1] | |||
|
51 | if "BYPASS" not in os.environ: | |||
|
52 | exitcode = 1 | |||
|
53 | break | |||
|
54 | ||||
|
55 | sys.exit(exitcode) |
@@ -0,0 +1,6 b'' | |||||
|
1 | FROM centos:centos5 | |||
|
2 | RUN yum install -y gcc make rpm-build gettext tar | |||
|
3 | RUN yum install -y python-devel python-docutils | |||
|
4 | # For creating repo meta data | |||
|
5 | RUN yum install -y createrepo | |||
|
6 | RUN yum install -y readline-devel openssl-devel ncurses-devel zlib-devel bzip2-devel |
@@ -0,0 +1,56 b'' | |||||
|
1 | ;; hg-test-mode.el - Major mode for editing Mercurial tests | |||
|
2 | ;; | |||
|
3 | ;; Copyright 2014 Matt Mackall <mpm@selenic.com> | |||
|
4 | ;; "I have no idea what I'm doing" | |||
|
5 | ;; | |||
|
6 | ;; This software may be used and distributed according to the terms of the | |||
|
7 | ;; GNU General Public License version 2 or any later version. | |||
|
8 | ;; | |||
|
9 | ;; To enable, add something like the following to your .emacs: | |||
|
10 | ;; | |||
|
11 | ;; (if (file-exists-p "~/hg/contrib/hg-test-mode.el") | |||
|
12 | ;; (load "~/hg/contrib/hg-test-mode.el")) | |||
|
13 | ||||
|
14 | (defvar hg-test-mode-hook nil) | |||
|
15 | ||||
|
16 | (defvar hg-test-mode-map | |||
|
17 | (let ((map (make-keymap))) | |||
|
18 | (define-key map "\C-j" 'newline-and-indent) | |||
|
19 | map) | |||
|
20 | "Keymap for hg test major mode") | |||
|
21 | ||||
|
22 | (add-to-list 'auto-mode-alist '("\\.t\\'" . hg-test-mode)) | |||
|
23 | ||||
|
24 | (defconst hg-test-font-lock-keywords-1 | |||
|
25 | (list | |||
|
26 | '("^ \\(\\$\\|>>>\\) " 1 font-lock-builtin-face) | |||
|
27 | '("^ \\(>\\|\\.\\.\\.\\) " 1 font-lock-constant-face) | |||
|
28 | '("^ \\([[][0-9]+[]]\\)$" 1 font-lock-warning-face) | |||
|
29 | '("^ \\(.*?\\)\\(\\( [(][-a-z]+[)]\\)*\\)$" 1 font-lock-string-face) | |||
|
30 | '("\\$?\\(HG\\|TEST\\)\\w+=?" . font-lock-variable-name-face) | |||
|
31 | '("^ \\(.*?\\)\\(\\( [(][-a-z]+[)]\\)+\\)$" 2 font-lock-type-face) | |||
|
32 | '("^#.*" . font-lock-preprocessor-face) | |||
|
33 | '("^\\([^ ].*\\)$" 1 font-lock-comment-face) | |||
|
34 | ) | |||
|
35 | "Minimal highlighting expressions for hg-test mode") | |||
|
36 | ||||
|
37 | (defvar hg-test-font-lock-keywords hg-test-font-lock-keywords-1 | |||
|
38 | "Default highlighting expressions for hg-test mode") | |||
|
39 | ||||
|
40 | (defvar hg-test-mode-syntax-table | |||
|
41 | (let ((st (make-syntax-table))) | |||
|
42 | (modify-syntax-entry ?\" "w" st) ;; disable standard quoting | |||
|
43 | st) | |||
|
44 | "Syntax table for hg-test mode") | |||
|
45 | ||||
|
46 | (defun hg-test-mode () | |||
|
47 | (interactive) | |||
|
48 | (kill-all-local-variables) | |||
|
49 | (use-local-map hg-test-mode-map) | |||
|
50 | (set-syntax-table hg-test-mode-syntax-table) | |||
|
51 | (set (make-local-variable 'font-lock-defaults) '(hg-test-font-lock-keywords)) | |||
|
52 | (setq major-mode 'hg-test-mode) | |||
|
53 | (setq mode-name "hg-test") | |||
|
54 | (run-hooks 'hg-test-mode-hook)) | |||
|
55 | ||||
|
56 | (provide 'hg-test-mode) |
@@ -0,0 +1,109 b'' | |||||
|
1 | # A minimal client for Mercurial's command server | |||
|
2 | ||||
|
3 | import os, sys, signal, struct, socket, subprocess, time, cStringIO | |||
|
4 | ||||
|
5 | def connectpipe(path=None): | |||
|
6 | cmdline = ['hg', 'serve', '--cmdserver', 'pipe'] | |||
|
7 | if path: | |||
|
8 | cmdline += ['-R', path] | |||
|
9 | ||||
|
10 | server = subprocess.Popen(cmdline, stdin=subprocess.PIPE, | |||
|
11 | stdout=subprocess.PIPE) | |||
|
12 | ||||
|
13 | return server | |||
|
14 | ||||
|
15 | class unixconnection(object): | |||
|
16 | def __init__(self, sockpath): | |||
|
17 | self.sock = sock = socket.socket(socket.AF_UNIX) | |||
|
18 | sock.connect(sockpath) | |||
|
19 | self.stdin = sock.makefile('wb') | |||
|
20 | self.stdout = sock.makefile('rb') | |||
|
21 | ||||
|
22 | def wait(self): | |||
|
23 | self.stdin.close() | |||
|
24 | self.stdout.close() | |||
|
25 | self.sock.close() | |||
|
26 | ||||
|
27 | class unixserver(object): | |||
|
28 | def __init__(self, sockpath, logpath=None, repopath=None): | |||
|
29 | self.sockpath = sockpath | |||
|
30 | cmdline = ['hg', 'serve', '--cmdserver', 'unix', '-a', sockpath] | |||
|
31 | if repopath: | |||
|
32 | cmdline += ['-R', repopath] | |||
|
33 | if logpath: | |||
|
34 | stdout = open(logpath, 'a') | |||
|
35 | stderr = subprocess.STDOUT | |||
|
36 | else: | |||
|
37 | stdout = stderr = None | |||
|
38 | self.server = subprocess.Popen(cmdline, stdout=stdout, stderr=stderr) | |||
|
39 | # wait for listen() | |||
|
40 | while self.server.poll() is None: | |||
|
41 | if os.path.exists(sockpath): | |||
|
42 | break | |||
|
43 | time.sleep(0.1) | |||
|
44 | ||||
|
45 | def connect(self): | |||
|
46 | return unixconnection(self.sockpath) | |||
|
47 | ||||
|
48 | def shutdown(self): | |||
|
49 | os.kill(self.server.pid, signal.SIGTERM) | |||
|
50 | self.server.wait() | |||
|
51 | ||||
|
52 | def writeblock(server, data): | |||
|
53 | server.stdin.write(struct.pack('>I', len(data))) | |||
|
54 | server.stdin.write(data) | |||
|
55 | server.stdin.flush() | |||
|
56 | ||||
|
57 | def readchannel(server): | |||
|
58 | data = server.stdout.read(5) | |||
|
59 | if not data: | |||
|
60 | raise EOFError | |||
|
61 | channel, length = struct.unpack('>cI', data) | |||
|
62 | if channel in 'IL': | |||
|
63 | return channel, length | |||
|
64 | else: | |||
|
65 | return channel, server.stdout.read(length) | |||
|
66 | ||||
|
67 | def sep(text): | |||
|
68 | return text.replace('\\', '/') | |||
|
69 | ||||
|
70 | def runcommand(server, args, output=sys.stdout, error=sys.stderr, input=None, | |||
|
71 | outfilter=lambda x: x): | |||
|
72 | print '*** runcommand', ' '.join(args) | |||
|
73 | sys.stdout.flush() | |||
|
74 | server.stdin.write('runcommand\n') | |||
|
75 | writeblock(server, '\0'.join(args)) | |||
|
76 | ||||
|
77 | if not input: | |||
|
78 | input = cStringIO.StringIO() | |||
|
79 | ||||
|
80 | while True: | |||
|
81 | ch, data = readchannel(server) | |||
|
82 | if ch == 'o': | |||
|
83 | output.write(outfilter(data)) | |||
|
84 | output.flush() | |||
|
85 | elif ch == 'e': | |||
|
86 | error.write(data) | |||
|
87 | error.flush() | |||
|
88 | elif ch == 'I': | |||
|
89 | writeblock(server, input.read(data)) | |||
|
90 | elif ch == 'L': | |||
|
91 | writeblock(server, input.readline(data)) | |||
|
92 | elif ch == 'r': | |||
|
93 | ret, = struct.unpack('>i', data) | |||
|
94 | if ret != 0: | |||
|
95 | print ' [%d]' % ret | |||
|
96 | return ret | |||
|
97 | else: | |||
|
98 | print "unexpected channel %c: %r" % (ch, data) | |||
|
99 | if ch.isupper(): | |||
|
100 | return | |||
|
101 | ||||
|
102 | def check(func, connect=connectpipe): | |||
|
103 | sys.stdout.flush() | |||
|
104 | server = connect() | |||
|
105 | try: | |||
|
106 | return func(server) | |||
|
107 | finally: | |||
|
108 | server.stdin.close() | |||
|
109 | server.wait() |
@@ -0,0 +1,56 b'' | |||||
|
1 | A dummy certificate that will make OS X 10.6+ Python use the system CA | |||
|
2 | certificate store: | |||
|
3 | ||||
|
4 | -----BEGIN CERTIFICATE----- | |||
|
5 | MIIBIzCBzgIJANjmj39sb3FmMA0GCSqGSIb3DQEBBQUAMBkxFzAVBgNVBAMTDmhn | |||
|
6 | LmV4YW1wbGUuY29tMB4XDTE0MDgzMDA4NDU1OVoXDTE0MDgyOTA4NDU1OVowGTEX | |||
|
7 | MBUGA1UEAxMOaGcuZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEA | |||
|
8 | mh/ZySGlcq0ALNLmA1gZqt61HruywPrRk6WyrLJRgt+X7OP9FFlEfl2tzHfzqvmK | |||
|
9 | CtSQoPINWOdAJMekBYFgKQIDAQABMA0GCSqGSIb3DQEBBQUAA0EAF9h49LkSqJ6a | |||
|
10 | IlpogZuUHtihXeKZBsiktVIDlDccYsNy0RSh9XxUfhk+XMLw8jBlYvcltSXdJ7We | |||
|
11 | aKdQRekuMQ== | |||
|
12 | -----END CERTIFICATE----- | |||
|
13 | ||||
|
14 | This certificate was generated to be syntactically valid but never be usable; | |||
|
15 | it expired before it became valid. | |||
|
16 | ||||
|
17 | Created as: | |||
|
18 | ||||
|
19 | $ cat > cn.conf << EOT | |||
|
20 | > [req] | |||
|
21 | > distinguished_name = req_distinguished_name | |||
|
22 | > [req_distinguished_name] | |||
|
23 | > commonName = Common Name | |||
|
24 | > commonName_default = no.example.com | |||
|
25 | > EOT | |||
|
26 | $ openssl req -nodes -new -x509 -keyout /dev/null \ | |||
|
27 | > -out dummycert.pem -days -1 -config cn.conf -subj '/CN=hg.example.com' | |||
|
28 | ||||
|
29 | To verify the content of this certificate: | |||
|
30 | ||||
|
31 | $ openssl x509 -in dummycert.pem -noout -text | |||
|
32 | Certificate: | |||
|
33 | Data: | |||
|
34 | Version: 1 (0x0) | |||
|
35 | Serial Number: 15629337334278746470 (0xd8e68f7f6c6f7166) | |||
|
36 | Signature Algorithm: sha1WithRSAEncryption | |||
|
37 | Issuer: CN=hg.example.com | |||
|
38 | Validity | |||
|
39 | Not Before: Aug 30 08:45:59 2014 GMT | |||
|
40 | Not After : Aug 29 08:45:59 2014 GMT | |||
|
41 | Subject: CN=hg.example.com | |||
|
42 | Subject Public Key Info: | |||
|
43 | Public Key Algorithm: rsaEncryption | |||
|
44 | Public-Key: (512 bit) | |||
|
45 | Modulus: | |||
|
46 | 00:9a:1f:d9:c9:21:a5:72:ad:00:2c:d2:e6:03:58: | |||
|
47 | 19:aa:de:b5:1e:bb:b2:c0:fa:d1:93:a5:b2:ac:b2: | |||
|
48 | 51:82:df:97:ec:e3:fd:14:59:44:7e:5d:ad:cc:77: | |||
|
49 | f3:aa:f9:8a:0a:d4:90:a0:f2:0d:58:e7:40:24:c7: | |||
|
50 | a4:05:81:60:29 | |||
|
51 | Exponent: 65537 (0x10001) | |||
|
52 | Signature Algorithm: sha1WithRSAEncryption | |||
|
53 | 17:d8:78:f4:b9:12:a8:9e:9a:22:5a:68:81:9b:94:1e:d8:a1: | |||
|
54 | 5d:e2:99:06:c8:a4:b5:52:03:94:37:1c:62:c3:72:d1:14:a1: | |||
|
55 | f5:7c:54:7e:19:3e:5c:c2:f0:f2:30:65:62:f7:25:b5:25:dd: | |||
|
56 | 27:b5:9e:68:a7:50:45:e9:2e:31 |
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 |
@@ -42,6 +42,7 b' mercurial.egg-info' | |||||
42 | .DS_Store |
|
42 | .DS_Store | |
43 | tags |
|
43 | tags | |
44 | cscope.* |
|
44 | cscope.* | |
|
45 | .idea/* | |||
45 | i18n/hg.pot |
|
46 | i18n/hg.pot | |
46 | locale/*/LC_MESSAGES/hg.mo |
|
47 | locale/*/LC_MESSAGES/hg.mo | |
47 | hgext/__index__.py |
|
48 | hgext/__index__.py |
@@ -56,7 +56,8 b' clean:' | |||||
56 | find contrib doc hgext i18n mercurial tests \ |
|
56 | find contrib doc hgext i18n mercurial tests \ | |
57 | \( -name '*.py[cdo]' -o -name '*.so' \) -exec rm -f '{}' ';' |
|
57 | \( -name '*.py[cdo]' -o -name '*.so' \) -exec rm -f '{}' ';' | |
58 | rm -f $(addprefix mercurial/,$(notdir $(wildcard mercurial/pure/[a-z]*.py))) |
|
58 | rm -f $(addprefix mercurial/,$(notdir $(wildcard mercurial/pure/[a-z]*.py))) | |
59 |
rm -f MANIFEST MANIFEST.in |
|
59 | rm -f MANIFEST MANIFEST.in hgext/__index__.py tests/*.err | |
|
60 | if test -d .hg; then rm -f mercurial/__version__.py; fi | |||
60 | rm -rf build mercurial/locale |
|
61 | rm -rf build mercurial/locale | |
61 | $(MAKE) -C doc clean |
|
62 | $(MAKE) -C doc clean | |
62 |
|
63 | |||
@@ -135,23 +136,34 b' i18n/hg.pot: $(PYFILES) $(DOCFILES) i18n' | |||||
135 | # Packaging targets |
|
136 | # Packaging targets | |
136 |
|
137 | |||
137 | osx: |
|
138 | osx: | |
138 |
@which |
|
139 | @which bdist_mpkg >/dev/null || \ | |
139 | (echo "Missing bdist_mpkg (easy_install bdist_mpkg)"; false) |
|
140 | (echo "Missing bdist_mpkg (easy_install bdist_mpkg)"; false) | |
|
141 | rm -rf dist/mercurial-*.mpkg | |||
140 | bdist_mpkg setup.py |
|
142 | bdist_mpkg setup.py | |
141 | mkdir -p packages/osx |
|
143 | mkdir -p packages/osx | |
|
144 | N=`cd dist && echo mercurial-*.mpkg | sed 's,\.mpkg$$,,'` && hdiutil create -srcfolder dist/$$N.mpkg/ -scrub -volname "$$N" -ov packages/osx/$$N.dmg | |||
142 | rm -rf dist/mercurial-*.mpkg |
|
145 | rm -rf dist/mercurial-*.mpkg | |
143 | mv dist/mercurial*macosx*.zip packages/osx |
|
|||
144 |
|
146 | |||
145 | fedora: |
|
147 | fedora20: | |
146 | mkdir -p packages/fedora |
|
148 | mkdir -p packages/fedora20 | |
147 | contrib/buildrpm |
|
149 | contrib/buildrpm | |
148 | cp rpmbuild/RPMS/*/* packages/fedora |
|
150 | cp rpmbuild/RPMS/*/* packages/fedora20 | |
149 | cp rpmbuild/SRPMS/* packages/fedora |
|
151 | cp rpmbuild/SRPMS/* packages/fedora20 | |
150 | rm -rf rpmbuild |
|
152 | rm -rf rpmbuild | |
151 |
|
153 | |||
152 | docker-fedora: |
|
154 | docker-fedora20: | |
153 | mkdir -p packages/fedora |
|
155 | mkdir -p packages/fedora20 | |
154 | contrib/dockerrpm fedora |
|
156 | contrib/dockerrpm fedora20 | |
|
157 | ||||
|
158 | centos5: | |||
|
159 | mkdir -p packages/centos5 | |||
|
160 | contrib/buildrpm --withpython | |||
|
161 | cp rpmbuild/RPMS/*/* packages/centos5 | |||
|
162 | cp rpmbuild/SRPMS/* packages/centos5 | |||
|
163 | ||||
|
164 | docker-centos5: | |||
|
165 | mkdir -p packages/centos5 | |||
|
166 | contrib/dockerrpm centos5 --withpython | |||
155 |
|
167 | |||
156 | centos6: |
|
168 | centos6: | |
157 | mkdir -p packages/centos6 |
|
169 | mkdir -p packages/centos6 | |
@@ -163,6 +175,16 b' docker-centos6:' | |||||
163 | mkdir -p packages/centos6 |
|
175 | mkdir -p packages/centos6 | |
164 | contrib/dockerrpm centos6 |
|
176 | contrib/dockerrpm centos6 | |
165 |
|
177 | |||
|
178 | centos7: | |||
|
179 | mkdir -p packages/centos7 | |||
|
180 | contrib/buildrpm | |||
|
181 | cp rpmbuild/RPMS/*/* packages/centos7 | |||
|
182 | cp rpmbuild/SRPMS/* packages/centos7 | |||
|
183 | ||||
|
184 | docker-centos7: | |||
|
185 | mkdir -p packages/centos7 | |||
|
186 | contrib/dockerrpm centos7 | |||
|
187 | ||||
166 | .PHONY: help all local build doc clean install install-bin install-doc \ |
|
188 | .PHONY: help all local build doc clean install install-bin install-doc \ | |
167 | install-home install-home-bin install-home-doc dist dist-notests tests \ |
|
189 | install-home install-home-bin install-home-doc dist dist-notests tests \ | |
168 | update-pot fedora docker-fedora |
|
190 | update-pot fedora20 docker-fedora20 |
@@ -7,9 +7,33 b'' | |||||
7 | # - CentOS 5 |
|
7 | # - CentOS 5 | |
8 | # - centOS 6 |
|
8 | # - centOS 6 | |
9 |
|
9 | |||
|
10 | BUILD=1 | |||
|
11 | RPMBUILDDIR="$PWD/rpmbuild" | |||
|
12 | while [ "$1" ]; do | |||
|
13 | case "$1" in | |||
|
14 | --prepare ) | |||
|
15 | shift | |||
|
16 | BUILD= | |||
|
17 | ;; | |||
|
18 | --withpython | --with-python) | |||
|
19 | shift | |||
|
20 | PYTHONVER=2.7.8 | |||
|
21 | ;; | |||
|
22 | --rpmbuilddir ) | |||
|
23 | shift | |||
|
24 | RPMBUILDDIR="$1" | |||
|
25 | shift | |||
|
26 | ;; | |||
|
27 | * ) | |||
|
28 | echo "Invalid parameter $1!" 1>&2 | |||
|
29 | exit 1 | |||
|
30 | ;; | |||
|
31 | esac | |||
|
32 | done | |||
|
33 | ||||
10 | cd "`dirname $0`/.." |
|
34 | cd "`dirname $0`/.." | |
11 |
|
35 | |||
12 | specfile=contrib/mercurial.spec |
|
36 | specfile=$PWD/contrib/mercurial.spec | |
13 | if [ ! -f $specfile ]; then |
|
37 | if [ ! -f $specfile ]; then | |
14 | echo "Cannot find $specfile!" 1>&2 |
|
38 | echo "Cannot find $specfile!" 1>&2 | |
15 | exit 1 |
|
39 | exit 1 | |
@@ -26,10 +50,7 b' HG="$PWD/hg"' | |||||
26 | PYTHONPATH="$PWD/mercurial/pure" |
|
50 | PYTHONPATH="$PWD/mercurial/pure" | |
27 | export PYTHONPATH |
|
51 | export PYTHONPATH | |
28 |
|
52 | |||
29 | rpmdir="$PWD/rpmbuild" |
|
53 | mkdir -p $RPMBUILDDIR/SOURCES $RPMBUILDDIR/SPECS $RPMBUILDDIR/RPMS $RPMBUILDDIR/SRPMS $RPMBUILDDIR/BUILD | |
30 |
|
||||
31 | rm -rf $rpmdir |
|
|||
32 | mkdir -p $rpmdir/SOURCES $rpmdir/SPECS $rpmdir/RPMS $rpmdir/SRPMS $rpmdir/BUILD |
|
|||
33 |
|
54 | |||
34 | hgversion=`$HG version | sed -ne 's/.*(version \(.*\))$/\1/p'` |
|
55 | hgversion=`$HG version | sed -ne 's/.*(version \(.*\))$/\1/p'` | |
35 |
|
56 | |||
@@ -42,9 +63,29 b' else' | |||||
42 | version=`echo $hgversion | sed -e 's/+.*//'` |
|
63 | version=`echo $hgversion | sed -e 's/+.*//'` | |
43 | release='0' |
|
64 | release='0' | |
44 | fi |
|
65 | fi | |
|
66 | if [ "$PYTHONVER" ]; then | |||
|
67 | release=$release+$PYTHONVER | |||
|
68 | RPMPYTHONVER=$PYTHONVER | |||
|
69 | else | |||
|
70 | RPMPYTHONVER=%{nil} | |||
|
71 | fi | |||
45 |
|
72 | |||
46 |
$HG archive -t tgz $ |
|
73 | $HG archive -t tgz $RPMBUILDDIR/SOURCES/mercurial-$version-$release.tar.gz | |
47 | rpmspec=$rpmdir/SPECS/mercurial.spec |
|
74 | if [ "$PYTHONVER" ]; then | |
|
75 | ( | |||
|
76 | cd build | |||
|
77 | PYTHON_SRCFILE=Python-$PYTHONVER.tgz | |||
|
78 | [ -f $PYTHON_SRCFILE ] || curl -Lo $PYTHON_SRCFILE http://www.python.org/ftp/python/$PYTHONVER/$PYTHON_SRCFILE | |||
|
79 | ln -f $PYTHON_SRCFILE $RPMBUILDDIR/SOURCES/$PYTHON_SRCFILE | |||
|
80 | ||||
|
81 | DOCUTILSVER=`sed -ne "s/^%global docutilsname docutils-//p" $specfile` | |||
|
82 | DOCUTILS_SRCFILE=docutils-$DOCUTILSVER.tar.gz | |||
|
83 | [ -f $DOCUTILS_SRCFILE ] || curl -Lo $DOCUTILS_SRCFILE http://downloads.sourceforge.net/project/docutils/docutils/$DOCUTILSVER/$DOCUTILS_SRCFILE | |||
|
84 | ln -f $DOCUTILS_SRCFILE $RPMBUILDDIR/SOURCES/$DOCUTILS_SRCFILE | |||
|
85 | ) | |||
|
86 | fi | |||
|
87 | ||||
|
88 | rpmspec=$RPMBUILDDIR/SPECS/mercurial.spec | |||
48 |
|
89 | |||
49 | sed -e "s,^Version:.*,Version: $version," \ |
|
90 | sed -e "s,^Version:.*,Version: $version," \ | |
50 | -e "s,^Release:.*,Release: $release," \ |
|
91 | -e "s,^Release:.*,Release: $release," \ | |
@@ -95,9 +136,18 b' for l in sys.stdin.readlines():' | |||||
95 |
|
136 | |||
96 | fi |
|
137 | fi | |
97 |
|
138 | |||
98 | rpmbuild --define "_topdir $rpmdir" -ba $rpmspec --clean |
|
139 | sed -i \ | |
99 | if [ $? = 0 ]; then |
|
140 | -e "s/^%define withpython.*$/%define withpython $RPMPYTHONVER/" \ | |
100 |
ec |
|
141 | $rpmspec | |
101 | echo "Packages are in $rpmdir:" |
|
142 | ||
102 | ls -l $rpmdir/*RPMS/* |
|
143 | if [ "$BUILD" ]; then | |
|
144 | rpmbuild --define "_topdir $RPMBUILDDIR" -ba $rpmspec --clean | |||
|
145 | if [ $? = 0 ]; then | |||
|
146 | echo | |||
|
147 | echo "Built packages for $version-$release:" | |||
|
148 | find $RPMBUILDDIR/*RPMS/ -type f -newer $rpmspec | |||
|
149 | fi | |||
|
150 | else | |||
|
151 | echo "Prepared sources for $version-$release $rpmspec are in $RPMBUILDDIR/SOURCES/ - use like:" | |||
|
152 | echo "rpmbuild --define '_topdir $RPMBUILDDIR' -ba $rpmspec --clean" | |||
103 | fi |
|
153 | fi |
@@ -94,7 +94,7 b' testpats = [' | |||||
94 | (r'sed.*-i', "don't use 'sed -i', use a temporary file"), |
|
94 | (r'sed.*-i', "don't use 'sed -i', use a temporary file"), | |
95 | (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"), |
|
95 | (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"), | |
96 | (r'echo -n', "don't use 'echo -n', use printf"), |
|
96 | (r'echo -n', "don't use 'echo -n', use printf"), | |
97 | (r'(^| )wc[^|]*$\n(?!.*\(re\))', "filter wc output"), |
|
97 | (r'(^| )\bwc\b[^|]*$\n(?!.*\(re\))', "filter wc output"), | |
98 | (r'head -c', "don't use 'head -c', use 'dd'"), |
|
98 | (r'head -c', "don't use 'head -c', use 'dd'"), | |
99 | (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"), |
|
99 | (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"), | |
100 | (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"), |
|
100 | (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"), | |
@@ -179,12 +179,14 b' utestpats = [' | |||||
179 | ] |
|
179 | ] | |
180 |
|
180 | |||
181 | for i in [0, 1]: |
|
181 | for i in [0, 1]: | |
182 |
for p |
|
182 | for tp in testpats[i]: | |
|
183 | p = tp[0] | |||
|
184 | m = tp[1] | |||
183 | if p.startswith(r'^'): |
|
185 | if p.startswith(r'^'): | |
184 | p = r"^ [$>] (%s)" % p[1:] |
|
186 | p = r"^ [$>] (%s)" % p[1:] | |
185 | else: |
|
187 | else: | |
186 | p = r"^ [$>] .*(%s)" % p |
|
188 | p = r"^ [$>] .*(%s)" % p | |
187 | utestpats[i].append((p, m)) |
|
189 | utestpats[i].append((p, m) + tp[2:]) | |
188 |
|
190 | |||
189 | utestfilters = [ |
|
191 | utestfilters = [ | |
190 | (r"<<(\S+)((.|\n)*?\n > \1)", rephere), |
|
192 | (r"<<(\S+)((.|\n)*?\n > \1)", rephere), | |
@@ -214,8 +216,9 b' pypats = [' | |||||
214 | (r'(\w|\)),\w', "missing whitespace after ,"), |
|
216 | (r'(\w|\)),\w', "missing whitespace after ,"), | |
215 | (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"), |
|
217 | (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"), | |
216 | (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"), |
|
218 | (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"), | |
217 | (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)\1except.*?:\n' |
|
219 | (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)(\1except.*?:\n' | |
218 |
r'((?:\n|\1\s.*\n)+?)\1finally:', |
|
220 | r'((?:\n|\1\s.*\n)+?))+\1finally:', | |
|
221 | 'no try/except/finally in Python 2.4'), | |||
219 | (r'(?<!def)(\s+|^|\()next\(.+\)', |
|
222 | (r'(?<!def)(\s+|^|\()next\(.+\)', | |
220 | 'no next(foo) in Python 2.4 and 2.5, use foo.next() instead'), |
|
223 | 'no next(foo) in Python 2.4 and 2.5, use foo.next() instead'), | |
221 | (r'(\s+)try:\n((?:\n|\1\s.*\n)*?)\1\s*yield\b.*?' |
|
224 | (r'(\s+)try:\n((?:\n|\1\s.*\n)*?)\1\s*yield\b.*?' | |
@@ -296,6 +299,7 b' pypats = [' | |||||
296 | (r'ui\.(status|progress|write|note|warn)\([\'\"]x', |
|
299 | (r'ui\.(status|progress|write|note|warn)\([\'\"]x', | |
297 | "missing _() in ui message (use () to hide false-positives)"), |
|
300 | "missing _() in ui message (use () to hide false-positives)"), | |
298 | (r'release\(.*wlock, .*lock\)', "wrong lock release order"), |
|
301 | (r'release\(.*wlock, .*lock\)', "wrong lock release order"), | |
|
302 | (r'\b__bool__\b', "__bool__ should be __nonzero__ in Python 2"), | |||
299 | ], |
|
303 | ], | |
300 | # warnings |
|
304 | # warnings | |
301 | [ |
|
305 | [ |
@@ -1,7 +1,9 b'' | |||||
1 | FROM centos |
|
1 | FROM centos:centos6 | |
2 | RUN yum install -y gcc |
|
2 | RUN yum install -y gcc | |
3 | RUN yum install -y python-devel python-docutils |
|
3 | RUN yum install -y python-devel python-docutils | |
4 | RUN yum install -y make |
|
4 | RUN yum install -y make | |
5 | RUN yum install -y rpm-build |
|
5 | RUN yum install -y rpm-build | |
6 | RUN yum install -y gettext |
|
6 | RUN yum install -y gettext | |
7 | RUN yum install -y tar |
|
7 | RUN yum install -y tar | |
|
8 | # For creating repo meta data | |||
|
9 | RUN yum install -y createrepo |
@@ -1,7 +1,9 b'' | |||||
1 | FROM centos |
|
1 | FROM centos:centos7 | |
2 | RUN yum install -y gcc |
|
2 | RUN yum install -y gcc | |
3 | RUN yum install -y python-devel python-docutils |
|
3 | RUN yum install -y python-devel python-docutils | |
4 | RUN yum install -y make |
|
4 | RUN yum install -y make | |
5 | RUN yum install -y rpm-build |
|
5 | RUN yum install -y rpm-build | |
6 | RUN yum install -y gettext |
|
6 | RUN yum install -y gettext | |
7 | RUN yum install -y tar |
|
7 | RUN yum install -y tar | |
|
8 | # For creating repo meta data | |||
|
9 | RUN yum install -y createrepo |
@@ -1,6 +1,8 b'' | |||||
1 | FROM fedora |
|
1 | FROM fedora:20 | |
2 | RUN yum install -y gcc |
|
2 | RUN yum install -y gcc | |
3 | RUN yum install -y python-devel python-docutils |
|
3 | RUN yum install -y python-devel python-docutils | |
4 | RUN yum install -y make |
|
4 | RUN yum install -y make | |
5 | RUN yum install -y rpm-build |
|
5 | RUN yum install -y rpm-build | |
6 | RUN yum install -y gettext |
|
6 | RUN yum install -y gettext | |
|
7 | # For creating repo meta data | |||
|
8 | RUN yum install -y createrepo |
@@ -1,14 +1,57 b'' | |||||
1 | #!/bin/bash |
|
1 | #!/bin/bash -e | |
2 |
|
2 | |||
3 | BUILDDIR=$(dirname $0) |
|
3 | BUILDDIR=$(dirname $0) | |
4 | ROOTDIR=$(cd $BUILDDIR/..; pwd) |
|
4 | ROOTDIR=$(cd $BUILDDIR/..; pwd) | |
5 |
|
5 | |||
6 | if which docker.io >> /dev/null ; then |
|
6 | if which docker.io >> /dev/null 2>&1 ; then | |
7 | DOCKER=docker.io |
|
7 | DOCKER=docker.io | |
8 | elif which docker >> /dev/null ; then |
|
8 | elif which docker >> /dev/null 2>&1 ; then | |
9 | DOCKER=docker |
|
9 | DOCKER=docker | |
|
10 | else | |||
|
11 | echo "Error: docker must be installed" | |||
|
12 | exit 1 | |||
10 | fi |
|
13 | fi | |
11 |
|
14 | |||
12 | $DOCKER build --tag "hg-dockerrpm-$1" - < $BUILDDIR/docker/$1 |
|
15 | $DOCKER -h 2> /dev/null | grep -q Jansens && { echo "Error: $DOCKER is the Docking System Tray - install docker.io instead"; exit 1; } | |
13 | $DOCKER run --rm -v $ROOTDIR:/hg "hg-dockerrpm-$1" bash -c \ |
|
16 | $DOCKER version | grep -q "^Client version:" || { echo "Error: unexpected output from \"$DOCKER version\""; exit 1; } | |
14 | "cp -a hg hg-build; cd hg-build; make clean local $1; cp packages/$1/* /hg/packages/$1/" |
|
17 | $DOCKER version | grep -q "^Server version:" || { echo "Error: could not get docker server version - check it is running and your permissions"; exit 1; } | |
|
18 | ||||
|
19 | PLATFORM="$1" | |||
|
20 | [ "$PLATFORM" ] || { echo "Error: platform name must be specified"; exit 1; } | |||
|
21 | shift # extra params are passed to buildrpm | |||
|
22 | ||||
|
23 | DFILE="$ROOTDIR/contrib/docker/$PLATFORM" | |||
|
24 | [ -f "$DFILE" ] || { echo "Error: docker file $DFILE not found"; exit 1; } | |||
|
25 | ||||
|
26 | CONTAINER="hg-dockerrpm-$PLATFORM" | |||
|
27 | ||||
|
28 | DBUILDUSER=build | |||
|
29 | ( | |||
|
30 | cat $DFILE | |||
|
31 | echo RUN groupadd $DBUILDUSER -g `id -g` | |||
|
32 | echo RUN useradd $DBUILDUSER -u `id -u` -g $DBUILDUSER | |||
|
33 | ) | $DOCKER build --tag $CONTAINER - | |||
|
34 | ||||
|
35 | RPMBUILDDIR=$ROOTDIR/packages/$PLATFORM | |||
|
36 | contrib/buildrpm --rpmbuilddir $RPMBUILDDIR --prepare $* | |||
|
37 | ||||
|
38 | DSHARED=/mnt/shared | |||
|
39 | $DOCKER run -u $DBUILDUSER --rm -v $RPMBUILDDIR:$DSHARED $CONTAINER \ | |||
|
40 | rpmbuild --define "_topdir $DSHARED" -ba $DSHARED/SPECS/mercurial.spec --clean | |||
|
41 | ||||
|
42 | $DOCKER run -u $DBUILDUSER --rm -v $RPMBUILDDIR:$DSHARED $CONTAINER \ | |||
|
43 | createrepo $DSHARED | |||
|
44 | ||||
|
45 | cat << EOF > $RPMBUILDDIR/mercurial.repo | |||
|
46 | # Place this file in /etc/yum.repos.d/mercurial.repo | |||
|
47 | [mercurial] | |||
|
48 | name=Mercurial packages for $NAME | |||
|
49 | # baseurl=file://$RPMBUILDDIR/ | |||
|
50 | baseurl=http://hg.example.com/build/$NAME/ | |||
|
51 | skip_if_unavailable=True | |||
|
52 | gpgcheck=0 | |||
|
53 | enabled=1 | |||
|
54 | EOF | |||
|
55 | ||||
|
56 | echo | |||
|
57 | echo "Build complete - results can be found in $RPMBUILDDIR" |
@@ -1271,9 +1271,9 b' proc drawtags {id x xt y1} {' | |||||
1271 | set rowtextx($idline($id)) [expr {$xr + $linespc}] |
|
1271 | set rowtextx($idline($id)) [expr {$xr + $linespc}] | |
1272 | } elseif {[incr nbookmarks -1] >= 0} { |
|
1272 | } elseif {[incr nbookmarks -1] >= 0} { | |
1273 | # draw a tag |
|
1273 | # draw a tag | |
1274 |
set col |
|
1274 | set col "#7f7f7f" | |
1275 | if {[string compare $bookmarkcurrent $tag] == 0} { |
|
1275 | if {[string compare $bookmarkcurrent $tag] == 0} { | |
1276 |
set col |
|
1276 | set col "#bebebe" | |
1277 | } |
|
1277 | } | |
1278 | set xl [expr $xl - $delta/2] |
|
1278 | set xl [expr $xl - $delta/2] | |
1279 | $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \ |
|
1279 | $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \ |
@@ -8,11 +8,13 b' import sys' | |||||
8 | import BaseHTTPServer |
|
8 | import BaseHTTPServer | |
9 | import zlib |
|
9 | import zlib | |
10 |
|
10 | |||
11 | def dotted_name_of_path(path): |
|
11 | def dotted_name_of_path(path, trimpure=False): | |
12 | """Given a relative path to a source file, return its dotted module name. |
|
12 | """Given a relative path to a source file, return its dotted module name. | |
13 |
|
13 | |||
14 | >>> dotted_name_of_path('mercurial/error.py') |
|
14 | >>> dotted_name_of_path('mercurial/error.py') | |
15 | 'mercurial.error' |
|
15 | 'mercurial.error' | |
|
16 | >>> dotted_name_of_path('mercurial/pure/parsers.py', trimpure=True) | |||
|
17 | 'mercurial.parsers' | |||
16 | >>> dotted_name_of_path('zlibmodule.so') |
|
18 | >>> dotted_name_of_path('zlibmodule.so') | |
17 | 'zlib' |
|
19 | 'zlib' | |
18 | """ |
|
20 | """ | |
@@ -20,6 +22,8 b' def dotted_name_of_path(path):' | |||||
20 | parts[-1] = parts[-1].split('.', 1)[0] # remove .py and .so and .ARCH.so |
|
22 | parts[-1] = parts[-1].split('.', 1)[0] # remove .py and .so and .ARCH.so | |
21 | if parts[-1].endswith('module'): |
|
23 | if parts[-1].endswith('module'): | |
22 | parts[-1] = parts[-1][:-6] |
|
24 | parts[-1] = parts[-1][:-6] | |
|
25 | if trimpure: | |||
|
26 | return '.'.join(p for p in parts if p != 'pure') | |||
23 | return '.'.join(parts) |
|
27 | return '.'.join(parts) | |
24 |
|
28 | |||
25 |
|
29 | |||
@@ -169,7 +173,7 b' def check_one_mod(mod, imports, path=Non' | |||||
169 | ignore = [] |
|
173 | ignore = [] | |
170 | path = path + [mod] |
|
174 | path = path + [mod] | |
171 | for i in sorted(imports.get(mod, [])): |
|
175 | for i in sorted(imports.get(mod, [])): | |
172 | if i not in stdlib_modules: |
|
176 | if i not in stdlib_modules and not i.startswith('mercurial.'): | |
173 | i = mod.rsplit('.', 1)[0] + '.' + i |
|
177 | i = mod.rsplit('.', 1)[0] + '.' + i | |
174 | if i in path: |
|
178 | if i in path: | |
175 | firstspot = path.index(i) |
|
179 | firstspot = path.index(i) | |
@@ -220,7 +224,7 b' def main(argv):' | |||||
220 | any_errors = False |
|
224 | any_errors = False | |
221 | for source_path in argv[1:]: |
|
225 | for source_path in argv[1:]: | |
222 | f = open(source_path) |
|
226 | f = open(source_path) | |
223 | modname = dotted_name_of_path(source_path) |
|
227 | modname = dotted_name_of_path(source_path, trimpure=True) | |
224 | src = f.read() |
|
228 | src = f.read() | |
225 | used_imports[modname] = sorted( |
|
229 | used_imports[modname] = sorted( | |
226 | imported_modules(src, ignore_nested=True)) |
|
230 | imported_modules(src, ignore_nested=True)) |
@@ -1,6 +1,23 b'' | |||||
1 | %global emacs_lispdir %{_datadir}/emacs/site-lisp |
|
1 | %global emacs_lispdir %{_datadir}/emacs/site-lisp | |
|
2 | ||||
|
3 | %define withpython %{nil} | |||
|
4 | ||||
|
5 | %if "%{?withpython}" | |||
|
6 | ||||
|
7 | %global pythonver %{withpython} | |||
|
8 | %global pythonname Python-%{withpython} | |||
|
9 | %global docutilsname docutils-0.11 | |||
|
10 | %global pythonhg python-hg | |||
|
11 | %global hgpyprefix /usr/%{pythonhg} | |||
|
12 | # byte compilation will fail on some some Python /test/ files | |||
|
13 | %global _python_bytecompile_errors_terminate_build 0 | |||
|
14 | ||||
|
15 | %else | |||
|
16 | ||||
2 | %global pythonver %(python -c 'import sys;print ".".join(map(str, sys.version_info[:2]))') |
|
17 | %global pythonver %(python -c 'import sys;print ".".join(map(str, sys.version_info[:2]))') | |
3 |
|
18 | |||
|
19 | %endif | |||
|
20 | ||||
4 | Summary: A fast, lightweight Source Control Management system |
|
21 | Summary: A fast, lightweight Source Control Management system | |
5 | Name: mercurial |
|
22 | Name: mercurial | |
6 | Version: snapshot |
|
23 | Version: snapshot | |
@@ -9,11 +26,19 b' License: GPLv2+' | |||||
9 | Group: Development/Tools |
|
26 | Group: Development/Tools | |
10 | URL: http://mercurial.selenic.com/ |
|
27 | URL: http://mercurial.selenic.com/ | |
11 | Source0: %{name}-%{version}-%{release}.tar.gz |
|
28 | Source0: %{name}-%{version}-%{release}.tar.gz | |
|
29 | %if "%{?withpython}" | |||
|
30 | Source1: %{pythonname}.tgz | |||
|
31 | Source2: %{docutilsname}.tar.gz | |||
|
32 | %endif | |||
12 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root |
|
33 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root | |
13 |
|
34 | |||
14 | BuildRequires: python >= 2.4, python-devel, make, gcc, python-docutils >= 0.5, gettext |
|
35 | BuildRequires: make, gcc, gettext | |
15 | Provides: hg = %{version}-%{release} |
|
36 | %if "%{?withpython}" | |
|
37 | BuildRequires: readline-devel, openssl-devel, ncurses-devel, zlib-devel, bzip2-devel | |||
|
38 | %else | |||
|
39 | BuildRequires: python >= 2.4, python-devel, python-docutils >= 0.5 | |||
16 | Requires: python >= 2.4 |
|
40 | Requires: python >= 2.4 | |
|
41 | %endif | |||
17 | # The hgk extension uses the wish tcl interpreter, but we don't enforce it |
|
42 | # The hgk extension uses the wish tcl interpreter, but we don't enforce it | |
18 | #Requires: tk |
|
43 | #Requires: tk | |
19 |
|
44 | |||
@@ -22,15 +47,69 b' Mercurial is a fast, lightweight source ' | |||||
22 | for efficient handling of very large distributed projects. |
|
47 | for efficient handling of very large distributed projects. | |
23 |
|
48 | |||
24 | %prep |
|
49 | %prep | |
|
50 | ||||
|
51 | %if "%{?withpython}" | |||
|
52 | %setup -q -n mercurial-%{version}-%{release} -a1 -a2 | |||
|
53 | # despite the comments in cgi.py, we do this to prevent rpmdeps from picking /usr/local/bin/python up | |||
|
54 | sed -i '1c#! /usr/bin/env python' %{pythonname}/Lib/cgi.py | |||
|
55 | %else | |||
25 | %setup -q -n mercurial-%{version}-%{release} |
|
56 | %setup -q -n mercurial-%{version}-%{release} | |
|
57 | %endif | |||
26 |
|
58 | |||
27 | %build |
|
59 | %build | |
|
60 | ||||
|
61 | %if "%{?withpython}" | |||
|
62 | ||||
|
63 | PYPATH=$PWD/%{pythonname} | |||
|
64 | cd $PYPATH | |||
|
65 | ./configure --prefix=%{hgpyprefix} | |||
|
66 | make all %{?_smp_mflags} | |||
|
67 | cd - | |||
|
68 | ||||
|
69 | cd %{docutilsname} | |||
|
70 | LD_LIBRARY_PATH=$PYPATH $PYPATH/python setup.py build | |||
|
71 | cd - | |||
|
72 | ||||
|
73 | # verify Python environment | |||
|
74 | LD_LIBRARY_PATH=$PYPATH PYTHONPATH=$PWD/%{docutilsname} $PYPATH/python -c 'import sys, zlib, bz2, ssl, curses, readline' | |||
|
75 | ||||
|
76 | # set environment for make | |||
|
77 | export PATH=$PYPATH:$PATH | |||
|
78 | export LD_LIBRARY_PATH=$PYPATH | |||
|
79 | export CFLAGS="-L $PYPATH" | |||
|
80 | export PYTHONPATH=$PWD/%{docutilsname} | |||
|
81 | ||||
|
82 | %endif | |||
|
83 | ||||
28 | make all |
|
84 | make all | |
29 |
|
85 | |||
30 | %install |
|
86 | %install | |
31 | rm -rf $RPM_BUILD_ROOT |
|
87 | rm -rf $RPM_BUILD_ROOT | |
|
88 | ||||
|
89 | %if "%{?withpython}" | |||
|
90 | ||||
|
91 | PYPATH=$PWD/%{pythonname} | |||
|
92 | cd $PYPATH | |||
|
93 | make install DESTDIR=$RPM_BUILD_ROOT | |||
|
94 | # these .a are not necessary and they are readonly and strip fails - kill them! | |||
|
95 | rm -f %{buildroot}%{hgpyprefix}/lib/{,python2.*/config}/libpython2.*.a | |||
|
96 | cd - | |||
|
97 | ||||
|
98 | cd %{docutilsname} | |||
|
99 | LD_LIBRARY_PATH=$PYPATH $PYPATH/python setup.py install --root="$RPM_BUILD_ROOT" | |||
|
100 | cd - | |||
|
101 | ||||
|
102 | PATH=$PYPATH:$PATH LD_LIBRARY_PATH=$PYPATH make install DESTDIR=$RPM_BUILD_ROOT PREFIX=%{hgpyprefix} MANDIR=%{_mandir} | |||
|
103 | mkdir -p $RPM_BUILD_ROOT%{_bindir} | |||
|
104 | ( cd $RPM_BUILD_ROOT%{_bindir}/ && ln -s ../..%{hgpyprefix}/bin/hg . ) | |||
|
105 | ( cd $RPM_BUILD_ROOT%{_bindir}/ && ln -s ../..%{hgpyprefix}/bin/python2.? %{pythonhg} ) | |||
|
106 | ||||
|
107 | %else | |||
|
108 | ||||
32 | make install DESTDIR=$RPM_BUILD_ROOT PREFIX=%{_prefix} MANDIR=%{_mandir} |
|
109 | make install DESTDIR=$RPM_BUILD_ROOT PREFIX=%{_prefix} MANDIR=%{_mandir} | |
33 |
|
110 | |||
|
111 | %endif | |||
|
112 | ||||
34 | install -m 755 contrib/hgk $RPM_BUILD_ROOT%{_bindir}/ |
|
113 | install -m 755 contrib/hgk $RPM_BUILD_ROOT%{_bindir}/ | |
35 | install -m 755 contrib/hg-ssh $RPM_BUILD_ROOT%{_bindir}/ |
|
114 | install -m 755 contrib/hg-ssh $RPM_BUILD_ROOT%{_bindir}/ | |
36 |
|
115 | |||
@@ -56,7 +135,7 b' rm -rf $RPM_BUILD_ROOT' | |||||
56 | %defattr(-,root,root,-) |
|
135 | %defattr(-,root,root,-) | |
57 | %doc CONTRIBUTORS COPYING doc/README doc/hg*.txt doc/hg*.html *.cgi contrib/*.fcgi |
|
136 | %doc CONTRIBUTORS COPYING doc/README doc/hg*.txt doc/hg*.html *.cgi contrib/*.fcgi | |
58 | %doc %attr(644,root,root) %{_mandir}/man?/hg* |
|
137 | %doc %attr(644,root,root) %{_mandir}/man?/hg* | |
59 |
%doc %attr(644,root,root) contrib/*.svg |
|
138 | %doc %attr(644,root,root) contrib/*.svg | |
60 | %dir %{_datadir}/zsh/ |
|
139 | %dir %{_datadir}/zsh/ | |
61 | %dir %{_datadir}/zsh/site-functions/ |
|
140 | %dir %{_datadir}/zsh/site-functions/ | |
62 | %{_datadir}/zsh/site-functions/_mercurial |
|
141 | %{_datadir}/zsh/site-functions/_mercurial | |
@@ -71,8 +150,13 b' rm -rf $RPM_BUILD_ROOT' | |||||
71 | %dir %{_sysconfdir}/mercurial |
|
150 | %dir %{_sysconfdir}/mercurial | |
72 | %dir %{_sysconfdir}/mercurial/hgrc.d |
|
151 | %dir %{_sysconfdir}/mercurial/hgrc.d | |
73 | %config(noreplace) %{_sysconfdir}/mercurial/hgrc.d/mergetools.rc |
|
152 | %config(noreplace) %{_sysconfdir}/mercurial/hgrc.d/mergetools.rc | |
|
153 | %if "%{?withpython}" | |||
|
154 | %{_bindir}/%{pythonhg} | |||
|
155 | %{hgpyprefix} | |||
|
156 | %else | |||
74 | %if "%{?pythonver}" != "2.4" |
|
157 | %if "%{?pythonver}" != "2.4" | |
75 | %{_libdir}/python%{pythonver}/site-packages/%{name}-*-py%{pythonver}.egg-info |
|
158 | %{_libdir}/python%{pythonver}/site-packages/%{name}-*-py%{pythonver}.egg-info | |
76 | %endif |
|
159 | %endif | |
77 | %{_libdir}/python%{pythonver}/site-packages/%{name} |
|
160 | %{_libdir}/python%{pythonver}/site-packages/%{name} | |
78 | %{_libdir}/python%{pythonver}/site-packages/hgext |
|
161 | %{_libdir}/python%{pythonver}/site-packages/hgext | |
|
162 | %endif |
@@ -58,6 +58,12 b' p4merge.gui=True' | |||||
58 | p4merge.priority=-8 |
|
58 | p4merge.priority=-8 | |
59 | p4merge.diffargs=$parent $child |
|
59 | p4merge.diffargs=$parent $child | |
60 |
|
60 | |||
|
61 | p4mergeosx.executable = /Applications/p4merge.app/Contents/MacOS/p4merge | |||
|
62 | p4mergeosx.args = $base $local $other $output | |||
|
63 | p4mergeosx.gui = True | |||
|
64 | p4mergeosx.priority=-8 | |||
|
65 | p4mergeosx.diffargs=$parent $child | |||
|
66 | ||||
61 | tortoisemerge.args=/base:$base /mine:$local /theirs:$other /merged:$output |
|
67 | tortoisemerge.args=/base:$base /mine:$local /theirs:$other /merged:$output | |
62 | tortoisemerge.regkey=Software\TortoiseSVN |
|
68 | tortoisemerge.regkey=Software\TortoiseSVN | |
63 | tortoisemerge.regkeyalt=Software\Wow6432Node\TortoiseSVN |
|
69 | tortoisemerge.regkeyalt=Software\Wow6432Node\TortoiseSVN |
@@ -139,6 +139,16 b' def perfdirstatedirs(ui, repo):' | |||||
139 | del repo.dirstate._dirs |
|
139 | del repo.dirstate._dirs | |
140 | timer(d) |
|
140 | timer(d) | |
141 |
|
141 | |||
|
142 | @command('perfdirstatefoldmap') | |||
|
143 | def perffoldmap(ui, repo): | |||
|
144 | dirstate = repo.dirstate | |||
|
145 | 'a' in dirstate | |||
|
146 | def d(): | |||
|
147 | dirstate._foldmap.get('a') | |||
|
148 | del dirstate._foldmap | |||
|
149 | del dirstate._dirs | |||
|
150 | timer(d) | |||
|
151 | ||||
142 | @command('perfdirstatewrite') |
|
152 | @command('perfdirstatewrite') | |
143 | def perfdirstatewrite(ui, repo): |
|
153 | def perfdirstatewrite(ui, repo): | |
144 | ds = repo.dirstate |
|
154 | ds = repo.dirstate |
@@ -19,8 +19,6 b' from subprocess import check_call, Popen' | |||||
19 | # cannot use argparse, python 2.7 only |
|
19 | # cannot use argparse, python 2.7 only | |
20 | from optparse import OptionParser |
|
20 | from optparse import OptionParser | |
21 |
|
21 | |||
22 |
|
||||
23 |
|
||||
24 | def check_output(*args, **kwargs): |
|
22 | def check_output(*args, **kwargs): | |
25 | kwargs.setdefault('stderr', PIPE) |
|
23 | kwargs.setdefault('stderr', PIPE) | |
26 | kwargs.setdefault('stdout', PIPE) |
|
24 | kwargs.setdefault('stdout', PIPE) | |
@@ -76,7 +74,8 b' def getrevs(spec):' | |||||
76 |
|
74 | |||
77 | parser = OptionParser(usage="usage: %prog [options] <revs>") |
|
75 | parser = OptionParser(usage="usage: %prog [options] <revs>") | |
78 | parser.add_option("-f", "--file", |
|
76 | parser.add_option("-f", "--file", | |
79 |
help="read revset from FILE", |
|
77 | help="read revset from FILE (stdin if omited)", | |
|
78 | metavar="FILE") | |||
80 | parser.add_option("-R", "--repo", |
|
79 | parser.add_option("-R", "--repo", | |
81 | help="run benchmark on REPO", metavar="REPO") |
|
80 | help="run benchmark on REPO", metavar="REPO") | |
82 |
|
81 | |||
@@ -95,7 +94,7 b' revsetsfile = sys.stdin' | |||||
95 | if options.file: |
|
94 | if options.file: | |
96 | revsetsfile = open(options.file) |
|
95 | revsetsfile = open(options.file) | |
97 |
|
96 | |||
98 | revsets = [l.strip() for l in revsetsfile] |
|
97 | revsets = [l.strip() for l in revsetsfile if not l.startswith('#')] | |
99 |
|
98 | |||
100 | print "Revsets to benchmark" |
|
99 | print "Revsets to benchmark" | |
101 | print "----------------------------" |
|
100 | print "----------------------------" |
@@ -14,7 +14,9 b' max(tip:0)' | |||||
14 | min(0:tip) |
|
14 | min(0:tip) | |
15 | 0:: |
|
15 | 0:: | |
16 | min(0::) |
|
16 | min(0::) | |
|
17 | # those two `roots(...)` inputs are close to what phase movement use. | |||
17 | roots((tip~100::) - (tip~100::tip)) |
|
18 | roots((tip~100::) - (tip~100::tip)) | |
|
19 | roots((0::) - (0::tip)) | |||
18 | ::p1(p1(tip)):: |
|
20 | ::p1(p1(tip)):: | |
19 | public() |
|
21 | public() | |
20 | :10000 and public() |
|
22 | :10000 and public() | |
@@ -22,3 +24,9 b' draft()' | |||||
22 | :10000 and draft() |
|
24 | :10000 and draft() | |
23 | max(::(tip~20) - obsolete()) |
|
25 | max(::(tip~20) - obsolete()) | |
24 | roots((0:tip)::) |
|
26 | roots((0:tip)::) | |
|
27 | (not public() - obsolete()) | |||
|
28 | (_intlist('20000\x0020001')) and merge() | |||
|
29 | parents(20000) | |||
|
30 | (20000::) - (20000) | |||
|
31 | # The one below is used by rebase | |||
|
32 | (children(ancestor(tip~5, tip)) and ::(tip~5)):: |
@@ -11,8 +11,7 b" options = [('L', 'label', [], _('labels " | |||||
11 | ('a', 'text', None, _('treat all files as text')), |
|
11 | ('a', 'text', None, _('treat all files as text')), | |
12 | ('p', 'print', None, |
|
12 | ('p', 'print', None, | |
13 | _('print results instead of overwriting LOCAL')), |
|
13 | _('print results instead of overwriting LOCAL')), | |
14 | ('', 'no-minimal', None, |
|
14 | ('', 'no-minimal', None, _('no effect (DEPRECATED)')), | |
15 | _('do not try to minimize conflict regions')), |
|
|||
16 | ('h', 'help', None, _('display help and exit')), |
|
15 | ('h', 'help', None, _('display help and exit')), | |
17 | ('q', 'quiet', None, _('suppress output'))] |
|
16 | ('q', 'quiet', None, _('suppress output'))] | |
18 |
|
17 |
@@ -23,6 +23,7 b' Properties that are analyzed and synthes' | |||||
23 | - Probability of a commit being a merge |
|
23 | - Probability of a commit being a merge | |
24 | - Probability of a newly added file being added to a new directory |
|
24 | - Probability of a newly added file being added to a new directory | |
25 | - Interarrival time, and time zone, of commits |
|
25 | - Interarrival time, and time zone, of commits | |
|
26 | - Number of files in each directory | |||
26 |
|
27 | |||
27 | A few obvious properties that are not currently handled realistically: |
|
28 | A few obvious properties that are not currently handled realistically: | |
28 |
|
29 | |||
@@ -35,10 +36,10 b' A few obvious properties that are not cu' | |||||
35 | - Symlinks and binary files are ignored |
|
36 | - Symlinks and binary files are ignored | |
36 | ''' |
|
37 | ''' | |
37 |
|
38 | |||
38 | import bisect, collections, json, os, random, time, sys |
|
39 | import bisect, collections, itertools, json, os, random, time, sys | |
39 | from mercurial import cmdutil, context, patch, scmutil, util, hg |
|
40 | from mercurial import cmdutil, context, patch, scmutil, util, hg | |
40 | from mercurial.i18n import _ |
|
41 | from mercurial.i18n import _ | |
41 | from mercurial.node import nullrev, nullid |
|
42 | from mercurial.node import nullrev, nullid, short | |
42 |
|
43 | |||
43 | testedwith = 'internal' |
|
44 | testedwith = 'internal' | |
44 |
|
45 | |||
@@ -81,21 +82,25 b' def parsegitdiff(lines):' | |||||
81 | yield filename, mar, lineadd, lineremove, binary |
|
82 | yield filename, mar, lineadd, lineremove, binary | |
82 |
|
83 | |||
83 | @command('analyze', |
|
84 | @command('analyze', | |
84 |
[('o', 'output', |
|
85 | [('o', 'output', '', _('write output to given file'), _('FILE')), | |
85 | ('r', 'rev', [], _('analyze specified revisions'), _('REV'))], |
|
86 | ('r', 'rev', [], _('analyze specified revisions'), _('REV'))], | |
86 | _('hg analyze')) |
|
87 | _('hg analyze'), optionalrepo=True) | |
87 | def analyze(ui, repo, *revs, **opts): |
|
88 | def analyze(ui, repo, *revs, **opts): | |
88 | '''create a simple model of a repository to use for later synthesis |
|
89 | '''create a simple model of a repository to use for later synthesis | |
89 |
|
90 | |||
90 | This command examines every changeset in the given range (or all |
|
91 | This command examines every changeset in the given range (or all | |
91 | of history if none are specified) and creates a simple statistical |
|
92 | of history if none are specified) and creates a simple statistical | |
92 | model of the history of the repository. |
|
93 | model of the history of the repository. It also measures the directory | |
|
94 | structure of the repository as checked out. | |||
93 |
|
95 | |||
94 | The model is written out to a JSON file, and can be used by |
|
96 | The model is written out to a JSON file, and can be used by | |
95 | :hg:`synthesize` to create or augment a repository with synthetic |
|
97 | :hg:`synthesize` to create or augment a repository with synthetic | |
96 | commits that have a structure that is statistically similar to the |
|
98 | commits that have a structure that is statistically similar to the | |
97 | analyzed repository. |
|
99 | analyzed repository. | |
98 | ''' |
|
100 | ''' | |
|
101 | root = repo.root | |||
|
102 | if not root.endswith(os.path.sep): | |||
|
103 | root += os.path.sep | |||
99 |
|
104 | |||
100 | revs = list(revs) |
|
105 | revs = list(revs) | |
101 | revs.extend(opts['rev']) |
|
106 | revs.extend(opts['rev']) | |
@@ -104,15 +109,24 b' def analyze(ui, repo, *revs, **opts):' | |||||
104 |
|
109 | |||
105 | output = opts['output'] |
|
110 | output = opts['output'] | |
106 | if not output: |
|
111 | if not output: | |
107 |
output = os.path.basename( |
|
112 | output = os.path.basename(root) + '.json' | |
108 |
|
113 | |||
109 | if output == '-': |
|
114 | if output == '-': | |
110 | fp = sys.stdout |
|
115 | fp = sys.stdout | |
111 | else: |
|
116 | else: | |
112 | fp = open(output, 'w') |
|
117 | fp = open(output, 'w') | |
113 |
|
118 | |||
114 | revs = scmutil.revrange(repo, revs) |
|
119 | # Always obtain file counts of each directory in the given root directory. | |
115 | revs.sort() |
|
120 | def onerror(e): | |
|
121 | ui.warn(_('error walking directory structure: %s\n') % e) | |||
|
122 | ||||
|
123 | dirs = {} | |||
|
124 | rootprefixlen = len(root) | |||
|
125 | for dirpath, dirnames, filenames in os.walk(root, onerror=onerror): | |||
|
126 | dirpathfromroot = dirpath[rootprefixlen:] | |||
|
127 | dirs[dirpathfromroot] = len(filenames) | |||
|
128 | if '.hg' in dirnames: | |||
|
129 | dirnames.remove('.hg') | |||
116 |
|
130 | |||
117 | lineschanged = zerodict() |
|
131 | lineschanged = zerodict() | |
118 | children = zerodict() |
|
132 | children = zerodict() | |
@@ -128,55 +142,61 b' def analyze(ui, repo, *revs, **opts):' | |||||
128 | dirsadded = zerodict() |
|
142 | dirsadded = zerodict() | |
129 | tzoffset = zerodict() |
|
143 | tzoffset = zerodict() | |
130 |
|
144 | |||
131 | progress = ui.progress |
|
145 | # If a mercurial repo is available, also model the commit history. | |
132 | _analyzing = _('analyzing') |
|
146 | if repo: | |
133 | _changesets = _('changesets') |
|
147 | revs = scmutil.revrange(repo, revs) | |
134 | _total = len(revs) |
|
148 | revs.sort() | |
|
149 | ||||
|
150 | progress = ui.progress | |||
|
151 | _analyzing = _('analyzing') | |||
|
152 | _changesets = _('changesets') | |||
|
153 | _total = len(revs) | |||
135 |
|
154 | |||
136 | for i, rev in enumerate(revs): |
|
155 | for i, rev in enumerate(revs): | |
137 | progress(_analyzing, i, unit=_changesets, total=_total) |
|
156 | progress(_analyzing, i, unit=_changesets, total=_total) | |
138 | ctx = repo[rev] |
|
157 | ctx = repo[rev] | |
139 | pl = ctx.parents() |
|
158 | pl = ctx.parents() | |
140 | pctx = pl[0] |
|
159 | pctx = pl[0] | |
141 | prev = pctx.rev() |
|
160 | prev = pctx.rev() | |
142 | children[prev] += 1 |
|
161 | children[prev] += 1 | |
143 | p1distance[rev - prev] += 1 |
|
162 | p1distance[rev - prev] += 1 | |
144 | parents[len(pl)] += 1 |
|
163 | parents[len(pl)] += 1 | |
145 | tzoffset[ctx.date()[1]] += 1 |
|
164 | tzoffset[ctx.date()[1]] += 1 | |
146 | if len(pl) > 1: |
|
165 | if len(pl) > 1: | |
147 | p2distance[rev - pl[1].rev()] += 1 |
|
166 | p2distance[rev - pl[1].rev()] += 1 | |
148 | if prev == rev - 1: |
|
167 | if prev == rev - 1: | |
149 | lastctx = pctx |
|
168 | lastctx = pctx | |
150 | else: |
|
169 | else: | |
151 | lastctx = repo[rev - 1] |
|
170 | lastctx = repo[rev - 1] | |
152 | if lastctx.rev() != nullrev: |
|
171 | if lastctx.rev() != nullrev: | |
153 |
|
|
172 | timedelta = ctx.date()[0] - lastctx.date()[0] | |
154 | diff = sum((d.splitlines() |
|
173 | interarrival[roundto(timedelta, 300)] += 1 | |
155 |
|
|
174 | diff = sum((d.splitlines() for d in ctx.diff(pctx, git=True)), []) | |
156 | fileadds, diradds, fileremoves, filechanges = 0, 0, 0, 0 |
|
175 | fileadds, diradds, fileremoves, filechanges = 0, 0, 0, 0 | |
157 |
for filename, mar, lineadd, lineremove, bin |
|
176 | for filename, mar, lineadd, lineremove, isbin in parsegitdiff(diff): | |
158 |
if bin |
|
177 | if isbin: | |
159 | continue |
|
178 | continue | |
160 | added = sum(lineadd.itervalues(), 0) |
|
179 | added = sum(lineadd.itervalues(), 0) | |
161 | if mar == 'm': |
|
180 | if mar == 'm': | |
162 | if added and lineremove: |
|
181 | if added and lineremove: | |
163 |
lineschanged[roundto(added, 5), |
|
182 | lineschanged[roundto(added, 5), | |
164 | filechanges += 1 |
|
183 | roundto(lineremove, 5)] += 1 | |
165 | elif mar == 'a': |
|
184 | filechanges += 1 | |
166 |
|
|
185 | elif mar == 'a': | |
167 |
|
|
186 | fileadds += 1 | |
168 |
|
|
187 | if '/' in filename: | |
169 |
|
|
188 | filedir = filename.rsplit('/', 1)[0] | |
170 |
|
|
189 | if filedir not in pctx.dirs(): | |
171 | linesinfilesadded[roundto(added, 5)] += 1 |
|
190 | diradds += 1 | |
172 | elif mar == 'r': |
|
191 | linesinfilesadded[roundto(added, 5)] += 1 | |
173 |
|
|
192 | elif mar == 'r': | |
174 | for length, count in lineadd.iteritems(): |
|
193 | fileremoves += 1 | |
175 | linelengths[length] += count |
|
194 | for length, count in lineadd.iteritems(): | |
176 | fileschanged[filechanges] += 1 |
|
195 | linelengths[length] += count | |
177 |
|
|
196 | fileschanged[filechanges] += 1 | |
178 |
|
|
197 | filesadded[fileadds] += 1 | |
179 |
|
|
198 | dirsadded[diradds] += 1 | |
|
199 | filesremoved[fileremoves] += 1 | |||
180 |
|
200 | |||
181 | invchildren = zerodict() |
|
201 | invchildren = zerodict() | |
182 |
|
202 | |||
@@ -190,6 +210,7 b' def analyze(ui, repo, *revs, **opts):' | |||||
190 | return sorted(d.iteritems(), key=lambda x: x[1], reverse=True) |
|
210 | return sorted(d.iteritems(), key=lambda x: x[1], reverse=True) | |
191 |
|
211 | |||
192 | json.dump({'revs': len(revs), |
|
212 | json.dump({'revs': len(revs), | |
|
213 | 'initdirs': pronk(dirs), | |||
193 | 'lineschanged': pronk(lineschanged), |
|
214 | 'lineschanged': pronk(lineschanged), | |
194 | 'children': pronk(invchildren), |
|
215 | 'children': pronk(invchildren), | |
195 | 'fileschanged': pronk(fileschanged), |
|
216 | 'fileschanged': pronk(fileschanged), | |
@@ -209,14 +230,17 b' def analyze(ui, repo, *revs, **opts):' | |||||
209 |
|
230 | |||
210 | @command('synthesize', |
|
231 | @command('synthesize', | |
211 | [('c', 'count', 0, _('create given number of commits'), _('COUNT')), |
|
232 | [('c', 'count', 0, _('create given number of commits'), _('COUNT')), | |
212 |
('', 'dict', '', _('path to a dictionary of words'), _('FILE')) |
|
233 | ('', 'dict', '', _('path to a dictionary of words'), _('FILE')), | |
|
234 | ('', 'initfiles', 0, _('initial file count to create'), _('COUNT'))], | |||
213 | _('hg synthesize [OPTION].. DESCFILE')) |
|
235 | _('hg synthesize [OPTION].. DESCFILE')) | |
214 | def synthesize(ui, repo, descpath, **opts): |
|
236 | def synthesize(ui, repo, descpath, **opts): | |
215 | '''synthesize commits based on a model of an existing repository |
|
237 | '''synthesize commits based on a model of an existing repository | |
216 |
|
238 | |||
217 | The model must have been generated by :hg:`analyze`. Commits will |
|
239 | The model must have been generated by :hg:`analyze`. Commits will | |
218 | be generated randomly according to the probabilities described in |
|
240 | be generated randomly according to the probabilities described in | |
219 | the model. |
|
241 | the model. If --initfiles is set, the repository will be seeded with | |
|
242 | the given number files following the modeled repository's directory | |||
|
243 | structure. | |||
220 |
|
244 | |||
221 | When synthesizing new content, commit descriptions, and user |
|
245 | When synthesizing new content, commit descriptions, and user | |
222 | names, words will be chosen randomly from a dictionary that is |
|
246 | names, words will be chosen randomly from a dictionary that is | |
@@ -262,9 +286,19 b' def synthesize(ui, repo, descpath, **opt' | |||||
262 | words = fp.read().splitlines() |
|
286 | words = fp.read().splitlines() | |
263 | fp.close() |
|
287 | fp.close() | |
264 |
|
288 | |||
|
289 | initdirs = {} | |||
|
290 | if desc['initdirs']: | |||
|
291 | for k, v in desc['initdirs']: | |||
|
292 | initdirs[k.encode('utf-8').replace('.hg', '_hg')] = v | |||
|
293 | initdirs = renamedirs(initdirs, words) | |||
|
294 | initdirscdf = cdf(initdirs) | |||
|
295 | ||||
265 | def pick(cdf): |
|
296 | def pick(cdf): | |
266 | return cdf[0][bisect.bisect_left(cdf[1], random.random())] |
|
297 | return cdf[0][bisect.bisect_left(cdf[1], random.random())] | |
267 |
|
298 | |||
|
299 | def pickpath(): | |||
|
300 | return os.path.join(pick(initdirscdf), random.choice(words)) | |||
|
301 | ||||
268 | def makeline(minimum=0): |
|
302 | def makeline(minimum=0): | |
269 | total = max(minimum, pick(linelengths)) |
|
303 | total = max(minimum, pick(linelengths)) | |
270 | c, l = 0, [] |
|
304 | c, l = 0, [] | |
@@ -281,8 +315,38 b' def synthesize(ui, repo, descpath, **opt' | |||||
281 |
|
315 | |||
282 | progress = ui.progress |
|
316 | progress = ui.progress | |
283 | _synthesizing = _('synthesizing') |
|
317 | _synthesizing = _('synthesizing') | |
|
318 | _files = _('initial files') | |||
284 | _changesets = _('changesets') |
|
319 | _changesets = _('changesets') | |
285 |
|
320 | |||
|
321 | # Synthesize a single initial revision adding files to the repo according | |||
|
322 | # to the modeled directory structure. | |||
|
323 | initcount = int(opts['initfiles']) | |||
|
324 | if initcount and initdirs: | |||
|
325 | pctx = repo[None].parents()[0] | |||
|
326 | files = {} | |||
|
327 | for i in xrange(0, initcount): | |||
|
328 | ui.progress(_synthesizing, i, unit=_files, total=initcount) | |||
|
329 | ||||
|
330 | path = pickpath() | |||
|
331 | while path in pctx.dirs(): | |||
|
332 | path = pickpath() | |||
|
333 | data = '%s contents\n' % path | |||
|
334 | files[path] = context.memfilectx(repo, path, data) | |||
|
335 | ||||
|
336 | def filectxfn(repo, memctx, path): | |||
|
337 | return files[path] | |||
|
338 | ||||
|
339 | ui.progress(_synthesizing, None) | |||
|
340 | message = 'synthesized wide repo with %d files' % (len(files),) | |||
|
341 | mc = context.memctx(repo, [pctx.node(), nullid], message, | |||
|
342 | files.iterkeys(), filectxfn, ui.username(), | |||
|
343 | '%d %d' % util.makedate()) | |||
|
344 | initnode = mc.commit() | |||
|
345 | hexfn = ui.debugflag and hex or short | |||
|
346 | ui.status(_('added commit %s with %d files\n') | |||
|
347 | % (hexfn(initnode), len(files))) | |||
|
348 | ||||
|
349 | # Synthesize incremental revisions to the repository, adding repo depth. | |||
286 | count = int(opts['count']) |
|
350 | count = int(opts['count']) | |
287 | heads = set(map(repo.changelog.rev, repo.heads())) |
|
351 | heads = set(map(repo.changelog.rev, repo.heads())) | |
288 | for i in xrange(count): |
|
352 | for i in xrange(count): | |
@@ -307,7 +371,8 b' def synthesize(ui, repo, descpath, **opt' | |||||
307 |
|
371 | |||
308 | # the number of heads will grow without bound if we use a pure |
|
372 | # the number of heads will grow without bound if we use a pure | |
309 | # model, so artificially constrain their proliferation |
|
373 | # model, so artificially constrain their proliferation | |
310 |
|
|
374 | toomanyheads = len(heads) > random.randint(1, 20) | |
|
375 | if p2distance[0] and (pick(parents) == 2 or toomanyheads): | |||
311 | r2, p2 = pickhead(heads.difference([r1]), p2distance) |
|
376 | r2, p2 = pickhead(heads.difference([r1]), p2distance) | |
312 | else: |
|
377 | else: | |
313 | r2, p2 = nullrev, nullid |
|
378 | r2, p2 = nullrev, nullid | |
@@ -356,10 +421,7 b' def synthesize(ui, repo, descpath, **opt' | |||||
356 | for __ in xrange(pick(linesinfilesadded))) + '\n' |
|
421 | for __ in xrange(pick(linesinfilesadded))) + '\n' | |
357 | changes[path] = context.memfilectx(repo, path, data) |
|
422 | changes[path] = context.memfilectx(repo, path, data) | |
358 | def filectxfn(repo, memctx, path): |
|
423 | def filectxfn(repo, memctx, path): | |
359 |
|
|
424 | return changes[path] | |
360 | if data is None: |
|
|||
361 | raise IOError |
|
|||
362 | return data |
|
|||
363 | if not changes: |
|
425 | if not changes: | |
364 | continue |
|
426 | continue | |
365 | if revs: |
|
427 | if revs: | |
@@ -377,3 +439,26 b' def synthesize(ui, repo, descpath, **opt' | |||||
377 |
|
439 | |||
378 | lock.release() |
|
440 | lock.release() | |
379 | wlock.release() |
|
441 | wlock.release() | |
|
442 | ||||
|
443 | def renamedirs(dirs, words): | |||
|
444 | '''Randomly rename the directory names in the per-dir file count dict.''' | |||
|
445 | wordgen = itertools.cycle(words) | |||
|
446 | replacements = {'': ''} | |||
|
447 | def rename(dirpath): | |||
|
448 | '''Recursively rename the directory and all path prefixes. | |||
|
449 | ||||
|
450 | The mapping from path to renamed path is stored for all path prefixes | |||
|
451 | as in dynamic programming, ensuring linear runtime and consistent | |||
|
452 | renaming regardless of iteration order through the model. | |||
|
453 | ''' | |||
|
454 | if dirpath in replacements: | |||
|
455 | return replacements[dirpath] | |||
|
456 | head, _ = os.path.split(dirpath) | |||
|
457 | head = head and rename(head) or '' | |||
|
458 | renamed = os.path.join(head, wordgen.next()) | |||
|
459 | replacements[dirpath] = renamed | |||
|
460 | return renamed | |||
|
461 | result = [] | |||
|
462 | for dirpath, count in dirs.iteritems(): | |||
|
463 | result.append([rename(dirpath.lstrip(os.sep)), count]) | |||
|
464 | return result |
@@ -52,6 +52,7 b' import sys' | |||||
52 | # Enable tracing. Run 'python -m win32traceutil' to debug |
|
52 | # Enable tracing. Run 'python -m win32traceutil' to debug | |
53 | if getattr(sys, 'isapidllhandle', None) is not None: |
|
53 | if getattr(sys, 'isapidllhandle', None) is not None: | |
54 | import win32traceutil |
|
54 | import win32traceutil | |
|
55 | win32traceutil.SetupForPrint # silence unused import warning | |||
55 |
|
56 | |||
56 | # To serve pages in local charset instead of UTF-8, remove the two lines below |
|
57 | # To serve pages in local charset instead of UTF-8, remove the two lines below | |
57 | import os |
|
58 | import os | |
@@ -90,6 +91,6 b' def __ExtensionFactory__():' | |||||
90 | return isapi_wsgi.ISAPISimpleHandler(handler) |
|
91 | return isapi_wsgi.ISAPISimpleHandler(handler) | |
91 |
|
92 | |||
92 | if __name__=='__main__': |
|
93 | if __name__=='__main__': | |
93 |
from isapi.install import |
|
94 | from isapi.install import ISAPIParameters, HandleCommandLine | |
94 | params = ISAPIParameters() |
|
95 | params = ISAPIParameters() | |
95 | HandleCommandLine(params) |
|
96 | HandleCommandLine(params) |
@@ -21,7 +21,6 b'' | |||||
21 | <File Name="hgweb.wsgi" /> |
|
21 | <File Name="hgweb.wsgi" /> | |
22 | <File Name="logo-droplets.svg" /> |
|
22 | <File Name="logo-droplets.svg" /> | |
23 | <File Name="mercurial.el" /> |
|
23 | <File Name="mercurial.el" /> | |
24 | <File Name="sample.hgrc" /> |
|
|||
25 | <File Name="tcsh_completion" /> |
|
24 | <File Name="tcsh_completion" /> | |
26 | <File Name="tcsh_completion_build.sh" /> |
|
25 | <File Name="tcsh_completion_build.sh" /> | |
27 | <File Name="xml.rnc" /> |
|
26 | <File Name="xml.rnc" /> |
@@ -5,7 +5,7 b'' | |||||
5 | your project. Component GUIDs have global namespace! --> |
|
5 | your project. Component GUIDs have global namespace! --> | |
6 |
|
6 | |||
7 | <!-- contrib.wxs --> |
|
7 | <!-- contrib.wxs --> | |
8 |
<?define contrib.guid = { |
|
8 | <?define contrib.guid = {4E11FFC2-E2F7-482A-8460-9394B5489F02} ?> | |
9 | <?define contrib.vim.guid = {BB04903A-652D-4C4F-9590-2BD07A2304F2} ?> |
|
9 | <?define contrib.vim.guid = {BB04903A-652D-4C4F-9590-2BD07A2304F2} ?> | |
10 |
|
10 | |||
11 | <!-- dist.wxs --> |
|
11 | <!-- dist.wxs --> |
@@ -1,4 +1,4 b'' | |||||
1 |
# color.py color output for |
|
1 | # color.py color output for Mercurial commands | |
2 | # |
|
2 | # | |
3 | # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com> |
|
3 | # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com> | |
4 | # |
|
4 | # | |
@@ -7,11 +7,14 b'' | |||||
7 |
|
7 | |||
8 | '''colorize output from some commands |
|
8 | '''colorize output from some commands | |
9 |
|
9 | |||
10 | This extension modifies the status and resolve commands to add color |
|
10 | The color extension colorizes output from several Mercurial commands. | |
11 | to their output to reflect file status, the qseries command to add |
|
11 | For example, the diff command shows additions in green and deletions | |
12 | color to reflect patch status (applied, unapplied, missing), and to |
|
12 | in red, while the status command shows modified files in magenta. Many | |
13 | diff-related commands to highlight additions, removals, diff headers, |
|
13 | other commands have analogous colors. It is possible to customize | |
14 | and trailing whitespace. |
|
14 | these colors. | |
|
15 | ||||
|
16 | Effects | |||
|
17 | ------- | |||
15 |
|
18 | |||
16 | Other effects in addition to color, like bold and underlined text, are |
|
19 | Other effects in addition to color, like bold and underlined text, are | |
17 | also available. By default, the terminfo database is used to find the |
|
20 | also available. By default, the terminfo database is used to find the | |
@@ -19,7 +22,32 b' terminal codes used to change color and ' | |||||
19 | available, then effects are rendered with the ECMA-48 SGR control |
|
22 | available, then effects are rendered with the ECMA-48 SGR control | |
20 | function (aka ANSI escape codes). |
|
23 | function (aka ANSI escape codes). | |
21 |
|
24 | |||
22 | Default effects may be overridden from your configuration file:: |
|
25 | The available effects in terminfo mode are 'blink', 'bold', 'dim', | |
|
26 | 'inverse', 'invisible', 'italic', 'standout', and 'underline'; in | |||
|
27 | ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and | |||
|
28 | 'underline'. How each is rendered depends on the terminal emulator. | |||
|
29 | Some may not be available for a given terminal type, and will be | |||
|
30 | silently ignored. | |||
|
31 | ||||
|
32 | Labels | |||
|
33 | ------ | |||
|
34 | ||||
|
35 | Text receives color effects depending on the labels that it has. Many | |||
|
36 | default Mercurial commands emit labelled text. You can also define | |||
|
37 | your own labels in templates using the label function, see :hg:`help | |||
|
38 | templates`. A single portion of text may have more than one label. In | |||
|
39 | that case, effects given to the last label will override any other | |||
|
40 | effects. This includes the special "none" effect, which nullifies | |||
|
41 | other effects. | |||
|
42 | ||||
|
43 | Labels are normally invisible. In order to see these labels and their | |||
|
44 | position in the text, use the global --color=debug option. The same | |||
|
45 | anchor text may be associated to multiple labels, e.g. | |||
|
46 | ||||
|
47 | [log.changeset changeset.secret|changeset: 22611:6f0a53c8f587] | |||
|
48 | ||||
|
49 | The following are the default effects for some default labels. Default | |||
|
50 | effects may be overridden from your configuration file:: | |||
23 |
|
51 | |||
24 | [color] |
|
52 | [color] | |
25 | status.modified = blue bold underline red_background |
|
53 | status.modified = blue bold underline red_background | |
@@ -45,8 +73,14 b' Default effects may be overridden from y' | |||||
45 | diff.deleted = red |
|
73 | diff.deleted = red | |
46 | diff.inserted = green |
|
74 | diff.inserted = green | |
47 | diff.changed = white |
|
75 | diff.changed = white | |
|
76 | diff.tab = | |||
48 | diff.trailingwhitespace = bold red_background |
|
77 | diff.trailingwhitespace = bold red_background | |
49 |
|
78 | |||
|
79 | # Blank so it inherits the style of the surrounding label | |||
|
80 | changeset.public = | |||
|
81 | changeset.draft = | |||
|
82 | changeset.secret = | |||
|
83 | ||||
50 | resolve.unresolved = red bold |
|
84 | resolve.unresolved = red bold | |
51 | resolve.resolved = green bold |
|
85 | resolve.resolved = green bold | |
52 |
|
86 | |||
@@ -69,20 +103,8 b' Default effects may be overridden from y' | |||||
69 |
|
103 | |||
70 | histedit.remaining = red bold |
|
104 | histedit.remaining = red bold | |
71 |
|
105 | |||
72 | The available effects in terminfo mode are 'blink', 'bold', 'dim', |
|
106 | Custom colors | |
73 | 'inverse', 'invisible', 'italic', 'standout', and 'underline'; in |
|
107 | ------------- | |
74 | ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and |
|
|||
75 | 'underline'. How each is rendered depends on the terminal emulator. |
|
|||
76 | Some may not be available for a given terminal type, and will be |
|
|||
77 | silently ignored. |
|
|||
78 |
|
||||
79 | Note that on some systems, terminfo mode may cause problems when using |
|
|||
80 | color with the pager extension and less -R. less with the -R option |
|
|||
81 | will only display ECMA-48 color codes, and terminfo mode may sometimes |
|
|||
82 | emit codes that less doesn't understand. You can work around this by |
|
|||
83 | either using ansi mode (or auto mode), or by using less -r (which will |
|
|||
84 | pass through all terminal control codes, not just color control |
|
|||
85 | codes). |
|
|||
86 |
|
108 | |||
87 | Because there are only eight standard colors, this module allows you |
|
109 | Because there are only eight standard colors, this module allows you | |
88 | to define color names for other color slots which might be available |
|
110 | to define color names for other color slots which might be available | |
@@ -98,6 +120,9 b' that have brighter colors defined in the' | |||||
98 | defined colors may then be used as any of the pre-defined eight, |
|
120 | defined colors may then be used as any of the pre-defined eight, | |
99 | including appending '_background' to set the background to that color. |
|
121 | including appending '_background' to set the background to that color. | |
100 |
|
122 | |||
|
123 | Modes | |||
|
124 | ----- | |||
|
125 | ||||
101 | By default, the color extension will use ANSI mode (or win32 mode on |
|
126 | By default, the color extension will use ANSI mode (or win32 mode on | |
102 | Windows) if it detects a terminal. To override auto mode (to enable |
|
127 | Windows) if it detects a terminal. To override auto mode (to enable | |
103 | terminfo mode, for example), set the following configuration option:: |
|
128 | terminfo mode, for example), set the following configuration option:: | |
@@ -107,6 +132,14 b' terminfo mode, for example), set the fol' | |||||
107 |
|
132 | |||
108 | Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will |
|
133 | Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will | |
109 | disable color. |
|
134 | disable color. | |
|
135 | ||||
|
136 | Note that on some systems, terminfo mode may cause problems when using | |||
|
137 | color with the pager extension and less -R. less with the -R option | |||
|
138 | will only display ECMA-48 color codes, and terminfo mode may sometimes | |||
|
139 | emit codes that less doesn't understand. You can work around this by | |||
|
140 | either using ansi mode (or auto mode), or by using less -r (which will | |||
|
141 | pass through all terminal control codes, not just color control | |||
|
142 | codes). | |||
110 | ''' |
|
143 | ''' | |
111 |
|
144 | |||
112 | import os |
|
145 | import os | |
@@ -168,6 +201,9 b' def _terminfosetup(ui, mode):' | |||||
168 | def _modesetup(ui, coloropt): |
|
201 | def _modesetup(ui, coloropt): | |
169 | global _terminfo_params |
|
202 | global _terminfo_params | |
170 |
|
203 | |||
|
204 | if coloropt == 'debug': | |||
|
205 | return 'debug' | |||
|
206 | ||||
171 | auto = (coloropt == 'auto') |
|
207 | auto = (coloropt == 'auto') | |
172 | always = not auto and util.parsebool(coloropt) |
|
208 | always = not auto and util.parsebool(coloropt) | |
173 | if not always and not auto: |
|
209 | if not always and not auto: | |
@@ -255,7 +291,11 b' except ImportError:' | |||||
255 | 'diff.file_b': 'green bold', |
|
291 | 'diff.file_b': 'green bold', | |
256 | 'diff.hunk': 'magenta', |
|
292 | 'diff.hunk': 'magenta', | |
257 | 'diff.inserted': 'green', |
|
293 | 'diff.inserted': 'green', | |
|
294 | 'diff.tab': '', | |||
258 | 'diff.trailingwhitespace': 'bold red_background', |
|
295 | 'diff.trailingwhitespace': 'bold red_background', | |
|
296 | 'changeset.public' : '', | |||
|
297 | 'changeset.draft' : '', | |||
|
298 | 'changeset.secret' : '', | |||
259 | 'diffstat.deleted': 'red', |
|
299 | 'diffstat.deleted': 'red', | |
260 | 'diffstat.inserted': 'green', |
|
300 | 'diffstat.inserted': 'green', | |
261 | 'histedit.remaining': 'red bold', |
|
301 | 'histedit.remaining': 'red bold', | |
@@ -378,10 +418,22 b' class colorui(uimod.ui):' | |||||
378 | return super(colorui, self).write_err( |
|
418 | return super(colorui, self).write_err( | |
379 | *[self.label(str(a), label) for a in args], **opts) |
|
419 | *[self.label(str(a), label) for a in args], **opts) | |
380 |
|
420 | |||
|
421 | def showlabel(self, msg, label): | |||
|
422 | if label and msg: | |||
|
423 | if msg[-1] == '\n': | |||
|
424 | return "[%s|%s]\n" % (label, msg[:-1]) | |||
|
425 | else: | |||
|
426 | return "[%s|%s]" % (label, msg) | |||
|
427 | else: | |||
|
428 | return msg | |||
|
429 | ||||
381 | def label(self, msg, label): |
|
430 | def label(self, msg, label): | |
382 | if self._colormode is None: |
|
431 | if self._colormode is None: | |
383 | return super(colorui, self).label(msg, label) |
|
432 | return super(colorui, self).label(msg, label) | |
384 |
|
433 | |||
|
434 | if self._colormode == 'debug': | |||
|
435 | return self.showlabel(msg, label) | |||
|
436 | ||||
385 | effects = [] |
|
437 | effects = [] | |
386 | for l in label.split(): |
|
438 | for l in label.split(): | |
387 | s = _styles.get(l, '') |
|
439 | s = _styles.get(l, '') | |
@@ -427,7 +479,7 b' def uisetup(ui):' | |||||
427 | def colorcmd(orig, ui_, opts, cmd, cmdfunc): |
|
479 | def colorcmd(orig, ui_, opts, cmd, cmdfunc): | |
428 | mode = _modesetup(ui_, opts['color']) |
|
480 | mode = _modesetup(ui_, opts['color']) | |
429 | colorui._colormode = mode |
|
481 | colorui._colormode = mode | |
430 | if mode: |
|
482 | if mode and mode != 'debug': | |
431 | extstyles() |
|
483 | extstyles() | |
432 | configstyles(ui_) |
|
484 | configstyles(ui_) | |
433 | return orig(ui_, opts, cmd, cmdfunc) |
|
485 | return orig(ui_, opts, cmd, cmdfunc) | |
@@ -437,9 +489,9 b' def uisetup(ui):' | |||||
437 | def extsetup(ui): |
|
489 | def extsetup(ui): | |
438 | commands.globalopts.append( |
|
490 | commands.globalopts.append( | |
439 | ('', 'color', 'auto', |
|
491 | ('', 'color', 'auto', | |
440 |
# i18n: 'always', 'auto', |
|
492 | # i18n: 'always', 'auto', 'never', and 'debug' are keywords | |
441 | # not be translated |
|
493 | # and should not be translated | |
442 |
_("when to colorize (boolean, always, auto, |
|
494 | _("when to colorize (boolean, always, auto, never, or debug)"), | |
443 | _('TYPE'))) |
|
495 | _('TYPE'))) | |
444 |
|
496 | |||
445 | @command('debugcolor', [], 'hg debugcolor') |
|
497 | @command('debugcolor', [], 'hg debugcolor') |
@@ -29,6 +29,8 b" testedwith = 'internal'" | |||||
29 | ('A', 'authormap', '', _('remap usernames using this file'), _('FILE')), |
|
29 | ('A', 'authormap', '', _('remap usernames using this file'), _('FILE')), | |
30 | ('', 'filemap', '', _('remap file names using contents of file'), |
|
30 | ('', 'filemap', '', _('remap file names using contents of file'), | |
31 | _('FILE')), |
|
31 | _('FILE')), | |
|
32 | ('', 'full', None, | |||
|
33 | _('apply filemap changes by converting all files again')), | |||
32 | ('', 'splicemap', '', _('splice synthesized history into place'), |
|
34 | ('', 'splicemap', '', _('splice synthesized history into place'), | |
33 | _('FILE')), |
|
35 | _('FILE')), | |
34 | ('', 'branchmap', '', _('change branch names while converting'), |
|
36 | ('', 'branchmap', '', _('change branch names while converting'), | |
@@ -131,6 +133,14 b' def convert(ui, src, dest=None, revmapfi' | |||||
131 | it is converted. To rename from a subdirectory into the root of |
|
133 | it is converted. To rename from a subdirectory into the root of | |
132 | the repository, use ``.`` as the path to rename to. |
|
134 | the repository, use ``.`` as the path to rename to. | |
133 |
|
135 | |||
|
136 | ``--full`` will make sure the converted changesets contain exactly | |||
|
137 | the right files with the right content. It will make a full | |||
|
138 | conversion of all files, not just the ones that have | |||
|
139 | changed. Files that already are correct will not be changed. This | |||
|
140 | can be used to apply filemap changes when converting | |||
|
141 | incrementally. This is currently only supported for Mercurial and | |||
|
142 | Subversion. | |||
|
143 | ||||
134 | The splicemap is a file that allows insertion of synthetic |
|
144 | The splicemap is a file that allows insertion of synthetic | |
135 | history, letting you specify the parents of a revision. This is |
|
145 | history, letting you specify the parents of a revision. This is | |
136 | useful if you want to e.g. give a Subversion merge two parents, or |
|
146 | useful if you want to e.g. give a Subversion merge two parents, or | |
@@ -272,6 +282,29 b' def convert(ui, src, dest=None, revmapfi' | |||||
272 | :convert.svn.startrev: specify start Subversion revision number. |
|
282 | :convert.svn.startrev: specify start Subversion revision number. | |
273 | The default is 0. |
|
283 | The default is 0. | |
274 |
|
284 | |||
|
285 | Git Source | |||
|
286 | ########## | |||
|
287 | ||||
|
288 | The Git importer converts commits from all reachable branches (refs | |||
|
289 | in refs/heads) and remotes (refs in refs/remotes) to Mercurial. | |||
|
290 | Branches are converted to bookmarks with the same name, with the | |||
|
291 | leading 'refs/heads' stripped. Git submodules are converted to Git | |||
|
292 | subrepos in Mercurial. | |||
|
293 | ||||
|
294 | The following options can be set with ``--config``: | |||
|
295 | ||||
|
296 | :convert.git.similarity: specify how similar files modified in a | |||
|
297 | commit must be to be imported as renames or copies, as a | |||
|
298 | percentage between ``0`` (disabled) and ``100`` (files must be | |||
|
299 | identical). For example, ``90`` means that a delete/add pair will | |||
|
300 | be imported as a rename if more than 90% of the file hasn't | |||
|
301 | changed. The default is ``50``. | |||
|
302 | ||||
|
303 | :convert.git.findcopiesharder: while detecting copies, look at all | |||
|
304 | files in the working copy instead of just changed ones. This | |||
|
305 | is very expensive for large projects, and is only effective when | |||
|
306 | ``convert.git.similarity`` is greater than 0. The default is False. | |||
|
307 | ||||
275 | Perforce Source |
|
308 | Perforce Source | |
276 | ############### |
|
309 | ############### | |
277 |
|
310 |
@@ -122,8 +122,7 b' class bzr_source(converter_source):' | |||||
122 | kind = revtree.kind(fileid) |
|
122 | kind = revtree.kind(fileid) | |
123 | if kind not in supportedkinds: |
|
123 | if kind not in supportedkinds: | |
124 | # the file is not available anymore - was deleted |
|
124 | # the file is not available anymore - was deleted | |
125 | raise IOError(_('%s is not available in %s anymore') % |
|
125 | return None, None | |
126 | (name, rev)) |
|
|||
127 | mode = self._modecache[(name, rev)] |
|
126 | mode = self._modecache[(name, rev)] | |
128 | if kind == 'symlink': |
|
127 | if kind == 'symlink': | |
129 | target = revtree.get_symlink_target(fileid) |
|
128 | target = revtree.get_symlink_target(fileid) | |
@@ -135,8 +134,9 b' class bzr_source(converter_source):' | |||||
135 | sio = revtree.get_file(fileid) |
|
134 | sio = revtree.get_file(fileid) | |
136 | return sio.read(), mode |
|
135 | return sio.read(), mode | |
137 |
|
136 | |||
138 | def getchanges(self, version): |
|
137 | def getchanges(self, version, full): | |
139 | # set up caches: modecache and revtree |
|
138 | if full: | |
|
139 | raise util.Abort(_("convert from cvs do not support --full")) | |||
140 | self._modecache = {} |
|
140 | self._modecache = {} | |
141 | self._revtree = self.sourcerepo.revision_tree(version) |
|
141 | self._revtree = self.sourcerepo.revision_tree(version) | |
142 | # get the parentids from the cache |
|
142 | # get the parentids from the cache |
@@ -88,17 +88,18 b' class converter_source(object):' | |||||
88 | def getfile(self, name, rev): |
|
88 | def getfile(self, name, rev): | |
89 | """Return a pair (data, mode) where data is the file content |
|
89 | """Return a pair (data, mode) where data is the file content | |
90 | as a string and mode one of '', 'x' or 'l'. rev is the |
|
90 | as a string and mode one of '', 'x' or 'l'. rev is the | |
91 |
identifier returned by a previous call to getchanges(). |
|
91 | identifier returned by a previous call to getchanges(). | |
92 |
|
|
92 | Data is None if file is missing/deleted in rev. | |
93 | """ |
|
93 | """ | |
94 | raise NotImplementedError |
|
94 | raise NotImplementedError | |
95 |
|
95 | |||
96 | def getchanges(self, version): |
|
96 | def getchanges(self, version, full): | |
97 | """Returns a tuple of (files, copies). |
|
97 | """Returns a tuple of (files, copies). | |
98 |
|
98 | |||
99 | files is a sorted list of (filename, id) tuples for all files |
|
99 | files is a sorted list of (filename, id) tuples for all files | |
100 | changed between version and its first parent returned by |
|
100 | changed between version and its first parent returned by | |
101 |
getcommit(). |
|
101 | getcommit(). If full, all files in that revision is returned. | |
|
102 | id is the source revision id of the file. | |||
102 |
|
103 | |||
103 | copies is a dictionary of dest: source |
|
104 | copies is a dictionary of dest: source | |
104 | """ |
|
105 | """ | |
@@ -108,6 +109,13 b' class converter_source(object):' | |||||
108 | """Return the commit object for version""" |
|
109 | """Return the commit object for version""" | |
109 | raise NotImplementedError |
|
110 | raise NotImplementedError | |
110 |
|
111 | |||
|
112 | def numcommits(self): | |||
|
113 | """Return the number of commits in this source. | |||
|
114 | ||||
|
115 | If unknown, return None. | |||
|
116 | """ | |||
|
117 | return None | |||
|
118 | ||||
111 | def gettags(self): |
|
119 | def gettags(self): | |
112 | """Return the tags as a dictionary of name: revision |
|
120 | """Return the tags as a dictionary of name: revision | |
113 |
|
121 | |||
@@ -204,7 +212,7 b' class converter_sink(object):' | |||||
204 | mapping equivalent authors identifiers for each system.""" |
|
212 | mapping equivalent authors identifiers for each system.""" | |
205 | return None |
|
213 | return None | |
206 |
|
214 | |||
207 | def putcommit(self, files, copies, parents, commit, source, revmap): |
|
215 | def putcommit(self, files, copies, parents, commit, source, revmap, full): | |
208 | """Create a revision with all changed files listed in 'files' |
|
216 | """Create a revision with all changed files listed in 'files' | |
209 | and having listed parents. 'commit' is a commit object |
|
217 | and having listed parents. 'commit' is a commit object | |
210 | containing at a minimum the author, date, and message for this |
|
218 | containing at a minimum the author, date, and message for this | |
@@ -212,7 +220,8 b' class converter_sink(object):' | |||||
212 | 'copies' is a dictionary mapping destinations to sources, |
|
220 | 'copies' is a dictionary mapping destinations to sources, | |
213 | 'source' is the source repository, and 'revmap' is a mapfile |
|
221 | 'source' is the source repository, and 'revmap' is a mapfile | |
214 | of source revisions to converted revisions. Only getfile() and |
|
222 | of source revisions to converted revisions. Only getfile() and | |
215 | lookuprev() should be called on 'source'. |
|
223 | lookuprev() should be called on 'source'. 'full' means that 'files' | |
|
224 | is complete and all other files should be removed. | |||
216 |
|
225 | |||
217 | Note that the sink repository is not told to update itself to |
|
226 | Note that the sink repository is not told to update itself to | |
218 | a particular revision (or even what that revision would be) |
|
227 | a particular revision (or even what that revision would be) |
@@ -171,6 +171,7 b' class converter(object):' | |||||
171 | visit = heads |
|
171 | visit = heads | |
172 | known = set() |
|
172 | known = set() | |
173 | parents = {} |
|
173 | parents = {} | |
|
174 | numcommits = self.source.numcommits() | |||
174 | while visit: |
|
175 | while visit: | |
175 | n = visit.pop(0) |
|
176 | n = visit.pop(0) | |
176 | if n in known: |
|
177 | if n in known: | |
@@ -180,7 +181,8 b' class converter(object):' | |||||
180 | if m == SKIPREV or self.dest.hascommitfrommap(m): |
|
181 | if m == SKIPREV or self.dest.hascommitfrommap(m): | |
181 | continue |
|
182 | continue | |
182 | known.add(n) |
|
183 | known.add(n) | |
183 |
self.ui.progress(_('scanning'), len(known), unit=_('revisions') |
|
184 | self.ui.progress(_('scanning'), len(known), unit=_('revisions'), | |
|
185 | total=numcommits) | |||
184 | commit = self.cachecommit(n) |
|
186 | commit = self.cachecommit(n) | |
185 | parents[n] = [] |
|
187 | parents[n] = [] | |
186 | for p in commit.parents: |
|
188 | for p in commit.parents: | |
@@ -386,8 +388,8 b' class converter(object):' | |||||
386 |
|
388 | |||
387 | def copy(self, rev): |
|
389 | def copy(self, rev): | |
388 | commit = self.commitcache[rev] |
|
390 | commit = self.commitcache[rev] | |
389 |
|
391 | full = self.opts.get('full') | ||
390 | changes = self.source.getchanges(rev) |
|
392 | changes = self.source.getchanges(rev, full) | |
391 | if isinstance(changes, basestring): |
|
393 | if isinstance(changes, basestring): | |
392 | if changes == SKIPREV: |
|
394 | if changes == SKIPREV: | |
393 | dest = SKIPREV |
|
395 | dest = SKIPREV | |
@@ -413,7 +415,7 b' class converter(object):' | |||||
413 | parents = [b[0] for b in pbranches] |
|
415 | parents = [b[0] for b in pbranches] | |
414 | source = progresssource(self.ui, self.source, len(files)) |
|
416 | source = progresssource(self.ui, self.source, len(files)) | |
415 | newnode = self.dest.putcommit(files, copies, parents, commit, |
|
417 | newnode = self.dest.putcommit(files, copies, parents, commit, | |
416 | source, self.map) |
|
418 | source, self.map, full) | |
417 | source.close() |
|
419 | source.close() | |
418 | self.source.converted(rev, newnode) |
|
420 | self.source.converted(rev, newnode) | |
419 | self.map[rev] = newnode |
|
421 | self.map[rev] = newnode |
@@ -220,7 +220,7 b' class convert_cvs(converter_source):' | |||||
220 |
|
220 | |||
221 | self._parse() |
|
221 | self._parse() | |
222 | if rev.endswith("(DEAD)"): |
|
222 | if rev.endswith("(DEAD)"): | |
223 | raise IOError |
|
223 | return None, None | |
224 |
|
224 | |||
225 | args = ("-N -P -kk -r %s --" % rev).split() |
|
225 | args = ("-N -P -kk -r %s --" % rev).split() | |
226 | args.append(self.cvsrepo + '/' + name) |
|
226 | args.append(self.cvsrepo + '/' + name) | |
@@ -258,7 +258,9 b' class convert_cvs(converter_source):' | |||||
258 | else: |
|
258 | else: | |
259 | raise util.Abort(_("unknown CVS response: %s") % line) |
|
259 | raise util.Abort(_("unknown CVS response: %s") % line) | |
260 |
|
260 | |||
261 | def getchanges(self, rev): |
|
261 | def getchanges(self, rev, full): | |
|
262 | if full: | |||
|
263 | raise util.Abort(_("convert from cvs do not support --full")) | |||
262 | self._parse() |
|
264 | self._parse() | |
263 | return sorted(self.files[rev].iteritems()), {} |
|
265 | return sorted(self.files[rev].iteritems()), {} | |
264 |
|
266 |
@@ -8,7 +8,6 b'' | |||||
8 | import os |
|
8 | import os | |
9 | import re |
|
9 | import re | |
10 | import cPickle as pickle |
|
10 | import cPickle as pickle | |
11 | from mercurial import util |
|
|||
12 | from mercurial.i18n import _ |
|
11 | from mercurial.i18n import _ | |
13 | from mercurial import hook |
|
12 | from mercurial import hook | |
14 | from mercurial import util |
|
13 | from mercurial import util | |
@@ -632,7 +631,19 b' def createchangeset(ui, log, fuzz=60, me' | |||||
632 | odd.add((l, r)) |
|
631 | odd.add((l, r)) | |
633 | d = -1 |
|
632 | d = -1 | |
634 | break |
|
633 | break | |
|
634 | # By this point, the changesets are sufficiently compared that | |||
|
635 | # we don't really care about ordering. However, this leaves | |||
|
636 | # some race conditions in the tests, so we compare on the | |||
|
637 | # number of files modified and the number of branchpoints in | |||
|
638 | # each changeset to ensure test output remains stable. | |||
635 |
|
639 | |||
|
640 | # recommended replacement for cmp from | |||
|
641 | # https://docs.python.org/3.0/whatsnew/3.0.html | |||
|
642 | c = lambda x, y: (x > y) - (x < y) | |||
|
643 | if not d: | |||
|
644 | d = c(len(l.entries), len(r.entries)) | |||
|
645 | if not d: | |||
|
646 | d = c(len(l.branchpoints), len(r.branchpoints)) | |||
636 | return d |
|
647 | return d | |
637 |
|
648 | |||
638 | changesets.sort(cscmp) |
|
649 | changesets.sort(cscmp) |
@@ -8,7 +8,7 b'' | |||||
8 | from common import NoRepo, checktool, commandline, commit, converter_source |
|
8 | from common import NoRepo, checktool, commandline, commit, converter_source | |
9 | from mercurial.i18n import _ |
|
9 | from mercurial.i18n import _ | |
10 | from mercurial import util |
|
10 | from mercurial import util | |
11 | import os, shutil, tempfile, re |
|
11 | import os, shutil, tempfile, re, errno | |
12 |
|
12 | |||
13 | # The naming drift of ElementTree is fun! |
|
13 | # The naming drift of ElementTree is fun! | |
14 |
|
14 | |||
@@ -156,7 +156,9 b' class darcs_source(converter_source, com' | |||||
156 | output, status = self.run('revert', all=True, repodir=self.tmppath) |
|
156 | output, status = self.run('revert', all=True, repodir=self.tmppath) | |
157 | self.checkexit(status, output) |
|
157 | self.checkexit(status, output) | |
158 |
|
158 | |||
159 | def getchanges(self, rev): |
|
159 | def getchanges(self, rev, full): | |
|
160 | if full: | |||
|
161 | raise util.Abort(_("convert from darcs do not support --full")) | |||
160 | copies = {} |
|
162 | copies = {} | |
161 | changes = [] |
|
163 | changes = [] | |
162 | man = None |
|
164 | man = None | |
@@ -192,8 +194,13 b' class darcs_source(converter_source, com' | |||||
192 | if rev != self.lastrev: |
|
194 | if rev != self.lastrev: | |
193 | raise util.Abort(_('internal calling inconsistency')) |
|
195 | raise util.Abort(_('internal calling inconsistency')) | |
194 | path = os.path.join(self.tmppath, name) |
|
196 | path = os.path.join(self.tmppath, name) | |
195 | data = util.readfile(path) |
|
197 | try: | |
196 | mode = os.lstat(path).st_mode |
|
198 | data = util.readfile(path) | |
|
199 | mode = os.lstat(path).st_mode | |||
|
200 | except IOError, inst: | |||
|
201 | if inst.errno == errno.ENOENT: | |||
|
202 | return None, None | |||
|
203 | raise | |||
197 | mode = (mode & 0111) and 'x' or '' |
|
204 | mode = (mode & 0111) and 'x' or '' | |
198 | return data, mode |
|
205 | return data, mode | |
199 |
|
206 |
@@ -304,7 +304,7 b' class filemap_source(converter_source):' | |||||
304 | wrev.add(rev) |
|
304 | wrev.add(rev) | |
305 | self.wantedancestors[rev] = wrev |
|
305 | self.wantedancestors[rev] = wrev | |
306 |
|
306 | |||
307 | def getchanges(self, rev): |
|
307 | def getchanges(self, rev, full): | |
308 | parents = self.commits[rev].parents |
|
308 | parents = self.commits[rev].parents | |
309 | if len(parents) > 1: |
|
309 | if len(parents) > 1: | |
310 | self.rebuild() |
|
310 | self.rebuild() | |
@@ -384,7 +384,7 b' class filemap_source(converter_source):' | |||||
384 | # Get the real changes and do the filtering/mapping. To be |
|
384 | # Get the real changes and do the filtering/mapping. To be | |
385 | # able to get the files later on in getfile, we hide the |
|
385 | # able to get the files later on in getfile, we hide the | |
386 | # original filename in the rev part of the return value. |
|
386 | # original filename in the rev part of the return value. | |
387 | changes, copies = self.base.getchanges(rev) |
|
387 | changes, copies = self.base.getchanges(rev, full) | |
388 | files = {} |
|
388 | files = {} | |
389 | for f, r in changes: |
|
389 | for f, r in changes: | |
390 | newf = self.filemapper(f) |
|
390 | newf = self.filemapper(f) |
@@ -94,6 +94,19 b' class convert_git(converter_source):' | |||||
94 | if not os.path.exists(path + "/objects"): |
|
94 | if not os.path.exists(path + "/objects"): | |
95 | raise NoRepo(_("%s does not look like a Git repository") % path) |
|
95 | raise NoRepo(_("%s does not look like a Git repository") % path) | |
96 |
|
96 | |||
|
97 | # The default value (50) is based on the default for 'git diff'. | |||
|
98 | similarity = ui.configint('convert', 'git.similarity', default=50) | |||
|
99 | if similarity < 0 or similarity > 100: | |||
|
100 | raise util.Abort(_('similarity must be between 0 and 100')) | |||
|
101 | if similarity > 0: | |||
|
102 | self.simopt = '--find-copies=%d%%' % similarity | |||
|
103 | findcopiesharder = ui.configbool('convert', 'git.findcopiesharder', | |||
|
104 | False) | |||
|
105 | if findcopiesharder: | |||
|
106 | self.simopt += ' --find-copies-harder' | |||
|
107 | else: | |||
|
108 | self.simopt = '' | |||
|
109 | ||||
97 | checktool('git', 'git') |
|
110 | checktool('git', 'git') | |
98 |
|
111 | |||
99 | self.path = path |
|
112 | self.path = path | |
@@ -135,7 +148,7 b' class convert_git(converter_source):' | |||||
135 |
|
148 | |||
136 | def getfile(self, name, rev): |
|
149 | def getfile(self, name, rev): | |
137 | if rev == hex(nullid): |
|
150 | if rev == hex(nullid): | |
138 | raise IOError |
|
151 | return None, None | |
139 | if name == '.hgsub': |
|
152 | if name == '.hgsub': | |
140 | data = '\n'.join([m.hgsub() for m in self.submoditer()]) |
|
153 | data = '\n'.join([m.hgsub() for m in self.submoditer()]) | |
141 | mode = '' |
|
154 | mode = '' | |
@@ -180,51 +193,78 b' class convert_git(converter_source):' | |||||
180 | continue |
|
193 | continue | |
181 | m.node = node.strip() |
|
194 | m.node = node.strip() | |
182 |
|
195 | |||
183 | def getchanges(self, version): |
|
196 | def getchanges(self, version, full): | |
|
197 | if full: | |||
|
198 | raise util.Abort(_("convert from git do not support --full")) | |||
184 | self.modecache = {} |
|
199 | self.modecache = {} | |
185 |
fh = self.gitopen("git diff-tree -z --root -m -r %s" % |
|
200 | fh = self.gitopen("git diff-tree -z --root -m -r %s %s" % ( | |
|
201 | self.simopt, version)) | |||
186 | changes = [] |
|
202 | changes = [] | |
|
203 | copies = {} | |||
187 | seen = set() |
|
204 | seen = set() | |
188 | entry = None |
|
205 | entry = None | |
189 | subexists = False |
|
206 | subexists = [False] | |
190 | subdeleted = False |
|
207 | subdeleted = [False] | |
191 |
|
|
208 | difftree = fh.read().split('\x00') | |
|
209 | lcount = len(difftree) | |||
|
210 | i = 0 | |||
|
211 | ||||
|
212 | def add(entry, f, isdest): | |||
|
213 | seen.add(f) | |||
|
214 | h = entry[3] | |||
|
215 | p = (entry[1] == "100755") | |||
|
216 | s = (entry[1] == "120000") | |||
|
217 | renamesource = (not isdest and entry[4][0] == 'R') | |||
|
218 | ||||
|
219 | if f == '.gitmodules': | |||
|
220 | subexists[0] = True | |||
|
221 | if entry[4] == 'D' or renamesource: | |||
|
222 | subdeleted[0] = True | |||
|
223 | changes.append(('.hgsub', hex(nullid))) | |||
|
224 | else: | |||
|
225 | changes.append(('.hgsub', '')) | |||
|
226 | elif entry[1] == '160000' or entry[0] == ':160000': | |||
|
227 | subexists[0] = True | |||
|
228 | else: | |||
|
229 | if renamesource: | |||
|
230 | h = hex(nullid) | |||
|
231 | self.modecache[(f, h)] = (p and "x") or (s and "l") or "" | |||
|
232 | changes.append((f, h)) | |||
|
233 | ||||
|
234 | while i < lcount: | |||
|
235 | l = difftree[i] | |||
|
236 | i += 1 | |||
192 | if not entry: |
|
237 | if not entry: | |
193 | if not l.startswith(':'): |
|
238 | if not l.startswith(':'): | |
194 | continue |
|
239 | continue | |
195 | entry = l |
|
240 | entry = l.split() | |
196 | continue |
|
241 | continue | |
197 | f = l |
|
242 | f = l | |
198 | if f not in seen: |
|
243 | if f not in seen: | |
199 |
|
|
244 | add(entry, f, False) | |
200 | entry = entry.split() |
|
245 | # A file can be copied multiple times, or modified and copied | |
201 | h = entry[3] |
|
246 | # simultaneously. So f can be repeated even if fdest isn't. | |
202 | p = (entry[1] == "100755") |
|
247 | if entry[4][0] in 'RC': | |
203 | s = (entry[1] == "120000") |
|
248 | # rename or copy: next line is the destination | |
204 |
|
249 | fdest = difftree[i] | ||
205 |
i |
|
250 | i += 1 | |
206 |
|
|
251 | if fdest not in seen: | |
207 |
|
|
252 | add(entry, fdest, True) | |
208 | subdeleted = True |
|
253 | # .gitmodules isn't imported at all, so it being copied to | |
209 | changes.append(('.hgsub', hex(nullid))) |
|
254 | # and fro doesn't really make sense | |
210 | else: |
|
255 | if f != '.gitmodules' and fdest != '.gitmodules': | |
211 |
c |
|
256 | copies[fdest] = f | |
212 | elif entry[1] == '160000' or entry[0] == ':160000': |
|
|||
213 | subexists = True |
|
|||
214 | else: |
|
|||
215 | self.modecache[(f, h)] = (p and "x") or (s and "l") or "" |
|
|||
216 | changes.append((f, h)) |
|
|||
217 | entry = None |
|
257 | entry = None | |
218 | if fh.close(): |
|
258 | if fh.close(): | |
219 | raise util.Abort(_('cannot read changes in %s') % version) |
|
259 | raise util.Abort(_('cannot read changes in %s') % version) | |
220 |
|
260 | |||
221 | if subexists: |
|
261 | if subexists[0]: | |
222 | if subdeleted: |
|
262 | if subdeleted[0]: | |
223 | changes.append(('.hgsubstate', hex(nullid))) |
|
263 | changes.append(('.hgsubstate', hex(nullid))) | |
224 | else: |
|
264 | else: | |
225 | self.retrievegitmodules(version) |
|
265 | self.retrievegitmodules(version) | |
226 | changes.append(('.hgsubstate', '')) |
|
266 | changes.append(('.hgsubstate', '')) | |
227 |
return (changes, |
|
267 | return (changes, copies) | |
228 |
|
268 | |||
229 | def getcommit(self, version): |
|
269 | def getcommit(self, version): | |
230 | c = self.catfile(version, "commit") # read the commit hash |
|
270 | c = self.catfile(version, "commit") # read the commit hash | |
@@ -261,6 +301,9 b' class convert_git(converter_source):' | |||||
261 | rev=version) |
|
301 | rev=version) | |
262 | return c |
|
302 | return c | |
263 |
|
303 | |||
|
304 | def numcommits(self): | |||
|
305 | return len([None for _ in self.gitopen('git rev-list --all')]) | |||
|
306 | ||||
264 | def gettags(self): |
|
307 | def gettags(self): | |
265 | tags = {} |
|
308 | tags = {} | |
266 | alltags = {} |
|
309 | alltags = {} | |
@@ -340,4 +383,3 b' class convert_git(converter_source):' | |||||
340 | def checkrevformat(self, revstr, mapname='splicemap'): |
|
383 | def checkrevformat(self, revstr, mapname='splicemap'): | |
341 | """ git revision string is a 40 byte hex """ |
|
384 | """ git revision string is a 40 byte hex """ | |
342 | self.checkhexformat(revstr, mapname) |
|
385 | self.checkhexformat(revstr, mapname) | |
343 |
|
@@ -137,13 +137,14 b' class gnuarch_source(converter_source, c' | |||||
137 | if rev != self.lastrev: |
|
137 | if rev != self.lastrev: | |
138 | raise util.Abort(_('internal calling inconsistency')) |
|
138 | raise util.Abort(_('internal calling inconsistency')) | |
139 |
|
139 | |||
140 | # Raise IOError if necessary (i.e. deleted files). |
|
|||
141 | if not os.path.lexists(os.path.join(self.tmppath, name)): |
|
140 | if not os.path.lexists(os.path.join(self.tmppath, name)): | |
142 | raise IOError |
|
141 | return None, None | |
143 |
|
142 | |||
144 | return self._getfile(name, rev) |
|
143 | return self._getfile(name, rev) | |
145 |
|
144 | |||
146 | def getchanges(self, rev): |
|
145 | def getchanges(self, rev, full): | |
|
146 | if full: | |||
|
147 | raise util.Abort(_("convert from arch do not support --full")) | |||
147 | self._update(rev) |
|
148 | self._update(rev) | |
148 | changes = [] |
|
149 | changes = [] | |
149 | copies = {} |
|
150 | copies = {} |
@@ -21,7 +21,7 b'' | |||||
21 | import os, time, cStringIO |
|
21 | import os, time, cStringIO | |
22 | from mercurial.i18n import _ |
|
22 | from mercurial.i18n import _ | |
23 | from mercurial.node import bin, hex, nullid |
|
23 | from mercurial.node import bin, hex, nullid | |
24 | from mercurial import hg, util, context, bookmarks, error, scmutil |
|
24 | from mercurial import hg, util, context, bookmarks, error, scmutil, exchange | |
25 |
|
25 | |||
26 | from common import NoRepo, commit, converter_source, converter_sink |
|
26 | from common import NoRepo, commit, converter_source, converter_sink | |
27 |
|
27 | |||
@@ -113,7 +113,8 b' class mercurial_sink(converter_sink):' | |||||
113 | pbranchpath = os.path.join(self.path, pbranch) |
|
113 | pbranchpath = os.path.join(self.path, pbranch) | |
114 | prepo = hg.peer(self.ui, {}, pbranchpath) |
|
114 | prepo = hg.peer(self.ui, {}, pbranchpath) | |
115 | self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch)) |
|
115 | self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch)) | |
116 | self.repo.pull(prepo, [prepo.lookup(h) for h in heads]) |
|
116 | exchange.pull(self.repo, prepo, | |
|
117 | [prepo.lookup(h) for h in heads]) | |||
117 | self.before() |
|
118 | self.before() | |
118 |
|
119 | |||
119 | def _rewritetags(self, source, revmap, data): |
|
120 | def _rewritetags(self, source, revmap, data): | |
@@ -128,12 +129,16 b' class mercurial_sink(converter_sink):' | |||||
128 | fp.write('%s %s\n' % (revid, s[1])) |
|
129 | fp.write('%s %s\n' % (revid, s[1])) | |
129 | return fp.getvalue() |
|
130 | return fp.getvalue() | |
130 |
|
131 | |||
131 | def putcommit(self, files, copies, parents, commit, source, revmap): |
|
132 | def putcommit(self, files, copies, parents, commit, source, revmap, full): | |
132 |
|
||||
133 | files = dict(files) |
|
133 | files = dict(files) | |
134 | def getfilectx(repo, memctx, f): |
|
134 | def getfilectx(repo, memctx, f): | |
135 |
|
|
135 | try: | |
|
136 | v = files[f] | |||
|
137 | except KeyError: | |||
|
138 | return None | |||
136 | data, mode = source.getfile(f, v) |
|
139 | data, mode = source.getfile(f, v) | |
|
140 | if data is None: | |||
|
141 | return None | |||
137 | if f == '.hgtags': |
|
142 | if f == '.hgtags': | |
138 | data = self._rewritetags(source, revmap, data) |
|
143 | data = self._rewritetags(source, revmap, data) | |
139 | return context.memfilectx(self.repo, f, data, 'l' in mode, |
|
144 | return context.memfilectx(self.repo, f, data, 'l' in mode, | |
@@ -191,7 +196,11 b' class mercurial_sink(converter_sink):' | |||||
191 | while parents: |
|
196 | while parents: | |
192 | p1 = p2 |
|
197 | p1 = p2 | |
193 | p2 = parents.pop(0) |
|
198 | p2 = parents.pop(0) | |
194 | ctx = context.memctx(self.repo, (p1, p2), text, files.keys(), |
|
199 | fileset = set(files) | |
|
200 | if full: | |||
|
201 | fileset.update(self.repo[p1]) | |||
|
202 | fileset.update(self.repo[p2]) | |||
|
203 | ctx = context.memctx(self.repo, (p1, p2), text, fileset, | |||
195 | getfilectx, commit.author, commit.date, extra) |
|
204 | getfilectx, commit.author, commit.date, extra) | |
196 | self.repo.commitctx(ctx) |
|
205 | self.repo.commitctx(ctx) | |
197 | text = "(octopus merge fixup)\n" |
|
206 | text = "(octopus merge fixup)\n" | |
@@ -299,7 +308,7 b' class mercurial_source(converter_source)' | |||||
299 | raise NoRepo(_("%s is not a local Mercurial repository") % path) |
|
308 | raise NoRepo(_("%s is not a local Mercurial repository") % path) | |
300 | self.lastrev = None |
|
309 | self.lastrev = None | |
301 | self.lastctx = None |
|
310 | self.lastctx = None | |
302 | self._changescache = None |
|
311 | self._changescache = None, None | |
303 | self.convertfp = None |
|
312 | self.convertfp = None | |
304 | # Restrict converted revisions to startrev descendants |
|
313 | # Restrict converted revisions to startrev descendants | |
305 | startnode = ui.config('convert', 'hg.startrev') |
|
314 | startnode = ui.config('convert', 'hg.startrev') | |
@@ -351,29 +360,28 b' class mercurial_source(converter_source)' | |||||
351 | try: |
|
360 | try: | |
352 | fctx = self.changectx(rev)[name] |
|
361 | fctx = self.changectx(rev)[name] | |
353 | return fctx.data(), fctx.flags() |
|
362 | return fctx.data(), fctx.flags() | |
354 |
except error.LookupError |
|
363 | except error.LookupError: | |
355 | raise IOError(err) |
|
364 | return None, None | |
356 |
|
365 | |||
357 | def getchanges(self, rev): |
|
366 | def getchanges(self, rev, full): | |
358 | ctx = self.changectx(rev) |
|
367 | ctx = self.changectx(rev) | |
359 | parents = self.parents(ctx) |
|
368 | parents = self.parents(ctx) | |
360 | if not parents: |
|
369 | if full or not parents: | |
361 |
files = |
|
370 | files = copyfiles = ctx.manifest() | |
362 | # getcopies() is not needed for roots, but it is a simple way to |
|
371 | if parents: | |
363 | # detect missing revlogs and abort on errors or populate |
|
372 | if self._changescache[0] == rev: | |
364 | # self.ignored |
|
373 | m, a, r = self._changescache[1] | |
365 | self.getcopies(ctx, parents, files) |
|
374 | else: | |
366 | return [(f, rev) for f in files if f not in self.ignored], {} |
|
375 | m, a, r = self.repo.status(parents[0].node(), ctx.node())[:3] | |
367 | if self._changescache and self._changescache[0] == rev: |
|
376 | if not full: | |
368 | m, a, r = self._changescache[1] |
|
377 | files = m + a + r | |
369 | else: |
|
378 | copyfiles = m + a | |
370 | m, a, r = self.repo.status(parents[0].node(), ctx.node())[:3] |
|
379 | # getcopies() is also run for roots and before filtering so missing | |
371 | # getcopies() detects missing revlogs early, run it before |
|
380 | # revlogs are detected early | |
372 | # filtering the changes. |
|
381 | copies = self.getcopies(ctx, parents, copyfiles) | |
373 | copies = self.getcopies(ctx, parents, m + a) |
|
382 | changes = [(f, rev) for f in files if f not in self.ignored] | |
374 | changes = [(name, rev) for name in m + a + r |
|
383 | changes.sort() | |
375 | if name not in self.ignored] |
|
384 | return changes, copies | |
376 | return sorted(changes), copies |
|
|||
377 |
|
385 | |||
378 | def getcopies(self, ctx, parents, files): |
|
386 | def getcopies(self, ctx, parents, files): | |
379 | copies = {} |
|
387 | copies = {} |
@@ -224,7 +224,9 b' class monotone_source(converter_source, ' | |||||
224 | else: |
|
224 | else: | |
225 | return [self.rev] |
|
225 | return [self.rev] | |
226 |
|
226 | |||
227 | def getchanges(self, rev): |
|
227 | def getchanges(self, rev, full): | |
|
228 | if full: | |||
|
229 | raise util.Abort(_("convert from monotone do not support --full")) | |||
228 | revision = self.mtnrun("get_revision", rev).split("\n\n") |
|
230 | revision = self.mtnrun("get_revision", rev).split("\n\n") | |
229 | files = {} |
|
231 | files = {} | |
230 | ignoremove = {} |
|
232 | ignoremove = {} | |
@@ -282,11 +284,11 b' class monotone_source(converter_source, ' | |||||
282 |
|
284 | |||
283 | def getfile(self, name, rev): |
|
285 | def getfile(self, name, rev): | |
284 | if not self.mtnisfile(name, rev): |
|
286 | if not self.mtnisfile(name, rev): | |
285 | raise IOError # file was deleted or renamed |
|
287 | return None, None | |
286 | try: |
|
288 | try: | |
287 | data = self.mtnrun("get_file_of", name, r=rev) |
|
289 | data = self.mtnrun("get_file_of", name, r=rev) | |
288 | except Exception: |
|
290 | except Exception: | |
289 | raise IOError # file was deleted or renamed |
|
291 | return None, None | |
290 | self.mtnloadmanifest(rev) |
|
292 | self.mtnloadmanifest(rev) | |
291 | node, attr = self.files.get(name, (None, "")) |
|
293 | node, attr = self.files.get(name, (None, "")) | |
292 | return data, attr |
|
294 | return data, attr |
@@ -164,6 +164,8 b' class p4_source(converter_source):' | |||||
164 | raise IOError(d["generic"], data) |
|
164 | raise IOError(d["generic"], data) | |
165 |
|
165 | |||
166 | elif code == "stat": |
|
166 | elif code == "stat": | |
|
167 | if d.get("action") == "purge": | |||
|
168 | return None, None | |||
167 | p4type = self.re_type.match(d["type"]) |
|
169 | p4type = self.re_type.match(d["type"]) | |
168 | if p4type: |
|
170 | if p4type: | |
169 | mode = "" |
|
171 | mode = "" | |
@@ -181,7 +183,7 b' class p4_source(converter_source):' | |||||
181 | contents += data |
|
183 | contents += data | |
182 |
|
184 | |||
183 | if mode is None: |
|
185 | if mode is None: | |
184 | raise IOError(0, "bad stat") |
|
186 | return None, None | |
185 |
|
187 | |||
186 | if keywords: |
|
188 | if keywords: | |
187 | contents = keywords.sub("$\\1$", contents) |
|
189 | contents = keywords.sub("$\\1$", contents) | |
@@ -190,7 +192,9 b' class p4_source(converter_source):' | |||||
190 |
|
192 | |||
191 | return contents, mode |
|
193 | return contents, mode | |
192 |
|
194 | |||
193 | def getchanges(self, rev): |
|
195 | def getchanges(self, rev, full): | |
|
196 | if full: | |||
|
197 | raise util.Abort(_("convert from p4 do not support --full")) | |||
194 | return self.files[rev], {} |
|
198 | return self.files[rev], {} | |
195 |
|
199 | |||
196 | def getcommit(self, rev): |
|
200 | def getcommit(self, rev): |
@@ -347,7 +347,7 b' class svn_source(converter_source):' | |||||
347 | % self.module) |
|
347 | % self.module) | |
348 | self.last_changed = self.revnum(self.head) |
|
348 | self.last_changed = self.revnum(self.head) | |
349 |
|
349 | |||
350 | self._changescache = None |
|
350 | self._changescache = (None, None) | |
351 |
|
351 | |||
352 | if os.path.exists(os.path.join(url, '.svn/entries')): |
|
352 | if os.path.exists(os.path.join(url, '.svn/entries')): | |
353 | self.wc = url |
|
353 | self.wc = url | |
@@ -444,34 +444,39 b' class svn_source(converter_source):' | |||||
444 |
|
444 | |||
445 | return self.heads |
|
445 | return self.heads | |
446 |
|
446 | |||
447 | def getchanges(self, rev): |
|
447 | def _getchanges(self, rev, full): | |
448 | if self._changescache and self._changescache[0] == rev: |
|
|||
449 | return self._changescache[1] |
|
|||
450 | self._changescache = None |
|
|||
451 | (paths, parents) = self.paths[rev] |
|
448 | (paths, parents) = self.paths[rev] | |
|
449 | copies = {} | |||
452 | if parents: |
|
450 | if parents: | |
453 | files, self.removed, copies = self.expandpaths(rev, paths, parents) |
|
451 | files, self.removed, copies = self.expandpaths(rev, paths, parents) | |
454 | else: |
|
452 | if full or not parents: | |
455 | # Perform a full checkout on roots |
|
453 | # Perform a full checkout on roots | |
456 | uuid, module, revnum = revsplit(rev) |
|
454 | uuid, module, revnum = revsplit(rev) | |
457 | entries = svn.client.ls(self.baseurl + quote(module), |
|
455 | entries = svn.client.ls(self.baseurl + quote(module), | |
458 | optrev(revnum), True, self.ctx) |
|
456 | optrev(revnum), True, self.ctx) | |
459 | files = [n for n, e in entries.iteritems() |
|
457 | files = [n for n, e in entries.iteritems() | |
460 | if e.kind == svn.core.svn_node_file] |
|
458 | if e.kind == svn.core.svn_node_file] | |
461 | copies = {} |
|
|||
462 | self.removed = set() |
|
459 | self.removed = set() | |
463 |
|
460 | |||
464 | files.sort() |
|
461 | files.sort() | |
465 | files = zip(files, [rev] * len(files)) |
|
462 | files = zip(files, [rev] * len(files)) | |
|
463 | return (files, copies) | |||
466 |
|
464 | |||
467 | # caller caches the result, so free it here to release memory |
|
465 | def getchanges(self, rev, full): | |
468 | del self.paths[rev] |
|
466 | # reuse cache from getchangedfiles | |
|
467 | if self._changescache[0] == rev and not full: | |||
|
468 | (files, copies) = self._changescache[1] | |||
|
469 | else: | |||
|
470 | (files, copies) = self._getchanges(rev, full) | |||
|
471 | # caller caches the result, so free it here to release memory | |||
|
472 | del self.paths[rev] | |||
469 | return (files, copies) |
|
473 | return (files, copies) | |
470 |
|
474 | |||
471 | def getchangedfiles(self, rev, i): |
|
475 | def getchangedfiles(self, rev, i): | |
472 | changes = self.getchanges(rev) |
|
476 | # called from filemap - cache computed values for reuse in getchanges | |
473 |
|
|
477 | (files, copies) = self._getchanges(rev, False) | |
474 | return [f[0] for f in changes[0]] |
|
478 | self._changescache = (rev, (files, copies)) | |
|
479 | return [f[0] for f in files] | |||
475 |
|
480 | |||
476 | def getcommit(self, rev): |
|
481 | def getcommit(self, rev): | |
477 | if rev not in self.commits: |
|
482 | if rev not in self.commits: | |
@@ -490,10 +495,10 b' class svn_source(converter_source):' | |||||
490 | self._fetch_revisions(revnum, stop) |
|
495 | self._fetch_revisions(revnum, stop) | |
491 | if rev not in self.commits: |
|
496 | if rev not in self.commits: | |
492 | raise util.Abort(_('svn: revision %s not found') % revnum) |
|
497 | raise util.Abort(_('svn: revision %s not found') % revnum) | |
493 | commit = self.commits[rev] |
|
498 | revcommit = self.commits[rev] | |
494 | # caller caches the result, so free it here to release memory |
|
499 | # caller caches the result, so free it here to release memory | |
495 | del self.commits[rev] |
|
500 | del self.commits[rev] | |
496 | return commit |
|
501 | return revcommit | |
497 |
|
502 | |||
498 | def checkrevformat(self, revstr, mapname='splicemap'): |
|
503 | def checkrevformat(self, revstr, mapname='splicemap'): | |
499 | """ fails if revision format does not match the correct format""" |
|
504 | """ fails if revision format does not match the correct format""" | |
@@ -503,6 +508,9 b' class svn_source(converter_source):' | |||||
503 | raise util.Abort(_('%s entry %s is not a valid revision' |
|
508 | raise util.Abort(_('%s entry %s is not a valid revision' | |
504 | ' identifier') % (mapname, revstr)) |
|
509 | ' identifier') % (mapname, revstr)) | |
505 |
|
510 | |||
|
511 | def numcommits(self): | |||
|
512 | return int(self.head.rsplit('@', 1)[1]) - self.startrev | |||
|
513 | ||||
506 | def gettags(self): |
|
514 | def gettags(self): | |
507 | tags = {} |
|
515 | tags = {} | |
508 | if self.tags is None: |
|
516 | if self.tags is None: | |
@@ -933,7 +941,7 b' class svn_source(converter_source):' | |||||
933 | def getfile(self, file, rev): |
|
941 | def getfile(self, file, rev): | |
934 | # TODO: ra.get_file transmits the whole file instead of diffs. |
|
942 | # TODO: ra.get_file transmits the whole file instead of diffs. | |
935 | if file in self.removed: |
|
943 | if file in self.removed: | |
936 | raise IOError |
|
944 | return None, None | |
937 | mode = '' |
|
945 | mode = '' | |
938 | try: |
|
946 | try: | |
939 | new_module, revnum = revsplit(rev)[1:] |
|
947 | new_module, revnum = revsplit(rev)[1:] | |
@@ -954,7 +962,7 b' class svn_source(converter_source):' | |||||
954 | notfound = (svn.core.SVN_ERR_FS_NOT_FOUND, |
|
962 | notfound = (svn.core.SVN_ERR_FS_NOT_FOUND, | |
955 | svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND) |
|
963 | svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND) | |
956 | if e.apr_err in notfound: # File not found |
|
964 | if e.apr_err in notfound: # File not found | |
957 |
r |
|
965 | return None, None | |
958 | raise |
|
966 | raise | |
959 | if mode == 'l': |
|
967 | if mode == 'l': | |
960 | link_prefix = "link " |
|
968 | link_prefix = "link " | |
@@ -1211,23 +1219,13 b' class svn_sink(converter_sink, commandli' | |||||
1211 | self.xargs(files, 'add', quiet=True) |
|
1219 | self.xargs(files, 'add', quiet=True) | |
1212 | return files |
|
1220 | return files | |
1213 |
|
1221 | |||
1214 | def tidy_dirs(self, names): |
|
|||
1215 | deleted = [] |
|
|||
1216 | for d in sorted(self.dirs_of(names), reverse=True): |
|
|||
1217 | wd = self.wjoin(d) |
|
|||
1218 | if os.listdir(wd) == '.svn': |
|
|||
1219 | self.run0('delete', d) |
|
|||
1220 | self.manifest.remove(d) |
|
|||
1221 | deleted.append(d) |
|
|||
1222 | return deleted |
|
|||
1223 |
|
||||
1224 | def addchild(self, parent, child): |
|
1222 | def addchild(self, parent, child): | |
1225 | self.childmap[parent] = child |
|
1223 | self.childmap[parent] = child | |
1226 |
|
1224 | |||
1227 | def revid(self, rev): |
|
1225 | def revid(self, rev): | |
1228 | return u"svn:%s@%s" % (self.uuid, rev) |
|
1226 | return u"svn:%s@%s" % (self.uuid, rev) | |
1229 |
|
1227 | |||
1230 | def putcommit(self, files, copies, parents, commit, source, revmap): |
|
1228 | def putcommit(self, files, copies, parents, commit, source, revmap, full): | |
1231 | for parent in parents: |
|
1229 | for parent in parents: | |
1232 | try: |
|
1230 | try: | |
1233 | return self.revid(self.childmap[parent]) |
|
1231 | return self.revid(self.childmap[parent]) | |
@@ -1236,14 +1234,15 b' class svn_sink(converter_sink, commandli' | |||||
1236 |
|
1234 | |||
1237 | # Apply changes to working copy |
|
1235 | # Apply changes to working copy | |
1238 | for f, v in files: |
|
1236 | for f, v in files: | |
1239 | try: |
|
1237 | data, mode = source.getfile(f, v) | |
1240 | data, mode = source.getfile(f, v) |
|
1238 | if data is None: | |
1241 | except IOError: |
|
|||
1242 | self.delete.append(f) |
|
1239 | self.delete.append(f) | |
1243 | else: |
|
1240 | else: | |
1244 | self.putfile(f, mode, data) |
|
1241 | self.putfile(f, mode, data) | |
1245 | if f in copies: |
|
1242 | if f in copies: | |
1246 | self.copies.append([copies[f], f]) |
|
1243 | self.copies.append([copies[f], f]) | |
|
1244 | if full: | |||
|
1245 | self.delete.extend(sorted(self.manifest.difference(files))) | |||
1247 | files = [f[0] for f in files] |
|
1246 | files = [f[0] for f in files] | |
1248 |
|
1247 | |||
1249 | entries = set(self.delete) |
|
1248 | entries = set(self.delete) | |
@@ -1259,7 +1258,6 b' class svn_sink(converter_sink, commandli' | |||||
1259 | self.manifest.remove(f) |
|
1258 | self.manifest.remove(f) | |
1260 | self.delete = [] |
|
1259 | self.delete = [] | |
1261 | entries.update(self.add_files(files.difference(entries))) |
|
1260 | entries.update(self.add_files(files.difference(entries))) | |
1262 | entries.update(self.tidy_dirs(entries)) |
|
|||
1263 | if self.delexec: |
|
1261 | if self.delexec: | |
1264 | self.xargs(self.delexec, 'propdel', 'svn:executable') |
|
1262 | self.xargs(self.delexec, 'propdel', 'svn:executable') | |
1265 | self.delexec = [] |
|
1263 | self.delexec = [] |
@@ -63,7 +63,7 b' pretty fast (at least faster than having' | |||||
63 |
|
63 | |||
64 | from mercurial.i18n import _ |
|
64 | from mercurial.i18n import _ | |
65 | from mercurial.node import short, nullid |
|
65 | from mercurial.node import short, nullid | |
66 |
from mercurial import cmdutil, scmutil, |
|
66 | from mercurial import cmdutil, scmutil, util, commands, encoding | |
67 | import os, shlex, shutil, tempfile, re |
|
67 | import os, shlex, shutil, tempfile, re | |
68 |
|
68 | |||
69 | cmdtable = {} |
|
69 | cmdtable = {} |
@@ -8,9 +8,10 b'' | |||||
8 | '''pull, update and merge in one command (DEPRECATED)''' |
|
8 | '''pull, update and merge in one command (DEPRECATED)''' | |
9 |
|
9 | |||
10 | from mercurial.i18n import _ |
|
10 | from mercurial.i18n import _ | |
11 |
from mercurial.node import |
|
11 | from mercurial.node import short | |
12 | from mercurial import commands, cmdutil, hg, util, error |
|
12 | from mercurial import commands, cmdutil, hg, util, error | |
13 | from mercurial.lock import release |
|
13 | from mercurial.lock import release | |
|
14 | from mercurial import exchange | |||
14 |
|
15 | |||
15 | cmdtable = {} |
|
16 | cmdtable = {} | |
16 | command = cmdutil.command(cmdtable) |
|
17 | command = cmdutil.command(cmdtable) | |
@@ -48,7 +49,7 b" def fetch(ui, repo, source='default', **" | |||||
48 | if date: |
|
49 | if date: | |
49 | opts['date'] = util.parsedate(date) |
|
50 | opts['date'] = util.parsedate(date) | |
50 |
|
51 | |||
51 | parent, p2 = repo.dirstate.parents() |
|
52 | parent, _p2 = repo.dirstate.parents() | |
52 | branch = repo.dirstate.branch() |
|
53 | branch = repo.dirstate.branch() | |
53 | try: |
|
54 | try: | |
54 | branchnode = repo.branchtip(branch) |
|
55 | branchnode = repo.branchtip(branch) | |
@@ -58,19 +59,13 b" def fetch(ui, repo, source='default', **" | |||||
58 | raise util.Abort(_('working dir not at branch tip ' |
|
59 | raise util.Abort(_('working dir not at branch tip ' | |
59 | '(use "hg update" to check out branch tip)')) |
|
60 | '(use "hg update" to check out branch tip)')) | |
60 |
|
61 | |||
61 | if p2 != nullid: |
|
|||
62 | raise util.Abort(_('outstanding uncommitted merge')) |
|
|||
63 |
|
||||
64 | wlock = lock = None |
|
62 | wlock = lock = None | |
65 | try: |
|
63 | try: | |
66 | wlock = repo.wlock() |
|
64 | wlock = repo.wlock() | |
67 | lock = repo.lock() |
|
65 | lock = repo.lock() | |
68 | mod, add, rem, del_ = repo.status()[:4] |
|
|||
69 |
|
66 | |||
70 | if mod or add or rem: |
|
67 | cmdutil.bailifchanged(repo) | |
71 | raise util.Abort(_('outstanding uncommitted changes')) |
|
68 | ||
72 | if del_: |
|
|||
73 | raise util.Abort(_('working directory is missing some files')) |
|
|||
74 | bheads = repo.branchheads(branch) |
|
69 | bheads = repo.branchheads(branch) | |
75 | bheads = [head for head in bheads if len(repo[head].children()) == 0] |
|
70 | bheads = [head for head in bheads if len(repo[head].children()) == 0] | |
76 | if len(bheads) > 1: |
|
71 | if len(bheads) > 1: | |
@@ -90,7 +85,7 b" def fetch(ui, repo, source='default', **" | |||||
90 | raise util.Abort(err) |
|
85 | raise util.Abort(err) | |
91 |
|
86 | |||
92 | # Are there any changes at all? |
|
87 | # Are there any changes at all? | |
93 |
modheads = |
|
88 | modheads = exchange.pull(repo, other, heads=revs).cgresult | |
94 | if modheads == 0: |
|
89 | if modheads == 0: | |
95 | return 0 |
|
90 | return 0 | |
96 |
|
91 | |||
@@ -143,8 +138,8 b" def fetch(ui, repo, source='default', **" | |||||
143 | ('Automated merge with %s' % |
|
138 | ('Automated merge with %s' % | |
144 | util.removeauth(other.url()))) |
|
139 | util.removeauth(other.url()))) | |
145 | editopt = opts.get('edit') or opts.get('force_editor') |
|
140 | editopt = opts.get('edit') or opts.get('force_editor') | |
146 | n = repo.commit(message, opts['user'], opts['date'], |
|
141 | editor = cmdutil.getcommiteditor(edit=editopt, editform='fetch') | |
147 | editor=cmdutil.getcommiteditor(edit=editopt)) |
|
142 | n = repo.commit(message, opts['user'], opts['date'], editor=editor) | |
148 | ui.status(_('new changeset %d:%s merges remote changes ' |
|
143 | ui.status(_('new changeset %d:%s merges remote changes ' | |
149 | 'with local\n') % (repo.changelog.rev(n), |
|
144 | 'with local\n') % (repo.changelog.rev(n), | |
150 | short(n))) |
|
145 | short(n))) |
@@ -253,12 +253,11 b' def sign(ui, repo, *revs, **opts):' | |||||
253 | repo.opener.append("localsigs", sigmessage) |
|
253 | repo.opener.append("localsigs", sigmessage) | |
254 | return |
|
254 | return | |
255 |
|
255 | |||
256 | msigs = match.exact(repo.root, '', ['.hgsigs']) |
|
256 | if not opts["force"]: | |
257 | s = repo.status(match=msigs, unknown=True, ignored=True)[:6] |
|
257 | msigs = match.exact(repo.root, '', ['.hgsigs']) | |
258 | if util.any(s) and not opts["force"]: |
|
258 | if util.any(repo.status(match=msigs, unknown=True, ignored=True)): | |
259 | raise util.Abort(_("working copy of .hgsigs is changed " |
|
259 | raise util.Abort(_("working copy of .hgsigs is changed "), | |
260 |
" |
|
260 | hint=_("please commit .hgsigs manually")) | |
261 | "or use --force)")) |
|
|||
262 |
|
261 | |||
263 | sigsfile = repo.wfile(".hgsigs", "ab") |
|
262 | sigsfile = repo.wfile(".hgsigs", "ab") | |
264 | sigsfile.write(sigmessage) |
|
263 | sigsfile.write(sigmessage) | |
@@ -277,8 +276,9 b' def sign(ui, repo, *revs, **opts):' | |||||
277 | % hgnode.short(n) |
|
276 | % hgnode.short(n) | |
278 | for n in nodes]) |
|
277 | for n in nodes]) | |
279 | try: |
|
278 | try: | |
|
279 | editor = cmdutil.getcommiteditor(editform='gpg.sign', **opts) | |||
280 | repo.commit(message, opts['user'], opts['date'], match=msigs, |
|
280 | repo.commit(message, opts['user'], opts['date'], match=msigs, | |
281 |
editor= |
|
281 | editor=editor) | |
282 | except ValueError, inst: |
|
282 | except ValueError, inst: | |
283 | raise util.Abort(str(inst)) |
|
283 | raise util.Abort(str(inst)) | |
284 |
|
284 |
@@ -82,14 +82,14 b' class ciamsg(object):' | |||||
82 | if url and url[-1] == '/': |
|
82 | if url and url[-1] == '/': | |
83 | url = url[:-1] |
|
83 | url = url[:-1] | |
84 | elems = [] |
|
84 | elems = [] | |
85 |
for path in f |
|
85 | for path in f.modified: | |
86 | uri = '%s/diff/%s/%s' % (url, short(n), path) |
|
86 | uri = '%s/diff/%s/%s' % (url, short(n), path) | |
87 | elems.append(self.fileelem(path, url and uri, 'modify')) |
|
87 | elems.append(self.fileelem(path, url and uri, 'modify')) | |
88 |
for path in f |
|
88 | for path in f.added: | |
89 | # TODO: copy/rename ? |
|
89 | # TODO: copy/rename ? | |
90 | uri = '%s/file/%s/%s' % (url, short(n), path) |
|
90 | uri = '%s/file/%s/%s' % (url, short(n), path) | |
91 | elems.append(self.fileelem(path, url and uri, 'add')) |
|
91 | elems.append(self.fileelem(path, url and uri, 'add')) | |
92 |
for path in f |
|
92 | for path in f.removed: | |
93 | elems.append(self.fileelem(path, '', 'remove')) |
|
93 | elems.append(self.fileelem(path, '', 'remove')) | |
94 |
|
94 | |||
95 | return '\n'.join(elems) |
|
95 | return '\n'.join(elems) |
@@ -204,10 +204,12 b' def revtree(ui, args, repo, full="tree",' | |||||
204 | l[chunk - x:] = [0] * (chunk - x) |
|
204 | l[chunk - x:] = [0] * (chunk - x) | |
205 | break |
|
205 | break | |
206 | if full is not None: |
|
206 | if full is not None: | |
207 |
|
|
207 | if (i + x) in repo: | |
208 |
l[x] |
|
208 | l[x] = repo[i + x] | |
|
209 | l[x].changeset() # force reading | |||
209 | else: |
|
210 | else: | |
210 |
|
|
211 | if (i + x) in repo: | |
|
212 | l[x] = 1 | |||
211 | for x in xrange(chunk - 1, -1, -1): |
|
213 | for x in xrange(chunk - 1, -1, -1): | |
212 | if l[x] != 0: |
|
214 | if l[x] != 0: | |
213 | yield (i + x, full is not None and l[x] or None) |
|
215 | yield (i + x, full is not None and l[x] or None) | |
@@ -259,6 +261,8 b' def revtree(ui, args, repo, full="tree",' | |||||
259 | # walk the repository looking for commits that are in our |
|
261 | # walk the repository looking for commits that are in our | |
260 | # reachability graph |
|
262 | # reachability graph | |
261 | for i, ctx in chlogwalk(): |
|
263 | for i, ctx in chlogwalk(): | |
|
264 | if i not in repo: | |||
|
265 | continue | |||
262 | n = repo.changelog.node(i) |
|
266 | n = repo.changelog.node(i) | |
263 | mask = is_reachable(want_sha1, reachable, n) |
|
267 | mask = is_reachable(want_sha1, reachable, n) | |
264 | if mask: |
|
268 | if mask: |
@@ -36,6 +36,7 b' file open in your editor::' | |||||
36 | # p, pick = use commit |
|
36 | # p, pick = use commit | |
37 | # e, edit = use commit, but stop for amending |
|
37 | # e, edit = use commit, but stop for amending | |
38 | # f, fold = use commit, but combine it with the one above |
|
38 | # f, fold = use commit, but combine it with the one above | |
|
39 | # r, roll = like fold, but discard this commit's description | |||
39 | # d, drop = remove commit from history |
|
40 | # d, drop = remove commit from history | |
40 | # m, mess = edit message without changing commit content |
|
41 | # m, mess = edit message without changing commit content | |
41 | # |
|
42 | # | |
@@ -57,6 +58,7 b' would reorganize the file to look like t' | |||||
57 | # p, pick = use commit |
|
58 | # p, pick = use commit | |
58 | # e, edit = use commit, but stop for amending |
|
59 | # e, edit = use commit, but stop for amending | |
59 | # f, fold = use commit, but combine it with the one above |
|
60 | # f, fold = use commit, but combine it with the one above | |
|
61 | # r, roll = like fold, but discard this commit's description | |||
60 | # d, drop = remove commit from history |
|
62 | # d, drop = remove commit from history | |
61 | # m, mess = edit message without changing commit content |
|
63 | # m, mess = edit message without changing commit content | |
62 | # |
|
64 | # | |
@@ -180,11 +182,53 b' editcomment = _("""# Edit history betwee' | |||||
180 | # p, pick = use commit |
|
182 | # p, pick = use commit | |
181 | # e, edit = use commit, but stop for amending |
|
183 | # e, edit = use commit, but stop for amending | |
182 | # f, fold = use commit, but combine it with the one above |
|
184 | # f, fold = use commit, but combine it with the one above | |
|
185 | # r, roll = like fold, but discard this commit's description | |||
183 | # d, drop = remove commit from history |
|
186 | # d, drop = remove commit from history | |
184 | # m, mess = edit message without changing commit content |
|
187 | # m, mess = edit message without changing commit content | |
185 | # |
|
188 | # | |
186 | """) |
|
189 | """) | |
187 |
|
190 | |||
|
191 | class histeditstate(object): | |||
|
192 | def __init__(self, repo, parentctx=None, rules=None, keep=None, | |||
|
193 | topmost=None, replacements=None, lock=None, wlock=None): | |||
|
194 | self.repo = repo | |||
|
195 | self.rules = rules | |||
|
196 | self.keep = keep | |||
|
197 | self.topmost = topmost | |||
|
198 | self.parentctx = parentctx | |||
|
199 | self.lock = lock | |||
|
200 | self.wlock = wlock | |||
|
201 | if replacements is None: | |||
|
202 | self.replacements = [] | |||
|
203 | else: | |||
|
204 | self.replacements = replacements | |||
|
205 | ||||
|
206 | def read(self): | |||
|
207 | """Load histedit state from disk and set fields appropriately.""" | |||
|
208 | try: | |||
|
209 | fp = self.repo.vfs('histedit-state', 'r') | |||
|
210 | except IOError, err: | |||
|
211 | if err.errno != errno.ENOENT: | |||
|
212 | raise | |||
|
213 | raise util.Abort(_('no histedit in progress')) | |||
|
214 | ||||
|
215 | parentctxnode, rules, keep, topmost, replacements = pickle.load(fp) | |||
|
216 | ||||
|
217 | self.parentctx = self.repo[parentctxnode] | |||
|
218 | self.rules = rules | |||
|
219 | self.keep = keep | |||
|
220 | self.topmost = topmost | |||
|
221 | self.replacements = replacements | |||
|
222 | ||||
|
223 | def write(self): | |||
|
224 | fp = self.repo.vfs('histedit-state', 'w') | |||
|
225 | pickle.dump((self.parentctx.node(), self.rules, self.keep, | |||
|
226 | self.topmost, self.replacements), fp) | |||
|
227 | fp.close() | |||
|
228 | ||||
|
229 | def clear(self): | |||
|
230 | self.repo.vfs.unlink('histedit-state') | |||
|
231 | ||||
188 | def commitfuncfor(repo, src): |
|
232 | def commitfuncfor(repo, src): | |
189 | """Build a commit function for the replacement of <src> |
|
233 | """Build a commit function for the replacement of <src> | |
190 |
|
234 | |||
@@ -209,8 +253,6 b' def commitfuncfor(repo, src):' | |||||
209 | repo.ui.restoreconfig(phasebackup) |
|
253 | repo.ui.restoreconfig(phasebackup) | |
210 | return commitfunc |
|
254 | return commitfunc | |
211 |
|
255 | |||
212 |
|
||||
213 |
|
||||
214 | def applychanges(ui, repo, ctx, opts): |
|
256 | def applychanges(ui, repo, ctx, opts): | |
215 | """Merge changeset from ctx (only) in the current working directory""" |
|
257 | """Merge changeset from ctx (only) in the current working directory""" | |
216 | wcpar = repo.dirstate.parents()[0] |
|
258 | wcpar = repo.dirstate.parents()[0] | |
@@ -224,14 +266,9 b' def applychanges(ui, repo, ctx, opts):' | |||||
224 | # ui.forcemerge is an internal variable, do not document |
|
266 | # ui.forcemerge is an internal variable, do not document | |
225 | repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), |
|
267 | repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), | |
226 | 'histedit') |
|
268 | 'histedit') | |
227 |
stats = mergemod. |
|
269 | stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'histedit']) | |
228 | ctx.p1().node()) |
|
|||
229 | finally: |
|
270 | finally: | |
230 | repo.ui.setconfig('ui', 'forcemerge', '', 'histedit') |
|
271 | repo.ui.setconfig('ui', 'forcemerge', '', 'histedit') | |
231 | repo.setparents(wcpar, node.nullid) |
|
|||
232 | repo.dirstate.write() |
|
|||
233 | # fix up dirstate for copies and renames |
|
|||
234 | cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev()) |
|
|||
235 | return stats |
|
272 | return stats | |
236 |
|
273 | |||
237 | def collapse(repo, first, last, commitopts): |
|
274 | def collapse(repo, first, last, commitopts): | |
@@ -283,7 +320,7 b' def collapse(repo, first, last, commitop' | |||||
283 | isexec='x' in flags, |
|
320 | isexec='x' in flags, | |
284 | copied=copied.get(path)) |
|
321 | copied=copied.get(path)) | |
285 | return mctx |
|
322 | return mctx | |
286 | raise IOError() |
|
323 | return None | |
287 |
|
324 | |||
288 | if commitopts.get('message'): |
|
325 | if commitopts.get('message'): | |
289 | message = commitopts['message'] |
|
326 | message = commitopts['message'] | |
@@ -294,6 +331,9 b' def collapse(repo, first, last, commitop' | |||||
294 | extra = commitopts.get('extra') |
|
331 | extra = commitopts.get('extra') | |
295 |
|
332 | |||
296 | parents = (first.p1().node(), first.p2().node()) |
|
333 | parents = (first.p1().node(), first.p2().node()) | |
|
334 | editor = None | |||
|
335 | if not commitopts.get('rollup'): | |||
|
336 | editor = cmdutil.getcommiteditor(edit=True, editform='histedit.fold') | |||
297 | new = context.memctx(repo, |
|
337 | new = context.memctx(repo, | |
298 | parents=parents, |
|
338 | parents=parents, | |
299 | text=message, |
|
339 | text=message, | |
@@ -302,10 +342,11 b' def collapse(repo, first, last, commitop' | |||||
302 | user=user, |
|
342 | user=user, | |
303 | date=date, |
|
343 | date=date, | |
304 | extra=extra, |
|
344 | extra=extra, | |
305 |
editor= |
|
345 | editor=editor) | |
306 | return repo.commitctx(new) |
|
346 | return repo.commitctx(new) | |
307 |
|
347 | |||
308 |
def pick(ui, |
|
348 | def pick(ui, state, ha, opts): | |
|
349 | repo, ctx = state.repo, state.parentctx | |||
309 | oldctx = repo[ha] |
|
350 | oldctx = repo[ha] | |
310 | if oldctx.parents()[0] == ctx: |
|
351 | if oldctx.parents()[0] == ctx: | |
311 | ui.debug('node %s unchanged\n' % ha) |
|
352 | ui.debug('node %s unchanged\n' % ha) | |
@@ -320,14 +361,14 b' def pick(ui, repo, ctx, ha, opts):' | |||||
320 | n = commit(text=oldctx.description(), user=oldctx.user(), |
|
361 | n = commit(text=oldctx.description(), user=oldctx.user(), | |
321 | date=oldctx.date(), extra=oldctx.extra()) |
|
362 | date=oldctx.date(), extra=oldctx.extra()) | |
322 | if n is None: |
|
363 | if n is None: | |
323 | ui.warn(_('%s: empty changeset\n') |
|
364 | ui.warn(_('%s: empty changeset\n') % node.hex(ha)) | |
324 | % node.hex(ha)) |
|
|||
325 | return ctx, [] |
|
365 | return ctx, [] | |
326 | new = repo[n] |
|
366 | new = repo[n] | |
327 | return new, [(oldctx.node(), (n,))] |
|
367 | return new, [(oldctx.node(), (n,))] | |
328 |
|
368 | |||
329 |
|
369 | |||
330 |
def edit(ui, |
|
370 | def edit(ui, state, ha, opts): | |
|
371 | repo, ctx = state.repo, state.parentctx | |||
331 | oldctx = repo[ha] |
|
372 | oldctx = repo[ha] | |
332 | hg.update(repo, ctx.node()) |
|
373 | hg.update(repo, ctx.node()) | |
333 | applychanges(ui, repo, oldctx, opts) |
|
374 | applychanges(ui, repo, oldctx, opts) | |
@@ -335,7 +376,13 b' def edit(ui, repo, ctx, ha, opts):' | |||||
335 | _('Make changes as needed, you may commit or record as needed now.\n' |
|
376 | _('Make changes as needed, you may commit or record as needed now.\n' | |
336 | 'When you are finished, run hg histedit --continue to resume.')) |
|
377 | 'When you are finished, run hg histedit --continue to resume.')) | |
337 |
|
378 | |||
338 |
def |
|
379 | def rollup(ui, state, ha, opts): | |
|
380 | rollupopts = opts.copy() | |||
|
381 | rollupopts['rollup'] = True | |||
|
382 | return fold(ui, state, ha, rollupopts) | |||
|
383 | ||||
|
384 | def fold(ui, state, ha, opts): | |||
|
385 | repo, ctx = state.repo, state.parentctx | |||
339 | oldctx = repo[ha] |
|
386 | oldctx = repo[ha] | |
340 | hg.update(repo, ctx.node()) |
|
387 | hg.update(repo, ctx.node()) | |
341 | stats = applychanges(ui, repo, oldctx, opts) |
|
388 | stats = applychanges(ui, repo, oldctx, opts) | |
@@ -345,8 +392,7 b' def fold(ui, repo, ctx, ha, opts):' | |||||
345 | n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(), |
|
392 | n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(), | |
346 | date=oldctx.date(), extra=oldctx.extra()) |
|
393 | date=oldctx.date(), extra=oldctx.extra()) | |
347 | if n is None: |
|
394 | if n is None: | |
348 | ui.warn(_('%s: empty changeset') |
|
395 | ui.warn(_('%s: empty changeset') % node.hex(ha)) | |
349 | % node.hex(ha)) |
|
|||
350 | return ctx, [] |
|
396 | return ctx, [] | |
351 | return finishfold(ui, repo, ctx, oldctx, n, opts, []) |
|
397 | return finishfold(ui, repo, ctx, oldctx, n, opts, []) | |
352 |
|
398 | |||
@@ -357,10 +403,13 b' def finishfold(ui, repo, ctx, oldctx, ne' | |||||
357 | commitopts = opts.copy() |
|
403 | commitopts = opts.copy() | |
358 | commitopts['user'] = ctx.user() |
|
404 | commitopts['user'] = ctx.user() | |
359 | # commit message |
|
405 | # commit message | |
360 | newmessage = '\n***\n'.join( |
|
406 | if opts.get('rollup'): | |
361 |
|
|
407 | newmessage = ctx.description() | |
362 | [repo[r].description() for r in internalchanges] + |
|
408 | else: | |
363 | [oldctx.description()]) + '\n' |
|
409 | newmessage = '\n***\n'.join( | |
|
410 | [ctx.description()] + | |||
|
411 | [repo[r].description() for r in internalchanges] + | |||
|
412 | [oldctx.description()]) + '\n' | |||
364 | commitopts['message'] = newmessage |
|
413 | commitopts['message'] = newmessage | |
365 | # date |
|
414 | # date | |
366 | commitopts['date'] = max(ctx.date(), oldctx.date()) |
|
415 | commitopts['date'] = max(ctx.date(), oldctx.date()) | |
@@ -381,18 +430,20 b' def finishfold(ui, repo, ctx, oldctx, ne' | |||||
381 | return ctx, [] |
|
430 | return ctx, [] | |
382 | hg.update(repo, n) |
|
431 | hg.update(repo, n) | |
383 | replacements = [(oldctx.node(), (newnode,)), |
|
432 | replacements = [(oldctx.node(), (newnode,)), | |
384 |
|
|
433 | (ctx.node(), (n,)), | |
385 |
|
|
434 | (newnode, (n,)), | |
386 |
|
|
435 | ] | |
387 | for ich in internalchanges: |
|
436 | for ich in internalchanges: | |
388 | replacements.append((ich, (n,))) |
|
437 | replacements.append((ich, (n,))) | |
389 | return repo[n], replacements |
|
438 | return repo[n], replacements | |
390 |
|
439 | |||
391 |
def drop(ui, |
|
440 | def drop(ui, state, ha, opts): | |
|
441 | repo, ctx = state.repo, state.parentctx | |||
392 | return ctx, [(repo[ha].node(), ())] |
|
442 | return ctx, [(repo[ha].node(), ())] | |
393 |
|
443 | |||
394 |
|
444 | |||
395 |
def message(ui, |
|
445 | def message(ui, state, ha, opts): | |
|
446 | repo, ctx = state.repo, state.parentctx | |||
396 | oldctx = repo[ha] |
|
447 | oldctx = repo[ha] | |
397 | hg.update(repo, ctx.node()) |
|
448 | hg.update(repo, ctx.node()) | |
398 | stats = applychanges(ui, repo, oldctx, opts) |
|
449 | stats = applychanges(ui, repo, oldctx, opts) | |
@@ -401,9 +452,9 b' def message(ui, repo, ctx, ha, opts):' | |||||
401 | _('Fix up the change and run hg histedit --continue')) |
|
452 | _('Fix up the change and run hg histedit --continue')) | |
402 | message = oldctx.description() |
|
453 | message = oldctx.description() | |
403 | commit = commitfuncfor(repo, oldctx) |
|
454 | commit = commitfuncfor(repo, oldctx) | |
|
455 | editor = cmdutil.getcommiteditor(edit=True, editform='histedit.mess') | |||
404 | new = commit(text=message, user=oldctx.user(), date=oldctx.date(), |
|
456 | new = commit(text=message, user=oldctx.user(), date=oldctx.date(), | |
405 | extra=oldctx.extra(), |
|
457 | extra=oldctx.extra(), editor=editor) | |
406 | editor=cmdutil.getcommiteditor(edit=True)) |
|
|||
407 | newctx = repo[new] |
|
458 | newctx = repo[new] | |
408 | if oldctx.node() != newctx.node(): |
|
459 | if oldctx.node() != newctx.node(): | |
409 | return newctx, [(oldctx.node(), (new,))] |
|
460 | return newctx, [(oldctx.node(), (new,))] | |
@@ -440,6 +491,8 b" actiontable = {'p': pick," | |||||
440 | 'edit': edit, |
|
491 | 'edit': edit, | |
441 | 'f': fold, |
|
492 | 'f': fold, | |
442 | 'fold': fold, |
|
493 | 'fold': fold, | |
|
494 | 'r': rollup, | |||
|
495 | 'roll': rollup, | |||
443 | 'd': drop, |
|
496 | 'd': drop, | |
444 | 'drop': drop, |
|
497 | 'drop': drop, | |
445 | 'm': message, |
|
498 | 'm': message, | |
@@ -481,15 +534,15 b' def histedit(ui, repo, *freeargs, **opts' | |||||
481 | for intentional "edit" command, but also for resolving unexpected |
|
534 | for intentional "edit" command, but also for resolving unexpected | |
482 | conflicts). |
|
535 | conflicts). | |
483 | """ |
|
536 | """ | |
484 | lock = wlock = None |
|
537 | state = histeditstate(repo) | |
485 | try: |
|
538 | try: | |
486 | wlock = repo.wlock() |
|
539 | state.wlock = repo.wlock() | |
487 | lock = repo.lock() |
|
540 | state.lock = repo.lock() | |
488 | _histedit(ui, repo, *freeargs, **opts) |
|
541 | _histedit(ui, repo, state, *freeargs, **opts) | |
489 | finally: |
|
542 | finally: | |
490 | release(lock, wlock) |
|
543 | release(state.lock, state.wlock) | |
491 |
|
544 | |||
492 | def _histedit(ui, repo, *freeargs, **opts): |
|
545 | def _histedit(ui, repo, state, *freeargs, **opts): | |
493 | # TODO only abort if we try and histedit mq patches, not just |
|
546 | # TODO only abort if we try and histedit mq patches, not just | |
494 | # blanket if mq patches are applied somewhere |
|
547 | # blanket if mq patches are applied somewhere | |
495 | mq = getattr(repo, 'mq', None) |
|
548 | mq = getattr(repo, 'mq', None) | |
@@ -531,26 +584,30 b' def _histedit(ui, repo, *freeargs, **opt' | |||||
531 | _('histedit requires exactly one ancestor revision')) |
|
584 | _('histedit requires exactly one ancestor revision')) | |
532 |
|
585 | |||
533 |
|
586 | |||
|
587 | replacements = [] | |||
|
588 | keep = opts.get('keep', False) | |||
|
589 | ||||
|
590 | # rebuild state | |||
534 | if goal == 'continue': |
|
591 | if goal == 'continue': | |
535 | (parentctxnode, rules, keep, topmost, replacements) = readstate(repo) |
|
592 | state = histeditstate(repo) | |
536 | parentctx = repo[parentctxnode] |
|
593 | state.read() | |
537 |
|
|
594 | state = bootstrapcontinue(ui, state, opts) | |
538 | replacements.extend(repl) |
|
|||
539 | elif goal == 'abort': |
|
595 | elif goal == 'abort': | |
540 | (parentctxnode, rules, keep, topmost, replacements) = readstate(repo) |
|
596 | state = histeditstate(repo) | |
541 | mapping, tmpnodes, leafs, _ntm = processreplacement(repo, replacements) |
|
597 | state.read() | |
542 | ui.debug('restore wc to old parent %s\n' % node.short(topmost)) |
|
598 | mapping, tmpnodes, leafs, _ntm = processreplacement(state) | |
|
599 | ui.debug('restore wc to old parent %s\n' % node.short(state.topmost)) | |||
543 | # check whether we should update away |
|
600 | # check whether we should update away | |
544 | parentnodes = [c.node() for c in repo[None].parents()] |
|
601 | parentnodes = [c.node() for c in repo[None].parents()] | |
545 | for n in leafs | set([parentctxnode]): |
|
602 | for n in leafs | set([state.parentctx.node()]): | |
546 | if n in parentnodes: |
|
603 | if n in parentnodes: | |
547 | hg.clean(repo, topmost) |
|
604 | hg.clean(repo, state.topmost) | |
548 | break |
|
605 | break | |
549 | else: |
|
606 | else: | |
550 | pass |
|
607 | pass | |
551 | cleanupnode(ui, repo, 'created', tmpnodes) |
|
608 | cleanupnode(ui, repo, 'created', tmpnodes) | |
552 | cleanupnode(ui, repo, 'temp', leafs) |
|
609 | cleanupnode(ui, repo, 'temp', leafs) | |
553 | os.unlink(os.path.join(repo.path, 'histedit-state')) |
|
610 | state.clear() | |
554 | return |
|
611 | return | |
555 | else: |
|
612 | else: | |
556 | cmdutil.checkunfinished(repo) |
|
613 | cmdutil.checkunfinished(repo) | |
@@ -570,7 +627,6 b' def _histedit(ui, repo, *freeargs, **opt' | |||||
570 | 'exactly one common root')) |
|
627 | 'exactly one common root')) | |
571 | root = rr[0].node() |
|
628 | root = rr[0].node() | |
572 |
|
629 | |||
573 | keep = opts.get('keep', False) |
|
|||
574 | revs = between(repo, root, topmost, keep) |
|
630 | revs = between(repo, root, topmost, keep) | |
575 | if not revs: |
|
631 | if not revs: | |
576 | raise util.Abort(_('%s is not an ancestor of working directory') % |
|
632 | raise util.Abort(_('%s is not an ancestor of working directory') % | |
@@ -596,25 +652,28 b' def _histedit(ui, repo, *freeargs, **opt' | |||||
596 | rules = f.read() |
|
652 | rules = f.read() | |
597 | f.close() |
|
653 | f.close() | |
598 | rules = [l for l in (r.strip() for r in rules.splitlines()) |
|
654 | rules = [l for l in (r.strip() for r in rules.splitlines()) | |
599 |
if l and not l |
|
655 | if l and not l.startswith('#')] | |
600 | rules = verifyrules(rules, repo, ctxs) |
|
656 | rules = verifyrules(rules, repo, ctxs) | |
601 |
|
657 | |||
602 | parentctx = repo[root].parents()[0] |
|
658 | parentctx = repo[root].parents()[0] | |
603 | keep = opts.get('keep', False) |
|
|||
604 | replacements = [] |
|
|||
605 |
|
||||
606 |
|
659 | |||
607 | while rules: |
|
660 | state.parentctx = parentctx | |
608 | writestate(repo, parentctx.node(), rules, keep, topmost, replacements) |
|
661 | state.rules = rules | |
609 | action, ha = rules.pop(0) |
|
662 | state.keep = keep | |
|
663 | state.topmost = topmost | |||
|
664 | state.replacements = replacements | |||
|
665 | ||||
|
666 | while state.rules: | |||
|
667 | state.write() | |||
|
668 | action, ha = state.rules.pop(0) | |||
610 | ui.debug('histedit: processing %s %s\n' % (action, ha)) |
|
669 | ui.debug('histedit: processing %s %s\n' % (action, ha)) | |
611 | actfunc = actiontable[action] |
|
670 | actfunc = actiontable[action] | |
612 |
parentctx, replacement_ = actfunc(ui, |
|
671 | state.parentctx, replacement_ = actfunc(ui, state, ha, opts) | |
613 | replacements.extend(replacement_) |
|
672 | state.replacements.extend(replacement_) | |
614 |
|
673 | |||
615 | hg.update(repo, parentctx.node()) |
|
674 | hg.update(repo, state.parentctx.node()) | |
616 |
|
675 | |||
617 |
mapping, tmpnodes, created, ntm = processreplacement( |
|
676 | mapping, tmpnodes, created, ntm = processreplacement(state) | |
618 | if mapping: |
|
677 | if mapping: | |
619 | for prec, succs in mapping.iteritems(): |
|
678 | for prec, succs in mapping.iteritems(): | |
620 | if not succs: |
|
679 | if not succs: | |
@@ -629,9 +688,9 b' def _histedit(ui, repo, *freeargs, **opt' | |||||
629 |
|
688 | |||
630 | if not keep: |
|
689 | if not keep: | |
631 | if mapping: |
|
690 | if mapping: | |
632 | movebookmarks(ui, repo, mapping, topmost, ntm) |
|
691 | movebookmarks(ui, repo, mapping, state.topmost, ntm) | |
633 | # TODO update mq state |
|
692 | # TODO update mq state | |
634 |
if obsolete. |
|
693 | if obsolete.isenabled(repo, obsolete.createmarkersopt): | |
635 | markers = [] |
|
694 | markers = [] | |
636 | # sort by revision number because it sound "right" |
|
695 | # sort by revision number because it sound "right" | |
637 | for prec in sorted(mapping, key=repo.changelog.rev): |
|
696 | for prec in sorted(mapping, key=repo.changelog.rev): | |
@@ -644,7 +703,7 b' def _histedit(ui, repo, *freeargs, **opt' | |||||
644 | cleanupnode(ui, repo, 'replaced', mapping) |
|
703 | cleanupnode(ui, repo, 'replaced', mapping) | |
645 |
|
704 | |||
646 | cleanupnode(ui, repo, 'temp', tmpnodes) |
|
705 | cleanupnode(ui, repo, 'temp', tmpnodes) | |
647 | os.unlink(os.path.join(repo.path, 'histedit-state')) |
|
706 | state.clear() | |
648 | if os.path.exists(repo.sjoin('undo')): |
|
707 | if os.path.exists(repo.sjoin('undo')): | |
649 | os.unlink(repo.sjoin('undo')) |
|
708 | os.unlink(repo.sjoin('undo')) | |
650 |
|
709 | |||
@@ -664,27 +723,29 b' def gatherchildren(repo, ctx):' | |||||
664 | newchildren.pop(0) # remove ctx |
|
723 | newchildren.pop(0) # remove ctx | |
665 | return newchildren |
|
724 | return newchildren | |
666 |
|
725 | |||
667 |
def bootstrapcontinue(ui, |
|
726 | def bootstrapcontinue(ui, state, opts): | |
668 | action, currentnode = rules.pop(0) |
|
727 | repo, parentctx = state.repo, state.parentctx | |
|
728 | action, currentnode = state.rules.pop(0) | |||
669 | ctx = repo[currentnode] |
|
729 | ctx = repo[currentnode] | |
670 |
|
730 | |||
671 | newchildren = gatherchildren(repo, parentctx) |
|
731 | newchildren = gatherchildren(repo, parentctx) | |
672 |
|
732 | |||
673 | # Commit dirty working directory if necessary |
|
733 | # Commit dirty working directory if necessary | |
674 | new = None |
|
734 | new = None | |
675 |
|
|
735 | s = repo.status() | |
676 | if m or a or r or d: |
|
736 | if s.modified or s.added or s.removed or s.deleted: | |
677 | # prepare the message for the commit to comes |
|
737 | # prepare the message for the commit to comes | |
678 | if action in ('f', 'fold'): |
|
738 | if action in ('f', 'fold', 'r', 'roll'): | |
679 | message = 'fold-temp-revision %s' % currentnode |
|
739 | message = 'fold-temp-revision %s' % currentnode | |
680 | else: |
|
740 | else: | |
681 | message = ctx.description() |
|
741 | message = ctx.description() | |
682 | editopt = action in ('e', 'edit', 'm', 'mess') |
|
742 | editopt = action in ('e', 'edit', 'm', 'mess') | |
683 | editor = cmdutil.getcommiteditor(edit=editopt) |
|
743 | canonaction = {'e': 'edit', 'm': 'mess', 'p': 'pick'} | |
|
744 | editform = 'histedit.%s' % canonaction.get(action, action) | |||
|
745 | editor = cmdutil.getcommiteditor(edit=editopt, editform=editform) | |||
684 | commit = commitfuncfor(repo, ctx) |
|
746 | commit = commitfuncfor(repo, ctx) | |
685 | new = commit(text=message, user=ctx.user(), |
|
747 | new = commit(text=message, user=ctx.user(), date=ctx.date(), | |
686 |
|
|
748 | extra=ctx.extra(), editor=editor) | |
687 | editor=editor) |
|
|||
688 | if new is not None: |
|
749 | if new is not None: | |
689 | newchildren.append(new) |
|
750 | newchildren.append(new) | |
690 |
|
751 | |||
@@ -696,15 +757,19 b' def bootstrapcontinue(ui, repo, parentct' | |||||
696 | # to parent. |
|
757 | # to parent. | |
697 | replacements.append((ctx.node(), tuple(newchildren))) |
|
758 | replacements.append((ctx.node(), tuple(newchildren))) | |
698 |
|
759 | |||
699 | if action in ('f', 'fold'): |
|
760 | if action in ('f', 'fold', 'r', 'roll'): | |
700 | if newchildren: |
|
761 | if newchildren: | |
701 | # finalize fold operation if applicable |
|
762 | # finalize fold operation if applicable | |
702 | if new is None: |
|
763 | if new is None: | |
703 | new = newchildren[-1] |
|
764 | new = newchildren[-1] | |
704 | else: |
|
765 | else: | |
705 | newchildren.pop() # remove new from internal changes |
|
766 | newchildren.pop() # remove new from internal changes | |
706 | parentctx, repl = finishfold(ui, repo, parentctx, ctx, new, opts, |
|
767 | foldopts = opts | |
707 | newchildren) |
|
768 | if action in ('r', 'roll'): | |
|
769 | foldopts = foldopts.copy() | |||
|
770 | foldopts['rollup'] = True | |||
|
771 | parentctx, repl = finishfold(ui, repo, parentctx, ctx, new, | |||
|
772 | foldopts, newchildren) | |||
708 | replacements.extend(repl) |
|
773 | replacements.extend(repl) | |
709 | else: |
|
774 | else: | |
710 | # newchildren is empty if the fold did not result in any commit |
|
775 | # newchildren is empty if the fold did not result in any commit | |
@@ -714,8 +779,11 b' def bootstrapcontinue(ui, repo, parentct' | |||||
714 | elif newchildren: |
|
779 | elif newchildren: | |
715 | # otherwise update "parentctx" before proceeding to further operation |
|
780 | # otherwise update "parentctx" before proceeding to further operation | |
716 | parentctx = repo[newchildren[-1]] |
|
781 | parentctx = repo[newchildren[-1]] | |
717 | return parentctx, replacements |
|
|||
718 |
|
782 | |||
|
783 | state.parentctx = parentctx | |||
|
784 | state.replacements.extend(replacements) | |||
|
785 | ||||
|
786 | return state | |||
719 |
|
787 | |||
720 | def between(repo, old, new, keep): |
|
788 | def between(repo, old, new, keep): | |
721 | """select and validate the set of revision to edit |
|
789 | """select and validate the set of revision to edit | |
@@ -723,34 +791,16 b' def between(repo, old, new, keep):' | |||||
723 | When keep is false, the specified set can't have children.""" |
|
791 | When keep is false, the specified set can't have children.""" | |
724 | ctxs = list(repo.set('%n::%n', old, new)) |
|
792 | ctxs = list(repo.set('%n::%n', old, new)) | |
725 | if ctxs and not keep: |
|
793 | if ctxs and not keep: | |
726 |
if (not obsolete. |
|
794 | if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and | |
727 | repo.revs('(%ld::) - (%ld)', ctxs, ctxs)): |
|
795 | repo.revs('(%ld::) - (%ld)', ctxs, ctxs)): | |
728 | raise util.Abort(_('cannot edit history that would orphan nodes')) |
|
796 | raise util.Abort(_('cannot edit history that would orphan nodes')) | |
729 | if repo.revs('(%ld) and merge()', ctxs): |
|
797 | if repo.revs('(%ld) and merge()', ctxs): | |
730 | raise util.Abort(_('cannot edit history that contains merges')) |
|
798 | raise util.Abort(_('cannot edit history that contains merges')) | |
731 | root = ctxs[0] # list is already sorted by repo.set |
|
799 | root = ctxs[0] # list is already sorted by repo.set | |
732 |
if not root. |
|
800 | if not root.mutable(): | |
733 | raise util.Abort(_('cannot edit immutable changeset: %s') % root) |
|
801 | raise util.Abort(_('cannot edit immutable changeset: %s') % root) | |
734 | return [c.node() for c in ctxs] |
|
802 | return [c.node() for c in ctxs] | |
735 |
|
803 | |||
736 |
|
||||
737 | def writestate(repo, parentnode, rules, keep, topmost, replacements): |
|
|||
738 | fp = open(os.path.join(repo.path, 'histedit-state'), 'w') |
|
|||
739 | pickle.dump((parentnode, rules, keep, topmost, replacements), fp) |
|
|||
740 | fp.close() |
|
|||
741 |
|
||||
742 | def readstate(repo): |
|
|||
743 | """Returns a tuple of (parentnode, rules, keep, topmost, replacements). |
|
|||
744 | """ |
|
|||
745 | try: |
|
|||
746 | fp = open(os.path.join(repo.path, 'histedit-state')) |
|
|||
747 | except IOError, err: |
|
|||
748 | if err.errno != errno.ENOENT: |
|
|||
749 | raise |
|
|||
750 | raise util.Abort(_('no histedit in progress')) |
|
|||
751 | return pickle.load(fp) |
|
|||
752 |
|
||||
753 |
|
||||
754 | def makedesc(c): |
|
804 | def makedesc(c): | |
755 | """build a initial action line for a ctx `c` |
|
805 | """build a initial action line for a ctx `c` | |
756 |
|
806 | |||
@@ -798,12 +848,13 b' def verifyrules(rules, repo, ctxs):' | |||||
798 | hint=_('do you want to use the drop action?')) |
|
848 | hint=_('do you want to use the drop action?')) | |
799 | return parsed |
|
849 | return parsed | |
800 |
|
850 | |||
801 |
def processreplacement( |
|
851 | def processreplacement(state): | |
802 | """process the list of replacements to return |
|
852 | """process the list of replacements to return | |
803 |
|
853 | |||
804 | 1) the final mapping between original and created nodes |
|
854 | 1) the final mapping between original and created nodes | |
805 | 2) the list of temporary node created by histedit |
|
855 | 2) the list of temporary node created by histedit | |
806 | 3) the list of new commit created by histedit""" |
|
856 | 3) the list of new commit created by histedit""" | |
|
857 | replacements = state.replacements | |||
807 | allsuccs = set() |
|
858 | allsuccs = set() | |
808 | replaced = set() |
|
859 | replaced = set() | |
809 | fullmapping = {} |
|
860 | fullmapping = {} | |
@@ -840,20 +891,21 b' def processreplacement(repo, replacement' | |||||
840 | del final[n] |
|
891 | del final[n] | |
841 | # we expect all changes involved in final to exist in the repo |
|
892 | # we expect all changes involved in final to exist in the repo | |
842 | # turn `final` into list (topologically sorted) |
|
893 | # turn `final` into list (topologically sorted) | |
843 | nm = repo.changelog.nodemap |
|
894 | nm = state.repo.changelog.nodemap | |
844 | for prec, succs in final.items(): |
|
895 | for prec, succs in final.items(): | |
845 | final[prec] = sorted(succs, key=nm.get) |
|
896 | final[prec] = sorted(succs, key=nm.get) | |
846 |
|
897 | |||
847 | # computed topmost element (necessary for bookmark) |
|
898 | # computed topmost element (necessary for bookmark) | |
848 | if new: |
|
899 | if new: | |
849 | newtopmost = sorted(new, key=repo.changelog.rev)[-1] |
|
900 | newtopmost = sorted(new, key=state.repo.changelog.rev)[-1] | |
850 | elif not final: |
|
901 | elif not final: | |
851 | # Nothing rewritten at all. we won't need `newtopmost` |
|
902 | # Nothing rewritten at all. we won't need `newtopmost` | |
852 | # It is the same as `oldtopmost` and `processreplacement` know it |
|
903 | # It is the same as `oldtopmost` and `processreplacement` know it | |
853 | newtopmost = None |
|
904 | newtopmost = None | |
854 | else: |
|
905 | else: | |
855 | # every body died. The newtopmost is the parent of the root. |
|
906 | # every body died. The newtopmost is the parent of the root. | |
856 | newtopmost = repo[sorted(final, key=repo.changelog.rev)[0]].p1().node() |
|
907 | r = state.repo.changelog.rev | |
|
908 | newtopmost = state.repo[sorted(final, key=r)[0]].p1().node() | |||
857 |
|
909 | |||
858 | return final, tmpnodes, new, newtopmost |
|
910 | return final, tmpnodes, new, newtopmost | |
859 |
|
911 | |||
@@ -903,7 +955,7 b' def cleanupnode(ui, repo, name, nodes):' | |||||
903 | # Find all node that need to be stripped |
|
955 | # Find all node that need to be stripped | |
904 | # (we hg %lr instead of %ln to silently ignore unknown item |
|
956 | # (we hg %lr instead of %ln to silently ignore unknown item | |
905 | nm = repo.changelog.nodemap |
|
957 | nm = repo.changelog.nodemap | |
906 |
nodes = |
|
958 | nodes = sorted(n for n in nodes if n in nm) | |
907 | roots = [c.node() for c in repo.set("roots(%ln)", nodes)] |
|
959 | roots = [c.node() for c in repo.set("roots(%ln)", nodes)] | |
908 | for c in roots: |
|
960 | for c in roots: | |
909 | # We should process node in reverse order to strip tip most first. |
|
961 | # We should process node in reverse order to strip tip most first. | |
@@ -916,12 +968,13 b' def cleanupnode(ui, repo, name, nodes):' | |||||
916 | def summaryhook(ui, repo): |
|
968 | def summaryhook(ui, repo): | |
917 | if not os.path.exists(repo.join('histedit-state')): |
|
969 | if not os.path.exists(repo.join('histedit-state')): | |
918 | return |
|
970 | return | |
919 | (parentctxnode, rules, keep, topmost, replacements) = readstate(repo) |
|
971 | state = histeditstate(repo) | |
920 | if rules: |
|
972 | state.read() | |
|
973 | if state.rules: | |||
921 | # i18n: column positioning for "hg summary" |
|
974 | # i18n: column positioning for "hg summary" | |
922 | ui.write(_('hist: %s (histedit --continue)\n') % |
|
975 | ui.write(_('hist: %s (histedit --continue)\n') % | |
923 | (ui.label(_('%d remaining'), 'histedit.remaining') % |
|
976 | (ui.label(_('%d remaining'), 'histedit.remaining') % | |
924 | len(rules))) |
|
977 | len(state.rules))) | |
925 |
|
978 | |||
926 | def extsetup(ui): |
|
979 | def extsetup(ui): | |
927 | cmdutil.summaryhooks.add('histedit', summaryhook) |
|
980 | cmdutil.summaryhooks.add('histedit', summaryhook) |
@@ -1,6 +1,6 b'' | |||||
1 | # keyword.py - $Keyword$ expansion for Mercurial |
|
1 | # keyword.py - $Keyword$ expansion for Mercurial | |
2 | # |
|
2 | # | |
3 |
# Copyright 2007-201 |
|
3 | # Copyright 2007-2014 Christian Ebert <blacktrash@gmx.net> | |
4 | # |
|
4 | # | |
5 | # This software may be used and distributed according to the terms of the |
|
5 | # This software may be used and distributed according to the terms of the | |
6 | # GNU General Public License version 2 or any later version. |
|
6 | # GNU General Public License version 2 or any later version. | |
@@ -87,7 +87,7 b' from mercurial import localrepo, match, ' | |||||
87 | from mercurial import scmutil, pathutil |
|
87 | from mercurial import scmutil, pathutil | |
88 | from mercurial.hgweb import webcommands |
|
88 | from mercurial.hgweb import webcommands | |
89 | from mercurial.i18n import _ |
|
89 | from mercurial.i18n import _ | |
90 |
import os, re, |
|
90 | import os, re, tempfile | |
91 |
|
91 | |||
92 | cmdtable = {} |
|
92 | cmdtable = {} | |
93 | command = cmdutil.command(cmdtable) |
|
93 | command = cmdutil.command(cmdtable) | |
@@ -171,9 +171,8 b' def _preselect(wstatus, changed):' | |||||
171 | '''Retrieves modified and added files from a working directory state |
|
171 | '''Retrieves modified and added files from a working directory state | |
172 | and returns the subset of each contained in given changed files |
|
172 | and returns the subset of each contained in given changed files | |
173 | retrieved from a change context.''' |
|
173 | retrieved from a change context.''' | |
174 | modified, added = wstatus[:2] |
|
174 | modified = [f for f in wstatus.modified if f in changed] | |
175 |
|
|
175 | added = [f for f in wstatus.added if f in changed] | |
176 | added = [f for f in added if f in changed] |
|
|||
177 | return modified, added |
|
176 | return modified, added | |
178 |
|
177 | |||
179 |
|
178 | |||
@@ -349,10 +348,9 b' def _kwfwrite(ui, repo, expand, *pats, *' | |||||
349 | wlock = repo.wlock() |
|
348 | wlock = repo.wlock() | |
350 | try: |
|
349 | try: | |
351 | status = _status(ui, repo, wctx, kwt, *pats, **opts) |
|
350 | status = _status(ui, repo, wctx, kwt, *pats, **opts) | |
352 | modified, added, removed, deleted, unknown, ignored, clean = status |
|
351 | if status.modified or status.added or status.removed or status.deleted: | |
353 | if modified or added or removed or deleted: |
|
|||
354 | raise util.Abort(_('outstanding uncommitted changes')) |
|
352 | raise util.Abort(_('outstanding uncommitted changes')) | |
355 | kwt.overwrite(wctx, clean, True, expand) |
|
353 | kwt.overwrite(wctx, status.clean, True, expand) | |
356 | finally: |
|
354 | finally: | |
357 | wlock.release() |
|
355 | wlock.release() | |
358 |
|
356 | |||
@@ -450,7 +448,12 b' def demo(ui, repo, *args, **opts):' | |||||
450 | repo.commit(text=msg) |
|
448 | repo.commit(text=msg) | |
451 | ui.status(_('\n\tkeywords expanded\n')) |
|
449 | ui.status(_('\n\tkeywords expanded\n')) | |
452 | ui.write(repo.wread(fn)) |
|
450 | ui.write(repo.wread(fn)) | |
453 | shutil.rmtree(tmpdir, ignore_errors=True) |
|
451 | for root, dirs, files in os.walk(tmpdir, topdown=False): | |
|
452 | for f in files: | |||
|
453 | util.unlink(os.path.join(root, f)) | |||
|
454 | for d in dirs: | |||
|
455 | os.rmdir(os.path.join(root, d)) | |||
|
456 | os.rmdir(tmpdir) | |||
454 |
|
457 | |||
455 | @command('kwexpand', |
|
458 | @command('kwexpand', | |
456 | commands.walkopts, |
|
459 | commands.walkopts, | |
@@ -498,20 +501,19 b' def files(ui, repo, *pats, **opts):' | |||||
498 | wctx = repo[None] |
|
501 | wctx = repo[None] | |
499 | status = _status(ui, repo, wctx, kwt, *pats, **opts) |
|
502 | status = _status(ui, repo, wctx, kwt, *pats, **opts) | |
500 | cwd = pats and repo.getcwd() or '' |
|
503 | cwd = pats and repo.getcwd() or '' | |
501 | modified, added, removed, deleted, unknown, ignored, clean = status |
|
|||
502 | files = [] |
|
504 | files = [] | |
503 | if not opts.get('unknown') or opts.get('all'): |
|
505 | if not opts.get('unknown') or opts.get('all'): | |
504 | files = sorted(modified + added + clean) |
|
506 | files = sorted(status.modified + status.added + status.clean) | |
505 | kwfiles = kwt.iskwfile(files, wctx) |
|
507 | kwfiles = kwt.iskwfile(files, wctx) | |
506 | kwdeleted = kwt.iskwfile(deleted, wctx) |
|
508 | kwdeleted = kwt.iskwfile(status.deleted, wctx) | |
507 | kwunknown = kwt.iskwfile(unknown, wctx) |
|
509 | kwunknown = kwt.iskwfile(status.unknown, wctx) | |
508 | if not opts.get('ignore') or opts.get('all'): |
|
510 | if not opts.get('ignore') or opts.get('all'): | |
509 | showfiles = kwfiles, kwdeleted, kwunknown |
|
511 | showfiles = kwfiles, kwdeleted, kwunknown | |
510 | else: |
|
512 | else: | |
511 | showfiles = [], [], [] |
|
513 | showfiles = [], [], [] | |
512 | if opts.get('all') or opts.get('ignore'): |
|
514 | if opts.get('all') or opts.get('ignore'): | |
513 | showfiles += ([f for f in files if f not in kwfiles], |
|
515 | showfiles += ([f for f in files if f not in kwfiles], | |
514 | [f for f in unknown if f not in kwunknown]) |
|
516 | [f for f in status.unknown if f not in kwunknown]) | |
515 | kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split() |
|
517 | kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split() | |
516 | kwstates = zip(kwlabels, 'K!kIi', showfiles) |
|
518 | kwstates = zip(kwlabels, 'K!kIi', showfiles) | |
517 | fm = ui.formatter('kwfiles', opts) |
|
519 | fm = ui.formatter('kwfiles', opts) |
@@ -146,7 +146,7 b' def _addchangeset(ui, rsrc, rdst, ctx, r' | |||||
146 | try: |
|
146 | try: | |
147 | fctx = ctx.filectx(lfutil.standin(f)) |
|
147 | fctx = ctx.filectx(lfutil.standin(f)) | |
148 | except error.LookupError: |
|
148 | except error.LookupError: | |
149 |
r |
|
149 | return None | |
150 | renamed = fctx.renamed() |
|
150 | renamed = fctx.renamed() | |
151 | if renamed: |
|
151 | if renamed: | |
152 | renamed = lfutil.splitstandin(renamed[0]) |
|
152 | renamed = lfutil.splitstandin(renamed[0]) | |
@@ -248,7 +248,7 b' def _lfconvert_addchangeset(rsrc, rdst, ' | |||||
248 | try: |
|
248 | try: | |
249 | fctx = ctx.filectx(srcfname) |
|
249 | fctx = ctx.filectx(srcfname) | |
250 | except error.LookupError: |
|
250 | except error.LookupError: | |
251 |
r |
|
251 | return None | |
252 | renamed = fctx.renamed() |
|
252 | renamed = fctx.renamed() | |
253 | if renamed: |
|
253 | if renamed: | |
254 | # standin is always a largefile because largefile-ness |
|
254 | # standin is always a largefile because largefile-ness | |
@@ -298,7 +298,7 b' def _getnormalcontext(repo, ctx, f, revm' | |||||
298 | try: |
|
298 | try: | |
299 | fctx = ctx.filectx(f) |
|
299 | fctx = ctx.filectx(f) | |
300 | except error.LookupError: |
|
300 | except error.LookupError: | |
301 |
r |
|
301 | return None | |
302 | renamed = fctx.renamed() |
|
302 | renamed = fctx.renamed() | |
303 | if renamed: |
|
303 | if renamed: | |
304 | renamed = renamed[0] |
|
304 | renamed = renamed[0] | |
@@ -443,6 +443,7 b' def updatelfiles(ui, repo, filelist=None' | |||||
443 | lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate) |
|
443 | lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate) | |
444 |
|
444 | |||
445 | if filelist is not None: |
|
445 | if filelist is not None: | |
|
446 | filelist = set(filelist) | |||
446 | lfiles = [f for f in lfiles if f in filelist] |
|
447 | lfiles = [f for f in lfiles if f in filelist] | |
447 |
|
448 | |||
448 | update = {} |
|
449 | update = {} | |
@@ -510,26 +511,20 b' def updatelfiles(ui, repo, filelist=None' | |||||
510 |
|
511 | |||
511 | updated += update1 |
|
512 | updated += update1 | |
512 |
|
513 | |||
513 | standin = lfutil.standin(lfile) |
|
514 | lfutil.synclfdirstate(repo, lfdirstate, lfile, normallookup) | |
514 | if standin in repo.dirstate: |
|
515 | ||
515 | stat = repo.dirstate._map[standin] |
|
516 | if filelist is not None: | |
516 | state, mtime = stat[0], stat[3] |
|
517 | # If "local largefile" is chosen at file merging, it is | |
517 | else: |
|
518 | # not listed in "filelist" (= dirstate syncing is | |
518 | state, mtime = '?', -1 |
|
519 | # omitted), because the standin file is not changed before and | |
519 |
|
|
520 | # after merging. | |
520 | if normallookup or mtime < 0: |
|
521 | # But the status of such files may have to be changed by | |
521 | # state 'n' doesn't ensure 'clean' in this case |
|
522 | # merging. For example, locally modified ("M") largefile | |
522 | lfdirstate.normallookup(lfile) |
|
523 | # has to become re-added("A"), if it is "normal" file in | |
523 | else: |
|
524 | # the target revision of linear-merging. | |
524 |
|
|
525 | for lfile in lfdirstate: | |
525 | elif state == 'm': |
|
526 | if lfile not in filelist: | |
526 | lfdirstate.normallookup(lfile) |
|
527 | lfutil.synclfdirstate(repo, lfdirstate, lfile, True) | |
527 | elif state == 'r': |
|
|||
528 | lfdirstate.remove(lfile) |
|
|||
529 | elif state == 'a': |
|
|||
530 | lfdirstate.add(lfile) |
|
|||
531 | elif state == '?': |
|
|||
532 | lfdirstate.drop(lfile) |
|
|||
533 |
|
528 | |||
534 | lfdirstate.write() |
|
529 | lfdirstate.write() | |
535 | if printmessage and lfiles: |
|
530 | if printmessage and lfiles: |
@@ -134,13 +134,14 b' def openlfdirstate(ui, repo, create=True' | |||||
134 | lfdirstate.normallookup(lfile) |
|
134 | lfdirstate.normallookup(lfile) | |
135 | return lfdirstate |
|
135 | return lfdirstate | |
136 |
|
136 | |||
137 |
def lfdirstatestatus(lfdirstate, repo |
|
137 | def lfdirstatestatus(lfdirstate, repo): | |
|
138 | wctx = repo['.'] | |||
138 | match = match_.always(repo.root, repo.getcwd()) |
|
139 | match = match_.always(repo.root, repo.getcwd()) | |
139 | s = lfdirstate.status(match, [], False, False, False) |
|
140 | unsure, s = lfdirstate.status(match, [], False, False, False) | |
140 | unsure, modified, added, removed, missing, unknown, ignored, clean = s |
|
141 | modified, clean = s.modified, s.clean | |
141 | for lfile in unsure: |
|
142 | for lfile in unsure: | |
142 | try: |
|
143 | try: | |
143 |
fctx = |
|
144 | fctx = wctx[standin(lfile)] | |
144 | except LookupError: |
|
145 | except LookupError: | |
145 | fctx = None |
|
146 | fctx = None | |
146 | if not fctx or fctx.data().strip() != hashfile(repo.wjoin(lfile)): |
|
147 | if not fctx or fctx.data().strip() != hashfile(repo.wjoin(lfile)): | |
@@ -148,7 +149,7 b' def lfdirstatestatus(lfdirstate, repo, r' | |||||
148 | else: |
|
149 | else: | |
149 | clean.append(lfile) |
|
150 | clean.append(lfile) | |
150 | lfdirstate.normal(lfile) |
|
151 | lfdirstate.normal(lfile) | |
151 | return (modified, added, removed, missing, unknown, ignored, clean) |
|
152 | return s | |
152 |
|
153 | |||
153 | def listlfiles(repo, rev=None, matcher=None): |
|
154 | def listlfiles(repo, rev=None, matcher=None): | |
154 | '''return a list of largefiles in the working copy or the |
|
155 | '''return a list of largefiles in the working copy or the | |
@@ -363,6 +364,28 b' def getstandinsstate(repo):' | |||||
363 | standins.append((lfile, hash)) |
|
364 | standins.append((lfile, hash)) | |
364 | return standins |
|
365 | return standins | |
365 |
|
366 | |||
|
367 | def synclfdirstate(repo, lfdirstate, lfile, normallookup): | |||
|
368 | lfstandin = standin(lfile) | |||
|
369 | if lfstandin in repo.dirstate: | |||
|
370 | stat = repo.dirstate._map[lfstandin] | |||
|
371 | state, mtime = stat[0], stat[3] | |||
|
372 | else: | |||
|
373 | state, mtime = '?', -1 | |||
|
374 | if state == 'n': | |||
|
375 | if normallookup or mtime < 0: | |||
|
376 | # state 'n' doesn't ensure 'clean' in this case | |||
|
377 | lfdirstate.normallookup(lfile) | |||
|
378 | else: | |||
|
379 | lfdirstate.normal(lfile) | |||
|
380 | elif state == 'm': | |||
|
381 | lfdirstate.normallookup(lfile) | |||
|
382 | elif state == 'r': | |||
|
383 | lfdirstate.remove(lfile) | |||
|
384 | elif state == 'a': | |||
|
385 | lfdirstate.add(lfile) | |||
|
386 | elif state == '?': | |||
|
387 | lfdirstate.drop(lfile) | |||
|
388 | ||||
366 | def getlfilestoupdate(oldstandins, newstandins): |
|
389 | def getlfilestoupdate(oldstandins, newstandins): | |
367 | changedstandins = set(oldstandins).symmetric_difference(set(newstandins)) |
|
390 | changedstandins = set(oldstandins).symmetric_difference(set(newstandins)) | |
368 | filelist = [] |
|
391 | filelist = [] |
@@ -12,7 +12,7 b' import os' | |||||
12 | import copy |
|
12 | import copy | |
13 |
|
13 | |||
14 | from mercurial import hg, commands, util, cmdutil, scmutil, match as match_, \ |
|
14 | from mercurial import hg, commands, util, cmdutil, scmutil, match as match_, \ | |
15 |
archival |
|
15 | archival, pathutil, revset | |
16 | from mercurial.i18n import _ |
|
16 | from mercurial.i18n import _ | |
17 | from mercurial.node import hex |
|
17 | from mercurial.node import hex | |
18 | from hgext import rebase |
|
18 | from hgext import rebase | |
@@ -115,13 +115,13 b' def addlargefiles(ui, repo, *pats, **opt' | |||||
115 | ui.status(_('adding %s as a largefile\n') % m.rel(f)) |
|
115 | ui.status(_('adding %s as a largefile\n') % m.rel(f)) | |
116 |
|
116 | |||
117 | bad = [] |
|
117 | bad = [] | |
118 | standins = [] |
|
|||
119 |
|
118 | |||
120 | # Need to lock, otherwise there could be a race condition between |
|
119 | # Need to lock, otherwise there could be a race condition between | |
121 | # when standins are created and added to the repo. |
|
120 | # when standins are created and added to the repo. | |
122 | wlock = repo.wlock() |
|
121 | wlock = repo.wlock() | |
123 | try: |
|
122 | try: | |
124 | if not opts.get('dry_run'): |
|
123 | if not opts.get('dry_run'): | |
|
124 | standins = [] | |||
125 | lfdirstate = lfutil.openlfdirstate(ui, repo) |
|
125 | lfdirstate = lfutil.openlfdirstate(ui, repo) | |
126 | for f in lfnames: |
|
126 | for f in lfnames: | |
127 | standinname = lfutil.standin(f) |
|
127 | standinname = lfutil.standin(f) | |
@@ -140,7 +140,7 b' def addlargefiles(ui, repo, *pats, **opt' | |||||
140 | wlock.release() |
|
140 | wlock.release() | |
141 | return bad |
|
141 | return bad | |
142 |
|
142 | |||
143 | def removelargefiles(ui, repo, *pats, **opts): |
|
143 | def removelargefiles(ui, repo, isaddremove, *pats, **opts): | |
144 | after = opts.get('after') |
|
144 | after = opts.get('after') | |
145 | if not pats and not after: |
|
145 | if not pats and not after: | |
146 | raise util.Abort(_('no files specified')) |
|
146 | raise util.Abort(_('no files specified')) | |
@@ -153,7 +153,8 b' def removelargefiles(ui, repo, *pats, **' | |||||
153 | manifest = repo[None].manifest() |
|
153 | manifest = repo[None].manifest() | |
154 | modified, added, deleted, clean = [[f for f in list |
|
154 | modified, added, deleted, clean = [[f for f in list | |
155 | if lfutil.standin(f) in manifest] |
|
155 | if lfutil.standin(f) in manifest] | |
156 |
for list in |
|
156 | for list in (s.modified, s.added, | |
|
157 | s.deleted, s.clean)] | |||
157 |
|
158 | |||
158 | def warn(files, msg): |
|
159 | def warn(files, msg): | |
159 | for f in files: |
|
160 | for f in files: | |
@@ -163,17 +164,17 b' def removelargefiles(ui, repo, *pats, **' | |||||
163 | result = 0 |
|
164 | result = 0 | |
164 |
|
165 | |||
165 | if after: |
|
166 | if after: | |
166 |
remove |
|
167 | remove = deleted | |
167 | result = warn(modified + added + clean, |
|
168 | result = warn(modified + added + clean, | |
168 | _('not removing %s: file still exists\n')) |
|
169 | _('not removing %s: file still exists\n')) | |
169 | else: |
|
170 | else: | |
170 |
remove |
|
171 | remove = deleted + clean | |
171 | result = warn(modified, _('not removing %s: file is modified (use -f' |
|
172 | result = warn(modified, _('not removing %s: file is modified (use -f' | |
172 | ' to force removal)\n')) |
|
173 | ' to force removal)\n')) | |
173 | result = warn(added, _('not removing %s: file has been marked for add' |
|
174 | result = warn(added, _('not removing %s: file has been marked for add' | |
174 | ' (use forget to undo)\n')) or result |
|
175 | ' (use forget to undo)\n')) or result | |
175 |
|
176 | |||
176 |
for f in sorted(remove |
|
177 | for f in sorted(remove): | |
177 | if ui.verbose or not m.exact(f): |
|
178 | if ui.verbose or not m.exact(f): | |
178 | ui.status(_('removing %s\n') % m.rel(f)) |
|
179 | ui.status(_('removing %s\n') % m.rel(f)) | |
179 |
|
180 | |||
@@ -186,17 +187,15 b' def removelargefiles(ui, repo, *pats, **' | |||||
186 | if not after: |
|
187 | if not after: | |
187 | # If this is being called by addremove, notify the user that we |
|
188 | # If this is being called by addremove, notify the user that we | |
188 | # are removing the file. |
|
189 | # are removing the file. | |
189 |
if |
|
190 | if isaddremove: | |
190 | ui.status(_('removing %s\n') % f) |
|
191 | ui.status(_('removing %s\n') % f) | |
191 | util.unlinkpath(repo.wjoin(f), ignoremissing=True) |
|
192 | util.unlinkpath(repo.wjoin(f), ignoremissing=True) | |
192 | lfdirstate.remove(f) |
|
193 | lfdirstate.remove(f) | |
193 | lfdirstate.write() |
|
194 | lfdirstate.write() | |
194 | forget = [lfutil.standin(f) for f in forget] |
|
|||
195 | remove = [lfutil.standin(f) for f in remove] |
|
195 | remove = [lfutil.standin(f) for f in remove] | |
196 | repo[None].forget(forget) |
|
|||
197 | # If this is being called by addremove, let the original addremove |
|
196 | # If this is being called by addremove, let the original addremove | |
198 | # function handle this. |
|
197 | # function handle this. | |
199 |
if not |
|
198 | if not isaddremove: | |
200 | for f in remove: |
|
199 | for f in remove: | |
201 | util.unlinkpath(repo.wjoin(f), ignoremissing=True) |
|
200 | util.unlinkpath(repo.wjoin(f), ignoremissing=True) | |
202 | repo[None].forget(remove) |
|
201 | repo[None].forget(remove) | |
@@ -233,7 +232,7 b' def overrideremove(orig, ui, repo, *pats' | |||||
233 | installnormalfilesmatchfn(repo[None].manifest()) |
|
232 | installnormalfilesmatchfn(repo[None].manifest()) | |
234 | result = orig(ui, repo, *pats, **opts) |
|
233 | result = orig(ui, repo, *pats, **opts) | |
235 | restorematchfn() |
|
234 | restorematchfn() | |
236 | return removelargefiles(ui, repo, *pats, **opts) or result |
|
235 | return removelargefiles(ui, repo, False, *pats, **opts) or result | |
237 |
|
236 | |||
238 | def overridestatusfn(orig, repo, rev2, **opts): |
|
237 | def overridestatusfn(orig, repo, rev2, **opts): | |
239 | try: |
|
238 | try: | |
@@ -352,13 +351,13 b' def overrideupdate(orig, ui, repo, *pats' | |||||
352 | # largefiles getting updated |
|
351 | # largefiles getting updated | |
353 | wlock = repo.wlock() |
|
352 | wlock = repo.wlock() | |
354 | try: |
|
353 | try: | |
355 | lfdirstate = lfutil.openlfdirstate(ui, repo) |
|
354 | if opts['check']: | |
356 | s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), |
|
355 | lfdirstate = lfutil.openlfdirstate(ui, repo) | |
357 | [], False, False, False) |
|
356 | unsure, s = lfdirstate.status( | |
358 | (unsure, modified, added, removed, missing, unknown, ignored, clean) = s |
|
357 | match_.always(repo.root, repo.getcwd()), | |
|
358 | [], False, False, False) | |||
359 |
|
359 | |||
360 | if opts['check']: |
|
360 | mod = len(s.modified) > 0 | |
361 | mod = len(modified) > 0 |
|
|||
362 | for lfile in unsure: |
|
361 | for lfile in unsure: | |
363 | standin = lfutil.standin(lfile) |
|
362 | standin = lfutil.standin(lfile) | |
364 | if repo['.'][standin].data().strip() != \ |
|
363 | if repo['.'][standin].data().strip() != \ | |
@@ -369,10 +368,6 b' def overrideupdate(orig, ui, repo, *pats' | |||||
369 | lfdirstate.write() |
|
368 | lfdirstate.write() | |
370 | if mod: |
|
369 | if mod: | |
371 | raise util.Abort(_('uncommitted changes')) |
|
370 | raise util.Abort(_('uncommitted changes')) | |
372 | # XXX handle removed differently |
|
|||
373 | if not opts['clean']: |
|
|||
374 | for lfile in unsure + modified + added: |
|
|||
375 | lfutil.updatestandin(repo, lfutil.standin(lfile)) |
|
|||
376 | return orig(ui, repo, *pats, **opts) |
|
371 | return orig(ui, repo, *pats, **opts) | |
377 | finally: |
|
372 | finally: | |
378 | wlock.release() |
|
373 | wlock.release() | |
@@ -430,6 +425,7 b' def overridecalculateupdates(origfn, rep' | |||||
430 | removes = set(a[0] for a in actions['r']) |
|
425 | removes = set(a[0] for a in actions['r']) | |
431 |
|
426 | |||
432 | newglist = [] |
|
427 | newglist = [] | |
|
428 | lfmr = [] # LargeFiles: Mark as Removed | |||
433 | for action in actions['g']: |
|
429 | for action in actions['g']: | |
434 | f, args, msg = action |
|
430 | f, args, msg = action | |
435 | splitstandin = f and lfutil.splitstandin(f) |
|
431 | splitstandin = f and lfutil.splitstandin(f) | |
@@ -456,7 +452,16 b' def overridecalculateupdates(origfn, rep' | |||||
456 | 'keep (l)argefile or use (n)ormal file?' |
|
452 | 'keep (l)argefile or use (n)ormal file?' | |
457 | '$$ &Largefile $$ &Normal file') % lfile |
|
453 | '$$ &Largefile $$ &Normal file') % lfile | |
458 | if repo.ui.promptchoice(msg, 0) == 0: |
|
454 | if repo.ui.promptchoice(msg, 0) == 0: | |
459 | actions['r'].append((lfile, None, msg)) |
|
455 | if branchmerge: | |
|
456 | # largefile can be restored from standin safely | |||
|
457 | actions['r'].append((lfile, None, msg)) | |||
|
458 | else: | |||
|
459 | # "lfile" should be marked as "removed" without | |||
|
460 | # removal of itself | |||
|
461 | lfmr.append((lfile, None, msg)) | |||
|
462 | ||||
|
463 | # linear-merge should treat this largefile as 're-added' | |||
|
464 | actions['a'].append((standin, None, msg)) | |||
460 | else: |
|
465 | else: | |
461 | actions['r'].append((standin, None, msg)) |
|
466 | actions['r'].append((standin, None, msg)) | |
462 | newglist.append((lfile, (p2.flags(lfile),), msg)) |
|
467 | newglist.append((lfile, (p2.flags(lfile),), msg)) | |
@@ -465,9 +470,22 b' def overridecalculateupdates(origfn, rep' | |||||
465 |
|
470 | |||
466 | newglist.sort() |
|
471 | newglist.sort() | |
467 | actions['g'] = newglist |
|
472 | actions['g'] = newglist | |
|
473 | if lfmr: | |||
|
474 | lfmr.sort() | |||
|
475 | actions['lfmr'] = lfmr | |||
468 |
|
476 | |||
469 | return actions |
|
477 | return actions | |
470 |
|
478 | |||
|
479 | def mergerecordupdates(orig, repo, actions, branchmerge): | |||
|
480 | if 'lfmr' in actions: | |||
|
481 | # this should be executed before 'orig', to execute 'remove' | |||
|
482 | # before all other actions | |||
|
483 | for lfile, args, msg in actions['lfmr']: | |||
|
484 | repo.dirstate.remove(lfile) | |||
|
485 | ||||
|
486 | return orig(repo, actions, branchmerge) | |||
|
487 | ||||
|
488 | ||||
471 | # Override filemerge to prompt the user about how they wish to merge |
|
489 | # Override filemerge to prompt the user about how they wish to merge | |
472 | # largefiles. This will handle identical edits without prompting the user. |
|
490 | # largefiles. This will handle identical edits without prompting the user. | |
473 | def overridefilemerge(origfn, repo, mynode, orig, fcd, fco, fca, labels=None): |
|
491 | def overridefilemerge(origfn, repo, mynode, orig, fcd, fco, fca, labels=None): | |
@@ -643,12 +661,11 b' def overriderevert(orig, ui, repo, *pats' | |||||
643 | wlock = repo.wlock() |
|
661 | wlock = repo.wlock() | |
644 | try: |
|
662 | try: | |
645 | lfdirstate = lfutil.openlfdirstate(ui, repo) |
|
663 | lfdirstate = lfutil.openlfdirstate(ui, repo) | |
646 | (modified, added, removed, missing, unknown, ignored, clean) = \ |
|
664 | s = lfutil.lfdirstatestatus(lfdirstate, repo) | |
647 | lfutil.lfdirstatestatus(lfdirstate, repo, repo['.'].rev()) |
|
|||
648 | lfdirstate.write() |
|
665 | lfdirstate.write() | |
649 | for lfile in modified: |
|
666 | for lfile in s.modified: | |
650 | lfutil.updatestandin(repo, lfutil.standin(lfile)) |
|
667 | lfutil.updatestandin(repo, lfutil.standin(lfile)) | |
651 |
for lfile in |
|
668 | for lfile in s.deleted: | |
652 | if (os.path.exists(repo.wjoin(lfutil.standin(lfile)))): |
|
669 | if (os.path.exists(repo.wjoin(lfutil.standin(lfile)))): | |
653 | os.unlink(repo.wjoin(lfutil.standin(lfile))) |
|
670 | os.unlink(repo.wjoin(lfutil.standin(lfile))) | |
654 |
|
671 | |||
@@ -695,25 +712,6 b' def overriderevert(orig, ui, repo, *pats' | |||||
695 | finally: |
|
712 | finally: | |
696 | wlock.release() |
|
713 | wlock.release() | |
697 |
|
714 | |||
698 | def hgupdaterepo(orig, repo, node, overwrite): |
|
|||
699 | if not overwrite: |
|
|||
700 | # Only call updatelfiles on the standins that have changed to save time |
|
|||
701 | oldstandins = lfutil.getstandinsstate(repo) |
|
|||
702 |
|
||||
703 | result = orig(repo, node, overwrite) |
|
|||
704 |
|
||||
705 | filelist = None |
|
|||
706 | if not overwrite: |
|
|||
707 | newstandins = lfutil.getstandinsstate(repo) |
|
|||
708 | filelist = lfutil.getlfilestoupdate(oldstandins, newstandins) |
|
|||
709 | lfcommands.updatelfiles(repo.ui, repo, filelist=filelist) |
|
|||
710 | return result |
|
|||
711 |
|
||||
712 | def hgmerge(orig, repo, node, force=None, remind=True): |
|
|||
713 | result = orig(repo, node, force, remind) |
|
|||
714 | lfcommands.updatelfiles(repo.ui, repo) |
|
|||
715 | return result |
|
|||
716 |
|
||||
717 | # When we rebase a repository with remotely changed largefiles, we need to |
|
715 | # When we rebase a repository with remotely changed largefiles, we need to | |
718 | # take some extra care so that the largefiles are correctly updated in the |
|
716 | # take some extra care so that the largefiles are correctly updated in the | |
719 | # working copy |
|
717 | # working copy | |
@@ -956,17 +954,17 b' def hgsubrepoarchive(orig, repo, ui, arc' | |||||
956 | def overridebailifchanged(orig, repo): |
|
954 | def overridebailifchanged(orig, repo): | |
957 | orig(repo) |
|
955 | orig(repo) | |
958 | repo.lfstatus = True |
|
956 | repo.lfstatus = True | |
959 | modified, added, removed, deleted = repo.status()[:4] |
|
957 | s = repo.status() | |
960 | repo.lfstatus = False |
|
958 | repo.lfstatus = False | |
961 | if modified or added or removed or deleted: |
|
959 | if s.modified or s.added or s.removed or s.deleted: | |
962 | raise util.Abort(_('uncommitted changes')) |
|
960 | raise util.Abort(_('uncommitted changes')) | |
963 |
|
961 | |||
964 | # Fetch doesn't use cmdutil.bailifchanged so override it to add the check |
|
962 | # Fetch doesn't use cmdutil.bailifchanged so override it to add the check | |
965 | def overridefetch(orig, ui, repo, *pats, **opts): |
|
963 | def overridefetch(orig, ui, repo, *pats, **opts): | |
966 | repo.lfstatus = True |
|
964 | repo.lfstatus = True | |
967 | modified, added, removed, deleted = repo.status()[:4] |
|
965 | s = repo.status() | |
968 | repo.lfstatus = False |
|
966 | repo.lfstatus = False | |
969 | if modified or added or removed or deleted: |
|
967 | if s.modified or s.added or s.removed or s.deleted: | |
970 | raise util.Abort(_('uncommitted changes')) |
|
968 | raise util.Abort(_('uncommitted changes')) | |
971 | return orig(ui, repo, *pats, **opts) |
|
969 | return orig(ui, repo, *pats, **opts) | |
972 |
|
970 | |||
@@ -981,7 +979,7 b' def overrideforget(orig, ui, repo, *pats' | |||||
981 | s = repo.status(match=m, clean=True) |
|
979 | s = repo.status(match=m, clean=True) | |
982 | finally: |
|
980 | finally: | |
983 | repo.lfstatus = False |
|
981 | repo.lfstatus = False | |
984 |
forget = sorted(s |
|
982 | forget = sorted(s.modified + s.added + s.deleted + s.clean) | |
985 | forget = [f for f in forget if lfutil.standin(f) in repo[None].manifest()] |
|
983 | forget = [f for f in forget if lfutil.standin(f) in repo[None].manifest()] | |
986 |
|
984 | |||
987 | for f in forget: |
|
985 | for f in forget: | |
@@ -1112,19 +1110,16 b' def scmutiladdremove(orig, repo, pats=[]' | |||||
1112 | return orig(repo, pats, opts, dry_run, similarity) |
|
1110 | return orig(repo, pats, opts, dry_run, similarity) | |
1113 | # Get the list of missing largefiles so we can remove them |
|
1111 | # Get the list of missing largefiles so we can remove them | |
1114 | lfdirstate = lfutil.openlfdirstate(repo.ui, repo) |
|
1112 | lfdirstate = lfutil.openlfdirstate(repo.ui, repo) | |
1115 |
s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], |
|
1113 | unsure, s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], | |
1116 | False, False) |
|
1114 | False, False, False) | |
1117 | (unsure, modified, added, removed, missing, unknown, ignored, clean) = s |
|
|||
1118 |
|
1115 | |||
1119 | # Call into the normal remove code, but the removing of the standin, we want |
|
1116 | # Call into the normal remove code, but the removing of the standin, we want | |
1120 | # to have handled by original addremove. Monkey patching here makes sure |
|
1117 | # to have handled by original addremove. Monkey patching here makes sure | |
1121 | # we don't remove the standin in the largefiles code, preventing a very |
|
1118 | # we don't remove the standin in the largefiles code, preventing a very | |
1122 | # confused state later. |
|
1119 | # confused state later. | |
1123 | if missing: |
|
1120 | if s.deleted: | |
1124 |
m = [repo.wjoin(f) for f in |
|
1121 | m = [repo.wjoin(f) for f in s.deleted] | |
1125 | repo._isaddremove = True |
|
1122 | removelargefiles(repo.ui, repo, True, *m, **opts) | |
1126 | removelargefiles(repo.ui, repo, *m, **opts) |
|
|||
1127 | repo._isaddremove = False |
|
|||
1128 | # Call into the normal add code, and any files that *should* be added as |
|
1123 | # Call into the normal add code, and any files that *should* be added as | |
1129 | # largefiles will be |
|
1124 | # largefiles will be | |
1130 | addlargefiles(repo.ui, repo, *pats, **opts) |
|
1125 | addlargefiles(repo.ui, repo, *pats, **opts) | |
@@ -1148,28 +1143,48 b' def overridepurge(orig, ui, repo, *dirs,' | |||||
1148 | r = oldstatus(node1, node2, match, ignored, clean, unknown, |
|
1143 | r = oldstatus(node1, node2, match, ignored, clean, unknown, | |
1149 | listsubrepos) |
|
1144 | listsubrepos) | |
1150 | lfdirstate = lfutil.openlfdirstate(ui, repo) |
|
1145 | lfdirstate = lfutil.openlfdirstate(ui, repo) | |
1151 | modified, added, removed, deleted, unknown, ignored, clean = r |
|
1146 | unknown = [f for f in r.unknown if lfdirstate[f] == '?'] | |
1152 |
|
|
1147 | ignored = [f for f in r.ignored if lfdirstate[f] == '?'] | |
1153 | ignored = [f for f in ignored if lfdirstate[f] == '?'] |
|
1148 | return scmutil.status(r.modified, r.added, r.removed, r.deleted, | |
1154 | return modified, added, removed, deleted, unknown, ignored, clean |
|
1149 | unknown, ignored, r.clean) | |
1155 | repo.status = overridestatus |
|
1150 | repo.status = overridestatus | |
1156 | orig(ui, repo, *dirs, **opts) |
|
1151 | orig(ui, repo, *dirs, **opts) | |
1157 | repo.status = oldstatus |
|
1152 | repo.status = oldstatus | |
1158 |
|
||||
1159 | def overriderollback(orig, ui, repo, **opts): |
|
1153 | def overriderollback(orig, ui, repo, **opts): | |
1160 | result = orig(ui, repo, **opts) |
|
|||
1161 | merge.update(repo, node=None, branchmerge=False, force=True, |
|
|||
1162 | partial=lfutil.isstandin) |
|
|||
1163 | wlock = repo.wlock() |
|
1154 | wlock = repo.wlock() | |
1164 | try: |
|
1155 | try: | |
|
1156 | before = repo.dirstate.parents() | |||
|
1157 | orphans = set(f for f in repo.dirstate | |||
|
1158 | if lfutil.isstandin(f) and repo.dirstate[f] != 'r') | |||
|
1159 | result = orig(ui, repo, **opts) | |||
|
1160 | after = repo.dirstate.parents() | |||
|
1161 | if before == after: | |||
|
1162 | return result # no need to restore standins | |||
|
1163 | ||||
|
1164 | pctx = repo['.'] | |||
|
1165 | for f in repo.dirstate: | |||
|
1166 | if lfutil.isstandin(f): | |||
|
1167 | orphans.discard(f) | |||
|
1168 | if repo.dirstate[f] == 'r': | |||
|
1169 | repo.wvfs.unlinkpath(f, ignoremissing=True) | |||
|
1170 | elif f in pctx: | |||
|
1171 | fctx = pctx[f] | |||
|
1172 | repo.wwrite(f, fctx.data(), fctx.flags()) | |||
|
1173 | else: | |||
|
1174 | # content of standin is not so important in 'a', | |||
|
1175 | # 'm' or 'n' (coming from the 2nd parent) cases | |||
|
1176 | lfutil.writestandin(repo, f, '', False) | |||
|
1177 | for standin in orphans: | |||
|
1178 | repo.wvfs.unlinkpath(standin, ignoremissing=True) | |||
|
1179 | ||||
1165 | lfdirstate = lfutil.openlfdirstate(ui, repo) |
|
1180 | lfdirstate = lfutil.openlfdirstate(ui, repo) | |
|
1181 | orphans = set(lfdirstate) | |||
1166 | lfiles = lfutil.listlfiles(repo) |
|
1182 | lfiles = lfutil.listlfiles(repo) | |
1167 | oldlfiles = lfutil.listlfiles(repo, repo[None].parents()[0].rev()) |
|
|||
1168 | for file in lfiles: |
|
1183 | for file in lfiles: | |
1169 | if file in oldlfiles: |
|
1184 | lfutil.synclfdirstate(repo, lfdirstate, file, True) | |
1170 | lfdirstate.normallookup(file) |
|
1185 | orphans.discard(file) | |
1171 | else: |
|
1186 | for lfile in orphans: | |
1172 |
|
|
1187 | lfdirstate.drop(lfile) | |
1173 | lfdirstate.write() |
|
1188 | lfdirstate.write() | |
1174 | finally: |
|
1189 | finally: | |
1175 | wlock.release() |
|
1190 | wlock.release() | |
@@ -1243,3 +1258,67 b' def mercurialsinkbefore(orig, sink):' | |||||
1243 | def mercurialsinkafter(orig, sink): |
|
1258 | def mercurialsinkafter(orig, sink): | |
1244 | sink.repo._isconverting = False |
|
1259 | sink.repo._isconverting = False | |
1245 | orig(sink) |
|
1260 | orig(sink) | |
|
1261 | ||||
|
1262 | def mergeupdate(orig, repo, node, branchmerge, force, partial, | |||
|
1263 | *args, **kwargs): | |||
|
1264 | wlock = repo.wlock() | |||
|
1265 | try: | |||
|
1266 | # branch | | | | |||
|
1267 | # merge | force | partial | action | |||
|
1268 | # -------+-------+---------+-------------- | |||
|
1269 | # x | x | x | linear-merge | |||
|
1270 | # o | x | x | branch-merge | |||
|
1271 | # x | o | x | overwrite (as clean update) | |||
|
1272 | # o | o | x | force-branch-merge (*1) | |||
|
1273 | # x | x | o | (*) | |||
|
1274 | # o | x | o | (*) | |||
|
1275 | # x | o | o | overwrite (as revert) | |||
|
1276 | # o | o | o | (*) | |||
|
1277 | # | |||
|
1278 | # (*) don't care | |||
|
1279 | # (*1) deprecated, but used internally (e.g: "rebase --collapse") | |||
|
1280 | ||||
|
1281 | linearmerge = not branchmerge and not force and not partial | |||
|
1282 | ||||
|
1283 | if linearmerge or (branchmerge and force and not partial): | |||
|
1284 | # update standins for linear-merge or force-branch-merge, | |||
|
1285 | # because largefiles in the working directory may be modified | |||
|
1286 | lfdirstate = lfutil.openlfdirstate(repo.ui, repo) | |||
|
1287 | unsure, s = lfdirstate.status(match_.always(repo.root, | |||
|
1288 | repo.getcwd()), | |||
|
1289 | [], False, False, False) | |||
|
1290 | for lfile in unsure + s.modified + s.added: | |||
|
1291 | lfutil.updatestandin(repo, lfutil.standin(lfile)) | |||
|
1292 | ||||
|
1293 | if linearmerge: | |||
|
1294 | # Only call updatelfiles on the standins that have changed | |||
|
1295 | # to save time | |||
|
1296 | oldstandins = lfutil.getstandinsstate(repo) | |||
|
1297 | ||||
|
1298 | result = orig(repo, node, branchmerge, force, partial, *args, **kwargs) | |||
|
1299 | ||||
|
1300 | filelist = None | |||
|
1301 | if linearmerge: | |||
|
1302 | newstandins = lfutil.getstandinsstate(repo) | |||
|
1303 | filelist = lfutil.getlfilestoupdate(oldstandins, newstandins) | |||
|
1304 | ||||
|
1305 | # suppress status message while automated committing | |||
|
1306 | printmessage = not (getattr(repo, "_isrebasing", False) or | |||
|
1307 | getattr(repo, "_istransplanting", False)) | |||
|
1308 | lfcommands.updatelfiles(repo.ui, repo, filelist=filelist, | |||
|
1309 | printmessage=printmessage, | |||
|
1310 | normallookup=partial) | |||
|
1311 | ||||
|
1312 | return result | |||
|
1313 | finally: | |||
|
1314 | wlock.release() | |||
|
1315 | ||||
|
1316 | def scmutilmarktouched(orig, repo, files, *args, **kwargs): | |||
|
1317 | result = orig(repo, files, *args, **kwargs) | |||
|
1318 | ||||
|
1319 | filelist = [lfutil.splitstandin(f) for f in files if lfutil.isstandin(f)] | |||
|
1320 | if filelist: | |||
|
1321 | lfcommands.updatelfiles(repo.ui, repo, filelist=filelist, | |||
|
1322 | printmessage=False, normallookup=True) | |||
|
1323 | ||||
|
1324 | return result |
@@ -12,7 +12,7 b' import os' | |||||
12 |
|
12 | |||
13 | from mercurial import error, manifest, match as match_, util |
|
13 | from mercurial import error, manifest, match as match_, util | |
14 | from mercurial.i18n import _ |
|
14 | from mercurial.i18n import _ | |
15 | from mercurial import localrepo |
|
15 | from mercurial import localrepo, scmutil | |
16 |
|
16 | |||
17 | import lfcommands |
|
17 | import lfcommands | |
18 | import lfutil |
|
18 | import lfutil | |
@@ -37,11 +37,8 b' def reposetup(ui, repo):' | |||||
37 | if self.lfstatus: |
|
37 | if self.lfstatus: | |
38 | class lfilesmanifestdict(manifest.manifestdict): |
|
38 | class lfilesmanifestdict(manifest.manifestdict): | |
39 | def __contains__(self, filename): |
|
39 | def __contains__(self, filename): | |
40 |
|
|
40 | orig = super(lfilesmanifestdict, self).__contains__ | |
41 | self).__contains__(filename): |
|
41 | return orig(filename) or orig(lfutil.standin(filename)) | |
42 | return True |
|
|||
43 | return super(lfilesmanifestdict, |
|
|||
44 | self).__contains__(lfutil.standin(filename)) |
|
|||
45 | class lfilesctx(ctx.__class__): |
|
42 | class lfilesctx(ctx.__class__): | |
46 | def files(self): |
|
43 | def files(self): | |
47 | filenames = super(lfilesctx, self).files() |
|
44 | filenames = super(lfilesctx, self).files() | |
@@ -51,22 +48,20 b' def reposetup(ui, repo):' | |||||
51 | man1.__class__ = lfilesmanifestdict |
|
48 | man1.__class__ = lfilesmanifestdict | |
52 | return man1 |
|
49 | return man1 | |
53 | def filectx(self, path, fileid=None, filelog=None): |
|
50 | def filectx(self, path, fileid=None, filelog=None): | |
|
51 | orig = super(lfilesctx, self).filectx | |||
54 | try: |
|
52 | try: | |
55 | if filelog is not None: |
|
53 | if filelog is not None: | |
56 |
result = |
|
54 | result = orig(path, fileid, filelog) | |
57 | path, fileid, filelog) |
|
|||
58 | else: |
|
55 | else: | |
59 |
result = |
|
56 | result = orig(path, fileid) | |
60 | path, fileid) |
|
|||
61 | except error.LookupError: |
|
57 | except error.LookupError: | |
62 | # Adding a null character will cause Mercurial to |
|
58 | # Adding a null character will cause Mercurial to | |
63 | # identify this as a binary file. |
|
59 | # identify this as a binary file. | |
64 | if filelog is not None: |
|
60 | if filelog is not None: | |
65 |
result = |
|
61 | result = orig(lfutil.standin(path), fileid, | |
66 |
|
|
62 | filelog) | |
67 | else: |
|
63 | else: | |
68 |
result = |
|
64 | result = orig(lfutil.standin(path), fileid) | |
69 | lfutil.standin(path), fileid) |
|
|||
70 | olddata = result.data |
|
65 | olddata = result.data | |
71 | result.data = lambda: olddata() + '\0' |
|
66 | result.data = lambda: olddata() + '\0' | |
72 | return result |
|
67 | return result | |
@@ -83,178 +78,158 b' def reposetup(ui, repo):' | |||||
83 | def status(self, node1='.', node2=None, match=None, ignored=False, |
|
78 | def status(self, node1='.', node2=None, match=None, ignored=False, | |
84 | clean=False, unknown=False, listsubrepos=False): |
|
79 | clean=False, unknown=False, listsubrepos=False): | |
85 | listignored, listclean, listunknown = ignored, clean, unknown |
|
80 | listignored, listclean, listunknown = ignored, clean, unknown | |
|
81 | orig = super(lfilesrepo, self).status | |||
86 | if not self.lfstatus: |
|
82 | if not self.lfstatus: | |
87 |
return |
|
83 | return orig(node1, node2, match, listignored, listclean, | |
88 |
|
|
84 | listunknown, listsubrepos) | |
89 | else: |
|
|||
90 | # some calls in this function rely on the old version of status |
|
|||
91 | self.lfstatus = False |
|
|||
92 | ctx1 = self[node1] |
|
|||
93 | ctx2 = self[node2] |
|
|||
94 | working = ctx2.rev() is None |
|
|||
95 | parentworking = working and ctx1 == self['.'] |
|
|||
96 |
|
85 | |||
97 | def inctx(file, ctx): |
|
86 | # some calls in this function rely on the old version of status | |
98 | try: |
|
87 | self.lfstatus = False | |
99 | if ctx.rev() is None: |
|
88 | ctx1 = self[node1] | |
100 | return file in ctx.manifest() |
|
89 | ctx2 = self[node2] | |
101 | ctx[file] |
|
90 | working = ctx2.rev() is None | |
102 | return True |
|
91 | parentworking = working and ctx1 == self['.'] | |
103 | except KeyError: |
|
92 | ||
104 | return False |
|
93 | if match is None: | |
|
94 | match = match_.always(self.root, self.getcwd()) | |||
105 |
|
95 | |||
106 |
|
|
96 | wlock = None | |
107 | match = match_.always(self.root, self.getcwd()) |
|
97 | try: | |
108 |
|
||||
109 | wlock = None |
|
|||
110 | try: |
|
98 | try: | |
111 | try: |
|
99 | # updating the dirstate is optional | |
112 | # updating the dirstate is optional |
|
100 | # so we don't wait on the lock | |
113 | # so we don't wait on the lock |
|
101 | wlock = self.wlock(False) | |
114 | wlock = self.wlock(False) |
|
102 | except error.LockError: | |
115 |
|
|
103 | pass | |
116 | pass |
|
|||
117 |
|
104 | |||
118 |
|
|
105 | # First check if there were files specified on the | |
119 |
|
|
106 | # command line. If there were, and none of them were | |
120 |
|
|
107 | # largefiles, we should just bail here and let super | |
121 |
|
|
108 | # handle it -- thus gaining a big performance boost. | |
122 |
|
|
109 | lfdirstate = lfutil.openlfdirstate(ui, self) | |
123 |
|
|
110 | if match.files() and not match.anypats(): | |
124 |
|
|
111 | for f in lfdirstate: | |
125 |
|
|
112 | if match(f): | |
126 |
|
|
113 | break | |
127 |
|
|
114 | else: | |
128 | return super(lfilesrepo, self).status(node1, node2, |
|
115 | return orig(node1, node2, match, listignored, listclean, | |
129 | match, listignored, listclean, |
|
|||
130 | listunknown, listsubrepos) |
|
116 | listunknown, listsubrepos) | |
131 |
|
117 | |||
132 |
|
|
118 | # Create a copy of match that matches standins instead | |
133 |
|
|
119 | # of largefiles. | |
134 |
|
|
120 | def tostandins(files): | |
135 |
|
|
121 | if not working: | |
136 |
|
|
122 | return files | |
137 |
|
|
123 | newfiles = [] | |
138 |
|
|
124 | dirstate = self.dirstate | |
139 |
|
|
125 | for f in files: | |
140 |
|
|
126 | sf = lfutil.standin(f) | |
141 |
|
|
127 | if sf in dirstate: | |
142 |
|
|
128 | newfiles.append(sf) | |
143 |
|
|
129 | elif sf in dirstate.dirs(): | |
144 |
|
|
130 | # Directory entries could be regular or | |
145 |
|
|
131 | # standin, check both | |
146 |
|
|
132 | newfiles.extend((f, sf)) | |
147 |
|
|
133 | else: | |
148 |
|
|
134 | newfiles.append(f) | |
149 |
|
|
135 | return newfiles | |
150 |
|
136 | |||
151 |
|
|
137 | m = copy.copy(match) | |
152 |
|
|
138 | m._files = tostandins(m._files) | |
153 |
|
139 | |||
154 | result = super(lfilesrepo, self).status(node1, node2, m, |
|
140 | result = orig(node1, node2, m, ignored, clean, unknown, | |
155 |
|
|
141 | listsubrepos) | |
156 |
|
|
142 | if working: | |
157 |
|
143 | |||
158 |
|
|
144 | def sfindirstate(f): | |
159 |
|
|
145 | sf = lfutil.standin(f) | |
160 |
|
|
146 | dirstate = self.dirstate | |
161 |
|
|
147 | return sf in dirstate or sf in dirstate.dirs() | |
162 |
|
148 | |||
163 |
|
|
149 | match._files = [f for f in match._files | |
164 |
|
|
150 | if sfindirstate(f)] | |
165 |
|
|
151 | # Don't waste time getting the ignored and unknown | |
166 |
|
|
152 | # files from lfdirstate | |
167 |
|
|
153 | unsure, s = lfdirstate.status(match, [], False, listclean, | |
168 |
|
|
154 | False) | |
169 |
|
|
155 | (modified, added, removed, clean) = (s.modified, s.added, | |
170 |
|
|
156 | s.removed, s.clean) | |
171 |
|
|
157 | if parentworking: | |
172 |
|
|
158 | for lfile in unsure: | |
173 |
|
|
159 | standin = lfutil.standin(lfile) | |
174 |
|
|
160 | if standin not in ctx1: | |
175 |
|
|
161 | # from second parent | |
176 |
|
|
162 | modified.append(lfile) | |
177 |
|
|
163 | elif ctx1[standin].data().strip() \ | |
178 |
|
|
164 | != lfutil.hashfile(self.wjoin(lfile)): | |
179 |
|
|
165 | modified.append(lfile) | |
180 |
|
|
166 | else: | |
|
167 | if listclean: | |||
181 | clean.append(lfile) |
|
168 | clean.append(lfile) | |
182 |
|
|
169 | lfdirstate.normal(lfile) | |
183 |
|
|
170 | else: | |
184 |
|
|
171 | tocheck = unsure + modified + added + clean | |
185 |
|
|
172 | modified, added, clean = [], [], [] | |
186 |
|
173 | |||
187 |
|
|
174 | for lfile in tocheck: | |
188 |
|
|
175 | standin = lfutil.standin(lfile) | |
189 |
|
|
176 | if standin in ctx1: | |
190 |
|
|
177 | if ctx1[standin].data().strip() != \ | |
191 |
|
|
178 | lfutil.hashfile(self.wjoin(lfile)): | |
192 |
|
|
179 | modified.append(lfile) | |
193 |
|
|
180 | elif listclean: | |
194 |
|
|
181 | clean.append(lfile) | |
195 |
|
|
182 | else: | |
196 |
|
|
183 | added.append(lfile) | |
197 |
|
184 | |||
198 |
|
|
185 | # Standins no longer found in lfdirstate has been | |
199 |
|
|
186 | # removed | |
200 |
|
|
187 | for standin in ctx1.walk(lfutil.getstandinmatcher(self)): | |
201 |
|
|
188 | lfile = lfutil.splitstandin(standin) | |
202 |
|
|
189 | if not match(lfile): | |
203 |
|
|
190 | continue | |
204 |
|
|
191 | if lfile not in lfdirstate: | |
205 |
|
|
192 | removed.append(lfile) | |
206 | if lfile not in lfdirstate: |
|
|||
207 | removed.append(lfile) |
|
|||
208 |
|
193 | |||
209 |
|
|
194 | # Filter result lists | |
210 |
|
|
195 | result = list(result) | |
211 |
|
||||
212 | # Largefiles are not really removed when they're |
|
|||
213 | # still in the normal dirstate. Likewise, normal |
|
|||
214 | # files are not really removed if they are still in |
|
|||
215 | # lfdirstate. This happens in merges where files |
|
|||
216 | # change type. |
|
|||
217 | removed = [f for f in removed |
|
|||
218 | if f not in self.dirstate] |
|
|||
219 | result[2] = [f for f in result[2] |
|
|||
220 | if f not in lfdirstate] |
|
|||
221 |
|
196 | |||
222 | lfiles = set(lfdirstate._map) |
|
197 | # Largefiles are not really removed when they're | |
223 | # Unknown files |
|
198 | # still in the normal dirstate. Likewise, normal | |
224 | result[4] = set(result[4]).difference(lfiles) |
|
199 | # files are not really removed if they are still in | |
225 | # Ignored files |
|
200 | # lfdirstate. This happens in merges where files | |
226 | result[5] = set(result[5]).difference(lfiles) |
|
201 | # change type. | |
227 | # combine normal files and largefiles |
|
202 | removed = [f for f in removed | |
228 |
|
|
203 | if f not in self.dirstate] | |
229 | if not lfutil.isstandin(fn)] |
|
204 | result[2] = [f for f in result[2] | |
230 |
|
|
205 | if f not in lfdirstate] | |
231 | lfiles = (modified, added, removed, missing, [], [], |
|
|||
232 | clean) |
|
|||
233 | result = [sorted(list1 + list2) |
|
|||
234 | for (list1, list2) in zip(normals, lfiles)] |
|
|||
235 | else: |
|
|||
236 | def toname(f): |
|
|||
237 | if lfutil.isstandin(f): |
|
|||
238 | return lfutil.splitstandin(f) |
|
|||
239 | return f |
|
|||
240 | result = [[toname(f) for f in items] |
|
|||
241 | for items in result] |
|
|||
242 |
|
206 | |||
243 | if wlock: |
|
207 | lfiles = set(lfdirstate._map) | |
244 |
|
|
208 | # Unknown files | |
245 |
|
209 | result[4] = set(result[4]).difference(lfiles) | ||
246 |
|
|
210 | # Ignored files | |
247 | if wlock: |
|
211 | result[5] = set(result[5]).difference(lfiles) | |
248 | wlock.release() |
|
212 | # combine normal files and largefiles | |
|
213 | normals = [[fn for fn in filelist | |||
|
214 | if not lfutil.isstandin(fn)] | |||
|
215 | for filelist in result] | |||
|
216 | lfstatus = (modified, added, removed, s.deleted, [], [], | |||
|
217 | clean) | |||
|
218 | result = [sorted(list1 + list2) | |||
|
219 | for (list1, list2) in zip(normals, lfstatus)] | |||
|
220 | else: # not against working directory | |||
|
221 | result = [[lfutil.splitstandin(f) or f for f in items] | |||
|
222 | for items in result] | |||
249 |
|
223 | |||
250 |
if |
|
224 | if wlock: | |
251 |
|
|
225 | lfdirstate.write() | |
252 | if not listignored: |
|
226 | ||
253 | result[5] = [] |
|
227 | finally: | |
254 |
if |
|
228 | if wlock: | |
255 |
|
|
229 | wlock.release() | |
256 | self.lfstatus = True |
|
230 | ||
257 | return result |
|
231 | self.lfstatus = True | |
|
232 | return scmutil.status(*result) | |||
258 |
|
233 | |||
259 | # As part of committing, copy all of the largefiles into the |
|
234 | # As part of committing, copy all of the largefiles into the | |
260 | # cache. |
|
235 | # cache. | |
@@ -272,19 +247,29 b' def reposetup(ui, repo):' | |||||
272 |
|
247 | |||
273 | wlock = self.wlock() |
|
248 | wlock = self.wlock() | |
274 | try: |
|
249 | try: | |
275 |
# Case 0: |
|
250 | # Case 0: Automated committing | |
276 | # We have to take the time to pull down the new largefiles now. |
|
251 | # | |
277 | # Otherwise, any largefiles that were modified in the |
|
252 | # While automated committing (like rebase, transplant | |
278 | # destination changesets get overwritten, either by the rebase |
|
253 | # and so on), this code path is used to avoid: | |
279 | # or in the first commit after the rebase or transplant. |
|
254 | # (1) updating standins, because standins should | |
280 | # updatelfiles will update the dirstate to mark any pulled |
|
255 | # be already updated at this point | |
281 | # largefiles as modified |
|
256 | # (2) aborting when stadnins are matched by "match", | |
|
257 | # because automated committing may specify them directly | |||
|
258 | # | |||
282 | if getattr(self, "_isrebasing", False) or \ |
|
259 | if getattr(self, "_isrebasing", False) or \ | |
283 | getattr(self, "_istransplanting", False): |
|
260 | getattr(self, "_istransplanting", False): | |
284 | lfcommands.updatelfiles(self.ui, self, filelist=None, |
|
|||
285 | printmessage=False) |
|
|||
286 | result = orig(text=text, user=user, date=date, match=match, |
|
261 | result = orig(text=text, user=user, date=date, match=match, | |
287 | force=force, editor=editor, extra=extra) |
|
262 | force=force, editor=editor, extra=extra) | |
|
263 | ||||
|
264 | if result: | |||
|
265 | lfdirstate = lfutil.openlfdirstate(ui, self) | |||
|
266 | for f in self[result].files(): | |||
|
267 | if lfutil.isstandin(f): | |||
|
268 | lfile = lfutil.splitstandin(f) | |||
|
269 | lfutil.synclfdirstate(self, lfdirstate, lfile, | |||
|
270 | False) | |||
|
271 | lfdirstate.write() | |||
|
272 | ||||
288 | return result |
|
273 | return result | |
289 | # Case 1: user calls commit with no specific files or |
|
274 | # Case 1: user calls commit with no specific files or | |
290 | # include/exclude patterns: refresh and commit all files that |
|
275 | # include/exclude patterns: refresh and commit all files that | |
@@ -298,10 +283,9 b' def reposetup(ui, repo):' | |||||
298 | # large. |
|
283 | # large. | |
299 | lfdirstate = lfutil.openlfdirstate(ui, self) |
|
284 | lfdirstate = lfutil.openlfdirstate(ui, self) | |
300 | dirtymatch = match_.always(self.root, self.getcwd()) |
|
285 | dirtymatch = match_.always(self.root, self.getcwd()) | |
301 |
s = lfdirstate.status(dirtymatch, [], False, False, |
|
286 | unsure, s = lfdirstate.status(dirtymatch, [], False, False, | |
302 | (unsure, modified, added, removed, _missing, _unknown, |
|
287 | False) | |
303 | _ignored, _clean) = s |
|
288 | modifiedfiles = unsure + s.modified + s.added + s.removed | |
304 | modifiedfiles = unsure + modified + added + removed |
|
|||
305 | lfiles = lfutil.listlfiles(self) |
|
289 | lfiles = lfutil.listlfiles(self) | |
306 | # this only loops through largefiles that exist (not |
|
290 | # this only loops through largefiles that exist (not | |
307 | # removed/renamed) |
|
291 | # removed/renamed) | |
@@ -381,10 +365,6 b' def reposetup(ui, repo):' | |||||
381 | if f in lfiles or fstandin in standins: |
|
365 | if f in lfiles or fstandin in standins: | |
382 | continue |
|
366 | continue | |
383 |
|
367 | |||
384 | # append directory separator to avoid collisions |
|
|||
385 | if not fstandin.endswith(os.sep): |
|
|||
386 | fstandin += os.sep |
|
|||
387 |
|
||||
388 | actualfiles.append(f) |
|
368 | actualfiles.append(f) | |
389 | match._files = actualfiles |
|
369 | match._files = actualfiles | |
390 |
|
370 |
@@ -99,6 +99,10 b' def uisetup(ui):' | |||||
99 | overrides.overridecheckunknownfile) |
|
99 | overrides.overridecheckunknownfile) | |
100 | entry = extensions.wrapfunction(merge, 'calculateupdates', |
|
100 | entry = extensions.wrapfunction(merge, 'calculateupdates', | |
101 | overrides.overridecalculateupdates) |
|
101 | overrides.overridecalculateupdates) | |
|
102 | entry = extensions.wrapfunction(merge, 'recordupdates', | |||
|
103 | overrides.mergerecordupdates) | |||
|
104 | entry = extensions.wrapfunction(merge, 'update', | |||
|
105 | overrides.mergeupdate) | |||
102 | entry = extensions.wrapfunction(filemerge, 'filemerge', |
|
106 | entry = extensions.wrapfunction(filemerge, 'filemerge', | |
103 | overrides.overridefilemerge) |
|
107 | overrides.overridefilemerge) | |
104 | entry = extensions.wrapfunction(cmdutil, 'copy', |
|
108 | entry = extensions.wrapfunction(cmdutil, 'copy', | |
@@ -115,15 +119,15 b' def uisetup(ui):' | |||||
115 | entry = extensions.wrapfunction(commands, 'revert', |
|
119 | entry = extensions.wrapfunction(commands, 'revert', | |
116 | overrides.overriderevert) |
|
120 | overrides.overriderevert) | |
117 |
|
121 | |||
118 | extensions.wrapfunction(hg, 'updaterepo', overrides.hgupdaterepo) |
|
|||
119 | extensions.wrapfunction(hg, 'merge', overrides.hgmerge) |
|
|||
120 |
|
||||
121 | extensions.wrapfunction(archival, 'archive', overrides.overridearchive) |
|
122 | extensions.wrapfunction(archival, 'archive', overrides.overridearchive) | |
122 | extensions.wrapfunction(subrepo.hgsubrepo, 'archive', |
|
123 | extensions.wrapfunction(subrepo.hgsubrepo, 'archive', | |
123 | overrides.hgsubrepoarchive) |
|
124 | overrides.hgsubrepoarchive) | |
124 | extensions.wrapfunction(cmdutil, 'bailifchanged', |
|
125 | extensions.wrapfunction(cmdutil, 'bailifchanged', | |
125 | overrides.overridebailifchanged) |
|
126 | overrides.overridebailifchanged) | |
126 |
|
127 | |||
|
128 | extensions.wrapfunction(scmutil, 'marktouched', | |||
|
129 | overrides.scmutilmarktouched) | |||
|
130 | ||||
127 | # create the new wireproto commands ... |
|
131 | # create the new wireproto commands ... | |
128 | wireproto.commands['putlfile'] = (proto.putlfile, 'sha') |
|
132 | wireproto.commands['putlfile'] = (proto.putlfile, 'sha') | |
129 | wireproto.commands['getlfile'] = (proto.getlfile, 'sha') |
|
133 | wireproto.commands['getlfile'] = (proto.getlfile, 'sha') |
@@ -104,6 +104,52 b' class statusentry(object):' | |||||
104 | def __repr__(self): |
|
104 | def __repr__(self): | |
105 | return hex(self.node) + ':' + self.name |
|
105 | return hex(self.node) + ':' + self.name | |
106 |
|
106 | |||
|
107 | # The order of the headers in 'hg export' HG patches: | |||
|
108 | HGHEADERS = [ | |||
|
109 | # '# HG changeset patch', | |||
|
110 | '# User ', | |||
|
111 | '# Date ', | |||
|
112 | '# ', | |||
|
113 | '# Branch ', | |||
|
114 | '# Node ID ', | |||
|
115 | '# Parent ', # can occur twice for merges - but that is not relevant for mq | |||
|
116 | '', # all lines after headers 'has' this prefix - simplifies the algorithm | |||
|
117 | ] | |||
|
118 | ||||
|
119 | def inserthgheader(lines, header, value): | |||
|
120 | """Assuming lines contains a HG patch header, add a header line with value. | |||
|
121 | >>> try: inserthgheader([], '# Date ', 'z') | |||
|
122 | ... except ValueError, inst: print "oops" | |||
|
123 | oops | |||
|
124 | >>> inserthgheader(['# HG changeset patch'], '# Date ', 'z') | |||
|
125 | ['# HG changeset patch', '# Date z'] | |||
|
126 | >>> inserthgheader(['# HG changeset patch', ''], '# Date ', 'z') | |||
|
127 | ['# HG changeset patch', '# Date z', ''] | |||
|
128 | >>> inserthgheader(['# HG changeset patch', '# User y'], '# Date ', 'z') | |||
|
129 | ['# HG changeset patch', '# User y', '# Date z'] | |||
|
130 | >>> inserthgheader(['# HG changeset patch', '# Date y'], '# Date ', 'z') | |||
|
131 | ['# HG changeset patch', '# Date z'] | |||
|
132 | >>> inserthgheader(['# HG changeset patch', '', '# Date y'], '# Date ', 'z') | |||
|
133 | ['# HG changeset patch', '# Date z', '', '# Date y'] | |||
|
134 | >>> inserthgheader(['# HG changeset patch', '# Parent y'], '# Date ', 'z') | |||
|
135 | ['# HG changeset patch', '# Date z', '# Parent y'] | |||
|
136 | """ | |||
|
137 | start = lines.index('# HG changeset patch') + 1 | |||
|
138 | newindex = HGHEADERS.index(header) | |||
|
139 | for i in range(start, len(lines)): | |||
|
140 | line = lines[i] | |||
|
141 | for lineindex, h in enumerate(HGHEADERS): | |||
|
142 | if line.startswith(h): | |||
|
143 | if lineindex < newindex: | |||
|
144 | break # next line | |||
|
145 | if lineindex == newindex: | |||
|
146 | lines[i] = header + value | |||
|
147 | else: | |||
|
148 | lines.insert(i, header + value) | |||
|
149 | return lines | |||
|
150 | lines.append(header + value) | |||
|
151 | return lines | |||
|
152 | ||||
107 | class patchheader(object): |
|
153 | class patchheader(object): | |
108 | def __init__(self, pf, plainmode=False): |
|
154 | def __init__(self, pf, plainmode=False): | |
109 | def eatdiff(lines): |
|
155 | def eatdiff(lines): | |
@@ -150,7 +196,7 b' class patchheader(object):' | |||||
150 | elif line.startswith("# Date "): |
|
196 | elif line.startswith("# Date "): | |
151 | date = line[7:] |
|
197 | date = line[7:] | |
152 | elif line.startswith("# Parent "): |
|
198 | elif line.startswith("# Parent "): | |
153 | parent = line[9:].lstrip() |
|
199 | parent = line[9:].lstrip() # handle double trailing space | |
154 | elif line.startswith("# Branch "): |
|
200 | elif line.startswith("# Branch "): | |
155 | branch = line[9:] |
|
201 | branch = line[9:] | |
156 | elif line.startswith("# Node ID "): |
|
202 | elif line.startswith("# Node ID "): | |
@@ -191,7 +237,6 b' class patchheader(object):' | |||||
191 |
|
237 | |||
192 | # make sure message isn't empty |
|
238 | # make sure message isn't empty | |
193 | if format and format.startswith("tag") and subject: |
|
239 | if format and format.startswith("tag") and subject: | |
194 | message.insert(0, "") |
|
|||
195 | message.insert(0, subject) |
|
240 | message.insert(0, subject) | |
196 |
|
241 | |||
197 | self.message = message |
|
242 | self.message = message | |
@@ -203,41 +248,45 b' class patchheader(object):' | |||||
203 | self.nodeid = nodeid |
|
248 | self.nodeid = nodeid | |
204 | self.branch = branch |
|
249 | self.branch = branch | |
205 | self.haspatch = diffstart > 1 |
|
250 | self.haspatch = diffstart > 1 | |
206 | self.plainmode = plainmode |
|
251 | self.plainmode = (plainmode or | |
|
252 | '# HG changeset patch' not in self.comments and | |||
|
253 | util.any(c.startswith('Date: ') or | |||
|
254 | c.startswith('From: ') | |||
|
255 | for c in self.comments)) | |||
207 |
|
256 | |||
208 | def setuser(self, user): |
|
257 | def setuser(self, user): | |
209 | if not self.updateheader(['From: ', '# User '], user): |
|
258 | if not self.updateheader(['From: ', '# User '], user): | |
210 | try: |
|
259 | try: | |
211 |
|
|
260 | inserthgheader(self.comments, '# User ', user) | |
212 | self.comments.insert(patchheaderat + 1, '# User ' + user) |
|
|||
213 | except ValueError: |
|
261 | except ValueError: | |
214 |
if self.plainmode |
|
262 | if self.plainmode: | |
215 | self.comments = ['From: ' + user] + self.comments |
|
263 | self.comments = ['From: ' + user] + self.comments | |
216 | else: |
|
264 | else: | |
217 |
tmp = ['# HG changeset patch', '# User ' + user |
|
265 | tmp = ['# HG changeset patch', '# User ' + user] | |
218 | self.comments = tmp + self.comments |
|
266 | self.comments = tmp + self.comments | |
219 | self.user = user |
|
267 | self.user = user | |
220 |
|
268 | |||
221 | def setdate(self, date): |
|
269 | def setdate(self, date): | |
222 | if not self.updateheader(['Date: ', '# Date '], date): |
|
270 | if not self.updateheader(['Date: ', '# Date '], date): | |
223 | try: |
|
271 | try: | |
224 |
|
|
272 | inserthgheader(self.comments, '# Date ', date) | |
225 | self.comments.insert(patchheaderat + 1, '# Date ' + date) |
|
|||
226 | except ValueError: |
|
273 | except ValueError: | |
227 |
if self.plainmode |
|
274 | if self.plainmode: | |
228 | self.comments = ['Date: ' + date] + self.comments |
|
275 | self.comments = ['Date: ' + date] + self.comments | |
229 | else: |
|
276 | else: | |
230 |
tmp = ['# HG changeset patch', '# Date ' + date |
|
277 | tmp = ['# HG changeset patch', '# Date ' + date] | |
231 | self.comments = tmp + self.comments |
|
278 | self.comments = tmp + self.comments | |
232 | self.date = date |
|
279 | self.date = date | |
233 |
|
280 | |||
234 | def setparent(self, parent): |
|
281 | def setparent(self, parent): | |
235 |
if not self.updateheader(['# Parent '], parent) |
|
282 | if not (self.updateheader(['# Parent '], parent) or | |
|
283 | self.updateheader(['# Parent '], parent)): | |||
236 | try: |
|
284 | try: | |
237 |
|
|
285 | inserthgheader(self.comments, '# Parent ', parent) | |
238 | self.comments.insert(patchheaderat + 1, '# Parent ' + parent) |
|
|||
239 | except ValueError: |
|
286 | except ValueError: | |
240 |
|
|
287 | if not self.plainmode: | |
|
288 | tmp = ['# HG changeset patch', '# Parent ' + parent] | |||
|
289 | self.comments = tmp + self.comments | |||
241 | self.parent = parent |
|
290 | self.parent = parent | |
242 |
|
291 | |||
243 | def setmessage(self, message): |
|
292 | def setmessage(self, message): | |
@@ -258,18 +307,11 b' class patchheader(object):' | |||||
258 | break |
|
307 | break | |
259 | return res |
|
308 | return res | |
260 |
|
309 | |||
261 | def _hasheader(self, prefixes): |
|
|||
262 | '''Check if a header starts with any of the given prefixes.''' |
|
|||
263 | for prefix in prefixes: |
|
|||
264 | for comment in self.comments: |
|
|||
265 | if comment.startswith(prefix): |
|
|||
266 | return True |
|
|||
267 | return False |
|
|||
268 |
|
||||
269 | def __str__(self): |
|
310 | def __str__(self): | |
270 |
|
|
311 | s = '\n'.join(self.comments).rstrip() | |
|
312 | if not s: | |||
271 | return '' |
|
313 | return '' | |
272 |
return |
|
314 | return s + '\n\n' | |
273 |
|
315 | |||
274 | def _delmsg(self): |
|
316 | def _delmsg(self): | |
275 | '''Remove existing message, keeping the rest of the comments fields. |
|
317 | '''Remove existing message, keeping the rest of the comments fields. | |
@@ -621,7 +663,7 b' class queue(object):' | |||||
621 |
|
663 | |||
622 | # apply failed, strip away that rev and merge. |
|
664 | # apply failed, strip away that rev and merge. | |
623 | hg.clean(repo, head) |
|
665 | hg.clean(repo, head) | |
624 |
strip(self.ui, repo, [n], update=False, backup= |
|
666 | strip(self.ui, repo, [n], update=False, backup=False) | |
625 |
|
667 | |||
626 | ctx = repo[rev] |
|
668 | ctx = repo[rev] | |
627 | ret = hg.merge(repo, rev) |
|
669 | ret = hg.merge(repo, rev) | |
@@ -816,12 +858,14 b' class queue(object):' | |||||
816 | merged.append(f) |
|
858 | merged.append(f) | |
817 | else: |
|
859 | else: | |
818 | removed.append(f) |
|
860 | removed.append(f) | |
|
861 | repo.dirstate.beginparentchange() | |||
819 | for f in removed: |
|
862 | for f in removed: | |
820 | repo.dirstate.remove(f) |
|
863 | repo.dirstate.remove(f) | |
821 | for f in merged: |
|
864 | for f in merged: | |
822 | repo.dirstate.merge(f) |
|
865 | repo.dirstate.merge(f) | |
823 | p1, p2 = repo.dirstate.parents() |
|
866 | p1, p2 = repo.dirstate.parents() | |
824 | repo.setparents(p1, merge) |
|
867 | repo.setparents(p1, merge) | |
|
868 | repo.dirstate.endparentchange() | |||
825 |
|
869 | |||
826 | if all_files and '.hgsubstate' in all_files: |
|
870 | if all_files and '.hgsubstate' in all_files: | |
827 | wctx = repo[None] |
|
871 | wctx = repo[None] | |
@@ -930,7 +974,12 b' class queue(object):' | |||||
930 | oldqbase = repo[qfinished[0]] |
|
974 | oldqbase = repo[qfinished[0]] | |
931 | tphase = repo.ui.config('phases', 'new-commit', phases.draft) |
|
975 | tphase = repo.ui.config('phases', 'new-commit', phases.draft) | |
932 | if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase: |
|
976 | if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase: | |
933 | phases.advanceboundary(repo, tphase, qfinished) |
|
977 | tr = repo.transaction('qfinish') | |
|
978 | try: | |||
|
979 | phases.advanceboundary(repo, tr, tphase, qfinished) | |||
|
980 | tr.close() | |||
|
981 | finally: | |||
|
982 | tr.release() | |||
934 |
|
983 | |||
935 | def delete(self, repo, patches, opts): |
|
984 | def delete(self, repo, patches, opts): | |
936 | if not patches and not opts.get('rev'): |
|
985 | if not patches and not opts.get('rev'): | |
@@ -953,8 +1002,7 b' class queue(object):' | |||||
953 | if not self.applied: |
|
1002 | if not self.applied: | |
954 | raise util.Abort(_('no patches applied')) |
|
1003 | raise util.Abort(_('no patches applied')) | |
955 | revs = scmutil.revrange(repo, opts.get('rev')) |
|
1004 | revs = scmutil.revrange(repo, opts.get('rev')) | |
956 | if len(revs) > 1 and revs[0] > revs[1]: |
|
1005 | revs.sort() | |
957 | revs.reverse() |
|
|||
958 | revpatches = self._revpatches(repo, revs) |
|
1006 | revpatches = self._revpatches(repo, revs) | |
959 | realpatches += revpatches |
|
1007 | realpatches += revpatches | |
960 | numrevs = len(revpatches) |
|
1008 | numrevs = len(revpatches) | |
@@ -1025,6 +1073,7 b' class queue(object):' | |||||
1025 | """ |
|
1073 | """ | |
1026 | msg = opts.get('msg') |
|
1074 | msg = opts.get('msg') | |
1027 | edit = opts.get('edit') |
|
1075 | edit = opts.get('edit') | |
|
1076 | editform = opts.get('editform', 'mq.qnew') | |||
1028 | user = opts.get('user') |
|
1077 | user = opts.get('user') | |
1029 | date = opts.get('date') |
|
1078 | date = opts.get('date') | |
1030 | if date: |
|
1079 | if date: | |
@@ -1062,24 +1111,8 b' class queue(object):' | |||||
1062 | raise util.Abort(_('cannot write patch "%s": %s') |
|
1111 | raise util.Abort(_('cannot write patch "%s": %s') | |
1063 | % (patchfn, e.strerror)) |
|
1112 | % (patchfn, e.strerror)) | |
1064 | try: |
|
1113 | try: | |
1065 | if self.plainmode: |
|
|||
1066 | if user: |
|
|||
1067 | p.write("From: " + user + "\n") |
|
|||
1068 | if not date: |
|
|||
1069 | p.write("\n") |
|
|||
1070 | if date: |
|
|||
1071 | p.write("Date: %d %d\n\n" % date) |
|
|||
1072 | else: |
|
|||
1073 | p.write("# HG changeset patch\n") |
|
|||
1074 | p.write("# Parent " |
|
|||
1075 | + hex(repo[None].p1().node()) + "\n") |
|
|||
1076 | if user: |
|
|||
1077 | p.write("# User " + user + "\n") |
|
|||
1078 | if date: |
|
|||
1079 | p.write("# Date %s %s\n\n" % date) |
|
|||
1080 |
|
||||
1081 | defaultmsg = "[mq]: %s" % patchfn |
|
1114 | defaultmsg = "[mq]: %s" % patchfn | |
1082 | editor = cmdutil.getcommiteditor() |
|
1115 | editor = cmdutil.getcommiteditor(editform=editform) | |
1083 | if edit: |
|
1116 | if edit: | |
1084 | def finishdesc(desc): |
|
1117 | def finishdesc(desc): | |
1085 | if desc.rstrip(): |
|
1118 | if desc.rstrip(): | |
@@ -1089,7 +1122,8 b' class queue(object):' | |||||
1089 | # i18n: this message is shown in editor with "HG: " prefix |
|
1122 | # i18n: this message is shown in editor with "HG: " prefix | |
1090 | extramsg = _('Leave message empty to use default message.') |
|
1123 | extramsg = _('Leave message empty to use default message.') | |
1091 | editor = cmdutil.getcommiteditor(finishdesc=finishdesc, |
|
1124 | editor = cmdutil.getcommiteditor(finishdesc=finishdesc, | |
1092 |
extramsg=extramsg |
|
1125 | extramsg=extramsg, | |
|
1126 | editform=editform) | |||
1093 | commitmsg = msg |
|
1127 | commitmsg = msg | |
1094 | else: |
|
1128 | else: | |
1095 | commitmsg = msg or defaultmsg |
|
1129 | commitmsg = msg or defaultmsg | |
@@ -1105,9 +1139,17 b' class queue(object):' | |||||
1105 | self.seriesdirty = True |
|
1139 | self.seriesdirty = True | |
1106 | self.applieddirty = True |
|
1140 | self.applieddirty = True | |
1107 | nctx = repo[n] |
|
1141 | nctx = repo[n] | |
1108 | if nctx.description() != defaultmsg.rstrip(): |
|
1142 | ph = patchheader(self.join(patchfn), self.plainmode) | |
1109 | msg = nctx.description() + "\n\n" |
|
1143 | if user: | |
1110 |
p. |
|
1144 | ph.setuser(user) | |
|
1145 | if date: | |||
|
1146 | ph.setdate('%s %s' % date) | |||
|
1147 | ph.setparent(hex(nctx.p1().node())) | |||
|
1148 | msg = nctx.description().strip() | |||
|
1149 | if msg == defaultmsg.strip(): | |||
|
1150 | msg = '' | |||
|
1151 | ph.setmessage(msg) | |||
|
1152 | p.write(str(ph)) | |||
1111 | if commitfiles: |
|
1153 | if commitfiles: | |
1112 | parent = self.qparents(repo, n) |
|
1154 | parent = self.qparents(repo, n) | |
1113 | if inclsubs: |
|
1155 | if inclsubs: | |
@@ -1317,11 +1359,12 b' class queue(object):' | |||||
1317 |
|
1359 | |||
1318 | tobackup = set() |
|
1360 | tobackup = set() | |
1319 | if (not nobackup and force) or keepchanges: |
|
1361 | if (not nobackup and force) or keepchanges: | |
1320 |
|
|
1362 | status = self.checklocalchanges(repo, force=True) | |
1321 | if keepchanges: |
|
1363 | if keepchanges: | |
1322 |
tobackup.update(m + a + |
|
1364 | tobackup.update(status.modified + status.added + | |
|
1365 | status.removed + status.deleted) | |||
1323 | else: |
|
1366 | else: | |
1324 | tobackup.update(m + a) |
|
1367 | tobackup.update(status.modified + status.added) | |
1325 |
|
1368 | |||
1326 | s = self.series[start:end] |
|
1369 | s = self.series[start:end] | |
1327 | all_files = set() |
|
1370 | all_files = set() | |
@@ -1405,13 +1448,13 b' class queue(object):' | |||||
1405 |
|
1448 | |||
1406 | tobackup = set() |
|
1449 | tobackup = set() | |
1407 | if update: |
|
1450 | if update: | |
1408 |
|
|
1451 | s = self.checklocalchanges(repo, force=force or keepchanges) | |
1409 | repo, force=force or keepchanges) |
|
|||
1410 | if force: |
|
1452 | if force: | |
1411 | if not nobackup: |
|
1453 | if not nobackup: | |
1412 | tobackup.update(m + a) |
|
1454 | tobackup.update(s.modified + s.added) | |
1413 | elif keepchanges: |
|
1455 | elif keepchanges: | |
1414 |
tobackup.update(m + a + |
|
1456 | tobackup.update(s.modified + s.added + | |
|
1457 | s.removed + s.deleted) | |||
1415 |
|
1458 | |||
1416 | self.applieddirty = True |
|
1459 | self.applieddirty = True | |
1417 | end = len(self.applied) |
|
1460 | end = len(self.applied) | |
@@ -1444,7 +1487,7 b' class queue(object):' | |||||
1444 | if keepchanges and tobackup: |
|
1487 | if keepchanges and tobackup: | |
1445 | raise util.Abort(_("local changes found, refresh first")) |
|
1488 | raise util.Abort(_("local changes found, refresh first")) | |
1446 | self.backup(repo, tobackup) |
|
1489 | self.backup(repo, tobackup) | |
1447 |
|
1490 | repo.dirstate.beginparentchange() | ||
1448 | for f in a: |
|
1491 | for f in a: | |
1449 | util.unlinkpath(repo.wjoin(f), ignoremissing=True) |
|
1492 | util.unlinkpath(repo.wjoin(f), ignoremissing=True) | |
1450 | repo.dirstate.drop(f) |
|
1493 | repo.dirstate.drop(f) | |
@@ -1453,10 +1496,11 b' class queue(object):' | |||||
1453 | repo.wwrite(f, fctx.data(), fctx.flags()) |
|
1496 | repo.wwrite(f, fctx.data(), fctx.flags()) | |
1454 | repo.dirstate.normal(f) |
|
1497 | repo.dirstate.normal(f) | |
1455 | repo.setparents(qp, nullid) |
|
1498 | repo.setparents(qp, nullid) | |
|
1499 | repo.dirstate.endparentchange() | |||
1456 | for patch in reversed(self.applied[start:end]): |
|
1500 | for patch in reversed(self.applied[start:end]): | |
1457 | self.ui.status(_("popping %s\n") % patch.name) |
|
1501 | self.ui.status(_("popping %s\n") % patch.name) | |
1458 | del self.applied[start:end] |
|
1502 | del self.applied[start:end] | |
1459 |
strip(self.ui, repo, [rev], update=False, backup= |
|
1503 | strip(self.ui, repo, [rev], update=False, backup=False) | |
1460 | for s, state in repo['.'].substate.items(): |
|
1504 | for s, state in repo['.'].substate.items(): | |
1461 | repo['.'].sub(s).get(state) |
|
1505 | repo['.'].sub(s).get(state) | |
1462 | if self.applied: |
|
1506 | if self.applied: | |
@@ -1485,6 +1529,7 b' class queue(object):' | |||||
1485 | return 1 |
|
1529 | return 1 | |
1486 | msg = opts.get('msg', '').rstrip() |
|
1530 | msg = opts.get('msg', '').rstrip() | |
1487 | edit = opts.get('edit') |
|
1531 | edit = opts.get('edit') | |
|
1532 | editform = opts.get('editform', 'mq.qrefresh') | |||
1488 | newuser = opts.get('user') |
|
1533 | newuser = opts.get('user') | |
1489 | newdate = opts.get('date') |
|
1534 | newdate = opts.get('date') | |
1490 | if newdate: |
|
1535 | if newdate: | |
@@ -1591,6 +1636,7 b' class queue(object):' | |||||
1591 | bmlist = repo[top].bookmarks() |
|
1636 | bmlist = repo[top].bookmarks() | |
1592 |
|
1637 | |||
1593 | try: |
|
1638 | try: | |
|
1639 | repo.dirstate.beginparentchange() | |||
1594 | if diffopts.git or diffopts.upgrade: |
|
1640 | if diffopts.git or diffopts.upgrade: | |
1595 | copies = {} |
|
1641 | copies = {} | |
1596 | for dst in a: |
|
1642 | for dst in a: | |
@@ -1643,9 +1689,10 b' class queue(object):' | |||||
1643 |
|
1689 | |||
1644 | # assumes strip can roll itself back if interrupted |
|
1690 | # assumes strip can roll itself back if interrupted | |
1645 | repo.setparents(*cparents) |
|
1691 | repo.setparents(*cparents) | |
|
1692 | repo.dirstate.endparentchange() | |||
1646 | self.applied.pop() |
|
1693 | self.applied.pop() | |
1647 | self.applieddirty = True |
|
1694 | self.applieddirty = True | |
1648 |
strip(self.ui, repo, [top], update=False, backup= |
|
1695 | strip(self.ui, repo, [top], update=False, backup=False) | |
1649 | except: # re-raises |
|
1696 | except: # re-raises | |
1650 | repo.dirstate.invalidate() |
|
1697 | repo.dirstate.invalidate() | |
1651 | raise |
|
1698 | raise | |
@@ -1654,7 +1701,7 b' class queue(object):' | |||||
1654 | # might be nice to attempt to roll back strip after this |
|
1701 | # might be nice to attempt to roll back strip after this | |
1655 |
|
1702 | |||
1656 | defaultmsg = "[mq]: %s" % patchfn |
|
1703 | defaultmsg = "[mq]: %s" % patchfn | |
1657 | editor = cmdutil.getcommiteditor() |
|
1704 | editor = cmdutil.getcommiteditor(editform=editform) | |
1658 | if edit: |
|
1705 | if edit: | |
1659 | def finishdesc(desc): |
|
1706 | def finishdesc(desc): | |
1660 | if desc.rstrip(): |
|
1707 | if desc.rstrip(): | |
@@ -1664,7 +1711,8 b' class queue(object):' | |||||
1664 | # i18n: this message is shown in editor with "HG: " prefix |
|
1711 | # i18n: this message is shown in editor with "HG: " prefix | |
1665 | extramsg = _('Leave message empty to use default message.') |
|
1712 | extramsg = _('Leave message empty to use default message.') | |
1666 | editor = cmdutil.getcommiteditor(finishdesc=finishdesc, |
|
1713 | editor = cmdutil.getcommiteditor(finishdesc=finishdesc, | |
1667 |
extramsg=extramsg |
|
1714 | extramsg=extramsg, | |
|
1715 | editform=editform) | |||
1668 | message = msg or "\n".join(ph.message) |
|
1716 | message = msg or "\n".join(ph.message) | |
1669 | elif not msg: |
|
1717 | elif not msg: | |
1670 | if not ph.message: |
|
1718 | if not ph.message: | |
@@ -1842,7 +1890,7 b' class queue(object):' | |||||
1842 | update = True |
|
1890 | update = True | |
1843 | else: |
|
1891 | else: | |
1844 | update = False |
|
1892 | update = False | |
1845 |
strip(self.ui, repo, [rev], update=update, backup= |
|
1893 | strip(self.ui, repo, [rev], update=update, backup=False) | |
1846 | if qpp: |
|
1894 | if qpp: | |
1847 | self.ui.warn(_("saved queue repository parents: %s %s\n") % |
|
1895 | self.ui.warn(_("saved queue repository parents: %s %s\n") % | |
1848 | (short(qpp[0]), short(qpp[1]))) |
|
1896 | (short(qpp[0]), short(qpp[1]))) | |
@@ -1945,62 +1993,70 b' class queue(object):' | |||||
1945 | # If mq patches are applied, we can only import revisions |
|
1993 | # If mq patches are applied, we can only import revisions | |
1946 | # that form a linear path to qbase. |
|
1994 | # that form a linear path to qbase. | |
1947 | # Otherwise, they should form a linear path to a head. |
|
1995 | # Otherwise, they should form a linear path to a head. | |
1948 |
heads = repo.changelog.heads(repo.changelog.node(rev |
|
1996 | heads = repo.changelog.heads(repo.changelog.node(rev.first())) | |
1949 | if len(heads) > 1: |
|
1997 | if len(heads) > 1: | |
1950 | raise util.Abort(_('revision %d is the root of more than one ' |
|
1998 | raise util.Abort(_('revision %d is the root of more than one ' | |
1951 |
'branch') % rev |
|
1999 | 'branch') % rev.last()) | |
1952 | if self.applied: |
|
2000 | if self.applied: | |
1953 |
base = repo.changelog.node(rev |
|
2001 | base = repo.changelog.node(rev.first()) | |
1954 | if base in [n.node for n in self.applied]: |
|
2002 | if base in [n.node for n in self.applied]: | |
1955 | raise util.Abort(_('revision %d is already managed') |
|
2003 | raise util.Abort(_('revision %d is already managed') | |
1956 | % rev[0]) |
|
2004 | % rev[0]) | |
1957 | if heads != [self.applied[-1].node]: |
|
2005 | if heads != [self.applied[-1].node]: | |
1958 | raise util.Abort(_('revision %d is not the parent of ' |
|
2006 | raise util.Abort(_('revision %d is not the parent of ' | |
1959 |
'the queue') % rev |
|
2007 | 'the queue') % rev.first()) | |
1960 | base = repo.changelog.rev(self.applied[0].node) |
|
2008 | base = repo.changelog.rev(self.applied[0].node) | |
1961 | lastparent = repo.changelog.parentrevs(base)[0] |
|
2009 | lastparent = repo.changelog.parentrevs(base)[0] | |
1962 | else: |
|
2010 | else: | |
1963 |
if heads != [repo.changelog.node(rev |
|
2011 | if heads != [repo.changelog.node(rev.first())]: | |
1964 | raise util.Abort(_('revision %d has unmanaged children') |
|
2012 | raise util.Abort(_('revision %d has unmanaged children') | |
1965 |
% rev |
|
2013 | % rev.first()) | |
1966 | lastparent = None |
|
2014 | lastparent = None | |
1967 |
|
2015 | |||
1968 | diffopts = self.diffopts({'git': git}) |
|
2016 | diffopts = self.diffopts({'git': git}) | |
1969 | for r in rev: |
|
2017 | tr = repo.transaction('qimport') | |
1970 | if not repo[r].mutable(): |
|
2018 | try: | |
1971 | raise util.Abort(_('revision %d is not mutable') % r, |
|
2019 | for r in rev: | |
1972 | hint=_('see "hg help phases" for details')) |
|
2020 | if not repo[r].mutable(): | |
1973 | p1, p2 = repo.changelog.parentrevs(r) |
|
2021 | raise util.Abort(_('revision %d is not mutable') % r, | |
1974 | n = repo.changelog.node(r) |
|
2022 | hint=_('see "hg help phases" ' | |
1975 | if p2 != nullrev: |
|
2023 | 'for details')) | |
1976 | raise util.Abort(_('cannot import merge revision %d') % r) |
|
2024 | p1, p2 = repo.changelog.parentrevs(r) | |
1977 | if lastparent and lastparent != r: |
|
2025 | n = repo.changelog.node(r) | |
1978 | raise util.Abort(_('revision %d is not the parent of %d') |
|
2026 | if p2 != nullrev: | |
1979 | % (r, lastparent)) |
|
2027 | raise util.Abort(_('cannot import merge revision %d') | |
1980 | lastparent = p1 |
|
2028 | % r) | |
1981 |
|
2029 | if lastparent and lastparent != r: | ||
1982 | if not patchname: |
|
2030 | raise util.Abort(_('revision %d is not the parent of ' | |
1983 | patchname = normname('%d.diff' % r) |
|
2031 | '%d') | |
1984 | checkseries(patchname) |
|
2032 | % (r, lastparent)) | |
1985 | self.checkpatchname(patchname, force) |
|
2033 | lastparent = p1 | |
1986 | self.fullseries.insert(0, patchname) |
|
2034 | ||
1987 |
|
2035 | if not patchname: | ||
1988 | patchf = self.opener(patchname, "w") |
|
2036 | patchname = normname('%d.diff' % r) | |
1989 | cmdutil.export(repo, [n], fp=patchf, opts=diffopts) |
|
2037 | checkseries(patchname) | |
1990 | patchf.close() |
|
2038 | self.checkpatchname(patchname, force) | |
1991 |
|
2039 | self.fullseries.insert(0, patchname) | ||
1992 | se = statusentry(n, patchname) |
|
2040 | ||
1993 |
self. |
|
2041 | patchf = self.opener(patchname, "w") | |
1994 |
|
2042 | cmdutil.export(repo, [n], fp=patchf, opts=diffopts) | ||
1995 | self.added.append(patchname) |
|
2043 | patchf.close() | |
1996 | imported.append(patchname) |
|
2044 | ||
1997 |
|
|
2045 | se = statusentry(n, patchname) | |
1998 | if rev and repo.ui.configbool('mq', 'secret', False): |
|
2046 | self.applied.insert(0, se) | |
1999 | # if we added anything with --rev, move the secret root |
|
2047 | ||
2000 | phases.retractboundary(repo, phases.secret, [n]) |
|
2048 | self.added.append(patchname) | |
2001 | self.parseseries() |
|
2049 | imported.append(patchname) | |
2002 | self.applieddirty = True |
|
2050 | patchname = None | |
2003 | self.seriesdirty = True |
|
2051 | if rev and repo.ui.configbool('mq', 'secret', False): | |
|
2052 | # if we added anything with --rev, move the secret root | |||
|
2053 | phases.retractboundary(repo, tr, phases.secret, [n]) | |||
|
2054 | self.parseseries() | |||
|
2055 | self.applieddirty = True | |||
|
2056 | self.seriesdirty = True | |||
|
2057 | tr.close() | |||
|
2058 | finally: | |||
|
2059 | tr.release() | |||
2004 |
|
2060 | |||
2005 | for i, filename in enumerate(files): |
|
2061 | for i, filename in enumerate(files): | |
2006 | if existing: |
|
2062 | if existing: | |
@@ -2585,7 +2641,8 b' def fold(ui, repo, *files, **opts):' | |||||
2585 | diffopts = q.patchopts(q.diffopts(), *patches) |
|
2641 | diffopts = q.patchopts(q.diffopts(), *patches) | |
2586 | wlock = repo.wlock() |
|
2642 | wlock = repo.wlock() | |
2587 | try: |
|
2643 | try: | |
2588 |
q.refresh(repo, msg=message, git=diffopts.git, edit=opts.get('edit') |
|
2644 | q.refresh(repo, msg=message, git=diffopts.git, edit=opts.get('edit'), | |
|
2645 | editform='mq.qfold') | |||
2589 | q.delete(repo, patches, opts) |
|
2646 | q.delete(repo, patches, opts) | |
2590 | q.savedirty() |
|
2647 | q.savedirty() | |
2591 | finally: |
|
2648 | finally: |
@@ -26,7 +26,7 b'' | |||||
26 |
|
26 | |||
27 | from mercurial import util, commands, cmdutil, scmutil |
|
27 | from mercurial import util, commands, cmdutil, scmutil | |
28 | from mercurial.i18n import _ |
|
28 | from mercurial.i18n import _ | |
29 |
import os |
|
29 | import os | |
30 |
|
30 | |||
31 | cmdtable = {} |
|
31 | cmdtable = {} | |
32 | command = cmdutil.command(cmdtable) |
|
32 | command = cmdutil.command(cmdtable) | |
@@ -95,27 +95,17 b' def purge(ui, repo, *dirs, **opts):' | |||||
95 | else: |
|
95 | else: | |
96 | ui.write('%s%s' % (name, eol)) |
|
96 | ui.write('%s%s' % (name, eol)) | |
97 |
|
97 | |||
98 | def removefile(path): |
|
|||
99 | try: |
|
|||
100 | os.remove(path) |
|
|||
101 | except OSError: |
|
|||
102 | # read-only files cannot be unlinked under Windows |
|
|||
103 | s = os.stat(path) |
|
|||
104 | if (s.st_mode & stat.S_IWRITE) != 0: |
|
|||
105 | raise |
|
|||
106 | os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE) |
|
|||
107 | os.remove(path) |
|
|||
108 |
|
||||
109 | directories = [] |
|
|||
110 | match = scmutil.match(repo[None], dirs, opts) |
|
98 | match = scmutil.match(repo[None], dirs, opts) | |
111 | match.explicitdir = match.traversedir = directories.append |
|
99 | if removedirs: | |
|
100 | directories = [] | |||
|
101 | match.explicitdir = match.traversedir = directories.append | |||
112 | status = repo.status(match=match, ignored=opts['all'], unknown=True) |
|
102 | status = repo.status(match=match, ignored=opts['all'], unknown=True) | |
113 |
|
103 | |||
114 | if removefiles: |
|
104 | if removefiles: | |
115 |
for f in sorted(status |
|
105 | for f in sorted(status.unknown + status.ignored): | |
116 | if act: |
|
106 | if act: | |
117 | ui.note(_('removing file %s\n') % f) |
|
107 | ui.note(_('removing file %s\n') % f) | |
118 |
remove( |
|
108 | remove(util.unlink, f) | |
119 |
|
109 | |||
120 | if removedirs: |
|
110 | if removedirs: | |
121 | for f in sorted(directories, reverse=True): |
|
111 | for f in sorted(directories, reverse=True): |
@@ -16,6 +16,7 b' http://mercurial.selenic.com/wiki/Rebase' | |||||
16 |
|
16 | |||
17 | from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks |
|
17 | from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks | |
18 | from mercurial import extensions, patch, scmutil, phases, obsolete, error |
|
18 | from mercurial import extensions, patch, scmutil, phases, obsolete, error | |
|
19 | from mercurial import copies | |||
19 | from mercurial.commands import templateopts |
|
20 | from mercurial.commands import templateopts | |
20 | from mercurial.node import nullrev |
|
21 | from mercurial.node import nullrev | |
21 | from mercurial.lock import release |
|
22 | from mercurial.lock import release | |
@@ -50,10 +51,9 b' def _makeextrafn(copiers):' | |||||
50 |
|
51 | |||
51 | @command('rebase', |
|
52 | @command('rebase', | |
52 | [('s', 'source', '', |
|
53 | [('s', 'source', '', | |
53 |
_('rebase |
|
54 | _('rebase the specified changeset and descendants'), _('REV')), | |
54 | ('b', 'base', '', |
|
55 | ('b', 'base', '', | |
55 |
_('rebase |
|
56 | _('rebase everything from branching point of specified changeset'), | |
56 | '(up to greatest common ancestor of base and dest)'), |
|
|||
57 | _('REV')), |
|
57 | _('REV')), | |
58 | ('r', 'rev', [], |
|
58 | ('r', 'rev', [], | |
59 | _('rebase these revisions'), |
|
59 | _('rebase these revisions'), | |
@@ -69,6 +69,7 b' def _makeextrafn(copiers):' | |||||
69 | ('', 'keep', False, _('keep original changesets')), |
|
69 | ('', 'keep', False, _('keep original changesets')), | |
70 | ('', 'keepbranches', False, _('keep original branch names')), |
|
70 | ('', 'keepbranches', False, _('keep original branch names')), | |
71 | ('D', 'detach', False, _('(DEPRECATED)')), |
|
71 | ('D', 'detach', False, _('(DEPRECATED)')), | |
|
72 | ('i', 'interactive', False, _('(DEPRECATED)')), | |||
72 | ('t', 'tool', '', _('specify merge tool')), |
|
73 | ('t', 'tool', '', _('specify merge tool')), | |
73 | ('c', 'continue', False, _('continue an interrupted rebase')), |
|
74 | ('c', 'continue', False, _('continue an interrupted rebase')), | |
74 | ('a', 'abort', False, _('abort an interrupted rebase'))] + |
|
75 | ('a', 'abort', False, _('abort an interrupted rebase'))] + | |
@@ -128,8 +129,39 b' def rebase(ui, repo, **opts):' | |||||
128 | If a rebase is interrupted to manually resolve a merge, it can be |
|
129 | If a rebase is interrupted to manually resolve a merge, it can be | |
129 | continued with --continue/-c or aborted with --abort/-a. |
|
130 | continued with --continue/-c or aborted with --abort/-a. | |
130 |
|
131 | |||
|
132 | .. container:: verbose | |||
|
133 | ||||
|
134 | Examples: | |||
|
135 | ||||
|
136 | - move "local changes" (current commit back to branching point) | |||
|
137 | to the current branch tip after a pull:: | |||
|
138 | ||||
|
139 | hg rebase | |||
|
140 | ||||
|
141 | - move a single changeset to the stable branch:: | |||
|
142 | ||||
|
143 | hg rebase -r 5f493448 -d stable | |||
|
144 | ||||
|
145 | - splice a commit and all its descendants onto another part of history:: | |||
|
146 | ||||
|
147 | hg rebase --source c0c3 --dest 4cf9 | |||
|
148 | ||||
|
149 | - rebase everything on a branch marked by a bookmark onto the | |||
|
150 | default branch:: | |||
|
151 | ||||
|
152 | hg rebase --base myfeature --dest default | |||
|
153 | ||||
|
154 | - collapse a sequence of changes into a single commit:: | |||
|
155 | ||||
|
156 | hg rebase --collapse -r 1520:1525 -d . | |||
|
157 | ||||
|
158 | - move a named branch while preserving its name:: | |||
|
159 | ||||
|
160 | hg rebase -r "branch(featureX)" -d 1.3 --keepbranches | |||
|
161 | ||||
131 | Returns 0 on success, 1 if nothing to rebase or there are |
|
162 | Returns 0 on success, 1 if nothing to rebase or there are | |
132 | unresolved conflicts. |
|
163 | unresolved conflicts. | |
|
164 | ||||
133 | """ |
|
165 | """ | |
134 | originalwd = target = None |
|
166 | originalwd = target = None | |
135 | activebookmark = None |
|
167 | activebookmark = None | |
@@ -138,7 +170,6 b' def rebase(ui, repo, **opts):' | |||||
138 | skipped = set() |
|
170 | skipped = set() | |
139 | targetancestors = set() |
|
171 | targetancestors = set() | |
140 |
|
172 | |||
141 | editor = cmdutil.getcommiteditor(**opts) |
|
|||
142 |
|
173 | |||
143 | lock = wlock = None |
|
174 | lock = wlock = None | |
144 | try: |
|
175 | try: | |
@@ -164,6 +195,11 b' def rebase(ui, repo, **opts):' | |||||
164 | # other extensions |
|
195 | # other extensions | |
165 | keepopen = opts.get('keepopen', False) |
|
196 | keepopen = opts.get('keepopen', False) | |
166 |
|
197 | |||
|
198 | if opts.get('interactive'): | |||
|
199 | msg = _("interactive history editing is supported by the " | |||
|
200 | "'histedit' extension (see 'hg help histedit')") | |||
|
201 | raise util.Abort(msg) | |||
|
202 | ||||
167 | if collapsemsg and not collapsef: |
|
203 | if collapsemsg and not collapsef: | |
168 | raise util.Abort( |
|
204 | raise util.Abort( | |
169 | _('message can only be specified with collapse')) |
|
205 | _('message can only be specified with collapse')) | |
@@ -241,7 +277,10 b' def rebase(ui, repo, **opts):' | |||||
241 | '(children(ancestor(%ld, %d)) and ::(%ld))::', |
|
277 | '(children(ancestor(%ld, %d)) and ::(%ld))::', | |
242 | base, dest, base) |
|
278 | base, dest, base) | |
243 | if not rebaseset: |
|
279 | if not rebaseset: | |
244 | if base == [dest.rev()]: |
|
280 | # transform to list because smartsets are not comparable to | |
|
281 | # lists. This should be improved to honor lazyness of | |||
|
282 | # smartset. | |||
|
283 | if list(base) == [dest.rev()]: | |||
245 | if basef: |
|
284 | if basef: | |
246 | ui.status(_('nothing to rebase - %s is both "base"' |
|
285 | ui.status(_('nothing to rebase - %s is both "base"' | |
247 | ' and destination\n') % dest) |
|
286 | ' and destination\n') % dest) | |
@@ -264,7 +303,8 b' def rebase(ui, repo, **opts):' | |||||
264 | ('+'.join(str(repo[r]) for r in base), dest)) |
|
303 | ('+'.join(str(repo[r]) for r in base), dest)) | |
265 | return 1 |
|
304 | return 1 | |
266 |
|
305 | |||
267 | if (not (keepf or obsolete._enabled) |
|
306 | allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt) | |
|
307 | if (not (keepf or allowunstable) | |||
268 | and repo.revs('first(children(%ld) - %ld)', |
|
308 | and repo.revs('first(children(%ld) - %ld)', | |
269 | rebaseset, rebaseset)): |
|
309 | rebaseset, rebaseset)): | |
270 | raise util.Abort( |
|
310 | raise util.Abort( | |
@@ -336,29 +376,25 b' def rebase(ui, repo, **opts):' | |||||
336 | try: |
|
376 | try: | |
337 | ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), |
|
377 | ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), | |
338 | 'rebase') |
|
378 | 'rebase') | |
339 |
stats = rebasenode(repo, rev, p1, state, collapsef |
|
379 | stats = rebasenode(repo, rev, p1, state, collapsef, | |
|
380 | target) | |||
340 | if stats and stats[3] > 0: |
|
381 | if stats and stats[3] > 0: | |
341 | raise error.InterventionRequired( |
|
382 | raise error.InterventionRequired( | |
342 | _('unresolved conflicts (see hg ' |
|
383 | _('unresolved conflicts (see hg ' | |
343 | 'resolve, then hg rebase --continue)')) |
|
384 | 'resolve, then hg rebase --continue)')) | |
344 | finally: |
|
385 | finally: | |
345 | ui.setconfig('ui', 'forcemerge', '', 'rebase') |
|
386 | ui.setconfig('ui', 'forcemerge', '', 'rebase') | |
346 | if collapsef: |
|
|||
347 | cmdutil.duplicatecopies(repo, rev, target) |
|
|||
348 | else: |
|
|||
349 | # If we're not using --collapse, we need to |
|
|||
350 | # duplicate copies between the revision we're |
|
|||
351 | # rebasing and its first parent, but *not* |
|
|||
352 | # duplicate any copies that have already been |
|
|||
353 | # performed in the destination. |
|
|||
354 | p1rev = repo[rev].p1().rev() |
|
|||
355 | cmdutil.duplicatecopies(repo, rev, p1rev, skiprev=target) |
|
|||
356 | if not collapsef: |
|
387 | if not collapsef: | |
|
388 | merging = repo[p2].rev() != nullrev | |||
|
389 | editform = cmdutil.mergeeditform(merging, 'rebase') | |||
|
390 | editor = cmdutil.getcommiteditor(editform=editform, **opts) | |||
357 | newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn, |
|
391 | newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn, | |
358 | editor=editor) |
|
392 | editor=editor) | |
359 | else: |
|
393 | else: | |
360 | # Skip commit if we are collapsing |
|
394 | # Skip commit if we are collapsing | |
|
395 | repo.dirstate.beginparentchange() | |||
361 | repo.setparents(repo[p1].node()) |
|
396 | repo.setparents(repo[p1].node()) | |
|
397 | repo.dirstate.endparentchange() | |||
362 | newrev = None |
|
398 | newrev = None | |
363 | # Update the state |
|
399 | # Update the state | |
364 | if newrev is not None: |
|
400 | if newrev is not None: | |
@@ -376,6 +412,8 b' def rebase(ui, repo, **opts):' | |||||
376 | if collapsef and not keepopen: |
|
412 | if collapsef and not keepopen: | |
377 | p1, p2 = defineparents(repo, min(state), target, |
|
413 | p1, p2 = defineparents(repo, min(state), target, | |
378 | state, targetancestors) |
|
414 | state, targetancestors) | |
|
415 | editopt = opts.get('edit') | |||
|
416 | editform = 'rebase.collapse' | |||
379 | if collapsemsg: |
|
417 | if collapsemsg: | |
380 | commitmsg = collapsemsg |
|
418 | commitmsg = collapsemsg | |
381 | else: |
|
419 | else: | |
@@ -383,7 +421,8 b' def rebase(ui, repo, **opts):' | |||||
383 | for rebased in state: |
|
421 | for rebased in state: | |
384 | if rebased not in skipped and state[rebased] > nullmerge: |
|
422 | if rebased not in skipped and state[rebased] > nullmerge: | |
385 | commitmsg += '\n* %s' % repo[rebased].description() |
|
423 | commitmsg += '\n* %s' % repo[rebased].description() | |
386 |
edito |
|
424 | editopt = True | |
|
425 | editor = cmdutil.getcommiteditor(edit=editopt, editform=editform) | |||
387 | newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg, |
|
426 | newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg, | |
388 | extrafn=extrafn, editor=editor) |
|
427 | extrafn=extrafn, editor=editor) | |
389 | for oldrev in state.iterkeys(): |
|
428 | for oldrev in state.iterkeys(): | |
@@ -461,29 +500,34 b' def externalparent(repo, state, targetan' | |||||
461 | def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None): |
|
500 | def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None): | |
462 | 'Commit the changes and store useful information in extra' |
|
501 | 'Commit the changes and store useful information in extra' | |
463 | try: |
|
502 | try: | |
|
503 | repo.dirstate.beginparentchange() | |||
464 | repo.setparents(repo[p1].node(), repo[p2].node()) |
|
504 | repo.setparents(repo[p1].node(), repo[p2].node()) | |
|
505 | repo.dirstate.endparentchange() | |||
465 | ctx = repo[rev] |
|
506 | ctx = repo[rev] | |
466 | if commitmsg is None: |
|
507 | if commitmsg is None: | |
467 | commitmsg = ctx.description() |
|
508 | commitmsg = ctx.description() | |
468 | extra = {'rebase_source': ctx.hex()} |
|
509 | extra = {'rebase_source': ctx.hex()} | |
469 | if extrafn: |
|
510 | if extrafn: | |
470 | extrafn(ctx, extra) |
|
511 | extrafn(ctx, extra) | |
471 | # Commit might fail if unresolved files exist |
|
512 | ||
472 | newrev = repo.commit(text=commitmsg, user=ctx.user(), |
|
513 | backup = repo.ui.backupconfig('phases', 'new-commit') | |
473 | date=ctx.date(), extra=extra, editor=editor) |
|
514 | try: | |
|
515 | targetphase = max(ctx.phase(), phases.draft) | |||
|
516 | repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase') | |||
|
517 | # Commit might fail if unresolved files exist | |||
|
518 | newrev = repo.commit(text=commitmsg, user=ctx.user(), | |||
|
519 | date=ctx.date(), extra=extra, editor=editor) | |||
|
520 | finally: | |||
|
521 | repo.ui.restoreconfig(backup) | |||
|
522 | ||||
474 | repo.dirstate.setbranch(repo[newrev].branch()) |
|
523 | repo.dirstate.setbranch(repo[newrev].branch()) | |
475 | targetphase = max(ctx.phase(), phases.draft) |
|
|||
476 | # retractboundary doesn't overwrite upper phase inherited from parent |
|
|||
477 | newnode = repo[newrev].node() |
|
|||
478 | if newnode: |
|
|||
479 | phases.retractboundary(repo, targetphase, [newnode]) |
|
|||
480 | return newrev |
|
524 | return newrev | |
481 | except util.Abort: |
|
525 | except util.Abort: | |
482 | # Invalidate the previous setparents |
|
526 | # Invalidate the previous setparents | |
483 | repo.dirstate.invalidate() |
|
527 | repo.dirstate.invalidate() | |
484 | raise |
|
528 | raise | |
485 |
|
529 | |||
486 | def rebasenode(repo, rev, p1, state, collapse): |
|
530 | def rebasenode(repo, rev, p1, state, collapse, target): | |
487 | 'Rebase a single revision' |
|
531 | 'Rebase a single revision' | |
488 | # Merge phase |
|
532 | # Merge phase | |
489 | # Update to target and merge it with local |
|
533 | # Update to target and merge it with local | |
@@ -540,15 +584,26 b' def rebasenode(repo, rev, p1, state, col' | |||||
540 | repo.ui.debug(" detach base %d:%s\n" % (repo[base].rev(), repo[base])) |
|
584 | repo.ui.debug(" detach base %d:%s\n" % (repo[base].rev(), repo[base])) | |
541 | # When collapsing in-place, the parent is the common ancestor, we |
|
585 | # When collapsing in-place, the parent is the common ancestor, we | |
542 | # have to allow merging with it. |
|
586 | # have to allow merging with it. | |
543 |
|
|
587 | stats = merge.update(repo, rev, True, True, False, base, collapse, | |
544 | labels=['dest', 'source']) |
|
588 | labels=['dest', 'source']) | |
|
589 | if collapse: | |||
|
590 | copies.duplicatecopies(repo, rev, target) | |||
|
591 | else: | |||
|
592 | # If we're not using --collapse, we need to | |||
|
593 | # duplicate copies between the revision we're | |||
|
594 | # rebasing and its first parent, but *not* | |||
|
595 | # duplicate any copies that have already been | |||
|
596 | # performed in the destination. | |||
|
597 | p1rev = repo[rev].p1().rev() | |||
|
598 | copies.duplicatecopies(repo, rev, p1rev, skiprev=target) | |||
|
599 | return stats | |||
545 |
|
600 | |||
546 | def nearestrebased(repo, rev, state): |
|
601 | def nearestrebased(repo, rev, state): | |
547 | """return the nearest ancestors of rev in the rebase result""" |
|
602 | """return the nearest ancestors of rev in the rebase result""" | |
548 | rebased = [r for r in state if state[r] > nullmerge] |
|
603 | rebased = [r for r in state if state[r] > nullmerge] | |
549 | candidates = repo.revs('max(%ld and (::%d))', rebased, rev) |
|
604 | candidates = repo.revs('max(%ld and (::%d))', rebased, rev) | |
550 | if candidates: |
|
605 | if candidates: | |
551 |
return state[candidates |
|
606 | return state[candidates.first()] | |
552 | else: |
|
607 | else: | |
553 | return None |
|
608 | return None | |
554 |
|
609 | |||
@@ -557,40 +612,40 b' def defineparents(repo, rev, target, sta' | |||||
557 | parents = repo[rev].parents() |
|
612 | parents = repo[rev].parents() | |
558 | p1 = p2 = nullrev |
|
613 | p1 = p2 = nullrev | |
559 |
|
614 | |||
560 |
|
|
615 | p1n = parents[0].rev() | |
561 |
if |
|
616 | if p1n in targetancestors: | |
562 | p1 = target |
|
617 | p1 = target | |
563 |
elif |
|
618 | elif p1n in state: | |
564 |
if state[ |
|
619 | if state[p1n] == nullmerge: | |
565 | p1 = target |
|
620 | p1 = target | |
566 |
elif state[ |
|
621 | elif state[p1n] == revignored: | |
567 |
p1 = nearestrebased(repo, |
|
622 | p1 = nearestrebased(repo, p1n, state) | |
568 | if p1 is None: |
|
623 | if p1 is None: | |
569 | p1 = target |
|
624 | p1 = target | |
570 | else: |
|
625 | else: | |
571 |
p1 = state[ |
|
626 | p1 = state[p1n] | |
572 |
else: # |
|
627 | else: # p1n external | |
573 | p1 = target |
|
628 | p1 = target | |
574 |
p2 = |
|
629 | p2 = p1n | |
575 |
|
630 | |||
576 | if len(parents) == 2 and parents[1].rev() not in targetancestors: |
|
631 | if len(parents) == 2 and parents[1].rev() not in targetancestors: | |
577 |
|
|
632 | p2n = parents[1].rev() | |
578 | # interesting second parent |
|
633 | # interesting second parent | |
579 |
if |
|
634 | if p2n in state: | |
580 |
if p1 == target: # |
|
635 | if p1 == target: # p1n in targetancestors or external | |
581 |
p1 = state[ |
|
636 | p1 = state[p2n] | |
582 |
elif state[ |
|
637 | elif state[p2n] == revignored: | |
583 |
p2 = nearestrebased(repo, |
|
638 | p2 = nearestrebased(repo, p2n, state) | |
584 | if p2 is None: |
|
639 | if p2 is None: | |
585 | # no ancestors rebased yet, detach |
|
640 | # no ancestors rebased yet, detach | |
586 | p2 = target |
|
641 | p2 = target | |
587 | else: |
|
642 | else: | |
588 |
p2 = state[ |
|
643 | p2 = state[p2n] | |
589 |
else: # |
|
644 | else: # p2n external | |
590 |
if p2 != nullrev: # |
|
645 | if p2 != nullrev: # p1n external too => rev is a merged revision | |
591 | raise util.Abort(_('cannot use revision %d as base, result ' |
|
646 | raise util.Abort(_('cannot use revision %d as base, result ' | |
592 | 'would have 3 parents') % rev) |
|
647 | 'would have 3 parents') % rev) | |
593 |
p2 = |
|
648 | p2 = p2n | |
594 | repo.ui.debug(" future parents are %d and %d\n" % |
|
649 | repo.ui.debug(" future parents are %d and %d\n" % | |
595 | (repo[p1].rev(), repo[p2].rev())) |
|
650 | (repo[p1].rev(), repo[p2].rev())) | |
596 | return p1, p2 |
|
651 | return p1, p2 | |
@@ -874,7 +929,7 b' def clearrebased(ui, repo, state, skippe' | |||||
874 |
|
929 | |||
875 | If `collapsedas` is not None, the rebase was a collapse whose result if the |
|
930 | If `collapsedas` is not None, the rebase was a collapse whose result if the | |
876 | `collapsedas` node.""" |
|
931 | `collapsedas` node.""" | |
877 | if obsolete._enabled: |
|
932 | if obsolete.isenabled(repo, obsolete.createmarkersopt): | |
878 | markers = [] |
|
933 | markers = [] | |
879 | for rev, newrev in sorted(state.items()): |
|
934 | for rev, newrev in sorted(state.items()): | |
880 | if newrev >= 0: |
|
935 | if newrev >= 0: |
@@ -519,12 +519,12 b' def dorecord(ui, repo, commitfunc, cmdsu' | |||||
519 | raise util.Abort(_('cannot partially commit a merge ' |
|
519 | raise util.Abort(_('cannot partially commit a merge ' | |
520 | '(use "hg commit" instead)')) |
|
520 | '(use "hg commit" instead)')) | |
521 |
|
521 | |||
522 |
|
|
522 | status = repo.status(match=match) | |
523 | diffopts = opts.copy() |
|
523 | diffopts = opts.copy() | |
524 | diffopts['nodates'] = True |
|
524 | diffopts['nodates'] = True | |
525 | diffopts['git'] = True |
|
525 | diffopts['git'] = True | |
526 | diffopts = patch.diffopts(ui, opts=diffopts) |
|
526 | diffopts = patch.diffopts(ui, opts=diffopts) | |
527 |
chunks = patch.diff(repo, changes= |
|
527 | chunks = patch.diff(repo, changes=status, opts=diffopts) | |
528 | fp = cStringIO.StringIO() |
|
528 | fp = cStringIO.StringIO() | |
529 | fp.write(''.join(chunks)) |
|
529 | fp.write(''.join(chunks)) | |
530 | fp.seek(0) |
|
530 | fp.seek(0) | |
@@ -544,13 +544,13 b' def dorecord(ui, repo, commitfunc, cmdsu' | |||||
544 | except AttributeError: |
|
544 | except AttributeError: | |
545 | pass |
|
545 | pass | |
546 |
|
546 | |||
547 | changed = changes[0] + changes[1] + changes[2] |
|
547 | changed = status.modified + status.added + status.removed | |
548 | newfiles = [f for f in changed if f in contenders] |
|
548 | newfiles = [f for f in changed if f in contenders] | |
549 | if not newfiles: |
|
549 | if not newfiles: | |
550 | ui.status(_('no changes to record\n')) |
|
550 | ui.status(_('no changes to record\n')) | |
551 | return 0 |
|
551 | return 0 | |
552 |
|
552 | |||
553 |
modified = set( |
|
553 | modified = set(status.modified) | |
554 |
|
554 | |||
555 | # 2. backup changed files, so we can restore them in the end |
|
555 | # 2. backup changed files, so we can restore them in the end | |
556 | if backupall: |
|
556 | if backupall: |
@@ -25,7 +25,7 b' from mercurial.i18n import _' | |||||
25 | from mercurial.node import nullid, nullrev, bin, hex |
|
25 | from mercurial.node import nullid, nullrev, bin, hex | |
26 | from mercurial import changegroup, cmdutil, scmutil, phases, commands |
|
26 | from mercurial import changegroup, cmdutil, scmutil, phases, commands | |
27 | from mercurial import error, hg, mdiff, merge, patch, repair, util |
|
27 | from mercurial import error, hg, mdiff, merge, patch, repair, util | |
28 |
from mercurial import templatefilters, change |
|
28 | from mercurial import templatefilters, exchange, bundlerepo | |
29 | from mercurial import lock as lockmod |
|
29 | from mercurial import lock as lockmod | |
30 | from hgext import rebase |
|
30 | from hgext import rebase | |
31 | import errno |
|
31 | import errno | |
@@ -37,7 +37,7 b" testedwith = 'internal'" | |||||
37 | class shelvedfile(object): |
|
37 | class shelvedfile(object): | |
38 | """Helper for the file storing a single shelve |
|
38 | """Helper for the file storing a single shelve | |
39 |
|
39 | |||
40 |
Handles common functions on shelve files (.hg/. |
|
40 | Handles common functions on shelve files (.hg/.patch) using | |
41 | the vfs layer""" |
|
41 | the vfs layer""" | |
42 | def __init__(self, repo, name, filetype=None): |
|
42 | def __init__(self, repo, name, filetype=None): | |
43 | self.repo = repo |
|
43 | self.repo = repo | |
@@ -73,10 +73,14 b' class shelvedfile(object):' | |||||
73 | try: |
|
73 | try: | |
74 | gen = exchange.readbundle(self.repo.ui, fp, self.fname, self.vfs) |
|
74 | gen = exchange.readbundle(self.repo.ui, fp, self.fname, self.vfs) | |
75 | changegroup.addchangegroup(self.repo, gen, 'unshelve', |
|
75 | changegroup.addchangegroup(self.repo, gen, 'unshelve', | |
76 |
'bundle:' + self.vfs.join(self.fname) |
|
76 | 'bundle:' + self.vfs.join(self.fname), | |
|
77 | targetphase=phases.secret) | |||
77 | finally: |
|
78 | finally: | |
78 | fp.close() |
|
79 | fp.close() | |
79 |
|
80 | |||
|
81 | def bundlerepo(self): | |||
|
82 | return bundlerepo.bundlerepository(self.repo.baseui, self.repo.root, | |||
|
83 | self.vfs.join(self.fname)) | |||
80 | def writebundle(self, cg): |
|
84 | def writebundle(self, cg): | |
81 | changegroup.writebundle(cg, self.fname, 'HG10UN', self.vfs) |
|
85 | changegroup.writebundle(cg, self.fname, 'HG10UN', self.vfs) | |
82 |
|
86 | |||
@@ -168,19 +172,18 b' def createcmd(ui, repo, pats, opts):' | |||||
168 | for i in xrange(1, 100): |
|
172 | for i in xrange(1, 100): | |
169 | yield '%s-%02d' % (label, i) |
|
173 | yield '%s-%02d' % (label, i) | |
170 |
|
174 | |||
171 | shelvedfiles = [] |
|
|||
172 |
|
||||
173 | def commitfunc(ui, repo, message, match, opts): |
|
175 | def commitfunc(ui, repo, message, match, opts): | |
174 | # check modified, added, removed, deleted only |
|
|||
175 | for flist in repo.status(match=match)[:4]: |
|
|||
176 | shelvedfiles.extend(flist) |
|
|||
177 | hasmq = util.safehasattr(repo, 'mq') |
|
176 | hasmq = util.safehasattr(repo, 'mq') | |
178 | if hasmq: |
|
177 | if hasmq: | |
179 | saved, repo.mq.checkapplied = repo.mq.checkapplied, False |
|
178 | saved, repo.mq.checkapplied = repo.mq.checkapplied, False | |
|
179 | backup = repo.ui.backupconfig('phases', 'new-commit') | |||
180 | try: |
|
180 | try: | |
|
181 | repo.ui. setconfig('phases', 'new-commit', phases.secret) | |||
|
182 | editor = cmdutil.getcommiteditor(editform='shelve.shelve', **opts) | |||
181 | return repo.commit(message, user, opts.get('date'), match, |
|
183 | return repo.commit(message, user, opts.get('date'), match, | |
182 |
editor= |
|
184 | editor=editor) | |
183 | finally: |
|
185 | finally: | |
|
186 | repo.ui.restoreconfig(backup) | |||
184 | if hasmq: |
|
187 | if hasmq: | |
185 | repo.mq.checkapplied = saved |
|
188 | repo.mq.checkapplied = saved | |
186 |
|
189 | |||
@@ -227,18 +230,13 b' def createcmd(ui, repo, pats, opts):' | |||||
227 |
|
230 | |||
228 | if not node: |
|
231 | if not node: | |
229 | stat = repo.status(match=scmutil.match(repo[None], pats, opts)) |
|
232 | stat = repo.status(match=scmutil.match(repo[None], pats, opts)) | |
230 |
if stat |
|
233 | if stat.deleted: | |
231 | ui.status(_("nothing changed (%d missing files, see " |
|
234 | ui.status(_("nothing changed (%d missing files, see " | |
232 |
"'hg status')\n") % len(stat |
|
235 | "'hg status')\n") % len(stat.deleted)) | |
233 | else: |
|
236 | else: | |
234 | ui.status(_("nothing changed\n")) |
|
237 | ui.status(_("nothing changed\n")) | |
235 | return 1 |
|
238 | return 1 | |
236 |
|
239 | |||
237 | phases.retractboundary(repo, phases.secret, [node]) |
|
|||
238 |
|
||||
239 | fp = shelvedfile(repo, name, 'files').opener('wb') |
|
|||
240 | fp.write('\0'.join(shelvedfiles)) |
|
|||
241 |
|
||||
242 | bases = list(publicancestors(repo[node])) |
|
240 | bases = list(publicancestors(repo[node])) | |
243 | cg = changegroup.changegroupsubset(repo, bases, [node], 'shelve') |
|
241 | cg = changegroup.changegroupsubset(repo, bases, [node], 'shelve') | |
244 | shelvedfile(repo, name, 'hg').writebundle(cg) |
|
242 | shelvedfile(repo, name, 'hg').writebundle(cg) | |
@@ -266,9 +264,9 b' def cleanupcmd(ui, repo):' | |||||
266 | wlock = None |
|
264 | wlock = None | |
267 | try: |
|
265 | try: | |
268 | wlock = repo.wlock() |
|
266 | wlock = repo.wlock() | |
269 | for (name, _) in repo.vfs.readdir('shelved'): |
|
267 | for (name, _type) in repo.vfs.readdir('shelved'): | |
270 | suffix = name.rsplit('.', 1)[-1] |
|
268 | suffix = name.rsplit('.', 1)[-1] | |
271 |
if suffix in ('hg', ' |
|
269 | if suffix in ('hg', 'patch'): | |
272 | shelvedfile(repo, name).unlink() |
|
270 | shelvedfile(repo, name).unlink() | |
273 | finally: |
|
271 | finally: | |
274 | lockmod.release(wlock) |
|
272 | lockmod.release(wlock) | |
@@ -282,7 +280,7 b' def deletecmd(ui, repo, pats):' | |||||
282 | wlock = repo.wlock() |
|
280 | wlock = repo.wlock() | |
283 | try: |
|
281 | try: | |
284 | for name in pats: |
|
282 | for name in pats: | |
285 |
for suffix in 'hg |
|
283 | for suffix in 'hg patch'.split(): | |
286 | shelvedfile(repo, name, suffix).unlink() |
|
284 | shelvedfile(repo, name, suffix).unlink() | |
287 | except OSError, err: |
|
285 | except OSError, err: | |
288 | if err.errno != errno.ENOENT: |
|
286 | if err.errno != errno.ENOENT: | |
@@ -300,7 +298,7 b' def listshelves(repo):' | |||||
300 | raise |
|
298 | raise | |
301 | return [] |
|
299 | return [] | |
302 | info = [] |
|
300 | info = [] | |
303 | for (name, _) in names: |
|
301 | for (name, _type) in names: | |
304 | pfx, sfx = name.rsplit('.', 1) |
|
302 | pfx, sfx = name.rsplit('.', 1) | |
305 | if not pfx or sfx != 'patch': |
|
303 | if not pfx or sfx != 'patch': | |
306 | continue |
|
304 | continue | |
@@ -388,7 +386,7 b' def unshelveabort(ui, repo, state, opts)' | |||||
388 |
|
386 | |||
389 | mergefiles(ui, repo, state.wctx, state.pendingctx) |
|
387 | mergefiles(ui, repo, state.wctx, state.pendingctx) | |
390 |
|
388 | |||
391 |
repair.strip(ui, repo, state.stripnodes, backup= |
|
389 | repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve') | |
392 | shelvedstate.clear(repo) |
|
390 | shelvedstate.clear(repo) | |
393 | ui.warn(_("unshelve of '%s' aborted\n") % state.name) |
|
391 | ui.warn(_("unshelve of '%s' aborted\n") % state.name) | |
394 | finally: |
|
392 | finally: | |
@@ -406,20 +404,21 b' def mergefiles(ui, repo, wctx, shelvectx' | |||||
406 | files.extend(shelvectx.parents()[0].files()) |
|
404 | files.extend(shelvectx.parents()[0].files()) | |
407 |
|
405 | |||
408 | # revert will overwrite unknown files, so move them out of the way |
|
406 | # revert will overwrite unknown files, so move them out of the way | |
409 |
|
|
407 | for file in repo.status(unknown=True).unknown: | |
410 | for file in u: |
|
|||
411 | if file in files: |
|
408 | if file in files: | |
412 | util.rename(file, file + ".orig") |
|
409 | util.rename(file, file + ".orig") | |
|
410 | ui.pushbuffer(True) | |||
413 | cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(), |
|
411 | cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(), | |
414 | *pathtofiles(repo, files), |
|
412 | *pathtofiles(repo, files), | |
415 | **{'no_backup': True}) |
|
413 | **{'no_backup': True}) | |
|
414 | ui.popbuffer() | |||
416 | finally: |
|
415 | finally: | |
417 | ui.quiet = oldquiet |
|
416 | ui.quiet = oldquiet | |
418 |
|
417 | |||
419 | def unshelvecleanup(ui, repo, name, opts): |
|
418 | def unshelvecleanup(ui, repo, name, opts): | |
420 | """remove related files after an unshelve""" |
|
419 | """remove related files after an unshelve""" | |
421 | if not opts['keep']: |
|
420 | if not opts['keep']: | |
422 |
for filetype in 'hg |
|
421 | for filetype in 'hg patch'.split(): | |
423 | shelvedfile(repo, name, filetype).unlink() |
|
422 | shelvedfile(repo, name, filetype).unlink() | |
424 |
|
423 | |||
425 | def unshelvecontinue(ui, repo, state, opts): |
|
424 | def unshelvecontinue(ui, repo, state, opts): | |
@@ -459,7 +458,7 b' def unshelvecontinue(ui, repo, state, op' | |||||
459 |
|
458 | |||
460 | mergefiles(ui, repo, state.wctx, shelvectx) |
|
459 | mergefiles(ui, repo, state.wctx, shelvectx) | |
461 |
|
460 | |||
462 |
repair.strip(ui, repo, state.stripnodes, backup= |
|
461 | repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve') | |
463 | shelvedstate.clear(repo) |
|
462 | shelvedstate.clear(repo) | |
464 | unshelvecleanup(ui, repo, state.name, opts) |
|
463 | unshelvecleanup(ui, repo, state.name, opts) | |
465 | ui.status(_("unshelve of '%s' complete\n") % state.name) |
|
464 | ui.status(_("unshelve of '%s' complete\n") % state.name) | |
@@ -530,7 +529,7 b' def unshelve(ui, repo, *shelved, **opts)' | |||||
530 | else: |
|
529 | else: | |
531 | basename = shelved[0] |
|
530 | basename = shelved[0] | |
532 |
|
531 | |||
533 |
if not shelvedfile(repo, basename, ' |
|
532 | if not shelvedfile(repo, basename, 'patch').exists(): | |
534 | raise util.Abort(_("shelved change '%s' not found") % basename) |
|
533 | raise util.Abort(_("shelved change '%s' not found") % basename) | |
535 |
|
534 | |||
536 | oldquiet = ui.quiet |
|
535 | oldquiet = ui.quiet | |
@@ -551,8 +550,8 b' def unshelve(ui, repo, *shelved, **opts)' | |||||
551 | # to the original pctx. |
|
550 | # to the original pctx. | |
552 |
|
551 | |||
553 | # Store pending changes in a commit |
|
552 | # Store pending changes in a commit | |
554 |
|
|
553 | s = repo.status() | |
555 | if m or a or r or d: |
|
554 | if s.modified or s.added or s.removed or s.deleted: | |
556 | ui.status(_("temporarily committing pending changes " |
|
555 | ui.status(_("temporarily committing pending changes " | |
557 | "(restore with 'hg unshelve --abort')\n")) |
|
556 | "(restore with 'hg unshelve --abort')\n")) | |
558 | def commitfunc(ui, repo, message, match, opts): |
|
557 | def commitfunc(ui, repo, message, match, opts): | |
@@ -560,10 +559,13 b' def unshelve(ui, repo, *shelved, **opts)' | |||||
560 | if hasmq: |
|
559 | if hasmq: | |
561 | saved, repo.mq.checkapplied = repo.mq.checkapplied, False |
|
560 | saved, repo.mq.checkapplied = repo.mq.checkapplied, False | |
562 |
|
561 | |||
|
562 | backup = repo.ui.backupconfig('phases', 'new-commit') | |||
563 | try: |
|
563 | try: | |
|
564 | repo.ui. setconfig('phases', 'new-commit', phases.secret) | |||
564 | return repo.commit(message, 'shelve@localhost', |
|
565 | return repo.commit(message, 'shelve@localhost', | |
565 | opts.get('date'), match) |
|
566 | opts.get('date'), match) | |
566 | finally: |
|
567 | finally: | |
|
568 | repo.ui.restoreconfig(backup) | |||
567 | if hasmq: |
|
569 | if hasmq: | |
568 | repo.mq.checkapplied = saved |
|
570 | repo.mq.checkapplied = saved | |
569 |
|
571 | |||
@@ -576,8 +578,6 b' def unshelve(ui, repo, *shelved, **opts)' | |||||
576 |
|
578 | |||
577 | ui.quiet = True |
|
579 | ui.quiet = True | |
578 | shelvedfile(repo, basename, 'hg').applybundle() |
|
580 | shelvedfile(repo, basename, 'hg').applybundle() | |
579 | nodes = [ctx.node() for ctx in repo.set('%d:', oldtiprev)] |
|
|||
580 | phases.retractboundary(repo, phases.secret, nodes) |
|
|||
581 |
|
581 | |||
582 | ui.quiet = oldquiet |
|
582 | ui.quiet = oldquiet | |
583 |
|
583 |
@@ -32,17 +32,17 b' def checksubstate(repo, baserev=None):' | |||||
32 |
|
32 | |||
33 | def checklocalchanges(repo, force=False, excsuffix=''): |
|
33 | def checklocalchanges(repo, force=False, excsuffix=''): | |
34 | cmdutil.checkunfinished(repo) |
|
34 | cmdutil.checkunfinished(repo) | |
35 |
|
|
35 | s = repo.status() | |
36 | if not force: |
|
36 | if not force: | |
37 |
if |
|
37 | if s.modified or s.added or s.removed or s.deleted: | |
38 | _("local changes found") # i18n tool detection |
|
38 | _("local changes found") # i18n tool detection | |
39 | raise util.Abort(_("local changes found" + excsuffix)) |
|
39 | raise util.Abort(_("local changes found" + excsuffix)) | |
40 | if checksubstate(repo): |
|
40 | if checksubstate(repo): | |
41 | _("local changed subrepos found") # i18n tool detection |
|
41 | _("local changed subrepos found") # i18n tool detection | |
42 | raise util.Abort(_("local changed subrepos found" + excsuffix)) |
|
42 | raise util.Abort(_("local changed subrepos found" + excsuffix)) | |
43 |
return |
|
43 | return s | |
44 |
|
44 | |||
45 |
def strip(ui, repo, revs, update=True, backup= |
|
45 | def strip(ui, repo, revs, update=True, backup=True, force=None, bookmark=None): | |
46 | wlock = lock = None |
|
46 | wlock = lock = None | |
47 | try: |
|
47 | try: | |
48 | wlock = repo.wlock() |
|
48 | wlock = repo.wlock() | |
@@ -114,11 +114,9 b' def stripcmd(ui, repo, *revs, **opts):' | |||||
114 |
|
114 | |||
115 | Return 0 on success. |
|
115 | Return 0 on success. | |
116 | """ |
|
116 | """ | |
117 |
backup = |
|
117 | backup = True | |
118 | if opts.get('backup'): |
|
118 | if opts.get('no_backup') or opts.get('nobackup'): | |
119 |
backup = |
|
119 | backup = False | |
120 | elif opts.get('no_backup') or opts.get('nobackup'): |
|
|||
121 | backup = 'none' |
|
|||
122 |
|
120 | |||
123 | cl = repo.changelog |
|
121 | cl = repo.changelog | |
124 | revs = list(revs) + opts.get('rev') |
|
122 | revs = list(revs) + opts.get('rev') |
@@ -19,7 +19,7 b' import os, tempfile' | |||||
19 | from mercurial.node import short |
|
19 | from mercurial.node import short | |
20 | from mercurial import bundlerepo, hg, merge, match |
|
20 | from mercurial import bundlerepo, hg, merge, match | |
21 | from mercurial import patch, revlog, scmutil, util, error, cmdutil |
|
21 | from mercurial import patch, revlog, scmutil, util, error, cmdutil | |
22 | from mercurial import revset, templatekw |
|
22 | from mercurial import revset, templatekw, exchange | |
23 |
|
23 | |||
24 | class TransplantError(error.Abort): |
|
24 | class TransplantError(error.Abort): | |
25 | pass |
|
25 | pass | |
@@ -86,7 +86,10 b' class transplanter(object):' | |||||
86 | self.opener = scmutil.opener(self.path) |
|
86 | self.opener = scmutil.opener(self.path) | |
87 | self.transplants = transplants(self.path, 'transplants', |
|
87 | self.transplants = transplants(self.path, 'transplants', | |
88 | opener=self.opener) |
|
88 | opener=self.opener) | |
89 |
|
|
89 | def getcommiteditor(): | |
|
90 | editform = cmdutil.mergeeditform(repo[None], 'transplant') | |||
|
91 | return cmdutil.getcommiteditor(editform=editform, **opts) | |||
|
92 | self.getcommiteditor = getcommiteditor | |||
90 |
|
93 | |||
91 | def applied(self, repo, node, parent): |
|
94 | def applied(self, repo, node, parent): | |
92 | '''returns True if a node is already an ancestor of parent |
|
95 | '''returns True if a node is already an ancestor of parent | |
@@ -142,7 +145,7 b' class transplanter(object):' | |||||
142 | continue |
|
145 | continue | |
143 | if pulls: |
|
146 | if pulls: | |
144 | if source != repo: |
|
147 | if source != repo: | |
145 |
|
|
148 | exchange.pull(repo, source.peer(), heads=pulls) | |
146 | merge.update(repo, pulls[-1], False, False, None) |
|
149 | merge.update(repo, pulls[-1], False, False, None) | |
147 | p1, p2 = repo.dirstate.parents() |
|
150 | p1, p2 = repo.dirstate.parents() | |
148 | pulls = [] |
|
151 | pulls = [] | |
@@ -154,7 +157,7 b' class transplanter(object):' | |||||
154 | # transplants before them fail. |
|
157 | # transplants before them fail. | |
155 | domerge = True |
|
158 | domerge = True | |
156 | if not hasnode(repo, node): |
|
159 | if not hasnode(repo, node): | |
157 |
|
|
160 | exchange.pull(repo, source.peer(), heads=[node]) | |
158 |
|
161 | |||
159 | skipmerge = False |
|
162 | skipmerge = False | |
160 | if parents[1] != revlog.nullid: |
|
163 | if parents[1] != revlog.nullid: | |
@@ -206,7 +209,7 b' class transplanter(object):' | |||||
206 | os.unlink(patchfile) |
|
209 | os.unlink(patchfile) | |
207 | tr.close() |
|
210 | tr.close() | |
208 | if pulls: |
|
211 | if pulls: | |
209 |
|
|
212 | exchange.pull(repo, source.peer(), heads=pulls) | |
210 | merge.update(repo, pulls[-1], False, False, None) |
|
213 | merge.update(repo, pulls[-1], False, False, None) | |
211 | finally: |
|
214 | finally: | |
212 | self.saveseries(revmap, merges) |
|
215 | self.saveseries(revmap, merges) | |
@@ -286,7 +289,7 b' class transplanter(object):' | |||||
286 | m = match.exact(repo.root, '', files) |
|
289 | m = match.exact(repo.root, '', files) | |
287 |
|
290 | |||
288 | n = repo.commit(message, user, date, extra=extra, match=m, |
|
291 | n = repo.commit(message, user, date, extra=extra, match=m, | |
289 | editor=self.editor) |
|
292 | editor=self.getcommiteditor()) | |
290 | if not n: |
|
293 | if not n: | |
291 | self.ui.warn(_('skipping emptied changeset %s\n') % short(node)) |
|
294 | self.ui.warn(_('skipping emptied changeset %s\n') % short(node)) | |
292 | return None |
|
295 | return None | |
@@ -342,7 +345,7 b' class transplanter(object):' | |||||
342 | if merge: |
|
345 | if merge: | |
343 | repo.setparents(p1, parents[1]) |
|
346 | repo.setparents(p1, parents[1]) | |
344 | n = repo.commit(message, user, date, extra=extra, |
|
347 | n = repo.commit(message, user, date, extra=extra, | |
345 | editor=self.editor) |
|
348 | editor=self.getcommiteditor()) | |
346 | if not n: |
|
349 | if not n: | |
347 | raise util.Abort(_('commit failed')) |
|
350 | raise util.Abort(_('commit failed')) | |
348 | if not merge: |
|
351 | if not merge: |
@@ -7,7 +7,7 b' import re' | |||||
7 |
|
7 | |||
8 | checkers = [] |
|
8 | checkers = [] | |
9 |
|
9 | |||
10 | def checker(level, msgidpat): |
|
10 | def levelchecker(level, msgidpat): | |
11 | def decorator(func): |
|
11 | def decorator(func): | |
12 | if msgidpat: |
|
12 | if msgidpat: | |
13 | match = re.compile(msgidpat).search |
|
13 | match = re.compile(msgidpat).search | |
@@ -33,7 +33,7 b' def match(checker, pe):' | |||||
33 | #################### |
|
33 | #################### | |
34 |
|
34 | |||
35 | def fatalchecker(msgidpat=None): |
|
35 | def fatalchecker(msgidpat=None): | |
36 | return checker('fatal', msgidpat) |
|
36 | return levelchecker('fatal', msgidpat) | |
37 |
|
37 | |||
38 | @fatalchecker(r'\$\$') |
|
38 | @fatalchecker(r'\$\$') | |
39 | def promptchoice(pe): |
|
39 | def promptchoice(pe): | |
@@ -64,7 +64,7 b' def promptchoice(pe):' | |||||
64 | #################### |
|
64 | #################### | |
65 |
|
65 | |||
66 | def warningchecker(msgidpat=None): |
|
66 | def warningchecker(msgidpat=None): | |
67 | return checker('warning', msgidpat) |
|
67 | return levelchecker('warning', msgidpat) | |
68 |
|
68 | |||
69 | @warningchecker() |
|
69 | @warningchecker() | |
70 | def taildoublecolons(pe): |
|
70 | def taildoublecolons(pe): |
@@ -246,6 +246,14 b' class lazyancestors(object):' | |||||
246 | else: |
|
246 | else: | |
247 | self._containsseen = set() |
|
247 | self._containsseen = set() | |
248 |
|
248 | |||
|
249 | def __nonzero__(self): | |||
|
250 | """False if the set is empty, True otherwise.""" | |||
|
251 | try: | |||
|
252 | iter(self).next() | |||
|
253 | return True | |||
|
254 | except StopIteration: | |||
|
255 | return False | |||
|
256 | ||||
249 | def __iter__(self): |
|
257 | def __iter__(self): | |
250 | """Generate the ancestors of _initrevs in reverse topological order. |
|
258 | """Generate the ancestors of _initrevs in reverse topological order. | |
251 |
|
259 |
@@ -7,7 +7,7 b'' | |||||
7 |
|
7 | |||
8 | from mercurial.i18n import _ |
|
8 | from mercurial.i18n import _ | |
9 | from mercurial.node import hex, bin |
|
9 | from mercurial.node import hex, bin | |
10 | from mercurial import encoding, error, util, obsolete |
|
10 | from mercurial import encoding, error, util, obsolete, lock as lockmod | |
11 | import errno |
|
11 | import errno | |
12 |
|
12 | |||
13 | class bmstore(dict): |
|
13 | class bmstore(dict): | |
@@ -47,6 +47,14 b' class bmstore(dict):' | |||||
47 | if inst.errno != errno.ENOENT: |
|
47 | if inst.errno != errno.ENOENT: | |
48 | raise |
|
48 | raise | |
49 |
|
49 | |||
|
50 | def recordchange(self, tr): | |||
|
51 | """record that bookmarks have been changed in a transaction | |||
|
52 | ||||
|
53 | The transaction is then responsible for updating the file content.""" | |||
|
54 | tr.addfilegenerator('bookmarks', ('bookmarks',), self._write, | |||
|
55 | vfs=self._repo.vfs) | |||
|
56 | tr.hookargs['bookmark_moved'] = '1' | |||
|
57 | ||||
50 | def write(self): |
|
58 | def write(self): | |
51 | '''Write bookmarks |
|
59 | '''Write bookmarks | |
52 |
|
60 | |||
@@ -64,8 +72,7 b' class bmstore(dict):' | |||||
64 | try: |
|
72 | try: | |
65 |
|
73 | |||
66 | file = repo.vfs('bookmarks', 'w', atomictemp=True) |
|
74 | file = repo.vfs('bookmarks', 'w', atomictemp=True) | |
67 |
|
|
75 | self._write(file) | |
68 | file.write("%s %s\n" % (hex(node), encoding.fromlocal(name))) |
|
|||
69 | file.close() |
|
76 | file.close() | |
70 |
|
77 | |||
71 | # touch 00changelog.i so hgweb reloads bookmarks (no lock needed) |
|
78 | # touch 00changelog.i so hgweb reloads bookmarks (no lock needed) | |
@@ -77,6 +84,10 b' class bmstore(dict):' | |||||
77 | finally: |
|
84 | finally: | |
78 | wlock.release() |
|
85 | wlock.release() | |
79 |
|
86 | |||
|
87 | def _write(self, fp): | |||
|
88 | for name, node in self.iteritems(): | |||
|
89 | fp.write("%s %s\n" % (hex(node), encoding.fromlocal(name))) | |||
|
90 | ||||
80 | def readcurrent(repo): |
|
91 | def readcurrent(repo): | |
81 | '''Get the current bookmark |
|
92 | '''Get the current bookmark | |
82 |
|
93 | |||
@@ -225,10 +236,14 b' def listbookmarks(repo):' | |||||
225 | return d |
|
236 | return d | |
226 |
|
237 | |||
227 | def pushbookmark(repo, key, old, new): |
|
238 | def pushbookmark(repo, key, old, new): | |
228 | w = repo.wlock() |
|
239 | w = l = tr = None | |
229 | try: |
|
240 | try: | |
|
241 | w = repo.wlock() | |||
|
242 | l = repo.lock() | |||
|
243 | tr = repo.transaction('bookmarks') | |||
230 | marks = repo._bookmarks |
|
244 | marks = repo._bookmarks | |
231 |
|
|
245 | existing = hex(marks.get(key, '')) | |
|
246 | if existing != old and existing != new: | |||
232 | return False |
|
247 | return False | |
233 | if new == '': |
|
248 | if new == '': | |
234 | del marks[key] |
|
249 | del marks[key] | |
@@ -236,10 +251,11 b' def pushbookmark(repo, key, old, new):' | |||||
236 | if new not in repo: |
|
251 | if new not in repo: | |
237 | return False |
|
252 | return False | |
238 | marks[key] = repo[new].node() |
|
253 | marks[key] = repo[new].node() | |
239 |
marks. |
|
254 | marks.recordchange(tr) | |
|
255 | tr.close() | |||
240 | return True |
|
256 | return True | |
241 | finally: |
|
257 | finally: | |
242 |
|
|
258 | lockmod.release(tr, l, w) | |
243 |
|
259 | |||
244 | def compare(repo, srcmarks, dstmarks, |
|
260 | def compare(repo, srcmarks, dstmarks, | |
245 | srchex=None, dsthex=None, targets=None): |
|
261 | srchex=None, dsthex=None, targets=None): | |
@@ -337,64 +353,60 b' def _diverge(ui, b, path, localmarks):' | |||||
337 | break |
|
353 | break | |
338 | # try to use an @pathalias suffix |
|
354 | # try to use an @pathalias suffix | |
339 | # if an @pathalias already exists, we overwrite (update) it |
|
355 | # if an @pathalias already exists, we overwrite (update) it | |
|
356 | if path.startswith("file:"): | |||
|
357 | path = util.url(path).path | |||
340 | for p, u in ui.configitems("paths"): |
|
358 | for p, u in ui.configitems("paths"): | |
|
359 | if u.startswith("file:"): | |||
|
360 | u = util.url(u).path | |||
341 | if path == u: |
|
361 | if path == u: | |
342 | n = '%s@%s' % (b, p) |
|
362 | n = '%s@%s' % (b, p) | |
343 | return n |
|
363 | return n | |
344 |
|
364 | |||
345 | def updatefromremote(ui, repo, remotemarks, path): |
|
365 | def updatefromremote(ui, repo, remotemarks, path, trfunc, explicit=()): | |
346 | ui.debug("checking for updated bookmarks\n") |
|
366 | ui.debug("checking for updated bookmarks\n") | |
347 | localmarks = repo._bookmarks |
|
367 | localmarks = repo._bookmarks | |
348 | (addsrc, adddst, advsrc, advdst, diverge, differ, invalid |
|
368 | (addsrc, adddst, advsrc, advdst, diverge, differ, invalid | |
349 | ) = compare(repo, remotemarks, localmarks, dsthex=hex) |
|
369 | ) = compare(repo, remotemarks, localmarks, dsthex=hex) | |
350 |
|
370 | |||
|
371 | status = ui.status | |||
|
372 | warn = ui.warn | |||
|
373 | if ui.configbool('ui', 'quietbookmarkmove', False): | |||
|
374 | status = warn = ui.debug | |||
|
375 | ||||
|
376 | explicit = set(explicit) | |||
351 | changed = [] |
|
377 | changed = [] | |
352 | for b, scid, dcid in addsrc: |
|
378 | for b, scid, dcid in addsrc: | |
353 | if scid in repo: # add remote bookmarks for changes we already have |
|
379 | if scid in repo: # add remote bookmarks for changes we already have | |
354 |
changed.append((b, bin(scid), |
|
380 | changed.append((b, bin(scid), status, | |
355 | _("adding remote bookmark %s\n") % (b))) |
|
381 | _("adding remote bookmark %s\n") % (b))) | |
356 | for b, scid, dcid in advsrc: |
|
382 | for b, scid, dcid in advsrc: | |
357 |
changed.append((b, bin(scid), |
|
383 | changed.append((b, bin(scid), status, | |
358 | _("updating bookmark %s\n") % (b))) |
|
384 | _("updating bookmark %s\n") % (b))) | |
|
385 | # remove normal movement from explicit set | |||
|
386 | explicit.difference_update(d[0] for d in changed) | |||
|
387 | ||||
359 | for b, scid, dcid in diverge: |
|
388 | for b, scid, dcid in diverge: | |
360 | db = _diverge(ui, b, path, localmarks) |
|
389 | if b in explicit: | |
361 | changed.append((db, bin(scid), ui.warn, |
|
390 | explicit.discard(b) | |
362 | _("divergent bookmark %s stored as %s\n") % (b, db))) |
|
391 | changed.append((b, bin(scid), status, | |
|
392 | _("importing bookmark %s\n") % (b, b))) | |||
|
393 | else: | |||
|
394 | db = _diverge(ui, b, path, localmarks) | |||
|
395 | changed.append((db, bin(scid), warn, | |||
|
396 | _("divergent bookmark %s stored as %s\n") | |||
|
397 | % (b, db))) | |||
|
398 | for b, scid, dcid in adddst + advdst: | |||
|
399 | if b in explicit: | |||
|
400 | explicit.discard(b) | |||
|
401 | changed.append((b, bin(scid), status, | |||
|
402 | _("importing bookmark %s\n") % (b, b))) | |||
|
403 | ||||
363 | if changed: |
|
404 | if changed: | |
|
405 | tr = trfunc() | |||
364 | for b, node, writer, msg in sorted(changed): |
|
406 | for b, node, writer, msg in sorted(changed): | |
365 | localmarks[b] = node |
|
407 | localmarks[b] = node | |
366 | writer(msg) |
|
408 | writer(msg) | |
367 |
localmarks. |
|
409 | localmarks.recordchange(tr) | |
368 |
|
||||
369 | def pushtoremote(ui, repo, remote, targets): |
|
|||
370 | (addsrc, adddst, advsrc, advdst, diverge, differ, invalid |
|
|||
371 | ) = compare(repo, repo._bookmarks, remote.listkeys('bookmarks'), |
|
|||
372 | srchex=hex, targets=targets) |
|
|||
373 | if invalid: |
|
|||
374 | b, scid, dcid = invalid[0] |
|
|||
375 | ui.warn(_('bookmark %s does not exist on the local ' |
|
|||
376 | 'or remote repository!\n') % b) |
|
|||
377 | return 2 |
|
|||
378 |
|
||||
379 | def push(b, old, new): |
|
|||
380 | r = remote.pushkey('bookmarks', b, old, new) |
|
|||
381 | if not r: |
|
|||
382 | ui.warn(_('updating bookmark %s failed!\n') % b) |
|
|||
383 | return 1 |
|
|||
384 | return 0 |
|
|||
385 | failed = 0 |
|
|||
386 | for b, scid, dcid in sorted(addsrc + advsrc + advdst + diverge + differ): |
|
|||
387 | ui.status(_("exporting bookmark %s\n") % b) |
|
|||
388 | if dcid is None: |
|
|||
389 | dcid = '' |
|
|||
390 | failed += push(b, dcid, scid) |
|
|||
391 | for b, scid, dcid in adddst: |
|
|||
392 | # treat as "deleted locally" |
|
|||
393 | ui.status(_("deleting remote bookmark %s\n") % b) |
|
|||
394 | failed += push(b, dcid, '') |
|
|||
395 |
|
||||
396 | if failed: |
|
|||
397 | return 1 |
|
|||
398 |
|
410 | |||
399 | def diff(ui, dst, src): |
|
411 | def diff(ui, dst, src): | |
400 | ui.status(_("searching for changed bookmarks\n")) |
|
412 | ui.status(_("searching for changed bookmarks\n")) |
@@ -62,8 +62,6 b' def read(repo):' | |||||
62 | partial = None |
|
62 | partial = None | |
63 | return partial |
|
63 | return partial | |
64 |
|
64 | |||
65 |
|
||||
66 |
|
||||
67 | ### Nearest subset relation |
|
65 | ### Nearest subset relation | |
68 | # Nearest subset of filter X is a filter Y so that: |
|
66 | # Nearest subset of filter X is a filter Y so that: | |
69 | # * Y is included in X, |
|
67 | # * Y is included in X, | |
@@ -241,6 +239,10 b' class branchcache(dict):' | |||||
241 | newbranches.setdefault(branch, []).append(r) |
|
239 | newbranches.setdefault(branch, []).append(r) | |
242 | if closesbranch: |
|
240 | if closesbranch: | |
243 | self._closednodes.add(cl.node(r)) |
|
241 | self._closednodes.add(cl.node(r)) | |
|
242 | ||||
|
243 | # fetch current topological heads to speed up filtering | |||
|
244 | topoheads = set(cl.headrevs()) | |||
|
245 | ||||
244 | # if older branchheads are reachable from new ones, they aren't |
|
246 | # if older branchheads are reachable from new ones, they aren't | |
245 | # really branchheads. Note checking parents is insufficient: |
|
247 | # really branchheads. Note checking parents is insufficient: | |
246 | # 1 (branch a) -> 2 (branch b) -> 3 (branch a) |
|
248 | # 1 (branch a) -> 2 (branch b) -> 3 (branch a) | |
@@ -254,14 +256,13 b' class branchcache(dict):' | |||||
254 | newheadrevs.sort() |
|
256 | newheadrevs.sort() | |
255 | bheadset.update(newheadrevs) |
|
257 | bheadset.update(newheadrevs) | |
256 |
|
258 | |||
257 |
# This |
|
259 | # This prunes out two kinds of heads - heads that are superseded by | |
258 |
# |
|
260 | # a head in newheadrevs, and newheadrevs that are not heads because | |
259 |
# |
|
261 | # an existing head is their descendant. | |
260 | while newheadrevs: |
|
262 | uncertain = bheadset - topoheads | |
261 | latest = newheadrevs.pop() |
|
263 | if uncertain: | |
262 | if latest not in bheadset: |
|
264 | floorrev = min(uncertain) | |
263 | continue |
|
265 | ancestors = set(cl.ancestors(newheadrevs, floorrev)) | |
264 | ancestors = set(cl.ancestors([latest], min(bheadset))) |
|
|||
265 | bheadset -= ancestors |
|
266 | bheadset -= ancestors | |
266 | bheadrevs = sorted(bheadset) |
|
267 | bheadrevs = sorted(bheadset) | |
267 | self[branch] = [cl.node(rev) for rev in bheadrevs] |
|
268 | self[branch] = [cl.node(rev) for rev in bheadrevs] |
@@ -31,7 +31,7 b' stream level parameters' | |||||
31 |
|
31 | |||
32 | Binary format is as follow |
|
32 | Binary format is as follow | |
33 |
|
33 | |||
34 |
:params size: |
|
34 | :params size: int32 | |
35 |
|
35 | |||
36 | The total number of Bytes used by the parameters |
|
36 | The total number of Bytes used by the parameters | |
37 |
|
37 | |||
@@ -64,7 +64,7 b' Payload part' | |||||
64 |
|
64 | |||
65 | Binary format is as follow |
|
65 | Binary format is as follow | |
66 |
|
66 | |||
67 |
:header size: |
|
67 | :header size: int32 | |
68 |
|
68 | |||
69 | The total number of Bytes used by the part headers. When the header is empty |
|
69 | The total number of Bytes used by the part headers. When the header is empty | |
70 | (size = 0) this is interpreted as the end of stream marker. |
|
70 | (size = 0) this is interpreted as the end of stream marker. | |
@@ -119,12 +119,15 b' Binary format is as follow' | |||||
119 |
|
119 | |||
120 | payload is a series of `<chunksize><chunkdata>`. |
|
120 | payload is a series of `<chunksize><chunkdata>`. | |
121 |
|
121 | |||
122 |
`chunksize` is a |
|
122 | `chunksize` is an int32, `chunkdata` are plain bytes (as much as | |
123 | `chunksize` says)` The payload part is concluded by a zero size chunk. |
|
123 | `chunksize` says)` The payload part is concluded by a zero size chunk. | |
124 |
|
124 | |||
125 | The current implementation always produces either zero or one chunk. |
|
125 | The current implementation always produces either zero or one chunk. | |
126 | This is an implementation limitation that will ultimately be lifted. |
|
126 | This is an implementation limitation that will ultimately be lifted. | |
127 |
|
127 | |||
|
128 | `chunksize` can be negative to trigger special case processing. No such | |||
|
129 | processing is in place yet. | |||
|
130 | ||||
128 | Bundle processing |
|
131 | Bundle processing | |
129 | ============================ |
|
132 | ============================ | |
130 |
|
133 | |||
@@ -146,7 +149,9 b' import util' | |||||
146 | import struct |
|
149 | import struct | |
147 | import urllib |
|
150 | import urllib | |
148 | import string |
|
151 | import string | |
|
152 | import obsolete | |||
149 | import pushkey |
|
153 | import pushkey | |
|
154 | import url | |||
150 |
|
155 | |||
151 | import changegroup, error |
|
156 | import changegroup, error | |
152 | from i18n import _ |
|
157 | from i18n import _ | |
@@ -154,13 +159,13 b' from i18n import _' | |||||
154 | _pack = struct.pack |
|
159 | _pack = struct.pack | |
155 | _unpack = struct.unpack |
|
160 | _unpack = struct.unpack | |
156 |
|
161 | |||
157 |
_magicstring = 'HG2 |
|
162 | _magicstring = 'HG2Y' | |
158 |
|
163 | |||
159 |
_fstreamparamsize = '> |
|
164 | _fstreamparamsize = '>i' | |
160 |
_fpartheadersize = '> |
|
165 | _fpartheadersize = '>i' | |
161 | _fparttypesize = '>B' |
|
166 | _fparttypesize = '>B' | |
162 | _fpartid = '>I' |
|
167 | _fpartid = '>I' | |
163 |
_fpayloadsize = '> |
|
168 | _fpayloadsize = '>i' | |
164 | _fpartparamcount = '>BB' |
|
169 | _fpartparamcount = '>BB' | |
165 |
|
170 | |||
166 | preferedchunksize = 4096 |
|
171 | preferedchunksize = 4096 | |
@@ -291,50 +296,8 b' def processbundle(repo, unbundler, trans' | |||||
291 | part = None |
|
296 | part = None | |
292 | try: |
|
297 | try: | |
293 | for part in iterparts: |
|
298 | for part in iterparts: | |
294 |
|
|
299 | _processpart(op, part) | |
295 | # part key are matched lower case |
|
|||
296 | key = parttype.lower() |
|
|||
297 | try: |
|
|||
298 | handler = parthandlermapping.get(key) |
|
|||
299 | if handler is None: |
|
|||
300 | raise error.BundleValueError(parttype=key) |
|
|||
301 | op.ui.debug('found a handler for part %r\n' % parttype) |
|
|||
302 | unknownparams = part.mandatorykeys - handler.params |
|
|||
303 | if unknownparams: |
|
|||
304 | unknownparams = list(unknownparams) |
|
|||
305 | unknownparams.sort() |
|
|||
306 | raise error.BundleValueError(parttype=key, |
|
|||
307 | params=unknownparams) |
|
|||
308 | except error.BundleValueError, exc: |
|
|||
309 | if key != parttype: # mandatory parts |
|
|||
310 | raise |
|
|||
311 | op.ui.debug('ignoring unsupported advisory part %s\n' % exc) |
|
|||
312 | # consuming the part |
|
|||
313 | part.read() |
|
|||
314 | continue |
|
|||
315 |
|
||||
316 |
|
||||
317 | # handler is called outside the above try block so that we don't |
|
|||
318 | # risk catching KeyErrors from anything other than the |
|
|||
319 | # parthandlermapping lookup (any KeyError raised by handler() |
|
|||
320 | # itself represents a defect of a different variety). |
|
|||
321 | output = None |
|
|||
322 | if op.reply is not None: |
|
|||
323 | op.ui.pushbuffer(error=True) |
|
|||
324 | output = '' |
|
|||
325 | try: |
|
|||
326 | handler(op, part) |
|
|||
327 | finally: |
|
|||
328 | if output is not None: |
|
|||
329 | output = op.ui.popbuffer() |
|
|||
330 | if output: |
|
|||
331 | outpart = op.reply.newpart('b2x:output', data=output) |
|
|||
332 | outpart.addparam('in-reply-to', str(part.id), mandatory=False) |
|
|||
333 | part.read() |
|
|||
334 | except Exception, exc: |
|
300 | except Exception, exc: | |
335 | if part is not None: |
|
|||
336 | # consume the bundle content |
|
|||
337 | part.read() |
|
|||
338 | for part in iterparts: |
|
301 | for part in iterparts: | |
339 | # consume the bundle content |
|
302 | # consume the bundle content | |
340 | part.read() |
|
303 | part.read() | |
@@ -347,6 +310,53 b' def processbundle(repo, unbundler, trans' | |||||
347 | raise |
|
310 | raise | |
348 | return op |
|
311 | return op | |
349 |
|
312 | |||
|
313 | def _processpart(op, part): | |||
|
314 | """process a single part from a bundle | |||
|
315 | ||||
|
316 | The part is guaranteed to have been fully consumed when the function exits | |||
|
317 | (even if an exception is raised).""" | |||
|
318 | try: | |||
|
319 | parttype = part.type | |||
|
320 | # part key are matched lower case | |||
|
321 | key = parttype.lower() | |||
|
322 | try: | |||
|
323 | handler = parthandlermapping.get(key) | |||
|
324 | if handler is None: | |||
|
325 | raise error.UnsupportedPartError(parttype=key) | |||
|
326 | op.ui.debug('found a handler for part %r\n' % parttype) | |||
|
327 | unknownparams = part.mandatorykeys - handler.params | |||
|
328 | if unknownparams: | |||
|
329 | unknownparams = list(unknownparams) | |||
|
330 | unknownparams.sort() | |||
|
331 | raise error.UnsupportedPartError(parttype=key, | |||
|
332 | params=unknownparams) | |||
|
333 | except error.UnsupportedPartError, exc: | |||
|
334 | if key != parttype: # mandatory parts | |||
|
335 | raise | |||
|
336 | op.ui.debug('ignoring unsupported advisory part %s\n' % exc) | |||
|
337 | return # skip to part processing | |||
|
338 | ||||
|
339 | # handler is called outside the above try block so that we don't | |||
|
340 | # risk catching KeyErrors from anything other than the | |||
|
341 | # parthandlermapping lookup (any KeyError raised by handler() | |||
|
342 | # itself represents a defect of a different variety). | |||
|
343 | output = None | |||
|
344 | if op.reply is not None: | |||
|
345 | op.ui.pushbuffer(error=True) | |||
|
346 | output = '' | |||
|
347 | try: | |||
|
348 | handler(op, part) | |||
|
349 | finally: | |||
|
350 | if output is not None: | |||
|
351 | output = op.ui.popbuffer() | |||
|
352 | if output: | |||
|
353 | outpart = op.reply.newpart('b2x:output', data=output) | |||
|
354 | outpart.addparam('in-reply-to', str(part.id), mandatory=False) | |||
|
355 | finally: | |||
|
356 | # consume the part content to not corrupt the stream. | |||
|
357 | part.read() | |||
|
358 | ||||
|
359 | ||||
350 | def decodecaps(blob): |
|
360 | def decodecaps(blob): | |
351 | """decode a bundle2 caps bytes blob into a dictionnary |
|
361 | """decode a bundle2 caps bytes blob into a dictionnary | |
352 |
|
362 | |||
@@ -446,7 +456,7 b' class bundle20(object):' | |||||
446 | for chunk in part.getchunks(): |
|
456 | for chunk in part.getchunks(): | |
447 | yield chunk |
|
457 | yield chunk | |
448 | self.ui.debug('end of bundle\n') |
|
458 | self.ui.debug('end of bundle\n') | |
449 | yield '\0\0' |
|
459 | yield _pack(_fpartheadersize, 0) | |
450 |
|
460 | |||
451 | def _paramchunk(self): |
|
461 | def _paramchunk(self): | |
452 | """return a encoded version of all stream parameters""" |
|
462 | """return a encoded version of all stream parameters""" | |
@@ -490,7 +500,7 b' class unbundle20(unpackermixin):' | |||||
490 | magic, version = header[0:2], header[2:4] |
|
500 | magic, version = header[0:2], header[2:4] | |
491 | if magic != 'HG': |
|
501 | if magic != 'HG': | |
492 | raise util.Abort(_('not a Mercurial bundle')) |
|
502 | raise util.Abort(_('not a Mercurial bundle')) | |
493 |
if version != '2 |
|
503 | if version != '2Y': | |
494 | raise util.Abort(_('unknown bundle version %s') % version) |
|
504 | raise util.Abort(_('unknown bundle version %s') % version) | |
495 | self.ui.debug('start processing of %s stream\n' % header) |
|
505 | self.ui.debug('start processing of %s stream\n' % header) | |
496 |
|
506 | |||
@@ -500,6 +510,9 b' class unbundle20(unpackermixin):' | |||||
500 | self.ui.debug('reading bundle2 stream parameters\n') |
|
510 | self.ui.debug('reading bundle2 stream parameters\n') | |
501 | params = {} |
|
511 | params = {} | |
502 | paramssize = self._unpack(_fstreamparamsize)[0] |
|
512 | paramssize = self._unpack(_fstreamparamsize)[0] | |
|
513 | if paramssize < 0: | |||
|
514 | raise error.BundleValueError('negative bundle param size: %i' | |||
|
515 | % paramssize) | |||
503 | if paramssize: |
|
516 | if paramssize: | |
504 | for p in self._readexact(paramssize).split(' '): |
|
517 | for p in self._readexact(paramssize).split(' '): | |
505 | p = p.split('=', 1) |
|
518 | p = p.split('=', 1) | |
@@ -529,7 +542,7 b' class unbundle20(unpackermixin):' | |||||
529 | if name[0].islower(): |
|
542 | if name[0].islower(): | |
530 | self.ui.debug("ignoring unknown parameter %r\n" % name) |
|
543 | self.ui.debug("ignoring unknown parameter %r\n" % name) | |
531 | else: |
|
544 | else: | |
532 |
raise error. |
|
545 | raise error.UnsupportedPartError(params=(name,)) | |
533 |
|
546 | |||
534 |
|
547 | |||
535 | def iterparts(self): |
|
548 | def iterparts(self): | |
@@ -549,6 +562,9 b' class unbundle20(unpackermixin):' | |||||
549 |
|
562 | |||
550 | returns None if empty""" |
|
563 | returns None if empty""" | |
551 | headersize = self._unpack(_fpartheadersize)[0] |
|
564 | headersize = self._unpack(_fpartheadersize)[0] | |
|
565 | if headersize < 0: | |||
|
566 | raise error.BundleValueError('negative part header size: %i' | |||
|
567 | % headersize) | |||
552 | self.ui.debug('part header size: %i\n' % headersize) |
|
568 | self.ui.debug('part header size: %i\n' % headersize) | |
553 | if headersize: |
|
569 | if headersize: | |
554 | return self._readexact(headersize) |
|
570 | return self._readexact(headersize) | |
@@ -756,6 +772,9 b' class unbundlepart(unpackermixin):' | |||||
756 | payloadsize = self._unpack(_fpayloadsize)[0] |
|
772 | payloadsize = self._unpack(_fpayloadsize)[0] | |
757 | self.ui.debug('payload chunk size: %i\n' % payloadsize) |
|
773 | self.ui.debug('payload chunk size: %i\n' % payloadsize) | |
758 | while payloadsize: |
|
774 | while payloadsize: | |
|
775 | if payloadsize < 0: | |||
|
776 | msg = 'negative payload chunk size: %i' % payloadsize | |||
|
777 | raise error.BundleValueError(msg) | |||
759 | yield self._readexact(payloadsize) |
|
778 | yield self._readexact(payloadsize) | |
760 | payloadsize = self._unpack(_fpayloadsize)[0] |
|
779 | payloadsize = self._unpack(_fpayloadsize)[0] | |
761 | self.ui.debug('payload chunk size: %i\n' % payloadsize) |
|
780 | self.ui.debug('payload chunk size: %i\n' % payloadsize) | |
@@ -775,6 +794,25 b' class unbundlepart(unpackermixin):' | |||||
775 | self.consumed = True |
|
794 | self.consumed = True | |
776 | return data |
|
795 | return data | |
777 |
|
796 | |||
|
797 | capabilities = {'HG2Y': (), | |||
|
798 | 'b2x:listkeys': (), | |||
|
799 | 'b2x:pushkey': (), | |||
|
800 | 'b2x:changegroup': (), | |||
|
801 | 'digests': tuple(sorted(util.DIGESTS.keys())), | |||
|
802 | 'b2x:remote-changegroup': ('http', 'https'), | |||
|
803 | } | |||
|
804 | ||||
|
805 | def getrepocaps(repo): | |||
|
806 | """return the bundle2 capabilities for a given repo | |||
|
807 | ||||
|
808 | Exists to allow extensions (like evolution) to mutate the capabilities. | |||
|
809 | """ | |||
|
810 | caps = capabilities.copy() | |||
|
811 | if obsolete.isenabled(repo, obsolete.exchangeopt): | |||
|
812 | supportedformat = tuple('V%i' % v for v in obsolete.formats) | |||
|
813 | caps['b2x:obsmarkers'] = supportedformat | |||
|
814 | return caps | |||
|
815 | ||||
778 | def bundle2caps(remote): |
|
816 | def bundle2caps(remote): | |
779 | """return the bundlecapabilities of a peer as dict""" |
|
817 | """return the bundlecapabilities of a peer as dict""" | |
780 | raw = remote.capable('bundle2-exp') |
|
818 | raw = remote.capable('bundle2-exp') | |
@@ -783,6 +821,12 b' def bundle2caps(remote):' | |||||
783 | capsblob = urllib.unquote(remote.capable('bundle2-exp')) |
|
821 | capsblob = urllib.unquote(remote.capable('bundle2-exp')) | |
784 | return decodecaps(capsblob) |
|
822 | return decodecaps(capsblob) | |
785 |
|
823 | |||
|
824 | def obsmarkersversion(caps): | |||
|
825 | """extract the list of supported obsmarkers versions from a bundle2caps dict | |||
|
826 | """ | |||
|
827 | obscaps = caps.get('b2x:obsmarkers', ()) | |||
|
828 | return [int(c[1:]) for c in obscaps if c.startswith('V')] | |||
|
829 | ||||
786 | @parthandler('b2x:changegroup') |
|
830 | @parthandler('b2x:changegroup') | |
787 | def handlechangegroup(op, inpart): |
|
831 | def handlechangegroup(op, inpart): | |
788 | """apply a changegroup part on the repo |
|
832 | """apply a changegroup part on the repo | |
@@ -796,7 +840,9 b' def handlechangegroup(op, inpart):' | |||||
796 | # we need to make sure we trigger the creation of a transaction object used |
|
840 | # we need to make sure we trigger the creation of a transaction object used | |
797 | # for the whole processing scope. |
|
841 | # for the whole processing scope. | |
798 | op.gettransaction() |
|
842 | op.gettransaction() | |
799 |
cg = changegroup. |
|
843 | cg = changegroup.cg1unpacker(inpart, 'UN') | |
|
844 | # the source and url passed here are overwritten by the one contained in | |||
|
845 | # the transaction.hookargs argument. So 'bundle2' is a placeholder | |||
800 | ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2') |
|
846 | ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2') | |
801 | op.records.add('changegroup', {'return': ret}) |
|
847 | op.records.add('changegroup', {'return': ret}) | |
802 | if op.reply is not None: |
|
848 | if op.reply is not None: | |
@@ -807,14 +853,88 b' def handlechangegroup(op, inpart):' | |||||
807 | part.addparam('return', '%i' % ret, mandatory=False) |
|
853 | part.addparam('return', '%i' % ret, mandatory=False) | |
808 | assert not inpart.read() |
|
854 | assert not inpart.read() | |
809 |
|
855 | |||
|
856 | _remotechangegroupparams = tuple(['url', 'size', 'digests'] + | |||
|
857 | ['digest:%s' % k for k in util.DIGESTS.keys()]) | |||
|
858 | @parthandler('b2x:remote-changegroup', _remotechangegroupparams) | |||
|
859 | def handleremotechangegroup(op, inpart): | |||
|
860 | """apply a bundle10 on the repo, given an url and validation information | |||
|
861 | ||||
|
862 | All the information about the remote bundle to import are given as | |||
|
863 | parameters. The parameters include: | |||
|
864 | - url: the url to the bundle10. | |||
|
865 | - size: the bundle10 file size. It is used to validate what was | |||
|
866 | retrieved by the client matches the server knowledge about the bundle. | |||
|
867 | - digests: a space separated list of the digest types provided as | |||
|
868 | parameters. | |||
|
869 | - digest:<digest-type>: the hexadecimal representation of the digest with | |||
|
870 | that name. Like the size, it is used to validate what was retrieved by | |||
|
871 | the client matches what the server knows about the bundle. | |||
|
872 | ||||
|
873 | When multiple digest types are given, all of them are checked. | |||
|
874 | """ | |||
|
875 | try: | |||
|
876 | raw_url = inpart.params['url'] | |||
|
877 | except KeyError: | |||
|
878 | raise util.Abort(_('remote-changegroup: missing "%s" param') % 'url') | |||
|
879 | parsed_url = util.url(raw_url) | |||
|
880 | if parsed_url.scheme not in capabilities['b2x:remote-changegroup']: | |||
|
881 | raise util.Abort(_('remote-changegroup does not support %s urls') % | |||
|
882 | parsed_url.scheme) | |||
|
883 | ||||
|
884 | try: | |||
|
885 | size = int(inpart.params['size']) | |||
|
886 | except ValueError: | |||
|
887 | raise util.Abort(_('remote-changegroup: invalid value for param "%s"') | |||
|
888 | % 'size') | |||
|
889 | except KeyError: | |||
|
890 | raise util.Abort(_('remote-changegroup: missing "%s" param') % 'size') | |||
|
891 | ||||
|
892 | digests = {} | |||
|
893 | for typ in inpart.params.get('digests', '').split(): | |||
|
894 | param = 'digest:%s' % typ | |||
|
895 | try: | |||
|
896 | value = inpart.params[param] | |||
|
897 | except KeyError: | |||
|
898 | raise util.Abort(_('remote-changegroup: missing "%s" param') % | |||
|
899 | param) | |||
|
900 | digests[typ] = value | |||
|
901 | ||||
|
902 | real_part = util.digestchecker(url.open(op.ui, raw_url), size, digests) | |||
|
903 | ||||
|
904 | # Make sure we trigger a transaction creation | |||
|
905 | # | |||
|
906 | # The addchangegroup function will get a transaction object by itself, but | |||
|
907 | # we need to make sure we trigger the creation of a transaction object used | |||
|
908 | # for the whole processing scope. | |||
|
909 | op.gettransaction() | |||
|
910 | import exchange | |||
|
911 | cg = exchange.readbundle(op.repo.ui, real_part, raw_url) | |||
|
912 | if not isinstance(cg, changegroup.cg1unpacker): | |||
|
913 | raise util.Abort(_('%s: not a bundle version 1.0') % | |||
|
914 | util.hidepassword(raw_url)) | |||
|
915 | ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2') | |||
|
916 | op.records.add('changegroup', {'return': ret}) | |||
|
917 | if op.reply is not None: | |||
|
918 | # This is definitly not the final form of this | |||
|
919 | # return. But one need to start somewhere. | |||
|
920 | part = op.reply.newpart('b2x:reply:changegroup') | |||
|
921 | part.addparam('in-reply-to', str(inpart.id), mandatory=False) | |||
|
922 | part.addparam('return', '%i' % ret, mandatory=False) | |||
|
923 | try: | |||
|
924 | real_part.validate() | |||
|
925 | except util.Abort, e: | |||
|
926 | raise util.Abort(_('bundle at %s is corrupted:\n%s') % | |||
|
927 | (util.hidepassword(raw_url), str(e))) | |||
|
928 | assert not inpart.read() | |||
|
929 | ||||
810 | @parthandler('b2x:reply:changegroup', ('return', 'in-reply-to')) |
|
930 | @parthandler('b2x:reply:changegroup', ('return', 'in-reply-to')) | |
811 | def handlechangegroup(op, inpart): |
|
931 | def handlereplychangegroup(op, inpart): | |
812 | ret = int(inpart.params['return']) |
|
932 | ret = int(inpart.params['return']) | |
813 | replyto = int(inpart.params['in-reply-to']) |
|
933 | replyto = int(inpart.params['in-reply-to']) | |
814 | op.records.add('changegroup', {'return': ret}, replyto) |
|
934 | op.records.add('changegroup', {'return': ret}, replyto) | |
815 |
|
935 | |||
816 | @parthandler('b2x:check:heads') |
|
936 | @parthandler('b2x:check:heads') | |
817 |
def handlech |
|
937 | def handlecheckheads(op, inpart): | |
818 | """check that head of the repo did not change |
|
938 | """check that head of the repo did not change | |
819 |
|
939 | |||
820 | This is used to detect a push race when using unbundle. |
|
940 | This is used to detect a push race when using unbundle. | |
@@ -860,7 +980,7 b' def handlereplycaps(op, inpart):' | |||||
860 | if params is not None: |
|
980 | if params is not None: | |
861 | kwargs['params'] = params.split('\0') |
|
981 | kwargs['params'] = params.split('\0') | |
862 |
|
982 | |||
863 |
raise error. |
|
983 | raise error.UnsupportedPartError(**kwargs) | |
864 |
|
984 | |||
865 | @parthandler('b2x:error:pushraced', ('message',)) |
|
985 | @parthandler('b2x:error:pushraced', ('message',)) | |
866 | def handlereplycaps(op, inpart): |
|
986 | def handlereplycaps(op, inpart): | |
@@ -899,3 +1019,24 b' def handlepushkeyreply(op, inpart):' | |||||
899 | ret = int(inpart.params['return']) |
|
1019 | ret = int(inpart.params['return']) | |
900 | partid = int(inpart.params['in-reply-to']) |
|
1020 | partid = int(inpart.params['in-reply-to']) | |
901 | op.records.add('pushkey', {'return': ret}, partid) |
|
1021 | op.records.add('pushkey', {'return': ret}, partid) | |
|
1022 | ||||
|
1023 | @parthandler('b2x:obsmarkers') | |||
|
1024 | def handleobsmarker(op, inpart): | |||
|
1025 | """add a stream of obsmarkers to the repo""" | |||
|
1026 | tr = op.gettransaction() | |||
|
1027 | new = op.repo.obsstore.mergemarkers(tr, inpart.read()) | |||
|
1028 | if new: | |||
|
1029 | op.repo.ui.status(_('%i new obsolescence markers\n') % new) | |||
|
1030 | op.records.add('obsmarkers', {'new': new}) | |||
|
1031 | if op.reply is not None: | |||
|
1032 | rpart = op.reply.newpart('b2x:reply:obsmarkers') | |||
|
1033 | rpart.addparam('in-reply-to', str(inpart.id), mandatory=False) | |||
|
1034 | rpart.addparam('new', '%i' % new, mandatory=False) | |||
|
1035 | ||||
|
1036 | ||||
|
1037 | @parthandler('b2x:reply:obsmarkers', ('new', 'in-reply-to')) | |||
|
1038 | def handlepushkeyreply(op, inpart): | |||
|
1039 | """retrieve the result of a pushkey request""" | |||
|
1040 | ret = int(inpart.params['new']) | |||
|
1041 | partid = int(inpart.params['in-reply-to']) | |||
|
1042 | op.records.add('obsmarkers', {'new': ret}, partid) |
@@ -12,7 +12,7 b' import mdiff, util, dagutil' | |||||
12 | import struct, os, bz2, zlib, tempfile |
|
12 | import struct, os, bz2, zlib, tempfile | |
13 | import discovery, error, phases, branchmap |
|
13 | import discovery, error, phases, branchmap | |
14 |
|
14 | |||
15 |
_ |
|
15 | _CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s" | |
16 |
|
16 | |||
17 | def readexactly(stream, n): |
|
17 | def readexactly(stream, n): | |
18 | '''read n bytes from stream.read and abort if less was available''' |
|
18 | '''read n bytes from stream.read and abort if less was available''' | |
@@ -123,8 +123,8 b' def decompressor(fh, alg):' | |||||
123 | raise util.Abort("unknown bundle compression '%s'" % alg) |
|
123 | raise util.Abort("unknown bundle compression '%s'" % alg) | |
124 | return util.chunkbuffer(generator(fh)) |
|
124 | return util.chunkbuffer(generator(fh)) | |
125 |
|
125 | |||
126 |
class |
|
126 | class cg1unpacker(object): | |
127 |
deltaheader = _ |
|
127 | deltaheader = _CHANGEGROUPV1_DELTA_HEADER | |
128 | deltaheadersize = struct.calcsize(deltaheader) |
|
128 | deltaheadersize = struct.calcsize(deltaheader) | |
129 | def __init__(self, fh, alg): |
|
129 | def __init__(self, fh, alg): | |
130 | self._stream = decompressor(fh, alg) |
|
130 | self._stream = decompressor(fh, alg) | |
@@ -227,8 +227,8 b' class headerlessfixup(object):' | |||||
227 | return d |
|
227 | return d | |
228 | return readexactly(self._fh, n) |
|
228 | return readexactly(self._fh, n) | |
229 |
|
229 | |||
230 |
class |
|
230 | class cg1packer(object): | |
231 |
deltaheader = _ |
|
231 | deltaheader = _CHANGEGROUPV1_DELTA_HEADER | |
232 | def __init__(self, repo, bundlecaps=None): |
|
232 | def __init__(self, repo, bundlecaps=None): | |
233 | """Given a source repo, construct a bundler. |
|
233 | """Given a source repo, construct a bundler. | |
234 |
|
234 | |||
@@ -456,7 +456,7 b' def getsubset(repo, outgoing, bundler, s' | |||||
456 | repo.hook('preoutgoing', throw=True, source=source) |
|
456 | repo.hook('preoutgoing', throw=True, source=source) | |
457 | _changegroupinfo(repo, csets, source) |
|
457 | _changegroupinfo(repo, csets, source) | |
458 | gengroup = bundler.generate(commonrevs, csets, fastpathlinkrev, source) |
|
458 | gengroup = bundler.generate(commonrevs, csets, fastpathlinkrev, source) | |
459 |
return |
|
459 | return cg1unpacker(util.chunkbuffer(gengroup), 'UN') | |
460 |
|
460 | |||
461 | def changegroupsubset(repo, roots, heads, source): |
|
461 | def changegroupsubset(repo, roots, heads, source): | |
462 | """Compute a changegroup consisting of all the nodes that are |
|
462 | """Compute a changegroup consisting of all the nodes that are | |
@@ -480,17 +480,17 b' def changegroupsubset(repo, roots, heads' | |||||
480 | for n in roots: |
|
480 | for n in roots: | |
481 | discbases.extend([p for p in cl.parents(n) if p != nullid]) |
|
481 | discbases.extend([p for p in cl.parents(n) if p != nullid]) | |
482 | outgoing = discovery.outgoing(cl, discbases, heads) |
|
482 | outgoing = discovery.outgoing(cl, discbases, heads) | |
483 |
bundler = |
|
483 | bundler = cg1packer(repo) | |
484 | return getsubset(repo, outgoing, bundler, source) |
|
484 | return getsubset(repo, outgoing, bundler, source) | |
485 |
|
485 | |||
486 |
def getlocal |
|
486 | def getlocalchangegroup(repo, source, outgoing, bundlecaps=None): | |
487 | """Like getbundle, but taking a discovery.outgoing as an argument. |
|
487 | """Like getbundle, but taking a discovery.outgoing as an argument. | |
488 |
|
488 | |||
489 | This is only implemented for local repos and reuses potentially |
|
489 | This is only implemented for local repos and reuses potentially | |
490 | precomputed sets in outgoing.""" |
|
490 | precomputed sets in outgoing.""" | |
491 | if not outgoing.missing: |
|
491 | if not outgoing.missing: | |
492 | return None |
|
492 | return None | |
493 |
bundler = |
|
493 | bundler = cg1packer(repo, bundlecaps) | |
494 | return getsubset(repo, outgoing, bundler, source) |
|
494 | return getsubset(repo, outgoing, bundler, source) | |
495 |
|
495 | |||
496 | def _computeoutgoing(repo, heads, common): |
|
496 | def _computeoutgoing(repo, heads, common): | |
@@ -512,7 +512,7 b' def _computeoutgoing(repo, heads, common' | |||||
512 | heads = cl.heads() |
|
512 | heads = cl.heads() | |
513 | return discovery.outgoing(cl, common, heads) |
|
513 | return discovery.outgoing(cl, common, heads) | |
514 |
|
514 | |||
515 |
def get |
|
515 | def getchangegroup(repo, source, heads=None, common=None, bundlecaps=None): | |
516 | """Like changegroupsubset, but returns the set difference between the |
|
516 | """Like changegroupsubset, but returns the set difference between the | |
517 | ancestors of heads and the ancestors common. |
|
517 | ancestors of heads and the ancestors common. | |
518 |
|
518 | |||
@@ -522,7 +522,7 b' def getbundle(repo, source, heads=None, ' | |||||
522 | current discovery protocol works. |
|
522 | current discovery protocol works. | |
523 | """ |
|
523 | """ | |
524 | outgoing = _computeoutgoing(repo, heads, common) |
|
524 | outgoing = _computeoutgoing(repo, heads, common) | |
525 |
return getlocal |
|
525 | return getlocalchangegroup(repo, source, outgoing, bundlecaps=bundlecaps) | |
526 |
|
526 | |||
527 | def changegroup(repo, basenodes, source): |
|
527 | def changegroup(repo, basenodes, source): | |
528 | # to avoid a race we use changegroupsubset() (issue1320) |
|
528 | # to avoid a race we use changegroupsubset() (issue1320) | |
@@ -569,7 +569,8 b' def addchangegroupfiles(repo, source, re' | |||||
569 |
|
569 | |||
570 | return revisions, files |
|
570 | return revisions, files | |
571 |
|
571 | |||
572 |
def addchangegroup(repo, source, srctype, url, emptyok=False |
|
572 | def addchangegroup(repo, source, srctype, url, emptyok=False, | |
|
573 | targetphase=phases.draft): | |||
573 | """Add the changegroup returned by source.read() to this repo. |
|
574 | """Add the changegroup returned by source.read() to this repo. | |
574 | srctype is a string like 'push', 'pull', or 'unbundle'. url is |
|
575 | srctype is a string like 'push', 'pull', or 'unbundle'. url is | |
575 | the URL of the repo where this changegroup is coming from. |
|
576 | the URL of the repo where this changegroup is coming from. | |
@@ -591,8 +592,6 b' def addchangegroup(repo, source, srctype' | |||||
591 | if not source: |
|
592 | if not source: | |
592 | return 0 |
|
593 | return 0 | |
593 |
|
594 | |||
594 | repo.hook('prechangegroup', throw=True, source=srctype, url=url) |
|
|||
595 |
|
||||
596 | changesets = files = revisions = 0 |
|
595 | changesets = files = revisions = 0 | |
597 | efiles = set() |
|
596 | efiles = set() | |
598 |
|
597 | |||
@@ -603,7 +602,15 b' def addchangegroup(repo, source, srctype' | |||||
603 | oldheads = cl.heads() |
|
602 | oldheads = cl.heads() | |
604 |
|
603 | |||
605 | tr = repo.transaction("\n".join([srctype, util.hidepassword(url)])) |
|
604 | tr = repo.transaction("\n".join([srctype, util.hidepassword(url)])) | |
|
605 | # The transaction could have been created before and already carries source | |||
|
606 | # information. In this case we use the top level data. We overwrite the | |||
|
607 | # argument because we need to use the top level value (if they exist) in | |||
|
608 | # this function. | |||
|
609 | srctype = tr.hookargs.setdefault('source', srctype) | |||
|
610 | url = tr.hookargs.setdefault('url', url) | |||
606 | try: |
|
611 | try: | |
|
612 | repo.hook('prechangegroup', throw=True, **tr.hookargs) | |||
|
613 | ||||
607 | trp = weakref.proxy(tr) |
|
614 | trp = weakref.proxy(tr) | |
608 | # pull off the changeset group |
|
615 | # pull off the changeset group | |
609 | repo.ui.status(_("adding changesets\n")) |
|
616 | repo.ui.status(_("adding changesets\n")) | |
@@ -686,8 +693,11 b' def addchangegroup(repo, source, srctype' | |||||
686 | p = lambda: cl.writepending() and repo.root or "" |
|
693 | p = lambda: cl.writepending() and repo.root or "" | |
687 | if 'node' not in tr.hookargs: |
|
694 | if 'node' not in tr.hookargs: | |
688 | tr.hookargs['node'] = hex(cl.node(clstart)) |
|
695 | tr.hookargs['node'] = hex(cl.node(clstart)) | |
689 | repo.hook('pretxnchangegroup', throw=True, source=srctype, |
|
696 | hookargs = dict(tr.hookargs) | |
690 | url=url, pending=p, **tr.hookargs) |
|
697 | else: | |
|
698 | hookargs = dict(tr.hookargs) | |||
|
699 | hookargs['node'] = hex(cl.node(clstart)) | |||
|
700 | repo.hook('pretxnchangegroup', throw=True, pending=p, **hookargs) | |||
691 |
|
701 | |||
692 | added = [cl.node(r) for r in xrange(clstart, clend)] |
|
702 | added = [cl.node(r) for r in xrange(clstart, clend)] | |
693 | publishing = repo.ui.configbool('phases', 'publish', True) |
|
703 | publishing = repo.ui.configbool('phases', 'publish', True) | |
@@ -699,15 +709,18 b' def addchangegroup(repo, source, srctype' | |||||
699 | # We should not use added here but the list of all change in |
|
709 | # We should not use added here but the list of all change in | |
700 | # the bundle |
|
710 | # the bundle | |
701 | if publishing: |
|
711 | if publishing: | |
702 | phases.advanceboundary(repo, phases.public, srccontent) |
|
712 | phases.advanceboundary(repo, tr, phases.public, srccontent) | |
703 | else: |
|
713 | else: | |
704 | phases.advanceboundary(repo, phases.draft, srccontent) |
|
714 | # Those changesets have been pushed from the outside, their | |
705 | phases.retractboundary(repo, phases.draft, added) |
|
715 | # phases are going to be pushed alongside. Therefor | |
|
716 | # `targetphase` is ignored. | |||
|
717 | phases.advanceboundary(repo, tr, phases.draft, srccontent) | |||
|
718 | phases.retractboundary(repo, tr, phases.draft, added) | |||
706 | elif srctype != 'strip': |
|
719 | elif srctype != 'strip': | |
707 | # publishing only alter behavior during push |
|
720 | # publishing only alter behavior during push | |
708 | # |
|
721 | # | |
709 | # strip should not touch boundary at all |
|
722 | # strip should not touch boundary at all | |
710 |
phases.retractboundary(repo, phase |
|
723 | phases.retractboundary(repo, tr, targetphase, added) | |
711 |
|
724 | |||
712 | # make changelog see real files again |
|
725 | # make changelog see real files again | |
713 | cl.finalize(trp) |
|
726 | cl.finalize(trp) | |
@@ -720,6 +733,7 b' def addchangegroup(repo, source, srctype' | |||||
720 | # `destroyed` will repair it. |
|
733 | # `destroyed` will repair it. | |
721 | # In other case we can safely update cache on disk. |
|
734 | # In other case we can safely update cache on disk. | |
722 | branchmap.updatecache(repo.filtered('served')) |
|
735 | branchmap.updatecache(repo.filtered('served')) | |
|
736 | ||||
723 | def runhooks(): |
|
737 | def runhooks(): | |
724 | # These hooks run when the lock releases, not when the |
|
738 | # These hooks run when the lock releases, not when the | |
725 | # transaction closes. So it's possible for the changelog |
|
739 | # transaction closes. So it's possible for the changelog | |
@@ -729,12 +743,12 b' def addchangegroup(repo, source, srctype' | |||||
729 |
|
743 | |||
730 | # forcefully update the on-disk branch cache |
|
744 | # forcefully update the on-disk branch cache | |
731 | repo.ui.debug("updating the branch cache\n") |
|
745 | repo.ui.debug("updating the branch cache\n") | |
732 |
repo.hook("changegroup", |
|
746 | repo.hook("changegroup", **hookargs) | |
733 | **tr.hookargs) |
|
|||
734 |
|
747 | |||
735 | for n in added: |
|
748 | for n in added: | |
736 | repo.hook("incoming", node=hex(n), source=srctype, |
|
749 | args = hookargs.copy() | |
737 | url=url) |
|
750 | args['node'] = hex(n) | |
|
751 | repo.hook("incoming", **args) | |||
738 |
|
752 | |||
739 | newheads = [h for h in repo.heads() if h not in oldheads] |
|
753 | newheads = [h for h in repo.heads() if h not in oldheads] | |
740 | repo.ui.log("incoming", |
|
754 | repo.ui.log("incoming", |
@@ -171,8 +171,13 b' class changelog(revlog.revlog):' | |||||
171 |
|
171 | |||
172 | def headrevs(self): |
|
172 | def headrevs(self): | |
173 | if self.filteredrevs: |
|
173 | if self.filteredrevs: | |
174 | # XXX we should fix and use the C version |
|
174 | try: | |
175 |
return self. |
|
175 | return self.index.headrevs(self.filteredrevs) | |
|
176 | # AttributeError covers non-c-extension environments. | |||
|
177 | # TypeError allows us work with old c extensions. | |||
|
178 | except (AttributeError, TypeError): | |||
|
179 | return self._headrevs() | |||
|
180 | ||||
176 | return super(changelog, self).headrevs() |
|
181 | return super(changelog, self).headrevs() | |
177 |
|
182 | |||
178 | def strip(self, *args, **kwargs): |
|
183 | def strip(self, *args, **kwargs): | |
@@ -185,31 +190,32 b' class changelog(revlog.revlog):' | |||||
185 | """filtered version of revlog.rev""" |
|
190 | """filtered version of revlog.rev""" | |
186 | r = super(changelog, self).rev(node) |
|
191 | r = super(changelog, self).rev(node) | |
187 | if r in self.filteredrevs: |
|
192 | if r in self.filteredrevs: | |
188 |
raise error.LookupError(hex(node), self.indexfile, |
|
193 | raise error.FilteredLookupError(hex(node), self.indexfile, | |
|
194 | _('filtered node')) | |||
189 | return r |
|
195 | return r | |
190 |
|
196 | |||
191 | def node(self, rev): |
|
197 | def node(self, rev): | |
192 | """filtered version of revlog.node""" |
|
198 | """filtered version of revlog.node""" | |
193 | if rev in self.filteredrevs: |
|
199 | if rev in self.filteredrevs: | |
194 | raise IndexError(rev) |
|
200 | raise error.FilteredIndexError(rev) | |
195 | return super(changelog, self).node(rev) |
|
201 | return super(changelog, self).node(rev) | |
196 |
|
202 | |||
197 | def linkrev(self, rev): |
|
203 | def linkrev(self, rev): | |
198 | """filtered version of revlog.linkrev""" |
|
204 | """filtered version of revlog.linkrev""" | |
199 | if rev in self.filteredrevs: |
|
205 | if rev in self.filteredrevs: | |
200 | raise IndexError(rev) |
|
206 | raise error.FilteredIndexError(rev) | |
201 | return super(changelog, self).linkrev(rev) |
|
207 | return super(changelog, self).linkrev(rev) | |
202 |
|
208 | |||
203 | def parentrevs(self, rev): |
|
209 | def parentrevs(self, rev): | |
204 | """filtered version of revlog.parentrevs""" |
|
210 | """filtered version of revlog.parentrevs""" | |
205 | if rev in self.filteredrevs: |
|
211 | if rev in self.filteredrevs: | |
206 | raise IndexError(rev) |
|
212 | raise error.FilteredIndexError(rev) | |
207 | return super(changelog, self).parentrevs(rev) |
|
213 | return super(changelog, self).parentrevs(rev) | |
208 |
|
214 | |||
209 | def flags(self, rev): |
|
215 | def flags(self, rev): | |
210 | """filtered version of revlog.flags""" |
|
216 | """filtered version of revlog.flags""" | |
211 | if rev in self.filteredrevs: |
|
217 | if rev in self.filteredrevs: | |
212 | raise IndexError(rev) |
|
218 | raise error.FilteredIndexError(rev) | |
213 | return super(changelog, self).flags(rev) |
|
219 | return super(changelog, self).flags(rev) | |
214 |
|
220 | |||
215 | def delayupdate(self): |
|
221 | def delayupdate(self): |
This diff has been collapsed as it changes many lines, (587 lines changed) Show them Hide them | |||||
@@ -13,6 +13,7 b' import match as matchmod' | |||||
13 | import context, repair, graphmod, revset, phases, obsolete, pathutil |
|
13 | import context, repair, graphmod, revset, phases, obsolete, pathutil | |
14 | import changelog |
|
14 | import changelog | |
15 | import bookmarks |
|
15 | import bookmarks | |
|
16 | import encoding | |||
16 | import lock as lockmod |
|
17 | import lock as lockmod | |
17 |
|
18 | |||
18 | def parsealiases(cmd): |
|
19 | def parsealiases(cmd): | |
@@ -109,7 +110,25 b' def logmessage(ui, opts):' | |||||
109 | (logfile, inst.strerror)) |
|
110 | (logfile, inst.strerror)) | |
110 | return message |
|
111 | return message | |
111 |
|
112 | |||
112 | def getcommiteditor(edit=False, finishdesc=None, extramsg=None, **opts): |
|
113 | def mergeeditform(ctxorbool, baseform): | |
|
114 | """build appropriate editform from ctxorbool and baseform | |||
|
115 | ||||
|
116 | 'cxtorbool' is one of a ctx to be committed, or a bool whether | |||
|
117 | merging is committed. | |||
|
118 | ||||
|
119 | This returns editform 'baseform' with '.merge' if merging is | |||
|
120 | committed, or one with '.normal' suffix otherwise. | |||
|
121 | """ | |||
|
122 | if isinstance(ctxorbool, bool): | |||
|
123 | if ctxorbool: | |||
|
124 | return baseform + ".merge" | |||
|
125 | elif 1 < len(ctxorbool.parents()): | |||
|
126 | return baseform + ".merge" | |||
|
127 | ||||
|
128 | return baseform + ".normal" | |||
|
129 | ||||
|
130 | def getcommiteditor(edit=False, finishdesc=None, extramsg=None, | |||
|
131 | editform='', **opts): | |||
113 | """get appropriate commit message editor according to '--edit' option |
|
132 | """get appropriate commit message editor according to '--edit' option | |
114 |
|
133 | |||
115 | 'finishdesc' is a function to be called with edited commit message |
|
134 | 'finishdesc' is a function to be called with edited commit message | |
@@ -122,6 +141,9 b' def getcommiteditor(edit=False, finishde' | |||||
122 | 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL |
|
141 | 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL | |
123 | is automatically added. |
|
142 | is automatically added. | |
124 |
|
143 | |||
|
144 | 'editform' is a dot-separated list of names, to distinguish | |||
|
145 | the purpose of commit text editing. | |||
|
146 | ||||
125 | 'getcommiteditor' returns 'commitforceeditor' regardless of |
|
147 | 'getcommiteditor' returns 'commitforceeditor' regardless of | |
126 | 'edit', if one of 'finishdesc' or 'extramsg' is specified, because |
|
148 | 'edit', if one of 'finishdesc' or 'extramsg' is specified, because | |
127 | they are specific for usage in MQ. |
|
149 | they are specific for usage in MQ. | |
@@ -129,7 +151,10 b' def getcommiteditor(edit=False, finishde' | |||||
129 | if edit or finishdesc or extramsg: |
|
151 | if edit or finishdesc or extramsg: | |
130 | return lambda r, c, s: commitforceeditor(r, c, s, |
|
152 | return lambda r, c, s: commitforceeditor(r, c, s, | |
131 | finishdesc=finishdesc, |
|
153 | finishdesc=finishdesc, | |
132 |
extramsg=extramsg |
|
154 | extramsg=extramsg, | |
|
155 | editform=editform) | |||
|
156 | elif editform: | |||
|
157 | return lambda r, c, s: commiteditor(r, c, s, editform=editform) | |||
133 | else: |
|
158 | else: | |
134 | return commiteditor |
|
159 | return commiteditor | |
135 |
|
160 | |||
@@ -586,7 +611,6 b' def tryimportone(ui, repo, hunk, parents' | |||||
586 | tmpname, message, user, date, branch, nodeid, p1, p2 = \ |
|
611 | tmpname, message, user, date, branch, nodeid, p1, p2 = \ | |
587 | patch.extract(ui, hunk) |
|
612 | patch.extract(ui, hunk) | |
588 |
|
613 | |||
589 | editor = getcommiteditor(**opts) |
|
|||
590 | update = not opts.get('bypass') |
|
614 | update = not opts.get('bypass') | |
591 | strip = opts["strip"] |
|
615 | strip = opts["strip"] | |
592 | sim = float(opts.get('similarity') or 0) |
|
616 | sim = float(opts.get('similarity') or 0) | |
@@ -636,6 +660,7 b' def tryimportone(ui, repo, hunk, parents' | |||||
636 |
|
660 | |||
637 | n = None |
|
661 | n = None | |
638 | if update: |
|
662 | if update: | |
|
663 | repo.dirstate.beginparentchange() | |||
639 | if p1 != parents[0]: |
|
664 | if p1 != parents[0]: | |
640 | updatefunc(repo, p1.node()) |
|
665 | updatefunc(repo, p1.node()) | |
641 | if p2 != parents[1]: |
|
666 | if p2 != parents[1]: | |
@@ -667,9 +692,15 b' def tryimportone(ui, repo, hunk, parents' | |||||
667 | m = None |
|
692 | m = None | |
668 | else: |
|
693 | else: | |
669 | m = scmutil.matchfiles(repo, files or []) |
|
694 | m = scmutil.matchfiles(repo, files or []) | |
|
695 | editform = mergeeditform(repo[None], 'import.normal') | |||
|
696 | if opts.get('exact'): | |||
|
697 | editor = None | |||
|
698 | else: | |||
|
699 | editor = getcommiteditor(editform=editform, **opts) | |||
670 | n = repo.commit(message, opts.get('user') or user, |
|
700 | n = repo.commit(message, opts.get('user') or user, | |
671 | opts.get('date') or date, match=m, |
|
701 | opts.get('date') or date, match=m, | |
672 | editor=editor, force=partial) |
|
702 | editor=editor, force=partial) | |
|
703 | repo.dirstate.endparentchange() | |||
673 | else: |
|
704 | else: | |
674 | if opts.get('exact') or opts.get('import_branch'): |
|
705 | if opts.get('exact') or opts.get('import_branch'): | |
675 | branch = branch or 'default' |
|
706 | branch = branch or 'default' | |
@@ -683,16 +714,24 b' def tryimportone(ui, repo, hunk, parents' | |||||
683 | files, eolmode=None) |
|
714 | files, eolmode=None) | |
684 | except patch.PatchError, e: |
|
715 | except patch.PatchError, e: | |
685 | raise util.Abort(str(e)) |
|
716 | raise util.Abort(str(e)) | |
|
717 | if opts.get('exact'): | |||
|
718 | editor = None | |||
|
719 | else: | |||
|
720 | editor = getcommiteditor(editform='import.bypass') | |||
686 | memctx = context.makememctx(repo, (p1.node(), p2.node()), |
|
721 | memctx = context.makememctx(repo, (p1.node(), p2.node()), | |
687 | message, |
|
722 | message, | |
688 | opts.get('user') or user, |
|
723 | opts.get('user') or user, | |
689 | opts.get('date') or date, |
|
724 | opts.get('date') or date, | |
690 | branch, files, store, |
|
725 | branch, files, store, | |
691 |
editor= |
|
726 | editor=editor) | |
692 | n = memctx.commit() |
|
727 | n = memctx.commit() | |
693 | finally: |
|
728 | finally: | |
694 | store.close() |
|
729 | store.close() | |
695 |
if opts.get('exact') and |
|
730 | if opts.get('exact') and opts.get('no_commit'): | |
|
731 | # --exact with --no-commit is still useful in that it does merge | |||
|
732 | # and branch bits | |||
|
733 | ui.warn(_("warning: can't check exact import with --no-commit\n")) | |||
|
734 | elif opts.get('exact') and hex(n) != nodeid: | |||
696 | raise util.Abort(_('patch is damaged or loses information')) |
|
735 | raise util.Abort(_('patch is damaged or loses information')) | |
697 | if n: |
|
736 | if n: | |
698 | # i18n: refers to a short changeset id |
|
737 | # i18n: refers to a short changeset id | |
@@ -805,11 +844,11 b' def diffordiffstat(ui, repo, diffopts, n' | |||||
805 | class changeset_printer(object): |
|
844 | class changeset_printer(object): | |
806 | '''show changeset information when templating not requested.''' |
|
845 | '''show changeset information when templating not requested.''' | |
807 |
|
846 | |||
808 |
def __init__(self, ui, repo, |
|
847 | def __init__(self, ui, repo, matchfn, diffopts, buffered): | |
809 | self.ui = ui |
|
848 | self.ui = ui | |
810 | self.repo = repo |
|
849 | self.repo = repo | |
811 | self.buffered = buffered |
|
850 | self.buffered = buffered | |
812 |
self. |
|
851 | self.matchfn = matchfn | |
813 | self.diffopts = diffopts |
|
852 | self.diffopts = diffopts | |
814 | self.header = {} |
|
853 | self.header = {} | |
815 | self.hunk = {} |
|
854 | self.hunk = {} | |
@@ -877,7 +916,7 b' class changeset_printer(object):' | |||||
877 | # i18n: column positioning for "hg log" |
|
916 | # i18n: column positioning for "hg log" | |
878 | self.ui.write(_("tag: %s\n") % tag, |
|
917 | self.ui.write(_("tag: %s\n") % tag, | |
879 | label='log.tag') |
|
918 | label='log.tag') | |
880 |
if self.ui.debugflag |
|
919 | if self.ui.debugflag: | |
881 | # i18n: column positioning for "hg log" |
|
920 | # i18n: column positioning for "hg log" | |
882 | self.ui.write(_("phase: %s\n") % _(ctx.phasestr()), |
|
921 | self.ui.write(_("phase: %s\n") % _(ctx.phasestr()), | |
883 | label='log.phase') |
|
922 | label='log.phase') | |
@@ -948,7 +987,7 b' class changeset_printer(object):' | |||||
948 |
|
987 | |||
949 | def showpatch(self, node, matchfn): |
|
988 | def showpatch(self, node, matchfn): | |
950 | if not matchfn: |
|
989 | if not matchfn: | |
951 |
matchfn = self. |
|
990 | matchfn = self.matchfn | |
952 | if matchfn: |
|
991 | if matchfn: | |
953 | stat = self.diffopts.get('stat') |
|
992 | stat = self.diffopts.get('stat') | |
954 | diff = self.diffopts.get('patch') |
|
993 | diff = self.diffopts.get('patch') | |
@@ -979,12 +1018,101 b' class changeset_printer(object):' | |||||
979 | parents = [parents[0]] |
|
1018 | parents = [parents[0]] | |
980 | return parents |
|
1019 | return parents | |
981 |
|
1020 | |||
|
1021 | class jsonchangeset(changeset_printer): | |||
|
1022 | '''format changeset information.''' | |||
|
1023 | ||||
|
1024 | def __init__(self, ui, repo, matchfn, diffopts, buffered): | |||
|
1025 | changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered) | |||
|
1026 | self.cache = {} | |||
|
1027 | self._first = True | |||
|
1028 | ||||
|
1029 | def close(self): | |||
|
1030 | if not self._first: | |||
|
1031 | self.ui.write("\n]\n") | |||
|
1032 | else: | |||
|
1033 | self.ui.write("[]\n") | |||
|
1034 | ||||
|
1035 | def _show(self, ctx, copies, matchfn, props): | |||
|
1036 | '''show a single changeset or file revision''' | |||
|
1037 | hexnode = hex(ctx.node()) | |||
|
1038 | rev = ctx.rev() | |||
|
1039 | j = encoding.jsonescape | |||
|
1040 | ||||
|
1041 | if self._first: | |||
|
1042 | self.ui.write("[\n {") | |||
|
1043 | self._first = False | |||
|
1044 | else: | |||
|
1045 | self.ui.write(",\n {") | |||
|
1046 | ||||
|
1047 | if self.ui.quiet: | |||
|
1048 | self.ui.write('\n "rev": %d' % rev) | |||
|
1049 | self.ui.write(',\n "node": "%s"' % hexnode) | |||
|
1050 | self.ui.write('\n }') | |||
|
1051 | return | |||
|
1052 | ||||
|
1053 | self.ui.write('\n "rev": %d' % rev) | |||
|
1054 | self.ui.write(',\n "node": "%s"' % hexnode) | |||
|
1055 | self.ui.write(',\n "branch": "%s"' % j(ctx.branch())) | |||
|
1056 | self.ui.write(',\n "phase": "%s"' % ctx.phasestr()) | |||
|
1057 | self.ui.write(',\n "user": "%s"' % j(ctx.user())) | |||
|
1058 | self.ui.write(',\n "date": [%d, %d]' % ctx.date()) | |||
|
1059 | self.ui.write(',\n "desc": "%s"' % j(ctx.description())) | |||
|
1060 | ||||
|
1061 | self.ui.write(',\n "bookmarks": [%s]' % | |||
|
1062 | ", ".join('"%s"' % j(b) for b in ctx.bookmarks())) | |||
|
1063 | self.ui.write(',\n "tags": [%s]' % | |||
|
1064 | ", ".join('"%s"' % j(t) for t in ctx.tags())) | |||
|
1065 | self.ui.write(',\n "parents": [%s]' % | |||
|
1066 | ", ".join('"%s"' % c.hex() for c in ctx.parents())) | |||
|
1067 | ||||
|
1068 | if self.ui.debugflag: | |||
|
1069 | self.ui.write(',\n "manifest": "%s"' % hex(ctx.manifestnode())) | |||
|
1070 | ||||
|
1071 | self.ui.write(',\n "extra": {%s}' % | |||
|
1072 | ", ".join('"%s": "%s"' % (j(k), j(v)) | |||
|
1073 | for k, v in ctx.extra().items())) | |||
|
1074 | ||||
|
1075 | files = ctx.status(ctx.p1()) | |||
|
1076 | self.ui.write(',\n "modified": [%s]' % | |||
|
1077 | ", ".join('"%s"' % j(f) for f in files[0])) | |||
|
1078 | self.ui.write(',\n "added": [%s]' % | |||
|
1079 | ", ".join('"%s"' % j(f) for f in files[1])) | |||
|
1080 | self.ui.write(',\n "removed": [%s]' % | |||
|
1081 | ", ".join('"%s"' % j(f) for f in files[2])) | |||
|
1082 | ||||
|
1083 | elif self.ui.verbose: | |||
|
1084 | self.ui.write(',\n "files": [%s]' % | |||
|
1085 | ", ".join('"%s"' % j(f) for f in ctx.files())) | |||
|
1086 | ||||
|
1087 | if copies: | |||
|
1088 | self.ui.write(',\n "copies": {%s}' % | |||
|
1089 | ", ".join('"%s": %s' % (j(k), j(copies[k])) | |||
|
1090 | for k in copies)) | |||
|
1091 | ||||
|
1092 | matchfn = self.matchfn | |||
|
1093 | if matchfn: | |||
|
1094 | stat = self.diffopts.get('stat') | |||
|
1095 | diff = self.diffopts.get('patch') | |||
|
1096 | diffopts = patch.diffopts(self.ui, self.diffopts) | |||
|
1097 | node, prev = ctx.node(), ctx.p1().node() | |||
|
1098 | if stat: | |||
|
1099 | self.ui.pushbuffer() | |||
|
1100 | diffordiffstat(self.ui, self.repo, diffopts, prev, node, | |||
|
1101 | match=matchfn, stat=True) | |||
|
1102 | self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer())) | |||
|
1103 | if diff: | |||
|
1104 | self.ui.pushbuffer() | |||
|
1105 | diffordiffstat(self.ui, self.repo, diffopts, prev, node, | |||
|
1106 | match=matchfn, stat=False) | |||
|
1107 | self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer())) | |||
|
1108 | ||||
|
1109 | self.ui.write("\n }") | |||
982 |
|
1110 | |||
983 | class changeset_templater(changeset_printer): |
|
1111 | class changeset_templater(changeset_printer): | |
984 | '''format changeset information.''' |
|
1112 | '''format changeset information.''' | |
985 |
|
1113 | |||
986 |
def __init__(self, ui, repo, |
|
1114 | def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered): | |
987 |
changeset_printer.__init__(self, ui, repo, |
|
1115 | changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered) | |
988 | formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12]) |
|
1116 | formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12]) | |
989 | defaulttempl = { |
|
1117 | defaulttempl = { | |
990 | 'parent': '{rev}:{node|formatnode} ', |
|
1118 | 'parent': '{rev}:{node|formatnode} ', | |
@@ -1024,7 +1152,9 b' class changeset_templater(changeset_prin' | |||||
1024 | # behaviour cannot be changed so leave it here for now. |
|
1152 | # behaviour cannot be changed so leave it here for now. | |
1025 | def showparents(**args): |
|
1153 | def showparents(**args): | |
1026 | ctx = args['ctx'] |
|
1154 | ctx = args['ctx'] | |
1027 |
parents = [[('rev', p.rev()), |
|
1155 | parents = [[('rev', p.rev()), | |
|
1156 | ('node', p.hex()), | |||
|
1157 | ('phase', p.phasestr())] | |||
1028 | for p in self._meaningful_parentrevs(ctx)] |
|
1158 | for p in self._meaningful_parentrevs(ctx)] | |
1029 | return showlist('parent', parents, **args) |
|
1159 | return showlist('parent', parents, **args) | |
1030 |
|
1160 | |||
@@ -1157,17 +1287,21 b' def show_changeset(ui, repo, opts, buffe' | |||||
1157 | regular display via changeset_printer() is done. |
|
1287 | regular display via changeset_printer() is done. | |
1158 | """ |
|
1288 | """ | |
1159 | # options |
|
1289 | # options | |
1160 |
|
|
1290 | matchfn = None | |
1161 | if opts.get('patch') or opts.get('stat'): |
|
1291 | if opts.get('patch') or opts.get('stat'): | |
1162 |
|
|
1292 | matchfn = scmutil.matchall(repo) | |
|
1293 | ||||
|
1294 | if opts.get('template') == 'json': | |||
|
1295 | return jsonchangeset(ui, repo, matchfn, opts, buffered) | |||
1163 |
|
1296 | |||
1164 | tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style')) |
|
1297 | tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style')) | |
1165 |
|
1298 | |||
1166 | if not tmpl and not mapfile: |
|
1299 | if not tmpl and not mapfile: | |
1167 |
return changeset_printer(ui, repo, |
|
1300 | return changeset_printer(ui, repo, matchfn, opts, buffered) | |
1168 |
|
1301 | |||
1169 | try: |
|
1302 | try: | |
1170 |
t = changeset_templater(ui, repo, |
|
1303 | t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile, | |
|
1304 | buffered) | |||
1171 | except SyntaxError, inst: |
|
1305 | except SyntaxError, inst: | |
1172 | raise util.Abort(inst.args[0]) |
|
1306 | raise util.Abort(inst.args[0]) | |
1173 | return t |
|
1307 | return t | |
@@ -1180,9 +1314,14 b' def showmarker(ui, marker):' | |||||
1180 | for repl in marker.succnodes(): |
|
1314 | for repl in marker.succnodes(): | |
1181 | ui.write(' ') |
|
1315 | ui.write(' ') | |
1182 | ui.write(hex(repl)) |
|
1316 | ui.write(hex(repl)) | |
1183 |
ui.write(' %X ' % marker. |
|
1317 | ui.write(' %X ' % marker.flags()) | |
|
1318 | parents = marker.parentnodes() | |||
|
1319 | if parents is not None: | |||
|
1320 | ui.write('{%s} ' % ', '.join(hex(p) for p in parents)) | |||
|
1321 | ui.write('(%s) ' % util.datestr(marker.date())) | |||
1184 | ui.write('{%s}' % (', '.join('%r: %r' % t for t in |
|
1322 | ui.write('{%s}' % (', '.join('%r: %r' % t for t in | |
1185 |
sorted(marker.metadata().items()) |
|
1323 | sorted(marker.metadata().items()) | |
|
1324 | if t[0] != 'date'))) | |||
1186 | ui.write('\n') |
|
1325 | ui.write('\n') | |
1187 |
|
1326 | |||
1188 | def finddate(ui, repo, date): |
|
1327 | def finddate(ui, repo, date): | |
@@ -1578,8 +1717,14 b' def _makelogrevset(repo, pats, opts, rev' | |||||
1578 | if not slowpath: |
|
1717 | if not slowpath: | |
1579 | for f in match.files(): |
|
1718 | for f in match.files(): | |
1580 | if follow and f not in pctx: |
|
1719 | if follow and f not in pctx: | |
1581 | raise util.Abort(_('cannot follow file not in parent ' |
|
1720 | # If the file exists, it may be a directory, so let it | |
1582 | 'revision: "%s"') % f) |
|
1721 | # take the slow path. | |
|
1722 | if os.path.exists(repo.wjoin(f)): | |||
|
1723 | slowpath = True | |||
|
1724 | continue | |||
|
1725 | else: | |||
|
1726 | raise util.Abort(_('cannot follow file not in parent ' | |||
|
1727 | 'revision: "%s"') % f) | |||
1583 | filelog = repo.file(f) |
|
1728 | filelog = repo.file(f) | |
1584 | if not filelog: |
|
1729 | if not filelog: | |
1585 | # A zero count may be a directory or deleted file, so |
|
1730 | # A zero count may be a directory or deleted file, so | |
@@ -1603,9 +1748,6 b' def _makelogrevset(repo, pats, opts, rev' | |||||
1603 | if slowpath: |
|
1748 | if slowpath: | |
1604 | # See walkchangerevs() slow path. |
|
1749 | # See walkchangerevs() slow path. | |
1605 | # |
|
1750 | # | |
1606 | if follow: |
|
|||
1607 | raise util.Abort(_('can only follow copies/renames for explicit ' |
|
|||
1608 | 'filenames')) |
|
|||
1609 | # pats/include/exclude cannot be represented as separate |
|
1751 | # pats/include/exclude cannot be represented as separate | |
1610 | # revset expressions as their filtering logic applies at file |
|
1752 | # revset expressions as their filtering logic applies at file | |
1611 | # level. For instance "-I a -X a" matches a revision touching |
|
1753 | # level. For instance "-I a -X a" matches a revision touching | |
@@ -1637,7 +1779,10 b' def _makelogrevset(repo, pats, opts, rev' | |||||
1637 |
|
1779 | |||
1638 | filematcher = None |
|
1780 | filematcher = None | |
1639 | if opts.get('patch') or opts.get('stat'): |
|
1781 | if opts.get('patch') or opts.get('stat'): | |
1640 | if follow and not match.always(): |
|
1782 | # When following files, track renames via a special matcher. | |
|
1783 | # If we're forced to take the slowpath it means we're following | |||
|
1784 | # at least one pattern/directory, so don't bother with rename tracking. | |||
|
1785 | if follow and not match.always() and not slowpath: | |||
1641 | # _makelogfilematcher expects its files argument to be relative to |
|
1786 | # _makelogfilematcher expects its files argument to be relative to | |
1642 | # the repo root, so use match.files(), not pats. |
|
1787 | # the repo root, so use match.files(), not pats. | |
1643 | filematcher = _makefollowlogfilematcher(repo, match.files(), |
|
1788 | filematcher = _makefollowlogfilematcher(repo, match.files(), | |
@@ -1711,12 +1856,12 b' def getgraphlogrevs(repo, pats, opts):' | |||||
1711 | revs = matcher(repo, revs) |
|
1856 | revs = matcher(repo, revs) | |
1712 | revs.sort(reverse=True) |
|
1857 | revs.sort(reverse=True) | |
1713 | if limit is not None: |
|
1858 | if limit is not None: | |
1714 |
limitedrevs = |
|
1859 | limitedrevs = [] | |
1715 | for idx, rev in enumerate(revs): |
|
1860 | for idx, rev in enumerate(revs): | |
1716 | if idx >= limit: |
|
1861 | if idx >= limit: | |
1717 | break |
|
1862 | break | |
1718 | limitedrevs.append(rev) |
|
1863 | limitedrevs.append(rev) | |
1719 | revs = limitedrevs |
|
1864 | revs = revset.baseset(limitedrevs) | |
1720 |
|
1865 | |||
1721 | return revs, expr, filematcher |
|
1866 | return revs, expr, filematcher | |
1722 |
|
1867 | |||
@@ -1756,7 +1901,7 b' def getlogrevs(repo, pats, opts):' | |||||
1756 | revs.sort(reverse=True) |
|
1901 | revs.sort(reverse=True) | |
1757 | if limit is not None: |
|
1902 | if limit is not None: | |
1758 | count = 0 |
|
1903 | count = 0 | |
1759 |
limitedrevs = |
|
1904 | limitedrevs = [] | |
1760 | it = iter(revs) |
|
1905 | it = iter(revs) | |
1761 | while count < limit: |
|
1906 | while count < limit: | |
1762 | try: |
|
1907 | try: | |
@@ -1764,7 +1909,7 b' def getlogrevs(repo, pats, opts):' | |||||
1764 | except (StopIteration): |
|
1909 | except (StopIteration): | |
1765 | break |
|
1910 | break | |
1766 | count += 1 |
|
1911 | count += 1 | |
1767 | revs = limitedrevs |
|
1912 | revs = revset.baseset(limitedrevs) | |
1768 |
|
1913 | |||
1769 | return revs, expr, filematcher |
|
1914 | return revs, expr, filematcher | |
1770 |
|
1915 | |||
@@ -1960,25 +2105,6 b' def cat(ui, repo, ctx, matcher, prefix, ' | |||||
1960 |
|
2105 | |||
1961 | return err |
|
2106 | return err | |
1962 |
|
2107 | |||
1963 | def duplicatecopies(repo, rev, fromrev, skiprev=None): |
|
|||
1964 | '''reproduce copies from fromrev to rev in the dirstate |
|
|||
1965 |
|
||||
1966 | If skiprev is specified, it's a revision that should be used to |
|
|||
1967 | filter copy records. Any copies that occur between fromrev and |
|
|||
1968 | skiprev will not be duplicated, even if they appear in the set of |
|
|||
1969 | copies between fromrev and rev. |
|
|||
1970 | ''' |
|
|||
1971 | exclude = {} |
|
|||
1972 | if skiprev is not None: |
|
|||
1973 | exclude = copies.pathcopies(repo[fromrev], repo[skiprev]) |
|
|||
1974 | for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems(): |
|
|||
1975 | # copies.pathcopies returns backward renames, so dst might not |
|
|||
1976 | # actually be in the dirstate |
|
|||
1977 | if dst in exclude: |
|
|||
1978 | continue |
|
|||
1979 | if repo.dirstate[dst] in "nma": |
|
|||
1980 | repo.dirstate.copy(src, dst) |
|
|||
1981 |
|
||||
1982 | def commit(ui, repo, commitfunc, pats, opts): |
|
2108 | def commit(ui, repo, commitfunc, pats, opts): | |
1983 | '''commit the specified files or all outstanding changes''' |
|
2109 | '''commit the specified files or all outstanding changes''' | |
1984 | date = opts.get('date') |
|
2110 | date = opts.get('date') | |
@@ -2091,7 +2217,7 b' def amend(ui, repo, commitfunc, old, ext' | |||||
2091 | copied=copied.get(path)) |
|
2217 | copied=copied.get(path)) | |
2092 | return mctx |
|
2218 | return mctx | |
2093 | except KeyError: |
|
2219 | except KeyError: | |
2094 |
r |
|
2220 | return None | |
2095 | else: |
|
2221 | else: | |
2096 | ui.note(_('copying changeset %s to %s\n') % (old, base)) |
|
2222 | ui.note(_('copying changeset %s to %s\n') % (old, base)) | |
2097 |
|
2223 | |||
@@ -2100,13 +2226,14 b' def amend(ui, repo, commitfunc, old, ext' | |||||
2100 | try: |
|
2226 | try: | |
2101 | return old.filectx(path) |
|
2227 | return old.filectx(path) | |
2102 | except KeyError: |
|
2228 | except KeyError: | |
2103 |
r |
|
2229 | return None | |
2104 |
|
2230 | |||
2105 | user = opts.get('user') or old.user() |
|
2231 | user = opts.get('user') or old.user() | |
2106 | date = opts.get('date') or old.date() |
|
2232 | date = opts.get('date') or old.date() | |
2107 |
editor = ge |
|
2233 | editform = mergeeditform(old, 'commit.amend') | |
|
2234 | editor = getcommiteditor(editform=editform, **opts) | |||
2108 | if not message: |
|
2235 | if not message: | |
2109 | editor = getcommiteditor(edit=True) |
|
2236 | editor = getcommiteditor(edit=True, editform=editform) | |
2110 | message = old.description() |
|
2237 | message = old.description() | |
2111 |
|
2238 | |||
2112 | pureextra = extra.copy() |
|
2239 | pureextra = extra.copy() | |
@@ -2156,7 +2283,8 b' def amend(ui, repo, commitfunc, old, ext' | |||||
2156 | marks[bm] = newid |
|
2283 | marks[bm] = newid | |
2157 | marks.write() |
|
2284 | marks.write() | |
2158 | #commit the whole amend process |
|
2285 | #commit the whole amend process | |
2159 | if obsolete._enabled and newid != old.node(): |
|
2286 | createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt) | |
|
2287 | if createmarkers and newid != old.node(): | |||
2160 | # mark the new changeset as successor of the rewritten one |
|
2288 | # mark the new changeset as successor of the rewritten one | |
2161 | new = repo[newid] |
|
2289 | new = repo[newid] | |
2162 | obs = [(old, (new,))] |
|
2290 | obs = [(old, (new,))] | |
@@ -2167,7 +2295,7 b' def amend(ui, repo, commitfunc, old, ext' | |||||
2167 | tr.close() |
|
2295 | tr.close() | |
2168 | finally: |
|
2296 | finally: | |
2169 | tr.release() |
|
2297 | tr.release() | |
2170 |
if |
|
2298 | if not createmarkers and newid != old.node(): | |
2171 | # Strip the intermediate commit (if there was one) and the amended |
|
2299 | # Strip the intermediate commit (if there was one) and the amended | |
2172 | # commit |
|
2300 | # commit | |
2173 | if node: |
|
2301 | if node: | |
@@ -2180,24 +2308,31 b' def amend(ui, repo, commitfunc, old, ext' | |||||
2180 | lockmod.release(lock, wlock) |
|
2308 | lockmod.release(lock, wlock) | |
2181 | return newid |
|
2309 | return newid | |
2182 |
|
2310 | |||
2183 | def commiteditor(repo, ctx, subs): |
|
2311 | def commiteditor(repo, ctx, subs, editform=''): | |
2184 | if ctx.description(): |
|
2312 | if ctx.description(): | |
2185 | return ctx.description() |
|
2313 | return ctx.description() | |
2186 | return commitforceeditor(repo, ctx, subs) |
|
2314 | return commitforceeditor(repo, ctx, subs, editform=editform) | |
2187 |
|
2315 | |||
2188 |
def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None |
|
2316 | def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None, | |
|
2317 | editform=''): | |||
2189 | if not extramsg: |
|
2318 | if not extramsg: | |
2190 | extramsg = _("Leave message empty to abort commit.") |
|
2319 | extramsg = _("Leave message empty to abort commit.") | |
2191 | tmpl = repo.ui.config('committemplate', 'changeset', '').strip() |
|
2320 | ||
2192 | if tmpl: |
|
2321 | forms = [e for e in editform.split('.') if e] | |
2193 | committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl) |
|
2322 | forms.insert(0, 'changeset') | |
|
2323 | while forms: | |||
|
2324 | tmpl = repo.ui.config('committemplate', '.'.join(forms)) | |||
|
2325 | if tmpl: | |||
|
2326 | committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl) | |||
|
2327 | break | |||
|
2328 | forms.pop() | |||
2194 | else: |
|
2329 | else: | |
2195 | committext = buildcommittext(repo, ctx, subs, extramsg) |
|
2330 | committext = buildcommittext(repo, ctx, subs, extramsg) | |
2196 |
|
2331 | |||
2197 | # run editor in the repository root |
|
2332 | # run editor in the repository root | |
2198 | olddir = os.getcwd() |
|
2333 | olddir = os.getcwd() | |
2199 | os.chdir(repo.root) |
|
2334 | os.chdir(repo.root) | |
2200 | text = repo.ui.edit(committext, ctx.user(), ctx.extra()) |
|
2335 | text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform) | |
2201 | text = re.sub("(?m)^HG:.*(\n|$)", "", text) |
|
2336 | text = re.sub("(?m)^HG:.*(\n|$)", "", text) | |
2202 | os.chdir(olddir) |
|
2337 | os.chdir(olddir) | |
2203 |
|
2338 | |||
@@ -2217,6 +2352,10 b' def buildcommittemplate(repo, ctx, subs,' | |||||
2217 | except SyntaxError, inst: |
|
2352 | except SyntaxError, inst: | |
2218 | raise util.Abort(inst.args[0]) |
|
2353 | raise util.Abort(inst.args[0]) | |
2219 |
|
2354 | |||
|
2355 | for k, v in repo.ui.configitems('committemplate'): | |||
|
2356 | if k != 'changeset': | |||
|
2357 | t.t.cache[k] = v | |||
|
2358 | ||||
2220 | if not extramsg: |
|
2359 | if not extramsg: | |
2221 | extramsg = '' # ensure that extramsg is string |
|
2360 | extramsg = '' # ensure that extramsg is string | |
2222 |
|
2361 | |||
@@ -2325,141 +2464,244 b' def revert(ui, repo, ctx, parents, *pats' | |||||
2325 | # walk dirstate to fill `names` |
|
2464 | # walk dirstate to fill `names` | |
2326 |
|
2465 | |||
2327 | m = scmutil.match(repo[None], pats, opts) |
|
2466 | m = scmutil.match(repo[None], pats, opts) | |
2328 | m.bad = lambda x, y: False |
|
2467 | if not m.always() or node != parent: | |
2329 | for abs in repo.walk(m): |
|
2468 | m.bad = lambda x, y: False | |
2330 | names[abs] = m.rel(abs), m.exact(abs) |
|
2469 | for abs in repo.walk(m): | |
2331 |
|
2470 | names[abs] = m.rel(abs), m.exact(abs) | ||
2332 | # walk target manifest to fill `names` |
|
2471 | ||
2333 |
|
2472 | # walk target manifest to fill `names` | ||
2334 | def badfn(path, msg): |
|
2473 | ||
2335 |
|
|
2474 | def badfn(path, msg): | |
2336 |
|
|
2475 | if path in names: | |
2337 | if path in ctx.substate: |
|
2476 | return | |
2338 | return |
|
2477 | if path in ctx.substate: | |
2339 | path_ = path + '/' |
|
|||
2340 | for f in names: |
|
|||
2341 | if f.startswith(path_): |
|
|||
2342 | return |
|
2478 | return | |
2343 | ui.warn("%s: %s\n" % (m.rel(path), msg)) |
|
2479 | path_ = path + '/' | |
2344 |
|
2480 | for f in names: | ||
2345 | m = scmutil.match(ctx, pats, opts) |
|
2481 | if f.startswith(path_): | |
2346 | m.bad = badfn |
|
2482 | return | |
2347 | for abs in ctx.walk(m): |
|
2483 | ui.warn("%s: %s\n" % (m.rel(path), msg)) | |
2348 | if abs not in names: |
|
2484 | ||
2349 | names[abs] = m.rel(abs), m.exact(abs) |
|
2485 | m = scmutil.match(ctx, pats, opts) | |
2350 |
|
2486 | m.bad = badfn | ||
2351 | # get the list of subrepos that must be reverted |
|
2487 | for abs in ctx.walk(m): | |
2352 | targetsubs = sorted(s for s in ctx.substate if m(s)) |
|
2488 | if abs not in names: | |
2353 |
|
2489 | names[abs] = m.rel(abs), m.exact(abs) | ||
2354 | # Find status of all file in `names`. (Against working directory parent) |
|
2490 | ||
2355 | m = scmutil.matchfiles(repo, names) |
|
2491 | # Find status of all file in `names`. | |
2356 | changes = repo.status(node1=parent, match=m)[:4] |
|
2492 | m = scmutil.matchfiles(repo, names) | |
2357 | modified, added, removed, deleted = map(set, changes) |
|
2493 | ||
|
2494 | changes = repo.status(node1=node, match=m, | |||
|
2495 | unknown=True, ignored=True, clean=True) | |||
|
2496 | else: | |||
|
2497 | changes = repo.status(match=m) | |||
|
2498 | for kind in changes: | |||
|
2499 | for abs in kind: | |||
|
2500 | names[abs] = m.rel(abs), m.exact(abs) | |||
|
2501 | ||||
|
2502 | m = scmutil.matchfiles(repo, names) | |||
|
2503 | ||||
|
2504 | modified = set(changes[0]) | |||
|
2505 | added = set(changes[1]) | |||
|
2506 | removed = set(changes[2]) | |||
|
2507 | _deleted = set(changes[3]) | |||
|
2508 | unknown = set(changes[4]) | |||
|
2509 | unknown.update(changes[5]) | |||
|
2510 | clean = set(changes[6]) | |||
|
2511 | modadded = set() | |||
|
2512 | ||||
|
2513 | # split between files known in target manifest and the others | |||
|
2514 | smf = set(mf) | |||
|
2515 | ||||
|
2516 | # determine the exact nature of the deleted changesets | |||
|
2517 | deladded = _deleted - smf | |||
|
2518 | deleted = _deleted - deladded | |||
|
2519 | ||||
|
2520 | # We need to account for the state of file in the dirstate | |||
|
2521 | # | |||
|
2522 | # Even, when we revert agains something else than parent. this will | |||
|
2523 | # slightly alter the behavior of revert (doing back up or not, delete | |||
|
2524 | # or just forget etc) | |||
|
2525 | if parent == node: | |||
|
2526 | dsmodified = modified | |||
|
2527 | dsadded = added | |||
|
2528 | dsremoved = removed | |||
|
2529 | modified, added, removed = set(), set(), set() | |||
|
2530 | else: | |||
|
2531 | changes = repo.status(node1=parent, match=m) | |||
|
2532 | dsmodified = set(changes[0]) | |||
|
2533 | dsadded = set(changes[1]) | |||
|
2534 | dsremoved = set(changes[2]) | |||
|
2535 | ||||
|
2536 | # only take into account for removes between wc and target | |||
|
2537 | clean |= dsremoved - removed | |||
|
2538 | dsremoved &= removed | |||
|
2539 | # distinct between dirstate remove and other | |||
|
2540 | removed -= dsremoved | |||
|
2541 | ||||
|
2542 | modadded = added & dsmodified | |||
|
2543 | added -= modadded | |||
|
2544 | ||||
|
2545 | # tell newly modified apart. | |||
|
2546 | dsmodified &= modified | |||
|
2547 | dsmodified |= modified & dsadded # dirstate added may needs backup | |||
|
2548 | modified -= dsmodified | |||
|
2549 | ||||
|
2550 | # We need to wait for some post-processing to update this set | |||
|
2551 | # before making the distinction. The dirstate will be used for | |||
|
2552 | # that purpose. | |||
|
2553 | dsadded = added | |||
|
2554 | ||||
|
2555 | # in case of merge, files that are actually added can be reported as | |||
|
2556 | # modified, we need to post process the result | |||
|
2557 | if p2 != nullid: | |||
|
2558 | if pmf is None: | |||
|
2559 | # only need parent manifest in the merge case, | |||
|
2560 | # so do not read by default | |||
|
2561 | pmf = repo[parent].manifest() | |||
|
2562 | mergeadd = dsmodified - set(pmf) | |||
|
2563 | dsadded |= mergeadd | |||
|
2564 | dsmodified -= mergeadd | |||
2358 |
|
2565 | |||
2359 | # if f is a rename, update `names` to also revert the source |
|
2566 | # if f is a rename, update `names` to also revert the source | |
2360 | cwd = repo.getcwd() |
|
2567 | cwd = repo.getcwd() | |
2361 | for f in added: |
|
2568 | for f in dsadded: | |
2362 | src = repo.dirstate.copied(f) |
|
2569 | src = repo.dirstate.copied(f) | |
|
2570 | # XXX should we check for rename down to target node? | |||
2363 | if src and src not in names and repo.dirstate[src] == 'r': |
|
2571 | if src and src not in names and repo.dirstate[src] == 'r': | |
2364 | removed.add(src) |
|
2572 | dsremoved.add(src) | |
2365 | names[src] = (repo.pathto(src, cwd), True) |
|
2573 | names[src] = (repo.pathto(src, cwd), True) | |
2366 |
|
2574 | |||
2367 | ## computation of the action to performs on `names` content. |
|
2575 | # distinguish between file to forget and the other | |
2368 |
|
2576 | added = set() | ||
2369 | def removeforget(abs): |
|
2577 | for abs in dsadded: | |
|
2578 | if repo.dirstate[abs] != 'a': | |||
|
2579 | added.add(abs) | |||
|
2580 | dsadded -= added | |||
|
2581 | ||||
|
2582 | for abs in deladded: | |||
2370 | if repo.dirstate[abs] == 'a': |
|
2583 | if repo.dirstate[abs] == 'a': | |
2371 | return _('forgetting %s\n') |
|
2584 | dsadded.add(abs) | |
2372 | return _('removing %s\n') |
|
2585 | deladded -= dsadded | |
|
2586 | ||||
|
2587 | # For files marked as removed, we check if an unknown file is present at | |||
|
2588 | # the same path. If a such file exists it may need to be backed up. | |||
|
2589 | # Making the distinction at this stage helps have simpler backup | |||
|
2590 | # logic. | |||
|
2591 | removunk = set() | |||
|
2592 | for abs in removed: | |||
|
2593 | target = repo.wjoin(abs) | |||
|
2594 | if os.path.lexists(target): | |||
|
2595 | removunk.add(abs) | |||
|
2596 | removed -= removunk | |||
|
2597 | ||||
|
2598 | dsremovunk = set() | |||
|
2599 | for abs in dsremoved: | |||
|
2600 | target = repo.wjoin(abs) | |||
|
2601 | if os.path.lexists(target): | |||
|
2602 | dsremovunk.add(abs) | |||
|
2603 | dsremoved -= dsremovunk | |||
2373 |
|
2604 | |||
2374 | # action to be actually performed by revert |
|
2605 | # action to be actually performed by revert | |
2375 | # (<list of file>, message>) tuple |
|
2606 | # (<list of file>, message>) tuple | |
2376 | actions = {'revert': ([], _('reverting %s\n')), |
|
2607 | actions = {'revert': ([], _('reverting %s\n')), | |
2377 | 'add': ([], _('adding %s\n')), |
|
2608 | 'add': ([], _('adding %s\n')), | |
2378 |
'remove': ([], |
|
2609 | 'remove': ([], _('removing %s\n')), | |
2379 |
' |
|
2610 | 'drop': ([], _('removing %s\n')), | |
|
2611 | 'forget': ([], _('forgetting %s\n')), | |||
|
2612 | 'undelete': ([], _('undeleting %s\n')), | |||
|
2613 | 'noop': (None, _('no changes needed to %s\n')), | |||
|
2614 | 'unknown': (None, _('file not managed: %s\n')), | |||
|
2615 | } | |||
|
2616 | ||||
|
2617 | # "constant" that convey the backup strategy. | |||
|
2618 | # All set to `discard` if `no-backup` is set do avoid checking | |||
|
2619 | # no_backup lower in the code. | |||
|
2620 | # These values are ordered for comparison purposes | |||
|
2621 | backup = 2 # unconditionally do backup | |||
|
2622 | check = 1 # check if the existing file differs from target | |||
|
2623 | discard = 0 # never do backup | |||
|
2624 | if opts.get('no_backup'): | |||
|
2625 | backup = check = discard | |||
|
2626 | ||||
|
2627 | backupanddel = actions['remove'] | |||
|
2628 | if not opts.get('no_backup'): | |||
|
2629 | backupanddel = actions['drop'] | |||
2380 |
|
2630 | |||
2381 | disptable = ( |
|
2631 | disptable = ( | |
2382 | # dispatch table: |
|
2632 | # dispatch table: | |
2383 | # file state |
|
2633 | # file state | |
2384 |
# action |
|
2634 | # action | |
2385 | # action if not in target manifest |
|
2635 | # make backup | |
2386 | # make backup if in target manifest |
|
2636 | ||
2387 | # make backup if not in target manifest |
|
2637 | ## Sets that results that will change file on disk | |
2388 | (modified, (actions['revert'], True), |
|
2638 | # Modified compared to target, no local change | |
2389 |
|
|
2639 | (modified, actions['revert'], discard), | |
2390 | (added, (actions['revert'], True), |
|
2640 | # Modified compared to target, but local file is deleted | |
2391 |
|
|
2641 | (deleted, actions['revert'], discard), | |
2392 | (removed, (actions['undelete'], True), |
|
2642 | # Modified compared to target, local change | |
2393 | (None, False)), |
|
2643 | (dsmodified, actions['revert'], backup), | |
2394 | (deleted, (actions['revert'], False), |
|
2644 | # Added since target | |
2395 |
|
|
2645 | (added, actions['remove'], discard), | |
|
2646 | # Added in working directory | |||
|
2647 | (dsadded, actions['forget'], discard), | |||
|
2648 | # Added since target, have local modification | |||
|
2649 | (modadded, backupanddel, backup), | |||
|
2650 | # Added since target but file is missing in working directory | |||
|
2651 | (deladded, actions['drop'], discard), | |||
|
2652 | # Removed since target, before working copy parent | |||
|
2653 | (removed, actions['add'], discard), | |||
|
2654 | # Same as `removed` but an unknown file exists at the same path | |||
|
2655 | (removunk, actions['add'], check), | |||
|
2656 | # Removed since targe, marked as such in working copy parent | |||
|
2657 | (dsremoved, actions['undelete'], discard), | |||
|
2658 | # Same as `dsremoved` but an unknown file exists at the same path | |||
|
2659 | (dsremovunk, actions['undelete'], check), | |||
|
2660 | ## the following sets does not result in any file changes | |||
|
2661 | # File with no modification | |||
|
2662 | (clean, actions['noop'], discard), | |||
|
2663 | # Existing file, not tracked anywhere | |||
|
2664 | (unknown, actions['unknown'], discard), | |||
2396 | ) |
|
2665 | ) | |
2397 |
|
2666 | |||
|
2667 | needdata = ('revert', 'add', 'undelete') | |||
|
2668 | _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata]) | |||
|
2669 | ||||
|
2670 | wctx = repo[None] | |||
2398 | for abs, (rel, exact) in sorted(names.items()): |
|
2671 | for abs, (rel, exact) in sorted(names.items()): | |
2399 | # hash on file in target manifest (or None if missing from target) |
|
|||
2400 | mfentry = mf.get(abs) |
|
|||
2401 | # target file to be touch on disk (relative to cwd) |
|
2672 | # target file to be touch on disk (relative to cwd) | |
2402 | target = repo.wjoin(abs) |
|
2673 | target = repo.wjoin(abs) | |
2403 | def handle(xlist, dobackup): |
|
|||
2404 | xlist[0].append(abs) |
|
|||
2405 | if (dobackup and not opts.get('no_backup') and |
|
|||
2406 | os.path.lexists(target) and |
|
|||
2407 | abs in ctx and repo[None][abs].cmp(ctx[abs])): |
|
|||
2408 | bakname = "%s.orig" % rel |
|
|||
2409 | ui.note(_('saving current version of %s as %s\n') % |
|
|||
2410 | (rel, bakname)) |
|
|||
2411 | if not opts.get('dry_run'): |
|
|||
2412 | util.rename(target, bakname) |
|
|||
2413 | if ui.verbose or not exact: |
|
|||
2414 | msg = xlist[1] |
|
|||
2415 | if not isinstance(msg, basestring): |
|
|||
2416 | msg = msg(abs) |
|
|||
2417 | ui.status(msg % rel) |
|
|||
2418 | # search the entry in the dispatch table. |
|
2674 | # search the entry in the dispatch table. | |
2419 |
# if the file is in any of th |
|
2675 | # if the file is in any of these sets, it was touched in the working | |
2420 | # directory parent and we are sure it needs to be reverted. |
|
2676 | # directory parent and we are sure it needs to be reverted. | |
2421 |
for table, |
|
2677 | for table, (xlist, msg), dobackup in disptable: | |
2422 | if abs not in table: |
|
2678 | if abs not in table: | |
2423 | continue |
|
2679 | continue | |
2424 | # file has changed in dirstate |
|
2680 | if xlist is not None: | |
2425 | if mfentry: |
|
2681 | xlist.append(abs) | |
2426 | handle(*hit) |
|
2682 | if dobackup and (backup <= dobackup | |
2427 | elif miss[0] is not None: |
|
2683 | or wctx[abs].cmp(ctx[abs])): | |
2428 | handle(*miss) |
|
2684 | bakname = "%s.orig" % rel | |
|
2685 | ui.note(_('saving current version of %s as %s\n') % | |||
|
2686 | (rel, bakname)) | |||
|
2687 | if not opts.get('dry_run'): | |||
|
2688 | util.rename(target, bakname) | |||
|
2689 | if ui.verbose or not exact: | |||
|
2690 | if not isinstance(msg, basestring): | |||
|
2691 | msg = msg(abs) | |||
|
2692 | ui.status(msg % rel) | |||
|
2693 | elif exact: | |||
|
2694 | ui.warn(msg % rel) | |||
2429 | break |
|
2695 | break | |
2430 | else: |
|
2696 | ||
2431 | # Not touched in current dirstate. |
|
|||
2432 |
|
||||
2433 | # file is unknown in parent, restore older version or ignore. |
|
|||
2434 | if abs not in repo.dirstate: |
|
|||
2435 | if mfentry: |
|
|||
2436 | handle(actions['add'], True) |
|
|||
2437 | elif exact: |
|
|||
2438 | ui.warn(_('file not managed: %s\n') % rel) |
|
|||
2439 | continue |
|
|||
2440 |
|
||||
2441 | # parent is target, no changes mean no changes |
|
|||
2442 | if node == parent: |
|
|||
2443 | if exact: |
|
|||
2444 | ui.warn(_('no changes needed to %s\n') % rel) |
|
|||
2445 | continue |
|
|||
2446 | # no change in dirstate but parent and target may differ |
|
|||
2447 | if pmf is None: |
|
|||
2448 | # only need parent manifest in this unlikely case, |
|
|||
2449 | # so do not read by default |
|
|||
2450 | pmf = repo[parent].manifest() |
|
|||
2451 | if abs in pmf and mfentry: |
|
|||
2452 | # if version of file is same in parent and target |
|
|||
2453 | # manifests, do nothing |
|
|||
2454 | if (pmf[abs] != mfentry or |
|
|||
2455 | pmf.flags(abs) != mf.flags(abs)): |
|
|||
2456 | handle(actions['revert'], False) |
|
|||
2457 | else: |
|
|||
2458 | handle(actions['remove'], False) |
|
|||
2459 |
|
2697 | |||
2460 | if not opts.get('dry_run'): |
|
2698 | if not opts.get('dry_run'): | |
2461 | _performrevert(repo, parents, ctx, actions) |
|
2699 | _performrevert(repo, parents, ctx, actions) | |
2462 |
|
2700 | |||
|
2701 | # get the list of subrepos that must be reverted | |||
|
2702 | subrepomatch = scmutil.match(ctx, pats, opts) | |||
|
2703 | targetsubs = sorted(s for s in ctx.substate if subrepomatch(s)) | |||
|
2704 | ||||
2463 | if targetsubs: |
|
2705 | if targetsubs: | |
2464 | # Revert the subrepos on the revert list |
|
2706 | # Revert the subrepos on the revert list | |
2465 | for sub in targetsubs: |
|
2707 | for sub in targetsubs: | |
@@ -2467,6 +2709,10 b' def revert(ui, repo, ctx, parents, *pats' | |||||
2467 | finally: |
|
2709 | finally: | |
2468 | wlock.release() |
|
2710 | wlock.release() | |
2469 |
|
2711 | |||
|
2712 | def _revertprefetch(repo, ctx, *files): | |||
|
2713 | """Let extension changing the storage layer prefetch content""" | |||
|
2714 | pass | |||
|
2715 | ||||
2470 | def _performrevert(repo, parents, ctx, actions): |
|
2716 | def _performrevert(repo, parents, ctx, actions): | |
2471 | """function that actually perform all the actions computed for revert |
|
2717 | """function that actually perform all the actions computed for revert | |
2472 |
|
2718 | |||
@@ -2482,15 +2728,14 b' def _performrevert(repo, parents, ctx, a' | |||||
2482 | repo.wwrite(f, fc.data(), fc.flags()) |
|
2728 | repo.wwrite(f, fc.data(), fc.flags()) | |
2483 |
|
2729 | |||
2484 | audit_path = pathutil.pathauditor(repo.root) |
|
2730 | audit_path = pathutil.pathauditor(repo.root) | |
|
2731 | for f in actions['forget'][0]: | |||
|
2732 | repo.dirstate.drop(f) | |||
2485 | for f in actions['remove'][0]: |
|
2733 | for f in actions['remove'][0]: | |
2486 | if repo.dirstate[f] == 'a': |
|
|||
2487 | repo.dirstate.drop(f) |
|
|||
2488 | continue |
|
|||
2489 | audit_path(f) |
|
2734 | audit_path(f) | |
2490 | try: |
|
2735 | util.unlinkpath(repo.wjoin(f)) | |
2491 | util.unlinkpath(repo.wjoin(f)) |
|
2736 | repo.dirstate.remove(f) | |
2492 | except OSError: |
|
2737 | for f in actions['drop'][0]: | |
2493 | pass |
|
2738 | audit_path(f) | |
2494 | repo.dirstate.remove(f) |
|
2739 | repo.dirstate.remove(f) | |
2495 |
|
2740 | |||
2496 | normal = None |
|
2741 | normal = None |
This diff has been collapsed as it changes many lines, (770 lines changed) Show them Hide them | |||||
@@ -9,7 +9,7 b' from node import hex, bin, nullid, nullr' | |||||
9 | from lock import release |
|
9 | from lock import release | |
10 | from i18n import _ |
|
10 | from i18n import _ | |
11 | import os, re, difflib, time, tempfile, errno, shlex |
|
11 | import os, re, difflib, time, tempfile, errno, shlex | |
12 | import sys |
|
12 | import sys, socket | |
13 | import hg, scmutil, util, revlog, copies, error, bookmarks |
|
13 | import hg, scmutil, util, revlog, copies, error, bookmarks | |
14 | import patch, help, encoding, templatekw, discovery |
|
14 | import patch, help, encoding, templatekw, discovery | |
15 | import archival, changegroup, cmdutil, hbisect |
|
15 | import archival, changegroup, cmdutil, hbisect | |
@@ -18,10 +18,11 b' import extensions' | |||||
18 | from hgweb import server as hgweb_server |
|
18 | from hgweb import server as hgweb_server | |
19 | import merge as mergemod |
|
19 | import merge as mergemod | |
20 | import minirst, revset, fileset |
|
20 | import minirst, revset, fileset | |
21 | import dagparser, context, simplemerge, graphmod |
|
21 | import dagparser, context, simplemerge, graphmod, copies | |
22 | import random |
|
22 | import random | |
23 | import setdiscovery, treediscovery, dagutil, pvec, localrepo |
|
23 | import setdiscovery, treediscovery, dagutil, pvec, localrepo | |
24 | import phases, obsolete, exchange |
|
24 | import phases, obsolete, exchange | |
|
25 | import ui as uimod | |||
25 |
|
26 | |||
26 | table = {} |
|
27 | table = {} | |
27 |
|
28 | |||
@@ -101,6 +102,12 b' commitopts2 = [' | |||||
101 | _('record the specified user as committer'), _('USER')), |
|
102 | _('record the specified user as committer'), _('USER')), | |
102 | ] |
|
103 | ] | |
103 |
|
104 | |||
|
105 | # hidden for now | |||
|
106 | formatteropts = [ | |||
|
107 | ('T', 'template', '', | |||
|
108 | _('display with template (DEPRECATED)'), _('TEMPLATE')), | |||
|
109 | ] | |||
|
110 | ||||
104 | templateopts = [ |
|
111 | templateopts = [ | |
105 | ('', 'style', '', |
|
112 | ('', 'style', '', | |
106 | _('display using template map file (DEPRECATED)'), _('STYLE')), |
|
113 | _('display using template map file (DEPRECATED)'), _('STYLE')), | |
@@ -241,7 +248,7 b' def addremove(ui, repo, *pats, **opts):' | |||||
241 | ('n', 'number', None, _('list the revision number (default)')), |
|
248 | ('n', 'number', None, _('list the revision number (default)')), | |
242 | ('c', 'changeset', None, _('list the changeset')), |
|
249 | ('c', 'changeset', None, _('list the changeset')), | |
243 | ('l', 'line-number', None, _('show line number at the first appearance')) |
|
250 | ('l', 'line-number', None, _('show line number at the first appearance')) | |
244 | ] + diffwsopts + walkopts, |
|
251 | ] + diffwsopts + walkopts + formatteropts, | |
245 | _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'), |
|
252 | _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'), | |
246 | inferrepo=True) |
|
253 | inferrepo=True) | |
247 | def annotate(ui, repo, *pats, **opts): |
|
254 | def annotate(ui, repo, *pats, **opts): | |
@@ -260,26 +267,26 b' def annotate(ui, repo, *pats, **opts):' | |||||
260 |
|
267 | |||
261 | Returns 0 on success. |
|
268 | Returns 0 on success. | |
262 | """ |
|
269 | """ | |
|
270 | if not pats: | |||
|
271 | raise util.Abort(_('at least one filename or pattern is required')) | |||
|
272 | ||||
263 | if opts.get('follow'): |
|
273 | if opts.get('follow'): | |
264 | # --follow is deprecated and now just an alias for -f/--file |
|
274 | # --follow is deprecated and now just an alias for -f/--file | |
265 | # to mimic the behavior of Mercurial before version 1.5 |
|
275 | # to mimic the behavior of Mercurial before version 1.5 | |
266 | opts['file'] = True |
|
276 | opts['file'] = True | |
267 |
|
277 | |||
|
278 | fm = ui.formatter('annotate', opts) | |||
268 | datefunc = ui.quiet and util.shortdate or util.datestr |
|
279 | datefunc = ui.quiet and util.shortdate or util.datestr | |
269 | getdate = util.cachefunc(lambda x: datefunc(x[0].date())) |
|
280 | hexfn = fm.hexfunc | |
270 |
|
281 | |||
271 | if not pats: |
|
282 | opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser), | |
272 | raise util.Abort(_('at least one filename or pattern is required')) |
|
283 | ('number', ' ', lambda x: x[0].rev(), str), | |
273 |
|
284 | ('changeset', ' ', lambda x: hexfn(x[0].node()), str), | ||
274 | hexfn = ui.debugflag and hex or short |
|
285 | ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)), | |
275 |
|
286 | ('file', ' ', lambda x: x[0].path(), str), | ||
276 | opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())), |
|
287 | ('line_number', ':', lambda x: x[1], str), | |
277 | ('number', ' ', lambda x: str(x[0].rev())), |
|
|||
278 | ('changeset', ' ', lambda x: hexfn(x[0].node())), |
|
|||
279 | ('date', ' ', getdate), |
|
|||
280 | ('file', ' ', lambda x: x[0].path()), |
|
|||
281 | ('line_number', ':', lambda x: str(x[1])), |
|
|||
282 | ] |
|
288 | ] | |
|
289 | fieldnamemap = {'number': 'rev', 'changeset': 'node'} | |||
283 |
|
290 | |||
284 | if (not opts.get('user') and not opts.get('changeset') |
|
291 | if (not opts.get('user') and not opts.get('changeset') | |
285 | and not opts.get('date') and not opts.get('file')): |
|
292 | and not opts.get('date') and not opts.get('file')): | |
@@ -289,8 +296,17 b' def annotate(ui, repo, *pats, **opts):' | |||||
289 | if linenumber and (not opts.get('changeset')) and (not opts.get('number')): |
|
296 | if linenumber and (not opts.get('changeset')) and (not opts.get('number')): | |
290 | raise util.Abort(_('at least one of -n/-c is required for -l')) |
|
297 | raise util.Abort(_('at least one of -n/-c is required for -l')) | |
291 |
|
298 | |||
292 | funcmap = [(func, sep) for op, sep, func in opmap if opts.get(op)] |
|
299 | if fm: | |
|
300 | def makefunc(get, fmt): | |||
|
301 | return get | |||
|
302 | else: | |||
|
303 | def makefunc(get, fmt): | |||
|
304 | return lambda x: fmt(get(x)) | |||
|
305 | funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap | |||
|
306 | if opts.get(op)] | |||
293 | funcmap[0] = (funcmap[0][0], '') # no separator in front of first column |
|
307 | funcmap[0] = (funcmap[0][0], '') # no separator in front of first column | |
|
308 | fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap | |||
|
309 | if opts.get(op)) | |||
294 |
|
310 | |||
295 | def bad(x, y): |
|
311 | def bad(x, y): | |
296 | raise util.Abort("%s: %s" % (x, y)) |
|
312 | raise util.Abort("%s: %s" % (x, y)) | |
@@ -303,27 +319,34 b' def annotate(ui, repo, *pats, **opts):' | |||||
303 | for abs in ctx.walk(m): |
|
319 | for abs in ctx.walk(m): | |
304 | fctx = ctx[abs] |
|
320 | fctx = ctx[abs] | |
305 | if not opts.get('text') and util.binary(fctx.data()): |
|
321 | if not opts.get('text') and util.binary(fctx.data()): | |
306 |
|
|
322 | fm.plain(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs)) | |
307 | continue |
|
323 | continue | |
308 |
|
324 | |||
309 | lines = fctx.annotate(follow=follow, linenumber=linenumber, |
|
325 | lines = fctx.annotate(follow=follow, linenumber=linenumber, | |
310 | diffopts=diffopts) |
|
326 | diffopts=diffopts) | |
|
327 | formats = [] | |||
311 | pieces = [] |
|
328 | pieces = [] | |
312 |
|
329 | |||
313 | for f, sep in funcmap: |
|
330 | for f, sep in funcmap: | |
314 | l = [f(n) for n, dummy in lines] |
|
331 | l = [f(n) for n, dummy in lines] | |
315 | if l: |
|
332 | if l: | |
316 | sized = [(x, encoding.colwidth(x)) for x in l] |
|
333 | if fm: | |
317 |
|
|
334 | formats.append(['%s' for x in l]) | |
318 | pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x) |
|
335 | else: | |
319 |
|
|
336 | sizes = [encoding.colwidth(x) for x in l] | |
320 |
|
337 | ml = max(sizes) | ||
321 | if pieces: |
|
338 | formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes]) | |
322 | for p, l in zip(zip(*pieces), lines): |
|
339 | pieces.append(l) | |
323 | ui.write("%s: %s" % ("".join(p), l[1])) |
|
340 | ||
324 |
|
341 | for f, p, l in zip(zip(*formats), zip(*pieces), lines): | ||
325 | if lines and not lines[-1][1].endswith('\n'): |
|
342 | fm.startitem() | |
326 | ui.write('\n') |
|
343 | fm.write(fields, "".join(f), *p) | |
|
344 | fm.write('line', ": %s", l[1]) | |||
|
345 | ||||
|
346 | if lines and not lines[-1][1].endswith('\n'): | |||
|
347 | fm.plain('\n') | |||
|
348 | ||||
|
349 | fm.end() | |||
327 |
|
350 | |||
328 | @command('archive', |
|
351 | @command('archive', | |
329 | [('', 'no-decode', None, _('do not pass files through decoders')), |
|
352 | [('', 'no-decode', None, _('do not pass files through decoders')), | |
@@ -455,7 +478,7 b' def backout(ui, repo, node=None, rev=Non' | |||||
455 | node = scmutil.revsingle(repo, rev).node() |
|
478 | node = scmutil.revsingle(repo, rev).node() | |
456 |
|
479 | |||
457 | op1, op2 = repo.dirstate.parents() |
|
480 | op1, op2 = repo.dirstate.parents() | |
458 |
if |
|
481 | if not repo.changelog.isancestor(node, op1): | |
459 | raise util.Abort(_('cannot backout change that is not an ancestor')) |
|
482 | raise util.Abort(_('cannot backout change that is not an ancestor')) | |
460 |
|
483 | |||
461 | p1, p2 = repo.changelog.parents(node) |
|
484 | p1, p2 = repo.changelog.parents(node) | |
@@ -484,9 +507,11 b' def backout(ui, repo, node=None, rev=Non' | |||||
484 | try: |
|
507 | try: | |
485 | ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), |
|
508 | ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), | |
486 | 'backout') |
|
509 | 'backout') | |
|
510 | repo.dirstate.beginparentchange() | |||
487 | stats = mergemod.update(repo, parent, True, True, False, |
|
511 | stats = mergemod.update(repo, parent, True, True, False, | |
488 | node, False) |
|
512 | node, False) | |
489 | repo.setparents(op1, op2) |
|
513 | repo.setparents(op1, op2) | |
|
514 | repo.dirstate.endparentchange() | |||
490 | hg._showstats(repo, stats) |
|
515 | hg._showstats(repo, stats) | |
491 | if stats[3]: |
|
516 | if stats[3]: | |
492 | repo.ui.status(_("use 'hg resolve' to retry unresolved " |
|
517 | repo.ui.status(_("use 'hg resolve' to retry unresolved " | |
@@ -505,11 +530,12 b' def backout(ui, repo, node=None, rev=Non' | |||||
505 |
|
530 | |||
506 |
|
531 | |||
507 | def commitfunc(ui, repo, message, match, opts): |
|
532 | def commitfunc(ui, repo, message, match, opts): | |
508 | e = cmdutil.getcommiteditor(**opts) |
|
533 | editform = 'backout' | |
|
534 | e = cmdutil.getcommiteditor(editform=editform, **opts) | |||
509 | if not message: |
|
535 | if not message: | |
510 | # we don't translate commit messages |
|
536 | # we don't translate commit messages | |
511 | message = "Backed out changeset %s" % short(node) |
|
537 | message = "Backed out changeset %s" % short(node) | |
512 | e = cmdutil.getcommiteditor(edit=True) |
|
538 | e = cmdutil.getcommiteditor(edit=True, editform=editform) | |
513 | return repo.commit(message, opts.get('user'), opts.get('date'), |
|
539 | return repo.commit(message, opts.get('user'), opts.get('date'), | |
514 | match, editor=e) |
|
540 | match, editor=e) | |
515 | newnode = cmdutil.commit(ui, repo, commitfunc, [], opts) |
|
541 | newnode = cmdutil.commit(ui, repo, commitfunc, [], opts) | |
@@ -808,7 +834,8 b' def bisect(ui, repo, rev=None, extra=Non' | |||||
808 | ('r', 'rev', '', _('revision'), _('REV')), |
|
834 | ('r', 'rev', '', _('revision'), _('REV')), | |
809 | ('d', 'delete', False, _('delete a given bookmark')), |
|
835 | ('d', 'delete', False, _('delete a given bookmark')), | |
810 | ('m', 'rename', '', _('rename a given bookmark'), _('NAME')), |
|
836 | ('m', 'rename', '', _('rename a given bookmark'), _('NAME')), | |
811 |
('i', 'inactive', False, _('mark a bookmark inactive')) |
|
837 | ('i', 'inactive', False, _('mark a bookmark inactive')), | |
|
838 | ] + formatteropts, | |||
812 | _('hg bookmarks [OPTIONS]... [NAME]...')) |
|
839 | _('hg bookmarks [OPTIONS]... [NAME]...')) | |
813 | def bookmark(ui, repo, *names, **opts): |
|
840 | def bookmark(ui, repo, *names, **opts): | |
814 | '''create a new bookmark or list existing bookmarks |
|
841 | '''create a new bookmark or list existing bookmarks | |
@@ -966,25 +993,28 b' def bookmark(ui, repo, *names, **opts):' | |||||
966 | finally: |
|
993 | finally: | |
967 | wlock.release() |
|
994 | wlock.release() | |
968 | else: # show bookmarks |
|
995 | else: # show bookmarks | |
969 | hexfn = ui.debugflag and hex or short |
|
996 | fm = ui.formatter('bookmarks', opts) | |
|
997 | hexfn = fm.hexfunc | |||
970 | marks = repo._bookmarks |
|
998 | marks = repo._bookmarks | |
971 | if len(marks) == 0: |
|
999 | if len(marks) == 0 and not fm: | |
972 | ui.status(_("no bookmarks set\n")) |
|
1000 | ui.status(_("no bookmarks set\n")) | |
973 | else: |
|
1001 | for bmark, n in sorted(marks.iteritems()): | |
974 | for bmark, n in sorted(marks.iteritems()): |
|
1002 | current = repo._bookmarkcurrent | |
975 | current = repo._bookmarkcurrent |
|
1003 | if bmark == current: | |
976 |
|
|
1004 | prefix, label = '*', 'bookmarks.current' | |
977 | prefix, label = '*', 'bookmarks.current' |
|
1005 | else: | |
978 |
|
|
1006 | prefix, label = ' ', '' | |
979 | prefix, label = ' ', '' |
|
1007 | ||
980 |
|
1008 | fm.startitem() | ||
981 |
|
|
1009 | if not ui.quiet: | |
982 |
|
|
1010 | fm.plain(' %s ' % prefix, label=label) | |
983 | else: |
|
1011 | fm.write('bookmark', '%s', bmark, label=label) | |
984 |
|
|
1012 | pad = " " * (25 - encoding.colwidth(bmark)) | |
985 | ui.write(" %s %s%s %d:%s\n" % ( |
|
1013 | fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s', | |
986 |
|
|
1014 | repo.changelog.rev(n), hexfn(n), label=label) | |
987 | label=label) |
|
1015 | fm.data(active=(bmark == current)) | |
|
1016 | fm.plain('\n') | |||
|
1017 | fm.end() | |||
988 |
|
1018 | |||
989 | @command('branch', |
|
1019 | @command('branch', | |
990 | [('f', 'force', None, |
|
1020 | [('f', 'force', None, | |
@@ -1048,9 +1078,10 b' def branch(ui, repo, label=None, **opts)' | |||||
1048 |
|
1078 | |||
1049 | @command('branches', |
|
1079 | @command('branches', | |
1050 | [('a', 'active', False, _('show only branches that have unmerged heads')), |
|
1080 | [('a', 'active', False, _('show only branches that have unmerged heads')), | |
1051 |
('c', 'closed', False, _('show normal and closed branches')) |
|
1081 | ('c', 'closed', False, _('show normal and closed branches')), | |
|
1082 | ] + formatteropts, | |||
1052 | _('[-ac]')) |
|
1083 | _('[-ac]')) | |
1053 | def branches(ui, repo, active=False, closed=False): |
|
1084 | def branches(ui, repo, active=False, closed=False, **opts): | |
1054 | """list repository named branches |
|
1085 | """list repository named branches | |
1055 |
|
1086 | |||
1056 | List the repository's named branches, indicating which ones are |
|
1087 | List the repository's named branches, indicating which ones are | |
@@ -1065,7 +1096,8 b' def branches(ui, repo, active=False, clo' | |||||
1065 | Returns 0. |
|
1096 | Returns 0. | |
1066 | """ |
|
1097 | """ | |
1067 |
|
1098 | |||
1068 | hexfunc = ui.debugflag and hex or short |
|
1099 | fm = ui.formatter('branches', opts) | |
|
1100 | hexfunc = fm.hexfunc | |||
1069 |
|
1101 | |||
1070 | allheads = set(repo.heads()) |
|
1102 | allheads = set(repo.heads()) | |
1071 | branches = [] |
|
1103 | branches = [] | |
@@ -1076,28 +1108,35 b' def branches(ui, repo, active=False, clo' | |||||
1076 | reverse=True) |
|
1108 | reverse=True) | |
1077 |
|
1109 | |||
1078 | for tag, ctx, isactive, isopen in branches: |
|
1110 | for tag, ctx, isactive, isopen in branches: | |
1079 |
if |
|
1111 | if active and not isactive: | |
1080 |
|
|
1112 | continue | |
1081 | label = 'branches.active' |
|
1113 | if isactive: | |
1082 | notice = '' |
|
1114 | label = 'branches.active' | |
1083 |
|
|
1115 | notice = '' | |
1084 |
|
|
1116 | elif not isopen: | |
1085 |
|
|
1117 | if not closed: | |
1086 | label = 'branches.closed' |
|
1118 | continue | |
1087 |
|
|
1119 | label = 'branches.closed' | |
1088 |
|
|
1120 | notice = _(' (closed)') | |
1089 | label = 'branches.inactive' |
|
1121 | else: | |
1090 |
|
|
1122 | label = 'branches.inactive' | |
1091 | if tag == repo.dirstate.branch(): |
|
1123 | notice = _(' (inactive)') | |
1092 | label = 'branches.current' |
|
1124 | current = (tag == repo.dirstate.branch()) | |
1093 | rev = str(ctx.rev()).rjust(31 - encoding.colwidth(tag)) |
|
1125 | if current: | |
1094 | rev = ui.label('%s:%s' % (rev, hexfunc(ctx.node())), |
|
1126 | label = 'branches.current' | |
1095 | 'log.changeset changeset.%s' % ctx.phasestr()) |
|
1127 | ||
1096 | labeledtag = ui.label(tag, label) |
|
1128 | fm.startitem() | |
1097 | if ui.quiet: |
|
1129 | fm.write('branch', '%s', tag, label=label) | |
1098 | ui.write("%s\n" % labeledtag) |
|
1130 | rev = ctx.rev() | |
1099 | else: |
|
1131 | padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0) | |
1100 | ui.write("%s %s%s\n" % (labeledtag, rev, notice)) |
|
1132 | fmt = ' ' * padsize + ' %d:%s' | |
|
1133 | fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()), | |||
|
1134 | label='log.changeset changeset.%s' % ctx.phasestr()) | |||
|
1135 | fm.data(active=isactive, closed=not isopen, current=current) | |||
|
1136 | if not ui.quiet: | |||
|
1137 | fm.plain(notice) | |||
|
1138 | fm.plain('\n') | |||
|
1139 | fm.end() | |||
1101 |
|
1140 | |||
1102 | @command('bundle', |
|
1141 | @command('bundle', | |
1103 | [('f', 'force', None, _('run even when the destination is unrelated')), |
|
1142 | [('f', 'force', None, _('run even when the destination is unrelated')), | |
@@ -1159,8 +1198,8 b' def bundle(ui, repo, fname, dest=None, *' | |||||
1159 | "a destination")) |
|
1198 | "a destination")) | |
1160 | common = [repo.lookup(rev) for rev in base] |
|
1199 | common = [repo.lookup(rev) for rev in base] | |
1161 | heads = revs and map(repo.lookup, revs) or revs |
|
1200 | heads = revs and map(repo.lookup, revs) or revs | |
1162 |
cg = changegroup.get |
|
1201 | cg = changegroup.getchangegroup(repo, 'bundle', heads=heads, | |
1163 | bundlecaps=bundlecaps) |
|
1202 | common=common, bundlecaps=bundlecaps) | |
1164 | outgoing = None |
|
1203 | outgoing = None | |
1165 | else: |
|
1204 | else: | |
1166 | dest = ui.expandpath(dest or 'default-push', dest or 'default') |
|
1205 | dest = ui.expandpath(dest or 'default-push', dest or 'default') | |
@@ -1172,7 +1211,8 b' def bundle(ui, repo, fname, dest=None, *' | |||||
1172 | onlyheads=heads, |
|
1211 | onlyheads=heads, | |
1173 | force=opts.get('force'), |
|
1212 | force=opts.get('force'), | |
1174 | portable=True) |
|
1213 | portable=True) | |
1175 |
cg = changegroup.getlocal |
|
1214 | cg = changegroup.getlocalchangegroup(repo, 'bundle', outgoing, | |
|
1215 | bundlecaps) | |||
1176 | if not cg: |
|
1216 | if not cg: | |
1177 | scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded) |
|
1217 | scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded) | |
1178 | return 1 |
|
1218 | return 1 | |
@@ -1385,9 +1425,6 b' def commit(ui, repo, *pats, **opts):' | |||||
1385 | # Let --subrepos on the command line override config setting. |
|
1425 | # Let --subrepos on the command line override config setting. | |
1386 | ui.setconfig('ui', 'commitsubrepos', True, 'commit') |
|
1426 | ui.setconfig('ui', 'commitsubrepos', True, 'commit') | |
1387 |
|
1427 | |||
1388 | # Save this for restoring it later |
|
|||
1389 | oldcommitphase = ui.config('phases', 'new-commit') |
|
|||
1390 |
|
||||
1391 | cmdutil.checkunfinished(repo, commit=True) |
|
1428 | cmdutil.checkunfinished(repo, commit=True) | |
1392 |
|
1429 | |||
1393 | branch = repo[None].branch() |
|
1430 | branch = repo[None].branch() | |
@@ -1409,11 +1446,12 b' def commit(ui, repo, *pats, **opts):' | |||||
1409 | raise util.Abort(_('cannot amend with ui.commitsubrepos enabled')) |
|
1446 | raise util.Abort(_('cannot amend with ui.commitsubrepos enabled')) | |
1410 |
|
1447 | |||
1411 | old = repo['.'] |
|
1448 | old = repo['.'] | |
1412 | if old.phase() == phases.public: |
|
1449 | if not old.mutable(): | |
1413 | raise util.Abort(_('cannot amend public changesets')) |
|
1450 | raise util.Abort(_('cannot amend public changesets')) | |
1414 | if len(repo[None].parents()) > 1: |
|
1451 | if len(repo[None].parents()) > 1: | |
1415 | raise util.Abort(_('cannot amend while merging')) |
|
1452 | raise util.Abort(_('cannot amend while merging')) | |
1416 | if (not obsolete._enabled) and old.children(): |
|
1453 | allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt) | |
|
1454 | if not allowunstable and old.children(): | |||
1417 | raise util.Abort(_('cannot amend changeset with children')) |
|
1455 | raise util.Abort(_('cannot amend changeset with children')) | |
1418 |
|
1456 | |||
1419 | # commitfunc is used only for temporary amend commit by cmdutil.amend |
|
1457 | # commitfunc is used only for temporary amend commit by cmdutil.amend | |
@@ -1441,21 +1479,24 b' def commit(ui, repo, *pats, **opts):' | |||||
1441 | newmarks.write() |
|
1479 | newmarks.write() | |
1442 | else: |
|
1480 | else: | |
1443 | def commitfunc(ui, repo, message, match, opts): |
|
1481 | def commitfunc(ui, repo, message, match, opts): | |
|
1482 | backup = ui.backupconfig('phases', 'new-commit') | |||
|
1483 | baseui = repo.baseui | |||
|
1484 | basebackup = baseui.backupconfig('phases', 'new-commit') | |||
1444 | try: |
|
1485 | try: | |
1445 | if opts.get('secret'): |
|
1486 | if opts.get('secret'): | |
1446 | ui.setconfig('phases', 'new-commit', 'secret', 'commit') |
|
1487 | ui.setconfig('phases', 'new-commit', 'secret', 'commit') | |
1447 | # Propagate to subrepos |
|
1488 | # Propagate to subrepos | |
1448 |
|
|
1489 | baseui.setconfig('phases', 'new-commit', 'secret', 'commit') | |
1449 | 'commit') |
|
1490 | ||
1450 |
|
1491 | editform = cmdutil.mergeeditform(repo[None], 'commit.normal') | ||
|
1492 | editor = cmdutil.getcommiteditor(editform=editform, **opts) | |||
1451 | return repo.commit(message, opts.get('user'), opts.get('date'), |
|
1493 | return repo.commit(message, opts.get('user'), opts.get('date'), | |
1452 | match, |
|
1494 | match, | |
1453 |
editor= |
|
1495 | editor=editor, | |
1454 | extra=extra) |
|
1496 | extra=extra) | |
1455 | finally: |
|
1497 | finally: | |
1456 | ui.setconfig('phases', 'new-commit', oldcommitphase, 'commit') |
|
1498 | ui.restoreconfig(backup) | |
1457 |
repo.baseui.s |
|
1499 | repo.baseui.restoreconfig(basebackup) | |
1458 | 'commit') |
|
|||
1459 |
|
1500 | |||
1460 |
|
1501 | |||
1461 | node = cmdutil.commit(ui, repo, commitfunc, pats, opts) |
|
1502 | node = cmdutil.commit(ui, repo, commitfunc, pats, opts) | |
@@ -1519,22 +1560,16 b' def config(ui, repo, *values, **opts):' | |||||
1519 | if os.path.exists(f): |
|
1560 | if os.path.exists(f): | |
1520 | break |
|
1561 | break | |
1521 | else: |
|
1562 | else: | |
|
1563 | if opts.get('global'): | |||
|
1564 | samplehgrc = uimod.samplehgrcs['global'] | |||
|
1565 | elif opts.get('local'): | |||
|
1566 | samplehgrc = uimod.samplehgrcs['local'] | |||
|
1567 | else: | |||
|
1568 | samplehgrc = uimod.samplehgrcs['user'] | |||
|
1569 | ||||
1522 | f = paths[0] |
|
1570 | f = paths[0] | |
1523 | fp = open(f, "w") |
|
1571 | fp = open(f, "w") | |
1524 | fp.write( |
|
1572 | fp.write(samplehgrc) | |
1525 | '# example config (see "hg help config" for more info)\n' |
|
|||
1526 | '\n' |
|
|||
1527 | '[ui]\n' |
|
|||
1528 | '# name and email, e.g.\n' |
|
|||
1529 | '# username = Jane Doe <jdoe@example.com>\n' |
|
|||
1530 | 'username =\n' |
|
|||
1531 | '\n' |
|
|||
1532 | '[extensions]\n' |
|
|||
1533 | '# uncomment these lines to enable some popular extensions\n' |
|
|||
1534 | '# (see "hg help extensions" for more info)\n' |
|
|||
1535 | '# pager =\n' |
|
|||
1536 | '# progress =\n' |
|
|||
1537 | '# color =\n') |
|
|||
1538 | fp.close() |
|
1573 | fp.close() | |
1539 |
|
1574 | |||
1540 | editor = ui.geteditor() |
|
1575 | editor = ui.geteditor() | |
@@ -1912,8 +1947,8 b' def debugdag(ui, repo, file_=None, *revs' | |||||
1912 | revs = set((int(r) for r in revs)) |
|
1947 | revs = set((int(r) for r in revs)) | |
1913 | def events(): |
|
1948 | def events(): | |
1914 | for r in rlog: |
|
1949 | for r in rlog: | |
1915 |
yield 'n', (r, list |
|
1950 | yield 'n', (r, list(p for p in rlog.parentrevs(r) | |
1916 |
if p != -1)) |
|
1951 | if p != -1)) | |
1917 | if r in revs: |
|
1952 | if r in revs: | |
1918 | yield 'l', (r, "r%i" % r) |
|
1953 | yield 'l', (r, "r%i" % r) | |
1919 | elif repo: |
|
1954 | elif repo: | |
@@ -1932,8 +1967,8 b' def debugdag(ui, repo, file_=None, *revs' | |||||
1932 | if newb != b: |
|
1967 | if newb != b: | |
1933 | yield 'a', newb |
|
1968 | yield 'a', newb | |
1934 | b = newb |
|
1969 | b = newb | |
1935 |
yield 'n', (r, list |
|
1970 | yield 'n', (r, list(p for p in cl.parentrevs(r) | |
1936 |
if p != -1)) |
|
1971 | if p != -1)) | |
1937 | if tags: |
|
1972 | if tags: | |
1938 | ls = labels.get(r) |
|
1973 | ls = labels.get(r) | |
1939 | if ls: |
|
1974 | if ls: | |
@@ -2231,7 +2266,7 b' def debuginstall(ui):' | |||||
2231 |
|
2266 | |||
2232 | # templates |
|
2267 | # templates | |
2233 | import templater |
|
2268 | import templater | |
2234 | p = templater.templatepath() |
|
2269 | p = templater.templatepaths() | |
2235 | ui.status(_("checking templates (%s)...\n") % ' '.join(p)) |
|
2270 | ui.status(_("checking templates (%s)...\n") % ' '.join(p)) | |
2236 | if p: |
|
2271 | if p: | |
2237 | m = templater.templatepath("map-cmdline.default") |
|
2272 | m = templater.templatepath("map-cmdline.default") | |
@@ -2313,8 +2348,83 b' def debuglabelcomplete(ui, repo, *args):' | |||||
2313 | ui.write('\n'.join(sorted(completions))) |
|
2348 | ui.write('\n'.join(sorted(completions))) | |
2314 | ui.write('\n') |
|
2349 | ui.write('\n') | |
2315 |
|
2350 | |||
|
2351 | @command('debuglocks', | |||
|
2352 | [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')), | |||
|
2353 | ('W', 'force-wlock', None, | |||
|
2354 | _('free the working state lock (DANGEROUS)'))], | |||
|
2355 | _('')) | |||
|
2356 | def debuglocks(ui, repo, **opts): | |||
|
2357 | """show or modify state of locks | |||
|
2358 | ||||
|
2359 | By default, this command will show which locks are held. This | |||
|
2360 | includes the user and process holding the lock, the amount of time | |||
|
2361 | the lock has been held, and the machine name where the process is | |||
|
2362 | running if it's not local. | |||
|
2363 | ||||
|
2364 | Locks protect the integrity of Mercurial's data, so should be | |||
|
2365 | treated with care. System crashes or other interruptions may cause | |||
|
2366 | locks to not be properly released, though Mercurial will usually | |||
|
2367 | detect and remove such stale locks automatically. | |||
|
2368 | ||||
|
2369 | However, detecting stale locks may not always be possible (for | |||
|
2370 | instance, on a shared filesystem). Removing locks may also be | |||
|
2371 | blocked by filesystem permissions. | |||
|
2372 | ||||
|
2373 | Returns 0 if no locks are held. | |||
|
2374 | ||||
|
2375 | """ | |||
|
2376 | ||||
|
2377 | if opts.get('force_lock'): | |||
|
2378 | repo.svfs.unlink('lock') | |||
|
2379 | if opts.get('force_wlock'): | |||
|
2380 | repo.vfs.unlink('wlock') | |||
|
2381 | if opts.get('force_lock') or opts.get('force_lock'): | |||
|
2382 | return 0 | |||
|
2383 | ||||
|
2384 | now = time.time() | |||
|
2385 | held = 0 | |||
|
2386 | ||||
|
2387 | def report(vfs, name, method): | |||
|
2388 | # this causes stale locks to get reaped for more accurate reporting | |||
|
2389 | try: | |||
|
2390 | l = method(False) | |||
|
2391 | except error.LockHeld: | |||
|
2392 | l = None | |||
|
2393 | ||||
|
2394 | if l: | |||
|
2395 | l.release() | |||
|
2396 | else: | |||
|
2397 | try: | |||
|
2398 | stat = repo.svfs.lstat(name) | |||
|
2399 | age = now - stat.st_mtime | |||
|
2400 | user = util.username(stat.st_uid) | |||
|
2401 | locker = vfs.readlock(name) | |||
|
2402 | if ":" in locker: | |||
|
2403 | host, pid = locker.split(':') | |||
|
2404 | if host == socket.gethostname(): | |||
|
2405 | locker = 'user %s, process %s' % (user, pid) | |||
|
2406 | else: | |||
|
2407 | locker = 'user %s, process %s, host %s' \ | |||
|
2408 | % (user, pid, host) | |||
|
2409 | ui.write("%-6s %s (%ds)\n" % (name + ":", locker, age)) | |||
|
2410 | return 1 | |||
|
2411 | except OSError, e: | |||
|
2412 | if e.errno != errno.ENOENT: | |||
|
2413 | raise | |||
|
2414 | ||||
|
2415 | ui.write("%-6s free\n" % (name + ":")) | |||
|
2416 | return 0 | |||
|
2417 | ||||
|
2418 | held += report(repo.svfs, "lock", repo.lock) | |||
|
2419 | held += report(repo.vfs, "wlock", repo.wlock) | |||
|
2420 | ||||
|
2421 | return held | |||
|
2422 | ||||
2316 | @command('debugobsolete', |
|
2423 | @command('debugobsolete', | |
2317 | [('', 'flags', 0, _('markers flag')), |
|
2424 | [('', 'flags', 0, _('markers flag')), | |
|
2425 | ('', 'record-parents', False, | |||
|
2426 | _('record parent information for the precursor')), | |||
|
2427 | ('r', 'rev', [], _('display markers relevant to REV')), | |||
2318 | ] + commitopts2, |
|
2428 | ] + commitopts2, | |
2319 | _('[OBSOLETED [REPLACEMENT] [REPL... ]')) |
|
2429 | _('[OBSOLETED [REPLACEMENT] [REPL... ]')) | |
2320 | def debugobsolete(ui, repo, precursor=None, *successors, **opts): |
|
2430 | def debugobsolete(ui, repo, precursor=None, *successors, **opts): | |
@@ -2336,9 +2446,9 b' def debugobsolete(ui, repo, precursor=No' | |||||
2336 | 'node identifiers') |
|
2446 | 'node identifiers') | |
2337 |
|
2447 | |||
2338 | if precursor is not None: |
|
2448 | if precursor is not None: | |
|
2449 | if opts['rev']: | |||
|
2450 | raise util.Abort('cannot select revision when creating marker') | |||
2339 | metadata = {} |
|
2451 | metadata = {} | |
2340 | if 'date' in opts: |
|
|||
2341 | metadata['date'] = opts['date'] |
|
|||
2342 | metadata['user'] = opts['user'] or ui.username() |
|
2452 | metadata['user'] = opts['user'] or ui.username() | |
2343 | succs = tuple(parsenodeid(succ) for succ in successors) |
|
2453 | succs = tuple(parsenodeid(succ) for succ in successors) | |
2344 | l = repo.lock() |
|
2454 | l = repo.lock() | |
@@ -2346,8 +2456,22 b' def debugobsolete(ui, repo, precursor=No' | |||||
2346 | tr = repo.transaction('debugobsolete') |
|
2456 | tr = repo.transaction('debugobsolete') | |
2347 | try: |
|
2457 | try: | |
2348 | try: |
|
2458 | try: | |
2349 | repo.obsstore.create(tr, parsenodeid(precursor), succs, |
|
2459 | date = opts.get('date') | |
2350 | opts['flags'], metadata) |
|
2460 | if date: | |
|
2461 | date = util.parsedate(date) | |||
|
2462 | else: | |||
|
2463 | date = None | |||
|
2464 | prec = parsenodeid(precursor) | |||
|
2465 | parents = None | |||
|
2466 | if opts['record_parents']: | |||
|
2467 | if prec not in repo.unfiltered(): | |||
|
2468 | raise util.Abort('cannot used --record-parents on ' | |||
|
2469 | 'unknown changesets') | |||
|
2470 | parents = repo.unfiltered()[prec].parents() | |||
|
2471 | parents = tuple(p.node() for p in parents) | |||
|
2472 | repo.obsstore.create(tr, prec, succs, opts['flags'], | |||
|
2473 | parents=parents, date=date, | |||
|
2474 | metadata=metadata) | |||
2351 | tr.close() |
|
2475 | tr.close() | |
2352 | except ValueError, exc: |
|
2476 | except ValueError, exc: | |
2353 | raise util.Abort(_('bad obsmarker input: %s') % exc) |
|
2477 | raise util.Abort(_('bad obsmarker input: %s') % exc) | |
@@ -2356,7 +2480,15 b' def debugobsolete(ui, repo, precursor=No' | |||||
2356 | finally: |
|
2480 | finally: | |
2357 | l.release() |
|
2481 | l.release() | |
2358 | else: |
|
2482 | else: | |
2359 | for m in obsolete.allmarkers(repo): |
|
2483 | if opts['rev']: | |
|
2484 | revs = scmutil.revrange(repo, opts['rev']) | |||
|
2485 | nodes = [repo[r].node() for r in revs] | |||
|
2486 | markers = list(obsolete.getmarkers(repo, nodes=nodes)) | |||
|
2487 | markers.sort(key=lambda x: x._data) | |||
|
2488 | else: | |||
|
2489 | markers = obsolete.getmarkers(repo) | |||
|
2490 | ||||
|
2491 | for m in markers: | |||
2360 | cmdutil.showmarker(ui, m) |
|
2492 | cmdutil.showmarker(ui, m) | |
2361 |
|
2493 | |||
2362 | @command('debugpathcomplete', |
|
2494 | @command('debugpathcomplete', | |
@@ -2518,24 +2650,36 b' def debugrevlog(ui, repo, file_=None, **' | |||||
2518 | if opts.get("dump"): |
|
2650 | if opts.get("dump"): | |
2519 | numrevs = len(r) |
|
2651 | numrevs = len(r) | |
2520 | ui.write("# rev p1rev p2rev start end deltastart base p1 p2" |
|
2652 | ui.write("# rev p1rev p2rev start end deltastart base p1 p2" | |
2521 | " rawsize totalsize compression heads\n") |
|
2653 | " rawsize totalsize compression heads chainlen\n") | |
2522 | ts = 0 |
|
2654 | ts = 0 | |
2523 | heads = set() |
|
2655 | heads = set() | |
|
2656 | rindex = r.index | |||
|
2657 | ||||
|
2658 | def chainbaseandlen(rev): | |||
|
2659 | clen = 0 | |||
|
2660 | base = rindex[rev][3] | |||
|
2661 | while base != rev: | |||
|
2662 | clen += 1 | |||
|
2663 | rev = base | |||
|
2664 | base = rindex[rev][3] | |||
|
2665 | return base, clen | |||
|
2666 | ||||
2524 | for rev in xrange(numrevs): |
|
2667 | for rev in xrange(numrevs): | |
2525 | dbase = r.deltaparent(rev) |
|
2668 | dbase = r.deltaparent(rev) | |
2526 | if dbase == -1: |
|
2669 | if dbase == -1: | |
2527 | dbase = rev |
|
2670 | dbase = rev | |
2528 |
cbase = |
|
2671 | cbase, clen = chainbaseandlen(rev) | |
2529 | p1, p2 = r.parentrevs(rev) |
|
2672 | p1, p2 = r.parentrevs(rev) | |
2530 | rs = r.rawsize(rev) |
|
2673 | rs = r.rawsize(rev) | |
2531 | ts = ts + rs |
|
2674 | ts = ts + rs | |
2532 | heads -= set(r.parentrevs(rev)) |
|
2675 | heads -= set(r.parentrevs(rev)) | |
2533 | heads.add(rev) |
|
2676 | heads.add(rev) | |
2534 |
ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d |
|
2677 | ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d " | |
|
2678 | "%11d %5d %8d\n" % | |||
2535 | (rev, p1, p2, r.start(rev), r.end(rev), |
|
2679 | (rev, p1, p2, r.start(rev), r.end(rev), | |
2536 | r.start(dbase), r.start(cbase), |
|
2680 | r.start(dbase), r.start(cbase), | |
2537 | r.start(p1), r.start(p2), |
|
2681 | r.start(p1), r.start(p2), | |
2538 | rs, ts, ts / r.end(rev), len(heads))) |
|
2682 | rs, ts, ts / r.end(rev), len(heads), clen)) | |
2539 | return 0 |
|
2683 | return 0 | |
2540 |
|
2684 | |||
2541 | v = r.version |
|
2685 | v = r.version | |
@@ -2716,7 +2860,9 b' def debugsetparents(ui, repo, rev1, rev2' | |||||
2716 |
|
2860 | |||
2717 | wlock = repo.wlock() |
|
2861 | wlock = repo.wlock() | |
2718 | try: |
|
2862 | try: | |
|
2863 | repo.dirstate.beginparentchange() | |||
2719 | repo.setparents(r1, r2) |
|
2864 | repo.setparents(r1, r2) | |
|
2865 | repo.dirstate.endparentchange() | |||
2720 | finally: |
|
2866 | finally: | |
2721 | wlock.release() |
|
2867 | wlock.release() | |
2722 |
|
2868 | |||
@@ -3023,6 +3169,82 b' def export(ui, repo, *changesets, **opts' | |||||
3023 | switch_parent=opts.get('switch_parent'), |
|
3169 | switch_parent=opts.get('switch_parent'), | |
3024 | opts=patch.diffopts(ui, opts)) |
|
3170 | opts=patch.diffopts(ui, opts)) | |
3025 |
|
3171 | |||
|
3172 | @command('files', | |||
|
3173 | [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')), | |||
|
3174 | ('0', 'print0', None, _('end filenames with NUL, for use with xargs')), | |||
|
3175 | ] + walkopts + formatteropts, | |||
|
3176 | _('[OPTION]... [PATTERN]...')) | |||
|
3177 | def files(ui, repo, *pats, **opts): | |||
|
3178 | """list tracked files | |||
|
3179 | ||||
|
3180 | Print files under Mercurial control in the working directory or | |||
|
3181 | specified revision whose names match the given patterns (excluding | |||
|
3182 | removed files). | |||
|
3183 | ||||
|
3184 | If no patterns are given to match, this command prints the names | |||
|
3185 | of all files under Mercurial control in the working copy. | |||
|
3186 | ||||
|
3187 | .. container:: verbose | |||
|
3188 | ||||
|
3189 | Examples: | |||
|
3190 | ||||
|
3191 | - list all files under the current directory:: | |||
|
3192 | ||||
|
3193 | hg files . | |||
|
3194 | ||||
|
3195 | - shows sizes and flags for current revision:: | |||
|
3196 | ||||
|
3197 | hg files -vr . | |||
|
3198 | ||||
|
3199 | - list all files named README:: | |||
|
3200 | ||||
|
3201 | hg files -I "**/README" | |||
|
3202 | ||||
|
3203 | - list all binary files:: | |||
|
3204 | ||||
|
3205 | hg files "set:binary()" | |||
|
3206 | ||||
|
3207 | - find files containing a regular expression: | |||
|
3208 | ||||
|
3209 | hg files "set:grep('bob')" | |||
|
3210 | ||||
|
3211 | - search tracked file contents with xargs and grep:: | |||
|
3212 | ||||
|
3213 | hg files -0 | xargs -0 grep foo | |||
|
3214 | ||||
|
3215 | See :hg:'help pattern' and :hg:'help revsets' for more information | |||
|
3216 | on specifying file patterns. | |||
|
3217 | ||||
|
3218 | Returns 0 if a match is found, 1 otherwise. | |||
|
3219 | ||||
|
3220 | """ | |||
|
3221 | ctx = scmutil.revsingle(repo, opts.get('rev'), None) | |||
|
3222 | rev = ctx.rev() | |||
|
3223 | ret = 1 | |||
|
3224 | ||||
|
3225 | end = '\n' | |||
|
3226 | if opts.get('print0'): | |||
|
3227 | end = '\0' | |||
|
3228 | fm = ui.formatter('files', opts) | |||
|
3229 | fmt = '%s' + end | |||
|
3230 | ||||
|
3231 | m = scmutil.match(ctx, pats, opts) | |||
|
3232 | ds = repo.dirstate | |||
|
3233 | for f in ctx.matches(m): | |||
|
3234 | if rev is None and ds[f] == 'r': | |||
|
3235 | continue | |||
|
3236 | fm.startitem() | |||
|
3237 | if ui.verbose: | |||
|
3238 | fc = ctx[f] | |||
|
3239 | fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags()) | |||
|
3240 | fm.data(abspath=f) | |||
|
3241 | fm.write('path', fmt, m.rel(f)) | |||
|
3242 | ret = 0 | |||
|
3243 | ||||
|
3244 | fm.end() | |||
|
3245 | ||||
|
3246 | return ret | |||
|
3247 | ||||
3026 | @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True) |
|
3248 | @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True) | |
3027 | def forget(ui, repo, *pats, **opts): |
|
3249 | def forget(ui, repo, *pats, **opts): | |
3028 | """forget the specified files on the next commit |
|
3250 | """forget the specified files on the next commit | |
@@ -3064,6 +3286,7 b' def forget(ui, repo, *pats, **opts):' | |||||
3064 | ('c', 'continue', False, _('resume interrupted graft')), |
|
3286 | ('c', 'continue', False, _('resume interrupted graft')), | |
3065 | ('e', 'edit', False, _('invoke editor on commit messages')), |
|
3287 | ('e', 'edit', False, _('invoke editor on commit messages')), | |
3066 | ('', 'log', None, _('append graft info to log message')), |
|
3288 | ('', 'log', None, _('append graft info to log message')), | |
|
3289 | ('f', 'force', False, _('force graft')), | |||
3067 | ('D', 'currentdate', False, |
|
3290 | ('D', 'currentdate', False, | |
3068 | _('record the current date as commit date')), |
|
3291 | _('record the current date as commit date')), | |
3069 | ('U', 'currentuser', False, |
|
3292 | ('U', 'currentuser', False, | |
@@ -3087,6 +3310,10 b' def graft(ui, repo, *revs, **opts):' | |||||
3087 |
|
3310 | |||
3088 | (grafted from CHANGESETHASH) |
|
3311 | (grafted from CHANGESETHASH) | |
3089 |
|
3312 | |||
|
3313 | If --force is specified, revisions will be grafted even if they | |||
|
3314 | are already ancestors of or have been grafted to the destination. | |||
|
3315 | This is useful when the revisions have since been backed out. | |||
|
3316 | ||||
3090 | If a graft merge results in conflicts, the graft process is |
|
3317 | If a graft merge results in conflicts, the graft process is | |
3091 | interrupted so that the current merge can be manually resolved. |
|
3318 | interrupted so that the current merge can be manually resolved. | |
3092 | Once all conflicts are addressed, the graft process can be |
|
3319 | Once all conflicts are addressed, the graft process can be | |
@@ -3094,7 +3321,8 b' def graft(ui, repo, *revs, **opts):' | |||||
3094 |
|
3321 | |||
3095 | .. note:: |
|
3322 | .. note:: | |
3096 |
|
3323 | |||
3097 |
The -c/--continue option does not reapply earlier options |
|
3324 | The -c/--continue option does not reapply earlier options, except | |
|
3325 | for --force. | |||
3098 |
|
3326 | |||
3099 | .. container:: verbose |
|
3327 | .. container:: verbose | |
3100 |
|
3328 | |||
@@ -3131,7 +3359,7 b' def graft(ui, repo, *revs, **opts):' | |||||
3131 | if not opts.get('date') and opts.get('currentdate'): |
|
3359 | if not opts.get('date') and opts.get('currentdate'): | |
3132 | opts['date'] = "%d %d" % util.makedate() |
|
3360 | opts['date'] = "%d %d" % util.makedate() | |
3133 |
|
3361 | |||
3134 | editor = cmdutil.getcommiteditor(**opts) |
|
3362 | editor = cmdutil.getcommiteditor(editform='graft', **opts) | |
3135 |
|
3363 | |||
3136 | cont = False |
|
3364 | cont = False | |
3137 | if opts['continue']: |
|
3365 | if opts['continue']: | |
@@ -3153,72 +3381,80 b' def graft(ui, repo, *revs, **opts):' | |||||
3153 | raise util.Abort(_('no revisions specified')) |
|
3381 | raise util.Abort(_('no revisions specified')) | |
3154 | revs = scmutil.revrange(repo, revs) |
|
3382 | revs = scmutil.revrange(repo, revs) | |
3155 |
|
3383 | |||
|
3384 | skipped = set() | |||
3156 | # check for merges |
|
3385 | # check for merges | |
3157 | for rev in repo.revs('%ld and merge()', revs): |
|
3386 | for rev in repo.revs('%ld and merge()', revs): | |
3158 | ui.warn(_('skipping ungraftable merge revision %s\n') % rev) |
|
3387 | ui.warn(_('skipping ungraftable merge revision %s\n') % rev) | |
3159 |
|
|
3388 | skipped.add(rev) | |
3160 | if not revs: |
|
3389 | revs = [r for r in revs if r not in skipped] | |
3161 | return -1 |
|
|||
3162 |
|
||||
3163 | # check for ancestors of dest branch |
|
|||
3164 | crev = repo['.'].rev() |
|
|||
3165 | ancestors = repo.changelog.ancestors([crev], inclusive=True) |
|
|||
3166 | # Cannot use x.remove(y) on smart set, this has to be a list. |
|
|||
3167 | # XXX make this lazy in the future |
|
|||
3168 | revs = list(revs) |
|
|||
3169 | # don't mutate while iterating, create a copy |
|
|||
3170 | for rev in list(revs): |
|
|||
3171 | if rev in ancestors: |
|
|||
3172 | ui.warn(_('skipping ancestor revision %s\n') % rev) |
|
|||
3173 | # XXX remove on list is slow |
|
|||
3174 | revs.remove(rev) |
|
|||
3175 | if not revs: |
|
3390 | if not revs: | |
3176 | return -1 |
|
3391 | return -1 | |
3177 |
|
3392 | |||
3178 | # analyze revs for earlier grafts |
|
3393 | # Don't check in the --continue case, in effect retaining --force across | |
3179 | ids = {} |
|
3394 | # --continues. That's because without --force, any revisions we decided to | |
3180 | for ctx in repo.set("%ld", revs): |
|
3395 | # skip would have been filtered out here, so they wouldn't have made their | |
3181 | ids[ctx.hex()] = ctx.rev() |
|
3396 | # way to the graftstate. With --force, any revisions we would have otherwise | |
3182 | n = ctx.extra().get('source') |
|
3397 | # skipped would not have been filtered out, and if they hadn't been applied | |
3183 | if n: |
|
3398 | # already, they'd have been in the graftstate. | |
3184 | ids[n] = ctx.rev() |
|
3399 | if not (cont or opts.get('force')): | |
3185 |
|
3400 | # check for ancestors of dest branch | ||
3186 | # check ancestors for earlier grafts |
|
3401 | crev = repo['.'].rev() | |
3187 | ui.debug('scanning for duplicate grafts\n') |
|
3402 | ancestors = repo.changelog.ancestors([crev], inclusive=True) | |
3188 |
|
3403 | # Cannot use x.remove(y) on smart set, this has to be a list. | ||
3189 | for rev in repo.changelog.findmissingrevs(revs, [crev]): |
|
3404 | # XXX make this lazy in the future | |
3190 |
|
|
3405 | revs = list(revs) | |
3191 | n = ctx.extra().get('source') |
|
3406 | # don't mutate while iterating, create a copy | |
3192 |
|
|
3407 | for rev in list(revs): | |
3193 |
|
|
3408 | if rev in ancestors: | |
3194 | r = repo[n].rev() |
|
3409 | ui.warn(_('skipping ancestor revision %s\n') % rev) | |
3195 | except error.RepoLookupError: |
|
3410 | # XXX remove on list is slow | |
3196 |
r |
|
3411 | revs.remove(rev) | |
3197 |
|
|
3412 | if not revs: | |
3198 | ui.warn(_('skipping revision %s (already grafted to %s)\n') |
|
3413 | return -1 | |
3199 | % (r, rev)) |
|
3414 | ||
|
3415 | # analyze revs for earlier grafts | |||
|
3416 | ids = {} | |||
|
3417 | for ctx in repo.set("%ld", revs): | |||
|
3418 | ids[ctx.hex()] = ctx.rev() | |||
|
3419 | n = ctx.extra().get('source') | |||
|
3420 | if n: | |||
|
3421 | ids[n] = ctx.rev() | |||
|
3422 | ||||
|
3423 | # check ancestors for earlier grafts | |||
|
3424 | ui.debug('scanning for duplicate grafts\n') | |||
|
3425 | ||||
|
3426 | for rev in repo.changelog.findmissingrevs(revs, [crev]): | |||
|
3427 | ctx = repo[rev] | |||
|
3428 | n = ctx.extra().get('source') | |||
|
3429 | if n in ids: | |||
|
3430 | try: | |||
|
3431 | r = repo[n].rev() | |||
|
3432 | except error.RepoLookupError: | |||
|
3433 | r = None | |||
|
3434 | if r in revs: | |||
|
3435 | ui.warn(_('skipping revision %s (already grafted to %s)\n') | |||
|
3436 | % (r, rev)) | |||
|
3437 | revs.remove(r) | |||
|
3438 | elif ids[n] in revs: | |||
|
3439 | if r is None: | |||
|
3440 | ui.warn(_('skipping already grafted revision %s ' | |||
|
3441 | '(%s also has unknown origin %s)\n') | |||
|
3442 | % (ids[n], rev, n)) | |||
|
3443 | else: | |||
|
3444 | ui.warn(_('skipping already grafted revision %s ' | |||
|
3445 | '(%s also has origin %d)\n') | |||
|
3446 | % (ids[n], rev, r)) | |||
|
3447 | revs.remove(ids[n]) | |||
|
3448 | elif ctx.hex() in ids: | |||
|
3449 | r = ids[ctx.hex()] | |||
|
3450 | ui.warn(_('skipping already grafted revision %s ' | |||
|
3451 | '(was grafted from %d)\n') % (r, rev)) | |||
3200 | revs.remove(r) |
|
3452 | revs.remove(r) | |
3201 |
|
|
3453 | if not revs: | |
3202 | if r is None: |
|
3454 | return -1 | |
3203 | ui.warn(_('skipping already grafted revision %s ' |
|
|||
3204 | '(%s also has unknown origin %s)\n') |
|
|||
3205 | % (ids[n], rev, n)) |
|
|||
3206 | else: |
|
|||
3207 | ui.warn(_('skipping already grafted revision %s ' |
|
|||
3208 | '(%s also has origin %d)\n') |
|
|||
3209 | % (ids[n], rev, r)) |
|
|||
3210 | revs.remove(ids[n]) |
|
|||
3211 | elif ctx.hex() in ids: |
|
|||
3212 | r = ids[ctx.hex()] |
|
|||
3213 | ui.warn(_('skipping already grafted revision %s ' |
|
|||
3214 | '(was grafted from %d)\n') % (r, rev)) |
|
|||
3215 | revs.remove(r) |
|
|||
3216 | if not revs: |
|
|||
3217 | return -1 |
|
|||
3218 |
|
3455 | |||
3219 | wlock = repo.wlock() |
|
3456 | wlock = repo.wlock() | |
3220 | try: |
|
3457 | try: | |
3221 | current = repo['.'] |
|
|||
3222 | for pos, ctx in enumerate(repo.set("%ld", revs)): |
|
3458 | for pos, ctx in enumerate(repo.set("%ld", revs)): | |
3223 |
|
3459 | |||
3224 | ui.status(_('grafting revision %s\n') % ctx.rev()) |
|
3460 | ui.status(_('grafting revision %s\n') % ctx.rev()) | |
@@ -3246,9 +3482,8 b' def graft(ui, repo, *revs, **opts):' | |||||
3246 | # ui.forcemerge is an internal variable, do not document |
|
3482 | # ui.forcemerge is an internal variable, do not document | |
3247 | repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), |
|
3483 | repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), | |
3248 | 'graft') |
|
3484 | 'graft') | |
3249 |
stats = mergemod. |
|
3485 | stats = mergemod.graft(repo, ctx, ctx.p1(), | |
3250 |
|
|
3486 | ['local', 'graft']) | |
3251 | labels=['local', 'graft']) |
|
|||
3252 | finally: |
|
3487 | finally: | |
3253 | repo.ui.setconfig('ui', 'forcemerge', '', 'graft') |
|
3488 | repo.ui.setconfig('ui', 'forcemerge', '', 'graft') | |
3254 | # report any conflicts |
|
3489 | # report any conflicts | |
@@ -3262,19 +3497,11 b' def graft(ui, repo, *revs, **opts):' | |||||
3262 | else: |
|
3497 | else: | |
3263 | cont = False |
|
3498 | cont = False | |
3264 |
|
3499 | |||
3265 | # drop the second merge parent |
|
|||
3266 | repo.setparents(current.node(), nullid) |
|
|||
3267 | repo.dirstate.write() |
|
|||
3268 | # fix up dirstate for copies and renames |
|
|||
3269 | cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev()) |
|
|||
3270 |
|
||||
3271 | # commit |
|
3500 | # commit | |
3272 | node = repo.commit(text=message, user=user, |
|
3501 | node = repo.commit(text=message, user=user, | |
3273 | date=date, extra=extra, editor=editor) |
|
3502 | date=date, extra=extra, editor=editor) | |
3274 | if node is None: |
|
3503 | if node is None: | |
3275 | ui.status(_('graft for revision %s is empty\n') % ctx.rev()) |
|
3504 | ui.status(_('graft for revision %s is empty\n') % ctx.rev()) | |
3276 | else: |
|
|||
3277 | current = repo[node] |
|
|||
3278 | finally: |
|
3505 | finally: | |
3279 | wlock.release() |
|
3506 | wlock.release() | |
3280 |
|
3507 | |||
@@ -3595,15 +3822,36 b' def help_(ui, name=None, **opts):' | |||||
3595 |
|
3822 | |||
3596 | textwidth = min(ui.termwidth(), 80) - 2 |
|
3823 | textwidth = min(ui.termwidth(), 80) - 2 | |
3597 |
|
3824 | |||
3598 | keep = ui.verbose and ['verbose'] or [] |
|
3825 | keep = [] | |
|
3826 | if ui.verbose: | |||
|
3827 | keep.append('verbose') | |||
|
3828 | if sys.platform.startswith('win'): | |||
|
3829 | keep.append('windows') | |||
|
3830 | elif sys.platform == 'OpenVMS': | |||
|
3831 | keep.append('vms') | |||
|
3832 | elif sys.platform == 'plan9': | |||
|
3833 | keep.append('plan9') | |||
|
3834 | else: | |||
|
3835 | keep.append('unix') | |||
|
3836 | keep.append(sys.platform.lower()) | |||
|
3837 | ||||
|
3838 | section = None | |||
|
3839 | if name and '.' in name: | |||
|
3840 | name, section = name.split('.') | |||
|
3841 | ||||
3599 | text = help.help_(ui, name, **opts) |
|
3842 | text = help.help_(ui, name, **opts) | |
3600 |
|
3843 | |||
3601 |
formatted, pruned = minirst.format(text, textwidth, keep=keep |
|
3844 | formatted, pruned = minirst.format(text, textwidth, keep=keep, | |
|
3845 | section=section) | |||
|
3846 | if section and not formatted: | |||
|
3847 | raise util.Abort(_("help section not found")) | |||
|
3848 | ||||
3602 | if 'verbose' in pruned: |
|
3849 | if 'verbose' in pruned: | |
3603 | keep.append('omitted') |
|
3850 | keep.append('omitted') | |
3604 | else: |
|
3851 | else: | |
3605 | keep.append('notomitted') |
|
3852 | keep.append('notomitted') | |
3606 |
formatted, pruned = minirst.format(text, textwidth, keep=keep |
|
3853 | formatted, pruned = minirst.format(text, textwidth, keep=keep, | |
|
3854 | section=section) | |||
3607 | ui.write(formatted) |
|
3855 | ui.write(formatted) | |
3608 |
|
3856 | |||
3609 |
|
3857 | |||
@@ -3858,6 +4106,8 b' def import_(ui, repo, patch1=None, *patc' | |||||
3858 | raise util.Abort(_('similarity must be between 0 and 100')) |
|
4106 | raise util.Abort(_('similarity must be between 0 and 100')) | |
3859 | if sim and not update: |
|
4107 | if sim and not update: | |
3860 | raise util.Abort(_('cannot use --similarity with --bypass')) |
|
4108 | raise util.Abort(_('cannot use --similarity with --bypass')) | |
|
4109 | if opts.get('exact') and opts.get('edit'): | |||
|
4110 | raise util.Abort(_('cannot use --exact with --edit')) | |||
3861 |
|
4111 | |||
3862 | if update: |
|
4112 | if update: | |
3863 | cmdutil.checkunfinished(repo) |
|
4113 | cmdutil.checkunfinished(repo) | |
@@ -3873,6 +4123,7 b' def import_(ui, repo, patch1=None, *patc' | |||||
3873 | try: |
|
4123 | try: | |
3874 | try: |
|
4124 | try: | |
3875 | wlock = repo.wlock() |
|
4125 | wlock = repo.wlock() | |
|
4126 | repo.dirstate.beginparentchange() | |||
3876 | if not opts.get('no_commit'): |
|
4127 | if not opts.get('no_commit'): | |
3877 | lock = repo.lock() |
|
4128 | lock = repo.lock() | |
3878 | tr = repo.transaction('import') |
|
4129 | tr = repo.transaction('import') | |
@@ -3913,6 +4164,7 b' def import_(ui, repo, patch1=None, *patc' | |||||
3913 | tr.close() |
|
4164 | tr.close() | |
3914 | if msgs: |
|
4165 | if msgs: | |
3915 | repo.savecommitmessage('\n* * *\n'.join(msgs)) |
|
4166 | repo.savecommitmessage('\n* * *\n'.join(msgs)) | |
|
4167 | repo.dirstate.endparentchange() | |||
3916 | return ret |
|
4168 | return ret | |
3917 | except: # re-raises |
|
4169 | except: # re-raises | |
3918 | # wlock.release() indirectly calls dirstate.write(): since |
|
4170 | # wlock.release() indirectly calls dirstate.write(): since | |
@@ -4023,7 +4275,7 b' def init(ui, dest=".", **opts):' | |||||
4023 | ] + walkopts, |
|
4275 | ] + walkopts, | |
4024 | _('[OPTION]... [PATTERN]...')) |
|
4276 | _('[OPTION]... [PATTERN]...')) | |
4025 | def locate(ui, repo, *pats, **opts): |
|
4277 | def locate(ui, repo, *pats, **opts): | |
4026 | """locate files matching specific patterns |
|
4278 | """locate files matching specific patterns (DEPRECATED) | |
4027 |
|
4279 | |||
4028 | Print files under Mercurial control in the working directory whose |
|
4280 | Print files under Mercurial control in the working directory whose | |
4029 | names match the given patterns. |
|
4281 | names match the given patterns. | |
@@ -4040,17 +4292,19 b' def locate(ui, repo, *pats, **opts):' | |||||
4040 | will avoid the problem of "xargs" treating single filenames that |
|
4292 | will avoid the problem of "xargs" treating single filenames that | |
4041 | contain whitespace as multiple filenames. |
|
4293 | contain whitespace as multiple filenames. | |
4042 |
|
4294 | |||
|
4295 | See :hg:`help files` for a more versatile command. | |||
|
4296 | ||||
4043 | Returns 0 if a match is found, 1 otherwise. |
|
4297 | Returns 0 if a match is found, 1 otherwise. | |
4044 | """ |
|
4298 | """ | |
4045 | end = opts.get('print0') and '\0' or '\n' |
|
4299 | end = opts.get('print0') and '\0' or '\n' | |
4046 | rev = scmutil.revsingle(repo, opts.get('rev'), None).node() |
|
4300 | rev = scmutil.revsingle(repo, opts.get('rev'), None).node() | |
4047 |
|
4301 | |||
4048 | ret = 1 |
|
4302 | ret = 1 | |
4049 | m = scmutil.match(repo[rev], pats, opts, default='relglob') |
|
4303 | ctx = repo[rev] | |
|
4304 | m = scmutil.match(ctx, pats, opts, default='relglob') | |||
4050 | m.bad = lambda x, y: False |
|
4305 | m.bad = lambda x, y: False | |
4051 | for abs in repo[rev].walk(m): |
|
4306 | ||
4052 | if not rev and abs not in repo.dirstate: |
|
4307 | for abs in ctx.matches(m): | |
4053 | continue |
|
|||
4054 | if opts.get('fullpath'): |
|
4308 | if opts.get('fullpath'): | |
4055 | ui.write(repo.wjoin(abs), end) |
|
4309 | ui.write(repo.wjoin(abs), end) | |
4056 | else: |
|
4310 | else: | |
@@ -4211,7 +4465,8 b' def log(ui, repo, *pats, **opts):' | |||||
4211 |
|
4465 | |||
4212 | @command('manifest', |
|
4466 | @command('manifest', | |
4213 | [('r', 'rev', '', _('revision to display'), _('REV')), |
|
4467 | [('r', 'rev', '', _('revision to display'), _('REV')), | |
4214 |
('', 'all', False, _("list files from all revisions"))] |
|
4468 | ('', 'all', False, _("list files from all revisions"))] | |
|
4469 | + formatteropts, | |||
4215 | _('[-r REV]')) |
|
4470 | _('[-r REV]')) | |
4216 | def manifest(ui, repo, node=None, rev=None, **opts): |
|
4471 | def manifest(ui, repo, node=None, rev=None, **opts): | |
4217 | """output the current or given revision of the project manifest |
|
4472 | """output the current or given revision of the project manifest | |
@@ -4446,7 +4701,7 b' def outgoing(ui, repo, dest=None, **opts' | |||||
4446 | _('[-r REV] [FILE]'), |
|
4701 | _('[-r REV] [FILE]'), | |
4447 | inferrepo=True) |
|
4702 | inferrepo=True) | |
4448 | def parents(ui, repo, file_=None, **opts): |
|
4703 | def parents(ui, repo, file_=None, **opts): | |
4449 | """show the parents of the working directory or revision |
|
4704 | """show the parents of the working directory or revision (DEPRECATED) | |
4450 |
|
4705 | |||
4451 | Print the working directory's parent revisions. If a revision is |
|
4706 | Print the working directory's parent revisions. If a revision is | |
4452 | given via -r/--rev, the parent of that revision will be printed. |
|
4707 | given via -r/--rev, the parent of that revision will be printed. | |
@@ -4454,6 +4709,8 b' def parents(ui, repo, file_=None, **opts' | |||||
4454 | last changed (before the working directory revision or the |
|
4709 | last changed (before the working directory revision or the | |
4455 | argument to --rev if given) is printed. |
|
4710 | argument to --rev if given) is printed. | |
4456 |
|
4711 | |||
|
4712 | See :hg:`summary` and :hg:`help revsets` for related information. | |||
|
4713 | ||||
4457 | Returns 0 on success. |
|
4714 | Returns 0 on success. | |
4458 | """ |
|
4715 | """ | |
4459 |
|
4716 | |||
@@ -4579,23 +4836,30 b' def phase(ui, repo, *revs, **opts):' | |||||
4579 | ctx = repo[r] |
|
4836 | ctx = repo[r] | |
4580 | ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr())) |
|
4837 | ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr())) | |
4581 | else: |
|
4838 | else: | |
|
4839 | tr = None | |||
4582 | lock = repo.lock() |
|
4840 | lock = repo.lock() | |
4583 | try: |
|
4841 | try: | |
|
4842 | tr = repo.transaction("phase") | |||
4584 | # set phase |
|
4843 | # set phase | |
4585 | if not revs: |
|
4844 | if not revs: | |
4586 | raise util.Abort(_('empty revision set')) |
|
4845 | raise util.Abort(_('empty revision set')) | |
4587 | nodes = [repo[r].node() for r in revs] |
|
4846 | nodes = [repo[r].node() for r in revs] | |
4588 | olddata = repo._phasecache.getphaserevs(repo)[:] |
|
4847 | # moving revision from public to draft may hide them | |
4589 | phases.advanceboundary(repo, targetphase, nodes) |
|
4848 | # We have to check result on an unfiltered repository | |
|
4849 | unfi = repo.unfiltered() | |||
|
4850 | getphase = unfi._phasecache.phase | |||
|
4851 | olddata = [getphase(unfi, r) for r in unfi] | |||
|
4852 | phases.advanceboundary(repo, tr, targetphase, nodes) | |||
4590 | if opts['force']: |
|
4853 | if opts['force']: | |
4591 | phases.retractboundary(repo, targetphase, nodes) |
|
4854 | phases.retractboundary(repo, tr, targetphase, nodes) | |
|
4855 | tr.close() | |||
4592 | finally: |
|
4856 | finally: | |
|
4857 | if tr is not None: | |||
|
4858 | tr.release() | |||
4593 | lock.release() |
|
4859 | lock.release() | |
4594 | # moving revision from public to draft may hide them |
|
4860 | getphase = unfi._phasecache.phase | |
4595 | # We have to check result on an unfiltered repository |
|
4861 | newdata = [getphase(unfi, r) for r in unfi] | |
4596 | unfi = repo.unfiltered() |
|
4862 | changes = sum(newdata[r] != olddata[r] for r in unfi) | |
4597 | newdata = repo._phasecache.getphaserevs(unfi) |
|
|||
4598 | changes = sum(o != newdata[i] for i, o in enumerate(olddata)) |
|
|||
4599 | cl = unfi.changelog |
|
4863 | cl = unfi.changelog | |
4600 | rejected = [n for n in nodes |
|
4864 | rejected = [n for n in nodes | |
4601 | if newdata[cl.rev(n)] < targetphase] |
|
4865 | if newdata[cl.rev(n)] < targetphase] | |
@@ -4697,8 +4961,9 b' def pull(ui, repo, source="default", **o' | |||||
4697 | "so a rev cannot be specified.") |
|
4961 | "so a rev cannot be specified.") | |
4698 | raise util.Abort(err) |
|
4962 | raise util.Abort(err) | |
4699 |
|
4963 | |||
4700 |
modheads = |
|
4964 | modheads = exchange.pull(repo, other, heads=revs, | |
4701 | bookmarks.updatefromremote(ui, repo, remotebookmarks, source) |
|
4965 | force=opts.get('force'), | |
|
4966 | bookmarks=opts.get('bookmark', ())).cgresult | |||
4702 | if checkout: |
|
4967 | if checkout: | |
4703 | checkout = str(repo.changelog.rev(other.lookup(checkout))) |
|
4968 | checkout = str(repo.changelog.rev(other.lookup(checkout))) | |
4704 | repo._subtoppath = source |
|
4969 | repo._subtoppath = source | |
@@ -4708,14 +4973,6 b' def pull(ui, repo, source="default", **o' | |||||
4708 | finally: |
|
4973 | finally: | |
4709 | del repo._subtoppath |
|
4974 | del repo._subtoppath | |
4710 |
|
4975 | |||
4711 | # update specified bookmarks |
|
|||
4712 | if opts.get('bookmark'): |
|
|||
4713 | marks = repo._bookmarks |
|
|||
4714 | for b in opts['bookmark']: |
|
|||
4715 | # explicit pull overrides local bookmark if any |
|
|||
4716 | ui.status(_("importing bookmark %s\n") % b) |
|
|||
4717 | marks[b] = repo[remotebookmarks[b]].node() |
|
|||
4718 | marks.write() |
|
|||
4719 | finally: |
|
4976 | finally: | |
4720 | other.close() |
|
4977 | other.close() | |
4721 | return ret |
|
4978 | return ret | |
@@ -4806,16 +5063,16 b' def push(ui, repo, dest=None, **opts):' | |||||
4806 | return not result |
|
5063 | return not result | |
4807 | finally: |
|
5064 | finally: | |
4808 | del repo._subtoppath |
|
5065 | del repo._subtoppath | |
4809 |
|
|
5066 | pushop = exchange.push(repo, other, opts.get('force'), revs=revs, | |
4810 |
newbranch=opts.get('new_branch') |
|
5067 | newbranch=opts.get('new_branch'), | |
4811 |
|
5068 | bookmarks=opts.get('bookmark', ())) | ||
4812 | result = not result |
|
5069 | ||
4813 |
|
5070 | result = not pushop.cgresult | ||
4814 | if opts.get('bookmark'): |
|
5071 | ||
4815 | bresult = bookmarks.pushtoremote(ui, repo, other, opts['bookmark']) |
|
5072 | if pushop.bkresult is not None: | |
4816 | if bresult == 2: |
|
5073 | if pushop.bkresult == 2: | |
4817 |
re |
|
5074 | result = 2 | |
4818 | if not result and bresult: |
|
5075 | elif not result and pushop.bkresult: | |
4819 | result = 2 |
|
5076 | result = 2 | |
4820 |
|
5077 | |||
4821 | return result |
|
5078 | return result | |
@@ -5026,7 +5283,7 b' def resolve(ui, repo, *pats, **opts):' | |||||
5026 | try: |
|
5283 | try: | |
5027 | ms = mergemod.mergestate(repo) |
|
5284 | ms = mergemod.mergestate(repo) | |
5028 |
|
5285 | |||
5029 | if not ms.active() and not show: |
|
5286 | if not (ms.active() or repo.dirstate.p2() != nullid) and not show: | |
5030 | raise util.Abort( |
|
5287 | raise util.Abort( | |
5031 | _('resolve command not applicable when not merging')) |
|
5288 | _('resolve command not applicable when not merging')) | |
5032 |
|
5289 | |||
@@ -5279,8 +5536,8 b' def serve(ui, repo, **opts):' | |||||
5279 | s.serve_forever() |
|
5536 | s.serve_forever() | |
5280 |
|
5537 | |||
5281 | if opts["cmdserver"]: |
|
5538 | if opts["cmdserver"]: | |
5282 |
s = commandserver.serve |
|
5539 | service = commandserver.createservice(ui, repo, opts) | |
5283 | return s.serve() |
|
5540 | return cmdutil.service(opts, initfn=service.init, runfn=service.run) | |
5284 |
|
5541 | |||
5285 | # this way we can check if something was given in the command-line |
|
5542 | # this way we can check if something was given in the command-line | |
5286 | if opts.get('port'): |
|
5543 | if opts.get('port'): | |
@@ -5365,7 +5622,7 b' class httpservice(object):' | |||||
5365 | ('0', 'print0', None, _('end filenames with NUL, for use with xargs')), |
|
5622 | ('0', 'print0', None, _('end filenames with NUL, for use with xargs')), | |
5366 | ('', 'rev', [], _('show difference from revision'), _('REV')), |
|
5623 | ('', 'rev', [], _('show difference from revision'), _('REV')), | |
5367 | ('', 'change', '', _('list the changed files of a revision'), _('REV')), |
|
5624 | ('', 'change', '', _('list the changed files of a revision'), _('REV')), | |
5368 | ] + walkopts + subrepoopts, |
|
5625 | ] + walkopts + subrepoopts + formatteropts, | |
5369 | _('[OPTION]... [FILE]...'), |
|
5626 | _('[OPTION]... [FILE]...'), | |
5370 | inferrepo=True) |
|
5627 | inferrepo=True) | |
5371 | def status(ui, repo, *pats, **opts): |
|
5628 | def status(ui, repo, *pats, **opts): | |
@@ -5531,39 +5788,35 b' def summary(ui, repo, **opts):' | |||||
5531 | ui.write(' ' + m, label='log.bookmark') |
|
5788 | ui.write(' ' + m, label='log.bookmark') | |
5532 | ui.write('\n', label='log.bookmark') |
|
5789 | ui.write('\n', label='log.bookmark') | |
5533 |
|
5790 | |||
5534 |
st = |
|
5791 | status = repo.status(unknown=True) | |
5535 |
|
5792 | |||
5536 | c = repo.dirstate.copies() |
|
5793 | c = repo.dirstate.copies() | |
5537 | copied, renamed = [], [] |
|
5794 | copied, renamed = [], [] | |
5538 | for d, s in c.iteritems(): |
|
5795 | for d, s in c.iteritems(): | |
5539 |
if s in st |
|
5796 | if s in status.removed: | |
5540 |
st |
|
5797 | status.removed.remove(s) | |
5541 | renamed.append(d) |
|
5798 | renamed.append(d) | |
5542 | else: |
|
5799 | else: | |
5543 | copied.append(d) |
|
5800 | copied.append(d) | |
5544 |
if d in st |
|
5801 | if d in status.added: | |
5545 |
st |
|
5802 | status.added.remove(d) | |
5546 | st.insert(3, renamed) |
|
|||
5547 | st.insert(4, copied) |
|
|||
5548 |
|
5803 | |||
5549 | ms = mergemod.mergestate(repo) |
|
5804 | ms = mergemod.mergestate(repo) | |
5550 |
|
|
5805 | unresolved = [f for f in ms if ms[f] == 'u'] | |
5551 |
|
5806 | |||
5552 | subs = [s for s in ctx.substate if ctx.sub(s).dirty()] |
|
5807 | subs = [s for s in ctx.substate if ctx.sub(s).dirty()] | |
5553 | st.append(subs) |
|
5808 | ||
5554 |
|
5809 | labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified), | ||
5555 |
|
|
5810 | (ui.label(_('%d added'), 'status.added'), status.added), | |
5556 |
ui.label(_('%d |
|
5811 | (ui.label(_('%d removed'), 'status.removed'), status.removed), | |
5557 |
ui.label(_('%d rem |
|
5812 | (ui.label(_('%d renamed'), 'status.copied'), renamed), | |
5558 |
ui.label(_('%d |
|
5813 | (ui.label(_('%d copied'), 'status.copied'), copied), | |
5559 |
ui.label(_('%d |
|
5814 | (ui.label(_('%d deleted'), 'status.deleted'), status.deleted), | |
5560 |
ui.label(_('%d |
|
5815 | (ui.label(_('%d unknown'), 'status.unknown'), status.unknown), | |
5561 |
ui.label(_('%d un |
|
5816 | (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved), | |
5562 |
ui.label(_('%d |
|
5817 | (ui.label(_('%d subrepos'), 'status.modified'), subs)] | |
5563 | ui.label(_('%d unresolved'), 'resolve.unresolved'), |
|
|||
5564 | ui.label(_('%d subrepos'), 'status.modified')] |
|
|||
5565 | t = [] |
|
5818 | t = [] | |
5566 |
for |
|
5819 | for l, s in labels: | |
5567 | if s: |
|
5820 | if s: | |
5568 | t.append(l % len(s)) |
|
5821 | t.append(l % len(s)) | |
5569 |
|
5822 | |||
@@ -5579,7 +5832,8 b' def summary(ui, repo, **opts):' | |||||
5579 | elif (parents[0].closesbranch() and |
|
5832 | elif (parents[0].closesbranch() and | |
5580 | pnode in repo.branchheads(branch, closed=True)): |
|
5833 | pnode in repo.branchheads(branch, closed=True)): | |
5581 | t += _(' (head closed)') |
|
5834 | t += _(' (head closed)') | |
5582 | elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]): |
|
5835 | elif not (status.modified or status.added or status.removed or renamed or | |
|
5836 | copied or subs): | |||
5583 | t += _(' (clean)') |
|
5837 | t += _(' (clean)') | |
5584 | cleanworkdir = True |
|
5838 | cleanworkdir = True | |
5585 | elif pnode not in bheads: |
|
5839 | elif pnode not in bheads: | |
@@ -5593,7 +5847,7 b' def summary(ui, repo, **opts):' | |||||
5593 | ui.write(_('commit: %s\n') % t.strip()) |
|
5847 | ui.write(_('commit: %s\n') % t.strip()) | |
5594 |
|
5848 | |||
5595 | # all ancestors of branch heads - all ancestors of parent = new csets |
|
5849 | # all ancestors of branch heads - all ancestors of parent = new csets | |
5596 | new = len(repo.changelog.findmissing([ctx.node() for ctx in parents], |
|
5850 | new = len(repo.changelog.findmissing([pctx.node() for pctx in parents], | |
5597 | bheads)) |
|
5851 | bheads)) | |
5598 |
|
5852 | |||
5599 | if new == 0: |
|
5853 | if new == 0: | |
@@ -5804,7 +6058,11 b' def tag(ui, repo, name1, *names, **opts)' | |||||
5804 | if date: |
|
6058 | if date: | |
5805 | date = util.parsedate(date) |
|
6059 | date = util.parsedate(date) | |
5806 |
|
6060 | |||
5807 | editor = cmdutil.getcommiteditor(**opts) |
|
6061 | if opts.get('remove'): | |
|
6062 | editform = 'tag.remove' | |||
|
6063 | else: | |||
|
6064 | editform = 'tag.add' | |||
|
6065 | editor = cmdutil.getcommiteditor(editform=editform, **opts) | |||
5808 |
|
6066 | |||
5809 | # don't allow tagging the null rev |
|
6067 | # don't allow tagging the null rev | |
5810 | if (not opts.get('remove') and |
|
6068 | if (not opts.get('remove') and | |
@@ -5816,7 +6074,7 b' def tag(ui, repo, name1, *names, **opts)' | |||||
5816 | finally: |
|
6074 | finally: | |
5817 | release(lock, wlock) |
|
6075 | release(lock, wlock) | |
5818 |
|
6076 | |||
5819 |
@command('tags', |
|
6077 | @command('tags', formatteropts, '') | |
5820 | def tags(ui, repo, **opts): |
|
6078 | def tags(ui, repo, **opts): | |
5821 | """list repository tags |
|
6079 | """list repository tags | |
5822 |
|
6080 | |||
@@ -5827,7 +6085,7 b' def tags(ui, repo, **opts):' | |||||
5827 | """ |
|
6085 | """ | |
5828 |
|
6086 | |||
5829 | fm = ui.formatter('tags', opts) |
|
6087 | fm = ui.formatter('tags', opts) | |
5830 | hexfunc = ui.debugflag and hex or short |
|
6088 | hexfunc = fm.hexfunc | |
5831 | tagtype = "" |
|
6089 | tagtype = "" | |
5832 |
|
6090 | |||
5833 | for t, n in reversed(repo.tagslist()): |
|
6091 | for t, n in reversed(repo.tagslist()): | |
@@ -5841,7 +6099,7 b' def tags(ui, repo, **opts):' | |||||
5841 | fm.startitem() |
|
6099 | fm.startitem() | |
5842 | fm.write('tag', '%s', t, label=label) |
|
6100 | fm.write('tag', '%s', t, label=label) | |
5843 | fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s' |
|
6101 | fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s' | |
5844 |
fm.condwrite(not ui.quiet, 'rev |
|
6102 | fm.condwrite(not ui.quiet, 'rev node', fmt, | |
5845 | repo.changelog.rev(n), hn, label=label) |
|
6103 | repo.changelog.rev(n), hn, label=label) | |
5846 | fm.condwrite(ui.verbose and tagtype, 'type', ' %s', |
|
6104 | fm.condwrite(ui.verbose and tagtype, 'type', ' %s', | |
5847 | tagtype, label=label) |
|
6105 | tagtype, label=label) |
@@ -7,7 +7,7 b'' | |||||
7 |
|
7 | |||
8 | from i18n import _ |
|
8 | from i18n import _ | |
9 | import struct |
|
9 | import struct | |
10 | import sys, os |
|
10 | import sys, os, errno, traceback, SocketServer | |
11 | import dispatch, encoding, util |
|
11 | import dispatch, encoding, util | |
12 |
|
12 | |||
13 | logfile = None |
|
13 | logfile = None | |
@@ -23,13 +23,12 b' def log(*args):' | |||||
23 |
|
23 | |||
24 | class channeledoutput(object): |
|
24 | class channeledoutput(object): | |
25 | """ |
|
25 | """ | |
26 |
Write data |
|
26 | Write data to out in the following format: | |
27 |
|
27 | |||
28 | data length (unsigned int), |
|
28 | data length (unsigned int), | |
29 | data |
|
29 | data | |
30 | """ |
|
30 | """ | |
31 |
def __init__(self, |
|
31 | def __init__(self, out, channel): | |
32 | self.in_ = in_ |
|
|||
33 | self.out = out |
|
32 | self.out = out | |
34 | self.channel = channel |
|
33 | self.channel = channel | |
35 |
|
34 | |||
@@ -43,7 +42,7 b' class channeledoutput(object):' | |||||
43 | def __getattr__(self, attr): |
|
42 | def __getattr__(self, attr): | |
44 | if attr in ('isatty', 'fileno'): |
|
43 | if attr in ('isatty', 'fileno'): | |
45 | raise AttributeError(attr) |
|
44 | raise AttributeError(attr) | |
46 |
return getattr(self. |
|
45 | return getattr(self.out, attr) | |
47 |
|
46 | |||
48 | class channeledinput(object): |
|
47 | class channeledinput(object): | |
49 | """ |
|
48 | """ | |
@@ -127,10 +126,10 b' class channeledinput(object):' | |||||
127 |
|
126 | |||
128 | class server(object): |
|
127 | class server(object): | |
129 | """ |
|
128 | """ | |
130 |
Listens for commands on |
|
129 | Listens for commands on fin, runs them and writes the output on a channel | |
131 |
based stream to |
|
130 | based stream to fout. | |
132 | """ |
|
131 | """ | |
133 |
def __init__(self, ui, repo, |
|
132 | def __init__(self, ui, repo, fin, fout): | |
134 | self.cwd = os.getcwd() |
|
133 | self.cwd = os.getcwd() | |
135 |
|
134 | |||
136 | logpath = ui.config("cmdserver", "log", None) |
|
135 | logpath = ui.config("cmdserver", "log", None) | |
@@ -138,7 +137,7 b' class server(object):' | |||||
138 | global logfile |
|
137 | global logfile | |
139 | if logpath == '-': |
|
138 | if logpath == '-': | |
140 | # write log on a special 'd' (debug) channel |
|
139 | # write log on a special 'd' (debug) channel | |
141 |
logfile = channeledoutput( |
|
140 | logfile = channeledoutput(fout, 'd') | |
142 | else: |
|
141 | else: | |
143 | logfile = open(logpath, 'a') |
|
142 | logfile = open(logpath, 'a') | |
144 |
|
143 | |||
@@ -152,15 +151,12 b' class server(object):' | |||||
152 | self.ui = ui |
|
151 | self.ui = ui | |
153 | self.repo = self.repoui = None |
|
152 | self.repo = self.repoui = None | |
154 |
|
153 | |||
155 | if mode == 'pipe': |
|
154 | self.cerr = channeledoutput(fout, 'e') | |
156 |
|
|
155 | self.cout = channeledoutput(fout, 'o') | |
157 |
|
|
156 | self.cin = channeledinput(fin, fout, 'I') | |
158 |
|
|
157 | self.cresult = channeledoutput(fout, 'r') | |
159 | self.cresult = channeledoutput(sys.stdout, sys.stdout, 'r') |
|
|||
160 |
|
158 | |||
161 |
|
|
159 | self.client = fin | |
162 | else: |
|
|||
163 | raise util.Abort(_('unknown mode %s') % mode) |
|
|||
164 |
|
160 | |||
165 | def _read(self, size): |
|
161 | def _read(self, size): | |
166 | if not size: |
|
162 | if not size: | |
@@ -236,6 +232,8 b' class server(object):' | |||||
236 | hellomsg = 'capabilities: ' + ' '.join(sorted(self.capabilities)) |
|
232 | hellomsg = 'capabilities: ' + ' '.join(sorted(self.capabilities)) | |
237 | hellomsg += '\n' |
|
233 | hellomsg += '\n' | |
238 | hellomsg += 'encoding: ' + encoding.encoding |
|
234 | hellomsg += 'encoding: ' + encoding.encoding | |
|
235 | hellomsg += '\n' | |||
|
236 | hellomsg += 'pid: %d' % os.getpid() | |||
239 |
|
237 | |||
240 | # write the hello msg in -one- chunk |
|
238 | # write the hello msg in -one- chunk | |
241 | self.cout.write(hellomsg) |
|
239 | self.cout.write(hellomsg) | |
@@ -249,3 +247,75 b' class server(object):' | |||||
249 | return 1 |
|
247 | return 1 | |
250 |
|
248 | |||
251 | return 0 |
|
249 | return 0 | |
|
250 | ||||
|
251 | class pipeservice(object): | |||
|
252 | def __init__(self, ui, repo, opts): | |||
|
253 | self.server = server(ui, repo, sys.stdin, sys.stdout) | |||
|
254 | ||||
|
255 | def init(self): | |||
|
256 | pass | |||
|
257 | ||||
|
258 | def run(self): | |||
|
259 | return self.server.serve() | |||
|
260 | ||||
|
261 | class _requesthandler(SocketServer.StreamRequestHandler): | |||
|
262 | def handle(self): | |||
|
263 | ui = self.server.ui | |||
|
264 | repo = self.server.repo | |||
|
265 | sv = server(ui, repo, self.rfile, self.wfile) | |||
|
266 | try: | |||
|
267 | try: | |||
|
268 | sv.serve() | |||
|
269 | # handle exceptions that may be raised by command server. most of | |||
|
270 | # known exceptions are caught by dispatch. | |||
|
271 | except util.Abort, inst: | |||
|
272 | ui.warn(_('abort: %s\n') % inst) | |||
|
273 | except IOError, inst: | |||
|
274 | if inst.errno != errno.EPIPE: | |||
|
275 | raise | |||
|
276 | except KeyboardInterrupt: | |||
|
277 | pass | |||
|
278 | except: # re-raises | |||
|
279 | # also write traceback to error channel. otherwise client cannot | |||
|
280 | # see it because it is written to server's stderr by default. | |||
|
281 | traceback.print_exc(file=sv.cerr) | |||
|
282 | raise | |||
|
283 | ||||
|
284 | class unixservice(object): | |||
|
285 | """ | |||
|
286 | Listens on unix domain socket and forks server per connection | |||
|
287 | """ | |||
|
288 | def __init__(self, ui, repo, opts): | |||
|
289 | self.ui = ui | |||
|
290 | self.repo = repo | |||
|
291 | self.address = opts['address'] | |||
|
292 | if not util.safehasattr(SocketServer, 'UnixStreamServer'): | |||
|
293 | raise util.Abort(_('unsupported platform')) | |||
|
294 | if not self.address: | |||
|
295 | raise util.Abort(_('no socket path specified with --address')) | |||
|
296 | ||||
|
297 | def init(self): | |||
|
298 | class cls(SocketServer.ForkingMixIn, SocketServer.UnixStreamServer): | |||
|
299 | ui = self.ui | |||
|
300 | repo = self.repo | |||
|
301 | self.server = cls(self.address, _requesthandler) | |||
|
302 | self.ui.status(_('listening at %s\n') % self.address) | |||
|
303 | self.ui.flush() # avoid buffering of status message | |||
|
304 | ||||
|
305 | def run(self): | |||
|
306 | try: | |||
|
307 | self.server.serve_forever() | |||
|
308 | finally: | |||
|
309 | os.unlink(self.address) | |||
|
310 | ||||
|
311 | _servicemap = { | |||
|
312 | 'pipe': pipeservice, | |||
|
313 | 'unix': unixservice, | |||
|
314 | } | |||
|
315 | ||||
|
316 | def createservice(ui, repo, opts): | |||
|
317 | mode = opts['cmdserver'] | |||
|
318 | try: | |||
|
319 | return _servicemap[mode](ui, repo, opts) | |||
|
320 | except KeyError: | |||
|
321 | raise util.Abort(_('unknown mode %s') % mode) |
@@ -76,7 +76,7 b' class config(object):' | |||||
76 | # no data before, remove everything |
|
76 | # no data before, remove everything | |
77 | section, item = data |
|
77 | section, item = data | |
78 | if section in self._data: |
|
78 | if section in self._data: | |
79 |
|
|
79 | self._data[section].pop(item, None) | |
80 | self._source.pop((section, item), None) |
|
80 | self._source.pop((section, item), None) | |
81 |
|
81 | |||
82 | def parse(self, src, data, sections=None, remap=None, include=None): |
|
82 | def parse(self, src, data, sections=None, remap=None, include=None): |
@@ -276,7 +276,7 b' class basectx(object):' | |||||
276 | def dirs(self): |
|
276 | def dirs(self): | |
277 | return self._dirs |
|
277 | return self._dirs | |
278 |
|
278 | |||
279 | def dirty(self): |
|
279 | def dirty(self, missing=False, merge=True, branch=True): | |
280 | return False |
|
280 | return False | |
281 |
|
281 | |||
282 | def status(self, other=None, match=None, listignored=False, |
|
282 | def status(self, other=None, match=None, listignored=False, | |
@@ -341,13 +341,16 b' class basectx(object):' | |||||
341 | l.sort() |
|
341 | l.sort() | |
342 |
|
342 | |||
343 | # we return a tuple to signify that this list isn't changing |
|
343 | # we return a tuple to signify that this list isn't changing | |
344 |
return |
|
344 | return scmutil.status(*r) | |
345 |
|
345 | |||
346 |
|
346 | |||
347 | def makememctx(repo, parents, text, user, date, branch, files, store, |
|
347 | def makememctx(repo, parents, text, user, date, branch, files, store, | |
348 | editor=None): |
|
348 | editor=None): | |
349 | def getfilectx(repo, memctx, path): |
|
349 | def getfilectx(repo, memctx, path): | |
350 |
data, |
|
350 | data, mode, copied = store.getfile(path) | |
|
351 | if data is None: | |||
|
352 | return None | |||
|
353 | islink, isexec = mode | |||
351 | return memfilectx(repo, path, data, islink=islink, isexec=isexec, |
|
354 | return memfilectx(repo, path, data, islink=islink, isexec=isexec, | |
352 | copied=copied, memctx=memctx) |
|
355 | copied=copied, memctx=memctx) | |
353 | extra = {} |
|
356 | extra = {} | |
@@ -373,91 +376,107 b' class changectx(basectx):' | |||||
373 | changeid = '.' |
|
376 | changeid = '.' | |
374 | self._repo = repo |
|
377 | self._repo = repo | |
375 |
|
378 | |||
376 | if isinstance(changeid, int): |
|
379 | try: | |
377 | try: |
|
380 | if isinstance(changeid, int): | |
378 | self._node = repo.changelog.node(changeid) |
|
381 | self._node = repo.changelog.node(changeid) | |
379 | except IndexError: |
|
382 | self._rev = changeid | |
380 | raise error.RepoLookupError( |
|
383 | return | |
381 | _("unknown revision '%s'") % changeid) |
|
384 | if isinstance(changeid, long): | |
382 |
|
|
385 | changeid = str(changeid) | |
383 | return |
|
386 | if changeid == '.': | |
384 | if isinstance(changeid, long): |
|
387 | self._node = repo.dirstate.p1() | |
385 | changeid = str(changeid) |
|
388 | self._rev = repo.changelog.rev(self._node) | |
386 | if changeid == '.': |
|
389 | return | |
387 | self._node = repo.dirstate.p1() |
|
390 | if changeid == 'null': | |
388 | self._rev = repo.changelog.rev(self._node) |
|
391 | self._node = nullid | |
389 |
|
|
392 | self._rev = nullrev | |
390 | if changeid == 'null': |
|
393 | return | |
391 | self._node = nullid |
|
394 | if changeid == 'tip': | |
392 |
self._ |
|
395 | self._node = repo.changelog.tip() | |
393 | return |
|
396 | self._rev = repo.changelog.rev(self._node) | |
394 | if changeid == 'tip': |
|
397 | return | |
395 | self._node = repo.changelog.tip() |
|
398 | if len(changeid) == 20: | |
396 | self._rev = repo.changelog.rev(self._node) |
|
399 | try: | |
397 | return |
|
400 | self._node = changeid | |
398 | if len(changeid) == 20: |
|
401 | self._rev = repo.changelog.rev(changeid) | |
|
402 | return | |||
|
403 | except error.FilteredRepoLookupError: | |||
|
404 | raise | |||
|
405 | except LookupError: | |||
|
406 | pass | |||
|
407 | ||||
399 | try: |
|
408 | try: | |
400 |
|
|
409 | r = int(changeid) | |
401 |
|
|
410 | if str(r) != changeid: | |
|
411 | raise ValueError | |||
|
412 | l = len(repo.changelog) | |||
|
413 | if r < 0: | |||
|
414 | r += l | |||
|
415 | if r < 0 or r >= l: | |||
|
416 | raise ValueError | |||
|
417 | self._rev = r | |||
|
418 | self._node = repo.changelog.node(r) | |||
402 | return |
|
419 | return | |
403 |
except |
|
420 | except error.FilteredIndexError: | |
|
421 | raise | |||
|
422 | except (ValueError, OverflowError, IndexError): | |||
404 | pass |
|
423 | pass | |
405 |
|
424 | |||
406 | try: |
|
425 | if len(changeid) == 40: | |
407 | r = int(changeid) |
|
426 | try: | |
408 |
|
|
427 | self._node = bin(changeid) | |
409 | raise ValueError |
|
428 | self._rev = repo.changelog.rev(self._node) | |
410 | l = len(repo.changelog) |
|
429 | return | |
411 | if r < 0: |
|
430 | except error.FilteredLookupError: | |
412 |
|
|
431 | raise | |
413 | if r < 0 or r >= l: |
|
432 | except (TypeError, LookupError): | |
414 |
|
|
433 | pass | |
415 | self._rev = r |
|
|||
416 | self._node = repo.changelog.node(r) |
|
|||
417 | return |
|
|||
418 | except (ValueError, OverflowError, IndexError): |
|
|||
419 | pass |
|
|||
420 |
|
434 | |||
421 | if len(changeid) == 40: |
|
435 | if changeid in repo._bookmarks: | |
422 | try: |
|
436 | self._node = repo._bookmarks[changeid] | |
423 | self._node = bin(changeid) |
|
|||
424 | self._rev = repo.changelog.rev(self._node) |
|
437 | self._rev = repo.changelog.rev(self._node) | |
425 | return |
|
438 | return | |
426 | except (TypeError, LookupError): |
|
439 | if changeid in repo._tagscache.tags: | |
|
440 | self._node = repo._tagscache.tags[changeid] | |||
|
441 | self._rev = repo.changelog.rev(self._node) | |||
|
442 | return | |||
|
443 | try: | |||
|
444 | self._node = repo.branchtip(changeid) | |||
|
445 | self._rev = repo.changelog.rev(self._node) | |||
|
446 | return | |||
|
447 | except error.FilteredRepoLookupError: | |||
|
448 | raise | |||
|
449 | except error.RepoLookupError: | |||
427 | pass |
|
450 | pass | |
428 |
|
451 | |||
429 | if changeid in repo._bookmarks: |
|
452 | self._node = repo.unfiltered().changelog._partialmatch(changeid) | |
430 | self._node = repo._bookmarks[changeid] |
|
453 | if self._node is not None: | |
431 | self._rev = repo.changelog.rev(self._node) |
|
454 | self._rev = repo.changelog.rev(self._node) | |
432 | return |
|
455 | return | |
433 | if changeid in repo._tagscache.tags: |
|
|||
434 | self._node = repo._tagscache.tags[changeid] |
|
|||
435 | self._rev = repo.changelog.rev(self._node) |
|
|||
436 | return |
|
|||
437 | try: |
|
|||
438 | self._node = repo.branchtip(changeid) |
|
|||
439 | self._rev = repo.changelog.rev(self._node) |
|
|||
440 | return |
|
|||
441 | except error.RepoLookupError: |
|
|||
442 | pass |
|
|||
443 |
|
456 | |||
444 | self._node = repo.changelog._partialmatch(changeid) |
|
457 | # lookup failed | |
445 | if self._node is not None: |
|
458 | # check if it might have come from damaged dirstate | |
446 | self._rev = repo.changelog.rev(self._node) |
|
459 | # | |
447 | return |
|
460 | # XXX we could avoid the unfiltered if we had a recognizable | |
448 |
|
461 | # exception for filtered changeset access | ||
449 | # lookup failed |
|
462 | if changeid in repo.unfiltered().dirstate.parents(): | |
450 | # check if it might have come from damaged dirstate |
|
463 | msg = _("working directory has unknown parent '%s'!") | |
451 | # |
|
464 | raise error.Abort(msg % short(changeid)) | |
452 | # XXX we could avoid the unfiltered if we had a recognizable exception |
|
465 | try: | |
453 | # for filtered changeset access |
|
466 | if len(changeid) == 20: | |
454 | if changeid in repo.unfiltered().dirstate.parents(): |
|
467 | changeid = hex(changeid) | |
455 | raise error.Abort(_("working directory has unknown parent '%s'!") |
|
468 | except TypeError: | |
456 | % short(changeid)) |
|
469 | pass | |
457 | try: |
|
470 | except (error.FilteredIndexError, error.FilteredLookupError, | |
458 | if len(changeid) == 20: |
|
471 | error.FilteredRepoLookupError): | |
459 | changeid = hex(changeid) |
|
472 | if repo.filtername == 'visible': | |
460 | except TypeError: |
|
473 | msg = _("hidden revision '%s'") % changeid | |
|
474 | hint = _('use --hidden to access hidden revisions') | |||
|
475 | raise error.FilteredRepoLookupError(msg, hint=hint) | |||
|
476 | msg = _("filtered revision '%s' (not in '%s' subset)") | |||
|
477 | msg %= (changeid, repo.filtername) | |||
|
478 | raise error.FilteredRepoLookupError(msg) | |||
|
479 | except IndexError: | |||
461 | pass |
|
480 | pass | |
462 | raise error.RepoLookupError( |
|
481 | raise error.RepoLookupError( | |
463 | _("unknown revision '%s'") % changeid) |
|
482 | _("unknown revision '%s'") % changeid) | |
@@ -539,9 +558,11 b' class changectx(basectx):' | |||||
539 | changectx=self, filelog=filelog) |
|
558 | changectx=self, filelog=filelog) | |
540 |
|
559 | |||
541 | def ancestor(self, c2, warn=False): |
|
560 | def ancestor(self, c2, warn=False): | |
542 | """ |
|
561 | """return the "best" ancestor context of self and c2 | |
543 | return the "best" ancestor context of self and c2 |
|
562 | ||
544 | """ |
|
563 | If there are multiple candidates, it will show a message and check | |
|
564 | merge.preferancestor configuration before falling back to the | |||
|
565 | revlog ancestor.""" | |||
545 | # deal with workingctxs |
|
566 | # deal with workingctxs | |
546 | n2 = c2._node |
|
567 | n2 = c2._node | |
547 | if n2 is None: |
|
568 | if n2 is None: | |
@@ -553,9 +574,10 b' class changectx(basectx):' | |||||
553 | anc = cahs[0] |
|
574 | anc = cahs[0] | |
554 | else: |
|
575 | else: | |
555 | for r in self._repo.ui.configlist('merge', 'preferancestor'): |
|
576 | for r in self._repo.ui.configlist('merge', 'preferancestor'): | |
556 |
|
|
577 | try: | |
|
578 | ctx = changectx(self._repo, r) | |||
|
579 | except error.RepoLookupError: | |||
557 | continue |
|
580 | continue | |
558 | ctx = changectx(self._repo, r) |
|
|||
559 | anc = ctx.node() |
|
581 | anc = ctx.node() | |
560 | if anc in cahs: |
|
582 | if anc in cahs: | |
561 | break |
|
583 | break | |
@@ -600,6 +622,9 b' class changectx(basectx):' | |||||
600 | continue |
|
622 | continue | |
601 | match.bad(fn, _('no such file in rev %s') % self) |
|
623 | match.bad(fn, _('no such file in rev %s') % self) | |
602 |
|
624 | |||
|
625 | def matches(self, match): | |||
|
626 | return self.walk(match) | |||
|
627 | ||||
603 | class basefilectx(object): |
|
628 | class basefilectx(object): | |
604 | """A filecontext object represents the common logic for its children: |
|
629 | """A filecontext object represents the common logic for its children: | |
605 | filectx: read-only access to a filerevision that is already present |
|
630 | filectx: read-only access to a filerevision that is already present | |
@@ -713,6 +738,10 b' class basefilectx(object):' | |||||
713 | return util.binary(self.data()) |
|
738 | return util.binary(self.data()) | |
714 | except IOError: |
|
739 | except IOError: | |
715 | return False |
|
740 | return False | |
|
741 | def isexec(self): | |||
|
742 | return 'x' in self.flags() | |||
|
743 | def islink(self): | |||
|
744 | return 'l' in self.flags() | |||
716 |
|
745 | |||
717 | def cmp(self, fctx): |
|
746 | def cmp(self, fctx): | |
718 | """compare with other file context |
|
747 | """compare with other file context | |
@@ -730,9 +759,9 b' class basefilectx(object):' | |||||
730 | return True |
|
759 | return True | |
731 |
|
760 | |||
732 | def parents(self): |
|
761 | def parents(self): | |
733 | p = self._path |
|
762 | _path = self._path | |
734 | fl = self._filelog |
|
763 | fl = self._filelog | |
735 | pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)] |
|
764 | pl = [(_path, n, fl) for n in self._filelog.parents(self._filenode)] | |
736 |
|
765 | |||
737 | r = self._filelog.renamed(self._filenode) |
|
766 | r = self._filelog.renamed(self._filenode) | |
738 | if r: |
|
767 | if r: | |
@@ -762,19 +791,16 b' class basefilectx(object):' | |||||
762 | this returns fixed value(False is used) as linenumber, |
|
791 | this returns fixed value(False is used) as linenumber, | |
763 | if "linenumber" parameter is "False".''' |
|
792 | if "linenumber" parameter is "False".''' | |
764 |
|
793 | |||
765 | def decorate_compat(text, rev): |
|
794 | if linenumber is None: | |
766 | return ([rev] * len(text.splitlines()), text) |
|
795 | def decorate(text, rev): | |
767 |
|
796 | return ([rev] * len(text.splitlines()), text) | ||
768 |
|
|
797 | elif linenumber: | |
769 | return ([(rev, False)] * len(text.splitlines()), text) |
|
798 | def decorate(text, rev): | |
770 |
|
799 | size = len(text.splitlines()) | ||
771 | def with_linenumber(text, rev): |
|
800 | return ([(rev, i) for i in xrange(1, size + 1)], text) | |
772 | size = len(text.splitlines()) |
|
801 | else: | |
773 | return ([(rev, i) for i in xrange(1, size + 1)], text) |
|
802 | def decorate(text, rev): | |
774 |
|
803 | return ([(rev, False)] * len(text.splitlines()), text) | ||
775 | decorate = (((linenumber is None) and decorate_compat) or |
|
|||
776 | (linenumber and with_linenumber) or |
|
|||
777 | without_linenumber) |
|
|||
778 |
|
804 | |||
779 | def pair(parent, child): |
|
805 | def pair(parent, child): | |
780 | blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts, |
|
806 | blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts, | |
@@ -920,7 +946,14 b' class filectx(basefilectx):' | |||||
920 | filelog=self._filelog) |
|
946 | filelog=self._filelog) | |
921 |
|
947 | |||
922 | def data(self): |
|
948 | def data(self): | |
923 | return self._filelog.read(self._filenode) |
|
949 | try: | |
|
950 | return self._filelog.read(self._filenode) | |||
|
951 | except error.CensoredNodeError: | |||
|
952 | if self._repo.ui.config("censor", "policy", "abort") == "ignore": | |||
|
953 | return "" | |||
|
954 | raise util.Abort(_("censored node: %s") % short(self._filenode), | |||
|
955 | hint="set censor.policy to ignore errors") | |||
|
956 | ||||
924 | def size(self): |
|
957 | def size(self): | |
925 | return self._filelog.size(self._filerev) |
|
958 | return self._filelog.size(self._filerev) | |
926 |
|
959 | |||
@@ -1041,17 +1074,16 b' class committablectx(basectx):' | |||||
1041 |
|
1074 | |||
1042 | copied = self._repo.dirstate.copies() |
|
1075 | copied = self._repo.dirstate.copies() | |
1043 | ff = self._flagfunc |
|
1076 | ff = self._flagfunc | |
1044 | modified, added, removed, deleted = self._status[:4] |
|
1077 | for i, l in (("a", self._status.added), ("m", self._status.modified)): | |
1045 | for i, l in (("a", added), ("m", modified)): |
|
|||
1046 | for f in l: |
|
1078 | for f in l: | |
1047 | orig = copied.get(f, f) |
|
1079 | orig = copied.get(f, f) | |
1048 | man[f] = getman(orig).get(orig, nullid) + i |
|
1080 | man[f] = getman(orig).get(orig, nullid) + i | |
1049 | try: |
|
1081 | try: | |
1050 | man.set(f, ff(f)) |
|
1082 | man.setflag(f, ff(f)) | |
1051 | except OSError: |
|
1083 | except OSError: | |
1052 | pass |
|
1084 | pass | |
1053 |
|
1085 | |||
1054 | for f in deleted + removed: |
|
1086 | for f in self._status.deleted + self._status.removed: | |
1055 | if f in man: |
|
1087 | if f in man: | |
1056 | del man[f] |
|
1088 | del man[f] | |
1057 |
|
1089 | |||
@@ -1079,22 +1111,23 b' class committablectx(basectx):' | |||||
1079 | def description(self): |
|
1111 | def description(self): | |
1080 | return self._text |
|
1112 | return self._text | |
1081 | def files(self): |
|
1113 | def files(self): | |
1082 |
return sorted(self._status |
|
1114 | return sorted(self._status.modified + self._status.added + | |
|
1115 | self._status.removed) | |||
1083 |
|
1116 | |||
1084 | def modified(self): |
|
1117 | def modified(self): | |
1085 |
return self._status |
|
1118 | return self._status.modified | |
1086 | def added(self): |
|
1119 | def added(self): | |
1087 |
return self._status |
|
1120 | return self._status.added | |
1088 | def removed(self): |
|
1121 | def removed(self): | |
1089 |
return self._status |
|
1122 | return self._status.removed | |
1090 | def deleted(self): |
|
1123 | def deleted(self): | |
1091 |
return self._status |
|
1124 | return self._status.deleted | |
1092 | def unknown(self): |
|
1125 | def unknown(self): | |
1093 |
return self._status |
|
1126 | return self._status.unknown | |
1094 | def ignored(self): |
|
1127 | def ignored(self): | |
1095 |
return self._status |
|
1128 | return self._status.ignored | |
1096 | def clean(self): |
|
1129 | def clean(self): | |
1097 |
return self._status |
|
1130 | return self._status.clean | |
1098 | def branch(self): |
|
1131 | def branch(self): | |
1099 | return encoding.tolocal(self._extra['branch']) |
|
1132 | return encoding.tolocal(self._extra['branch']) | |
1100 | def closesbranch(self): |
|
1133 | def closesbranch(self): | |
@@ -1139,13 +1172,16 b' class committablectx(basectx):' | |||||
1139 | return '' |
|
1172 | return '' | |
1140 |
|
1173 | |||
1141 | def ancestor(self, c2): |
|
1174 | def ancestor(self, c2): | |
1142 | """return the ancestor context of self and c2""" |
|
1175 | """return the "best" ancestor context of self and c2""" | |
1143 | return self._parents[0].ancestor(c2) # punt on two parents for now |
|
1176 | return self._parents[0].ancestor(c2) # punt on two parents for now | |
1144 |
|
1177 | |||
1145 | def walk(self, match): |
|
1178 | def walk(self, match): | |
1146 | return sorted(self._repo.dirstate.walk(match, sorted(self.substate), |
|
1179 | return sorted(self._repo.dirstate.walk(match, sorted(self.substate), | |
1147 | True, False)) |
|
1180 | True, False)) | |
1148 |
|
1181 | |||
|
1182 | def matches(self, match): | |||
|
1183 | return sorted(self._repo.dirstate.matches(match)) | |||
|
1184 | ||||
1149 | def ancestors(self): |
|
1185 | def ancestors(self): | |
1150 | for a in self._repo.changelog.ancestors( |
|
1186 | for a in self._repo.changelog.ancestors( | |
1151 | [p.rev() for p in self._parents]): |
|
1187 | [p.rev() for p in self._parents]): | |
@@ -1161,11 +1197,13 b' class committablectx(basectx):' | |||||
1161 |
|
1197 | |||
1162 | """ |
|
1198 | """ | |
1163 |
|
1199 | |||
|
1200 | self._repo.dirstate.beginparentchange() | |||
1164 | for f in self.modified() + self.added(): |
|
1201 | for f in self.modified() + self.added(): | |
1165 | self._repo.dirstate.normal(f) |
|
1202 | self._repo.dirstate.normal(f) | |
1166 | for f in self.removed(): |
|
1203 | for f in self.removed(): | |
1167 | self._repo.dirstate.drop(f) |
|
1204 | self._repo.dirstate.drop(f) | |
1168 | self._repo.dirstate.setparents(node) |
|
1205 | self._repo.dirstate.setparents(node) | |
|
1206 | self._repo.dirstate.endparentchange() | |||
1169 |
|
1207 | |||
1170 | def dirs(self): |
|
1208 | def dirs(self): | |
1171 | return self._repo.dirstate.dirs() |
|
1209 | return self._repo.dirstate.dirs() | |
@@ -1367,7 +1405,7 b' class workingctx(committablectx):' | |||||
1367 | modified, added, removed = s[0:3] |
|
1405 | modified, added, removed = s[0:3] | |
1368 | for f in modified + added: |
|
1406 | for f in modified + added: | |
1369 | mf[f] = None |
|
1407 | mf[f] = None | |
1370 | mf.set(f, self.flags(f)) |
|
1408 | mf.setflag(f, self.flags(f)) | |
1371 | for f in removed: |
|
1409 | for f in removed: | |
1372 | if f in mf: |
|
1410 | if f in mf: | |
1373 | del mf[f] |
|
1411 | del mf[f] | |
@@ -1392,7 +1430,7 b' class workingctx(committablectx):' | |||||
1392 | susposed to be linking to. |
|
1430 | susposed to be linking to. | |
1393 | """ |
|
1431 | """ | |
1394 | s[0] = self._filtersuspectsymlink(s[0]) |
|
1432 | s[0] = self._filtersuspectsymlink(s[0]) | |
1395 |
self._status = s |
|
1433 | self._status = scmutil.status(*s) | |
1396 | return s |
|
1434 | return s | |
1397 |
|
1435 | |||
1398 | def _dirstatestatus(self, match=None, ignored=False, clean=False, |
|
1436 | def _dirstatestatus(self, match=None, ignored=False, clean=False, | |
@@ -1403,9 +1441,9 b' class workingctx(committablectx):' | |||||
1403 | subrepos = [] |
|
1441 | subrepos = [] | |
1404 | if '.hgsub' in self: |
|
1442 | if '.hgsub' in self: | |
1405 | subrepos = sorted(self.substate) |
|
1443 | subrepos = sorted(self.substate) | |
1406 | s = self._repo.dirstate.status(match, subrepos, listignored, |
|
1444 | cmp, s = self._repo.dirstate.status(match, subrepos, listignored, | |
1407 | listclean, listunknown) |
|
1445 | listclean, listunknown) | |
1408 |
|
|
1446 | modified, added, removed, deleted, unknown, ignored, clean = s | |
1409 |
|
1447 | |||
1410 | # check for any possibly clean files |
|
1448 | # check for any possibly clean files | |
1411 | if cmp: |
|
1449 | if cmp: | |
@@ -1467,7 +1505,7 b' class workingctx(committablectx):' | |||||
1467 | # (s[1] is 'added' and s[2] is 'removed') |
|
1505 | # (s[1] is 'added' and s[2] is 'removed') | |
1468 | s = list(s) |
|
1506 | s = list(s) | |
1469 | s[1], s[2] = s[2], s[1] |
|
1507 | s[1], s[2] = s[2], s[1] | |
1470 |
return |
|
1508 | return scmutil.status(*s) | |
1471 |
|
1509 | |||
1472 | class committablefilectx(basefilectx): |
|
1510 | class committablefilectx(basefilectx): | |
1473 | """A committablefilectx provides common functionality for a file context |
|
1511 | """A committablefilectx provides common functionality for a file context | |
@@ -1548,6 +1586,14 b' class workingfilectx(committablefilectx)' | |||||
1548 | # invert comparison to reuse the same code path |
|
1586 | # invert comparison to reuse the same code path | |
1549 | return fctx.cmp(self) |
|
1587 | return fctx.cmp(self) | |
1550 |
|
1588 | |||
|
1589 | def remove(self, ignoremissing=False): | |||
|
1590 | """wraps unlink for a repo's working directory""" | |||
|
1591 | util.unlinkpath(self._repo.wjoin(self._path), ignoremissing) | |||
|
1592 | ||||
|
1593 | def write(self, data, flags): | |||
|
1594 | """wraps repo.wwrite""" | |||
|
1595 | self._repo.wwrite(self._path, data, flags) | |||
|
1596 | ||||
1551 | class memctx(committablectx): |
|
1597 | class memctx(committablectx): | |
1552 | """Use memctx to perform in-memory commits via localrepo.commitctx(). |
|
1598 | """Use memctx to perform in-memory commits via localrepo.commitctx(). | |
1553 |
|
1599 | |||
@@ -1575,6 +1621,12 b' class memctx(committablectx):' | |||||
1575 | supported by util.parsedate() and defaults to current date, extra |
|
1621 | supported by util.parsedate() and defaults to current date, extra | |
1576 | is a dictionary of metadata or is left empty. |
|
1622 | is a dictionary of metadata or is left empty. | |
1577 | """ |
|
1623 | """ | |
|
1624 | ||||
|
1625 | # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files. | |||
|
1626 | # Extensions that need to retain compatibility across Mercurial 3.1 can use | |||
|
1627 | # this field to determine what to do in filectxfn. | |||
|
1628 | _returnnoneformissingfiles = True | |||
|
1629 | ||||
1578 | def __init__(self, repo, parents, text, files, filectxfn, user=None, |
|
1630 | def __init__(self, repo, parents, text, files, filectxfn, user=None, | |
1579 | date=None, extra=None, editor=False): |
|
1631 | date=None, extra=None, editor=False): | |
1580 | super(memctx, self).__init__(repo, text, user, date, extra) |
|
1632 | super(memctx, self).__init__(repo, text, user, date, extra) | |
@@ -1584,10 +1636,24 b' class memctx(committablectx):' | |||||
1584 | p1, p2 = parents |
|
1636 | p1, p2 = parents | |
1585 | self._parents = [changectx(self._repo, p) for p in (p1, p2)] |
|
1637 | self._parents = [changectx(self._repo, p) for p in (p1, p2)] | |
1586 | files = sorted(set(files)) |
|
1638 | files = sorted(set(files)) | |
1587 |
self._status = |
|
1639 | self._status = scmutil.status(files, [], [], [], [], [], []) | |
1588 | self._filectxfn = filectxfn |
|
1640 | self._filectxfn = filectxfn | |
1589 | self.substate = {} |
|
1641 | self.substate = {} | |
1590 |
|
1642 | |||
|
1643 | # if store is not callable, wrap it in a function | |||
|
1644 | if not callable(filectxfn): | |||
|
1645 | def getfilectx(repo, memctx, path): | |||
|
1646 | fctx = filectxfn[path] | |||
|
1647 | # this is weird but apparently we only keep track of one parent | |||
|
1648 | # (why not only store that instead of a tuple?) | |||
|
1649 | copied = fctx.renamed() | |||
|
1650 | if copied: | |||
|
1651 | copied = copied[0] | |||
|
1652 | return memfilectx(repo, path, fctx.data(), | |||
|
1653 | islink=fctx.islink(), isexec=fctx.isexec(), | |||
|
1654 | copied=copied, memctx=memctx) | |||
|
1655 | self._filectxfn = getfilectx | |||
|
1656 | ||||
1591 | self._extra = extra and extra.copy() or {} |
|
1657 | self._extra = extra and extra.copy() or {} | |
1592 | if self._extra.get('branch', '') == '': |
|
1658 | if self._extra.get('branch', '') == '': | |
1593 | self._extra['branch'] = 'default' |
|
1659 | self._extra['branch'] = 'default' | |
@@ -1597,7 +1663,9 b' class memctx(committablectx):' | |||||
1597 | self._repo.savecommitmessage(self._text) |
|
1663 | self._repo.savecommitmessage(self._text) | |
1598 |
|
1664 | |||
1599 | def filectx(self, path, filelog=None): |
|
1665 | def filectx(self, path, filelog=None): | |
1600 |
"""get a file context from the working directory |
|
1666 | """get a file context from the working directory | |
|
1667 | ||||
|
1668 | Returns None if file doesn't exist and should be removed.""" | |||
1601 | return self._filectxfn(self._repo, self, path) |
|
1669 | return self._filectxfn(self._repo, self, path) | |
1602 |
|
1670 | |||
1603 | def commit(self): |
|
1671 | def commit(self): | |
@@ -1615,7 +1683,7 b' class memctx(committablectx):' | |||||
1615 | for f, fnode in man.iteritems(): |
|
1683 | for f, fnode in man.iteritems(): | |
1616 | p1node = nullid |
|
1684 | p1node = nullid | |
1617 | p2node = nullid |
|
1685 | p2node = nullid | |
1618 | p = pctx[f].parents() |
|
1686 | p = pctx[f].parents() # if file isn't in pctx, check p2? | |
1619 | if len(p) > 0: |
|
1687 | if len(p) > 0: | |
1620 | p1node = p[0].node() |
|
1688 | p1node = p[0].node() | |
1621 | if len(p) > 1: |
|
1689 | if len(p) > 1: | |
@@ -1652,9 +1720,14 b' class memfilectx(committablefilectx):' | |||||
1652 | return len(self.data()) |
|
1720 | return len(self.data()) | |
1653 | def flags(self): |
|
1721 | def flags(self): | |
1654 | return self._flags |
|
1722 | return self._flags | |
1655 | def isexec(self): |
|
|||
1656 | return 'x' in self._flags |
|
|||
1657 | def islink(self): |
|
|||
1658 | return 'l' in self._flags |
|
|||
1659 | def renamed(self): |
|
1723 | def renamed(self): | |
1660 | return self._copied |
|
1724 | return self._copied | |
|
1725 | ||||
|
1726 | def remove(self, ignoremissing=False): | |||
|
1727 | """wraps unlink for a repo's working directory""" | |||
|
1728 | # need to figure out what to do here | |||
|
1729 | del self._changectx[self._path] | |||
|
1730 | ||||
|
1731 | def write(self, data, flags): | |||
|
1732 | """wraps repo.wwrite""" | |||
|
1733 | self._data = data |
@@ -420,3 +420,22 b' def checkcopies(ctx, f, m1, m2, ca, limi' | |||||
420 |
|
420 | |||
421 | if of in ma: |
|
421 | if of in ma: | |
422 | diverge.setdefault(of, []).append(f) |
|
422 | diverge.setdefault(of, []).append(f) | |
|
423 | ||||
|
424 | def duplicatecopies(repo, rev, fromrev, skiprev=None): | |||
|
425 | '''reproduce copies from fromrev to rev in the dirstate | |||
|
426 | ||||
|
427 | If skiprev is specified, it's a revision that should be used to | |||
|
428 | filter copy records. Any copies that occur between fromrev and | |||
|
429 | skiprev will not be duplicated, even if they appear in the set of | |||
|
430 | copies between fromrev and rev. | |||
|
431 | ''' | |||
|
432 | exclude = {} | |||
|
433 | if skiprev is not None: | |||
|
434 | exclude = pathcopies(repo[fromrev], repo[skiprev]) | |||
|
435 | for dst, src in pathcopies(repo[fromrev], repo[rev]).iteritems(): | |||
|
436 | # copies.pathcopies returns backward renames, so dst might not | |||
|
437 | # actually be in the dirstate | |||
|
438 | if dst in exclude: | |||
|
439 | continue | |||
|
440 | if repo.dirstate[dst] in "nma": | |||
|
441 | repo.dirstate.copy(src, dst) |
@@ -62,7 +62,7 b' class basedag(object):' | |||||
62 | raise NotImplementedError |
|
62 | raise NotImplementedError | |
63 |
|
63 | |||
64 | def externalize(self, ix): |
|
64 | def externalize(self, ix): | |
65 |
'''return a |
|
65 | '''return a node id''' | |
66 | return self._externalize(ix) |
|
66 | return self._externalize(ix) | |
67 |
|
67 | |||
68 | def externalizeall(self, ixs): |
|
68 | def externalizeall(self, ixs): | |
@@ -73,7 +73,7 b' class basedag(object):' | |||||
73 | return list(ids) |
|
73 | return list(ids) | |
74 |
|
74 | |||
75 | def internalize(self, id): |
|
75 | def internalize(self, id): | |
76 |
'''return a |
|
76 | '''return a node ix''' | |
77 | return self._internalize(id) |
|
77 | return self._internalize(id) | |
78 |
|
78 | |||
79 | def internalizeall(self, ids, filterunknown=False): |
|
79 | def internalizeall(self, ids, filterunknown=False): |
@@ -44,6 +44,30 b' class dirstate(object):' | |||||
44 | self._lastnormaltime = 0 |
|
44 | self._lastnormaltime = 0 | |
45 | self._ui = ui |
|
45 | self._ui = ui | |
46 | self._filecache = {} |
|
46 | self._filecache = {} | |
|
47 | self._parentwriters = 0 | |||
|
48 | ||||
|
49 | def beginparentchange(self): | |||
|
50 | '''Marks the beginning of a set of changes that involve changing | |||
|
51 | the dirstate parents. If there is an exception during this time, | |||
|
52 | the dirstate will not be written when the wlock is released. This | |||
|
53 | prevents writing an incoherent dirstate where the parent doesn't | |||
|
54 | match the contents. | |||
|
55 | ''' | |||
|
56 | self._parentwriters += 1 | |||
|
57 | ||||
|
58 | def endparentchange(self): | |||
|
59 | '''Marks the end of a set of changes that involve changing the | |||
|
60 | dirstate parents. Once all parent changes have been marked done, | |||
|
61 | the wlock will be free to write the dirstate on release. | |||
|
62 | ''' | |||
|
63 | if self._parentwriters > 0: | |||
|
64 | self._parentwriters -= 1 | |||
|
65 | ||||
|
66 | def pendingparentchange(self): | |||
|
67 | '''Returns true if the dirstate is in the middle of a set of changes | |||
|
68 | that modify the dirstate parent. | |||
|
69 | ''' | |||
|
70 | return self._parentwriters > 0 | |||
47 |
|
71 | |||
48 | @propertycache |
|
72 | @propertycache | |
49 | def _map(self): |
|
73 | def _map(self): | |
@@ -60,11 +84,12 b' class dirstate(object):' | |||||
60 | @propertycache |
|
84 | @propertycache | |
61 | def _foldmap(self): |
|
85 | def _foldmap(self): | |
62 | f = {} |
|
86 | f = {} | |
|
87 | normcase = util.normcase | |||
63 | for name, s in self._map.iteritems(): |
|
88 | for name, s in self._map.iteritems(): | |
64 | if s[0] != 'r': |
|
89 | if s[0] != 'r': | |
65 |
f[ |
|
90 | f[normcase(name)] = name | |
66 | for name in self._dirs: |
|
91 | for name in self._dirs: | |
67 |
f[ |
|
92 | f[normcase(name)] = name | |
68 | f['.'] = '.' # prevents useless util.fspath() invocation |
|
93 | f['.'] = '.' # prevents useless util.fspath() invocation | |
69 | return f |
|
94 | return f | |
70 |
|
95 | |||
@@ -232,17 +257,26 b' class dirstate(object):' | |||||
232 |
|
257 | |||
233 | See localrepo.setparents() |
|
258 | See localrepo.setparents() | |
234 | """ |
|
259 | """ | |
|
260 | if self._parentwriters == 0: | |||
|
261 | raise ValueError("cannot set dirstate parent without " | |||
|
262 | "calling dirstate.beginparentchange") | |||
|
263 | ||||
235 | self._dirty = self._dirtypl = True |
|
264 | self._dirty = self._dirtypl = True | |
236 | oldp2 = self._pl[1] |
|
265 | oldp2 = self._pl[1] | |
237 | self._pl = p1, p2 |
|
266 | self._pl = p1, p2 | |
238 | copies = {} |
|
267 | copies = {} | |
239 | if oldp2 != nullid and p2 == nullid: |
|
268 | if oldp2 != nullid and p2 == nullid: | |
240 | # Discard 'm' markers when moving away from a merge state |
|
|||
241 | for f, s in self._map.iteritems(): |
|
269 | for f, s in self._map.iteritems(): | |
|
270 | # Discard 'm' markers when moving away from a merge state | |||
242 | if s[0] == 'm': |
|
271 | if s[0] == 'm': | |
243 | if f in self._copymap: |
|
272 | if f in self._copymap: | |
244 | copies[f] = self._copymap[f] |
|
273 | copies[f] = self._copymap[f] | |
245 | self.normallookup(f) |
|
274 | self.normallookup(f) | |
|
275 | # Also fix up otherparent markers | |||
|
276 | elif s[0] == 'n' and s[2] == -2: | |||
|
277 | if f in self._copymap: | |||
|
278 | copies[f] = self._copymap[f] | |||
|
279 | self.add(f) | |||
246 | return copies |
|
280 | return copies | |
247 |
|
281 | |||
248 | def setbranch(self, branch): |
|
282 | def setbranch(self, branch): | |
@@ -300,6 +334,7 b' class dirstate(object):' | |||||
300 | delattr(self, a) |
|
334 | delattr(self, a) | |
301 | self._lastnormaltime = 0 |
|
335 | self._lastnormaltime = 0 | |
302 | self._dirty = False |
|
336 | self._dirty = False | |
|
337 | self._parentwriters = 0 | |||
303 |
|
338 | |||
304 | def copy(self, source, dest): |
|
339 | def copy(self, source, dest): | |
305 | """Mark dest as a copy of source. Unmark dest if source is None.""" |
|
340 | """Mark dest as a copy of source. Unmark dest if source is None.""" | |
@@ -380,7 +415,13 b' class dirstate(object):' | |||||
380 | if self._pl[1] == nullid: |
|
415 | if self._pl[1] == nullid: | |
381 | raise util.Abort(_("setting %r to other parent " |
|
416 | raise util.Abort(_("setting %r to other parent " | |
382 | "only allowed in merges") % f) |
|
417 | "only allowed in merges") % f) | |
383 | self._addpath(f, 'n', 0, -2, -1) |
|
418 | if f in self and self[f] == 'n': | |
|
419 | # merge-like | |||
|
420 | self._addpath(f, 'm', 0, -2, -1) | |||
|
421 | else: | |||
|
422 | # add-like | |||
|
423 | self._addpath(f, 'n', 0, -2, -1) | |||
|
424 | ||||
384 | if f in self._copymap: |
|
425 | if f in self._copymap: | |
385 | del self._copymap[f] |
|
426 | del self._copymap[f] | |
386 |
|
427 | |||
@@ -410,11 +451,7 b' class dirstate(object):' | |||||
410 | '''Mark a file merged.''' |
|
451 | '''Mark a file merged.''' | |
411 | if self._pl[1] == nullid: |
|
452 | if self._pl[1] == nullid: | |
412 | return self.normallookup(f) |
|
453 | return self.normallookup(f) | |
413 | s = os.lstat(self._join(f)) |
|
454 | return self.otherparent(f) | |
414 | self._addpath(f, 'm', s.st_mode, |
|
|||
415 | s.st_size & _rangemask, int(s.st_mtime) & _rangemask) |
|
|||
416 | if f in self._copymap: |
|
|||
417 | del self._copymap[f] |
|
|||
418 |
|
455 | |||
419 | def drop(self, f): |
|
456 | def drop(self, f): | |
420 | '''Drop a file from the dirstate''' |
|
457 | '''Drop a file from the dirstate''' | |
@@ -772,28 +809,17 b' class dirstate(object):' | |||||
772 |
|
809 | |||
773 | def status(self, match, subrepos, ignored, clean, unknown): |
|
810 | def status(self, match, subrepos, ignored, clean, unknown): | |
774 | '''Determine the status of the working copy relative to the |
|
811 | '''Determine the status of the working copy relative to the | |
775 |
dirstate and return a |
|
812 | dirstate and return a pair of (unsure, status), where status is of type | |
776 | removed, deleted, unknown, ignored, clean), where: |
|
813 | scmutil.status and: | |
777 |
|
814 | |||
778 | unsure: |
|
815 | unsure: | |
779 | files that might have been modified since the dirstate was |
|
816 | files that might have been modified since the dirstate was | |
780 | written, but need to be read to be sure (size is the same |
|
817 | written, but need to be read to be sure (size is the same | |
781 | but mtime differs) |
|
818 | but mtime differs) | |
782 | modified: |
|
819 | status.modified: | |
783 | files that have definitely been modified since the dirstate |
|
820 | files that have definitely been modified since the dirstate | |
784 | was written (different size or mode) |
|
821 | was written (different size or mode) | |
785 |
|
|
822 | status.clean: | |
786 | files that have been explicitly added with hg add |
|
|||
787 | removed: |
|
|||
788 | files that have been explicitly removed with hg remove |
|
|||
789 | deleted: |
|
|||
790 | files that have been deleted through other means ("missing") |
|
|||
791 | unknown: |
|
|||
792 | files not in the dirstate that are not ignored |
|
|||
793 | ignored: |
|
|||
794 | files not in the dirstate that are ignored |
|
|||
795 | (by _dirignore()) |
|
|||
796 | clean: |
|
|||
797 | files that have definitely not been modified since the |
|
823 | files that have definitely not been modified since the | |
798 | dirstate was written |
|
824 | dirstate was written | |
799 | ''' |
|
825 | ''' | |
@@ -871,5 +897,23 b' class dirstate(object):' | |||||
871 | elif state == 'r': |
|
897 | elif state == 'r': | |
872 | radd(fn) |
|
898 | radd(fn) | |
873 |
|
899 | |||
874 |
return (lookup, modified, added, removed, deleted, |
|
900 | return (lookup, scmutil.status(modified, added, removed, deleted, | |
875 | clean) |
|
901 | unknown, ignored, clean)) | |
|
902 | ||||
|
903 | def matches(self, match): | |||
|
904 | ''' | |||
|
905 | return files in the dirstate (in whatever state) filtered by match | |||
|
906 | ''' | |||
|
907 | dmap = self._map | |||
|
908 | if match.always(): | |||
|
909 | return dmap.keys() | |||
|
910 | files = match.files() | |||
|
911 | if match.matchfn == match.exact: | |||
|
912 | # fast path -- filter the other way around, since typically files is | |||
|
913 | # much smaller than dmap | |||
|
914 | return [f for f in files if f in dmap] | |||
|
915 | if not match.anypats() and util.all(fn in dmap for fn in files): | |||
|
916 | # fast path -- all the values are known to be files, so just return | |||
|
917 | # that | |||
|
918 | return list(files) | |||
|
919 | return [f for f in dmap if match(f)] |
@@ -58,6 +58,8 b' def dispatch(req):' | |||||
58 | if len(inst.args) > 1: |
|
58 | if len(inst.args) > 1: | |
59 | ferr.write(_("hg: parse error at %s: %s\n") % |
|
59 | ferr.write(_("hg: parse error at %s: %s\n") % | |
60 | (inst.args[1], inst.args[0])) |
|
60 | (inst.args[1], inst.args[0])) | |
|
61 | if (inst.args[0][0] == ' '): | |||
|
62 | ferr.write(_("unexpected leading whitespace\n")) | |||
61 | else: |
|
63 | else: | |
62 | ferr.write(_("hg: parse error: %s\n") % inst.args[0]) |
|
64 | ferr.write(_("hg: parse error: %s\n") % inst.args[0]) | |
63 | return -1 |
|
65 | return -1 | |
@@ -155,6 +157,8 b' def _runcatch(req):' | |||||
155 | if len(inst.args) > 1: |
|
157 | if len(inst.args) > 1: | |
156 | ui.warn(_("hg: parse error at %s: %s\n") % |
|
158 | ui.warn(_("hg: parse error at %s: %s\n") % | |
157 | (inst.args[1], inst.args[0])) |
|
159 | (inst.args[1], inst.args[0])) | |
|
160 | if (inst.args[0][0] == ' '): | |||
|
161 | ui.warn(_("unexpected leading whitespace\n")) | |||
158 | else: |
|
162 | else: | |
159 | ui.warn(_("hg: parse error: %s\n") % inst.args[0]) |
|
163 | ui.warn(_("hg: parse error: %s\n") % inst.args[0]) | |
160 | return -1 |
|
164 | return -1 | |
@@ -189,6 +193,8 b' def _runcatch(req):' | |||||
189 | ui.warn(_(" empty string\n")) |
|
193 | ui.warn(_(" empty string\n")) | |
190 | else: |
|
194 | else: | |
191 | ui.warn("\n%r\n" % util.ellipsis(inst.args[1])) |
|
195 | ui.warn("\n%r\n" % util.ellipsis(inst.args[1])) | |
|
196 | except error.CensoredNodeError, inst: | |||
|
197 | ui.warn(_("abort: file censored %s!\n") % inst) | |||
192 | except error.RevlogError, inst: |
|
198 | except error.RevlogError, inst: | |
193 | ui.warn(_("abort: %s!\n") % inst) |
|
199 | ui.warn(_("abort: %s!\n") % inst) | |
194 | except error.SignalInterrupt: |
|
200 | except error.SignalInterrupt: | |
@@ -331,17 +337,40 b' def aliasargs(fn, givenargs):' | |||||
331 | args = shlex.split(cmd) |
|
337 | args = shlex.split(cmd) | |
332 | return args + givenargs |
|
338 | return args + givenargs | |
333 |
|
339 | |||
|
340 | def aliasinterpolate(name, args, cmd): | |||
|
341 | '''interpolate args into cmd for shell aliases | |||
|
342 | ||||
|
343 | This also handles $0, $@ and "$@". | |||
|
344 | ''' | |||
|
345 | # util.interpolate can't deal with "$@" (with quotes) because it's only | |||
|
346 | # built to match prefix + patterns. | |||
|
347 | replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args)) | |||
|
348 | replacemap['$0'] = name | |||
|
349 | replacemap['$$'] = '$' | |||
|
350 | replacemap['$@'] = ' '.join(args) | |||
|
351 | # Typical Unix shells interpolate "$@" (with quotes) as all the positional | |||
|
352 | # parameters, separated out into words. Emulate the same behavior here by | |||
|
353 | # quoting the arguments individually. POSIX shells will then typically | |||
|
354 | # tokenize each argument into exactly one word. | |||
|
355 | replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args) | |||
|
356 | # escape '\$' for regex | |||
|
357 | regex = '|'.join(replacemap.keys()).replace('$', r'\$') | |||
|
358 | r = re.compile(regex) | |||
|
359 | return r.sub(lambda x: replacemap[x.group()], cmd) | |||
|
360 | ||||
334 | class cmdalias(object): |
|
361 | class cmdalias(object): | |
335 | def __init__(self, name, definition, cmdtable): |
|
362 | def __init__(self, name, definition, cmdtable): | |
336 | self.name = self.cmd = name |
|
363 | self.name = self.cmd = name | |
337 | self.cmdname = '' |
|
364 | self.cmdname = '' | |
338 | self.definition = definition |
|
365 | self.definition = definition | |
|
366 | self.fn = None | |||
339 | self.args = [] |
|
367 | self.args = [] | |
340 | self.opts = [] |
|
368 | self.opts = [] | |
341 | self.help = '' |
|
369 | self.help = '' | |
342 | self.norepo = True |
|
370 | self.norepo = True | |
343 | self.optionalrepo = False |
|
371 | self.optionalrepo = False | |
344 |
self.badalias = |
|
372 | self.badalias = None | |
|
373 | self.unknowncmd = False | |||
345 |
|
374 | |||
346 | try: |
|
375 | try: | |
347 | aliases, entry = cmdutil.findcmd(self.name, cmdtable) |
|
376 | aliases, entry = cmdutil.findcmd(self.name, cmdtable) | |
@@ -354,11 +383,7 b' class cmdalias(object):' | |||||
354 | self.shadows = False |
|
383 | self.shadows = False | |
355 |
|
384 | |||
356 | if not self.definition: |
|
385 | if not self.definition: | |
357 | def fn(ui, *args): |
|
386 | self.badalias = _("no definition for alias '%s'") % self.name | |
358 | ui.warn(_("no definition for alias '%s'\n") % self.name) |
|
|||
359 | return -1 |
|
|||
360 | self.fn = fn |
|
|||
361 | self.badalias = True |
|
|||
362 | return |
|
387 | return | |
363 |
|
388 | |||
364 | if self.definition.startswith('!'): |
|
389 | if self.definition.startswith('!'): | |
@@ -376,10 +401,7 b' class cmdalias(object):' | |||||
376 | % (int(m.groups()[0]), self.name)) |
|
401 | % (int(m.groups()[0]), self.name)) | |
377 | return '' |
|
402 | return '' | |
378 | cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:]) |
|
403 | cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:]) | |
379 | replace = dict((str(i + 1), arg) for i, arg in enumerate(args)) |
|
404 | cmd = aliasinterpolate(self.name, args, cmd) | |
380 | replace['0'] = self.name |
|
|||
381 | replace['@'] = ' '.join(args) |
|
|||
382 | cmd = util.interpolate(r'\$', replace, cmd, escape_prefix=True) |
|
|||
383 | return util.system(cmd, environ=env, out=ui.fout) |
|
405 | return util.system(cmd, environ=env, out=ui.fout) | |
384 | self.fn = fn |
|
406 | self.fn = fn | |
385 | return |
|
407 | return | |
@@ -387,26 +409,17 b' class cmdalias(object):' | |||||
387 | try: |
|
409 | try: | |
388 | args = shlex.split(self.definition) |
|
410 | args = shlex.split(self.definition) | |
389 | except ValueError, inst: |
|
411 | except ValueError, inst: | |
390 | def fn(ui, *args): |
|
412 | self.badalias = (_("error in definition for alias '%s': %s") | |
391 | ui.warn(_("error in definition for alias '%s': %s\n") |
|
413 | % (self.name, inst)) | |
392 | % (self.name, inst)) |
|
|||
393 | return -1 |
|
|||
394 | self.fn = fn |
|
|||
395 | self.badalias = True |
|
|||
396 | return |
|
414 | return | |
397 | self.cmdname = cmd = args.pop(0) |
|
415 | self.cmdname = cmd = args.pop(0) | |
398 | args = map(util.expandpath, args) |
|
416 | args = map(util.expandpath, args) | |
399 |
|
417 | |||
400 | for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"): |
|
418 | for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"): | |
401 | if _earlygetopt([invalidarg], args): |
|
419 | if _earlygetopt([invalidarg], args): | |
402 | def fn(ui, *args): |
|
420 | self.badalias = (_("error in definition for alias '%s': %s may " | |
403 | ui.warn(_("error in definition for alias '%s': %s may only " |
|
421 | "only be given on the command line") | |
404 |
|
|
422 | % (self.name, invalidarg)) | |
405 | % (self.name, invalidarg)) |
|
|||
406 | return -1 |
|
|||
407 |
|
||||
408 | self.fn = fn |
|
|||
409 | self.badalias = True |
|
|||
410 | return |
|
423 | return | |
411 |
|
424 | |||
412 | try: |
|
425 | try: | |
@@ -427,26 +440,24 b' class cmdalias(object):' | |||||
427 | self.__doc__ = self.fn.__doc__ |
|
440 | self.__doc__ = self.fn.__doc__ | |
428 |
|
441 | |||
429 | except error.UnknownCommand: |
|
442 | except error.UnknownCommand: | |
430 | def fn(ui, *args): |
|
443 | self.badalias = (_("alias '%s' resolves to unknown command '%s'") | |
431 | ui.warn(_("alias '%s' resolves to unknown command '%s'\n") \ |
|
444 | % (self.name, cmd)) | |
432 | % (self.name, cmd)) |
|
445 | self.unknowncmd = True | |
|
446 | except error.AmbiguousCommand: | |||
|
447 | self.badalias = (_("alias '%s' resolves to ambiguous command '%s'") | |||
|
448 | % (self.name, cmd)) | |||
|
449 | ||||
|
450 | def __call__(self, ui, *args, **opts): | |||
|
451 | if self.badalias: | |||
|
452 | hint = None | |||
|
453 | if self.unknowncmd: | |||
433 | try: |
|
454 | try: | |
434 | # check if the command is in a disabled extension |
|
455 | # check if the command is in a disabled extension | |
435 | commands.help_(ui, cmd, unknowncmd=True) |
|
456 | cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2] | |
|
457 | hint = _("'%s' is provided by '%s' extension") % (cmd, ext) | |||
436 | except error.UnknownCommand: |
|
458 | except error.UnknownCommand: | |
437 | pass |
|
459 | pass | |
438 | return -1 |
|
460 | raise util.Abort(self.badalias, hint=hint) | |
439 | self.fn = fn |
|
|||
440 | self.badalias = True |
|
|||
441 | except error.AmbiguousCommand: |
|
|||
442 | def fn(ui, *args): |
|
|||
443 | ui.warn(_("alias '%s' resolves to ambiguous command '%s'\n") \ |
|
|||
444 | % (self.name, cmd)) |
|
|||
445 | return -1 |
|
|||
446 | self.fn = fn |
|
|||
447 | self.badalias = True |
|
|||
448 |
|
||||
449 | def __call__(self, ui, *args, **opts): |
|
|||
450 | if self.shadows: |
|
461 | if self.shadows: | |
451 | ui.debug("alias '%s' shadows command '%s'\n" % |
|
462 | ui.debug("alias '%s' shadows command '%s'\n" % | |
452 | (self.name, self.cmdname)) |
|
463 | (self.name, self.cmdname)) |
@@ -258,11 +258,26 b" def trim(s, width, ellipsis='', leftside" | |||||
258 | return concat(usub.encode(encoding)) |
|
258 | return concat(usub.encode(encoding)) | |
259 | return ellipsis # no enough room for multi-column characters |
|
259 | return ellipsis # no enough room for multi-column characters | |
260 |
|
260 | |||
|
261 | def _asciilower(s): | |||
|
262 | '''convert a string to lowercase if ASCII | |||
|
263 | ||||
|
264 | Raises UnicodeDecodeError if non-ASCII characters are found.''' | |||
|
265 | s.decode('ascii') | |||
|
266 | return s.lower() | |||
|
267 | ||||
|
268 | def asciilower(s): | |||
|
269 | # delay importing avoids cyclic dependency around "parsers" in | |||
|
270 | # pure Python build (util => i18n => encoding => parsers => util) | |||
|
271 | import parsers | |||
|
272 | impl = getattr(parsers, 'asciilower', _asciilower) | |||
|
273 | global asciilower | |||
|
274 | asciilower = impl | |||
|
275 | return impl(s) | |||
|
276 | ||||
261 | def lower(s): |
|
277 | def lower(s): | |
262 | "best-effort encoding-aware case-folding of local string s" |
|
278 | "best-effort encoding-aware case-folding of local string s" | |
263 | try: |
|
279 | try: | |
264 | s.decode('ascii') # throw exception for non-ASCII character |
|
280 | return asciilower(s) | |
265 | return s.lower() |
|
|||
266 | except UnicodeDecodeError: |
|
281 | except UnicodeDecodeError: | |
267 | pass |
|
282 | pass | |
268 | try: |
|
283 | try: | |
@@ -302,6 +317,49 b' def upper(s):' | |||||
302 | except LookupError, k: |
|
317 | except LookupError, k: | |
303 | raise error.Abort(k, hint="please check your locale settings") |
|
318 | raise error.Abort(k, hint="please check your locale settings") | |
304 |
|
319 | |||
|
320 | _jsonmap = {} | |||
|
321 | ||||
|
322 | def jsonescape(s): | |||
|
323 | '''returns a string suitable for JSON | |||
|
324 | ||||
|
325 | JSON is problematic for us because it doesn't support non-Unicode | |||
|
326 | bytes. To deal with this, we take the following approach: | |||
|
327 | ||||
|
328 | - localstr objects are converted back to UTF-8 | |||
|
329 | - valid UTF-8/ASCII strings are passed as-is | |||
|
330 | - other strings are converted to UTF-8b surrogate encoding | |||
|
331 | - apply JSON-specified string escaping | |||
|
332 | ||||
|
333 | (escapes are doubled in these tests) | |||
|
334 | ||||
|
335 | >>> jsonescape('this is a test') | |||
|
336 | 'this is a test' | |||
|
337 | >>> jsonescape('escape characters: \\0 \\x0b \\t \\n \\r \\" \\\\') | |||
|
338 | 'escape characters: \\\\u0000 \\\\u000b \\\\t \\\\n \\\\r \\\\" \\\\\\\\' | |||
|
339 | >>> jsonescape('a weird byte: \\xdd') | |||
|
340 | 'a weird byte: \\xed\\xb3\\x9d' | |||
|
341 | >>> jsonescape('utf-8: caf\\xc3\\xa9') | |||
|
342 | 'utf-8: caf\\xc3\\xa9' | |||
|
343 | >>> jsonescape('') | |||
|
344 | '' | |||
|
345 | ''' | |||
|
346 | ||||
|
347 | if not _jsonmap: | |||
|
348 | for x in xrange(32): | |||
|
349 | _jsonmap[chr(x)] = "\u%04x" %x | |||
|
350 | for x in xrange(32, 256): | |||
|
351 | c = chr(x) | |||
|
352 | _jsonmap[c] = c | |||
|
353 | _jsonmap['\t'] = '\\t' | |||
|
354 | _jsonmap['\n'] = '\\n' | |||
|
355 | _jsonmap['\"'] = '\\"' | |||
|
356 | _jsonmap['\\'] = '\\\\' | |||
|
357 | _jsonmap['\b'] = '\\b' | |||
|
358 | _jsonmap['\f'] = '\\f' | |||
|
359 | _jsonmap['\r'] = '\\r' | |||
|
360 | ||||
|
361 | return ''.join(_jsonmap[c] for c in toutf8b(s)) | |||
|
362 | ||||
305 | def toutf8b(s): |
|
363 | def toutf8b(s): | |
306 | '''convert a local, possibly-binary string into UTF-8b |
|
364 | '''convert a local, possibly-binary string into UTF-8b | |
307 |
|
365 | |||
@@ -336,8 +394,8 b' def toutf8b(s):' | |||||
336 | return s._utf8 |
|
394 | return s._utf8 | |
337 |
|
395 | |||
338 | try: |
|
396 | try: | |
339 |
|
|
397 | s.decode('utf-8') | |
340 |
|
|
398 | return s | |
341 | except UnicodeDecodeError: |
|
399 | except UnicodeDecodeError: | |
342 | # surrogate-encode any characters that don't round-trip |
|
400 | # surrogate-encode any characters that don't round-trip | |
343 | s2 = s.decode('utf-8', 'ignore').encode('utf-8') |
|
401 | s2 = s.decode('utf-8', 'ignore').encode('utf-8') |
@@ -16,6 +16,9 b' imports.' | |||||
16 | class RevlogError(Exception): |
|
16 | class RevlogError(Exception): | |
17 | pass |
|
17 | pass | |
18 |
|
18 | |||
|
19 | class FilteredIndexError(IndexError): | |||
|
20 | pass | |||
|
21 | ||||
19 | class LookupError(RevlogError, KeyError): |
|
22 | class LookupError(RevlogError, KeyError): | |
20 | def __init__(self, name, index, message): |
|
23 | def __init__(self, name, index, message): | |
21 | self.name = name |
|
24 | self.name = name | |
@@ -27,6 +30,9 b' class LookupError(RevlogError, KeyError)' | |||||
27 | def __str__(self): |
|
30 | def __str__(self): | |
28 | return RevlogError.__str__(self) |
|
31 | return RevlogError.__str__(self) | |
29 |
|
32 | |||
|
33 | class FilteredLookupError(LookupError): | |||
|
34 | pass | |||
|
35 | ||||
30 | class ManifestLookupError(LookupError): |
|
36 | class ManifestLookupError(LookupError): | |
31 | pass |
|
37 | pass | |
32 |
|
38 | |||
@@ -43,13 +49,13 b' class Abort(Exception):' | |||||
43 | self.hint = kw.get('hint') |
|
49 | self.hint = kw.get('hint') | |
44 |
|
50 | |||
45 | class ConfigError(Abort): |
|
51 | class ConfigError(Abort): | |
46 |
|
|
52 | """Exception raised when parsing config files""" | |
47 |
|
53 | |||
48 | class OutOfBandError(Exception): |
|
54 | class OutOfBandError(Exception): | |
49 |
|
|
55 | """Exception raised when a remote repo reports failure""" | |
50 |
|
56 | |||
51 | class ParseError(Exception): |
|
57 | class ParseError(Exception): | |
52 |
|
|
58 | """Exception raised when parsing config files (msg[, pos])""" | |
53 |
|
59 | |||
54 | class RepoError(Exception): |
|
60 | class RepoError(Exception): | |
55 | def __init__(self, *args, **kw): |
|
61 | def __init__(self, *args, **kw): | |
@@ -59,6 +65,9 b' class RepoError(Exception):' | |||||
59 | class RepoLookupError(RepoError): |
|
65 | class RepoLookupError(RepoError): | |
60 | pass |
|
66 | pass | |
61 |
|
67 | |||
|
68 | class FilteredRepoLookupError(RepoLookupError): | |||
|
69 | pass | |||
|
70 | ||||
62 | class CapabilityError(RepoError): |
|
71 | class CapabilityError(RepoError): | |
63 | pass |
|
72 | pass | |
64 |
|
73 | |||
@@ -102,6 +111,7 b' class PushRaced(RuntimeError):' | |||||
102 | class BundleValueError(ValueError): |
|
111 | class BundleValueError(ValueError): | |
103 | """error raised when bundle2 cannot be processed""" |
|
112 | """error raised when bundle2 cannot be processed""" | |
104 |
|
113 | |||
|
114 | class UnsupportedPartError(BundleValueError): | |||
105 | def __init__(self, parttype=None, params=()): |
|
115 | def __init__(self, parttype=None, params=()): | |
106 | self.parttype = parttype |
|
116 | self.parttype = parttype | |
107 | self.params = params |
|
117 | self.params = params | |
@@ -117,3 +127,9 b' class ReadOnlyPartError(RuntimeError):' | |||||
117 | """error raised when code tries to alter a part being generated""" |
|
127 | """error raised when code tries to alter a part being generated""" | |
118 | pass |
|
128 | pass | |
119 |
|
129 | |||
|
130 | class CensoredNodeError(RevlogError): | |||
|
131 | """error raised when content verification fails on a censored node""" | |||
|
132 | ||||
|
133 | def __init__(self, filename, node): | |||
|
134 | from node import short | |||
|
135 | RevlogError.__init__(self, '%s:%s' % (filename, short(node))) |
This diff has been collapsed as it changes many lines, (734 lines changed) Show them Hide them | |||||
@@ -9,7 +9,7 b' from i18n import _' | |||||
9 | from node import hex, nullid |
|
9 | from node import hex, nullid | |
10 | import errno, urllib |
|
10 | import errno, urllib | |
11 | import util, scmutil, changegroup, base85, error |
|
11 | import util, scmutil, changegroup, base85, error | |
12 | import discovery, phases, obsolete, bookmarks, bundle2, pushkey |
|
12 | import discovery, phases, obsolete, bookmarks as bookmod, bundle2, pushkey | |
13 |
|
13 | |||
14 | def readbundle(ui, fh, fname, vfs=None): |
|
14 | def readbundle(ui, fh, fname, vfs=None): | |
15 | header = changegroup.readexactly(fh, 4) |
|
15 | header = changegroup.readexactly(fh, 4) | |
@@ -31,12 +31,26 b' def readbundle(ui, fh, fname, vfs=None):' | |||||
31 | if version == '10': |
|
31 | if version == '10': | |
32 | if alg is None: |
|
32 | if alg is None: | |
33 | alg = changegroup.readexactly(fh, 2) |
|
33 | alg = changegroup.readexactly(fh, 2) | |
34 |
return changegroup. |
|
34 | return changegroup.cg1unpacker(fh, alg) | |
35 |
elif version == '2 |
|
35 | elif version == '2Y': | |
36 | return bundle2.unbundle20(ui, fh, header=magic + version) |
|
36 | return bundle2.unbundle20(ui, fh, header=magic + version) | |
37 | else: |
|
37 | else: | |
38 | raise util.Abort(_('%s: unknown bundle version %s') % (fname, version)) |
|
38 | raise util.Abort(_('%s: unknown bundle version %s') % (fname, version)) | |
39 |
|
39 | |||
|
40 | def buildobsmarkerspart(bundler, markers): | |||
|
41 | """add an obsmarker part to the bundler with <markers> | |||
|
42 | ||||
|
43 | No part is created if markers is empty. | |||
|
44 | Raises ValueError if the bundler doesn't support any known obsmarker format. | |||
|
45 | """ | |||
|
46 | if markers: | |||
|
47 | remoteversions = bundle2.obsmarkersversion(bundler.capabilities) | |||
|
48 | version = obsolete.commonversion(remoteversions) | |||
|
49 | if version is None: | |||
|
50 | raise ValueError('bundler do not support common obsmarker format') | |||
|
51 | stream = obsolete.encodemarkers(markers, True, version=version) | |||
|
52 | return bundler.newpart('B2X:OBSMARKERS', data=stream) | |||
|
53 | return None | |||
40 |
|
54 | |||
41 | class pushoperation(object): |
|
55 | class pushoperation(object): | |
42 | """A object that represent a single push operation |
|
56 | """A object that represent a single push operation | |
@@ -47,7 +61,8 b' class pushoperation(object):' | |||||
47 | afterward. |
|
61 | afterward. | |
48 | """ |
|
62 | """ | |
49 |
|
63 | |||
50 |
def __init__(self, repo, remote, force=False, revs=None, newbranch=False |
|
64 | def __init__(self, repo, remote, force=False, revs=None, newbranch=False, | |
|
65 | bookmarks=()): | |||
51 | # repo we push from |
|
66 | # repo we push from | |
52 | self.repo = repo |
|
67 | self.repo = repo | |
53 | self.ui = repo.ui |
|
68 | self.ui = repo.ui | |
@@ -57,6 +72,8 b' class pushoperation(object):' | |||||
57 | self.force = force |
|
72 | self.force = force | |
58 | # revs to be pushed (None is "all") |
|
73 | # revs to be pushed (None is "all") | |
59 | self.revs = revs |
|
74 | self.revs = revs | |
|
75 | # bookmark explicitly pushed | |||
|
76 | self.bookmarks = bookmarks | |||
60 | # allow push of new branch |
|
77 | # allow push of new branch | |
61 | self.newbranch = newbranch |
|
78 | self.newbranch = newbranch | |
62 | # did a local lock get acquired? |
|
79 | # did a local lock get acquired? | |
@@ -64,23 +81,86 b' class pushoperation(object):' | |||||
64 | # step already performed |
|
81 | # step already performed | |
65 | # (used to check what steps have been already performed through bundle2) |
|
82 | # (used to check what steps have been already performed through bundle2) | |
66 | self.stepsdone = set() |
|
83 | self.stepsdone = set() | |
67 | # Integer version of the push result |
|
84 | # Integer version of the changegroup push result | |
68 | # - None means nothing to push |
|
85 | # - None means nothing to push | |
69 | # - 0 means HTTP error |
|
86 | # - 0 means HTTP error | |
70 | # - 1 means we pushed and remote head count is unchanged *or* |
|
87 | # - 1 means we pushed and remote head count is unchanged *or* | |
71 | # we have outgoing changesets but refused to push |
|
88 | # we have outgoing changesets but refused to push | |
72 | # - other values as described by addchangegroup() |
|
89 | # - other values as described by addchangegroup() | |
73 | self.ret = None |
|
90 | self.cgresult = None | |
|
91 | # Boolean value for the bookmark push | |||
|
92 | self.bkresult = None | |||
74 | # discover.outgoing object (contains common and outgoing data) |
|
93 | # discover.outgoing object (contains common and outgoing data) | |
75 | self.outgoing = None |
|
94 | self.outgoing = None | |
76 | # all remote heads before the push |
|
95 | # all remote heads before the push | |
77 | self.remoteheads = None |
|
96 | self.remoteheads = None | |
78 | # testable as a boolean indicating if any nodes are missing locally. |
|
97 | # testable as a boolean indicating if any nodes are missing locally. | |
79 | self.incoming = None |
|
98 | self.incoming = None | |
80 | # set of all heads common after changeset bundle push |
|
99 | # phases changes that must be pushed along side the changesets | |
81 |
self. |
|
100 | self.outdatedphases = None | |
|
101 | # phases changes that must be pushed if changeset push fails | |||
|
102 | self.fallbackoutdatedphases = None | |||
|
103 | # outgoing obsmarkers | |||
|
104 | self.outobsmarkers = set() | |||
|
105 | # outgoing bookmarks | |||
|
106 | self.outbookmarks = [] | |||
|
107 | ||||
|
108 | @util.propertycache | |||
|
109 | def futureheads(self): | |||
|
110 | """future remote heads if the changeset push succeeds""" | |||
|
111 | return self.outgoing.missingheads | |||
82 |
|
112 | |||
83 | def push(repo, remote, force=False, revs=None, newbranch=False): |
|
113 | @util.propertycache | |
|
114 | def fallbackheads(self): | |||
|
115 | """future remote heads if the changeset push fails""" | |||
|
116 | if self.revs is None: | |||
|
117 | # not target to push, all common are relevant | |||
|
118 | return self.outgoing.commonheads | |||
|
119 | unfi = self.repo.unfiltered() | |||
|
120 | # I want cheads = heads(::missingheads and ::commonheads) | |||
|
121 | # (missingheads is revs with secret changeset filtered out) | |||
|
122 | # | |||
|
123 | # This can be expressed as: | |||
|
124 | # cheads = ( (missingheads and ::commonheads) | |||
|
125 | # + (commonheads and ::missingheads))" | |||
|
126 | # ) | |||
|
127 | # | |||
|
128 | # while trying to push we already computed the following: | |||
|
129 | # common = (::commonheads) | |||
|
130 | # missing = ((commonheads::missingheads) - commonheads) | |||
|
131 | # | |||
|
132 | # We can pick: | |||
|
133 | # * missingheads part of common (::commonheads) | |||
|
134 | common = set(self.outgoing.common) | |||
|
135 | nm = self.repo.changelog.nodemap | |||
|
136 | cheads = [node for node in self.revs if nm[node] in common] | |||
|
137 | # and | |||
|
138 | # * commonheads parents on missing | |||
|
139 | revset = unfi.set('%ln and parents(roots(%ln))', | |||
|
140 | self.outgoing.commonheads, | |||
|
141 | self.outgoing.missing) | |||
|
142 | cheads.extend(c.node() for c in revset) | |||
|
143 | return cheads | |||
|
144 | ||||
|
145 | @property | |||
|
146 | def commonheads(self): | |||
|
147 | """set of all common heads after changeset bundle push""" | |||
|
148 | if self.cgresult: | |||
|
149 | return self.futureheads | |||
|
150 | else: | |||
|
151 | return self.fallbackheads | |||
|
152 | ||||
|
153 | # mapping of message used when pushing bookmark | |||
|
154 | bookmsgmap = {'update': (_("updating bookmark %s\n"), | |||
|
155 | _('updating bookmark %s failed!\n')), | |||
|
156 | 'export': (_("exporting bookmark %s\n"), | |||
|
157 | _('exporting bookmark %s failed!\n')), | |||
|
158 | 'delete': (_("deleting remote bookmark %s\n"), | |||
|
159 | _('deleting remote bookmark %s failed!\n')), | |||
|
160 | } | |||
|
161 | ||||
|
162 | ||||
|
163 | def push(repo, remote, force=False, revs=None, newbranch=False, bookmarks=()): | |||
84 | '''Push outgoing changesets (limited by revs) from a local |
|
164 | '''Push outgoing changesets (limited by revs) from a local | |
85 | repository to remote. Return an integer: |
|
165 | repository to remote. Return an integer: | |
86 | - None means nothing to push |
|
166 | - None means nothing to push | |
@@ -89,7 +169,7 b' def push(repo, remote, force=False, revs' | |||||
89 | we have outgoing changesets but refused to push |
|
169 | we have outgoing changesets but refused to push | |
90 | - other values as described by addchangegroup() |
|
170 | - other values as described by addchangegroup() | |
91 | ''' |
|
171 | ''' | |
92 | pushop = pushoperation(repo, remote, force, revs, newbranch) |
|
172 | pushop = pushoperation(repo, remote, force, revs, newbranch, bookmarks) | |
93 | if pushop.remote.local(): |
|
173 | if pushop.remote.local(): | |
94 | missing = (set(pushop.repo.requirements) |
|
174 | missing = (set(pushop.repo.requirements) | |
95 | - pushop.remote.local().supported) |
|
175 | - pushop.remote.local().supported) | |
@@ -136,9 +216,9 b' def push(repo, remote, force=False, revs' | |||||
136 | and pushop.remote.capable('bundle2-exp')): |
|
216 | and pushop.remote.capable('bundle2-exp')): | |
137 | _pushbundle2(pushop) |
|
217 | _pushbundle2(pushop) | |
138 | _pushchangeset(pushop) |
|
218 | _pushchangeset(pushop) | |
139 | _pushcomputecommonheads(pushop) |
|
|||
140 | _pushsyncphase(pushop) |
|
219 | _pushsyncphase(pushop) | |
141 | _pushobsolete(pushop) |
|
220 | _pushobsolete(pushop) | |
|
221 | _pushbookmark(pushop) | |||
142 | finally: |
|
222 | finally: | |
143 | if lock is not None: |
|
223 | if lock is not None: | |
144 | lock.release() |
|
224 | lock.release() | |
@@ -146,11 +226,41 b' def push(repo, remote, force=False, revs' | |||||
146 | if locallock is not None: |
|
226 | if locallock is not None: | |
147 | locallock.release() |
|
227 | locallock.release() | |
148 |
|
228 | |||
149 | _pushbookmark(pushop) |
|
229 | return pushop | |
150 | return pushop.ret |
|
230 | ||
|
231 | # list of steps to perform discovery before push | |||
|
232 | pushdiscoveryorder = [] | |||
|
233 | ||||
|
234 | # Mapping between step name and function | |||
|
235 | # | |||
|
236 | # This exists to help extensions wrap steps if necessary | |||
|
237 | pushdiscoverymapping = {} | |||
|
238 | ||||
|
239 | def pushdiscovery(stepname): | |||
|
240 | """decorator for function performing discovery before push | |||
|
241 | ||||
|
242 | The function is added to the step -> function mapping and appended to the | |||
|
243 | list of steps. Beware that decorated function will be added in order (this | |||
|
244 | may matter). | |||
|
245 | ||||
|
246 | You can only use this decorator for a new step, if you want to wrap a step | |||
|
247 | from an extension, change the pushdiscovery dictionary directly.""" | |||
|
248 | def dec(func): | |||
|
249 | assert stepname not in pushdiscoverymapping | |||
|
250 | pushdiscoverymapping[stepname] = func | |||
|
251 | pushdiscoveryorder.append(stepname) | |||
|
252 | return func | |||
|
253 | return dec | |||
151 |
|
254 | |||
152 | def _pushdiscovery(pushop): |
|
255 | def _pushdiscovery(pushop): | |
153 | # discovery |
|
256 | """Run all discovery steps""" | |
|
257 | for stepname in pushdiscoveryorder: | |||
|
258 | step = pushdiscoverymapping[stepname] | |||
|
259 | step(pushop) | |||
|
260 | ||||
|
261 | @pushdiscovery('changeset') | |||
|
262 | def _pushdiscoverychangeset(pushop): | |||
|
263 | """discover the changeset that need to be pushed""" | |||
154 | unfi = pushop.repo.unfiltered() |
|
264 | unfi = pushop.repo.unfiltered() | |
155 | fci = discovery.findcommonincoming |
|
265 | fci = discovery.findcommonincoming | |
156 | commoninc = fci(unfi, pushop.remote, force=pushop.force) |
|
266 | commoninc = fci(unfi, pushop.remote, force=pushop.force) | |
@@ -162,6 +272,99 b' def _pushdiscovery(pushop):' | |||||
162 | pushop.remoteheads = remoteheads |
|
272 | pushop.remoteheads = remoteheads | |
163 | pushop.incoming = inc |
|
273 | pushop.incoming = inc | |
164 |
|
274 | |||
|
275 | @pushdiscovery('phase') | |||
|
276 | def _pushdiscoveryphase(pushop): | |||
|
277 | """discover the phase that needs to be pushed | |||
|
278 | ||||
|
279 | (computed for both success and failure case for changesets push)""" | |||
|
280 | outgoing = pushop.outgoing | |||
|
281 | unfi = pushop.repo.unfiltered() | |||
|
282 | remotephases = pushop.remote.listkeys('phases') | |||
|
283 | publishing = remotephases.get('publishing', False) | |||
|
284 | ana = phases.analyzeremotephases(pushop.repo, | |||
|
285 | pushop.fallbackheads, | |||
|
286 | remotephases) | |||
|
287 | pheads, droots = ana | |||
|
288 | extracond = '' | |||
|
289 | if not publishing: | |||
|
290 | extracond = ' and public()' | |||
|
291 | revset = 'heads((%%ln::%%ln) %s)' % extracond | |||
|
292 | # Get the list of all revs draft on remote by public here. | |||
|
293 | # XXX Beware that revset break if droots is not strictly | |||
|
294 | # XXX root we may want to ensure it is but it is costly | |||
|
295 | fallback = list(unfi.set(revset, droots, pushop.fallbackheads)) | |||
|
296 | if not outgoing.missing: | |||
|
297 | future = fallback | |||
|
298 | else: | |||
|
299 | # adds changeset we are going to push as draft | |||
|
300 | # | |||
|
301 | # should not be necessary for pushblishing server, but because of an | |||
|
302 | # issue fixed in xxxxx we have to do it anyway. | |||
|
303 | fdroots = list(unfi.set('roots(%ln + %ln::)', | |||
|
304 | outgoing.missing, droots)) | |||
|
305 | fdroots = [f.node() for f in fdroots] | |||
|
306 | future = list(unfi.set(revset, fdroots, pushop.futureheads)) | |||
|
307 | pushop.outdatedphases = future | |||
|
308 | pushop.fallbackoutdatedphases = fallback | |||
|
309 | ||||
|
310 | @pushdiscovery('obsmarker') | |||
|
311 | def _pushdiscoveryobsmarkers(pushop): | |||
|
312 | if (obsolete.isenabled(pushop.repo, obsolete.exchangeopt) | |||
|
313 | and pushop.repo.obsstore | |||
|
314 | and 'obsolete' in pushop.remote.listkeys('namespaces')): | |||
|
315 | repo = pushop.repo | |||
|
316 | # very naive computation, that can be quite expensive on big repo. | |||
|
317 | # However: evolution is currently slow on them anyway. | |||
|
318 | nodes = (c.node() for c in repo.set('::%ln', pushop.futureheads)) | |||
|
319 | pushop.outobsmarkers = pushop.repo.obsstore.relevantmarkers(nodes) | |||
|
320 | ||||
|
321 | @pushdiscovery('bookmarks') | |||
|
322 | def _pushdiscoverybookmarks(pushop): | |||
|
323 | ui = pushop.ui | |||
|
324 | repo = pushop.repo.unfiltered() | |||
|
325 | remote = pushop.remote | |||
|
326 | ui.debug("checking for updated bookmarks\n") | |||
|
327 | ancestors = () | |||
|
328 | if pushop.revs: | |||
|
329 | revnums = map(repo.changelog.rev, pushop.revs) | |||
|
330 | ancestors = repo.changelog.ancestors(revnums, inclusive=True) | |||
|
331 | remotebookmark = remote.listkeys('bookmarks') | |||
|
332 | ||||
|
333 | explicit = set(pushop.bookmarks) | |||
|
334 | ||||
|
335 | comp = bookmod.compare(repo, repo._bookmarks, remotebookmark, srchex=hex) | |||
|
336 | addsrc, adddst, advsrc, advdst, diverge, differ, invalid = comp | |||
|
337 | for b, scid, dcid in advsrc: | |||
|
338 | if b in explicit: | |||
|
339 | explicit.remove(b) | |||
|
340 | if not ancestors or repo[scid].rev() in ancestors: | |||
|
341 | pushop.outbookmarks.append((b, dcid, scid)) | |||
|
342 | # search added bookmark | |||
|
343 | for b, scid, dcid in addsrc: | |||
|
344 | if b in explicit: | |||
|
345 | explicit.remove(b) | |||
|
346 | pushop.outbookmarks.append((b, '', scid)) | |||
|
347 | # search for overwritten bookmark | |||
|
348 | for b, scid, dcid in advdst + diverge + differ: | |||
|
349 | if b in explicit: | |||
|
350 | explicit.remove(b) | |||
|
351 | pushop.outbookmarks.append((b, dcid, scid)) | |||
|
352 | # search for bookmark to delete | |||
|
353 | for b, scid, dcid in adddst: | |||
|
354 | if b in explicit: | |||
|
355 | explicit.remove(b) | |||
|
356 | # treat as "deleted locally" | |||
|
357 | pushop.outbookmarks.append((b, dcid, '')) | |||
|
358 | ||||
|
359 | if explicit: | |||
|
360 | explicit = sorted(explicit) | |||
|
361 | # we should probably list all of them | |||
|
362 | ui.warn(_('bookmark %s does not exist on the local ' | |||
|
363 | 'or remote repository!\n') % explicit[0]) | |||
|
364 | pushop.bkresult = 2 | |||
|
365 | ||||
|
366 | pushop.outbookmarks.sort() | |||
|
367 | ||||
165 | def _pushcheckoutgoing(pushop): |
|
368 | def _pushcheckoutgoing(pushop): | |
166 | outgoing = pushop.outgoing |
|
369 | outgoing = pushop.outgoing | |
167 | unfi = pushop.repo.unfiltered() |
|
370 | unfi = pushop.repo.unfiltered() | |
@@ -176,11 +379,9 b' def _pushcheckoutgoing(pushop):' | |||||
176 | if unfi.obsstore: |
|
379 | if unfi.obsstore: | |
177 | # this message are here for 80 char limit reason |
|
380 | # this message are here for 80 char limit reason | |
178 | mso = _("push includes obsolete changeset: %s!") |
|
381 | mso = _("push includes obsolete changeset: %s!") | |
179 |
mst = "push includes |
|
382 | mst = {"unstable": _("push includes unstable changeset: %s!"), | |
180 | # plain versions for i18n tool to detect them |
|
383 | "bumped": _("push includes bumped changeset: %s!"), | |
181 |
_("push includes |
|
384 | "divergent": _("push includes divergent changeset: %s!")} | |
182 | _("push includes bumped changeset: %s!") |
|
|||
183 | _("push includes divergent changeset: %s!") |
|
|||
184 | # If we are to push if there is at least one |
|
385 | # If we are to push if there is at least one | |
185 | # obsolete or unstable changeset in missing, at |
|
386 | # obsolete or unstable changeset in missing, at | |
186 | # least one of the missinghead will be obsolete or |
|
387 | # least one of the missinghead will be obsolete or | |
@@ -190,9 +391,7 b' def _pushcheckoutgoing(pushop):' | |||||
190 | if ctx.obsolete(): |
|
391 | if ctx.obsolete(): | |
191 | raise util.Abort(mso % ctx) |
|
392 | raise util.Abort(mso % ctx) | |
192 | elif ctx.troubled(): |
|
393 | elif ctx.troubled(): | |
193 |
raise util.Abort( |
|
394 | raise util.Abort(mst[ctx.troubles()[0]] % ctx) | |
194 | % (ctx.troubles()[0], |
|
|||
195 | ctx)) |
|
|||
196 | newbm = pushop.ui.configlist('bookmarks', 'pushing') |
|
395 | newbm = pushop.ui.configlist('bookmarks', 'pushing') | |
197 | discovery.checkheads(unfi, pushop.remote, outgoing, |
|
396 | discovery.checkheads(unfi, pushop.remote, outgoing, | |
198 | pushop.remoteheads, |
|
397 | pushop.remoteheads, | |
@@ -201,16 +400,40 b' def _pushcheckoutgoing(pushop):' | |||||
201 | newbm) |
|
400 | newbm) | |
202 | return True |
|
401 | return True | |
203 |
|
402 | |||
|
403 | # List of names of steps to perform for an outgoing bundle2, order matters. | |||
|
404 | b2partsgenorder = [] | |||
|
405 | ||||
|
406 | # Mapping between step name and function | |||
|
407 | # | |||
|
408 | # This exists to help extensions wrap steps if necessary | |||
|
409 | b2partsgenmapping = {} | |||
|
410 | ||||
|
411 | def b2partsgenerator(stepname): | |||
|
412 | """decorator for function generating bundle2 part | |||
|
413 | ||||
|
414 | The function is added to the step -> function mapping and appended to the | |||
|
415 | list of steps. Beware that decorated functions will be added in order | |||
|
416 | (this may matter). | |||
|
417 | ||||
|
418 | You can only use this decorator for new steps, if you want to wrap a step | |||
|
419 | from an extension, attack the b2partsgenmapping dictionary directly.""" | |||
|
420 | def dec(func): | |||
|
421 | assert stepname not in b2partsgenmapping | |||
|
422 | b2partsgenmapping[stepname] = func | |||
|
423 | b2partsgenorder.append(stepname) | |||
|
424 | return func | |||
|
425 | return dec | |||
|
426 | ||||
|
427 | @b2partsgenerator('changeset') | |||
204 | def _pushb2ctx(pushop, bundler): |
|
428 | def _pushb2ctx(pushop, bundler): | |
205 | """handle changegroup push through bundle2 |
|
429 | """handle changegroup push through bundle2 | |
206 |
|
430 | |||
207 | addchangegroup result is stored in the ``pushop.ret`` attribute. |
|
431 | addchangegroup result is stored in the ``pushop.cgresult`` attribute. | |
208 | """ |
|
432 | """ | |
209 | if 'changesets' in pushop.stepsdone: |
|
433 | if 'changesets' in pushop.stepsdone: | |
210 | return |
|
434 | return | |
211 | pushop.stepsdone.add('changesets') |
|
435 | pushop.stepsdone.add('changesets') | |
212 | # Send known heads to the server for race detection. |
|
436 | # Send known heads to the server for race detection. | |
213 | pushop.stepsdone.add('changesets') |
|
|||
214 | if not _pushcheckoutgoing(pushop): |
|
437 | if not _pushcheckoutgoing(pushop): | |
215 | return |
|
438 | return | |
216 | pushop.repo.prepushoutgoinghooks(pushop.repo, |
|
439 | pushop.repo.prepushoutgoinghooks(pushop.repo, | |
@@ -218,17 +441,101 b' def _pushb2ctx(pushop, bundler):' | |||||
218 | pushop.outgoing) |
|
441 | pushop.outgoing) | |
219 | if not pushop.force: |
|
442 | if not pushop.force: | |
220 | bundler.newpart('B2X:CHECK:HEADS', data=iter(pushop.remoteheads)) |
|
443 | bundler.newpart('B2X:CHECK:HEADS', data=iter(pushop.remoteheads)) | |
221 |
cg = changegroup.getlocal |
|
444 | cg = changegroup.getlocalchangegroup(pushop.repo, 'push', pushop.outgoing) | |
222 | cgpart = bundler.newpart('B2X:CHANGEGROUP', data=cg.getchunks()) |
|
445 | cgpart = bundler.newpart('B2X:CHANGEGROUP', data=cg.getchunks()) | |
223 | def handlereply(op): |
|
446 | def handlereply(op): | |
224 | """extract addchangroup returns from server reply""" |
|
447 | """extract addchangroup returns from server reply""" | |
225 | cgreplies = op.records.getreplies(cgpart.id) |
|
448 | cgreplies = op.records.getreplies(cgpart.id) | |
226 | assert len(cgreplies['changegroup']) == 1 |
|
449 | assert len(cgreplies['changegroup']) == 1 | |
227 | pushop.ret = cgreplies['changegroup'][0]['return'] |
|
450 | pushop.cgresult = cgreplies['changegroup'][0]['return'] | |
|
451 | return handlereply | |||
|
452 | ||||
|
453 | @b2partsgenerator('phase') | |||
|
454 | def _pushb2phases(pushop, bundler): | |||
|
455 | """handle phase push through bundle2""" | |||
|
456 | if 'phases' in pushop.stepsdone: | |||
|
457 | return | |||
|
458 | b2caps = bundle2.bundle2caps(pushop.remote) | |||
|
459 | if not 'b2x:pushkey' in b2caps: | |||
|
460 | return | |||
|
461 | pushop.stepsdone.add('phases') | |||
|
462 | part2node = [] | |||
|
463 | enc = pushkey.encode | |||
|
464 | for newremotehead in pushop.outdatedphases: | |||
|
465 | part = bundler.newpart('b2x:pushkey') | |||
|
466 | part.addparam('namespace', enc('phases')) | |||
|
467 | part.addparam('key', enc(newremotehead.hex())) | |||
|
468 | part.addparam('old', enc(str(phases.draft))) | |||
|
469 | part.addparam('new', enc(str(phases.public))) | |||
|
470 | part2node.append((part.id, newremotehead)) | |||
|
471 | def handlereply(op): | |||
|
472 | for partid, node in part2node: | |||
|
473 | partrep = op.records.getreplies(partid) | |||
|
474 | results = partrep['pushkey'] | |||
|
475 | assert len(results) <= 1 | |||
|
476 | msg = None | |||
|
477 | if not results: | |||
|
478 | msg = _('server ignored update of %s to public!\n') % node | |||
|
479 | elif not int(results[0]['return']): | |||
|
480 | msg = _('updating %s to public failed!\n') % node | |||
|
481 | if msg is not None: | |||
|
482 | pushop.ui.warn(msg) | |||
228 | return handlereply |
|
483 | return handlereply | |
229 |
|
484 | |||
230 | # list of function that may decide to add parts to an outgoing bundle2 |
|
485 | @b2partsgenerator('obsmarkers') | |
231 | bundle2partsgenerators = [_pushb2ctx] |
|
486 | def _pushb2obsmarkers(pushop, bundler): | |
|
487 | if 'obsmarkers' in pushop.stepsdone: | |||
|
488 | return | |||
|
489 | remoteversions = bundle2.obsmarkersversion(bundler.capabilities) | |||
|
490 | if obsolete.commonversion(remoteversions) is None: | |||
|
491 | return | |||
|
492 | pushop.stepsdone.add('obsmarkers') | |||
|
493 | if pushop.outobsmarkers: | |||
|
494 | buildobsmarkerspart(bundler, pushop.outobsmarkers) | |||
|
495 | ||||
|
496 | @b2partsgenerator('bookmarks') | |||
|
497 | def _pushb2bookmarks(pushop, bundler): | |||
|
498 | """handle phase push through bundle2""" | |||
|
499 | if 'bookmarks' in pushop.stepsdone: | |||
|
500 | return | |||
|
501 | b2caps = bundle2.bundle2caps(pushop.remote) | |||
|
502 | if 'b2x:pushkey' not in b2caps: | |||
|
503 | return | |||
|
504 | pushop.stepsdone.add('bookmarks') | |||
|
505 | part2book = [] | |||
|
506 | enc = pushkey.encode | |||
|
507 | for book, old, new in pushop.outbookmarks: | |||
|
508 | part = bundler.newpart('b2x:pushkey') | |||
|
509 | part.addparam('namespace', enc('bookmarks')) | |||
|
510 | part.addparam('key', enc(book)) | |||
|
511 | part.addparam('old', enc(old)) | |||
|
512 | part.addparam('new', enc(new)) | |||
|
513 | action = 'update' | |||
|
514 | if not old: | |||
|
515 | action = 'export' | |||
|
516 | elif not new: | |||
|
517 | action = 'delete' | |||
|
518 | part2book.append((part.id, book, action)) | |||
|
519 | ||||
|
520 | ||||
|
521 | def handlereply(op): | |||
|
522 | ui = pushop.ui | |||
|
523 | for partid, book, action in part2book: | |||
|
524 | partrep = op.records.getreplies(partid) | |||
|
525 | results = partrep['pushkey'] | |||
|
526 | assert len(results) <= 1 | |||
|
527 | if not results: | |||
|
528 | pushop.ui.warn(_('server ignored bookmark %s update\n') % book) | |||
|
529 | else: | |||
|
530 | ret = int(results[0]['return']) | |||
|
531 | if ret: | |||
|
532 | ui.status(bookmsgmap[action][0] % book) | |||
|
533 | else: | |||
|
534 | ui.warn(bookmsgmap[action][1] % book) | |||
|
535 | if pushop.bkresult is not None: | |||
|
536 | pushop.bkresult = 1 | |||
|
537 | return handlereply | |||
|
538 | ||||
232 |
|
539 | |||
233 | def _pushbundle2(pushop): |
|
540 | def _pushbundle2(pushop): | |
234 | """push data to the remote using bundle2 |
|
541 | """push data to the remote using bundle2 | |
@@ -237,10 +544,11 b' def _pushbundle2(pushop):' | |||||
237 | evolve in the future.""" |
|
544 | evolve in the future.""" | |
238 | bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote)) |
|
545 | bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote)) | |
239 | # create reply capability |
|
546 | # create reply capability | |
240 |
capsblob = bundle2.encodecaps(pushop.repo |
|
547 | capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo)) | |
241 | bundler.newpart('b2x:replycaps', data=capsblob) |
|
548 | bundler.newpart('b2x:replycaps', data=capsblob) | |
242 | replyhandlers = [] |
|
549 | replyhandlers = [] | |
243 |
for partgen in b |
|
550 | for partgenname in b2partsgenorder: | |
|
551 | partgen = b2partsgenmapping[partgenname] | |||
244 | ret = partgen(pushop, bundler) |
|
552 | ret = partgen(pushop, bundler) | |
245 | if callable(ret): |
|
553 | if callable(ret): | |
246 | replyhandlers.append(ret) |
|
554 | replyhandlers.append(ret) | |
@@ -278,14 +586,14 b' def _pushchangeset(pushop):' | |||||
278 | or pushop.repo.changelog.filteredrevs): |
|
586 | or pushop.repo.changelog.filteredrevs): | |
279 | # push everything, |
|
587 | # push everything, | |
280 | # use the fast path, no race possible on push |
|
588 | # use the fast path, no race possible on push | |
281 |
bundler = changegroup. |
|
589 | bundler = changegroup.cg1packer(pushop.repo, bundlecaps) | |
282 | cg = changegroup.getsubset(pushop.repo, |
|
590 | cg = changegroup.getsubset(pushop.repo, | |
283 | outgoing, |
|
591 | outgoing, | |
284 | bundler, |
|
592 | bundler, | |
285 | 'push', |
|
593 | 'push', | |
286 | fastpath=True) |
|
594 | fastpath=True) | |
287 | else: |
|
595 | else: | |
288 |
cg = changegroup.getlocal |
|
596 | cg = changegroup.getlocalchangegroup(pushop.repo, 'push', outgoing, | |
289 | bundlecaps) |
|
597 | bundlecaps) | |
290 |
|
598 | |||
291 | # apply changegroup to remote |
|
599 | # apply changegroup to remote | |
@@ -300,56 +608,22 b' def _pushchangeset(pushop):' | |||||
300 | remoteheads = pushop.remoteheads |
|
608 | remoteheads = pushop.remoteheads | |
301 | # ssh: return remote's addchangegroup() |
|
609 | # ssh: return remote's addchangegroup() | |
302 | # http: return remote's addchangegroup() or 0 for error |
|
610 | # http: return remote's addchangegroup() or 0 for error | |
303 | pushop.ret = pushop.remote.unbundle(cg, remoteheads, |
|
611 | pushop.cgresult = pushop.remote.unbundle(cg, remoteheads, | |
304 | pushop.repo.url()) |
|
612 | pushop.repo.url()) | |
305 | else: |
|
613 | else: | |
306 | # we return an integer indicating remote head count |
|
614 | # we return an integer indicating remote head count | |
307 | # change |
|
615 | # change | |
308 |
pushop.ret = pushop.remote.addchangegroup(cg, 'push', |
|
616 | pushop.cgresult = pushop.remote.addchangegroup(cg, 'push', | |
309 |
|
617 | pushop.repo.url()) | ||
310 | def _pushcomputecommonheads(pushop): |
|
|||
311 | unfi = pushop.repo.unfiltered() |
|
|||
312 | if pushop.ret: |
|
|||
313 | # push succeed, synchronize target of the push |
|
|||
314 | cheads = pushop.outgoing.missingheads |
|
|||
315 | elif pushop.revs is None: |
|
|||
316 | # All out push fails. synchronize all common |
|
|||
317 | cheads = pushop.outgoing.commonheads |
|
|||
318 | else: |
|
|||
319 | # I want cheads = heads(::missingheads and ::commonheads) |
|
|||
320 | # (missingheads is revs with secret changeset filtered out) |
|
|||
321 | # |
|
|||
322 | # This can be expressed as: |
|
|||
323 | # cheads = ( (missingheads and ::commonheads) |
|
|||
324 | # + (commonheads and ::missingheads))" |
|
|||
325 | # ) |
|
|||
326 | # |
|
|||
327 | # while trying to push we already computed the following: |
|
|||
328 | # common = (::commonheads) |
|
|||
329 | # missing = ((commonheads::missingheads) - commonheads) |
|
|||
330 | # |
|
|||
331 | # We can pick: |
|
|||
332 | # * missingheads part of common (::commonheads) |
|
|||
333 | common = set(pushop.outgoing.common) |
|
|||
334 | nm = pushop.repo.changelog.nodemap |
|
|||
335 | cheads = [node for node in pushop.revs if nm[node] in common] |
|
|||
336 | # and |
|
|||
337 | # * commonheads parents on missing |
|
|||
338 | revset = unfi.set('%ln and parents(roots(%ln))', |
|
|||
339 | pushop.outgoing.commonheads, |
|
|||
340 | pushop.outgoing.missing) |
|
|||
341 | cheads.extend(c.node() for c in revset) |
|
|||
342 | pushop.commonheads = cheads |
|
|||
343 |
|
618 | |||
344 | def _pushsyncphase(pushop): |
|
619 | def _pushsyncphase(pushop): | |
345 | """synchronise phase information locally and remotely""" |
|
620 | """synchronise phase information locally and remotely""" | |
346 | unfi = pushop.repo.unfiltered() |
|
|||
347 | cheads = pushop.commonheads |
|
621 | cheads = pushop.commonheads | |
348 | # even when we don't push, exchanging phase data is useful |
|
622 | # even when we don't push, exchanging phase data is useful | |
349 | remotephases = pushop.remote.listkeys('phases') |
|
623 | remotephases = pushop.remote.listkeys('phases') | |
350 | if (pushop.ui.configbool('ui', '_usedassubrepo', False) |
|
624 | if (pushop.ui.configbool('ui', '_usedassubrepo', False) | |
351 | and remotephases # server supports phases |
|
625 | and remotephases # server supports phases | |
352 | and pushop.ret is None # nothing was pushed |
|
626 | and pushop.cgresult is None # nothing was pushed | |
353 | and remotephases.get('publishing', False)): |
|
627 | and remotephases.get('publishing', False)): | |
354 | # When: |
|
628 | # When: | |
355 | # - this is a subrepo push |
|
629 | # - this is a subrepo push | |
@@ -376,19 +650,25 b' def _pushsyncphase(pushop):' | |||||
376 | _localphasemove(pushop, cheads, phases.draft) |
|
650 | _localphasemove(pushop, cheads, phases.draft) | |
377 | ### Apply local phase on remote |
|
651 | ### Apply local phase on remote | |
378 |
|
652 | |||
379 | # Get the list of all revs draft on remote by public here. |
|
653 | if pushop.cgresult: | |
380 | # XXX Beware that revset break if droots is not strictly |
|
654 | if 'phases' in pushop.stepsdone: | |
381 | # XXX root we may want to ensure it is but it is costly |
|
655 | # phases already pushed though bundle2 | |
382 | outdated = unfi.set('heads((%ln::%ln) and public())', |
|
656 | return | |
383 | droots, cheads) |
|
657 | outdated = pushop.outdatedphases | |
|
658 | else: | |||
|
659 | outdated = pushop.fallbackoutdatedphases | |||
384 |
|
660 | |||
|
661 | pushop.stepsdone.add('phases') | |||
|
662 | ||||
|
663 | # filter heads already turned public by the push | |||
|
664 | outdated = [c for c in outdated if c.node() not in pheads] | |||
385 | b2caps = bundle2.bundle2caps(pushop.remote) |
|
665 | b2caps = bundle2.bundle2caps(pushop.remote) | |
386 | if 'b2x:pushkey' in b2caps: |
|
666 | if 'b2x:pushkey' in b2caps: | |
387 | # server supports bundle2, let's do a batched push through it |
|
667 | # server supports bundle2, let's do a batched push through it | |
388 | # |
|
668 | # | |
389 | # This will eventually be unified with the changesets bundle2 push |
|
669 | # This will eventually be unified with the changesets bundle2 push | |
390 | bundler = bundle2.bundle20(pushop.ui, b2caps) |
|
670 | bundler = bundle2.bundle20(pushop.ui, b2caps) | |
391 |
capsblob = bundle2.encodecaps(pushop.repo |
|
671 | capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo)) | |
392 | bundler.newpart('b2x:replycaps', data=capsblob) |
|
672 | bundler.newpart('b2x:replycaps', data=capsblob) | |
393 | part2node = [] |
|
673 | part2node = [] | |
394 | enc = pushkey.encode |
|
674 | enc = pushkey.encode | |
@@ -431,7 +711,12 b' def _pushsyncphase(pushop):' | |||||
431 | def _localphasemove(pushop, nodes, phase=phases.public): |
|
711 | def _localphasemove(pushop, nodes, phase=phases.public): | |
432 | """move <nodes> to <phase> in the local source repo""" |
|
712 | """move <nodes> to <phase> in the local source repo""" | |
433 | if pushop.locallocked: |
|
713 | if pushop.locallocked: | |
434 | phases.advanceboundary(pushop.repo, phase, nodes) |
|
714 | tr = pushop.repo.transaction('push-phase-sync') | |
|
715 | try: | |||
|
716 | phases.advanceboundary(pushop.repo, tr, phase, nodes) | |||
|
717 | tr.close() | |||
|
718 | finally: | |||
|
719 | tr.release() | |||
435 | else: |
|
720 | else: | |
436 | # repo is not locked, do not change any phases! |
|
721 | # repo is not locked, do not change any phases! | |
437 | # Informs the user that phases should have been moved when |
|
722 | # Informs the user that phases should have been moved when | |
@@ -444,13 +729,15 b' def _localphasemove(pushop, nodes, phase' | |||||
444 |
|
729 | |||
445 | def _pushobsolete(pushop): |
|
730 | def _pushobsolete(pushop): | |
446 | """utility function to push obsolete markers to a remote""" |
|
731 | """utility function to push obsolete markers to a remote""" | |
|
732 | if 'obsmarkers' in pushop.stepsdone: | |||
|
733 | return | |||
447 | pushop.ui.debug('try to push obsolete markers to remote\n') |
|
734 | pushop.ui.debug('try to push obsolete markers to remote\n') | |
448 | repo = pushop.repo |
|
735 | repo = pushop.repo | |
449 | remote = pushop.remote |
|
736 | remote = pushop.remote | |
450 | if (obsolete._enabled and repo.obsstore and |
|
737 | pushop.stepsdone.add('obsmarkers') | |
451 | 'obsolete' in remote.listkeys('namespaces')): |
|
738 | if pushop.outobsmarkers: | |
452 | rslts = [] |
|
739 | rslts = [] | |
453 | remotedata = repo.listkeys('obsolete') |
|
740 | remotedata = obsolete._pushkeyescape(pushop.outobsmarkers) | |
454 | for key in sorted(remotedata, reverse=True): |
|
741 | for key in sorted(remotedata, reverse=True): | |
455 | # reverse sort to ensure we end with dump0 |
|
742 | # reverse sort to ensure we end with dump0 | |
456 | data = remotedata[key] |
|
743 | data = remotedata[key] | |
@@ -461,23 +748,25 b' def _pushobsolete(pushop):' | |||||
461 |
|
748 | |||
462 | def _pushbookmark(pushop): |
|
749 | def _pushbookmark(pushop): | |
463 | """Update bookmark position on remote""" |
|
750 | """Update bookmark position on remote""" | |
|
751 | if pushop.cgresult == 0 or 'bookmarks' in pushop.stepsdone: | |||
|
752 | return | |||
|
753 | pushop.stepsdone.add('bookmarks') | |||
464 | ui = pushop.ui |
|
754 | ui = pushop.ui | |
465 | repo = pushop.repo.unfiltered() |
|
|||
466 | remote = pushop.remote |
|
755 | remote = pushop.remote | |
467 | ui.debug("checking for updated bookmarks\n") |
|
|||
468 | revnums = map(repo.changelog.rev, pushop.revs or []) |
|
|||
469 | ancestors = [a for a in repo.changelog.ancestors(revnums, inclusive=True)] |
|
|||
470 | (addsrc, adddst, advsrc, advdst, diverge, differ, invalid |
|
|||
471 | ) = bookmarks.compare(repo, repo._bookmarks, remote.listkeys('bookmarks'), |
|
|||
472 | srchex=hex) |
|
|||
473 |
|
756 | |||
474 | for b, scid, dcid in advsrc: |
|
757 | for b, old, new in pushop.outbookmarks: | |
475 | if ancestors and repo[scid].rev() not in ancestors: |
|
758 | action = 'update' | |
476 | continue |
|
759 | if not old: | |
477 | if remote.pushkey('bookmarks', b, dcid, scid): |
|
760 | action = 'export' | |
478 | ui.status(_("updating bookmark %s\n") % b) |
|
761 | elif not new: | |
|
762 | action = 'delete' | |||
|
763 | if remote.pushkey('bookmarks', b, old, new): | |||
|
764 | ui.status(bookmsgmap[action][0] % b) | |||
479 | else: |
|
765 | else: | |
480 |
ui.warn( |
|
766 | ui.warn(bookmsgmap[action][1] % b) | |
|
767 | # discovery can have set the value form invalid entry | |||
|
768 | if pushop.bkresult is not None: | |||
|
769 | pushop.bkresult = 1 | |||
481 |
|
770 | |||
482 | class pulloperation(object): |
|
771 | class pulloperation(object): | |
483 | """A object that represent a single pull operation |
|
772 | """A object that represent a single pull operation | |
@@ -488,13 +777,15 b' class pulloperation(object):' | |||||
488 | afterward. |
|
777 | afterward. | |
489 | """ |
|
778 | """ | |
490 |
|
779 | |||
491 | def __init__(self, repo, remote, heads=None, force=False): |
|
780 | def __init__(self, repo, remote, heads=None, force=False, bookmarks=()): | |
492 | # repo we pull into |
|
781 | # repo we pull into | |
493 | self.repo = repo |
|
782 | self.repo = repo | |
494 | # repo we pull from |
|
783 | # repo we pull from | |
495 | self.remote = remote |
|
784 | self.remote = remote | |
496 | # revision we try to pull (None is "all") |
|
785 | # revision we try to pull (None is "all") | |
497 | self.heads = heads |
|
786 | self.heads = heads | |
|
787 | # bookmark pulled explicitly | |||
|
788 | self.explicitbookmarks = bookmarks | |||
498 | # do we force pull? |
|
789 | # do we force pull? | |
499 | self.force = force |
|
790 | self.force = force | |
500 | # the name the pull transaction |
|
791 | # the name the pull transaction | |
@@ -507,10 +798,12 b' class pulloperation(object):' | |||||
507 | self.rheads = None |
|
798 | self.rheads = None | |
508 | # list of missing changeset to fetch remotely |
|
799 | # list of missing changeset to fetch remotely | |
509 | self.fetch = None |
|
800 | self.fetch = None | |
|
801 | # remote bookmarks data | |||
|
802 | self.remotebookmarks = None | |||
510 | # result of changegroup pulling (used as return code by pull) |
|
803 | # result of changegroup pulling (used as return code by pull) | |
511 | self.cgresult = None |
|
804 | self.cgresult = None | |
512 | # list of step remaining todo (related to future bundle2 usage) |
|
805 | # list of step already done | |
513 | self.todosteps = set(['changegroup', 'phases', 'obsmarkers']) |
|
806 | self.stepsdone = set() | |
514 |
|
807 | |||
515 | @util.propertycache |
|
808 | @util.propertycache | |
516 | def pulledsubset(self): |
|
809 | def pulledsubset(self): | |
@@ -534,20 +827,32 b' class pulloperation(object):' | |||||
534 | """get appropriate pull transaction, creating it if needed""" |
|
827 | """get appropriate pull transaction, creating it if needed""" | |
535 | if self._tr is None: |
|
828 | if self._tr is None: | |
536 | self._tr = self.repo.transaction(self._trname) |
|
829 | self._tr = self.repo.transaction(self._trname) | |
|
830 | self._tr.hookargs['source'] = 'pull' | |||
|
831 | self._tr.hookargs['url'] = self.remote.url() | |||
537 | return self._tr |
|
832 | return self._tr | |
538 |
|
833 | |||
539 | def closetransaction(self): |
|
834 | def closetransaction(self): | |
540 | """close transaction if created""" |
|
835 | """close transaction if created""" | |
541 | if self._tr is not None: |
|
836 | if self._tr is not None: | |
|
837 | repo = self.repo | |||
|
838 | cl = repo.unfiltered().changelog | |||
|
839 | p = cl.writepending() and repo.root or "" | |||
|
840 | p = cl.writepending() and repo.root or "" | |||
|
841 | repo.hook('b2x-pretransactionclose', throw=True, pending=p, | |||
|
842 | **self._tr.hookargs) | |||
542 | self._tr.close() |
|
843 | self._tr.close() | |
|
844 | hookargs = dict(self._tr.hookargs) | |||
|
845 | def runhooks(): | |||
|
846 | repo.hook('b2x-transactionclose', **hookargs) | |||
|
847 | repo._afterlock(runhooks) | |||
543 |
|
848 | |||
544 | def releasetransaction(self): |
|
849 | def releasetransaction(self): | |
545 | """release transaction if created""" |
|
850 | """release transaction if created""" | |
546 | if self._tr is not None: |
|
851 | if self._tr is not None: | |
547 | self._tr.release() |
|
852 | self._tr.release() | |
548 |
|
853 | |||
549 | def pull(repo, remote, heads=None, force=False): |
|
854 | def pull(repo, remote, heads=None, force=False, bookmarks=()): | |
550 | pullop = pulloperation(repo, remote, heads, force) |
|
855 | pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks) | |
551 | if pullop.remote.local(): |
|
856 | if pullop.remote.local(): | |
552 | missing = set(pullop.remote.requirements) - pullop.repo.supported |
|
857 | missing = set(pullop.remote.requirements) - pullop.repo.supported | |
553 | if missing: |
|
858 | if missing: | |
@@ -556,26 +861,56 b' def pull(repo, remote, heads=None, force' | |||||
556 | " %s") % (', '.join(sorted(missing))) |
|
861 | " %s") % (', '.join(sorted(missing))) | |
557 | raise util.Abort(msg) |
|
862 | raise util.Abort(msg) | |
558 |
|
863 | |||
|
864 | pullop.remotebookmarks = remote.listkeys('bookmarks') | |||
559 | lock = pullop.repo.lock() |
|
865 | lock = pullop.repo.lock() | |
560 | try: |
|
866 | try: | |
561 | _pulldiscovery(pullop) |
|
867 | _pulldiscovery(pullop) | |
562 | if (pullop.repo.ui.configbool('experimental', 'bundle2-exp', False) |
|
868 | if (pullop.repo.ui.configbool('experimental', 'bundle2-exp', False) | |
563 | and pullop.remote.capable('bundle2-exp')): |
|
869 | and pullop.remote.capable('bundle2-exp')): | |
564 | _pullbundle2(pullop) |
|
870 | _pullbundle2(pullop) | |
565 | if 'changegroup' in pullop.todosteps: |
|
871 | _pullchangeset(pullop) | |
566 |
|
|
872 | _pullphase(pullop) | |
567 | if 'phases' in pullop.todosteps: |
|
873 | _pullbookmarks(pullop) | |
568 |
|
|
874 | _pullobsolete(pullop) | |
569 | if 'obsmarkers' in pullop.todosteps: |
|
|||
570 | _pullobsolete(pullop) |
|
|||
571 | pullop.closetransaction() |
|
875 | pullop.closetransaction() | |
572 | finally: |
|
876 | finally: | |
573 | pullop.releasetransaction() |
|
877 | pullop.releasetransaction() | |
574 | lock.release() |
|
878 | lock.release() | |
575 |
|
879 | |||
576 |
return pullop |
|
880 | return pullop | |
|
881 | ||||
|
882 | # list of steps to perform discovery before pull | |||
|
883 | pulldiscoveryorder = [] | |||
|
884 | ||||
|
885 | # Mapping between step name and function | |||
|
886 | # | |||
|
887 | # This exists to help extensions wrap steps if necessary | |||
|
888 | pulldiscoverymapping = {} | |||
|
889 | ||||
|
890 | def pulldiscovery(stepname): | |||
|
891 | """decorator for function performing discovery before pull | |||
|
892 | ||||
|
893 | The function is added to the step -> function mapping and appended to the | |||
|
894 | list of steps. Beware that decorated function will be added in order (this | |||
|
895 | may matter). | |||
|
896 | ||||
|
897 | You can only use this decorator for a new step, if you want to wrap a step | |||
|
898 | from an extension, change the pulldiscovery dictionary directly.""" | |||
|
899 | def dec(func): | |||
|
900 | assert stepname not in pulldiscoverymapping | |||
|
901 | pulldiscoverymapping[stepname] = func | |||
|
902 | pulldiscoveryorder.append(stepname) | |||
|
903 | return func | |||
|
904 | return dec | |||
577 |
|
905 | |||
578 | def _pulldiscovery(pullop): |
|
906 | def _pulldiscovery(pullop): | |
|
907 | """Run all discovery steps""" | |||
|
908 | for stepname in pulldiscoveryorder: | |||
|
909 | step = pulldiscoverymapping[stepname] | |||
|
910 | step(pullop) | |||
|
911 | ||||
|
912 | @pulldiscovery('changegroup') | |||
|
913 | def _pulldiscoverychangegroup(pullop): | |||
579 | """discovery phase for the pull |
|
914 | """discovery phase for the pull | |
580 |
|
915 | |||
581 | Current handle changeset discovery only, will change handle all discovery |
|
916 | Current handle changeset discovery only, will change handle all discovery | |
@@ -593,18 +928,24 b' def _pullbundle2(pullop):' | |||||
593 | remotecaps = bundle2.bundle2caps(pullop.remote) |
|
928 | remotecaps = bundle2.bundle2caps(pullop.remote) | |
594 | kwargs = {'bundlecaps': caps20to10(pullop.repo)} |
|
929 | kwargs = {'bundlecaps': caps20to10(pullop.repo)} | |
595 | # pulling changegroup |
|
930 | # pulling changegroup | |
596 |
pullop. |
|
931 | pullop.stepsdone.add('changegroup') | |
597 |
|
932 | |||
598 | kwargs['common'] = pullop.common |
|
933 | kwargs['common'] = pullop.common | |
599 | kwargs['heads'] = pullop.heads or pullop.rheads |
|
934 | kwargs['heads'] = pullop.heads or pullop.rheads | |
|
935 | kwargs['cg'] = pullop.fetch | |||
600 | if 'b2x:listkeys' in remotecaps: |
|
936 | if 'b2x:listkeys' in remotecaps: | |
601 | kwargs['listkeys'] = ['phase'] |
|
937 | kwargs['listkeys'] = ['phase', 'bookmarks'] | |
602 | if not pullop.fetch: |
|
938 | if not pullop.fetch: | |
603 | pullop.repo.ui.status(_("no changes found\n")) |
|
939 | pullop.repo.ui.status(_("no changes found\n")) | |
604 | pullop.cgresult = 0 |
|
940 | pullop.cgresult = 0 | |
605 | else: |
|
941 | else: | |
606 | if pullop.heads is None and list(pullop.common) == [nullid]: |
|
942 | if pullop.heads is None and list(pullop.common) == [nullid]: | |
607 | pullop.repo.ui.status(_("requesting all changes\n")) |
|
943 | pullop.repo.ui.status(_("requesting all changes\n")) | |
|
944 | if obsolete.isenabled(pullop.repo, obsolete.exchangeopt): | |||
|
945 | remoteversions = bundle2.obsmarkersversion(remotecaps) | |||
|
946 | if obsolete.commonversion(remoteversions) is not None: | |||
|
947 | kwargs['obsmarkers'] = True | |||
|
948 | pullop.stepsdone.add('obsmarkers') | |||
608 | _pullbundle2extraprepare(pullop, kwargs) |
|
949 | _pullbundle2extraprepare(pullop, kwargs) | |
609 | if kwargs.keys() == ['format']: |
|
950 | if kwargs.keys() == ['format']: | |
610 | return # nothing to pull |
|
951 | return # nothing to pull | |
@@ -615,14 +956,34 b' def _pullbundle2(pullop):' | |||||
615 | raise util.Abort('missing support for %s' % exc) |
|
956 | raise util.Abort('missing support for %s' % exc) | |
616 |
|
957 | |||
617 | if pullop.fetch: |
|
958 | if pullop.fetch: | |
618 | assert len(op.records['changegroup']) == 1 |
|
959 | changedheads = 0 | |
619 | pullop.cgresult = op.records['changegroup'][0]['return'] |
|
960 | pullop.cgresult = 1 | |
|
961 | for cg in op.records['changegroup']: | |||
|
962 | ret = cg['return'] | |||
|
963 | # If any changegroup result is 0, return 0 | |||
|
964 | if ret == 0: | |||
|
965 | pullop.cgresult = 0 | |||
|
966 | break | |||
|
967 | if ret < -1: | |||
|
968 | changedheads += ret + 1 | |||
|
969 | elif ret > 1: | |||
|
970 | changedheads += ret - 1 | |||
|
971 | if changedheads > 0: | |||
|
972 | pullop.cgresult = 1 + changedheads | |||
|
973 | elif changedheads < 0: | |||
|
974 | pullop.cgresult = -1 + changedheads | |||
620 |
|
975 | |||
621 | # processing phases change |
|
976 | # processing phases change | |
622 | for namespace, value in op.records['listkeys']: |
|
977 | for namespace, value in op.records['listkeys']: | |
623 | if namespace == 'phases': |
|
978 | if namespace == 'phases': | |
624 | _pullapplyphases(pullop, value) |
|
979 | _pullapplyphases(pullop, value) | |
625 |
|
980 | |||
|
981 | # processing bookmark update | |||
|
982 | for namespace, value in op.records['listkeys']: | |||
|
983 | if namespace == 'bookmarks': | |||
|
984 | pullop.remotebookmarks = value | |||
|
985 | _pullbookmarks(pullop) | |||
|
986 | ||||
626 | def _pullbundle2extraprepare(pullop, kwargs): |
|
987 | def _pullbundle2extraprepare(pullop, kwargs): | |
627 | """hook function so that extensions can extend the getbundle call""" |
|
988 | """hook function so that extensions can extend the getbundle call""" | |
628 | pass |
|
989 | pass | |
@@ -632,7 +993,9 b' def _pullchangeset(pullop):' | |||||
632 | # We delay the open of the transaction as late as possible so we |
|
993 | # We delay the open of the transaction as late as possible so we | |
633 | # don't open transaction for nothing or you break future useful |
|
994 | # don't open transaction for nothing or you break future useful | |
634 | # rollback call |
|
995 | # rollback call | |
635 | pullop.todosteps.remove('changegroup') |
|
996 | if 'changegroup' in pullop.stepsdone: | |
|
997 | return | |||
|
998 | pullop.stepsdone.add('changegroup') | |||
636 | if not pullop.fetch: |
|
999 | if not pullop.fetch: | |
637 | pullop.repo.ui.status(_("no changes found\n")) |
|
1000 | pullop.repo.ui.status(_("no changes found\n")) | |
638 | pullop.cgresult = 0 |
|
1001 | pullop.cgresult = 0 | |
@@ -661,26 +1024,57 b' def _pullchangeset(pullop):' | |||||
661 |
|
1024 | |||
662 | def _pullphase(pullop): |
|
1025 | def _pullphase(pullop): | |
663 | # Get remote phases data from remote |
|
1026 | # Get remote phases data from remote | |
|
1027 | if 'phases' in pullop.stepsdone: | |||
|
1028 | return | |||
664 | remotephases = pullop.remote.listkeys('phases') |
|
1029 | remotephases = pullop.remote.listkeys('phases') | |
665 | _pullapplyphases(pullop, remotephases) |
|
1030 | _pullapplyphases(pullop, remotephases) | |
666 |
|
1031 | |||
667 | def _pullapplyphases(pullop, remotephases): |
|
1032 | def _pullapplyphases(pullop, remotephases): | |
668 | """apply phase movement from observed remote state""" |
|
1033 | """apply phase movement from observed remote state""" | |
669 | pullop.todosteps.remove('phases') |
|
1034 | if 'phases' in pullop.stepsdone: | |
|
1035 | return | |||
|
1036 | pullop.stepsdone.add('phases') | |||
670 | publishing = bool(remotephases.get('publishing', False)) |
|
1037 | publishing = bool(remotephases.get('publishing', False)) | |
671 | if remotephases and not publishing: |
|
1038 | if remotephases and not publishing: | |
672 | # remote is new and unpublishing |
|
1039 | # remote is new and unpublishing | |
673 | pheads, _dr = phases.analyzeremotephases(pullop.repo, |
|
1040 | pheads, _dr = phases.analyzeremotephases(pullop.repo, | |
674 | pullop.pulledsubset, |
|
1041 | pullop.pulledsubset, | |
675 | remotephases) |
|
1042 | remotephases) | |
676 | phases.advanceboundary(pullop.repo, phases.public, pheads) |
|
1043 | dheads = pullop.pulledsubset | |
677 | phases.advanceboundary(pullop.repo, phases.draft, |
|
|||
678 | pullop.pulledsubset) |
|
|||
679 | else: |
|
1044 | else: | |
680 | # Remote is old or publishing all common changesets |
|
1045 | # Remote is old or publishing all common changesets | |
681 | # should be seen as public |
|
1046 | # should be seen as public | |
682 | phases.advanceboundary(pullop.repo, phases.public, |
|
1047 | pheads = pullop.pulledsubset | |
683 | pullop.pulledsubset) |
|
1048 | dheads = [] | |
|
1049 | unfi = pullop.repo.unfiltered() | |||
|
1050 | phase = unfi._phasecache.phase | |||
|
1051 | rev = unfi.changelog.nodemap.get | |||
|
1052 | public = phases.public | |||
|
1053 | draft = phases.draft | |||
|
1054 | ||||
|
1055 | # exclude changesets already public locally and update the others | |||
|
1056 | pheads = [pn for pn in pheads if phase(unfi, rev(pn)) > public] | |||
|
1057 | if pheads: | |||
|
1058 | tr = pullop.gettransaction() | |||
|
1059 | phases.advanceboundary(pullop.repo, tr, public, pheads) | |||
|
1060 | ||||
|
1061 | # exclude changesets already draft locally and update the others | |||
|
1062 | dheads = [pn for pn in dheads if phase(unfi, rev(pn)) > draft] | |||
|
1063 | if dheads: | |||
|
1064 | tr = pullop.gettransaction() | |||
|
1065 | phases.advanceboundary(pullop.repo, tr, draft, dheads) | |||
|
1066 | ||||
|
1067 | def _pullbookmarks(pullop): | |||
|
1068 | """process the remote bookmark information to update the local one""" | |||
|
1069 | if 'bookmarks' in pullop.stepsdone: | |||
|
1070 | return | |||
|
1071 | pullop.stepsdone.add('bookmarks') | |||
|
1072 | repo = pullop.repo | |||
|
1073 | remotebookmarks = pullop.remotebookmarks | |||
|
1074 | bookmod.updatefromremote(repo.ui, repo, remotebookmarks, | |||
|
1075 | pullop.remote.url(), | |||
|
1076 | pullop.gettransaction, | |||
|
1077 | explicit=pullop.explicitbookmarks) | |||
684 |
|
1078 | |||
685 | def _pullobsolete(pullop): |
|
1079 | def _pullobsolete(pullop): | |
686 | """utility function to pull obsolete markers from a remote |
|
1080 | """utility function to pull obsolete markers from a remote | |
@@ -690,9 +1084,11 b' def _pullobsolete(pullop):' | |||||
690 | a new transaction have been created (when applicable). |
|
1084 | a new transaction have been created (when applicable). | |
691 |
|
1085 | |||
692 | Exists mostly to allow overriding for experimentation purpose""" |
|
1086 | Exists mostly to allow overriding for experimentation purpose""" | |
693 | pullop.todosteps.remove('obsmarkers') |
|
1087 | if 'obsmarkers' in pullop.stepsdone: | |
|
1088 | return | |||
|
1089 | pullop.stepsdone.add('obsmarkers') | |||
694 | tr = None |
|
1090 | tr = None | |
695 | if obsolete._enabled: |
|
1091 | if obsolete.isenabled(pullop.repo, obsolete.exchangeopt): | |
696 | pullop.repo.ui.debug('fetching remote obsolete markers\n') |
|
1092 | pullop.repo.ui.debug('fetching remote obsolete markers\n') | |
697 | remoteobs = pullop.remote.listkeys('obsolete') |
|
1093 | remoteobs = pullop.remote.listkeys('obsolete') | |
698 | if 'dump0' in remoteobs: |
|
1094 | if 'dump0' in remoteobs: | |
@@ -706,58 +1102,112 b' def _pullobsolete(pullop):' | |||||
706 |
|
1102 | |||
707 | def caps20to10(repo): |
|
1103 | def caps20to10(repo): | |
708 | """return a set with appropriate options to use bundle20 during getbundle""" |
|
1104 | """return a set with appropriate options to use bundle20 during getbundle""" | |
709 |
caps = set(['HG2 |
|
1105 | caps = set(['HG2Y']) | |
710 |
capsblob = bundle2.encodecaps(repo |
|
1106 | capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo)) | |
711 | caps.add('bundle2=' + urllib.quote(capsblob)) |
|
1107 | caps.add('bundle2=' + urllib.quote(capsblob)) | |
712 | return caps |
|
1108 | return caps | |
713 |
|
1109 | |||
|
1110 | # List of names of steps to perform for a bundle2 for getbundle, order matters. | |||
|
1111 | getbundle2partsorder = [] | |||
|
1112 | ||||
|
1113 | # Mapping between step name and function | |||
|
1114 | # | |||
|
1115 | # This exists to help extensions wrap steps if necessary | |||
|
1116 | getbundle2partsmapping = {} | |||
|
1117 | ||||
|
1118 | def getbundle2partsgenerator(stepname): | |||
|
1119 | """decorator for function generating bundle2 part for getbundle | |||
|
1120 | ||||
|
1121 | The function is added to the step -> function mapping and appended to the | |||
|
1122 | list of steps. Beware that decorated functions will be added in order | |||
|
1123 | (this may matter). | |||
|
1124 | ||||
|
1125 | You can only use this decorator for new steps, if you want to wrap a step | |||
|
1126 | from an extension, attack the getbundle2partsmapping dictionary directly.""" | |||
|
1127 | def dec(func): | |||
|
1128 | assert stepname not in getbundle2partsmapping | |||
|
1129 | getbundle2partsmapping[stepname] = func | |||
|
1130 | getbundle2partsorder.append(stepname) | |||
|
1131 | return func | |||
|
1132 | return dec | |||
|
1133 | ||||
714 | def getbundle(repo, source, heads=None, common=None, bundlecaps=None, |
|
1134 | def getbundle(repo, source, heads=None, common=None, bundlecaps=None, | |
715 | **kwargs): |
|
1135 | **kwargs): | |
716 | """return a full bundle (with potentially multiple kind of parts) |
|
1136 | """return a full bundle (with potentially multiple kind of parts) | |
717 |
|
1137 | |||
718 |
Could be a bundle HG10 or a bundle HG2 |
|
1138 | Could be a bundle HG10 or a bundle HG2Y depending on bundlecaps | |
719 | passed. For now, the bundle can contain only changegroup, but this will |
|
1139 | passed. For now, the bundle can contain only changegroup, but this will | |
720 | changes when more part type will be available for bundle2. |
|
1140 | changes when more part type will be available for bundle2. | |
721 |
|
1141 | |||
722 |
This is different from changegroup.get |
|
1142 | This is different from changegroup.getchangegroup that only returns an HG10 | |
723 | changegroup bundle. They may eventually get reunited in the future when we |
|
1143 | changegroup bundle. They may eventually get reunited in the future when we | |
724 | have a clearer idea of the API we what to query different data. |
|
1144 | have a clearer idea of the API we what to query different data. | |
725 |
|
1145 | |||
726 | The implementation is at a very early stage and will get massive rework |
|
1146 | The implementation is at a very early stage and will get massive rework | |
727 | when the API of bundle is refined. |
|
1147 | when the API of bundle is refined. | |
728 | """ |
|
1148 | """ | |
729 | # build changegroup bundle here. |
|
1149 | # bundle10 case | |
730 | cg = changegroup.getbundle(repo, source, heads=heads, |
|
1150 | if bundlecaps is None or 'HG2Y' not in bundlecaps: | |
731 | common=common, bundlecaps=bundlecaps) |
|
1151 | if bundlecaps and not kwargs.get('cg', True): | |
732 | if bundlecaps is None or 'HG2X' not in bundlecaps: |
|
1152 | raise ValueError(_('request for bundle10 must include changegroup')) | |
|
1153 | ||||
733 | if kwargs: |
|
1154 | if kwargs: | |
734 | raise ValueError(_('unsupported getbundle arguments: %s') |
|
1155 | raise ValueError(_('unsupported getbundle arguments: %s') | |
735 | % ', '.join(sorted(kwargs.keys()))) |
|
1156 | % ', '.join(sorted(kwargs.keys()))) | |
736 | return cg |
|
1157 | return changegroup.getchangegroup(repo, source, heads=heads, | |
737 | # very crude first implementation, |
|
1158 | common=common, bundlecaps=bundlecaps) | |
738 | # the bundle API will change and the generation will be done lazily. |
|
1159 | ||
|
1160 | # bundle20 case | |||
739 | b2caps = {} |
|
1161 | b2caps = {} | |
740 | for bcaps in bundlecaps: |
|
1162 | for bcaps in bundlecaps: | |
741 | if bcaps.startswith('bundle2='): |
|
1163 | if bcaps.startswith('bundle2='): | |
742 | blob = urllib.unquote(bcaps[len('bundle2='):]) |
|
1164 | blob = urllib.unquote(bcaps[len('bundle2='):]) | |
743 | b2caps.update(bundle2.decodecaps(blob)) |
|
1165 | b2caps.update(bundle2.decodecaps(blob)) | |
744 | bundler = bundle2.bundle20(repo.ui, b2caps) |
|
1166 | bundler = bundle2.bundle20(repo.ui, b2caps) | |
|
1167 | ||||
|
1168 | for name in getbundle2partsorder: | |||
|
1169 | func = getbundle2partsmapping[name] | |||
|
1170 | kwargs['heads'] = heads | |||
|
1171 | kwargs['common'] = common | |||
|
1172 | func(bundler, repo, source, bundlecaps=bundlecaps, b2caps=b2caps, | |||
|
1173 | **kwargs) | |||
|
1174 | ||||
|
1175 | return util.chunkbuffer(bundler.getchunks()) | |||
|
1176 | ||||
|
1177 | @getbundle2partsgenerator('changegroup') | |||
|
1178 | def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None, | |||
|
1179 | b2caps=None, heads=None, common=None, **kwargs): | |||
|
1180 | """add a changegroup part to the requested bundle""" | |||
|
1181 | cg = None | |||
|
1182 | if kwargs.get('cg', True): | |||
|
1183 | # build changegroup bundle here. | |||
|
1184 | cg = changegroup.getchangegroup(repo, source, heads=heads, | |||
|
1185 | common=common, bundlecaps=bundlecaps) | |||
|
1186 | ||||
745 | if cg: |
|
1187 | if cg: | |
746 | bundler.newpart('b2x:changegroup', data=cg.getchunks()) |
|
1188 | bundler.newpart('b2x:changegroup', data=cg.getchunks()) | |
|
1189 | ||||
|
1190 | @getbundle2partsgenerator('listkeys') | |||
|
1191 | def _getbundlelistkeysparts(bundler, repo, source, bundlecaps=None, | |||
|
1192 | b2caps=None, **kwargs): | |||
|
1193 | """add parts containing listkeys namespaces to the requested bundle""" | |||
747 | listkeys = kwargs.get('listkeys', ()) |
|
1194 | listkeys = kwargs.get('listkeys', ()) | |
748 | for namespace in listkeys: |
|
1195 | for namespace in listkeys: | |
749 | part = bundler.newpart('b2x:listkeys') |
|
1196 | part = bundler.newpart('b2x:listkeys') | |
750 | part.addparam('namespace', namespace) |
|
1197 | part.addparam('namespace', namespace) | |
751 | keys = repo.listkeys(namespace).items() |
|
1198 | keys = repo.listkeys(namespace).items() | |
752 | part.data = pushkey.encodekeys(keys) |
|
1199 | part.data = pushkey.encodekeys(keys) | |
753 | _getbundleextrapart(bundler, repo, source, heads=heads, common=common, |
|
|||
754 | bundlecaps=bundlecaps, **kwargs) |
|
|||
755 | return util.chunkbuffer(bundler.getchunks()) |
|
|||
756 |
|
1200 | |||
757 | def _getbundleextrapart(bundler, repo, source, heads=None, common=None, |
|
1201 | @getbundle2partsgenerator('obsmarkers') | |
758 | bundlecaps=None, **kwargs): |
|
1202 | def _getbundleobsmarkerpart(bundler, repo, source, bundlecaps=None, | |
759 | """hook function to let extensions add parts to the requested bundle""" |
|
1203 | b2caps=None, heads=None, **kwargs): | |
760 | pass |
|
1204 | """add an obsolescence markers part to the requested bundle""" | |
|
1205 | if kwargs.get('obsmarkers', False): | |||
|
1206 | if heads is None: | |||
|
1207 | heads = repo.heads() | |||
|
1208 | subset = [c.node() for c in repo.set('::%ln', heads)] | |||
|
1209 | markers = repo.obsstore.relevantmarkers(subset) | |||
|
1210 | buildobsmarkerspart(bundler, markers) | |||
761 |
|
1211 | |||
762 | def check_heads(repo, their_heads, context): |
|
1212 | def check_heads(repo, their_heads, context): | |
763 | """check if the heads of a repo have been modified |
|
1213 | """check if the heads of a repo have been modified | |
@@ -791,15 +1241,19 b' def unbundle(repo, cg, heads, source, ur' | |||||
791 | if util.safehasattr(cg, 'params'): |
|
1241 | if util.safehasattr(cg, 'params'): | |
792 | try: |
|
1242 | try: | |
793 | tr = repo.transaction('unbundle') |
|
1243 | tr = repo.transaction('unbundle') | |
|
1244 | tr.hookargs['source'] = source | |||
|
1245 | tr.hookargs['url'] = url | |||
794 | tr.hookargs['bundle2-exp'] = '1' |
|
1246 | tr.hookargs['bundle2-exp'] = '1' | |
795 | r = bundle2.processbundle(repo, cg, lambda: tr).reply |
|
1247 | r = bundle2.processbundle(repo, cg, lambda: tr).reply | |
796 | cl = repo.unfiltered().changelog |
|
1248 | cl = repo.unfiltered().changelog | |
797 | p = cl.writepending() and repo.root or "" |
|
1249 | p = cl.writepending() and repo.root or "" | |
798 |
repo.hook('b2x-pretransactionclose', throw=True, |
|
1250 | repo.hook('b2x-pretransactionclose', throw=True, pending=p, | |
799 |
|
|
1251 | **tr.hookargs) | |
800 | tr.close() |
|
1252 | tr.close() | |
801 | repo.hook('b2x-transactionclose', source=source, url=url, |
|
1253 | hookargs = dict(tr.hookargs) | |
802 |
|
|
1254 | def runhooks(): | |
|
1255 | repo.hook('b2x-transactionclose', **hookargs) | |||
|
1256 | repo._afterlock(runhooks) | |||
803 | except Exception, exc: |
|
1257 | except Exception, exc: | |
804 | exc.duringunbundle2 = True |
|
1258 | exc.duringunbundle2 = True | |
805 | raise |
|
1259 | raise |
@@ -5,29 +5,31 b'' | |||||
5 | # This software may be used and distributed according to the terms of the |
|
5 | # This software may be used and distributed according to the terms of the | |
6 | # GNU General Public License version 2 or any later version. |
|
6 | # GNU General Public License version 2 or any later version. | |
7 |
|
7 | |||
8 | import revlog |
|
8 | import error, revlog | |
9 | import re |
|
9 | import re | |
10 |
|
10 | |||
11 | _mdre = re.compile('\1\n') |
|
11 | _mdre = re.compile('\1\n') | |
12 |
def |
|
12 | def parsemeta(text): | |
13 | """return (metadatadict, keylist, metadatasize)""" |
|
13 | """return (metadatadict, keylist, metadatasize)""" | |
14 | # text can be buffer, so we can't use .startswith or .index |
|
14 | # text can be buffer, so we can't use .startswith or .index | |
15 | if text[:2] != '\1\n': |
|
15 | if text[:2] != '\1\n': | |
16 |
return None, None |
|
16 | return None, None | |
17 | s = _mdre.search(text, 2).start() |
|
17 | s = _mdre.search(text, 2).start() | |
18 | mtext = text[2:s] |
|
18 | mtext = text[2:s] | |
19 | meta = {} |
|
19 | meta = {} | |
20 | keys = [] |
|
|||
21 | for l in mtext.splitlines(): |
|
20 | for l in mtext.splitlines(): | |
22 | k, v = l.split(": ", 1) |
|
21 | k, v = l.split(": ", 1) | |
23 | meta[k] = v |
|
22 | meta[k] = v | |
24 | keys.append(k) |
|
23 | return meta, (s + 2) | |
25 | return meta, keys, (s + 2) |
|
|||
26 |
|
24 | |||
27 |
def |
|
25 | def packmeta(meta, text): | |
28 | if not keys: |
|
26 | keys = sorted(meta.iterkeys()) | |
29 | keys = sorted(meta.iterkeys()) |
|
27 | metatext = "".join("%s: %s\n" % (k, meta[k]) for k in keys) | |
30 | return "".join("%s: %s\n" % (k, meta[k]) for k in keys) |
|
28 | return "\1\n%s\1\n%s" % (metatext, text) | |
|
29 | ||||
|
30 | def _censoredtext(text): | |||
|
31 | m, offs = parsemeta(text) | |||
|
32 | return m and "censored" in m and not text[offs:] | |||
31 |
|
33 | |||
32 | class filelog(revlog.revlog): |
|
34 | class filelog(revlog.revlog): | |
33 | def __init__(self, opener, path): |
|
35 | def __init__(self, opener, path): | |
@@ -43,14 +45,14 b' class filelog(revlog.revlog):' | |||||
43 |
|
45 | |||
44 | def add(self, text, meta, transaction, link, p1=None, p2=None): |
|
46 | def add(self, text, meta, transaction, link, p1=None, p2=None): | |
45 | if meta or text.startswith('\1\n'): |
|
47 | if meta or text.startswith('\1\n'): | |
46 |
text = |
|
48 | text = packmeta(meta, text) | |
47 | return self.addrevision(text, transaction, link, p1, p2) |
|
49 | return self.addrevision(text, transaction, link, p1, p2) | |
48 |
|
50 | |||
49 | def renamed(self, node): |
|
51 | def renamed(self, node): | |
50 | if self.parents(node)[0] != revlog.nullid: |
|
52 | if self.parents(node)[0] != revlog.nullid: | |
51 | return False |
|
53 | return False | |
52 | t = self.revision(node) |
|
54 | t = self.revision(node) | |
53 |
m = |
|
55 | m = parsemeta(t)[0] | |
54 | if m and "copy" in m: |
|
56 | if m and "copy" in m: | |
55 | return (m["copy"], revlog.bin(m["copyrev"])) |
|
57 | return (m["copy"], revlog.bin(m["copyrev"])) | |
56 | return False |
|
58 | return False | |
@@ -62,6 +64,8 b' class filelog(revlog.revlog):' | |||||
62 | node = self.node(rev) |
|
64 | node = self.node(rev) | |
63 | if self.renamed(node): |
|
65 | if self.renamed(node): | |
64 | return len(self.read(node)) |
|
66 | return len(self.read(node)) | |
|
67 | if self._iscensored(rev): | |||
|
68 | return 0 | |||
65 |
|
69 | |||
66 | # XXX if self.read(node).startswith("\1\n"), this returns (size+4) |
|
70 | # XXX if self.read(node).startswith("\1\n"), this returns (size+4) | |
67 | return super(filelog, self).size(rev) |
|
71 | return super(filelog, self).size(rev) | |
@@ -80,6 +84,10 b' class filelog(revlog.revlog):' | |||||
80 | if samehashes: |
|
84 | if samehashes: | |
81 | return False |
|
85 | return False | |
82 |
|
86 | |||
|
87 | # censored files compare against the empty file | |||
|
88 | if self._iscensored(node): | |||
|
89 | return text != '' | |||
|
90 | ||||
83 | # renaming a file produces a different hash, even if the data |
|
91 | # renaming a file produces a different hash, even if the data | |
84 | # remains unchanged. Check if it's the case (slow): |
|
92 | # remains unchanged. Check if it's the case (slow): | |
85 | if self.renamed(node): |
|
93 | if self.renamed(node): | |
@@ -88,5 +96,21 b' class filelog(revlog.revlog):' | |||||
88 |
|
96 | |||
89 | return True |
|
97 | return True | |
90 |
|
98 | |||
|
99 | def checkhash(self, text, p1, p2, node, rev=None): | |||
|
100 | try: | |||
|
101 | super(filelog, self).checkhash(text, p1, p2, node, rev=rev) | |||
|
102 | except error.RevlogError: | |||
|
103 | if _censoredtext(text): | |||
|
104 | raise error.CensoredNodeError(self.indexfile, node) | |||
|
105 | raise | |||
|
106 | ||||
91 | def _file(self, f): |
|
107 | def _file(self, f): | |
92 | return filelog(self.opener, f) |
|
108 | return filelog(self.opener, f) | |
|
109 | ||||
|
110 | def _iscensored(self, revornode): | |||
|
111 | """Check if a file revision is censored.""" | |||
|
112 | try: | |||
|
113 | self.revision(revornode) | |||
|
114 | return False | |||
|
115 | except error.CensoredNodeError: | |||
|
116 | return True |
@@ -25,9 +25,10 b' internals = {}' | |||||
25 | def internaltool(name, trymerge, onfailure=None): |
|
25 | def internaltool(name, trymerge, onfailure=None): | |
26 | '''return a decorator for populating internal merge tool table''' |
|
26 | '''return a decorator for populating internal merge tool table''' | |
27 | def decorator(func): |
|
27 | def decorator(func): | |
28 |
fullname = ' |
|
28 | fullname = ':' + name | |
29 | func.__doc__ = "``%s``\n" % fullname + func.__doc__.strip() |
|
29 | func.__doc__ = "``%s``\n" % fullname + func.__doc__.strip() | |
30 | internals[fullname] = func |
|
30 | internals[fullname] = func | |
|
31 | internals['internal:' + name] = func | |||
31 | func.trymerge = trymerge |
|
32 | func.trymerge = trymerge | |
32 | func.onfailure = onfailure |
|
33 | func.onfailure = onfailure | |
33 | return func |
|
34 | return func | |
@@ -111,8 +112,8 b' def _picktool(repo, ui, path, binary, sy' | |||||
111 |
|
112 | |||
112 | # internal merge or prompt as last resort |
|
113 | # internal merge or prompt as last resort | |
113 | if symlink or binary: |
|
114 | if symlink or binary: | |
114 |
return " |
|
115 | return ":prompt", None | |
115 |
return " |
|
116 | return ":merge", None | |
116 |
|
117 | |||
117 | def _eoltype(data): |
|
118 | def _eoltype(data): | |
118 | "Guess the EOL type of a file" |
|
119 | "Guess the EOL type of a file" | |
@@ -178,24 +179,30 b' def _premerge(repo, toolconf, files, lab' | |||||
178 |
|
179 | |||
179 | ui = repo.ui |
|
180 | ui = repo.ui | |
180 |
|
181 | |||
|
182 | validkeep = ['keep', 'keep-merge3'] | |||
|
183 | ||||
181 | # do we attempt to simplemerge first? |
|
184 | # do we attempt to simplemerge first? | |
182 | try: |
|
185 | try: | |
183 | premerge = _toolbool(ui, tool, "premerge", not binary) |
|
186 | premerge = _toolbool(ui, tool, "premerge", not binary) | |
184 | except error.ConfigError: |
|
187 | except error.ConfigError: | |
185 | premerge = _toolstr(ui, tool, "premerge").lower() |
|
188 | premerge = _toolstr(ui, tool, "premerge").lower() | |
186 | valid = 'keep'.split() |
|
189 | if premerge not in validkeep: | |
187 | if premerge not in valid: |
|
190 | _valid = ', '.join(["'" + v + "'" for v in validkeep]) | |
188 | _valid = ', '.join(["'" + v + "'" for v in valid]) |
|
|||
189 | raise error.ConfigError(_("%s.premerge not valid " |
|
191 | raise error.ConfigError(_("%s.premerge not valid " | |
190 | "('%s' is neither boolean nor %s)") % |
|
192 | "('%s' is neither boolean nor %s)") % | |
191 | (tool, premerge, _valid)) |
|
193 | (tool, premerge, _valid)) | |
192 |
|
194 | |||
193 | if premerge: |
|
195 | if premerge: | |
|
196 | if premerge == 'keep-merge3': | |||
|
197 | if not labels: | |||
|
198 | labels = _defaultconflictlabels | |||
|
199 | if len(labels) < 3: | |||
|
200 | labels.append('base') | |||
194 | r = simplemerge.simplemerge(ui, a, b, c, quiet=True, label=labels) |
|
201 | r = simplemerge.simplemerge(ui, a, b, c, quiet=True, label=labels) | |
195 | if not r: |
|
202 | if not r: | |
196 | ui.debug(" premerge successful\n") |
|
203 | ui.debug(" premerge successful\n") | |
197 | return 0 |
|
204 | return 0 | |
198 |
if premerge |
|
205 | if premerge not in validkeep: | |
199 | util.copyfile(back, a) # restore from backup and try again |
|
206 | util.copyfile(back, a) # restore from backup and try again | |
200 | return 1 # continue merging |
|
207 | return 1 # continue merging | |
201 |
|
208 | |||
@@ -206,10 +213,11 b' def _imerge(repo, mynode, orig, fcd, fco' | |||||
206 | """ |
|
213 | """ | |
207 | Uses the internal non-interactive simple merge algorithm for merging |
|
214 | Uses the internal non-interactive simple merge algorithm for merging | |
208 | files. It will fail if there are any conflicts and leave markers in |
|
215 | files. It will fail if there are any conflicts and leave markers in | |
209 | the partially merged file.""" |
|
216 | the partially merged file. Markers will have two sections, one for each side | |
|
217 | of merge.""" | |||
210 | tool, toolpath, binary, symlink = toolconf |
|
218 | tool, toolpath, binary, symlink = toolconf | |
211 | if symlink: |
|
219 | if symlink: | |
212 | repo.ui.warn(_('warning: internal:merge cannot merge symlinks ' |
|
220 | repo.ui.warn(_('warning: internal :merge cannot merge symlinks ' | |
213 | 'for %s\n') % fcd.path()) |
|
221 | 'for %s\n') % fcd.path()) | |
214 | return False, 1 |
|
222 | return False, 1 | |
215 | r = _premerge(repo, toolconf, files, labels=labels) |
|
223 | r = _premerge(repo, toolconf, files, labels=labels) | |
@@ -218,13 +226,28 b' def _imerge(repo, mynode, orig, fcd, fco' | |||||
218 |
|
226 | |||
219 | ui = repo.ui |
|
227 | ui = repo.ui | |
220 |
|
228 | |||
221 |
r = simplemerge.simplemerge(ui, a, b, c, label=labels |
|
229 | r = simplemerge.simplemerge(ui, a, b, c, label=labels) | |
222 | return True, r |
|
230 | return True, r | |
223 | return False, 0 |
|
231 | return False, 0 | |
224 |
|
232 | |||
|
233 | @internaltool('merge3', True, | |||
|
234 | _("merging %s incomplete! " | |||
|
235 | "(edit conflicts, then use 'hg resolve --mark')\n")) | |||
|
236 | def _imerge3(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): | |||
|
237 | """ | |||
|
238 | Uses the internal non-interactive simple merge algorithm for merging | |||
|
239 | files. It will fail if there are any conflicts and leave markers in | |||
|
240 | the partially merged file. Marker will have three sections, one from each | |||
|
241 | side of the merge and one for the base content.""" | |||
|
242 | if not labels: | |||
|
243 | labels = _defaultconflictlabels | |||
|
244 | if len(labels) < 3: | |||
|
245 | labels.append('base') | |||
|
246 | return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels) | |||
|
247 | ||||
225 | @internaltool('tagmerge', True, |
|
248 | @internaltool('tagmerge', True, | |
226 | _("automatic tag merging of %s failed! " |
|
249 | _("automatic tag merging of %s failed! " | |
227 |
"(use 'hg resolve --tool |
|
250 | "(use 'hg resolve --tool :merge' or another merge " | |
228 | "tool of your choice)\n")) |
|
251 | "tool of your choice)\n")) | |
229 | def _itagmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): |
|
252 | def _itagmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): | |
230 | """ |
|
253 | """ | |
@@ -312,23 +335,27 b' def _formatconflictmarker(repo, ctx, tem' | |||||
312 |
|
335 | |||
313 | _defaultconflictlabels = ['local', 'other'] |
|
336 | _defaultconflictlabels = ['local', 'other'] | |
314 |
|
337 | |||
315 | def _formatlabels(repo, fcd, fco, labels): |
|
338 | def _formatlabels(repo, fcd, fco, fca, labels): | |
316 | """Formats the given labels using the conflict marker template. |
|
339 | """Formats the given labels using the conflict marker template. | |
317 |
|
340 | |||
318 | Returns a list of formatted labels. |
|
341 | Returns a list of formatted labels. | |
319 | """ |
|
342 | """ | |
320 | cd = fcd.changectx() |
|
343 | cd = fcd.changectx() | |
321 | co = fco.changectx() |
|
344 | co = fco.changectx() | |
|
345 | ca = fca.changectx() | |||
322 |
|
346 | |||
323 | ui = repo.ui |
|
347 | ui = repo.ui | |
324 | template = ui.config('ui', 'mergemarkertemplate', _defaultconflictmarker) |
|
348 | template = ui.config('ui', 'mergemarkertemplate', _defaultconflictmarker) | |
325 | template = templater.parsestring(template, quoted=False) |
|
349 | template = templater.parsestring(template, quoted=False) | |
326 |
tmpl = templater.templater(None, cache={ |
|
350 | tmpl = templater.templater(None, cache={'conflictmarker': template}) | |
|
351 | ||||
|
352 | pad = max(len(l) for l in labels) | |||
327 |
|
353 | |||
328 | pad = max(len(labels[0]), len(labels[1])) |
|
354 | newlabels = [_formatconflictmarker(repo, cd, tmpl, labels[0], pad), | |
329 |
|
355 | _formatconflictmarker(repo, co, tmpl, labels[1], pad)] | ||
330 | return [_formatconflictmarker(repo, cd, tmpl, labels[0], pad), |
|
356 | if len(labels) > 2: | |
331 |
|
|
357 | newlabels.append(_formatconflictmarker(repo, ca, tmpl, labels[2], pad)) | |
|
358 | return newlabels | |||
332 |
|
359 | |||
333 | def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None): |
|
360 | def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None): | |
334 | """perform a 3-way merge in the working directory |
|
361 | """perform a 3-way merge in the working directory | |
@@ -388,16 +415,13 b' def filemerge(repo, mynode, orig, fcd, f' | |||||
388 | ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca)) |
|
415 | ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca)) | |
389 |
|
416 | |||
390 | markerstyle = ui.config('ui', 'mergemarkers', 'basic') |
|
417 | markerstyle = ui.config('ui', 'mergemarkers', 'basic') | |
391 | if markerstyle == 'basic': |
|
418 | if not labels: | |
392 |
|
|
419 | labels = _defaultconflictlabels | |
393 | else: |
|
420 | if markerstyle != 'basic': | |
394 | if not labels: |
|
421 | labels = _formatlabels(repo, fcd, fco, fca, labels) | |
395 | labels = _defaultconflictlabels |
|
|||
396 |
|
||||
397 | formattedlabels = _formatlabels(repo, fcd, fco, labels) |
|
|||
398 |
|
422 | |||
399 | needcheck, r = func(repo, mynode, orig, fcd, fco, fca, toolconf, |
|
423 | needcheck, r = func(repo, mynode, orig, fcd, fco, fca, toolconf, | |
400 |
(a, b, c, back), labels= |
|
424 | (a, b, c, back), labels=labels) | |
401 | if not needcheck: |
|
425 | if not needcheck: | |
402 | if r: |
|
426 | if r: | |
403 | if onfailure: |
|
427 | if onfailure: |
@@ -124,7 +124,7 b' def modified(mctx, x):' | |||||
124 | """ |
|
124 | """ | |
125 | # i18n: "modified" is a keyword |
|
125 | # i18n: "modified" is a keyword | |
126 | getargs(x, 0, 0, _("modified takes no arguments")) |
|
126 | getargs(x, 0, 0, _("modified takes no arguments")) | |
127 |
s = mctx.status() |
|
127 | s = mctx.status().modified | |
128 | return [f for f in mctx.subset if f in s] |
|
128 | return [f for f in mctx.subset if f in s] | |
129 |
|
129 | |||
130 | def added(mctx, x): |
|
130 | def added(mctx, x): | |
@@ -133,7 +133,7 b' def added(mctx, x):' | |||||
133 | """ |
|
133 | """ | |
134 | # i18n: "added" is a keyword |
|
134 | # i18n: "added" is a keyword | |
135 | getargs(x, 0, 0, _("added takes no arguments")) |
|
135 | getargs(x, 0, 0, _("added takes no arguments")) | |
136 |
s = mctx.status() |
|
136 | s = mctx.status().added | |
137 | return [f for f in mctx.subset if f in s] |
|
137 | return [f for f in mctx.subset if f in s] | |
138 |
|
138 | |||
139 | def removed(mctx, x): |
|
139 | def removed(mctx, x): | |
@@ -142,7 +142,7 b' def removed(mctx, x):' | |||||
142 | """ |
|
142 | """ | |
143 | # i18n: "removed" is a keyword |
|
143 | # i18n: "removed" is a keyword | |
144 | getargs(x, 0, 0, _("removed takes no arguments")) |
|
144 | getargs(x, 0, 0, _("removed takes no arguments")) | |
145 |
s = mctx.status() |
|
145 | s = mctx.status().removed | |
146 | return [f for f in mctx.subset if f in s] |
|
146 | return [f for f in mctx.subset if f in s] | |
147 |
|
147 | |||
148 | def deleted(mctx, x): |
|
148 | def deleted(mctx, x): | |
@@ -151,7 +151,7 b' def deleted(mctx, x):' | |||||
151 | """ |
|
151 | """ | |
152 | # i18n: "deleted" is a keyword |
|
152 | # i18n: "deleted" is a keyword | |
153 | getargs(x, 0, 0, _("deleted takes no arguments")) |
|
153 | getargs(x, 0, 0, _("deleted takes no arguments")) | |
154 |
s = mctx.status() |
|
154 | s = mctx.status().deleted | |
155 | return [f for f in mctx.subset if f in s] |
|
155 | return [f for f in mctx.subset if f in s] | |
156 |
|
156 | |||
157 | def unknown(mctx, x): |
|
157 | def unknown(mctx, x): | |
@@ -161,7 +161,7 b' def unknown(mctx, x):' | |||||
161 | """ |
|
161 | """ | |
162 | # i18n: "unknown" is a keyword |
|
162 | # i18n: "unknown" is a keyword | |
163 | getargs(x, 0, 0, _("unknown takes no arguments")) |
|
163 | getargs(x, 0, 0, _("unknown takes no arguments")) | |
164 |
s = mctx.status() |
|
164 | s = mctx.status().unknown | |
165 | return [f for f in mctx.subset if f in s] |
|
165 | return [f for f in mctx.subset if f in s] | |
166 |
|
166 | |||
167 | def ignored(mctx, x): |
|
167 | def ignored(mctx, x): | |
@@ -171,7 +171,7 b' def ignored(mctx, x):' | |||||
171 | """ |
|
171 | """ | |
172 | # i18n: "ignored" is a keyword |
|
172 | # i18n: "ignored" is a keyword | |
173 | getargs(x, 0, 0, _("ignored takes no arguments")) |
|
173 | getargs(x, 0, 0, _("ignored takes no arguments")) | |
174 |
s = mctx.status() |
|
174 | s = mctx.status().ignored | |
175 | return [f for f in mctx.subset if f in s] |
|
175 | return [f for f in mctx.subset if f in s] | |
176 |
|
176 | |||
177 | def clean(mctx, x): |
|
177 | def clean(mctx, x): | |
@@ -180,7 +180,7 b' def clean(mctx, x):' | |||||
180 | """ |
|
180 | """ | |
181 | # i18n: "clean" is a keyword |
|
181 | # i18n: "clean" is a keyword | |
182 | getargs(x, 0, 0, _("clean takes no arguments")) |
|
182 | getargs(x, 0, 0, _("clean takes no arguments")) | |
183 |
s = mctx.status() |
|
183 | s = mctx.status().clean | |
184 | return [f for f in mctx.subset if f in s] |
|
184 | return [f for f in mctx.subset if f in s] | |
185 |
|
185 | |||
186 | def func(mctx, a, b): |
|
186 | def func(mctx, a, b): |
@@ -5,6 +5,11 b'' | |||||
5 | # This software may be used and distributed according to the terms of the |
|
5 | # This software may be used and distributed according to the terms of the | |
6 | # GNU General Public License version 2 or any later version. |
|
6 | # GNU General Public License version 2 or any later version. | |
7 |
|
7 | |||
|
8 | import cPickle | |||
|
9 | from node import hex, short | |||
|
10 | from i18n import _ | |||
|
11 | import encoding, util | |||
|
12 | ||||
8 | class baseformatter(object): |
|
13 | class baseformatter(object): | |
9 | def __init__(self, ui, topic, opts): |
|
14 | def __init__(self, ui, topic, opts): | |
10 | self._ui = ui |
|
15 | self._ui = ui | |
@@ -12,7 +17,9 b' class baseformatter(object):' | |||||
12 | self._style = opts.get("style") |
|
17 | self._style = opts.get("style") | |
13 | self._template = opts.get("template") |
|
18 | self._template = opts.get("template") | |
14 | self._item = None |
|
19 | self._item = None | |
15 | def __bool__(self): |
|
20 | # function to convert node to string suitable for this output | |
|
21 | self.hexfunc = hex | |||
|
22 | def __nonzero__(self): | |||
16 | '''return False if we're not doing real templating so we can |
|
23 | '''return False if we're not doing real templating so we can | |
17 | skip extra work''' |
|
24 | skip extra work''' | |
18 | return True |
|
25 | return True | |
@@ -47,7 +54,11 b' class plainformatter(baseformatter):' | |||||
47 | '''the default text output scheme''' |
|
54 | '''the default text output scheme''' | |
48 | def __init__(self, ui, topic, opts): |
|
55 | def __init__(self, ui, topic, opts): | |
49 | baseformatter.__init__(self, ui, topic, opts) |
|
56 | baseformatter.__init__(self, ui, topic, opts) | |
50 | def __bool__(self): |
|
57 | if ui.debugflag: | |
|
58 | self.hexfunc = hex | |||
|
59 | else: | |||
|
60 | self.hexfunc = short | |||
|
61 | def __nonzero__(self): | |||
51 | return False |
|
62 | return False | |
52 | def startitem(self): |
|
63 | def startitem(self): | |
53 | pass |
|
64 | pass | |
@@ -67,14 +78,71 b' class plainformatter(baseformatter):' | |||||
67 | class debugformatter(baseformatter): |
|
78 | class debugformatter(baseformatter): | |
68 | def __init__(self, ui, topic, opts): |
|
79 | def __init__(self, ui, topic, opts): | |
69 | baseformatter.__init__(self, ui, topic, opts) |
|
80 | baseformatter.__init__(self, ui, topic, opts) | |
70 |
self._ui.write("%s = |
|
81 | self._ui.write("%s = [\n" % self._topic) | |
71 | def _showitem(self): |
|
82 | def _showitem(self): | |
72 | self._ui.write(" " + repr(self._item) + ",\n") |
|
83 | self._ui.write(" " + repr(self._item) + ",\n") | |
73 | def end(self): |
|
84 | def end(self): | |
74 | baseformatter.end(self) |
|
85 | baseformatter.end(self) | |
75 |
self._ui.write(" |
|
86 | self._ui.write("]\n") | |
|
87 | ||||
|
88 | class pickleformatter(baseformatter): | |||
|
89 | def __init__(self, ui, topic, opts): | |||
|
90 | baseformatter.__init__(self, ui, topic, opts) | |||
|
91 | self._data = [] | |||
|
92 | def _showitem(self): | |||
|
93 | self._data.append(self._item) | |||
|
94 | def end(self): | |||
|
95 | baseformatter.end(self) | |||
|
96 | self._ui.write(cPickle.dumps(self._data)) | |||
|
97 | ||||
|
98 | def _jsonifyobj(v): | |||
|
99 | if isinstance(v, tuple): | |||
|
100 | return '[' + ', '.join(_jsonifyobj(e) for e in v) + ']' | |||
|
101 | elif v is True: | |||
|
102 | return 'true' | |||
|
103 | elif v is False: | |||
|
104 | return 'false' | |||
|
105 | elif isinstance(v, (int, float)): | |||
|
106 | return str(v) | |||
|
107 | else: | |||
|
108 | return '"%s"' % encoding.jsonescape(v) | |||
|
109 | ||||
|
110 | class jsonformatter(baseformatter): | |||
|
111 | def __init__(self, ui, topic, opts): | |||
|
112 | baseformatter.__init__(self, ui, topic, opts) | |||
|
113 | self._ui.write("[") | |||
|
114 | self._ui._first = True | |||
|
115 | def _showitem(self): | |||
|
116 | if self._ui._first: | |||
|
117 | self._ui._first = False | |||
|
118 | else: | |||
|
119 | self._ui.write(",") | |||
|
120 | ||||
|
121 | self._ui.write("\n {\n") | |||
|
122 | first = True | |||
|
123 | for k, v in sorted(self._item.items()): | |||
|
124 | if first: | |||
|
125 | first = False | |||
|
126 | else: | |||
|
127 | self._ui.write(",\n") | |||
|
128 | self._ui.write(' "%s": %s' % (k, _jsonifyobj(v))) | |||
|
129 | self._ui.write("\n }") | |||
|
130 | def end(self): | |||
|
131 | baseformatter.end(self) | |||
|
132 | self._ui.write("\n]\n") | |||
76 |
|
133 | |||
77 | def formatter(ui, topic, opts): |
|
134 | def formatter(ui, topic, opts): | |
78 | if ui.configbool('ui', 'formatdebug'): |
|
135 | template = opts.get("template", "") | |
|
136 | if template == "json": | |||
|
137 | return jsonformatter(ui, topic, opts) | |||
|
138 | elif template == "pickle": | |||
|
139 | return pickleformatter(ui, topic, opts) | |||
|
140 | elif template == "debug": | |||
79 | return debugformatter(ui, topic, opts) |
|
141 | return debugformatter(ui, topic, opts) | |
|
142 | elif template != "": | |||
|
143 | raise util.Abort(_("custom templates not yet supported")) | |||
|
144 | elif ui.configbool('ui', 'formatdebug'): | |||
|
145 | return debugformatter(ui, topic, opts) | |||
|
146 | elif ui.configbool('ui', 'formatjson'): | |||
|
147 | return jsonformatter(ui, topic, opts) | |||
80 | return plainformatter(ui, topic, opts) |
|
148 | return plainformatter(ui, topic, opts) |
@@ -37,11 +37,10 b' def dagwalker(repo, revs):' | |||||
37 | lowestrev = revs.min() |
|
37 | lowestrev = revs.min() | |
38 | gpcache = {} |
|
38 | gpcache = {} | |
39 |
|
39 | |||
40 | knownrevs = revs.set() |
|
|||
41 | for rev in revs: |
|
40 | for rev in revs: | |
42 | ctx = repo[rev] |
|
41 | ctx = repo[rev] | |
43 | parents = sorted(set([p.rev() for p in ctx.parents() |
|
42 | parents = sorted(set([p.rev() for p in ctx.parents() | |
44 |
if p.rev() in |
|
43 | if p.rev() in revs])) | |
45 | mpars = [p.rev() for p in ctx.parents() if |
|
44 | mpars = [p.rev() for p in ctx.parents() if | |
46 | p.rev() != nullrev and p.rev() not in parents] |
|
45 | p.rev() != nullrev and p.rev() not in parents] | |
47 |
|
46 |
@@ -6,7 +6,7 b'' | |||||
6 | # GNU General Public License version 2 or any later version. |
|
6 | # GNU General Public License version 2 or any later version. | |
7 |
|
7 | |||
8 | from i18n import gettext, _ |
|
8 | from i18n import gettext, _ | |
9 |
import itertools, |
|
9 | import itertools, os | |
10 | import error |
|
10 | import error | |
11 | import extensions, revset, fileset, templatekw, templatefilters, filemerge |
|
11 | import extensions, revset, fileset, templatekw, templatefilters, filemerge | |
12 | import encoding, util, minirst |
|
12 | import encoding, util, minirst | |
@@ -31,7 +31,7 b' def extshelp():' | |||||
31 | doc = ''.join(rst) |
|
31 | doc = ''.join(rst) | |
32 | return doc |
|
32 | return doc | |
33 |
|
33 | |||
34 | def optrst(options, verbose): |
|
34 | def optrst(header, options, verbose): | |
35 | data = [] |
|
35 | data = [] | |
36 | multioccur = False |
|
36 | multioccur = False | |
37 | for option in options: |
|
37 | for option in options: | |
@@ -59,10 +59,11 b' def optrst(options, verbose):' | |||||
59 |
|
59 | |||
60 | data.append((so, lo, desc)) |
|
60 | data.append((so, lo, desc)) | |
61 |
|
61 | |||
62 | rst = minirst.maketable(data, 1) |
|
62 | if multioccur: | |
|
63 | header += (_(" ([+] can be repeated)")) | |||
63 |
|
64 | |||
64 | if multioccur: |
|
65 | rst = ['\n%s:\n\n' % header] | |
65 | rst.append(_("\n[+] marked option can be specified multiple times\n")) |
|
66 | rst.extend(minirst.maketable(data, 1)) | |
66 |
|
67 | |||
67 | return ''.join(rst) |
|
68 | return ''.join(rst) | |
68 |
|
69 | |||
@@ -128,17 +129,7 b' def loaddoc(topic):' | |||||
128 | """Return a delayed loader for help/topic.txt.""" |
|
129 | """Return a delayed loader for help/topic.txt.""" | |
129 |
|
130 | |||
130 | def loader(): |
|
131 | def loader(): | |
131 | if util.mainfrozen(): |
|
132 | docdir = os.path.join(util.datapath, 'help') | |
132 | module = sys.executable |
|
|||
133 | else: |
|
|||
134 | module = __file__ |
|
|||
135 | base = os.path.dirname(module) |
|
|||
136 |
|
||||
137 | for dir in ('.', '..'): |
|
|||
138 | docdir = os.path.join(base, dir, 'help') |
|
|||
139 | if os.path.isdir(docdir): |
|
|||
140 | break |
|
|||
141 |
|
||||
142 | path = os.path.join(docdir, topic + ".txt") |
|
133 | path = os.path.join(docdir, topic + ".txt") | |
143 | doc = gettext(util.readfile(path)) |
|
134 | doc = gettext(util.readfile(path)) | |
144 | for rewriter in helphooks.get(topic, []): |
|
135 | for rewriter in helphooks.get(topic, []): | |
@@ -235,11 +226,13 b' def help_(ui, name, unknowncmd=False, fu' | |||||
235 | rst = [] |
|
226 | rst = [] | |
236 |
|
227 | |||
237 | # check if it's an invalid alias and display its error if it is |
|
228 | # check if it's an invalid alias and display its error if it is | |
238 |
if getattr(entry[0], 'badalias', |
|
229 | if getattr(entry[0], 'badalias', None): | |
239 | if not unknowncmd: |
|
230 | rst.append(entry[0].badalias + '\n') | |
240 | ui.pushbuffer() |
|
231 | if entry[0].unknowncmd: | |
241 |
|
|
232 | try: | |
242 | rst.append(ui.popbuffer()) |
|
233 | rst.extend(helpextcmd(entry[0].cmdname)) | |
|
234 | except error.UnknownCommand: | |||
|
235 | pass | |||
243 | return rst |
|
236 | return rst | |
244 |
|
237 | |||
245 | # synopsis |
|
238 | # synopsis | |
@@ -277,31 +270,27 b' def help_(ui, name, unknowncmd=False, fu' | |||||
277 | mod = extensions.find(name) |
|
270 | mod = extensions.find(name) | |
278 | doc = gettext(mod.__doc__) or '' |
|
271 | doc = gettext(mod.__doc__) or '' | |
279 | if '\n' in doc.strip(): |
|
272 | if '\n' in doc.strip(): | |
280 | msg = _('use "hg help -e %s" to show help for ' |
|
273 | msg = _('(use "hg help -e %s" to show help for ' | |
281 | 'the %s extension') % (name, name) |
|
274 | 'the %s extension)') % (name, name) | |
282 | rst.append('\n%s\n' % msg) |
|
275 | rst.append('\n%s\n' % msg) | |
283 | except KeyError: |
|
276 | except KeyError: | |
284 | pass |
|
277 | pass | |
285 |
|
278 | |||
286 | # options |
|
279 | # options | |
287 | if not ui.quiet and entry[1]: |
|
280 | if not ui.quiet and entry[1]: | |
288 |
rst.append( |
|
281 | rst.append(optrst(_("options"), entry[1], ui.verbose)) | |
289 | rst.append(optrst(entry[1], ui.verbose)) |
|
|||
290 |
|
282 | |||
291 | if ui.verbose: |
|
283 | if ui.verbose: | |
292 |
rst.append( |
|
284 | rst.append(optrst(_("global options"), | |
293 |
|
|
285 | commands.globalopts, ui.verbose)) | |
294 |
|
286 | |||
295 | if not ui.verbose: |
|
287 | if not ui.verbose: | |
296 | if not full: |
|
288 | if not full: | |
297 |
rst.append(_('\nuse "hg |
|
289 | rst.append(_('\n(use "hg %s -h" to show more help)\n') | |
298 | % name) |
|
290 | % name) | |
299 | elif not ui.quiet: |
|
291 | elif not ui.quiet: | |
300 | omitted = _('use "hg -v help %s" to show more complete' |
|
292 | rst.append(_('\n(some details hidden, use --verbose ' | |
301 | ' help and the global options') % name |
|
293 | 'to show complete help)')) | |
302 | notomitted = _('use "hg -v help %s" to show' |
|
|||
303 | ' the global options') % name |
|
|||
304 | indicateomitted(rst, omitted, notomitted) |
|
|||
305 |
|
294 | |||
306 | return rst |
|
295 | return rst | |
307 |
|
296 | |||
@@ -367,30 +356,25 b' def help_(ui, name, unknowncmd=False, fu' | |||||
367 | for t, desc in topics: |
|
356 | for t, desc in topics: | |
368 | rst.append(" :%s: %s\n" % (t, desc)) |
|
357 | rst.append(" :%s: %s\n" % (t, desc)) | |
369 |
|
358 | |||
370 | optlist = [] |
|
359 | if ui.quiet: | |
371 | if not ui.quiet: |
|
360 | pass | |
372 |
|
|
361 | elif ui.verbose: | |
373 |
|
|
362 | rst.append('\n%s\n' % optrst(_("global options"), | |
374 | if name == 'shortlist': |
|
363 | commands.globalopts, ui.verbose)) | |
375 | optlist.append((_('use "hg help" for the full list ' |
|
364 | if name == 'shortlist': | |
376 | 'of commands'), ())) |
|
365 | rst.append(_('\n(use "hg help" for the full list ' | |
|
366 | 'of commands)\n')) | |||
|
367 | else: | |||
|
368 | if name == 'shortlist': | |||
|
369 | rst.append(_('\n(use "hg help" for the full list of commands ' | |||
|
370 | 'or "hg -v" for details)\n')) | |||
|
371 | elif name and not full: | |||
|
372 | rst.append(_('\n(use "hg help %s" to show the full help ' | |||
|
373 | 'text)\n') % name) | |||
377 | else: |
|
374 | else: | |
378 | if name == 'shortlist': |
|
375 | rst.append(_('\n(use "hg help -v%s" to show built-in aliases ' | |
379 | msg = _('use "hg help" for the full list of commands ' |
|
376 | 'and global options)\n') | |
380 |
|
|
377 | % (name and " " + name or "")) | |
381 | elif name and not full: |
|
|||
382 | msg = _('use "hg help %s" to show the full help ' |
|
|||
383 | 'text') % name |
|
|||
384 | else: |
|
|||
385 | msg = _('use "hg -v help%s" to show builtin aliases and ' |
|
|||
386 | 'global options') % (name and " " + name or "") |
|
|||
387 | optlist.append((msg, ())) |
|
|||
388 |
|
||||
389 | if optlist: |
|
|||
390 | for title, options in optlist: |
|
|||
391 | rst.append('\n%s\n' % title) |
|
|||
392 | if options: |
|
|||
393 | rst.append('\n%s\n' % optrst(options, ui.verbose)) |
|
|||
394 | return rst |
|
378 | return rst | |
395 |
|
379 | |||
396 | def helptopic(name): |
|
380 | def helptopic(name): | |
@@ -409,8 +393,8 b' def help_(ui, name, unknowncmd=False, fu' | |||||
409 | rst += [" %s\n" % l for l in doc().splitlines()] |
|
393 | rst += [" %s\n" % l for l in doc().splitlines()] | |
410 |
|
394 | |||
411 | if not ui.verbose: |
|
395 | if not ui.verbose: | |
412 | omitted = (_('use "hg help -v %s" to show more complete help') % |
|
396 | omitted = _('(some details hidden, use --verbose' | |
413 |
|
|
397 | ' to show complete help)') | |
414 | indicateomitted(rst, omitted) |
|
398 | indicateomitted(rst, omitted) | |
415 |
|
399 | |||
416 | try: |
|
400 | try: | |
@@ -441,8 +425,8 b' def help_(ui, name, unknowncmd=False, fu' | |||||
441 | rst.append('\n') |
|
425 | rst.append('\n') | |
442 |
|
426 | |||
443 | if not ui.verbose: |
|
427 | if not ui.verbose: | |
444 | omitted = (_('use "hg help -v %s" to show more complete help') % |
|
428 | omitted = _('(some details hidden, use --verbose' | |
445 |
|
|
429 | ' to show complete help)') | |
446 | indicateomitted(rst, omitted) |
|
430 | indicateomitted(rst, omitted) | |
447 |
|
431 | |||
448 | if mod: |
|
432 | if mod: | |
@@ -453,8 +437,8 b' def help_(ui, name, unknowncmd=False, fu' | |||||
453 | modcmds = set([c.split('|', 1)[0] for c in ct]) |
|
437 | modcmds = set([c.split('|', 1)[0] for c in ct]) | |
454 | rst.extend(helplist(modcmds.__contains__)) |
|
438 | rst.extend(helplist(modcmds.__contains__)) | |
455 | else: |
|
439 | else: | |
456 |
rst.append(_('use "hg help extensions" for information on enabling |
|
440 | rst.append(_('(use "hg help extensions" for information on enabling' | |
457 | 'extensions\n')) |
|
441 | ' extensions)\n')) | |
458 | return rst |
|
442 | return rst | |
459 |
|
443 | |||
460 | def helpextcmd(name): |
|
444 | def helpextcmd(name): | |
@@ -465,8 +449,8 b' def help_(ui, name, unknowncmd=False, fu' | |||||
465 | rst = listexts(_("'%s' is provided by the following " |
|
449 | rst = listexts(_("'%s' is provided by the following " | |
466 | "extension:") % cmd, {ext: doc}, indent=4) |
|
450 | "extension:") % cmd, {ext: doc}, indent=4) | |
467 | rst.append('\n') |
|
451 | rst.append('\n') | |
468 | rst.append(_('use "hg help extensions" for information on enabling ' |
|
452 | rst.append(_('(use "hg help extensions" for information on enabling ' | |
469 | 'extensions\n')) |
|
453 | 'extensions)\n')) | |
470 | return rst |
|
454 | return rst | |
471 |
|
455 | |||
472 |
|
456 |
@@ -28,68 +28,80 b' alphabetical order, later ones overridin' | |||||
28 | paths are given below, settings from earlier paths override later |
|
28 | paths are given below, settings from earlier paths override later | |
29 | ones. |
|
29 | ones. | |
30 |
|
30 | |||
31 | | (All) ``<repo>/.hg/hgrc`` |
|
31 | .. container:: verbose.unix | |
32 |
|
32 | |||
33 | Per-repository configuration options that only apply in a |
|
33 | On Unix, the following files are consulted: | |
34 | particular repository. This file is not version-controlled, and |
|
|||
35 | will not get transferred during a "clone" operation. Options in |
|
|||
36 | this file override options in all other configuration files. On |
|
|||
37 | Plan 9 and Unix, most of this file will be ignored if it doesn't |
|
|||
38 | belong to a trusted user or to a trusted group. See the documentation |
|
|||
39 | for the ``[trusted]`` section below for more details. |
|
|||
40 |
|
34 | |||
41 | | (Plan 9) ``$home/lib/hgrc`` |
|
35 | - ``<repo>/.hg/hgrc`` (per-repository) | |
42 |
|
|
36 | - ``$HOME/.hgrc`` (per-user) | |
43 | | (Windows) ``%USERPROFILE%\.hgrc`` |
|
37 | - ``<install-root>/etc/mercurial/hgrc`` (per-installation) | |
44 | | (Windows) ``%USERPROFILE%\Mercurial.ini`` |
|
38 | - ``<install-root>/etc/mercurial/hgrc.d/*.rc`` (per-installation) | |
45 | | (Windows) ``%HOME%\.hgrc`` |
|
39 | - ``/etc/mercurial/hgrc`` (per-system) | |
46 | | (Windows) ``%HOME%\Mercurial.ini`` |
|
40 | - ``/etc/mercurial/hgrc.d/*.rc`` (per-system) | |
47 |
|
41 | |||
48 | Per-user configuration file(s), for the user running Mercurial. On |
|
42 | .. container:: verbose.windows | |
49 | Windows 9x, ``%HOME%`` is replaced by ``%APPDATA%``. Options in these |
|
|||
50 | files apply to all Mercurial commands executed by this user in any |
|
|||
51 | directory. Options in these files override per-system and per-installation |
|
|||
52 | options. |
|
|||
53 |
|
||||
54 | | (Plan 9) ``/lib/mercurial/hgrc`` |
|
|||
55 | | (Plan 9) ``/lib/mercurial/hgrc.d/*.rc`` |
|
|||
56 | | (Unix) ``/etc/mercurial/hgrc`` |
|
|||
57 | | (Unix) ``/etc/mercurial/hgrc.d/*.rc`` |
|
|||
58 |
|
43 | |||
59 | Per-system configuration files, for the system on which Mercurial |
|
44 | On Windows, the following files are consulted: | |
60 | is running. Options in these files apply to all Mercurial commands |
|
|||
61 | executed by any user in any directory. Options in these files |
|
|||
62 | override per-installation options. |
|
|||
63 |
|
||||
64 | | (Plan 9) ``<install-root>/lib/mercurial/hgrc`` |
|
|||
65 | | (Plan 9) ``<install-root>/lib/mercurial/hgrc.d/*.rc`` |
|
|||
66 | | (Unix) ``<install-root>/etc/mercurial/hgrc`` |
|
|||
67 | | (Unix) ``<install-root>/etc/mercurial/hgrc.d/*.rc`` |
|
|||
68 |
|
45 | |||
69 | Per-installation configuration files, searched for in the |
|
46 | - ``<repo>/.hg/hgrc`` (per-repository) | |
70 | directory where Mercurial is installed. ``<install-root>`` is the |
|
47 | - ``%USERPROFILE%\.hgrc`` (per-user) | |
71 | parent directory of the **hg** executable (or symlink) being run. For |
|
48 | - ``%USERPROFILE%\Mercurial.ini`` (per-user) | |
72 | example, if installed in ``/shared/tools/bin/hg``, Mercurial will look |
|
49 | - ``%HOME%\.hgrc`` (per-user) | |
73 | in ``/shared/tools/etc/mercurial/hgrc``. Options in these files apply |
|
50 | - ``%HOME%\Mercurial.ini`` (per-user) | |
74 | to all Mercurial commands executed by any user in any directory. |
|
51 | - ``<install-dir>\Mercurial.ini`` (per-installation) | |
|
52 | - ``<install-dir>\hgrc.d\*.rc`` (per-installation) | |||
|
53 | - ``HKEY_LOCAL_MACHINE\SOFTWARE\Mercurial`` (per-installation) | |||
75 |
|
54 | |||
76 | | (Windows) ``<install-dir>\Mercurial.ini`` **or** |
|
55 | .. note:: | |
77 | | (Windows) ``<install-dir>\hgrc.d\*.rc`` **or** |
|
|||
78 | | (Windows) ``HKEY_LOCAL_MACHINE\SOFTWARE\Mercurial`` |
|
|||
79 |
|
||||
80 | Per-installation/system configuration files, for the system on |
|
|||
81 | which Mercurial is running. Options in these files apply to all |
|
|||
82 | Mercurial commands executed by any user in any directory. Registry |
|
|||
83 | keys contain PATH-like strings, every part of which must reference |
|
|||
84 | a ``Mercurial.ini`` file or be a directory where ``*.rc`` files will |
|
|||
85 | be read. Mercurial checks each of these locations in the specified |
|
|||
86 | order until one or more configuration files are detected. |
|
|||
87 |
|
||||
88 | .. note:: |
|
|||
89 |
|
56 | |||
90 | The registry key ``HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Mercurial`` |
|
57 | The registry key ``HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Mercurial`` | |
91 | is used when running 32-bit Python on 64-bit Windows. |
|
58 | is used when running 32-bit Python on 64-bit Windows. | |
92 |
|
59 | |||
|
60 | .. container:: verbose.plan9 | |||
|
61 | ||||
|
62 | On Plan9, the following files are consulted: | |||
|
63 | ||||
|
64 | - ``<repo>/.hg/hgrc`` (per-repository) | |||
|
65 | - ``$home/lib/hgrc`` (per-user) | |||
|
66 | - ``<install-root>/lib/mercurial/hgrc`` (per-installation) | |||
|
67 | - ``<install-root>/lib/mercurial/hgrc.d/*.rc`` (per-installation) | |||
|
68 | - ``/lib/mercurial/hgrc`` (per-system) | |||
|
69 | - ``/lib/mercurial/hgrc.d/*.rc`` (per-system) | |||
|
70 | ||||
|
71 | Per-repository configuration options only apply in a | |||
|
72 | particular repository. This file is not version-controlled, and | |||
|
73 | will not get transferred during a "clone" operation. Options in | |||
|
74 | this file override options in all other configuration files. On | |||
|
75 | Plan 9 and Unix, most of this file will be ignored if it doesn't | |||
|
76 | belong to a trusted user or to a trusted group. See the documentation | |||
|
77 | for the ``[trusted]`` section below for more details. | |||
|
78 | ||||
|
79 | Per-user configuration file(s) are for the user running Mercurial. On | |||
|
80 | Windows 9x, ``%HOME%`` is replaced by ``%APPDATA%``. Options in these | |||
|
81 | files apply to all Mercurial commands executed by this user in any | |||
|
82 | directory. Options in these files override per-system and per-installation | |||
|
83 | options. | |||
|
84 | ||||
|
85 | Per-installation configuration files are searched for in the | |||
|
86 | directory where Mercurial is installed. ``<install-root>`` is the | |||
|
87 | parent directory of the **hg** executable (or symlink) being run. For | |||
|
88 | example, if installed in ``/shared/tools/bin/hg``, Mercurial will look | |||
|
89 | in ``/shared/tools/etc/mercurial/hgrc``. Options in these files apply | |||
|
90 | to all Mercurial commands executed by any user in any directory. | |||
|
91 | ||||
|
92 | Per-installation configuration files are for the system on | |||
|
93 | which Mercurial is running. Options in these files apply to all | |||
|
94 | Mercurial commands executed by any user in any directory. Registry | |||
|
95 | keys contain PATH-like strings, every part of which must reference | |||
|
96 | a ``Mercurial.ini`` file or be a directory where ``*.rc`` files will | |||
|
97 | be read. Mercurial checks each of these locations in the specified | |||
|
98 | order until one or more configuration files are detected. | |||
|
99 | ||||
|
100 | Per-system configuration files are for the system on which Mercurial | |||
|
101 | is running. Options in these files apply to all Mercurial commands | |||
|
102 | executed by any user in any directory. Options in these files | |||
|
103 | override per-installation options. | |||
|
104 | ||||
93 | Syntax |
|
105 | Syntax | |
94 | ====== |
|
106 | ====== | |
95 |
|
107 | |||
@@ -229,8 +241,9 b' repository in the same manner as the pur' | |||||
229 | Positional arguments like ``$1``, ``$2``, etc. in the alias definition |
|
241 | Positional arguments like ``$1``, ``$2``, etc. in the alias definition | |
230 | expand to the command arguments. Unmatched arguments are |
|
242 | expand to the command arguments. Unmatched arguments are | |
231 | removed. ``$0`` expands to the alias name and ``$@`` expands to all |
|
243 | removed. ``$0`` expands to the alias name and ``$@`` expands to all | |
232 |
arguments separated by a space. |
|
244 | arguments separated by a space. ``"$@"`` (with quotes) expands to all | |
233 | command is passed to the shell. |
|
245 | arguments quoted individually and separated by a space. These expansions | |
|
246 | happen before the command is passed to the shell. | |||
234 |
|
247 | |||
235 | Shell aliases are executed in an environment where ``$HG`` expands to |
|
248 | Shell aliases are executed in an environment where ``$HG`` expands to | |
236 | the path of the Mercurial that was used to execute the alias. This is |
|
249 | the path of the Mercurial that was used to execute the alias. This is | |
@@ -388,6 +401,57 b' required):' | |||||
388 | - :hg:`tag` |
|
401 | - :hg:`tag` | |
389 | - :hg:`transplant` |
|
402 | - :hg:`transplant` | |
390 |
|
403 | |||
|
404 | Configuring items below instead of ``changeset`` allows showing | |||
|
405 | customized message only for specific actions, or showing different | |||
|
406 | messages for each actions. | |||
|
407 | ||||
|
408 | - ``changeset.backout`` for :hg:`backout` | |||
|
409 | - ``changeset.commit.amend.merge`` for :hg:`commit --amend` on merges | |||
|
410 | - ``changeset.commit.amend.normal`` for :hg:`commit --amend` on other | |||
|
411 | - ``changeset.commit.normal.merge`` for :hg:`commit` on merges | |||
|
412 | - ``changeset.commit.normal.normal`` for :hg:`commit` on other | |||
|
413 | - ``changeset.fetch`` for :hg:`fetch` (impling merge commit) | |||
|
414 | - ``changeset.gpg.sign`` for :hg:`sign` | |||
|
415 | - ``changeset.graft`` for :hg:`graft` | |||
|
416 | - ``changeset.histedit.edit`` for ``edit`` of :hg:`histedit` | |||
|
417 | - ``changeset.histedit.fold`` for ``fold`` of :hg:`histedit` | |||
|
418 | - ``changeset.histedit.mess`` for ``mess`` of :hg:`histedit` | |||
|
419 | - ``changeset.histedit.pick`` for ``pick`` of :hg:`histedit` | |||
|
420 | - ``changeset.import.bypass`` for :hg:`import --bypass` | |||
|
421 | - ``changeset.import.normal.merge`` for :hg:`import` on merges | |||
|
422 | - ``changeset.import.normal.normal`` for :hg:`import` on other | |||
|
423 | - ``changeset.mq.qnew`` for :hg:`qnew` | |||
|
424 | - ``changeset.mq.qfold`` for :hg:`qfold` | |||
|
425 | - ``changeset.mq.qrefresh`` for :hg:`qrefresh` | |||
|
426 | - ``changeset.rebase.collapse`` for :hg:`rebase --collapse` | |||
|
427 | - ``changeset.rebase.merge`` for :hg:`rebase` on merges | |||
|
428 | - ``changeset.rebase.normal`` for :hg:`rebase` on other | |||
|
429 | - ``changeset.shelve.shelve`` for :hg:`shelve` | |||
|
430 | - ``changeset.tag.add`` for :hg:`tag` without ``--remove`` | |||
|
431 | - ``changeset.tag.remove`` for :hg:`tag --remove` | |||
|
432 | - ``changeset.transplant.merge`` for :hg:`transplant` on merges | |||
|
433 | - ``changeset.transplant.normal`` for :hg:`transplant` on other | |||
|
434 | ||||
|
435 | These dot-separated lists of names are treated as hierarchical ones. | |||
|
436 | For example, ``changeset.tag.remove`` customizes the commit message | |||
|
437 | only for :hg:`tag --remove`, but ``changeset.tag`` customizes the | |||
|
438 | commit message for :hg:`tag` regardless of ``--remove`` option. | |||
|
439 | ||||
|
440 | At the external editor invocation for committing, corresponding | |||
|
441 | dot-separated list of names without ``changeset.`` prefix | |||
|
442 | (e.g. ``commit.normal.normal``) is in ``HGEDITFORM`` environment variable. | |||
|
443 | ||||
|
444 | In this section, items other than ``changeset`` can be referred from | |||
|
445 | others. For example, the configuration to list committed files up | |||
|
446 | below can be referred as ``{listupfiles}``:: | |||
|
447 | ||||
|
448 | [committemplate] | |||
|
449 | listupfiles = {file_adds % | |||
|
450 | "HG: added {file}\n" }{file_mods % | |||
|
451 | "HG: changed {file}\n" }{file_dels % | |||
|
452 | "HG: removed {file}\n" }{if(files, "", | |||
|
453 | "HG: no files changed\n")} | |||
|
454 | ||||
391 | ``decode/encode`` |
|
455 | ``decode/encode`` | |
392 | ----------------- |
|
456 | ----------------- | |
393 |
|
457 | |||
@@ -915,8 +979,10 b' Supported arguments:' | |||||
915 |
|
979 | |||
916 | ``premerge`` |
|
980 | ``premerge`` | |
917 | Attempt to run internal non-interactive 3-way merge tool before |
|
981 | Attempt to run internal non-interactive 3-way merge tool before | |
918 |
launching external tool. Options are ``true``, ``false``, |
|
982 | launching external tool. Options are ``true``, ``false``, ``keep`` or | |
919 | to leave markers in the file if the premerge fails. |
|
983 | ``keep-merge3``. The ``keep`` option will leave markers in the file if the | |
|
984 | premerge fails. The ``keep-merge3`` will do the same but include information | |||
|
985 | about the base of the merge in the marker (see internal:merge3). | |||
920 | Default: True |
|
986 | Default: True | |
921 |
|
987 | |||
922 | ``binary`` |
|
988 | ``binary`` | |
@@ -1589,10 +1655,13 b' The full set of options is:' | |||||
1589 | Default is 1; set to 0 to disable. |
|
1655 | Default is 1; set to 0 to disable. | |
1590 |
|
1656 | |||
1591 | ``style`` |
|
1657 | ``style`` | |
1592 | Which template map style to use. |
|
1658 | Which template map style to use. The available options are the names of | |
|
1659 | subdirectories in the HTML templates path. Default is ``paper``. | |||
|
1660 | Example: ``monoblue`` | |||
1593 |
|
1661 | |||
1594 | ``templates`` |
|
1662 | ``templates`` | |
1595 |
Where to find the HTML templates. |
|
1663 | Where to find the HTML templates. The default path to the HTML templates | |
|
1664 | can be obtained from ``hg debuginstall``. | |||
1596 |
|
1665 | |||
1597 | ``websub`` |
|
1666 | ``websub`` | |
1598 | ---------- |
|
1667 | ---------- |
@@ -177,6 +177,9 b' DAG' | |||||
177 | Mercurial, the DAG is limited by the requirement for children to |
|
177 | Mercurial, the DAG is limited by the requirement for children to | |
178 | have at most two parents. |
|
178 | have at most two parents. | |
179 |
|
179 | |||
|
180 | Deprecated | |||
|
181 | Feature removed from documentation, but not scheduled for removal. | |||
|
182 | ||||
180 | Default branch |
|
183 | Default branch | |
181 | See 'Branch, default'. |
|
184 | See 'Branch, default'. | |
182 |
|
185 | |||
@@ -217,6 +220,9 b' Draft' | |||||
217 | repositories and may thus be safely changed by history-modifying |
|
220 | repositories and may thus be safely changed by history-modifying | |
218 | extensions. See :hg:`help phases`. |
|
221 | extensions. See :hg:`help phases`. | |
219 |
|
222 | |||
|
223 | Experimental | |||
|
224 | Feature that may change or be removed at a later date. | |||
|
225 | ||||
220 | Graph |
|
226 | Graph | |
221 | See DAG and :hg:`log --graph`. |
|
227 | See DAG and :hg:`log --graph`. | |
222 |
|
228 |
@@ -68,7 +68,7 b' 6. If a program named ``hgmerge`` can be' | |||||
68 | it will by default not be used for symlinks and binary files. |
|
68 | it will by default not be used for symlinks and binary files. | |
69 |
|
69 | |||
70 | 7. If the file to be merged is not binary and is not a symlink, then |
|
70 | 7. If the file to be merged is not binary and is not a symlink, then | |
71 |
|
|
71 | internal ``:merge`` is used. | |
72 |
|
72 | |||
73 | 8. The merge of the file fails and must be resolved before commit. |
|
73 | 8. The merge of the file fails and must be resolved before commit. | |
74 |
|
74 |
@@ -43,6 +43,8 b' In addition to filters, there are some b' | |||||
43 |
|
43 | |||
44 | - date(date[, fmt]) |
|
44 | - date(date[, fmt]) | |
45 |
|
45 | |||
|
46 | - diff([includepattern [, excludepattern]]) | |||
|
47 | ||||
46 | - fill(text[, width]) |
|
48 | - fill(text[, width]) | |
47 |
|
49 | |||
48 | - get(dict, key) |
|
50 | - get(dict, key) |
@@ -8,10 +8,12 b'' | |||||
8 |
|
8 | |||
9 | from i18n import _ |
|
9 | from i18n import _ | |
10 | from lock import release |
|
10 | from lock import release | |
11 |
from node import |
|
11 | from node import nullid | |
|
12 | ||||
12 | import localrepo, bundlerepo, unionrepo, httppeer, sshpeer, statichttprepo |
|
13 | import localrepo, bundlerepo, unionrepo, httppeer, sshpeer, statichttprepo | |
13 | import bookmarks, lock, util, extensions, error, node, scmutil, phases, url |
|
14 | import bookmarks, lock, util, extensions, error, node, scmutil, phases, url | |
14 | import cmdutil, discovery |
|
15 | import cmdutil, discovery, repoview, exchange | |
|
16 | import ui as uimod | |||
15 | import merge as mergemod |
|
17 | import merge as mergemod | |
16 | import verify as verifymod |
|
18 | import verify as verifymod | |
17 | import errno, os, shutil |
|
19 | import errno, os, shutil | |
@@ -24,7 +26,14 b' def addbranchrevs(lrepo, other, branches' | |||||
24 | peer = other.peer() # a courtesy to callers using a localrepo for other |
|
26 | peer = other.peer() # a courtesy to callers using a localrepo for other | |
25 | hashbranch, branches = branches |
|
27 | hashbranch, branches = branches | |
26 | if not hashbranch and not branches: |
|
28 | if not hashbranch and not branches: | |
27 | return revs or None, revs and revs[0] or None |
|
29 | x = revs or None | |
|
30 | if util.safehasattr(revs, 'first'): | |||
|
31 | y = revs.first() | |||
|
32 | elif revs: | |||
|
33 | y = revs[0] | |||
|
34 | else: | |||
|
35 | y = None | |||
|
36 | return x, y | |||
28 | revs = revs and list(revs) or [] |
|
37 | revs = revs and list(revs) or [] | |
29 | if not peer.capable('branchmap'): |
|
38 | if not peer.capable('branchmap'): | |
30 | if branches: |
|
39 | if branches: | |
@@ -363,16 +372,28 b' def clone(ui, peeropts, source, dest=Non' | |||||
363 | raise |
|
372 | raise | |
364 |
|
373 | |||
365 | destlock = copystore(ui, srcrepo, destpath) |
|
374 | destlock = copystore(ui, srcrepo, destpath) | |
|
375 | # copy bookmarks over | |||
|
376 | srcbookmarks = srcrepo.join('bookmarks') | |||
|
377 | dstbookmarks = os.path.join(destpath, 'bookmarks') | |||
|
378 | if os.path.exists(srcbookmarks): | |||
|
379 | util.copyfile(srcbookmarks, dstbookmarks) | |||
366 |
|
380 | |||
367 | # Recomputing branch cache might be slow on big repos, |
|
381 | # Recomputing branch cache might be slow on big repos, | |
368 | # so just copy it |
|
382 | # so just copy it | |
|
383 | def copybranchcache(fname): | |||
|
384 | srcbranchcache = srcrepo.join('cache/%s' % fname) | |||
|
385 | dstbranchcache = os.path.join(dstcachedir, fname) | |||
|
386 | if os.path.exists(srcbranchcache): | |||
|
387 | if not os.path.exists(dstcachedir): | |||
|
388 | os.mkdir(dstcachedir) | |||
|
389 | util.copyfile(srcbranchcache, dstbranchcache) | |||
|
390 | ||||
369 | dstcachedir = os.path.join(destpath, 'cache') |
|
391 | dstcachedir = os.path.join(destpath, 'cache') | |
370 | srcbranchcache = srcrepo.sjoin('cache/branch2') |
|
392 | # In local clones we're copying all nodes, not just served | |
371 | dstbranchcache = os.path.join(dstcachedir, 'branch2') |
|
393 | # ones. Therefore copy all branchcaches over. | |
372 |
|
|
394 | copybranchcache('branch2') | |
373 | if not os.path.exists(dstcachedir): |
|
395 | for cachename in repoview.filtertable: | |
374 | os.mkdir(dstcachedir) |
|
396 | copybranchcache('branch2-%s' % cachename) | |
375 | util.copyfile(srcbranchcache, dstbranchcache) |
|
|||
376 |
|
397 | |||
377 | # we need to re-init the repo after manually copying the data |
|
398 | # we need to re-init the repo after manually copying the data | |
378 | # into it |
|
399 | # into it | |
@@ -401,36 +422,21 b' def clone(ui, peeropts, source, dest=Non' | |||||
401 | if destpeer.local(): |
|
422 | if destpeer.local(): | |
402 | destpeer.local().clone(srcpeer, heads=revs, stream=stream) |
|
423 | destpeer.local().clone(srcpeer, heads=revs, stream=stream) | |
403 | elif srcrepo: |
|
424 | elif srcrepo: | |
404 |
|
|
425 | exchange.push(srcrepo, destpeer, revs=revs, | |
|
426 | bookmarks=srcrepo._bookmarks.keys()) | |||
405 | else: |
|
427 | else: | |
406 | raise util.Abort(_("clone from remote to remote not supported")) |
|
428 | raise util.Abort(_("clone from remote to remote not supported")) | |
407 |
|
429 | |||
408 | cleandir = None |
|
430 | cleandir = None | |
409 |
|
431 | |||
410 | # clone all bookmarks except divergent ones |
|
|||
411 | destrepo = destpeer.local() |
|
432 | destrepo = destpeer.local() | |
412 | if destrepo and srcpeer.capable("pushkey"): |
|
|||
413 | rb = srcpeer.listkeys('bookmarks') |
|
|||
414 | marks = destrepo._bookmarks |
|
|||
415 | for k, n in rb.iteritems(): |
|
|||
416 | try: |
|
|||
417 | m = destrepo.lookup(n) |
|
|||
418 | marks[k] = m |
|
|||
419 | except error.RepoLookupError: |
|
|||
420 | pass |
|
|||
421 | if rb: |
|
|||
422 | marks.write() |
|
|||
423 | elif srcrepo and destpeer.capable("pushkey"): |
|
|||
424 | for k, n in srcrepo._bookmarks.iteritems(): |
|
|||
425 | destpeer.pushkey('bookmarks', k, '', hex(n)) |
|
|||
426 |
|
||||
427 | if destrepo: |
|
433 | if destrepo: | |
|
434 | template = uimod.samplehgrcs['cloned'] | |||
428 | fp = destrepo.opener("hgrc", "w", text=True) |
|
435 | fp = destrepo.opener("hgrc", "w", text=True) | |
429 | fp.write("[paths]\n") |
|
|||
430 | u = util.url(abspath) |
|
436 | u = util.url(abspath) | |
431 | u.passwd = None |
|
437 | u.passwd = None | |
432 | defaulturl = str(u) |
|
438 | defaulturl = str(u) | |
433 |
fp.write( |
|
439 | fp.write(template % defaulturl) | |
434 | fp.close() |
|
440 | fp.close() | |
435 |
|
441 | |||
436 | destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone') |
|
442 | destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone') |
@@ -113,7 +113,7 b' class hgweb(object):' | |||||
113 | # we need to compare file size in addition to mtime to catch |
|
113 | # we need to compare file size in addition to mtime to catch | |
114 | # changes made less than a second ago |
|
114 | # changes made less than a second ago | |
115 | if repostate != self.repostate: |
|
115 | if repostate != self.repostate: | |
116 |
r = hg.repository(self.repo.baseui, self.repo. |
|
116 | r = hg.repository(self.repo.baseui, self.repo.url()) | |
117 | self.repo = self._getview(r) |
|
117 | self.repo = self._getview(r) | |
118 | self.maxchanges = int(self.config("web", "maxchanges", 10)) |
|
118 | self.maxchanges = int(self.config("web", "maxchanges", 10)) | |
119 | self.stripecount = int(self.config("web", "stripes", 1)) |
|
119 | self.stripecount = int(self.config("web", "stripes", 1)) | |
@@ -394,5 +394,5 b' class hgweb(object):' | |||||
394 | } |
|
394 | } | |
395 |
|
395 | |||
396 | def check_perm(self, req, op): |
|
396 | def check_perm(self, req, op): | |
397 | for hook in permhooks: |
|
397 | for permhook in permhooks: | |
398 | hook(self, req, op) |
|
398 | permhook(self, req, op) |
@@ -193,7 +193,7 b' class hgwebdir(object):' | |||||
193 | static = self.ui.config("web", "static", None, |
|
193 | static = self.ui.config("web", "static", None, | |
194 | untrusted=False) |
|
194 | untrusted=False) | |
195 | if not static: |
|
195 | if not static: | |
196 | tp = self.templatepath or templater.templatepath() |
|
196 | tp = self.templatepath or templater.templatepaths() | |
197 | if isinstance(tp, str): |
|
197 | if isinstance(tp, str): | |
198 | tp = [tp] |
|
198 | tp = [tp] | |
199 | static = [os.path.join(p, 'static') for p in tp] |
|
199 | static = [os.path.join(p, 'static') for p in tp] |
@@ -933,7 +933,7 b' def static(web, req, tmpl):' | |||||
933 | # readable by the user running the CGI script |
|
933 | # readable by the user running the CGI script | |
934 | static = web.config("web", "static", None, untrusted=False) |
|
934 | static = web.config("web", "static", None, untrusted=False) | |
935 | if not static: |
|
935 | if not static: | |
936 | tp = web.templatepath or templater.templatepath() |
|
936 | tp = web.templatepath or templater.templatepaths() | |
937 | if isinstance(tp, str): |
|
937 | if isinstance(tp, str): | |
938 | tp = [tp] |
|
938 | tp = [tp] | |
939 | static = [os.path.join(p, 'static') for p in tp] |
|
939 | static = [os.path.join(p, 'static') for p in tp] | |
@@ -1069,7 +1069,7 b' def help(web, req, tmpl):' | |||||
1069 | topicname = req.form.get('node', [None])[0] |
|
1069 | topicname = req.form.get('node', [None])[0] | |
1070 | if not topicname: |
|
1070 | if not topicname: | |
1071 | def topics(**map): |
|
1071 | def topics(**map): | |
1072 | for entries, summary, _ in helpmod.helptable: |
|
1072 | for entries, summary, _doc in helpmod.helptable: | |
1073 | yield {'topic': entries[0], 'summary': summary} |
|
1073 | yield {'topic': entries[0], 'summary': summary} | |
1074 |
|
1074 | |||
1075 | early, other = [], [] |
|
1075 | early, other = [], [] |
@@ -6,7 +6,7 b'' | |||||
6 | # GNU General Public License version 2 or any later version. |
|
6 | # GNU General Public License version 2 or any later version. | |
7 |
|
7 | |||
8 | import encoding |
|
8 | import encoding | |
9 | import gettext, sys, os |
|
9 | import gettext as gettextmod, sys, os, locale | |
10 |
|
10 | |||
11 | # modelled after templater.templatepath: |
|
11 | # modelled after templater.templatepath: | |
12 | if getattr(sys, 'frozen', None) is not None: |
|
12 | if getattr(sys, 'frozen', None) is not None: | |
@@ -14,13 +14,34 b" if getattr(sys, 'frozen', None) is not N" | |||||
14 | else: |
|
14 | else: | |
15 | module = __file__ |
|
15 | module = __file__ | |
16 |
|
16 | |||
17 | base = os.path.dirname(module) |
|
|||
18 | for dir in ('.', '..'): |
|
|||
19 | localedir = os.path.join(base, dir, 'locale') |
|
|||
20 | if os.path.isdir(localedir): |
|
|||
21 | break |
|
|||
22 |
|
17 | |||
23 | t = gettext.translation('hg', localedir, fallback=True) |
|
18 | _languages = None | |
|
19 | if (os.name == 'nt' | |||
|
20 | and 'LANGUAGE' not in os.environ | |||
|
21 | and 'LC_ALL' not in os.environ | |||
|
22 | and 'LC_MESSAGES' not in os.environ | |||
|
23 | and 'LANG' not in os.environ): | |||
|
24 | # Try to detect UI language by "User Interface Language Management" API | |||
|
25 | # if no locale variables are set. Note that locale.getdefaultlocale() | |||
|
26 | # uses GetLocaleInfo(), which may be different from UI language. | |||
|
27 | # (See http://msdn.microsoft.com/en-us/library/dd374098(v=VS.85).aspx ) | |||
|
28 | try: | |||
|
29 | import ctypes | |||
|
30 | langid = ctypes.windll.kernel32.GetUserDefaultUILanguage() | |||
|
31 | _languages = [locale.windows_locale[langid]] | |||
|
32 | except (ImportError, AttributeError, KeyError): | |||
|
33 | # ctypes not found or unknown langid | |||
|
34 | pass | |||
|
35 | ||||
|
36 | _ugettext = None | |||
|
37 | ||||
|
38 | def setdatapath(datapath): | |||
|
39 | localedir = os.path.join(datapath, 'locale') | |||
|
40 | t = gettextmod.translation('hg', localedir, _languages, fallback=True) | |||
|
41 | global _ugettext | |||
|
42 | _ugettext = t.ugettext | |||
|
43 | ||||
|
44 | _msgcache = {} | |||
24 |
|
45 | |||
25 | def gettext(message): |
|
46 | def gettext(message): | |
26 | """Translate message. |
|
47 | """Translate message. | |
@@ -33,27 +54,29 b' def gettext(message):' | |||||
33 | """ |
|
54 | """ | |
34 | # If message is None, t.ugettext will return u'None' as the |
|
55 | # If message is None, t.ugettext will return u'None' as the | |
35 | # translation whereas our callers expect us to return None. |
|
56 | # translation whereas our callers expect us to return None. | |
36 | if message is None: |
|
57 | if message is None or not _ugettext: | |
37 | return message |
|
58 | return message | |
38 |
|
59 | |||
39 |
if |
|
60 | if message not in _msgcache: | |
40 | # goofy unicode docstrings in test |
|
61 | if type(message) is unicode: | |
41 | paragraphs = message.split(u'\n\n') |
|
62 | # goofy unicode docstrings in test | |
42 | else: |
|
63 | paragraphs = message.split(u'\n\n') | |
43 | paragraphs = [p.decode("ascii") for p in message.split('\n\n')] |
|
64 | else: | |
44 | # Be careful not to translate the empty string -- it holds the |
|
65 | paragraphs = [p.decode("ascii") for p in message.split('\n\n')] | |
45 | # meta data of the .po file. |
|
66 | # Be careful not to translate the empty string -- it holds the | |
46 | u = u'\n\n'.join([p and t.ugettext(p) or '' for p in paragraphs]) |
|
67 | # meta data of the .po file. | |
47 | try: |
|
68 | u = u'\n\n'.join([p and _ugettext(p) or '' for p in paragraphs]) | |
48 | # encoding.tolocal cannot be used since it will first try to |
|
69 | try: | |
49 | # decode the Unicode string. Calling u.decode(enc) really |
|
70 | # encoding.tolocal cannot be used since it will first try to | |
50 | # means u.encode(sys.getdefaultencoding()).decode(enc). Since |
|
71 | # decode the Unicode string. Calling u.decode(enc) really | |
51 | # the Python encoding defaults to 'ascii', this fails if the |
|
72 | # means u.encode(sys.getdefaultencoding()).decode(enc). Since | |
52 | # translated string use non-ASCII characters. |
|
73 | # the Python encoding defaults to 'ascii', this fails if the | |
53 | return u.encode(encoding.encoding, "replace") |
|
74 | # translated string use non-ASCII characters. | |
54 | except LookupError: |
|
75 | _msgcache[message] = u.encode(encoding.encoding, "replace") | |
55 | # An unknown encoding results in a LookupError. |
|
76 | except LookupError: | |
56 | return message |
|
77 | # An unknown encoding results in a LookupError. | |
|
78 | _msgcache[message] = message | |||
|
79 | return _msgcache[message] | |||
57 |
|
80 | |||
58 | def _plain(): |
|
81 | def _plain(): | |
59 | if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ: |
|
82 | if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ: |
@@ -19,8 +19,6 b'' | |||||
19 | # - fix for digest auth (inspired from urllib2.py @ Python v2.4) |
|
19 | # - fix for digest auth (inspired from urllib2.py @ Python v2.4) | |
20 | # Modified by Dirkjan Ochtman: |
|
20 | # Modified by Dirkjan Ochtman: | |
21 | # - import md5 function from a local util module |
|
21 | # - import md5 function from a local util module | |
22 | # Modified by Martin Geisler: |
|
|||
23 | # - moved md5 function from local util module to this module |
|
|||
24 | # Modified by Augie Fackler: |
|
22 | # Modified by Augie Fackler: | |
25 | # - add safesend method and use it to prevent broken pipe errors |
|
23 | # - add safesend method and use it to prevent broken pipe errors | |
26 | # on large POST requests |
|
24 | # on large POST requests | |
@@ -617,16 +615,8 b' def error_handler(url):' | |||||
617 | print "open connections:", hosts |
|
615 | print "open connections:", hosts | |
618 | keepalive_handler.close_all() |
|
616 | keepalive_handler.close_all() | |
619 |
|
617 | |||
620 | def md5(s): |
|
|||
621 | try: |
|
|||
622 | from hashlib import md5 as _md5 |
|
|||
623 | except ImportError: |
|
|||
624 | from md5 import md5 as _md5 |
|
|||
625 | global md5 |
|
|||
626 | md5 = _md5 |
|
|||
627 | return _md5(s) |
|
|||
628 |
|
||||
629 | def continuity(url): |
|
618 | def continuity(url): | |
|
619 | from util import md5 | |||
630 | format = '%25s: %s' |
|
620 | format = '%25s: %s' | |
631 |
|
621 | |||
632 | # first fetch the file with the normal http handler |
|
622 | # first fetch the file with the normal http handler |
@@ -109,7 +109,7 b' class localpeer(peer.peerrepository):' | |||||
109 | format='HG10', **kwargs): |
|
109 | format='HG10', **kwargs): | |
110 | cg = exchange.getbundle(self._repo, source, heads=heads, |
|
110 | cg = exchange.getbundle(self._repo, source, heads=heads, | |
111 | common=common, bundlecaps=bundlecaps, **kwargs) |
|
111 | common=common, bundlecaps=bundlecaps, **kwargs) | |
112 |
if bundlecaps is not None and 'HG2 |
|
112 | if bundlecaps is not None and 'HG2Y' in bundlecaps: | |
113 | # When requesting a bundle2, getbundle returns a stream to make the |
|
113 | # When requesting a bundle2, getbundle returns a stream to make the | |
114 | # wire level function happier. We need to build a proper object |
|
114 | # wire level function happier. We need to build a proper object | |
115 | # from it in local peer. |
|
115 | # from it in local peer. | |
@@ -180,10 +180,6 b' class localrepository(object):' | |||||
180 | requirements = ['revlogv1'] |
|
180 | requirements = ['revlogv1'] | |
181 | filtername = None |
|
181 | filtername = None | |
182 |
|
182 | |||
183 | bundle2caps = {'HG2X': (), |
|
|||
184 | 'b2x:listkeys': (), |
|
|||
185 | 'b2x:pushkey': ()} |
|
|||
186 |
|
||||
187 | # a list of (ui, featureset) functions. |
|
183 | # a list of (ui, featureset) functions. | |
188 | # only functions defined in module of enabled extensions are invoked |
|
184 | # only functions defined in module of enabled extensions are invoked | |
189 | featuresetupfuncs = set() |
|
185 | featuresetupfuncs = set() | |
@@ -309,7 +305,7 b' class localrepository(object):' | |||||
309 | # required by the tests (or some brave tester) |
|
305 | # required by the tests (or some brave tester) | |
310 | if self.ui.configbool('experimental', 'bundle2-exp', False): |
|
306 | if self.ui.configbool('experimental', 'bundle2-exp', False): | |
311 | caps = set(caps) |
|
307 | caps = set(caps) | |
312 |
capsblob = bundle2.encodecaps(self |
|
308 | capsblob = bundle2.encodecaps(bundle2.getrepocaps(self)) | |
313 | caps.add('bundle2-exp=' + urllib.quote(capsblob)) |
|
309 | caps.add('bundle2-exp=' + urllib.quote(capsblob)) | |
314 | return caps |
|
310 | return caps | |
315 |
|
311 | |||
@@ -404,8 +400,16 b' class localrepository(object):' | |||||
404 |
|
400 | |||
405 | @storecache('obsstore') |
|
401 | @storecache('obsstore') | |
406 | def obsstore(self): |
|
402 | def obsstore(self): | |
407 | store = obsolete.obsstore(self.sopener) |
|
403 | # read default format for new obsstore. | |
408 | if store and not obsolete._enabled: |
|
404 | defaultformat = self.ui.configint('format', 'obsstore-version', None) | |
|
405 | # rely on obsstore class default when possible. | |||
|
406 | kwargs = {} | |||
|
407 | if defaultformat is not None: | |||
|
408 | kwargs['defaultformat'] = defaultformat | |||
|
409 | readonly = not obsolete.isenabled(self, obsolete.createmarkersopt) | |||
|
410 | store = obsolete.obsstore(self.sopener, readonly=readonly, | |||
|
411 | **kwargs) | |||
|
412 | if store and readonly: | |||
409 | # message is rare enough to not be translated |
|
413 | # message is rare enough to not be translated | |
410 | msg = 'obsolete feature not enabled but %i markers found!\n' |
|
414 | msg = 'obsolete feature not enabled but %i markers found!\n' | |
411 | self.ui.warn(msg % len(list(store))) |
|
415 | self.ui.warn(msg % len(list(store))) | |
@@ -578,10 +582,10 b' class localrepository(object):' | |||||
578 | date: date tuple to use if committing''' |
|
582 | date: date tuple to use if committing''' | |
579 |
|
583 | |||
580 | if not local: |
|
584 | if not local: | |
581 | for x in self.status()[:5]: |
|
585 | m = matchmod.exact(self.root, '', ['.hgtags']) | |
582 | if '.hgtags' in x: |
|
586 | if util.any(self.status(match=m, unknown=True, ignored=True)): | |
583 |
|
|
587 | raise util.Abort(_('working copy of .hgtags is changed'), | |
584 |
|
|
588 | hint=_('please commit .hgtags manually')) | |
585 |
|
589 | |||
586 | self.tags() # instantiate the cache |
|
590 | self.tags() # instantiate the cache | |
587 | self._tag(names, node, message, local, user, date, editor=editor) |
|
591 | self._tag(names, node, message, local, user, date, editor=editor) | |
@@ -674,8 +678,7 b' class localrepository(object):' | |||||
674 | if not self._tagscache.tagslist: |
|
678 | if not self._tagscache.tagslist: | |
675 | l = [] |
|
679 | l = [] | |
676 | for t, n in self.tags().iteritems(): |
|
680 | for t, n in self.tags().iteritems(): | |
677 |
|
|
681 | l.append((self.changelog.rev(n), t, n)) | |
678 | l.append((r, t, n)) |
|
|||
679 | self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)] |
|
682 | self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)] | |
680 |
|
683 | |||
681 | return self._tagscache.tagslist |
|
684 | return self._tagscache.tagslist | |
@@ -744,11 +747,11 b' class localrepository(object):' | |||||
744 | # if publishing we can't copy if there is filtered content |
|
747 | # if publishing we can't copy if there is filtered content | |
745 | return not self.filtered('visible').changelog.filteredrevs |
|
748 | return not self.filtered('visible').changelog.filteredrevs | |
746 |
|
749 | |||
747 | def join(self, f): |
|
750 | def join(self, f, *insidef): | |
748 | return os.path.join(self.path, f) |
|
751 | return os.path.join(self.path, f, *insidef) | |
749 |
|
752 | |||
750 | def wjoin(self, f): |
|
753 | def wjoin(self, f, *insidef): | |
751 | return os.path.join(self.root, f) |
|
754 | return os.path.join(self.root, f, *insidef) | |
752 |
|
755 | |||
753 | def file(self, f): |
|
756 | def file(self, f): | |
754 | if f[0] == '/': |
|
757 | if f[0] == '/': | |
@@ -763,6 +766,7 b' class localrepository(object):' | |||||
763 | return self[changeid].parents() |
|
766 | return self[changeid].parents() | |
764 |
|
767 | |||
765 | def setparents(self, p1, p2=nullid): |
|
768 | def setparents(self, p1, p2=nullid): | |
|
769 | self.dirstate.beginparentchange() | |||
766 | copies = self.dirstate.setparents(p1, p2) |
|
770 | copies = self.dirstate.setparents(p1, p2) | |
767 | pctx = self[p1] |
|
771 | pctx = self[p1] | |
768 | if copies: |
|
772 | if copies: | |
@@ -776,6 +780,7 b' class localrepository(object):' | |||||
776 | for f, s in sorted(self.dirstate.copies().items()): |
|
780 | for f, s in sorted(self.dirstate.copies().items()): | |
777 | if f not in pctx and s not in pctx: |
|
781 | if f not in pctx and s not in pctx: | |
778 | self.dirstate.copy(None, f) |
|
782 | self.dirstate.copy(None, f) | |
|
783 | self.dirstate.endparentchange() | |||
779 |
|
784 | |||
780 | def filectx(self, path, changeid=None, fileid=None): |
|
785 | def filectx(self, path, changeid=None, fileid=None): | |
781 | """changeid can be a changeset revision, node, or tag. |
|
786 | """changeid can be a changeset revision, node, or tag. | |
@@ -1087,8 +1092,6 b' class localrepository(object):' | |||||
1087 | return l |
|
1092 | return l | |
1088 |
|
1093 | |||
1089 | def unlock(): |
|
1094 | def unlock(): | |
1090 | if hasunfilteredcache(self, '_phasecache'): |
|
|||
1091 | self._phasecache.write() |
|
|||
1092 | for k, ce in self._filecache.items(): |
|
1095 | for k, ce in self._filecache.items(): | |
1093 | if k == 'dirstate' or k not in self.__dict__: |
|
1096 | if k == 'dirstate' or k not in self.__dict__: | |
1094 | continue |
|
1097 | continue | |
@@ -1109,7 +1112,11 b' class localrepository(object):' | |||||
1109 | return l |
|
1112 | return l | |
1110 |
|
1113 | |||
1111 | def unlock(): |
|
1114 | def unlock(): | |
1112 |
self.dirstate. |
|
1115 | if self.dirstate.pendingparentchange(): | |
|
1116 | self.dirstate.invalidate() | |||
|
1117 | else: | |||
|
1118 | self.dirstate.write() | |||
|
1119 | ||||
1113 | self._filecache['dirstate'].refresh() |
|
1120 | self._filecache['dirstate'].refresh() | |
1114 |
|
1121 | |||
1115 | l = self._lock(self.vfs, "wlock", wait, unlock, |
|
1122 | l = self._lock(self.vfs, "wlock", wait, unlock, | |
@@ -1230,9 +1237,9 b' class localrepository(object):' | |||||
1230 | raise util.Abort(_('cannot partially commit a merge ' |
|
1237 | raise util.Abort(_('cannot partially commit a merge ' | |
1231 | '(do not specify files or patterns)')) |
|
1238 | '(do not specify files or patterns)')) | |
1232 |
|
1239 | |||
1233 |
|
|
1240 | status = self.status(match=match, clean=force) | |
1234 | if force: |
|
1241 | if force: | |
1235 |
|
|
1242 | status.modified.extend(status.clean) # mq may commit clean files | |
1236 |
|
1243 | |||
1237 | # check subrepos |
|
1244 | # check subrepos | |
1238 | subs = [] |
|
1245 | subs = [] | |
@@ -1241,7 +1248,7 b' class localrepository(object):' | |||||
1241 | # only manage subrepos and .hgsubstate if .hgsub is present |
|
1248 | # only manage subrepos and .hgsubstate if .hgsub is present | |
1242 | if '.hgsub' in wctx: |
|
1249 | if '.hgsub' in wctx: | |
1243 | # we'll decide whether to track this ourselves, thanks |
|
1250 | # we'll decide whether to track this ourselves, thanks | |
1244 | for c in changes[:3]: |
|
1251 | for c in status.modified, status.added, status.removed: | |
1245 | if '.hgsubstate' in c: |
|
1252 | if '.hgsubstate' in c: | |
1246 | c.remove('.hgsubstate') |
|
1253 | c.remove('.hgsubstate') | |
1247 |
|
1254 | |||
@@ -1279,23 +1286,24 b' class localrepository(object):' | |||||
1279 | '.hgsub' in (wctx.modified() + wctx.added())): |
|
1286 | '.hgsub' in (wctx.modified() + wctx.added())): | |
1280 | raise util.Abort( |
|
1287 | raise util.Abort( | |
1281 | _("can't commit subrepos without .hgsub")) |
|
1288 | _("can't commit subrepos without .hgsub")) | |
1282 |
|
|
1289 | status.modified.insert(0, '.hgsubstate') | |
1283 |
|
1290 | |||
1284 |
elif '.hgsub' in |
|
1291 | elif '.hgsub' in status.removed: | |
1285 | # clean up .hgsubstate when .hgsub is removed |
|
1292 | # clean up .hgsubstate when .hgsub is removed | |
1286 | if ('.hgsubstate' in wctx and |
|
1293 | if ('.hgsubstate' in wctx and | |
1287 |
'.hgsubstate' not in |
|
1294 | '.hgsubstate' not in (status.modified + status.added + | |
1288 | changes[2].insert(0, '.hgsubstate') |
|
1295 | status.removed)): | |
|
1296 | status.removed.insert(0, '.hgsubstate') | |||
1289 |
|
1297 | |||
1290 | # make sure all explicit patterns are matched |
|
1298 | # make sure all explicit patterns are matched | |
1291 | if not force and match.files(): |
|
1299 | if not force and match.files(): | |
1292 | matched = set(changes[0] + changes[1] + changes[2]) |
|
1300 | matched = set(status.modified + status.added + status.removed) | |
1293 |
|
1301 | |||
1294 | for f in match.files(): |
|
1302 | for f in match.files(): | |
1295 | f = self.dirstate.normalize(f) |
|
1303 | f = self.dirstate.normalize(f) | |
1296 | if f == '.' or f in matched or f in wctx.substate: |
|
1304 | if f == '.' or f in matched or f in wctx.substate: | |
1297 | continue |
|
1305 | continue | |
1298 |
if f in |
|
1306 | if f in status.deleted: | |
1299 | fail(f, _('file not found!')) |
|
1307 | fail(f, _('file not found!')) | |
1300 | if f in vdirs: # visited directory |
|
1308 | if f in vdirs: # visited directory | |
1301 | d = f + '/' |
|
1309 | d = f + '/' | |
@@ -1307,7 +1315,7 b' class localrepository(object):' | |||||
1307 | elif f not in self.dirstate: |
|
1315 | elif f not in self.dirstate: | |
1308 | fail(f, _("file not tracked!")) |
|
1316 | fail(f, _("file not tracked!")) | |
1309 |
|
1317 | |||
1310 |
cctx = context.workingctx(self, text, user, date, extra, |
|
1318 | cctx = context.workingctx(self, text, user, date, extra, status) | |
1311 |
|
1319 | |||
1312 | if (not force and not extra.get("close") and not merge |
|
1320 | if (not force and not extra.get("close") and not merge | |
1313 | and not cctx.files() |
|
1321 | and not cctx.files() | |
@@ -1318,7 +1326,7 b' class localrepository(object):' | |||||
1318 | raise util.Abort(_("cannot commit merge with missing files")) |
|
1326 | raise util.Abort(_("cannot commit merge with missing files")) | |
1319 |
|
1327 | |||
1320 | ms = mergemod.mergestate(self) |
|
1328 | ms = mergemod.mergestate(self) | |
1321 |
for f in |
|
1329 | for f in status.modified: | |
1322 | if f in ms and ms[f] == 'u': |
|
1330 | if f in ms and ms[f] == 'u': | |
1323 | raise util.Abort(_("unresolved merge conflicts " |
|
1331 | raise util.Abort(_("unresolved merge conflicts " | |
1324 | "(see hg help resolve)")) |
|
1332 | "(see hg help resolve)")) | |
@@ -1372,8 +1380,7 b' class localrepository(object):' | |||||
1372 | Revision information is passed via the context argument. |
|
1380 | Revision information is passed via the context argument. | |
1373 | """ |
|
1381 | """ | |
1374 |
|
1382 | |||
1375 |
tr = |
|
1383 | tr = None | |
1376 | removed = list(ctx.removed()) |
|
|||
1377 | p1, p2 = ctx.p1(), ctx.p2() |
|
1384 | p1, p2 = ctx.p1(), ctx.p2() | |
1378 | user = ctx.user() |
|
1385 | user = ctx.user() | |
1379 |
|
1386 | |||
@@ -1383,20 +1390,26 b' class localrepository(object):' | |||||
1383 | trp = weakref.proxy(tr) |
|
1390 | trp = weakref.proxy(tr) | |
1384 |
|
1391 | |||
1385 | if ctx.files(): |
|
1392 | if ctx.files(): | |
1386 |
m1 = p1.manifest() |
|
1393 | m1 = p1.manifest() | |
1387 | m2 = p2.manifest() |
|
1394 | m2 = p2.manifest() | |
|
1395 | m = m1.copy() | |||
1388 |
|
1396 | |||
1389 | # check in files |
|
1397 | # check in files | |
1390 |
|
|
1398 | added = [] | |
1391 | changed = [] |
|
1399 | changed = [] | |
|
1400 | removed = list(ctx.removed()) | |||
1392 | linkrev = len(self) |
|
1401 | linkrev = len(self) | |
1393 | for f in sorted(ctx.modified() + ctx.added()): |
|
1402 | for f in sorted(ctx.modified() + ctx.added()): | |
1394 | self.ui.note(f + "\n") |
|
1403 | self.ui.note(f + "\n") | |
1395 | try: |
|
1404 | try: | |
1396 | fctx = ctx[f] |
|
1405 | fctx = ctx[f] | |
1397 | new[f] = self._filecommit(fctx, m1, m2, linkrev, trp, |
|
1406 | if fctx is None: | |
1398 |
|
|
1407 | removed.append(f) | |
1399 |
|
|
1408 | else: | |
|
1409 | added.append(f) | |||
|
1410 | m[f] = self._filecommit(fctx, m1, m2, linkrev, | |||
|
1411 | trp, changed) | |||
|
1412 | m.setflag(f, fctx.flags()) | |||
1400 | except OSError, inst: |
|
1413 | except OSError, inst: | |
1401 | self.ui.warn(_("trouble committing %s!\n") % f) |
|
1414 | self.ui.warn(_("trouble committing %s!\n") % f) | |
1402 | raise |
|
1415 | raise | |
@@ -1404,18 +1417,16 b' class localrepository(object):' | |||||
1404 | errcode = getattr(inst, 'errno', errno.ENOENT) |
|
1417 | errcode = getattr(inst, 'errno', errno.ENOENT) | |
1405 | if error or errcode and errcode != errno.ENOENT: |
|
1418 | if error or errcode and errcode != errno.ENOENT: | |
1406 | self.ui.warn(_("trouble committing %s!\n") % f) |
|
1419 | self.ui.warn(_("trouble committing %s!\n") % f) | |
1407 |
|
|
1420 | raise | |
1408 | else: |
|
|||
1409 | removed.append(f) |
|
|||
1410 |
|
1421 | |||
1411 | # update manifest |
|
1422 | # update manifest | |
1412 | m1.update(new) |
|
|||
1413 | removed = [f for f in sorted(removed) if f in m1 or f in m2] |
|
1423 | removed = [f for f in sorted(removed) if f in m1 or f in m2] | |
1414 |
drop = [f for f in removed if f in m |
|
1424 | drop = [f for f in removed if f in m] | |
1415 | for f in drop: |
|
1425 | for f in drop: | |
1416 |
del m |
|
1426 | del m[f] | |
1417 |
mn = self.manifest.add(m |
|
1427 | mn = self.manifest.add(m, trp, linkrev, | |
1418 |
p2.manifestnode(), |
|
1428 | p1.manifestnode(), p2.manifestnode(), | |
|
1429 | added, drop) | |||
1419 | files = changed + removed |
|
1430 | files = changed + removed | |
1420 | else: |
|
1431 | else: | |
1421 | mn = p1.manifestnode() |
|
1432 | mn = p1.manifestnode() | |
@@ -1439,7 +1450,7 b' class localrepository(object):' | |||||
1439 | # be compliant anyway |
|
1450 | # be compliant anyway | |
1440 | # |
|
1451 | # | |
1441 | # if minimal phase was 0 we don't need to retract anything |
|
1452 | # if minimal phase was 0 we don't need to retract anything | |
1442 | phases.retractboundary(self, targetphase, [n]) |
|
1453 | phases.retractboundary(self, tr, targetphase, [n]) | |
1443 | tr.close() |
|
1454 | tr.close() | |
1444 | branchmap.updatecache(self.filtered('served')) |
|
1455 | branchmap.updatecache(self.filtered('served')) | |
1445 | return n |
|
1456 | return n | |
@@ -1574,9 +1585,6 b' class localrepository(object):' | |||||
1574 |
|
1585 | |||
1575 | return r |
|
1586 | return r | |
1576 |
|
1587 | |||
1577 | def pull(self, remote, heads=None, force=False): |
|
|||
1578 | return exchange.pull (self, remote, heads, force) |
|
|||
1579 |
|
||||
1580 | def checkpush(self, pushop): |
|
1588 | def checkpush(self, pushop): | |
1581 | """Extensions can override this function if additional checks have |
|
1589 | """Extensions can override this function if additional checks have | |
1582 | to be performed before pushing, or call it if they override push |
|
1590 | to be performed before pushing, or call it if they override push | |
@@ -1591,9 +1599,6 b' class localrepository(object):' | |||||
1591 | """ |
|
1599 | """ | |
1592 | return util.hooks() |
|
1600 | return util.hooks() | |
1593 |
|
1601 | |||
1594 | def push(self, remote, force=False, revs=None, newbranch=False): |
|
|||
1595 | return exchange.push(self, remote, force, revs, newbranch) |
|
|||
1596 |
|
||||
1597 | def stream_in(self, remote, requirements): |
|
1602 | def stream_in(self, remote, requirements): | |
1598 | lock = self.lock() |
|
1603 | lock = self.lock() | |
1599 | try: |
|
1604 | try: | |
@@ -1727,7 +1732,14 b' class localrepository(object):' | |||||
1727 | # if we support it, stream in and adjust our requirements |
|
1732 | # if we support it, stream in and adjust our requirements | |
1728 | if not streamreqs - self.supportedformats: |
|
1733 | if not streamreqs - self.supportedformats: | |
1729 | return self.stream_in(remote, streamreqs) |
|
1734 | return self.stream_in(remote, streamreqs) | |
1730 | return self.pull(remote, heads) |
|
1735 | ||
|
1736 | quiet = self.ui.backupconfig('ui', 'quietbookmarkmove') | |||
|
1737 | try: | |||
|
1738 | self.ui.setconfig('ui', 'quietbookmarkmove', True, 'clone') | |||
|
1739 | ret = exchange.pull(self, remote, heads).cgresult | |||
|
1740 | finally: | |||
|
1741 | self.ui.restoreconfig(quiet) | |||
|
1742 | return ret | |||
1731 |
|
1743 | |||
1732 | def pushkey(self, namespace, key, old, new): |
|
1744 | def pushkey(self, namespace, key, old, new): | |
1733 | self.hook('prepushkey', throw=True, namespace=namespace, key=key, |
|
1745 | self.hook('prepushkey', throw=True, namespace=namespace, key=key, |
@@ -139,12 +139,14 b' class lock(object):' | |||||
139 | if os.getpid() != self.pid: |
|
139 | if os.getpid() != self.pid: | |
140 | # we forked, and are not the parent |
|
140 | # we forked, and are not the parent | |
141 | return |
|
141 | return | |
142 | if self.releasefn: |
|
|||
143 | self.releasefn() |
|
|||
144 | try: |
|
142 | try: | |
145 |
self. |
|
143 | if self.releasefn: | |
146 | except OSError: |
|
144 | self.releasefn() | |
147 |
|
|
145 | finally: | |
|
146 | try: | |||
|
147 | self.vfs.unlink(self.f) | |||
|
148 | except OSError: | |||
|
149 | pass | |||
148 | for callback in self.postrelease: |
|
150 | for callback in self.postrelease: | |
149 | callback() |
|
151 | callback() | |
150 |
|
152 |
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-bundle2.t to tests/test-bundle2-exchange.t |
|
NO CONTENT: file renamed from tests/test-bundle2.t to tests/test-bundle2-exchange.t | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file copied from tests/test-bundle2.t to tests/test-bundle2-format.t |
|
NO CONTENT: file copied from tests/test-bundle2.t to tests/test-bundle2-format.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: 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: 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 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 |
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 |
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