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