##// END OF EJS Templates
merge: fix execute bit update issue introduced by 89207edf3973
Patrick Mezard -
r7630:a679bd37 default
parent child Browse files
Show More
@@ -1,502 +1,502 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
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from node import nullid, nullrev, hex, bin
8 from node import nullid, nullrev, hex, bin
9 from i18n import _
9 from i18n import _
10 import errno, util, os, filemerge, copies, shutil
10 import errno, util, os, filemerge, copies, shutil
11
11
12 class mergestate(object):
12 class mergestate(object):
13 '''track 3-way merge state of individual files'''
13 '''track 3-way merge state of individual files'''
14 def __init__(self, repo):
14 def __init__(self, repo):
15 self._repo = repo
15 self._repo = repo
16 self._read()
16 self._read()
17 def reset(self, node):
17 def reset(self, node):
18 self._state = {}
18 self._state = {}
19 self._local = node
19 self._local = node
20 shutil.rmtree(self._repo.join("merge"), True)
20 shutil.rmtree(self._repo.join("merge"), True)
21 def _read(self):
21 def _read(self):
22 self._state = {}
22 self._state = {}
23 try:
23 try:
24 localnode = None
24 localnode = None
25 f = self._repo.opener("merge/state")
25 f = self._repo.opener("merge/state")
26 for i, l in enumerate(f):
26 for i, l in enumerate(f):
27 if i == 0:
27 if i == 0:
28 localnode = l[:-1]
28 localnode = l[:-1]
29 else:
29 else:
30 bits = l[:-1].split("\0")
30 bits = l[:-1].split("\0")
31 self._state[bits[0]] = bits[1:]
31 self._state[bits[0]] = bits[1:]
32 self._local = bin(localnode)
32 self._local = bin(localnode)
33 except IOError, err:
33 except IOError, err:
34 if err.errno != errno.ENOENT:
34 if err.errno != errno.ENOENT:
35 raise
35 raise
36 def _write(self):
36 def _write(self):
37 f = self._repo.opener("merge/state", "w")
37 f = self._repo.opener("merge/state", "w")
38 f.write(hex(self._local) + "\n")
38 f.write(hex(self._local) + "\n")
39 for d, v in self._state.items():
39 for d, v in self._state.items():
40 f.write("\0".join([d] + v) + "\n")
40 f.write("\0".join([d] + v) + "\n")
41 def add(self, fcl, fco, fca, fd, flags):
41 def add(self, fcl, fco, fca, fd, flags):
42 hash = util.sha1(fcl.path()).hexdigest()
42 hash = util.sha1(fcl.path()).hexdigest()
43 self._repo.opener("merge/" + hash, "w").write(fcl.data())
43 self._repo.opener("merge/" + hash, "w").write(fcl.data())
44 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
44 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
45 hex(fca.filenode()), fco.path(), flags]
45 hex(fca.filenode()), fco.path(), flags]
46 self._write()
46 self._write()
47 def __contains__(self, dfile):
47 def __contains__(self, dfile):
48 return dfile in self._state
48 return dfile in self._state
49 def __getitem__(self, dfile):
49 def __getitem__(self, dfile):
50 return self._state[dfile][0]
50 return self._state[dfile][0]
51 def __iter__(self):
51 def __iter__(self):
52 l = self._state.keys()
52 l = self._state.keys()
53 l.sort()
53 l.sort()
54 for f in l:
54 for f in l:
55 yield f
55 yield f
56 def mark(self, dfile, state):
56 def mark(self, dfile, state):
57 self._state[dfile][0] = state
57 self._state[dfile][0] = state
58 self._write()
58 self._write()
59 def resolve(self, dfile, wctx, octx):
59 def resolve(self, dfile, wctx, octx):
60 if self[dfile] == 'r':
60 if self[dfile] == 'r':
61 return 0
61 return 0
62 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
62 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
63 f = self._repo.opener("merge/" + hash)
63 f = self._repo.opener("merge/" + hash)
64 self._repo.wwrite(dfile, f.read(), flags)
64 self._repo.wwrite(dfile, f.read(), flags)
65 fcd = wctx[dfile]
65 fcd = wctx[dfile]
66 fco = octx[ofile]
66 fco = octx[ofile]
67 fca = self._repo.filectx(afile, fileid=anode)
67 fca = self._repo.filectx(afile, fileid=anode)
68 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
68 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
69 if not r:
69 if not r:
70 self.mark(dfile, 'r')
70 self.mark(dfile, 'r')
71 return r
71 return r
72
72
73 def _checkunknown(wctx, mctx):
73 def _checkunknown(wctx, mctx):
74 "check for collisions between unknown files and files in mctx"
74 "check for collisions between unknown files and files in mctx"
75 for f in wctx.unknown():
75 for f in wctx.unknown():
76 if f in mctx and mctx[f].cmp(wctx[f].data()):
76 if f in mctx and mctx[f].cmp(wctx[f].data()):
77 raise util.Abort(_("untracked file in working directory differs"
77 raise util.Abort(_("untracked file in working directory differs"
78 " from file in requested revision: '%s'") % f)
78 " from file in requested revision: '%s'") % f)
79
79
80 def _checkcollision(mctx):
80 def _checkcollision(mctx):
81 "check for case folding collisions in the destination context"
81 "check for case folding collisions in the destination context"
82 folded = {}
82 folded = {}
83 for fn in mctx:
83 for fn in mctx:
84 fold = fn.lower()
84 fold = fn.lower()
85 if fold in folded:
85 if fold in folded:
86 raise util.Abort(_("case-folding collision between %s and %s")
86 raise util.Abort(_("case-folding collision between %s and %s")
87 % (fn, folded[fold]))
87 % (fn, folded[fold]))
88 folded[fold] = fn
88 folded[fold] = fn
89
89
90 def _forgetremoved(wctx, mctx, branchmerge):
90 def _forgetremoved(wctx, mctx, branchmerge):
91 """
91 """
92 Forget removed files
92 Forget removed files
93
93
94 If we're jumping between revisions (as opposed to merging), and if
94 If we're jumping between revisions (as opposed to merging), and if
95 neither the working directory nor the target rev has the file,
95 neither the working directory nor the target rev has the file,
96 then we need to remove it from the dirstate, to prevent the
96 then we need to remove it from the dirstate, to prevent the
97 dirstate from listing the file when it is no longer in the
97 dirstate from listing the file when it is no longer in the
98 manifest.
98 manifest.
99
99
100 If we're merging, and the other revision has removed a file
100 If we're merging, and the other revision has removed a file
101 that is not present in the working directory, we need to mark it
101 that is not present in the working directory, we need to mark it
102 as removed.
102 as removed.
103 """
103 """
104
104
105 action = []
105 action = []
106 state = branchmerge and 'r' or 'f'
106 state = branchmerge and 'r' or 'f'
107 for f in wctx.deleted():
107 for f in wctx.deleted():
108 if f not in mctx:
108 if f not in mctx:
109 action.append((f, state))
109 action.append((f, state))
110
110
111 if not branchmerge:
111 if not branchmerge:
112 for f in wctx.removed():
112 for f in wctx.removed():
113 if f not in mctx:
113 if f not in mctx:
114 action.append((f, "f"))
114 action.append((f, "f"))
115
115
116 return action
116 return action
117
117
118 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
118 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
119 """
119 """
120 Merge p1 and p2 with ancestor ma and generate merge action list
120 Merge p1 and p2 with ancestor ma and generate merge action list
121
121
122 overwrite = whether we clobber working files
122 overwrite = whether we clobber working files
123 partial = function to filter file lists
123 partial = function to filter file lists
124 """
124 """
125
125
126 repo.ui.note(_("resolving manifests\n"))
126 repo.ui.note(_("resolving manifests\n"))
127 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
127 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
128 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
128 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
129
129
130 m1 = p1.manifest()
130 m1 = p1.manifest()
131 m2 = p2.manifest()
131 m2 = p2.manifest()
132 ma = pa.manifest()
132 ma = pa.manifest()
133 backwards = (pa == p2)
133 backwards = (pa == p2)
134 action = []
134 action = []
135 copy, copied, diverge = {}, {}, {}
135 copy, copied, diverge = {}, {}, {}
136
136
137 def fmerge(f, f2=None, fa=None):
137 def fmerge(f, f2=None, fa=None):
138 """merge flags"""
138 """merge flags"""
139 if not f2:
139 if not f2:
140 f2 = f
140 f2 = f
141 fa = f
141 fa = f
142 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
142 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
143 if m == n: # flags agree
143 if m == n: # flags agree
144 return m # unchanged
144 return m # unchanged
145 if m and n: # flags are set but don't agree
145 if m and n: # flags are set but don't agree
146 if not a: # both differ from parent
146 if not a: # both differ from parent
147 r = repo.ui.prompt(
147 r = repo.ui.prompt(
148 _(" conflicting flags for %s\n"
148 _(" conflicting flags for %s\n"
149 "(n)one, e(x)ec or sym(l)ink?") % f, "[nxl]", "n")
149 "(n)one, e(x)ec or sym(l)ink?") % f, "[nxl]", "n")
150 return r != "n" and r or ''
150 return r != "n" and r or ''
151 if m == a:
151 if m == a:
152 return n # changed from m to n
152 return n # changed from m to n
153 return m # changed from n to m
153 return m # changed from n to m
154 if m and m != a: # changed from a to m
154 if m and m != a: # changed from a to m
155 return m
155 return m
156 if n and n != a: # changed from a to n
156 if n and n != a: # changed from a to n
157 return n
157 return n
158 return '' # flag was cleared
158 return '' # flag was cleared
159
159
160 def act(msg, m, f, *args):
160 def act(msg, m, f, *args):
161 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
161 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
162 action.append((f, m) + args)
162 action.append((f, m) + args)
163
163
164 if pa and not (backwards or overwrite):
164 if pa and not (backwards or overwrite):
165 if repo.ui.configbool("merge", "followcopies", True):
165 if repo.ui.configbool("merge", "followcopies", True):
166 dirs = repo.ui.configbool("merge", "followdirs", True)
166 dirs = repo.ui.configbool("merge", "followdirs", True)
167 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
167 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
168 copied = dict.fromkeys(copy.values())
168 copied = dict.fromkeys(copy.values())
169 for of, fl in diverge.items():
169 for of, fl in diverge.items():
170 act("divergent renames", "dr", of, fl)
170 act("divergent renames", "dr", of, fl)
171
171
172 # Compare manifests
172 # Compare manifests
173 for f, n in m1.iteritems():
173 for f, n in m1.iteritems():
174 if partial and not partial(f):
174 if partial and not partial(f):
175 continue
175 continue
176 if f in m2:
176 if f in m2:
177 if overwrite or backwards:
177 if overwrite or backwards:
178 rflags = m2.flags(f)
178 rflags = m2.flags(f)
179 else:
179 else:
180 rflags = fmerge(f)
180 rflags = fmerge(f)
181 # are files different?
181 # are files different?
182 if n != m2[f]:
182 if n != m2[f]:
183 a = ma.get(f, nullid)
183 a = ma.get(f, nullid)
184 # are we clobbering?
184 # are we clobbering?
185 if overwrite:
185 if overwrite:
186 act("clobbering", "g", f, rflags)
186 act("clobbering", "g", f, rflags)
187 # or are we going back in time and clean?
187 # or are we going back in time and clean?
188 elif backwards:
188 elif backwards:
189 if not n[20:] or not p2[f].cmp(p1[f].data()):
189 if not n[20:] or not p2[f].cmp(p1[f].data()):
190 act("reverting", "g", f, rflags)
190 act("reverting", "g", f, rflags)
191 # are both different from the ancestor?
191 # are both different from the ancestor?
192 elif n != a and m2[f] != a:
192 elif n != a and m2[f] != a:
193 act("versions differ", "m", f, f, f, rflags, False)
193 act("versions differ", "m", f, f, f, rflags, False)
194 # is remote's version newer?
194 # is remote's version newer?
195 elif m2[f] != a:
195 elif m2[f] != a:
196 act("remote is newer", "g", f, rflags)
196 act("remote is newer", "g", f, rflags)
197 # local is newer, not overwrite, check mode bits
197 # local is newer, not overwrite, check mode bits
198 elif m1.flags(f) != rflags:
198 elif m1.flags(f) != rflags:
199 act("update permissions", "e", f, rflags)
199 act("update permissions", "e", f, rflags)
200 # contents same, check mode bits
200 # contents same, check mode bits
201 elif m1.flags(f) != rflags:
201 elif m1.flags(f) != rflags:
202 act("update permissions", "e", f, rflags)
202 act("update permissions", "e", f, rflags)
203 elif f in copied:
203 elif f in copied:
204 continue
204 continue
205 elif f in copy:
205 elif f in copy:
206 f2 = copy[f]
206 f2 = copy[f]
207 if f2 not in m2: # directory rename
207 if f2 not in m2: # directory rename
208 act("remote renamed directory to " + f2, "d",
208 act("remote renamed directory to " + f2, "d",
209 f, None, f2, m1.flags(f))
209 f, None, f2, m1.flags(f))
210 elif f2 in m1: # case 2 A,B/B/B
210 elif f2 in m1: # case 2 A,B/B/B
211 act("local copied to " + f2, "m",
211 act("local copied to " + f2, "m",
212 f, f2, f, fmerge(f, f2, f2), False)
212 f, f2, f, fmerge(f, f2, f2), False)
213 else: # case 4,21 A/B/B
213 else: # case 4,21 A/B/B
214 act("local moved to " + f2, "m",
214 act("local moved to " + f2, "m",
215 f, f2, f, fmerge(f, f2, f2), False)
215 f, f2, f, fmerge(f, f2, f2), False)
216 elif f in ma:
216 elif f in ma:
217 if n != ma[f] and not overwrite:
217 if n != ma[f] and not overwrite:
218 if repo.ui.prompt(
218 if repo.ui.prompt(
219 _(" local changed %s which remote deleted\n"
219 _(" local changed %s which remote deleted\n"
220 "use (c)hanged version or (d)elete?") % f,
220 "use (c)hanged version or (d)elete?") % f,
221 _("[cd]"), _("c")) == _("d"):
221 _("[cd]"), _("c")) == _("d"):
222 act("prompt delete", "r", f)
222 act("prompt delete", "r", f)
223 else:
223 else:
224 act("other deleted", "r", f)
224 act("other deleted", "r", f)
225 else:
225 else:
226 # file is created on branch or in working directory
226 # file is created on branch or in working directory
227 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
227 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
228 act("remote deleted", "r", f)
228 act("remote deleted", "r", f)
229
229
230 for f, n in m2.iteritems():
230 for f, n in m2.iteritems():
231 if partial and not partial(f):
231 if partial and not partial(f):
232 continue
232 continue
233 if f in m1:
233 if f in m1:
234 continue
234 continue
235 if f in copied:
235 if f in copied:
236 continue
236 continue
237 if f in copy:
237 if f in copy:
238 f2 = copy[f]
238 f2 = copy[f]
239 if f2 not in m1: # directory rename
239 if f2 not in m1: # directory rename
240 act("local renamed directory to " + f2, "d",
240 act("local renamed directory to " + f2, "d",
241 None, f, f2, m2.flags(f))
241 None, f, f2, m2.flags(f))
242 elif f2 in m2: # rename case 1, A/A,B/A
242 elif f2 in m2: # rename case 1, A/A,B/A
243 act("remote copied to " + f, "m",
243 act("remote copied to " + f, "m",
244 f2, f, f, fmerge(f2, f, f2), False)
244 f2, f, f, fmerge(f2, f, f2), False)
245 else: # case 3,20 A/B/A
245 else: # case 3,20 A/B/A
246 act("remote moved to " + f, "m",
246 act("remote moved to " + f, "m",
247 f2, f, f, fmerge(f2, f, f2), True)
247 f2, f, f, fmerge(f2, f, f2), True)
248 elif f in ma:
248 elif f in ma:
249 if overwrite or backwards:
249 if overwrite or backwards:
250 act("recreating", "g", f, m2.flags(f))
250 act("recreating", "g", f, m2.flags(f))
251 elif n != ma[f]:
251 elif n != ma[f]:
252 if repo.ui.prompt(
252 if repo.ui.prompt(
253 _("remote changed %s which local deleted\n"
253 _("remote changed %s which local deleted\n"
254 "use (c)hanged version or leave (d)eleted?") % f,
254 "use (c)hanged version or leave (d)eleted?") % f,
255 _("[cd]"), _("c")) == _("c"):
255 _("[cd]"), _("c")) == _("c"):
256 act("prompt recreating", "g", f, m2.flags(f))
256 act("prompt recreating", "g", f, m2.flags(f))
257 else:
257 else:
258 act("remote created", "g", f, m2.flags(f))
258 act("remote created", "g", f, m2.flags(f))
259
259
260 return action
260 return action
261
261
262 def actioncmp(a1, a2):
262 def actioncmp(a1, a2):
263 m1 = a1[1]
263 m1 = a1[1]
264 m2 = a2[1]
264 m2 = a2[1]
265 if m1 == m2:
265 if m1 == m2:
266 return cmp(a1, a2)
266 return cmp(a1, a2)
267 if m1 == 'r':
267 if m1 == 'r':
268 return -1
268 return -1
269 if m2 == 'r':
269 if m2 == 'r':
270 return 1
270 return 1
271 return cmp(a1, a2)
271 return cmp(a1, a2)
272
272
273 def applyupdates(repo, action, wctx, mctx):
273 def applyupdates(repo, action, wctx, mctx):
274 "apply the merge action list to the working directory"
274 "apply the merge action list to the working directory"
275
275
276 updated, merged, removed, unresolved = 0, 0, 0, 0
276 updated, merged, removed, unresolved = 0, 0, 0, 0
277 ms = mergestate(repo)
277 ms = mergestate(repo)
278 ms.reset(wctx.parents()[0].node())
278 ms.reset(wctx.parents()[0].node())
279 moves = []
279 moves = []
280 action.sort(actioncmp)
280 action.sort(actioncmp)
281
281
282 # prescan for merges
282 # prescan for merges
283 for a in action:
283 for a in action:
284 f, m = a[:2]
284 f, m = a[:2]
285 if m == 'm': # merge
285 if m == 'm': # merge
286 f2, fd, flags, move = a[2:]
286 f2, fd, flags, move = a[2:]
287 repo.ui.debug(_("preserving %s for resolve of %s\n") % (f, fd))
287 repo.ui.debug(_("preserving %s for resolve of %s\n") % (f, fd))
288 fcl = wctx[f]
288 fcl = wctx[f]
289 fco = mctx[f2]
289 fco = mctx[f2]
290 fca = fcl.ancestor(fco) or repo.filectx(f, fileid=nullrev)
290 fca = fcl.ancestor(fco) or repo.filectx(f, fileid=nullrev)
291 ms.add(fcl, fco, fca, fd, flags)
291 ms.add(fcl, fco, fca, fd, flags)
292 if f != fd and move:
292 if f != fd and move:
293 moves.append(f)
293 moves.append(f)
294
294
295 # remove renamed files after safely stored
295 # remove renamed files after safely stored
296 for f in moves:
296 for f in moves:
297 if util.lexists(repo.wjoin(f)):
297 if util.lexists(repo.wjoin(f)):
298 repo.ui.debug(_("removing %s\n") % f)
298 repo.ui.debug(_("removing %s\n") % f)
299 os.unlink(repo.wjoin(f))
299 os.unlink(repo.wjoin(f))
300
300
301 audit_path = util.path_auditor(repo.root)
301 audit_path = util.path_auditor(repo.root)
302
302
303 for a in action:
303 for a in action:
304 f, m = a[:2]
304 f, m = a[:2]
305 if f and f[0] == "/":
305 if f and f[0] == "/":
306 continue
306 continue
307 if m == "r": # remove
307 if m == "r": # remove
308 repo.ui.note(_("removing %s\n") % f)
308 repo.ui.note(_("removing %s\n") % f)
309 audit_path(f)
309 audit_path(f)
310 try:
310 try:
311 util.unlink(repo.wjoin(f))
311 util.unlink(repo.wjoin(f))
312 except OSError, inst:
312 except OSError, inst:
313 if inst.errno != errno.ENOENT:
313 if inst.errno != errno.ENOENT:
314 repo.ui.warn(_("update failed to remove %s: %s!\n") %
314 repo.ui.warn(_("update failed to remove %s: %s!\n") %
315 (f, inst.strerror))
315 (f, inst.strerror))
316 removed += 1
316 removed += 1
317 elif m == "m": # merge
317 elif m == "m": # merge
318 f2, fd, flags, move = a[2:]
318 f2, fd, flags, move = a[2:]
319 r = ms.resolve(fd, wctx, mctx)
319 r = ms.resolve(fd, wctx, mctx)
320 if r > 0:
320 if r > 0:
321 unresolved += 1
321 unresolved += 1
322 else:
322 else:
323 if r is None:
323 if r is None:
324 updated += 1
324 updated += 1
325 else:
325 else:
326 merged += 1
326 merged += 1
327 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
327 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
328 if f != fd and move and util.lexists(repo.wjoin(f)):
328 if f != fd and move and util.lexists(repo.wjoin(f)):
329 repo.ui.debug(_("removing %s\n") % f)
329 repo.ui.debug(_("removing %s\n") % f)
330 os.unlink(repo.wjoin(f))
330 os.unlink(repo.wjoin(f))
331 elif m == "g": # get
331 elif m == "g": # get
332 flags = a[2]
332 flags = a[2]
333 repo.ui.note(_("getting %s\n") % f)
333 repo.ui.note(_("getting %s\n") % f)
334 t = mctx.filectx(f).data()
334 t = mctx.filectx(f).data()
335 repo.wwrite(f, t, flags)
335 repo.wwrite(f, t, flags)
336 updated += 1
336 updated += 1
337 elif m == "d": # directory rename
337 elif m == "d": # directory rename
338 f2, fd, flags = a[2:]
338 f2, fd, flags = a[2:]
339 if f:
339 if f:
340 repo.ui.note(_("moving %s to %s\n") % (f, fd))
340 repo.ui.note(_("moving %s to %s\n") % (f, fd))
341 t = wctx.filectx(f).data()
341 t = wctx.filectx(f).data()
342 repo.wwrite(fd, t, flags)
342 repo.wwrite(fd, t, flags)
343 util.unlink(repo.wjoin(f))
343 util.unlink(repo.wjoin(f))
344 if f2:
344 if f2:
345 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
345 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
346 t = mctx.filectx(f2).data()
346 t = mctx.filectx(f2).data()
347 repo.wwrite(fd, t, flags)
347 repo.wwrite(fd, t, flags)
348 updated += 1
348 updated += 1
349 elif m == "dr": # divergent renames
349 elif m == "dr": # divergent renames
350 fl = a[2]
350 fl = a[2]
351 repo.ui.warn(_("warning: detected divergent renames of %s to:\n") % f)
351 repo.ui.warn(_("warning: detected divergent renames of %s to:\n") % f)
352 for nf in fl:
352 for nf in fl:
353 repo.ui.warn(" %s\n" % nf)
353 repo.ui.warn(" %s\n" % nf)
354 elif m == "e": # exec
354 elif m == "e": # exec
355 flags = a[2]
355 flags = a[2]
356 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
356 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
357
357
358 return updated, merged, removed, unresolved
358 return updated, merged, removed, unresolved
359
359
360 def recordupdates(repo, action, branchmerge):
360 def recordupdates(repo, action, branchmerge):
361 "record merge actions to the dirstate"
361 "record merge actions to the dirstate"
362
362
363 for a in action:
363 for a in action:
364 f, m = a[:2]
364 f, m = a[:2]
365 if m == "r": # remove
365 if m == "r": # remove
366 if branchmerge:
366 if branchmerge:
367 repo.dirstate.remove(f)
367 repo.dirstate.remove(f)
368 else:
368 else:
369 repo.dirstate.forget(f)
369 repo.dirstate.forget(f)
370 elif m == "f": # forget
370 elif m == "f": # forget
371 repo.dirstate.forget(f)
371 repo.dirstate.forget(f)
372 elif m == "e": # exec change
372 elif m == "e": # exec change
373 repo.dirstate.normaldirty(f)
373 repo.dirstate.normallookup(f)
374 elif m == "g": # get
374 elif m == "g": # get
375 if branchmerge:
375 if branchmerge:
376 repo.dirstate.normaldirty(f)
376 repo.dirstate.normaldirty(f)
377 else:
377 else:
378 repo.dirstate.normal(f)
378 repo.dirstate.normal(f)
379 elif m == "m": # merge
379 elif m == "m": # merge
380 f2, fd, flag, move = a[2:]
380 f2, fd, flag, move = a[2:]
381 if branchmerge:
381 if branchmerge:
382 # We've done a branch merge, mark this file as merged
382 # We've done a branch merge, mark this file as merged
383 # so that we properly record the merger later
383 # so that we properly record the merger later
384 repo.dirstate.merge(fd)
384 repo.dirstate.merge(fd)
385 if f != f2: # copy/rename
385 if f != f2: # copy/rename
386 if move:
386 if move:
387 repo.dirstate.remove(f)
387 repo.dirstate.remove(f)
388 if f != fd:
388 if f != fd:
389 repo.dirstate.copy(f, fd)
389 repo.dirstate.copy(f, fd)
390 else:
390 else:
391 repo.dirstate.copy(f2, fd)
391 repo.dirstate.copy(f2, fd)
392 else:
392 else:
393 # We've update-merged a locally modified file, so
393 # We've update-merged a locally modified file, so
394 # we set the dirstate to emulate a normal checkout
394 # we set the dirstate to emulate a normal checkout
395 # of that file some time in the past. Thus our
395 # of that file some time in the past. Thus our
396 # merge will appear as a normal local file
396 # merge will appear as a normal local file
397 # modification.
397 # modification.
398 repo.dirstate.normallookup(fd)
398 repo.dirstate.normallookup(fd)
399 if move:
399 if move:
400 repo.dirstate.forget(f)
400 repo.dirstate.forget(f)
401 elif m == "d": # directory rename
401 elif m == "d": # directory rename
402 f2, fd, flag = a[2:]
402 f2, fd, flag = a[2:]
403 if not f2 and f not in repo.dirstate:
403 if not f2 and f not in repo.dirstate:
404 # untracked file moved
404 # untracked file moved
405 continue
405 continue
406 if branchmerge:
406 if branchmerge:
407 repo.dirstate.add(fd)
407 repo.dirstate.add(fd)
408 if f:
408 if f:
409 repo.dirstate.remove(f)
409 repo.dirstate.remove(f)
410 repo.dirstate.copy(f, fd)
410 repo.dirstate.copy(f, fd)
411 if f2:
411 if f2:
412 repo.dirstate.copy(f2, fd)
412 repo.dirstate.copy(f2, fd)
413 else:
413 else:
414 repo.dirstate.normal(fd)
414 repo.dirstate.normal(fd)
415 if f:
415 if f:
416 repo.dirstate.forget(f)
416 repo.dirstate.forget(f)
417
417
418 def update(repo, node, branchmerge, force, partial):
418 def update(repo, node, branchmerge, force, partial):
419 """
419 """
420 Perform a merge between the working directory and the given node
420 Perform a merge between the working directory and the given node
421
421
422 branchmerge = whether to merge between branches
422 branchmerge = whether to merge between branches
423 force = whether to force branch merging or file overwriting
423 force = whether to force branch merging or file overwriting
424 partial = a function to filter file lists (dirstate not updated)
424 partial = a function to filter file lists (dirstate not updated)
425 """
425 """
426
426
427 wlock = repo.wlock()
427 wlock = repo.wlock()
428 try:
428 try:
429 wc = repo[None]
429 wc = repo[None]
430 if node is None:
430 if node is None:
431 # tip of current branch
431 # tip of current branch
432 try:
432 try:
433 node = repo.branchtags()[wc.branch()]
433 node = repo.branchtags()[wc.branch()]
434 except KeyError:
434 except KeyError:
435 if wc.branch() == "default": # no default branch!
435 if wc.branch() == "default": # no default branch!
436 node = repo.lookup("tip") # update to tip
436 node = repo.lookup("tip") # update to tip
437 else:
437 else:
438 raise util.Abort(_("branch %s not found") % wc.branch())
438 raise util.Abort(_("branch %s not found") % wc.branch())
439 overwrite = force and not branchmerge
439 overwrite = force and not branchmerge
440 pl = wc.parents()
440 pl = wc.parents()
441 p1, p2 = pl[0], repo[node]
441 p1, p2 = pl[0], repo[node]
442 pa = p1.ancestor(p2)
442 pa = p1.ancestor(p2)
443 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
443 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
444 fastforward = False
444 fastforward = False
445
445
446 ### check phase
446 ### check phase
447 if not overwrite and len(pl) > 1:
447 if not overwrite and len(pl) > 1:
448 raise util.Abort(_("outstanding uncommitted merges"))
448 raise util.Abort(_("outstanding uncommitted merges"))
449 if branchmerge:
449 if branchmerge:
450 if pa == p2:
450 if pa == p2:
451 raise util.Abort(_("can't merge with ancestor"))
451 raise util.Abort(_("can't merge with ancestor"))
452 elif pa == p1:
452 elif pa == p1:
453 if p1.branch() != p2.branch():
453 if p1.branch() != p2.branch():
454 fastforward = True
454 fastforward = True
455 else:
455 else:
456 raise util.Abort(_("nothing to merge (use 'hg update'"
456 raise util.Abort(_("nothing to merge (use 'hg update'"
457 " or check 'hg heads')"))
457 " or check 'hg heads')"))
458 if not force and (wc.files() or wc.deleted()):
458 if not force and (wc.files() or wc.deleted()):
459 raise util.Abort(_("outstanding uncommitted changes"))
459 raise util.Abort(_("outstanding uncommitted changes"))
460 elif not overwrite:
460 elif not overwrite:
461 if pa == p1 or pa == p2: # linear
461 if pa == p1 or pa == p2: # linear
462 pass # all good
462 pass # all good
463 elif p1.branch() == p2.branch():
463 elif p1.branch() == p2.branch():
464 if wc.files() or wc.deleted():
464 if wc.files() or wc.deleted():
465 raise util.Abort(_("crosses branches (use 'hg merge' or "
465 raise util.Abort(_("crosses branches (use 'hg merge' or "
466 "'hg update -C' to discard changes)"))
466 "'hg update -C' to discard changes)"))
467 raise util.Abort(_("crosses branches (use 'hg merge' "
467 raise util.Abort(_("crosses branches (use 'hg merge' "
468 "or 'hg update -C')"))
468 "or 'hg update -C')"))
469 elif wc.files() or wc.deleted():
469 elif wc.files() or wc.deleted():
470 raise util.Abort(_("crosses named branches (use "
470 raise util.Abort(_("crosses named branches (use "
471 "'hg update -C' to discard changes)"))
471 "'hg update -C' to discard changes)"))
472 else:
472 else:
473 # Allow jumping branches if there are no changes
473 # Allow jumping branches if there are no changes
474 overwrite = True
474 overwrite = True
475
475
476 ### calculate phase
476 ### calculate phase
477 action = []
477 action = []
478 if not force:
478 if not force:
479 _checkunknown(wc, p2)
479 _checkunknown(wc, p2)
480 if not util.checkcase(repo.path):
480 if not util.checkcase(repo.path):
481 _checkcollision(p2)
481 _checkcollision(p2)
482 action += _forgetremoved(wc, p2, branchmerge)
482 action += _forgetremoved(wc, p2, branchmerge)
483 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
483 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
484
484
485 ### apply phase
485 ### apply phase
486 if not branchmerge: # just jump to the new rev
486 if not branchmerge: # just jump to the new rev
487 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
487 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
488 if not partial:
488 if not partial:
489 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
489 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
490
490
491 stats = applyupdates(repo, action, wc, p2)
491 stats = applyupdates(repo, action, wc, p2)
492
492
493 if not partial:
493 if not partial:
494 recordupdates(repo, action, branchmerge)
494 recordupdates(repo, action, branchmerge)
495 repo.dirstate.setparents(fp1, fp2)
495 repo.dirstate.setparents(fp1, fp2)
496 if not branchmerge and not fastforward:
496 if not branchmerge and not fastforward:
497 repo.dirstate.setbranch(p2.branch())
497 repo.dirstate.setbranch(p2.branch())
498 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
498 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
499
499
500 return stats
500 return stats
501 finally:
501 finally:
502 del wlock
502 del wlock
@@ -1,17 +1,24 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 rm -rf a
3 rm -rf a
4 hg init a
4 hg init a
5 cd a
5 cd a
6
6
7 echo foo > foo
7 echo foo > foo
8 hg ci -qAm0
8 hg ci -qAm0
9 chmod +x foo
9 chmod +x foo
10 hg ci -m1
10 hg ci -m1
11 hg co -q 0
11 hg co -q 0
12 echo dirty > foo
12 echo dirty > foo
13 sleep 1
13 sleep 1
14 hg up -q
14 hg up -q
15 cat foo
15 cat foo
16 hg st -A
16 hg st -A
17
17
18 echo '% validate update of standalone execute bit change'
19 hg up -C 0
20 chmod -x foo
21 hg ci -m removeexec
22 hg up -C 0
23 hg up
24 hg st
@@ -1,2 +1,7 b''
1 dirty
1 dirty
2 M foo
2 M foo
3 % validate update of standalone execute bit change
4 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
5 nothing changed
6 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
7 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
General Comments 0
You need to be logged in to leave comments. Login now