##// END OF EJS Templates
merge: run update hook after the last wlock release...
Matt Harbison -
r24881:51844b8b stable
parent child Browse files
Show More
@@ -1,1205 +1,1207 b''
1 # merge.py - directory-level update/merge handling for Mercurial
1 # merge.py - directory-level update/merge handling for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import struct
8 import struct
9
9
10 from node import nullid, nullrev, hex, bin
10 from node import nullid, nullrev, hex, bin
11 from i18n import _
11 from i18n import _
12 from mercurial import obsolete
12 from mercurial import obsolete
13 import error as errormod, util, filemerge, copies, subrepo, worker
13 import error as errormod, util, filemerge, copies, subrepo, worker
14 import errno, os, shutil
14 import errno, os, shutil
15
15
16 _pack = struct.pack
16 _pack = struct.pack
17 _unpack = struct.unpack
17 _unpack = struct.unpack
18
18
19 def _droponode(data):
19 def _droponode(data):
20 # used for compatibility for v1
20 # used for compatibility for v1
21 bits = data.split('\0')
21 bits = data.split('\0')
22 bits = bits[:-2] + bits[-1:]
22 bits = bits[:-2] + bits[-1:]
23 return '\0'.join(bits)
23 return '\0'.join(bits)
24
24
25 class mergestate(object):
25 class mergestate(object):
26 '''track 3-way merge state of individual files
26 '''track 3-way merge state of individual files
27
27
28 it is stored on disk when needed. Two file are used, one with an old
28 it is stored on disk when needed. Two file are used, one with an old
29 format, one with a new format. Both contains similar data, but the new
29 format, one with a new format. Both contains similar data, but the new
30 format can store new kind of field.
30 format can store new kind of field.
31
31
32 Current new format is a list of arbitrary record of the form:
32 Current new format is a list of arbitrary record of the form:
33
33
34 [type][length][content]
34 [type][length][content]
35
35
36 Type is a single character, length is a 4 bytes integer, content is an
36 Type is a single character, length is a 4 bytes integer, content is an
37 arbitrary suites of bytes of length `length`.
37 arbitrary suites of bytes of length `length`.
38
38
39 Type should be a letter. Capital letter are mandatory record, Mercurial
39 Type should be a letter. Capital letter are mandatory record, Mercurial
40 should abort if they are unknown. lower case record can be safely ignored.
40 should abort if they are unknown. lower case record can be safely ignored.
41
41
42 Currently known record:
42 Currently known record:
43
43
44 L: the node of the "local" part of the merge (hexified version)
44 L: the node of the "local" part of the merge (hexified version)
45 O: the node of the "other" part of the merge (hexified version)
45 O: the node of the "other" part of the merge (hexified version)
46 F: a file to be merged entry
46 F: a file to be merged entry
47 '''
47 '''
48 statepathv1 = 'merge/state'
48 statepathv1 = 'merge/state'
49 statepathv2 = 'merge/state2'
49 statepathv2 = 'merge/state2'
50
50
51 def __init__(self, repo):
51 def __init__(self, repo):
52 self._repo = repo
52 self._repo = repo
53 self._dirty = False
53 self._dirty = False
54 self._read()
54 self._read()
55
55
56 def reset(self, node=None, other=None):
56 def reset(self, node=None, other=None):
57 self._state = {}
57 self._state = {}
58 self._local = None
58 self._local = None
59 self._other = None
59 self._other = None
60 if node:
60 if node:
61 self._local = node
61 self._local = node
62 self._other = other
62 self._other = other
63 shutil.rmtree(self._repo.join('merge'), True)
63 shutil.rmtree(self._repo.join('merge'), True)
64 self._dirty = False
64 self._dirty = False
65
65
66 def _read(self):
66 def _read(self):
67 """Analyse each record content to restore a serialized state from disk
67 """Analyse each record content to restore a serialized state from disk
68
68
69 This function process "record" entry produced by the de-serialization
69 This function process "record" entry produced by the de-serialization
70 of on disk file.
70 of on disk file.
71 """
71 """
72 self._state = {}
72 self._state = {}
73 self._local = None
73 self._local = None
74 self._other = None
74 self._other = None
75 records = self._readrecords()
75 records = self._readrecords()
76 for rtype, record in records:
76 for rtype, record in records:
77 if rtype == 'L':
77 if rtype == 'L':
78 self._local = bin(record)
78 self._local = bin(record)
79 elif rtype == 'O':
79 elif rtype == 'O':
80 self._other = bin(record)
80 self._other = bin(record)
81 elif rtype == 'F':
81 elif rtype == 'F':
82 bits = record.split('\0')
82 bits = record.split('\0')
83 self._state[bits[0]] = bits[1:]
83 self._state[bits[0]] = bits[1:]
84 elif not rtype.islower():
84 elif not rtype.islower():
85 raise util.Abort(_('unsupported merge state record: %s')
85 raise util.Abort(_('unsupported merge state record: %s')
86 % rtype)
86 % rtype)
87 self._dirty = False
87 self._dirty = False
88
88
89 def _readrecords(self):
89 def _readrecords(self):
90 """Read merge state from disk and return a list of record (TYPE, data)
90 """Read merge state from disk and return a list of record (TYPE, data)
91
91
92 We read data from both v1 and v2 files and decide which one to use.
92 We read data from both v1 and v2 files and decide which one to use.
93
93
94 V1 has been used by version prior to 2.9.1 and contains less data than
94 V1 has been used by version prior to 2.9.1 and contains less data than
95 v2. We read both versions and check if no data in v2 contradicts
95 v2. We read both versions and check if no data in v2 contradicts
96 v1. If there is not contradiction we can safely assume that both v1
96 v1. If there is not contradiction we can safely assume that both v1
97 and v2 were written at the same time and use the extract data in v2. If
97 and v2 were written at the same time and use the extract data in v2. If
98 there is contradiction we ignore v2 content as we assume an old version
98 there is contradiction we ignore v2 content as we assume an old version
99 of Mercurial has overwritten the mergestate file and left an old v2
99 of Mercurial has overwritten the mergestate file and left an old v2
100 file around.
100 file around.
101
101
102 returns list of record [(TYPE, data), ...]"""
102 returns list of record [(TYPE, data), ...]"""
103 v1records = self._readrecordsv1()
103 v1records = self._readrecordsv1()
104 v2records = self._readrecordsv2()
104 v2records = self._readrecordsv2()
105 oldv2 = set() # old format version of v2 record
105 oldv2 = set() # old format version of v2 record
106 for rec in v2records:
106 for rec in v2records:
107 if rec[0] == 'L':
107 if rec[0] == 'L':
108 oldv2.add(rec)
108 oldv2.add(rec)
109 elif rec[0] == 'F':
109 elif rec[0] == 'F':
110 # drop the onode data (not contained in v1)
110 # drop the onode data (not contained in v1)
111 oldv2.add(('F', _droponode(rec[1])))
111 oldv2.add(('F', _droponode(rec[1])))
112 for rec in v1records:
112 for rec in v1records:
113 if rec not in oldv2:
113 if rec not in oldv2:
114 # v1 file is newer than v2 file, use it
114 # v1 file is newer than v2 file, use it
115 # we have to infer the "other" changeset of the merge
115 # we have to infer the "other" changeset of the merge
116 # we cannot do better than that with v1 of the format
116 # we cannot do better than that with v1 of the format
117 mctx = self._repo[None].parents()[-1]
117 mctx = self._repo[None].parents()[-1]
118 v1records.append(('O', mctx.hex()))
118 v1records.append(('O', mctx.hex()))
119 # add place holder "other" file node information
119 # add place holder "other" file node information
120 # nobody is using it yet so we do no need to fetch the data
120 # nobody is using it yet so we do no need to fetch the data
121 # if mctx was wrong `mctx[bits[-2]]` may fails.
121 # if mctx was wrong `mctx[bits[-2]]` may fails.
122 for idx, r in enumerate(v1records):
122 for idx, r in enumerate(v1records):
123 if r[0] == 'F':
123 if r[0] == 'F':
124 bits = r[1].split('\0')
124 bits = r[1].split('\0')
125 bits.insert(-2, '')
125 bits.insert(-2, '')
126 v1records[idx] = (r[0], '\0'.join(bits))
126 v1records[idx] = (r[0], '\0'.join(bits))
127 return v1records
127 return v1records
128 else:
128 else:
129 return v2records
129 return v2records
130
130
131 def _readrecordsv1(self):
131 def _readrecordsv1(self):
132 """read on disk merge state for version 1 file
132 """read on disk merge state for version 1 file
133
133
134 returns list of record [(TYPE, data), ...]
134 returns list of record [(TYPE, data), ...]
135
135
136 Note: the "F" data from this file are one entry short
136 Note: the "F" data from this file are one entry short
137 (no "other file node" entry)
137 (no "other file node" entry)
138 """
138 """
139 records = []
139 records = []
140 try:
140 try:
141 f = self._repo.vfs(self.statepathv1)
141 f = self._repo.vfs(self.statepathv1)
142 for i, l in enumerate(f):
142 for i, l in enumerate(f):
143 if i == 0:
143 if i == 0:
144 records.append(('L', l[:-1]))
144 records.append(('L', l[:-1]))
145 else:
145 else:
146 records.append(('F', l[:-1]))
146 records.append(('F', l[:-1]))
147 f.close()
147 f.close()
148 except IOError, err:
148 except IOError, err:
149 if err.errno != errno.ENOENT:
149 if err.errno != errno.ENOENT:
150 raise
150 raise
151 return records
151 return records
152
152
153 def _readrecordsv2(self):
153 def _readrecordsv2(self):
154 """read on disk merge state for version 2 file
154 """read on disk merge state for version 2 file
155
155
156 returns list of record [(TYPE, data), ...]
156 returns list of record [(TYPE, data), ...]
157 """
157 """
158 records = []
158 records = []
159 try:
159 try:
160 f = self._repo.vfs(self.statepathv2)
160 f = self._repo.vfs(self.statepathv2)
161 data = f.read()
161 data = f.read()
162 off = 0
162 off = 0
163 end = len(data)
163 end = len(data)
164 while off < end:
164 while off < end:
165 rtype = data[off]
165 rtype = data[off]
166 off += 1
166 off += 1
167 length = _unpack('>I', data[off:(off + 4)])[0]
167 length = _unpack('>I', data[off:(off + 4)])[0]
168 off += 4
168 off += 4
169 record = data[off:(off + length)]
169 record = data[off:(off + length)]
170 off += length
170 off += length
171 records.append((rtype, record))
171 records.append((rtype, record))
172 f.close()
172 f.close()
173 except IOError, err:
173 except IOError, err:
174 if err.errno != errno.ENOENT:
174 if err.errno != errno.ENOENT:
175 raise
175 raise
176 return records
176 return records
177
177
178 def active(self):
178 def active(self):
179 """Whether mergestate is active.
179 """Whether mergestate is active.
180
180
181 Returns True if there appears to be mergestate. This is a rough proxy
181 Returns True if there appears to be mergestate. This is a rough proxy
182 for "is a merge in progress."
182 for "is a merge in progress."
183 """
183 """
184 # Check local variables before looking at filesystem for performance
184 # Check local variables before looking at filesystem for performance
185 # reasons.
185 # reasons.
186 return bool(self._local) or bool(self._state) or \
186 return bool(self._local) or bool(self._state) or \
187 self._repo.vfs.exists(self.statepathv1) or \
187 self._repo.vfs.exists(self.statepathv1) or \
188 self._repo.vfs.exists(self.statepathv2)
188 self._repo.vfs.exists(self.statepathv2)
189
189
190 def commit(self):
190 def commit(self):
191 """Write current state on disk (if necessary)"""
191 """Write current state on disk (if necessary)"""
192 if self._dirty:
192 if self._dirty:
193 records = []
193 records = []
194 records.append(('L', hex(self._local)))
194 records.append(('L', hex(self._local)))
195 records.append(('O', hex(self._other)))
195 records.append(('O', hex(self._other)))
196 for d, v in self._state.iteritems():
196 for d, v in self._state.iteritems():
197 records.append(('F', '\0'.join([d] + v)))
197 records.append(('F', '\0'.join([d] + v)))
198 self._writerecords(records)
198 self._writerecords(records)
199 self._dirty = False
199 self._dirty = False
200
200
201 def _writerecords(self, records):
201 def _writerecords(self, records):
202 """Write current state on disk (both v1 and v2)"""
202 """Write current state on disk (both v1 and v2)"""
203 self._writerecordsv1(records)
203 self._writerecordsv1(records)
204 self._writerecordsv2(records)
204 self._writerecordsv2(records)
205
205
206 def _writerecordsv1(self, records):
206 def _writerecordsv1(self, records):
207 """Write current state on disk in a version 1 file"""
207 """Write current state on disk in a version 1 file"""
208 f = self._repo.vfs(self.statepathv1, 'w')
208 f = self._repo.vfs(self.statepathv1, 'w')
209 irecords = iter(records)
209 irecords = iter(records)
210 lrecords = irecords.next()
210 lrecords = irecords.next()
211 assert lrecords[0] == 'L'
211 assert lrecords[0] == 'L'
212 f.write(hex(self._local) + '\n')
212 f.write(hex(self._local) + '\n')
213 for rtype, data in irecords:
213 for rtype, data in irecords:
214 if rtype == 'F':
214 if rtype == 'F':
215 f.write('%s\n' % _droponode(data))
215 f.write('%s\n' % _droponode(data))
216 f.close()
216 f.close()
217
217
218 def _writerecordsv2(self, records):
218 def _writerecordsv2(self, records):
219 """Write current state on disk in a version 2 file"""
219 """Write current state on disk in a version 2 file"""
220 f = self._repo.vfs(self.statepathv2, 'w')
220 f = self._repo.vfs(self.statepathv2, 'w')
221 for key, data in records:
221 for key, data in records:
222 assert len(key) == 1
222 assert len(key) == 1
223 format = '>sI%is' % len(data)
223 format = '>sI%is' % len(data)
224 f.write(_pack(format, key, len(data), data))
224 f.write(_pack(format, key, len(data), data))
225 f.close()
225 f.close()
226
226
227 def add(self, fcl, fco, fca, fd):
227 def add(self, fcl, fco, fca, fd):
228 """add a new (potentially?) conflicting file the merge state
228 """add a new (potentially?) conflicting file the merge state
229 fcl: file context for local,
229 fcl: file context for local,
230 fco: file context for remote,
230 fco: file context for remote,
231 fca: file context for ancestors,
231 fca: file context for ancestors,
232 fd: file path of the resulting merge.
232 fd: file path of the resulting merge.
233
233
234 note: also write the local version to the `.hg/merge` directory.
234 note: also write the local version to the `.hg/merge` directory.
235 """
235 """
236 hash = util.sha1(fcl.path()).hexdigest()
236 hash = util.sha1(fcl.path()).hexdigest()
237 self._repo.vfs.write('merge/' + hash, fcl.data())
237 self._repo.vfs.write('merge/' + hash, fcl.data())
238 self._state[fd] = ['u', hash, fcl.path(),
238 self._state[fd] = ['u', hash, fcl.path(),
239 fca.path(), hex(fca.filenode()),
239 fca.path(), hex(fca.filenode()),
240 fco.path(), hex(fco.filenode()),
240 fco.path(), hex(fco.filenode()),
241 fcl.flags()]
241 fcl.flags()]
242 self._dirty = True
242 self._dirty = True
243
243
244 def __contains__(self, dfile):
244 def __contains__(self, dfile):
245 return dfile in self._state
245 return dfile in self._state
246
246
247 def __getitem__(self, dfile):
247 def __getitem__(self, dfile):
248 return self._state[dfile][0]
248 return self._state[dfile][0]
249
249
250 def __iter__(self):
250 def __iter__(self):
251 return iter(sorted(self._state))
251 return iter(sorted(self._state))
252
252
253 def files(self):
253 def files(self):
254 return self._state.keys()
254 return self._state.keys()
255
255
256 def mark(self, dfile, state):
256 def mark(self, dfile, state):
257 self._state[dfile][0] = state
257 self._state[dfile][0] = state
258 self._dirty = True
258 self._dirty = True
259
259
260 def unresolved(self):
260 def unresolved(self):
261 """Obtain the paths of unresolved files."""
261 """Obtain the paths of unresolved files."""
262
262
263 for f, entry in self._state.items():
263 for f, entry in self._state.items():
264 if entry[0] == 'u':
264 if entry[0] == 'u':
265 yield f
265 yield f
266
266
267 def resolve(self, dfile, wctx, labels=None):
267 def resolve(self, dfile, wctx, labels=None):
268 """rerun merge process for file path `dfile`"""
268 """rerun merge process for file path `dfile`"""
269 if self[dfile] == 'r':
269 if self[dfile] == 'r':
270 return 0
270 return 0
271 stateentry = self._state[dfile]
271 stateentry = self._state[dfile]
272 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
272 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
273 octx = self._repo[self._other]
273 octx = self._repo[self._other]
274 fcd = wctx[dfile]
274 fcd = wctx[dfile]
275 fco = octx[ofile]
275 fco = octx[ofile]
276 fca = self._repo.filectx(afile, fileid=anode)
276 fca = self._repo.filectx(afile, fileid=anode)
277 # "premerge" x flags
277 # "premerge" x flags
278 flo = fco.flags()
278 flo = fco.flags()
279 fla = fca.flags()
279 fla = fca.flags()
280 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
280 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
281 if fca.node() == nullid:
281 if fca.node() == nullid:
282 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
282 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
283 afile)
283 afile)
284 elif flags == fla:
284 elif flags == fla:
285 flags = flo
285 flags = flo
286 # restore local
286 # restore local
287 f = self._repo.vfs('merge/' + hash)
287 f = self._repo.vfs('merge/' + hash)
288 self._repo.wwrite(dfile, f.read(), flags)
288 self._repo.wwrite(dfile, f.read(), flags)
289 f.close()
289 f.close()
290 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca,
290 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca,
291 labels=labels)
291 labels=labels)
292 if r is None:
292 if r is None:
293 # no real conflict
293 # no real conflict
294 del self._state[dfile]
294 del self._state[dfile]
295 self._dirty = True
295 self._dirty = True
296 elif not r:
296 elif not r:
297 self.mark(dfile, 'r')
297 self.mark(dfile, 'r')
298 return r
298 return r
299
299
300 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
300 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
301 if f2 is None:
301 if f2 is None:
302 f2 = f
302 f2 = f
303 return (os.path.isfile(repo.wjoin(f))
303 return (os.path.isfile(repo.wjoin(f))
304 and repo.wvfs.audit.check(f)
304 and repo.wvfs.audit.check(f)
305 and repo.dirstate.normalize(f) not in repo.dirstate
305 and repo.dirstate.normalize(f) not in repo.dirstate
306 and mctx[f2].cmp(wctx[f]))
306 and mctx[f2].cmp(wctx[f]))
307
307
308 def _checkunknownfiles(repo, wctx, mctx, force, actions):
308 def _checkunknownfiles(repo, wctx, mctx, force, actions):
309 """
309 """
310 Considers any actions that care about the presence of conflicting unknown
310 Considers any actions that care about the presence of conflicting unknown
311 files. For some actions, the result is to abort; for others, it is to
311 files. For some actions, the result is to abort; for others, it is to
312 choose a different action.
312 choose a different action.
313 """
313 """
314 aborts = []
314 aborts = []
315 if not force:
315 if not force:
316 for f, (m, args, msg) in actions.iteritems():
316 for f, (m, args, msg) in actions.iteritems():
317 if m in ('c', 'dc'):
317 if m in ('c', 'dc'):
318 if _checkunknownfile(repo, wctx, mctx, f):
318 if _checkunknownfile(repo, wctx, mctx, f):
319 aborts.append(f)
319 aborts.append(f)
320 elif m == 'dg':
320 elif m == 'dg':
321 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
321 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
322 aborts.append(f)
322 aborts.append(f)
323
323
324 for f in sorted(aborts):
324 for f in sorted(aborts):
325 repo.ui.warn(_("%s: untracked file differs\n") % f)
325 repo.ui.warn(_("%s: untracked file differs\n") % f)
326 if aborts:
326 if aborts:
327 raise util.Abort(_("untracked files in working directory differ "
327 raise util.Abort(_("untracked files in working directory differ "
328 "from files in requested revision"))
328 "from files in requested revision"))
329
329
330 for f, (m, args, msg) in actions.iteritems():
330 for f, (m, args, msg) in actions.iteritems():
331 if m == 'c':
331 if m == 'c':
332 actions[f] = ('g', args, msg)
332 actions[f] = ('g', args, msg)
333 elif m == 'cm':
333 elif m == 'cm':
334 fl2, anc = args
334 fl2, anc = args
335 different = _checkunknownfile(repo, wctx, mctx, f)
335 different = _checkunknownfile(repo, wctx, mctx, f)
336 if different:
336 if different:
337 actions[f] = ('m', (f, f, None, False, anc),
337 actions[f] = ('m', (f, f, None, False, anc),
338 "remote differs from untracked local")
338 "remote differs from untracked local")
339 else:
339 else:
340 actions[f] = ('g', (fl2,), "remote created")
340 actions[f] = ('g', (fl2,), "remote created")
341
341
342 def _forgetremoved(wctx, mctx, branchmerge):
342 def _forgetremoved(wctx, mctx, branchmerge):
343 """
343 """
344 Forget removed files
344 Forget removed files
345
345
346 If we're jumping between revisions (as opposed to merging), and if
346 If we're jumping between revisions (as opposed to merging), and if
347 neither the working directory nor the target rev has the file,
347 neither the working directory nor the target rev has the file,
348 then we need to remove it from the dirstate, to prevent the
348 then we need to remove it from the dirstate, to prevent the
349 dirstate from listing the file when it is no longer in the
349 dirstate from listing the file when it is no longer in the
350 manifest.
350 manifest.
351
351
352 If we're merging, and the other revision has removed a file
352 If we're merging, and the other revision has removed a file
353 that is not present in the working directory, we need to mark it
353 that is not present in the working directory, we need to mark it
354 as removed.
354 as removed.
355 """
355 """
356
356
357 actions = {}
357 actions = {}
358 m = 'f'
358 m = 'f'
359 if branchmerge:
359 if branchmerge:
360 m = 'r'
360 m = 'r'
361 for f in wctx.deleted():
361 for f in wctx.deleted():
362 if f not in mctx:
362 if f not in mctx:
363 actions[f] = m, None, "forget deleted"
363 actions[f] = m, None, "forget deleted"
364
364
365 if not branchmerge:
365 if not branchmerge:
366 for f in wctx.removed():
366 for f in wctx.removed():
367 if f not in mctx:
367 if f not in mctx:
368 actions[f] = 'f', None, "forget removed"
368 actions[f] = 'f', None, "forget removed"
369
369
370 return actions
370 return actions
371
371
372 def _checkcollision(repo, wmf, actions):
372 def _checkcollision(repo, wmf, actions):
373 # build provisional merged manifest up
373 # build provisional merged manifest up
374 pmmf = set(wmf)
374 pmmf = set(wmf)
375
375
376 if actions:
376 if actions:
377 # k, dr, e and rd are no-op
377 # k, dr, e and rd are no-op
378 for m in 'a', 'f', 'g', 'cd', 'dc':
378 for m in 'a', 'f', 'g', 'cd', 'dc':
379 for f, args, msg in actions[m]:
379 for f, args, msg in actions[m]:
380 pmmf.add(f)
380 pmmf.add(f)
381 for f, args, msg in actions['r']:
381 for f, args, msg in actions['r']:
382 pmmf.discard(f)
382 pmmf.discard(f)
383 for f, args, msg in actions['dm']:
383 for f, args, msg in actions['dm']:
384 f2, flags = args
384 f2, flags = args
385 pmmf.discard(f2)
385 pmmf.discard(f2)
386 pmmf.add(f)
386 pmmf.add(f)
387 for f, args, msg in actions['dg']:
387 for f, args, msg in actions['dg']:
388 pmmf.add(f)
388 pmmf.add(f)
389 for f, args, msg in actions['m']:
389 for f, args, msg in actions['m']:
390 f1, f2, fa, move, anc = args
390 f1, f2, fa, move, anc = args
391 if move:
391 if move:
392 pmmf.discard(f1)
392 pmmf.discard(f1)
393 pmmf.add(f)
393 pmmf.add(f)
394
394
395 # check case-folding collision in provisional merged manifest
395 # check case-folding collision in provisional merged manifest
396 foldmap = {}
396 foldmap = {}
397 for f in sorted(pmmf):
397 for f in sorted(pmmf):
398 fold = util.normcase(f)
398 fold = util.normcase(f)
399 if fold in foldmap:
399 if fold in foldmap:
400 raise util.Abort(_("case-folding collision between %s and %s")
400 raise util.Abort(_("case-folding collision between %s and %s")
401 % (f, foldmap[fold]))
401 % (f, foldmap[fold]))
402 foldmap[fold] = f
402 foldmap[fold] = f
403
403
404 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
404 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
405 acceptremote, followcopies):
405 acceptremote, followcopies):
406 """
406 """
407 Merge p1 and p2 with ancestor pa and generate merge action list
407 Merge p1 and p2 with ancestor pa and generate merge action list
408
408
409 branchmerge and force are as passed in to update
409 branchmerge and force are as passed in to update
410 partial = function to filter file lists
410 partial = function to filter file lists
411 acceptremote = accept the incoming changes without prompting
411 acceptremote = accept the incoming changes without prompting
412 """
412 """
413
413
414 copy, movewithdir, diverge, renamedelete = {}, {}, {}, {}
414 copy, movewithdir, diverge, renamedelete = {}, {}, {}, {}
415
415
416 # manifests fetched in order are going to be faster, so prime the caches
416 # manifests fetched in order are going to be faster, so prime the caches
417 [x.manifest() for x in
417 [x.manifest() for x in
418 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
418 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
419
419
420 if followcopies:
420 if followcopies:
421 ret = copies.mergecopies(repo, wctx, p2, pa)
421 ret = copies.mergecopies(repo, wctx, p2, pa)
422 copy, movewithdir, diverge, renamedelete = ret
422 copy, movewithdir, diverge, renamedelete = ret
423
423
424 repo.ui.note(_("resolving manifests\n"))
424 repo.ui.note(_("resolving manifests\n"))
425 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
425 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
426 % (bool(branchmerge), bool(force), bool(partial)))
426 % (bool(branchmerge), bool(force), bool(partial)))
427 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
427 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
428
428
429 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
429 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
430 copied = set(copy.values())
430 copied = set(copy.values())
431 copied.update(movewithdir.values())
431 copied.update(movewithdir.values())
432
432
433 if '.hgsubstate' in m1:
433 if '.hgsubstate' in m1:
434 # check whether sub state is modified
434 # check whether sub state is modified
435 for s in sorted(wctx.substate):
435 for s in sorted(wctx.substate):
436 if wctx.sub(s).dirty():
436 if wctx.sub(s).dirty():
437 m1['.hgsubstate'] += '+'
437 m1['.hgsubstate'] += '+'
438 break
438 break
439
439
440 # Compare manifests
440 # Compare manifests
441 diff = m1.diff(m2)
441 diff = m1.diff(m2)
442
442
443 actions = {}
443 actions = {}
444 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
444 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
445 if partial and not partial(f):
445 if partial and not partial(f):
446 continue
446 continue
447 if n1 and n2: # file exists on both local and remote side
447 if n1 and n2: # file exists on both local and remote side
448 if f not in ma:
448 if f not in ma:
449 fa = copy.get(f, None)
449 fa = copy.get(f, None)
450 if fa is not None:
450 if fa is not None:
451 actions[f] = ('m', (f, f, fa, False, pa.node()),
451 actions[f] = ('m', (f, f, fa, False, pa.node()),
452 "both renamed from " + fa)
452 "both renamed from " + fa)
453 else:
453 else:
454 actions[f] = ('m', (f, f, None, False, pa.node()),
454 actions[f] = ('m', (f, f, None, False, pa.node()),
455 "both created")
455 "both created")
456 else:
456 else:
457 a = ma[f]
457 a = ma[f]
458 fla = ma.flags(f)
458 fla = ma.flags(f)
459 nol = 'l' not in fl1 + fl2 + fla
459 nol = 'l' not in fl1 + fl2 + fla
460 if n2 == a and fl2 == fla:
460 if n2 == a and fl2 == fla:
461 actions[f] = ('k' , (), "remote unchanged")
461 actions[f] = ('k' , (), "remote unchanged")
462 elif n1 == a and fl1 == fla: # local unchanged - use remote
462 elif n1 == a and fl1 == fla: # local unchanged - use remote
463 if n1 == n2: # optimization: keep local content
463 if n1 == n2: # optimization: keep local content
464 actions[f] = ('e', (fl2,), "update permissions")
464 actions[f] = ('e', (fl2,), "update permissions")
465 else:
465 else:
466 actions[f] = ('g', (fl2,), "remote is newer")
466 actions[f] = ('g', (fl2,), "remote is newer")
467 elif nol and n2 == a: # remote only changed 'x'
467 elif nol and n2 == a: # remote only changed 'x'
468 actions[f] = ('e', (fl2,), "update permissions")
468 actions[f] = ('e', (fl2,), "update permissions")
469 elif nol and n1 == a: # local only changed 'x'
469 elif nol and n1 == a: # local only changed 'x'
470 actions[f] = ('g', (fl1,), "remote is newer")
470 actions[f] = ('g', (fl1,), "remote is newer")
471 else: # both changed something
471 else: # both changed something
472 actions[f] = ('m', (f, f, f, False, pa.node()),
472 actions[f] = ('m', (f, f, f, False, pa.node()),
473 "versions differ")
473 "versions differ")
474 elif n1: # file exists only on local side
474 elif n1: # file exists only on local side
475 if f in copied:
475 if f in copied:
476 pass # we'll deal with it on m2 side
476 pass # we'll deal with it on m2 side
477 elif f in movewithdir: # directory rename, move local
477 elif f in movewithdir: # directory rename, move local
478 f2 = movewithdir[f]
478 f2 = movewithdir[f]
479 if f2 in m2:
479 if f2 in m2:
480 actions[f2] = ('m', (f, f2, None, True, pa.node()),
480 actions[f2] = ('m', (f, f2, None, True, pa.node()),
481 "remote directory rename, both created")
481 "remote directory rename, both created")
482 else:
482 else:
483 actions[f2] = ('dm', (f, fl1),
483 actions[f2] = ('dm', (f, fl1),
484 "remote directory rename - move from " + f)
484 "remote directory rename - move from " + f)
485 elif f in copy:
485 elif f in copy:
486 f2 = copy[f]
486 f2 = copy[f]
487 actions[f] = ('m', (f, f2, f2, False, pa.node()),
487 actions[f] = ('m', (f, f2, f2, False, pa.node()),
488 "local copied/moved from " + f2)
488 "local copied/moved from " + f2)
489 elif f in ma: # clean, a different, no remote
489 elif f in ma: # clean, a different, no remote
490 if n1 != ma[f]:
490 if n1 != ma[f]:
491 if acceptremote:
491 if acceptremote:
492 actions[f] = ('r', None, "remote delete")
492 actions[f] = ('r', None, "remote delete")
493 else:
493 else:
494 actions[f] = ('cd', None, "prompt changed/deleted")
494 actions[f] = ('cd', None, "prompt changed/deleted")
495 elif n1[20:] == 'a':
495 elif n1[20:] == 'a':
496 # This extra 'a' is added by working copy manifest to mark
496 # This extra 'a' is added by working copy manifest to mark
497 # the file as locally added. We should forget it instead of
497 # the file as locally added. We should forget it instead of
498 # deleting it.
498 # deleting it.
499 actions[f] = ('f', None, "remote deleted")
499 actions[f] = ('f', None, "remote deleted")
500 else:
500 else:
501 actions[f] = ('r', None, "other deleted")
501 actions[f] = ('r', None, "other deleted")
502 elif n2: # file exists only on remote side
502 elif n2: # file exists only on remote side
503 if f in copied:
503 if f in copied:
504 pass # we'll deal with it on m1 side
504 pass # we'll deal with it on m1 side
505 elif f in movewithdir:
505 elif f in movewithdir:
506 f2 = movewithdir[f]
506 f2 = movewithdir[f]
507 if f2 in m1:
507 if f2 in m1:
508 actions[f2] = ('m', (f2, f, None, False, pa.node()),
508 actions[f2] = ('m', (f2, f, None, False, pa.node()),
509 "local directory rename, both created")
509 "local directory rename, both created")
510 else:
510 else:
511 actions[f2] = ('dg', (f, fl2),
511 actions[f2] = ('dg', (f, fl2),
512 "local directory rename - get from " + f)
512 "local directory rename - get from " + f)
513 elif f in copy:
513 elif f in copy:
514 f2 = copy[f]
514 f2 = copy[f]
515 if f2 in m2:
515 if f2 in m2:
516 actions[f] = ('m', (f2, f, f2, False, pa.node()),
516 actions[f] = ('m', (f2, f, f2, False, pa.node()),
517 "remote copied from " + f2)
517 "remote copied from " + f2)
518 else:
518 else:
519 actions[f] = ('m', (f2, f, f2, True, pa.node()),
519 actions[f] = ('m', (f2, f, f2, True, pa.node()),
520 "remote moved from " + f2)
520 "remote moved from " + f2)
521 elif f not in ma:
521 elif f not in ma:
522 # local unknown, remote created: the logic is described by the
522 # local unknown, remote created: the logic is described by the
523 # following table:
523 # following table:
524 #
524 #
525 # force branchmerge different | action
525 # force branchmerge different | action
526 # n * * | create
526 # n * * | create
527 # y n * | create
527 # y n * | create
528 # y y n | create
528 # y y n | create
529 # y y y | merge
529 # y y y | merge
530 #
530 #
531 # Checking whether the files are different is expensive, so we
531 # Checking whether the files are different is expensive, so we
532 # don't do that when we can avoid it.
532 # don't do that when we can avoid it.
533 if not force:
533 if not force:
534 actions[f] = ('c', (fl2,), "remote created")
534 actions[f] = ('c', (fl2,), "remote created")
535 elif not branchmerge:
535 elif not branchmerge:
536 actions[f] = ('c', (fl2,), "remote created")
536 actions[f] = ('c', (fl2,), "remote created")
537 else:
537 else:
538 actions[f] = ('cm', (fl2, pa.node()),
538 actions[f] = ('cm', (fl2, pa.node()),
539 "remote created, get or merge")
539 "remote created, get or merge")
540 elif n2 != ma[f]:
540 elif n2 != ma[f]:
541 if acceptremote:
541 if acceptremote:
542 actions[f] = ('c', (fl2,), "remote recreating")
542 actions[f] = ('c', (fl2,), "remote recreating")
543 else:
543 else:
544 actions[f] = ('dc', (fl2,), "prompt deleted/changed")
544 actions[f] = ('dc', (fl2,), "prompt deleted/changed")
545
545
546 return actions, diverge, renamedelete
546 return actions, diverge, renamedelete
547
547
548 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
548 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
549 """Resolves false conflicts where the nodeid changed but the content
549 """Resolves false conflicts where the nodeid changed but the content
550 remained the same."""
550 remained the same."""
551
551
552 for f, (m, args, msg) in actions.items():
552 for f, (m, args, msg) in actions.items():
553 if m == 'cd' and f in ancestor and not wctx[f].cmp(ancestor[f]):
553 if m == 'cd' and f in ancestor and not wctx[f].cmp(ancestor[f]):
554 # local did change but ended up with same content
554 # local did change but ended up with same content
555 actions[f] = 'r', None, "prompt same"
555 actions[f] = 'r', None, "prompt same"
556 elif m == 'dc' and f in ancestor and not mctx[f].cmp(ancestor[f]):
556 elif m == 'dc' and f in ancestor and not mctx[f].cmp(ancestor[f]):
557 # remote did change but ended up with same content
557 # remote did change but ended up with same content
558 del actions[f] # don't get = keep local deleted
558 del actions[f] # don't get = keep local deleted
559
559
560 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, partial,
560 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, partial,
561 acceptremote, followcopies):
561 acceptremote, followcopies):
562 "Calculate the actions needed to merge mctx into wctx using ancestors"
562 "Calculate the actions needed to merge mctx into wctx using ancestors"
563
563
564 if len(ancestors) == 1: # default
564 if len(ancestors) == 1: # default
565 actions, diverge, renamedelete = manifestmerge(
565 actions, diverge, renamedelete = manifestmerge(
566 repo, wctx, mctx, ancestors[0], branchmerge, force, partial,
566 repo, wctx, mctx, ancestors[0], branchmerge, force, partial,
567 acceptremote, followcopies)
567 acceptremote, followcopies)
568 _checkunknownfiles(repo, wctx, mctx, force, actions)
568 _checkunknownfiles(repo, wctx, mctx, force, actions)
569
569
570 else: # only when merge.preferancestor=* - the default
570 else: # only when merge.preferancestor=* - the default
571 repo.ui.note(
571 repo.ui.note(
572 _("note: merging %s and %s using bids from ancestors %s\n") %
572 _("note: merging %s and %s using bids from ancestors %s\n") %
573 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
573 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
574
574
575 # Call for bids
575 # Call for bids
576 fbids = {} # mapping filename to bids (action method to list af actions)
576 fbids = {} # mapping filename to bids (action method to list af actions)
577 diverge, renamedelete = None, None
577 diverge, renamedelete = None, None
578 for ancestor in ancestors:
578 for ancestor in ancestors:
579 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
579 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
580 actions, diverge1, renamedelete1 = manifestmerge(
580 actions, diverge1, renamedelete1 = manifestmerge(
581 repo, wctx, mctx, ancestor, branchmerge, force, partial,
581 repo, wctx, mctx, ancestor, branchmerge, force, partial,
582 acceptremote, followcopies)
582 acceptremote, followcopies)
583 _checkunknownfiles(repo, wctx, mctx, force, actions)
583 _checkunknownfiles(repo, wctx, mctx, force, actions)
584 if diverge is None: # and renamedelete is None.
584 if diverge is None: # and renamedelete is None.
585 # Arbitrarily pick warnings from first iteration
585 # Arbitrarily pick warnings from first iteration
586 diverge = diverge1
586 diverge = diverge1
587 renamedelete = renamedelete1
587 renamedelete = renamedelete1
588 for f, a in sorted(actions.iteritems()):
588 for f, a in sorted(actions.iteritems()):
589 m, args, msg = a
589 m, args, msg = a
590 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
590 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
591 if f in fbids:
591 if f in fbids:
592 d = fbids[f]
592 d = fbids[f]
593 if m in d:
593 if m in d:
594 d[m].append(a)
594 d[m].append(a)
595 else:
595 else:
596 d[m] = [a]
596 d[m] = [a]
597 else:
597 else:
598 fbids[f] = {m: [a]}
598 fbids[f] = {m: [a]}
599
599
600 # Pick the best bid for each file
600 # Pick the best bid for each file
601 repo.ui.note(_('\nauction for merging merge bids\n'))
601 repo.ui.note(_('\nauction for merging merge bids\n'))
602 actions = {}
602 actions = {}
603 for f, bids in sorted(fbids.items()):
603 for f, bids in sorted(fbids.items()):
604 # bids is a mapping from action method to list af actions
604 # bids is a mapping from action method to list af actions
605 # Consensus?
605 # Consensus?
606 if len(bids) == 1: # all bids are the same kind of method
606 if len(bids) == 1: # all bids are the same kind of method
607 m, l = bids.items()[0]
607 m, l = bids.items()[0]
608 if util.all(a == l[0] for a in l[1:]): # len(bids) is > 1
608 if util.all(a == l[0] for a in l[1:]): # len(bids) is > 1
609 repo.ui.note(" %s: consensus for %s\n" % (f, m))
609 repo.ui.note(" %s: consensus for %s\n" % (f, m))
610 actions[f] = l[0]
610 actions[f] = l[0]
611 continue
611 continue
612 # If keep is an option, just do it.
612 # If keep is an option, just do it.
613 if 'k' in bids:
613 if 'k' in bids:
614 repo.ui.note(" %s: picking 'keep' action\n" % f)
614 repo.ui.note(" %s: picking 'keep' action\n" % f)
615 actions[f] = bids['k'][0]
615 actions[f] = bids['k'][0]
616 continue
616 continue
617 # If there are gets and they all agree [how could they not?], do it.
617 # If there are gets and they all agree [how could they not?], do it.
618 if 'g' in bids:
618 if 'g' in bids:
619 ga0 = bids['g'][0]
619 ga0 = bids['g'][0]
620 if util.all(a == ga0 for a in bids['g'][1:]):
620 if util.all(a == ga0 for a in bids['g'][1:]):
621 repo.ui.note(" %s: picking 'get' action\n" % f)
621 repo.ui.note(" %s: picking 'get' action\n" % f)
622 actions[f] = ga0
622 actions[f] = ga0
623 continue
623 continue
624 # TODO: Consider other simple actions such as mode changes
624 # TODO: Consider other simple actions such as mode changes
625 # Handle inefficient democrazy.
625 # Handle inefficient democrazy.
626 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
626 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
627 for m, l in sorted(bids.items()):
627 for m, l in sorted(bids.items()):
628 for _f, args, msg in l:
628 for _f, args, msg in l:
629 repo.ui.note(' %s -> %s\n' % (msg, m))
629 repo.ui.note(' %s -> %s\n' % (msg, m))
630 # Pick random action. TODO: Instead, prompt user when resolving
630 # Pick random action. TODO: Instead, prompt user when resolving
631 m, l = bids.items()[0]
631 m, l = bids.items()[0]
632 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
632 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
633 (f, m))
633 (f, m))
634 actions[f] = l[0]
634 actions[f] = l[0]
635 continue
635 continue
636 repo.ui.note(_('end of auction\n\n'))
636 repo.ui.note(_('end of auction\n\n'))
637
637
638 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
638 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
639
639
640 if wctx.rev() is None:
640 if wctx.rev() is None:
641 fractions = _forgetremoved(wctx, mctx, branchmerge)
641 fractions = _forgetremoved(wctx, mctx, branchmerge)
642 actions.update(fractions)
642 actions.update(fractions)
643
643
644 return actions, diverge, renamedelete
644 return actions, diverge, renamedelete
645
645
646 def batchremove(repo, actions):
646 def batchremove(repo, actions):
647 """apply removes to the working directory
647 """apply removes to the working directory
648
648
649 yields tuples for progress updates
649 yields tuples for progress updates
650 """
650 """
651 verbose = repo.ui.verbose
651 verbose = repo.ui.verbose
652 unlink = util.unlinkpath
652 unlink = util.unlinkpath
653 wjoin = repo.wjoin
653 wjoin = repo.wjoin
654 audit = repo.wvfs.audit
654 audit = repo.wvfs.audit
655 i = 0
655 i = 0
656 for f, args, msg in actions:
656 for f, args, msg in actions:
657 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
657 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
658 if verbose:
658 if verbose:
659 repo.ui.note(_("removing %s\n") % f)
659 repo.ui.note(_("removing %s\n") % f)
660 audit(f)
660 audit(f)
661 try:
661 try:
662 unlink(wjoin(f), ignoremissing=True)
662 unlink(wjoin(f), ignoremissing=True)
663 except OSError, inst:
663 except OSError, inst:
664 repo.ui.warn(_("update failed to remove %s: %s!\n") %
664 repo.ui.warn(_("update failed to remove %s: %s!\n") %
665 (f, inst.strerror))
665 (f, inst.strerror))
666 if i == 100:
666 if i == 100:
667 yield i, f
667 yield i, f
668 i = 0
668 i = 0
669 i += 1
669 i += 1
670 if i > 0:
670 if i > 0:
671 yield i, f
671 yield i, f
672
672
673 def batchget(repo, mctx, actions):
673 def batchget(repo, mctx, actions):
674 """apply gets to the working directory
674 """apply gets to the working directory
675
675
676 mctx is the context to get from
676 mctx is the context to get from
677
677
678 yields tuples for progress updates
678 yields tuples for progress updates
679 """
679 """
680 verbose = repo.ui.verbose
680 verbose = repo.ui.verbose
681 fctx = mctx.filectx
681 fctx = mctx.filectx
682 wwrite = repo.wwrite
682 wwrite = repo.wwrite
683 i = 0
683 i = 0
684 for f, args, msg in actions:
684 for f, args, msg in actions:
685 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
685 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
686 if verbose:
686 if verbose:
687 repo.ui.note(_("getting %s\n") % f)
687 repo.ui.note(_("getting %s\n") % f)
688 wwrite(f, fctx(f).data(), args[0])
688 wwrite(f, fctx(f).data(), args[0])
689 if i == 100:
689 if i == 100:
690 yield i, f
690 yield i, f
691 i = 0
691 i = 0
692 i += 1
692 i += 1
693 if i > 0:
693 if i > 0:
694 yield i, f
694 yield i, f
695
695
696 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
696 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
697 """apply the merge action list to the working directory
697 """apply the merge action list to the working directory
698
698
699 wctx is the working copy context
699 wctx is the working copy context
700 mctx is the context to be merged into the working copy
700 mctx is the context to be merged into the working copy
701
701
702 Return a tuple of counts (updated, merged, removed, unresolved) that
702 Return a tuple of counts (updated, merged, removed, unresolved) that
703 describes how many files were affected by the update.
703 describes how many files were affected by the update.
704 """
704 """
705
705
706 updated, merged, removed, unresolved = 0, 0, 0, 0
706 updated, merged, removed, unresolved = 0, 0, 0, 0
707 ms = mergestate(repo)
707 ms = mergestate(repo)
708 ms.reset(wctx.p1().node(), mctx.node())
708 ms.reset(wctx.p1().node(), mctx.node())
709 moves = []
709 moves = []
710 for m, l in actions.items():
710 for m, l in actions.items():
711 l.sort()
711 l.sort()
712
712
713 # prescan for merges
713 # prescan for merges
714 for f, args, msg in actions['m']:
714 for f, args, msg in actions['m']:
715 f1, f2, fa, move, anc = args
715 f1, f2, fa, move, anc = args
716 if f == '.hgsubstate': # merged internally
716 if f == '.hgsubstate': # merged internally
717 continue
717 continue
718 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
718 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
719 fcl = wctx[f1]
719 fcl = wctx[f1]
720 fco = mctx[f2]
720 fco = mctx[f2]
721 actx = repo[anc]
721 actx = repo[anc]
722 if fa in actx:
722 if fa in actx:
723 fca = actx[fa]
723 fca = actx[fa]
724 else:
724 else:
725 fca = repo.filectx(f1, fileid=nullrev)
725 fca = repo.filectx(f1, fileid=nullrev)
726 ms.add(fcl, fco, fca, f)
726 ms.add(fcl, fco, fca, f)
727 if f1 != f and move:
727 if f1 != f and move:
728 moves.append(f1)
728 moves.append(f1)
729
729
730 audit = repo.wvfs.audit
730 audit = repo.wvfs.audit
731 _updating = _('updating')
731 _updating = _('updating')
732 _files = _('files')
732 _files = _('files')
733 progress = repo.ui.progress
733 progress = repo.ui.progress
734
734
735 # remove renamed files after safely stored
735 # remove renamed files after safely stored
736 for f in moves:
736 for f in moves:
737 if os.path.lexists(repo.wjoin(f)):
737 if os.path.lexists(repo.wjoin(f)):
738 repo.ui.debug("removing %s\n" % f)
738 repo.ui.debug("removing %s\n" % f)
739 audit(f)
739 audit(f)
740 util.unlinkpath(repo.wjoin(f))
740 util.unlinkpath(repo.wjoin(f))
741
741
742 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
742 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
743
743
744 def dirtysubstate():
744 def dirtysubstate():
745 # mark '.hgsubstate' as possibly dirty forcibly, because
745 # mark '.hgsubstate' as possibly dirty forcibly, because
746 # modified '.hgsubstate' is misunderstood as clean,
746 # modified '.hgsubstate' is misunderstood as clean,
747 # when both st_size/st_mtime of '.hgsubstate' aren't changed,
747 # when both st_size/st_mtime of '.hgsubstate' aren't changed,
748 # even if "submerge" fails and '.hgsubstate' is inconsistent
748 # even if "submerge" fails and '.hgsubstate' is inconsistent
749 repo.dirstate.normallookup('.hgsubstate')
749 repo.dirstate.normallookup('.hgsubstate')
750
750
751 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
751 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
752 dirtysubstate()
752 dirtysubstate()
753 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
753 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
754
754
755 # remove in parallel (must come first)
755 # remove in parallel (must come first)
756 z = 0
756 z = 0
757 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
757 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
758 for i, item in prog:
758 for i, item in prog:
759 z += i
759 z += i
760 progress(_updating, z, item=item, total=numupdates, unit=_files)
760 progress(_updating, z, item=item, total=numupdates, unit=_files)
761 removed = len(actions['r'])
761 removed = len(actions['r'])
762
762
763 # get in parallel
763 # get in parallel
764 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
764 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
765 for i, item in prog:
765 for i, item in prog:
766 z += i
766 z += i
767 progress(_updating, z, item=item, total=numupdates, unit=_files)
767 progress(_updating, z, item=item, total=numupdates, unit=_files)
768 updated = len(actions['g'])
768 updated = len(actions['g'])
769
769
770 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
770 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
771 dirtysubstate()
771 dirtysubstate()
772 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
772 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
773
773
774 # forget (manifest only, just log it) (must come first)
774 # forget (manifest only, just log it) (must come first)
775 for f, args, msg in actions['f']:
775 for f, args, msg in actions['f']:
776 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
776 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
777 z += 1
777 z += 1
778 progress(_updating, z, item=f, total=numupdates, unit=_files)
778 progress(_updating, z, item=f, total=numupdates, unit=_files)
779
779
780 # re-add (manifest only, just log it)
780 # re-add (manifest only, just log it)
781 for f, args, msg in actions['a']:
781 for f, args, msg in actions['a']:
782 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
782 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
783 z += 1
783 z += 1
784 progress(_updating, z, item=f, total=numupdates, unit=_files)
784 progress(_updating, z, item=f, total=numupdates, unit=_files)
785
785
786 # keep (noop, just log it)
786 # keep (noop, just log it)
787 for f, args, msg in actions['k']:
787 for f, args, msg in actions['k']:
788 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
788 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
789 # no progress
789 # no progress
790
790
791 # merge
791 # merge
792 for f, args, msg in actions['m']:
792 for f, args, msg in actions['m']:
793 repo.ui.debug(" %s: %s -> m\n" % (f, msg))
793 repo.ui.debug(" %s: %s -> m\n" % (f, msg))
794 z += 1
794 z += 1
795 progress(_updating, z, item=f, total=numupdates, unit=_files)
795 progress(_updating, z, item=f, total=numupdates, unit=_files)
796 if f == '.hgsubstate': # subrepo states need updating
796 if f == '.hgsubstate': # subrepo states need updating
797 dirtysubstate()
797 dirtysubstate()
798 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
798 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
799 overwrite)
799 overwrite)
800 continue
800 continue
801 audit(f)
801 audit(f)
802 r = ms.resolve(f, wctx, labels=labels)
802 r = ms.resolve(f, wctx, labels=labels)
803 if r is not None and r > 0:
803 if r is not None and r > 0:
804 unresolved += 1
804 unresolved += 1
805 else:
805 else:
806 if r is None:
806 if r is None:
807 updated += 1
807 updated += 1
808 else:
808 else:
809 merged += 1
809 merged += 1
810
810
811 # directory rename, move local
811 # directory rename, move local
812 for f, args, msg in actions['dm']:
812 for f, args, msg in actions['dm']:
813 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
813 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
814 z += 1
814 z += 1
815 progress(_updating, z, item=f, total=numupdates, unit=_files)
815 progress(_updating, z, item=f, total=numupdates, unit=_files)
816 f0, flags = args
816 f0, flags = args
817 repo.ui.note(_("moving %s to %s\n") % (f0, f))
817 repo.ui.note(_("moving %s to %s\n") % (f0, f))
818 audit(f)
818 audit(f)
819 repo.wwrite(f, wctx.filectx(f0).data(), flags)
819 repo.wwrite(f, wctx.filectx(f0).data(), flags)
820 util.unlinkpath(repo.wjoin(f0))
820 util.unlinkpath(repo.wjoin(f0))
821 updated += 1
821 updated += 1
822
822
823 # local directory rename, get
823 # local directory rename, get
824 for f, args, msg in actions['dg']:
824 for f, args, msg in actions['dg']:
825 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
825 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
826 z += 1
826 z += 1
827 progress(_updating, z, item=f, total=numupdates, unit=_files)
827 progress(_updating, z, item=f, total=numupdates, unit=_files)
828 f0, flags = args
828 f0, flags = args
829 repo.ui.note(_("getting %s to %s\n") % (f0, f))
829 repo.ui.note(_("getting %s to %s\n") % (f0, f))
830 repo.wwrite(f, mctx.filectx(f0).data(), flags)
830 repo.wwrite(f, mctx.filectx(f0).data(), flags)
831 updated += 1
831 updated += 1
832
832
833 # exec
833 # exec
834 for f, args, msg in actions['e']:
834 for f, args, msg in actions['e']:
835 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
835 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
836 z += 1
836 z += 1
837 progress(_updating, z, item=f, total=numupdates, unit=_files)
837 progress(_updating, z, item=f, total=numupdates, unit=_files)
838 flags, = args
838 flags, = args
839 audit(f)
839 audit(f)
840 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
840 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
841 updated += 1
841 updated += 1
842
842
843 ms.commit()
843 ms.commit()
844 progress(_updating, None, total=numupdates, unit=_files)
844 progress(_updating, None, total=numupdates, unit=_files)
845
845
846 return updated, merged, removed, unresolved
846 return updated, merged, removed, unresolved
847
847
848 def recordupdates(repo, actions, branchmerge):
848 def recordupdates(repo, actions, branchmerge):
849 "record merge actions to the dirstate"
849 "record merge actions to the dirstate"
850 # remove (must come first)
850 # remove (must come first)
851 for f, args, msg in actions['r']:
851 for f, args, msg in actions['r']:
852 if branchmerge:
852 if branchmerge:
853 repo.dirstate.remove(f)
853 repo.dirstate.remove(f)
854 else:
854 else:
855 repo.dirstate.drop(f)
855 repo.dirstate.drop(f)
856
856
857 # forget (must come first)
857 # forget (must come first)
858 for f, args, msg in actions['f']:
858 for f, args, msg in actions['f']:
859 repo.dirstate.drop(f)
859 repo.dirstate.drop(f)
860
860
861 # re-add
861 # re-add
862 for f, args, msg in actions['a']:
862 for f, args, msg in actions['a']:
863 if not branchmerge:
863 if not branchmerge:
864 repo.dirstate.add(f)
864 repo.dirstate.add(f)
865
865
866 # exec change
866 # exec change
867 for f, args, msg in actions['e']:
867 for f, args, msg in actions['e']:
868 repo.dirstate.normallookup(f)
868 repo.dirstate.normallookup(f)
869
869
870 # keep
870 # keep
871 for f, args, msg in actions['k']:
871 for f, args, msg in actions['k']:
872 pass
872 pass
873
873
874 # get
874 # get
875 for f, args, msg in actions['g']:
875 for f, args, msg in actions['g']:
876 if branchmerge:
876 if branchmerge:
877 repo.dirstate.otherparent(f)
877 repo.dirstate.otherparent(f)
878 else:
878 else:
879 repo.dirstate.normal(f)
879 repo.dirstate.normal(f)
880
880
881 # merge
881 # merge
882 for f, args, msg in actions['m']:
882 for f, args, msg in actions['m']:
883 f1, f2, fa, move, anc = args
883 f1, f2, fa, move, anc = args
884 if branchmerge:
884 if branchmerge:
885 # We've done a branch merge, mark this file as merged
885 # We've done a branch merge, mark this file as merged
886 # so that we properly record the merger later
886 # so that we properly record the merger later
887 repo.dirstate.merge(f)
887 repo.dirstate.merge(f)
888 if f1 != f2: # copy/rename
888 if f1 != f2: # copy/rename
889 if move:
889 if move:
890 repo.dirstate.remove(f1)
890 repo.dirstate.remove(f1)
891 if f1 != f:
891 if f1 != f:
892 repo.dirstate.copy(f1, f)
892 repo.dirstate.copy(f1, f)
893 else:
893 else:
894 repo.dirstate.copy(f2, f)
894 repo.dirstate.copy(f2, f)
895 else:
895 else:
896 # We've update-merged a locally modified file, so
896 # We've update-merged a locally modified file, so
897 # we set the dirstate to emulate a normal checkout
897 # we set the dirstate to emulate a normal checkout
898 # of that file some time in the past. Thus our
898 # of that file some time in the past. Thus our
899 # merge will appear as a normal local file
899 # merge will appear as a normal local file
900 # modification.
900 # modification.
901 if f2 == f: # file not locally copied/moved
901 if f2 == f: # file not locally copied/moved
902 repo.dirstate.normallookup(f)
902 repo.dirstate.normallookup(f)
903 if move:
903 if move:
904 repo.dirstate.drop(f1)
904 repo.dirstate.drop(f1)
905
905
906 # directory rename, move local
906 # directory rename, move local
907 for f, args, msg in actions['dm']:
907 for f, args, msg in actions['dm']:
908 f0, flag = args
908 f0, flag = args
909 if branchmerge:
909 if branchmerge:
910 repo.dirstate.add(f)
910 repo.dirstate.add(f)
911 repo.dirstate.remove(f0)
911 repo.dirstate.remove(f0)
912 repo.dirstate.copy(f0, f)
912 repo.dirstate.copy(f0, f)
913 else:
913 else:
914 repo.dirstate.normal(f)
914 repo.dirstate.normal(f)
915 repo.dirstate.drop(f0)
915 repo.dirstate.drop(f0)
916
916
917 # directory rename, get
917 # directory rename, get
918 for f, args, msg in actions['dg']:
918 for f, args, msg in actions['dg']:
919 f0, flag = args
919 f0, flag = args
920 if branchmerge:
920 if branchmerge:
921 repo.dirstate.add(f)
921 repo.dirstate.add(f)
922 repo.dirstate.copy(f0, f)
922 repo.dirstate.copy(f0, f)
923 else:
923 else:
924 repo.dirstate.normal(f)
924 repo.dirstate.normal(f)
925
925
926 def update(repo, node, branchmerge, force, partial, ancestor=None,
926 def update(repo, node, branchmerge, force, partial, ancestor=None,
927 mergeancestor=False, labels=None):
927 mergeancestor=False, labels=None):
928 """
928 """
929 Perform a merge between the working directory and the given node
929 Perform a merge between the working directory and the given node
930
930
931 node = the node to update to, or None if unspecified
931 node = the node to update to, or None if unspecified
932 branchmerge = whether to merge between branches
932 branchmerge = whether to merge between branches
933 force = whether to force branch merging or file overwriting
933 force = whether to force branch merging or file overwriting
934 partial = a function to filter file lists (dirstate not updated)
934 partial = a function to filter file lists (dirstate not updated)
935 mergeancestor = whether it is merging with an ancestor. If true,
935 mergeancestor = whether it is merging with an ancestor. If true,
936 we should accept the incoming changes for any prompts that occur.
936 we should accept the incoming changes for any prompts that occur.
937 If false, merging with an ancestor (fast-forward) is only allowed
937 If false, merging with an ancestor (fast-forward) is only allowed
938 between different named branches. This flag is used by rebase extension
938 between different named branches. This flag is used by rebase extension
939 as a temporary fix and should be avoided in general.
939 as a temporary fix and should be avoided in general.
940
940
941 The table below shows all the behaviors of the update command
941 The table below shows all the behaviors of the update command
942 given the -c and -C or no options, whether the working directory
942 given the -c and -C or no options, whether the working directory
943 is dirty, whether a revision is specified, and the relationship of
943 is dirty, whether a revision is specified, and the relationship of
944 the parent rev to the target rev (linear, on the same named
944 the parent rev to the target rev (linear, on the same named
945 branch, or on another named branch).
945 branch, or on another named branch).
946
946
947 This logic is tested by test-update-branches.t.
947 This logic is tested by test-update-branches.t.
948
948
949 -c -C dirty rev | linear same cross
949 -c -C dirty rev | linear same cross
950 n n n n | ok (1) x
950 n n n n | ok (1) x
951 n n n y | ok ok ok
951 n n n y | ok ok ok
952 n n y n | merge (2) (2)
952 n n y n | merge (2) (2)
953 n n y y | merge (3) (3)
953 n n y y | merge (3) (3)
954 n y * * | --- discard ---
954 n y * * | --- discard ---
955 y n y * | --- (4) ---
955 y n y * | --- (4) ---
956 y n n * | --- ok ---
956 y n n * | --- ok ---
957 y y * * | --- (5) ---
957 y y * * | --- (5) ---
958
958
959 x = can't happen
959 x = can't happen
960 * = don't-care
960 * = don't-care
961 1 = abort: not a linear update (merge or update --check to force update)
961 1 = abort: not a linear update (merge or update --check to force update)
962 2 = abort: uncommitted changes (commit and merge, or update --clean to
962 2 = abort: uncommitted changes (commit and merge, or update --clean to
963 discard changes)
963 discard changes)
964 3 = abort: uncommitted changes (commit or update --clean to discard changes)
964 3 = abort: uncommitted changes (commit or update --clean to discard changes)
965 4 = abort: uncommitted changes (checked in commands.py)
965 4 = abort: uncommitted changes (checked in commands.py)
966 5 = incompatible options (checked in commands.py)
966 5 = incompatible options (checked in commands.py)
967
967
968 Return the same tuple as applyupdates().
968 Return the same tuple as applyupdates().
969 """
969 """
970
970
971 onode = node
971 onode = node
972 wlock = repo.wlock()
972 wlock = repo.wlock()
973 try:
973 try:
974 wc = repo[None]
974 wc = repo[None]
975 pl = wc.parents()
975 pl = wc.parents()
976 p1 = pl[0]
976 p1 = pl[0]
977 pas = [None]
977 pas = [None]
978 if ancestor is not None:
978 if ancestor is not None:
979 pas = [repo[ancestor]]
979 pas = [repo[ancestor]]
980
980
981 if node is None:
981 if node is None:
982 # Here is where we should consider bookmarks, divergent bookmarks,
982 # Here is where we should consider bookmarks, divergent bookmarks,
983 # foreground changesets (successors), and tip of current branch;
983 # foreground changesets (successors), and tip of current branch;
984 # but currently we are only checking the branch tips.
984 # but currently we are only checking the branch tips.
985 try:
985 try:
986 node = repo.branchtip(wc.branch())
986 node = repo.branchtip(wc.branch())
987 except errormod.RepoLookupError:
987 except errormod.RepoLookupError:
988 if wc.branch() == 'default': # no default branch!
988 if wc.branch() == 'default': # no default branch!
989 node = repo.lookup('tip') # update to tip
989 node = repo.lookup('tip') # update to tip
990 else:
990 else:
991 raise util.Abort(_("branch %s not found") % wc.branch())
991 raise util.Abort(_("branch %s not found") % wc.branch())
992
992
993 if p1.obsolete() and not p1.children():
993 if p1.obsolete() and not p1.children():
994 # allow updating to successors
994 # allow updating to successors
995 successors = obsolete.successorssets(repo, p1.node())
995 successors = obsolete.successorssets(repo, p1.node())
996
996
997 # behavior of certain cases is as follows,
997 # behavior of certain cases is as follows,
998 #
998 #
999 # divergent changesets: update to highest rev, similar to what
999 # divergent changesets: update to highest rev, similar to what
1000 # is currently done when there are more than one head
1000 # is currently done when there are more than one head
1001 # (i.e. 'tip')
1001 # (i.e. 'tip')
1002 #
1002 #
1003 # replaced changesets: same as divergent except we know there
1003 # replaced changesets: same as divergent except we know there
1004 # is no conflict
1004 # is no conflict
1005 #
1005 #
1006 # pruned changeset: no update is done; though, we could
1006 # pruned changeset: no update is done; though, we could
1007 # consider updating to the first non-obsolete parent,
1007 # consider updating to the first non-obsolete parent,
1008 # similar to what is current done for 'hg prune'
1008 # similar to what is current done for 'hg prune'
1009
1009
1010 if successors:
1010 if successors:
1011 # flatten the list here handles both divergent (len > 1)
1011 # flatten the list here handles both divergent (len > 1)
1012 # and the usual case (len = 1)
1012 # and the usual case (len = 1)
1013 successors = [n for sub in successors for n in sub]
1013 successors = [n for sub in successors for n in sub]
1014
1014
1015 # get the max revision for the given successors set,
1015 # get the max revision for the given successors set,
1016 # i.e. the 'tip' of a set
1016 # i.e. the 'tip' of a set
1017 node = repo.revs('max(%ln)', successors).first()
1017 node = repo.revs('max(%ln)', successors).first()
1018 pas = [p1]
1018 pas = [p1]
1019
1019
1020 overwrite = force and not branchmerge
1020 overwrite = force and not branchmerge
1021
1021
1022 p2 = repo[node]
1022 p2 = repo[node]
1023 if pas[0] is None:
1023 if pas[0] is None:
1024 if repo.ui.config('merge', 'preferancestor', '*') == '*':
1024 if repo.ui.config('merge', 'preferancestor', '*') == '*':
1025 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1025 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1026 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1026 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1027 else:
1027 else:
1028 pas = [p1.ancestor(p2, warn=branchmerge)]
1028 pas = [p1.ancestor(p2, warn=branchmerge)]
1029
1029
1030 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1030 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1031
1031
1032 ### check phase
1032 ### check phase
1033 if not overwrite and len(pl) > 1:
1033 if not overwrite and len(pl) > 1:
1034 raise util.Abort(_("outstanding uncommitted merge"))
1034 raise util.Abort(_("outstanding uncommitted merge"))
1035 if branchmerge:
1035 if branchmerge:
1036 if pas == [p2]:
1036 if pas == [p2]:
1037 raise util.Abort(_("merging with a working directory ancestor"
1037 raise util.Abort(_("merging with a working directory ancestor"
1038 " has no effect"))
1038 " has no effect"))
1039 elif pas == [p1]:
1039 elif pas == [p1]:
1040 if not mergeancestor and p1.branch() == p2.branch():
1040 if not mergeancestor and p1.branch() == p2.branch():
1041 raise util.Abort(_("nothing to merge"),
1041 raise util.Abort(_("nothing to merge"),
1042 hint=_("use 'hg update' "
1042 hint=_("use 'hg update' "
1043 "or check 'hg heads'"))
1043 "or check 'hg heads'"))
1044 if not force and (wc.files() or wc.deleted()):
1044 if not force and (wc.files() or wc.deleted()):
1045 raise util.Abort(_("uncommitted changes"),
1045 raise util.Abort(_("uncommitted changes"),
1046 hint=_("use 'hg status' to list changes"))
1046 hint=_("use 'hg status' to list changes"))
1047 for s in sorted(wc.substate):
1047 for s in sorted(wc.substate):
1048 wc.sub(s).bailifchanged()
1048 wc.sub(s).bailifchanged()
1049
1049
1050 elif not overwrite:
1050 elif not overwrite:
1051 if p1 == p2: # no-op update
1051 if p1 == p2: # no-op update
1052 # call the hooks and exit early
1052 # call the hooks and exit early
1053 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1053 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1054 repo.hook('update', parent1=xp2, parent2='', error=0)
1054 repo.hook('update', parent1=xp2, parent2='', error=0)
1055 return 0, 0, 0, 0
1055 return 0, 0, 0, 0
1056
1056
1057 if pas not in ([p1], [p2]): # nonlinear
1057 if pas not in ([p1], [p2]): # nonlinear
1058 dirty = wc.dirty(missing=True)
1058 dirty = wc.dirty(missing=True)
1059 if dirty or onode is None:
1059 if dirty or onode is None:
1060 # Branching is a bit strange to ensure we do the minimal
1060 # Branching is a bit strange to ensure we do the minimal
1061 # amount of call to obsolete.background.
1061 # amount of call to obsolete.background.
1062 foreground = obsolete.foreground(repo, [p1.node()])
1062 foreground = obsolete.foreground(repo, [p1.node()])
1063 # note: the <node> variable contains a random identifier
1063 # note: the <node> variable contains a random identifier
1064 if repo[node].node() in foreground:
1064 if repo[node].node() in foreground:
1065 pas = [p1] # allow updating to successors
1065 pas = [p1] # allow updating to successors
1066 elif dirty:
1066 elif dirty:
1067 msg = _("uncommitted changes")
1067 msg = _("uncommitted changes")
1068 if onode is None:
1068 if onode is None:
1069 hint = _("commit and merge, or update --clean to"
1069 hint = _("commit and merge, or update --clean to"
1070 " discard changes")
1070 " discard changes")
1071 else:
1071 else:
1072 hint = _("commit or update --clean to discard"
1072 hint = _("commit or update --clean to discard"
1073 " changes")
1073 " changes")
1074 raise util.Abort(msg, hint=hint)
1074 raise util.Abort(msg, hint=hint)
1075 else: # node is none
1075 else: # node is none
1076 msg = _("not a linear update")
1076 msg = _("not a linear update")
1077 hint = _("merge or update --check to force update")
1077 hint = _("merge or update --check to force update")
1078 raise util.Abort(msg, hint=hint)
1078 raise util.Abort(msg, hint=hint)
1079 else:
1079 else:
1080 # Allow jumping branches if clean and specific rev given
1080 # Allow jumping branches if clean and specific rev given
1081 pas = [p1]
1081 pas = [p1]
1082
1082
1083 followcopies = False
1083 followcopies = False
1084 if overwrite:
1084 if overwrite:
1085 pas = [wc]
1085 pas = [wc]
1086 elif pas == [p2]: # backwards
1086 elif pas == [p2]: # backwards
1087 pas = [wc.p1()]
1087 pas = [wc.p1()]
1088 elif not branchmerge and not wc.dirty(missing=True):
1088 elif not branchmerge and not wc.dirty(missing=True):
1089 pass
1089 pass
1090 elif pas[0] and repo.ui.configbool('merge', 'followcopies', True):
1090 elif pas[0] and repo.ui.configbool('merge', 'followcopies', True):
1091 followcopies = True
1091 followcopies = True
1092
1092
1093 ### calculate phase
1093 ### calculate phase
1094 actionbyfile, diverge, renamedelete = calculateupdates(
1094 actionbyfile, diverge, renamedelete = calculateupdates(
1095 repo, wc, p2, pas, branchmerge, force, partial, mergeancestor,
1095 repo, wc, p2, pas, branchmerge, force, partial, mergeancestor,
1096 followcopies)
1096 followcopies)
1097 # Convert to dictionary-of-lists format
1097 # Convert to dictionary-of-lists format
1098 actions = dict((m, []) for m in 'a f g cd dc r dm dg m e k'.split())
1098 actions = dict((m, []) for m in 'a f g cd dc r dm dg m e k'.split())
1099 for f, (m, args, msg) in actionbyfile.iteritems():
1099 for f, (m, args, msg) in actionbyfile.iteritems():
1100 if m not in actions:
1100 if m not in actions:
1101 actions[m] = []
1101 actions[m] = []
1102 actions[m].append((f, args, msg))
1102 actions[m].append((f, args, msg))
1103
1103
1104 if not util.checkcase(repo.path):
1104 if not util.checkcase(repo.path):
1105 # check collision between files only in p2 for clean update
1105 # check collision between files only in p2 for clean update
1106 if (not branchmerge and
1106 if (not branchmerge and
1107 (force or not wc.dirty(missing=True, branch=False))):
1107 (force or not wc.dirty(missing=True, branch=False))):
1108 _checkcollision(repo, p2.manifest(), None)
1108 _checkcollision(repo, p2.manifest(), None)
1109 else:
1109 else:
1110 _checkcollision(repo, wc.manifest(), actions)
1110 _checkcollision(repo, wc.manifest(), actions)
1111
1111
1112 # Prompt and create actions. TODO: Move this towards resolve phase.
1112 # Prompt and create actions. TODO: Move this towards resolve phase.
1113 for f, args, msg in sorted(actions['cd']):
1113 for f, args, msg in sorted(actions['cd']):
1114 if repo.ui.promptchoice(
1114 if repo.ui.promptchoice(
1115 _("local changed %s which remote deleted\n"
1115 _("local changed %s which remote deleted\n"
1116 "use (c)hanged version or (d)elete?"
1116 "use (c)hanged version or (d)elete?"
1117 "$$ &Changed $$ &Delete") % f, 0):
1117 "$$ &Changed $$ &Delete") % f, 0):
1118 actions['r'].append((f, None, "prompt delete"))
1118 actions['r'].append((f, None, "prompt delete"))
1119 else:
1119 else:
1120 actions['a'].append((f, None, "prompt keep"))
1120 actions['a'].append((f, None, "prompt keep"))
1121 del actions['cd'][:]
1121 del actions['cd'][:]
1122
1122
1123 for f, args, msg in sorted(actions['dc']):
1123 for f, args, msg in sorted(actions['dc']):
1124 flags, = args
1124 flags, = args
1125 if repo.ui.promptchoice(
1125 if repo.ui.promptchoice(
1126 _("remote changed %s which local deleted\n"
1126 _("remote changed %s which local deleted\n"
1127 "use (c)hanged version or leave (d)eleted?"
1127 "use (c)hanged version or leave (d)eleted?"
1128 "$$ &Changed $$ &Deleted") % f, 0) == 0:
1128 "$$ &Changed $$ &Deleted") % f, 0) == 0:
1129 actions['g'].append((f, (flags,), "prompt recreating"))
1129 actions['g'].append((f, (flags,), "prompt recreating"))
1130 del actions['dc'][:]
1130 del actions['dc'][:]
1131
1131
1132 ### apply phase
1132 ### apply phase
1133 if not branchmerge: # just jump to the new rev
1133 if not branchmerge: # just jump to the new rev
1134 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1134 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1135 if not partial:
1135 if not partial:
1136 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1136 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1137 # note that we're in the middle of an update
1137 # note that we're in the middle of an update
1138 repo.vfs.write('updatestate', p2.hex())
1138 repo.vfs.write('updatestate', p2.hex())
1139
1139
1140 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1140 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1141
1141
1142 # divergent renames
1142 # divergent renames
1143 for f, fl in sorted(diverge.iteritems()):
1143 for f, fl in sorted(diverge.iteritems()):
1144 repo.ui.warn(_("note: possible conflict - %s was renamed "
1144 repo.ui.warn(_("note: possible conflict - %s was renamed "
1145 "multiple times to:\n") % f)
1145 "multiple times to:\n") % f)
1146 for nf in fl:
1146 for nf in fl:
1147 repo.ui.warn(" %s\n" % nf)
1147 repo.ui.warn(" %s\n" % nf)
1148
1148
1149 # rename and delete
1149 # rename and delete
1150 for f, fl in sorted(renamedelete.iteritems()):
1150 for f, fl in sorted(renamedelete.iteritems()):
1151 repo.ui.warn(_("note: possible conflict - %s was deleted "
1151 repo.ui.warn(_("note: possible conflict - %s was deleted "
1152 "and renamed to:\n") % f)
1152 "and renamed to:\n") % f)
1153 for nf in fl:
1153 for nf in fl:
1154 repo.ui.warn(" %s\n" % nf)
1154 repo.ui.warn(" %s\n" % nf)
1155
1155
1156 if not partial:
1156 if not partial:
1157 repo.dirstate.beginparentchange()
1157 repo.dirstate.beginparentchange()
1158 repo.setparents(fp1, fp2)
1158 repo.setparents(fp1, fp2)
1159 recordupdates(repo, actions, branchmerge)
1159 recordupdates(repo, actions, branchmerge)
1160 # update completed, clear state
1160 # update completed, clear state
1161 util.unlink(repo.join('updatestate'))
1161 util.unlink(repo.join('updatestate'))
1162
1162
1163 if not branchmerge:
1163 if not branchmerge:
1164 repo.dirstate.setbranch(p2.branch())
1164 repo.dirstate.setbranch(p2.branch())
1165 repo.dirstate.endparentchange()
1165 repo.dirstate.endparentchange()
1166 finally:
1166 finally:
1167 wlock.release()
1167 wlock.release()
1168
1168
1169 if not partial:
1169 if not partial:
1170 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1170 def updatehook(parent1=xp1, parent2=xp2, error=stats[3]):
1171 repo.hook('update', parent1=parent1, parent2=parent2, error=error)
1172 repo._afterlock(updatehook)
1171 return stats
1173 return stats
1172
1174
1173 def graft(repo, ctx, pctx, labels):
1175 def graft(repo, ctx, pctx, labels):
1174 """Do a graft-like merge.
1176 """Do a graft-like merge.
1175
1177
1176 This is a merge where the merge ancestor is chosen such that one
1178 This is a merge where the merge ancestor is chosen such that one
1177 or more changesets are grafted onto the current changeset. In
1179 or more changesets are grafted onto the current changeset. In
1178 addition to the merge, this fixes up the dirstate to include only
1180 addition to the merge, this fixes up the dirstate to include only
1179 a single parent and tries to duplicate any renames/copies
1181 a single parent and tries to duplicate any renames/copies
1180 appropriately.
1182 appropriately.
1181
1183
1182 ctx - changeset to rebase
1184 ctx - changeset to rebase
1183 pctx - merge base, usually ctx.p1()
1185 pctx - merge base, usually ctx.p1()
1184 labels - merge labels eg ['local', 'graft']
1186 labels - merge labels eg ['local', 'graft']
1185
1187
1186 """
1188 """
1187 # If we're grafting a descendant onto an ancestor, be sure to pass
1189 # If we're grafting a descendant onto an ancestor, be sure to pass
1188 # mergeancestor=True to update. This does two things: 1) allows the merge if
1190 # mergeancestor=True to update. This does two things: 1) allows the merge if
1189 # the destination is the same as the parent of the ctx (so we can use graft
1191 # the destination is the same as the parent of the ctx (so we can use graft
1190 # to copy commits), and 2) informs update that the incoming changes are
1192 # to copy commits), and 2) informs update that the incoming changes are
1191 # newer than the destination so it doesn't prompt about "remote changed foo
1193 # newer than the destination so it doesn't prompt about "remote changed foo
1192 # which local deleted".
1194 # which local deleted".
1193 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
1195 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
1194
1196
1195 stats = update(repo, ctx.node(), True, True, False, pctx.node(),
1197 stats = update(repo, ctx.node(), True, True, False, pctx.node(),
1196 mergeancestor=mergeancestor, labels=labels)
1198 mergeancestor=mergeancestor, labels=labels)
1197
1199
1198 # drop the second merge parent
1200 # drop the second merge parent
1199 repo.dirstate.beginparentchange()
1201 repo.dirstate.beginparentchange()
1200 repo.setparents(repo['.'].node(), nullid)
1202 repo.setparents(repo['.'].node(), nullid)
1201 repo.dirstate.write()
1203 repo.dirstate.write()
1202 # fix up dirstate for copies and renames
1204 # fix up dirstate for copies and renames
1203 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1205 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1204 repo.dirstate.endparentchange()
1206 repo.dirstate.endparentchange()
1205 return stats
1207 return stats
@@ -1,688 +1,690 b''
1 commit hooks can see env vars
1 commit hooks can see env vars
2 (and post-transaction one are run unlocked)
2 (and post-transaction one are run unlocked)
3
3
4 $ hg init a
4 $ hg init a
5 $ cd a
5 $ cd a
6 $ cat > .hg/hgrc <<EOF
6 $ cat > .hg/hgrc <<EOF
7 > [hooks]
7 > [hooks]
8 > commit = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" commit"
8 > commit = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" commit"
9 > commit.b = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" commit.b"
9 > commit.b = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" commit.b"
10 > precommit = sh -c "HG_LOCAL= HG_NODE= HG_TAG= python \"$TESTDIR/printenv.py\" precommit"
10 > precommit = sh -c "HG_LOCAL= HG_NODE= HG_TAG= python \"$TESTDIR/printenv.py\" precommit"
11 > pretxncommit = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" pretxncommit"
11 > pretxncommit = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" pretxncommit"
12 > pretxncommit.tip = hg -q tip
12 > pretxncommit.tip = hg -q tip
13 > pre-identify = python "$TESTDIR/printenv.py" pre-identify 1
13 > pre-identify = python "$TESTDIR/printenv.py" pre-identify 1
14 > pre-cat = python "$TESTDIR/printenv.py" pre-cat
14 > pre-cat = python "$TESTDIR/printenv.py" pre-cat
15 > post-cat = python "$TESTDIR/printenv.py" post-cat
15 > post-cat = python "$TESTDIR/printenv.py" post-cat
16 > pretxnopen = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" pretxnopen"
16 > pretxnopen = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" pretxnopen"
17 > pretxnclose = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" pretxnclose"
17 > pretxnclose = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" pretxnclose"
18 > txnclose = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" txnclose"
18 > txnclose = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" txnclose"
19 > txnabort = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" txnabort"
19 > txnabort = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" txnabort"
20 > txnclose.checklock = sh -c "hg debuglock > /dev/null"
20 > txnclose.checklock = sh -c "hg debuglock > /dev/null"
21 > EOF
21 > EOF
22 $ echo a > a
22 $ echo a > a
23 $ hg add a
23 $ hg add a
24 $ hg commit -m a
24 $ hg commit -m a
25 precommit hook: HG_PARENT1=0000000000000000000000000000000000000000
25 precommit hook: HG_PARENT1=0000000000000000000000000000000000000000
26 pretxnopen hook: HG_TXNNAME=commit
26 pretxnopen hook: HG_TXNNAME=commit
27 pretxncommit hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000 HG_PENDING=$TESTTMP/a
27 pretxncommit hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000 HG_PENDING=$TESTTMP/a
28 0:cb9a9f314b8b
28 0:cb9a9f314b8b
29 pretxnclose hook: HG_PENDING=$TESTTMP/a HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_XNNAME=commit (glob)
29 pretxnclose hook: HG_PENDING=$TESTTMP/a HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_XNNAME=commit (glob)
30 txnclose hook: HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
30 txnclose hook: HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
31 commit hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
31 commit hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
32 commit.b hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
32 commit.b hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
33
33
34 $ hg clone . ../b
34 $ hg clone . ../b
35 updating to branch default
35 updating to branch default
36 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
37 $ cd ../b
37 $ cd ../b
38
38
39 changegroup hooks can see env vars
39 changegroup hooks can see env vars
40
40
41 $ cat > .hg/hgrc <<EOF
41 $ cat > .hg/hgrc <<EOF
42 > [hooks]
42 > [hooks]
43 > prechangegroup = python "$TESTDIR/printenv.py" prechangegroup
43 > prechangegroup = python "$TESTDIR/printenv.py" prechangegroup
44 > changegroup = python "$TESTDIR/printenv.py" changegroup
44 > changegroup = python "$TESTDIR/printenv.py" changegroup
45 > incoming = python "$TESTDIR/printenv.py" incoming
45 > incoming = python "$TESTDIR/printenv.py" incoming
46 > EOF
46 > EOF
47
47
48 pretxncommit and commit hooks can see both parents of merge
48 pretxncommit and commit hooks can see both parents of merge
49
49
50 $ cd ../a
50 $ cd ../a
51 $ echo b >> a
51 $ echo b >> a
52 $ hg commit -m a1 -d "1 0"
52 $ hg commit -m a1 -d "1 0"
53 precommit hook: HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
53 precommit hook: HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
54 pretxnopen hook: HG_TXNNAME=commit
54 pretxnopen hook: HG_TXNNAME=commit
55 pretxncommit hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
55 pretxncommit hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
56 1:ab228980c14d
56 1:ab228980c14d
57 pretxnclose hook: HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_XNNAME=commit (glob)
57 pretxnclose hook: HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_XNNAME=commit (glob)
58 txnclose hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
58 txnclose hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
59 commit hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
59 commit hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
60 commit.b hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
60 commit.b hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
61 $ hg update -C 0
61 $ hg update -C 0
62 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
62 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
63 $ echo b > b
63 $ echo b > b
64 $ hg add b
64 $ hg add b
65 $ hg commit -m b -d '1 0'
65 $ hg commit -m b -d '1 0'
66 precommit hook: HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
66 precommit hook: HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
67 pretxnopen hook: HG_TXNNAME=commit
67 pretxnopen hook: HG_TXNNAME=commit
68 pretxncommit hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
68 pretxncommit hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
69 2:ee9deb46ab31
69 2:ee9deb46ab31
70 pretxnclose hook: HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_XNNAME=commit (glob)
70 pretxnclose hook: HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_XNNAME=commit (glob)
71 txnclose hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
71 txnclose hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
72 commit hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
72 commit hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
73 commit.b hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
73 commit.b hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
74 created new head
74 created new head
75 $ hg merge 1
75 $ hg merge 1
76 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
76 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
77 (branch merge, don't forget to commit)
77 (branch merge, don't forget to commit)
78 $ hg commit -m merge -d '2 0'
78 $ hg commit -m merge -d '2 0'
79 precommit hook: HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
79 precommit hook: HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
80 pretxnopen hook: HG_TXNNAME=commit
80 pretxnopen hook: HG_TXNNAME=commit
81 pretxncommit hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd HG_PENDING=$TESTTMP/a
81 pretxncommit hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd HG_PENDING=$TESTTMP/a
82 3:07f3376c1e65
82 3:07f3376c1e65
83 pretxnclose hook: HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_XNNAME=commit (glob)
83 pretxnclose hook: HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_XNNAME=commit (glob)
84 txnclose hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
84 txnclose hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
85 commit hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
85 commit hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
86 commit.b hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
86 commit.b hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
87
87
88 test generic hooks
88 test generic hooks
89
89
90 $ hg id
90 $ hg id
91 pre-identify hook: HG_ARGS=id HG_OPTS={'bookmarks': None, 'branch': None, 'id': None, 'insecure': None, 'num': None, 'remotecmd': '', 'rev': '', 'ssh': '', 'tags': None} HG_PATS=[]
91 pre-identify hook: HG_ARGS=id HG_OPTS={'bookmarks': None, 'branch': None, 'id': None, 'insecure': None, 'num': None, 'remotecmd': '', 'rev': '', 'ssh': '', 'tags': None} HG_PATS=[]
92 abort: pre-identify hook exited with status 1
92 abort: pre-identify hook exited with status 1
93 [255]
93 [255]
94 $ hg cat b
94 $ hg cat b
95 pre-cat hook: HG_ARGS=cat b HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': ''} HG_PATS=['b']
95 pre-cat hook: HG_ARGS=cat b HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': ''} HG_PATS=['b']
96 b
96 b
97 post-cat hook: HG_ARGS=cat b HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': ''} HG_PATS=['b'] HG_RESULT=0
97 post-cat hook: HG_ARGS=cat b HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': ''} HG_PATS=['b'] HG_RESULT=0
98
98
99 $ cd ../b
99 $ cd ../b
100 $ hg pull ../a
100 $ hg pull ../a
101 pulling from ../a
101 pulling from ../a
102 searching for changes
102 searching for changes
103 prechangegroup hook: HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
103 prechangegroup hook: HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
104 adding changesets
104 adding changesets
105 adding manifests
105 adding manifests
106 adding file changes
106 adding file changes
107 added 3 changesets with 2 changes to 2 files
107 added 3 changesets with 2 changes to 2 files
108 changegroup hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
108 changegroup hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
109 incoming hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
109 incoming hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
110 incoming hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
110 incoming hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
111 incoming hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
111 incoming hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
112 (run 'hg update' to get a working copy)
112 (run 'hg update' to get a working copy)
113
113
114 tag hooks can see env vars
114 tag hooks can see env vars
115
115
116 $ cd ../a
116 $ cd ../a
117 $ cat >> .hg/hgrc <<EOF
117 $ cat >> .hg/hgrc <<EOF
118 > pretag = python "$TESTDIR/printenv.py" pretag
118 > pretag = python "$TESTDIR/printenv.py" pretag
119 > tag = sh -c "HG_PARENT1= HG_PARENT2= python \"$TESTDIR/printenv.py\" tag"
119 > tag = sh -c "HG_PARENT1= HG_PARENT2= python \"$TESTDIR/printenv.py\" tag"
120 > EOF
120 > EOF
121 $ hg tag -d '3 0' a
121 $ hg tag -d '3 0' a
122 pretag hook: HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
122 pretag hook: HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
123 precommit hook: HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
123 precommit hook: HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
124 pretxnopen hook: HG_TXNNAME=commit
124 pretxnopen hook: HG_TXNNAME=commit
125 pretxncommit hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PENDING=$TESTTMP/a
125 pretxncommit hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PENDING=$TESTTMP/a
126 4:539e4b31b6dc
126 4:539e4b31b6dc
127 pretxnclose hook: HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_XNNAME=commit (glob)
127 pretxnclose hook: HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_XNNAME=commit (glob)
128 tag hook: HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
128 tag hook: HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
129 txnclose hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
129 txnclose hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
130 commit hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
130 commit hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
131 commit.b hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
131 commit.b hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
132 $ hg tag -l la
132 $ hg tag -l la
133 pretag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=la
133 pretag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=la
134 tag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=la
134 tag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=la
135
135
136 pretag hook can forbid tagging
136 pretag hook can forbid tagging
137
137
138 $ echo "pretag.forbid = python \"$TESTDIR/printenv.py\" pretag.forbid 1" >> .hg/hgrc
138 $ echo "pretag.forbid = python \"$TESTDIR/printenv.py\" pretag.forbid 1" >> .hg/hgrc
139 $ hg tag -d '4 0' fa
139 $ hg tag -d '4 0' fa
140 pretag hook: HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa
140 pretag hook: HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa
141 pretag.forbid hook: HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa
141 pretag.forbid hook: HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa
142 abort: pretag.forbid hook exited with status 1
142 abort: pretag.forbid hook exited with status 1
143 [255]
143 [255]
144 $ hg tag -l fla
144 $ hg tag -l fla
145 pretag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fla
145 pretag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fla
146 pretag.forbid hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fla
146 pretag.forbid hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fla
147 abort: pretag.forbid hook exited with status 1
147 abort: pretag.forbid hook exited with status 1
148 [255]
148 [255]
149
149
150 pretxncommit hook can see changeset, can roll back txn, changeset no
150 pretxncommit hook can see changeset, can roll back txn, changeset no
151 more there after
151 more there after
152
152
153 $ echo "pretxncommit.forbid0 = hg tip -q" >> .hg/hgrc
153 $ echo "pretxncommit.forbid0 = hg tip -q" >> .hg/hgrc
154 $ echo "pretxncommit.forbid1 = python \"$TESTDIR/printenv.py\" pretxncommit.forbid 1" >> .hg/hgrc
154 $ echo "pretxncommit.forbid1 = python \"$TESTDIR/printenv.py\" pretxncommit.forbid 1" >> .hg/hgrc
155 $ echo z > z
155 $ echo z > z
156 $ hg add z
156 $ hg add z
157 $ hg -q tip
157 $ hg -q tip
158 4:539e4b31b6dc
158 4:539e4b31b6dc
159 $ hg commit -m 'fail' -d '4 0'
159 $ hg commit -m 'fail' -d '4 0'
160 precommit hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
160 precommit hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
161 pretxnopen hook: HG_TXNNAME=commit
161 pretxnopen hook: HG_TXNNAME=commit
162 pretxncommit hook: HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/a
162 pretxncommit hook: HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/a
163 5:6f611f8018c1
163 5:6f611f8018c1
164 5:6f611f8018c1
164 5:6f611f8018c1
165 pretxncommit.forbid hook: HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/a
165 pretxncommit.forbid hook: HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/a
166 transaction abort!
166 transaction abort!
167 txnabort hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
167 txnabort hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
168 rollback completed
168 rollback completed
169 abort: pretxncommit.forbid1 hook exited with status 1
169 abort: pretxncommit.forbid1 hook exited with status 1
170 [255]
170 [255]
171 $ hg -q tip
171 $ hg -q tip
172 4:539e4b31b6dc
172 4:539e4b31b6dc
173
173
174 (Check that no 'changelog.i.a' file were left behind)
174 (Check that no 'changelog.i.a' file were left behind)
175
175
176 $ ls -1 .hg/store/
176 $ ls -1 .hg/store/
177 00changelog.i
177 00changelog.i
178 00manifest.i
178 00manifest.i
179 data
179 data
180 fncache
180 fncache
181 journal.phaseroots
181 journal.phaseroots
182 phaseroots
182 phaseroots
183 undo
183 undo
184 undo.backup.fncache
184 undo.backup.fncache
185 undo.backupfiles
185 undo.backupfiles
186 undo.phaseroots
186 undo.phaseroots
187
187
188
188
189 precommit hook can prevent commit
189 precommit hook can prevent commit
190
190
191 $ echo "precommit.forbid = python \"$TESTDIR/printenv.py\" precommit.forbid 1" >> .hg/hgrc
191 $ echo "precommit.forbid = python \"$TESTDIR/printenv.py\" precommit.forbid 1" >> .hg/hgrc
192 $ hg commit -m 'fail' -d '4 0'
192 $ hg commit -m 'fail' -d '4 0'
193 precommit hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
193 precommit hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
194 precommit.forbid hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
194 precommit.forbid hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
195 abort: precommit.forbid hook exited with status 1
195 abort: precommit.forbid hook exited with status 1
196 [255]
196 [255]
197 $ hg -q tip
197 $ hg -q tip
198 4:539e4b31b6dc
198 4:539e4b31b6dc
199
199
200 preupdate hook can prevent update
200 preupdate hook can prevent update
201
201
202 $ echo "preupdate = python \"$TESTDIR/printenv.py\" preupdate" >> .hg/hgrc
202 $ echo "preupdate = python \"$TESTDIR/printenv.py\" preupdate" >> .hg/hgrc
203 $ hg update 1
203 $ hg update 1
204 preupdate hook: HG_PARENT1=ab228980c14d
204 preupdate hook: HG_PARENT1=ab228980c14d
205 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
205 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
206
206
207 update hook
207 update hook
208
208
209 $ echo "update = python \"$TESTDIR/printenv.py\" update" >> .hg/hgrc
209 $ echo "update = python \"$TESTDIR/printenv.py\" update" >> .hg/hgrc
210 $ hg update
210 $ hg update
211 preupdate hook: HG_PARENT1=539e4b31b6dc
211 preupdate hook: HG_PARENT1=539e4b31b6dc
212 update hook: HG_ERROR=0 HG_PARENT1=539e4b31b6dc
212 update hook: HG_ERROR=0 HG_PARENT1=539e4b31b6dc
213 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
213 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
214
214
215 pushkey hook
215 pushkey hook
216
216
217 $ echo "pushkey = python \"$TESTDIR/printenv.py\" pushkey" >> .hg/hgrc
217 $ echo "pushkey = python \"$TESTDIR/printenv.py\" pushkey" >> .hg/hgrc
218 $ cd ../b
218 $ cd ../b
219 $ hg bookmark -r null foo
219 $ hg bookmark -r null foo
220 $ hg push -B foo ../a
220 $ hg push -B foo ../a
221 pushing to ../a
221 pushing to ../a
222 searching for changes
222 searching for changes
223 no changes found
223 no changes found
224 pretxnopen hook: HG_TXNNAME=bookmarks
224 pretxnopen hook: HG_TXNNAME=bookmarks
225 pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_XNNAME=bookmarks (glob)
225 pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_XNNAME=bookmarks (glob)
226 txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmarks (glob)
226 txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmarks (glob)
227 pushkey hook: HG_KEY=foo HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 HG_RET=1
227 pushkey hook: HG_KEY=foo HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 HG_RET=1
228 exporting bookmark foo
228 exporting bookmark foo
229 [1]
229 [1]
230 $ cd ../a
230 $ cd ../a
231
231
232 listkeys hook
232 listkeys hook
233
233
234 $ echo "listkeys = python \"$TESTDIR/printenv.py\" listkeys" >> .hg/hgrc
234 $ echo "listkeys = python \"$TESTDIR/printenv.py\" listkeys" >> .hg/hgrc
235 $ hg bookmark -r null bar
235 $ hg bookmark -r null bar
236 $ cd ../b
236 $ cd ../b
237 $ hg pull -B bar ../a
237 $ hg pull -B bar ../a
238 pulling from ../a
238 pulling from ../a
239 listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
239 listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
240 listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
240 listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
241 no changes found
241 no changes found
242 listkeys hook: HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
242 listkeys hook: HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
243 adding remote bookmark bar
243 adding remote bookmark bar
244 $ cd ../a
244 $ cd ../a
245
245
246 test that prepushkey can prevent incoming keys
246 test that prepushkey can prevent incoming keys
247
247
248 $ echo "prepushkey = python \"$TESTDIR/printenv.py\" prepushkey.forbid 1" >> .hg/hgrc
248 $ echo "prepushkey = python \"$TESTDIR/printenv.py\" prepushkey.forbid 1" >> .hg/hgrc
249 $ cd ../b
249 $ cd ../b
250 $ hg bookmark -r null baz
250 $ hg bookmark -r null baz
251 $ hg push -B baz ../a
251 $ hg push -B baz ../a
252 pushing to ../a
252 pushing to ../a
253 searching for changes
253 searching for changes
254 listkeys hook: HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
254 listkeys hook: HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
255 listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
255 listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
256 no changes found
256 no changes found
257 listkeys hook: HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
257 listkeys hook: HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
258 prepushkey.forbid hook: HG_KEY=baz HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000
258 prepushkey.forbid hook: HG_KEY=baz HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000
259 pushkey-abort: prepushkey hook exited with status 1
259 pushkey-abort: prepushkey hook exited with status 1
260 exporting bookmark baz failed!
260 exporting bookmark baz failed!
261 [1]
261 [1]
262 $ cd ../a
262 $ cd ../a
263
263
264 test that prelistkeys can prevent listing keys
264 test that prelistkeys can prevent listing keys
265
265
266 $ echo "prelistkeys = python \"$TESTDIR/printenv.py\" prelistkeys.forbid 1" >> .hg/hgrc
266 $ echo "prelistkeys = python \"$TESTDIR/printenv.py\" prelistkeys.forbid 1" >> .hg/hgrc
267 $ hg bookmark -r null quux
267 $ hg bookmark -r null quux
268 $ cd ../b
268 $ cd ../b
269 $ hg pull -B quux ../a
269 $ hg pull -B quux ../a
270 pulling from ../a
270 pulling from ../a
271 prelistkeys.forbid hook: HG_NAMESPACE=bookmarks
271 prelistkeys.forbid hook: HG_NAMESPACE=bookmarks
272 abort: prelistkeys hook exited with status 1
272 abort: prelistkeys hook exited with status 1
273 [255]
273 [255]
274 $ cd ../a
274 $ cd ../a
275 $ rm .hg/hgrc
275 $ rm .hg/hgrc
276
276
277 prechangegroup hook can prevent incoming changes
277 prechangegroup hook can prevent incoming changes
278
278
279 $ cd ../b
279 $ cd ../b
280 $ hg -q tip
280 $ hg -q tip
281 3:07f3376c1e65
281 3:07f3376c1e65
282 $ cat > .hg/hgrc <<EOF
282 $ cat > .hg/hgrc <<EOF
283 > [hooks]
283 > [hooks]
284 > prechangegroup.forbid = python "$TESTDIR/printenv.py" prechangegroup.forbid 1
284 > prechangegroup.forbid = python "$TESTDIR/printenv.py" prechangegroup.forbid 1
285 > EOF
285 > EOF
286 $ hg pull ../a
286 $ hg pull ../a
287 pulling from ../a
287 pulling from ../a
288 searching for changes
288 searching for changes
289 prechangegroup.forbid hook: HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
289 prechangegroup.forbid hook: HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
290 abort: prechangegroup.forbid hook exited with status 1
290 abort: prechangegroup.forbid hook exited with status 1
291 [255]
291 [255]
292
292
293 pretxnchangegroup hook can see incoming changes, can roll back txn,
293 pretxnchangegroup hook can see incoming changes, can roll back txn,
294 incoming changes no longer there after
294 incoming changes no longer there after
295
295
296 $ cat > .hg/hgrc <<EOF
296 $ cat > .hg/hgrc <<EOF
297 > [hooks]
297 > [hooks]
298 > pretxnchangegroup.forbid0 = hg tip -q
298 > pretxnchangegroup.forbid0 = hg tip -q
299 > pretxnchangegroup.forbid1 = python "$TESTDIR/printenv.py" pretxnchangegroup.forbid 1
299 > pretxnchangegroup.forbid1 = python "$TESTDIR/printenv.py" pretxnchangegroup.forbid 1
300 > EOF
300 > EOF
301 $ hg pull ../a
301 $ hg pull ../a
302 pulling from ../a
302 pulling from ../a
303 searching for changes
303 searching for changes
304 adding changesets
304 adding changesets
305 adding manifests
305 adding manifests
306 adding file changes
306 adding file changes
307 added 1 changesets with 1 changes to 1 files
307 added 1 changesets with 1 changes to 1 files
308 4:539e4b31b6dc
308 4:539e4b31b6dc
309 pretxnchangegroup.forbid hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/b HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
309 pretxnchangegroup.forbid hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/b HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
310 transaction abort!
310 transaction abort!
311 rollback completed
311 rollback completed
312 abort: pretxnchangegroup.forbid1 hook exited with status 1
312 abort: pretxnchangegroup.forbid1 hook exited with status 1
313 [255]
313 [255]
314 $ hg -q tip
314 $ hg -q tip
315 3:07f3376c1e65
315 3:07f3376c1e65
316
316
317 outgoing hooks can see env vars
317 outgoing hooks can see env vars
318
318
319 $ rm .hg/hgrc
319 $ rm .hg/hgrc
320 $ cat > ../a/.hg/hgrc <<EOF
320 $ cat > ../a/.hg/hgrc <<EOF
321 > [hooks]
321 > [hooks]
322 > preoutgoing = python "$TESTDIR/printenv.py" preoutgoing
322 > preoutgoing = python "$TESTDIR/printenv.py" preoutgoing
323 > outgoing = python "$TESTDIR/printenv.py" outgoing
323 > outgoing = python "$TESTDIR/printenv.py" outgoing
324 > EOF
324 > EOF
325 $ hg pull ../a
325 $ hg pull ../a
326 pulling from ../a
326 pulling from ../a
327 searching for changes
327 searching for changes
328 preoutgoing hook: HG_SOURCE=pull
328 preoutgoing hook: HG_SOURCE=pull
329 adding changesets
329 adding changesets
330 outgoing hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_SOURCE=pull
330 outgoing hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_SOURCE=pull
331 adding manifests
331 adding manifests
332 adding file changes
332 adding file changes
333 added 1 changesets with 1 changes to 1 files
333 added 1 changesets with 1 changes to 1 files
334 adding remote bookmark quux
334 adding remote bookmark quux
335 (run 'hg update' to get a working copy)
335 (run 'hg update' to get a working copy)
336 $ hg rollback
336 $ hg rollback
337 repository tip rolled back to revision 3 (undo pull)
337 repository tip rolled back to revision 3 (undo pull)
338
338
339 preoutgoing hook can prevent outgoing changes
339 preoutgoing hook can prevent outgoing changes
340
340
341 $ echo "preoutgoing.forbid = python \"$TESTDIR/printenv.py\" preoutgoing.forbid 1" >> ../a/.hg/hgrc
341 $ echo "preoutgoing.forbid = python \"$TESTDIR/printenv.py\" preoutgoing.forbid 1" >> ../a/.hg/hgrc
342 $ hg pull ../a
342 $ hg pull ../a
343 pulling from ../a
343 pulling from ../a
344 searching for changes
344 searching for changes
345 preoutgoing hook: HG_SOURCE=pull
345 preoutgoing hook: HG_SOURCE=pull
346 preoutgoing.forbid hook: HG_SOURCE=pull
346 preoutgoing.forbid hook: HG_SOURCE=pull
347 abort: preoutgoing.forbid hook exited with status 1
347 abort: preoutgoing.forbid hook exited with status 1
348 [255]
348 [255]
349
349
350 outgoing hooks work for local clones
350 outgoing hooks work for local clones
351
351
352 $ cd ..
352 $ cd ..
353 $ cat > a/.hg/hgrc <<EOF
353 $ cat > a/.hg/hgrc <<EOF
354 > [hooks]
354 > [hooks]
355 > preoutgoing = python "$TESTDIR/printenv.py" preoutgoing
355 > preoutgoing = python "$TESTDIR/printenv.py" preoutgoing
356 > outgoing = python "$TESTDIR/printenv.py" outgoing
356 > outgoing = python "$TESTDIR/printenv.py" outgoing
357 > EOF
357 > EOF
358 $ hg clone a c
358 $ hg clone a c
359 preoutgoing hook: HG_SOURCE=clone
359 preoutgoing hook: HG_SOURCE=clone
360 outgoing hook: HG_NODE=0000000000000000000000000000000000000000 HG_SOURCE=clone
360 outgoing hook: HG_NODE=0000000000000000000000000000000000000000 HG_SOURCE=clone
361 updating to branch default
361 updating to branch default
362 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
362 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
363 $ rm -rf c
363 $ rm -rf c
364
364
365 preoutgoing hook can prevent outgoing changes for local clones
365 preoutgoing hook can prevent outgoing changes for local clones
366
366
367 $ echo "preoutgoing.forbid = python \"$TESTDIR/printenv.py\" preoutgoing.forbid 1" >> a/.hg/hgrc
367 $ echo "preoutgoing.forbid = python \"$TESTDIR/printenv.py\" preoutgoing.forbid 1" >> a/.hg/hgrc
368 $ hg clone a zzz
368 $ hg clone a zzz
369 preoutgoing hook: HG_SOURCE=clone
369 preoutgoing hook: HG_SOURCE=clone
370 preoutgoing.forbid hook: HG_SOURCE=clone
370 preoutgoing.forbid hook: HG_SOURCE=clone
371 abort: preoutgoing.forbid hook exited with status 1
371 abort: preoutgoing.forbid hook exited with status 1
372 [255]
372 [255]
373
373
374 $ cd "$TESTTMP/b"
374 $ cd "$TESTTMP/b"
375
375
376 $ cat > hooktests.py <<EOF
376 $ cat > hooktests.py <<EOF
377 > from mercurial import util
377 > from mercurial import util
378 >
378 >
379 > uncallable = 0
379 > uncallable = 0
380 >
380 >
381 > def printargs(args):
381 > def printargs(args):
382 > args.pop('ui', None)
382 > args.pop('ui', None)
383 > args.pop('repo', None)
383 > args.pop('repo', None)
384 > a = list(args.items())
384 > a = list(args.items())
385 > a.sort()
385 > a.sort()
386 > print 'hook args:'
386 > print 'hook args:'
387 > for k, v in a:
387 > for k, v in a:
388 > print ' ', k, v
388 > print ' ', k, v
389 >
389 >
390 > def passhook(**args):
390 > def passhook(**args):
391 > printargs(args)
391 > printargs(args)
392 >
392 >
393 > def failhook(**args):
393 > def failhook(**args):
394 > printargs(args)
394 > printargs(args)
395 > return True
395 > return True
396 >
396 >
397 > class LocalException(Exception):
397 > class LocalException(Exception):
398 > pass
398 > pass
399 >
399 >
400 > def raisehook(**args):
400 > def raisehook(**args):
401 > raise LocalException('exception from hook')
401 > raise LocalException('exception from hook')
402 >
402 >
403 > def aborthook(**args):
403 > def aborthook(**args):
404 > raise util.Abort('raise abort from hook')
404 > raise util.Abort('raise abort from hook')
405 >
405 >
406 > def brokenhook(**args):
406 > def brokenhook(**args):
407 > return 1 + {}
407 > return 1 + {}
408 >
408 >
409 > def verbosehook(ui, **args):
409 > def verbosehook(ui, **args):
410 > ui.note('verbose output from hook\n')
410 > ui.note('verbose output from hook\n')
411 >
411 >
412 > def printtags(ui, repo, **args):
412 > def printtags(ui, repo, **args):
413 > print sorted(repo.tags())
413 > print sorted(repo.tags())
414 >
414 >
415 > class container:
415 > class container:
416 > unreachable = 1
416 > unreachable = 1
417 > EOF
417 > EOF
418
418
419 test python hooks
419 test python hooks
420
420
421 #if windows
421 #if windows
422 $ PYTHONPATH="$TESTTMP/b;$PYTHONPATH"
422 $ PYTHONPATH="$TESTTMP/b;$PYTHONPATH"
423 #else
423 #else
424 $ PYTHONPATH="$TESTTMP/b:$PYTHONPATH"
424 $ PYTHONPATH="$TESTTMP/b:$PYTHONPATH"
425 #endif
425 #endif
426 $ export PYTHONPATH
426 $ export PYTHONPATH
427
427
428 $ echo '[hooks]' > ../a/.hg/hgrc
428 $ echo '[hooks]' > ../a/.hg/hgrc
429 $ echo 'preoutgoing.broken = python:hooktests.brokenhook' >> ../a/.hg/hgrc
429 $ echo 'preoutgoing.broken = python:hooktests.brokenhook' >> ../a/.hg/hgrc
430 $ hg pull ../a 2>&1 | grep 'raised an exception'
430 $ hg pull ../a 2>&1 | grep 'raised an exception'
431 error: preoutgoing.broken hook raised an exception: unsupported operand type(s) for +: 'int' and 'dict'
431 error: preoutgoing.broken hook raised an exception: unsupported operand type(s) for +: 'int' and 'dict'
432
432
433 $ echo '[hooks]' > ../a/.hg/hgrc
433 $ echo '[hooks]' > ../a/.hg/hgrc
434 $ echo 'preoutgoing.raise = python:hooktests.raisehook' >> ../a/.hg/hgrc
434 $ echo 'preoutgoing.raise = python:hooktests.raisehook' >> ../a/.hg/hgrc
435 $ hg pull ../a 2>&1 | grep 'raised an exception'
435 $ hg pull ../a 2>&1 | grep 'raised an exception'
436 error: preoutgoing.raise hook raised an exception: exception from hook
436 error: preoutgoing.raise hook raised an exception: exception from hook
437
437
438 $ echo '[hooks]' > ../a/.hg/hgrc
438 $ echo '[hooks]' > ../a/.hg/hgrc
439 $ echo 'preoutgoing.abort = python:hooktests.aborthook' >> ../a/.hg/hgrc
439 $ echo 'preoutgoing.abort = python:hooktests.aborthook' >> ../a/.hg/hgrc
440 $ hg pull ../a
440 $ hg pull ../a
441 pulling from ../a
441 pulling from ../a
442 searching for changes
442 searching for changes
443 error: preoutgoing.abort hook failed: raise abort from hook
443 error: preoutgoing.abort hook failed: raise abort from hook
444 abort: raise abort from hook
444 abort: raise abort from hook
445 [255]
445 [255]
446
446
447 $ echo '[hooks]' > ../a/.hg/hgrc
447 $ echo '[hooks]' > ../a/.hg/hgrc
448 $ echo 'preoutgoing.fail = python:hooktests.failhook' >> ../a/.hg/hgrc
448 $ echo 'preoutgoing.fail = python:hooktests.failhook' >> ../a/.hg/hgrc
449 $ hg pull ../a
449 $ hg pull ../a
450 pulling from ../a
450 pulling from ../a
451 searching for changes
451 searching for changes
452 hook args:
452 hook args:
453 hooktype preoutgoing
453 hooktype preoutgoing
454 source pull
454 source pull
455 abort: preoutgoing.fail hook failed
455 abort: preoutgoing.fail hook failed
456 [255]
456 [255]
457
457
458 $ echo '[hooks]' > ../a/.hg/hgrc
458 $ echo '[hooks]' > ../a/.hg/hgrc
459 $ echo 'preoutgoing.uncallable = python:hooktests.uncallable' >> ../a/.hg/hgrc
459 $ echo 'preoutgoing.uncallable = python:hooktests.uncallable' >> ../a/.hg/hgrc
460 $ hg pull ../a
460 $ hg pull ../a
461 pulling from ../a
461 pulling from ../a
462 searching for changes
462 searching for changes
463 abort: preoutgoing.uncallable hook is invalid ("hooktests.uncallable" is not callable)
463 abort: preoutgoing.uncallable hook is invalid ("hooktests.uncallable" is not callable)
464 [255]
464 [255]
465
465
466 $ echo '[hooks]' > ../a/.hg/hgrc
466 $ echo '[hooks]' > ../a/.hg/hgrc
467 $ echo 'preoutgoing.nohook = python:hooktests.nohook' >> ../a/.hg/hgrc
467 $ echo 'preoutgoing.nohook = python:hooktests.nohook' >> ../a/.hg/hgrc
468 $ hg pull ../a
468 $ hg pull ../a
469 pulling from ../a
469 pulling from ../a
470 searching for changes
470 searching for changes
471 abort: preoutgoing.nohook hook is invalid ("hooktests.nohook" is not defined)
471 abort: preoutgoing.nohook hook is invalid ("hooktests.nohook" is not defined)
472 [255]
472 [255]
473
473
474 $ echo '[hooks]' > ../a/.hg/hgrc
474 $ echo '[hooks]' > ../a/.hg/hgrc
475 $ echo 'preoutgoing.nomodule = python:nomodule' >> ../a/.hg/hgrc
475 $ echo 'preoutgoing.nomodule = python:nomodule' >> ../a/.hg/hgrc
476 $ hg pull ../a
476 $ hg pull ../a
477 pulling from ../a
477 pulling from ../a
478 searching for changes
478 searching for changes
479 abort: preoutgoing.nomodule hook is invalid ("nomodule" not in a module)
479 abort: preoutgoing.nomodule hook is invalid ("nomodule" not in a module)
480 [255]
480 [255]
481
481
482 $ echo '[hooks]' > ../a/.hg/hgrc
482 $ echo '[hooks]' > ../a/.hg/hgrc
483 $ echo 'preoutgoing.badmodule = python:nomodule.nowhere' >> ../a/.hg/hgrc
483 $ echo 'preoutgoing.badmodule = python:nomodule.nowhere' >> ../a/.hg/hgrc
484 $ hg pull ../a
484 $ hg pull ../a
485 pulling from ../a
485 pulling from ../a
486 searching for changes
486 searching for changes
487 abort: preoutgoing.badmodule hook is invalid (import of "nomodule" failed)
487 abort: preoutgoing.badmodule hook is invalid (import of "nomodule" failed)
488 [255]
488 [255]
489
489
490 $ echo '[hooks]' > ../a/.hg/hgrc
490 $ echo '[hooks]' > ../a/.hg/hgrc
491 $ echo 'preoutgoing.unreachable = python:hooktests.container.unreachable' >> ../a/.hg/hgrc
491 $ echo 'preoutgoing.unreachable = python:hooktests.container.unreachable' >> ../a/.hg/hgrc
492 $ hg pull ../a
492 $ hg pull ../a
493 pulling from ../a
493 pulling from ../a
494 searching for changes
494 searching for changes
495 abort: preoutgoing.unreachable hook is invalid (import of "hooktests.container" failed)
495 abort: preoutgoing.unreachable hook is invalid (import of "hooktests.container" failed)
496 [255]
496 [255]
497
497
498 $ echo '[hooks]' > ../a/.hg/hgrc
498 $ echo '[hooks]' > ../a/.hg/hgrc
499 $ echo 'preoutgoing.pass = python:hooktests.passhook' >> ../a/.hg/hgrc
499 $ echo 'preoutgoing.pass = python:hooktests.passhook' >> ../a/.hg/hgrc
500 $ hg pull ../a
500 $ hg pull ../a
501 pulling from ../a
501 pulling from ../a
502 searching for changes
502 searching for changes
503 hook args:
503 hook args:
504 hooktype preoutgoing
504 hooktype preoutgoing
505 source pull
505 source pull
506 adding changesets
506 adding changesets
507 adding manifests
507 adding manifests
508 adding file changes
508 adding file changes
509 added 1 changesets with 1 changes to 1 files
509 added 1 changesets with 1 changes to 1 files
510 adding remote bookmark quux
510 adding remote bookmark quux
511 (run 'hg update' to get a working copy)
511 (run 'hg update' to get a working copy)
512
512
513 make sure --traceback works
513 make sure --traceback works
514
514
515 $ echo '[hooks]' > .hg/hgrc
515 $ echo '[hooks]' > .hg/hgrc
516 $ echo 'commit.abort = python:hooktests.aborthook' >> .hg/hgrc
516 $ echo 'commit.abort = python:hooktests.aborthook' >> .hg/hgrc
517
517
518 $ echo aa > a
518 $ echo aa > a
519 $ hg --traceback commit -d '0 0' -ma 2>&1 | grep '^Traceback'
519 $ hg --traceback commit -d '0 0' -ma 2>&1 | grep '^Traceback'
520 Traceback (most recent call last):
520 Traceback (most recent call last):
521
521
522 $ cd ..
522 $ cd ..
523 $ hg init c
523 $ hg init c
524 $ cd c
524 $ cd c
525
525
526 $ cat > hookext.py <<EOF
526 $ cat > hookext.py <<EOF
527 > def autohook(**args):
527 > def autohook(**args):
528 > print "Automatically installed hook"
528 > print "Automatically installed hook"
529 >
529 >
530 > def reposetup(ui, repo):
530 > def reposetup(ui, repo):
531 > repo.ui.setconfig("hooks", "commit.auto", autohook)
531 > repo.ui.setconfig("hooks", "commit.auto", autohook)
532 > EOF
532 > EOF
533 $ echo '[extensions]' >> .hg/hgrc
533 $ echo '[extensions]' >> .hg/hgrc
534 $ echo 'hookext = hookext.py' >> .hg/hgrc
534 $ echo 'hookext = hookext.py' >> .hg/hgrc
535
535
536 $ touch foo
536 $ touch foo
537 $ hg add foo
537 $ hg add foo
538 $ hg ci -d '0 0' -m 'add foo'
538 $ hg ci -d '0 0' -m 'add foo'
539 Automatically installed hook
539 Automatically installed hook
540 $ echo >> foo
540 $ echo >> foo
541 $ hg ci --debug -d '0 0' -m 'change foo'
541 $ hg ci --debug -d '0 0' -m 'change foo'
542 committing files:
542 committing files:
543 foo
543 foo
544 committing manifest
544 committing manifest
545 committing changelog
545 committing changelog
546 calling hook commit.auto: hgext_hookext.autohook
546 calling hook commit.auto: hgext_hookext.autohook
547 Automatically installed hook
547 Automatically installed hook
548 committed changeset 1:52998019f6252a2b893452765fcb0a47351a5708
548 committed changeset 1:52998019f6252a2b893452765fcb0a47351a5708
549
549
550 $ hg showconfig hooks
550 $ hg showconfig hooks
551 hooks.commit.auto=<function autohook at *> (glob)
551 hooks.commit.auto=<function autohook at *> (glob)
552
552
553 test python hook configured with python:[file]:[hook] syntax
553 test python hook configured with python:[file]:[hook] syntax
554
554
555 $ cd ..
555 $ cd ..
556 $ mkdir d
556 $ mkdir d
557 $ cd d
557 $ cd d
558 $ hg init repo
558 $ hg init repo
559 $ mkdir hooks
559 $ mkdir hooks
560
560
561 $ cd hooks
561 $ cd hooks
562 $ cat > testhooks.py <<EOF
562 $ cat > testhooks.py <<EOF
563 > def testhook(**args):
563 > def testhook(**args):
564 > print 'hook works'
564 > print 'hook works'
565 > EOF
565 > EOF
566 $ echo '[hooks]' > ../repo/.hg/hgrc
566 $ echo '[hooks]' > ../repo/.hg/hgrc
567 $ echo "pre-commit.test = python:`pwd`/testhooks.py:testhook" >> ../repo/.hg/hgrc
567 $ echo "pre-commit.test = python:`pwd`/testhooks.py:testhook" >> ../repo/.hg/hgrc
568
568
569 $ cd ../repo
569 $ cd ../repo
570 $ hg commit -d '0 0'
570 $ hg commit -d '0 0'
571 hook works
571 hook works
572 nothing changed
572 nothing changed
573 [1]
573 [1]
574
574
575 $ echo '[hooks]' > .hg/hgrc
575 $ echo '[hooks]' > .hg/hgrc
576 $ echo "update.ne = python:`pwd`/nonexistent.py:testhook" >> .hg/hgrc
576 $ echo "update.ne = python:`pwd`/nonexistent.py:testhook" >> .hg/hgrc
577 $ echo "pre-identify.npmd = python:`pwd`/:no_python_module_dir" >> .hg/hgrc
577 $ echo "pre-identify.npmd = python:`pwd`/:no_python_module_dir" >> .hg/hgrc
578
578
579 $ hg up null
579 $ hg up null
580 loading update.ne hook failed:
580 loading update.ne hook failed:
581 abort: No such file or directory: $TESTTMP/d/repo/nonexistent.py
581 abort: No such file or directory: $TESTTMP/d/repo/nonexistent.py
582 [255]
582 [255]
583
583
584 $ hg id
584 $ hg id
585 loading pre-identify.npmd hook failed:
585 loading pre-identify.npmd hook failed:
586 abort: No module named repo!
586 abort: No module named repo!
587 [255]
587 [255]
588
588
589 $ cd ../../b
589 $ cd ../../b
590
590
591 make sure --traceback works on hook import failure
591 make sure --traceback works on hook import failure
592
592
593 $ cat > importfail.py <<EOF
593 $ cat > importfail.py <<EOF
594 > import somebogusmodule
594 > import somebogusmodule
595 > # dereference something in the module to force demandimport to load it
595 > # dereference something in the module to force demandimport to load it
596 > somebogusmodule.whatever
596 > somebogusmodule.whatever
597 > EOF
597 > EOF
598
598
599 $ echo '[hooks]' > .hg/hgrc
599 $ echo '[hooks]' > .hg/hgrc
600 $ echo 'precommit.importfail = python:importfail.whatever' >> .hg/hgrc
600 $ echo 'precommit.importfail = python:importfail.whatever' >> .hg/hgrc
601
601
602 $ echo a >> a
602 $ echo a >> a
603 $ hg --traceback commit -ma 2>&1 | egrep -v '^( +File| [a-zA-Z(])'
603 $ hg --traceback commit -ma 2>&1 | egrep -v '^( +File| [a-zA-Z(])'
604 exception from first failed import attempt:
604 exception from first failed import attempt:
605 Traceback (most recent call last):
605 Traceback (most recent call last):
606 ImportError: No module named somebogusmodule
606 ImportError: No module named somebogusmodule
607 exception from second failed import attempt:
607 exception from second failed import attempt:
608 Traceback (most recent call last):
608 Traceback (most recent call last):
609 ImportError: No module named hgext_importfail
609 ImportError: No module named hgext_importfail
610 Traceback (most recent call last):
610 Traceback (most recent call last):
611 Abort: precommit.importfail hook is invalid (import of "importfail" failed)
611 Abort: precommit.importfail hook is invalid (import of "importfail" failed)
612 abort: precommit.importfail hook is invalid (import of "importfail" failed)
612 abort: precommit.importfail hook is invalid (import of "importfail" failed)
613
613
614 Issue1827: Hooks Update & Commit not completely post operation
614 Issue1827: Hooks Update & Commit not completely post operation
615
615
616 commit and update hooks should run after command completion
616 commit and update hooks should run after command completion. The largefiles
617 use demonstrates a recursive wlock, showing the hook doesn't run until the
618 final release (and dirstate flush).
617
619
618 $ echo '[hooks]' > .hg/hgrc
620 $ echo '[hooks]' > .hg/hgrc
619 $ echo 'commit = hg id' >> .hg/hgrc
621 $ echo 'commit = hg id' >> .hg/hgrc
620 $ echo 'update = hg id' >> .hg/hgrc
622 $ echo 'update = hg id' >> .hg/hgrc
621 $ echo bb > a
623 $ echo bb > a
622 $ hg ci -ma
624 $ hg ci -ma
623 223eafe2750c tip
625 223eafe2750c tip
624 $ hg up 0
626 $ hg up 0 --config extensions.largefiles=
625 cb9a9f314b8b
627 cb9a9f314b8b
626 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
628 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
627
629
628 make sure --verbose (and --quiet/--debug etc.) are propagated to the local ui
630 make sure --verbose (and --quiet/--debug etc.) are propagated to the local ui
629 that is passed to pre/post hooks
631 that is passed to pre/post hooks
630
632
631 $ echo '[hooks]' > .hg/hgrc
633 $ echo '[hooks]' > .hg/hgrc
632 $ echo 'pre-identify = python:hooktests.verbosehook' >> .hg/hgrc
634 $ echo 'pre-identify = python:hooktests.verbosehook' >> .hg/hgrc
633 $ hg id
635 $ hg id
634 cb9a9f314b8b
636 cb9a9f314b8b
635 $ hg id --verbose
637 $ hg id --verbose
636 calling hook pre-identify: hooktests.verbosehook
638 calling hook pre-identify: hooktests.verbosehook
637 verbose output from hook
639 verbose output from hook
638 cb9a9f314b8b
640 cb9a9f314b8b
639
641
640 Ensure hooks can be prioritized
642 Ensure hooks can be prioritized
641
643
642 $ echo '[hooks]' > .hg/hgrc
644 $ echo '[hooks]' > .hg/hgrc
643 $ echo 'pre-identify.a = python:hooktests.verbosehook' >> .hg/hgrc
645 $ echo 'pre-identify.a = python:hooktests.verbosehook' >> .hg/hgrc
644 $ echo 'pre-identify.b = python:hooktests.verbosehook' >> .hg/hgrc
646 $ echo 'pre-identify.b = python:hooktests.verbosehook' >> .hg/hgrc
645 $ echo 'priority.pre-identify.b = 1' >> .hg/hgrc
647 $ echo 'priority.pre-identify.b = 1' >> .hg/hgrc
646 $ echo 'pre-identify.c = python:hooktests.verbosehook' >> .hg/hgrc
648 $ echo 'pre-identify.c = python:hooktests.verbosehook' >> .hg/hgrc
647 $ hg id --verbose
649 $ hg id --verbose
648 calling hook pre-identify.b: hooktests.verbosehook
650 calling hook pre-identify.b: hooktests.verbosehook
649 verbose output from hook
651 verbose output from hook
650 calling hook pre-identify.a: hooktests.verbosehook
652 calling hook pre-identify.a: hooktests.verbosehook
651 verbose output from hook
653 verbose output from hook
652 calling hook pre-identify.c: hooktests.verbosehook
654 calling hook pre-identify.c: hooktests.verbosehook
653 verbose output from hook
655 verbose output from hook
654 cb9a9f314b8b
656 cb9a9f314b8b
655
657
656 new tags must be visible in pretxncommit (issue3210)
658 new tags must be visible in pretxncommit (issue3210)
657
659
658 $ echo 'pretxncommit.printtags = python:hooktests.printtags' >> .hg/hgrc
660 $ echo 'pretxncommit.printtags = python:hooktests.printtags' >> .hg/hgrc
659 $ hg tag -f foo
661 $ hg tag -f foo
660 ['a', 'foo', 'tip']
662 ['a', 'foo', 'tip']
661
663
662 new commits must be visible in pretxnchangegroup (issue3428)
664 new commits must be visible in pretxnchangegroup (issue3428)
663
665
664 $ cd ..
666 $ cd ..
665 $ hg init to
667 $ hg init to
666 $ echo '[hooks]' >> to/.hg/hgrc
668 $ echo '[hooks]' >> to/.hg/hgrc
667 $ echo 'pretxnchangegroup = hg --traceback tip' >> to/.hg/hgrc
669 $ echo 'pretxnchangegroup = hg --traceback tip' >> to/.hg/hgrc
668 $ echo a >> to/a
670 $ echo a >> to/a
669 $ hg --cwd to ci -Ama
671 $ hg --cwd to ci -Ama
670 adding a
672 adding a
671 $ hg clone to from
673 $ hg clone to from
672 updating to branch default
674 updating to branch default
673 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
675 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
674 $ echo aa >> from/a
676 $ echo aa >> from/a
675 $ hg --cwd from ci -mb
677 $ hg --cwd from ci -mb
676 $ hg --cwd from push
678 $ hg --cwd from push
677 pushing to $TESTTMP/to (glob)
679 pushing to $TESTTMP/to (glob)
678 searching for changes
680 searching for changes
679 adding changesets
681 adding changesets
680 adding manifests
682 adding manifests
681 adding file changes
683 adding file changes
682 added 1 changesets with 1 changes to 1 files
684 added 1 changesets with 1 changes to 1 files
683 changeset: 1:9836a07b9b9d
685 changeset: 1:9836a07b9b9d
684 tag: tip
686 tag: tip
685 user: test
687 user: test
686 date: Thu Jan 01 00:00:00 1970 +0000
688 date: Thu Jan 01 00:00:00 1970 +0000
687 summary: b
689 summary: b
688
690
General Comments 0
You need to be logged in to leave comments. Login now