##// END OF EJS Templates
submerge: properly deal with overwrites...
Matt Mackall -
r9783:ee00ef6f default
parent child Browse files
Show More
@@ -1,511 +1,512 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, incorporated herein by reference.
6 # GNU General Public License version 2, 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 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._read()
17 self._read()
18 def reset(self, node=None):
18 def reset(self, node=None):
19 self._state = {}
19 self._state = {}
20 if node:
20 if node:
21 self._local = node
21 self._local = node
22 shutil.rmtree(self._repo.join("merge"), True)
22 shutil.rmtree(self._repo.join("merge"), True)
23 def _read(self):
23 def _read(self):
24 self._state = {}
24 self._state = {}
25 try:
25 try:
26 localnode = None
26 localnode = None
27 f = self._repo.opener("merge/state")
27 f = self._repo.opener("merge/state")
28 for i, l in enumerate(f):
28 for i, l in enumerate(f):
29 if i == 0:
29 if i == 0:
30 localnode = l[:-1]
30 localnode = l[:-1]
31 else:
31 else:
32 bits = l[:-1].split("\0")
32 bits = l[:-1].split("\0")
33 self._state[bits[0]] = bits[1:]
33 self._state[bits[0]] = bits[1:]
34 self._local = bin(localnode)
34 self._local = bin(localnode)
35 except IOError, err:
35 except IOError, err:
36 if err.errno != errno.ENOENT:
36 if err.errno != errno.ENOENT:
37 raise
37 raise
38 def _write(self):
38 def _write(self):
39 f = self._repo.opener("merge/state", "w")
39 f = self._repo.opener("merge/state", "w")
40 f.write(hex(self._local) + "\n")
40 f.write(hex(self._local) + "\n")
41 for d, v in self._state.iteritems():
41 for d, v in self._state.iteritems():
42 f.write("\0".join([d] + v) + "\n")
42 f.write("\0".join([d] + v) + "\n")
43 def add(self, fcl, fco, fca, fd, flags):
43 def add(self, fcl, fco, fca, fd, flags):
44 hash = util.sha1(fcl.path()).hexdigest()
44 hash = util.sha1(fcl.path()).hexdigest()
45 self._repo.opener("merge/" + hash, "w").write(fcl.data())
45 self._repo.opener("merge/" + hash, "w").write(fcl.data())
46 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
46 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
47 hex(fca.filenode()), fco.path(), flags]
47 hex(fca.filenode()), fco.path(), flags]
48 self._write()
48 self._write()
49 def __contains__(self, dfile):
49 def __contains__(self, dfile):
50 return dfile in self._state
50 return dfile in self._state
51 def __getitem__(self, dfile):
51 def __getitem__(self, dfile):
52 return self._state[dfile][0]
52 return self._state[dfile][0]
53 def __iter__(self):
53 def __iter__(self):
54 l = self._state.keys()
54 l = self._state.keys()
55 l.sort()
55 l.sort()
56 for f in l:
56 for f in l:
57 yield f
57 yield f
58 def mark(self, dfile, state):
58 def mark(self, dfile, state):
59 self._state[dfile][0] = state
59 self._state[dfile][0] = state
60 self._write()
60 self._write()
61 def resolve(self, dfile, wctx, octx):
61 def resolve(self, dfile, wctx, octx):
62 if self[dfile] == 'r':
62 if self[dfile] == 'r':
63 return 0
63 return 0
64 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
64 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
65 f = self._repo.opener("merge/" + hash)
65 f = self._repo.opener("merge/" + hash)
66 self._repo.wwrite(dfile, f.read(), flags)
66 self._repo.wwrite(dfile, f.read(), flags)
67 fcd = wctx[dfile]
67 fcd = wctx[dfile]
68 fco = octx[ofile]
68 fco = octx[ofile]
69 fca = self._repo.filectx(afile, fileid=anode)
69 fca = self._repo.filectx(afile, fileid=anode)
70 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
70 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
71 if not r:
71 if not r:
72 self.mark(dfile, 'r')
72 self.mark(dfile, 'r')
73 return r
73 return r
74
74
75 def _checkunknown(wctx, mctx):
75 def _checkunknown(wctx, mctx):
76 "check for collisions between unknown files and files in mctx"
76 "check for collisions between unknown files and files in mctx"
77 for f in wctx.unknown():
77 for f in wctx.unknown():
78 if f in mctx and mctx[f].cmp(wctx[f].data()):
78 if f in mctx and mctx[f].cmp(wctx[f].data()):
79 raise util.Abort(_("untracked file in working directory differs"
79 raise util.Abort(_("untracked file in working directory differs"
80 " from file in requested revision: '%s'") % f)
80 " from file in requested revision: '%s'") % f)
81
81
82 def _checkcollision(mctx):
82 def _checkcollision(mctx):
83 "check for case folding collisions in the destination context"
83 "check for case folding collisions in the destination context"
84 folded = {}
84 folded = {}
85 for fn in mctx:
85 for fn in mctx:
86 fold = fn.lower()
86 fold = fn.lower()
87 if fold in folded:
87 if fold in folded:
88 raise util.Abort(_("case-folding collision between %s and %s")
88 raise util.Abort(_("case-folding collision between %s and %s")
89 % (fn, folded[fold]))
89 % (fn, folded[fold]))
90 folded[fold] = fn
90 folded[fold] = fn
91
91
92 def _forgetremoved(wctx, mctx, branchmerge):
92 def _forgetremoved(wctx, mctx, branchmerge):
93 """
93 """
94 Forget removed files
94 Forget removed files
95
95
96 If we're jumping between revisions (as opposed to merging), and if
96 If we're jumping between revisions (as opposed to merging), and if
97 neither the working directory nor the target rev has the file,
97 neither the working directory nor the target rev has the file,
98 then we need to remove it from the dirstate, to prevent the
98 then we need to remove it from the dirstate, to prevent the
99 dirstate from listing the file when it is no longer in the
99 dirstate from listing the file when it is no longer in the
100 manifest.
100 manifest.
101
101
102 If we're merging, and the other revision has removed a file
102 If we're merging, and the other revision has removed a file
103 that is not present in the working directory, we need to mark it
103 that is not present in the working directory, we need to mark it
104 as removed.
104 as removed.
105 """
105 """
106
106
107 action = []
107 action = []
108 state = branchmerge and 'r' or 'f'
108 state = branchmerge and 'r' or 'f'
109 for f in wctx.deleted():
109 for f in wctx.deleted():
110 if f not in mctx:
110 if f not in mctx:
111 action.append((f, state))
111 action.append((f, state))
112
112
113 if not branchmerge:
113 if not branchmerge:
114 for f in wctx.removed():
114 for f in wctx.removed():
115 if f not in mctx:
115 if f not in mctx:
116 action.append((f, "f"))
116 action.append((f, "f"))
117
117
118 return action
118 return action
119
119
120 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
120 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
121 """
121 """
122 Merge p1 and p2 with ancestor ma and generate merge action list
122 Merge p1 and p2 with ancestor ma and generate merge action list
123
123
124 overwrite = whether we clobber working files
124 overwrite = whether we clobber working files
125 partial = function to filter file lists
125 partial = function to filter file lists
126 """
126 """
127
127
128 def fmerge(f, f2, fa):
128 def fmerge(f, f2, fa):
129 """merge flags"""
129 """merge flags"""
130 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
130 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
131 if m == n: # flags agree
131 if m == n: # flags agree
132 return m # unchanged
132 return m # unchanged
133 if m and n and not a: # flags set, don't agree, differ from parent
133 if m and n and not a: # flags set, don't agree, differ from parent
134 r = repo.ui.promptchoice(
134 r = repo.ui.promptchoice(
135 _(" conflicting flags for %s\n"
135 _(" conflicting flags for %s\n"
136 "(n)one, e(x)ec or sym(l)ink?") % f,
136 "(n)one, e(x)ec or sym(l)ink?") % f,
137 (_("&None"), _("E&xec"), _("Sym&link")), 0)
137 (_("&None"), _("E&xec"), _("Sym&link")), 0)
138 if r == 1: return "x" # Exec
138 if r == 1: return "x" # Exec
139 if r == 2: return "l" # Symlink
139 if r == 2: return "l" # Symlink
140 return ""
140 return ""
141 if m and m != a: # changed from a to m
141 if m and m != a: # changed from a to m
142 return m
142 return m
143 if n and n != a: # changed from a to n
143 if n and n != a: # changed from a to n
144 return n
144 return n
145 return '' # flag was cleared
145 return '' # flag was cleared
146
146
147 def act(msg, m, f, *args):
147 def act(msg, m, f, *args):
148 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
148 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
149 action.append((f, m) + args)
149 action.append((f, m) + args)
150
150
151 action, copy = [], {}
151 action, copy = [], {}
152
152
153 if overwrite:
153 if overwrite:
154 pa = p1
154 pa = p1
155 elif pa == p2: # backwards
155 elif pa == p2: # backwards
156 pa = p1.p1()
156 pa = p1.p1()
157 elif pa and repo.ui.configbool("merge", "followcopies", True):
157 elif pa and repo.ui.configbool("merge", "followcopies", True):
158 dirs = repo.ui.configbool("merge", "followdirs", True)
158 dirs = repo.ui.configbool("merge", "followdirs", True)
159 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
159 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
160 for of, fl in diverge.iteritems():
160 for of, fl in diverge.iteritems():
161 act("divergent renames", "dr", of, fl)
161 act("divergent renames", "dr", of, fl)
162
162
163 repo.ui.note(_("resolving manifests\n"))
163 repo.ui.note(_("resolving manifests\n"))
164 repo.ui.debug(" overwrite %s partial %s\n" % (overwrite, bool(partial)))
164 repo.ui.debug(" overwrite %s partial %s\n" % (overwrite, bool(partial)))
165 repo.ui.debug(" ancestor %s local %s remote %s\n" % (pa, p1, p2))
165 repo.ui.debug(" ancestor %s local %s remote %s\n" % (pa, p1, p2))
166
166
167 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
167 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
168 copied = set(copy.values())
168 copied = set(copy.values())
169
169
170 if not overwrite and '.hgsubstate' in m1:
171 # check whether sub state is modified
172 for s in p1.substate:
173 if p1.sub(s).dirty():
174 m1['.hgsubstate'] += "+"
175 break
176
170 # Compare manifests
177 # Compare manifests
171 for f, n in m1.iteritems():
178 for f, n in m1.iteritems():
172 if f == '.hgsubstate':
173 # check whether sub state is modified
174 for s in p1.substate:
175 if p1.sub(s).dirty():
176 n += "+"
177 break
178 if partial and not partial(f):
179 if partial and not partial(f):
179 continue
180 continue
180 if f in m2:
181 if f in m2:
181 rflags = fmerge(f, f, f)
182 rflags = fmerge(f, f, f)
182 a = ma.get(f, nullid)
183 a = ma.get(f, nullid)
183 if n == m2[f] or m2[f] == a: # same or local newer
184 if n == m2[f] or m2[f] == a: # same or local newer
184 if m1.flags(f) != rflags:
185 if m1.flags(f) != rflags:
185 act("update permissions", "e", f, rflags)
186 act("update permissions", "e", f, rflags)
186 elif n == a: # remote newer
187 elif n == a: # remote newer
187 act("remote is newer", "g", f, rflags)
188 act("remote is newer", "g", f, rflags)
188 else: # both changed
189 else: # both changed
189 act("versions differ", "m", f, f, f, rflags, False)
190 act("versions differ", "m", f, f, f, rflags, False)
190 elif f in copied: # files we'll deal with on m2 side
191 elif f in copied: # files we'll deal with on m2 side
191 pass
192 pass
192 elif f in copy:
193 elif f in copy:
193 f2 = copy[f]
194 f2 = copy[f]
194 if f2 not in m2: # directory rename
195 if f2 not in m2: # directory rename
195 act("remote renamed directory to " + f2, "d",
196 act("remote renamed directory to " + f2, "d",
196 f, None, f2, m1.flags(f))
197 f, None, f2, m1.flags(f))
197 else: # case 2 A,B/B/B or case 4,21 A/B/B
198 else: # case 2 A,B/B/B or case 4,21 A/B/B
198 act("local copied/moved to " + f2, "m",
199 act("local copied/moved to " + f2, "m",
199 f, f2, f, fmerge(f, f2, f2), False)
200 f, f2, f, fmerge(f, f2, f2), False)
200 elif f in ma: # clean, a different, no remote
201 elif f in ma: # clean, a different, no remote
201 if n != ma[f]:
202 if n != ma[f]:
202 if repo.ui.promptchoice(
203 if repo.ui.promptchoice(
203 _(" local changed %s which remote deleted\n"
204 _(" local changed %s which remote deleted\n"
204 "use (c)hanged version or (d)elete?") % f,
205 "use (c)hanged version or (d)elete?") % f,
205 (_("&Changed"), _("&Delete")), 0):
206 (_("&Changed"), _("&Delete")), 0):
206 act("prompt delete", "r", f)
207 act("prompt delete", "r", f)
207 else:
208 else:
208 act("prompt keep", "a", f)
209 act("prompt keep", "a", f)
209 elif n[20:] == "a": # added, no remote
210 elif n[20:] == "a": # added, no remote
210 act("remote deleted", "f", f)
211 act("remote deleted", "f", f)
211 elif n[20:] != "u":
212 elif n[20:] != "u":
212 act("other deleted", "r", f)
213 act("other deleted", "r", f)
213
214
214 for f, n in m2.iteritems():
215 for f, n in m2.iteritems():
215 if partial and not partial(f):
216 if partial and not partial(f):
216 continue
217 continue
217 if f in m1 or f in copied: # files already visited
218 if f in m1 or f in copied: # files already visited
218 continue
219 continue
219 if f in copy:
220 if f in copy:
220 f2 = copy[f]
221 f2 = copy[f]
221 if f2 not in m1: # directory rename
222 if f2 not in m1: # directory rename
222 act("local renamed directory to " + f2, "d",
223 act("local renamed directory to " + f2, "d",
223 None, f, f2, m2.flags(f))
224 None, f, f2, m2.flags(f))
224 elif f2 in m2: # rename case 1, A/A,B/A
225 elif f2 in m2: # rename case 1, A/A,B/A
225 act("remote copied to " + f, "m",
226 act("remote copied to " + f, "m",
226 f2, f, f, fmerge(f2, f, f2), False)
227 f2, f, f, fmerge(f2, f, f2), False)
227 else: # case 3,20 A/B/A
228 else: # case 3,20 A/B/A
228 act("remote moved to " + f, "m",
229 act("remote moved to " + f, "m",
229 f2, f, f, fmerge(f2, f, f2), True)
230 f2, f, f, fmerge(f2, f, f2), True)
230 elif f not in ma:
231 elif f not in ma:
231 act("remote created", "g", f, m2.flags(f))
232 act("remote created", "g", f, m2.flags(f))
232 elif n != ma[f]:
233 elif n != ma[f]:
233 if repo.ui.promptchoice(
234 if repo.ui.promptchoice(
234 _("remote changed %s which local deleted\n"
235 _("remote changed %s which local deleted\n"
235 "use (c)hanged version or leave (d)eleted?") % f,
236 "use (c)hanged version or leave (d)eleted?") % f,
236 (_("&Changed"), _("&Deleted")), 0) == 0:
237 (_("&Changed"), _("&Deleted")), 0) == 0:
237 act("prompt recreating", "g", f, m2.flags(f))
238 act("prompt recreating", "g", f, m2.flags(f))
238
239
239 return action
240 return action
240
241
241 def actionkey(a):
242 def actionkey(a):
242 return a[1] == 'r' and -1 or 0, a
243 return a[1] == 'r' and -1 or 0, a
243
244
244 def applyupdates(repo, action, wctx, mctx):
245 def applyupdates(repo, action, wctx, mctx):
245 "apply the merge action list to the working directory"
246 "apply the merge action list to the working directory"
246
247
247 updated, merged, removed, unresolved = 0, 0, 0, 0
248 updated, merged, removed, unresolved = 0, 0, 0, 0
248 ms = mergestate(repo)
249 ms = mergestate(repo)
249 ms.reset(wctx.parents()[0].node())
250 ms.reset(wctx.parents()[0].node())
250 moves = []
251 moves = []
251 action.sort(key=actionkey)
252 action.sort(key=actionkey)
252 substate = wctx.substate # prime
253 substate = wctx.substate # prime
253
254
254 # prescan for merges
255 # prescan for merges
255 for a in action:
256 for a in action:
256 f, m = a[:2]
257 f, m = a[:2]
257 if m == 'm': # merge
258 if m == 'm': # merge
258 f2, fd, flags, move = a[2:]
259 f2, fd, flags, move = a[2:]
259 if f == '.hgsubstate': # merged internally
260 if f == '.hgsubstate': # merged internally
260 continue
261 continue
261 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
262 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
262 fcl = wctx[f]
263 fcl = wctx[f]
263 fco = mctx[f2]
264 fco = mctx[f2]
264 fca = fcl.ancestor(fco) or repo.filectx(f, fileid=nullrev)
265 fca = fcl.ancestor(fco) or repo.filectx(f, fileid=nullrev)
265 ms.add(fcl, fco, fca, fd, flags)
266 ms.add(fcl, fco, fca, fd, flags)
266 if f != fd and move:
267 if f != fd and move:
267 moves.append(f)
268 moves.append(f)
268
269
269 # remove renamed files after safely stored
270 # remove renamed files after safely stored
270 for f in moves:
271 for f in moves:
271 if util.lexists(repo.wjoin(f)):
272 if util.lexists(repo.wjoin(f)):
272 repo.ui.debug("removing %s\n" % f)
273 repo.ui.debug("removing %s\n" % f)
273 os.unlink(repo.wjoin(f))
274 os.unlink(repo.wjoin(f))
274
275
275 audit_path = util.path_auditor(repo.root)
276 audit_path = util.path_auditor(repo.root)
276
277
277 for a in action:
278 for a in action:
278 f, m = a[:2]
279 f, m = a[:2]
279 if f and f[0] == "/":
280 if f and f[0] == "/":
280 continue
281 continue
281 if m == "r": # remove
282 if m == "r": # remove
282 repo.ui.note(_("removing %s\n") % f)
283 repo.ui.note(_("removing %s\n") % f)
283 audit_path(f)
284 audit_path(f)
284 if f == '.hgsubstate': # subrepo states need updating
285 if f == '.hgsubstate': # subrepo states need updating
285 subrepo.submerge(repo, wctx, mctx, wctx)
286 subrepo.submerge(repo, wctx, mctx, wctx)
286 try:
287 try:
287 util.unlink(repo.wjoin(f))
288 util.unlink(repo.wjoin(f))
288 except OSError, inst:
289 except OSError, inst:
289 if inst.errno != errno.ENOENT:
290 if inst.errno != errno.ENOENT:
290 repo.ui.warn(_("update failed to remove %s: %s!\n") %
291 repo.ui.warn(_("update failed to remove %s: %s!\n") %
291 (f, inst.strerror))
292 (f, inst.strerror))
292 removed += 1
293 removed += 1
293 elif m == "m": # merge
294 elif m == "m": # merge
294 if f == '.hgsubstate': # subrepo states need updating
295 if f == '.hgsubstate': # subrepo states need updating
295 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx))
296 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx))
296 continue
297 continue
297 f2, fd, flags, move = a[2:]
298 f2, fd, flags, move = a[2:]
298 r = ms.resolve(fd, wctx, mctx)
299 r = ms.resolve(fd, wctx, mctx)
299 if r is not None and r > 0:
300 if r is not None and r > 0:
300 unresolved += 1
301 unresolved += 1
301 else:
302 else:
302 if r is None:
303 if r is None:
303 updated += 1
304 updated += 1
304 else:
305 else:
305 merged += 1
306 merged += 1
306 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
307 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
307 if f != fd and move and util.lexists(repo.wjoin(f)):
308 if f != fd and move and util.lexists(repo.wjoin(f)):
308 repo.ui.debug("removing %s\n" % f)
309 repo.ui.debug("removing %s\n" % f)
309 os.unlink(repo.wjoin(f))
310 os.unlink(repo.wjoin(f))
310 elif m == "g": # get
311 elif m == "g": # get
311 flags = a[2]
312 flags = a[2]
312 repo.ui.note(_("getting %s\n") % f)
313 repo.ui.note(_("getting %s\n") % f)
313 t = mctx.filectx(f).data()
314 t = mctx.filectx(f).data()
314 repo.wwrite(f, t, flags)
315 repo.wwrite(f, t, flags)
315 updated += 1
316 updated += 1
316 if f == '.hgsubstate': # subrepo states need updating
317 if f == '.hgsubstate': # subrepo states need updating
317 subrepo.submerge(repo, wctx, mctx, wctx)
318 subrepo.submerge(repo, wctx, mctx, wctx)
318 elif m == "d": # directory rename
319 elif m == "d": # directory rename
319 f2, fd, flags = a[2:]
320 f2, fd, flags = a[2:]
320 if f:
321 if f:
321 repo.ui.note(_("moving %s to %s\n") % (f, fd))
322 repo.ui.note(_("moving %s to %s\n") % (f, fd))
322 t = wctx.filectx(f).data()
323 t = wctx.filectx(f).data()
323 repo.wwrite(fd, t, flags)
324 repo.wwrite(fd, t, flags)
324 util.unlink(repo.wjoin(f))
325 util.unlink(repo.wjoin(f))
325 if f2:
326 if f2:
326 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
327 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
327 t = mctx.filectx(f2).data()
328 t = mctx.filectx(f2).data()
328 repo.wwrite(fd, t, flags)
329 repo.wwrite(fd, t, flags)
329 updated += 1
330 updated += 1
330 elif m == "dr": # divergent renames
331 elif m == "dr": # divergent renames
331 fl = a[2]
332 fl = a[2]
332 repo.ui.warn(_("warning: detected divergent renames of %s to:\n") % f)
333 repo.ui.warn(_("warning: detected divergent renames of %s to:\n") % f)
333 for nf in fl:
334 for nf in fl:
334 repo.ui.warn(" %s\n" % nf)
335 repo.ui.warn(" %s\n" % nf)
335 elif m == "e": # exec
336 elif m == "e": # exec
336 flags = a[2]
337 flags = a[2]
337 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
338 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
338
339
339 return updated, merged, removed, unresolved
340 return updated, merged, removed, unresolved
340
341
341 def recordupdates(repo, action, branchmerge):
342 def recordupdates(repo, action, branchmerge):
342 "record merge actions to the dirstate"
343 "record merge actions to the dirstate"
343
344
344 for a in action:
345 for a in action:
345 f, m = a[:2]
346 f, m = a[:2]
346 if m == "r": # remove
347 if m == "r": # remove
347 if branchmerge:
348 if branchmerge:
348 repo.dirstate.remove(f)
349 repo.dirstate.remove(f)
349 else:
350 else:
350 repo.dirstate.forget(f)
351 repo.dirstate.forget(f)
351 elif m == "a": # re-add
352 elif m == "a": # re-add
352 if not branchmerge:
353 if not branchmerge:
353 repo.dirstate.add(f)
354 repo.dirstate.add(f)
354 elif m == "f": # forget
355 elif m == "f": # forget
355 repo.dirstate.forget(f)
356 repo.dirstate.forget(f)
356 elif m == "e": # exec change
357 elif m == "e": # exec change
357 repo.dirstate.normallookup(f)
358 repo.dirstate.normallookup(f)
358 elif m == "g": # get
359 elif m == "g": # get
359 if branchmerge:
360 if branchmerge:
360 repo.dirstate.normaldirty(f)
361 repo.dirstate.normaldirty(f)
361 else:
362 else:
362 repo.dirstate.normal(f)
363 repo.dirstate.normal(f)
363 elif m == "m": # merge
364 elif m == "m": # merge
364 f2, fd, flag, move = a[2:]
365 f2, fd, flag, move = a[2:]
365 if branchmerge:
366 if branchmerge:
366 # We've done a branch merge, mark this file as merged
367 # We've done a branch merge, mark this file as merged
367 # so that we properly record the merger later
368 # so that we properly record the merger later
368 repo.dirstate.merge(fd)
369 repo.dirstate.merge(fd)
369 if f != f2: # copy/rename
370 if f != f2: # copy/rename
370 if move:
371 if move:
371 repo.dirstate.remove(f)
372 repo.dirstate.remove(f)
372 if f != fd:
373 if f != fd:
373 repo.dirstate.copy(f, fd)
374 repo.dirstate.copy(f, fd)
374 else:
375 else:
375 repo.dirstate.copy(f2, fd)
376 repo.dirstate.copy(f2, fd)
376 else:
377 else:
377 # We've update-merged a locally modified file, so
378 # We've update-merged a locally modified file, so
378 # we set the dirstate to emulate a normal checkout
379 # we set the dirstate to emulate a normal checkout
379 # of that file some time in the past. Thus our
380 # of that file some time in the past. Thus our
380 # merge will appear as a normal local file
381 # merge will appear as a normal local file
381 # modification.
382 # modification.
382 repo.dirstate.normallookup(fd)
383 repo.dirstate.normallookup(fd)
383 if move:
384 if move:
384 repo.dirstate.forget(f)
385 repo.dirstate.forget(f)
385 elif m == "d": # directory rename
386 elif m == "d": # directory rename
386 f2, fd, flag = a[2:]
387 f2, fd, flag = a[2:]
387 if not f2 and f not in repo.dirstate:
388 if not f2 and f not in repo.dirstate:
388 # untracked file moved
389 # untracked file moved
389 continue
390 continue
390 if branchmerge:
391 if branchmerge:
391 repo.dirstate.add(fd)
392 repo.dirstate.add(fd)
392 if f:
393 if f:
393 repo.dirstate.remove(f)
394 repo.dirstate.remove(f)
394 repo.dirstate.copy(f, fd)
395 repo.dirstate.copy(f, fd)
395 if f2:
396 if f2:
396 repo.dirstate.copy(f2, fd)
397 repo.dirstate.copy(f2, fd)
397 else:
398 else:
398 repo.dirstate.normal(fd)
399 repo.dirstate.normal(fd)
399 if f:
400 if f:
400 repo.dirstate.forget(f)
401 repo.dirstate.forget(f)
401
402
402 def update(repo, node, branchmerge, force, partial):
403 def update(repo, node, branchmerge, force, partial):
403 """
404 """
404 Perform a merge between the working directory and the given node
405 Perform a merge between the working directory and the given node
405
406
406 node = the node to update to, or None if unspecified
407 node = the node to update to, or None if unspecified
407 branchmerge = whether to merge between branches
408 branchmerge = whether to merge between branches
408 force = whether to force branch merging or file overwriting
409 force = whether to force branch merging or file overwriting
409 partial = a function to filter file lists (dirstate not updated)
410 partial = a function to filter file lists (dirstate not updated)
410
411
411 The table below shows all the behaviors of the update command
412 The table below shows all the behaviors of the update command
412 given the -c and -C or no options, whether the working directory
413 given the -c and -C or no options, whether the working directory
413 is dirty, whether a revision is specified, and the relationship of
414 is dirty, whether a revision is specified, and the relationship of
414 the parent rev to the target rev (linear, on the same named
415 the parent rev to the target rev (linear, on the same named
415 branch, or on another named branch).
416 branch, or on another named branch).
416
417
417 This logic is tested by test-update-branches.
418 This logic is tested by test-update-branches.
418
419
419 -c -C dirty rev | linear same cross
420 -c -C dirty rev | linear same cross
420 n n n n | ok (1) x
421 n n n n | ok (1) x
421 n n n y | ok ok ok
422 n n n y | ok ok ok
422 n n y * | merge (2) (2)
423 n n y * | merge (2) (2)
423 n y * * | --- discard ---
424 n y * * | --- discard ---
424 y n y * | --- (3) ---
425 y n y * | --- (3) ---
425 y n n * | --- ok ---
426 y n n * | --- ok ---
426 y y * * | --- (4) ---
427 y y * * | --- (4) ---
427
428
428 x = can't happen
429 x = can't happen
429 * = don't-care
430 * = don't-care
430 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
431 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
431 2 = abort: crosses branches (use 'hg merge' to merge or
432 2 = abort: crosses branches (use 'hg merge' to merge or
432 use 'hg update -C' to discard changes)
433 use 'hg update -C' to discard changes)
433 3 = abort: uncommitted local changes
434 3 = abort: uncommitted local changes
434 4 = incompatible options (checked in commands.py)
435 4 = incompatible options (checked in commands.py)
435 """
436 """
436
437
437 onode = node
438 onode = node
438 wlock = repo.wlock()
439 wlock = repo.wlock()
439 try:
440 try:
440 wc = repo[None]
441 wc = repo[None]
441 if node is None:
442 if node is None:
442 # tip of current branch
443 # tip of current branch
443 try:
444 try:
444 node = repo.branchtags()[wc.branch()]
445 node = repo.branchtags()[wc.branch()]
445 except KeyError:
446 except KeyError:
446 if wc.branch() == "default": # no default branch!
447 if wc.branch() == "default": # no default branch!
447 node = repo.lookup("tip") # update to tip
448 node = repo.lookup("tip") # update to tip
448 else:
449 else:
449 raise util.Abort(_("branch %s not found") % wc.branch())
450 raise util.Abort(_("branch %s not found") % wc.branch())
450 overwrite = force and not branchmerge
451 overwrite = force and not branchmerge
451 pl = wc.parents()
452 pl = wc.parents()
452 p1, p2 = pl[0], repo[node]
453 p1, p2 = pl[0], repo[node]
453 pa = p1.ancestor(p2)
454 pa = p1.ancestor(p2)
454 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
455 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
455 fastforward = False
456 fastforward = False
456
457
457 ### check phase
458 ### check phase
458 if not overwrite and len(pl) > 1:
459 if not overwrite and len(pl) > 1:
459 raise util.Abort(_("outstanding uncommitted merges"))
460 raise util.Abort(_("outstanding uncommitted merges"))
460 if branchmerge:
461 if branchmerge:
461 if pa == p2:
462 if pa == p2:
462 raise util.Abort(_("can't merge with ancestor"))
463 raise util.Abort(_("can't merge with ancestor"))
463 elif pa == p1:
464 elif pa == p1:
464 if p1.branch() != p2.branch():
465 if p1.branch() != p2.branch():
465 fastforward = True
466 fastforward = True
466 else:
467 else:
467 raise util.Abort(_("nothing to merge (use 'hg update'"
468 raise util.Abort(_("nothing to merge (use 'hg update'"
468 " or check 'hg heads')"))
469 " or check 'hg heads')"))
469 if not force and (wc.files() or wc.deleted()):
470 if not force and (wc.files() or wc.deleted()):
470 raise util.Abort(_("outstanding uncommitted changes "
471 raise util.Abort(_("outstanding uncommitted changes "
471 "(use 'hg status' to list changes)"))
472 "(use 'hg status' to list changes)"))
472 elif not overwrite:
473 elif not overwrite:
473 if pa == p1 or pa == p2: # linear
474 if pa == p1 or pa == p2: # linear
474 pass # all good
475 pass # all good
475 elif wc.files() or wc.deleted():
476 elif wc.files() or wc.deleted():
476 raise util.Abort(_("crosses branches (use 'hg merge' to merge "
477 raise util.Abort(_("crosses branches (use 'hg merge' to merge "
477 "or use 'hg update -C' to discard changes)"))
478 "or use 'hg update -C' to discard changes)"))
478 elif onode is None:
479 elif onode is None:
479 raise util.Abort(_("crosses branches (use 'hg merge' or use "
480 raise util.Abort(_("crosses branches (use 'hg merge' or use "
480 "'hg update -c')"))
481 "'hg update -c')"))
481 else:
482 else:
482 # Allow jumping branches if clean and specific rev given
483 # Allow jumping branches if clean and specific rev given
483 overwrite = True
484 overwrite = True
484
485
485 ### calculate phase
486 ### calculate phase
486 action = []
487 action = []
487 if not force:
488 if not force:
488 _checkunknown(wc, p2)
489 _checkunknown(wc, p2)
489 if not util.checkcase(repo.path):
490 if not util.checkcase(repo.path):
490 _checkcollision(p2)
491 _checkcollision(p2)
491 action += _forgetremoved(wc, p2, branchmerge)
492 action += _forgetremoved(wc, p2, branchmerge)
492 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
493 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
493
494
494 ### apply phase
495 ### apply phase
495 if not branchmerge: # just jump to the new rev
496 if not branchmerge: # just jump to the new rev
496 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
497 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
497 if not partial:
498 if not partial:
498 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
499 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
499
500
500 stats = applyupdates(repo, action, wc, p2)
501 stats = applyupdates(repo, action, wc, p2)
501
502
502 if not partial:
503 if not partial:
503 recordupdates(repo, action, branchmerge)
504 recordupdates(repo, action, branchmerge)
504 repo.dirstate.setparents(fp1, fp2)
505 repo.dirstate.setparents(fp1, fp2)
505 if not branchmerge and not fastforward:
506 if not branchmerge and not fastforward:
506 repo.dirstate.setbranch(p2.branch())
507 repo.dirstate.setbranch(p2.branch())
507 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
508 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
508
509
509 return stats
510 return stats
510 finally:
511 finally:
511 wlock.release()
512 wlock.release()
@@ -1,226 +1,226 b''
1 # subrepo.py - sub-repository handling for Mercurial
1 # subrepo.py - sub-repository 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, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 import errno, os
8 import errno, os
9 from i18n import _
9 from i18n import _
10 import config, util, node, error
10 import config, util, node, error
11 hg = None
11 hg = None
12
12
13 nullstate = ('', '')
13 nullstate = ('', '')
14
14
15 def state(ctx):
15 def state(ctx):
16 p = config.config()
16 p = config.config()
17 def read(f, sections=None, remap=None):
17 def read(f, sections=None, remap=None):
18 if f in ctx:
18 if f in ctx:
19 try:
19 try:
20 p.parse(f, ctx[f].data(), sections, remap)
20 p.parse(f, ctx[f].data(), sections, remap)
21 except IOError, err:
21 except IOError, err:
22 if err.errno != errno.ENOENT:
22 if err.errno != errno.ENOENT:
23 raise
23 raise
24 read('.hgsub')
24 read('.hgsub')
25
25
26 rev = {}
26 rev = {}
27 if '.hgsubstate' in ctx:
27 if '.hgsubstate' in ctx:
28 try:
28 try:
29 for l in ctx['.hgsubstate'].data().splitlines():
29 for l in ctx['.hgsubstate'].data().splitlines():
30 revision, path = l.split(" ", 1)
30 revision, path = l.split(" ", 1)
31 rev[path] = revision
31 rev[path] = revision
32 except IOError, err:
32 except IOError, err:
33 if err.errno != errno.ENOENT:
33 if err.errno != errno.ENOENT:
34 raise
34 raise
35
35
36 state = {}
36 state = {}
37 for path, src in p[''].items():
37 for path, src in p[''].items():
38 state[path] = (src, rev.get(path, ''))
38 state[path] = (src, rev.get(path, ''))
39
39
40 return state
40 return state
41
41
42 def writestate(repo, state):
42 def writestate(repo, state):
43 repo.wwrite('.hgsubstate',
43 repo.wwrite('.hgsubstate',
44 ''.join(['%s %s\n' % (state[s][1], s)
44 ''.join(['%s %s\n' % (state[s][1], s)
45 for s in sorted(state)]), '')
45 for s in sorted(state)]), '')
46
46
47 def submerge(repo, wctx, mctx, actx):
47 def submerge(repo, wctx, mctx, actx):
48 if mctx == actx: # backwards?
48 if mctx == actx: # backwards?
49 actx = wctx.p1()
49 actx = wctx.p1()
50 s1 = wctx.substate
50 s1 = wctx.substate
51 s2 = mctx.substate
51 s2 = mctx.substate
52 sa = actx.substate
52 sa = actx.substate
53 sm = {}
53 sm = {}
54
54
55 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
55 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
56
56
57 def debug(s, msg, r=""):
57 def debug(s, msg, r=""):
58 if r:
58 if r:
59 r = "%s:%s" % r
59 r = "%s:%s" % r
60 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
60 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
61
61
62 for s, l in s1.items():
62 for s, l in s1.items():
63 if wctx.sub(s).dirty():
63 if wctx != actx and wctx.sub(s).dirty():
64 l = (l[0], l[1] + "+")
64 l = (l[0], l[1] + "+")
65 a = sa.get(s, nullstate)
65 a = sa.get(s, nullstate)
66 if s in s2:
66 if s in s2:
67 r = s2[s]
67 r = s2[s]
68 if l == r or r == a: # no change or local is newer
68 if l == r or r == a: # no change or local is newer
69 sm[s] = l
69 sm[s] = l
70 continue
70 continue
71 elif l == a: # other side changed
71 elif l == a: # other side changed
72 debug(s, "other changed, get", r)
72 debug(s, "other changed, get", r)
73 wctx.sub(s).get(r)
73 wctx.sub(s).get(r)
74 sm[s] = r
74 sm[s] = r
75 elif l[0] != r[0]: # sources differ
75 elif l[0] != r[0]: # sources differ
76 if repo.ui.promptchoice(
76 if repo.ui.promptchoice(
77 _(' subrepository sources for %s differ\n'
77 _(' subrepository sources for %s differ\n'
78 'use (l)ocal source (%s) or (r)emote source (%s)?')
78 'use (l)ocal source (%s) or (r)emote source (%s)?')
79 % (s, l[0], r[0]),
79 % (s, l[0], r[0]),
80 (_('&Local'), _('&Remote')), 0):
80 (_('&Local'), _('&Remote')), 0):
81 debug(s, "prompt changed, get", r)
81 debug(s, "prompt changed, get", r)
82 wctx.sub(s).get(r)
82 wctx.sub(s).get(r)
83 sm[s] = r
83 sm[s] = r
84 elif l[1] == a[1]: # local side is unchanged
84 elif l[1] == a[1]: # local side is unchanged
85 debug(s, "other side changed, get", r)
85 debug(s, "other side changed, get", r)
86 wctx.sub(s).get(r)
86 wctx.sub(s).get(r)
87 sm[s] = r
87 sm[s] = r
88 else:
88 else:
89 debug(s, "both sides changed, merge with", r)
89 debug(s, "both sides changed, merge with", r)
90 wctx.sub(s).merge(r)
90 wctx.sub(s).merge(r)
91 sm[s] = l
91 sm[s] = l
92 elif l == a: # remote removed, local unchanged
92 elif l == a: # remote removed, local unchanged
93 debug(s, "remote removed, remove")
93 debug(s, "remote removed, remove")
94 wctx.sub(s).remove()
94 wctx.sub(s).remove()
95 else:
95 else:
96 if repo.ui.promptchoice(
96 if repo.ui.promptchoice(
97 _(' local changed subrepository %s which remote removed\n'
97 _(' local changed subrepository %s which remote removed\n'
98 'use (c)hanged version or (d)elete?') % s,
98 'use (c)hanged version or (d)elete?') % s,
99 (_('&Changed'), _('&Delete')), 0):
99 (_('&Changed'), _('&Delete')), 0):
100 debug(s, "prompt remove")
100 debug(s, "prompt remove")
101 wctx.sub(s).remove()
101 wctx.sub(s).remove()
102
102
103 for s, r in s2.items():
103 for s, r in s2.items():
104 if s in s1:
104 if s in s1:
105 continue
105 continue
106 elif s not in sa:
106 elif s not in sa:
107 debug(s, "remote added, get", r)
107 debug(s, "remote added, get", r)
108 wctx.sub(s).get(r)
108 wctx.sub(s).get(r)
109 sm[s] = r
109 sm[s] = r
110 elif r != sa[s]:
110 elif r != sa[s]:
111 if repo.ui.promptchoice(
111 if repo.ui.promptchoice(
112 _(' remote changed subrepository %s which local removed\n'
112 _(' remote changed subrepository %s which local removed\n'
113 'use (c)hanged version or (d)elete?') % s,
113 'use (c)hanged version or (d)elete?') % s,
114 (_('&Changed'), _('&Delete')), 0) == 0:
114 (_('&Changed'), _('&Delete')), 0) == 0:
115 debug(s, "prompt recreate", r)
115 debug(s, "prompt recreate", r)
116 wctx.sub(s).get(r)
116 wctx.sub(s).get(r)
117 sm[s] = r
117 sm[s] = r
118
118
119 # record merged .hgsubstate
119 # record merged .hgsubstate
120 writestate(repo, sm)
120 writestate(repo, sm)
121
121
122 def _abssource(repo, push=False):
122 def _abssource(repo, push=False):
123 if hasattr(repo, '_subparent'):
123 if hasattr(repo, '_subparent'):
124 source = repo._subsource
124 source = repo._subsource
125 if source.startswith('/') or '://' in source:
125 if source.startswith('/') or '://' in source:
126 return source
126 return source
127 parent = _abssource(repo._subparent)
127 parent = _abssource(repo._subparent)
128 if '://' in parent:
128 if '://' in parent:
129 if parent[-1] == '/':
129 if parent[-1] == '/':
130 parent = parent[:-1]
130 parent = parent[:-1]
131 return parent + '/' + source
131 return parent + '/' + source
132 return os.path.join(parent, repo._subsource)
132 return os.path.join(parent, repo._subsource)
133 if push and repo.ui.config('paths', 'default-push'):
133 if push and repo.ui.config('paths', 'default-push'):
134 return repo.ui.config('paths', 'default-push', repo.root)
134 return repo.ui.config('paths', 'default-push', repo.root)
135 return repo.ui.config('paths', 'default', repo.root)
135 return repo.ui.config('paths', 'default', repo.root)
136
136
137 def subrepo(ctx, path):
137 def subrepo(ctx, path):
138 # subrepo inherently violates our import layering rules
138 # subrepo inherently violates our import layering rules
139 # because it wants to make repo objects from deep inside the stack
139 # because it wants to make repo objects from deep inside the stack
140 # so we manually delay the circular imports to not break
140 # so we manually delay the circular imports to not break
141 # scripts that don't use our demand-loading
141 # scripts that don't use our demand-loading
142 global hg
142 global hg
143 import hg as h
143 import hg as h
144 hg = h
144 hg = h
145
145
146 util.path_auditor(ctx._repo.root)(path)
146 util.path_auditor(ctx._repo.root)(path)
147 state = ctx.substate.get(path, nullstate)
147 state = ctx.substate.get(path, nullstate)
148 if state[0].startswith('['): # future expansion
148 if state[0].startswith('['): # future expansion
149 raise error.Abort('unknown subrepo source %s' % state[0])
149 raise error.Abort('unknown subrepo source %s' % state[0])
150 return hgsubrepo(ctx, path, state)
150 return hgsubrepo(ctx, path, state)
151
151
152 class hgsubrepo(object):
152 class hgsubrepo(object):
153 def __init__(self, ctx, path, state):
153 def __init__(self, ctx, path, state):
154 self._path = path
154 self._path = path
155 self._state = state
155 self._state = state
156 r = ctx._repo
156 r = ctx._repo
157 root = r.wjoin(path)
157 root = r.wjoin(path)
158 if os.path.exists(os.path.join(root, '.hg')):
158 if os.path.exists(os.path.join(root, '.hg')):
159 self._repo = hg.repository(r.ui, root)
159 self._repo = hg.repository(r.ui, root)
160 else:
160 else:
161 util.makedirs(root)
161 util.makedirs(root)
162 self._repo = hg.repository(r.ui, root, create=True)
162 self._repo = hg.repository(r.ui, root, create=True)
163 self._repo._subparent = r
163 self._repo._subparent = r
164 self._repo._subsource = state[0]
164 self._repo._subsource = state[0]
165
165
166 def dirty(self):
166 def dirty(self):
167 r = self._state[1]
167 r = self._state[1]
168 if r == '':
168 if r == '':
169 return True
169 return True
170 w = self._repo[None]
170 w = self._repo[None]
171 if w.p1() != self._repo[r]: # version checked out changed
171 if w.p1() != self._repo[r]: # version checked out changed
172 return True
172 return True
173 return w.dirty() # working directory changed
173 return w.dirty() # working directory changed
174
174
175 def commit(self, text, user, date):
175 def commit(self, text, user, date):
176 self._repo.ui.debug("committing subrepo %s\n" % self._path)
176 self._repo.ui.debug("committing subrepo %s\n" % self._path)
177 n = self._repo.commit(text, user, date)
177 n = self._repo.commit(text, user, date)
178 if not n:
178 if not n:
179 return self._repo['.'].hex() # different version checked out
179 return self._repo['.'].hex() # different version checked out
180 return node.hex(n)
180 return node.hex(n)
181
181
182 def remove(self):
182 def remove(self):
183 # we can't fully delete the repository as it may contain
183 # we can't fully delete the repository as it may contain
184 # local-only history
184 # local-only history
185 self._repo.ui.note(_('removing subrepo %s\n') % self._path)
185 self._repo.ui.note(_('removing subrepo %s\n') % self._path)
186 hg.clean(self._repo, node.nullid, False)
186 hg.clean(self._repo, node.nullid, False)
187
187
188 def _get(self, state):
188 def _get(self, state):
189 source, revision = state
189 source, revision = state
190 try:
190 try:
191 self._repo.lookup(revision)
191 self._repo.lookup(revision)
192 except error.RepoError:
192 except error.RepoError:
193 self._repo._subsource = source
193 self._repo._subsource = source
194 self._repo.ui.status(_('pulling subrepo %s\n') % self._path)
194 self._repo.ui.status(_('pulling subrepo %s\n') % self._path)
195 srcurl = _abssource(self._repo)
195 srcurl = _abssource(self._repo)
196 other = hg.repository(self._repo.ui, srcurl)
196 other = hg.repository(self._repo.ui, srcurl)
197 self._repo.pull(other)
197 self._repo.pull(other)
198
198
199 def get(self, state):
199 def get(self, state):
200 self._get(state)
200 self._get(state)
201 source, revision = state
201 source, revision = state
202 self._repo.ui.debug("getting subrepo %s\n" % self._path)
202 self._repo.ui.debug("getting subrepo %s\n" % self._path)
203 hg.clean(self._repo, revision, False)
203 hg.clean(self._repo, revision, False)
204
204
205 def merge(self, state):
205 def merge(self, state):
206 self._get(state)
206 self._get(state)
207 cur = self._repo['.']
207 cur = self._repo['.']
208 dst = self._repo[state[1]]
208 dst = self._repo[state[1]]
209 if dst.ancestor(cur) == cur:
209 if dst.ancestor(cur) == cur:
210 self._repo.ui.debug("updating subrepo %s\n" % self._path)
210 self._repo.ui.debug("updating subrepo %s\n" % self._path)
211 hg.update(self._repo, state[1])
211 hg.update(self._repo, state[1])
212 else:
212 else:
213 self._repo.ui.debug("merging subrepo %s\n" % self._path)
213 self._repo.ui.debug("merging subrepo %s\n" % self._path)
214 hg.merge(self._repo, state[1], remind=False)
214 hg.merge(self._repo, state[1], remind=False)
215
215
216 def push(self, force):
216 def push(self, force):
217 # push subrepos depth-first for coherent ordering
217 # push subrepos depth-first for coherent ordering
218 c = self._repo['']
218 c = self._repo['']
219 subs = c.substate # only repos that are committed
219 subs = c.substate # only repos that are committed
220 for s in sorted(subs):
220 for s in sorted(subs):
221 c.sub(s).push(force)
221 c.sub(s).push(force)
222
222
223 self._repo.ui.status(_('pushing subrepo %s\n') % self._path)
223 self._repo.ui.status(_('pushing subrepo %s\n') % self._path)
224 dsturl = _abssource(self._repo, True)
224 dsturl = _abssource(self._repo, True)
225 other = hg.repository(self._repo.ui, dsturl)
225 other = hg.repository(self._repo.ui, dsturl)
226 self._repo.push(other, force)
226 self._repo.push(other, force)
@@ -1,197 +1,203 b''
1 % first revision, no sub
1 % first revision, no sub
2 adding a
2 adding a
3 % add first sub
3 % add first sub
4 adding a
4 adding a
5 committing subrepository s
5 committing subrepository s
6 % add sub sub
6 % add sub sub
7 committing subrepository s
7 committing subrepository s
8 committing subrepository ss
8 committing subrepository ss
9 % bump sub rev
9 % bump sub rev
10 committing subrepository s
10 committing subrepository s
11 % leave sub dirty
11 % leave sub dirty
12 committing subrepository s
12 committing subrepository s
13 changeset: 3:1c833a7a9e3a
13 changeset: 3:1c833a7a9e3a
14 tag: tip
14 tag: tip
15 user: test
15 user: test
16 date: Thu Jan 01 00:00:00 1970 +0000
16 date: Thu Jan 01 00:00:00 1970 +0000
17 summary: 4
17 summary: 4
18
18
19 % check caching
19 % check caching
20 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
20 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
21 % restore
21 % restore
22 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
22 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
23 path s
23 path s
24 source s
24 source s
25 revision 1c833a7a9e3a4445c711aaf0f012379cd0d4034e
25 revision 1c833a7a9e3a4445c711aaf0f012379cd0d4034e
26 % new branch for merge tests
26 % new branch for merge tests
27 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
27 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
28 adding t/t
28 adding t/t
29 % 5
29 % 5
30 committing subrepository t
30 committing subrepository t
31 created new head
31 created new head
32 % 6
32 % 6
33 committing subrepository t
33 committing subrepository t
34 path s
34 path s
35 source s
35 source s
36 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
36 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
37 path t
37 path t
38 source t
38 source t
39 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
39 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
40 % 7
40 % 7
41 committing subrepository t
41 committing subrepository t
42 % 8
42 % 8
43 % merge tests
43 % merge tests
44 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
44 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
45 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
45 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
46 (branch merge, don't forget to commit)
46 (branch merge, don't forget to commit)
47 path s
47 path s
48 source s
48 source s
49 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
49 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
50 path t
50 path t
51 source t
51 source t
52 revision 60ca1237c19474e7a3978b0dc1ca4e6f36d51382
52 revision 60ca1237c19474e7a3978b0dc1ca4e6f36d51382
53 created new head
53 created new head
54 searching for copies back to rev 2
54 searching for copies back to rev 2
55 resolving manifests
55 resolving manifests
56 overwrite None partial False
56 overwrite None partial False
57 ancestor 1f14a2e2d3ec local f0d2028bf86d+ remote 1831e14459c4
57 ancestor 1f14a2e2d3ec local f0d2028bf86d+ remote 1831e14459c4
58 .hgsubstate: versions differ -> m
58 .hgsubstate: versions differ -> m
59 subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec
60 subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad
61 getting subrepo t
59 resolving manifests
62 resolving manifests
60 overwrite True partial False
63 overwrite True partial False
61 ancestor 60ca1237c194+ local 60ca1237c194+ remote 6747d179aa9a
64 ancestor 60ca1237c194+ local 60ca1237c194+ remote 6747d179aa9a
62 t: remote is newer -> g
65 t: remote is newer -> g
63 getting t
66 getting t
64 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
67 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
65 (branch merge, don't forget to commit)
68 (branch merge, don't forget to commit)
66 path s
69 path s
67 source s
70 source s
68 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
71 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
69 path t
72 path t
70 source t
73 source t
71 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
74 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
72 committing subrepository t
75 committing subrepository t
73 searching for copies back to rev 2
76 searching for copies back to rev 2
74 resolving manifests
77 resolving manifests
75 overwrite None partial False
78 overwrite None partial False
76 ancestor 1831e14459c4 local e45c8b14af55+ remote f94576341bcf
79 ancestor 1831e14459c4 local e45c8b14af55+ remote f94576341bcf
77 .hgsubstate: versions differ -> m
80 .hgsubstate: versions differ -> m
81 subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4
82 subrepo t: both sides changed, merge with t:7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4
83 merging subrepo t
78 searching for copies back to rev 2
84 searching for copies back to rev 2
79 resolving manifests
85 resolving manifests
80 overwrite None partial False
86 overwrite None partial False
81 ancestor 6747d179aa9a local 20a0db6fbf6c+ remote 7af322bc1198
87 ancestor 6747d179aa9a local 20a0db6fbf6c+ remote 7af322bc1198
82 t: versions differ -> m
88 t: versions differ -> m
83 preserving t for resolve of t
89 preserving t for resolve of t
84 picked tool 'internal:merge' for t (binary False symlink False)
90 picked tool 'internal:merge' for t (binary False symlink False)
85 merging t
91 merging t
86 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
92 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
87 warning: conflicts during merge.
93 warning: conflicts during merge.
88 merging t failed!
94 merging t failed!
89 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
95 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
90 use 'hg resolve' to retry unresolved file merges or 'hg update -C' to abandon
96 use 'hg resolve' to retry unresolved file merges or 'hg update -C' to abandon
91 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
97 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
92 (branch merge, don't forget to commit)
98 (branch merge, don't forget to commit)
93 % should conflict
99 % should conflict
94 <<<<<<< local
100 <<<<<<< local
95 conflict
101 conflict
96 =======
102 =======
97 t3
103 t3
98 >>>>>>> other
104 >>>>>>> other
99 % clone
105 % clone
100 updating to branch default
106 updating to branch default
101 pulling subrepo s
107 pulling subrepo s
102 requesting all changes
108 requesting all changes
103 adding changesets
109 adding changesets
104 adding manifests
110 adding manifests
105 adding file changes
111 adding file changes
106 added 4 changesets with 5 changes to 3 files
112 added 4 changesets with 5 changes to 3 files
107 pulling subrepo ss
113 pulling subrepo ss
108 requesting all changes
114 requesting all changes
109 adding changesets
115 adding changesets
110 adding manifests
116 adding manifests
111 adding file changes
117 adding file changes
112 added 1 changesets with 1 changes to 1 files
118 added 1 changesets with 1 changes to 1 files
113 pulling subrepo t
119 pulling subrepo t
114 requesting all changes
120 requesting all changes
115 adding changesets
121 adding changesets
116 adding manifests
122 adding manifests
117 adding file changes
123 adding file changes
118 added 4 changesets with 4 changes to 1 files (+1 heads)
124 added 4 changesets with 4 changes to 1 files (+1 heads)
119 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
125 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
120 path s
126 path s
121 source s
127 source s
122 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
128 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
123 path t
129 path t
124 source t
130 source t
125 revision 20a0db6fbf6c3d2836e6519a642ae929bfc67c0e
131 revision 20a0db6fbf6c3d2836e6519a642ae929bfc67c0e
126 % push
132 % push
127 committing subrepository t
133 committing subrepository t
128 pushing ...sub/t
134 pushing ...sub/t
129 pushing ...subrepo ss
135 pushing ...subrepo ss
130 searching for changes
136 searching for changes
131 no changes found
137 no changes found
132 pushing ...subrepo s
138 pushing ...subrepo s
133 searching for changes
139 searching for changes
134 no changes found
140 no changes found
135 pushing ...subrepo t
141 pushing ...subrepo t
136 searching for changes
142 searching for changes
137 adding changesets
143 adding changesets
138 adding manifests
144 adding manifests
139 adding file changes
145 adding file changes
140 added 1 changesets with 1 changes to 1 files
146 added 1 changesets with 1 changes to 1 files
141 searching for changes
147 searching for changes
142 adding changesets
148 adding changesets
143 adding manifests
149 adding manifests
144 adding file changes
150 adding file changes
145 added 1 changesets with 1 changes to 1 files
151 added 1 changesets with 1 changes to 1 files
146 % push -f
152 % push -f
147 committing subrepository s
153 committing subrepository s
148 abort: push creates new remote heads!
154 abort: push creates new remote heads!
149 pushing ...sub/t
155 pushing ...sub/t
150 pushing ...subrepo ss
156 pushing ...subrepo ss
151 searching for changes
157 searching for changes
152 no changes found
158 no changes found
153 pushing ...subrepo s
159 pushing ...subrepo s
154 searching for changes
160 searching for changes
155 (did you forget to merge? use push -f to force)
161 (did you forget to merge? use push -f to force)
156 pushing ...subrepo t
162 pushing ...subrepo t
157 searching for changes
163 searching for changes
158 no changes found
164 no changes found
159 searching for changes
165 searching for changes
160 adding changesets
166 adding changesets
161 adding manifests
167 adding manifests
162 adding file changes
168 adding file changes
163 added 1 changesets with 1 changes to 1 files
169 added 1 changesets with 1 changes to 1 files
164 pushing ...sub/t
170 pushing ...sub/t
165 pushing ...subrepo ss
171 pushing ...subrepo ss
166 searching for changes
172 searching for changes
167 no changes found
173 no changes found
168 pushing ...subrepo s
174 pushing ...subrepo s
169 searching for changes
175 searching for changes
170 adding changesets
176 adding changesets
171 adding manifests
177 adding manifests
172 adding file changes
178 adding file changes
173 added 1 changesets with 1 changes to 1 files (+1 heads)
179 added 1 changesets with 1 changes to 1 files (+1 heads)
174 pushing ...subrepo t
180 pushing ...subrepo t
175 searching for changes
181 searching for changes
176 no changes found
182 no changes found
177 searching for changes
183 searching for changes
178 no changes found
184 no changes found
179 % update
185 % update
180 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
186 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
181 committing subrepository t
187 committing subrepository t
182 % pull
188 % pull
183 pulling ...sub/t
189 pulling ...sub/t
184 searching for changes
190 searching for changes
185 adding changesets
191 adding changesets
186 adding manifests
192 adding manifests
187 adding file changes
193 adding file changes
188 added 1 changesets with 1 changes to 1 files
194 added 1 changesets with 1 changes to 1 files
189 (run 'hg update' to get a working copy)
195 (run 'hg update' to get a working copy)
190 pulling subrepo t
196 pulling subrepo t
191 searching for changes
197 searching for changes
192 adding changesets
198 adding changesets
193 adding manifests
199 adding manifests
194 adding file changes
200 adding file changes
195 added 1 changesets with 1 changes to 1 files
201 added 1 changesets with 1 changes to 1 files
196 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
202 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
197 blah
203 blah
General Comments 0
You need to be logged in to leave comments. Login now