##// END OF EJS Templates
merge default into stable for 3.2 freeze
Matt Mackall -
r23048:ee5f8340 merge stable
parent child Browse files
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 mercurial/__version__.py hgext/__index__.py tests/*.err
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 -s bdist_mpkg || \
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 $rpmdir/SOURCES/mercurial-$version-$release.tar.gz
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 echo
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, m in testpats[i]:
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:', 'no try/except/finally in Python 2.4'),
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 gray50
1274 set col "#7f7f7f"
1275 if {[string compare $bookmarkcurrent $tag] == 0} {
1275 if {[string compare $bookmarkcurrent $tag] == 0} {
1276 set col gray
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 contrib/sample.hgrc
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", metavar="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', [], _('write output to given file'), _('FILE')),
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(repo.root) + '.json'
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 interarrival[roundto(ctx.date()[0] - lastctx.date()[0], 300)] += 1
172 timedelta = ctx.date()[0] - lastctx.date()[0]
154 diff = sum((d.splitlines()
173 interarrival[roundto(timedelta, 300)] += 1
155 for d in ctx.diff(pctx, opts={'git': True})), [])
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, binary in parsegitdiff(diff):
176 for filename, mar, lineadd, lineremove, isbin in parsegitdiff(diff):
158 if binary:
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), roundto(lineremove, 5)] += 1
182 lineschanged[roundto(added, 5),
164 filechanges += 1
183 roundto(lineremove, 5)] += 1
165 elif mar == 'a':
184 filechanges += 1
166 fileadds += 1
185 elif mar == 'a':
167 if '/' in filename:
186 fileadds += 1
168 filedir = filename.rsplit('/', 1)[0]
187 if '/' in filename:
169 if filedir not in pctx.dirs():
188 filedir = filename.rsplit('/', 1)[0]
170 diradds += 1
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 fileremoves += 1
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 filesadded[fileadds] += 1
196 fileschanged[filechanges] += 1
178 dirsadded[diradds] += 1
197 filesadded[fileadds] += 1
179 filesremoved[fileremoves] += 1
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 if pick(parents) == 2 or len(heads) > random.randint(1, 20):
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 data = changes[path]
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 = {F17D27B7-4A6B-4cd2-AE72-FED3CFAA585E} ?>
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 the status and qseries commands
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', and 'never' are keywords and should
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, or never)"),
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(). Raise
91 identifier returned by a previous call to getchanges().
92 IOError to indicate that name was deleted in rev.
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(). id is the source revision id of the file.
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" % version)
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 for l in fh.read().split('\x00'):
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 seen.add(f)
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 if f == '.gitmodules':
250 i += 1
206 subexists = True
251 if fdest not in seen:
207 if entry[4] == 'D':
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 changes.append(('.hgsub', ''))
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 v = files[f]
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, err:
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 = sorted(ctx.manifest())
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 self._changescache = (rev, changes)
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 raise IOError
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, scmutil, util, commands, encoding
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 nullid, short
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 = repo.pull(other, heads=revs)
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 "(please commit .hgsigs manually "
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=cmdutil.getcommiteditor(**opts))
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[0]:
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[1]:
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[2]:
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 l[x] = repo[i + x]
207 if (i + x) in repo:
208 l[x].changeset() # force reading
208 l[x] = repo[i + x]
209 l[x].changeset() # force reading
209 else:
210 else:
210 l[x] = 1
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.update(repo, ctx.node(), True, True, False,
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=cmdutil.getcommiteditor(edit=True))
345 editor=editor)
306 return repo.commitctx(new)
346 return repo.commitctx(new)
307
347
308 def pick(ui, repo, ctx, ha, opts):
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, repo, ctx, ha, opts):
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 fold(ui, repo, ctx, ha, opts):
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 [ctx.description()] +
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 (ctx.node(), (n,)),
433 (ctx.node(), (n,)),
385 (newnode, (n,)),
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, repo, ctx, ha, opts):
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, repo, ctx, ha, opts):
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 parentctx, repl = bootstrapcontinue(ui, repo, parentctx, rules, opts)
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[0] == '#']
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, repo, parentctx, ha, opts)
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(repo, replacements)
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._enabled:
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, repo, parentctx, rules, opts):
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 m, a, r, d = repo.status()[:4]
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 date=ctx.date(), extra=ctx.extra(),
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._enabled and
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.phase():
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(repo, replacements):
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 = [n for n in nodes if n in nm]
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-2012 Christian Ebert <blacktrash@gmx.net>
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, shutil, tempfile
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 modified = [f for f in modified if f in changed]
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 raise IOError
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 raise IOError
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 raise IOError
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 if state == 'n':
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 lfdirstate.normal(lfile)
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, rev):
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 = repo[rev][standin(lfile)]
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, merge, pathutil, revset
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 [s[0], s[1], s[3], s[6]]]
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, forget = deleted, []
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, forget = deleted + clean, []
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 + forget):
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 getattr(repo, "_isaddremove", False):
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 getattr(repo, "_isaddremove", False):
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 missing:
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[0] + s[1] + s[3] + s[6])
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()), [], False,
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 missing]
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 unknown = [f for f in unknown if lfdirstate[f] == '?']
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 lfdirstate.add(file)
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 if super(lfilesmanifestdict,
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 = super(lfilesctx, self).filectx(
54 result = orig(path, fileid, filelog)
57 path, fileid, filelog)
58 else:
55 else:
59 result = super(lfilesctx, self).filectx(
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 = super(lfilesctx, self).filectx(
61 result = orig(lfutil.standin(path), fileid,
66 lfutil.standin(path), fileid, filelog)
62 filelog)
67 else:
63 else:
68 result = super(lfilesctx, self).filectx(
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 super(lfilesrepo, self).status(node1, node2, match,
83 return orig(node1, node2, match, listignored, listclean,
88 listignored, listclean, listunknown, listsubrepos)
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 if match is None:
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 except error.LockError:
103 pass
116 pass
117
104
118 # First check if there were files specified on the
105 # First check if there were files specified on the
119 # command line. If there were, and none of them were
106 # command line. If there were, and none of them were
120 # largefiles, we should just bail here and let super
107 # largefiles, we should just bail here and let super
121 # handle it -- thus gaining a big performance boost.
108 # handle it -- thus gaining a big performance boost.
122 lfdirstate = lfutil.openlfdirstate(ui, self)
109 lfdirstate = lfutil.openlfdirstate(ui, self)
123 if match.files() and not match.anypats():
110 if match.files() and not match.anypats():
124 for f in lfdirstate:
111 for f in lfdirstate:
125 if match(f):
112 if match(f):
126 break
113 break
127 else:
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 # Create a copy of match that matches standins instead
118 # Create a copy of match that matches standins instead
133 # of largefiles.
119 # of largefiles.
134 def tostandins(files):
120 def tostandins(files):
135 if not working:
121 if not working:
136 return files
122 return files
137 newfiles = []
123 newfiles = []
138 dirstate = self.dirstate
124 dirstate = self.dirstate
139 for f in files:
125 for f in files:
140 sf = lfutil.standin(f)
126 sf = lfutil.standin(f)
141 if sf in dirstate:
127 if sf in dirstate:
142 newfiles.append(sf)
128 newfiles.append(sf)
143 elif sf in dirstate.dirs():
129 elif sf in dirstate.dirs():
144 # Directory entries could be regular or
130 # Directory entries could be regular or
145 # standin, check both
131 # standin, check both
146 newfiles.extend((f, sf))
132 newfiles.extend((f, sf))
147 else:
133 else:
148 newfiles.append(f)
134 newfiles.append(f)
149 return newfiles
135 return newfiles
150
136
151 m = copy.copy(match)
137 m = copy.copy(match)
152 m._files = tostandins(m._files)
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 ignored, clean, unknown, listsubrepos)
141 listsubrepos)
156 if working:
142 if working:
157
143
158 def sfindirstate(f):
144 def sfindirstate(f):
159 sf = lfutil.standin(f)
145 sf = lfutil.standin(f)
160 dirstate = self.dirstate
146 dirstate = self.dirstate
161 return sf in dirstate or sf in dirstate.dirs()
147 return sf in dirstate or sf in dirstate.dirs()
162
148
163 match._files = [f for f in match._files
149 match._files = [f for f in match._files
164 if sfindirstate(f)]
150 if sfindirstate(f)]
165 # Don't waste time getting the ignored and unknown
151 # Don't waste time getting the ignored and unknown
166 # files from lfdirstate
152 # files from lfdirstate
167 s = lfdirstate.status(match, [], False,
153 unsure, s = lfdirstate.status(match, [], False, listclean,
168 listclean, False)
154 False)
169 (unsure, modified, added, removed, missing, _unknown,
155 (modified, added, removed, clean) = (s.modified, s.added,
170 _ignored, clean) = s
156 s.removed, s.clean)
171 if parentworking:
157 if parentworking:
172 for lfile in unsure:
158 for lfile in unsure:
173 standin = lfutil.standin(lfile)
159 standin = lfutil.standin(lfile)
174 if standin not in ctx1:
160 if standin not in ctx1:
175 # from second parent
161 # from second parent
176 modified.append(lfile)
162 modified.append(lfile)
177 elif ctx1[standin].data().strip() \
163 elif ctx1[standin].data().strip() \
178 != lfutil.hashfile(self.wjoin(lfile)):
164 != lfutil.hashfile(self.wjoin(lfile)):
179 modified.append(lfile)
165 modified.append(lfile)
180 else:
166 else:
167 if listclean:
181 clean.append(lfile)
168 clean.append(lfile)
182 lfdirstate.normal(lfile)
169 lfdirstate.normal(lfile)
183 else:
170 else:
184 tocheck = unsure + modified + added + clean
171 tocheck = unsure + modified + added + clean
185 modified, added, clean = [], [], []
172 modified, added, clean = [], [], []
186
173
187 for lfile in tocheck:
174 for lfile in tocheck:
188 standin = lfutil.standin(lfile)
175 standin = lfutil.standin(lfile)
189 if inctx(standin, ctx1):
176 if standin in ctx1:
190 if ctx1[standin].data().strip() != \
177 if ctx1[standin].data().strip() != \
191 lfutil.hashfile(self.wjoin(lfile)):
178 lfutil.hashfile(self.wjoin(lfile)):
192 modified.append(lfile)
179 modified.append(lfile)
193 else:
180 elif listclean:
194 clean.append(lfile)
181 clean.append(lfile)
195 else:
182 else:
196 added.append(lfile)
183 added.append(lfile)
197
184
198 # Standins no longer found in lfdirstate has been
185 # Standins no longer found in lfdirstate has been
199 # removed
186 # removed
200 for standin in ctx1.manifest():
187 for standin in ctx1.walk(lfutil.getstandinmatcher(self)):
201 if not lfutil.isstandin(standin):
188 lfile = lfutil.splitstandin(standin)
202 continue
189 if not match(lfile):
203 lfile = lfutil.splitstandin(standin)
190 continue
204 if not match(lfile):
191 if lfile not in lfdirstate:
205 continue
192 removed.append(lfile)
206 if lfile not in lfdirstate:
207 removed.append(lfile)
208
193
209 # Filter result lists
194 # Filter result lists
210 result = list(result)
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 normals = [[fn for fn in filelist
203 if f not in self.dirstate]
229 if not lfutil.isstandin(fn)]
204 result[2] = [f for f in result[2]
230 for filelist in result]
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 lfdirstate.write()
208 # Unknown files
245
209 result[4] = set(result[4]).difference(lfiles)
246 finally:
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 not listunknown:
224 if wlock:
251 result[4] = []
225 lfdirstate.write()
252 if not listignored:
226
253 result[5] = []
227 finally:
254 if not listclean:
228 if wlock:
255 result[6] = []
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: Rebase or Transplant
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, 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 patchheaderat = self.comments.index('# HG changeset patch')
260 inserthgheader(self.comments, '# User ', user)
212 self.comments.insert(patchheaderat + 1, '# User ' + user)
213 except ValueError:
261 except ValueError:
214 if self.plainmode or self._hasheader(['Date: ']):
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 patchheaderat = self.comments.index('# HG changeset patch')
272 inserthgheader(self.comments, '# Date ', date)
225 self.comments.insert(patchheaderat + 1, '# Date ' + date)
226 except ValueError:
273 except ValueError:
227 if self.plainmode or self._hasheader(['From: ']):
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 patchheaderat = self.comments.index('# HG changeset patch')
285 inserthgheader(self.comments, '# Parent ', parent)
238 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
239 except ValueError:
286 except ValueError:
240 pass
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 if not self.comments:
311 s = '\n'.join(self.comments).rstrip()
312 if not s:
271 return ''
313 return ''
272 return '\n'.join(self.comments) + '\n\n'
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='strip')
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.write(msg)
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 m, a, r, d = self.checklocalchanges(repo, force=True)
1362 status = self.checklocalchanges(repo, force=True)
1321 if keepchanges:
1363 if keepchanges:
1322 tobackup.update(m + a + r + d)
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 m, a, r, d = self.checklocalchanges(
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 + r + d)
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='strip')
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='strip')
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='strip')
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[-1]))
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[-1])
1999 'branch') % rev.last())
1952 if self.applied:
2000 if self.applied:
1953 base = repo.changelog.node(rev[0])
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[0])
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[0])]:
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[0])
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.applied.insert(0, se)
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 patchname = None
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, stat
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[4] + status[5]):
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(removefile, f)
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 from the specified changeset'), _('REV')),
54 _('rebase the specified changeset and descendants'), _('REV')),
54 ('b', 'base', '',
55 ('b', 'base', '',
55 _('rebase from the base of the specified changeset '
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 editor = cmdutil.getcommiteditor(edit=True)
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 return merge.update(repo, rev, True, True, False, base, collapse,
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[0]]
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 P1n = parents[0].rev()
615 p1n = parents[0].rev()
561 if P1n in targetancestors:
616 if p1n in targetancestors:
562 p1 = target
617 p1 = target
563 elif P1n in state:
618 elif p1n in state:
564 if state[P1n] == nullmerge:
619 if state[p1n] == nullmerge:
565 p1 = target
620 p1 = target
566 elif state[P1n] == revignored:
621 elif state[p1n] == revignored:
567 p1 = nearestrebased(repo, P1n, state)
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[P1n]
626 p1 = state[p1n]
572 else: # P1n external
627 else: # p1n external
573 p1 = target
628 p1 = target
574 p2 = P1n
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 P2n = parents[1].rev()
632 p2n = parents[1].rev()
578 # interesting second parent
633 # interesting second parent
579 if P2n in state:
634 if p2n in state:
580 if p1 == target: # P1n in targetancestors or external
635 if p1 == target: # p1n in targetancestors or external
581 p1 = state[P2n]
636 p1 = state[p2n]
582 elif state[P2n] == revignored:
637 elif state[p2n] == revignored:
583 p2 = nearestrebased(repo, P2n, state)
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[P2n]
643 p2 = state[p2n]
589 else: # P2n external
644 else: # p2n external
590 if p2 != nullrev: # P1n external too => rev is a merged revision
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 = P2n
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 changes = repo.status(match=match)[:3]
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=changes, opts=diffopts)
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(changes[0])
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, changegroup, exchange
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/.files/.patch) using
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=cmdutil.getcommiteditor(**opts))
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[3]:
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[3]))
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', 'files', 'patch'):
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 files patch'.split():
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='none', topic='shelve')
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 m, a, r, d, u = repo.status(unknown=True)[:5]
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 files patch'.split():
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='none', topic='shelve')
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, 'files').exists():
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 m, a, r, d = repo.status()[:4]
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 m, a, r, d = repo.status()[:4]
35 s = repo.status()
36 if not force:
36 if not force:
37 if (m or a or r or d):
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 m, a, r, d
43 return s
44
44
45 def strip(ui, repo, revs, update=True, backup="all", force=None, bookmark=None):
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 = 'all'
117 backup = True
118 if opts.get('backup'):
118 if opts.get('no_backup') or opts.get('nobackup'):
119 backup = 'strip'
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 self.editor = cmdutil.getcommiteditor(**opts)
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 repo.pull(source.peer(), heads=pulls)
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 repo.pull(source.peer(), heads=[node])
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 repo.pull(source.peer(), heads=pulls)
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 for name, node in self.iteritems():
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 if hex(marks.get(key, '')) != old:
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.write()
254 marks.recordchange(tr)
255 tr.close()
240 return True
256 return True
241 finally:
257 finally:
242 w.release()
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), ui.status,
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), ui.status,
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.write()
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 loop prunes out two kinds of heads - heads that are
259 # This prunes out two kinds of heads - heads that are superseded by
258 # superseded by a head in newheadrevs, and newheadrevs that are not
260 # a head in newheadrevs, and newheadrevs that are not heads because
259 # heads because an existing head is their descendant.
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: (16 bits integer)
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: (16 bits inter)
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 32 bits integer, `chunkdata` are plain bytes (as much as
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 = 'HG2X'
162 _magicstring = 'HG2Y'
158
163
159 _fstreamparamsize = '>H'
164 _fstreamparamsize = '>i'
160 _fpartheadersize = '>H'
165 _fpartheadersize = '>i'
161 _fparttypesize = '>B'
166 _fparttypesize = '>B'
162 _fpartid = '>I'
167 _fpartid = '>I'
163 _fpayloadsize = '>I'
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 parttype = part.type
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 != '2X':
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.BundleValueError(params=(name,))
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.unbundle10(inpart, 'UN')
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 handlechangegroup(op, inpart):
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.BundleValueError(**kwargs)
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 _BUNDLE10_DELTA_HEADER = "20s20s20s20s"
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 unbundle10(object):
126 class cg1unpacker(object):
127 deltaheader = _BUNDLE10_DELTA_HEADER
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 bundle10(object):
230 class cg1packer(object):
231 deltaheader = _BUNDLE10_DELTA_HEADER
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 unbundle10(util.chunkbuffer(gengroup), 'UN')
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 = bundle10(repo)
483 bundler = cg1packer(repo)
484 return getsubset(repo, outgoing, bundler, source)
484 return getsubset(repo, outgoing, bundler, source)
485
485
486 def getlocalbundle(repo, source, outgoing, bundlecaps=None):
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 = bundle10(repo, bundlecaps)
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 getbundle(repo, source, heads=None, common=None, bundlecaps=None):
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 getlocalbundle(repo, source, outgoing, bundlecaps=bundlecaps)
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, phases.draft, added)
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", source=srctype, url=url,
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._headrevs()
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, _('no node'))
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=getcommiteditor())
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 hex(n) != nodeid:
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, patch, diffopts, buffered):
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.patch = patch
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 and ctx.phase():
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.patch
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, patch, diffopts, tmpl, mapfile, buffered):
1114 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
987 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
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()), ('node', p.hex())]
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 patch = None
1290 matchfn = None
1161 if opts.get('patch') or opts.get('stat'):
1291 if opts.get('patch') or opts.get('stat'):
1162 patch = scmutil.matchall(repo)
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, patch, opts, buffered)
1300 return changeset_printer(ui, repo, matchfn, opts, buffered)
1168
1301
1169 try:
1302 try:
1170 t = changeset_templater(ui, repo, patch, opts, tmpl, mapfile, buffered)
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._data[2])
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 = revset.baseset()
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 = revset.baseset([])
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 raise IOError
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 raise IOError
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 = getcommiteditor(**opts)
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 (not obsolete._enabled) and newid != old.node():
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 if path in names:
2474 def badfn(path, msg):
2336 return
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': ([], removeforget),
2609 'remove': ([], _('removing %s\n')),
2379 'undelete': ([], _('undeleting %s\n'))}
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 if in target manifest
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 (actions['remove'], True)),
2639 (modified, actions['revert'], discard),
2390 (added, (actions['revert'], True),
2640 # Modified compared to target, but local file is deleted
2391 (actions['remove'], False)),
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 (actions['remove'], False)),
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 this sets, it was touched in the working
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, hit, miss in disptable:
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 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
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 ml = max([w for x, w in sized])
334 formats.append(['%s' for x in l])
318 pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x)
335 else:
319 for x, w in sized])
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 node not in repo.changelog.commonancestorsheads(op1, node):
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 if bmark == current:
1004 prefix, label = '*', 'bookmarks.current'
977 prefix, label = '*', 'bookmarks.current'
1005 else:
978 else:
1006 prefix, label = ' ', ''
979 prefix, label = ' ', ''
1007
980
1008 fm.startitem()
981 if ui.quiet:
1009 if not ui.quiet:
982 ui.write("%s\n" % bmark, label=label)
1010 fm.plain(' %s ' % prefix, label=label)
983 else:
1011 fm.write('bookmark', '%s', bmark, label=label)
984 pad = " " * (25 - encoding.colwidth(bmark))
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 prefix, bmark, pad, repo.changelog.rev(n), hexfn(n)),
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 (not active) or isactive:
1111 if active and not isactive:
1080 if isactive:
1112 continue
1081 label = 'branches.active'
1113 if isactive:
1082 notice = ''
1114 label = 'branches.active'
1083 elif not isopen:
1115 notice = ''
1084 if not closed:
1116 elif not isopen:
1085 continue
1117 if not closed:
1086 label = 'branches.closed'
1118 continue
1087 notice = _(' (closed)')
1119 label = 'branches.closed'
1088 else:
1120 notice = _(' (closed)')
1089 label = 'branches.inactive'
1121 else:
1090 notice = _(' (inactive)')
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.getbundle(repo, 'bundle', heads=heads, common=common,
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.getlocalbundle(repo, 'bundle', outgoing, bundlecaps)
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 repo.baseui.setconfig('phases', 'new-commit', 'secret',
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=cmdutil.getcommiteditor(**opts),
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.setconfig('phases', 'new-commit', oldcommitphase,
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(set(p for p in rlog.parentrevs(r)
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(set(p for p in cl.parentrevs(r)
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 = r.chainbase(rev)
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 %11d %5d\n" %
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 revs.remove(rev)
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 ctx = repo[rev]
3405 revs = list(revs)
3191 n = ctx.extra().get('source')
3406 # don't mutate while iterating, create a copy
3192 if n in ids:
3407 for rev in list(revs):
3193 try:
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 = None
3411 revs.remove(rev)
3197 if r in revs:
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 elif ids[n] in revs:
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.update(repo, ctx.node(), True, True, False,
3485 stats = mergemod.graft(repo, ctx, ctx.p1(),
3250 ctx.p1().node(),
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 = repo.pull(other, heads=revs, force=opts.get('force'))
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 result = repo.push(other, opts.get('force'), revs=revs,
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 return 2
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.server(ui, repo, opts["cmdserver"])
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 = list(repo.status(unknown=True))[:6]
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[2]:
5796 if s in status.removed:
5540 st[2].remove(s)
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[1]:
5801 if d in status.added:
5545 st[1].remove(d)
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 st.append([f for f in ms if ms[f] == 'u'])
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 labels = [ui.label(_('%d modified'), 'status.modified'),
5810 (ui.label(_('%d added'), 'status.added'), status.added),
5556 ui.label(_('%d added'), 'status.added'),
5811 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5557 ui.label(_('%d removed'), 'status.removed'),
5812 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5558 ui.label(_('%d renamed'), 'status.copied'),
5813 (ui.label(_('%d copied'), 'status.copied'), copied),
5559 ui.label(_('%d copied'), 'status.copied'),
5814 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5560 ui.label(_('%d deleted'), 'status.deleted'),
5815 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5561 ui.label(_('%d unknown'), 'status.unknown'),
5816 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5562 ui.label(_('%d ignored'), 'status.ignored'),
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 s, l in zip(st, labels):
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 id', fmt,
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 from in_ to out in the following format:
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, in_, out, channel):
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.in_, attr)
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 stdin, runs them and writes the output on a channel
129 Listens for commands on fin, runs them and writes the output on a channel
131 based stream to stdout.
130 based stream to fout.
132 """
131 """
133 def __init__(self, ui, repo, mode):
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(sys.stdout, sys.stdout, 'd')
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 self.cerr = channeledoutput(sys.stderr, sys.stdout, 'e')
155 self.cout = channeledoutput(fout, 'o')
157 self.cout = channeledoutput(sys.stdout, sys.stdout, 'o')
156 self.cin = channeledinput(fin, fout, 'I')
158 self.cin = channeledinput(sys.stdin, sys.stdout, 'I')
157 self.cresult = channeledoutput(fout, 'r')
159 self.cresult = channeledoutput(sys.stdout, sys.stdout, 'r')
160
158
161 self.client = sys.stdin
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 del self._data[section][item]
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 tuple(r)
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, (islink, isexec), copied = store.getfile(path)
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 self._rev = changeid
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 return
392 self._rev = nullrev
390 if changeid == 'null':
393 return
391 self._node = nullid
394 if changeid == 'tip':
392 self._rev = nullrev
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 self._node = changeid
409 r = int(changeid)
401 self._rev = repo.changelog.rev(changeid)
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 LookupError:
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 if str(r) != changeid:
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 r += l
431 raise
413 if r < 0 or r >= l:
432 except (TypeError, LookupError):
414 raise ValueError
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 if r == '*':
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 def without_linenumber(text, rev):
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[0] + self._status[1] + self._status[2])
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[0]
1118 return self._status.modified
1086 def added(self):
1119 def added(self):
1087 return self._status[1]
1120 return self._status.added
1088 def removed(self):
1121 def removed(self):
1089 return self._status[2]
1122 return self._status.removed
1090 def deleted(self):
1123 def deleted(self):
1091 return self._status[3]
1124 return self._status.deleted
1092 def unknown(self):
1125 def unknown(self):
1093 return self._status[4]
1126 return self._status.unknown
1094 def ignored(self):
1127 def ignored(self):
1095 return self._status[5]
1128 return self._status.ignored
1096 def clean(self):
1129 def clean(self):
1097 return self._status[6]
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 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
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 tuple(s)
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 = [files, [], [], [], []]
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 list of (or set if given a set) of node ids'''
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 list of (or set if given a set) of node ixs'''
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[util.normcase(name)] = name
90 f[normcase(name)] = name
66 for name in self._dirs:
91 for name in self._dirs:
67 f[util.normcase(name)] = name
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 tuple of lists (unsure, modified, added,
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 added:
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, unknown, ignored,
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 = False
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 "be given on the command line\n")
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 if s.decode('utf-8'):
397 s.decode('utf-8')
340 return s
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 'Exception raised when parsing config files'
52 """Exception raised when parsing config files"""
47
53
48 class OutOfBandError(Exception):
54 class OutOfBandError(Exception):
49 'Exception raised when a remote repo reports failure'
55 """Exception raised when a remote repo reports failure"""
50
56
51 class ParseError(Exception):
57 class ParseError(Exception):
52 'Exception raised when parsing config files (msg[, pos])'
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.unbundle10(fh, alg)
34 return changegroup.cg1unpacker(fh, alg)
35 elif version == '2X':
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.commonheads = None
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 %s changeset: %s!"
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 unstable changeset: %s!")
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(_(mst)
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.getlocalbundle(pushop.repo, 'push', pushop.outgoing)
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.bundle2caps)
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 bundle2partsgenerators:
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.bundle10(pushop.repo, bundlecaps)
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.getlocalbundle(pushop.repo, 'push', outgoing,
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', pushop.repo.url())
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.bundle2caps)
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(_('updating bookmark %s failed!\n') % b)
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 _pullchangeset(pullop)
872 _pullphase(pullop)
567 if 'phases' in pullop.todosteps:
873 _pullbookmarks(pullop)
568 _pullphase(pullop)
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.cgresult
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.todosteps.remove('changegroup')
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(['HG2X'])
1105 caps = set(['HG2Y'])
710 capsblob = bundle2.encodecaps(repo.bundle2caps)
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 HG2X depending on bundlecaps
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.getbundle that only returns an HG10
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, source=source,
1250 repo.hook('b2x-pretransactionclose', throw=True, pending=p,
799 url=url, pending=p, **tr.hookargs)
1251 **tr.hookargs)
800 tr.close()
1252 tr.close()
801 repo.hook('b2x-transactionclose', source=source, url=url,
1253 hookargs = dict(tr.hookargs)
802 **tr.hookargs)
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 _parsemeta(text):
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, 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 _packmeta(meta, keys=None):
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 = "\1\n%s\1\n%s" % (_packmeta(meta), 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 = _parsemeta(t)[0]
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 = 'internal:' + name
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 "internal:prompt", None
115 return ":prompt", None
115 return "internal:merge", None
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 != 'keep':
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, no_minimal=True)
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 internal:merge' or another merge "
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={ 'conflictmarker' : template })
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 _formatconflictmarker(repo, co, tmpl, labels[1], pad)]
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 formattedlabels = _defaultconflictlabels
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=formattedlabels)
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()[0]
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()[1]
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()[2]
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()[3]
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()[4]
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()[5]
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()[6]
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 = {\n" % self._topic)
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("}\n")
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 knownrevs]))
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, sys, os
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', False):
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 entry[0](ui)
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('\n%s\n\n' % _("options:"))
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('\n%s\n\n' % _("global options:"))
284 rst.append(optrst(_("global options"),
293 rst.append(optrst(commands.globalopts, ui.verbose))
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 help %s" to show the full help text\n')
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 if ui.verbose:
361 elif ui.verbose:
373 optlist.append((_("global options:"), commands.globalopts))
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 'or "hg -v" for details')
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 name)
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 name)
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 | (Unix) ``$HOME/.hgrc``
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. These expansions happen before the
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``, or ``keep``
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. Default is install path.
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 ``internal:merge`` is used.
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 hex, nullid
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 if os.path.exists(srcbranchcache):
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 srcrepo.push(destpeer, revs=revs)
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("default = %s\n" % defaulturl)
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.root)
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 type(message) is unicode:
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 'HG2X' in bundlecaps:
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.bundle2caps)
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 raise util.Abort(_('working copy of .hgtags is changed '
587 raise util.Abort(_('working copy of .hgtags is changed'),
584 '(please commit .hgtags manually)'))
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 r = self.changelog.rev(n)
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.write()
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 changes = self.status(match=match, clean=force)
1240 status = self.status(match=match, clean=force)
1234 if force:
1241 if force:
1235 changes[0].extend(changes[6]) # mq may commit unchanged files
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 changes[0].insert(0, '.hgsubstate')
1289 status.modified.insert(0, '.hgsubstate')
1283
1290
1284 elif '.hgsub' in changes[2]:
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 changes[0] + changes[1] + changes[2]):
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 changes[3]: # missing
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, changes)
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 changes[0]:
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 = lock = None
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().copy()
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 new = {}
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 changed)
1407 removed.append(f)
1399 m1.set(f, fctx.flags())
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 raise
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 m1]
1424 drop = [f for f in removed if f in m]
1415 for f in drop:
1425 for f in drop:
1416 del m1[f]
1426 del m[f]
1417 mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
1427 mn = self.manifest.add(m, trp, linkrev,
1418 p2.manifestnode(), (new, drop))
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.vfs.unlink(self.f)
143 if self.releasefn:
146 except OSError:
144 self.releasefn()
147 pass
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: 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