##// END OF EJS Templates
discovery: add new set-based discovery...
Peter Arrenbrecht -
r14164:cb98fed5 default
parent child Browse files
Show More
@@ -0,0 +1,242 b''
1 # dagutil.py - dag utilities for mercurial
2 #
3 # Copyright 2010 Benoit Boissinot <bboissin@gmail.com>
4 # and Peter Arrenbrecht <peter@arrenbrecht.ch>
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 from node import nullrev
10
11
12 class basedag(object):
13 '''generic interface for DAGs
14
15 terms:
16 "ix" (short for index) identifies a nodes internally,
17 "id" identifies one externally.
18
19 All params are ixs unless explicitly suffixed otherwise.
20 Pluralized params are lists or sets.
21 '''
22
23 def __init__(self):
24 self._inverse = None
25
26 def nodeset(self):
27 '''set of all node idxs'''
28 raise NotImplementedError()
29
30 def heads(self):
31 '''list of head ixs'''
32 raise NotImplementedError()
33
34 def parents(self, ix):
35 '''list of parents ixs of ix'''
36 raise NotImplementedError()
37
38 def inverse(self):
39 '''inverse DAG, where parents becomes children, etc.'''
40 raise NotImplementedError()
41
42 def ancestorset(self, starts, stops=None):
43 '''set of all ancestors of starts (incl), but stop walk at stops (excl)'''
44 raise NotImplementedError()
45
46 def descendantset(self, starts, stops=None):
47 '''set of all descendants of starts (incl), but stop walk at stops (excl)'''
48 return self.inverse().ancestorset(starts, stops)
49
50 def headsetofconnecteds(self, ixs):
51 '''subset of connected list of ixs so that no node has a descendant in it
52
53 By "connected list" we mean that if an ancestor and a descendant are in
54 the list, then so is at least one path connecting them.'''
55 raise NotImplementedError()
56
57 def externalize(self, ix):
58 '''return a list of (or set if given a set) of node ids'''
59 return self._externalize(ix)
60
61 def externalizeall(self, ixs):
62 '''return a list of (or set if given a set) of node ids'''
63 ids = self._externalizeall(ixs)
64 if isinstance(ixs, set):
65 return set(ids)
66 return list(ids)
67
68 def internalize(self, id):
69 '''return a list of (or set if given a set) of node ixs'''
70 return self._internalize(id)
71
72 def internalizeall(self, ids, filterunknown=False):
73 '''return a list of (or set if given a set) of node ids'''
74 ixs = self._internalizeall(ids, filterunknown)
75 if isinstance(ids, set):
76 return set(ixs)
77 return list(ixs)
78
79
80 class genericdag(basedag):
81 '''generic implementations for DAGs'''
82
83 def ancestorset(self, starts, stops=None):
84 stops = stops and set(stops) or set()
85 seen = set()
86 pending = list(starts)
87 while pending:
88 n = pending.pop()
89 if n not in seen and n not in stops:
90 seen.add(n)
91 pending.extend(self.parents(n))
92 return seen
93
94 def headsetofconnecteds(self, ixs):
95 hds = set(ixs)
96 if not hds:
97 return hds
98 for n in ixs:
99 for p in self.parents(n):
100 hds.discard(p)
101 assert hds
102 return hds
103
104
105 class revlogbaseddag(basedag):
106 '''generic dag interface to a revlog'''
107
108 def __init__(self, revlog, nodeset):
109 basedag.__init__(self)
110 self._revlog = revlog
111 self._heads = None
112 self._nodeset = nodeset
113
114 def nodeset(self):
115 return self._nodeset
116
117 def heads(self):
118 if self._heads is None:
119 self._heads = self._getheads()
120 return self._heads
121
122 def _externalize(self, ix):
123 return self._revlog.index[ix][7]
124 def _externalizeall(self, ixs):
125 idx = self._revlog.index
126 return [idx[i][7] for i in ixs]
127
128 def _internalize(self, id):
129 ix = self._revlog.rev(id)
130 if ix == nullrev:
131 raise LookupError(id, self._revlog.indexfile, _('nullid'))
132 return ix
133 def _internalizeall(self, ids, filterunknown):
134 rl = self._revlog
135 if filterunknown:
136 return [r for r in map(rl.nodemap.get, ids)
137 if r is not None and r != nullrev]
138 return map(self._internalize, ids)
139
140
141 class revlogdag(revlogbaseddag):
142 '''dag interface to a revlog'''
143
144 def __init__(self, revlog):
145 revlogbaseddag.__init__(self, revlog, set(xrange(len(revlog))))
146
147 def _getheads(self):
148 return [r for r in self._revlog.headrevs() if r != nullrev]
149
150 def parents(self, ix):
151 rlog = self._revlog
152 idx = rlog.index
153 revdata = idx[ix]
154 prev = revdata[5]
155 if prev != nullrev:
156 prev2 = revdata[6]
157 if prev2 == nullrev:
158 return [prev]
159 return [prev, prev2]
160 prev2 = revdata[6]
161 if prev2 != nullrev:
162 return [prev2]
163 return []
164
165 def inverse(self):
166 if self._inverse is None:
167 self._inverse = inverserevlogdag(self)
168 return self._inverse
169
170 def ancestorset(self, starts, stops=None):
171 rlog = self._revlog
172 idx = rlog.index
173 stops = stops and set(stops) or set()
174 seen = set()
175 pending = list(starts)
176 while pending:
177 rev = pending.pop()
178 if rev not in seen and rev not in stops:
179 seen.add(rev)
180 revdata = idx[rev]
181 for i in [5, 6]:
182 prev = revdata[i]
183 if prev != nullrev:
184 pending.append(prev)
185 return seen
186
187 def headsetofconnecteds(self, ixs):
188 if not ixs:
189 return set()
190 rlog = self._revlog
191 idx = rlog.index
192 headrevs = set(ixs)
193 for rev in ixs:
194 revdata = idx[rev]
195 for i in [5, 6]:
196 prev = revdata[i]
197 if prev != nullrev:
198 headrevs.discard(prev)
199 assert headrevs
200 return headrevs
201
202
203 class inverserevlogdag(revlogbaseddag, genericdag):
204 '''inverse of an existing revlog dag; see revlogdag.inverse()'''
205
206 def __init__(self, orig):
207 revlogbaseddag.__init__(self, orig._revlog, orig._nodeset)
208 self._orig = orig
209 self._children = {}
210 self._roots = []
211 self._walkfrom = len(self._revlog) - 1
212
213 def _walkto(self, walkto):
214 rev = self._walkfrom
215 cs = self._children
216 roots = self._roots
217 idx = self._revlog.index
218 while rev >= walkto:
219 data = idx[rev]
220 isroot = True
221 for prev in [data[5], data[6]]: # parent revs
222 if prev != nullrev:
223 cs.setdefault(prev, []).append(rev)
224 isroot = False
225 if isroot:
226 roots.append(rev)
227 rev -= 1
228 self._walkfrom = rev - 1
229
230 def _getheads(self):
231 self._walkto(nullrev)
232 return self._roots
233
234 def parents(self, ix):
235 if ix is None:
236 return []
237 if ix <= self._walkfrom:
238 self._walkto(ix)
239 return self._children.get(ix, [])
240
241 def inverse(self):
242 return self._orig
@@ -0,0 +1,178 b''
1 # setdiscovery.py - improved discovery of common nodeset for mercurial
2 #
3 # Copyright 2010 Benoit Boissinot <bboissin@gmail.com>
4 # and Peter Arrenbrecht <peter@arrenbrecht.ch>
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 from node import nullid
10 from i18n import _
11 import random, collections, util, dagutil
12
13 def _updatesample(dag, nodes, sample, always, quicksamplesize=0):
14 # if nodes is empty we scan the entire graph
15 if nodes:
16 heads = dag.headsetofconnecteds(nodes)
17 else:
18 heads = dag.heads()
19 dist = {}
20 visit = collections.deque(heads)
21 seen = set()
22 factor = 1
23 while visit:
24 curr = visit.popleft()
25 if curr in seen:
26 continue
27 d = dist.setdefault(curr, 1)
28 if d > factor:
29 factor *= 2
30 if d == factor:
31 if curr not in always: # need this check for the early exit below
32 sample.add(curr)
33 if quicksamplesize and (len(sample) >= quicksamplesize):
34 return
35 seen.add(curr)
36 for p in dag.parents(curr):
37 if not nodes or p in nodes:
38 dist.setdefault(p, d + 1)
39 visit.append(p)
40
41 def _setupsample(dag, nodes, size):
42 if len(nodes) <= size:
43 return set(nodes), None, 0
44 always = set(dag.heads())
45 desiredlen = size - len(always)
46 if desiredlen <= 0:
47 # This could be bad if there are very many heads, all unknown to the
48 # server. We're counting on long request support here.
49 return always, None, desiredlen
50 return always, set(), desiredlen
51
52 def _takequicksample(dag, nodes, size, initial):
53 always, sample, desiredlen = _setupsample(dag, nodes, size)
54 if sample is None:
55 return always
56 if initial:
57 fromset = None
58 else:
59 fromset = nodes
60 _updatesample(dag, fromset, sample, always, quicksamplesize=desiredlen)
61 sample.update(always)
62 return sample
63
64 def _takefullsample(dag, nodes, size):
65 always, sample, desiredlen = _setupsample(dag, nodes, size)
66 if sample is None:
67 return always
68 # update from heads
69 _updatesample(dag, nodes, sample, always)
70 # update from roots
71 _updatesample(dag.inverse(), nodes, sample, always)
72 assert sample
73 if len(sample) > desiredlen:
74 sample = set(random.sample(sample, desiredlen))
75 elif len(sample) < desiredlen:
76 more = desiredlen - len(sample)
77 sample.update(random.sample(list(nodes - sample - always), more))
78 sample.update(always)
79 return sample
80
81 def findcommonheads(ui, local, remote,
82 initialsamplesize=100,
83 fullsamplesize=200,
84 abortwhenunrelated=True):
85 '''Return a tuple (common, anyincoming, remoteheads) used to identify missing
86 nodes from or in remote.
87
88 shortcutlocal determines whether we try use direct access to localrepo if
89 remote is actually local.
90 '''
91 roundtrips = 0
92 cl = local.changelog
93 dag = dagutil.revlogdag(cl)
94 nodes = dag.nodeset()
95
96 # early exit if we know all the specified server heads already
97 ui.debug("query 1; heads\n")
98 roundtrips += 1
99 srvheadhashes = remote.heads()
100
101 ## TODO We might want to request an additional random sample of the server's
102 ## nodes batched with the heads query here.
103
104 if cl.tip() == nullid:
105 if srvheadhashes != [nullid]:
106 return [nullid], True, srvheadhashes
107 return [nullid], False, []
108
109 # start actual discovery (we note this before the next "if" for compatibility
110 # reasons)
111 ui.status(_("searching for changes\n"))
112
113 srvheads = dag.internalizeall(srvheadhashes, filterunknown=True)
114 if len(srvheads) == len(srvheadhashes):
115 ui.note("all remote heads known locally\n")
116 return (srvheadhashes, False, srvheadhashes,)
117
118 # full blown discovery
119 undecided = nodes # own nodes where I don't know if the server knows them
120 common = set() # own nodes I know we both know
121 missing = set() # own nodes I know the server lacks
122
123 # treat remote heads as a first implicit sample response
124 common.update(dag.ancestorset(srvheads))
125 undecided.difference_update(common)
126 # use cheapish initial sample
127 if common:
128 ui.debug("taking initial sample\n")
129 sample = _takefullsample(dag, undecided, size=fullsamplesize)
130 else:
131 ui.debug("taking quick initial sample\n")
132 sample = _takequicksample(dag, nodes, size=initialsamplesize,
133 initial=True)
134
135 roundtrips += 1
136 ui.progress(_('searching'), roundtrips, unit=_('queries'))
137 ui.debug("query %i; still undecided: %i, sample size is: %i\n"
138 % (roundtrips, len(undecided), len(sample)))
139 # indices between sample and externalized version must match
140 sample = list(sample)
141 yesno = remote.known(dag.externalizeall(sample))
142
143 while undecided:
144 commoninsample = set(n for i, n in enumerate(sample) if yesno[i])
145 common.update(dag.ancestorset(commoninsample, common))
146
147 missinginsample = [n for i, n in enumerate(sample) if not yesno[i]]
148 missing.update(dag.descendantset(missinginsample, missing))
149
150 undecided.difference_update(missing)
151 undecided.difference_update(common)
152
153 if not undecided:
154 break
155
156 ui.note("sampling from both directions\n")
157 sample = _takefullsample(dag, undecided, size=fullsamplesize)
158
159 roundtrips += 1
160 ui.progress(_('searching'), roundtrips, unit=_('queries'))
161 ui.debug("query %i; still undecided: %i, sample size is: %i\n"
162 % (roundtrips, len(undecided), len(sample)))
163 # indices between sample and externalized version must match
164 sample = list(sample)
165 yesno = remote.known(dag.externalizeall(sample))
166
167 result = dag.headsetofconnecteds(common)
168 ui.progress(_('searching'), None)
169 ui.debug("%d total queries\n" % roundtrips)
170
171 if not result and srvheadhashes != [nullid]:
172 if abortwhenunrelated:
173 raise util.Abort(_("repository is unrelated"))
174 else:
175 ui.warn(_("warning: repository is unrelated\n"))
176 return (set([nullid]), True, srvheadhashes,)
177
178 return (dag.externalizeall(result), True, srvheadhashes,)
@@ -0,0 +1,271 b''
1
2 Function to test discovery between two repos in both directions, using both the local shortcut
3 (which is currently not activated by default) and the full remotable protocol:
4
5 $ testdesc() { # revs_a, revs_b, dagdesc
6 > if [ -e foo ]; then rm -rf foo; fi
7 > hg init foo
8 > cd foo
9 > hg debugbuilddag "$3"
10 > hg clone . a $1 --quiet
11 > hg clone . b $2 --quiet
12 > echo
13 > echo "% -- a -> b tree"
14 > hg -R a debugdiscovery b --verbose --old
15 > echo
16 > echo "% -- a -> b set"
17 > hg -R a debugdiscovery b --verbose --debug
18 > echo
19 > echo "% -- b -> a tree"
20 > hg -R b debugdiscovery a --verbose --old
21 > echo
22 > echo "% -- b -> a set"
23 > hg -R b debugdiscovery a --verbose --debug
24 > cd ..
25 > }
26
27
28 Small superset:
29
30 $ testdesc '-ra1 -ra2' '-rb1 -rb2 -rb3' '
31 > +2:f +1:a1:b1
32 > <f +4 :a2
33 > +5 :b2
34 > <f +3 :b3'
35
36 % -- a -> b tree
37 comparing with b
38 searching for changes
39 unpruned common: b5714e113bc0 66f7d451a68b 01241442b3c2
40 common heads: b5714e113bc0 01241442b3c2
41 local is subset
42
43 % -- a -> b set
44 comparing with b
45 query 1; heads
46 searching for changes
47 taking initial sample
48 searching: 2 queries
49 query 2; still undecided: 4, sample size is: 4
50 2 total queries
51 common heads: b5714e113bc0 01241442b3c2
52 local is subset
53
54 % -- b -> a tree
55 comparing with a
56 searching for changes
57 unpruned common: b5714e113bc0 01241442b3c2
58 common heads: b5714e113bc0 01241442b3c2
59 remote is subset
60
61 % -- b -> a set
62 comparing with a
63 query 1; heads
64 searching for changes
65 all remote heads known locally
66 common heads: b5714e113bc0 01241442b3c2
67 remote is subset
68
69
70 Many new:
71
72 $ testdesc '-ra1 -ra2' '-rb' '
73 > +2:f +3:a1 +3:b
74 > <f +30 :a2'
75
76 % -- a -> b tree
77 comparing with b
78 searching for changes
79 unpruned common: bebd167eb94d
80 common heads: bebd167eb94d
81
82 % -- a -> b set
83 comparing with b
84 query 1; heads
85 searching for changes
86 taking quick initial sample
87 searching: 2 queries
88 query 2; still undecided: 35, sample size is: 35
89 2 total queries
90 common heads: bebd167eb94d
91
92 % -- b -> a tree
93 comparing with a
94 searching for changes
95 unpruned common: bebd167eb94d 66f7d451a68b
96 common heads: bebd167eb94d
97
98 % -- b -> a set
99 comparing with a
100 query 1; heads
101 searching for changes
102 taking initial sample
103 searching: 2 queries
104 query 2; still undecided: 3, sample size is: 3
105 2 total queries
106 common heads: bebd167eb94d
107
108
109 Both sides many new with stub:
110
111 $ testdesc '-ra1 -ra2' '-rb' '
112 > +2:f +2:a1 +30 :b
113 > <f +30 :a2'
114
115 % -- a -> b tree
116 comparing with b
117 searching for changes
118 unpruned common: 2dc09a01254d
119 common heads: 2dc09a01254d
120
121 % -- a -> b set
122 comparing with b
123 query 1; heads
124 searching for changes
125 taking quick initial sample
126 searching: 2 queries
127 query 2; still undecided: 34, sample size is: 34
128 2 total queries
129 common heads: 2dc09a01254d
130
131 % -- b -> a tree
132 comparing with a
133 searching for changes
134 unpruned common: 66f7d451a68b 2dc09a01254d
135 common heads: 2dc09a01254d
136
137 % -- b -> a set
138 comparing with a
139 query 1; heads
140 searching for changes
141 taking initial sample
142 searching: 2 queries
143 query 2; still undecided: 30, sample size is: 30
144 2 total queries
145 common heads: 2dc09a01254d
146
147
148 Both many new:
149
150 $ testdesc '-ra' '-rb' '
151 > +2:f +30 :b
152 > <f +30 :a'
153
154 % -- a -> b tree
155 comparing with b
156 searching for changes
157 unpruned common: 66f7d451a68b
158 common heads: 66f7d451a68b
159
160 % -- a -> b set
161 comparing with b
162 query 1; heads
163 searching for changes
164 taking quick initial sample
165 searching: 2 queries
166 query 2; still undecided: 32, sample size is: 32
167 2 total queries
168 common heads: 66f7d451a68b
169
170 % -- b -> a tree
171 comparing with a
172 searching for changes
173 unpruned common: 66f7d451a68b
174 common heads: 66f7d451a68b
175
176 % -- b -> a set
177 comparing with a
178 query 1; heads
179 searching for changes
180 taking quick initial sample
181 searching: 2 queries
182 query 2; still undecided: 32, sample size is: 32
183 2 total queries
184 common heads: 66f7d451a68b
185
186
187 Both many new skewed:
188
189 $ testdesc '-ra' '-rb' '
190 > +2:f +30 :b
191 > <f +50 :a'
192
193 % -- a -> b tree
194 comparing with b
195 searching for changes
196 unpruned common: 66f7d451a68b
197 common heads: 66f7d451a68b
198
199 % -- a -> b set
200 comparing with b
201 query 1; heads
202 searching for changes
203 taking quick initial sample
204 searching: 2 queries
205 query 2; still undecided: 52, sample size is: 52
206 2 total queries
207 common heads: 66f7d451a68b
208
209 % -- b -> a tree
210 comparing with a
211 searching for changes
212 unpruned common: 66f7d451a68b
213 common heads: 66f7d451a68b
214
215 % -- b -> a set
216 comparing with a
217 query 1; heads
218 searching for changes
219 taking quick initial sample
220 searching: 2 queries
221 query 2; still undecided: 32, sample size is: 32
222 2 total queries
223 common heads: 66f7d451a68b
224
225
226 Both many new on top of long history:
227
228 $ testdesc '-ra' '-rb' '
229 > +1000:f +30 :b
230 > <f +50 :a'
231
232 % -- a -> b tree
233 comparing with b
234 searching for changes
235 unpruned common: 7ead0cba2838
236 common heads: 7ead0cba2838
237
238 % -- a -> b set
239 comparing with b
240 query 1; heads
241 searching for changes
242 taking quick initial sample
243 searching: 2 queries
244 query 2; still undecided: 1050, sample size is: 11
245 sampling from both directions
246 searching: 3 queries
247 query 3; still undecided: 31, sample size is: 31
248 3 total queries
249 common heads: 7ead0cba2838
250
251 % -- b -> a tree
252 comparing with a
253 searching for changes
254 unpruned common: 7ead0cba2838
255 common heads: 7ead0cba2838
256
257 % -- b -> a set
258 comparing with a
259 query 1; heads
260 searching for changes
261 taking quick initial sample
262 searching: 2 queries
263 query 2; still undecided: 1030, sample size is: 11
264 sampling from both directions
265 searching: 3 queries
266 query 3; still undecided: 16, sample size is: 16
267 3 total queries
268 common heads: 7ead0cba2838
269
270
271
@@ -1,4901 +1,4969 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import hex, bin, nullid, nullrev, short
9 9 from lock import release
10 10 from i18n import _, gettext
11 11 import os, re, sys, difflib, time, tempfile
12 12 import hg, scmutil, util, revlog, extensions, copies, error, bookmarks
13 13 import patch, help, url, encoding, templatekw, discovery
14 14 import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server
15 15 import merge as mergemod
16 16 import minirst, revset, templatefilters
17 17 import dagparser, context, simplemerge
18 import random, setdiscovery, treediscovery, dagutil
18 19
19 20 # Commands start here, listed alphabetically
20 21
21 22 def add(ui, repo, *pats, **opts):
22 23 """add the specified files on the next commit
23 24
24 25 Schedule files to be version controlled and added to the
25 26 repository.
26 27
27 28 The files will be added to the repository at the next commit. To
28 29 undo an add before that, see :hg:`forget`.
29 30
30 31 If no names are given, add all files to the repository.
31 32
32 33 .. container:: verbose
33 34
34 35 An example showing how new (unknown) files are added
35 36 automatically by :hg:`add`::
36 37
37 38 $ ls
38 39 foo.c
39 40 $ hg status
40 41 ? foo.c
41 42 $ hg add
42 43 adding foo.c
43 44 $ hg status
44 45 A foo.c
45 46
46 47 Returns 0 if all files are successfully added.
47 48 """
48 49
49 50 m = cmdutil.match(repo, pats, opts)
50 51 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
51 52 opts.get('subrepos'), prefix="")
52 53 return rejected and 1 or 0
53 54
54 55 def addremove(ui, repo, *pats, **opts):
55 56 """add all new files, delete all missing files
56 57
57 58 Add all new files and remove all missing files from the
58 59 repository.
59 60
60 61 New files are ignored if they match any of the patterns in
61 62 ``.hgignore``. As with add, these changes take effect at the next
62 63 commit.
63 64
64 65 Use the -s/--similarity option to detect renamed files. With a
65 66 parameter greater than 0, this compares every removed file with
66 67 every added file and records those similar enough as renames. This
67 68 option takes a percentage between 0 (disabled) and 100 (files must
68 69 be identical) as its parameter. Detecting renamed files this way
69 70 can be expensive. After using this option, :hg:`status -C` can be
70 71 used to check which files were identified as moved or renamed.
71 72
72 73 Returns 0 if all files are successfully added.
73 74 """
74 75 try:
75 76 sim = float(opts.get('similarity') or 100)
76 77 except ValueError:
77 78 raise util.Abort(_('similarity must be a number'))
78 79 if sim < 0 or sim > 100:
79 80 raise util.Abort(_('similarity must be between 0 and 100'))
80 81 return cmdutil.addremove(repo, pats, opts, similarity=sim / 100.0)
81 82
82 83 def annotate(ui, repo, *pats, **opts):
83 84 """show changeset information by line for each file
84 85
85 86 List changes in files, showing the revision id responsible for
86 87 each line
87 88
88 89 This command is useful for discovering when a change was made and
89 90 by whom.
90 91
91 92 Without the -a/--text option, annotate will avoid processing files
92 93 it detects as binary. With -a, annotate will annotate the file
93 94 anyway, although the results will probably be neither useful
94 95 nor desirable.
95 96
96 97 Returns 0 on success.
97 98 """
98 99 if opts.get('follow'):
99 100 # --follow is deprecated and now just an alias for -f/--file
100 101 # to mimic the behavior of Mercurial before version 1.5
101 102 opts['file'] = 1
102 103
103 104 datefunc = ui.quiet and util.shortdate or util.datestr
104 105 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
105 106
106 107 if not pats:
107 108 raise util.Abort(_('at least one filename or pattern is required'))
108 109
109 110 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
110 111 ('number', lambda x: str(x[0].rev())),
111 112 ('changeset', lambda x: short(x[0].node())),
112 113 ('date', getdate),
113 114 ('file', lambda x: x[0].path()),
114 115 ]
115 116
116 117 if (not opts.get('user') and not opts.get('changeset')
117 118 and not opts.get('date') and not opts.get('file')):
118 119 opts['number'] = 1
119 120
120 121 linenumber = opts.get('line_number') is not None
121 122 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
122 123 raise util.Abort(_('at least one of -n/-c is required for -l'))
123 124
124 125 funcmap = [func for op, func in opmap if opts.get(op)]
125 126 if linenumber:
126 127 lastfunc = funcmap[-1]
127 128 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
128 129
129 130 def bad(x, y):
130 131 raise util.Abort("%s: %s" % (x, y))
131 132
132 133 ctx = cmdutil.revsingle(repo, opts.get('rev'))
133 134 m = cmdutil.match(repo, pats, opts)
134 135 m.bad = bad
135 136 follow = not opts.get('no_follow')
136 137 for abs in ctx.walk(m):
137 138 fctx = ctx[abs]
138 139 if not opts.get('text') and util.binary(fctx.data()):
139 140 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
140 141 continue
141 142
142 143 lines = fctx.annotate(follow=follow, linenumber=linenumber)
143 144 pieces = []
144 145
145 146 for f in funcmap:
146 147 l = [f(n) for n, dummy in lines]
147 148 if l:
148 149 sized = [(x, encoding.colwidth(x)) for x in l]
149 150 ml = max([w for x, w in sized])
150 151 pieces.append(["%s%s" % (' ' * (ml - w), x) for x, w in sized])
151 152
152 153 if pieces:
153 154 for p, l in zip(zip(*pieces), lines):
154 155 ui.write("%s: %s" % (" ".join(p), l[1]))
155 156
156 157 def archive(ui, repo, dest, **opts):
157 158 '''create an unversioned archive of a repository revision
158 159
159 160 By default, the revision used is the parent of the working
160 161 directory; use -r/--rev to specify a different revision.
161 162
162 163 The archive type is automatically detected based on file
163 164 extension (or override using -t/--type).
164 165
165 166 Valid types are:
166 167
167 168 :``files``: a directory full of files (default)
168 169 :``tar``: tar archive, uncompressed
169 170 :``tbz2``: tar archive, compressed using bzip2
170 171 :``tgz``: tar archive, compressed using gzip
171 172 :``uzip``: zip archive, uncompressed
172 173 :``zip``: zip archive, compressed using deflate
173 174
174 175 The exact name of the destination archive or directory is given
175 176 using a format string; see :hg:`help export` for details.
176 177
177 178 Each member added to an archive file has a directory prefix
178 179 prepended. Use -p/--prefix to specify a format string for the
179 180 prefix. The default is the basename of the archive, with suffixes
180 181 removed.
181 182
182 183 Returns 0 on success.
183 184 '''
184 185
185 186 ctx = cmdutil.revsingle(repo, opts.get('rev'))
186 187 if not ctx:
187 188 raise util.Abort(_('no working directory: please specify a revision'))
188 189 node = ctx.node()
189 190 dest = cmdutil.make_filename(repo, dest, node)
190 191 if os.path.realpath(dest) == repo.root:
191 192 raise util.Abort(_('repository root cannot be destination'))
192 193
193 194 kind = opts.get('type') or archival.guesskind(dest) or 'files'
194 195 prefix = opts.get('prefix')
195 196
196 197 if dest == '-':
197 198 if kind == 'files':
198 199 raise util.Abort(_('cannot archive plain files to stdout'))
199 200 dest = sys.stdout
200 201 if not prefix:
201 202 prefix = os.path.basename(repo.root) + '-%h'
202 203
203 204 prefix = cmdutil.make_filename(repo, prefix, node)
204 205 matchfn = cmdutil.match(repo, [], opts)
205 206 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
206 207 matchfn, prefix, subrepos=opts.get('subrepos'))
207 208
208 209 def backout(ui, repo, node=None, rev=None, **opts):
209 210 '''reverse effect of earlier changeset
210 211
211 212 Prepare a new changeset with the effect of REV undone in the
212 213 current working directory.
213 214
214 215 If REV is the parent of the working directory, then this new changeset
215 216 is committed automatically. Otherwise, hg needs to merge the
216 217 changes and the merged result is left uncommitted.
217 218
218 219 By default, the pending changeset will have one parent,
219 220 maintaining a linear history. With --merge, the pending changeset
220 221 will instead have two parents: the old parent of the working
221 222 directory and a new child of REV that simply undoes REV.
222 223
223 224 Before version 1.7, the behavior without --merge was equivalent to
224 225 specifying --merge followed by :hg:`update --clean .` to cancel
225 226 the merge and leave the child of REV as a head to be merged
226 227 separately.
227 228
228 229 See :hg:`help dates` for a list of formats valid for -d/--date.
229 230
230 231 Returns 0 on success.
231 232 '''
232 233 if rev and node:
233 234 raise util.Abort(_("please specify just one revision"))
234 235
235 236 if not rev:
236 237 rev = node
237 238
238 239 if not rev:
239 240 raise util.Abort(_("please specify a revision to backout"))
240 241
241 242 date = opts.get('date')
242 243 if date:
243 244 opts['date'] = util.parsedate(date)
244 245
245 246 cmdutil.bail_if_changed(repo)
246 247 node = cmdutil.revsingle(repo, rev).node()
247 248
248 249 op1, op2 = repo.dirstate.parents()
249 250 a = repo.changelog.ancestor(op1, node)
250 251 if a != node:
251 252 raise util.Abort(_('cannot backout change on a different branch'))
252 253
253 254 p1, p2 = repo.changelog.parents(node)
254 255 if p1 == nullid:
255 256 raise util.Abort(_('cannot backout a change with no parents'))
256 257 if p2 != nullid:
257 258 if not opts.get('parent'):
258 259 raise util.Abort(_('cannot backout a merge changeset without '
259 260 '--parent'))
260 261 p = repo.lookup(opts['parent'])
261 262 if p not in (p1, p2):
262 263 raise util.Abort(_('%s is not a parent of %s') %
263 264 (short(p), short(node)))
264 265 parent = p
265 266 else:
266 267 if opts.get('parent'):
267 268 raise util.Abort(_('cannot use --parent on non-merge changeset'))
268 269 parent = p1
269 270
270 271 # the backout should appear on the same branch
271 272 branch = repo.dirstate.branch()
272 273 hg.clean(repo, node, show_stats=False)
273 274 repo.dirstate.setbranch(branch)
274 275 revert_opts = opts.copy()
275 276 revert_opts['date'] = None
276 277 revert_opts['all'] = True
277 278 revert_opts['rev'] = hex(parent)
278 279 revert_opts['no_backup'] = None
279 280 revert(ui, repo, **revert_opts)
280 281 if not opts.get('merge') and op1 != node:
281 282 try:
282 283 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
283 284 return hg.update(repo, op1)
284 285 finally:
285 286 ui.setconfig('ui', 'forcemerge', '')
286 287
287 288 commit_opts = opts.copy()
288 289 commit_opts['addremove'] = False
289 290 if not commit_opts['message'] and not commit_opts['logfile']:
290 291 # we don't translate commit messages
291 292 commit_opts['message'] = "Backed out changeset %s" % short(node)
292 293 commit_opts['force_editor'] = True
293 294 commit(ui, repo, **commit_opts)
294 295 def nice(node):
295 296 return '%d:%s' % (repo.changelog.rev(node), short(node))
296 297 ui.status(_('changeset %s backs out changeset %s\n') %
297 298 (nice(repo.changelog.tip()), nice(node)))
298 299 if opts.get('merge') and op1 != node:
299 300 hg.clean(repo, op1, show_stats=False)
300 301 ui.status(_('merging with changeset %s\n')
301 302 % nice(repo.changelog.tip()))
302 303 try:
303 304 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
304 305 return hg.merge(repo, hex(repo.changelog.tip()))
305 306 finally:
306 307 ui.setconfig('ui', 'forcemerge', '')
307 308 return 0
308 309
309 310 def bisect(ui, repo, rev=None, extra=None, command=None,
310 311 reset=None, good=None, bad=None, skip=None, extend=None,
311 312 noupdate=None):
312 313 """subdivision search of changesets
313 314
314 315 This command helps to find changesets which introduce problems. To
315 316 use, mark the earliest changeset you know exhibits the problem as
316 317 bad, then mark the latest changeset which is free from the problem
317 318 as good. Bisect will update your working directory to a revision
318 319 for testing (unless the -U/--noupdate option is specified). Once
319 320 you have performed tests, mark the working directory as good or
320 321 bad, and bisect will either update to another candidate changeset
321 322 or announce that it has found the bad revision.
322 323
323 324 As a shortcut, you can also use the revision argument to mark a
324 325 revision as good or bad without checking it out first.
325 326
326 327 If you supply a command, it will be used for automatic bisection.
327 328 Its exit status will be used to mark revisions as good or bad:
328 329 status 0 means good, 125 means to skip the revision, 127
329 330 (command not found) will abort the bisection, and any other
330 331 non-zero exit status means the revision is bad.
331 332
332 333 Returns 0 on success.
333 334 """
334 335 def extendbisectrange(nodes, good):
335 336 # bisect is incomplete when it ends on a merge node and
336 337 # one of the parent was not checked.
337 338 parents = repo[nodes[0]].parents()
338 339 if len(parents) > 1:
339 340 side = good and state['bad'] or state['good']
340 341 num = len(set(i.node() for i in parents) & set(side))
341 342 if num == 1:
342 343 return parents[0].ancestor(parents[1])
343 344 return None
344 345
345 346 def print_result(nodes, good):
346 347 displayer = cmdutil.show_changeset(ui, repo, {})
347 348 if len(nodes) == 1:
348 349 # narrowed it down to a single revision
349 350 if good:
350 351 ui.write(_("The first good revision is:\n"))
351 352 else:
352 353 ui.write(_("The first bad revision is:\n"))
353 354 displayer.show(repo[nodes[0]])
354 355 extendnode = extendbisectrange(nodes, good)
355 356 if extendnode is not None:
356 357 ui.write(_('Not all ancestors of this changeset have been'
357 358 ' checked.\nUse bisect --extend to continue the '
358 359 'bisection from\nthe common ancestor, %s.\n')
359 360 % extendnode)
360 361 else:
361 362 # multiple possible revisions
362 363 if good:
363 364 ui.write(_("Due to skipped revisions, the first "
364 365 "good revision could be any of:\n"))
365 366 else:
366 367 ui.write(_("Due to skipped revisions, the first "
367 368 "bad revision could be any of:\n"))
368 369 for n in nodes:
369 370 displayer.show(repo[n])
370 371 displayer.close()
371 372
372 373 def check_state(state, interactive=True):
373 374 if not state['good'] or not state['bad']:
374 375 if (good or bad or skip or reset) and interactive:
375 376 return
376 377 if not state['good']:
377 378 raise util.Abort(_('cannot bisect (no known good revisions)'))
378 379 else:
379 380 raise util.Abort(_('cannot bisect (no known bad revisions)'))
380 381 return True
381 382
382 383 # backward compatibility
383 384 if rev in "good bad reset init".split():
384 385 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
385 386 cmd, rev, extra = rev, extra, None
386 387 if cmd == "good":
387 388 good = True
388 389 elif cmd == "bad":
389 390 bad = True
390 391 else:
391 392 reset = True
392 393 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
393 394 raise util.Abort(_('incompatible arguments'))
394 395
395 396 if reset:
396 397 p = repo.join("bisect.state")
397 398 if os.path.exists(p):
398 399 os.unlink(p)
399 400 return
400 401
401 402 state = hbisect.load_state(repo)
402 403
403 404 if command:
404 405 changesets = 1
405 406 try:
406 407 while changesets:
407 408 # update state
408 409 status = util.system(command)
409 410 if status == 125:
410 411 transition = "skip"
411 412 elif status == 0:
412 413 transition = "good"
413 414 # status < 0 means process was killed
414 415 elif status == 127:
415 416 raise util.Abort(_("failed to execute %s") % command)
416 417 elif status < 0:
417 418 raise util.Abort(_("%s killed") % command)
418 419 else:
419 420 transition = "bad"
420 421 ctx = cmdutil.revsingle(repo, rev)
421 422 rev = None # clear for future iterations
422 423 state[transition].append(ctx.node())
423 424 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
424 425 check_state(state, interactive=False)
425 426 # bisect
426 427 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
427 428 # update to next check
428 429 cmdutil.bail_if_changed(repo)
429 430 hg.clean(repo, nodes[0], show_stats=False)
430 431 finally:
431 432 hbisect.save_state(repo, state)
432 433 print_result(nodes, good)
433 434 return
434 435
435 436 # update state
436 437
437 438 if rev:
438 439 nodes = [repo.lookup(i) for i in cmdutil.revrange(repo, [rev])]
439 440 else:
440 441 nodes = [repo.lookup('.')]
441 442
442 443 if good or bad or skip:
443 444 if good:
444 445 state['good'] += nodes
445 446 elif bad:
446 447 state['bad'] += nodes
447 448 elif skip:
448 449 state['skip'] += nodes
449 450 hbisect.save_state(repo, state)
450 451
451 452 if not check_state(state):
452 453 return
453 454
454 455 # actually bisect
455 456 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
456 457 if extend:
457 458 if not changesets:
458 459 extendnode = extendbisectrange(nodes, good)
459 460 if extendnode is not None:
460 461 ui.write(_("Extending search to changeset %d:%s\n"
461 462 % (extendnode.rev(), extendnode)))
462 463 if noupdate:
463 464 return
464 465 cmdutil.bail_if_changed(repo)
465 466 return hg.clean(repo, extendnode.node())
466 467 raise util.Abort(_("nothing to extend"))
467 468
468 469 if changesets == 0:
469 470 print_result(nodes, good)
470 471 else:
471 472 assert len(nodes) == 1 # only a single node can be tested next
472 473 node = nodes[0]
473 474 # compute the approximate number of remaining tests
474 475 tests, size = 0, 2
475 476 while size <= changesets:
476 477 tests, size = tests + 1, size * 2
477 478 rev = repo.changelog.rev(node)
478 479 ui.write(_("Testing changeset %d:%s "
479 480 "(%d changesets remaining, ~%d tests)\n")
480 481 % (rev, short(node), changesets, tests))
481 482 if not noupdate:
482 483 cmdutil.bail_if_changed(repo)
483 484 return hg.clean(repo, node)
484 485
485 486 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
486 487 '''track a line of development with movable markers
487 488
488 489 Bookmarks are pointers to certain commits that move when
489 490 committing. Bookmarks are local. They can be renamed, copied and
490 491 deleted. It is possible to use bookmark names in :hg:`merge` and
491 492 :hg:`update` to merge and update respectively to a given bookmark.
492 493
493 494 You can use :hg:`bookmark NAME` to set a bookmark on the working
494 495 directory's parent revision with the given name. If you specify
495 496 a revision using -r REV (where REV may be an existing bookmark),
496 497 the bookmark is assigned to that revision.
497 498
498 499 Bookmarks can be pushed and pulled between repositories (see :hg:`help
499 500 push` and :hg:`help pull`). This requires both the local and remote
500 501 repositories to support bookmarks. For versions prior to 1.8, this means
501 502 the bookmarks extension must be enabled.
502 503 '''
503 504 hexfn = ui.debugflag and hex or short
504 505 marks = repo._bookmarks
505 506 cur = repo.changectx('.').node()
506 507
507 508 if rename:
508 509 if rename not in marks:
509 510 raise util.Abort(_("bookmark '%s' does not exist") % rename)
510 511 if mark in marks and not force:
511 512 raise util.Abort(_("bookmark '%s' already exists "
512 513 "(use -f to force)") % mark)
513 514 if mark is None:
514 515 raise util.Abort(_("new bookmark name required"))
515 516 marks[mark] = marks[rename]
516 517 if repo._bookmarkcurrent == rename:
517 518 bookmarks.setcurrent(repo, mark)
518 519 del marks[rename]
519 520 bookmarks.write(repo)
520 521 return
521 522
522 523 if delete:
523 524 if mark is None:
524 525 raise util.Abort(_("bookmark name required"))
525 526 if mark not in marks:
526 527 raise util.Abort(_("bookmark '%s' does not exist") % mark)
527 528 if mark == repo._bookmarkcurrent:
528 529 bookmarks.setcurrent(repo, None)
529 530 del marks[mark]
530 531 bookmarks.write(repo)
531 532 return
532 533
533 534 if mark is not None:
534 535 if "\n" in mark:
535 536 raise util.Abort(_("bookmark name cannot contain newlines"))
536 537 mark = mark.strip()
537 538 if not mark:
538 539 raise util.Abort(_("bookmark names cannot consist entirely of "
539 540 "whitespace"))
540 541 if mark in marks and not force:
541 542 raise util.Abort(_("bookmark '%s' already exists "
542 543 "(use -f to force)") % mark)
543 544 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
544 545 and not force):
545 546 raise util.Abort(
546 547 _("a bookmark cannot have the name of an existing branch"))
547 548 if rev:
548 549 marks[mark] = repo.lookup(rev)
549 550 else:
550 551 marks[mark] = repo.changectx('.').node()
551 552 if repo.changectx('.').node() == marks[mark]:
552 553 bookmarks.setcurrent(repo, mark)
553 554 bookmarks.write(repo)
554 555 return
555 556
556 557 if mark is None:
557 558 if rev:
558 559 raise util.Abort(_("bookmark name required"))
559 560 if len(marks) == 0:
560 561 ui.status(_("no bookmarks set\n"))
561 562 else:
562 563 for bmark, n in sorted(marks.iteritems()):
563 564 current = repo._bookmarkcurrent
564 565 if bmark == current and n == cur:
565 566 prefix, label = '*', 'bookmarks.current'
566 567 else:
567 568 prefix, label = ' ', ''
568 569
569 570 if ui.quiet:
570 571 ui.write("%s\n" % bmark, label=label)
571 572 else:
572 573 ui.write(" %s %-25s %d:%s\n" % (
573 574 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
574 575 label=label)
575 576 return
576 577
577 578 def branch(ui, repo, label=None, **opts):
578 579 """set or show the current branch name
579 580
580 581 With no argument, show the current branch name. With one argument,
581 582 set the working directory branch name (the branch will not exist
582 583 in the repository until the next commit). Standard practice
583 584 recommends that primary development take place on the 'default'
584 585 branch.
585 586
586 587 Unless -f/--force is specified, branch will not let you set a
587 588 branch name that already exists, even if it's inactive.
588 589
589 590 Use -C/--clean to reset the working directory branch to that of
590 591 the parent of the working directory, negating a previous branch
591 592 change.
592 593
593 594 Use the command :hg:`update` to switch to an existing branch. Use
594 595 :hg:`commit --close-branch` to mark this branch as closed.
595 596
596 597 Returns 0 on success.
597 598 """
598 599
599 600 if opts.get('clean'):
600 601 label = repo[None].p1().branch()
601 602 repo.dirstate.setbranch(label)
602 603 ui.status(_('reset working directory to branch %s\n') % label)
603 604 elif label:
604 605 if not opts.get('force') and label in repo.branchtags():
605 606 if label not in [p.branch() for p in repo.parents()]:
606 607 raise util.Abort(_('a branch of the same name already exists'
607 608 " (use 'hg update' to switch to it)"))
608 609 repo.dirstate.setbranch(label)
609 610 ui.status(_('marked working directory as branch %s\n') % label)
610 611 else:
611 612 ui.write("%s\n" % repo.dirstate.branch())
612 613
613 614 def branches(ui, repo, active=False, closed=False):
614 615 """list repository named branches
615 616
616 617 List the repository's named branches, indicating which ones are
617 618 inactive. If -c/--closed is specified, also list branches which have
618 619 been marked closed (see :hg:`commit --close-branch`).
619 620
620 621 If -a/--active is specified, only show active branches. A branch
621 622 is considered active if it contains repository heads.
622 623
623 624 Use the command :hg:`update` to switch to an existing branch.
624 625
625 626 Returns 0.
626 627 """
627 628
628 629 hexfunc = ui.debugflag and hex or short
629 630 activebranches = [repo[n].branch() for n in repo.heads()]
630 631 def testactive(tag, node):
631 632 realhead = tag in activebranches
632 633 open = node in repo.branchheads(tag, closed=False)
633 634 return realhead and open
634 635 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
635 636 for tag, node in repo.branchtags().items()],
636 637 reverse=True)
637 638
638 639 for isactive, node, tag in branches:
639 640 if (not active) or isactive:
640 641 if ui.quiet:
641 642 ui.write("%s\n" % tag)
642 643 else:
643 644 hn = repo.lookup(node)
644 645 if isactive:
645 646 label = 'branches.active'
646 647 notice = ''
647 648 elif hn not in repo.branchheads(tag, closed=False):
648 649 if not closed:
649 650 continue
650 651 label = 'branches.closed'
651 652 notice = _(' (closed)')
652 653 else:
653 654 label = 'branches.inactive'
654 655 notice = _(' (inactive)')
655 656 if tag == repo.dirstate.branch():
656 657 label = 'branches.current'
657 658 rev = str(node).rjust(31 - encoding.colwidth(tag))
658 659 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
659 660 tag = ui.label(tag, label)
660 661 ui.write("%s %s%s\n" % (tag, rev, notice))
661 662
662 663 def bundle(ui, repo, fname, dest=None, **opts):
663 664 """create a changegroup file
664 665
665 666 Generate a compressed changegroup file collecting changesets not
666 667 known to be in another repository.
667 668
668 669 If you omit the destination repository, then hg assumes the
669 670 destination will have all the nodes you specify with --base
670 671 parameters. To create a bundle containing all changesets, use
671 672 -a/--all (or --base null).
672 673
673 674 You can change compression method with the -t/--type option.
674 675 The available compression methods are: none, bzip2, and
675 676 gzip (by default, bundles are compressed using bzip2).
676 677
677 678 The bundle file can then be transferred using conventional means
678 679 and applied to another repository with the unbundle or pull
679 680 command. This is useful when direct push and pull are not
680 681 available or when exporting an entire repository is undesirable.
681 682
682 683 Applying bundles preserves all changeset contents including
683 684 permissions, copy/rename information, and revision history.
684 685
685 686 Returns 0 on success, 1 if no changes found.
686 687 """
687 688 revs = None
688 689 if 'rev' in opts:
689 690 revs = cmdutil.revrange(repo, opts['rev'])
690 691
691 692 if opts.get('all'):
692 693 base = ['null']
693 694 else:
694 695 base = cmdutil.revrange(repo, opts.get('base'))
695 696 if base:
696 697 if dest:
697 698 raise util.Abort(_("--base is incompatible with specifying "
698 699 "a destination"))
699 700 common = [repo.lookup(rev) for rev in base]
700 701 else:
701 702 dest = ui.expandpath(dest or 'default-push', dest or 'default')
702 703 dest, branches = hg.parseurl(dest, opts.get('branch'))
703 704 other = hg.repository(hg.remoteui(repo, opts), dest)
704 705 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
705 706 inc = discovery.findcommonincoming(repo, other, force=opts.get('force'))
706 707 common, _anyinc, _heads = inc
707 708
708 709 nodes = revs and map(repo.lookup, revs) or revs
709 710 cg = repo.getbundle('bundle', common=common, heads=nodes)
710 711 if not cg:
711 712 ui.status(_("no changes found\n"))
712 713 return 1
713 714
714 715 bundletype = opts.get('type', 'bzip2').lower()
715 716 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
716 717 bundletype = btypes.get(bundletype)
717 718 if bundletype not in changegroup.bundletypes:
718 719 raise util.Abort(_('unknown bundle type specified with --type'))
719 720
720 721 changegroup.writebundle(cg, fname, bundletype)
721 722
722 723 def cat(ui, repo, file1, *pats, **opts):
723 724 """output the current or given revision of files
724 725
725 726 Print the specified files as they were at the given revision. If
726 727 no revision is given, the parent of the working directory is used,
727 728 or tip if no revision is checked out.
728 729
729 730 Output may be to a file, in which case the name of the file is
730 731 given using a format string. The formatting rules are the same as
731 732 for the export command, with the following additions:
732 733
733 734 :``%s``: basename of file being printed
734 735 :``%d``: dirname of file being printed, or '.' if in repository root
735 736 :``%p``: root-relative path name of file being printed
736 737
737 738 Returns 0 on success.
738 739 """
739 740 ctx = cmdutil.revsingle(repo, opts.get('rev'))
740 741 err = 1
741 742 m = cmdutil.match(repo, (file1,) + pats, opts)
742 743 for abs in ctx.walk(m):
743 744 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
744 745 data = ctx[abs].data()
745 746 if opts.get('decode'):
746 747 data = repo.wwritedata(abs, data)
747 748 fp.write(data)
748 749 fp.close()
749 750 err = 0
750 751 return err
751 752
752 753 def clone(ui, source, dest=None, **opts):
753 754 """make a copy of an existing repository
754 755
755 756 Create a copy of an existing repository in a new directory.
756 757
757 758 If no destination directory name is specified, it defaults to the
758 759 basename of the source.
759 760
760 761 The location of the source is added to the new repository's
761 762 ``.hg/hgrc`` file, as the default to be used for future pulls.
762 763
763 764 See :hg:`help urls` for valid source format details.
764 765
765 766 It is possible to specify an ``ssh://`` URL as the destination, but no
766 767 ``.hg/hgrc`` and working directory will be created on the remote side.
767 768 Please see :hg:`help urls` for important details about ``ssh://`` URLs.
768 769
769 770 A set of changesets (tags, or branch names) to pull may be specified
770 771 by listing each changeset (tag, or branch name) with -r/--rev.
771 772 If -r/--rev is used, the cloned repository will contain only a subset
772 773 of the changesets of the source repository. Only the set of changesets
773 774 defined by all -r/--rev options (including all their ancestors)
774 775 will be pulled into the destination repository.
775 776 No subsequent changesets (including subsequent tags) will be present
776 777 in the destination.
777 778
778 779 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
779 780 local source repositories.
780 781
781 782 For efficiency, hardlinks are used for cloning whenever the source
782 783 and destination are on the same filesystem (note this applies only
783 784 to the repository data, not to the working directory). Some
784 785 filesystems, such as AFS, implement hardlinking incorrectly, but
785 786 do not report errors. In these cases, use the --pull option to
786 787 avoid hardlinking.
787 788
788 789 In some cases, you can clone repositories and the working directory
789 790 using full hardlinks with ::
790 791
791 792 $ cp -al REPO REPOCLONE
792 793
793 794 This is the fastest way to clone, but it is not always safe. The
794 795 operation is not atomic (making sure REPO is not modified during
795 796 the operation is up to you) and you have to make sure your editor
796 797 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
797 798 this is not compatible with certain extensions that place their
798 799 metadata under the .hg directory, such as mq.
799 800
800 801 Mercurial will update the working directory to the first applicable
801 802 revision from this list:
802 803
803 804 a) null if -U or the source repository has no changesets
804 805 b) if -u . and the source repository is local, the first parent of
805 806 the source repository's working directory
806 807 c) the changeset specified with -u (if a branch name, this means the
807 808 latest head of that branch)
808 809 d) the changeset specified with -r
809 810 e) the tipmost head specified with -b
810 811 f) the tipmost head specified with the url#branch source syntax
811 812 g) the tipmost head of the default branch
812 813 h) tip
813 814
814 815 Returns 0 on success.
815 816 """
816 817 if opts.get('noupdate') and opts.get('updaterev'):
817 818 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
818 819
819 820 r = hg.clone(hg.remoteui(ui, opts), source, dest,
820 821 pull=opts.get('pull'),
821 822 stream=opts.get('uncompressed'),
822 823 rev=opts.get('rev'),
823 824 update=opts.get('updaterev') or not opts.get('noupdate'),
824 825 branch=opts.get('branch'))
825 826
826 827 return r is None
827 828
828 829 def commit(ui, repo, *pats, **opts):
829 830 """commit the specified files or all outstanding changes
830 831
831 832 Commit changes to the given files into the repository. Unlike a
832 833 centralized SCM, this operation is a local operation. See
833 834 :hg:`push` for a way to actively distribute your changes.
834 835
835 836 If a list of files is omitted, all changes reported by :hg:`status`
836 837 will be committed.
837 838
838 839 If you are committing the result of a merge, do not provide any
839 840 filenames or -I/-X filters.
840 841
841 842 If no commit message is specified, Mercurial starts your
842 843 configured editor where you can enter a message. In case your
843 844 commit fails, you will find a backup of your message in
844 845 ``.hg/last-message.txt``.
845 846
846 847 See :hg:`help dates` for a list of formats valid for -d/--date.
847 848
848 849 Returns 0 on success, 1 if nothing changed.
849 850 """
850 851 extra = {}
851 852 if opts.get('close_branch'):
852 853 if repo['.'].node() not in repo.branchheads():
853 854 # The topo heads set is included in the branch heads set of the
854 855 # current branch, so it's sufficient to test branchheads
855 856 raise util.Abort(_('can only close branch heads'))
856 857 extra['close'] = 1
857 858 e = cmdutil.commiteditor
858 859 if opts.get('force_editor'):
859 860 e = cmdutil.commitforceeditor
860 861
861 862 def commitfunc(ui, repo, message, match, opts):
862 863 return repo.commit(message, opts.get('user'), opts.get('date'), match,
863 864 editor=e, extra=extra)
864 865
865 866 branch = repo[None].branch()
866 867 bheads = repo.branchheads(branch)
867 868
868 869 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
869 870 if not node:
870 871 stat = repo.status(match=cmdutil.match(repo, pats, opts))
871 872 if stat[3]:
872 873 ui.status(_("nothing changed (%d missing files, see 'hg status')\n")
873 874 % len(stat[3]))
874 875 else:
875 876 ui.status(_("nothing changed\n"))
876 877 return 1
877 878
878 879 ctx = repo[node]
879 880 parents = ctx.parents()
880 881
881 882 if bheads and not [x for x in parents
882 883 if x.node() in bheads and x.branch() == branch]:
883 884 ui.status(_('created new head\n'))
884 885 # The message is not printed for initial roots. For the other
885 886 # changesets, it is printed in the following situations:
886 887 #
887 888 # Par column: for the 2 parents with ...
888 889 # N: null or no parent
889 890 # B: parent is on another named branch
890 891 # C: parent is a regular non head changeset
891 892 # H: parent was a branch head of the current branch
892 893 # Msg column: whether we print "created new head" message
893 894 # In the following, it is assumed that there already exists some
894 895 # initial branch heads of the current branch, otherwise nothing is
895 896 # printed anyway.
896 897 #
897 898 # Par Msg Comment
898 899 # NN y additional topo root
899 900 #
900 901 # BN y additional branch root
901 902 # CN y additional topo head
902 903 # HN n usual case
903 904 #
904 905 # BB y weird additional branch root
905 906 # CB y branch merge
906 907 # HB n merge with named branch
907 908 #
908 909 # CC y additional head from merge
909 910 # CH n merge with a head
910 911 #
911 912 # HH n head merge: head count decreases
912 913
913 914 if not opts.get('close_branch'):
914 915 for r in parents:
915 916 if r.extra().get('close') and r.branch() == branch:
916 917 ui.status(_('reopening closed branch head %d\n') % r)
917 918
918 919 if ui.debugflag:
919 920 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
920 921 elif ui.verbose:
921 922 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
922 923
923 924 def copy(ui, repo, *pats, **opts):
924 925 """mark files as copied for the next commit
925 926
926 927 Mark dest as having copies of source files. If dest is a
927 928 directory, copies are put in that directory. If dest is a file,
928 929 the source must be a single file.
929 930
930 931 By default, this command copies the contents of files as they
931 932 exist in the working directory. If invoked with -A/--after, the
932 933 operation is recorded, but no copying is performed.
933 934
934 935 This command takes effect with the next commit. To undo a copy
935 936 before that, see :hg:`revert`.
936 937
937 938 Returns 0 on success, 1 if errors are encountered.
938 939 """
939 940 wlock = repo.wlock(False)
940 941 try:
941 942 return cmdutil.copy(ui, repo, pats, opts)
942 943 finally:
943 944 wlock.release()
944 945
945 946 def debugancestor(ui, repo, *args):
946 947 """find the ancestor revision of two revisions in a given index"""
947 948 if len(args) == 3:
948 949 index, rev1, rev2 = args
949 950 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
950 951 lookup = r.lookup
951 952 elif len(args) == 2:
952 953 if not repo:
953 954 raise util.Abort(_("there is no Mercurial repository here "
954 955 "(.hg not found)"))
955 956 rev1, rev2 = args
956 957 r = repo.changelog
957 958 lookup = repo.lookup
958 959 else:
959 960 raise util.Abort(_('either two or three arguments required'))
960 961 a = r.ancestor(lookup(rev1), lookup(rev2))
961 962 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
962 963
963 964 def debugbuilddag(ui, repo, text,
964 965 mergeable_file=False,
965 966 overwritten_file=False,
966 967 new_file=False):
967 968 """builds a repo with a given dag from scratch in the current empty repo
968 969
969 970 Elements:
970 971
971 972 - "+n" is a linear run of n nodes based on the current default parent
972 973 - "." is a single node based on the current default parent
973 974 - "$" resets the default parent to null (implied at the start);
974 975 otherwise the default parent is always the last node created
975 976 - "<p" sets the default parent to the backref p
976 977 - "*p" is a fork at parent p, which is a backref
977 978 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
978 979 - "/p2" is a merge of the preceding node and p2
979 980 - ":tag" defines a local tag for the preceding node
980 981 - "@branch" sets the named branch for subsequent nodes
981 982 - "#...\\n" is a comment up to the end of the line
982 983
983 984 Whitespace between the above elements is ignored.
984 985
985 986 A backref is either
986 987
987 988 - a number n, which references the node curr-n, where curr is the current
988 989 node, or
989 990 - the name of a local tag you placed earlier using ":tag", or
990 991 - empty to denote the default parent.
991 992
992 993 All string valued-elements are either strictly alphanumeric, or must
993 994 be enclosed in double quotes ("..."), with "\\" as escape character.
994 995 """
995 996
996 997 cl = repo.changelog
997 998 if len(cl) > 0:
998 999 raise util.Abort(_('repository is not empty'))
999 1000
1000 1001 if mergeable_file:
1001 1002 linesperrev = 2
1002 1003 # determine number of revs in DAG
1003 1004 n = 0
1004 1005 for type, data in dagparser.parsedag(text):
1005 1006 if type == 'n':
1006 1007 n += 1
1007 1008 # make a file with k lines per rev
1008 1009 initialmergedlines = [str(i) for i in xrange(0, n * linesperrev)]
1009 1010 initialmergedlines.append("")
1010 1011
1011 1012 tags = []
1012 1013
1013 1014 tr = repo.transaction("builddag")
1014 1015 try:
1015 1016
1016 1017 at = -1
1017 1018 atbranch = 'default'
1018 1019 nodeids = []
1019 1020 for type, data in dagparser.parsedag(text):
1020 1021 if type == 'n':
1021 1022 ui.note('node %s\n' % str(data))
1022 1023 id, ps = data
1023 1024
1024 1025 files = []
1025 1026 fctxs = {}
1026 1027
1027 1028 p2 = None
1028 1029 if mergeable_file:
1029 1030 fn = "mf"
1030 1031 p1 = repo[ps[0]]
1031 1032 if len(ps) > 1:
1032 1033 p2 = repo[ps[1]]
1033 1034 pa = p1.ancestor(p2)
1034 1035 base, local, other = [x[fn].data() for x in pa, p1, p2]
1035 1036 m3 = simplemerge.Merge3Text(base, local, other)
1036 1037 ml = [l.strip() for l in m3.merge_lines()]
1037 1038 ml.append("")
1038 1039 elif at > 0:
1039 1040 ml = p1[fn].data().split("\n")
1040 1041 else:
1041 1042 ml = initialmergedlines
1042 1043 ml[id * linesperrev] += " r%i" % id
1043 1044 mergedtext = "\n".join(ml)
1044 1045 files.append(fn)
1045 1046 fctxs[fn] = context.memfilectx(fn, mergedtext)
1046 1047
1047 1048 if overwritten_file:
1048 1049 fn = "of"
1049 1050 files.append(fn)
1050 1051 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1051 1052
1052 1053 if new_file:
1053 1054 fn = "nf%i" % id
1054 1055 files.append(fn)
1055 1056 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1056 1057 if len(ps) > 1:
1057 1058 if not p2:
1058 1059 p2 = repo[ps[1]]
1059 1060 for fn in p2:
1060 1061 if fn.startswith("nf"):
1061 1062 files.append(fn)
1062 1063 fctxs[fn] = p2[fn]
1063 1064
1064 1065 def fctxfn(repo, cx, path):
1065 1066 return fctxs.get(path)
1066 1067
1067 1068 if len(ps) == 0 or ps[0] < 0:
1068 1069 pars = [None, None]
1069 1070 elif len(ps) == 1:
1070 1071 pars = [nodeids[ps[0]], None]
1071 1072 else:
1072 1073 pars = [nodeids[p] for p in ps]
1073 1074 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1074 1075 date=(id, 0),
1075 1076 user="debugbuilddag",
1076 1077 extra={'branch': atbranch})
1077 1078 nodeid = repo.commitctx(cx)
1078 1079 nodeids.append(nodeid)
1079 1080 at = id
1080 1081 elif type == 'l':
1081 1082 id, name = data
1082 1083 ui.note('tag %s\n' % name)
1083 1084 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1084 1085 elif type == 'a':
1085 1086 ui.note('branch %s\n' % data)
1086 1087 atbranch = data
1087 1088 tr.close()
1088 1089 finally:
1089 1090 tr.release()
1090 1091
1091 1092 if tags:
1092 1093 tagsf = repo.opener("localtags", "w")
1093 1094 try:
1094 1095 tagsf.write("".join(tags))
1095 1096 finally:
1096 1097 tagsf.close()
1097 1098
1098 1099 def debugcommands(ui, cmd='', *args):
1099 1100 """list all available commands and options"""
1100 1101 for cmd, vals in sorted(table.iteritems()):
1101 1102 cmd = cmd.split('|')[0].strip('^')
1102 1103 opts = ', '.join([i[1] for i in vals[1]])
1103 1104 ui.write('%s: %s\n' % (cmd, opts))
1104 1105
1105 1106 def debugcomplete(ui, cmd='', **opts):
1106 1107 """returns the completion list associated with the given command"""
1107 1108
1108 1109 if opts.get('options'):
1109 1110 options = []
1110 1111 otables = [globalopts]
1111 1112 if cmd:
1112 1113 aliases, entry = cmdutil.findcmd(cmd, table, False)
1113 1114 otables.append(entry[1])
1114 1115 for t in otables:
1115 1116 for o in t:
1116 1117 if "(DEPRECATED)" in o[3]:
1117 1118 continue
1118 1119 if o[0]:
1119 1120 options.append('-%s' % o[0])
1120 1121 options.append('--%s' % o[1])
1121 1122 ui.write("%s\n" % "\n".join(options))
1122 1123 return
1123 1124
1124 1125 cmdlist = cmdutil.findpossible(cmd, table)
1125 1126 if ui.verbose:
1126 1127 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1127 1128 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1128 1129
1129 1130 def debugfsinfo(ui, path = "."):
1130 1131 """show information detected about current filesystem"""
1131 1132 open('.debugfsinfo', 'w').write('')
1132 1133 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1133 1134 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1134 1135 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1135 1136 and 'yes' or 'no'))
1136 1137 os.unlink('.debugfsinfo')
1137 1138
1138 1139 def debugrebuildstate(ui, repo, rev="tip"):
1139 1140 """rebuild the dirstate as it would look like for the given revision"""
1140 1141 ctx = cmdutil.revsingle(repo, rev)
1141 1142 wlock = repo.wlock()
1142 1143 try:
1143 1144 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1144 1145 finally:
1145 1146 wlock.release()
1146 1147
1147 1148 def debugcheckstate(ui, repo):
1148 1149 """validate the correctness of the current dirstate"""
1149 1150 parent1, parent2 = repo.dirstate.parents()
1150 1151 m1 = repo[parent1].manifest()
1151 1152 m2 = repo[parent2].manifest()
1152 1153 errors = 0
1153 1154 for f in repo.dirstate:
1154 1155 state = repo.dirstate[f]
1155 1156 if state in "nr" and f not in m1:
1156 1157 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1157 1158 errors += 1
1158 1159 if state in "a" and f in m1:
1159 1160 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1160 1161 errors += 1
1161 1162 if state in "m" and f not in m1 and f not in m2:
1162 1163 ui.warn(_("%s in state %s, but not in either manifest\n") %
1163 1164 (f, state))
1164 1165 errors += 1
1165 1166 for f in m1:
1166 1167 state = repo.dirstate[f]
1167 1168 if state not in "nrm":
1168 1169 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1169 1170 errors += 1
1170 1171 if errors:
1171 1172 error = _(".hg/dirstate inconsistent with current parent's manifest")
1172 1173 raise util.Abort(error)
1173 1174
1174 1175 def showconfig(ui, repo, *values, **opts):
1175 1176 """show combined config settings from all hgrc files
1176 1177
1177 1178 With no arguments, print names and values of all config items.
1178 1179
1179 1180 With one argument of the form section.name, print just the value
1180 1181 of that config item.
1181 1182
1182 1183 With multiple arguments, print names and values of all config
1183 1184 items with matching section names.
1184 1185
1185 1186 With --debug, the source (filename and line number) is printed
1186 1187 for each config item.
1187 1188
1188 1189 Returns 0 on success.
1189 1190 """
1190 1191
1191 1192 for f in scmutil.rcpath():
1192 1193 ui.debug(_('read config from: %s\n') % f)
1193 1194 untrusted = bool(opts.get('untrusted'))
1194 1195 if values:
1195 1196 sections = [v for v in values if '.' not in v]
1196 1197 items = [v for v in values if '.' in v]
1197 1198 if len(items) > 1 or items and sections:
1198 1199 raise util.Abort(_('only one config item permitted'))
1199 1200 for section, name, value in ui.walkconfig(untrusted=untrusted):
1200 1201 value = str(value).replace('\n', '\\n')
1201 1202 sectname = section + '.' + name
1202 1203 if values:
1203 1204 for v in values:
1204 1205 if v == section:
1205 1206 ui.debug('%s: ' %
1206 1207 ui.configsource(section, name, untrusted))
1207 1208 ui.write('%s=%s\n' % (sectname, value))
1208 1209 elif v == sectname:
1209 1210 ui.debug('%s: ' %
1210 1211 ui.configsource(section, name, untrusted))
1211 1212 ui.write(value, '\n')
1212 1213 else:
1213 1214 ui.debug('%s: ' %
1214 1215 ui.configsource(section, name, untrusted))
1215 1216 ui.write('%s=%s\n' % (sectname, value))
1216 1217
1217 1218 def debugknown(ui, repopath, *ids, **opts):
1218 1219 """test whether node ids are known to a repo
1219 1220
1220 1221 Every ID must be a full-length hex node id string. Returns a list of 0s and 1s
1221 1222 indicating unknown/known.
1222 1223 """
1223 1224 repo = hg.repository(ui, repopath)
1224 1225 if not repo.capable('known'):
1225 1226 raise util.Abort("known() not supported by target repository")
1226 1227 flags = repo.known([bin(s) for s in ids])
1227 1228 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1228 1229
1229 1230 def debugbundle(ui, bundlepath, all=None, **opts):
1230 1231 """lists the contents of a bundle"""
1231 1232 f = url.open(ui, bundlepath)
1232 1233 try:
1233 1234 gen = changegroup.readbundle(f, bundlepath)
1234 1235 if all:
1235 1236 ui.write("format: id, p1, p2, cset, delta base, len(delta)\n")
1236 1237
1237 1238 def showchunks(named):
1238 1239 ui.write("\n%s\n" % named)
1239 1240 chain = None
1240 1241 while 1:
1241 1242 chunkdata = gen.deltachunk(chain)
1242 1243 if not chunkdata:
1243 1244 break
1244 1245 node = chunkdata['node']
1245 1246 p1 = chunkdata['p1']
1246 1247 p2 = chunkdata['p2']
1247 1248 cs = chunkdata['cs']
1248 1249 deltabase = chunkdata['deltabase']
1249 1250 delta = chunkdata['delta']
1250 1251 ui.write("%s %s %s %s %s %s\n" %
1251 1252 (hex(node), hex(p1), hex(p2),
1252 1253 hex(cs), hex(deltabase), len(delta)))
1253 1254 chain = node
1254 1255
1255 1256 chunkdata = gen.changelogheader()
1256 1257 showchunks("changelog")
1257 1258 chunkdata = gen.manifestheader()
1258 1259 showchunks("manifest")
1259 1260 while 1:
1260 1261 chunkdata = gen.filelogheader()
1261 1262 if not chunkdata:
1262 1263 break
1263 1264 fname = chunkdata['filename']
1264 1265 showchunks(fname)
1265 1266 else:
1266 1267 chunkdata = gen.changelogheader()
1267 1268 chain = None
1268 1269 while 1:
1269 1270 chunkdata = gen.deltachunk(chain)
1270 1271 if not chunkdata:
1271 1272 break
1272 1273 node = chunkdata['node']
1273 1274 ui.write("%s\n" % hex(node))
1274 1275 chain = node
1275 1276 finally:
1276 1277 f.close()
1277 1278
1278 1279 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1279 1280 """retrieves a bundle from a repo
1280 1281
1281 1282 Every ID must be a full-length hex node id string. Saves the bundle to the
1282 1283 given file.
1283 1284 """
1284 1285 repo = hg.repository(ui, repopath)
1285 1286 if not repo.capable('getbundle'):
1286 1287 raise util.Abort("getbundle() not supported by target repository")
1287 1288 args = {}
1288 1289 if common:
1289 1290 args['common'] = [bin(s) for s in common]
1290 1291 if head:
1291 1292 args['heads'] = [bin(s) for s in head]
1292 1293 bundle = repo.getbundle('debug', **args)
1293 1294
1294 1295 bundletype = opts.get('type', 'bzip2').lower()
1295 1296 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1296 1297 bundletype = btypes.get(bundletype)
1297 1298 if bundletype not in changegroup.bundletypes:
1298 1299 raise util.Abort(_('unknown bundle type specified with --type'))
1299 1300 changegroup.writebundle(bundle, bundlepath, bundletype)
1300 1301
1301 1302 def debugpushkey(ui, repopath, namespace, *keyinfo):
1302 1303 '''access the pushkey key/value protocol
1303 1304
1304 1305 With two args, list the keys in the given namespace.
1305 1306
1306 1307 With five args, set a key to new if it currently is set to old.
1307 1308 Reports success or failure.
1308 1309 '''
1309 1310
1310 1311 target = hg.repository(ui, repopath)
1311 1312 if keyinfo:
1312 1313 key, old, new = keyinfo
1313 1314 r = target.pushkey(namespace, key, old, new)
1314 1315 ui.status(str(r) + '\n')
1315 1316 return not r
1316 1317 else:
1317 1318 for k, v in target.listkeys(namespace).iteritems():
1318 1319 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1319 1320 v.encode('string-escape')))
1320 1321
1321 1322 def debugrevspec(ui, repo, expr):
1322 1323 '''parse and apply a revision specification'''
1323 1324 if ui.verbose:
1324 1325 tree = revset.parse(expr)[0]
1325 1326 ui.note(tree, "\n")
1326 1327 newtree = revset.findaliases(ui, tree)
1327 1328 if newtree != tree:
1328 1329 ui.note(newtree, "\n")
1329 1330 func = revset.match(ui, expr)
1330 1331 for c in func(repo, range(len(repo))):
1331 1332 ui.write("%s\n" % c)
1332 1333
1333 1334 def debugsetparents(ui, repo, rev1, rev2=None):
1334 1335 """manually set the parents of the current working directory
1335 1336
1336 1337 This is useful for writing repository conversion tools, but should
1337 1338 be used with care.
1338 1339
1339 1340 Returns 0 on success.
1340 1341 """
1341 1342
1342 1343 r1 = cmdutil.revsingle(repo, rev1).node()
1343 1344 r2 = cmdutil.revsingle(repo, rev2, 'null').node()
1344 1345
1345 1346 wlock = repo.wlock()
1346 1347 try:
1347 1348 repo.dirstate.setparents(r1, r2)
1348 1349 finally:
1349 1350 wlock.release()
1350 1351
1351 1352 def debugstate(ui, repo, nodates=None, datesort=None):
1352 1353 """show the contents of the current dirstate"""
1353 1354 timestr = ""
1354 1355 showdate = not nodates
1355 1356 if datesort:
1356 1357 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
1357 1358 else:
1358 1359 keyfunc = None # sort by filename
1359 1360 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
1360 1361 if showdate:
1361 1362 if ent[3] == -1:
1362 1363 # Pad or slice to locale representation
1363 1364 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
1364 1365 time.localtime(0)))
1365 1366 timestr = 'unset'
1366 1367 timestr = (timestr[:locale_len] +
1367 1368 ' ' * (locale_len - len(timestr)))
1368 1369 else:
1369 1370 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
1370 1371 time.localtime(ent[3]))
1371 1372 if ent[1] & 020000:
1372 1373 mode = 'lnk'
1373 1374 else:
1374 1375 mode = '%3o' % (ent[1] & 0777)
1375 1376 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
1376 1377 for f in repo.dirstate.copies():
1377 1378 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
1378 1379
1379 1380 def debugsub(ui, repo, rev=None):
1380 1381 ctx = cmdutil.revsingle(repo, rev, None)
1381 1382 for k, v in sorted(ctx.substate.items()):
1382 1383 ui.write('path %s\n' % k)
1383 1384 ui.write(' source %s\n' % v[0])
1384 1385 ui.write(' revision %s\n' % v[1])
1385 1386
1386 1387 def debugdag(ui, repo, file_=None, *revs, **opts):
1387 1388 """format the changelog or an index DAG as a concise textual description
1388 1389
1389 1390 If you pass a revlog index, the revlog's DAG is emitted. If you list
1390 1391 revision numbers, they get labelled in the output as rN.
1391 1392
1392 1393 Otherwise, the changelog DAG of the current repo is emitted.
1393 1394 """
1394 1395 spaces = opts.get('spaces')
1395 1396 dots = opts.get('dots')
1396 1397 if file_:
1397 1398 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1398 1399 revs = set((int(r) for r in revs))
1399 1400 def events():
1400 1401 for r in rlog:
1401 1402 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1402 1403 if r in revs:
1403 1404 yield 'l', (r, "r%i" % r)
1404 1405 elif repo:
1405 1406 cl = repo.changelog
1406 1407 tags = opts.get('tags')
1407 1408 branches = opts.get('branches')
1408 1409 if tags:
1409 1410 labels = {}
1410 1411 for l, n in repo.tags().items():
1411 1412 labels.setdefault(cl.rev(n), []).append(l)
1412 1413 def events():
1413 1414 b = "default"
1414 1415 for r in cl:
1415 1416 if branches:
1416 1417 newb = cl.read(cl.node(r))[5]['branch']
1417 1418 if newb != b:
1418 1419 yield 'a', newb
1419 1420 b = newb
1420 1421 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1421 1422 if tags:
1422 1423 ls = labels.get(r)
1423 1424 if ls:
1424 1425 for l in ls:
1425 1426 yield 'l', (r, l)
1426 1427 else:
1427 1428 raise util.Abort(_('need repo for changelog dag'))
1428 1429
1429 1430 for line in dagparser.dagtextlines(events(),
1430 1431 addspaces=spaces,
1431 1432 wraplabels=True,
1432 1433 wrapannotations=True,
1433 1434 wrapnonlinear=dots,
1434 1435 usedots=dots,
1435 1436 maxlinewidth=70):
1436 1437 ui.write(line)
1437 1438 ui.write("\n")
1438 1439
1439 1440 def debugdata(ui, repo, file_, rev):
1440 1441 """dump the contents of a data file revision"""
1441 1442 r = None
1442 1443 if repo:
1443 1444 filelog = repo.file(file_)
1444 1445 if len(filelog):
1445 1446 r = filelog
1446 1447 if not r:
1447 1448 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
1448 1449 file_[:-2] + ".i")
1449 1450 try:
1450 1451 ui.write(r.revision(r.lookup(rev)))
1451 1452 except KeyError:
1452 1453 raise util.Abort(_('invalid revision identifier %s') % rev)
1453 1454
1454 1455 def debugdate(ui, date, range=None, **opts):
1455 1456 """parse and display a date"""
1456 1457 if opts["extended"]:
1457 1458 d = util.parsedate(date, util.extendeddateformats)
1458 1459 else:
1459 1460 d = util.parsedate(date)
1460 1461 ui.write("internal: %s %s\n" % d)
1461 1462 ui.write("standard: %s\n" % util.datestr(d))
1462 1463 if range:
1463 1464 m = util.matchdate(range)
1464 1465 ui.write("match: %s\n" % m(d[0]))
1465 1466
1466 1467 def debugignore(ui, repo, *values, **opts):
1467 1468 """display the combined ignore pattern"""
1468 1469 ignore = repo.dirstate._ignore
1469 1470 if hasattr(ignore, 'includepat'):
1470 1471 ui.write("%s\n" % ignore.includepat)
1471 1472 else:
1472 1473 raise util.Abort(_("no ignore patterns found"))
1473 1474
1475 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1476 """runs the changeset discovery protocol in isolation"""
1477 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl), opts.get('branch'))
1478 remote = hg.repository(hg.remoteui(repo, opts), remoteurl)
1479 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1480
1481 # make sure tests are repeatable
1482 random.seed(12323)
1483
1484 def doit(localheads, remoteheads):
1485 if opts.get('old'):
1486 if localheads:
1487 raise util.Abort('cannot use localheads with old style discovery')
1488 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1489 force=True)
1490 common = set(common)
1491 if not opts.get('nonheads'):
1492 ui.write("unpruned common: %s\n" % " ".join([short(n)
1493 for n in common]))
1494 dag = dagutil.revlogdag(repo.changelog)
1495 all = dag.ancestorset(dag.internalizeall(common))
1496 common = dag.externalizeall(dag.headsetofconnecteds(all))
1497 else:
1498 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1499 common = set(common)
1500 rheads = set(hds)
1501 lheads = set(repo.heads())
1502 ui.write("common heads: %s\n" % " ".join([short(n) for n in common]))
1503 if lheads <= common:
1504 ui.write("local is subset\n")
1505 elif rheads <= common:
1506 ui.write("remote is subset\n")
1507
1508 serverlogs = opts.get('serverlog')
1509 if serverlogs:
1510 for filename in serverlogs:
1511 logfile = open(filename, 'r')
1512 try:
1513 line = logfile.readline()
1514 while line:
1515 parts = line.strip().split(';')
1516 op = parts[1]
1517 if op == 'cg':
1518 pass
1519 elif op == 'cgss':
1520 doit(parts[2].split(' '), parts[3].split(' '))
1521 elif op == 'unb':
1522 doit(parts[3].split(' '), parts[2].split(' '))
1523 line = logfile.readline()
1524 finally:
1525 logfile.close()
1526
1527 else:
1528 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1529 opts.get('remote_head'))
1530 localrevs = opts.get('local_head')
1531 doit(localrevs, remoterevs)
1532
1533
1474 1534 def debugindex(ui, repo, file_, **opts):
1475 1535 """dump the contents of an index file"""
1476 1536 r = None
1477 1537 if repo:
1478 1538 filelog = repo.file(file_)
1479 1539 if len(filelog):
1480 1540 r = filelog
1481 1541
1482 1542 format = opts.get('format', 0)
1483 1543 if format not in (0, 1):
1484 1544 raise util.Abort(_("unknown format %d") % format)
1485 1545
1486 1546 if not r:
1487 1547 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1488 1548
1489 1549 if format == 0:
1490 1550 ui.write(" rev offset length base linkrev"
1491 1551 " nodeid p1 p2\n")
1492 1552 elif format == 1:
1493 1553 ui.write(" rev flag offset length"
1494 1554 " size base link p1 p2 nodeid\n")
1495 1555
1496 1556 for i in r:
1497 1557 node = r.node(i)
1498 1558 if format == 0:
1499 1559 try:
1500 1560 pp = r.parents(node)
1501 1561 except:
1502 1562 pp = [nullid, nullid]
1503 1563 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1504 1564 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
1505 1565 short(node), short(pp[0]), short(pp[1])))
1506 1566 elif format == 1:
1507 1567 pr = r.parentrevs(i)
1508 1568 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1509 1569 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1510 1570 r.base(i), r.linkrev(i), pr[0], pr[1], short(node)))
1511 1571
1512 1572 def debugindexdot(ui, repo, file_):
1513 1573 """dump an index DAG as a graphviz dot file"""
1514 1574 r = None
1515 1575 if repo:
1516 1576 filelog = repo.file(file_)
1517 1577 if len(filelog):
1518 1578 r = filelog
1519 1579 if not r:
1520 1580 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1521 1581 ui.write("digraph G {\n")
1522 1582 for i in r:
1523 1583 node = r.node(i)
1524 1584 pp = r.parents(node)
1525 1585 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1526 1586 if pp[1] != nullid:
1527 1587 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1528 1588 ui.write("}\n")
1529 1589
1530 1590 def debuginstall(ui):
1531 1591 '''test Mercurial installation
1532 1592
1533 1593 Returns 0 on success.
1534 1594 '''
1535 1595
1536 1596 def writetemp(contents):
1537 1597 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1538 1598 f = os.fdopen(fd, "wb")
1539 1599 f.write(contents)
1540 1600 f.close()
1541 1601 return name
1542 1602
1543 1603 problems = 0
1544 1604
1545 1605 # encoding
1546 1606 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1547 1607 try:
1548 1608 encoding.fromlocal("test")
1549 1609 except util.Abort, inst:
1550 1610 ui.write(" %s\n" % inst)
1551 1611 ui.write(_(" (check that your locale is properly set)\n"))
1552 1612 problems += 1
1553 1613
1554 1614 # compiled modules
1555 1615 ui.status(_("Checking installed modules (%s)...\n")
1556 1616 % os.path.dirname(__file__))
1557 1617 try:
1558 1618 import bdiff, mpatch, base85, osutil
1559 1619 except Exception, inst:
1560 1620 ui.write(" %s\n" % inst)
1561 1621 ui.write(_(" One or more extensions could not be found"))
1562 1622 ui.write(_(" (check that you compiled the extensions)\n"))
1563 1623 problems += 1
1564 1624
1565 1625 # templates
1566 1626 ui.status(_("Checking templates...\n"))
1567 1627 try:
1568 1628 import templater
1569 1629 templater.templater(templater.templatepath("map-cmdline.default"))
1570 1630 except Exception, inst:
1571 1631 ui.write(" %s\n" % inst)
1572 1632 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1573 1633 problems += 1
1574 1634
1575 1635 # editor
1576 1636 ui.status(_("Checking commit editor...\n"))
1577 1637 editor = ui.geteditor()
1578 1638 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
1579 1639 if not cmdpath:
1580 1640 if editor == 'vi':
1581 1641 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1582 1642 ui.write(_(" (specify a commit editor in your configuration"
1583 1643 " file)\n"))
1584 1644 else:
1585 1645 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1586 1646 ui.write(_(" (specify a commit editor in your configuration"
1587 1647 " file)\n"))
1588 1648 problems += 1
1589 1649
1590 1650 # check username
1591 1651 ui.status(_("Checking username...\n"))
1592 1652 try:
1593 1653 ui.username()
1594 1654 except util.Abort, e:
1595 1655 ui.write(" %s\n" % e)
1596 1656 ui.write(_(" (specify a username in your configuration file)\n"))
1597 1657 problems += 1
1598 1658
1599 1659 if not problems:
1600 1660 ui.status(_("No problems detected\n"))
1601 1661 else:
1602 1662 ui.write(_("%s problems detected,"
1603 1663 " please check your install!\n") % problems)
1604 1664
1605 1665 return problems
1606 1666
1607 1667 def debugrename(ui, repo, file1, *pats, **opts):
1608 1668 """dump rename information"""
1609 1669
1610 1670 ctx = cmdutil.revsingle(repo, opts.get('rev'))
1611 1671 m = cmdutil.match(repo, (file1,) + pats, opts)
1612 1672 for abs in ctx.walk(m):
1613 1673 fctx = ctx[abs]
1614 1674 o = fctx.filelog().renamed(fctx.filenode())
1615 1675 rel = m.rel(abs)
1616 1676 if o:
1617 1677 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1618 1678 else:
1619 1679 ui.write(_("%s not renamed\n") % rel)
1620 1680
1621 1681 def debugwalk(ui, repo, *pats, **opts):
1622 1682 """show how files match on given patterns"""
1623 1683 m = cmdutil.match(repo, pats, opts)
1624 1684 items = list(repo.walk(m))
1625 1685 if not items:
1626 1686 return
1627 1687 fmt = 'f %%-%ds %%-%ds %%s' % (
1628 1688 max([len(abs) for abs in items]),
1629 1689 max([len(m.rel(abs)) for abs in items]))
1630 1690 for abs in items:
1631 1691 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1632 1692 ui.write("%s\n" % line.rstrip())
1633 1693
1634 1694 def debugwireargs(ui, repopath, *vals, **opts):
1635 1695 repo = hg.repository(hg.remoteui(ui, opts), repopath)
1636 1696 for opt in remoteopts:
1637 1697 del opts[opt[1]]
1638 1698 args = {}
1639 1699 for k, v in opts.iteritems():
1640 1700 if v:
1641 1701 args[k] = v
1642 1702 # run twice to check that we don't mess up the stream for the next command
1643 1703 res1 = repo.debugwireargs(*vals, **args)
1644 1704 res2 = repo.debugwireargs(*vals, **args)
1645 1705 ui.write("%s\n" % res1)
1646 1706 if res1 != res2:
1647 1707 ui.warn("%s\n" % res2)
1648 1708
1649 1709 def diff(ui, repo, *pats, **opts):
1650 1710 """diff repository (or selected files)
1651 1711
1652 1712 Show differences between revisions for the specified files.
1653 1713
1654 1714 Differences between files are shown using the unified diff format.
1655 1715
1656 1716 .. note::
1657 1717 diff may generate unexpected results for merges, as it will
1658 1718 default to comparing against the working directory's first
1659 1719 parent changeset if no revisions are specified.
1660 1720
1661 1721 When two revision arguments are given, then changes are shown
1662 1722 between those revisions. If only one revision is specified then
1663 1723 that revision is compared to the working directory, and, when no
1664 1724 revisions are specified, the working directory files are compared
1665 1725 to its parent.
1666 1726
1667 1727 Alternatively you can specify -c/--change with a revision to see
1668 1728 the changes in that changeset relative to its first parent.
1669 1729
1670 1730 Without the -a/--text option, diff will avoid generating diffs of
1671 1731 files it detects as binary. With -a, diff will generate a diff
1672 1732 anyway, probably with undesirable results.
1673 1733
1674 1734 Use the -g/--git option to generate diffs in the git extended diff
1675 1735 format. For more information, read :hg:`help diffs`.
1676 1736
1677 1737 Returns 0 on success.
1678 1738 """
1679 1739
1680 1740 revs = opts.get('rev')
1681 1741 change = opts.get('change')
1682 1742 stat = opts.get('stat')
1683 1743 reverse = opts.get('reverse')
1684 1744
1685 1745 if revs and change:
1686 1746 msg = _('cannot specify --rev and --change at the same time')
1687 1747 raise util.Abort(msg)
1688 1748 elif change:
1689 1749 node2 = cmdutil.revsingle(repo, change, None).node()
1690 1750 node1 = repo[node2].p1().node()
1691 1751 else:
1692 1752 node1, node2 = cmdutil.revpair(repo, revs)
1693 1753
1694 1754 if reverse:
1695 1755 node1, node2 = node2, node1
1696 1756
1697 1757 diffopts = patch.diffopts(ui, opts)
1698 1758 m = cmdutil.match(repo, pats, opts)
1699 1759 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1700 1760 listsubrepos=opts.get('subrepos'))
1701 1761
1702 1762 def export(ui, repo, *changesets, **opts):
1703 1763 """dump the header and diffs for one or more changesets
1704 1764
1705 1765 Print the changeset header and diffs for one or more revisions.
1706 1766
1707 1767 The information shown in the changeset header is: author, date,
1708 1768 branch name (if non-default), changeset hash, parent(s) and commit
1709 1769 comment.
1710 1770
1711 1771 .. note::
1712 1772 export may generate unexpected diff output for merge
1713 1773 changesets, as it will compare the merge changeset against its
1714 1774 first parent only.
1715 1775
1716 1776 Output may be to a file, in which case the name of the file is
1717 1777 given using a format string. The formatting rules are as follows:
1718 1778
1719 1779 :``%%``: literal "%" character
1720 1780 :``%H``: changeset hash (40 hexadecimal digits)
1721 1781 :``%N``: number of patches being generated
1722 1782 :``%R``: changeset revision number
1723 1783 :``%b``: basename of the exporting repository
1724 1784 :``%h``: short-form changeset hash (12 hexadecimal digits)
1725 1785 :``%n``: zero-padded sequence number, starting at 1
1726 1786 :``%r``: zero-padded changeset revision number
1727 1787
1728 1788 Without the -a/--text option, export will avoid generating diffs
1729 1789 of files it detects as binary. With -a, export will generate a
1730 1790 diff anyway, probably with undesirable results.
1731 1791
1732 1792 Use the -g/--git option to generate diffs in the git extended diff
1733 1793 format. See :hg:`help diffs` for more information.
1734 1794
1735 1795 With the --switch-parent option, the diff will be against the
1736 1796 second parent. It can be useful to review a merge.
1737 1797
1738 1798 Returns 0 on success.
1739 1799 """
1740 1800 changesets += tuple(opts.get('rev', []))
1741 1801 if not changesets:
1742 1802 raise util.Abort(_("export requires at least one changeset"))
1743 1803 revs = cmdutil.revrange(repo, changesets)
1744 1804 if len(revs) > 1:
1745 1805 ui.note(_('exporting patches:\n'))
1746 1806 else:
1747 1807 ui.note(_('exporting patch:\n'))
1748 1808 cmdutil.export(repo, revs, template=opts.get('output'),
1749 1809 switch_parent=opts.get('switch_parent'),
1750 1810 opts=patch.diffopts(ui, opts))
1751 1811
1752 1812 def forget(ui, repo, *pats, **opts):
1753 1813 """forget the specified files on the next commit
1754 1814
1755 1815 Mark the specified files so they will no longer be tracked
1756 1816 after the next commit.
1757 1817
1758 1818 This only removes files from the current branch, not from the
1759 1819 entire project history, and it does not delete them from the
1760 1820 working directory.
1761 1821
1762 1822 To undo a forget before the next commit, see :hg:`add`.
1763 1823
1764 1824 Returns 0 on success.
1765 1825 """
1766 1826
1767 1827 if not pats:
1768 1828 raise util.Abort(_('no files specified'))
1769 1829
1770 1830 m = cmdutil.match(repo, pats, opts)
1771 1831 s = repo.status(match=m, clean=True)
1772 1832 forget = sorted(s[0] + s[1] + s[3] + s[6])
1773 1833 errs = 0
1774 1834
1775 1835 for f in m.files():
1776 1836 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1777 1837 ui.warn(_('not removing %s: file is already untracked\n')
1778 1838 % m.rel(f))
1779 1839 errs = 1
1780 1840
1781 1841 for f in forget:
1782 1842 if ui.verbose or not m.exact(f):
1783 1843 ui.status(_('removing %s\n') % m.rel(f))
1784 1844
1785 1845 repo[None].remove(forget, unlink=False)
1786 1846 return errs
1787 1847
1788 1848 def grep(ui, repo, pattern, *pats, **opts):
1789 1849 """search for a pattern in specified files and revisions
1790 1850
1791 1851 Search revisions of files for a regular expression.
1792 1852
1793 1853 This command behaves differently than Unix grep. It only accepts
1794 1854 Python/Perl regexps. It searches repository history, not the
1795 1855 working directory. It always prints the revision number in which a
1796 1856 match appears.
1797 1857
1798 1858 By default, grep only prints output for the first revision of a
1799 1859 file in which it finds a match. To get it to print every revision
1800 1860 that contains a change in match status ("-" for a match that
1801 1861 becomes a non-match, or "+" for a non-match that becomes a match),
1802 1862 use the --all flag.
1803 1863
1804 1864 Returns 0 if a match is found, 1 otherwise.
1805 1865 """
1806 1866 reflags = 0
1807 1867 if opts.get('ignore_case'):
1808 1868 reflags |= re.I
1809 1869 try:
1810 1870 regexp = re.compile(pattern, reflags)
1811 1871 except re.error, inst:
1812 1872 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1813 1873 return 1
1814 1874 sep, eol = ':', '\n'
1815 1875 if opts.get('print0'):
1816 1876 sep = eol = '\0'
1817 1877
1818 1878 getfile = util.lrucachefunc(repo.file)
1819 1879
1820 1880 def matchlines(body):
1821 1881 begin = 0
1822 1882 linenum = 0
1823 1883 while True:
1824 1884 match = regexp.search(body, begin)
1825 1885 if not match:
1826 1886 break
1827 1887 mstart, mend = match.span()
1828 1888 linenum += body.count('\n', begin, mstart) + 1
1829 1889 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1830 1890 begin = body.find('\n', mend) + 1 or len(body)
1831 1891 lend = begin - 1
1832 1892 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1833 1893
1834 1894 class linestate(object):
1835 1895 def __init__(self, line, linenum, colstart, colend):
1836 1896 self.line = line
1837 1897 self.linenum = linenum
1838 1898 self.colstart = colstart
1839 1899 self.colend = colend
1840 1900
1841 1901 def __hash__(self):
1842 1902 return hash((self.linenum, self.line))
1843 1903
1844 1904 def __eq__(self, other):
1845 1905 return self.line == other.line
1846 1906
1847 1907 matches = {}
1848 1908 copies = {}
1849 1909 def grepbody(fn, rev, body):
1850 1910 matches[rev].setdefault(fn, [])
1851 1911 m = matches[rev][fn]
1852 1912 for lnum, cstart, cend, line in matchlines(body):
1853 1913 s = linestate(line, lnum, cstart, cend)
1854 1914 m.append(s)
1855 1915
1856 1916 def difflinestates(a, b):
1857 1917 sm = difflib.SequenceMatcher(None, a, b)
1858 1918 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1859 1919 if tag == 'insert':
1860 1920 for i in xrange(blo, bhi):
1861 1921 yield ('+', b[i])
1862 1922 elif tag == 'delete':
1863 1923 for i in xrange(alo, ahi):
1864 1924 yield ('-', a[i])
1865 1925 elif tag == 'replace':
1866 1926 for i in xrange(alo, ahi):
1867 1927 yield ('-', a[i])
1868 1928 for i in xrange(blo, bhi):
1869 1929 yield ('+', b[i])
1870 1930
1871 1931 def display(fn, ctx, pstates, states):
1872 1932 rev = ctx.rev()
1873 1933 datefunc = ui.quiet and util.shortdate or util.datestr
1874 1934 found = False
1875 1935 filerevmatches = {}
1876 1936 def binary():
1877 1937 flog = getfile(fn)
1878 1938 return util.binary(flog.read(ctx.filenode(fn)))
1879 1939
1880 1940 if opts.get('all'):
1881 1941 iter = difflinestates(pstates, states)
1882 1942 else:
1883 1943 iter = [('', l) for l in states]
1884 1944 for change, l in iter:
1885 1945 cols = [fn, str(rev)]
1886 1946 before, match, after = None, None, None
1887 1947 if opts.get('line_number'):
1888 1948 cols.append(str(l.linenum))
1889 1949 if opts.get('all'):
1890 1950 cols.append(change)
1891 1951 if opts.get('user'):
1892 1952 cols.append(ui.shortuser(ctx.user()))
1893 1953 if opts.get('date'):
1894 1954 cols.append(datefunc(ctx.date()))
1895 1955 if opts.get('files_with_matches'):
1896 1956 c = (fn, rev)
1897 1957 if c in filerevmatches:
1898 1958 continue
1899 1959 filerevmatches[c] = 1
1900 1960 else:
1901 1961 before = l.line[:l.colstart]
1902 1962 match = l.line[l.colstart:l.colend]
1903 1963 after = l.line[l.colend:]
1904 1964 ui.write(sep.join(cols))
1905 1965 if before is not None:
1906 1966 if not opts.get('text') and binary():
1907 1967 ui.write(sep + " Binary file matches")
1908 1968 else:
1909 1969 ui.write(sep + before)
1910 1970 ui.write(match, label='grep.match')
1911 1971 ui.write(after)
1912 1972 ui.write(eol)
1913 1973 found = True
1914 1974 return found
1915 1975
1916 1976 skip = {}
1917 1977 revfiles = {}
1918 1978 matchfn = cmdutil.match(repo, pats, opts)
1919 1979 found = False
1920 1980 follow = opts.get('follow')
1921 1981
1922 1982 def prep(ctx, fns):
1923 1983 rev = ctx.rev()
1924 1984 pctx = ctx.p1()
1925 1985 parent = pctx.rev()
1926 1986 matches.setdefault(rev, {})
1927 1987 matches.setdefault(parent, {})
1928 1988 files = revfiles.setdefault(rev, [])
1929 1989 for fn in fns:
1930 1990 flog = getfile(fn)
1931 1991 try:
1932 1992 fnode = ctx.filenode(fn)
1933 1993 except error.LookupError:
1934 1994 continue
1935 1995
1936 1996 copied = flog.renamed(fnode)
1937 1997 copy = follow and copied and copied[0]
1938 1998 if copy:
1939 1999 copies.setdefault(rev, {})[fn] = copy
1940 2000 if fn in skip:
1941 2001 if copy:
1942 2002 skip[copy] = True
1943 2003 continue
1944 2004 files.append(fn)
1945 2005
1946 2006 if fn not in matches[rev]:
1947 2007 grepbody(fn, rev, flog.read(fnode))
1948 2008
1949 2009 pfn = copy or fn
1950 2010 if pfn not in matches[parent]:
1951 2011 try:
1952 2012 fnode = pctx.filenode(pfn)
1953 2013 grepbody(pfn, parent, flog.read(fnode))
1954 2014 except error.LookupError:
1955 2015 pass
1956 2016
1957 2017 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
1958 2018 rev = ctx.rev()
1959 2019 parent = ctx.p1().rev()
1960 2020 for fn in sorted(revfiles.get(rev, [])):
1961 2021 states = matches[rev][fn]
1962 2022 copy = copies.get(rev, {}).get(fn)
1963 2023 if fn in skip:
1964 2024 if copy:
1965 2025 skip[copy] = True
1966 2026 continue
1967 2027 pstates = matches.get(parent, {}).get(copy or fn, [])
1968 2028 if pstates or states:
1969 2029 r = display(fn, ctx, pstates, states)
1970 2030 found = found or r
1971 2031 if r and not opts.get('all'):
1972 2032 skip[fn] = True
1973 2033 if copy:
1974 2034 skip[copy] = True
1975 2035 del matches[rev]
1976 2036 del revfiles[rev]
1977 2037
1978 2038 return not found
1979 2039
1980 2040 def heads(ui, repo, *branchrevs, **opts):
1981 2041 """show current repository heads or show branch heads
1982 2042
1983 2043 With no arguments, show all repository branch heads.
1984 2044
1985 2045 Repository "heads" are changesets with no child changesets. They are
1986 2046 where development generally takes place and are the usual targets
1987 2047 for update and merge operations. Branch heads are changesets that have
1988 2048 no child changeset on the same branch.
1989 2049
1990 2050 If one or more REVs are given, only branch heads on the branches
1991 2051 associated with the specified changesets are shown.
1992 2052
1993 2053 If -c/--closed is specified, also show branch heads marked closed
1994 2054 (see :hg:`commit --close-branch`).
1995 2055
1996 2056 If STARTREV is specified, only those heads that are descendants of
1997 2057 STARTREV will be displayed.
1998 2058
1999 2059 If -t/--topo is specified, named branch mechanics will be ignored and only
2000 2060 changesets without children will be shown.
2001 2061
2002 2062 Returns 0 if matching heads are found, 1 if not.
2003 2063 """
2004 2064
2005 2065 start = None
2006 2066 if 'rev' in opts:
2007 2067 start = cmdutil.revsingle(repo, opts['rev'], None).node()
2008 2068
2009 2069 if opts.get('topo'):
2010 2070 heads = [repo[h] for h in repo.heads(start)]
2011 2071 else:
2012 2072 heads = []
2013 2073 for b, ls in repo.branchmap().iteritems():
2014 2074 if start is None:
2015 2075 heads += [repo[h] for h in ls]
2016 2076 continue
2017 2077 startrev = repo.changelog.rev(start)
2018 2078 descendants = set(repo.changelog.descendants(startrev))
2019 2079 descendants.add(startrev)
2020 2080 rev = repo.changelog.rev
2021 2081 heads += [repo[h] for h in ls if rev(h) in descendants]
2022 2082
2023 2083 if branchrevs:
2024 2084 branches = set(repo[br].branch() for br in branchrevs)
2025 2085 heads = [h for h in heads if h.branch() in branches]
2026 2086
2027 2087 if not opts.get('closed'):
2028 2088 heads = [h for h in heads if not h.extra().get('close')]
2029 2089
2030 2090 if opts.get('active') and branchrevs:
2031 2091 dagheads = repo.heads(start)
2032 2092 heads = [h for h in heads if h.node() in dagheads]
2033 2093
2034 2094 if branchrevs:
2035 2095 haveheads = set(h.branch() for h in heads)
2036 2096 if branches - haveheads:
2037 2097 headless = ', '.join(b for b in branches - haveheads)
2038 2098 msg = _('no open branch heads found on branches %s')
2039 2099 if opts.get('rev'):
2040 2100 msg += _(' (started at %s)' % opts['rev'])
2041 2101 ui.warn((msg + '\n') % headless)
2042 2102
2043 2103 if not heads:
2044 2104 return 1
2045 2105
2046 2106 heads = sorted(heads, key=lambda x: -x.rev())
2047 2107 displayer = cmdutil.show_changeset(ui, repo, opts)
2048 2108 for ctx in heads:
2049 2109 displayer.show(ctx)
2050 2110 displayer.close()
2051 2111
2052 2112 def help_(ui, name=None, with_version=False, unknowncmd=False, full=True):
2053 2113 """show help for a given topic or a help overview
2054 2114
2055 2115 With no arguments, print a list of commands with short help messages.
2056 2116
2057 2117 Given a topic, extension, or command name, print help for that
2058 2118 topic.
2059 2119
2060 2120 Returns 0 if successful.
2061 2121 """
2062 2122 option_lists = []
2063 2123 textwidth = min(ui.termwidth(), 80) - 2
2064 2124
2065 2125 def addglobalopts(aliases):
2066 2126 if ui.verbose:
2067 2127 option_lists.append((_("global options:"), globalopts))
2068 2128 if name == 'shortlist':
2069 2129 option_lists.append((_('use "hg help" for the full list '
2070 2130 'of commands'), ()))
2071 2131 else:
2072 2132 if name == 'shortlist':
2073 2133 msg = _('use "hg help" for the full list of commands '
2074 2134 'or "hg -v" for details')
2075 2135 elif name and not full:
2076 2136 msg = _('use "hg help %s" to show the full help text' % name)
2077 2137 elif aliases:
2078 2138 msg = _('use "hg -v help%s" to show builtin aliases and '
2079 2139 'global options') % (name and " " + name or "")
2080 2140 else:
2081 2141 msg = _('use "hg -v help %s" to show global options') % name
2082 2142 option_lists.append((msg, ()))
2083 2143
2084 2144 def helpcmd(name):
2085 2145 if with_version:
2086 2146 version_(ui)
2087 2147 ui.write('\n')
2088 2148
2089 2149 try:
2090 2150 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
2091 2151 except error.AmbiguousCommand, inst:
2092 2152 # py3k fix: except vars can't be used outside the scope of the
2093 2153 # except block, nor can be used inside a lambda. python issue4617
2094 2154 prefix = inst.args[0]
2095 2155 select = lambda c: c.lstrip('^').startswith(prefix)
2096 2156 helplist(_('list of commands:\n\n'), select)
2097 2157 return
2098 2158
2099 2159 # check if it's an invalid alias and display its error if it is
2100 2160 if getattr(entry[0], 'badalias', False):
2101 2161 if not unknowncmd:
2102 2162 entry[0](ui)
2103 2163 return
2104 2164
2105 2165 # synopsis
2106 2166 if len(entry) > 2:
2107 2167 if entry[2].startswith('hg'):
2108 2168 ui.write("%s\n" % entry[2])
2109 2169 else:
2110 2170 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
2111 2171 else:
2112 2172 ui.write('hg %s\n' % aliases[0])
2113 2173
2114 2174 # aliases
2115 2175 if full and not ui.quiet and len(aliases) > 1:
2116 2176 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
2117 2177
2118 2178 # description
2119 2179 doc = gettext(entry[0].__doc__)
2120 2180 if not doc:
2121 2181 doc = _("(no help text available)")
2122 2182 if hasattr(entry[0], 'definition'): # aliased command
2123 2183 if entry[0].definition.startswith('!'): # shell alias
2124 2184 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
2125 2185 else:
2126 2186 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
2127 2187 if ui.quiet or not full:
2128 2188 doc = doc.splitlines()[0]
2129 2189 keep = ui.verbose and ['verbose'] or []
2130 2190 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
2131 2191 ui.write("\n%s\n" % formatted)
2132 2192 if pruned:
2133 2193 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
2134 2194
2135 2195 if not ui.quiet:
2136 2196 # options
2137 2197 if entry[1]:
2138 2198 option_lists.append((_("options:\n"), entry[1]))
2139 2199
2140 2200 addglobalopts(False)
2141 2201
2142 2202 def helplist(header, select=None):
2143 2203 h = {}
2144 2204 cmds = {}
2145 2205 for c, e in table.iteritems():
2146 2206 f = c.split("|", 1)[0]
2147 2207 if select and not select(f):
2148 2208 continue
2149 2209 if (not select and name != 'shortlist' and
2150 2210 e[0].__module__ != __name__):
2151 2211 continue
2152 2212 if name == "shortlist" and not f.startswith("^"):
2153 2213 continue
2154 2214 f = f.lstrip("^")
2155 2215 if not ui.debugflag and f.startswith("debug"):
2156 2216 continue
2157 2217 doc = e[0].__doc__
2158 2218 if doc and 'DEPRECATED' in doc and not ui.verbose:
2159 2219 continue
2160 2220 doc = gettext(doc)
2161 2221 if not doc:
2162 2222 doc = _("(no help text available)")
2163 2223 h[f] = doc.splitlines()[0].rstrip()
2164 2224 cmds[f] = c.lstrip("^")
2165 2225
2166 2226 if not h:
2167 2227 ui.status(_('no commands defined\n'))
2168 2228 return
2169 2229
2170 2230 ui.status(header)
2171 2231 fns = sorted(h)
2172 2232 m = max(map(len, fns))
2173 2233 for f in fns:
2174 2234 if ui.verbose:
2175 2235 commands = cmds[f].replace("|",", ")
2176 2236 ui.write(" %s:\n %s\n"%(commands, h[f]))
2177 2237 else:
2178 2238 ui.write('%s\n' % (util.wrap(h[f], textwidth,
2179 2239 initindent=' %-*s ' % (m, f),
2180 2240 hangindent=' ' * (m + 4))))
2181 2241
2182 2242 if not ui.quiet:
2183 2243 addglobalopts(True)
2184 2244
2185 2245 def helptopic(name):
2186 2246 for names, header, doc in help.helptable:
2187 2247 if name in names:
2188 2248 break
2189 2249 else:
2190 2250 raise error.UnknownCommand(name)
2191 2251
2192 2252 # description
2193 2253 if not doc:
2194 2254 doc = _("(no help text available)")
2195 2255 if hasattr(doc, '__call__'):
2196 2256 doc = doc()
2197 2257
2198 2258 ui.write("%s\n\n" % header)
2199 2259 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
2200 2260
2201 2261 def helpext(name):
2202 2262 try:
2203 2263 mod = extensions.find(name)
2204 2264 doc = gettext(mod.__doc__) or _('no help text available')
2205 2265 except KeyError:
2206 2266 mod = None
2207 2267 doc = extensions.disabledext(name)
2208 2268 if not doc:
2209 2269 raise error.UnknownCommand(name)
2210 2270
2211 2271 if '\n' not in doc:
2212 2272 head, tail = doc, ""
2213 2273 else:
2214 2274 head, tail = doc.split('\n', 1)
2215 2275 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
2216 2276 if tail:
2217 2277 ui.write(minirst.format(tail, textwidth))
2218 2278 ui.status('\n\n')
2219 2279
2220 2280 if mod:
2221 2281 try:
2222 2282 ct = mod.cmdtable
2223 2283 except AttributeError:
2224 2284 ct = {}
2225 2285 modcmds = set([c.split('|', 1)[0] for c in ct])
2226 2286 helplist(_('list of commands:\n\n'), modcmds.__contains__)
2227 2287 else:
2228 2288 ui.write(_('use "hg help extensions" for information on enabling '
2229 2289 'extensions\n'))
2230 2290
2231 2291 def helpextcmd(name):
2232 2292 cmd, ext, mod = extensions.disabledcmd(ui, name, ui.config('ui', 'strict'))
2233 2293 doc = gettext(mod.__doc__).splitlines()[0]
2234 2294
2235 2295 msg = help.listexts(_("'%s' is provided by the following "
2236 2296 "extension:") % cmd, {ext: doc}, len(ext),
2237 2297 indent=4)
2238 2298 ui.write(minirst.format(msg, textwidth))
2239 2299 ui.write('\n\n')
2240 2300 ui.write(_('use "hg help extensions" for information on enabling '
2241 2301 'extensions\n'))
2242 2302
2243 2303 help.addtopichook('revsets', revset.makedoc)
2244 2304 help.addtopichook('templates', templatekw.makedoc)
2245 2305 help.addtopichook('templates', templatefilters.makedoc)
2246 2306
2247 2307 if name and name != 'shortlist':
2248 2308 i = None
2249 2309 if unknowncmd:
2250 2310 queries = (helpextcmd,)
2251 2311 else:
2252 2312 queries = (helptopic, helpcmd, helpext, helpextcmd)
2253 2313 for f in queries:
2254 2314 try:
2255 2315 f(name)
2256 2316 i = None
2257 2317 break
2258 2318 except error.UnknownCommand, inst:
2259 2319 i = inst
2260 2320 if i:
2261 2321 raise i
2262 2322
2263 2323 else:
2264 2324 # program name
2265 2325 if ui.verbose or with_version:
2266 2326 version_(ui)
2267 2327 else:
2268 2328 ui.status(_("Mercurial Distributed SCM\n"))
2269 2329 ui.status('\n')
2270 2330
2271 2331 # list of commands
2272 2332 if name == "shortlist":
2273 2333 header = _('basic commands:\n\n')
2274 2334 else:
2275 2335 header = _('list of commands:\n\n')
2276 2336
2277 2337 helplist(header)
2278 2338 if name != 'shortlist':
2279 2339 exts, maxlength = extensions.enabled()
2280 2340 text = help.listexts(_('enabled extensions:'), exts, maxlength)
2281 2341 if text:
2282 2342 ui.write("\n%s\n" % minirst.format(text, textwidth))
2283 2343
2284 2344 # list all option lists
2285 2345 opt_output = []
2286 2346 multioccur = False
2287 2347 for title, options in option_lists:
2288 2348 opt_output.append(("\n%s" % title, None))
2289 2349 for option in options:
2290 2350 if len(option) == 5:
2291 2351 shortopt, longopt, default, desc, optlabel = option
2292 2352 else:
2293 2353 shortopt, longopt, default, desc = option
2294 2354 optlabel = _("VALUE") # default label
2295 2355
2296 2356 if _("DEPRECATED") in desc and not ui.verbose:
2297 2357 continue
2298 2358 if isinstance(default, list):
2299 2359 numqualifier = " %s [+]" % optlabel
2300 2360 multioccur = True
2301 2361 elif (default is not None) and not isinstance(default, bool):
2302 2362 numqualifier = " %s" % optlabel
2303 2363 else:
2304 2364 numqualifier = ""
2305 2365 opt_output.append(("%2s%s" %
2306 2366 (shortopt and "-%s" % shortopt,
2307 2367 longopt and " --%s%s" %
2308 2368 (longopt, numqualifier)),
2309 2369 "%s%s" % (desc,
2310 2370 default
2311 2371 and _(" (default: %s)") % default
2312 2372 or "")))
2313 2373 if multioccur:
2314 2374 msg = _("\n[+] marked option can be specified multiple times")
2315 2375 if ui.verbose and name != 'shortlist':
2316 2376 opt_output.append((msg, None))
2317 2377 else:
2318 2378 opt_output.insert(-1, (msg, None))
2319 2379
2320 2380 if not name:
2321 2381 ui.write(_("\nadditional help topics:\n\n"))
2322 2382 topics = []
2323 2383 for names, header, doc in help.helptable:
2324 2384 topics.append((sorted(names, key=len, reverse=True)[0], header))
2325 2385 topics_len = max([len(s[0]) for s in topics])
2326 2386 for t, desc in topics:
2327 2387 ui.write(" %-*s %s\n" % (topics_len, t, desc))
2328 2388
2329 2389 if opt_output:
2330 2390 colwidth = encoding.colwidth
2331 2391 # normalize: (opt or message, desc or None, width of opt)
2332 2392 entries = [desc and (opt, desc, colwidth(opt)) or (opt, None, 0)
2333 2393 for opt, desc in opt_output]
2334 2394 hanging = max([e[2] for e in entries])
2335 2395 for opt, desc, width in entries:
2336 2396 if desc:
2337 2397 initindent = ' %s%s ' % (opt, ' ' * (hanging - width))
2338 2398 hangindent = ' ' * (hanging + 3)
2339 2399 ui.write('%s\n' % (util.wrap(desc, textwidth,
2340 2400 initindent=initindent,
2341 2401 hangindent=hangindent)))
2342 2402 else:
2343 2403 ui.write("%s\n" % opt)
2344 2404
2345 2405 def identify(ui, repo, source=None, rev=None,
2346 2406 num=None, id=None, branch=None, tags=None, bookmarks=None):
2347 2407 """identify the working copy or specified revision
2348 2408
2349 2409 Print a summary identifying the repository state at REV using one or
2350 2410 two parent hash identifiers, followed by a "+" if the working
2351 2411 directory has uncommitted changes, the branch name (if not default),
2352 2412 a list of tags, and a list of bookmarks.
2353 2413
2354 2414 When REV is not given, print a summary of the current state of the
2355 2415 repository.
2356 2416
2357 2417 Specifying a path to a repository root or Mercurial bundle will
2358 2418 cause lookup to operate on that repository/bundle.
2359 2419
2360 2420 Returns 0 if successful.
2361 2421 """
2362 2422
2363 2423 if not repo and not source:
2364 2424 raise util.Abort(_("there is no Mercurial repository here "
2365 2425 "(.hg not found)"))
2366 2426
2367 2427 hexfunc = ui.debugflag and hex or short
2368 2428 default = not (num or id or branch or tags or bookmarks)
2369 2429 output = []
2370 2430 revs = []
2371 2431
2372 2432 if source:
2373 2433 source, branches = hg.parseurl(ui.expandpath(source))
2374 2434 repo = hg.repository(ui, source)
2375 2435 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
2376 2436
2377 2437 if not repo.local():
2378 2438 if num or branch or tags:
2379 2439 raise util.Abort(
2380 2440 _("can't query remote revision number, branch, or tags"))
2381 2441 if not rev and revs:
2382 2442 rev = revs[0]
2383 2443 if not rev:
2384 2444 rev = "tip"
2385 2445
2386 2446 remoterev = repo.lookup(rev)
2387 2447 if default or id:
2388 2448 output = [hexfunc(remoterev)]
2389 2449
2390 2450 def getbms():
2391 2451 bms = []
2392 2452
2393 2453 if 'bookmarks' in repo.listkeys('namespaces'):
2394 2454 hexremoterev = hex(remoterev)
2395 2455 bms = [bm for bm, bmr in repo.listkeys('bookmarks').iteritems()
2396 2456 if bmr == hexremoterev]
2397 2457
2398 2458 return bms
2399 2459
2400 2460 if bookmarks:
2401 2461 output.extend(getbms())
2402 2462 elif default and not ui.quiet:
2403 2463 # multiple bookmarks for a single parent separated by '/'
2404 2464 bm = '/'.join(getbms())
2405 2465 if bm:
2406 2466 output.append(bm)
2407 2467 else:
2408 2468 if not rev:
2409 2469 ctx = repo[None]
2410 2470 parents = ctx.parents()
2411 2471 changed = ""
2412 2472 if default or id or num:
2413 2473 changed = util.any(repo.status()) and "+" or ""
2414 2474 if default or id:
2415 2475 output = ["%s%s" %
2416 2476 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
2417 2477 if num:
2418 2478 output.append("%s%s" %
2419 2479 ('+'.join([str(p.rev()) for p in parents]), changed))
2420 2480 else:
2421 2481 ctx = cmdutil.revsingle(repo, rev)
2422 2482 if default or id:
2423 2483 output = [hexfunc(ctx.node())]
2424 2484 if num:
2425 2485 output.append(str(ctx.rev()))
2426 2486
2427 2487 if default and not ui.quiet:
2428 2488 b = ctx.branch()
2429 2489 if b != 'default':
2430 2490 output.append("(%s)" % b)
2431 2491
2432 2492 # multiple tags for a single parent separated by '/'
2433 2493 t = '/'.join(ctx.tags())
2434 2494 if t:
2435 2495 output.append(t)
2436 2496
2437 2497 # multiple bookmarks for a single parent separated by '/'
2438 2498 bm = '/'.join(ctx.bookmarks())
2439 2499 if bm:
2440 2500 output.append(bm)
2441 2501 else:
2442 2502 if branch:
2443 2503 output.append(ctx.branch())
2444 2504
2445 2505 if tags:
2446 2506 output.extend(ctx.tags())
2447 2507
2448 2508 if bookmarks:
2449 2509 output.extend(ctx.bookmarks())
2450 2510
2451 2511 ui.write("%s\n" % ' '.join(output))
2452 2512
2453 2513 def import_(ui, repo, patch1, *patches, **opts):
2454 2514 """import an ordered set of patches
2455 2515
2456 2516 Import a list of patches and commit them individually (unless
2457 2517 --no-commit is specified).
2458 2518
2459 2519 If there are outstanding changes in the working directory, import
2460 2520 will abort unless given the -f/--force flag.
2461 2521
2462 2522 You can import a patch straight from a mail message. Even patches
2463 2523 as attachments work (to use the body part, it must have type
2464 2524 text/plain or text/x-patch). From and Subject headers of email
2465 2525 message are used as default committer and commit message. All
2466 2526 text/plain body parts before first diff are added to commit
2467 2527 message.
2468 2528
2469 2529 If the imported patch was generated by :hg:`export`, user and
2470 2530 description from patch override values from message headers and
2471 2531 body. Values given on command line with -m/--message and -u/--user
2472 2532 override these.
2473 2533
2474 2534 If --exact is specified, import will set the working directory to
2475 2535 the parent of each patch before applying it, and will abort if the
2476 2536 resulting changeset has a different ID than the one recorded in
2477 2537 the patch. This may happen due to character set problems or other
2478 2538 deficiencies in the text patch format.
2479 2539
2480 2540 With -s/--similarity, hg will attempt to discover renames and
2481 2541 copies in the patch in the same way as 'addremove'.
2482 2542
2483 2543 To read a patch from standard input, use "-" as the patch name. If
2484 2544 a URL is specified, the patch will be downloaded from it.
2485 2545 See :hg:`help dates` for a list of formats valid for -d/--date.
2486 2546
2487 2547 Returns 0 on success.
2488 2548 """
2489 2549 patches = (patch1,) + patches
2490 2550
2491 2551 date = opts.get('date')
2492 2552 if date:
2493 2553 opts['date'] = util.parsedate(date)
2494 2554
2495 2555 try:
2496 2556 sim = float(opts.get('similarity') or 0)
2497 2557 except ValueError:
2498 2558 raise util.Abort(_('similarity must be a number'))
2499 2559 if sim < 0 or sim > 100:
2500 2560 raise util.Abort(_('similarity must be between 0 and 100'))
2501 2561
2502 2562 if opts.get('exact') or not opts.get('force'):
2503 2563 cmdutil.bail_if_changed(repo)
2504 2564
2505 2565 d = opts["base"]
2506 2566 strip = opts["strip"]
2507 2567 wlock = lock = None
2508 2568 msgs = []
2509 2569
2510 2570 def tryone(ui, hunk):
2511 2571 tmpname, message, user, date, branch, nodeid, p1, p2 = \
2512 2572 patch.extract(ui, hunk)
2513 2573
2514 2574 if not tmpname:
2515 2575 return None
2516 2576 commitid = _('to working directory')
2517 2577
2518 2578 try:
2519 2579 cmdline_message = cmdutil.logmessage(opts)
2520 2580 if cmdline_message:
2521 2581 # pickup the cmdline msg
2522 2582 message = cmdline_message
2523 2583 elif message:
2524 2584 # pickup the patch msg
2525 2585 message = message.strip()
2526 2586 else:
2527 2587 # launch the editor
2528 2588 message = None
2529 2589 ui.debug('message:\n%s\n' % message)
2530 2590
2531 2591 wp = repo.parents()
2532 2592 if opts.get('exact'):
2533 2593 if not nodeid or not p1:
2534 2594 raise util.Abort(_('not a Mercurial patch'))
2535 2595 p1 = repo.lookup(p1)
2536 2596 p2 = repo.lookup(p2 or hex(nullid))
2537 2597
2538 2598 if p1 != wp[0].node():
2539 2599 hg.clean(repo, p1)
2540 2600 repo.dirstate.setparents(p1, p2)
2541 2601 elif p2:
2542 2602 try:
2543 2603 p1 = repo.lookup(p1)
2544 2604 p2 = repo.lookup(p2)
2545 2605 if p1 == wp[0].node():
2546 2606 repo.dirstate.setparents(p1, p2)
2547 2607 except error.RepoError:
2548 2608 pass
2549 2609 if opts.get('exact') or opts.get('import_branch'):
2550 2610 repo.dirstate.setbranch(branch or 'default')
2551 2611
2552 2612 files = {}
2553 2613 try:
2554 2614 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
2555 2615 files=files, eolmode=None)
2556 2616 finally:
2557 2617 files = cmdutil.updatedir(ui, repo, files,
2558 2618 similarity=sim / 100.0)
2559 2619 if opts.get('no_commit'):
2560 2620 if message:
2561 2621 msgs.append(message)
2562 2622 else:
2563 2623 if opts.get('exact'):
2564 2624 m = None
2565 2625 else:
2566 2626 m = cmdutil.matchfiles(repo, files or [])
2567 2627 n = repo.commit(message, opts.get('user') or user,
2568 2628 opts.get('date') or date, match=m,
2569 2629 editor=cmdutil.commiteditor)
2570 2630 if opts.get('exact'):
2571 2631 if hex(n) != nodeid:
2572 2632 repo.rollback()
2573 2633 raise util.Abort(_('patch is damaged'
2574 2634 ' or loses information'))
2575 2635 # Force a dirstate write so that the next transaction
2576 2636 # backups an up-do-date file.
2577 2637 repo.dirstate.write()
2578 2638 if n:
2579 2639 commitid = short(n)
2580 2640
2581 2641 return commitid
2582 2642 finally:
2583 2643 os.unlink(tmpname)
2584 2644
2585 2645 try:
2586 2646 wlock = repo.wlock()
2587 2647 lock = repo.lock()
2588 2648 lastcommit = None
2589 2649 for p in patches:
2590 2650 pf = os.path.join(d, p)
2591 2651
2592 2652 if pf == '-':
2593 2653 ui.status(_("applying patch from stdin\n"))
2594 2654 pf = sys.stdin
2595 2655 else:
2596 2656 ui.status(_("applying %s\n") % p)
2597 2657 pf = url.open(ui, pf)
2598 2658
2599 2659 haspatch = False
2600 2660 for hunk in patch.split(pf):
2601 2661 commitid = tryone(ui, hunk)
2602 2662 if commitid:
2603 2663 haspatch = True
2604 2664 if lastcommit:
2605 2665 ui.status(_('applied %s\n') % lastcommit)
2606 2666 lastcommit = commitid
2607 2667
2608 2668 if not haspatch:
2609 2669 raise util.Abort(_('no diffs found'))
2610 2670
2611 2671 if msgs:
2612 2672 repo.opener('last-message.txt', 'wb').write('\n* * *\n'.join(msgs))
2613 2673 finally:
2614 2674 release(lock, wlock)
2615 2675
2616 2676 def incoming(ui, repo, source="default", **opts):
2617 2677 """show new changesets found in source
2618 2678
2619 2679 Show new changesets found in the specified path/URL or the default
2620 2680 pull location. These are the changesets that would have been pulled
2621 2681 if a pull at the time you issued this command.
2622 2682
2623 2683 For remote repository, using --bundle avoids downloading the
2624 2684 changesets twice if the incoming is followed by a pull.
2625 2685
2626 2686 See pull for valid source format details.
2627 2687
2628 2688 Returns 0 if there are incoming changes, 1 otherwise.
2629 2689 """
2630 2690 if opts.get('bundle') and opts.get('subrepos'):
2631 2691 raise util.Abort(_('cannot combine --bundle and --subrepos'))
2632 2692
2633 2693 if opts.get('bookmarks'):
2634 2694 source, branches = hg.parseurl(ui.expandpath(source),
2635 2695 opts.get('branch'))
2636 2696 other = hg.repository(hg.remoteui(repo, opts), source)
2637 2697 if 'bookmarks' not in other.listkeys('namespaces'):
2638 2698 ui.warn(_("remote doesn't support bookmarks\n"))
2639 2699 return 0
2640 2700 ui.status(_('comparing with %s\n') % util.hidepassword(source))
2641 2701 return bookmarks.diff(ui, repo, other)
2642 2702
2643 2703 ret = hg.incoming(ui, repo, source, opts)
2644 2704 return ret
2645 2705
2646 2706 def init(ui, dest=".", **opts):
2647 2707 """create a new repository in the given directory
2648 2708
2649 2709 Initialize a new repository in the given directory. If the given
2650 2710 directory does not exist, it will be created.
2651 2711
2652 2712 If no directory is given, the current directory is used.
2653 2713
2654 2714 It is possible to specify an ``ssh://`` URL as the destination.
2655 2715 See :hg:`help urls` for more information.
2656 2716
2657 2717 Returns 0 on success.
2658 2718 """
2659 2719 hg.repository(hg.remoteui(ui, opts), ui.expandpath(dest), create=1)
2660 2720
2661 2721 def locate(ui, repo, *pats, **opts):
2662 2722 """locate files matching specific patterns
2663 2723
2664 2724 Print files under Mercurial control in the working directory whose
2665 2725 names match the given patterns.
2666 2726
2667 2727 By default, this command searches all directories in the working
2668 2728 directory. To search just the current directory and its
2669 2729 subdirectories, use "--include .".
2670 2730
2671 2731 If no patterns are given to match, this command prints the names
2672 2732 of all files under Mercurial control in the working directory.
2673 2733
2674 2734 If you want to feed the output of this command into the "xargs"
2675 2735 command, use the -0 option to both this command and "xargs". This
2676 2736 will avoid the problem of "xargs" treating single filenames that
2677 2737 contain whitespace as multiple filenames.
2678 2738
2679 2739 Returns 0 if a match is found, 1 otherwise.
2680 2740 """
2681 2741 end = opts.get('print0') and '\0' or '\n'
2682 2742 rev = cmdutil.revsingle(repo, opts.get('rev'), None).node()
2683 2743
2684 2744 ret = 1
2685 2745 m = cmdutil.match(repo, pats, opts, default='relglob')
2686 2746 m.bad = lambda x, y: False
2687 2747 for abs in repo[rev].walk(m):
2688 2748 if not rev and abs not in repo.dirstate:
2689 2749 continue
2690 2750 if opts.get('fullpath'):
2691 2751 ui.write(repo.wjoin(abs), end)
2692 2752 else:
2693 2753 ui.write(((pats and m.rel(abs)) or abs), end)
2694 2754 ret = 0
2695 2755
2696 2756 return ret
2697 2757
2698 2758 def log(ui, repo, *pats, **opts):
2699 2759 """show revision history of entire repository or files
2700 2760
2701 2761 Print the revision history of the specified files or the entire
2702 2762 project.
2703 2763
2704 2764 File history is shown without following rename or copy history of
2705 2765 files. Use -f/--follow with a filename to follow history across
2706 2766 renames and copies. --follow without a filename will only show
2707 2767 ancestors or descendants of the starting revision. --follow-first
2708 2768 only follows the first parent of merge revisions.
2709 2769
2710 2770 If no revision range is specified, the default is ``tip:0`` unless
2711 2771 --follow is set, in which case the working directory parent is
2712 2772 used as the starting revision. You can specify a revision set for
2713 2773 log, see :hg:`help revsets` for more information.
2714 2774
2715 2775 See :hg:`help dates` for a list of formats valid for -d/--date.
2716 2776
2717 2777 By default this command prints revision number and changeset id,
2718 2778 tags, non-trivial parents, user, date and time, and a summary for
2719 2779 each commit. When the -v/--verbose switch is used, the list of
2720 2780 changed files and full commit message are shown.
2721 2781
2722 2782 .. note::
2723 2783 log -p/--patch may generate unexpected diff output for merge
2724 2784 changesets, as it will only compare the merge changeset against
2725 2785 its first parent. Also, only files different from BOTH parents
2726 2786 will appear in files:.
2727 2787
2728 2788 Returns 0 on success.
2729 2789 """
2730 2790
2731 2791 matchfn = cmdutil.match(repo, pats, opts)
2732 2792 limit = cmdutil.loglimit(opts)
2733 2793 count = 0
2734 2794
2735 2795 endrev = None
2736 2796 if opts.get('copies') and opts.get('rev'):
2737 2797 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
2738 2798
2739 2799 df = False
2740 2800 if opts["date"]:
2741 2801 df = util.matchdate(opts["date"])
2742 2802
2743 2803 branches = opts.get('branch', []) + opts.get('only_branch', [])
2744 2804 opts['branch'] = [repo.lookupbranch(b) for b in branches]
2745 2805
2746 2806 displayer = cmdutil.show_changeset(ui, repo, opts, True)
2747 2807 def prep(ctx, fns):
2748 2808 rev = ctx.rev()
2749 2809 parents = [p for p in repo.changelog.parentrevs(rev)
2750 2810 if p != nullrev]
2751 2811 if opts.get('no_merges') and len(parents) == 2:
2752 2812 return
2753 2813 if opts.get('only_merges') and len(parents) != 2:
2754 2814 return
2755 2815 if opts.get('branch') and ctx.branch() not in opts['branch']:
2756 2816 return
2757 2817 if df and not df(ctx.date()[0]):
2758 2818 return
2759 2819 if opts['user'] and not [k for k in opts['user']
2760 2820 if k.lower() in ctx.user().lower()]:
2761 2821 return
2762 2822 if opts.get('keyword'):
2763 2823 for k in [kw.lower() for kw in opts['keyword']]:
2764 2824 if (k in ctx.user().lower() or
2765 2825 k in ctx.description().lower() or
2766 2826 k in " ".join(ctx.files()).lower()):
2767 2827 break
2768 2828 else:
2769 2829 return
2770 2830
2771 2831 copies = None
2772 2832 if opts.get('copies') and rev:
2773 2833 copies = []
2774 2834 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2775 2835 for fn in ctx.files():
2776 2836 rename = getrenamed(fn, rev)
2777 2837 if rename:
2778 2838 copies.append((fn, rename[0]))
2779 2839
2780 2840 revmatchfn = None
2781 2841 if opts.get('patch') or opts.get('stat'):
2782 2842 if opts.get('follow') or opts.get('follow_first'):
2783 2843 # note: this might be wrong when following through merges
2784 2844 revmatchfn = cmdutil.match(repo, fns, default='path')
2785 2845 else:
2786 2846 revmatchfn = matchfn
2787 2847
2788 2848 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2789 2849
2790 2850 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2791 2851 if count == limit:
2792 2852 break
2793 2853 if displayer.flush(ctx.rev()):
2794 2854 count += 1
2795 2855 displayer.close()
2796 2856
2797 2857 def manifest(ui, repo, node=None, rev=None):
2798 2858 """output the current or given revision of the project manifest
2799 2859
2800 2860 Print a list of version controlled files for the given revision.
2801 2861 If no revision is given, the first parent of the working directory
2802 2862 is used, or the null revision if no revision is checked out.
2803 2863
2804 2864 With -v, print file permissions, symlink and executable bits.
2805 2865 With --debug, print file revision hashes.
2806 2866
2807 2867 Returns 0 on success.
2808 2868 """
2809 2869
2810 2870 if rev and node:
2811 2871 raise util.Abort(_("please specify just one revision"))
2812 2872
2813 2873 if not node:
2814 2874 node = rev
2815 2875
2816 2876 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2817 2877 ctx = cmdutil.revsingle(repo, node)
2818 2878 for f in ctx:
2819 2879 if ui.debugflag:
2820 2880 ui.write("%40s " % hex(ctx.manifest()[f]))
2821 2881 if ui.verbose:
2822 2882 ui.write(decor[ctx.flags(f)])
2823 2883 ui.write("%s\n" % f)
2824 2884
2825 2885 def merge(ui, repo, node=None, **opts):
2826 2886 """merge working directory with another revision
2827 2887
2828 2888 The current working directory is updated with all changes made in
2829 2889 the requested revision since the last common predecessor revision.
2830 2890
2831 2891 Files that changed between either parent are marked as changed for
2832 2892 the next commit and a commit must be performed before any further
2833 2893 updates to the repository are allowed. The next commit will have
2834 2894 two parents.
2835 2895
2836 2896 ``--tool`` can be used to specify the merge tool used for file
2837 2897 merges. It overrides the HGMERGE environment variable and your
2838 2898 configuration files. See :hg:`help merge-tools` for options.
2839 2899
2840 2900 If no revision is specified, the working directory's parent is a
2841 2901 head revision, and the current branch contains exactly one other
2842 2902 head, the other head is merged with by default. Otherwise, an
2843 2903 explicit revision with which to merge with must be provided.
2844 2904
2845 2905 :hg:`resolve` must be used to resolve unresolved files.
2846 2906
2847 2907 To undo an uncommitted merge, use :hg:`update --clean .` which
2848 2908 will check out a clean copy of the original merge parent, losing
2849 2909 all changes.
2850 2910
2851 2911 Returns 0 on success, 1 if there are unresolved files.
2852 2912 """
2853 2913
2854 2914 if opts.get('rev') and node:
2855 2915 raise util.Abort(_("please specify just one revision"))
2856 2916 if not node:
2857 2917 node = opts.get('rev')
2858 2918
2859 2919 if not node:
2860 2920 branch = repo[None].branch()
2861 2921 bheads = repo.branchheads(branch)
2862 2922 if len(bheads) > 2:
2863 2923 raise util.Abort(_(
2864 2924 'branch \'%s\' has %d heads - '
2865 2925 'please merge with an explicit rev\n'
2866 2926 '(run \'hg heads .\' to see heads)')
2867 2927 % (branch, len(bheads)))
2868 2928
2869 2929 parent = repo.dirstate.p1()
2870 2930 if len(bheads) == 1:
2871 2931 if len(repo.heads()) > 1:
2872 2932 raise util.Abort(_(
2873 2933 'branch \'%s\' has one head - '
2874 2934 'please merge with an explicit rev\n'
2875 2935 '(run \'hg heads\' to see all heads)')
2876 2936 % branch)
2877 2937 msg = _('there is nothing to merge')
2878 2938 if parent != repo.lookup(repo[None].branch()):
2879 2939 msg = _('%s - use "hg update" instead') % msg
2880 2940 raise util.Abort(msg)
2881 2941
2882 2942 if parent not in bheads:
2883 2943 raise util.Abort(_('working dir not at a head rev - '
2884 2944 'use "hg update" or merge with an explicit rev'))
2885 2945 node = parent == bheads[0] and bheads[-1] or bheads[0]
2886 2946 else:
2887 2947 node = cmdutil.revsingle(repo, node).node()
2888 2948
2889 2949 if opts.get('preview'):
2890 2950 # find nodes that are ancestors of p2 but not of p1
2891 2951 p1 = repo.lookup('.')
2892 2952 p2 = repo.lookup(node)
2893 2953 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
2894 2954
2895 2955 displayer = cmdutil.show_changeset(ui, repo, opts)
2896 2956 for node in nodes:
2897 2957 displayer.show(repo[node])
2898 2958 displayer.close()
2899 2959 return 0
2900 2960
2901 2961 try:
2902 2962 # ui.forcemerge is an internal variable, do not document
2903 2963 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2904 2964 return hg.merge(repo, node, force=opts.get('force'))
2905 2965 finally:
2906 2966 ui.setconfig('ui', 'forcemerge', '')
2907 2967
2908 2968 def outgoing(ui, repo, dest=None, **opts):
2909 2969 """show changesets not found in the destination
2910 2970
2911 2971 Show changesets not found in the specified destination repository
2912 2972 or the default push location. These are the changesets that would
2913 2973 be pushed if a push was requested.
2914 2974
2915 2975 See pull for details of valid destination formats.
2916 2976
2917 2977 Returns 0 if there are outgoing changes, 1 otherwise.
2918 2978 """
2919 2979
2920 2980 if opts.get('bookmarks'):
2921 2981 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2922 2982 dest, branches = hg.parseurl(dest, opts.get('branch'))
2923 2983 other = hg.repository(hg.remoteui(repo, opts), dest)
2924 2984 if 'bookmarks' not in other.listkeys('namespaces'):
2925 2985 ui.warn(_("remote doesn't support bookmarks\n"))
2926 2986 return 0
2927 2987 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
2928 2988 return bookmarks.diff(ui, other, repo)
2929 2989
2930 2990 ret = hg.outgoing(ui, repo, dest, opts)
2931 2991 return ret
2932 2992
2933 2993 def parents(ui, repo, file_=None, **opts):
2934 2994 """show the parents of the working directory or revision
2935 2995
2936 2996 Print the working directory's parent revisions. If a revision is
2937 2997 given via -r/--rev, the parent of that revision will be printed.
2938 2998 If a file argument is given, the revision in which the file was
2939 2999 last changed (before the working directory revision or the
2940 3000 argument to --rev if given) is printed.
2941 3001
2942 3002 Returns 0 on success.
2943 3003 """
2944 3004
2945 3005 ctx = cmdutil.revsingle(repo, opts.get('rev'), None)
2946 3006
2947 3007 if file_:
2948 3008 m = cmdutil.match(repo, (file_,), opts)
2949 3009 if m.anypats() or len(m.files()) != 1:
2950 3010 raise util.Abort(_('can only specify an explicit filename'))
2951 3011 file_ = m.files()[0]
2952 3012 filenodes = []
2953 3013 for cp in ctx.parents():
2954 3014 if not cp:
2955 3015 continue
2956 3016 try:
2957 3017 filenodes.append(cp.filenode(file_))
2958 3018 except error.LookupError:
2959 3019 pass
2960 3020 if not filenodes:
2961 3021 raise util.Abort(_("'%s' not found in manifest!") % file_)
2962 3022 fl = repo.file(file_)
2963 3023 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2964 3024 else:
2965 3025 p = [cp.node() for cp in ctx.parents()]
2966 3026
2967 3027 displayer = cmdutil.show_changeset(ui, repo, opts)
2968 3028 for n in p:
2969 3029 if n != nullid:
2970 3030 displayer.show(repo[n])
2971 3031 displayer.close()
2972 3032
2973 3033 def paths(ui, repo, search=None):
2974 3034 """show aliases for remote repositories
2975 3035
2976 3036 Show definition of symbolic path name NAME. If no name is given,
2977 3037 show definition of all available names.
2978 3038
2979 3039 Path names are defined in the [paths] section of your
2980 3040 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
2981 3041 repository, ``.hg/hgrc`` is used, too.
2982 3042
2983 3043 The path names ``default`` and ``default-push`` have a special
2984 3044 meaning. When performing a push or pull operation, they are used
2985 3045 as fallbacks if no location is specified on the command-line.
2986 3046 When ``default-push`` is set, it will be used for push and
2987 3047 ``default`` will be used for pull; otherwise ``default`` is used
2988 3048 as the fallback for both. When cloning a repository, the clone
2989 3049 source is written as ``default`` in ``.hg/hgrc``. Note that
2990 3050 ``default`` and ``default-push`` apply to all inbound (e.g.
2991 3051 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
2992 3052 :hg:`bundle`) operations.
2993 3053
2994 3054 See :hg:`help urls` for more information.
2995 3055
2996 3056 Returns 0 on success.
2997 3057 """
2998 3058 if search:
2999 3059 for name, path in ui.configitems("paths"):
3000 3060 if name == search:
3001 3061 ui.write("%s\n" % util.hidepassword(path))
3002 3062 return
3003 3063 ui.warn(_("not found!\n"))
3004 3064 return 1
3005 3065 else:
3006 3066 for name, path in ui.configitems("paths"):
3007 3067 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
3008 3068
3009 3069 def postincoming(ui, repo, modheads, optupdate, checkout):
3010 3070 if modheads == 0:
3011 3071 return
3012 3072 if optupdate:
3013 3073 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
3014 3074 return hg.update(repo, checkout)
3015 3075 else:
3016 3076 ui.status(_("not updating, since new heads added\n"))
3017 3077 if modheads > 1:
3018 3078 currentbranchheads = len(repo.branchheads())
3019 3079 if currentbranchheads == modheads:
3020 3080 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3021 3081 elif currentbranchheads > 1:
3022 3082 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to merge)\n"))
3023 3083 else:
3024 3084 ui.status(_("(run 'hg heads' to see heads)\n"))
3025 3085 else:
3026 3086 ui.status(_("(run 'hg update' to get a working copy)\n"))
3027 3087
3028 3088 def pull(ui, repo, source="default", **opts):
3029 3089 """pull changes from the specified source
3030 3090
3031 3091 Pull changes from a remote repository to a local one.
3032 3092
3033 3093 This finds all changes from the repository at the specified path
3034 3094 or URL and adds them to a local repository (the current one unless
3035 3095 -R is specified). By default, this does not update the copy of the
3036 3096 project in the working directory.
3037 3097
3038 3098 Use :hg:`incoming` if you want to see what would have been added
3039 3099 by a pull at the time you issued this command. If you then decide
3040 3100 to add those changes to the repository, you should use :hg:`pull
3041 3101 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3042 3102
3043 3103 If SOURCE is omitted, the 'default' path will be used.
3044 3104 See :hg:`help urls` for more information.
3045 3105
3046 3106 Returns 0 on success, 1 if an update had unresolved files.
3047 3107 """
3048 3108 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3049 3109 other = hg.repository(hg.remoteui(repo, opts), source)
3050 3110 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3051 3111 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3052 3112
3053 3113 if opts.get('bookmark'):
3054 3114 if not revs:
3055 3115 revs = []
3056 3116 rb = other.listkeys('bookmarks')
3057 3117 for b in opts['bookmark']:
3058 3118 if b not in rb:
3059 3119 raise util.Abort(_('remote bookmark %s not found!') % b)
3060 3120 revs.append(rb[b])
3061 3121
3062 3122 if revs:
3063 3123 try:
3064 3124 revs = [other.lookup(rev) for rev in revs]
3065 3125 except error.CapabilityError:
3066 3126 err = _("other repository doesn't support revision lookup, "
3067 3127 "so a rev cannot be specified.")
3068 3128 raise util.Abort(err)
3069 3129
3070 3130 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
3071 3131 bookmarks.updatefromremote(ui, repo, other)
3072 3132 if checkout:
3073 3133 checkout = str(repo.changelog.rev(other.lookup(checkout)))
3074 3134 repo._subtoppath = source
3075 3135 try:
3076 3136 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
3077 3137
3078 3138 finally:
3079 3139 del repo._subtoppath
3080 3140
3081 3141 # update specified bookmarks
3082 3142 if opts.get('bookmark'):
3083 3143 for b in opts['bookmark']:
3084 3144 # explicit pull overrides local bookmark if any
3085 3145 ui.status(_("importing bookmark %s\n") % b)
3086 3146 repo._bookmarks[b] = repo[rb[b]].node()
3087 3147 bookmarks.write(repo)
3088 3148
3089 3149 return ret
3090 3150
3091 3151 def push(ui, repo, dest=None, **opts):
3092 3152 """push changes to the specified destination
3093 3153
3094 3154 Push changesets from the local repository to the specified
3095 3155 destination.
3096 3156
3097 3157 This operation is symmetrical to pull: it is identical to a pull
3098 3158 in the destination repository from the current one.
3099 3159
3100 3160 By default, push will not allow creation of new heads at the
3101 3161 destination, since multiple heads would make it unclear which head
3102 3162 to use. In this situation, it is recommended to pull and merge
3103 3163 before pushing.
3104 3164
3105 3165 Use --new-branch if you want to allow push to create a new named
3106 3166 branch that is not present at the destination. This allows you to
3107 3167 only create a new branch without forcing other changes.
3108 3168
3109 3169 Use -f/--force to override the default behavior and push all
3110 3170 changesets on all branches.
3111 3171
3112 3172 If -r/--rev is used, the specified revision and all its ancestors
3113 3173 will be pushed to the remote repository.
3114 3174
3115 3175 Please see :hg:`help urls` for important details about ``ssh://``
3116 3176 URLs. If DESTINATION is omitted, a default path will be used.
3117 3177
3118 3178 Returns 0 if push was successful, 1 if nothing to push.
3119 3179 """
3120 3180
3121 3181 if opts.get('bookmark'):
3122 3182 for b in opts['bookmark']:
3123 3183 # translate -B options to -r so changesets get pushed
3124 3184 if b in repo._bookmarks:
3125 3185 opts.setdefault('rev', []).append(b)
3126 3186 else:
3127 3187 # if we try to push a deleted bookmark, translate it to null
3128 3188 # this lets simultaneous -r, -b options continue working
3129 3189 opts.setdefault('rev', []).append("null")
3130 3190
3131 3191 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3132 3192 dest, branches = hg.parseurl(dest, opts.get('branch'))
3133 3193 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
3134 3194 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
3135 3195 other = hg.repository(hg.remoteui(repo, opts), dest)
3136 3196 if revs:
3137 3197 revs = [repo.lookup(rev) for rev in revs]
3138 3198
3139 3199 repo._subtoppath = dest
3140 3200 try:
3141 3201 # push subrepos depth-first for coherent ordering
3142 3202 c = repo['']
3143 3203 subs = c.substate # only repos that are committed
3144 3204 for s in sorted(subs):
3145 3205 if not c.sub(s).push(opts.get('force')):
3146 3206 return False
3147 3207 finally:
3148 3208 del repo._subtoppath
3149 3209 result = repo.push(other, opts.get('force'), revs=revs,
3150 3210 newbranch=opts.get('new_branch'))
3151 3211
3152 3212 result = (result == 0)
3153 3213
3154 3214 if opts.get('bookmark'):
3155 3215 rb = other.listkeys('bookmarks')
3156 3216 for b in opts['bookmark']:
3157 3217 # explicit push overrides remote bookmark if any
3158 3218 if b in repo._bookmarks:
3159 3219 ui.status(_("exporting bookmark %s\n") % b)
3160 3220 new = repo[b].hex()
3161 3221 elif b in rb:
3162 3222 ui.status(_("deleting remote bookmark %s\n") % b)
3163 3223 new = '' # delete
3164 3224 else:
3165 3225 ui.warn(_('bookmark %s does not exist on the local '
3166 3226 'or remote repository!\n') % b)
3167 3227 return 2
3168 3228 old = rb.get(b, '')
3169 3229 r = other.pushkey('bookmarks', b, old, new)
3170 3230 if not r:
3171 3231 ui.warn(_('updating bookmark %s failed!\n') % b)
3172 3232 if not result:
3173 3233 result = 2
3174 3234
3175 3235 return result
3176 3236
3177 3237 def recover(ui, repo):
3178 3238 """roll back an interrupted transaction
3179 3239
3180 3240 Recover from an interrupted commit or pull.
3181 3241
3182 3242 This command tries to fix the repository status after an
3183 3243 interrupted operation. It should only be necessary when Mercurial
3184 3244 suggests it.
3185 3245
3186 3246 Returns 0 if successful, 1 if nothing to recover or verify fails.
3187 3247 """
3188 3248 if repo.recover():
3189 3249 return hg.verify(repo)
3190 3250 return 1
3191 3251
3192 3252 def remove(ui, repo, *pats, **opts):
3193 3253 """remove the specified files on the next commit
3194 3254
3195 3255 Schedule the indicated files for removal from the repository.
3196 3256
3197 3257 This only removes files from the current branch, not from the
3198 3258 entire project history. -A/--after can be used to remove only
3199 3259 files that have already been deleted, -f/--force can be used to
3200 3260 force deletion, and -Af can be used to remove files from the next
3201 3261 revision without deleting them from the working directory.
3202 3262
3203 3263 The following table details the behavior of remove for different
3204 3264 file states (columns) and option combinations (rows). The file
3205 3265 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
3206 3266 reported by :hg:`status`). The actions are Warn, Remove (from
3207 3267 branch) and Delete (from disk)::
3208 3268
3209 3269 A C M !
3210 3270 none W RD W R
3211 3271 -f R RD RD R
3212 3272 -A W W W R
3213 3273 -Af R R R R
3214 3274
3215 3275 This command schedules the files to be removed at the next commit.
3216 3276 To undo a remove before that, see :hg:`revert`.
3217 3277
3218 3278 Returns 0 on success, 1 if any warnings encountered.
3219 3279 """
3220 3280
3221 3281 ret = 0
3222 3282 after, force = opts.get('after'), opts.get('force')
3223 3283 if not pats and not after:
3224 3284 raise util.Abort(_('no files specified'))
3225 3285
3226 3286 m = cmdutil.match(repo, pats, opts)
3227 3287 s = repo.status(match=m, clean=True)
3228 3288 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
3229 3289
3230 3290 for f in m.files():
3231 3291 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
3232 3292 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
3233 3293 ret = 1
3234 3294
3235 3295 if force:
3236 3296 remove, forget = modified + deleted + clean, added
3237 3297 elif after:
3238 3298 remove, forget = deleted, []
3239 3299 for f in modified + added + clean:
3240 3300 ui.warn(_('not removing %s: file still exists (use -f'
3241 3301 ' to force removal)\n') % m.rel(f))
3242 3302 ret = 1
3243 3303 else:
3244 3304 remove, forget = deleted + clean, []
3245 3305 for f in modified:
3246 3306 ui.warn(_('not removing %s: file is modified (use -f'
3247 3307 ' to force removal)\n') % m.rel(f))
3248 3308 ret = 1
3249 3309 for f in added:
3250 3310 ui.warn(_('not removing %s: file has been marked for add (use -f'
3251 3311 ' to force removal)\n') % m.rel(f))
3252 3312 ret = 1
3253 3313
3254 3314 for f in sorted(remove + forget):
3255 3315 if ui.verbose or not m.exact(f):
3256 3316 ui.status(_('removing %s\n') % m.rel(f))
3257 3317
3258 3318 repo[None].forget(forget)
3259 3319 repo[None].remove(remove, unlink=not after)
3260 3320 return ret
3261 3321
3262 3322 def rename(ui, repo, *pats, **opts):
3263 3323 """rename files; equivalent of copy + remove
3264 3324
3265 3325 Mark dest as copies of sources; mark sources for deletion. If dest
3266 3326 is a directory, copies are put in that directory. If dest is a
3267 3327 file, there can only be one source.
3268 3328
3269 3329 By default, this command copies the contents of files as they
3270 3330 exist in the working directory. If invoked with -A/--after, the
3271 3331 operation is recorded, but no copying is performed.
3272 3332
3273 3333 This command takes effect at the next commit. To undo a rename
3274 3334 before that, see :hg:`revert`.
3275 3335
3276 3336 Returns 0 on success, 1 if errors are encountered.
3277 3337 """
3278 3338 wlock = repo.wlock(False)
3279 3339 try:
3280 3340 return cmdutil.copy(ui, repo, pats, opts, rename=True)
3281 3341 finally:
3282 3342 wlock.release()
3283 3343
3284 3344 def resolve(ui, repo, *pats, **opts):
3285 3345 """redo merges or set/view the merge status of files
3286 3346
3287 3347 Merges with unresolved conflicts are often the result of
3288 3348 non-interactive merging using the ``internal:merge`` configuration
3289 3349 setting, or a command-line merge tool like ``diff3``. The resolve
3290 3350 command is used to manage the files involved in a merge, after
3291 3351 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
3292 3352 working directory must have two parents).
3293 3353
3294 3354 The resolve command can be used in the following ways:
3295 3355
3296 3356 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
3297 3357 files, discarding any previous merge attempts. Re-merging is not
3298 3358 performed for files already marked as resolved. Use ``--all/-a``
3299 3359 to selects all unresolved files. ``--tool`` can be used to specify
3300 3360 the merge tool used for the given files. It overrides the HGMERGE
3301 3361 environment variable and your configuration files.
3302 3362
3303 3363 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
3304 3364 (e.g. after having manually fixed-up the files). The default is
3305 3365 to mark all unresolved files.
3306 3366
3307 3367 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
3308 3368 default is to mark all resolved files.
3309 3369
3310 3370 - :hg:`resolve -l`: list files which had or still have conflicts.
3311 3371 In the printed list, ``U`` = unresolved and ``R`` = resolved.
3312 3372
3313 3373 Note that Mercurial will not let you commit files with unresolved
3314 3374 merge conflicts. You must use :hg:`resolve -m ...` before you can
3315 3375 commit after a conflicting merge.
3316 3376
3317 3377 Returns 0 on success, 1 if any files fail a resolve attempt.
3318 3378 """
3319 3379
3320 3380 all, mark, unmark, show, nostatus = \
3321 3381 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
3322 3382
3323 3383 if (show and (mark or unmark)) or (mark and unmark):
3324 3384 raise util.Abort(_("too many options specified"))
3325 3385 if pats and all:
3326 3386 raise util.Abort(_("can't specify --all and patterns"))
3327 3387 if not (all or pats or show or mark or unmark):
3328 3388 raise util.Abort(_('no files or directories specified; '
3329 3389 'use --all to remerge all files'))
3330 3390
3331 3391 ms = mergemod.mergestate(repo)
3332 3392 m = cmdutil.match(repo, pats, opts)
3333 3393 ret = 0
3334 3394
3335 3395 for f in ms:
3336 3396 if m(f):
3337 3397 if show:
3338 3398 if nostatus:
3339 3399 ui.write("%s\n" % f)
3340 3400 else:
3341 3401 ui.write("%s %s\n" % (ms[f].upper(), f),
3342 3402 label='resolve.' +
3343 3403 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
3344 3404 elif mark:
3345 3405 ms.mark(f, "r")
3346 3406 elif unmark:
3347 3407 ms.mark(f, "u")
3348 3408 else:
3349 3409 wctx = repo[None]
3350 3410 mctx = wctx.parents()[-1]
3351 3411
3352 3412 # backup pre-resolve (merge uses .orig for its own purposes)
3353 3413 a = repo.wjoin(f)
3354 3414 util.copyfile(a, a + ".resolve")
3355 3415
3356 3416 try:
3357 3417 # resolve file
3358 3418 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
3359 3419 if ms.resolve(f, wctx, mctx):
3360 3420 ret = 1
3361 3421 finally:
3362 3422 ui.setconfig('ui', 'forcemerge', '')
3363 3423
3364 3424 # replace filemerge's .orig file with our resolve file
3365 3425 util.rename(a + ".resolve", a + ".orig")
3366 3426
3367 3427 ms.commit()
3368 3428 return ret
3369 3429
3370 3430 def revert(ui, repo, *pats, **opts):
3371 3431 """restore individual files or directories to an earlier state
3372 3432
3373 3433 .. note::
3374 3434 This command is most likely not what you are looking for.
3375 3435 Revert will partially overwrite content in the working
3376 3436 directory without changing the working directory parents. Use
3377 3437 :hg:`update -r rev` to check out earlier revisions, or
3378 3438 :hg:`update --clean .` to undo a merge which has added another
3379 3439 parent.
3380 3440
3381 3441 With no revision specified, revert the named files or directories
3382 3442 to the contents they had in the parent of the working directory.
3383 3443 This restores the contents of the affected files to an unmodified
3384 3444 state and unschedules adds, removes, copies, and renames. If the
3385 3445 working directory has two parents, you must explicitly specify a
3386 3446 revision.
3387 3447
3388 3448 Using the -r/--rev option, revert the given files or directories
3389 3449 to their contents as of a specific revision. This can be helpful
3390 3450 to "roll back" some or all of an earlier change. See :hg:`help
3391 3451 dates` for a list of formats valid for -d/--date.
3392 3452
3393 3453 Revert modifies the working directory. It does not commit any
3394 3454 changes, or change the parent of the working directory. If you
3395 3455 revert to a revision other than the parent of the working
3396 3456 directory, the reverted files will thus appear modified
3397 3457 afterwards.
3398 3458
3399 3459 If a file has been deleted, it is restored. Files scheduled for
3400 3460 addition are just unscheduled and left as they are. If the
3401 3461 executable mode of a file was changed, it is reset.
3402 3462
3403 3463 If names are given, all files matching the names are reverted.
3404 3464 If no arguments are given, no files are reverted.
3405 3465
3406 3466 Modified files are saved with a .orig suffix before reverting.
3407 3467 To disable these backups, use --no-backup.
3408 3468
3409 3469 Returns 0 on success.
3410 3470 """
3411 3471
3412 3472 if opts.get("date"):
3413 3473 if opts.get("rev"):
3414 3474 raise util.Abort(_("you can't specify a revision and a date"))
3415 3475 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
3416 3476
3417 3477 parent, p2 = repo.dirstate.parents()
3418 3478 if not opts.get('rev') and p2 != nullid:
3419 3479 raise util.Abort(_('uncommitted merge - '
3420 3480 'use "hg update", see "hg help revert"'))
3421 3481
3422 3482 if not pats and not opts.get('all'):
3423 3483 raise util.Abort(_('no files or directories specified; '
3424 3484 'use --all to revert the whole repo'))
3425 3485
3426 3486 ctx = cmdutil.revsingle(repo, opts.get('rev'))
3427 3487 node = ctx.node()
3428 3488 mf = ctx.manifest()
3429 3489 if node == parent:
3430 3490 pmf = mf
3431 3491 else:
3432 3492 pmf = None
3433 3493
3434 3494 # need all matching names in dirstate and manifest of target rev,
3435 3495 # so have to walk both. do not print errors if files exist in one
3436 3496 # but not other.
3437 3497
3438 3498 names = {}
3439 3499
3440 3500 wlock = repo.wlock()
3441 3501 try:
3442 3502 # walk dirstate.
3443 3503
3444 3504 m = cmdutil.match(repo, pats, opts)
3445 3505 m.bad = lambda x, y: False
3446 3506 for abs in repo.walk(m):
3447 3507 names[abs] = m.rel(abs), m.exact(abs)
3448 3508
3449 3509 # walk target manifest.
3450 3510
3451 3511 def badfn(path, msg):
3452 3512 if path in names:
3453 3513 return
3454 3514 path_ = path + '/'
3455 3515 for f in names:
3456 3516 if f.startswith(path_):
3457 3517 return
3458 3518 ui.warn("%s: %s\n" % (m.rel(path), msg))
3459 3519
3460 3520 m = cmdutil.match(repo, pats, opts)
3461 3521 m.bad = badfn
3462 3522 for abs in repo[node].walk(m):
3463 3523 if abs not in names:
3464 3524 names[abs] = m.rel(abs), m.exact(abs)
3465 3525
3466 3526 m = cmdutil.matchfiles(repo, names)
3467 3527 changes = repo.status(match=m)[:4]
3468 3528 modified, added, removed, deleted = map(set, changes)
3469 3529
3470 3530 # if f is a rename, also revert the source
3471 3531 cwd = repo.getcwd()
3472 3532 for f in added:
3473 3533 src = repo.dirstate.copied(f)
3474 3534 if src and src not in names and repo.dirstate[src] == 'r':
3475 3535 removed.add(src)
3476 3536 names[src] = (repo.pathto(src, cwd), True)
3477 3537
3478 3538 def removeforget(abs):
3479 3539 if repo.dirstate[abs] == 'a':
3480 3540 return _('forgetting %s\n')
3481 3541 return _('removing %s\n')
3482 3542
3483 3543 revert = ([], _('reverting %s\n'))
3484 3544 add = ([], _('adding %s\n'))
3485 3545 remove = ([], removeforget)
3486 3546 undelete = ([], _('undeleting %s\n'))
3487 3547
3488 3548 disptable = (
3489 3549 # dispatch table:
3490 3550 # file state
3491 3551 # action if in target manifest
3492 3552 # action if not in target manifest
3493 3553 # make backup if in target manifest
3494 3554 # make backup if not in target manifest
3495 3555 (modified, revert, remove, True, True),
3496 3556 (added, revert, remove, True, False),
3497 3557 (removed, undelete, None, False, False),
3498 3558 (deleted, revert, remove, False, False),
3499 3559 )
3500 3560
3501 3561 for abs, (rel, exact) in sorted(names.items()):
3502 3562 mfentry = mf.get(abs)
3503 3563 target = repo.wjoin(abs)
3504 3564 def handle(xlist, dobackup):
3505 3565 xlist[0].append(abs)
3506 3566 if (dobackup and not opts.get('no_backup') and
3507 3567 os.path.lexists(target)):
3508 3568 bakname = "%s.orig" % rel
3509 3569 ui.note(_('saving current version of %s as %s\n') %
3510 3570 (rel, bakname))
3511 3571 if not opts.get('dry_run'):
3512 3572 util.rename(target, bakname)
3513 3573 if ui.verbose or not exact:
3514 3574 msg = xlist[1]
3515 3575 if not isinstance(msg, basestring):
3516 3576 msg = msg(abs)
3517 3577 ui.status(msg % rel)
3518 3578 for table, hitlist, misslist, backuphit, backupmiss in disptable:
3519 3579 if abs not in table:
3520 3580 continue
3521 3581 # file has changed in dirstate
3522 3582 if mfentry:
3523 3583 handle(hitlist, backuphit)
3524 3584 elif misslist is not None:
3525 3585 handle(misslist, backupmiss)
3526 3586 break
3527 3587 else:
3528 3588 if abs not in repo.dirstate:
3529 3589 if mfentry:
3530 3590 handle(add, True)
3531 3591 elif exact:
3532 3592 ui.warn(_('file not managed: %s\n') % rel)
3533 3593 continue
3534 3594 # file has not changed in dirstate
3535 3595 if node == parent:
3536 3596 if exact:
3537 3597 ui.warn(_('no changes needed to %s\n') % rel)
3538 3598 continue
3539 3599 if pmf is None:
3540 3600 # only need parent manifest in this unlikely case,
3541 3601 # so do not read by default
3542 3602 pmf = repo[parent].manifest()
3543 3603 if abs in pmf:
3544 3604 if mfentry:
3545 3605 # if version of file is same in parent and target
3546 3606 # manifests, do nothing
3547 3607 if (pmf[abs] != mfentry or
3548 3608 pmf.flags(abs) != mf.flags(abs)):
3549 3609 handle(revert, False)
3550 3610 else:
3551 3611 handle(remove, False)
3552 3612
3553 3613 if not opts.get('dry_run'):
3554 3614 def checkout(f):
3555 3615 fc = ctx[f]
3556 3616 repo.wwrite(f, fc.data(), fc.flags())
3557 3617
3558 3618 audit_path = scmutil.path_auditor(repo.root)
3559 3619 for f in remove[0]:
3560 3620 if repo.dirstate[f] == 'a':
3561 3621 repo.dirstate.forget(f)
3562 3622 continue
3563 3623 audit_path(f)
3564 3624 try:
3565 3625 util.unlinkpath(repo.wjoin(f))
3566 3626 except OSError:
3567 3627 pass
3568 3628 repo.dirstate.remove(f)
3569 3629
3570 3630 normal = None
3571 3631 if node == parent:
3572 3632 # We're reverting to our parent. If possible, we'd like status
3573 3633 # to report the file as clean. We have to use normallookup for
3574 3634 # merges to avoid losing information about merged/dirty files.
3575 3635 if p2 != nullid:
3576 3636 normal = repo.dirstate.normallookup
3577 3637 else:
3578 3638 normal = repo.dirstate.normal
3579 3639 for f in revert[0]:
3580 3640 checkout(f)
3581 3641 if normal:
3582 3642 normal(f)
3583 3643
3584 3644 for f in add[0]:
3585 3645 checkout(f)
3586 3646 repo.dirstate.add(f)
3587 3647
3588 3648 normal = repo.dirstate.normallookup
3589 3649 if node == parent and p2 == nullid:
3590 3650 normal = repo.dirstate.normal
3591 3651 for f in undelete[0]:
3592 3652 checkout(f)
3593 3653 normal(f)
3594 3654
3595 3655 finally:
3596 3656 wlock.release()
3597 3657
3598 3658 def rollback(ui, repo, **opts):
3599 3659 """roll back the last transaction (dangerous)
3600 3660
3601 3661 This command should be used with care. There is only one level of
3602 3662 rollback, and there is no way to undo a rollback. It will also
3603 3663 restore the dirstate at the time of the last transaction, losing
3604 3664 any dirstate changes since that time. This command does not alter
3605 3665 the working directory.
3606 3666
3607 3667 Transactions are used to encapsulate the effects of all commands
3608 3668 that create new changesets or propagate existing changesets into a
3609 3669 repository. For example, the following commands are transactional,
3610 3670 and their effects can be rolled back:
3611 3671
3612 3672 - commit
3613 3673 - import
3614 3674 - pull
3615 3675 - push (with this repository as the destination)
3616 3676 - unbundle
3617 3677
3618 3678 This command is not intended for use on public repositories. Once
3619 3679 changes are visible for pull by other users, rolling a transaction
3620 3680 back locally is ineffective (someone else may already have pulled
3621 3681 the changes). Furthermore, a race is possible with readers of the
3622 3682 repository; for example an in-progress pull from the repository
3623 3683 may fail if a rollback is performed.
3624 3684
3625 3685 Returns 0 on success, 1 if no rollback data is available.
3626 3686 """
3627 3687 return repo.rollback(opts.get('dry_run'))
3628 3688
3629 3689 def root(ui, repo):
3630 3690 """print the root (top) of the current working directory
3631 3691
3632 3692 Print the root directory of the current repository.
3633 3693
3634 3694 Returns 0 on success.
3635 3695 """
3636 3696 ui.write(repo.root + "\n")
3637 3697
3638 3698 def serve(ui, repo, **opts):
3639 3699 """start stand-alone webserver
3640 3700
3641 3701 Start a local HTTP repository browser and pull server. You can use
3642 3702 this for ad-hoc sharing and browsing of repositories. It is
3643 3703 recommended to use a real web server to serve a repository for
3644 3704 longer periods of time.
3645 3705
3646 3706 Please note that the server does not implement access control.
3647 3707 This means that, by default, anybody can read from the server and
3648 3708 nobody can write to it by default. Set the ``web.allow_push``
3649 3709 option to ``*`` to allow everybody to push to the server. You
3650 3710 should use a real web server if you need to authenticate users.
3651 3711
3652 3712 By default, the server logs accesses to stdout and errors to
3653 3713 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
3654 3714 files.
3655 3715
3656 3716 To have the server choose a free port number to listen on, specify
3657 3717 a port number of 0; in this case, the server will print the port
3658 3718 number it uses.
3659 3719
3660 3720 Returns 0 on success.
3661 3721 """
3662 3722
3663 3723 if opts["stdio"]:
3664 3724 if repo is None:
3665 3725 raise error.RepoError(_("There is no Mercurial repository here"
3666 3726 " (.hg not found)"))
3667 3727 s = sshserver.sshserver(ui, repo)
3668 3728 s.serve_forever()
3669 3729
3670 3730 # this way we can check if something was given in the command-line
3671 3731 if opts.get('port'):
3672 3732 opts['port'] = util.getport(opts.get('port'))
3673 3733
3674 3734 baseui = repo and repo.baseui or ui
3675 3735 optlist = ("name templates style address port prefix ipv6"
3676 3736 " accesslog errorlog certificate encoding")
3677 3737 for o in optlist.split():
3678 3738 val = opts.get(o, '')
3679 3739 if val in (None, ''): # should check against default options instead
3680 3740 continue
3681 3741 baseui.setconfig("web", o, val)
3682 3742 if repo and repo.ui != baseui:
3683 3743 repo.ui.setconfig("web", o, val)
3684 3744
3685 3745 o = opts.get('web_conf') or opts.get('webdir_conf')
3686 3746 if not o:
3687 3747 if not repo:
3688 3748 raise error.RepoError(_("There is no Mercurial repository"
3689 3749 " here (.hg not found)"))
3690 3750 o = repo.root
3691 3751
3692 3752 app = hgweb.hgweb(o, baseui=ui)
3693 3753
3694 3754 class service(object):
3695 3755 def init(self):
3696 3756 util.set_signal_handler()
3697 3757 self.httpd = hgweb.server.create_server(ui, app)
3698 3758
3699 3759 if opts['port'] and not ui.verbose:
3700 3760 return
3701 3761
3702 3762 if self.httpd.prefix:
3703 3763 prefix = self.httpd.prefix.strip('/') + '/'
3704 3764 else:
3705 3765 prefix = ''
3706 3766
3707 3767 port = ':%d' % self.httpd.port
3708 3768 if port == ':80':
3709 3769 port = ''
3710 3770
3711 3771 bindaddr = self.httpd.addr
3712 3772 if bindaddr == '0.0.0.0':
3713 3773 bindaddr = '*'
3714 3774 elif ':' in bindaddr: # IPv6
3715 3775 bindaddr = '[%s]' % bindaddr
3716 3776
3717 3777 fqaddr = self.httpd.fqaddr
3718 3778 if ':' in fqaddr:
3719 3779 fqaddr = '[%s]' % fqaddr
3720 3780 if opts['port']:
3721 3781 write = ui.status
3722 3782 else:
3723 3783 write = ui.write
3724 3784 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
3725 3785 (fqaddr, port, prefix, bindaddr, self.httpd.port))
3726 3786
3727 3787 def run(self):
3728 3788 self.httpd.serve_forever()
3729 3789
3730 3790 service = service()
3731 3791
3732 3792 cmdutil.service(opts, initfn=service.init, runfn=service.run)
3733 3793
3734 3794 def status(ui, repo, *pats, **opts):
3735 3795 """show changed files in the working directory
3736 3796
3737 3797 Show status of files in the repository. If names are given, only
3738 3798 files that match are shown. Files that are clean or ignored or
3739 3799 the source of a copy/move operation, are not listed unless
3740 3800 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
3741 3801 Unless options described with "show only ..." are given, the
3742 3802 options -mardu are used.
3743 3803
3744 3804 Option -q/--quiet hides untracked (unknown and ignored) files
3745 3805 unless explicitly requested with -u/--unknown or -i/--ignored.
3746 3806
3747 3807 .. note::
3748 3808 status may appear to disagree with diff if permissions have
3749 3809 changed or a merge has occurred. The standard diff format does
3750 3810 not report permission changes and diff only reports changes
3751 3811 relative to one merge parent.
3752 3812
3753 3813 If one revision is given, it is used as the base revision.
3754 3814 If two revisions are given, the differences between them are
3755 3815 shown. The --change option can also be used as a shortcut to list
3756 3816 the changed files of a revision from its first parent.
3757 3817
3758 3818 The codes used to show the status of files are::
3759 3819
3760 3820 M = modified
3761 3821 A = added
3762 3822 R = removed
3763 3823 C = clean
3764 3824 ! = missing (deleted by non-hg command, but still tracked)
3765 3825 ? = not tracked
3766 3826 I = ignored
3767 3827 = origin of the previous file listed as A (added)
3768 3828
3769 3829 Returns 0 on success.
3770 3830 """
3771 3831
3772 3832 revs = opts.get('rev')
3773 3833 change = opts.get('change')
3774 3834
3775 3835 if revs and change:
3776 3836 msg = _('cannot specify --rev and --change at the same time')
3777 3837 raise util.Abort(msg)
3778 3838 elif change:
3779 3839 node2 = repo.lookup(change)
3780 3840 node1 = repo[node2].p1().node()
3781 3841 else:
3782 3842 node1, node2 = cmdutil.revpair(repo, revs)
3783 3843
3784 3844 cwd = (pats and repo.getcwd()) or ''
3785 3845 end = opts.get('print0') and '\0' or '\n'
3786 3846 copy = {}
3787 3847 states = 'modified added removed deleted unknown ignored clean'.split()
3788 3848 show = [k for k in states if opts.get(k)]
3789 3849 if opts.get('all'):
3790 3850 show += ui.quiet and (states[:4] + ['clean']) or states
3791 3851 if not show:
3792 3852 show = ui.quiet and states[:4] or states[:5]
3793 3853
3794 3854 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
3795 3855 'ignored' in show, 'clean' in show, 'unknown' in show,
3796 3856 opts.get('subrepos'))
3797 3857 changestates = zip(states, 'MAR!?IC', stat)
3798 3858
3799 3859 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
3800 3860 ctxn = repo[nullid]
3801 3861 ctx1 = repo[node1]
3802 3862 ctx2 = repo[node2]
3803 3863 added = stat[1]
3804 3864 if node2 is None:
3805 3865 added = stat[0] + stat[1] # merged?
3806 3866
3807 3867 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
3808 3868 if k in added:
3809 3869 copy[k] = v
3810 3870 elif v in added:
3811 3871 copy[v] = k
3812 3872
3813 3873 for state, char, files in changestates:
3814 3874 if state in show:
3815 3875 format = "%s %%s%s" % (char, end)
3816 3876 if opts.get('no_status'):
3817 3877 format = "%%s%s" % end
3818 3878
3819 3879 for f in files:
3820 3880 ui.write(format % repo.pathto(f, cwd),
3821 3881 label='status.' + state)
3822 3882 if f in copy:
3823 3883 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
3824 3884 label='status.copied')
3825 3885
3826 3886 def summary(ui, repo, **opts):
3827 3887 """summarize working directory state
3828 3888
3829 3889 This generates a brief summary of the working directory state,
3830 3890 including parents, branch, commit status, and available updates.
3831 3891
3832 3892 With the --remote option, this will check the default paths for
3833 3893 incoming and outgoing changes. This can be time-consuming.
3834 3894
3835 3895 Returns 0 on success.
3836 3896 """
3837 3897
3838 3898 ctx = repo[None]
3839 3899 parents = ctx.parents()
3840 3900 pnode = parents[0].node()
3841 3901
3842 3902 for p in parents:
3843 3903 # label with log.changeset (instead of log.parent) since this
3844 3904 # shows a working directory parent *changeset*:
3845 3905 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
3846 3906 label='log.changeset')
3847 3907 ui.write(' '.join(p.tags()), label='log.tag')
3848 3908 if p.bookmarks():
3849 3909 ui.write(' ' + ' '.join(p.bookmarks()), label='log.bookmark')
3850 3910 if p.rev() == -1:
3851 3911 if not len(repo):
3852 3912 ui.write(_(' (empty repository)'))
3853 3913 else:
3854 3914 ui.write(_(' (no revision checked out)'))
3855 3915 ui.write('\n')
3856 3916 if p.description():
3857 3917 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
3858 3918 label='log.summary')
3859 3919
3860 3920 branch = ctx.branch()
3861 3921 bheads = repo.branchheads(branch)
3862 3922 m = _('branch: %s\n') % branch
3863 3923 if branch != 'default':
3864 3924 ui.write(m, label='log.branch')
3865 3925 else:
3866 3926 ui.status(m, label='log.branch')
3867 3927
3868 3928 st = list(repo.status(unknown=True))[:6]
3869 3929
3870 3930 c = repo.dirstate.copies()
3871 3931 copied, renamed = [], []
3872 3932 for d, s in c.iteritems():
3873 3933 if s in st[2]:
3874 3934 st[2].remove(s)
3875 3935 renamed.append(d)
3876 3936 else:
3877 3937 copied.append(d)
3878 3938 if d in st[1]:
3879 3939 st[1].remove(d)
3880 3940 st.insert(3, renamed)
3881 3941 st.insert(4, copied)
3882 3942
3883 3943 ms = mergemod.mergestate(repo)
3884 3944 st.append([f for f in ms if ms[f] == 'u'])
3885 3945
3886 3946 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
3887 3947 st.append(subs)
3888 3948
3889 3949 labels = [ui.label(_('%d modified'), 'status.modified'),
3890 3950 ui.label(_('%d added'), 'status.added'),
3891 3951 ui.label(_('%d removed'), 'status.removed'),
3892 3952 ui.label(_('%d renamed'), 'status.copied'),
3893 3953 ui.label(_('%d copied'), 'status.copied'),
3894 3954 ui.label(_('%d deleted'), 'status.deleted'),
3895 3955 ui.label(_('%d unknown'), 'status.unknown'),
3896 3956 ui.label(_('%d ignored'), 'status.ignored'),
3897 3957 ui.label(_('%d unresolved'), 'resolve.unresolved'),
3898 3958 ui.label(_('%d subrepos'), 'status.modified')]
3899 3959 t = []
3900 3960 for s, l in zip(st, labels):
3901 3961 if s:
3902 3962 t.append(l % len(s))
3903 3963
3904 3964 t = ', '.join(t)
3905 3965 cleanworkdir = False
3906 3966
3907 3967 if len(parents) > 1:
3908 3968 t += _(' (merge)')
3909 3969 elif branch != parents[0].branch():
3910 3970 t += _(' (new branch)')
3911 3971 elif (parents[0].extra().get('close') and
3912 3972 pnode in repo.branchheads(branch, closed=True)):
3913 3973 t += _(' (head closed)')
3914 3974 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
3915 3975 t += _(' (clean)')
3916 3976 cleanworkdir = True
3917 3977 elif pnode not in bheads:
3918 3978 t += _(' (new branch head)')
3919 3979
3920 3980 if cleanworkdir:
3921 3981 ui.status(_('commit: %s\n') % t.strip())
3922 3982 else:
3923 3983 ui.write(_('commit: %s\n') % t.strip())
3924 3984
3925 3985 # all ancestors of branch heads - all ancestors of parent = new csets
3926 3986 new = [0] * len(repo)
3927 3987 cl = repo.changelog
3928 3988 for a in [cl.rev(n) for n in bheads]:
3929 3989 new[a] = 1
3930 3990 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
3931 3991 new[a] = 1
3932 3992 for a in [p.rev() for p in parents]:
3933 3993 if a >= 0:
3934 3994 new[a] = 0
3935 3995 for a in cl.ancestors(*[p.rev() for p in parents]):
3936 3996 new[a] = 0
3937 3997 new = sum(new)
3938 3998
3939 3999 if new == 0:
3940 4000 ui.status(_('update: (current)\n'))
3941 4001 elif pnode not in bheads:
3942 4002 ui.write(_('update: %d new changesets (update)\n') % new)
3943 4003 else:
3944 4004 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
3945 4005 (new, len(bheads)))
3946 4006
3947 4007 if opts.get('remote'):
3948 4008 t = []
3949 4009 source, branches = hg.parseurl(ui.expandpath('default'))
3950 4010 other = hg.repository(hg.remoteui(repo, {}), source)
3951 4011 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3952 4012 ui.debug('comparing with %s\n' % util.hidepassword(source))
3953 4013 repo.ui.pushbuffer()
3954 4014 common, incoming, rheads = discovery.findcommonincoming(repo, other)
3955 4015 repo.ui.popbuffer()
3956 4016 if incoming:
3957 4017 t.append(_('1 or more incoming'))
3958 4018
3959 4019 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
3960 4020 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3961 4021 other = hg.repository(hg.remoteui(repo, {}), dest)
3962 4022 ui.debug('comparing with %s\n' % util.hidepassword(dest))
3963 4023 repo.ui.pushbuffer()
3964 4024 common, _anyinc, _heads = discovery.findcommonincoming(repo, other)
3965 4025 repo.ui.popbuffer()
3966 4026 o = repo.changelog.findmissing(common=common)
3967 4027 if o:
3968 4028 t.append(_('%d outgoing') % len(o))
3969 4029 if 'bookmarks' in other.listkeys('namespaces'):
3970 4030 lmarks = repo.listkeys('bookmarks')
3971 4031 rmarks = other.listkeys('bookmarks')
3972 4032 diff = set(rmarks) - set(lmarks)
3973 4033 if len(diff) > 0:
3974 4034 t.append(_('%d incoming bookmarks') % len(diff))
3975 4035 diff = set(lmarks) - set(rmarks)
3976 4036 if len(diff) > 0:
3977 4037 t.append(_('%d outgoing bookmarks') % len(diff))
3978 4038
3979 4039 if t:
3980 4040 ui.write(_('remote: %s\n') % (', '.join(t)))
3981 4041 else:
3982 4042 ui.status(_('remote: (synced)\n'))
3983 4043
3984 4044 def tag(ui, repo, name1, *names, **opts):
3985 4045 """add one or more tags for the current or given revision
3986 4046
3987 4047 Name a particular revision using <name>.
3988 4048
3989 4049 Tags are used to name particular revisions of the repository and are
3990 4050 very useful to compare different revisions, to go back to significant
3991 4051 earlier versions or to mark branch points as releases, etc. Changing
3992 4052 an existing tag is normally disallowed; use -f/--force to override.
3993 4053
3994 4054 If no revision is given, the parent of the working directory is
3995 4055 used, or tip if no revision is checked out.
3996 4056
3997 4057 To facilitate version control, distribution, and merging of tags,
3998 4058 they are stored as a file named ".hgtags" which is managed similarly
3999 4059 to other project files and can be hand-edited if necessary. This
4000 4060 also means that tagging creates a new commit. The file
4001 4061 ".hg/localtags" is used for local tags (not shared among
4002 4062 repositories).
4003 4063
4004 4064 Tag commits are usually made at the head of a branch. If the parent
4005 4065 of the working directory is not a branch head, :hg:`tag` aborts; use
4006 4066 -f/--force to force the tag commit to be based on a non-head
4007 4067 changeset.
4008 4068
4009 4069 See :hg:`help dates` for a list of formats valid for -d/--date.
4010 4070
4011 4071 Since tag names have priority over branch names during revision
4012 4072 lookup, using an existing branch name as a tag name is discouraged.
4013 4073
4014 4074 Returns 0 on success.
4015 4075 """
4016 4076
4017 4077 rev_ = "."
4018 4078 names = [t.strip() for t in (name1,) + names]
4019 4079 if len(names) != len(set(names)):
4020 4080 raise util.Abort(_('tag names must be unique'))
4021 4081 for n in names:
4022 4082 if n in ['tip', '.', 'null']:
4023 4083 raise util.Abort(_('the name \'%s\' is reserved') % n)
4024 4084 if not n:
4025 4085 raise util.Abort(_('tag names cannot consist entirely of whitespace'))
4026 4086 if opts.get('rev') and opts.get('remove'):
4027 4087 raise util.Abort(_("--rev and --remove are incompatible"))
4028 4088 if opts.get('rev'):
4029 4089 rev_ = opts['rev']
4030 4090 message = opts.get('message')
4031 4091 if opts.get('remove'):
4032 4092 expectedtype = opts.get('local') and 'local' or 'global'
4033 4093 for n in names:
4034 4094 if not repo.tagtype(n):
4035 4095 raise util.Abort(_('tag \'%s\' does not exist') % n)
4036 4096 if repo.tagtype(n) != expectedtype:
4037 4097 if expectedtype == 'global':
4038 4098 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
4039 4099 else:
4040 4100 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
4041 4101 rev_ = nullid
4042 4102 if not message:
4043 4103 # we don't translate commit messages
4044 4104 message = 'Removed tag %s' % ', '.join(names)
4045 4105 elif not opts.get('force'):
4046 4106 for n in names:
4047 4107 if n in repo.tags():
4048 4108 raise util.Abort(_('tag \'%s\' already exists '
4049 4109 '(use -f to force)') % n)
4050 4110 if not opts.get('local'):
4051 4111 p1, p2 = repo.dirstate.parents()
4052 4112 if p2 != nullid:
4053 4113 raise util.Abort(_('uncommitted merge'))
4054 4114 bheads = repo.branchheads()
4055 4115 if not opts.get('force') and bheads and p1 not in bheads:
4056 4116 raise util.Abort(_('not at a branch head (use -f to force)'))
4057 4117 r = cmdutil.revsingle(repo, rev_).node()
4058 4118
4059 4119 if not message:
4060 4120 # we don't translate commit messages
4061 4121 message = ('Added tag %s for changeset %s' %
4062 4122 (', '.join(names), short(r)))
4063 4123
4064 4124 date = opts.get('date')
4065 4125 if date:
4066 4126 date = util.parsedate(date)
4067 4127
4068 4128 if opts.get('edit'):
4069 4129 message = ui.edit(message, ui.username())
4070 4130
4071 4131 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
4072 4132
4073 4133 def tags(ui, repo):
4074 4134 """list repository tags
4075 4135
4076 4136 This lists both regular and local tags. When the -v/--verbose
4077 4137 switch is used, a third column "local" is printed for local tags.
4078 4138
4079 4139 Returns 0 on success.
4080 4140 """
4081 4141
4082 4142 hexfunc = ui.debugflag and hex or short
4083 4143 tagtype = ""
4084 4144
4085 4145 for t, n in reversed(repo.tagslist()):
4086 4146 if ui.quiet:
4087 4147 ui.write("%s\n" % t)
4088 4148 continue
4089 4149
4090 4150 hn = hexfunc(n)
4091 4151 r = "%5d:%s" % (repo.changelog.rev(n), hn)
4092 4152 spaces = " " * (30 - encoding.colwidth(t))
4093 4153
4094 4154 if ui.verbose:
4095 4155 if repo.tagtype(t) == 'local':
4096 4156 tagtype = " local"
4097 4157 else:
4098 4158 tagtype = ""
4099 4159 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
4100 4160
4101 4161 def tip(ui, repo, **opts):
4102 4162 """show the tip revision
4103 4163
4104 4164 The tip revision (usually just called the tip) is the changeset
4105 4165 most recently added to the repository (and therefore the most
4106 4166 recently changed head).
4107 4167
4108 4168 If you have just made a commit, that commit will be the tip. If
4109 4169 you have just pulled changes from another repository, the tip of
4110 4170 that repository becomes the current tip. The "tip" tag is special
4111 4171 and cannot be renamed or assigned to a different changeset.
4112 4172
4113 4173 Returns 0 on success.
4114 4174 """
4115 4175 displayer = cmdutil.show_changeset(ui, repo, opts)
4116 4176 displayer.show(repo[len(repo) - 1])
4117 4177 displayer.close()
4118 4178
4119 4179 def unbundle(ui, repo, fname1, *fnames, **opts):
4120 4180 """apply one or more changegroup files
4121 4181
4122 4182 Apply one or more compressed changegroup files generated by the
4123 4183 bundle command.
4124 4184
4125 4185 Returns 0 on success, 1 if an update has unresolved files.
4126 4186 """
4127 4187 fnames = (fname1,) + fnames
4128 4188
4129 4189 lock = repo.lock()
4130 4190 wc = repo['.']
4131 4191 try:
4132 4192 for fname in fnames:
4133 4193 f = url.open(ui, fname)
4134 4194 gen = changegroup.readbundle(f, fname)
4135 4195 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname,
4136 4196 lock=lock)
4137 4197 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
4138 4198 finally:
4139 4199 lock.release()
4140 4200 return postincoming(ui, repo, modheads, opts.get('update'), None)
4141 4201
4142 4202 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
4143 4203 """update working directory (or switch revisions)
4144 4204
4145 4205 Update the repository's working directory to the specified
4146 4206 changeset. If no changeset is specified, update to the tip of the
4147 4207 current named branch.
4148 4208
4149 4209 If the changeset is not a descendant of the working directory's
4150 4210 parent, the update is aborted. With the -c/--check option, the
4151 4211 working directory is checked for uncommitted changes; if none are
4152 4212 found, the working directory is updated to the specified
4153 4213 changeset.
4154 4214
4155 4215 The following rules apply when the working directory contains
4156 4216 uncommitted changes:
4157 4217
4158 4218 1. If neither -c/--check nor -C/--clean is specified, and if
4159 4219 the requested changeset is an ancestor or descendant of
4160 4220 the working directory's parent, the uncommitted changes
4161 4221 are merged into the requested changeset and the merged
4162 4222 result is left uncommitted. If the requested changeset is
4163 4223 not an ancestor or descendant (that is, it is on another
4164 4224 branch), the update is aborted and the uncommitted changes
4165 4225 are preserved.
4166 4226
4167 4227 2. With the -c/--check option, the update is aborted and the
4168 4228 uncommitted changes are preserved.
4169 4229
4170 4230 3. With the -C/--clean option, uncommitted changes are discarded and
4171 4231 the working directory is updated to the requested changeset.
4172 4232
4173 4233 Use null as the changeset to remove the working directory (like
4174 4234 :hg:`clone -U`).
4175 4235
4176 4236 If you want to update just one file to an older changeset, use
4177 4237 :hg:`revert`.
4178 4238
4179 4239 See :hg:`help dates` for a list of formats valid for -d/--date.
4180 4240
4181 4241 Returns 0 on success, 1 if there are unresolved files.
4182 4242 """
4183 4243 if rev and node:
4184 4244 raise util.Abort(_("please specify just one revision"))
4185 4245
4186 4246 if rev is None or rev == '':
4187 4247 rev = node
4188 4248
4189 4249 # if we defined a bookmark, we have to remember the original bookmark name
4190 4250 brev = rev
4191 4251 rev = cmdutil.revsingle(repo, rev, rev).rev()
4192 4252
4193 4253 if check and clean:
4194 4254 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
4195 4255
4196 4256 if check:
4197 4257 # we could use dirty() but we can ignore merge and branch trivia
4198 4258 c = repo[None]
4199 4259 if c.modified() or c.added() or c.removed():
4200 4260 raise util.Abort(_("uncommitted local changes"))
4201 4261
4202 4262 if date:
4203 4263 if rev is not None:
4204 4264 raise util.Abort(_("you can't specify a revision and a date"))
4205 4265 rev = cmdutil.finddate(ui, repo, date)
4206 4266
4207 4267 if clean or check:
4208 4268 ret = hg.clean(repo, rev)
4209 4269 else:
4210 4270 ret = hg.update(repo, rev)
4211 4271
4212 4272 if brev in repo._bookmarks:
4213 4273 bookmarks.setcurrent(repo, brev)
4214 4274
4215 4275 return ret
4216 4276
4217 4277 def verify(ui, repo):
4218 4278 """verify the integrity of the repository
4219 4279
4220 4280 Verify the integrity of the current repository.
4221 4281
4222 4282 This will perform an extensive check of the repository's
4223 4283 integrity, validating the hashes and checksums of each entry in
4224 4284 the changelog, manifest, and tracked files, as well as the
4225 4285 integrity of their crosslinks and indices.
4226 4286
4227 4287 Returns 0 on success, 1 if errors are encountered.
4228 4288 """
4229 4289 return hg.verify(repo)
4230 4290
4231 4291 def version_(ui):
4232 4292 """output version and copyright information"""
4233 4293 ui.write(_("Mercurial Distributed SCM (version %s)\n")
4234 4294 % util.version())
4235 4295 ui.status(_(
4236 4296 "(see http://mercurial.selenic.com for more information)\n"
4237 4297 "\nCopyright (C) 2005-2011 Matt Mackall and others\n"
4238 4298 "This is free software; see the source for copying conditions. "
4239 4299 "There is NO\nwarranty; "
4240 4300 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
4241 4301 ))
4242 4302
4243 4303 # Command options and aliases are listed here, alphabetically
4244 4304
4245 4305 globalopts = [
4246 4306 ('R', 'repository', '',
4247 4307 _('repository root directory or name of overlay bundle file'),
4248 4308 _('REPO')),
4249 4309 ('', 'cwd', '',
4250 4310 _('change working directory'), _('DIR')),
4251 4311 ('y', 'noninteractive', None,
4252 4312 _('do not prompt, assume \'yes\' for any required answers')),
4253 4313 ('q', 'quiet', None, _('suppress output')),
4254 4314 ('v', 'verbose', None, _('enable additional output')),
4255 4315 ('', 'config', [],
4256 4316 _('set/override config option (use \'section.name=value\')'),
4257 4317 _('CONFIG')),
4258 4318 ('', 'debug', None, _('enable debugging output')),
4259 4319 ('', 'debugger', None, _('start debugger')),
4260 4320 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
4261 4321 _('ENCODE')),
4262 4322 ('', 'encodingmode', encoding.encodingmode,
4263 4323 _('set the charset encoding mode'), _('MODE')),
4264 4324 ('', 'traceback', None, _('always print a traceback on exception')),
4265 4325 ('', 'time', None, _('time how long the command takes')),
4266 4326 ('', 'profile', None, _('print command execution profile')),
4267 4327 ('', 'version', None, _('output version information and exit')),
4268 4328 ('h', 'help', None, _('display help and exit')),
4269 4329 ]
4270 4330
4271 4331 dryrunopts = [('n', 'dry-run', None,
4272 4332 _('do not perform actions, just print output'))]
4273 4333
4274 4334 remoteopts = [
4275 4335 ('e', 'ssh', '',
4276 4336 _('specify ssh command to use'), _('CMD')),
4277 4337 ('', 'remotecmd', '',
4278 4338 _('specify hg command to run on the remote side'), _('CMD')),
4279 4339 ('', 'insecure', None,
4280 4340 _('do not verify server certificate (ignoring web.cacerts config)')),
4281 4341 ]
4282 4342
4283 4343 walkopts = [
4284 4344 ('I', 'include', [],
4285 4345 _('include names matching the given patterns'), _('PATTERN')),
4286 4346 ('X', 'exclude', [],
4287 4347 _('exclude names matching the given patterns'), _('PATTERN')),
4288 4348 ]
4289 4349
4290 4350 commitopts = [
4291 4351 ('m', 'message', '',
4292 4352 _('use text as commit message'), _('TEXT')),
4293 4353 ('l', 'logfile', '',
4294 4354 _('read commit message from file'), _('FILE')),
4295 4355 ]
4296 4356
4297 4357 commitopts2 = [
4298 4358 ('d', 'date', '',
4299 4359 _('record the specified date as commit date'), _('DATE')),
4300 4360 ('u', 'user', '',
4301 4361 _('record the specified user as committer'), _('USER')),
4302 4362 ]
4303 4363
4304 4364 templateopts = [
4305 4365 ('', 'style', '',
4306 4366 _('display using template map file'), _('STYLE')),
4307 4367 ('', 'template', '',
4308 4368 _('display with template'), _('TEMPLATE')),
4309 4369 ]
4310 4370
4311 4371 logopts = [
4312 4372 ('p', 'patch', None, _('show patch')),
4313 4373 ('g', 'git', None, _('use git extended diff format')),
4314 4374 ('l', 'limit', '',
4315 4375 _('limit number of changes displayed'), _('NUM')),
4316 4376 ('M', 'no-merges', None, _('do not show merges')),
4317 4377 ('', 'stat', None, _('output diffstat-style summary of changes')),
4318 4378 ] + templateopts
4319 4379
4320 4380 diffopts = [
4321 4381 ('a', 'text', None, _('treat all files as text')),
4322 4382 ('g', 'git', None, _('use git extended diff format')),
4323 4383 ('', 'nodates', None, _('omit dates from diff headers'))
4324 4384 ]
4325 4385
4326 4386 diffopts2 = [
4327 4387 ('p', 'show-function', None, _('show which function each change is in')),
4328 4388 ('', 'reverse', None, _('produce a diff that undoes the changes')),
4329 4389 ('w', 'ignore-all-space', None,
4330 4390 _('ignore white space when comparing lines')),
4331 4391 ('b', 'ignore-space-change', None,
4332 4392 _('ignore changes in the amount of white space')),
4333 4393 ('B', 'ignore-blank-lines', None,
4334 4394 _('ignore changes whose lines are all blank')),
4335 4395 ('U', 'unified', '',
4336 4396 _('number of lines of context to show'), _('NUM')),
4337 4397 ('', 'stat', None, _('output diffstat-style summary of changes')),
4338 4398 ]
4339 4399
4340 4400 similarityopts = [
4341 4401 ('s', 'similarity', '',
4342 4402 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
4343 4403 ]
4344 4404
4345 4405 subrepoopts = [
4346 4406 ('S', 'subrepos', None,
4347 4407 _('recurse into subrepositories'))
4348 4408 ]
4349 4409
4350 4410 table = {
4351 4411 "^add": (add, walkopts + subrepoopts + dryrunopts,
4352 4412 _('[OPTION]... [FILE]...')),
4353 4413 "addremove":
4354 4414 (addremove, similarityopts + walkopts + dryrunopts,
4355 4415 _('[OPTION]... [FILE]...')),
4356 4416 "^annotate|blame":
4357 4417 (annotate,
4358 4418 [('r', 'rev', '',
4359 4419 _('annotate the specified revision'), _('REV')),
4360 4420 ('', 'follow', None,
4361 4421 _('follow copies/renames and list the filename (DEPRECATED)')),
4362 4422 ('', 'no-follow', None, _("don't follow copies and renames")),
4363 4423 ('a', 'text', None, _('treat all files as text')),
4364 4424 ('u', 'user', None, _('list the author (long with -v)')),
4365 4425 ('f', 'file', None, _('list the filename')),
4366 4426 ('d', 'date', None, _('list the date (short with -q)')),
4367 4427 ('n', 'number', None, _('list the revision number (default)')),
4368 4428 ('c', 'changeset', None, _('list the changeset')),
4369 4429 ('l', 'line-number', None,
4370 4430 _('show line number at the first appearance'))
4371 4431 ] + walkopts,
4372 4432 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
4373 4433 "archive":
4374 4434 (archive,
4375 4435 [('', 'no-decode', None, _('do not pass files through decoders')),
4376 4436 ('p', 'prefix', '',
4377 4437 _('directory prefix for files in archive'), _('PREFIX')),
4378 4438 ('r', 'rev', '',
4379 4439 _('revision to distribute'), _('REV')),
4380 4440 ('t', 'type', '',
4381 4441 _('type of distribution to create'), _('TYPE')),
4382 4442 ] + subrepoopts + walkopts,
4383 4443 _('[OPTION]... DEST')),
4384 4444 "backout":
4385 4445 (backout,
4386 4446 [('', 'merge', None,
4387 4447 _('merge with old dirstate parent after backout')),
4388 4448 ('', 'parent', '',
4389 4449 _('parent to choose when backing out merge'), _('REV')),
4390 4450 ('t', 'tool', '',
4391 4451 _('specify merge tool')),
4392 4452 ('r', 'rev', '',
4393 4453 _('revision to backout'), _('REV')),
4394 4454 ] + walkopts + commitopts + commitopts2,
4395 4455 _('[OPTION]... [-r] REV')),
4396 4456 "bisect":
4397 4457 (bisect,
4398 4458 [('r', 'reset', False, _('reset bisect state')),
4399 4459 ('g', 'good', False, _('mark changeset good')),
4400 4460 ('b', 'bad', False, _('mark changeset bad')),
4401 4461 ('s', 'skip', False, _('skip testing changeset')),
4402 4462 ('e', 'extend', False, _('extend the bisect range')),
4403 4463 ('c', 'command', '',
4404 4464 _('use command to check changeset state'), _('CMD')),
4405 4465 ('U', 'noupdate', False, _('do not update to target'))],
4406 4466 _("[-gbsr] [-U] [-c CMD] [REV]")),
4407 4467 "bookmarks":
4408 4468 (bookmark,
4409 4469 [('f', 'force', False, _('force')),
4410 4470 ('r', 'rev', '', _('revision'), _('REV')),
4411 4471 ('d', 'delete', False, _('delete a given bookmark')),
4412 4472 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
4413 4473 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
4414 4474 "branch":
4415 4475 (branch,
4416 4476 [('f', 'force', None,
4417 4477 _('set branch name even if it shadows an existing branch')),
4418 4478 ('C', 'clean', None, _('reset branch name to parent branch name'))],
4419 4479 _('[-fC] [NAME]')),
4420 4480 "branches":
4421 4481 (branches,
4422 4482 [('a', 'active', False,
4423 4483 _('show only branches that have unmerged heads')),
4424 4484 ('c', 'closed', False,
4425 4485 _('show normal and closed branches'))],
4426 4486 _('[-ac]')),
4427 4487 "bundle":
4428 4488 (bundle,
4429 4489 [('f', 'force', None,
4430 4490 _('run even when the destination is unrelated')),
4431 4491 ('r', 'rev', [],
4432 4492 _('a changeset intended to be added to the destination'),
4433 4493 _('REV')),
4434 4494 ('b', 'branch', [],
4435 4495 _('a specific branch you would like to bundle'),
4436 4496 _('BRANCH')),
4437 4497 ('', 'base', [],
4438 4498 _('a base changeset assumed to be available at the destination'),
4439 4499 _('REV')),
4440 4500 ('a', 'all', None, _('bundle all changesets in the repository')),
4441 4501 ('t', 'type', 'bzip2',
4442 4502 _('bundle compression type to use'), _('TYPE')),
4443 4503 ] + remoteopts,
4444 4504 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
4445 4505 "cat":
4446 4506 (cat,
4447 4507 [('o', 'output', '',
4448 4508 _('print output to file with formatted name'), _('FORMAT')),
4449 4509 ('r', 'rev', '',
4450 4510 _('print the given revision'), _('REV')),
4451 4511 ('', 'decode', None, _('apply any matching decode filter')),
4452 4512 ] + walkopts,
4453 4513 _('[OPTION]... FILE...')),
4454 4514 "^clone":
4455 4515 (clone,
4456 4516 [('U', 'noupdate', None,
4457 4517 _('the clone will include an empty working copy (only a repository)')),
4458 4518 ('u', 'updaterev', '',
4459 4519 _('revision, tag or branch to check out'), _('REV')),
4460 4520 ('r', 'rev', [],
4461 4521 _('include the specified changeset'), _('REV')),
4462 4522 ('b', 'branch', [],
4463 4523 _('clone only the specified branch'), _('BRANCH')),
4464 4524 ('', 'pull', None, _('use pull protocol to copy metadata')),
4465 4525 ('', 'uncompressed', None,
4466 4526 _('use uncompressed transfer (fast over LAN)')),
4467 4527 ] + remoteopts,
4468 4528 _('[OPTION]... SOURCE [DEST]')),
4469 4529 "^commit|ci":
4470 4530 (commit,
4471 4531 [('A', 'addremove', None,
4472 4532 _('mark new/missing files as added/removed before committing')),
4473 4533 ('', 'close-branch', None,
4474 4534 _('mark a branch as closed, hiding it from the branch list')),
4475 4535 ] + walkopts + commitopts + commitopts2,
4476 4536 _('[OPTION]... [FILE]...')),
4477 4537 "copy|cp":
4478 4538 (copy,
4479 4539 [('A', 'after', None, _('record a copy that has already occurred')),
4480 4540 ('f', 'force', None,
4481 4541 _('forcibly copy over an existing managed file')),
4482 4542 ] + walkopts + dryrunopts,
4483 4543 _('[OPTION]... [SOURCE]... DEST')),
4484 4544 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
4485 4545 "debugbuilddag":
4486 4546 (debugbuilddag,
4487 4547 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
4488 4548 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
4489 4549 ('n', 'new-file', None, _('add new file at each rev')),
4490 4550 ],
4491 4551 _('[OPTION]... TEXT')),
4492 4552 "debugbundle":
4493 4553 (debugbundle,
4494 4554 [('a', 'all', None, _('show all details')),
4495 4555 ],
4496 4556 _('FILE')),
4497 4557 "debugcheckstate": (debugcheckstate, [], ''),
4498 4558 "debugcommands": (debugcommands, [], _('[COMMAND]')),
4499 4559 "debugcomplete":
4500 4560 (debugcomplete,
4501 4561 [('o', 'options', None, _('show the command options'))],
4502 4562 _('[-o] CMD')),
4503 4563 "debugdag":
4504 4564 (debugdag,
4505 4565 [('t', 'tags', None, _('use tags as labels')),
4506 4566 ('b', 'branches', None, _('annotate with branch names')),
4507 4567 ('', 'dots', None, _('use dots for runs')),
4508 4568 ('s', 'spaces', None, _('separate elements by spaces')),
4509 4569 ],
4510 4570 _('[OPTION]... [FILE [REV]...]')),
4511 4571 "debugdate":
4512 4572 (debugdate,
4513 4573 [('e', 'extended', None, _('try extended date formats'))],
4514 4574 _('[-e] DATE [RANGE]')),
4515 4575 "debugdata": (debugdata, [], _('FILE REV')),
4576 "debugdiscovery": (debugdiscovery,
4577 [('', 'old', None,
4578 _('use old-style discovery')),
4579 ('', 'nonheads', None,
4580 _('use old-style discovery with non-heads included')),
4581 ] + remoteopts,
4582 _('[-l REV] [-r REV] [-b BRANCH]...'
4583 ' [OTHER]')),
4516 4584 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
4517 4585 "debuggetbundle":
4518 4586 (debuggetbundle,
4519 4587 [('H', 'head', [], _('id of head node'), _('ID')),
4520 4588 ('C', 'common', [], _('id of common node'), _('ID')),
4521 4589 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
4522 4590 ],
4523 4591 _('REPO FILE [-H|-C ID]...')),
4524 4592 "debugignore": (debugignore, [], ''),
4525 4593 "debugindex": (debugindex,
4526 4594 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
4527 4595 _('FILE')),
4528 4596 "debugindexdot": (debugindexdot, [], _('FILE')),
4529 4597 "debuginstall": (debuginstall, [], ''),
4530 4598 "debugknown": (debugknown, [], _('REPO ID...')),
4531 4599 "debugpushkey": (debugpushkey, [], _('REPO NAMESPACE [KEY OLD NEW]')),
4532 4600 "debugrebuildstate":
4533 4601 (debugrebuildstate,
4534 4602 [('r', 'rev', '',
4535 4603 _('revision to rebuild to'), _('REV'))],
4536 4604 _('[-r REV] [REV]')),
4537 4605 "debugrename":
4538 4606 (debugrename,
4539 4607 [('r', 'rev', '',
4540 4608 _('revision to debug'), _('REV'))],
4541 4609 _('[-r REV] FILE')),
4542 4610 "debugrevspec":
4543 4611 (debugrevspec, [], ('REVSPEC')),
4544 4612 "debugsetparents":
4545 4613 (debugsetparents, [], _('REV1 [REV2]')),
4546 4614 "debugstate":
4547 4615 (debugstate,
4548 4616 [('', 'nodates', None, _('do not display the saved mtime')),
4549 4617 ('', 'datesort', None, _('sort by saved mtime'))],
4550 4618 _('[OPTION]...')),
4551 4619 "debugsub":
4552 4620 (debugsub,
4553 4621 [('r', 'rev', '',
4554 4622 _('revision to check'), _('REV'))],
4555 4623 _('[-r REV] [REV]')),
4556 4624 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
4557 4625 "debugwireargs":
4558 4626 (debugwireargs,
4559 4627 [('', 'three', '', 'three'),
4560 4628 ('', 'four', '', 'four'),
4561 4629 ('', 'five', '', 'five'),
4562 4630 ] + remoteopts,
4563 4631 _('REPO [OPTIONS]... [ONE [TWO]]')),
4564 4632 "^diff":
4565 4633 (diff,
4566 4634 [('r', 'rev', [],
4567 4635 _('revision'), _('REV')),
4568 4636 ('c', 'change', '',
4569 4637 _('change made by revision'), _('REV'))
4570 4638 ] + diffopts + diffopts2 + walkopts + subrepoopts,
4571 4639 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...')),
4572 4640 "^export":
4573 4641 (export,
4574 4642 [('o', 'output', '',
4575 4643 _('print output to file with formatted name'), _('FORMAT')),
4576 4644 ('', 'switch-parent', None, _('diff against the second parent')),
4577 4645 ('r', 'rev', [],
4578 4646 _('revisions to export'), _('REV')),
4579 4647 ] + diffopts,
4580 4648 _('[OPTION]... [-o OUTFILESPEC] REV...')),
4581 4649 "^forget":
4582 4650 (forget,
4583 4651 [] + walkopts,
4584 4652 _('[OPTION]... FILE...')),
4585 4653 "grep":
4586 4654 (grep,
4587 4655 [('0', 'print0', None, _('end fields with NUL')),
4588 4656 ('', 'all', None, _('print all revisions that match')),
4589 4657 ('a', 'text', None, _('treat all files as text')),
4590 4658 ('f', 'follow', None,
4591 4659 _('follow changeset history,'
4592 4660 ' or file history across copies and renames')),
4593 4661 ('i', 'ignore-case', None, _('ignore case when matching')),
4594 4662 ('l', 'files-with-matches', None,
4595 4663 _('print only filenames and revisions that match')),
4596 4664 ('n', 'line-number', None, _('print matching line numbers')),
4597 4665 ('r', 'rev', [],
4598 4666 _('only search files changed within revision range'), _('REV')),
4599 4667 ('u', 'user', None, _('list the author (long with -v)')),
4600 4668 ('d', 'date', None, _('list the date (short with -q)')),
4601 4669 ] + walkopts,
4602 4670 _('[OPTION]... PATTERN [FILE]...')),
4603 4671 "heads":
4604 4672 (heads,
4605 4673 [('r', 'rev', '',
4606 4674 _('show only heads which are descendants of STARTREV'),
4607 4675 _('STARTREV')),
4608 4676 ('t', 'topo', False, _('show topological heads only')),
4609 4677 ('a', 'active', False,
4610 4678 _('show active branchheads only (DEPRECATED)')),
4611 4679 ('c', 'closed', False,
4612 4680 _('show normal and closed branch heads')),
4613 4681 ] + templateopts,
4614 4682 _('[-ac] [-r STARTREV] [REV]...')),
4615 4683 "help": (help_, [], _('[TOPIC]')),
4616 4684 "identify|id":
4617 4685 (identify,
4618 4686 [('r', 'rev', '',
4619 4687 _('identify the specified revision'), _('REV')),
4620 4688 ('n', 'num', None, _('show local revision number')),
4621 4689 ('i', 'id', None, _('show global revision id')),
4622 4690 ('b', 'branch', None, _('show branch')),
4623 4691 ('t', 'tags', None, _('show tags')),
4624 4692 ('B', 'bookmarks', None, _('show bookmarks'))],
4625 4693 _('[-nibtB] [-r REV] [SOURCE]')),
4626 4694 "import|patch":
4627 4695 (import_,
4628 4696 [('p', 'strip', 1,
4629 4697 _('directory strip option for patch. This has the same '
4630 4698 'meaning as the corresponding patch option'),
4631 4699 _('NUM')),
4632 4700 ('b', 'base', '',
4633 4701 _('base path'), _('PATH')),
4634 4702 ('f', 'force', None,
4635 4703 _('skip check for outstanding uncommitted changes')),
4636 4704 ('', 'no-commit', None,
4637 4705 _("don't commit, just update the working directory")),
4638 4706 ('', 'exact', None,
4639 4707 _('apply patch to the nodes from which it was generated')),
4640 4708 ('', 'import-branch', None,
4641 4709 _('use any branch information in patch (implied by --exact)'))] +
4642 4710 commitopts + commitopts2 + similarityopts,
4643 4711 _('[OPTION]... PATCH...')),
4644 4712 "incoming|in":
4645 4713 (incoming,
4646 4714 [('f', 'force', None,
4647 4715 _('run even if remote repository is unrelated')),
4648 4716 ('n', 'newest-first', None, _('show newest record first')),
4649 4717 ('', 'bundle', '',
4650 4718 _('file to store the bundles into'), _('FILE')),
4651 4719 ('r', 'rev', [],
4652 4720 _('a remote changeset intended to be added'), _('REV')),
4653 4721 ('B', 'bookmarks', False, _("compare bookmarks")),
4654 4722 ('b', 'branch', [],
4655 4723 _('a specific branch you would like to pull'), _('BRANCH')),
4656 4724 ] + logopts + remoteopts + subrepoopts,
4657 4725 _('[-p] [-n] [-M] [-f] [-r REV]...'
4658 4726 ' [--bundle FILENAME] [SOURCE]')),
4659 4727 "^init":
4660 4728 (init,
4661 4729 remoteopts,
4662 4730 _('[-e CMD] [--remotecmd CMD] [DEST]')),
4663 4731 "locate":
4664 4732 (locate,
4665 4733 [('r', 'rev', '',
4666 4734 _('search the repository as it is in REV'), _('REV')),
4667 4735 ('0', 'print0', None,
4668 4736 _('end filenames with NUL, for use with xargs')),
4669 4737 ('f', 'fullpath', None,
4670 4738 _('print complete paths from the filesystem root')),
4671 4739 ] + walkopts,
4672 4740 _('[OPTION]... [PATTERN]...')),
4673 4741 "^log|history":
4674 4742 (log,
4675 4743 [('f', 'follow', None,
4676 4744 _('follow changeset history,'
4677 4745 ' or file history across copies and renames')),
4678 4746 ('', 'follow-first', None,
4679 4747 _('only follow the first parent of merge changesets')),
4680 4748 ('d', 'date', '',
4681 4749 _('show revisions matching date spec'), _('DATE')),
4682 4750 ('C', 'copies', None, _('show copied files')),
4683 4751 ('k', 'keyword', [],
4684 4752 _('do case-insensitive search for a given text'), _('TEXT')),
4685 4753 ('r', 'rev', [],
4686 4754 _('show the specified revision or range'), _('REV')),
4687 4755 ('', 'removed', None, _('include revisions where files were removed')),
4688 4756 ('m', 'only-merges', None, _('show only merges')),
4689 4757 ('u', 'user', [],
4690 4758 _('revisions committed by user'), _('USER')),
4691 4759 ('', 'only-branch', [],
4692 4760 _('show only changesets within the given named branch (DEPRECATED)'),
4693 4761 _('BRANCH')),
4694 4762 ('b', 'branch', [],
4695 4763 _('show changesets within the given named branch'), _('BRANCH')),
4696 4764 ('P', 'prune', [],
4697 4765 _('do not display revision or any of its ancestors'), _('REV')),
4698 4766 ] + logopts + walkopts,
4699 4767 _('[OPTION]... [FILE]')),
4700 4768 "manifest":
4701 4769 (manifest,
4702 4770 [('r', 'rev', '',
4703 4771 _('revision to display'), _('REV'))],
4704 4772 _('[-r REV]')),
4705 4773 "^merge":
4706 4774 (merge,
4707 4775 [('f', 'force', None, _('force a merge with outstanding changes')),
4708 4776 ('t', 'tool', '', _('specify merge tool')),
4709 4777 ('r', 'rev', '',
4710 4778 _('revision to merge'), _('REV')),
4711 4779 ('P', 'preview', None,
4712 4780 _('review revisions to merge (no merge is performed)'))],
4713 4781 _('[-P] [-f] [[-r] REV]')),
4714 4782 "outgoing|out":
4715 4783 (outgoing,
4716 4784 [('f', 'force', None,
4717 4785 _('run even when the destination is unrelated')),
4718 4786 ('r', 'rev', [],
4719 4787 _('a changeset intended to be included in the destination'),
4720 4788 _('REV')),
4721 4789 ('n', 'newest-first', None, _('show newest record first')),
4722 4790 ('B', 'bookmarks', False, _("compare bookmarks")),
4723 4791 ('b', 'branch', [],
4724 4792 _('a specific branch you would like to push'), _('BRANCH')),
4725 4793 ] + logopts + remoteopts + subrepoopts,
4726 4794 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
4727 4795 "parents":
4728 4796 (parents,
4729 4797 [('r', 'rev', '',
4730 4798 _('show parents of the specified revision'), _('REV')),
4731 4799 ] + templateopts,
4732 4800 _('[-r REV] [FILE]')),
4733 4801 "paths": (paths, [], _('[NAME]')),
4734 4802 "^pull":
4735 4803 (pull,
4736 4804 [('u', 'update', None,
4737 4805 _('update to new branch head if changesets were pulled')),
4738 4806 ('f', 'force', None,
4739 4807 _('run even when remote repository is unrelated')),
4740 4808 ('r', 'rev', [],
4741 4809 _('a remote changeset intended to be added'), _('REV')),
4742 4810 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4743 4811 ('b', 'branch', [],
4744 4812 _('a specific branch you would like to pull'), _('BRANCH')),
4745 4813 ] + remoteopts,
4746 4814 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
4747 4815 "^push":
4748 4816 (push,
4749 4817 [('f', 'force', None, _('force push')),
4750 4818 ('r', 'rev', [],
4751 4819 _('a changeset intended to be included in the destination'),
4752 4820 _('REV')),
4753 4821 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4754 4822 ('b', 'branch', [],
4755 4823 _('a specific branch you would like to push'), _('BRANCH')),
4756 4824 ('', 'new-branch', False, _('allow pushing a new branch')),
4757 4825 ] + remoteopts,
4758 4826 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
4759 4827 "recover": (recover, []),
4760 4828 "^remove|rm":
4761 4829 (remove,
4762 4830 [('A', 'after', None, _('record delete for missing files')),
4763 4831 ('f', 'force', None,
4764 4832 _('remove (and delete) file even if added or modified')),
4765 4833 ] + walkopts,
4766 4834 _('[OPTION]... FILE...')),
4767 4835 "rename|move|mv":
4768 4836 (rename,
4769 4837 [('A', 'after', None, _('record a rename that has already occurred')),
4770 4838 ('f', 'force', None,
4771 4839 _('forcibly copy over an existing managed file')),
4772 4840 ] + walkopts + dryrunopts,
4773 4841 _('[OPTION]... SOURCE... DEST')),
4774 4842 "resolve":
4775 4843 (resolve,
4776 4844 [('a', 'all', None, _('select all unresolved files')),
4777 4845 ('l', 'list', None, _('list state of files needing merge')),
4778 4846 ('m', 'mark', None, _('mark files as resolved')),
4779 4847 ('u', 'unmark', None, _('mark files as unresolved')),
4780 4848 ('t', 'tool', '', _('specify merge tool')),
4781 4849 ('n', 'no-status', None, _('hide status prefix'))]
4782 4850 + walkopts,
4783 4851 _('[OPTION]... [FILE]...')),
4784 4852 "revert":
4785 4853 (revert,
4786 4854 [('a', 'all', None, _('revert all changes when no arguments given')),
4787 4855 ('d', 'date', '',
4788 4856 _('tipmost revision matching date'), _('DATE')),
4789 4857 ('r', 'rev', '',
4790 4858 _('revert to the specified revision'), _('REV')),
4791 4859 ('', 'no-backup', None, _('do not save backup copies of files')),
4792 4860 ] + walkopts + dryrunopts,
4793 4861 _('[OPTION]... [-r REV] [NAME]...')),
4794 4862 "rollback": (rollback, dryrunopts),
4795 4863 "root": (root, []),
4796 4864 "^serve":
4797 4865 (serve,
4798 4866 [('A', 'accesslog', '',
4799 4867 _('name of access log file to write to'), _('FILE')),
4800 4868 ('d', 'daemon', None, _('run server in background')),
4801 4869 ('', 'daemon-pipefds', '',
4802 4870 _('used internally by daemon mode'), _('NUM')),
4803 4871 ('E', 'errorlog', '',
4804 4872 _('name of error log file to write to'), _('FILE')),
4805 4873 # use string type, then we can check if something was passed
4806 4874 ('p', 'port', '',
4807 4875 _('port to listen on (default: 8000)'), _('PORT')),
4808 4876 ('a', 'address', '',
4809 4877 _('address to listen on (default: all interfaces)'), _('ADDR')),
4810 4878 ('', 'prefix', '',
4811 4879 _('prefix path to serve from (default: server root)'), _('PREFIX')),
4812 4880 ('n', 'name', '',
4813 4881 _('name to show in web pages (default: working directory)'),
4814 4882 _('NAME')),
4815 4883 ('', 'web-conf', '',
4816 4884 _('name of the hgweb config file (see "hg help hgweb")'),
4817 4885 _('FILE')),
4818 4886 ('', 'webdir-conf', '',
4819 4887 _('name of the hgweb config file (DEPRECATED)'), _('FILE')),
4820 4888 ('', 'pid-file', '',
4821 4889 _('name of file to write process ID to'), _('FILE')),
4822 4890 ('', 'stdio', None, _('for remote clients')),
4823 4891 ('t', 'templates', '',
4824 4892 _('web templates to use'), _('TEMPLATE')),
4825 4893 ('', 'style', '',
4826 4894 _('template style to use'), _('STYLE')),
4827 4895 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4828 4896 ('', 'certificate', '',
4829 4897 _('SSL certificate file'), _('FILE'))],
4830 4898 _('[OPTION]...')),
4831 4899 "showconfig|debugconfig":
4832 4900 (showconfig,
4833 4901 [('u', 'untrusted', None, _('show untrusted configuration options'))],
4834 4902 _('[-u] [NAME]...')),
4835 4903 "^summary|sum":
4836 4904 (summary,
4837 4905 [('', 'remote', None, _('check for push and pull'))], '[--remote]'),
4838 4906 "^status|st":
4839 4907 (status,
4840 4908 [('A', 'all', None, _('show status of all files')),
4841 4909 ('m', 'modified', None, _('show only modified files')),
4842 4910 ('a', 'added', None, _('show only added files')),
4843 4911 ('r', 'removed', None, _('show only removed files')),
4844 4912 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4845 4913 ('c', 'clean', None, _('show only files without changes')),
4846 4914 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4847 4915 ('i', 'ignored', None, _('show only ignored files')),
4848 4916 ('n', 'no-status', None, _('hide status prefix')),
4849 4917 ('C', 'copies', None, _('show source of copied files')),
4850 4918 ('0', 'print0', None,
4851 4919 _('end filenames with NUL, for use with xargs')),
4852 4920 ('', 'rev', [],
4853 4921 _('show difference from revision'), _('REV')),
4854 4922 ('', 'change', '',
4855 4923 _('list the changed files of a revision'), _('REV')),
4856 4924 ] + walkopts + subrepoopts,
4857 4925 _('[OPTION]... [FILE]...')),
4858 4926 "tag":
4859 4927 (tag,
4860 4928 [('f', 'force', None, _('force tag')),
4861 4929 ('l', 'local', None, _('make the tag local')),
4862 4930 ('r', 'rev', '',
4863 4931 _('revision to tag'), _('REV')),
4864 4932 ('', 'remove', None, _('remove a tag')),
4865 4933 # -l/--local is already there, commitopts cannot be used
4866 4934 ('e', 'edit', None, _('edit commit message')),
4867 4935 ('m', 'message', '',
4868 4936 _('use <text> as commit message'), _('TEXT')),
4869 4937 ] + commitopts2,
4870 4938 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
4871 4939 "tags": (tags, [], ''),
4872 4940 "tip":
4873 4941 (tip,
4874 4942 [('p', 'patch', None, _('show patch')),
4875 4943 ('g', 'git', None, _('use git extended diff format')),
4876 4944 ] + templateopts,
4877 4945 _('[-p] [-g]')),
4878 4946 "unbundle":
4879 4947 (unbundle,
4880 4948 [('u', 'update', None,
4881 4949 _('update to new branch head if changesets were unbundled'))],
4882 4950 _('[-u] FILE...')),
4883 4951 "^update|up|checkout|co":
4884 4952 (update,
4885 4953 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
4886 4954 ('c', 'check', None,
4887 4955 _('update across branches if no uncommitted changes')),
4888 4956 ('d', 'date', '',
4889 4957 _('tipmost revision matching date'), _('DATE')),
4890 4958 ('r', 'rev', '',
4891 4959 _('revision'), _('REV'))],
4892 4960 _('[-c] [-C] [-d DATE] [[-r] REV]')),
4893 4961 "verify": (verify, []),
4894 4962 "version": (version_, []),
4895 4963 }
4896 4964
4897 4965 norepo = ("clone init version help debugcommands debugcomplete"
4898 4966 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
4899 4967 " debugknown debuggetbundle debugbundle")
4900 4968 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
4901 4969 " debugdata debugindex debugindexdot")
@@ -1,288 +1,169 b''
1 1 # discovery.py - protocol changeset discovery functions
2 2 #
3 3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import nullid, short
9 9 from i18n import _
10 import util, error
10 import util, error, setdiscovery, treediscovery
11 11
12 12 def findcommonincoming(repo, remote, heads=None, force=False):
13 13 """Return a tuple (common, anyincoming, heads) used to identify the common
14 14 subset of nodes between repo and remote.
15 15
16 16 "common" is a list of (at least) the heads of the common subset.
17 17 "anyincoming" is testable as a boolean indicating if any nodes are missing
18 18 locally. If remote does not support getbundle, this actually is a list of
19 19 roots of the nodes that would be incoming, to be supplied to
20 20 changegroupsubset. No code except for pull should be relying on this fact
21 21 any longer.
22 22 "heads" is either the supplied heads, or else the remote's heads.
23
24 If you pass heads and they are all known locally, the reponse lists justs
25 these heads in "common" and in "heads".
23 26 """
24 27
25 m = repo.changelog.nodemap
26 search = []
27 fetch = set()
28 seen = set()
29 seenbranch = set()
30 base = set()
31
32 if not heads:
33 heads = remote.heads()
34
35 if repo.changelog.tip() == nullid:
36 base.add(nullid)
37 if heads != [nullid]:
38 return [nullid], [nullid], list(heads)
39 return [nullid], [], []
40
41 # assume we're closer to the tip than the root
42 # and start by examining the heads
43 repo.ui.status(_("searching for changes\n"))
44
45 if remote.capable('getbundle'):
46 myheads = repo.heads()
47 known = remote.known(myheads)
48 if util.all(known):
49 hasincoming = set(heads).difference(set(myheads)) and True
50 return myheads, hasincoming, heads
51
52 unknown = []
53 for h in heads:
54 if h not in m:
55 unknown.append(h)
56 else:
57 base.add(h)
58
59 heads = unknown
60 if not unknown:
61 return list(base), [], []
62
63 req = set(unknown)
64 reqcnt = 0
65
66 # search through remote branches
67 # a 'branch' here is a linear segment of history, with four parts:
68 # head, root, first parent, second parent
69 # (a branch always has two parents (or none) by definition)
70 unknown = remote.branches(unknown)
71 while unknown:
72 r = []
73 while unknown:
74 n = unknown.pop(0)
75 if n[0] in seen:
76 continue
28 if not remote.capable('getbundle'):
29 return treediscovery.findcommonincoming(repo, remote, heads, force)
77 30
78 repo.ui.debug("examining %s:%s\n"
79 % (short(n[0]), short(n[1])))
80 if n[0] == nullid: # found the end of the branch
81 pass
82 elif n in seenbranch:
83 repo.ui.debug("branch already found\n")
84 continue
85 elif n[1] and n[1] in m: # do we know the base?
86 repo.ui.debug("found incomplete branch %s:%s\n"
87 % (short(n[0]), short(n[1])))
88 search.append(n[0:2]) # schedule branch range for scanning
89 seenbranch.add(n)
90 else:
91 if n[1] not in seen and n[1] not in fetch:
92 if n[2] in m and n[3] in m:
93 repo.ui.debug("found new changeset %s\n" %
94 short(n[1]))
95 fetch.add(n[1]) # earliest unknown
96 for p in n[2:4]:
97 if p in m:
98 base.add(p) # latest known
99
100 for p in n[2:4]:
101 if p not in req and p not in m:
102 r.append(p)
103 req.add(p)
104 seen.add(n[0])
105
106 if r:
107 reqcnt += 1
108 repo.ui.progress(_('searching'), reqcnt, unit=_('queries'))
109 repo.ui.debug("request %d: %s\n" %
110 (reqcnt, " ".join(map(short, r))))
111 for p in xrange(0, len(r), 10):
112 for b in remote.branches(r[p:p + 10]):
113 repo.ui.debug("received %s:%s\n" %
114 (short(b[0]), short(b[1])))
115 unknown.append(b)
31 if heads:
32 allknown = True
33 nm = repo.changelog.nodemap
34 for h in heads:
35 if nm.get(h) is None:
36 allknown = False
37 break
38 if allknown:
39 return (heads, False, heads)
116 40
117 # do binary search on the branches we found
118 while search:
119 newsearch = []
120 reqcnt += 1
121 repo.ui.progress(_('searching'), reqcnt, unit=_('queries'))
122 for n, l in zip(search, remote.between(search)):
123 l.append(n[1])
124 p = n[0]
125 f = 1
126 for i in l:
127 repo.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
128 if i in m:
129 if f <= 2:
130 repo.ui.debug("found new branch changeset %s\n" %
131 short(p))
132 fetch.add(p)
133 base.add(i)
134 else:
135 repo.ui.debug("narrowed branch search to %s:%s\n"
136 % (short(p), short(i)))
137 newsearch.append((p, i))
138 break
139 p, f = i, f * 2
140 search = newsearch
141
142 # sanity check our fetch list
143 for f in fetch:
144 if f in m:
145 raise error.RepoError(_("already have changeset ")
146 + short(f[:4]))
147
148 base = list(base)
149 if base == [nullid]:
150 if force:
151 repo.ui.warn(_("warning: repository is unrelated\n"))
152 else:
153 raise util.Abort(_("repository is unrelated"))
154
155 repo.ui.debug("found new changesets starting at " +
156 " ".join([short(f) for f in fetch]) + "\n")
157
158 repo.ui.progress(_('searching'), None)
159 repo.ui.debug("%d total queries\n" % reqcnt)
160
161 return base, list(fetch), heads
41 res = setdiscovery.findcommonheads(repo.ui, repo, remote,
42 abortwhenunrelated=not force)
43 common, anyinc, srvheads = res
44 return (list(common), anyinc, heads or list(srvheads))
162 45
163 46 def prepush(repo, remote, force, revs, newbranch):
164 47 '''Analyze the local and remote repositories and determine which
165 48 changesets need to be pushed to the remote. Return value depends
166 49 on circumstances:
167 50
168 51 If we are not going to push anything, return a tuple (None,
169 52 outgoing) where outgoing is 0 if there are no outgoing
170 53 changesets and 1 if there are, but we refuse to push them
171 54 (e.g. would create new remote heads).
172 55
173 56 Otherwise, return a tuple (changegroup, remoteheads), where
174 57 changegroup is a readable file-like object whose read() returns
175 58 successive changegroup chunks ready to be sent over the wire and
176 59 remoteheads is the list of remote heads.'''
177 remoteheads = remote.heads()
178 common, inc, _rheads = findcommonincoming(repo, remote, heads=remoteheads,
179 force=force)
60 common, inc, remoteheads = findcommonincoming(repo, remote, force=force)
180 61
181 62 cl = repo.changelog
182 63 outg = cl.findmissing(common, revs)
183 64
184 65 if not outg:
185 66 repo.ui.status(_("no changes found\n"))
186 67 return None, 1
187 68
188 69 if not force and remoteheads != [nullid]:
189 70 if remote.capable('branchmap'):
190 71 # Check for each named branch if we're creating new remote heads.
191 72 # To be a remote head after push, node must be either:
192 73 # - unknown locally
193 74 # - a local outgoing head descended from update
194 75 # - a remote head that's known locally and not
195 76 # ancestral to an outgoing head
196 77
197 78 # 1. Create set of branches involved in the push.
198 79 branches = set(repo[n].branch() for n in outg)
199 80
200 81 # 2. Check for new branches on the remote.
201 82 remotemap = remote.branchmap()
202 83 newbranches = branches - set(remotemap)
203 84 if newbranches and not newbranch: # new branch requires --new-branch
204 85 branchnames = ', '.join(sorted(newbranches))
205 86 raise util.Abort(_("push creates new remote branches: %s!")
206 87 % branchnames,
207 88 hint=_("use 'hg push --new-branch' to create"
208 89 " new remote branches"))
209 90 branches.difference_update(newbranches)
210 91
211 92 # 3. Construct the initial oldmap and newmap dicts.
212 93 # They contain information about the remote heads before and
213 94 # after the push, respectively.
214 95 # Heads not found locally are not included in either dict,
215 96 # since they won't be affected by the push.
216 97 # unsynced contains all branches with incoming changesets.
217 98 oldmap = {}
218 99 newmap = {}
219 100 unsynced = set()
220 101 for branch in branches:
221 102 remotebrheads = remotemap[branch]
222 103 prunedbrheads = [h for h in remotebrheads if h in cl.nodemap]
223 104 oldmap[branch] = prunedbrheads
224 105 newmap[branch] = list(prunedbrheads)
225 106 if len(remotebrheads) > len(prunedbrheads):
226 107 unsynced.add(branch)
227 108
228 109 # 4. Update newmap with outgoing changes.
229 110 # This will possibly add new heads and remove existing ones.
230 111 ctxgen = (repo[n] for n in outg)
231 112 repo._updatebranchcache(newmap, ctxgen)
232 113
233 114 else:
234 115 # 1-4b. old servers: Check for new topological heads.
235 116 # Construct {old,new}map with branch = None (topological branch).
236 117 # (code based on _updatebranchcache)
237 118 oldheads = set(h for h in remoteheads if h in cl.nodemap)
238 119 newheads = oldheads.union(outg)
239 120 if len(newheads) > 1:
240 121 for latest in reversed(outg):
241 122 if latest not in newheads:
242 123 continue
243 124 minhrev = min(cl.rev(h) for h in newheads)
244 125 reachable = cl.reachable(latest, cl.node(minhrev))
245 126 reachable.remove(latest)
246 127 newheads.difference_update(reachable)
247 128 branches = set([None])
248 129 newmap = {None: newheads}
249 130 oldmap = {None: oldheads}
250 131 unsynced = inc and branches or set()
251 132
252 133 # 5. Check for new heads.
253 134 # If there are more heads after the push than before, a suitable
254 135 # error message, depending on unsynced status, is displayed.
255 136 error = None
256 137 for branch in branches:
257 138 newhs = set(newmap[branch])
258 139 oldhs = set(oldmap[branch])
259 140 if len(newhs) > len(oldhs):
260 141 if error is None:
261 142 if branch:
262 143 error = _("push creates new remote heads "
263 144 "on branch '%s'!") % branch
264 145 else:
265 146 error = _("push creates new remote heads!")
266 147 if branch in unsynced:
267 148 hint = _("you should pull and merge or "
268 149 "use push -f to force")
269 150 else:
270 151 hint = _("did you forget to merge? "
271 152 "use push -f to force")
272 153 if branch:
273 154 repo.ui.debug("new remote heads on branch '%s'\n" % branch)
274 155 for h in (newhs - oldhs):
275 156 repo.ui.debug("new remote head %s\n" % short(h))
276 157 if error:
277 158 raise util.Abort(error, hint=hint)
278 159
279 160 # 6. Check for unsynced changes on involved branches.
280 161 if unsynced:
281 162 repo.ui.warn(_("note: unsynced remote changes!\n"))
282 163
283 164 if revs is None:
284 165 # use the fast path, no race possible on push
285 166 cg = repo._changegroup(outg, 'push')
286 167 else:
287 168 cg = repo.getbundle('push', heads=revs, common=common)
288 169 return cg, remoteheads
@@ -1,1265 +1,1270 b''
1 1 # revlog.py - storage back-end for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 """Storage back-end for Mercurial.
9 9
10 10 This provides efficient delta storage with O(1) retrieve and append
11 11 and O(changes) merge between branches.
12 12 """
13 13
14 14 # import stuff from node for others to import from revlog
15 15 from node import bin, hex, nullid, nullrev, short #@UnusedImport
16 16 from i18n import _
17 17 import ancestor, mdiff, parsers, error, util
18 18 import struct, zlib, errno
19 19
20 20 _pack = struct.pack
21 21 _unpack = struct.unpack
22 22 _compress = zlib.compress
23 23 _decompress = zlib.decompress
24 24 _sha = util.sha1
25 25
26 26 # revlog header flags
27 27 REVLOGV0 = 0
28 28 REVLOGNG = 1
29 29 REVLOGNGINLINEDATA = (1 << 16)
30 30 REVLOGSHALLOW = (1 << 17)
31 31 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
32 32 REVLOG_DEFAULT_FORMAT = REVLOGNG
33 33 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
34 34 REVLOGNG_FLAGS = REVLOGNGINLINEDATA | REVLOGSHALLOW
35 35
36 36 # revlog index flags
37 37 REVIDX_PARENTDELTA = 1
38 38 REVIDX_PUNCHED_FLAG = 2
39 39 REVIDX_KNOWN_FLAGS = REVIDX_PUNCHED_FLAG | REVIDX_PARENTDELTA
40 40
41 41 # max size of revlog with inline data
42 42 _maxinline = 131072
43 43 _chunksize = 1048576
44 44
45 45 RevlogError = error.RevlogError
46 46 LookupError = error.LookupError
47 47
48 48 def getoffset(q):
49 49 return int(q >> 16)
50 50
51 51 def gettype(q):
52 52 return int(q & 0xFFFF)
53 53
54 54 def offset_type(offset, type):
55 55 return long(long(offset) << 16 | type)
56 56
57 57 nullhash = _sha(nullid)
58 58
59 59 def hash(text, p1, p2):
60 60 """generate a hash from the given text and its parent hashes
61 61
62 62 This hash combines both the current file contents and its history
63 63 in a manner that makes it easy to distinguish nodes with the same
64 64 content in the revision graph.
65 65 """
66 66 # As of now, if one of the parent node is null, p2 is null
67 67 if p2 == nullid:
68 68 # deep copy of a hash is faster than creating one
69 69 s = nullhash.copy()
70 70 s.update(p1)
71 71 else:
72 72 # none of the parent nodes are nullid
73 73 l = [p1, p2]
74 74 l.sort()
75 75 s = _sha(l[0])
76 76 s.update(l[1])
77 77 s.update(text)
78 78 return s.digest()
79 79
80 80 def compress(text):
81 81 """ generate a possibly-compressed representation of text """
82 82 if not text:
83 83 return ("", text)
84 84 l = len(text)
85 85 bin = None
86 86 if l < 44:
87 87 pass
88 88 elif l > 1000000:
89 89 # zlib makes an internal copy, thus doubling memory usage for
90 90 # large files, so lets do this in pieces
91 91 z = zlib.compressobj()
92 92 p = []
93 93 pos = 0
94 94 while pos < l:
95 95 pos2 = pos + 2**20
96 96 p.append(z.compress(text[pos:pos2]))
97 97 pos = pos2
98 98 p.append(z.flush())
99 99 if sum(map(len, p)) < l:
100 100 bin = "".join(p)
101 101 else:
102 102 bin = _compress(text)
103 103 if bin is None or len(bin) > l:
104 104 if text[0] == '\0':
105 105 return ("", text)
106 106 return ('u', text)
107 107 return ("", bin)
108 108
109 109 def decompress(bin):
110 110 """ decompress the given input """
111 111 if not bin:
112 112 return bin
113 113 t = bin[0]
114 114 if t == '\0':
115 115 return bin
116 116 if t == 'x':
117 117 return _decompress(bin)
118 118 if t == 'u':
119 119 return bin[1:]
120 120 raise RevlogError(_("unknown compression type %r") % t)
121 121
122 122 indexformatv0 = ">4l20s20s20s"
123 123 v0shaoffset = 56
124 124
125 125 class revlogoldio(object):
126 126 def __init__(self):
127 127 self.size = struct.calcsize(indexformatv0)
128 128
129 129 def parseindex(self, data, inline):
130 130 s = self.size
131 131 index = []
132 132 nodemap = {nullid: nullrev}
133 133 n = off = 0
134 134 l = len(data)
135 135 while off + s <= l:
136 136 cur = data[off:off + s]
137 137 off += s
138 138 e = _unpack(indexformatv0, cur)
139 139 # transform to revlogv1 format
140 140 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
141 141 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
142 142 index.append(e2)
143 143 nodemap[e[6]] = n
144 144 n += 1
145 145
146 146 # add the magic null revision at -1
147 147 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
148 148
149 149 return index, nodemap, None
150 150
151 151 def packentry(self, entry, node, version, rev):
152 152 if gettype(entry[0]):
153 153 raise RevlogError(_("index entry flags need RevlogNG"))
154 154 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
155 155 node(entry[5]), node(entry[6]), entry[7])
156 156 return _pack(indexformatv0, *e2)
157 157
158 158 # index ng:
159 159 # 6 bytes: offset
160 160 # 2 bytes: flags
161 161 # 4 bytes: compressed length
162 162 # 4 bytes: uncompressed length
163 163 # 4 bytes: base rev
164 164 # 4 bytes: link rev
165 165 # 4 bytes: parent 1 rev
166 166 # 4 bytes: parent 2 rev
167 167 # 32 bytes: nodeid
168 168 indexformatng = ">Qiiiiii20s12x"
169 169 ngshaoffset = 32
170 170 versionformat = ">I"
171 171
172 172 class revlogio(object):
173 173 def __init__(self):
174 174 self.size = struct.calcsize(indexformatng)
175 175
176 176 def parseindex(self, data, inline):
177 177 # call the C implementation to parse the index data
178 178 index, cache = parsers.parse_index2(data, inline)
179 179 return index, None, cache
180 180
181 181 def packentry(self, entry, node, version, rev):
182 182 p = _pack(indexformatng, *entry)
183 183 if rev == 0:
184 184 p = _pack(versionformat, version) + p[4:]
185 185 return p
186 186
187 187 class revlog(object):
188 188 """
189 189 the underlying revision storage object
190 190
191 191 A revlog consists of two parts, an index and the revision data.
192 192
193 193 The index is a file with a fixed record size containing
194 194 information on each revision, including its nodeid (hash), the
195 195 nodeids of its parents, the position and offset of its data within
196 196 the data file, and the revision it's based on. Finally, each entry
197 197 contains a linkrev entry that can serve as a pointer to external
198 198 data.
199 199
200 200 The revision data itself is a linear collection of data chunks.
201 201 Each chunk represents a revision and is usually represented as a
202 202 delta against the previous chunk. To bound lookup time, runs of
203 203 deltas are limited to about 2 times the length of the original
204 204 version data. This makes retrieval of a version proportional to
205 205 its size, or O(1) relative to the number of revisions.
206 206
207 207 Both pieces of the revlog are written to in an append-only
208 208 fashion, which means we never need to rewrite a file to insert or
209 209 remove data, and can use some simple techniques to avoid the need
210 210 for locking while reading.
211 211 """
212 212 def __init__(self, opener, indexfile, shallowroot=None):
213 213 """
214 214 create a revlog object
215 215
216 216 opener is a function that abstracts the file opening operation
217 217 and can be used to implement COW semantics or the like.
218 218 """
219 219 self.indexfile = indexfile
220 220 self.datafile = indexfile[:-2] + ".d"
221 221 self.opener = opener
222 222 self._cache = None
223 223 self._chunkcache = (0, '')
224 224 self.index = []
225 225 self._shallowroot = shallowroot
226 226 self._parentdelta = 0
227 227 self._pcache = {}
228 228 self._nodecache = {nullid: nullrev}
229 229 self._nodepos = None
230 230
231 231 v = REVLOG_DEFAULT_VERSION
232 232 if hasattr(opener, 'options') and 'defversion' in opener.options:
233 233 v = opener.options['defversion']
234 234 if v & REVLOGNG:
235 235 v |= REVLOGNGINLINEDATA
236 236 if v & REVLOGNG and 'parentdelta' in opener.options:
237 237 self._parentdelta = 1
238 238
239 239 if shallowroot:
240 240 v |= REVLOGSHALLOW
241 241
242 242 i = ''
243 243 try:
244 244 f = self.opener(self.indexfile)
245 245 i = f.read()
246 246 f.close()
247 247 if len(i) > 0:
248 248 v = struct.unpack(versionformat, i[:4])[0]
249 249 except IOError, inst:
250 250 if inst.errno != errno.ENOENT:
251 251 raise
252 252
253 253 self.version = v
254 254 self._inline = v & REVLOGNGINLINEDATA
255 255 self._shallow = v & REVLOGSHALLOW
256 256 flags = v & ~0xFFFF
257 257 fmt = v & 0xFFFF
258 258 if fmt == REVLOGV0 and flags:
259 259 raise RevlogError(_("index %s unknown flags %#04x for format v0")
260 260 % (self.indexfile, flags >> 16))
261 261 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
262 262 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
263 263 % (self.indexfile, flags >> 16))
264 264 elif fmt > REVLOGNG:
265 265 raise RevlogError(_("index %s unknown format %d")
266 266 % (self.indexfile, fmt))
267 267
268 268 self._io = revlogio()
269 269 if self.version == REVLOGV0:
270 270 self._io = revlogoldio()
271 271 try:
272 272 d = self._io.parseindex(i, self._inline)
273 273 except (ValueError, IndexError):
274 274 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
275 275 self.index, nodemap, self._chunkcache = d
276 276 if nodemap is not None:
277 277 self.nodemap = self._nodecache = nodemap
278 278 if not self._chunkcache:
279 279 self._chunkclear()
280 280
281 281 def tip(self):
282 282 return self.node(len(self.index) - 2)
283 283 def __len__(self):
284 284 return len(self.index) - 1
285 285 def __iter__(self):
286 286 for i in xrange(len(self)):
287 287 yield i
288 288
289 289 @util.propertycache
290 290 def nodemap(self):
291 291 self.rev(self.node(0))
292 292 return self._nodecache
293 293
294 294 def rev(self, node):
295 295 try:
296 296 return self._nodecache[node]
297 297 except KeyError:
298 298 n = self._nodecache
299 299 i = self.index
300 300 p = self._nodepos
301 301 if p is None:
302 302 p = len(i) - 2
303 303 for r in xrange(p, -1, -1):
304 304 v = i[r][7]
305 305 n[v] = r
306 306 if v == node:
307 307 self._nodepos = r - 1
308 308 return r
309 309 raise LookupError(node, self.indexfile, _('no node'))
310 310
311 311 def node(self, rev):
312 312 return self.index[rev][7]
313 313 def linkrev(self, rev):
314 314 return self.index[rev][4]
315 315 def parents(self, node):
316 316 i = self.index
317 317 d = i[self.rev(node)]
318 318 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
319 319 def parentrevs(self, rev):
320 320 return self.index[rev][5:7]
321 321 def start(self, rev):
322 322 return int(self.index[rev][0] >> 16)
323 323 def end(self, rev):
324 324 return self.start(rev) + self.length(rev)
325 325 def length(self, rev):
326 326 return self.index[rev][1]
327 327 def base(self, rev):
328 328 return self.index[rev][3]
329 329 def flags(self, rev):
330 330 return self.index[rev][0] & 0xFFFF
331 331 def rawsize(self, rev):
332 332 """return the length of the uncompressed text for a given revision"""
333 333 l = self.index[rev][2]
334 334 if l >= 0:
335 335 return l
336 336
337 337 t = self.revision(self.node(rev))
338 338 return len(t)
339 339 size = rawsize
340 340
341 341 def reachable(self, node, stop=None):
342 342 """return the set of all nodes ancestral to a given node, including
343 343 the node itself, stopping when stop is matched"""
344 344 reachable = set((node,))
345 345 visit = [node]
346 346 if stop:
347 347 stopn = self.rev(stop)
348 348 else:
349 349 stopn = 0
350 350 while visit:
351 351 n = visit.pop(0)
352 352 if n == stop:
353 353 continue
354 354 if n == nullid:
355 355 continue
356 356 for p in self.parents(n):
357 357 if self.rev(p) < stopn:
358 358 continue
359 359 if p not in reachable:
360 360 reachable.add(p)
361 361 visit.append(p)
362 362 return reachable
363 363
364 364 def ancestors(self, *revs):
365 365 """Generate the ancestors of 'revs' in reverse topological order.
366 366
367 367 Yield a sequence of revision numbers starting with the parents
368 368 of each revision in revs, i.e., each revision is *not* considered
369 369 an ancestor of itself. Results are in breadth-first order:
370 370 parents of each rev in revs, then parents of those, etc. Result
371 371 does not include the null revision."""
372 372 visit = list(revs)
373 373 seen = set([nullrev])
374 374 while visit:
375 375 for parent in self.parentrevs(visit.pop(0)):
376 376 if parent not in seen:
377 377 visit.append(parent)
378 378 seen.add(parent)
379 379 yield parent
380 380
381 381 def descendants(self, *revs):
382 382 """Generate the descendants of 'revs' in revision order.
383 383
384 384 Yield a sequence of revision numbers starting with a child of
385 385 some rev in revs, i.e., each revision is *not* considered a
386 386 descendant of itself. Results are ordered by revision number (a
387 387 topological sort)."""
388 388 first = min(revs)
389 389 if first == nullrev:
390 390 for i in self:
391 391 yield i
392 392 return
393 393
394 394 seen = set(revs)
395 395 for i in xrange(first + 1, len(self)):
396 396 for x in self.parentrevs(i):
397 397 if x != nullrev and x in seen:
398 398 seen.add(i)
399 399 yield i
400 400 break
401 401
402 402 def findcommonmissing(self, common=None, heads=None):
403 403 """Return a tuple of the ancestors of common and the ancestors of heads
404 404 that are not ancestors of common.
405 405
406 406 More specifically, the second element is a list of nodes N such that
407 407 every N satisfies the following constraints:
408 408
409 409 1. N is an ancestor of some node in 'heads'
410 410 2. N is not an ancestor of any node in 'common'
411 411
412 412 The list is sorted by revision number, meaning it is
413 413 topologically sorted.
414 414
415 415 'heads' and 'common' are both lists of node IDs. If heads is
416 416 not supplied, uses all of the revlog's heads. If common is not
417 417 supplied, uses nullid."""
418 418 if common is None:
419 419 common = [nullid]
420 420 if heads is None:
421 421 heads = self.heads()
422 422
423 423 common = [self.rev(n) for n in common]
424 424 heads = [self.rev(n) for n in heads]
425 425
426 426 # we want the ancestors, but inclusive
427 427 has = set(self.ancestors(*common))
428 428 has.add(nullrev)
429 429 has.update(common)
430 430
431 431 # take all ancestors from heads that aren't in has
432 432 missing = set()
433 433 visit = [r for r in heads if r not in has]
434 434 while visit:
435 435 r = visit.pop(0)
436 436 if r in missing:
437 437 continue
438 438 else:
439 439 missing.add(r)
440 440 for p in self.parentrevs(r):
441 441 if p not in has:
442 442 visit.append(p)
443 443 missing = list(missing)
444 444 missing.sort()
445 445 return has, [self.node(r) for r in missing]
446 446
447 447 def findmissing(self, common=None, heads=None):
448 448 """Return the ancestors of heads that are not ancestors of common.
449 449
450 450 More specifically, return a list of nodes N such that every N
451 451 satisfies the following constraints:
452 452
453 453 1. N is an ancestor of some node in 'heads'
454 454 2. N is not an ancestor of any node in 'common'
455 455
456 456 The list is sorted by revision number, meaning it is
457 457 topologically sorted.
458 458
459 459 'heads' and 'common' are both lists of node IDs. If heads is
460 460 not supplied, uses all of the revlog's heads. If common is not
461 461 supplied, uses nullid."""
462 462 _common, missing = self.findcommonmissing(common, heads)
463 463 return missing
464 464
465 465 def nodesbetween(self, roots=None, heads=None):
466 466 """Return a topological path from 'roots' to 'heads'.
467 467
468 468 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
469 469 topologically sorted list of all nodes N that satisfy both of
470 470 these constraints:
471 471
472 472 1. N is a descendant of some node in 'roots'
473 473 2. N is an ancestor of some node in 'heads'
474 474
475 475 Every node is considered to be both a descendant and an ancestor
476 476 of itself, so every reachable node in 'roots' and 'heads' will be
477 477 included in 'nodes'.
478 478
479 479 'outroots' is the list of reachable nodes in 'roots', i.e., the
480 480 subset of 'roots' that is returned in 'nodes'. Likewise,
481 481 'outheads' is the subset of 'heads' that is also in 'nodes'.
482 482
483 483 'roots' and 'heads' are both lists of node IDs. If 'roots' is
484 484 unspecified, uses nullid as the only root. If 'heads' is
485 485 unspecified, uses list of all of the revlog's heads."""
486 486 nonodes = ([], [], [])
487 487 if roots is not None:
488 488 roots = list(roots)
489 489 if not roots:
490 490 return nonodes
491 491 lowestrev = min([self.rev(n) for n in roots])
492 492 else:
493 493 roots = [nullid] # Everybody's a descendent of nullid
494 494 lowestrev = nullrev
495 495 if (lowestrev == nullrev) and (heads is None):
496 496 # We want _all_ the nodes!
497 497 return ([self.node(r) for r in self], [nullid], list(self.heads()))
498 498 if heads is None:
499 499 # All nodes are ancestors, so the latest ancestor is the last
500 500 # node.
501 501 highestrev = len(self) - 1
502 502 # Set ancestors to None to signal that every node is an ancestor.
503 503 ancestors = None
504 504 # Set heads to an empty dictionary for later discovery of heads
505 505 heads = {}
506 506 else:
507 507 heads = list(heads)
508 508 if not heads:
509 509 return nonodes
510 510 ancestors = set()
511 511 # Turn heads into a dictionary so we can remove 'fake' heads.
512 512 # Also, later we will be using it to filter out the heads we can't
513 513 # find from roots.
514 514 heads = dict.fromkeys(heads, 0)
515 515 # Start at the top and keep marking parents until we're done.
516 516 nodestotag = set(heads)
517 517 # Remember where the top was so we can use it as a limit later.
518 518 highestrev = max([self.rev(n) for n in nodestotag])
519 519 while nodestotag:
520 520 # grab a node to tag
521 521 n = nodestotag.pop()
522 522 # Never tag nullid
523 523 if n == nullid:
524 524 continue
525 525 # A node's revision number represents its place in a
526 526 # topologically sorted list of nodes.
527 527 r = self.rev(n)
528 528 if r >= lowestrev:
529 529 if n not in ancestors:
530 530 # If we are possibly a descendent of one of the roots
531 531 # and we haven't already been marked as an ancestor
532 532 ancestors.add(n) # Mark as ancestor
533 533 # Add non-nullid parents to list of nodes to tag.
534 534 nodestotag.update([p for p in self.parents(n) if
535 535 p != nullid])
536 536 elif n in heads: # We've seen it before, is it a fake head?
537 537 # So it is, real heads should not be the ancestors of
538 538 # any other heads.
539 539 heads.pop(n)
540 540 if not ancestors:
541 541 return nonodes
542 542 # Now that we have our set of ancestors, we want to remove any
543 543 # roots that are not ancestors.
544 544
545 545 # If one of the roots was nullid, everything is included anyway.
546 546 if lowestrev > nullrev:
547 547 # But, since we weren't, let's recompute the lowest rev to not
548 548 # include roots that aren't ancestors.
549 549
550 550 # Filter out roots that aren't ancestors of heads
551 551 roots = [n for n in roots if n in ancestors]
552 552 # Recompute the lowest revision
553 553 if roots:
554 554 lowestrev = min([self.rev(n) for n in roots])
555 555 else:
556 556 # No more roots? Return empty list
557 557 return nonodes
558 558 else:
559 559 # We are descending from nullid, and don't need to care about
560 560 # any other roots.
561 561 lowestrev = nullrev
562 562 roots = [nullid]
563 563 # Transform our roots list into a set.
564 564 descendents = set(roots)
565 565 # Also, keep the original roots so we can filter out roots that aren't
566 566 # 'real' roots (i.e. are descended from other roots).
567 567 roots = descendents.copy()
568 568 # Our topologically sorted list of output nodes.
569 569 orderedout = []
570 570 # Don't start at nullid since we don't want nullid in our output list,
571 571 # and if nullid shows up in descedents, empty parents will look like
572 572 # they're descendents.
573 573 for r in xrange(max(lowestrev, 0), highestrev + 1):
574 574 n = self.node(r)
575 575 isdescendent = False
576 576 if lowestrev == nullrev: # Everybody is a descendent of nullid
577 577 isdescendent = True
578 578 elif n in descendents:
579 579 # n is already a descendent
580 580 isdescendent = True
581 581 # This check only needs to be done here because all the roots
582 582 # will start being marked is descendents before the loop.
583 583 if n in roots:
584 584 # If n was a root, check if it's a 'real' root.
585 585 p = tuple(self.parents(n))
586 586 # If any of its parents are descendents, it's not a root.
587 587 if (p[0] in descendents) or (p[1] in descendents):
588 588 roots.remove(n)
589 589 else:
590 590 p = tuple(self.parents(n))
591 591 # A node is a descendent if either of its parents are
592 592 # descendents. (We seeded the dependents list with the roots
593 593 # up there, remember?)
594 594 if (p[0] in descendents) or (p[1] in descendents):
595 595 descendents.add(n)
596 596 isdescendent = True
597 597 if isdescendent and ((ancestors is None) or (n in ancestors)):
598 598 # Only include nodes that are both descendents and ancestors.
599 599 orderedout.append(n)
600 600 if (ancestors is not None) and (n in heads):
601 601 # We're trying to figure out which heads are reachable
602 602 # from roots.
603 603 # Mark this head as having been reached
604 604 heads[n] = 1
605 605 elif ancestors is None:
606 606 # Otherwise, we're trying to discover the heads.
607 607 # Assume this is a head because if it isn't, the next step
608 608 # will eventually remove it.
609 609 heads[n] = 1
610 610 # But, obviously its parents aren't.
611 611 for p in self.parents(n):
612 612 heads.pop(p, None)
613 613 heads = [n for n in heads.iterkeys() if heads[n] != 0]
614 614 roots = list(roots)
615 615 assert orderedout
616 616 assert roots
617 617 assert heads
618 618 return (orderedout, roots, heads)
619 619
620 def headrevs(self):
621 count = len(self)
622 if not count:
623 return [nullrev]
624 ishead = [1] * (count + 1)
625 index = self.index
626 for r in xrange(count):
627 e = index[r]
628 ishead[e[5]] = ishead[e[6]] = 0
629 return [r for r in xrange(count) if ishead[r]]
630
620 631 def heads(self, start=None, stop=None):
621 632 """return the list of all nodes that have no children
622 633
623 634 if start is specified, only heads that are descendants of
624 635 start will be returned
625 636 if stop is specified, it will consider all the revs from stop
626 637 as if they had no children
627 638 """
628 639 if start is None and stop is None:
629 count = len(self)
630 if not count:
640 if not len(self):
631 641 return [nullid]
632 ishead = [1] * (count + 1)
633 index = self.index
634 for r in xrange(count):
635 e = index[r]
636 ishead[e[5]] = ishead[e[6]] = 0
637 return [self.node(r) for r in xrange(count) if ishead[r]]
642 return [self.node(r) for r in self.headrevs()]
638 643
639 644 if start is None:
640 645 start = nullid
641 646 if stop is None:
642 647 stop = []
643 648 stoprevs = set([self.rev(n) for n in stop])
644 649 startrev = self.rev(start)
645 650 reachable = set((startrev,))
646 651 heads = set((startrev,))
647 652
648 653 parentrevs = self.parentrevs
649 654 for r in xrange(startrev + 1, len(self)):
650 655 for p in parentrevs(r):
651 656 if p in reachable:
652 657 if r not in stoprevs:
653 658 reachable.add(r)
654 659 heads.add(r)
655 660 if p in heads and p not in stoprevs:
656 661 heads.remove(p)
657 662
658 663 return [self.node(r) for r in heads]
659 664
660 665 def children(self, node):
661 666 """find the children of a given node"""
662 667 c = []
663 668 p = self.rev(node)
664 669 for r in range(p + 1, len(self)):
665 670 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
666 671 if prevs:
667 672 for pr in prevs:
668 673 if pr == p:
669 674 c.append(self.node(r))
670 675 elif p == nullrev:
671 676 c.append(self.node(r))
672 677 return c
673 678
674 679 def descendant(self, start, end):
675 680 if start == nullrev:
676 681 return True
677 682 for i in self.descendants(start):
678 683 if i == end:
679 684 return True
680 685 elif i > end:
681 686 break
682 687 return False
683 688
684 689 def ancestor(self, a, b):
685 690 """calculate the least common ancestor of nodes a and b"""
686 691
687 692 # fast path, check if it is a descendant
688 693 a, b = self.rev(a), self.rev(b)
689 694 start, end = sorted((a, b))
690 695 if self.descendant(start, end):
691 696 return self.node(start)
692 697
693 698 def parents(rev):
694 699 return [p for p in self.parentrevs(rev) if p != nullrev]
695 700
696 701 c = ancestor.ancestor(a, b, parents)
697 702 if c is None:
698 703 return nullid
699 704
700 705 return self.node(c)
701 706
702 707 def _match(self, id):
703 708 if isinstance(id, (long, int)):
704 709 # rev
705 710 return self.node(id)
706 711 if len(id) == 20:
707 712 # possibly a binary node
708 713 # odds of a binary node being all hex in ASCII are 1 in 10**25
709 714 try:
710 715 node = id
711 716 self.rev(node) # quick search the index
712 717 return node
713 718 except LookupError:
714 719 pass # may be partial hex id
715 720 try:
716 721 # str(rev)
717 722 rev = int(id)
718 723 if str(rev) != id:
719 724 raise ValueError
720 725 if rev < 0:
721 726 rev = len(self) + rev
722 727 if rev < 0 or rev >= len(self):
723 728 raise ValueError
724 729 return self.node(rev)
725 730 except (ValueError, OverflowError):
726 731 pass
727 732 if len(id) == 40:
728 733 try:
729 734 # a full hex nodeid?
730 735 node = bin(id)
731 736 self.rev(node)
732 737 return node
733 738 except (TypeError, LookupError):
734 739 pass
735 740
736 741 def _partialmatch(self, id):
737 742 if id in self._pcache:
738 743 return self._pcache[id]
739 744
740 745 if len(id) < 40:
741 746 try:
742 747 # hex(node)[:...]
743 748 l = len(id) // 2 # grab an even number of digits
744 749 prefix = bin(id[:l * 2])
745 750 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
746 751 nl = [n for n in nl if hex(n).startswith(id)]
747 752 if len(nl) > 0:
748 753 if len(nl) == 1:
749 754 self._pcache[id] = nl[0]
750 755 return nl[0]
751 756 raise LookupError(id, self.indexfile,
752 757 _('ambiguous identifier'))
753 758 return None
754 759 except TypeError:
755 760 pass
756 761
757 762 def lookup(self, id):
758 763 """locate a node based on:
759 764 - revision number or str(revision number)
760 765 - nodeid or subset of hex nodeid
761 766 """
762 767 n = self._match(id)
763 768 if n is not None:
764 769 return n
765 770 n = self._partialmatch(id)
766 771 if n:
767 772 return n
768 773
769 774 raise LookupError(id, self.indexfile, _('no match found'))
770 775
771 776 def cmp(self, node, text):
772 777 """compare text with a given file revision
773 778
774 779 returns True if text is different than what is stored.
775 780 """
776 781 p1, p2 = self.parents(node)
777 782 return hash(text, p1, p2) != node
778 783
779 784 def _addchunk(self, offset, data):
780 785 o, d = self._chunkcache
781 786 # try to add to existing cache
782 787 if o + len(d) == offset and len(d) + len(data) < _chunksize:
783 788 self._chunkcache = o, d + data
784 789 else:
785 790 self._chunkcache = offset, data
786 791
787 792 def _loadchunk(self, offset, length):
788 793 if self._inline:
789 794 df = self.opener(self.indexfile)
790 795 else:
791 796 df = self.opener(self.datafile)
792 797
793 798 readahead = max(65536, length)
794 799 df.seek(offset)
795 800 d = df.read(readahead)
796 801 self._addchunk(offset, d)
797 802 if readahead > length:
798 803 return d[:length]
799 804 return d
800 805
801 806 def _getchunk(self, offset, length):
802 807 o, d = self._chunkcache
803 808 l = len(d)
804 809
805 810 # is it in the cache?
806 811 cachestart = offset - o
807 812 cacheend = cachestart + length
808 813 if cachestart >= 0 and cacheend <= l:
809 814 if cachestart == 0 and cacheend == l:
810 815 return d # avoid a copy
811 816 return d[cachestart:cacheend]
812 817
813 818 return self._loadchunk(offset, length)
814 819
815 820 def _chunkraw(self, startrev, endrev):
816 821 start = self.start(startrev)
817 822 length = self.end(endrev) - start
818 823 if self._inline:
819 824 start += (startrev + 1) * self._io.size
820 825 return self._getchunk(start, length)
821 826
822 827 def _chunk(self, rev):
823 828 return decompress(self._chunkraw(rev, rev))
824 829
825 830 def _chunkbase(self, rev):
826 831 return self._chunk(rev)
827 832
828 833 def _chunkclear(self):
829 834 self._chunkcache = (0, '')
830 835
831 836 def deltaparent(self, rev):
832 837 """return previous revision or parentrev according to flags"""
833 838 if self.flags(rev) & REVIDX_PARENTDELTA:
834 839 return self.parentrevs(rev)[0]
835 840 else:
836 841 return rev - 1
837 842
838 843 def revdiff(self, rev1, rev2):
839 844 """return or calculate a delta between two revisions"""
840 845 if self.base(rev2) != rev2 and self.deltaparent(rev2) == rev1:
841 846 return self._chunk(rev2)
842 847
843 848 return mdiff.textdiff(self.revision(self.node(rev1)),
844 849 self.revision(self.node(rev2)))
845 850
846 851 def revision(self, node):
847 852 """return an uncompressed revision of a given node"""
848 853 cachedrev = None
849 854 if node == nullid:
850 855 return ""
851 856 if self._cache:
852 857 if self._cache[0] == node:
853 858 return self._cache[2]
854 859 cachedrev = self._cache[1]
855 860
856 861 # look up what we need to read
857 862 text = None
858 863 rev = self.rev(node)
859 864 base = self.base(rev)
860 865
861 866 # check rev flags
862 867 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
863 868 raise RevlogError(_('incompatible revision flag %x') %
864 869 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
865 870
866 871 # build delta chain
867 872 chain = []
868 873 index = self.index # for performance
869 874 iterrev = rev
870 875 e = index[iterrev]
871 876 while iterrev != base and iterrev != cachedrev:
872 877 chain.append(iterrev)
873 878 if e[0] & REVIDX_PARENTDELTA:
874 879 iterrev = e[5]
875 880 else:
876 881 iterrev -= 1
877 882 e = index[iterrev]
878 883 chain.reverse()
879 884 base = iterrev
880 885
881 886 if iterrev == cachedrev:
882 887 # cache hit
883 888 text = self._cache[2]
884 889
885 890 # drop cache to save memory
886 891 self._cache = None
887 892
888 893 self._chunkraw(base, rev)
889 894 if text is None:
890 895 text = self._chunkbase(base)
891 896
892 897 bins = [self._chunk(r) for r in chain]
893 898 text = mdiff.patches(text, bins)
894 899
895 900 text = self._checkhash(text, node, rev)
896 901
897 902 self._cache = (node, rev, text)
898 903 return text
899 904
900 905 def _checkhash(self, text, node, rev):
901 906 p1, p2 = self.parents(node)
902 907 if (node != hash(text, p1, p2) and
903 908 not (self.flags(rev) & REVIDX_PUNCHED_FLAG)):
904 909 raise RevlogError(_("integrity check failed on %s:%d")
905 910 % (self.indexfile, rev))
906 911 return text
907 912
908 913 def checkinlinesize(self, tr, fp=None):
909 914 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
910 915 return
911 916
912 917 trinfo = tr.find(self.indexfile)
913 918 if trinfo is None:
914 919 raise RevlogError(_("%s not found in the transaction")
915 920 % self.indexfile)
916 921
917 922 trindex = trinfo[2]
918 923 dataoff = self.start(trindex)
919 924
920 925 tr.add(self.datafile, dataoff)
921 926
922 927 if fp:
923 928 fp.flush()
924 929 fp.close()
925 930
926 931 df = self.opener(self.datafile, 'w')
927 932 try:
928 933 for r in self:
929 934 df.write(self._chunkraw(r, r))
930 935 finally:
931 936 df.close()
932 937
933 938 fp = self.opener(self.indexfile, 'w', atomictemp=True)
934 939 self.version &= ~(REVLOGNGINLINEDATA)
935 940 self._inline = False
936 941 for i in self:
937 942 e = self._io.packentry(self.index[i], self.node, self.version, i)
938 943 fp.write(e)
939 944
940 945 # if we don't call rename, the temp file will never replace the
941 946 # real index
942 947 fp.rename()
943 948
944 949 tr.replace(self.indexfile, trindex * self._io.size)
945 950 self._chunkclear()
946 951
947 952 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None):
948 953 """add a revision to the log
949 954
950 955 text - the revision data to add
951 956 transaction - the transaction object used for rollback
952 957 link - the linkrev data to add
953 958 p1, p2 - the parent nodeids of the revision
954 959 cachedelta - an optional precomputed delta
955 960 """
956 961 node = hash(text, p1, p2)
957 962 if (node in self.nodemap and
958 963 (not self.flags(self.rev(node)) & REVIDX_PUNCHED_FLAG)):
959 964 return node
960 965
961 966 dfh = None
962 967 if not self._inline:
963 968 dfh = self.opener(self.datafile, "a")
964 969 ifh = self.opener(self.indexfile, "a+")
965 970 try:
966 971 return self._addrevision(node, text, transaction, link, p1, p2,
967 972 cachedelta, ifh, dfh)
968 973 finally:
969 974 if dfh:
970 975 dfh.close()
971 976 ifh.close()
972 977
973 978 def _addrevision(self, node, text, transaction, link, p1, p2,
974 979 cachedelta, ifh, dfh):
975 980
976 981 btext = [text]
977 982 def buildtext():
978 983 if btext[0] is not None:
979 984 return btext[0]
980 985 # flush any pending writes here so we can read it in revision
981 986 if dfh:
982 987 dfh.flush()
983 988 ifh.flush()
984 989 basetext = self.revision(self.node(cachedelta[0]))
985 990 btext[0] = mdiff.patch(basetext, cachedelta[1])
986 991 chk = hash(btext[0], p1, p2)
987 992 if chk != node:
988 993 raise RevlogError(_("consistency error in delta"))
989 994 return btext[0]
990 995
991 996 def builddelta(rev):
992 997 # can we use the cached delta?
993 998 if cachedelta and cachedelta[0] == rev:
994 999 delta = cachedelta[1]
995 1000 else:
996 1001 t = buildtext()
997 1002 ptext = self.revision(self.node(rev))
998 1003 delta = mdiff.textdiff(ptext, t)
999 1004 data = compress(delta)
1000 1005 l = len(data[1]) + len(data[0])
1001 1006 base = self.base(rev)
1002 1007 dist = l + offset - self.start(base)
1003 1008 return dist, l, data, base
1004 1009
1005 1010 curr = len(self)
1006 1011 prev = curr - 1
1007 1012 base = curr
1008 1013 offset = self.end(prev)
1009 1014 flags = 0
1010 1015 d = None
1011 1016 p1r, p2r = self.rev(p1), self.rev(p2)
1012 1017
1013 1018 # should we try to build a delta?
1014 1019 if prev != nullrev:
1015 1020 d = builddelta(prev)
1016 1021 if self._parentdelta and prev != p1r:
1017 1022 d2 = builddelta(p1r)
1018 1023 if d2 < d:
1019 1024 d = d2
1020 1025 flags = REVIDX_PARENTDELTA
1021 1026 dist, l, data, base = d
1022 1027
1023 1028 # full versions are inserted when the needed deltas
1024 1029 # become comparable to the uncompressed text
1025 1030 # or the base revision is punched
1026 1031 if text is None:
1027 1032 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1028 1033 cachedelta[1])
1029 1034 else:
1030 1035 textlen = len(text)
1031 1036 if (d is None or dist > textlen * 2 or
1032 1037 (self.flags(base) & REVIDX_PUNCHED_FLAG)):
1033 1038 text = buildtext()
1034 1039 data = compress(text)
1035 1040 l = len(data[1]) + len(data[0])
1036 1041 base = curr
1037 1042
1038 1043 e = (offset_type(offset, flags), l, textlen,
1039 1044 base, link, p1r, p2r, node)
1040 1045 self.index.insert(-1, e)
1041 1046 self.nodemap[node] = curr
1042 1047
1043 1048 entry = self._io.packentry(e, self.node, self.version, curr)
1044 1049 if not self._inline:
1045 1050 transaction.add(self.datafile, offset)
1046 1051 transaction.add(self.indexfile, curr * len(entry))
1047 1052 if data[0]:
1048 1053 dfh.write(data[0])
1049 1054 dfh.write(data[1])
1050 1055 dfh.flush()
1051 1056 ifh.write(entry)
1052 1057 else:
1053 1058 offset += curr * self._io.size
1054 1059 transaction.add(self.indexfile, offset, curr)
1055 1060 ifh.write(entry)
1056 1061 ifh.write(data[0])
1057 1062 ifh.write(data[1])
1058 1063 self.checkinlinesize(transaction, ifh)
1059 1064
1060 1065 if type(text) == str: # only accept immutable objects
1061 1066 self._cache = (node, curr, text)
1062 1067 return node
1063 1068
1064 1069 def group(self, nodelist, bundler):
1065 1070 """Calculate a delta group, yielding a sequence of changegroup chunks
1066 1071 (strings).
1067 1072
1068 1073 Given a list of changeset revs, return a set of deltas and
1069 1074 metadata corresponding to nodes. The first delta is
1070 1075 first parent(nodelist[0]) -> nodelist[0], the receiver is
1071 1076 guaranteed to have this parent as it has all history before
1072 1077 these changesets. In the case firstparent is nullrev the
1073 1078 changegroup starts with a full revision.
1074 1079 """
1075 1080
1076 1081 revs = sorted([self.rev(n) for n in nodelist])
1077 1082
1078 1083 # if we don't have any revisions touched by these changesets, bail
1079 1084 if not revs:
1080 1085 yield bundler.close()
1081 1086 return
1082 1087
1083 1088 # add the parent of the first rev
1084 1089 p = self.parentrevs(revs[0])[0]
1085 1090 revs.insert(0, p)
1086 1091
1087 1092 # build deltas
1088 1093 for r in xrange(len(revs) - 1):
1089 1094 prev, curr = revs[r], revs[r + 1]
1090 1095 for c in bundler.revchunk(self, curr, prev):
1091 1096 yield c
1092 1097
1093 1098 yield bundler.close()
1094 1099
1095 1100 def addgroup(self, bundle, linkmapper, transaction):
1096 1101 """
1097 1102 add a delta group
1098 1103
1099 1104 given a set of deltas, add them to the revision log. the
1100 1105 first delta is against its parent, which should be in our
1101 1106 log, the rest are against the previous delta.
1102 1107 """
1103 1108
1104 1109 # track the base of the current delta log
1105 1110 node = None
1106 1111
1107 1112 r = len(self)
1108 1113 end = 0
1109 1114 if r:
1110 1115 end = self.end(r - 1)
1111 1116 ifh = self.opener(self.indexfile, "a+")
1112 1117 isize = r * self._io.size
1113 1118 if self._inline:
1114 1119 transaction.add(self.indexfile, end + isize, r)
1115 1120 dfh = None
1116 1121 else:
1117 1122 transaction.add(self.indexfile, isize, r)
1118 1123 transaction.add(self.datafile, end)
1119 1124 dfh = self.opener(self.datafile, "a")
1120 1125
1121 1126 try:
1122 1127 # loop through our set of deltas
1123 1128 chain = None
1124 1129 while 1:
1125 1130 chunkdata = bundle.deltachunk(chain)
1126 1131 if not chunkdata:
1127 1132 break
1128 1133 node = chunkdata['node']
1129 1134 p1 = chunkdata['p1']
1130 1135 p2 = chunkdata['p2']
1131 1136 cs = chunkdata['cs']
1132 1137 deltabase = chunkdata['deltabase']
1133 1138 delta = chunkdata['delta']
1134 1139
1135 1140 link = linkmapper(cs)
1136 1141 if (node in self.nodemap and
1137 1142 (not self.flags(self.rev(node)) & REVIDX_PUNCHED_FLAG)):
1138 1143 # this can happen if two branches make the same change
1139 1144 chain = node
1140 1145 continue
1141 1146
1142 1147 for p in (p1, p2):
1143 1148 if not p in self.nodemap:
1144 1149 if self._shallow:
1145 1150 # add null entries for missing parents
1146 1151 # XXX FIXME
1147 1152 #if base == nullrev:
1148 1153 # base = len(self)
1149 1154 #e = (offset_type(end, REVIDX_PUNCHED_FLAG),
1150 1155 # 0, 0, base, nullrev, nullrev, nullrev, p)
1151 1156 #self.index.insert(-1, e)
1152 1157 #self.nodemap[p] = r
1153 1158 #entry = self._io.packentry(e, self.node,
1154 1159 # self.version, r)
1155 1160 #ifh.write(entry)
1156 1161 #t, r = r, r + 1
1157 1162 raise LookupError(p, self.indexfile,
1158 1163 _('unknown parent'))
1159 1164 else:
1160 1165 raise LookupError(p, self.indexfile,
1161 1166 _('unknown parent'))
1162 1167
1163 1168 if deltabase not in self.nodemap:
1164 1169 raise LookupError(deltabase, self.indexfile,
1165 1170 _('unknown delta base'))
1166 1171
1167 1172 baserev = self.rev(deltabase)
1168 1173 chain = self._addrevision(node, None, transaction, link,
1169 1174 p1, p2, (baserev, delta), ifh, dfh)
1170 1175 if not dfh and not self._inline:
1171 1176 # addrevision switched from inline to conventional
1172 1177 # reopen the index
1173 1178 ifh.close()
1174 1179 dfh = self.opener(self.datafile, "a")
1175 1180 ifh = self.opener(self.indexfile, "a")
1176 1181 finally:
1177 1182 if dfh:
1178 1183 dfh.close()
1179 1184 ifh.close()
1180 1185
1181 1186 return node
1182 1187
1183 1188 def strip(self, minlink, transaction):
1184 1189 """truncate the revlog on the first revision with a linkrev >= minlink
1185 1190
1186 1191 This function is called when we're stripping revision minlink and
1187 1192 its descendants from the repository.
1188 1193
1189 1194 We have to remove all revisions with linkrev >= minlink, because
1190 1195 the equivalent changelog revisions will be renumbered after the
1191 1196 strip.
1192 1197
1193 1198 So we truncate the revlog on the first of these revisions, and
1194 1199 trust that the caller has saved the revisions that shouldn't be
1195 1200 removed and that it'll readd them after this truncation.
1196 1201 """
1197 1202 if len(self) == 0:
1198 1203 return
1199 1204
1200 1205 for rev in self:
1201 1206 if self.index[rev][4] >= minlink:
1202 1207 break
1203 1208 else:
1204 1209 return
1205 1210
1206 1211 # first truncate the files on disk
1207 1212 end = self.start(rev)
1208 1213 if not self._inline:
1209 1214 transaction.add(self.datafile, end)
1210 1215 end = rev * self._io.size
1211 1216 else:
1212 1217 end += rev * self._io.size
1213 1218
1214 1219 transaction.add(self.indexfile, end)
1215 1220
1216 1221 # then reset internal state in memory to forget those revisions
1217 1222 self._cache = None
1218 1223 self._chunkclear()
1219 1224 for x in xrange(rev, len(self)):
1220 1225 del self.nodemap[self.node(x)]
1221 1226
1222 1227 del self.index[rev:-1]
1223 1228
1224 1229 def checksize(self):
1225 1230 expected = 0
1226 1231 if len(self):
1227 1232 expected = max(0, self.end(len(self) - 1))
1228 1233
1229 1234 try:
1230 1235 f = self.opener(self.datafile)
1231 1236 f.seek(0, 2)
1232 1237 actual = f.tell()
1233 1238 f.close()
1234 1239 dd = actual - expected
1235 1240 except IOError, inst:
1236 1241 if inst.errno != errno.ENOENT:
1237 1242 raise
1238 1243 dd = 0
1239 1244
1240 1245 try:
1241 1246 f = self.opener(self.indexfile)
1242 1247 f.seek(0, 2)
1243 1248 actual = f.tell()
1244 1249 f.close()
1245 1250 s = self._io.size
1246 1251 i = max(0, actual // s)
1247 1252 di = actual - (i * s)
1248 1253 if self._inline:
1249 1254 databytes = 0
1250 1255 for r in self:
1251 1256 databytes += max(0, self.length(r))
1252 1257 dd = 0
1253 1258 di = actual - len(self) * s - databytes
1254 1259 except IOError, inst:
1255 1260 if inst.errno != errno.ENOENT:
1256 1261 raise
1257 1262 di = 0
1258 1263
1259 1264 return (dd, di)
1260 1265
1261 1266 def files(self):
1262 1267 res = [self.indexfile]
1263 1268 if not self._inline:
1264 1269 res.append(self.datafile)
1265 1270 return res
@@ -1,288 +1,151 b''
1 1 # discovery.py - protocol changeset discovery functions
2 2 #
3 3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import nullid, short
9 9 from i18n import _
10 10 import util, error
11 11
12 12 def findcommonincoming(repo, remote, heads=None, force=False):
13 """Return a tuple (common, anyincoming, heads) used to identify the common
13 """Return a tuple (common, fetch, heads) used to identify the common
14 14 subset of nodes between repo and remote.
15 15
16 16 "common" is a list of (at least) the heads of the common subset.
17 "anyincoming" is testable as a boolean indicating if any nodes are missing
18 locally. If remote does not support getbundle, this actually is a list of
19 roots of the nodes that would be incoming, to be supplied to
20 changegroupsubset. No code except for pull should be relying on this fact
21 any longer.
17 "fetch" is a list of roots of the nodes that would be incoming, to be
18 supplied to changegroupsubset.
22 19 "heads" is either the supplied heads, or else the remote's heads.
23 20 """
24 21
25 22 m = repo.changelog.nodemap
26 23 search = []
27 24 fetch = set()
28 25 seen = set()
29 26 seenbranch = set()
30 27 base = set()
31 28
32 29 if not heads:
33 30 heads = remote.heads()
34 31
35 32 if repo.changelog.tip() == nullid:
36 33 base.add(nullid)
37 34 if heads != [nullid]:
38 35 return [nullid], [nullid], list(heads)
39 36 return [nullid], [], []
40 37
41 38 # assume we're closer to the tip than the root
42 39 # and start by examining the heads
43 40 repo.ui.status(_("searching for changes\n"))
44 41
45 if remote.capable('getbundle'):
46 myheads = repo.heads()
47 known = remote.known(myheads)
48 if util.all(known):
49 hasincoming = set(heads).difference(set(myheads)) and True
50 return myheads, hasincoming, heads
51
52 42 unknown = []
53 43 for h in heads:
54 44 if h not in m:
55 45 unknown.append(h)
56 46 else:
57 47 base.add(h)
58 48
59 49 heads = unknown
60 50 if not unknown:
61 51 return list(base), [], []
62 52
63 53 req = set(unknown)
64 54 reqcnt = 0
65 55
66 56 # search through remote branches
67 57 # a 'branch' here is a linear segment of history, with four parts:
68 58 # head, root, first parent, second parent
69 59 # (a branch always has two parents (or none) by definition)
70 60 unknown = remote.branches(unknown)
71 61 while unknown:
72 62 r = []
73 63 while unknown:
74 64 n = unknown.pop(0)
75 65 if n[0] in seen:
76 66 continue
77 67
78 68 repo.ui.debug("examining %s:%s\n"
79 69 % (short(n[0]), short(n[1])))
80 70 if n[0] == nullid: # found the end of the branch
81 71 pass
82 72 elif n in seenbranch:
83 73 repo.ui.debug("branch already found\n")
84 74 continue
85 75 elif n[1] and n[1] in m: # do we know the base?
86 76 repo.ui.debug("found incomplete branch %s:%s\n"
87 77 % (short(n[0]), short(n[1])))
88 78 search.append(n[0:2]) # schedule branch range for scanning
89 79 seenbranch.add(n)
90 80 else:
91 81 if n[1] not in seen and n[1] not in fetch:
92 82 if n[2] in m and n[3] in m:
93 83 repo.ui.debug("found new changeset %s\n" %
94 84 short(n[1]))
95 85 fetch.add(n[1]) # earliest unknown
96 86 for p in n[2:4]:
97 87 if p in m:
98 88 base.add(p) # latest known
99 89
100 90 for p in n[2:4]:
101 91 if p not in req and p not in m:
102 92 r.append(p)
103 93 req.add(p)
104 94 seen.add(n[0])
105 95
106 96 if r:
107 97 reqcnt += 1
108 98 repo.ui.progress(_('searching'), reqcnt, unit=_('queries'))
109 99 repo.ui.debug("request %d: %s\n" %
110 100 (reqcnt, " ".join(map(short, r))))
111 101 for p in xrange(0, len(r), 10):
112 102 for b in remote.branches(r[p:p + 10]):
113 103 repo.ui.debug("received %s:%s\n" %
114 104 (short(b[0]), short(b[1])))
115 105 unknown.append(b)
116 106
117 107 # do binary search on the branches we found
118 108 while search:
119 109 newsearch = []
120 110 reqcnt += 1
121 111 repo.ui.progress(_('searching'), reqcnt, unit=_('queries'))
122 112 for n, l in zip(search, remote.between(search)):
123 113 l.append(n[1])
124 114 p = n[0]
125 115 f = 1
126 116 for i in l:
127 117 repo.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
128 118 if i in m:
129 119 if f <= 2:
130 120 repo.ui.debug("found new branch changeset %s\n" %
131 121 short(p))
132 122 fetch.add(p)
133 123 base.add(i)
134 124 else:
135 125 repo.ui.debug("narrowed branch search to %s:%s\n"
136 126 % (short(p), short(i)))
137 127 newsearch.append((p, i))
138 128 break
139 129 p, f = i, f * 2
140 130 search = newsearch
141 131
142 132 # sanity check our fetch list
143 133 for f in fetch:
144 134 if f in m:
145 135 raise error.RepoError(_("already have changeset ")
146 136 + short(f[:4]))
147 137
148 138 base = list(base)
149 139 if base == [nullid]:
150 140 if force:
151 141 repo.ui.warn(_("warning: repository is unrelated\n"))
152 142 else:
153 143 raise util.Abort(_("repository is unrelated"))
154 144
155 145 repo.ui.debug("found new changesets starting at " +
156 146 " ".join([short(f) for f in fetch]) + "\n")
157 147
158 148 repo.ui.progress(_('searching'), None)
159 149 repo.ui.debug("%d total queries\n" % reqcnt)
160 150
161 151 return base, list(fetch), heads
162
163 def prepush(repo, remote, force, revs, newbranch):
164 '''Analyze the local and remote repositories and determine which
165 changesets need to be pushed to the remote. Return value depends
166 on circumstances:
167
168 If we are not going to push anything, return a tuple (None,
169 outgoing) where outgoing is 0 if there are no outgoing
170 changesets and 1 if there are, but we refuse to push them
171 (e.g. would create new remote heads).
172
173 Otherwise, return a tuple (changegroup, remoteheads), where
174 changegroup is a readable file-like object whose read() returns
175 successive changegroup chunks ready to be sent over the wire and
176 remoteheads is the list of remote heads.'''
177 remoteheads = remote.heads()
178 common, inc, _rheads = findcommonincoming(repo, remote, heads=remoteheads,
179 force=force)
180
181 cl = repo.changelog
182 outg = cl.findmissing(common, revs)
183
184 if not outg:
185 repo.ui.status(_("no changes found\n"))
186 return None, 1
187
188 if not force and remoteheads != [nullid]:
189 if remote.capable('branchmap'):
190 # Check for each named branch if we're creating new remote heads.
191 # To be a remote head after push, node must be either:
192 # - unknown locally
193 # - a local outgoing head descended from update
194 # - a remote head that's known locally and not
195 # ancestral to an outgoing head
196
197 # 1. Create set of branches involved in the push.
198 branches = set(repo[n].branch() for n in outg)
199
200 # 2. Check for new branches on the remote.
201 remotemap = remote.branchmap()
202 newbranches = branches - set(remotemap)
203 if newbranches and not newbranch: # new branch requires --new-branch
204 branchnames = ', '.join(sorted(newbranches))
205 raise util.Abort(_("push creates new remote branches: %s!")
206 % branchnames,
207 hint=_("use 'hg push --new-branch' to create"
208 " new remote branches"))
209 branches.difference_update(newbranches)
210
211 # 3. Construct the initial oldmap and newmap dicts.
212 # They contain information about the remote heads before and
213 # after the push, respectively.
214 # Heads not found locally are not included in either dict,
215 # since they won't be affected by the push.
216 # unsynced contains all branches with incoming changesets.
217 oldmap = {}
218 newmap = {}
219 unsynced = set()
220 for branch in branches:
221 remotebrheads = remotemap[branch]
222 prunedbrheads = [h for h in remotebrheads if h in cl.nodemap]
223 oldmap[branch] = prunedbrheads
224 newmap[branch] = list(prunedbrheads)
225 if len(remotebrheads) > len(prunedbrheads):
226 unsynced.add(branch)
227
228 # 4. Update newmap with outgoing changes.
229 # This will possibly add new heads and remove existing ones.
230 ctxgen = (repo[n] for n in outg)
231 repo._updatebranchcache(newmap, ctxgen)
232
233 else:
234 # 1-4b. old servers: Check for new topological heads.
235 # Construct {old,new}map with branch = None (topological branch).
236 # (code based on _updatebranchcache)
237 oldheads = set(h for h in remoteheads if h in cl.nodemap)
238 newheads = oldheads.union(outg)
239 if len(newheads) > 1:
240 for latest in reversed(outg):
241 if latest not in newheads:
242 continue
243 minhrev = min(cl.rev(h) for h in newheads)
244 reachable = cl.reachable(latest, cl.node(minhrev))
245 reachable.remove(latest)
246 newheads.difference_update(reachable)
247 branches = set([None])
248 newmap = {None: newheads}
249 oldmap = {None: oldheads}
250 unsynced = inc and branches or set()
251
252 # 5. Check for new heads.
253 # If there are more heads after the push than before, a suitable
254 # error message, depending on unsynced status, is displayed.
255 error = None
256 for branch in branches:
257 newhs = set(newmap[branch])
258 oldhs = set(oldmap[branch])
259 if len(newhs) > len(oldhs):
260 if error is None:
261 if branch:
262 error = _("push creates new remote heads "
263 "on branch '%s'!") % branch
264 else:
265 error = _("push creates new remote heads!")
266 if branch in unsynced:
267 hint = _("you should pull and merge or "
268 "use push -f to force")
269 else:
270 hint = _("did you forget to merge? "
271 "use push -f to force")
272 if branch:
273 repo.ui.debug("new remote heads on branch '%s'\n" % branch)
274 for h in (newhs - oldhs):
275 repo.ui.debug("new remote head %s\n" % short(h))
276 if error:
277 raise util.Abort(error, hint=hint)
278
279 # 6. Check for unsynced changes on involved branches.
280 if unsynced:
281 repo.ui.warn(_("note: unsynced remote changes!\n"))
282
283 if revs is None:
284 # use the fast path, no race possible on push
285 cg = repo._changegroup(outg, 'push')
286 else:
287 cg = repo.getbundle('push', heads=revs, common=common)
288 return cg, remoteheads
@@ -1,1870 +1,1920 b''
1 1 > do_push()
2 2 > {
3 3 > user=$1
4 4 > shift
5 5 > echo "Pushing as user $user"
6 6 > echo 'hgrc = """'
7 7 > sed -e 1,2d b/.hg/hgrc | grep -v fakegroups.py
8 8 > echo '"""'
9 9 > if test -f acl.config; then
10 10 > echo 'acl.config = """'
11 11 > cat acl.config
12 12 > echo '"""'
13 13 > fi
14 14 > # On AIX /etc/profile sets LOGNAME read-only. So
15 15 > # LOGNAME=$user hg --cws a --debug push ../b
16 16 > # fails with "This variable is read only."
17 17 > # Use env to work around this.
18 18 > env LOGNAME=$user hg --cwd a --debug push ../b
19 19 > hg --cwd b rollback
20 20 > hg --cwd b --quiet tip
21 21 > echo
22 22 > }
23 23
24 24 > init_config()
25 25 > {
26 26 > cat > fakegroups.py <<EOF
27 27 > from hgext import acl
28 28 > def fakegetusers(ui, group):
29 29 > try:
30 30 > return acl._getusersorig(ui, group)
31 31 > except:
32 32 > return ["fred", "betty"]
33 33 > acl._getusersorig = acl._getusers
34 34 > acl._getusers = fakegetusers
35 35 > EOF
36 36 > rm -f acl.config
37 37 > cat > $config <<EOF
38 38 > [hooks]
39 39 > pretxnchangegroup.acl = python:hgext.acl.hook
40 40 > [acl]
41 41 > sources = push
42 42 > [extensions]
43 43 > f=`pwd`/fakegroups.py
44 44 > EOF
45 45 > }
46 46
47 47 $ hg init a
48 48 $ cd a
49 49 $ mkdir foo foo/Bar quux
50 50 $ echo 'in foo' > foo/file.txt
51 51 $ echo 'in foo/Bar' > foo/Bar/file.txt
52 52 $ echo 'in quux' > quux/file.py
53 53 $ hg add -q
54 54 $ hg ci -m 'add files' -d '1000000 0'
55 55 $ echo >> foo/file.txt
56 56 $ hg ci -m 'change foo/file' -d '1000001 0'
57 57 $ echo >> foo/Bar/file.txt
58 58 $ hg ci -m 'change foo/Bar/file' -d '1000002 0'
59 59 $ echo >> quux/file.py
60 60 $ hg ci -m 'change quux/file' -d '1000003 0'
61 61 $ hg tip --quiet
62 62 3:911600dab2ae
63 63
64 64 $ cd ..
65 65 $ hg clone -r 0 a b
66 66 adding changesets
67 67 adding manifests
68 68 adding file changes
69 69 added 1 changesets with 3 changes to 3 files
70 70 updating to branch default
71 71 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
72 72
73 73 $ echo '[extensions]' >> $HGRCPATH
74 74 $ echo 'acl =' >> $HGRCPATH
75 75
76 76 $ config=b/.hg/hgrc
77 77
78 78 Extension disabled for lack of a hook
79 79
80 80 $ do_push fred
81 81 Pushing as user fred
82 82 hgrc = """
83 83 """
84 84 pushing to ../b
85 query 1; heads
85 86 searching for changes
87 all remote heads known locally
86 88 3 changesets found
87 89 list of changesets:
88 90 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
89 91 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
90 92 911600dab2ae7a9baff75958b84fe606851ce955
91 93 adding changesets
92 94 bundling: 1 changesets
93 95 bundling: 2 changesets
94 96 bundling: 3 changesets
95 97 bundling: 1/3 manifests (33.33%)
96 98 bundling: 2/3 manifests (66.67%)
97 99 bundling: 3/3 manifests (100.00%)
98 100 bundling: foo/Bar/file.txt 0/3 files (0.00%)
99 101 bundling: foo/file.txt 1/3 files (33.33%)
100 102 bundling: quux/file.py 2/3 files (66.67%)
101 103 changesets: 1 chunks
102 104 add changeset ef1ea85a6374
103 105 changesets: 2 chunks
104 106 add changeset f9cafe1212c8
105 107 changesets: 3 chunks
106 108 add changeset 911600dab2ae
107 109 adding manifests
108 110 manifests: 1/3 chunks (33.33%)
109 111 manifests: 2/3 chunks (66.67%)
110 112 manifests: 3/3 chunks (100.00%)
111 113 adding file changes
112 114 adding foo/Bar/file.txt revisions
113 115 files: 1/3 chunks (33.33%)
114 116 adding foo/file.txt revisions
115 117 files: 2/3 chunks (66.67%)
116 118 adding quux/file.py revisions
117 119 files: 3/3 chunks (100.00%)
118 120 added 3 changesets with 3 changes to 3 files
119 121 updating the branch cache
120 122 checking for updated bookmarks
121 123 repository tip rolled back to revision 0 (undo push)
122 124 working directory now based on revision 0
123 125 0:6675d58eff77
124 126
125 127
126 128 $ echo '[hooks]' >> $config
127 129 $ echo 'pretxnchangegroup.acl = python:hgext.acl.hook' >> $config
128 130
129 131 Extension disabled for lack of acl.sources
130 132
131 133 $ do_push fred
132 134 Pushing as user fred
133 135 hgrc = """
134 136 [hooks]
135 137 pretxnchangegroup.acl = python:hgext.acl.hook
136 138 """
137 139 pushing to ../b
140 query 1; heads
138 141 searching for changes
142 all remote heads known locally
139 143 invalidating branch cache (tip differs)
140 144 3 changesets found
141 145 list of changesets:
142 146 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
143 147 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
144 148 911600dab2ae7a9baff75958b84fe606851ce955
145 149 adding changesets
146 150 bundling: 1 changesets
147 151 bundling: 2 changesets
148 152 bundling: 3 changesets
149 153 bundling: 1/3 manifests (33.33%)
150 154 bundling: 2/3 manifests (66.67%)
151 155 bundling: 3/3 manifests (100.00%)
152 156 bundling: foo/Bar/file.txt 0/3 files (0.00%)
153 157 bundling: foo/file.txt 1/3 files (33.33%)
154 158 bundling: quux/file.py 2/3 files (66.67%)
155 159 changesets: 1 chunks
156 160 add changeset ef1ea85a6374
157 161 changesets: 2 chunks
158 162 add changeset f9cafe1212c8
159 163 changesets: 3 chunks
160 164 add changeset 911600dab2ae
161 165 adding manifests
162 166 manifests: 1/3 chunks (33.33%)
163 167 manifests: 2/3 chunks (66.67%)
164 168 manifests: 3/3 chunks (100.00%)
165 169 adding file changes
166 170 adding foo/Bar/file.txt revisions
167 171 files: 1/3 chunks (33.33%)
168 172 adding foo/file.txt revisions
169 173 files: 2/3 chunks (66.67%)
170 174 adding quux/file.py revisions
171 175 files: 3/3 chunks (100.00%)
172 176 added 3 changesets with 3 changes to 3 files
173 177 calling hook pretxnchangegroup.acl: hgext.acl.hook
174 178 acl: changes have source "push" - skipping
175 179 updating the branch cache
176 180 checking for updated bookmarks
177 181 repository tip rolled back to revision 0 (undo push)
178 182 working directory now based on revision 0
179 183 0:6675d58eff77
180 184
181 185
182 186 No [acl.allow]/[acl.deny]
183 187
184 188 $ echo '[acl]' >> $config
185 189 $ echo 'sources = push' >> $config
186 190 $ do_push fred
187 191 Pushing as user fred
188 192 hgrc = """
189 193 [hooks]
190 194 pretxnchangegroup.acl = python:hgext.acl.hook
191 195 [acl]
192 196 sources = push
193 197 """
194 198 pushing to ../b
199 query 1; heads
195 200 searching for changes
201 all remote heads known locally
196 202 invalidating branch cache (tip differs)
197 203 3 changesets found
198 204 list of changesets:
199 205 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
200 206 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
201 207 911600dab2ae7a9baff75958b84fe606851ce955
202 208 adding changesets
203 209 bundling: 1 changesets
204 210 bundling: 2 changesets
205 211 bundling: 3 changesets
206 212 bundling: 1/3 manifests (33.33%)
207 213 bundling: 2/3 manifests (66.67%)
208 214 bundling: 3/3 manifests (100.00%)
209 215 bundling: foo/Bar/file.txt 0/3 files (0.00%)
210 216 bundling: foo/file.txt 1/3 files (33.33%)
211 217 bundling: quux/file.py 2/3 files (66.67%)
212 218 changesets: 1 chunks
213 219 add changeset ef1ea85a6374
214 220 changesets: 2 chunks
215 221 add changeset f9cafe1212c8
216 222 changesets: 3 chunks
217 223 add changeset 911600dab2ae
218 224 adding manifests
219 225 manifests: 1/3 chunks (33.33%)
220 226 manifests: 2/3 chunks (66.67%)
221 227 manifests: 3/3 chunks (100.00%)
222 228 adding file changes
223 229 adding foo/Bar/file.txt revisions
224 230 files: 1/3 chunks (33.33%)
225 231 adding foo/file.txt revisions
226 232 files: 2/3 chunks (66.67%)
227 233 adding quux/file.py revisions
228 234 files: 3/3 chunks (100.00%)
229 235 added 3 changesets with 3 changes to 3 files
230 236 calling hook pretxnchangegroup.acl: hgext.acl.hook
231 237 acl: acl.allow.branches not enabled
232 238 acl: acl.deny.branches not enabled
233 239 acl: acl.allow not enabled
234 240 acl: acl.deny not enabled
235 241 acl: branch access granted: "ef1ea85a6374" on branch "default"
236 242 acl: allowing changeset ef1ea85a6374
237 243 acl: branch access granted: "f9cafe1212c8" on branch "default"
238 244 acl: allowing changeset f9cafe1212c8
239 245 acl: branch access granted: "911600dab2ae" on branch "default"
240 246 acl: allowing changeset 911600dab2ae
241 247 updating the branch cache
242 248 checking for updated bookmarks
243 249 repository tip rolled back to revision 0 (undo push)
244 250 working directory now based on revision 0
245 251 0:6675d58eff77
246 252
247 253
248 254 Empty [acl.allow]
249 255
250 256 $ echo '[acl.allow]' >> $config
251 257 $ do_push fred
252 258 Pushing as user fred
253 259 hgrc = """
254 260 [hooks]
255 261 pretxnchangegroup.acl = python:hgext.acl.hook
256 262 [acl]
257 263 sources = push
258 264 [acl.allow]
259 265 """
260 266 pushing to ../b
267 query 1; heads
261 268 searching for changes
269 all remote heads known locally
262 270 invalidating branch cache (tip differs)
263 271 3 changesets found
264 272 list of changesets:
265 273 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
266 274 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
267 275 911600dab2ae7a9baff75958b84fe606851ce955
268 276 adding changesets
269 277 bundling: 1 changesets
270 278 bundling: 2 changesets
271 279 bundling: 3 changesets
272 280 bundling: 1/3 manifests (33.33%)
273 281 bundling: 2/3 manifests (66.67%)
274 282 bundling: 3/3 manifests (100.00%)
275 283 bundling: foo/Bar/file.txt 0/3 files (0.00%)
276 284 bundling: foo/file.txt 1/3 files (33.33%)
277 285 bundling: quux/file.py 2/3 files (66.67%)
278 286 changesets: 1 chunks
279 287 add changeset ef1ea85a6374
280 288 changesets: 2 chunks
281 289 add changeset f9cafe1212c8
282 290 changesets: 3 chunks
283 291 add changeset 911600dab2ae
284 292 adding manifests
285 293 manifests: 1/3 chunks (33.33%)
286 294 manifests: 2/3 chunks (66.67%)
287 295 manifests: 3/3 chunks (100.00%)
288 296 adding file changes
289 297 adding foo/Bar/file.txt revisions
290 298 files: 1/3 chunks (33.33%)
291 299 adding foo/file.txt revisions
292 300 files: 2/3 chunks (66.67%)
293 301 adding quux/file.py revisions
294 302 files: 3/3 chunks (100.00%)
295 303 added 3 changesets with 3 changes to 3 files
296 304 calling hook pretxnchangegroup.acl: hgext.acl.hook
297 305 acl: acl.allow.branches not enabled
298 306 acl: acl.deny.branches not enabled
299 307 acl: acl.allow enabled, 0 entries for user fred
300 308 acl: acl.deny not enabled
301 309 acl: branch access granted: "ef1ea85a6374" on branch "default"
302 310 acl: user fred not allowed on foo/file.txt
303 311 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset ef1ea85a6374
304 312 transaction abort!
305 313 rollback completed
306 314 abort: acl: access denied for changeset ef1ea85a6374
307 315 no rollback information available
308 316 0:6675d58eff77
309 317
310 318
311 319 fred is allowed inside foo/
312 320
313 321 $ echo 'foo/** = fred' >> $config
314 322 $ do_push fred
315 323 Pushing as user fred
316 324 hgrc = """
317 325 [hooks]
318 326 pretxnchangegroup.acl = python:hgext.acl.hook
319 327 [acl]
320 328 sources = push
321 329 [acl.allow]
322 330 foo/** = fred
323 331 """
324 332 pushing to ../b
333 query 1; heads
325 334 searching for changes
335 all remote heads known locally
326 336 3 changesets found
327 337 list of changesets:
328 338 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
329 339 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
330 340 911600dab2ae7a9baff75958b84fe606851ce955
331 341 adding changesets
332 342 bundling: 1 changesets
333 343 bundling: 2 changesets
334 344 bundling: 3 changesets
335 345 bundling: 1/3 manifests (33.33%)
336 346 bundling: 2/3 manifests (66.67%)
337 347 bundling: 3/3 manifests (100.00%)
338 348 bundling: foo/Bar/file.txt 0/3 files (0.00%)
339 349 bundling: foo/file.txt 1/3 files (33.33%)
340 350 bundling: quux/file.py 2/3 files (66.67%)
341 351 changesets: 1 chunks
342 352 add changeset ef1ea85a6374
343 353 changesets: 2 chunks
344 354 add changeset f9cafe1212c8
345 355 changesets: 3 chunks
346 356 add changeset 911600dab2ae
347 357 adding manifests
348 358 manifests: 1/3 chunks (33.33%)
349 359 manifests: 2/3 chunks (66.67%)
350 360 manifests: 3/3 chunks (100.00%)
351 361 adding file changes
352 362 adding foo/Bar/file.txt revisions
353 363 files: 1/3 chunks (33.33%)
354 364 adding foo/file.txt revisions
355 365 files: 2/3 chunks (66.67%)
356 366 adding quux/file.py revisions
357 367 files: 3/3 chunks (100.00%)
358 368 added 3 changesets with 3 changes to 3 files
359 369 calling hook pretxnchangegroup.acl: hgext.acl.hook
360 370 acl: acl.allow.branches not enabled
361 371 acl: acl.deny.branches not enabled
362 372 acl: acl.allow enabled, 1 entries for user fred
363 373 acl: acl.deny not enabled
364 374 acl: branch access granted: "ef1ea85a6374" on branch "default"
365 375 acl: allowing changeset ef1ea85a6374
366 376 acl: branch access granted: "f9cafe1212c8" on branch "default"
367 377 acl: allowing changeset f9cafe1212c8
368 378 acl: branch access granted: "911600dab2ae" on branch "default"
369 379 acl: user fred not allowed on quux/file.py
370 380 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae
371 381 transaction abort!
372 382 rollback completed
373 383 abort: acl: access denied for changeset 911600dab2ae
374 384 no rollback information available
375 385 0:6675d58eff77
376 386
377 387
378 388 Empty [acl.deny]
379 389
380 390 $ echo '[acl.deny]' >> $config
381 391 $ do_push barney
382 392 Pushing as user barney
383 393 hgrc = """
384 394 [hooks]
385 395 pretxnchangegroup.acl = python:hgext.acl.hook
386 396 [acl]
387 397 sources = push
388 398 [acl.allow]
389 399 foo/** = fred
390 400 [acl.deny]
391 401 """
392 402 pushing to ../b
403 query 1; heads
393 404 searching for changes
405 all remote heads known locally
394 406 3 changesets found
395 407 list of changesets:
396 408 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
397 409 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
398 410 911600dab2ae7a9baff75958b84fe606851ce955
399 411 adding changesets
400 412 bundling: 1 changesets
401 413 bundling: 2 changesets
402 414 bundling: 3 changesets
403 415 bundling: 1/3 manifests (33.33%)
404 416 bundling: 2/3 manifests (66.67%)
405 417 bundling: 3/3 manifests (100.00%)
406 418 bundling: foo/Bar/file.txt 0/3 files (0.00%)
407 419 bundling: foo/file.txt 1/3 files (33.33%)
408 420 bundling: quux/file.py 2/3 files (66.67%)
409 421 changesets: 1 chunks
410 422 add changeset ef1ea85a6374
411 423 changesets: 2 chunks
412 424 add changeset f9cafe1212c8
413 425 changesets: 3 chunks
414 426 add changeset 911600dab2ae
415 427 adding manifests
416 428 manifests: 1/3 chunks (33.33%)
417 429 manifests: 2/3 chunks (66.67%)
418 430 manifests: 3/3 chunks (100.00%)
419 431 adding file changes
420 432 adding foo/Bar/file.txt revisions
421 433 files: 1/3 chunks (33.33%)
422 434 adding foo/file.txt revisions
423 435 files: 2/3 chunks (66.67%)
424 436 adding quux/file.py revisions
425 437 files: 3/3 chunks (100.00%)
426 438 added 3 changesets with 3 changes to 3 files
427 439 calling hook pretxnchangegroup.acl: hgext.acl.hook
428 440 acl: acl.allow.branches not enabled
429 441 acl: acl.deny.branches not enabled
430 442 acl: acl.allow enabled, 0 entries for user barney
431 443 acl: acl.deny enabled, 0 entries for user barney
432 444 acl: branch access granted: "ef1ea85a6374" on branch "default"
433 445 acl: user barney not allowed on foo/file.txt
434 446 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset ef1ea85a6374
435 447 transaction abort!
436 448 rollback completed
437 449 abort: acl: access denied for changeset ef1ea85a6374
438 450 no rollback information available
439 451 0:6675d58eff77
440 452
441 453
442 454 fred is allowed inside foo/, but not foo/bar/ (case matters)
443 455
444 456 $ echo 'foo/bar/** = fred' >> $config
445 457 $ do_push fred
446 458 Pushing as user fred
447 459 hgrc = """
448 460 [hooks]
449 461 pretxnchangegroup.acl = python:hgext.acl.hook
450 462 [acl]
451 463 sources = push
452 464 [acl.allow]
453 465 foo/** = fred
454 466 [acl.deny]
455 467 foo/bar/** = fred
456 468 """
457 469 pushing to ../b
470 query 1; heads
458 471 searching for changes
472 all remote heads known locally
459 473 3 changesets found
460 474 list of changesets:
461 475 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
462 476 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
463 477 911600dab2ae7a9baff75958b84fe606851ce955
464 478 adding changesets
465 479 bundling: 1 changesets
466 480 bundling: 2 changesets
467 481 bundling: 3 changesets
468 482 bundling: 1/3 manifests (33.33%)
469 483 bundling: 2/3 manifests (66.67%)
470 484 bundling: 3/3 manifests (100.00%)
471 485 bundling: foo/Bar/file.txt 0/3 files (0.00%)
472 486 bundling: foo/file.txt 1/3 files (33.33%)
473 487 bundling: quux/file.py 2/3 files (66.67%)
474 488 changesets: 1 chunks
475 489 add changeset ef1ea85a6374
476 490 changesets: 2 chunks
477 491 add changeset f9cafe1212c8
478 492 changesets: 3 chunks
479 493 add changeset 911600dab2ae
480 494 adding manifests
481 495 manifests: 1/3 chunks (33.33%)
482 496 manifests: 2/3 chunks (66.67%)
483 497 manifests: 3/3 chunks (100.00%)
484 498 adding file changes
485 499 adding foo/Bar/file.txt revisions
486 500 files: 1/3 chunks (33.33%)
487 501 adding foo/file.txt revisions
488 502 files: 2/3 chunks (66.67%)
489 503 adding quux/file.py revisions
490 504 files: 3/3 chunks (100.00%)
491 505 added 3 changesets with 3 changes to 3 files
492 506 calling hook pretxnchangegroup.acl: hgext.acl.hook
493 507 acl: acl.allow.branches not enabled
494 508 acl: acl.deny.branches not enabled
495 509 acl: acl.allow enabled, 1 entries for user fred
496 510 acl: acl.deny enabled, 1 entries for user fred
497 511 acl: branch access granted: "ef1ea85a6374" on branch "default"
498 512 acl: allowing changeset ef1ea85a6374
499 513 acl: branch access granted: "f9cafe1212c8" on branch "default"
500 514 acl: allowing changeset f9cafe1212c8
501 515 acl: branch access granted: "911600dab2ae" on branch "default"
502 516 acl: user fred not allowed on quux/file.py
503 517 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae
504 518 transaction abort!
505 519 rollback completed
506 520 abort: acl: access denied for changeset 911600dab2ae
507 521 no rollback information available
508 522 0:6675d58eff77
509 523
510 524
511 525 fred is allowed inside foo/, but not foo/Bar/
512 526
513 527 $ echo 'foo/Bar/** = fred' >> $config
514 528 $ do_push fred
515 529 Pushing as user fred
516 530 hgrc = """
517 531 [hooks]
518 532 pretxnchangegroup.acl = python:hgext.acl.hook
519 533 [acl]
520 534 sources = push
521 535 [acl.allow]
522 536 foo/** = fred
523 537 [acl.deny]
524 538 foo/bar/** = fred
525 539 foo/Bar/** = fred
526 540 """
527 541 pushing to ../b
542 query 1; heads
528 543 searching for changes
544 all remote heads known locally
529 545 3 changesets found
530 546 list of changesets:
531 547 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
532 548 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
533 549 911600dab2ae7a9baff75958b84fe606851ce955
534 550 adding changesets
535 551 bundling: 1 changesets
536 552 bundling: 2 changesets
537 553 bundling: 3 changesets
538 554 bundling: 1/3 manifests (33.33%)
539 555 bundling: 2/3 manifests (66.67%)
540 556 bundling: 3/3 manifests (100.00%)
541 557 bundling: foo/Bar/file.txt 0/3 files (0.00%)
542 558 bundling: foo/file.txt 1/3 files (33.33%)
543 559 bundling: quux/file.py 2/3 files (66.67%)
544 560 changesets: 1 chunks
545 561 add changeset ef1ea85a6374
546 562 changesets: 2 chunks
547 563 add changeset f9cafe1212c8
548 564 changesets: 3 chunks
549 565 add changeset 911600dab2ae
550 566 adding manifests
551 567 manifests: 1/3 chunks (33.33%)
552 568 manifests: 2/3 chunks (66.67%)
553 569 manifests: 3/3 chunks (100.00%)
554 570 adding file changes
555 571 adding foo/Bar/file.txt revisions
556 572 files: 1/3 chunks (33.33%)
557 573 adding foo/file.txt revisions
558 574 files: 2/3 chunks (66.67%)
559 575 adding quux/file.py revisions
560 576 files: 3/3 chunks (100.00%)
561 577 added 3 changesets with 3 changes to 3 files
562 578 calling hook pretxnchangegroup.acl: hgext.acl.hook
563 579 acl: acl.allow.branches not enabled
564 580 acl: acl.deny.branches not enabled
565 581 acl: acl.allow enabled, 1 entries for user fred
566 582 acl: acl.deny enabled, 2 entries for user fred
567 583 acl: branch access granted: "ef1ea85a6374" on branch "default"
568 584 acl: allowing changeset ef1ea85a6374
569 585 acl: branch access granted: "f9cafe1212c8" on branch "default"
570 586 acl: user fred denied on foo/Bar/file.txt
571 587 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset f9cafe1212c8
572 588 transaction abort!
573 589 rollback completed
574 590 abort: acl: access denied for changeset f9cafe1212c8
575 591 no rollback information available
576 592 0:6675d58eff77
577 593
578 594
579 595 $ echo 'barney is not mentioned => not allowed anywhere'
580 596 barney is not mentioned => not allowed anywhere
581 597 $ do_push barney
582 598 Pushing as user barney
583 599 hgrc = """
584 600 [hooks]
585 601 pretxnchangegroup.acl = python:hgext.acl.hook
586 602 [acl]
587 603 sources = push
588 604 [acl.allow]
589 605 foo/** = fred
590 606 [acl.deny]
591 607 foo/bar/** = fred
592 608 foo/Bar/** = fred
593 609 """
594 610 pushing to ../b
611 query 1; heads
595 612 searching for changes
613 all remote heads known locally
596 614 3 changesets found
597 615 list of changesets:
598 616 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
599 617 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
600 618 911600dab2ae7a9baff75958b84fe606851ce955
601 619 adding changesets
602 620 bundling: 1 changesets
603 621 bundling: 2 changesets
604 622 bundling: 3 changesets
605 623 bundling: 1/3 manifests (33.33%)
606 624 bundling: 2/3 manifests (66.67%)
607 625 bundling: 3/3 manifests (100.00%)
608 626 bundling: foo/Bar/file.txt 0/3 files (0.00%)
609 627 bundling: foo/file.txt 1/3 files (33.33%)
610 628 bundling: quux/file.py 2/3 files (66.67%)
611 629 changesets: 1 chunks
612 630 add changeset ef1ea85a6374
613 631 changesets: 2 chunks
614 632 add changeset f9cafe1212c8
615 633 changesets: 3 chunks
616 634 add changeset 911600dab2ae
617 635 adding manifests
618 636 manifests: 1/3 chunks (33.33%)
619 637 manifests: 2/3 chunks (66.67%)
620 638 manifests: 3/3 chunks (100.00%)
621 639 adding file changes
622 640 adding foo/Bar/file.txt revisions
623 641 files: 1/3 chunks (33.33%)
624 642 adding foo/file.txt revisions
625 643 files: 2/3 chunks (66.67%)
626 644 adding quux/file.py revisions
627 645 files: 3/3 chunks (100.00%)
628 646 added 3 changesets with 3 changes to 3 files
629 647 calling hook pretxnchangegroup.acl: hgext.acl.hook
630 648 acl: acl.allow.branches not enabled
631 649 acl: acl.deny.branches not enabled
632 650 acl: acl.allow enabled, 0 entries for user barney
633 651 acl: acl.deny enabled, 0 entries for user barney
634 652 acl: branch access granted: "ef1ea85a6374" on branch "default"
635 653 acl: user barney not allowed on foo/file.txt
636 654 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset ef1ea85a6374
637 655 transaction abort!
638 656 rollback completed
639 657 abort: acl: access denied for changeset ef1ea85a6374
640 658 no rollback information available
641 659 0:6675d58eff77
642 660
643 661
644 662 barney is allowed everywhere
645 663
646 664 $ echo '[acl.allow]' >> $config
647 665 $ echo '** = barney' >> $config
648 666 $ do_push barney
649 667 Pushing as user barney
650 668 hgrc = """
651 669 [hooks]
652 670 pretxnchangegroup.acl = python:hgext.acl.hook
653 671 [acl]
654 672 sources = push
655 673 [acl.allow]
656 674 foo/** = fred
657 675 [acl.deny]
658 676 foo/bar/** = fred
659 677 foo/Bar/** = fred
660 678 [acl.allow]
661 679 ** = barney
662 680 """
663 681 pushing to ../b
682 query 1; heads
664 683 searching for changes
684 all remote heads known locally
665 685 3 changesets found
666 686 list of changesets:
667 687 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
668 688 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
669 689 911600dab2ae7a9baff75958b84fe606851ce955
670 690 adding changesets
671 691 bundling: 1 changesets
672 692 bundling: 2 changesets
673 693 bundling: 3 changesets
674 694 bundling: 1/3 manifests (33.33%)
675 695 bundling: 2/3 manifests (66.67%)
676 696 bundling: 3/3 manifests (100.00%)
677 697 bundling: foo/Bar/file.txt 0/3 files (0.00%)
678 698 bundling: foo/file.txt 1/3 files (33.33%)
679 699 bundling: quux/file.py 2/3 files (66.67%)
680 700 changesets: 1 chunks
681 701 add changeset ef1ea85a6374
682 702 changesets: 2 chunks
683 703 add changeset f9cafe1212c8
684 704 changesets: 3 chunks
685 705 add changeset 911600dab2ae
686 706 adding manifests
687 707 manifests: 1/3 chunks (33.33%)
688 708 manifests: 2/3 chunks (66.67%)
689 709 manifests: 3/3 chunks (100.00%)
690 710 adding file changes
691 711 adding foo/Bar/file.txt revisions
692 712 files: 1/3 chunks (33.33%)
693 713 adding foo/file.txt revisions
694 714 files: 2/3 chunks (66.67%)
695 715 adding quux/file.py revisions
696 716 files: 3/3 chunks (100.00%)
697 717 added 3 changesets with 3 changes to 3 files
698 718 calling hook pretxnchangegroup.acl: hgext.acl.hook
699 719 acl: acl.allow.branches not enabled
700 720 acl: acl.deny.branches not enabled
701 721 acl: acl.allow enabled, 1 entries for user barney
702 722 acl: acl.deny enabled, 0 entries for user barney
703 723 acl: branch access granted: "ef1ea85a6374" on branch "default"
704 724 acl: allowing changeset ef1ea85a6374
705 725 acl: branch access granted: "f9cafe1212c8" on branch "default"
706 726 acl: allowing changeset f9cafe1212c8
707 727 acl: branch access granted: "911600dab2ae" on branch "default"
708 728 acl: allowing changeset 911600dab2ae
709 729 updating the branch cache
710 730 checking for updated bookmarks
711 731 repository tip rolled back to revision 0 (undo push)
712 732 working directory now based on revision 0
713 733 0:6675d58eff77
714 734
715 735
716 736 wilma can change files with a .txt extension
717 737
718 738 $ echo '**/*.txt = wilma' >> $config
719 739 $ do_push wilma
720 740 Pushing as user wilma
721 741 hgrc = """
722 742 [hooks]
723 743 pretxnchangegroup.acl = python:hgext.acl.hook
724 744 [acl]
725 745 sources = push
726 746 [acl.allow]
727 747 foo/** = fred
728 748 [acl.deny]
729 749 foo/bar/** = fred
730 750 foo/Bar/** = fred
731 751 [acl.allow]
732 752 ** = barney
733 753 **/*.txt = wilma
734 754 """
735 755 pushing to ../b
756 query 1; heads
736 757 searching for changes
758 all remote heads known locally
737 759 invalidating branch cache (tip differs)
738 760 3 changesets found
739 761 list of changesets:
740 762 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
741 763 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
742 764 911600dab2ae7a9baff75958b84fe606851ce955
743 765 adding changesets
744 766 bundling: 1 changesets
745 767 bundling: 2 changesets
746 768 bundling: 3 changesets
747 769 bundling: 1/3 manifests (33.33%)
748 770 bundling: 2/3 manifests (66.67%)
749 771 bundling: 3/3 manifests (100.00%)
750 772 bundling: foo/Bar/file.txt 0/3 files (0.00%)
751 773 bundling: foo/file.txt 1/3 files (33.33%)
752 774 bundling: quux/file.py 2/3 files (66.67%)
753 775 changesets: 1 chunks
754 776 add changeset ef1ea85a6374
755 777 changesets: 2 chunks
756 778 add changeset f9cafe1212c8
757 779 changesets: 3 chunks
758 780 add changeset 911600dab2ae
759 781 adding manifests
760 782 manifests: 1/3 chunks (33.33%)
761 783 manifests: 2/3 chunks (66.67%)
762 784 manifests: 3/3 chunks (100.00%)
763 785 adding file changes
764 786 adding foo/Bar/file.txt revisions
765 787 files: 1/3 chunks (33.33%)
766 788 adding foo/file.txt revisions
767 789 files: 2/3 chunks (66.67%)
768 790 adding quux/file.py revisions
769 791 files: 3/3 chunks (100.00%)
770 792 added 3 changesets with 3 changes to 3 files
771 793 calling hook pretxnchangegroup.acl: hgext.acl.hook
772 794 acl: acl.allow.branches not enabled
773 795 acl: acl.deny.branches not enabled
774 796 acl: acl.allow enabled, 1 entries for user wilma
775 797 acl: acl.deny enabled, 0 entries for user wilma
776 798 acl: branch access granted: "ef1ea85a6374" on branch "default"
777 799 acl: allowing changeset ef1ea85a6374
778 800 acl: branch access granted: "f9cafe1212c8" on branch "default"
779 801 acl: allowing changeset f9cafe1212c8
780 802 acl: branch access granted: "911600dab2ae" on branch "default"
781 803 acl: user wilma not allowed on quux/file.py
782 804 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae
783 805 transaction abort!
784 806 rollback completed
785 807 abort: acl: access denied for changeset 911600dab2ae
786 808 no rollback information available
787 809 0:6675d58eff77
788 810
789 811
790 812 file specified by acl.config does not exist
791 813
792 814 $ echo '[acl]' >> $config
793 815 $ echo 'config = ../acl.config' >> $config
794 816 $ do_push barney
795 817 Pushing as user barney
796 818 hgrc = """
797 819 [hooks]
798 820 pretxnchangegroup.acl = python:hgext.acl.hook
799 821 [acl]
800 822 sources = push
801 823 [acl.allow]
802 824 foo/** = fred
803 825 [acl.deny]
804 826 foo/bar/** = fred
805 827 foo/Bar/** = fred
806 828 [acl.allow]
807 829 ** = barney
808 830 **/*.txt = wilma
809 831 [acl]
810 832 config = ../acl.config
811 833 """
812 834 pushing to ../b
835 query 1; heads
813 836 searching for changes
837 all remote heads known locally
814 838 3 changesets found
815 839 list of changesets:
816 840 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
817 841 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
818 842 911600dab2ae7a9baff75958b84fe606851ce955
819 843 adding changesets
820 844 bundling: 1 changesets
821 845 bundling: 2 changesets
822 846 bundling: 3 changesets
823 847 bundling: 1/3 manifests (33.33%)
824 848 bundling: 2/3 manifests (66.67%)
825 849 bundling: 3/3 manifests (100.00%)
826 850 bundling: foo/Bar/file.txt 0/3 files (0.00%)
827 851 bundling: foo/file.txt 1/3 files (33.33%)
828 852 bundling: quux/file.py 2/3 files (66.67%)
829 853 changesets: 1 chunks
830 854 add changeset ef1ea85a6374
831 855 changesets: 2 chunks
832 856 add changeset f9cafe1212c8
833 857 changesets: 3 chunks
834 858 add changeset 911600dab2ae
835 859 adding manifests
836 860 manifests: 1/3 chunks (33.33%)
837 861 manifests: 2/3 chunks (66.67%)
838 862 manifests: 3/3 chunks (100.00%)
839 863 adding file changes
840 864 adding foo/Bar/file.txt revisions
841 865 files: 1/3 chunks (33.33%)
842 866 adding foo/file.txt revisions
843 867 files: 2/3 chunks (66.67%)
844 868 adding quux/file.py revisions
845 869 files: 3/3 chunks (100.00%)
846 870 added 3 changesets with 3 changes to 3 files
847 871 calling hook pretxnchangegroup.acl: hgext.acl.hook
848 872 error: pretxnchangegroup.acl hook raised an exception: [Errno 2] No such file or directory: '../acl.config'
849 873 transaction abort!
850 874 rollback completed
851 875 abort: No such file or directory: ../acl.config
852 876 no rollback information available
853 877 0:6675d58eff77
854 878
855 879
856 880 betty is allowed inside foo/ by a acl.config file
857 881
858 882 $ echo '[acl.allow]' >> acl.config
859 883 $ echo 'foo/** = betty' >> acl.config
860 884 $ do_push betty
861 885 Pushing as user betty
862 886 hgrc = """
863 887 [hooks]
864 888 pretxnchangegroup.acl = python:hgext.acl.hook
865 889 [acl]
866 890 sources = push
867 891 [acl.allow]
868 892 foo/** = fred
869 893 [acl.deny]
870 894 foo/bar/** = fred
871 895 foo/Bar/** = fred
872 896 [acl.allow]
873 897 ** = barney
874 898 **/*.txt = wilma
875 899 [acl]
876 900 config = ../acl.config
877 901 """
878 902 acl.config = """
879 903 [acl.allow]
880 904 foo/** = betty
881 905 """
882 906 pushing to ../b
907 query 1; heads
883 908 searching for changes
909 all remote heads known locally
884 910 3 changesets found
885 911 list of changesets:
886 912 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
887 913 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
888 914 911600dab2ae7a9baff75958b84fe606851ce955
889 915 adding changesets
890 916 bundling: 1 changesets
891 917 bundling: 2 changesets
892 918 bundling: 3 changesets
893 919 bundling: 1/3 manifests (33.33%)
894 920 bundling: 2/3 manifests (66.67%)
895 921 bundling: 3/3 manifests (100.00%)
896 922 bundling: foo/Bar/file.txt 0/3 files (0.00%)
897 923 bundling: foo/file.txt 1/3 files (33.33%)
898 924 bundling: quux/file.py 2/3 files (66.67%)
899 925 changesets: 1 chunks
900 926 add changeset ef1ea85a6374
901 927 changesets: 2 chunks
902 928 add changeset f9cafe1212c8
903 929 changesets: 3 chunks
904 930 add changeset 911600dab2ae
905 931 adding manifests
906 932 manifests: 1/3 chunks (33.33%)
907 933 manifests: 2/3 chunks (66.67%)
908 934 manifests: 3/3 chunks (100.00%)
909 935 adding file changes
910 936 adding foo/Bar/file.txt revisions
911 937 files: 1/3 chunks (33.33%)
912 938 adding foo/file.txt revisions
913 939 files: 2/3 chunks (66.67%)
914 940 adding quux/file.py revisions
915 941 files: 3/3 chunks (100.00%)
916 942 added 3 changesets with 3 changes to 3 files
917 943 calling hook pretxnchangegroup.acl: hgext.acl.hook
918 944 acl: acl.allow.branches not enabled
919 945 acl: acl.deny.branches not enabled
920 946 acl: acl.allow enabled, 1 entries for user betty
921 947 acl: acl.deny enabled, 0 entries for user betty
922 948 acl: branch access granted: "ef1ea85a6374" on branch "default"
923 949 acl: allowing changeset ef1ea85a6374
924 950 acl: branch access granted: "f9cafe1212c8" on branch "default"
925 951 acl: allowing changeset f9cafe1212c8
926 952 acl: branch access granted: "911600dab2ae" on branch "default"
927 953 acl: user betty not allowed on quux/file.py
928 954 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae
929 955 transaction abort!
930 956 rollback completed
931 957 abort: acl: access denied for changeset 911600dab2ae
932 958 no rollback information available
933 959 0:6675d58eff77
934 960
935 961
936 962 acl.config can set only [acl.allow]/[acl.deny]
937 963
938 964 $ echo '[hooks]' >> acl.config
939 965 $ echo 'changegroup.acl = false' >> acl.config
940 966 $ do_push barney
941 967 Pushing as user barney
942 968 hgrc = """
943 969 [hooks]
944 970 pretxnchangegroup.acl = python:hgext.acl.hook
945 971 [acl]
946 972 sources = push
947 973 [acl.allow]
948 974 foo/** = fred
949 975 [acl.deny]
950 976 foo/bar/** = fred
951 977 foo/Bar/** = fred
952 978 [acl.allow]
953 979 ** = barney
954 980 **/*.txt = wilma
955 981 [acl]
956 982 config = ../acl.config
957 983 """
958 984 acl.config = """
959 985 [acl.allow]
960 986 foo/** = betty
961 987 [hooks]
962 988 changegroup.acl = false
963 989 """
964 990 pushing to ../b
991 query 1; heads
965 992 searching for changes
993 all remote heads known locally
966 994 3 changesets found
967 995 list of changesets:
968 996 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
969 997 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
970 998 911600dab2ae7a9baff75958b84fe606851ce955
971 999 adding changesets
972 1000 bundling: 1 changesets
973 1001 bundling: 2 changesets
974 1002 bundling: 3 changesets
975 1003 bundling: 1/3 manifests (33.33%)
976 1004 bundling: 2/3 manifests (66.67%)
977 1005 bundling: 3/3 manifests (100.00%)
978 1006 bundling: foo/Bar/file.txt 0/3 files (0.00%)
979 1007 bundling: foo/file.txt 1/3 files (33.33%)
980 1008 bundling: quux/file.py 2/3 files (66.67%)
981 1009 changesets: 1 chunks
982 1010 add changeset ef1ea85a6374
983 1011 changesets: 2 chunks
984 1012 add changeset f9cafe1212c8
985 1013 changesets: 3 chunks
986 1014 add changeset 911600dab2ae
987 1015 adding manifests
988 1016 manifests: 1/3 chunks (33.33%)
989 1017 manifests: 2/3 chunks (66.67%)
990 1018 manifests: 3/3 chunks (100.00%)
991 1019 adding file changes
992 1020 adding foo/Bar/file.txt revisions
993 1021 files: 1/3 chunks (33.33%)
994 1022 adding foo/file.txt revisions
995 1023 files: 2/3 chunks (66.67%)
996 1024 adding quux/file.py revisions
997 1025 files: 3/3 chunks (100.00%)
998 1026 added 3 changesets with 3 changes to 3 files
999 1027 calling hook pretxnchangegroup.acl: hgext.acl.hook
1000 1028 acl: acl.allow.branches not enabled
1001 1029 acl: acl.deny.branches not enabled
1002 1030 acl: acl.allow enabled, 1 entries for user barney
1003 1031 acl: acl.deny enabled, 0 entries for user barney
1004 1032 acl: branch access granted: "ef1ea85a6374" on branch "default"
1005 1033 acl: allowing changeset ef1ea85a6374
1006 1034 acl: branch access granted: "f9cafe1212c8" on branch "default"
1007 1035 acl: allowing changeset f9cafe1212c8
1008 1036 acl: branch access granted: "911600dab2ae" on branch "default"
1009 1037 acl: allowing changeset 911600dab2ae
1010 1038 updating the branch cache
1011 1039 checking for updated bookmarks
1012 1040 repository tip rolled back to revision 0 (undo push)
1013 1041 working directory now based on revision 0
1014 1042 0:6675d58eff77
1015 1043
1016 1044
1017 1045 asterisk
1018 1046
1019 1047 $ init_config
1020 1048
1021 1049 asterisk test
1022 1050
1023 1051 $ echo '[acl.allow]' >> $config
1024 1052 $ echo "** = fred" >> $config
1025 1053
1026 1054 fred is always allowed
1027 1055
1028 1056 $ do_push fred
1029 1057 Pushing as user fred
1030 1058 hgrc = """
1031 1059 [acl]
1032 1060 sources = push
1033 1061 [extensions]
1034 1062 [acl.allow]
1035 1063 ** = fred
1036 1064 """
1037 1065 pushing to ../b
1066 query 1; heads
1038 1067 searching for changes
1068 all remote heads known locally
1039 1069 invalidating branch cache (tip differs)
1040 1070 3 changesets found
1041 1071 list of changesets:
1042 1072 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1043 1073 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1044 1074 911600dab2ae7a9baff75958b84fe606851ce955
1045 1075 adding changesets
1046 1076 bundling: 1 changesets
1047 1077 bundling: 2 changesets
1048 1078 bundling: 3 changesets
1049 1079 bundling: 1/3 manifests (33.33%)
1050 1080 bundling: 2/3 manifests (66.67%)
1051 1081 bundling: 3/3 manifests (100.00%)
1052 1082 bundling: foo/Bar/file.txt 0/3 files (0.00%)
1053 1083 bundling: foo/file.txt 1/3 files (33.33%)
1054 1084 bundling: quux/file.py 2/3 files (66.67%)
1055 1085 changesets: 1 chunks
1056 1086 add changeset ef1ea85a6374
1057 1087 changesets: 2 chunks
1058 1088 add changeset f9cafe1212c8
1059 1089 changesets: 3 chunks
1060 1090 add changeset 911600dab2ae
1061 1091 adding manifests
1062 1092 manifests: 1/3 chunks (33.33%)
1063 1093 manifests: 2/3 chunks (66.67%)
1064 1094 manifests: 3/3 chunks (100.00%)
1065 1095 adding file changes
1066 1096 adding foo/Bar/file.txt revisions
1067 1097 files: 1/3 chunks (33.33%)
1068 1098 adding foo/file.txt revisions
1069 1099 files: 2/3 chunks (66.67%)
1070 1100 adding quux/file.py revisions
1071 1101 files: 3/3 chunks (100.00%)
1072 1102 added 3 changesets with 3 changes to 3 files
1073 1103 calling hook pretxnchangegroup.acl: hgext.acl.hook
1074 1104 acl: acl.allow.branches not enabled
1075 1105 acl: acl.deny.branches not enabled
1076 1106 acl: acl.allow enabled, 1 entries for user fred
1077 1107 acl: acl.deny not enabled
1078 1108 acl: branch access granted: "ef1ea85a6374" on branch "default"
1079 1109 acl: allowing changeset ef1ea85a6374
1080 1110 acl: branch access granted: "f9cafe1212c8" on branch "default"
1081 1111 acl: allowing changeset f9cafe1212c8
1082 1112 acl: branch access granted: "911600dab2ae" on branch "default"
1083 1113 acl: allowing changeset 911600dab2ae
1084 1114 updating the branch cache
1085 1115 checking for updated bookmarks
1086 1116 repository tip rolled back to revision 0 (undo push)
1087 1117 working directory now based on revision 0
1088 1118 0:6675d58eff77
1089 1119
1090 1120
1091 1121 $ echo '[acl.deny]' >> $config
1092 1122 $ echo "foo/Bar/** = *" >> $config
1093 1123
1094 1124 no one is allowed inside foo/Bar/
1095 1125
1096 1126 $ do_push fred
1097 1127 Pushing as user fred
1098 1128 hgrc = """
1099 1129 [acl]
1100 1130 sources = push
1101 1131 [extensions]
1102 1132 [acl.allow]
1103 1133 ** = fred
1104 1134 [acl.deny]
1105 1135 foo/Bar/** = *
1106 1136 """
1107 1137 pushing to ../b
1138 query 1; heads
1108 1139 searching for changes
1140 all remote heads known locally
1109 1141 invalidating branch cache (tip differs)
1110 1142 3 changesets found
1111 1143 list of changesets:
1112 1144 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1113 1145 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1114 1146 911600dab2ae7a9baff75958b84fe606851ce955
1115 1147 adding changesets
1116 1148 bundling: 1 changesets
1117 1149 bundling: 2 changesets
1118 1150 bundling: 3 changesets
1119 1151 bundling: 1/3 manifests (33.33%)
1120 1152 bundling: 2/3 manifests (66.67%)
1121 1153 bundling: 3/3 manifests (100.00%)
1122 1154 bundling: foo/Bar/file.txt 0/3 files (0.00%)
1123 1155 bundling: foo/file.txt 1/3 files (33.33%)
1124 1156 bundling: quux/file.py 2/3 files (66.67%)
1125 1157 changesets: 1 chunks
1126 1158 add changeset ef1ea85a6374
1127 1159 changesets: 2 chunks
1128 1160 add changeset f9cafe1212c8
1129 1161 changesets: 3 chunks
1130 1162 add changeset 911600dab2ae
1131 1163 adding manifests
1132 1164 manifests: 1/3 chunks (33.33%)
1133 1165 manifests: 2/3 chunks (66.67%)
1134 1166 manifests: 3/3 chunks (100.00%)
1135 1167 adding file changes
1136 1168 adding foo/Bar/file.txt revisions
1137 1169 files: 1/3 chunks (33.33%)
1138 1170 adding foo/file.txt revisions
1139 1171 files: 2/3 chunks (66.67%)
1140 1172 adding quux/file.py revisions
1141 1173 files: 3/3 chunks (100.00%)
1142 1174 added 3 changesets with 3 changes to 3 files
1143 1175 calling hook pretxnchangegroup.acl: hgext.acl.hook
1144 1176 acl: acl.allow.branches not enabled
1145 1177 acl: acl.deny.branches not enabled
1146 1178 acl: acl.allow enabled, 1 entries for user fred
1147 1179 acl: acl.deny enabled, 1 entries for user fred
1148 1180 acl: branch access granted: "ef1ea85a6374" on branch "default"
1149 1181 acl: allowing changeset ef1ea85a6374
1150 1182 acl: branch access granted: "f9cafe1212c8" on branch "default"
1151 1183 acl: user fred denied on foo/Bar/file.txt
1152 1184 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset f9cafe1212c8
1153 1185 transaction abort!
1154 1186 rollback completed
1155 1187 abort: acl: access denied for changeset f9cafe1212c8
1156 1188 no rollback information available
1157 1189 0:6675d58eff77
1158 1190
1159 1191
1160 1192 Groups
1161 1193
1162 1194 $ init_config
1163 1195
1164 1196 OS-level groups
1165 1197
1166 1198 $ echo '[acl.allow]' >> $config
1167 1199 $ echo "** = @group1" >> $config
1168 1200
1169 1201 @group1 is always allowed
1170 1202
1171 1203 $ do_push fred
1172 1204 Pushing as user fred
1173 1205 hgrc = """
1174 1206 [acl]
1175 1207 sources = push
1176 1208 [extensions]
1177 1209 [acl.allow]
1178 1210 ** = @group1
1179 1211 """
1180 1212 pushing to ../b
1213 query 1; heads
1181 1214 searching for changes
1215 all remote heads known locally
1182 1216 3 changesets found
1183 1217 list of changesets:
1184 1218 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1185 1219 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1186 1220 911600dab2ae7a9baff75958b84fe606851ce955
1187 1221 adding changesets
1188 1222 bundling: 1 changesets
1189 1223 bundling: 2 changesets
1190 1224 bundling: 3 changesets
1191 1225 bundling: 1/3 manifests (33.33%)
1192 1226 bundling: 2/3 manifests (66.67%)
1193 1227 bundling: 3/3 manifests (100.00%)
1194 1228 bundling: foo/Bar/file.txt 0/3 files (0.00%)
1195 1229 bundling: foo/file.txt 1/3 files (33.33%)
1196 1230 bundling: quux/file.py 2/3 files (66.67%)
1197 1231 changesets: 1 chunks
1198 1232 add changeset ef1ea85a6374
1199 1233 changesets: 2 chunks
1200 1234 add changeset f9cafe1212c8
1201 1235 changesets: 3 chunks
1202 1236 add changeset 911600dab2ae
1203 1237 adding manifests
1204 1238 manifests: 1/3 chunks (33.33%)
1205 1239 manifests: 2/3 chunks (66.67%)
1206 1240 manifests: 3/3 chunks (100.00%)
1207 1241 adding file changes
1208 1242 adding foo/Bar/file.txt revisions
1209 1243 files: 1/3 chunks (33.33%)
1210 1244 adding foo/file.txt revisions
1211 1245 files: 2/3 chunks (66.67%)
1212 1246 adding quux/file.py revisions
1213 1247 files: 3/3 chunks (100.00%)
1214 1248 added 3 changesets with 3 changes to 3 files
1215 1249 calling hook pretxnchangegroup.acl: hgext.acl.hook
1216 1250 acl: acl.allow.branches not enabled
1217 1251 acl: acl.deny.branches not enabled
1218 1252 acl: "group1" not defined in [acl.groups]
1219 1253 acl: acl.allow enabled, 1 entries for user fred
1220 1254 acl: acl.deny not enabled
1221 1255 acl: branch access granted: "ef1ea85a6374" on branch "default"
1222 1256 acl: allowing changeset ef1ea85a6374
1223 1257 acl: branch access granted: "f9cafe1212c8" on branch "default"
1224 1258 acl: allowing changeset f9cafe1212c8
1225 1259 acl: branch access granted: "911600dab2ae" on branch "default"
1226 1260 acl: allowing changeset 911600dab2ae
1227 1261 updating the branch cache
1228 1262 checking for updated bookmarks
1229 1263 repository tip rolled back to revision 0 (undo push)
1230 1264 working directory now based on revision 0
1231 1265 0:6675d58eff77
1232 1266
1233 1267
1234 1268 $ echo '[acl.deny]' >> $config
1235 1269 $ echo "foo/Bar/** = @group1" >> $config
1236 1270
1237 1271 @group is allowed inside anything but foo/Bar/
1238 1272
1239 1273 $ do_push fred
1240 1274 Pushing as user fred
1241 1275 hgrc = """
1242 1276 [acl]
1243 1277 sources = push
1244 1278 [extensions]
1245 1279 [acl.allow]
1246 1280 ** = @group1
1247 1281 [acl.deny]
1248 1282 foo/Bar/** = @group1
1249 1283 """
1250 1284 pushing to ../b
1285 query 1; heads
1251 1286 searching for changes
1287 all remote heads known locally
1252 1288 invalidating branch cache (tip differs)
1253 1289 3 changesets found
1254 1290 list of changesets:
1255 1291 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1256 1292 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1257 1293 911600dab2ae7a9baff75958b84fe606851ce955
1258 1294 adding changesets
1259 1295 bundling: 1 changesets
1260 1296 bundling: 2 changesets
1261 1297 bundling: 3 changesets
1262 1298 bundling: 1/3 manifests (33.33%)
1263 1299 bundling: 2/3 manifests (66.67%)
1264 1300 bundling: 3/3 manifests (100.00%)
1265 1301 bundling: foo/Bar/file.txt 0/3 files (0.00%)
1266 1302 bundling: foo/file.txt 1/3 files (33.33%)
1267 1303 bundling: quux/file.py 2/3 files (66.67%)
1268 1304 changesets: 1 chunks
1269 1305 add changeset ef1ea85a6374
1270 1306 changesets: 2 chunks
1271 1307 add changeset f9cafe1212c8
1272 1308 changesets: 3 chunks
1273 1309 add changeset 911600dab2ae
1274 1310 adding manifests
1275 1311 manifests: 1/3 chunks (33.33%)
1276 1312 manifests: 2/3 chunks (66.67%)
1277 1313 manifests: 3/3 chunks (100.00%)
1278 1314 adding file changes
1279 1315 adding foo/Bar/file.txt revisions
1280 1316 files: 1/3 chunks (33.33%)
1281 1317 adding foo/file.txt revisions
1282 1318 files: 2/3 chunks (66.67%)
1283 1319 adding quux/file.py revisions
1284 1320 files: 3/3 chunks (100.00%)
1285 1321 added 3 changesets with 3 changes to 3 files
1286 1322 calling hook pretxnchangegroup.acl: hgext.acl.hook
1287 1323 acl: acl.allow.branches not enabled
1288 1324 acl: acl.deny.branches not enabled
1289 1325 acl: "group1" not defined in [acl.groups]
1290 1326 acl: acl.allow enabled, 1 entries for user fred
1291 1327 acl: "group1" not defined in [acl.groups]
1292 1328 acl: acl.deny enabled, 1 entries for user fred
1293 1329 acl: branch access granted: "ef1ea85a6374" on branch "default"
1294 1330 acl: allowing changeset ef1ea85a6374
1295 1331 acl: branch access granted: "f9cafe1212c8" on branch "default"
1296 1332 acl: user fred denied on foo/Bar/file.txt
1297 1333 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset f9cafe1212c8
1298 1334 transaction abort!
1299 1335 rollback completed
1300 1336 abort: acl: access denied for changeset f9cafe1212c8
1301 1337 no rollback information available
1302 1338 0:6675d58eff77
1303 1339
1304 1340
1305 1341 Invalid group
1306 1342
1307 1343 Disable the fakegroups trick to get real failures
1308 1344
1309 1345 $ grep -v fakegroups $config > config.tmp
1310 1346 $ mv config.tmp $config
1311 1347 $ echo '[acl.allow]' >> $config
1312 1348 $ echo "** = @unlikelytoexist" >> $config
1313 1349 $ do_push fred 2>&1 | grep unlikelytoexist
1314 1350 ** = @unlikelytoexist
1315 1351 acl: "unlikelytoexist" not defined in [acl.groups]
1316 1352 error: pretxnchangegroup.acl hook failed: group 'unlikelytoexist' is undefined
1317 1353 abort: group 'unlikelytoexist' is undefined
1318 1354
1319 1355
1320 1356 Branch acl tests setup
1321 1357
1322 1358 $ init_config
1323 1359 $ cd b
1324 1360 $ hg up
1325 1361 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1326 1362 $ hg branch foobar
1327 1363 marked working directory as branch foobar
1328 1364 $ hg commit -m 'create foobar'
1329 1365 $ echo 'foo contents' > abc.txt
1330 1366 $ hg add abc.txt
1331 1367 $ hg commit -m 'foobar contents'
1332 1368 $ cd ..
1333 1369 $ hg --cwd a pull ../b
1334 1370 pulling from ../b
1335 1371 searching for changes
1336 1372 adding changesets
1337 1373 adding manifests
1338 1374 adding file changes
1339 1375 added 2 changesets with 1 changes to 1 files (+1 heads)
1340 1376 (run 'hg heads' to see heads)
1341 1377
1342 1378 Create additional changeset on foobar branch
1343 1379
1344 1380 $ cd a
1345 1381 $ hg up -C foobar
1346 1382 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
1347 1383 $ echo 'foo contents2' > abc.txt
1348 1384 $ hg commit -m 'foobar contents2'
1349 1385 $ cd ..
1350 1386
1351 1387
1352 1388 No branch acls specified
1353 1389
1354 1390 $ do_push astro
1355 1391 Pushing as user astro
1356 1392 hgrc = """
1357 1393 [acl]
1358 1394 sources = push
1359 1395 [extensions]
1360 1396 """
1361 1397 pushing to ../b
1398 query 1; heads
1362 1399 searching for changes
1400 all remote heads known locally
1363 1401 4 changesets found
1364 1402 list of changesets:
1365 1403 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1366 1404 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1367 1405 911600dab2ae7a9baff75958b84fe606851ce955
1368 1406 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1369 1407 adding changesets
1370 1408 bundling: 1 changesets
1371 1409 bundling: 2 changesets
1372 1410 bundling: 3 changesets
1373 1411 bundling: 4 changesets
1374 1412 bundling: 1/4 manifests (25.00%)
1375 1413 bundling: 2/4 manifests (50.00%)
1376 1414 bundling: 3/4 manifests (75.00%)
1377 1415 bundling: 4/4 manifests (100.00%)
1378 1416 bundling: abc.txt 0/4 files (0.00%)
1379 1417 bundling: foo/Bar/file.txt 1/4 files (25.00%)
1380 1418 bundling: foo/file.txt 2/4 files (50.00%)
1381 1419 bundling: quux/file.py 3/4 files (75.00%)
1382 1420 changesets: 1 chunks
1383 1421 add changeset ef1ea85a6374
1384 1422 changesets: 2 chunks
1385 1423 add changeset f9cafe1212c8
1386 1424 changesets: 3 chunks
1387 1425 add changeset 911600dab2ae
1388 1426 changesets: 4 chunks
1389 1427 add changeset e8fc755d4d82
1390 1428 adding manifests
1391 1429 manifests: 1/4 chunks (25.00%)
1392 1430 manifests: 2/4 chunks (50.00%)
1393 1431 manifests: 3/4 chunks (75.00%)
1394 1432 manifests: 4/4 chunks (100.00%)
1395 1433 adding file changes
1396 1434 adding abc.txt revisions
1397 1435 files: 1/4 chunks (25.00%)
1398 1436 adding foo/Bar/file.txt revisions
1399 1437 files: 2/4 chunks (50.00%)
1400 1438 adding foo/file.txt revisions
1401 1439 files: 3/4 chunks (75.00%)
1402 1440 adding quux/file.py revisions
1403 1441 files: 4/4 chunks (100.00%)
1404 1442 added 4 changesets with 4 changes to 4 files (+1 heads)
1405 1443 calling hook pretxnchangegroup.acl: hgext.acl.hook
1406 1444 acl: acl.allow.branches not enabled
1407 1445 acl: acl.deny.branches not enabled
1408 1446 acl: acl.allow not enabled
1409 1447 acl: acl.deny not enabled
1410 1448 acl: branch access granted: "ef1ea85a6374" on branch "default"
1411 1449 acl: allowing changeset ef1ea85a6374
1412 1450 acl: branch access granted: "f9cafe1212c8" on branch "default"
1413 1451 acl: allowing changeset f9cafe1212c8
1414 1452 acl: branch access granted: "911600dab2ae" on branch "default"
1415 1453 acl: allowing changeset 911600dab2ae
1416 1454 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
1417 1455 acl: allowing changeset e8fc755d4d82
1418 1456 updating the branch cache
1419 1457 checking for updated bookmarks
1420 1458 repository tip rolled back to revision 2 (undo push)
1421 1459 working directory now based on revision 2
1422 1460 2:fb35475503ef
1423 1461
1424 1462
1425 1463 Branch acl deny test
1426 1464
1427 1465 $ echo "[acl.deny.branches]" >> $config
1428 1466 $ echo "foobar = *" >> $config
1429 1467 $ do_push astro
1430 1468 Pushing as user astro
1431 1469 hgrc = """
1432 1470 [acl]
1433 1471 sources = push
1434 1472 [extensions]
1435 1473 [acl.deny.branches]
1436 1474 foobar = *
1437 1475 """
1438 1476 pushing to ../b
1477 query 1; heads
1439 1478 searching for changes
1479 all remote heads known locally
1440 1480 invalidating branch cache (tip differs)
1441 1481 4 changesets found
1442 1482 list of changesets:
1443 1483 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1444 1484 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1445 1485 911600dab2ae7a9baff75958b84fe606851ce955
1446 1486 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1447 1487 adding changesets
1448 1488 bundling: 1 changesets
1449 1489 bundling: 2 changesets
1450 1490 bundling: 3 changesets
1451 1491 bundling: 4 changesets
1452 1492 bundling: 1/4 manifests (25.00%)
1453 1493 bundling: 2/4 manifests (50.00%)
1454 1494 bundling: 3/4 manifests (75.00%)
1455 1495 bundling: 4/4 manifests (100.00%)
1456 1496 bundling: abc.txt 0/4 files (0.00%)
1457 1497 bundling: foo/Bar/file.txt 1/4 files (25.00%)
1458 1498 bundling: foo/file.txt 2/4 files (50.00%)
1459 1499 bundling: quux/file.py 3/4 files (75.00%)
1460 1500 changesets: 1 chunks
1461 1501 add changeset ef1ea85a6374
1462 1502 changesets: 2 chunks
1463 1503 add changeset f9cafe1212c8
1464 1504 changesets: 3 chunks
1465 1505 add changeset 911600dab2ae
1466 1506 changesets: 4 chunks
1467 1507 add changeset e8fc755d4d82
1468 1508 adding manifests
1469 1509 manifests: 1/4 chunks (25.00%)
1470 1510 manifests: 2/4 chunks (50.00%)
1471 1511 manifests: 3/4 chunks (75.00%)
1472 1512 manifests: 4/4 chunks (100.00%)
1473 1513 adding file changes
1474 1514 adding abc.txt revisions
1475 1515 files: 1/4 chunks (25.00%)
1476 1516 adding foo/Bar/file.txt revisions
1477 1517 files: 2/4 chunks (50.00%)
1478 1518 adding foo/file.txt revisions
1479 1519 files: 3/4 chunks (75.00%)
1480 1520 adding quux/file.py revisions
1481 1521 files: 4/4 chunks (100.00%)
1482 1522 added 4 changesets with 4 changes to 4 files (+1 heads)
1483 1523 calling hook pretxnchangegroup.acl: hgext.acl.hook
1484 1524 acl: acl.allow.branches not enabled
1485 1525 acl: acl.deny.branches enabled, 1 entries for user astro
1486 1526 acl: acl.allow not enabled
1487 1527 acl: acl.deny not enabled
1488 1528 acl: branch access granted: "ef1ea85a6374" on branch "default"
1489 1529 acl: allowing changeset ef1ea85a6374
1490 1530 acl: branch access granted: "f9cafe1212c8" on branch "default"
1491 1531 acl: allowing changeset f9cafe1212c8
1492 1532 acl: branch access granted: "911600dab2ae" on branch "default"
1493 1533 acl: allowing changeset 911600dab2ae
1494 1534 error: pretxnchangegroup.acl hook failed: acl: user "astro" denied on branch "foobar" (changeset "e8fc755d4d82")
1495 1535 transaction abort!
1496 1536 rollback completed
1497 1537 abort: acl: user "astro" denied on branch "foobar" (changeset "e8fc755d4d82")
1498 1538 no rollback information available
1499 1539 2:fb35475503ef
1500 1540
1501 1541
1502 1542 Branch acl empty allow test
1503 1543
1504 1544 $ init_config
1505 1545 $ echo "[acl.allow.branches]" >> $config
1506 1546 $ do_push astro
1507 1547 Pushing as user astro
1508 1548 hgrc = """
1509 1549 [acl]
1510 1550 sources = push
1511 1551 [extensions]
1512 1552 [acl.allow.branches]
1513 1553 """
1514 1554 pushing to ../b
1555 query 1; heads
1515 1556 searching for changes
1557 all remote heads known locally
1516 1558 4 changesets found
1517 1559 list of changesets:
1518 1560 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1519 1561 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1520 1562 911600dab2ae7a9baff75958b84fe606851ce955
1521 1563 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1522 1564 adding changesets
1523 1565 bundling: 1 changesets
1524 1566 bundling: 2 changesets
1525 1567 bundling: 3 changesets
1526 1568 bundling: 4 changesets
1527 1569 bundling: 1/4 manifests (25.00%)
1528 1570 bundling: 2/4 manifests (50.00%)
1529 1571 bundling: 3/4 manifests (75.00%)
1530 1572 bundling: 4/4 manifests (100.00%)
1531 1573 bundling: abc.txt 0/4 files (0.00%)
1532 1574 bundling: foo/Bar/file.txt 1/4 files (25.00%)
1533 1575 bundling: foo/file.txt 2/4 files (50.00%)
1534 1576 bundling: quux/file.py 3/4 files (75.00%)
1535 1577 changesets: 1 chunks
1536 1578 add changeset ef1ea85a6374
1537 1579 changesets: 2 chunks
1538 1580 add changeset f9cafe1212c8
1539 1581 changesets: 3 chunks
1540 1582 add changeset 911600dab2ae
1541 1583 changesets: 4 chunks
1542 1584 add changeset e8fc755d4d82
1543 1585 adding manifests
1544 1586 manifests: 1/4 chunks (25.00%)
1545 1587 manifests: 2/4 chunks (50.00%)
1546 1588 manifests: 3/4 chunks (75.00%)
1547 1589 manifests: 4/4 chunks (100.00%)
1548 1590 adding file changes
1549 1591 adding abc.txt revisions
1550 1592 files: 1/4 chunks (25.00%)
1551 1593 adding foo/Bar/file.txt revisions
1552 1594 files: 2/4 chunks (50.00%)
1553 1595 adding foo/file.txt revisions
1554 1596 files: 3/4 chunks (75.00%)
1555 1597 adding quux/file.py revisions
1556 1598 files: 4/4 chunks (100.00%)
1557 1599 added 4 changesets with 4 changes to 4 files (+1 heads)
1558 1600 calling hook pretxnchangegroup.acl: hgext.acl.hook
1559 1601 acl: acl.allow.branches enabled, 0 entries for user astro
1560 1602 acl: acl.deny.branches not enabled
1561 1603 acl: acl.allow not enabled
1562 1604 acl: acl.deny not enabled
1563 1605 error: pretxnchangegroup.acl hook failed: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
1564 1606 transaction abort!
1565 1607 rollback completed
1566 1608 abort: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
1567 1609 no rollback information available
1568 1610 2:fb35475503ef
1569 1611
1570 1612
1571 1613 Branch acl allow other
1572 1614
1573 1615 $ init_config
1574 1616 $ echo "[acl.allow.branches]" >> $config
1575 1617 $ echo "* = george" >> $config
1576 1618 $ do_push astro
1577 1619 Pushing as user astro
1578 1620 hgrc = """
1579 1621 [acl]
1580 1622 sources = push
1581 1623 [extensions]
1582 1624 [acl.allow.branches]
1583 1625 * = george
1584 1626 """
1585 1627 pushing to ../b
1628 query 1; heads
1586 1629 searching for changes
1630 all remote heads known locally
1587 1631 4 changesets found
1588 1632 list of changesets:
1589 1633 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1590 1634 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1591 1635 911600dab2ae7a9baff75958b84fe606851ce955
1592 1636 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1593 1637 adding changesets
1594 1638 bundling: 1 changesets
1595 1639 bundling: 2 changesets
1596 1640 bundling: 3 changesets
1597 1641 bundling: 4 changesets
1598 1642 bundling: 1/4 manifests (25.00%)
1599 1643 bundling: 2/4 manifests (50.00%)
1600 1644 bundling: 3/4 manifests (75.00%)
1601 1645 bundling: 4/4 manifests (100.00%)
1602 1646 bundling: abc.txt 0/4 files (0.00%)
1603 1647 bundling: foo/Bar/file.txt 1/4 files (25.00%)
1604 1648 bundling: foo/file.txt 2/4 files (50.00%)
1605 1649 bundling: quux/file.py 3/4 files (75.00%)
1606 1650 changesets: 1 chunks
1607 1651 add changeset ef1ea85a6374
1608 1652 changesets: 2 chunks
1609 1653 add changeset f9cafe1212c8
1610 1654 changesets: 3 chunks
1611 1655 add changeset 911600dab2ae
1612 1656 changesets: 4 chunks
1613 1657 add changeset e8fc755d4d82
1614 1658 adding manifests
1615 1659 manifests: 1/4 chunks (25.00%)
1616 1660 manifests: 2/4 chunks (50.00%)
1617 1661 manifests: 3/4 chunks (75.00%)
1618 1662 manifests: 4/4 chunks (100.00%)
1619 1663 adding file changes
1620 1664 adding abc.txt revisions
1621 1665 files: 1/4 chunks (25.00%)
1622 1666 adding foo/Bar/file.txt revisions
1623 1667 files: 2/4 chunks (50.00%)
1624 1668 adding foo/file.txt revisions
1625 1669 files: 3/4 chunks (75.00%)
1626 1670 adding quux/file.py revisions
1627 1671 files: 4/4 chunks (100.00%)
1628 1672 added 4 changesets with 4 changes to 4 files (+1 heads)
1629 1673 calling hook pretxnchangegroup.acl: hgext.acl.hook
1630 1674 acl: acl.allow.branches enabled, 0 entries for user astro
1631 1675 acl: acl.deny.branches not enabled
1632 1676 acl: acl.allow not enabled
1633 1677 acl: acl.deny not enabled
1634 1678 error: pretxnchangegroup.acl hook failed: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
1635 1679 transaction abort!
1636 1680 rollback completed
1637 1681 abort: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
1638 1682 no rollback information available
1639 1683 2:fb35475503ef
1640 1684
1641 1685 $ do_push george
1642 1686 Pushing as user george
1643 1687 hgrc = """
1644 1688 [acl]
1645 1689 sources = push
1646 1690 [extensions]
1647 1691 [acl.allow.branches]
1648 1692 * = george
1649 1693 """
1650 1694 pushing to ../b
1695 query 1; heads
1651 1696 searching for changes
1697 all remote heads known locally
1652 1698 4 changesets found
1653 1699 list of changesets:
1654 1700 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1655 1701 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1656 1702 911600dab2ae7a9baff75958b84fe606851ce955
1657 1703 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1658 1704 adding changesets
1659 1705 bundling: 1 changesets
1660 1706 bundling: 2 changesets
1661 1707 bundling: 3 changesets
1662 1708 bundling: 4 changesets
1663 1709 bundling: 1/4 manifests (25.00%)
1664 1710 bundling: 2/4 manifests (50.00%)
1665 1711 bundling: 3/4 manifests (75.00%)
1666 1712 bundling: 4/4 manifests (100.00%)
1667 1713 bundling: abc.txt 0/4 files (0.00%)
1668 1714 bundling: foo/Bar/file.txt 1/4 files (25.00%)
1669 1715 bundling: foo/file.txt 2/4 files (50.00%)
1670 1716 bundling: quux/file.py 3/4 files (75.00%)
1671 1717 changesets: 1 chunks
1672 1718 add changeset ef1ea85a6374
1673 1719 changesets: 2 chunks
1674 1720 add changeset f9cafe1212c8
1675 1721 changesets: 3 chunks
1676 1722 add changeset 911600dab2ae
1677 1723 changesets: 4 chunks
1678 1724 add changeset e8fc755d4d82
1679 1725 adding manifests
1680 1726 manifests: 1/4 chunks (25.00%)
1681 1727 manifests: 2/4 chunks (50.00%)
1682 1728 manifests: 3/4 chunks (75.00%)
1683 1729 manifests: 4/4 chunks (100.00%)
1684 1730 adding file changes
1685 1731 adding abc.txt revisions
1686 1732 files: 1/4 chunks (25.00%)
1687 1733 adding foo/Bar/file.txt revisions
1688 1734 files: 2/4 chunks (50.00%)
1689 1735 adding foo/file.txt revisions
1690 1736 files: 3/4 chunks (75.00%)
1691 1737 adding quux/file.py revisions
1692 1738 files: 4/4 chunks (100.00%)
1693 1739 added 4 changesets with 4 changes to 4 files (+1 heads)
1694 1740 calling hook pretxnchangegroup.acl: hgext.acl.hook
1695 1741 acl: acl.allow.branches enabled, 1 entries for user george
1696 1742 acl: acl.deny.branches not enabled
1697 1743 acl: acl.allow not enabled
1698 1744 acl: acl.deny not enabled
1699 1745 acl: branch access granted: "ef1ea85a6374" on branch "default"
1700 1746 acl: allowing changeset ef1ea85a6374
1701 1747 acl: branch access granted: "f9cafe1212c8" on branch "default"
1702 1748 acl: allowing changeset f9cafe1212c8
1703 1749 acl: branch access granted: "911600dab2ae" on branch "default"
1704 1750 acl: allowing changeset 911600dab2ae
1705 1751 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
1706 1752 acl: allowing changeset e8fc755d4d82
1707 1753 updating the branch cache
1708 1754 checking for updated bookmarks
1709 1755 repository tip rolled back to revision 2 (undo push)
1710 1756 working directory now based on revision 2
1711 1757 2:fb35475503ef
1712 1758
1713 1759
1714 1760 Branch acl conflicting allow
1715 1761 asterisk ends up applying to all branches and allowing george to
1716 1762 push foobar into the remote
1717 1763
1718 1764 $ init_config
1719 1765 $ echo "[acl.allow.branches]" >> $config
1720 1766 $ echo "foobar = astro" >> $config
1721 1767 $ echo "* = george" >> $config
1722 1768 $ do_push george
1723 1769 Pushing as user george
1724 1770 hgrc = """
1725 1771 [acl]
1726 1772 sources = push
1727 1773 [extensions]
1728 1774 [acl.allow.branches]
1729 1775 foobar = astro
1730 1776 * = george
1731 1777 """
1732 1778 pushing to ../b
1779 query 1; heads
1733 1780 searching for changes
1781 all remote heads known locally
1734 1782 invalidating branch cache (tip differs)
1735 1783 4 changesets found
1736 1784 list of changesets:
1737 1785 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1738 1786 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1739 1787 911600dab2ae7a9baff75958b84fe606851ce955
1740 1788 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1741 1789 adding changesets
1742 1790 bundling: 1 changesets
1743 1791 bundling: 2 changesets
1744 1792 bundling: 3 changesets
1745 1793 bundling: 4 changesets
1746 1794 bundling: 1/4 manifests (25.00%)
1747 1795 bundling: 2/4 manifests (50.00%)
1748 1796 bundling: 3/4 manifests (75.00%)
1749 1797 bundling: 4/4 manifests (100.00%)
1750 1798 bundling: abc.txt 0/4 files (0.00%)
1751 1799 bundling: foo/Bar/file.txt 1/4 files (25.00%)
1752 1800 bundling: foo/file.txt 2/4 files (50.00%)
1753 1801 bundling: quux/file.py 3/4 files (75.00%)
1754 1802 changesets: 1 chunks
1755 1803 add changeset ef1ea85a6374
1756 1804 changesets: 2 chunks
1757 1805 add changeset f9cafe1212c8
1758 1806 changesets: 3 chunks
1759 1807 add changeset 911600dab2ae
1760 1808 changesets: 4 chunks
1761 1809 add changeset e8fc755d4d82
1762 1810 adding manifests
1763 1811 manifests: 1/4 chunks (25.00%)
1764 1812 manifests: 2/4 chunks (50.00%)
1765 1813 manifests: 3/4 chunks (75.00%)
1766 1814 manifests: 4/4 chunks (100.00%)
1767 1815 adding file changes
1768 1816 adding abc.txt revisions
1769 1817 files: 1/4 chunks (25.00%)
1770 1818 adding foo/Bar/file.txt revisions
1771 1819 files: 2/4 chunks (50.00%)
1772 1820 adding foo/file.txt revisions
1773 1821 files: 3/4 chunks (75.00%)
1774 1822 adding quux/file.py revisions
1775 1823 files: 4/4 chunks (100.00%)
1776 1824 added 4 changesets with 4 changes to 4 files (+1 heads)
1777 1825 calling hook pretxnchangegroup.acl: hgext.acl.hook
1778 1826 acl: acl.allow.branches enabled, 1 entries for user george
1779 1827 acl: acl.deny.branches not enabled
1780 1828 acl: acl.allow not enabled
1781 1829 acl: acl.deny not enabled
1782 1830 acl: branch access granted: "ef1ea85a6374" on branch "default"
1783 1831 acl: allowing changeset ef1ea85a6374
1784 1832 acl: branch access granted: "f9cafe1212c8" on branch "default"
1785 1833 acl: allowing changeset f9cafe1212c8
1786 1834 acl: branch access granted: "911600dab2ae" on branch "default"
1787 1835 acl: allowing changeset 911600dab2ae
1788 1836 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
1789 1837 acl: allowing changeset e8fc755d4d82
1790 1838 updating the branch cache
1791 1839 checking for updated bookmarks
1792 1840 repository tip rolled back to revision 2 (undo push)
1793 1841 working directory now based on revision 2
1794 1842 2:fb35475503ef
1795 1843
1796 1844 Branch acl conflicting deny
1797 1845
1798 1846 $ init_config
1799 1847 $ echo "[acl.deny.branches]" >> $config
1800 1848 $ echo "foobar = astro" >> $config
1801 1849 $ echo "default = astro" >> $config
1802 1850 $ echo "* = george" >> $config
1803 1851 $ do_push george
1804 1852 Pushing as user george
1805 1853 hgrc = """
1806 1854 [acl]
1807 1855 sources = push
1808 1856 [extensions]
1809 1857 [acl.deny.branches]
1810 1858 foobar = astro
1811 1859 default = astro
1812 1860 * = george
1813 1861 """
1814 1862 pushing to ../b
1863 query 1; heads
1815 1864 searching for changes
1865 all remote heads known locally
1816 1866 invalidating branch cache (tip differs)
1817 1867 4 changesets found
1818 1868 list of changesets:
1819 1869 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1820 1870 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1821 1871 911600dab2ae7a9baff75958b84fe606851ce955
1822 1872 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1823 1873 adding changesets
1824 1874 bundling: 1 changesets
1825 1875 bundling: 2 changesets
1826 1876 bundling: 3 changesets
1827 1877 bundling: 4 changesets
1828 1878 bundling: 1/4 manifests (25.00%)
1829 1879 bundling: 2/4 manifests (50.00%)
1830 1880 bundling: 3/4 manifests (75.00%)
1831 1881 bundling: 4/4 manifests (100.00%)
1832 1882 bundling: abc.txt 0/4 files (0.00%)
1833 1883 bundling: foo/Bar/file.txt 1/4 files (25.00%)
1834 1884 bundling: foo/file.txt 2/4 files (50.00%)
1835 1885 bundling: quux/file.py 3/4 files (75.00%)
1836 1886 changesets: 1 chunks
1837 1887 add changeset ef1ea85a6374
1838 1888 changesets: 2 chunks
1839 1889 add changeset f9cafe1212c8
1840 1890 changesets: 3 chunks
1841 1891 add changeset 911600dab2ae
1842 1892 changesets: 4 chunks
1843 1893 add changeset e8fc755d4d82
1844 1894 adding manifests
1845 1895 manifests: 1/4 chunks (25.00%)
1846 1896 manifests: 2/4 chunks (50.00%)
1847 1897 manifests: 3/4 chunks (75.00%)
1848 1898 manifests: 4/4 chunks (100.00%)
1849 1899 adding file changes
1850 1900 adding abc.txt revisions
1851 1901 files: 1/4 chunks (25.00%)
1852 1902 adding foo/Bar/file.txt revisions
1853 1903 files: 2/4 chunks (50.00%)
1854 1904 adding foo/file.txt revisions
1855 1905 files: 3/4 chunks (75.00%)
1856 1906 adding quux/file.py revisions
1857 1907 files: 4/4 chunks (100.00%)
1858 1908 added 4 changesets with 4 changes to 4 files (+1 heads)
1859 1909 calling hook pretxnchangegroup.acl: hgext.acl.hook
1860 1910 acl: acl.allow.branches not enabled
1861 1911 acl: acl.deny.branches enabled, 1 entries for user george
1862 1912 acl: acl.allow not enabled
1863 1913 acl: acl.deny not enabled
1864 1914 error: pretxnchangegroup.acl hook failed: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
1865 1915 transaction abort!
1866 1916 rollback completed
1867 1917 abort: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
1868 1918 no rollback information available
1869 1919 2:fb35475503ef
1870 1920
@@ -1,195 +1,193 b''
1 1 initialize
2 2
3 3 $ hg init a
4 4 $ cd a
5 5 $ echo 'test' > test
6 6 $ hg commit -Am'test'
7 7 adding test
8 8
9 9 set bookmarks
10 10
11 11 $ hg bookmark X
12 12 $ hg bookmark Y
13 13 $ hg bookmark Z
14 14
15 15 import bookmark by name
16 16
17 17 $ hg init ../b
18 18 $ cd ../b
19 19 $ hg book Y
20 20 $ hg book
21 21 * Y -1:000000000000
22 22 $ hg pull ../a
23 23 pulling from ../a
24 24 requesting all changes
25 25 adding changesets
26 26 adding manifests
27 27 adding file changes
28 28 added 1 changesets with 1 changes to 1 files
29 29 updating bookmark Y
30 30 (run 'hg update' to get a working copy)
31 31 $ hg bookmarks
32 32 Y 0:4e3505fd9583
33 33 $ hg debugpushkey ../a namespaces
34 34 bookmarks
35 35 namespaces
36 36 $ hg debugpushkey ../a bookmarks
37 37 Y 4e3505fd95835d721066b76e75dbb8cc554d7f77
38 38 X 4e3505fd95835d721066b76e75dbb8cc554d7f77
39 39 Z 4e3505fd95835d721066b76e75dbb8cc554d7f77
40 40 $ hg pull -B X ../a
41 41 pulling from ../a
42 searching for changes
43 42 no changes found
44 43 importing bookmark X
45 44 $ hg bookmark
46 45 X 0:4e3505fd9583
47 46 Y 0:4e3505fd9583
48 47
49 48 export bookmark by name
50 49
51 50 $ hg bookmark W
52 51 $ hg bookmark foo
53 52 $ hg bookmark foobar
54 53 $ hg push -B W ../a
55 54 pushing to ../a
56 55 searching for changes
57 56 no changes found
58 57 exporting bookmark W
59 58 $ hg -R ../a bookmarks
60 59 W -1:000000000000
61 60 X 0:4e3505fd9583
62 61 Y 0:4e3505fd9583
63 62 * Z 0:4e3505fd9583
64 63
65 64 delete a remote bookmark
66 65
67 66 $ hg book -d W
68 67 $ hg push -B W ../a
69 68 pushing to ../a
70 69 searching for changes
71 70 no changes found
72 71 deleting remote bookmark W
73 72
74 73 push/pull name that doesn't exist
75 74
76 75 $ hg push -B badname ../a
77 76 pushing to ../a
78 77 searching for changes
79 78 no changes found
80 79 bookmark badname does not exist on the local or remote repository!
81 80 [2]
82 81 $ hg pull -B anotherbadname ../a
83 82 pulling from ../a
84 83 abort: remote bookmark anotherbadname not found!
85 84 [255]
86 85
87 86 divergent bookmarks
88 87
89 88 $ cd ../a
90 89 $ echo c1 > f1
91 90 $ hg ci -Am1
92 91 adding f1
93 92 $ hg book -f X
94 93 $ hg book
95 94 * X 1:0d2164f0ce0d
96 95 Y 0:4e3505fd9583
97 96 Z 1:0d2164f0ce0d
98 97
99 98 $ cd ../b
100 99 $ hg up
101 100 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
102 101 $ echo c2 > f2
103 102 $ hg ci -Am2
104 103 adding f2
105 104 $ hg book -f X
106 105 $ hg book
107 106 * X 1:9b140be10808
108 107 Y 0:4e3505fd9583
109 108 foo -1:000000000000
110 109 foobar -1:000000000000
111 110
112 111 $ hg pull ../a
113 112 pulling from ../a
114 113 searching for changes
115 114 adding changesets
116 115 adding manifests
117 116 adding file changes
118 117 added 1 changesets with 1 changes to 1 files (+1 heads)
119 118 not updating divergent bookmark X
120 119 (run 'hg heads' to see heads, 'hg merge' to merge)
121 120 $ hg book
122 121 * X 1:9b140be10808
123 122 Y 0:4e3505fd9583
124 123 foo -1:000000000000
125 124 foobar -1:000000000000
126 125 $ hg push -f ../a
127 126 pushing to ../a
128 127 searching for changes
129 128 adding changesets
130 129 adding manifests
131 130 adding file changes
132 131 added 1 changesets with 1 changes to 1 files (+1 heads)
133 132 $ hg -R ../a book
134 133 * X 1:0d2164f0ce0d
135 134 Y 0:4e3505fd9583
136 135 Z 1:0d2164f0ce0d
137 136
138 137 hgweb
139 138
140 139 $ cat <<EOF > .hg/hgrc
141 140 > [web]
142 141 > push_ssl = false
143 142 > allow_push = *
144 143 > EOF
145 144
146 145 $ hg serve -p $HGPORT -d --pid-file=../hg.pid -E errors.log
147 146 $ cat ../hg.pid >> $DAEMON_PIDS
148 147 $ cd ../a
149 148
150 149 $ hg debugpushkey http://localhost:$HGPORT/ namespaces
151 150 bookmarks
152 151 namespaces
153 152 $ hg debugpushkey http://localhost:$HGPORT/ bookmarks
154 153 Y 4e3505fd95835d721066b76e75dbb8cc554d7f77
155 154 X 9b140be1080824d768c5a4691a564088eede71f9
156 155 foo 0000000000000000000000000000000000000000
157 156 foobar 0000000000000000000000000000000000000000
158 157 $ hg out -B http://localhost:$HGPORT/
159 158 comparing with http://localhost:$HGPORT/
160 159 searching for changed bookmarks
161 160 Z 0d2164f0ce0d
162 161 $ hg push -B Z http://localhost:$HGPORT/
163 162 pushing to http://localhost:$HGPORT/
164 163 searching for changes
165 164 no changes found
166 165 exporting bookmark Z
167 166 $ hg book -d Z
168 167 $ hg in -B http://localhost:$HGPORT/
169 168 comparing with http://localhost:$HGPORT/
170 169 searching for changed bookmarks
171 170 Z 0d2164f0ce0d
172 171 foo 000000000000
173 172 foobar 000000000000
174 173 $ hg pull -B Z http://localhost:$HGPORT/
175 174 pulling from http://localhost:$HGPORT/
176 searching for changes
177 175 no changes found
178 176 not updating divergent bookmark X
179 177 importing bookmark Z
180 178 $ hg clone http://localhost:$HGPORT/ cloned-bookmarks
181 179 requesting all changes
182 180 adding changesets
183 181 adding manifests
184 182 adding file changes
185 183 added 3 changesets with 3 changes to 3 files (+1 heads)
186 184 updating to branch default
187 185 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
188 186 $ hg -R cloned-bookmarks bookmarks
189 187 X 1:9b140be10808
190 188 Y 0:4e3505fd9583
191 189 Z 2:0d2164f0ce0d
192 190 foo -1:000000000000
193 191 foobar -1:000000000000
194 192
195 193 $ kill `cat ../hg.pid`
@@ -1,575 +1,577 b''
1 1 Setting up test
2 2
3 3 $ hg init test
4 4 $ cd test
5 5 $ echo 0 > afile
6 6 $ hg add afile
7 7 $ hg commit -m "0.0"
8 8 $ echo 1 >> afile
9 9 $ hg commit -m "0.1"
10 10 $ echo 2 >> afile
11 11 $ hg commit -m "0.2"
12 12 $ echo 3 >> afile
13 13 $ hg commit -m "0.3"
14 14 $ hg update -C 0
15 15 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
16 16 $ echo 1 >> afile
17 17 $ hg commit -m "1.1"
18 18 created new head
19 19 $ echo 2 >> afile
20 20 $ hg commit -m "1.2"
21 21 $ echo "a line" > fred
22 22 $ echo 3 >> afile
23 23 $ hg add fred
24 24 $ hg commit -m "1.3"
25 25 $ hg mv afile adifferentfile
26 26 $ hg commit -m "1.3m"
27 27 $ hg update -C 3
28 28 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
29 29 $ hg mv afile anotherfile
30 30 $ hg commit -m "0.3m"
31 31 $ hg verify
32 32 checking changesets
33 33 checking manifests
34 34 crosschecking files in changesets and manifests
35 35 checking files
36 36 4 files, 9 changesets, 7 total revisions
37 37 $ cd ..
38 38 $ hg init empty
39 39
40 40 Bundle --all
41 41
42 42 $ hg -R test bundle --all all.hg
43 43 9 changesets found
44 44
45 45 Bundle test to full.hg
46 46
47 47 $ hg -R test bundle full.hg empty
48 48 searching for changes
49 49 9 changesets found
50 50
51 51 Unbundle full.hg in test
52 52
53 53 $ hg -R test unbundle full.hg
54 54 adding changesets
55 55 adding manifests
56 56 adding file changes
57 57 added 0 changesets with 0 changes to 4 files
58 58 (run 'hg update' to get a working copy)
59 59
60 60 Verify empty
61 61
62 62 $ hg -R empty heads
63 63 [1]
64 64 $ hg -R empty verify
65 65 checking changesets
66 66 checking manifests
67 67 crosschecking files in changesets and manifests
68 68 checking files
69 69 0 files, 0 changesets, 0 total revisions
70 70
71 71 Pull full.hg into test (using --cwd)
72 72
73 73 $ hg --cwd test pull ../full.hg
74 74 pulling from ../full.hg
75 75 searching for changes
76 76 no changes found
77 77
78 78 Pull full.hg into empty (using --cwd)
79 79
80 80 $ hg --cwd empty pull ../full.hg
81 81 pulling from ../full.hg
82 82 requesting all changes
83 83 adding changesets
84 84 adding manifests
85 85 adding file changes
86 86 added 9 changesets with 7 changes to 4 files (+1 heads)
87 87 (run 'hg heads' to see heads, 'hg merge' to merge)
88 88
89 89 Rollback empty
90 90
91 91 $ hg -R empty rollback
92 92 repository tip rolled back to revision -1 (undo pull)
93 93 working directory now based on revision -1
94 94
95 95 Pull full.hg into empty again (using --cwd)
96 96
97 97 $ hg --cwd empty pull ../full.hg
98 98 pulling from ../full.hg
99 99 requesting all changes
100 100 adding changesets
101 101 adding manifests
102 102 adding file changes
103 103 added 9 changesets with 7 changes to 4 files (+1 heads)
104 104 (run 'hg heads' to see heads, 'hg merge' to merge)
105 105
106 106 Pull full.hg into test (using -R)
107 107
108 108 $ hg -R test pull full.hg
109 109 pulling from full.hg
110 110 searching for changes
111 111 no changes found
112 112
113 113 Pull full.hg into empty (using -R)
114 114
115 115 $ hg -R empty pull full.hg
116 116 pulling from full.hg
117 117 searching for changes
118 118 no changes found
119 119
120 120 Rollback empty
121 121
122 122 $ hg -R empty rollback
123 123 repository tip rolled back to revision -1 (undo pull)
124 124 working directory now based on revision -1
125 125
126 126 Pull full.hg into empty again (using -R)
127 127
128 128 $ hg -R empty pull full.hg
129 129 pulling from full.hg
130 130 requesting all changes
131 131 adding changesets
132 132 adding manifests
133 133 adding file changes
134 134 added 9 changesets with 7 changes to 4 files (+1 heads)
135 135 (run 'hg heads' to see heads, 'hg merge' to merge)
136 136
137 137 Log -R full.hg in fresh empty
138 138
139 139 $ rm -r empty
140 140 $ hg init empty
141 141 $ cd empty
142 142 $ hg -R bundle://../full.hg log
143 143 changeset: 8:aa35859c02ea
144 144 tag: tip
145 145 parent: 3:eebf5a27f8ca
146 146 user: test
147 147 date: Thu Jan 01 00:00:00 1970 +0000
148 148 summary: 0.3m
149 149
150 150 changeset: 7:a6a34bfa0076
151 151 user: test
152 152 date: Thu Jan 01 00:00:00 1970 +0000
153 153 summary: 1.3m
154 154
155 155 changeset: 6:7373c1169842
156 156 user: test
157 157 date: Thu Jan 01 00:00:00 1970 +0000
158 158 summary: 1.3
159 159
160 160 changeset: 5:1bb50a9436a7
161 161 user: test
162 162 date: Thu Jan 01 00:00:00 1970 +0000
163 163 summary: 1.2
164 164
165 165 changeset: 4:095197eb4973
166 166 parent: 0:f9ee2f85a263
167 167 user: test
168 168 date: Thu Jan 01 00:00:00 1970 +0000
169 169 summary: 1.1
170 170
171 171 changeset: 3:eebf5a27f8ca
172 172 user: test
173 173 date: Thu Jan 01 00:00:00 1970 +0000
174 174 summary: 0.3
175 175
176 176 changeset: 2:e38ba6f5b7e0
177 177 user: test
178 178 date: Thu Jan 01 00:00:00 1970 +0000
179 179 summary: 0.2
180 180
181 181 changeset: 1:34c2bf6b0626
182 182 user: test
183 183 date: Thu Jan 01 00:00:00 1970 +0000
184 184 summary: 0.1
185 185
186 186 changeset: 0:f9ee2f85a263
187 187 user: test
188 188 date: Thu Jan 01 00:00:00 1970 +0000
189 189 summary: 0.0
190 190
191 191 Make sure bundlerepo doesn't leak tempfiles (issue2491)
192 192
193 193 $ ls .hg
194 194 00changelog.i
195 195 cache
196 196 requires
197 197 store
198 198
199 199 Pull ../full.hg into empty (with hook)
200 200
201 201 $ echo '[hooks]' >> .hg/hgrc
202 202 $ echo 'changegroup = python "$TESTDIR"/printenv.py changegroup' >> .hg/hgrc
203 203
204 204 doesn't work (yet ?)
205 205
206 206 hg -R bundle://../full.hg verify
207 207
208 208 $ hg pull bundle://../full.hg
209 209 pulling from bundle:../full.hg
210 210 requesting all changes
211 211 adding changesets
212 212 adding manifests
213 213 adding file changes
214 214 added 9 changesets with 7 changes to 4 files (+1 heads)
215 215 changegroup hook: HG_NODE=f9ee2f85a263049e9ae6d37a0e67e96194ffb735 HG_SOURCE=pull HG_URL=bundle:../full.hg
216 216 (run 'hg heads' to see heads, 'hg merge' to merge)
217 217
218 218 Rollback empty
219 219
220 220 $ hg rollback
221 221 repository tip rolled back to revision -1 (undo pull)
222 222 working directory now based on revision -1
223 223 $ cd ..
224 224
225 225 Log -R bundle:empty+full.hg
226 226
227 227 $ hg -R bundle:empty+full.hg log --template="{rev} "; echo ""
228 228 8 7 6 5 4 3 2 1 0
229 229
230 230 Pull full.hg into empty again (using -R; with hook)
231 231
232 232 $ hg -R empty pull full.hg
233 233 pulling from full.hg
234 234 requesting all changes
235 235 adding changesets
236 236 adding manifests
237 237 adding file changes
238 238 added 9 changesets with 7 changes to 4 files (+1 heads)
239 239 changegroup hook: HG_NODE=f9ee2f85a263049e9ae6d37a0e67e96194ffb735 HG_SOURCE=pull HG_URL=bundle:empty+full.hg
240 240 (run 'hg heads' to see heads, 'hg merge' to merge)
241 241
242 242 Create partial clones
243 243
244 244 $ rm -r empty
245 245 $ hg init empty
246 246 $ hg clone -r 3 test partial
247 247 adding changesets
248 248 adding manifests
249 249 adding file changes
250 250 added 4 changesets with 4 changes to 1 files
251 251 updating to branch default
252 252 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
253 253 $ hg clone partial partial2
254 254 updating to branch default
255 255 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
256 256 $ cd partial
257 257
258 258 Log -R full.hg in partial
259 259
260 260 $ hg -R bundle://../full.hg log
261 261 changeset: 8:aa35859c02ea
262 262 tag: tip
263 263 parent: 3:eebf5a27f8ca
264 264 user: test
265 265 date: Thu Jan 01 00:00:00 1970 +0000
266 266 summary: 0.3m
267 267
268 268 changeset: 7:a6a34bfa0076
269 269 user: test
270 270 date: Thu Jan 01 00:00:00 1970 +0000
271 271 summary: 1.3m
272 272
273 273 changeset: 6:7373c1169842
274 274 user: test
275 275 date: Thu Jan 01 00:00:00 1970 +0000
276 276 summary: 1.3
277 277
278 278 changeset: 5:1bb50a9436a7
279 279 user: test
280 280 date: Thu Jan 01 00:00:00 1970 +0000
281 281 summary: 1.2
282 282
283 283 changeset: 4:095197eb4973
284 284 parent: 0:f9ee2f85a263
285 285 user: test
286 286 date: Thu Jan 01 00:00:00 1970 +0000
287 287 summary: 1.1
288 288
289 289 changeset: 3:eebf5a27f8ca
290 290 user: test
291 291 date: Thu Jan 01 00:00:00 1970 +0000
292 292 summary: 0.3
293 293
294 294 changeset: 2:e38ba6f5b7e0
295 295 user: test
296 296 date: Thu Jan 01 00:00:00 1970 +0000
297 297 summary: 0.2
298 298
299 299 changeset: 1:34c2bf6b0626
300 300 user: test
301 301 date: Thu Jan 01 00:00:00 1970 +0000
302 302 summary: 0.1
303 303
304 304 changeset: 0:f9ee2f85a263
305 305 user: test
306 306 date: Thu Jan 01 00:00:00 1970 +0000
307 307 summary: 0.0
308 308
309 309
310 310 Incoming full.hg in partial
311 311
312 312 $ hg incoming bundle://../full.hg
313 313 comparing with bundle:../full.hg
314 314 searching for changes
315 315 changeset: 4:095197eb4973
316 316 parent: 0:f9ee2f85a263
317 317 user: test
318 318 date: Thu Jan 01 00:00:00 1970 +0000
319 319 summary: 1.1
320 320
321 321 changeset: 5:1bb50a9436a7
322 322 user: test
323 323 date: Thu Jan 01 00:00:00 1970 +0000
324 324 summary: 1.2
325 325
326 326 changeset: 6:7373c1169842
327 327 user: test
328 328 date: Thu Jan 01 00:00:00 1970 +0000
329 329 summary: 1.3
330 330
331 331 changeset: 7:a6a34bfa0076
332 332 user: test
333 333 date: Thu Jan 01 00:00:00 1970 +0000
334 334 summary: 1.3m
335 335
336 336 changeset: 8:aa35859c02ea
337 337 tag: tip
338 338 parent: 3:eebf5a27f8ca
339 339 user: test
340 340 date: Thu Jan 01 00:00:00 1970 +0000
341 341 summary: 0.3m
342 342
343 343
344 344 Outgoing -R full.hg vs partial2 in partial
345 345
346 346 $ hg -R bundle://../full.hg outgoing ../partial2
347 347 comparing with ../partial2
348 348 searching for changes
349 349 changeset: 4:095197eb4973
350 350 parent: 0:f9ee2f85a263
351 351 user: test
352 352 date: Thu Jan 01 00:00:00 1970 +0000
353 353 summary: 1.1
354 354
355 355 changeset: 5:1bb50a9436a7
356 356 user: test
357 357 date: Thu Jan 01 00:00:00 1970 +0000
358 358 summary: 1.2
359 359
360 360 changeset: 6:7373c1169842
361 361 user: test
362 362 date: Thu Jan 01 00:00:00 1970 +0000
363 363 summary: 1.3
364 364
365 365 changeset: 7:a6a34bfa0076
366 366 user: test
367 367 date: Thu Jan 01 00:00:00 1970 +0000
368 368 summary: 1.3m
369 369
370 370 changeset: 8:aa35859c02ea
371 371 tag: tip
372 372 parent: 3:eebf5a27f8ca
373 373 user: test
374 374 date: Thu Jan 01 00:00:00 1970 +0000
375 375 summary: 0.3m
376 376
377 377
378 378 Outgoing -R does-not-exist.hg vs partial2 in partial
379 379
380 380 $ hg -R bundle://../does-not-exist.hg outgoing ../partial2
381 381 abort: No such file or directory: ../does-not-exist.hg
382 382 [255]
383 383 $ cd ..
384 384
385 385 Direct clone from bundle (all-history)
386 386
387 387 $ hg clone full.hg full-clone
388 388 requesting all changes
389 389 adding changesets
390 390 adding manifests
391 391 adding file changes
392 392 added 9 changesets with 7 changes to 4 files (+1 heads)
393 393 updating to branch default
394 394 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
395 395 $ hg -R full-clone heads
396 396 changeset: 8:aa35859c02ea
397 397 tag: tip
398 398 parent: 3:eebf5a27f8ca
399 399 user: test
400 400 date: Thu Jan 01 00:00:00 1970 +0000
401 401 summary: 0.3m
402 402
403 403 changeset: 7:a6a34bfa0076
404 404 user: test
405 405 date: Thu Jan 01 00:00:00 1970 +0000
406 406 summary: 1.3m
407 407
408 408 $ rm -r full-clone
409 409
410 410 When cloning from a non-copiable repository into '', do not
411 411 recurse infinitely (issue 2528)
412 412
413 413 $ hg clone full.hg ''
414 414 abort: No such file or directory
415 415 [255]
416 416
417 417 test for http://mercurial.selenic.com/bts/issue216
418 418
419 419 Unbundle incremental bundles into fresh empty in one go
420 420
421 421 $ rm -r empty
422 422 $ hg init empty
423 423 $ hg -R test bundle --base null -r 0 ../0.hg
424 424 1 changesets found
425 425 $ hg -R test bundle --base 0 -r 1 ../1.hg
426 426 1 changesets found
427 427 $ hg -R empty unbundle -u ../0.hg ../1.hg
428 428 adding changesets
429 429 adding manifests
430 430 adding file changes
431 431 added 1 changesets with 1 changes to 1 files
432 432 adding changesets
433 433 adding manifests
434 434 adding file changes
435 435 added 1 changesets with 1 changes to 1 files
436 436 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
437 437
438 438 test for 540d1059c802
439 439
440 440 test for 540d1059c802
441 441
442 442 $ hg init orig
443 443 $ cd orig
444 444 $ echo foo > foo
445 445 $ hg add foo
446 446 $ hg ci -m 'add foo'
447 447
448 448 $ hg clone . ../copy
449 449 updating to branch default
450 450 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
451 451 $ hg tag foo
452 452
453 453 $ cd ../copy
454 454 $ echo >> foo
455 455 $ hg ci -m 'change foo'
456 456 $ hg bundle ../bundle.hg ../orig
457 457 searching for changes
458 458 1 changesets found
459 459
460 460 $ cd ../orig
461 461 $ hg incoming ../bundle.hg
462 462 comparing with ../bundle.hg
463 463 searching for changes
464 464 changeset: 2:ed1b79f46b9a
465 465 tag: tip
466 466 parent: 0:bbd179dfa0a7
467 467 user: test
468 468 date: Thu Jan 01 00:00:00 1970 +0000
469 469 summary: change foo
470 470
471 471 $ cd ..
472 472
473 473 test bundle with # in the filename (issue2154):
474 474
475 475 $ cp bundle.hg 'test#bundle.hg'
476 476 $ cd orig
477 477 $ hg incoming '../test#bundle.hg'
478 478 comparing with ../test
479 479 abort: unknown revision 'bundle.hg'!
480 480 [255]
481 481
482 482 note that percent encoding is not handled:
483 483
484 484 $ hg incoming ../test%23bundle.hg
485 485 abort: repository ../test%23bundle.hg not found!
486 486 [255]
487 487 $ cd ..
488 488
489 489 test for http://mercurial.selenic.com/bts/issue1144
490 490
491 491 test that verify bundle does not traceback
492 492
493 493 partial history bundle, fails w/ unkown parent
494 494
495 495 $ hg -R bundle.hg verify
496 496 abort: 00changelog.i@bbd179dfa0a7: unknown parent!
497 497 [255]
498 498
499 499 full history bundle, refuses to verify non-local repo
500 500
501 501 $ hg -R all.hg verify
502 502 abort: cannot verify bundle or remote repos
503 503 [255]
504 504
505 505 but, regular verify must continue to work
506 506
507 507 $ hg -R orig verify
508 508 checking changesets
509 509 checking manifests
510 510 crosschecking files in changesets and manifests
511 511 checking files
512 512 2 files, 2 changesets, 2 total revisions
513 513
514 514 diff against bundle
515 515
516 516 $ hg init b
517 517 $ cd b
518 518 $ hg -R ../all.hg diff -r tip
519 519 diff -r aa35859c02ea anotherfile
520 520 --- a/anotherfile Thu Jan 01 00:00:00 1970 +0000
521 521 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
522 522 @@ -1,4 +0,0 @@
523 523 -0
524 524 -1
525 525 -2
526 526 -3
527 527 $ cd ..
528 528
529 529 bundle single branch
530 530
531 531 $ hg init branchy
532 532 $ cd branchy
533 533 $ echo a >a
534 534 $ hg ci -Ama
535 535 adding a
536 536 $ echo b >b
537 537 $ hg ci -Amb
538 538 adding b
539 539 $ echo b1 >b1
540 540 $ hg ci -Amb1
541 541 adding b1
542 542 $ hg up 0
543 543 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
544 544 $ echo c >c
545 545 $ hg ci -Amc
546 546 adding c
547 547 created new head
548 548 $ echo c1 >c1
549 549 $ hg ci -Amc1
550 550 adding c1
551 551 $ hg clone -q .#tip part
552 552
553 553 == bundling via incoming
554 554
555 555 $ hg in -R part --bundle incoming.hg --template "{node}\n" .
556 556 comparing with .
557 557 searching for changes
558 558 d2ae7f538514cd87c17547b0de4cea71fe1af9fb
559 559 5ece8e77363e2b5269e27c66828b72da29e4341a
560 560
561 561 == bundling
562 562
563 563 $ hg bundle bundle.hg part --debug
564 query 1; heads
564 565 searching for changes
566 all remote heads known locally
565 567 2 changesets found
566 568 list of changesets:
567 569 d2ae7f538514cd87c17547b0de4cea71fe1af9fb
568 570 5ece8e77363e2b5269e27c66828b72da29e4341a
569 571 bundling: 1 changesets
570 572 bundling: 2 changesets
571 573 bundling: 1/2 manifests (50.00%)
572 574 bundling: 2/2 manifests (100.00%)
573 575 bundling: b 0/2 files (0.00%)
574 576 bundling: b1 1/2 files (50.00%)
575 577
@@ -1,261 +1,263 b''
1 1 Show all commands except debug commands
2 2 $ hg debugcomplete
3 3 add
4 4 addremove
5 5 annotate
6 6 archive
7 7 backout
8 8 bisect
9 9 bookmarks
10 10 branch
11 11 branches
12 12 bundle
13 13 cat
14 14 clone
15 15 commit
16 16 copy
17 17 diff
18 18 export
19 19 forget
20 20 grep
21 21 heads
22 22 help
23 23 identify
24 24 import
25 25 incoming
26 26 init
27 27 locate
28 28 log
29 29 manifest
30 30 merge
31 31 outgoing
32 32 parents
33 33 paths
34 34 pull
35 35 push
36 36 recover
37 37 remove
38 38 rename
39 39 resolve
40 40 revert
41 41 rollback
42 42 root
43 43 serve
44 44 showconfig
45 45 status
46 46 summary
47 47 tag
48 48 tags
49 49 tip
50 50 unbundle
51 51 update
52 52 verify
53 53 version
54 54
55 55 Show all commands that start with "a"
56 56 $ hg debugcomplete a
57 57 add
58 58 addremove
59 59 annotate
60 60 archive
61 61
62 62 Do not show debug commands if there are other candidates
63 63 $ hg debugcomplete d
64 64 diff
65 65
66 66 Show debug commands if there are no other candidates
67 67 $ hg debugcomplete debug
68 68 debugancestor
69 69 debugbuilddag
70 70 debugbundle
71 71 debugcheckstate
72 72 debugcommands
73 73 debugcomplete
74 74 debugconfig
75 75 debugdag
76 76 debugdata
77 77 debugdate
78 debugdiscovery
78 79 debugfsinfo
79 80 debuggetbundle
80 81 debugignore
81 82 debugindex
82 83 debugindexdot
83 84 debuginstall
84 85 debugknown
85 86 debugpushkey
86 87 debugrebuildstate
87 88 debugrename
88 89 debugrevspec
89 90 debugsetparents
90 91 debugstate
91 92 debugsub
92 93 debugwalk
93 94 debugwireargs
94 95
95 96 Do not show the alias of a debug command if there are other candidates
96 97 (this should hide rawcommit)
97 98 $ hg debugcomplete r
98 99 recover
99 100 remove
100 101 rename
101 102 resolve
102 103 revert
103 104 rollback
104 105 root
105 106 Show the alias of a debug command if there are no other candidates
106 107 $ hg debugcomplete rawc
107 108
108 109
109 110 Show the global options
110 111 $ hg debugcomplete --options | sort
111 112 --config
112 113 --cwd
113 114 --debug
114 115 --debugger
115 116 --encoding
116 117 --encodingmode
117 118 --help
118 119 --noninteractive
119 120 --profile
120 121 --quiet
121 122 --repository
122 123 --time
123 124 --traceback
124 125 --verbose
125 126 --version
126 127 -R
127 128 -h
128 129 -q
129 130 -v
130 131 -y
131 132
132 133 Show the options for the "serve" command
133 134 $ hg debugcomplete --options serve | sort
134 135 --accesslog
135 136 --address
136 137 --certificate
137 138 --config
138 139 --cwd
139 140 --daemon
140 141 --daemon-pipefds
141 142 --debug
142 143 --debugger
143 144 --encoding
144 145 --encodingmode
145 146 --errorlog
146 147 --help
147 148 --ipv6
148 149 --name
149 150 --noninteractive
150 151 --pid-file
151 152 --port
152 153 --prefix
153 154 --profile
154 155 --quiet
155 156 --repository
156 157 --stdio
157 158 --style
158 159 --templates
159 160 --time
160 161 --traceback
161 162 --verbose
162 163 --version
163 164 --web-conf
164 165 -6
165 166 -A
166 167 -E
167 168 -R
168 169 -a
169 170 -d
170 171 -h
171 172 -n
172 173 -p
173 174 -q
174 175 -t
175 176 -v
176 177 -y
177 178
178 179 Show an error if we use --options with an ambiguous abbreviation
179 180 $ hg debugcomplete --options s
180 181 hg: command 's' is ambiguous:
181 182 serve showconfig status summary
182 183 [255]
183 184
184 185 Show all commands + options
185 186 $ hg debugcommands
186 187 add: include, exclude, subrepos, dry-run
187 188 annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, include, exclude
188 189 clone: noupdate, updaterev, rev, branch, pull, uncompressed, ssh, remotecmd, insecure
189 190 commit: addremove, close-branch, include, exclude, message, logfile, date, user
190 191 diff: rev, change, text, git, nodates, show-function, reverse, ignore-all-space, ignore-space-change, ignore-blank-lines, unified, stat, include, exclude, subrepos
191 192 export: output, switch-parent, rev, text, git, nodates
192 193 forget: include, exclude
193 194 init: ssh, remotecmd, insecure
194 195 log: follow, follow-first, date, copies, keyword, rev, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, style, template, include, exclude
195 196 merge: force, tool, rev, preview
196 197 pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure
197 198 push: force, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure
198 199 remove: after, force, include, exclude
199 200 serve: accesslog, daemon, daemon-pipefds, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, templates, style, ipv6, certificate
200 201 status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, copies, print0, rev, change, include, exclude, subrepos
201 202 summary: remote
202 203 update: clean, check, date, rev
203 204 addremove: similarity, include, exclude, dry-run
204 205 archive: no-decode, prefix, rev, type, subrepos, include, exclude
205 206 backout: merge, parent, tool, rev, include, exclude, message, logfile, date, user
206 207 bisect: reset, good, bad, skip, extend, command, noupdate
207 208 bookmarks: force, rev, delete, rename
208 209 branch: force, clean
209 210 branches: active, closed
210 211 bundle: force, rev, branch, base, all, type, ssh, remotecmd, insecure
211 212 cat: output, rev, decode, include, exclude
212 213 copy: after, force, include, exclude, dry-run
213 214 debugancestor:
214 215 debugbuilddag: mergeable-file, overwritten-file, new-file
215 216 debugbundle: all
216 217 debugcheckstate:
217 218 debugcommands:
218 219 debugcomplete: options
219 220 debugdag: tags, branches, dots, spaces
220 221 debugdata:
221 222 debugdate: extended
223 debugdiscovery: old, nonheads, ssh, remotecmd, insecure
222 224 debugfsinfo:
223 225 debuggetbundle: head, common, type
224 226 debugignore:
225 227 debugindex: format
226 228 debugindexdot:
227 229 debuginstall:
228 230 debugknown:
229 231 debugpushkey:
230 232 debugrebuildstate: rev
231 233 debugrename: rev
232 234 debugrevspec:
233 235 debugsetparents:
234 236 debugstate: nodates, datesort
235 237 debugsub: rev
236 238 debugwalk: include, exclude
237 239 debugwireargs: three, four, five, ssh, remotecmd, insecure
238 240 grep: print0, all, text, follow, ignore-case, files-with-matches, line-number, rev, user, date, include, exclude
239 241 heads: rev, topo, active, closed, style, template
240 242 help:
241 243 identify: rev, num, id, branch, tags, bookmarks
242 244 import: strip, base, force, no-commit, exact, import-branch, message, logfile, date, user, similarity
243 245 incoming: force, newest-first, bundle, rev, bookmarks, branch, patch, git, limit, no-merges, stat, style, template, ssh, remotecmd, insecure, subrepos
244 246 locate: rev, print0, fullpath, include, exclude
245 247 manifest: rev
246 248 outgoing: force, rev, newest-first, bookmarks, branch, patch, git, limit, no-merges, stat, style, template, ssh, remotecmd, insecure, subrepos
247 249 parents: rev, style, template
248 250 paths:
249 251 recover:
250 252 rename: after, force, include, exclude, dry-run
251 253 resolve: all, list, mark, unmark, tool, no-status, include, exclude
252 254 revert: all, date, rev, no-backup, include, exclude, dry-run
253 255 rollback: dry-run
254 256 root:
255 257 showconfig: untrusted
256 258 tag: force, local, rev, remove, edit, message, date, user
257 259 tags:
258 260 tip: patch, git, style, template
259 261 unbundle: update
260 262 verify:
261 263 version:
@@ -1,537 +1,537 b''
1 1 commit hooks can see env vars
2 2
3 3 $ hg init a
4 4 $ cd a
5 5 $ echo "[hooks]" > .hg/hgrc
6 6 $ echo 'commit = unset HG_LOCAL HG_TAG; python "$TESTDIR"/printenv.py commit' >> .hg/hgrc
7 7 $ echo 'commit.b = unset HG_LOCAL HG_TAG; python "$TESTDIR"/printenv.py commit.b' >> .hg/hgrc
8 8 $ echo 'precommit = unset HG_LOCAL HG_NODE HG_TAG; python "$TESTDIR"/printenv.py precommit' >> .hg/hgrc
9 9 $ echo 'pretxncommit = unset HG_LOCAL HG_TAG; python "$TESTDIR"/printenv.py pretxncommit' >> .hg/hgrc
10 10 $ echo 'pretxncommit.tip = hg -q tip' >> .hg/hgrc
11 11 $ echo 'pre-identify = python "$TESTDIR"/printenv.py pre-identify 1' >> .hg/hgrc
12 12 $ echo 'pre-cat = python "$TESTDIR"/printenv.py pre-cat' >> .hg/hgrc
13 13 $ echo 'post-cat = python "$TESTDIR"/printenv.py post-cat' >> .hg/hgrc
14 14 $ echo a > a
15 15 $ hg add a
16 16 $ hg commit -m a
17 17 precommit hook: HG_PARENT1=0000000000000000000000000000000000000000
18 18 pretxncommit hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000 HG_PENDING=$TESTTMP/a
19 19 0:cb9a9f314b8b
20 20 commit hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
21 21 commit.b hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
22 22
23 23 $ hg clone . ../b
24 24 updating to branch default
25 25 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
26 26 $ cd ../b
27 27
28 28 changegroup hooks can see env vars
29 29
30 30 $ echo '[hooks]' > .hg/hgrc
31 31 $ echo 'prechangegroup = python "$TESTDIR"/printenv.py prechangegroup' >> .hg/hgrc
32 32 $ echo 'changegroup = python "$TESTDIR"/printenv.py changegroup' >> .hg/hgrc
33 33 $ echo 'incoming = python "$TESTDIR"/printenv.py incoming' >> .hg/hgrc
34 34
35 35 pretxncommit and commit hooks can see both parents of merge
36 36
37 37 $ cd ../a
38 38 $ echo b >> a
39 39 $ hg commit -m a1 -d "1 0"
40 40 precommit hook: HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
41 41 pretxncommit hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
42 42 1:ab228980c14d
43 43 commit hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
44 44 commit.b hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
45 45 $ hg update -C 0
46 46 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
47 47 $ echo b > b
48 48 $ hg add b
49 49 $ hg commit -m b -d '1 0'
50 50 precommit hook: HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
51 51 pretxncommit hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
52 52 2:ee9deb46ab31
53 53 commit hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
54 54 commit.b hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
55 55 created new head
56 56 $ hg merge 1
57 57 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
58 58 (branch merge, don't forget to commit)
59 59 $ hg commit -m merge -d '2 0'
60 60 precommit hook: HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
61 61 pretxncommit hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd HG_PENDING=$TESTTMP/a
62 62 3:07f3376c1e65
63 63 commit hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
64 64 commit.b hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
65 65
66 66 test generic hooks
67 67
68 68 $ hg id
69 69 pre-identify hook: HG_ARGS=id HG_OPTS={'bookmarks': None, 'branch': None, 'id': None, 'num': None, 'rev': '', 'tags': None} HG_PATS=[]
70 70 warning: pre-identify hook exited with status 1
71 71 [1]
72 72 $ hg cat b
73 73 pre-cat hook: HG_ARGS=cat b HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': ''} HG_PATS=['b']
74 74 b
75 75 post-cat hook: HG_ARGS=cat b HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': ''} HG_PATS=['b'] HG_RESULT=0
76 76
77 77 $ cd ../b
78 78 $ hg pull ../a
79 79 pulling from ../a
80 80 searching for changes
81 81 prechangegroup hook: HG_SOURCE=pull HG_URL=file:$TESTTMP/a
82 82 adding changesets
83 83 adding manifests
84 84 adding file changes
85 85 added 3 changesets with 2 changes to 2 files
86 86 changegroup hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_SOURCE=pull HG_URL=file:$TESTTMP/a
87 87 incoming hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_SOURCE=pull HG_URL=file:$TESTTMP/a
88 88 incoming hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_SOURCE=pull HG_URL=file:$TESTTMP/a
89 89 incoming hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_SOURCE=pull HG_URL=file:$TESTTMP/a
90 90 (run 'hg update' to get a working copy)
91 91
92 92 tag hooks can see env vars
93 93
94 94 $ cd ../a
95 95 $ echo 'pretag = python "$TESTDIR"/printenv.py pretag' >> .hg/hgrc
96 96 $ echo 'tag = unset HG_PARENT1 HG_PARENT2; python "$TESTDIR"/printenv.py tag' >> .hg/hgrc
97 97 $ hg tag -d '3 0' a
98 98 pretag hook: HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
99 99 precommit hook: HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
100 100 pretxncommit hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PENDING=$TESTTMP/a
101 101 4:539e4b31b6dc
102 102 commit hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
103 103 commit.b hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
104 104 tag hook: HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
105 105 $ hg tag -l la
106 106 pretag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=la
107 107 tag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=la
108 108
109 109 pretag hook can forbid tagging
110 110
111 111 $ echo 'pretag.forbid = python "$TESTDIR"/printenv.py pretag.forbid 1' >> .hg/hgrc
112 112 $ hg tag -d '4 0' fa
113 113 pretag hook: HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa
114 114 pretag.forbid hook: HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa
115 115 abort: pretag.forbid hook exited with status 1
116 116 [255]
117 117 $ hg tag -l fla
118 118 pretag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fla
119 119 pretag.forbid hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fla
120 120 abort: pretag.forbid hook exited with status 1
121 121 [255]
122 122
123 123 pretxncommit hook can see changeset, can roll back txn, changeset no
124 124 more there after
125 125
126 126 $ echo 'pretxncommit.forbid0 = hg tip -q' >> .hg/hgrc
127 127 $ echo 'pretxncommit.forbid1 = python "$TESTDIR"/printenv.py pretxncommit.forbid 1' >> .hg/hgrc
128 128 $ echo z > z
129 129 $ hg add z
130 130 $ hg -q tip
131 131 4:539e4b31b6dc
132 132 $ hg commit -m 'fail' -d '4 0'
133 133 precommit hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
134 134 pretxncommit hook: HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/a
135 135 5:6f611f8018c1
136 136 5:6f611f8018c1
137 137 pretxncommit.forbid hook: HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/a
138 138 transaction abort!
139 139 rollback completed
140 140 abort: pretxncommit.forbid1 hook exited with status 1
141 141 [255]
142 142 $ hg -q tip
143 143 4:539e4b31b6dc
144 144
145 145 precommit hook can prevent commit
146 146
147 147 $ echo 'precommit.forbid = python "$TESTDIR"/printenv.py precommit.forbid 1' >> .hg/hgrc
148 148 $ hg commit -m 'fail' -d '4 0'
149 149 precommit hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
150 150 precommit.forbid hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
151 151 abort: precommit.forbid hook exited with status 1
152 152 [255]
153 153 $ hg -q tip
154 154 4:539e4b31b6dc
155 155
156 156 preupdate hook can prevent update
157 157
158 158 $ echo 'preupdate = python "$TESTDIR"/printenv.py preupdate' >> .hg/hgrc
159 159 $ hg update 1
160 160 preupdate hook: HG_PARENT1=ab228980c14d
161 161 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
162 162
163 163 update hook
164 164
165 165 $ echo 'update = python "$TESTDIR"/printenv.py update' >> .hg/hgrc
166 166 $ hg update
167 167 preupdate hook: HG_PARENT1=539e4b31b6dc
168 168 update hook: HG_ERROR=0 HG_PARENT1=539e4b31b6dc
169 169 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
170 170
171 171 pushkey hook
172 172
173 173 $ echo 'pushkey = python "$TESTDIR"/printenv.py pushkey' >> .hg/hgrc
174 174 $ cd ../b
175 175 $ hg bookmark -r null foo
176 176 $ hg push -B foo ../a
177 177 pushing to ../a
178 178 searching for changes
179 179 no changes found
180 180 exporting bookmark foo
181 181 pushkey hook: HG_KEY=foo HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 HG_RET=1
182 182 $ cd ../a
183 183
184 184 listkeys hook
185 185
186 186 $ echo 'listkeys = python "$TESTDIR"/printenv.py listkeys' >> .hg/hgrc
187 187 $ hg bookmark -r null bar
188 188 $ cd ../b
189 189 $ hg pull -B bar ../a
190 190 pulling from ../a
191 191 listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
192 searching for changes
192 no changes found
193 193 listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
194 194 importing bookmark bar
195 195 $ cd ../a
196 196
197 197 test that prepushkey can prevent incoming keys
198 198
199 199 $ echo 'prepushkey = python "$TESTDIR"/printenv.py prepushkey.forbid 1' >> .hg/hgrc
200 200 $ cd ../b
201 201 $ hg bookmark -r null baz
202 202 $ hg push -B baz ../a
203 203 pushing to ../a
204 204 searching for changes
205 205 no changes found
206 206 listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
207 207 listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
208 208 exporting bookmark baz
209 209 prepushkey.forbid hook: HG_KEY=baz HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000
210 210 abort: prepushkey hook exited with status 1
211 211 [255]
212 212 $ cd ../a
213 213
214 214 test that prelistkeys can prevent listing keys
215 215
216 216 $ echo 'prelistkeys = python "$TESTDIR"/printenv.py prelistkeys.forbid 1' >> .hg/hgrc
217 217 $ hg bookmark -r null quux
218 218 $ cd ../b
219 219 $ hg pull -B quux ../a
220 220 pulling from ../a
221 221 prelistkeys.forbid hook: HG_NAMESPACE=bookmarks
222 222 abort: prelistkeys hook exited with status 1
223 223 [255]
224 224 $ cd ../a
225 225
226 226 prechangegroup hook can prevent incoming changes
227 227
228 228 $ cd ../b
229 229 $ hg -q tip
230 230 3:07f3376c1e65
231 231 $ echo '[hooks]' > .hg/hgrc
232 232 $ echo 'prechangegroup.forbid = python "$TESTDIR"/printenv.py prechangegroup.forbid 1' >> .hg/hgrc
233 233 $ hg pull ../a
234 234 pulling from ../a
235 235 searching for changes
236 236 prechangegroup.forbid hook: HG_SOURCE=pull HG_URL=file:$TESTTMP/a
237 237 abort: prechangegroup.forbid hook exited with status 1
238 238 [255]
239 239
240 240 pretxnchangegroup hook can see incoming changes, can roll back txn,
241 241 incoming changes no longer there after
242 242
243 243 $ echo '[hooks]' > .hg/hgrc
244 244 $ echo 'pretxnchangegroup.forbid0 = hg tip -q' >> .hg/hgrc
245 245 $ echo 'pretxnchangegroup.forbid1 = python "$TESTDIR"/printenv.py pretxnchangegroup.forbid 1' >> .hg/hgrc
246 246 $ hg pull ../a
247 247 pulling from ../a
248 248 searching for changes
249 249 adding changesets
250 250 adding manifests
251 251 adding file changes
252 252 added 1 changesets with 1 changes to 1 files
253 253 4:539e4b31b6dc
254 254 pretxnchangegroup.forbid hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/b HG_SOURCE=pull HG_URL=file:$TESTTMP/a
255 255 transaction abort!
256 256 rollback completed
257 257 abort: pretxnchangegroup.forbid1 hook exited with status 1
258 258 [255]
259 259 $ hg -q tip
260 260 3:07f3376c1e65
261 261
262 262 outgoing hooks can see env vars
263 263
264 264 $ rm .hg/hgrc
265 265 $ echo '[hooks]' > ../a/.hg/hgrc
266 266 $ echo 'preoutgoing = python "$TESTDIR"/printenv.py preoutgoing' >> ../a/.hg/hgrc
267 267 $ echo 'outgoing = python "$TESTDIR"/printenv.py outgoing' >> ../a/.hg/hgrc
268 268 $ hg pull ../a
269 269 pulling from ../a
270 270 searching for changes
271 271 preoutgoing hook: HG_SOURCE=pull
272 272 adding changesets
273 273 outgoing hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_SOURCE=pull
274 274 adding manifests
275 275 adding file changes
276 276 added 1 changesets with 1 changes to 1 files
277 277 (run 'hg update' to get a working copy)
278 278 $ hg rollback
279 279 repository tip rolled back to revision 3 (undo pull)
280 280 working directory now based on revision 0
281 281
282 282 preoutgoing hook can prevent outgoing changes
283 283
284 284 $ echo 'preoutgoing.forbid = python "$TESTDIR"/printenv.py preoutgoing.forbid 1' >> ../a/.hg/hgrc
285 285 $ hg pull ../a
286 286 pulling from ../a
287 287 searching for changes
288 288 preoutgoing hook: HG_SOURCE=pull
289 289 preoutgoing.forbid hook: HG_SOURCE=pull
290 290 abort: preoutgoing.forbid hook exited with status 1
291 291 [255]
292 292
293 293 outgoing hooks work for local clones
294 294
295 295 $ cd ..
296 296 $ echo '[hooks]' > a/.hg/hgrc
297 297 $ echo 'preoutgoing = python "$TESTDIR"/printenv.py preoutgoing' >> a/.hg/hgrc
298 298 $ echo 'outgoing = python "$TESTDIR"/printenv.py outgoing' >> a/.hg/hgrc
299 299 $ hg clone a c
300 300 preoutgoing hook: HG_SOURCE=clone
301 301 outgoing hook: HG_NODE=0000000000000000000000000000000000000000 HG_SOURCE=clone
302 302 updating to branch default
303 303 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
304 304 $ rm -rf c
305 305
306 306 preoutgoing hook can prevent outgoing changes for local clones
307 307
308 308 $ echo 'preoutgoing.forbid = python "$TESTDIR"/printenv.py preoutgoing.forbid 1' >> a/.hg/hgrc
309 309 $ hg clone a zzz
310 310 preoutgoing hook: HG_SOURCE=clone
311 311 preoutgoing.forbid hook: HG_SOURCE=clone
312 312 abort: preoutgoing.forbid hook exited with status 1
313 313 [255]
314 314 $ cd b
315 315
316 316 $ cat > hooktests.py <<EOF
317 317 > from mercurial import util
318 318 >
319 319 > uncallable = 0
320 320 >
321 321 > def printargs(args):
322 322 > args.pop('ui', None)
323 323 > args.pop('repo', None)
324 324 > a = list(args.items())
325 325 > a.sort()
326 326 > print 'hook args:'
327 327 > for k, v in a:
328 328 > print ' ', k, v
329 329 >
330 330 > def passhook(**args):
331 331 > printargs(args)
332 332 >
333 333 > def failhook(**args):
334 334 > printargs(args)
335 335 > return True
336 336 >
337 337 > class LocalException(Exception):
338 338 > pass
339 339 >
340 340 > def raisehook(**args):
341 341 > raise LocalException('exception from hook')
342 342 >
343 343 > def aborthook(**args):
344 344 > raise util.Abort('raise abort from hook')
345 345 >
346 346 > def brokenhook(**args):
347 347 > return 1 + {}
348 348 >
349 349 > class container:
350 350 > unreachable = 1
351 351 > EOF
352 352
353 353 test python hooks
354 354
355 355 $ PYTHONPATH="`pwd`:$PYTHONPATH"
356 356 $ export PYTHONPATH
357 357
358 358 $ echo '[hooks]' > ../a/.hg/hgrc
359 359 $ echo 'preoutgoing.broken = python:hooktests.brokenhook' >> ../a/.hg/hgrc
360 360 $ hg pull ../a 2>&1 | grep 'raised an exception'
361 361 error: preoutgoing.broken hook raised an exception: unsupported operand type(s) for +: 'int' and 'dict'
362 362
363 363 $ echo '[hooks]' > ../a/.hg/hgrc
364 364 $ echo 'preoutgoing.raise = python:hooktests.raisehook' >> ../a/.hg/hgrc
365 365 $ hg pull ../a 2>&1 | grep 'raised an exception'
366 366 error: preoutgoing.raise hook raised an exception: exception from hook
367 367
368 368 $ echo '[hooks]' > ../a/.hg/hgrc
369 369 $ echo 'preoutgoing.abort = python:hooktests.aborthook' >> ../a/.hg/hgrc
370 370 $ hg pull ../a
371 371 pulling from ../a
372 372 searching for changes
373 373 error: preoutgoing.abort hook failed: raise abort from hook
374 374 abort: raise abort from hook
375 375 [255]
376 376
377 377 $ echo '[hooks]' > ../a/.hg/hgrc
378 378 $ echo 'preoutgoing.fail = python:hooktests.failhook' >> ../a/.hg/hgrc
379 379 $ hg pull ../a
380 380 pulling from ../a
381 381 searching for changes
382 382 hook args:
383 383 hooktype preoutgoing
384 384 source pull
385 385 abort: preoutgoing.fail hook failed
386 386 [255]
387 387
388 388 $ echo '[hooks]' > ../a/.hg/hgrc
389 389 $ echo 'preoutgoing.uncallable = python:hooktests.uncallable' >> ../a/.hg/hgrc
390 390 $ hg pull ../a
391 391 pulling from ../a
392 392 searching for changes
393 393 abort: preoutgoing.uncallable hook is invalid ("hooktests.uncallable" is not callable)
394 394 [255]
395 395
396 396 $ echo '[hooks]' > ../a/.hg/hgrc
397 397 $ echo 'preoutgoing.nohook = python:hooktests.nohook' >> ../a/.hg/hgrc
398 398 $ hg pull ../a
399 399 pulling from ../a
400 400 searching for changes
401 401 abort: preoutgoing.nohook hook is invalid ("hooktests.nohook" is not defined)
402 402 [255]
403 403
404 404 $ echo '[hooks]' > ../a/.hg/hgrc
405 405 $ echo 'preoutgoing.nomodule = python:nomodule' >> ../a/.hg/hgrc
406 406 $ hg pull ../a
407 407 pulling from ../a
408 408 searching for changes
409 409 abort: preoutgoing.nomodule hook is invalid ("nomodule" not in a module)
410 410 [255]
411 411
412 412 $ echo '[hooks]' > ../a/.hg/hgrc
413 413 $ echo 'preoutgoing.badmodule = python:nomodule.nowhere' >> ../a/.hg/hgrc
414 414 $ hg pull ../a
415 415 pulling from ../a
416 416 searching for changes
417 417 abort: preoutgoing.badmodule hook is invalid (import of "nomodule" failed)
418 418 [255]
419 419
420 420 $ echo '[hooks]' > ../a/.hg/hgrc
421 421 $ echo 'preoutgoing.unreachable = python:hooktests.container.unreachable' >> ../a/.hg/hgrc
422 422 $ hg pull ../a
423 423 pulling from ../a
424 424 searching for changes
425 425 abort: preoutgoing.unreachable hook is invalid (import of "hooktests.container" failed)
426 426 [255]
427 427
428 428 $ echo '[hooks]' > ../a/.hg/hgrc
429 429 $ echo 'preoutgoing.pass = python:hooktests.passhook' >> ../a/.hg/hgrc
430 430 $ hg pull ../a
431 431 pulling from ../a
432 432 searching for changes
433 433 hook args:
434 434 hooktype preoutgoing
435 435 source pull
436 436 adding changesets
437 437 adding manifests
438 438 adding file changes
439 439 added 1 changesets with 1 changes to 1 files
440 440 (run 'hg update' to get a working copy)
441 441
442 442 make sure --traceback works
443 443
444 444 $ echo '[hooks]' > .hg/hgrc
445 445 $ echo 'commit.abort = python:hooktests.aborthook' >> .hg/hgrc
446 446
447 447 $ echo aa > a
448 448 $ hg --traceback commit -d '0 0' -ma 2>&1 | grep '^Traceback'
449 449 Traceback (most recent call last):
450 450
451 451 $ cd ..
452 452 $ hg init c
453 453 $ cd c
454 454
455 455 $ cat > hookext.py <<EOF
456 456 > def autohook(**args):
457 457 > print "Automatically installed hook"
458 458 >
459 459 > def reposetup(ui, repo):
460 460 > repo.ui.setconfig("hooks", "commit.auto", autohook)
461 461 > EOF
462 462 $ echo '[extensions]' >> .hg/hgrc
463 463 $ echo 'hookext = hookext.py' >> .hg/hgrc
464 464
465 465 $ touch foo
466 466 $ hg add foo
467 467 $ hg ci -d '0 0' -m 'add foo'
468 468 Automatically installed hook
469 469 $ echo >> foo
470 470 $ hg ci --debug -d '0 0' -m 'change foo'
471 471 foo
472 472 calling hook commit.auto: <function autohook at *> (glob)
473 473 Automatically installed hook
474 474 committed changeset 1:52998019f6252a2b893452765fcb0a47351a5708
475 475
476 476 $ hg showconfig hooks
477 477 hooks.commit.auto=<function autohook at *> (glob)
478 478
479 479 test python hook configured with python:[file]:[hook] syntax
480 480
481 481 $ cd ..
482 482 $ mkdir d
483 483 $ cd d
484 484 $ hg init repo
485 485 $ mkdir hooks
486 486
487 487 $ cd hooks
488 488 $ cat > testhooks.py <<EOF
489 489 > def testhook(**args):
490 490 > print 'hook works'
491 491 > EOF
492 492 $ echo '[hooks]' > ../repo/.hg/hgrc
493 493 $ echo "pre-commit.test = python:`pwd`/testhooks.py:testhook" >> ../repo/.hg/hgrc
494 494
495 495 $ cd ../repo
496 496 $ hg commit -d '0 0'
497 497 hook works
498 498 nothing changed
499 499 [1]
500 500
501 501 $ cd ../../b
502 502
503 503 make sure --traceback works on hook import failure
504 504
505 505 $ cat > importfail.py <<EOF
506 506 > import somebogusmodule
507 507 > # dereference something in the module to force demandimport to load it
508 508 > somebogusmodule.whatever
509 509 > EOF
510 510
511 511 $ echo '[hooks]' > .hg/hgrc
512 512 $ echo 'precommit.importfail = python:importfail.whatever' >> .hg/hgrc
513 513
514 514 $ echo a >> a
515 515 $ hg --traceback commit -ma 2>&1 | egrep '^(exception|Traceback|ImportError)'
516 516 exception from first failed import attempt:
517 517 Traceback (most recent call last):
518 518 ImportError: No module named somebogusmodule
519 519 exception from second failed import attempt:
520 520 Traceback (most recent call last):
521 521 ImportError: No module named hgext_importfail
522 522 Traceback (most recent call last):
523 523
524 524 Issue1827: Hooks Update & Commit not completely post operation
525 525
526 526 commit and update hooks should run after command completion
527 527
528 528 $ echo '[hooks]' > .hg/hgrc
529 529 $ echo 'commit = hg id' >> .hg/hgrc
530 530 $ echo 'update = hg id' >> .hg/hgrc
531 531 $ echo bb > a
532 532 $ hg ci -ma
533 533 223eafe2750c tip
534 534 $ hg up 0
535 535 cb9a9f314b8b
536 536 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
537 537
@@ -1,716 +1,714 b''
1 1 $ echo "[extensions]" >> $HGRCPATH
2 2 $ echo "graphlog=" >> $HGRCPATH
3 3
4 4 $ hg init a
5 5 $ cd a
6 6 $ echo foo > t1
7 7 $ hg add t1
8 8 $ hg commit -m "1"
9 9
10 10 $ cd ..
11 11 $ hg clone a b
12 12 updating to branch default
13 13 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
14 14
15 15 $ cd a
16 16 $ echo foo > t2
17 17 $ hg add t2
18 18 $ hg commit -m "2"
19 19
20 20 $ cd ../b
21 21 $ echo foo > t3
22 22 $ hg add t3
23 23 $ hg commit -m "3"
24 24
25 25 $ hg push ../a
26 26 pushing to ../a
27 27 searching for changes
28 28 abort: push creates new remote heads on branch 'default'!
29 29 (you should pull and merge or use push -f to force)
30 30 [255]
31 31
32 32 $ hg push --debug ../a
33 33 pushing to ../a
34 query 1; heads
34 35 searching for changes
35 examining 1c9246a22a0a:d8d565842d04
36 found incomplete branch 1c9246a22a0a:d8d565842d04
37 searching: 1 queries
38 narrowing 1:1 d8d565842d04
39 found new branch changeset 1c9246a22a0a
40 found new changesets starting at 1c9246a22a0a
41 1 total queries
36 taking quick initial sample
37 searching: 2 queries
38 query 2; still undecided: 2, sample size is: 2
39 2 total queries
42 40 new remote heads on branch 'default'
43 41 new remote head 1e108cc5548c
44 42 abort: push creates new remote heads on branch 'default'!
45 43 (you should pull and merge or use push -f to force)
46 44 [255]
47 45
48 46 $ hg pull ../a
49 47 pulling from ../a
50 48 searching for changes
51 49 adding changesets
52 50 adding manifests
53 51 adding file changes
54 52 added 1 changesets with 1 changes to 1 files (+1 heads)
55 53 (run 'hg heads' to see heads, 'hg merge' to merge)
56 54
57 55 $ hg push ../a
58 56 pushing to ../a
59 57 searching for changes
60 58 abort: push creates new remote heads on branch 'default'!
61 59 (did you forget to merge? use push -f to force)
62 60 [255]
63 61
64 62 $ hg merge
65 63 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
66 64 (branch merge, don't forget to commit)
67 65
68 66 $ hg commit -m "4"
69 67 $ hg push ../a
70 68 pushing to ../a
71 69 searching for changes
72 70 adding changesets
73 71 adding manifests
74 72 adding file changes
75 73 added 2 changesets with 1 changes to 1 files
76 74
77 75 $ cd ..
78 76
79 77 $ hg init c
80 78 $ cd c
81 79 $ for i in 0 1 2; do
82 80 > echo $i >> foo
83 81 > hg ci -Am $i
84 82 > done
85 83 adding foo
86 84 $ cd ..
87 85
88 86 $ hg clone c d
89 87 updating to branch default
90 88 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
91 89
92 90 $ cd d
93 91 $ for i in 0 1; do
94 92 > hg co -C $i
95 93 > echo d-$i >> foo
96 94 > hg ci -m d-$i
97 95 > done
98 96 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
99 97 created new head
100 98 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
101 99 created new head
102 100
103 101 $ HGMERGE=true hg merge 3
104 102 merging foo
105 103 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
106 104 (branch merge, don't forget to commit)
107 105
108 106 $ hg ci -m c-d
109 107
110 108 $ hg push ../c
111 109 pushing to ../c
112 110 searching for changes
113 111 abort: push creates new remote heads on branch 'default'!
114 112 (did you forget to merge? use push -f to force)
115 113 [255]
116 114
117 115 $ hg push -r 2 ../c
118 116 pushing to ../c
119 117 searching for changes
120 118 no changes found
121 119
122 120 $ hg push -r 3 ../c
123 121 pushing to ../c
124 122 searching for changes
125 123 abort: push creates new remote heads on branch 'default'!
126 124 (did you forget to merge? use push -f to force)
127 125 [255]
128 126
129 127 $ hg push -r 3 -r 4 ../c
130 128 pushing to ../c
131 129 searching for changes
132 130 abort: push creates new remote heads on branch 'default'!
133 131 (did you forget to merge? use push -f to force)
134 132 [255]
135 133
136 134 $ hg push -f -r 3 -r 4 ../c
137 135 pushing to ../c
138 136 searching for changes
139 137 adding changesets
140 138 adding manifests
141 139 adding file changes
142 140 added 2 changesets with 2 changes to 1 files (+2 heads)
143 141
144 142 $ hg push -r 5 ../c
145 143 pushing to ../c
146 144 searching for changes
147 145 adding changesets
148 146 adding manifests
149 147 adding file changes
150 148 added 1 changesets with 1 changes to 1 files (-1 heads)
151 149
152 150 $ hg in ../c
153 151 comparing with ../c
154 152 searching for changes
155 153 no changes found
156 154 [1]
157 155
158 156
159 157 Issue450: push -r warns about remote head creation even if no heads
160 158 will be created
161 159
162 160 $ hg init ../e
163 161 $ hg push -r 0 ../e
164 162 pushing to ../e
165 163 searching for changes
166 164 adding changesets
167 165 adding manifests
168 166 adding file changes
169 167 added 1 changesets with 1 changes to 1 files
170 168
171 169 $ hg push -r 1 ../e
172 170 pushing to ../e
173 171 searching for changes
174 172 adding changesets
175 173 adding manifests
176 174 adding file changes
177 175 added 1 changesets with 1 changes to 1 files
178 176
179 177 $ cd ..
180 178
181 179
182 180 Issue736: named branches are not considered for detection of
183 181 unmerged heads in "hg push"
184 182
185 183 $ hg init f
186 184 $ cd f
187 185 $ hg -q branch a
188 186 $ echo 0 > foo
189 187 $ hg -q ci -Am 0
190 188 $ echo 1 > foo
191 189 $ hg -q ci -m 1
192 190 $ hg -q up 0
193 191 $ echo 2 > foo
194 192 $ hg -q ci -m 2
195 193 $ hg -q up 0
196 194 $ hg -q branch b
197 195 $ echo 3 > foo
198 196 $ hg -q ci -m 3
199 197 $ cd ..
200 198
201 199 $ hg -q clone f g
202 200 $ cd g
203 201
204 202 Push on existing branch and new branch:
205 203
206 204 $ hg -q up 1
207 205 $ echo 4 > foo
208 206 $ hg -q ci -m 4
209 207 $ hg -q up 0
210 208 $ echo 5 > foo
211 209 $ hg -q branch c
212 210 $ hg -q ci -m 5
213 211
214 212 $ hg push ../f
215 213 pushing to ../f
216 214 searching for changes
217 215 abort: push creates new remote branches: c!
218 216 (use 'hg push --new-branch' to create new remote branches)
219 217 [255]
220 218
221 219 $ hg push -r 4 -r 5 ../f
222 220 pushing to ../f
223 221 searching for changes
224 222 abort: push creates new remote branches: c!
225 223 (use 'hg push --new-branch' to create new remote branches)
226 224 [255]
227 225
228 226
229 227 Multiple new branches:
230 228
231 229 $ hg -q branch d
232 230 $ echo 6 > foo
233 231 $ hg -q ci -m 6
234 232
235 233 $ hg push ../f
236 234 pushing to ../f
237 235 searching for changes
238 236 abort: push creates new remote branches: c, d!
239 237 (use 'hg push --new-branch' to create new remote branches)
240 238 [255]
241 239
242 240 $ hg push -r 4 -r 6 ../f
243 241 pushing to ../f
244 242 searching for changes
245 243 abort: push creates new remote branches: c, d!
246 244 (use 'hg push --new-branch' to create new remote branches)
247 245 [255]
248 246
249 247 $ cd ../g
250 248
251 249
252 250 Fail on multiple head push:
253 251
254 252 $ hg -q up 1
255 253 $ echo 7 > foo
256 254 $ hg -q ci -m 7
257 255
258 256 $ hg push -r 4 -r 7 ../f
259 257 pushing to ../f
260 258 searching for changes
261 259 abort: push creates new remote heads on branch 'a'!
262 260 (did you forget to merge? use push -f to force)
263 261 [255]
264 262
265 263 Push replacement head on existing branches:
266 264
267 265 $ hg -q up 3
268 266 $ echo 8 > foo
269 267 $ hg -q ci -m 8
270 268
271 269 $ hg push -r 7 -r 8 ../f
272 270 pushing to ../f
273 271 searching for changes
274 272 adding changesets
275 273 adding manifests
276 274 adding file changes
277 275 added 2 changesets with 2 changes to 1 files
278 276
279 277
280 278 Merge of branch a to other branch b followed by unrelated push
281 279 on branch a:
282 280
283 281 $ hg -q up 7
284 282 $ HGMERGE=true hg -q merge 8
285 283 $ hg -q ci -m 9
286 284 $ hg -q up 8
287 285 $ echo 10 > foo
288 286 $ hg -q ci -m 10
289 287
290 288 $ hg push -r 9 ../f
291 289 pushing to ../f
292 290 searching for changes
293 291 adding changesets
294 292 adding manifests
295 293 adding file changes
296 294 added 1 changesets with 1 changes to 1 files (-1 heads)
297 295
298 296 $ hg push -r 10 ../f
299 297 pushing to ../f
300 298 searching for changes
301 299 adding changesets
302 300 adding manifests
303 301 adding file changes
304 302 added 1 changesets with 1 changes to 1 files (+1 heads)
305 303
306 304
307 305 Cheating the counting algorithm:
308 306
309 307 $ hg -q up 9
310 308 $ HGMERGE=true hg -q merge 2
311 309 $ hg -q ci -m 11
312 310 $ hg -q up 1
313 311 $ echo 12 > foo
314 312 $ hg -q ci -m 12
315 313
316 314 $ hg push -r 11 -r 12 ../f
317 315 pushing to ../f
318 316 searching for changes
319 317 adding changesets
320 318 adding manifests
321 319 adding file changes
322 320 added 2 changesets with 2 changes to 1 files
323 321
324 322
325 323 Failed push of new named branch:
326 324
327 325 $ echo 12 > foo
328 326 $ hg -q ci -m 12a
329 327 [1]
330 328 $ hg -q up 11
331 329 $ echo 13 > foo
332 330 $ hg -q branch e
333 331 $ hg -q ci -m 13d
334 332
335 333 $ hg push -r 12 -r 13 ../f
336 334 pushing to ../f
337 335 searching for changes
338 336 abort: push creates new remote branches: e!
339 337 (use 'hg push --new-branch' to create new remote branches)
340 338 [255]
341 339
342 340
343 341 Using --new-branch to push new named branch:
344 342
345 343 $ hg push --new-branch -r 12 -r 13 ../f
346 344 pushing to ../f
347 345 searching for changes
348 346 adding changesets
349 347 adding manifests
350 348 adding file changes
351 349 added 1 changesets with 1 changes to 1 files
352 350
353 351
354 352 Checking prepush logic does not allow silently pushing
355 353 multiple new heads:
356 354
357 355 $ cd ..
358 356 $ hg init h
359 357 $ echo init > h/init
360 358 $ hg -R h ci -Am init
361 359 adding init
362 360 $ echo a > h/a
363 361 $ hg -R h ci -Am a
364 362 adding a
365 363 $ hg clone h i
366 364 updating to branch default
367 365 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
368 366 $ hg -R h up 0
369 367 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
370 368 $ echo b > h/b
371 369 $ hg -R h ci -Am b
372 370 adding b
373 371 created new head
374 372 $ hg -R i up 0
375 373 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
376 374 $ echo c > i/c
377 375 $ hg -R i ci -Am c
378 376 adding c
379 377 created new head
380 378
381 379 $ hg -R i push h
382 380 pushing to h
383 381 searching for changes
384 382 abort: push creates new remote heads on branch 'default'!
385 383 (you should pull and merge or use push -f to force)
386 384 [255]
387 385
388 386
389 387 Check prepush logic with merged branches:
390 388
391 389 $ hg init j
392 390 $ hg -R j branch a
393 391 marked working directory as branch a
394 392 $ echo init > j/foo
395 393 $ hg -R j ci -Am init
396 394 adding foo
397 395 $ hg clone j k
398 396 updating to branch a
399 397 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
400 398 $ echo a1 > j/foo
401 399 $ hg -R j ci -m a1
402 400 $ hg -R k branch b
403 401 marked working directory as branch b
404 402 $ echo b > k/foo
405 403 $ hg -R k ci -m b
406 404 $ hg -R k up 0
407 405 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
408 406
409 407 $ hg -R k merge b
410 408 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
411 409 (branch merge, don't forget to commit)
412 410
413 411 $ hg -R k ci -m merge
414 412
415 413 $ hg -R k push -r a j
416 414 pushing to j
417 415 searching for changes
418 416 abort: push creates new remote branches: b!
419 417 (use 'hg push --new-branch' to create new remote branches)
420 418 [255]
421 419
422 420
423 421 Prepush -r should not allow you to sneak in new heads:
424 422
425 423 $ hg init l
426 424 $ cd l
427 425 $ echo a >> foo
428 426 $ hg -q add foo
429 427 $ hg -q branch a
430 428 $ hg -q ci -ma
431 429 $ hg -q up null
432 430 $ echo a >> foo
433 431 $ hg -q add foo
434 432 $ hg -q branch b
435 433 $ hg -q ci -mb
436 434 $ cd ..
437 435 $ hg -q clone l m -u a
438 436 $ cd m
439 437 $ hg -q merge b
440 438 $ hg -q ci -mmb
441 439 $ hg -q up 0
442 440 $ echo a >> foo
443 441 $ hg -q ci -ma2
444 442 $ hg -q up 2
445 443 $ echo a >> foo
446 444 $ hg -q branch -f b
447 445 $ hg -q ci -mb2
448 446 $ hg -q merge 3
449 447 $ hg -q ci -mma
450 448
451 449 $ hg push ../l -b b
452 450 pushing to ../l
453 451 searching for changes
454 452 abort: push creates new remote heads on branch 'a'!
455 453 (did you forget to merge? use push -f to force)
456 454 [255]
457 455
458 456 $ cd ..
459 457
460 458
461 459 Check prepush with new branch head on former topo non-head:
462 460
463 461 $ hg init n
464 462 $ cd n
465 463 $ hg branch A
466 464 marked working directory as branch A
467 465 $ echo a >a
468 466 $ hg ci -Ama
469 467 adding a
470 468 $ hg branch B
471 469 marked working directory as branch B
472 470 $ echo b >b
473 471 $ hg ci -Amb
474 472 adding b
475 473
476 474 b is now branch head of B, and a topological head
477 475 a is now branch head of A, but not a topological head
478 476
479 477 $ hg clone . inner
480 478 updating to branch B
481 479 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
482 480 $ cd inner
483 481 $ hg up B
484 482 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
485 483 $ echo b1 >b1
486 484 $ hg ci -Amb1
487 485 adding b1
488 486
489 487 in the clone b1 is now the head of B
490 488
491 489 $ cd ..
492 490 $ hg up 0
493 491 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
494 492 $ echo a2 >a2
495 493 $ hg ci -Ama2
496 494 adding a2
497 495
498 496 a2 is now the new branch head of A, and a new topological head
499 497 it replaces a former inner branch head, so it should at most warn about
500 498 A, not B
501 499
502 500 glog of local:
503 501
504 502 $ hg glog --template "{rev}: {branches} {desc}\n"
505 503 @ 2: A a2
506 504 |
507 505 | o 1: B b
508 506 |/
509 507 o 0: A a
510 508
511 509 glog of remote:
512 510
513 511 $ hg glog -R inner --template "{rev}: {branches} {desc}\n"
514 512 @ 2: B b1
515 513 |
516 514 o 1: B b
517 515 |
518 516 o 0: A a
519 517
520 518 outgoing:
521 519
522 520 $ hg out inner --template "{rev}: {branches} {desc}\n"
523 521 comparing with inner
524 522 searching for changes
525 523 2: A a2
526 524
527 525 $ hg push inner
528 526 pushing to inner
529 527 searching for changes
530 528 adding changesets
531 529 adding manifests
532 530 adding file changes
533 531 added 1 changesets with 1 changes to 1 files (+1 heads)
534 532
535 533 $ cd ..
536 534
537 535
538 536 Check prepush with new branch head on former topo head:
539 537
540 538 $ hg init o
541 539 $ cd o
542 540 $ hg branch A
543 541 marked working directory as branch A
544 542 $ echo a >a
545 543 $ hg ci -Ama
546 544 adding a
547 545 $ hg branch B
548 546 marked working directory as branch B
549 547 $ echo b >b
550 548 $ hg ci -Amb
551 549 adding b
552 550
553 551 b is now branch head of B, and a topological head
554 552
555 553 $ hg up 0
556 554 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
557 555 $ echo a1 >a1
558 556 $ hg ci -Ama1
559 557 adding a1
560 558
561 559 a1 is now branch head of A, and a topological head
562 560
563 561 $ hg clone . inner
564 562 updating to branch A
565 563 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
566 564 $ cd inner
567 565 $ hg up B
568 566 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
569 567 $ echo b1 >b1
570 568 $ hg ci -Amb1
571 569 adding b1
572 570
573 571 in the clone b1 is now the head of B
574 572
575 573 $ cd ..
576 574 $ echo a2 >a2
577 575 $ hg ci -Ama2
578 576 adding a2
579 577
580 578 a2 is now the new branch head of A, and a topological head
581 579 it replaces a former topological and branch head, so this should not warn
582 580
583 581 glog of local:
584 582
585 583 $ hg glog --template "{rev}: {branches} {desc}\n"
586 584 @ 3: A a2
587 585 |
588 586 o 2: A a1
589 587 |
590 588 | o 1: B b
591 589 |/
592 590 o 0: A a
593 591
594 592 glog of remote:
595 593
596 594 $ hg glog -R inner --template "{rev}: {branches} {desc}\n"
597 595 @ 3: B b1
598 596 |
599 597 | o 2: A a1
600 598 | |
601 599 o | 1: B b
602 600 |/
603 601 o 0: A a
604 602
605 603 outgoing:
606 604
607 605 $ hg out inner --template "{rev}: {branches} {desc}\n"
608 606 comparing with inner
609 607 searching for changes
610 608 3: A a2
611 609
612 610 $ hg push inner
613 611 pushing to inner
614 612 searching for changes
615 613 adding changesets
616 614 adding manifests
617 615 adding file changes
618 616 added 1 changesets with 1 changes to 1 files
619 617
620 618 $ cd ..
621 619
622 620
623 621 Check prepush with new branch head and new child of former branch head
624 622 but child is on different branch:
625 623
626 624 $ hg init p
627 625 $ cd p
628 626 $ hg branch A
629 627 marked working directory as branch A
630 628 $ echo a0 >a
631 629 $ hg ci -Ama0
632 630 adding a
633 631 $ echo a1 >a
634 632 $ hg ci -ma1
635 633 $ hg up null
636 634 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
637 635 $ hg branch B
638 636 marked working directory as branch B
639 637 $ echo b0 >b
640 638 $ hg ci -Amb0
641 639 adding b
642 640 $ echo b1 >b
643 641 $ hg ci -mb1
644 642
645 643 $ hg clone . inner
646 644 updating to branch B
647 645 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
648 646
649 647 $ hg up A
650 648 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
651 649 $ hg branch -f B
652 650 marked working directory as branch B
653 651 $ echo a3 >a
654 652 $ hg ci -ma3
655 653 created new head
656 654 $ hg up 3
657 655 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
658 656 $ hg branch -f A
659 657 marked working directory as branch A
660 658 $ echo b3 >b
661 659 $ hg ci -mb3
662 660 created new head
663 661
664 662 glog of local:
665 663
666 664 $ hg glog --template "{rev}: {branches} {desc}\n"
667 665 @ 5: A b3
668 666 |
669 667 | o 4: B a3
670 668 | |
671 669 o | 3: B b1
672 670 | |
673 671 o | 2: B b0
674 672 /
675 673 o 1: A a1
676 674 |
677 675 o 0: A a0
678 676
679 677 glog of remote:
680 678
681 679 $ hg glog -R inner --template "{rev}: {branches} {desc}\n"
682 680 @ 3: B b1
683 681 |
684 682 o 2: B b0
685 683
686 684 o 1: A a1
687 685 |
688 686 o 0: A a0
689 687
690 688 outgoing:
691 689
692 690 $ hg out inner --template "{rev}: {branches} {desc}\n"
693 691 comparing with inner
694 692 searching for changes
695 693 4: B a3
696 694 5: A b3
697 695
698 696 $ hg push inner
699 697 pushing to inner
700 698 searching for changes
701 699 abort: push creates new remote heads on branch 'A'!
702 700 (did you forget to merge? use push -f to force)
703 701 [255]
704 702
705 703 $ hg push inner -r4 -r5
706 704 pushing to inner
707 705 searching for changes
708 706 abort: push creates new remote heads on branch 'A'!
709 707 (did you forget to merge? use push -f to force)
710 708 [255]
711 709
712 710 $ hg in inner
713 711 comparing with inner
714 712 searching for changes
715 713 no changes found
716 714 [1]
@@ -1,46 +1,47 b''
1 1
2 2 $ cat <<EOF >> $HGRCPATH
3 3 > [extensions]
4 4 > schemes=
5 5 >
6 6 > [schemes]
7 7 > l = http://localhost:$HGPORT/
8 8 > parts = http://{1}:$HGPORT/
9 9 > z = file:\$PWD/
10 10 > EOF
11 11 $ hg init test
12 12 $ cd test
13 13 $ echo a > a
14 14 $ hg ci -Am initial
15 15 adding a
16 16 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
17 17 $ cat hg.pid >> $DAEMON_PIDS
18 18 $ hg incoming l://
19 19 comparing with l://
20 20 searching for changes
21 21 no changes found
22 22 [1]
23 23
24 24 check that {1} syntax works
25 25
26 26 $ hg incoming --debug parts://localhost
27 27 using http://localhost:$HGPORT/
28 28 sending capabilities command
29 29 comparing with parts://localhost/
30 query 1; heads
30 31 sending heads command
31 32 searching for changes
32 sending known command
33 all remote heads known locally
33 34 no changes found
34 35 [1]
35 36
36 37 check that paths are expanded
37 38
38 39 $ PWD=`pwd` hg incoming z://
39 40 comparing with z://
40 41 searching for changes
41 42 no changes found
42 43 [1]
43 44
44 45 errors
45 46
46 47 $ cat errors.log
@@ -1,311 +1,310 b''
1 1
2 2
3 3 This test tries to exercise the ssh functionality with a dummy script
4 4
5 5 $ cat <<EOF > dummyssh
6 6 > import sys
7 7 > import os
8 8 >
9 9 > os.chdir(os.path.dirname(sys.argv[0]))
10 10 > if sys.argv[1] != "user@dummy":
11 11 > sys.exit(-1)
12 12 >
13 13 > if not os.path.exists("dummyssh"):
14 14 > sys.exit(-1)
15 15 >
16 16 > os.environ["SSH_CLIENT"] = "127.0.0.1 1 2"
17 17 >
18 18 > log = open("dummylog", "ab")
19 19 > log.write("Got arguments")
20 20 > for i, arg in enumerate(sys.argv[1:]):
21 21 > log.write(" %d:%s" % (i+1, arg))
22 22 > log.write("\n")
23 23 > log.close()
24 24 > r = os.system(sys.argv[2])
25 25 > sys.exit(bool(r))
26 26 > EOF
27 27 $ cat <<EOF > badhook
28 28 > import sys
29 29 > sys.stdout.write("KABOOM\n")
30 30 > EOF
31 31
32 32 creating 'remote
33 33
34 34 $ hg init remote
35 35 $ cd remote
36 36 $ echo this > foo
37 37 $ echo this > fooO
38 38 $ hg ci -A -m "init" foo fooO
39 39 $ echo <<EOF > .hg/hgrc
40 40 > [server]
41 41 > uncompressed = True
42 42 >
43 43 > [hooks]
44 44 > changegroup = python "$TESTDIR"/printenv.py changegroup-in-remote 0 ../dummylog
45 45 > EOF
46 46 $ cd ..
47 47
48 48 repo not found error
49 49
50 50 $ hg clone -e "python ./dummyssh" ssh://user@dummy/nonexistent local
51 51 remote: abort: There is no Mercurial repository here (.hg not found)!
52 52 abort: no suitable response from remote hg!
53 53 [255]
54 54
55 55 non-existent absolute path
56 56
57 57 $ hg clone -e "python ./dummyssh" ssh://user@dummy//`pwd`/nonexistent local
58 58 remote: abort: There is no Mercurial repository here (.hg not found)!
59 59 abort: no suitable response from remote hg!
60 60 [255]
61 61
62 62 clone remote via stream
63 63
64 64 $ hg clone -e "python ./dummyssh" --uncompressed ssh://user@dummy/remote local-stream
65 65 streaming all changes
66 66 4 files to transfer, 392 bytes of data
67 67 transferred 392 bytes in * seconds (*/sec) (glob)
68 68 updating to branch default
69 69 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
70 70 $ cd local-stream
71 71 $ hg verify
72 72 checking changesets
73 73 checking manifests
74 74 crosschecking files in changesets and manifests
75 75 checking files
76 76 2 files, 1 changesets, 2 total revisions
77 77 $ cd ..
78 78
79 79 clone remote via pull
80 80
81 81 $ hg clone -e "python ./dummyssh" ssh://user@dummy/remote local
82 82 requesting all changes
83 83 adding changesets
84 84 adding manifests
85 85 adding file changes
86 86 added 1 changesets with 2 changes to 2 files
87 87 updating to branch default
88 88 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
89 89
90 90 verify
91 91
92 92 $ cd local
93 93 $ hg verify
94 94 checking changesets
95 95 checking manifests
96 96 crosschecking files in changesets and manifests
97 97 checking files
98 98 2 files, 1 changesets, 2 total revisions
99 99 $ echo '[hooks]' >> .hg/hgrc
100 100 $ echo 'changegroup = python "$TESTDIR"/printenv.py changegroup-in-local 0 ../dummylog' >> .hg/hgrc
101 101
102 102 empty default pull
103 103
104 104 $ hg paths
105 105 default = ssh://user@dummy/remote
106 106 $ hg pull -e "python ../dummyssh"
107 107 pulling from ssh://user@dummy/remote
108 108 searching for changes
109 109 no changes found
110 110
111 111 local change
112 112
113 113 $ echo bleah > foo
114 114 $ hg ci -m "add"
115 115
116 116 updating rc
117 117
118 118 $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
119 119 $ echo "[ui]" >> .hg/hgrc
120 120 $ echo "ssh = python ../dummyssh" >> .hg/hgrc
121 121
122 122 find outgoing
123 123
124 124 $ hg out ssh://user@dummy/remote
125 125 comparing with ssh://user@dummy/remote
126 126 searching for changes
127 127 changeset: 1:a28a9d1a809c
128 128 tag: tip
129 129 user: test
130 130 date: Thu Jan 01 00:00:00 1970 +0000
131 131 summary: add
132 132
133 133
134 134 find incoming on the remote side
135 135
136 136 $ hg incoming -R ../remote -e "python ../dummyssh" ssh://user@dummy/local
137 137 comparing with ssh://user@dummy/local
138 138 searching for changes
139 139 changeset: 1:a28a9d1a809c
140 140 tag: tip
141 141 user: test
142 142 date: Thu Jan 01 00:00:00 1970 +0000
143 143 summary: add
144 144
145 145
146 146 find incoming on the remote side (using absolute path)
147 147
148 148 $ hg incoming -R ../remote -e "python ../dummyssh" "ssh://user@dummy/`pwd`"
149 149 comparing with ssh://user@dummy/$TESTTMP/local
150 150 searching for changes
151 151 changeset: 1:a28a9d1a809c
152 152 tag: tip
153 153 user: test
154 154 date: Thu Jan 01 00:00:00 1970 +0000
155 155 summary: add
156 156
157 157
158 158 push
159 159
160 160 $ hg push
161 161 pushing to ssh://user@dummy/remote
162 162 searching for changes
163 163 remote: adding changesets
164 164 remote: adding manifests
165 165 remote: adding file changes
166 166 remote: added 1 changesets with 1 changes to 1 files
167 167 $ cd ../remote
168 168
169 169 check remote tip
170 170
171 171 $ hg tip
172 172 changeset: 1:a28a9d1a809c
173 173 tag: tip
174 174 user: test
175 175 date: Thu Jan 01 00:00:00 1970 +0000
176 176 summary: add
177 177
178 178 $ hg verify
179 179 checking changesets
180 180 checking manifests
181 181 crosschecking files in changesets and manifests
182 182 checking files
183 183 2 files, 2 changesets, 3 total revisions
184 184 $ hg cat -r tip foo
185 185 bleah
186 186 $ echo z > z
187 187 $ hg ci -A -m z z
188 188 created new head
189 189
190 190 test pushkeys and bookmarks
191 191
192 192 $ cd ../local
193 193 $ hg debugpushkey --config ui.ssh="python ../dummyssh" ssh://user@dummy/remote namespaces
194 194 bookmarks
195 195 namespaces
196 196 $ hg book foo -r 0
197 197 $ hg out -B
198 198 comparing with ssh://user@dummy/remote
199 199 searching for changed bookmarks
200 200 foo 1160648e36ce
201 201 $ hg push -B foo
202 202 pushing to ssh://user@dummy/remote
203 203 searching for changes
204 204 no changes found
205 205 exporting bookmark foo
206 206 $ hg debugpushkey --config ui.ssh="python ../dummyssh" ssh://user@dummy/remote bookmarks
207 207 foo 1160648e36cec0054048a7edc4110c6f84fde594
208 208 $ hg book -f foo
209 209 $ hg push --traceback
210 210 pushing to ssh://user@dummy/remote
211 211 searching for changes
212 212 no changes found
213 213 updating bookmark foo
214 214 $ hg book -d foo
215 215 $ hg in -B
216 216 comparing with ssh://user@dummy/remote
217 217 searching for changed bookmarks
218 218 foo a28a9d1a809c
219 219 $ hg book -f -r 0 foo
220 220 $ hg pull -B foo
221 221 pulling from ssh://user@dummy/remote
222 searching for changes
223 222 no changes found
224 223 updating bookmark foo
225 224 importing bookmark foo
226 225 $ hg book -d foo
227 226 $ hg push -B foo
228 227 pushing to ssh://user@dummy/remote
229 228 searching for changes
230 229 no changes found
231 230 deleting remote bookmark foo
232 231
233 232 a bad, evil hook that prints to stdout
234 233
235 234 $ echo '[hooks]' >> ../remote/.hg/hgrc
236 235 $ echo 'changegroup.stdout = python ../badhook' >> ../remote/.hg/hgrc
237 236 $ echo r > r
238 237 $ hg ci -A -m z r
239 238
240 239 push should succeed even though it has an unexpected response
241 240
242 241 $ hg push
243 242 pushing to ssh://user@dummy/remote
244 243 searching for changes
245 244 note: unsynced remote changes!
246 245 remote: adding changesets
247 246 remote: adding manifests
248 247 remote: adding file changes
249 248 remote: added 1 changesets with 1 changes to 1 files
250 249 remote: KABOOM
251 250 $ hg -R ../remote heads
252 251 changeset: 3:1383141674ec
253 252 tag: tip
254 253 parent: 1:a28a9d1a809c
255 254 user: test
256 255 date: Thu Jan 01 00:00:00 1970 +0000
257 256 summary: z
258 257
259 258 changeset: 2:6c0482d977a3
260 259 parent: 0:1160648e36ce
261 260 user: test
262 261 date: Thu Jan 01 00:00:00 1970 +0000
263 262 summary: z
264 263
265 264
266 265 clone bookmarks
267 266
268 267 $ hg -R ../remote bookmark test
269 268 $ hg -R ../remote bookmarks
270 269 * test 2:6c0482d977a3
271 270 $ hg clone -e "python ../dummyssh" ssh://user@dummy/remote local-bookmarks
272 271 requesting all changes
273 272 adding changesets
274 273 adding manifests
275 274 adding file changes
276 275 added 4 changesets with 5 changes to 4 files (+1 heads)
277 276 updating to branch default
278 277 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
279 278 $ hg -R local-bookmarks bookmarks
280 279 test 2:6c0482d977a3
281 280
282 281 passwords in ssh urls are not supported
283 282 (we use a glob here because different Python versions give different
284 283 results here)
285 284
286 285 $ hg push ssh://user:erroneouspwd@dummy/remote
287 286 pushing to ssh://user:*@dummy/remote (glob)
288 287 abort: password in URL not supported!
289 288 [255]
290 289
291 290 $ cd ..
292 291 $ cat dummylog
293 292 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
294 293 Got arguments 1:user@dummy 2:hg -R /$TESTTMP/nonexistent serve --stdio
295 294 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
296 295 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
297 296 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
298 297 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
299 298 Got arguments 1:user@dummy 2:hg -R local serve --stdio
300 299 Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
301 300 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
302 301 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
303 302 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
304 303 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
305 304 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
306 305 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
307 306 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
308 307 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
309 308 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
310 309 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
311 310 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
General Comments 0
You need to be logged in to leave comments. Login now