##// END OF EJS Templates
subrepo: make update -C clean the working directory for svn subrepos...
Erik Zielke -
r13322:c19b9282 stable
parent child Browse files
Show More
@@ -1,544 +1,544 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 except IOError, err:
35 except IOError, err:
36 if err.errno != errno.ENOENT:
36 if err.errno != errno.ENOENT:
37 raise
37 raise
38 self._dirty = False
38 self._dirty = False
39 def commit(self):
39 def commit(self):
40 if self._dirty:
40 if self._dirty:
41 f = self._repo.opener("merge/state", "w")
41 f = self._repo.opener("merge/state", "w")
42 f.write(hex(self._local) + "\n")
42 f.write(hex(self._local) + "\n")
43 for d, v in self._state.iteritems():
43 for d, v in self._state.iteritems():
44 f.write("\0".join([d] + v) + "\n")
44 f.write("\0".join([d] + v) + "\n")
45 self._dirty = False
45 self._dirty = False
46 def add(self, fcl, fco, fca, fd, flags):
46 def add(self, fcl, fco, fca, fd, flags):
47 hash = util.sha1(fcl.path()).hexdigest()
47 hash = util.sha1(fcl.path()).hexdigest()
48 self._repo.opener("merge/" + hash, "w").write(fcl.data())
48 self._repo.opener("merge/" + hash, "w").write(fcl.data())
49 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
49 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
50 hex(fca.filenode()), fco.path(), flags]
50 hex(fca.filenode()), fco.path(), flags]
51 self._dirty = True
51 self._dirty = True
52 def __contains__(self, dfile):
52 def __contains__(self, dfile):
53 return dfile in self._state
53 return dfile in self._state
54 def __getitem__(self, dfile):
54 def __getitem__(self, dfile):
55 return self._state[dfile][0]
55 return self._state[dfile][0]
56 def __iter__(self):
56 def __iter__(self):
57 l = self._state.keys()
57 l = self._state.keys()
58 l.sort()
58 l.sort()
59 for f in l:
59 for f in l:
60 yield f
60 yield f
61 def mark(self, dfile, state):
61 def mark(self, dfile, state):
62 self._state[dfile][0] = state
62 self._state[dfile][0] = state
63 self._dirty = True
63 self._dirty = True
64 def resolve(self, dfile, wctx, octx):
64 def resolve(self, dfile, wctx, octx):
65 if self[dfile] == 'r':
65 if self[dfile] == 'r':
66 return 0
66 return 0
67 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
67 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
68 f = self._repo.opener("merge/" + hash)
68 f = self._repo.opener("merge/" + hash)
69 self._repo.wwrite(dfile, f.read(), flags)
69 self._repo.wwrite(dfile, f.read(), flags)
70 fcd = wctx[dfile]
70 fcd = wctx[dfile]
71 fco = octx[ofile]
71 fco = octx[ofile]
72 fca = self._repo.filectx(afile, fileid=anode)
72 fca = self._repo.filectx(afile, fileid=anode)
73 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
73 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
74 if not r:
74 if not r:
75 self.mark(dfile, 'r')
75 self.mark(dfile, 'r')
76 return r
76 return r
77
77
78 def _checkunknown(wctx, mctx):
78 def _checkunknown(wctx, mctx):
79 "check for collisions between unknown files and files in mctx"
79 "check for collisions between unknown files and files in mctx"
80 for f in wctx.unknown():
80 for f in wctx.unknown():
81 if f in mctx and mctx[f].cmp(wctx[f]):
81 if f in mctx and mctx[f].cmp(wctx[f]):
82 raise util.Abort(_("untracked file in working directory differs"
82 raise util.Abort(_("untracked file in working directory differs"
83 " from file in requested revision: '%s'") % f)
83 " from file in requested revision: '%s'") % f)
84
84
85 def _checkcollision(mctx):
85 def _checkcollision(mctx):
86 "check for case folding collisions in the destination context"
86 "check for case folding collisions in the destination context"
87 folded = {}
87 folded = {}
88 for fn in mctx:
88 for fn in mctx:
89 fold = fn.lower()
89 fold = fn.lower()
90 if fold in folded:
90 if fold in folded:
91 raise util.Abort(_("case-folding collision between %s and %s")
91 raise util.Abort(_("case-folding collision between %s and %s")
92 % (fn, folded[fold]))
92 % (fn, folded[fold]))
93 folded[fold] = fn
93 folded[fold] = fn
94
94
95 def _forgetremoved(wctx, mctx, branchmerge):
95 def _forgetremoved(wctx, mctx, branchmerge):
96 """
96 """
97 Forget removed files
97 Forget removed files
98
98
99 If we're jumping between revisions (as opposed to merging), and if
99 If we're jumping between revisions (as opposed to merging), and if
100 neither the working directory nor the target rev has the file,
100 neither the working directory nor the target rev has the file,
101 then we need to remove it from the dirstate, to prevent the
101 then we need to remove it from the dirstate, to prevent the
102 dirstate from listing the file when it is no longer in the
102 dirstate from listing the file when it is no longer in the
103 manifest.
103 manifest.
104
104
105 If we're merging, and the other revision has removed a file
105 If we're merging, and the other revision has removed a file
106 that is not present in the working directory, we need to mark it
106 that is not present in the working directory, we need to mark it
107 as removed.
107 as removed.
108 """
108 """
109
109
110 action = []
110 action = []
111 state = branchmerge and 'r' or 'f'
111 state = branchmerge and 'r' or 'f'
112 for f in wctx.deleted():
112 for f in wctx.deleted():
113 if f not in mctx:
113 if f not in mctx:
114 action.append((f, state))
114 action.append((f, state))
115
115
116 if not branchmerge:
116 if not branchmerge:
117 for f in wctx.removed():
117 for f in wctx.removed():
118 if f not in mctx:
118 if f not in mctx:
119 action.append((f, "f"))
119 action.append((f, "f"))
120
120
121 return action
121 return action
122
122
123 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
123 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
124 """
124 """
125 Merge p1 and p2 with ancestor pa and generate merge action list
125 Merge p1 and p2 with ancestor pa and generate merge action list
126
126
127 overwrite = whether we clobber working files
127 overwrite = whether we clobber working files
128 partial = function to filter file lists
128 partial = function to filter file lists
129 """
129 """
130
130
131 def fmerge(f, f2, fa):
131 def fmerge(f, f2, fa):
132 """merge flags"""
132 """merge flags"""
133 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
133 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
134 if m == n: # flags agree
134 if m == n: # flags agree
135 return m # unchanged
135 return m # unchanged
136 if m and n and not a: # flags set, don't agree, differ from parent
136 if m and n and not a: # flags set, don't agree, differ from parent
137 r = repo.ui.promptchoice(
137 r = repo.ui.promptchoice(
138 _(" conflicting flags for %s\n"
138 _(" conflicting flags for %s\n"
139 "(n)one, e(x)ec or sym(l)ink?") % f,
139 "(n)one, e(x)ec or sym(l)ink?") % f,
140 (_("&None"), _("E&xec"), _("Sym&link")), 0)
140 (_("&None"), _("E&xec"), _("Sym&link")), 0)
141 if r == 1:
141 if r == 1:
142 return "x" # Exec
142 return "x" # Exec
143 if r == 2:
143 if r == 2:
144 return "l" # Symlink
144 return "l" # Symlink
145 return ""
145 return ""
146 if m and m != a: # changed from a to m
146 if m and m != a: # changed from a to m
147 return m
147 return m
148 if n and n != a: # changed from a to n
148 if n and n != a: # changed from a to n
149 return n
149 return n
150 return '' # flag was cleared
150 return '' # flag was cleared
151
151
152 def act(msg, m, f, *args):
152 def act(msg, m, f, *args):
153 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
153 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
154 action.append((f, m) + args)
154 action.append((f, m) + args)
155
155
156 action, copy = [], {}
156 action, copy = [], {}
157
157
158 if overwrite:
158 if overwrite:
159 pa = p1
159 pa = p1
160 elif pa == p2: # backwards
160 elif pa == p2: # backwards
161 pa = p1.p1()
161 pa = p1.p1()
162 elif pa and repo.ui.configbool("merge", "followcopies", True):
162 elif pa and repo.ui.configbool("merge", "followcopies", True):
163 dirs = repo.ui.configbool("merge", "followdirs", True)
163 dirs = repo.ui.configbool("merge", "followdirs", True)
164 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
164 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
165 for of, fl in diverge.iteritems():
165 for of, fl in diverge.iteritems():
166 act("divergent renames", "dr", of, fl)
166 act("divergent renames", "dr", of, fl)
167
167
168 repo.ui.note(_("resolving manifests\n"))
168 repo.ui.note(_("resolving manifests\n"))
169 repo.ui.debug(" overwrite %s partial %s\n" % (overwrite, bool(partial)))
169 repo.ui.debug(" overwrite %s partial %s\n" % (overwrite, bool(partial)))
170 repo.ui.debug(" ancestor %s local %s remote %s\n" % (pa, p1, p2))
170 repo.ui.debug(" ancestor %s local %s remote %s\n" % (pa, p1, p2))
171
171
172 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
172 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
173 copied = set(copy.values())
173 copied = set(copy.values())
174
174
175 if '.hgsubstate' in m1:
175 if '.hgsubstate' in m1:
176 # check whether sub state is modified
176 # check whether sub state is modified
177 for s in p1.substate:
177 for s in p1.substate:
178 if p1.sub(s).dirty():
178 if p1.sub(s).dirty():
179 m1['.hgsubstate'] += "+"
179 m1['.hgsubstate'] += "+"
180 break
180 break
181
181
182 # Compare manifests
182 # Compare manifests
183 for f, n in m1.iteritems():
183 for f, n in m1.iteritems():
184 if partial and not partial(f):
184 if partial and not partial(f):
185 continue
185 continue
186 if f in m2:
186 if f in m2:
187 rflags = fmerge(f, f, f)
187 rflags = fmerge(f, f, f)
188 a = ma.get(f, nullid)
188 a = ma.get(f, nullid)
189 if n == m2[f] or m2[f] == a: # same or local newer
189 if n == m2[f] or m2[f] == a: # same or local newer
190 # is file locally modified or flags need changing?
190 # is file locally modified or flags need changing?
191 # dirstate flags may need to be made current
191 # dirstate flags may need to be made current
192 if m1.flags(f) != rflags or n[20:]:
192 if m1.flags(f) != rflags or n[20:]:
193 act("update permissions", "e", f, rflags)
193 act("update permissions", "e", f, rflags)
194 elif n == a: # remote newer
194 elif n == a: # remote newer
195 act("remote is newer", "g", f, rflags)
195 act("remote is newer", "g", f, rflags)
196 else: # both changed
196 else: # both changed
197 act("versions differ", "m", f, f, f, rflags, False)
197 act("versions differ", "m", f, f, f, rflags, False)
198 elif f in copied: # files we'll deal with on m2 side
198 elif f in copied: # files we'll deal with on m2 side
199 pass
199 pass
200 elif f in copy:
200 elif f in copy:
201 f2 = copy[f]
201 f2 = copy[f]
202 if f2 not in m2: # directory rename
202 if f2 not in m2: # directory rename
203 act("remote renamed directory to " + f2, "d",
203 act("remote renamed directory to " + f2, "d",
204 f, None, f2, m1.flags(f))
204 f, None, f2, m1.flags(f))
205 else: # case 2 A,B/B/B or case 4,21 A/B/B
205 else: # case 2 A,B/B/B or case 4,21 A/B/B
206 act("local copied/moved to " + f2, "m",
206 act("local copied/moved to " + f2, "m",
207 f, f2, f, fmerge(f, f2, f2), False)
207 f, f2, f, fmerge(f, f2, f2), False)
208 elif f in ma: # clean, a different, no remote
208 elif f in ma: # clean, a different, no remote
209 if n != ma[f]:
209 if n != ma[f]:
210 if repo.ui.promptchoice(
210 if repo.ui.promptchoice(
211 _(" local changed %s which remote deleted\n"
211 _(" local changed %s which remote deleted\n"
212 "use (c)hanged version or (d)elete?") % f,
212 "use (c)hanged version or (d)elete?") % f,
213 (_("&Changed"), _("&Delete")), 0):
213 (_("&Changed"), _("&Delete")), 0):
214 act("prompt delete", "r", f)
214 act("prompt delete", "r", f)
215 else:
215 else:
216 act("prompt keep", "a", f)
216 act("prompt keep", "a", f)
217 elif n[20:] == "a": # added, no remote
217 elif n[20:] == "a": # added, no remote
218 act("remote deleted", "f", f)
218 act("remote deleted", "f", f)
219 elif n[20:] != "u":
219 elif n[20:] != "u":
220 act("other deleted", "r", f)
220 act("other deleted", "r", f)
221
221
222 for f, n in m2.iteritems():
222 for f, n in m2.iteritems():
223 if partial and not partial(f):
223 if partial and not partial(f):
224 continue
224 continue
225 if f in m1 or f in copied: # files already visited
225 if f in m1 or f in copied: # files already visited
226 continue
226 continue
227 if f in copy:
227 if f in copy:
228 f2 = copy[f]
228 f2 = copy[f]
229 if f2 not in m1: # directory rename
229 if f2 not in m1: # directory rename
230 act("local renamed directory to " + f2, "d",
230 act("local renamed directory to " + f2, "d",
231 None, f, f2, m2.flags(f))
231 None, f, f2, m2.flags(f))
232 elif f2 in m2: # rename case 1, A/A,B/A
232 elif f2 in m2: # rename case 1, A/A,B/A
233 act("remote copied to " + f, "m",
233 act("remote copied to " + f, "m",
234 f2, f, f, fmerge(f2, f, f2), False)
234 f2, f, f, fmerge(f2, f, f2), False)
235 else: # case 3,20 A/B/A
235 else: # case 3,20 A/B/A
236 act("remote moved to " + f, "m",
236 act("remote moved to " + f, "m",
237 f2, f, f, fmerge(f2, f, f2), True)
237 f2, f, f, fmerge(f2, f, f2), True)
238 elif f not in ma:
238 elif f not in ma:
239 act("remote created", "g", f, m2.flags(f))
239 act("remote created", "g", f, m2.flags(f))
240 elif n != ma[f]:
240 elif n != ma[f]:
241 if repo.ui.promptchoice(
241 if repo.ui.promptchoice(
242 _("remote changed %s which local deleted\n"
242 _("remote changed %s which local deleted\n"
243 "use (c)hanged version or leave (d)eleted?") % f,
243 "use (c)hanged version or leave (d)eleted?") % f,
244 (_("&Changed"), _("&Deleted")), 0) == 0:
244 (_("&Changed"), _("&Deleted")), 0) == 0:
245 act("prompt recreating", "g", f, m2.flags(f))
245 act("prompt recreating", "g", f, m2.flags(f))
246
246
247 return action
247 return action
248
248
249 def actionkey(a):
249 def actionkey(a):
250 return a[1] == 'r' and -1 or 0, a
250 return a[1] == 'r' and -1 or 0, a
251
251
252 def applyupdates(repo, action, wctx, mctx, actx):
252 def applyupdates(repo, action, wctx, mctx, actx, overwrite):
253 """apply the merge action list to the working directory
253 """apply the merge action list to the working directory
254
254
255 wctx is the working copy context
255 wctx is the working copy context
256 mctx is the context to be merged into the working copy
256 mctx is the context to be merged into the working copy
257 actx is the context of the common ancestor
257 actx is the context of the common ancestor
258 """
258 """
259
259
260 updated, merged, removed, unresolved = 0, 0, 0, 0
260 updated, merged, removed, unresolved = 0, 0, 0, 0
261 ms = mergestate(repo)
261 ms = mergestate(repo)
262 ms.reset(wctx.parents()[0].node())
262 ms.reset(wctx.parents()[0].node())
263 moves = []
263 moves = []
264 action.sort(key=actionkey)
264 action.sort(key=actionkey)
265 substate = wctx.substate # prime
265 substate = wctx.substate # prime
266
266
267 # prescan for merges
267 # prescan for merges
268 u = repo.ui
268 u = repo.ui
269 for a in action:
269 for a in action:
270 f, m = a[:2]
270 f, m = a[:2]
271 if m == 'm': # merge
271 if m == 'm': # merge
272 f2, fd, flags, move = a[2:]
272 f2, fd, flags, move = a[2:]
273 if f == '.hgsubstate': # merged internally
273 if f == '.hgsubstate': # merged internally
274 continue
274 continue
275 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
275 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
276 fcl = wctx[f]
276 fcl = wctx[f]
277 fco = mctx[f2]
277 fco = mctx[f2]
278 if mctx == actx: # backwards, use working dir parent as ancestor
278 if mctx == actx: # backwards, use working dir parent as ancestor
279 if fcl.parents():
279 if fcl.parents():
280 fca = fcl.parents()[0]
280 fca = fcl.parents()[0]
281 else:
281 else:
282 fca = repo.filectx(f, fileid=nullrev)
282 fca = repo.filectx(f, fileid=nullrev)
283 else:
283 else:
284 fca = fcl.ancestor(fco, actx)
284 fca = fcl.ancestor(fco, actx)
285 if not fca:
285 if not fca:
286 fca = repo.filectx(f, fileid=nullrev)
286 fca = repo.filectx(f, fileid=nullrev)
287 ms.add(fcl, fco, fca, fd, flags)
287 ms.add(fcl, fco, fca, fd, flags)
288 if f != fd and move:
288 if f != fd and move:
289 moves.append(f)
289 moves.append(f)
290
290
291 # remove renamed files after safely stored
291 # remove renamed files after safely stored
292 for f in moves:
292 for f in moves:
293 if os.path.lexists(repo.wjoin(f)):
293 if os.path.lexists(repo.wjoin(f)):
294 repo.ui.debug("removing %s\n" % f)
294 repo.ui.debug("removing %s\n" % f)
295 os.unlink(repo.wjoin(f))
295 os.unlink(repo.wjoin(f))
296
296
297 audit_path = util.path_auditor(repo.root)
297 audit_path = util.path_auditor(repo.root)
298
298
299 numupdates = len(action)
299 numupdates = len(action)
300 for i, a in enumerate(action):
300 for i, a in enumerate(action):
301 f, m = a[:2]
301 f, m = a[:2]
302 u.progress(_('updating'), i + 1, item=f, total=numupdates,
302 u.progress(_('updating'), i + 1, item=f, total=numupdates,
303 unit=_('files'))
303 unit=_('files'))
304 if f and f[0] == "/":
304 if f and f[0] == "/":
305 continue
305 continue
306 if m == "r": # remove
306 if m == "r": # remove
307 repo.ui.note(_("removing %s\n") % f)
307 repo.ui.note(_("removing %s\n") % f)
308 audit_path(f)
308 audit_path(f)
309 if f == '.hgsubstate': # subrepo states need updating
309 if f == '.hgsubstate': # subrepo states need updating
310 subrepo.submerge(repo, wctx, mctx, wctx)
310 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
311 try:
311 try:
312 util.unlink(repo.wjoin(f))
312 util.unlink(repo.wjoin(f))
313 except OSError, inst:
313 except OSError, inst:
314 if inst.errno != errno.ENOENT:
314 if inst.errno != errno.ENOENT:
315 repo.ui.warn(_("update failed to remove %s: %s!\n") %
315 repo.ui.warn(_("update failed to remove %s: %s!\n") %
316 (f, inst.strerror))
316 (f, inst.strerror))
317 removed += 1
317 removed += 1
318 elif m == "m": # merge
318 elif m == "m": # merge
319 if f == '.hgsubstate': # subrepo states need updating
319 if f == '.hgsubstate': # subrepo states need updating
320 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx))
320 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx), overwrite)
321 continue
321 continue
322 f2, fd, flags, move = a[2:]
322 f2, fd, flags, move = a[2:]
323 r = ms.resolve(fd, wctx, mctx)
323 r = ms.resolve(fd, wctx, mctx)
324 if r is not None and r > 0:
324 if r is not None and r > 0:
325 unresolved += 1
325 unresolved += 1
326 else:
326 else:
327 if r is None:
327 if r is None:
328 updated += 1
328 updated += 1
329 else:
329 else:
330 merged += 1
330 merged += 1
331 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
331 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
332 if f != fd and move and os.path.lexists(repo.wjoin(f)):
332 if f != fd and move and os.path.lexists(repo.wjoin(f)):
333 repo.ui.debug("removing %s\n" % f)
333 repo.ui.debug("removing %s\n" % f)
334 os.unlink(repo.wjoin(f))
334 os.unlink(repo.wjoin(f))
335 elif m == "g": # get
335 elif m == "g": # get
336 flags = a[2]
336 flags = a[2]
337 repo.ui.note(_("getting %s\n") % f)
337 repo.ui.note(_("getting %s\n") % f)
338 t = mctx.filectx(f).data()
338 t = mctx.filectx(f).data()
339 repo.wwrite(f, t, flags)
339 repo.wwrite(f, t, flags)
340 t = None
340 t = None
341 updated += 1
341 updated += 1
342 if f == '.hgsubstate': # subrepo states need updating
342 if f == '.hgsubstate': # subrepo states need updating
343 subrepo.submerge(repo, wctx, mctx, wctx)
343 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
344 elif m == "d": # directory rename
344 elif m == "d": # directory rename
345 f2, fd, flags = a[2:]
345 f2, fd, flags = a[2:]
346 if f:
346 if f:
347 repo.ui.note(_("moving %s to %s\n") % (f, fd))
347 repo.ui.note(_("moving %s to %s\n") % (f, fd))
348 t = wctx.filectx(f).data()
348 t = wctx.filectx(f).data()
349 repo.wwrite(fd, t, flags)
349 repo.wwrite(fd, t, flags)
350 util.unlink(repo.wjoin(f))
350 util.unlink(repo.wjoin(f))
351 if f2:
351 if f2:
352 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
352 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
353 t = mctx.filectx(f2).data()
353 t = mctx.filectx(f2).data()
354 repo.wwrite(fd, t, flags)
354 repo.wwrite(fd, t, flags)
355 updated += 1
355 updated += 1
356 elif m == "dr": # divergent renames
356 elif m == "dr": # divergent renames
357 fl = a[2]
357 fl = a[2]
358 repo.ui.warn(_("note: possible conflict - %s was renamed "
358 repo.ui.warn(_("note: possible conflict - %s was renamed "
359 "multiple times to:\n") % f)
359 "multiple times to:\n") % f)
360 for nf in fl:
360 for nf in fl:
361 repo.ui.warn(" %s\n" % nf)
361 repo.ui.warn(" %s\n" % nf)
362 elif m == "e": # exec
362 elif m == "e": # exec
363 flags = a[2]
363 flags = a[2]
364 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
364 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
365 ms.commit()
365 ms.commit()
366 u.progress(_('updating'), None, total=numupdates, unit=_('files'))
366 u.progress(_('updating'), None, total=numupdates, unit=_('files'))
367
367
368 return updated, merged, removed, unresolved
368 return updated, merged, removed, unresolved
369
369
370 def recordupdates(repo, action, branchmerge):
370 def recordupdates(repo, action, branchmerge):
371 "record merge actions to the dirstate"
371 "record merge actions to the dirstate"
372
372
373 for a in action:
373 for a in action:
374 f, m = a[:2]
374 f, m = a[:2]
375 if m == "r": # remove
375 if m == "r": # remove
376 if branchmerge:
376 if branchmerge:
377 repo.dirstate.remove(f)
377 repo.dirstate.remove(f)
378 else:
378 else:
379 repo.dirstate.forget(f)
379 repo.dirstate.forget(f)
380 elif m == "a": # re-add
380 elif m == "a": # re-add
381 if not branchmerge:
381 if not branchmerge:
382 repo.dirstate.add(f)
382 repo.dirstate.add(f)
383 elif m == "f": # forget
383 elif m == "f": # forget
384 repo.dirstate.forget(f)
384 repo.dirstate.forget(f)
385 elif m == "e": # exec change
385 elif m == "e": # exec change
386 repo.dirstate.normallookup(f)
386 repo.dirstate.normallookup(f)
387 elif m == "g": # get
387 elif m == "g": # get
388 if branchmerge:
388 if branchmerge:
389 repo.dirstate.otherparent(f)
389 repo.dirstate.otherparent(f)
390 else:
390 else:
391 repo.dirstate.normal(f)
391 repo.dirstate.normal(f)
392 elif m == "m": # merge
392 elif m == "m": # merge
393 f2, fd, flag, move = a[2:]
393 f2, fd, flag, move = a[2:]
394 if branchmerge:
394 if branchmerge:
395 # We've done a branch merge, mark this file as merged
395 # We've done a branch merge, mark this file as merged
396 # so that we properly record the merger later
396 # so that we properly record the merger later
397 repo.dirstate.merge(fd)
397 repo.dirstate.merge(fd)
398 if f != f2: # copy/rename
398 if f != f2: # copy/rename
399 if move:
399 if move:
400 repo.dirstate.remove(f)
400 repo.dirstate.remove(f)
401 if f != fd:
401 if f != fd:
402 repo.dirstate.copy(f, fd)
402 repo.dirstate.copy(f, fd)
403 else:
403 else:
404 repo.dirstate.copy(f2, fd)
404 repo.dirstate.copy(f2, fd)
405 else:
405 else:
406 # We've update-merged a locally modified file, so
406 # We've update-merged a locally modified file, so
407 # we set the dirstate to emulate a normal checkout
407 # we set the dirstate to emulate a normal checkout
408 # of that file some time in the past. Thus our
408 # of that file some time in the past. Thus our
409 # merge will appear as a normal local file
409 # merge will appear as a normal local file
410 # modification.
410 # modification.
411 if f2 == fd: # file not locally copied/moved
411 if f2 == fd: # file not locally copied/moved
412 repo.dirstate.normallookup(fd)
412 repo.dirstate.normallookup(fd)
413 if move:
413 if move:
414 repo.dirstate.forget(f)
414 repo.dirstate.forget(f)
415 elif m == "d": # directory rename
415 elif m == "d": # directory rename
416 f2, fd, flag = a[2:]
416 f2, fd, flag = a[2:]
417 if not f2 and f not in repo.dirstate:
417 if not f2 and f not in repo.dirstate:
418 # untracked file moved
418 # untracked file moved
419 continue
419 continue
420 if branchmerge:
420 if branchmerge:
421 repo.dirstate.add(fd)
421 repo.dirstate.add(fd)
422 if f:
422 if f:
423 repo.dirstate.remove(f)
423 repo.dirstate.remove(f)
424 repo.dirstate.copy(f, fd)
424 repo.dirstate.copy(f, fd)
425 if f2:
425 if f2:
426 repo.dirstate.copy(f2, fd)
426 repo.dirstate.copy(f2, fd)
427 else:
427 else:
428 repo.dirstate.normal(fd)
428 repo.dirstate.normal(fd)
429 if f:
429 if f:
430 repo.dirstate.forget(f)
430 repo.dirstate.forget(f)
431
431
432 def update(repo, node, branchmerge, force, partial):
432 def update(repo, node, branchmerge, force, partial):
433 """
433 """
434 Perform a merge between the working directory and the given node
434 Perform a merge between the working directory and the given node
435
435
436 node = the node to update to, or None if unspecified
436 node = the node to update to, or None if unspecified
437 branchmerge = whether to merge between branches
437 branchmerge = whether to merge between branches
438 force = whether to force branch merging or file overwriting
438 force = whether to force branch merging or file overwriting
439 partial = a function to filter file lists (dirstate not updated)
439 partial = a function to filter file lists (dirstate not updated)
440
440
441 The table below shows all the behaviors of the update command
441 The table below shows all the behaviors of the update command
442 given the -c and -C or no options, whether the working directory
442 given the -c and -C or no options, whether the working directory
443 is dirty, whether a revision is specified, and the relationship of
443 is dirty, whether a revision is specified, and the relationship of
444 the parent rev to the target rev (linear, on the same named
444 the parent rev to the target rev (linear, on the same named
445 branch, or on another named branch).
445 branch, or on another named branch).
446
446
447 This logic is tested by test-update-branches.t.
447 This logic is tested by test-update-branches.t.
448
448
449 -c -C dirty rev | linear same cross
449 -c -C dirty rev | linear same cross
450 n n n n | ok (1) x
450 n n n n | ok (1) x
451 n n n y | ok ok ok
451 n n n y | ok ok ok
452 n n y * | merge (2) (2)
452 n n y * | merge (2) (2)
453 n y * * | --- discard ---
453 n y * * | --- discard ---
454 y n y * | --- (3) ---
454 y n y * | --- (3) ---
455 y n n * | --- ok ---
455 y n n * | --- ok ---
456 y y * * | --- (4) ---
456 y y * * | --- (4) ---
457
457
458 x = can't happen
458 x = can't happen
459 * = don't-care
459 * = don't-care
460 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
460 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
461 2 = abort: crosses branches (use 'hg merge' to merge or
461 2 = abort: crosses branches (use 'hg merge' to merge or
462 use 'hg update -C' to discard changes)
462 use 'hg update -C' to discard changes)
463 3 = abort: uncommitted local changes
463 3 = abort: uncommitted local changes
464 4 = incompatible options (checked in commands.py)
464 4 = incompatible options (checked in commands.py)
465 """
465 """
466
466
467 onode = node
467 onode = node
468 wlock = repo.wlock()
468 wlock = repo.wlock()
469 try:
469 try:
470 wc = repo[None]
470 wc = repo[None]
471 if node is None:
471 if node is None:
472 # tip of current branch
472 # tip of current branch
473 try:
473 try:
474 node = repo.branchtags()[wc.branch()]
474 node = repo.branchtags()[wc.branch()]
475 except KeyError:
475 except KeyError:
476 if wc.branch() == "default": # no default branch!
476 if wc.branch() == "default": # no default branch!
477 node = repo.lookup("tip") # update to tip
477 node = repo.lookup("tip") # update to tip
478 else:
478 else:
479 raise util.Abort(_("branch %s not found") % wc.branch())
479 raise util.Abort(_("branch %s not found") % wc.branch())
480 overwrite = force and not branchmerge
480 overwrite = force and not branchmerge
481 pl = wc.parents()
481 pl = wc.parents()
482 p1, p2 = pl[0], repo[node]
482 p1, p2 = pl[0], repo[node]
483 pa = p1.ancestor(p2)
483 pa = p1.ancestor(p2)
484 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
484 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
485 fastforward = False
485 fastforward = False
486
486
487 ### check phase
487 ### check phase
488 if not overwrite and len(pl) > 1:
488 if not overwrite and len(pl) > 1:
489 raise util.Abort(_("outstanding uncommitted merges"))
489 raise util.Abort(_("outstanding uncommitted merges"))
490 if branchmerge:
490 if branchmerge:
491 if pa == p2:
491 if pa == p2:
492 raise util.Abort(_("merging with a working directory ancestor"
492 raise util.Abort(_("merging with a working directory ancestor"
493 " has no effect"))
493 " has no effect"))
494 elif pa == p1:
494 elif pa == p1:
495 if p1.branch() != p2.branch():
495 if p1.branch() != p2.branch():
496 fastforward = True
496 fastforward = True
497 else:
497 else:
498 raise util.Abort(_("nothing to merge (use 'hg update'"
498 raise util.Abort(_("nothing to merge (use 'hg update'"
499 " or check 'hg heads')"))
499 " or check 'hg heads')"))
500 if not force and (wc.files() or wc.deleted()):
500 if not force and (wc.files() or wc.deleted()):
501 raise util.Abort(_("outstanding uncommitted changes "
501 raise util.Abort(_("outstanding uncommitted changes "
502 "(use 'hg status' to list changes)"))
502 "(use 'hg status' to list changes)"))
503 elif not overwrite:
503 elif not overwrite:
504 if pa == p1 or pa == p2: # linear
504 if pa == p1 or pa == p2: # linear
505 pass # all good
505 pass # all good
506 elif wc.files() or wc.deleted():
506 elif wc.files() or wc.deleted():
507 raise util.Abort(_("crosses branches (merge branches or use"
507 raise util.Abort(_("crosses branches (merge branches or use"
508 " --clean to discard changes)"))
508 " --clean to discard changes)"))
509 elif onode is None:
509 elif onode is None:
510 raise util.Abort(_("crosses branches (merge branches or use"
510 raise util.Abort(_("crosses branches (merge branches or use"
511 " --check to force update)"))
511 " --check to force update)"))
512 else:
512 else:
513 # Allow jumping branches if clean and specific rev given
513 # Allow jumping branches if clean and specific rev given
514 overwrite = True
514 overwrite = True
515
515
516 ### calculate phase
516 ### calculate phase
517 action = []
517 action = []
518 wc.status(unknown=True) # prime cache
518 wc.status(unknown=True) # prime cache
519 if not force:
519 if not force:
520 _checkunknown(wc, p2)
520 _checkunknown(wc, p2)
521 if not util.checkcase(repo.path):
521 if not util.checkcase(repo.path):
522 _checkcollision(p2)
522 _checkcollision(p2)
523 action += _forgetremoved(wc, p2, branchmerge)
523 action += _forgetremoved(wc, p2, branchmerge)
524 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
524 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
525
525
526 ### apply phase
526 ### apply phase
527 if not branchmerge: # just jump to the new rev
527 if not branchmerge: # just jump to the new rev
528 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
528 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
529 if not partial:
529 if not partial:
530 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
530 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
531
531
532 stats = applyupdates(repo, action, wc, p2, pa)
532 stats = applyupdates(repo, action, wc, p2, pa, overwrite)
533
533
534 if not partial:
534 if not partial:
535 repo.dirstate.setparents(fp1, fp2)
535 repo.dirstate.setparents(fp1, fp2)
536 recordupdates(repo, action, branchmerge)
536 recordupdates(repo, action, branchmerge)
537 if not branchmerge and not fastforward:
537 if not branchmerge and not fastforward:
538 repo.dirstate.setbranch(p2.branch())
538 repo.dirstate.setbranch(p2.branch())
539 finally:
539 finally:
540 wlock.release()
540 wlock.release()
541
541
542 if not partial:
542 if not partial:
543 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
543 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
544 return stats
544 return stats
@@ -1,620 +1,622 b''
1 # subrepo.py - sub-repository handling for Mercurial
1 # subrepo.py - sub-repository handling for Mercurial
2 #
2 #
3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import errno, os, re, xml.dom.minidom, shutil, urlparse, posixpath
8 import errno, os, re, xml.dom.minidom, shutil, urlparse, posixpath
9 import stat, subprocess
9 import stat, subprocess
10 from i18n import _
10 from i18n import _
11 import config, util, node, error, cmdutil
11 import config, util, node, error, cmdutil
12 hg = None
12 hg = None
13
13
14 nullstate = ('', '', 'empty')
14 nullstate = ('', '', 'empty')
15
15
16 def state(ctx, ui):
16 def state(ctx, ui):
17 """return a state dict, mapping subrepo paths configured in .hgsub
17 """return a state dict, mapping subrepo paths configured in .hgsub
18 to tuple: (source from .hgsub, revision from .hgsubstate, kind
18 to tuple: (source from .hgsub, revision from .hgsubstate, kind
19 (key in types dict))
19 (key in types dict))
20 """
20 """
21 p = config.config()
21 p = config.config()
22 def read(f, sections=None, remap=None):
22 def read(f, sections=None, remap=None):
23 if f in ctx:
23 if f in ctx:
24 try:
24 try:
25 data = ctx[f].data()
25 data = ctx[f].data()
26 except IOError, err:
26 except IOError, err:
27 if err.errno != errno.ENOENT:
27 if err.errno != errno.ENOENT:
28 raise
28 raise
29 # handle missing subrepo spec files as removed
29 # handle missing subrepo spec files as removed
30 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
30 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
31 return
31 return
32 p.parse(f, data, sections, remap, read)
32 p.parse(f, data, sections, remap, read)
33 else:
33 else:
34 raise util.Abort(_("subrepo spec file %s not found") % f)
34 raise util.Abort(_("subrepo spec file %s not found") % f)
35
35
36 if '.hgsub' in ctx:
36 if '.hgsub' in ctx:
37 read('.hgsub')
37 read('.hgsub')
38
38
39 for path, src in ui.configitems('subpaths'):
39 for path, src in ui.configitems('subpaths'):
40 p.set('subpaths', path, src, ui.configsource('subpaths', path))
40 p.set('subpaths', path, src, ui.configsource('subpaths', path))
41
41
42 rev = {}
42 rev = {}
43 if '.hgsubstate' in ctx:
43 if '.hgsubstate' in ctx:
44 try:
44 try:
45 for l in ctx['.hgsubstate'].data().splitlines():
45 for l in ctx['.hgsubstate'].data().splitlines():
46 revision, path = l.split(" ", 1)
46 revision, path = l.split(" ", 1)
47 rev[path] = revision
47 rev[path] = revision
48 except IOError, err:
48 except IOError, err:
49 if err.errno != errno.ENOENT:
49 if err.errno != errno.ENOENT:
50 raise
50 raise
51
51
52 state = {}
52 state = {}
53 for path, src in p[''].items():
53 for path, src in p[''].items():
54 kind = 'hg'
54 kind = 'hg'
55 if src.startswith('['):
55 if src.startswith('['):
56 if ']' not in src:
56 if ']' not in src:
57 raise util.Abort(_('missing ] in subrepo source'))
57 raise util.Abort(_('missing ] in subrepo source'))
58 kind, src = src.split(']', 1)
58 kind, src = src.split(']', 1)
59 kind = kind[1:]
59 kind = kind[1:]
60
60
61 for pattern, repl in p.items('subpaths'):
61 for pattern, repl in p.items('subpaths'):
62 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
62 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
63 # does a string decode.
63 # does a string decode.
64 repl = repl.encode('string-escape')
64 repl = repl.encode('string-escape')
65 # However, we still want to allow back references to go
65 # However, we still want to allow back references to go
66 # through unharmed, so we turn r'\\1' into r'\1'. Again,
66 # through unharmed, so we turn r'\\1' into r'\1'. Again,
67 # extra escapes are needed because re.sub string decodes.
67 # extra escapes are needed because re.sub string decodes.
68 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
68 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
69 try:
69 try:
70 src = re.sub(pattern, repl, src, 1)
70 src = re.sub(pattern, repl, src, 1)
71 except re.error, e:
71 except re.error, e:
72 raise util.Abort(_("bad subrepository pattern in %s: %s")
72 raise util.Abort(_("bad subrepository pattern in %s: %s")
73 % (p.source('subpaths', pattern), e))
73 % (p.source('subpaths', pattern), e))
74
74
75 state[path] = (src.strip(), rev.get(path, ''), kind)
75 state[path] = (src.strip(), rev.get(path, ''), kind)
76
76
77 return state
77 return state
78
78
79 def writestate(repo, state):
79 def writestate(repo, state):
80 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
80 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
81 repo.wwrite('.hgsubstate',
81 repo.wwrite('.hgsubstate',
82 ''.join(['%s %s\n' % (state[s][1], s)
82 ''.join(['%s %s\n' % (state[s][1], s)
83 for s in sorted(state)]), '')
83 for s in sorted(state)]), '')
84
84
85 def submerge(repo, wctx, mctx, actx):
85 def submerge(repo, wctx, mctx, actx, overwrite):
86 """delegated from merge.applyupdates: merging of .hgsubstate file
86 """delegated from merge.applyupdates: merging of .hgsubstate file
87 in working context, merging context and ancestor context"""
87 in working context, merging context and ancestor context"""
88 if mctx == actx: # backwards?
88 if mctx == actx: # backwards?
89 actx = wctx.p1()
89 actx = wctx.p1()
90 s1 = wctx.substate
90 s1 = wctx.substate
91 s2 = mctx.substate
91 s2 = mctx.substate
92 sa = actx.substate
92 sa = actx.substate
93 sm = {}
93 sm = {}
94
94
95 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
95 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
96
96
97 def debug(s, msg, r=""):
97 def debug(s, msg, r=""):
98 if r:
98 if r:
99 r = "%s:%s:%s" % r
99 r = "%s:%s:%s" % r
100 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
100 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
101
101
102 for s, l in s1.items():
102 for s, l in s1.items():
103 a = sa.get(s, nullstate)
103 a = sa.get(s, nullstate)
104 ld = l # local state with possible dirty flag for compares
104 ld = l # local state with possible dirty flag for compares
105 if wctx.sub(s).dirty():
105 if wctx.sub(s).dirty():
106 ld = (l[0], l[1] + "+")
106 ld = (l[0], l[1] + "+")
107 if wctx == actx: # overwrite
107 if wctx == actx: # overwrite
108 a = ld
108 a = ld
109
109
110 if s in s2:
110 if s in s2:
111 r = s2[s]
111 r = s2[s]
112 if ld == r or r == a: # no change or local is newer
112 if ld == r or r == a: # no change or local is newer
113 sm[s] = l
113 sm[s] = l
114 continue
114 continue
115 elif ld == a: # other side changed
115 elif ld == a: # other side changed
116 debug(s, "other changed, get", r)
116 debug(s, "other changed, get", r)
117 wctx.sub(s).get(r)
117 wctx.sub(s).get(r, overwrite)
118 sm[s] = r
118 sm[s] = r
119 elif ld[0] != r[0]: # sources differ
119 elif ld[0] != r[0]: # sources differ
120 if repo.ui.promptchoice(
120 if repo.ui.promptchoice(
121 _(' subrepository sources for %s differ\n'
121 _(' subrepository sources for %s differ\n'
122 'use (l)ocal source (%s) or (r)emote source (%s)?')
122 'use (l)ocal source (%s) or (r)emote source (%s)?')
123 % (s, l[0], r[0]),
123 % (s, l[0], r[0]),
124 (_('&Local'), _('&Remote')), 0):
124 (_('&Local'), _('&Remote')), 0):
125 debug(s, "prompt changed, get", r)
125 debug(s, "prompt changed, get", r)
126 wctx.sub(s).get(r)
126 wctx.sub(s).get(r, overwrite)
127 sm[s] = r
127 sm[s] = r
128 elif ld[1] == a[1]: # local side is unchanged
128 elif ld[1] == a[1]: # local side is unchanged
129 debug(s, "other side changed, get", r)
129 debug(s, "other side changed, get", r)
130 wctx.sub(s).get(r)
130 wctx.sub(s).get(r, overwrite)
131 sm[s] = r
131 sm[s] = r
132 else:
132 else:
133 debug(s, "both sides changed, merge with", r)
133 debug(s, "both sides changed, merge with", r)
134 wctx.sub(s).merge(r)
134 wctx.sub(s).merge(r)
135 sm[s] = l
135 sm[s] = l
136 elif ld == a: # remote removed, local unchanged
136 elif ld == a: # remote removed, local unchanged
137 debug(s, "remote removed, remove")
137 debug(s, "remote removed, remove")
138 wctx.sub(s).remove()
138 wctx.sub(s).remove()
139 else:
139 else:
140 if repo.ui.promptchoice(
140 if repo.ui.promptchoice(
141 _(' local changed subrepository %s which remote removed\n'
141 _(' local changed subrepository %s which remote removed\n'
142 'use (c)hanged version or (d)elete?') % s,
142 'use (c)hanged version or (d)elete?') % s,
143 (_('&Changed'), _('&Delete')), 0):
143 (_('&Changed'), _('&Delete')), 0):
144 debug(s, "prompt remove")
144 debug(s, "prompt remove")
145 wctx.sub(s).remove()
145 wctx.sub(s).remove()
146
146
147 for s, r in s2.items():
147 for s, r in s2.items():
148 if s in s1:
148 if s in s1:
149 continue
149 continue
150 elif s not in sa:
150 elif s not in sa:
151 debug(s, "remote added, get", r)
151 debug(s, "remote added, get", r)
152 mctx.sub(s).get(r)
152 mctx.sub(s).get(r)
153 sm[s] = r
153 sm[s] = r
154 elif r != sa[s]:
154 elif r != sa[s]:
155 if repo.ui.promptchoice(
155 if repo.ui.promptchoice(
156 _(' remote changed subrepository %s which local removed\n'
156 _(' remote changed subrepository %s which local removed\n'
157 'use (c)hanged version or (d)elete?') % s,
157 'use (c)hanged version or (d)elete?') % s,
158 (_('&Changed'), _('&Delete')), 0) == 0:
158 (_('&Changed'), _('&Delete')), 0) == 0:
159 debug(s, "prompt recreate", r)
159 debug(s, "prompt recreate", r)
160 wctx.sub(s).get(r)
160 wctx.sub(s).get(r)
161 sm[s] = r
161 sm[s] = r
162
162
163 # record merged .hgsubstate
163 # record merged .hgsubstate
164 writestate(repo, sm)
164 writestate(repo, sm)
165
165
166 def reporelpath(repo):
166 def reporelpath(repo):
167 """return path to this (sub)repo as seen from outermost repo"""
167 """return path to this (sub)repo as seen from outermost repo"""
168 parent = repo
168 parent = repo
169 while hasattr(parent, '_subparent'):
169 while hasattr(parent, '_subparent'):
170 parent = parent._subparent
170 parent = parent._subparent
171 return repo.root[len(parent.root)+1:]
171 return repo.root[len(parent.root)+1:]
172
172
173 def subrelpath(sub):
173 def subrelpath(sub):
174 """return path to this subrepo as seen from outermost repo"""
174 """return path to this subrepo as seen from outermost repo"""
175 if not hasattr(sub, '_repo'):
175 if not hasattr(sub, '_repo'):
176 return sub._path
176 return sub._path
177 return reporelpath(sub._repo)
177 return reporelpath(sub._repo)
178
178
179 def _abssource(repo, push=False, abort=True):
179 def _abssource(repo, push=False, abort=True):
180 """return pull/push path of repo - either based on parent repo .hgsub info
180 """return pull/push path of repo - either based on parent repo .hgsub info
181 or on the top repo config. Abort or return None if no source found."""
181 or on the top repo config. Abort or return None if no source found."""
182 if hasattr(repo, '_subparent'):
182 if hasattr(repo, '_subparent'):
183 source = repo._subsource
183 source = repo._subsource
184 if source.startswith('/') or '://' in source:
184 if source.startswith('/') or '://' in source:
185 return source
185 return source
186 parent = _abssource(repo._subparent, push, abort=False)
186 parent = _abssource(repo._subparent, push, abort=False)
187 if parent:
187 if parent:
188 if '://' in parent:
188 if '://' in parent:
189 if parent[-1] == '/':
189 if parent[-1] == '/':
190 parent = parent[:-1]
190 parent = parent[:-1]
191 r = urlparse.urlparse(parent + '/' + source)
191 r = urlparse.urlparse(parent + '/' + source)
192 r = urlparse.urlunparse((r[0], r[1],
192 r = urlparse.urlunparse((r[0], r[1],
193 posixpath.normpath(r[2]),
193 posixpath.normpath(r[2]),
194 r[3], r[4], r[5]))
194 r[3], r[4], r[5]))
195 return r
195 return r
196 else: # plain file system path
196 else: # plain file system path
197 return posixpath.normpath(os.path.join(parent, repo._subsource))
197 return posixpath.normpath(os.path.join(parent, repo._subsource))
198 else: # recursion reached top repo
198 else: # recursion reached top repo
199 if hasattr(repo, '_subtoppath'):
199 if hasattr(repo, '_subtoppath'):
200 return repo._subtoppath
200 return repo._subtoppath
201 if push and repo.ui.config('paths', 'default-push'):
201 if push and repo.ui.config('paths', 'default-push'):
202 return repo.ui.config('paths', 'default-push')
202 return repo.ui.config('paths', 'default-push')
203 if repo.ui.config('paths', 'default'):
203 if repo.ui.config('paths', 'default'):
204 return repo.ui.config('paths', 'default')
204 return repo.ui.config('paths', 'default')
205 if abort:
205 if abort:
206 raise util.Abort(_("default path for subrepository %s not found") %
206 raise util.Abort(_("default path for subrepository %s not found") %
207 reporelpath(repo))
207 reporelpath(repo))
208
208
209 def itersubrepos(ctx1, ctx2):
209 def itersubrepos(ctx1, ctx2):
210 """find subrepos in ctx1 or ctx2"""
210 """find subrepos in ctx1 or ctx2"""
211 # Create a (subpath, ctx) mapping where we prefer subpaths from
211 # Create a (subpath, ctx) mapping where we prefer subpaths from
212 # ctx1. The subpaths from ctx2 are important when the .hgsub file
212 # ctx1. The subpaths from ctx2 are important when the .hgsub file
213 # has been modified (in ctx2) but not yet committed (in ctx1).
213 # has been modified (in ctx2) but not yet committed (in ctx1).
214 subpaths = dict.fromkeys(ctx2.substate, ctx2)
214 subpaths = dict.fromkeys(ctx2.substate, ctx2)
215 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
215 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
216 for subpath, ctx in sorted(subpaths.iteritems()):
216 for subpath, ctx in sorted(subpaths.iteritems()):
217 yield subpath, ctx.sub(subpath)
217 yield subpath, ctx.sub(subpath)
218
218
219 def subrepo(ctx, path):
219 def subrepo(ctx, path):
220 """return instance of the right subrepo class for subrepo in path"""
220 """return instance of the right subrepo class for subrepo in path"""
221 # subrepo inherently violates our import layering rules
221 # subrepo inherently violates our import layering rules
222 # because it wants to make repo objects from deep inside the stack
222 # because it wants to make repo objects from deep inside the stack
223 # so we manually delay the circular imports to not break
223 # so we manually delay the circular imports to not break
224 # scripts that don't use our demand-loading
224 # scripts that don't use our demand-loading
225 global hg
225 global hg
226 import hg as h
226 import hg as h
227 hg = h
227 hg = h
228
228
229 util.path_auditor(ctx._repo.root)(path)
229 util.path_auditor(ctx._repo.root)(path)
230 state = ctx.substate.get(path, nullstate)
230 state = ctx.substate.get(path, nullstate)
231 if state[2] not in types:
231 if state[2] not in types:
232 raise util.Abort(_('unknown subrepo type %s') % state[2])
232 raise util.Abort(_('unknown subrepo type %s') % state[2])
233 return types[state[2]](ctx, path, state[:2])
233 return types[state[2]](ctx, path, state[:2])
234
234
235 # subrepo classes need to implement the following abstract class:
235 # subrepo classes need to implement the following abstract class:
236
236
237 class abstractsubrepo(object):
237 class abstractsubrepo(object):
238
238
239 def dirty(self):
239 def dirty(self):
240 """returns true if the dirstate of the subrepo does not match
240 """returns true if the dirstate of the subrepo does not match
241 current stored state
241 current stored state
242 """
242 """
243 raise NotImplementedError
243 raise NotImplementedError
244
244
245 def checknested(self, path):
245 def checknested(self, path):
246 """check if path is a subrepository within this repository"""
246 """check if path is a subrepository within this repository"""
247 return False
247 return False
248
248
249 def commit(self, text, user, date):
249 def commit(self, text, user, date):
250 """commit the current changes to the subrepo with the given
250 """commit the current changes to the subrepo with the given
251 log message. Use given user and date if possible. Return the
251 log message. Use given user and date if possible. Return the
252 new state of the subrepo.
252 new state of the subrepo.
253 """
253 """
254 raise NotImplementedError
254 raise NotImplementedError
255
255
256 def remove(self):
256 def remove(self):
257 """remove the subrepo
257 """remove the subrepo
258
258
259 (should verify the dirstate is not dirty first)
259 (should verify the dirstate is not dirty first)
260 """
260 """
261 raise NotImplementedError
261 raise NotImplementedError
262
262
263 def get(self, state):
263 def get(self, state, overwrite=False):
264 """run whatever commands are needed to put the subrepo into
264 """run whatever commands are needed to put the subrepo into
265 this state
265 this state
266 """
266 """
267 raise NotImplementedError
267 raise NotImplementedError
268
268
269 def merge(self, state):
269 def merge(self, state, overwrite=False):
270 """merge currently-saved state with the new state."""
270 """merge currently-saved state with the new state."""
271 raise NotImplementedError
271 raise NotImplementedError
272
272
273 def push(self, force):
273 def push(self, force):
274 """perform whatever action is analogous to 'hg push'
274 """perform whatever action is analogous to 'hg push'
275
275
276 This may be a no-op on some systems.
276 This may be a no-op on some systems.
277 """
277 """
278 raise NotImplementedError
278 raise NotImplementedError
279
279
280 def add(self, ui, match, dryrun, prefix):
280 def add(self, ui, match, dryrun, prefix):
281 return []
281 return []
282
282
283 def status(self, rev2, **opts):
283 def status(self, rev2, **opts):
284 return [], [], [], [], [], [], []
284 return [], [], [], [], [], [], []
285
285
286 def diff(self, diffopts, node2, match, prefix, **opts):
286 def diff(self, diffopts, node2, match, prefix, **opts):
287 pass
287 pass
288
288
289 def outgoing(self, ui, dest, opts):
289 def outgoing(self, ui, dest, opts):
290 return 1
290 return 1
291
291
292 def incoming(self, ui, source, opts):
292 def incoming(self, ui, source, opts):
293 return 1
293 return 1
294
294
295 def files(self):
295 def files(self):
296 """return filename iterator"""
296 """return filename iterator"""
297 raise NotImplementedError
297 raise NotImplementedError
298
298
299 def filedata(self, name):
299 def filedata(self, name):
300 """return file data"""
300 """return file data"""
301 raise NotImplementedError
301 raise NotImplementedError
302
302
303 def fileflags(self, name):
303 def fileflags(self, name):
304 """return file flags"""
304 """return file flags"""
305 return ''
305 return ''
306
306
307 def archive(self, archiver, prefix):
307 def archive(self, archiver, prefix):
308 for name in self.files():
308 for name in self.files():
309 flags = self.fileflags(name)
309 flags = self.fileflags(name)
310 mode = 'x' in flags and 0755 or 0644
310 mode = 'x' in flags and 0755 or 0644
311 symlink = 'l' in flags
311 symlink = 'l' in flags
312 archiver.addfile(os.path.join(prefix, self._path, name),
312 archiver.addfile(os.path.join(prefix, self._path, name),
313 mode, symlink, self.filedata(name))
313 mode, symlink, self.filedata(name))
314
314
315
315
316 class hgsubrepo(abstractsubrepo):
316 class hgsubrepo(abstractsubrepo):
317 def __init__(self, ctx, path, state):
317 def __init__(self, ctx, path, state):
318 self._path = path
318 self._path = path
319 self._state = state
319 self._state = state
320 r = ctx._repo
320 r = ctx._repo
321 root = r.wjoin(path)
321 root = r.wjoin(path)
322 create = False
322 create = False
323 if not os.path.exists(os.path.join(root, '.hg')):
323 if not os.path.exists(os.path.join(root, '.hg')):
324 create = True
324 create = True
325 util.makedirs(root)
325 util.makedirs(root)
326 self._repo = hg.repository(r.ui, root, create=create)
326 self._repo = hg.repository(r.ui, root, create=create)
327 self._repo._subparent = r
327 self._repo._subparent = r
328 self._repo._subsource = state[0]
328 self._repo._subsource = state[0]
329
329
330 if create:
330 if create:
331 fp = self._repo.opener("hgrc", "w", text=True)
331 fp = self._repo.opener("hgrc", "w", text=True)
332 fp.write('[paths]\n')
332 fp.write('[paths]\n')
333
333
334 def addpathconfig(key, value):
334 def addpathconfig(key, value):
335 if value:
335 if value:
336 fp.write('%s = %s\n' % (key, value))
336 fp.write('%s = %s\n' % (key, value))
337 self._repo.ui.setconfig('paths', key, value)
337 self._repo.ui.setconfig('paths', key, value)
338
338
339 defpath = _abssource(self._repo, abort=False)
339 defpath = _abssource(self._repo, abort=False)
340 defpushpath = _abssource(self._repo, True, abort=False)
340 defpushpath = _abssource(self._repo, True, abort=False)
341 addpathconfig('default', defpath)
341 addpathconfig('default', defpath)
342 if defpath != defpushpath:
342 if defpath != defpushpath:
343 addpathconfig('default-push', defpushpath)
343 addpathconfig('default-push', defpushpath)
344 fp.close()
344 fp.close()
345
345
346 def add(self, ui, match, dryrun, prefix):
346 def add(self, ui, match, dryrun, prefix):
347 return cmdutil.add(ui, self._repo, match, dryrun, True,
347 return cmdutil.add(ui, self._repo, match, dryrun, True,
348 os.path.join(prefix, self._path))
348 os.path.join(prefix, self._path))
349
349
350 def status(self, rev2, **opts):
350 def status(self, rev2, **opts):
351 try:
351 try:
352 rev1 = self._state[1]
352 rev1 = self._state[1]
353 ctx1 = self._repo[rev1]
353 ctx1 = self._repo[rev1]
354 ctx2 = self._repo[rev2]
354 ctx2 = self._repo[rev2]
355 return self._repo.status(ctx1, ctx2, **opts)
355 return self._repo.status(ctx1, ctx2, **opts)
356 except error.RepoLookupError, inst:
356 except error.RepoLookupError, inst:
357 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
357 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
358 % (inst, subrelpath(self)))
358 % (inst, subrelpath(self)))
359 return [], [], [], [], [], [], []
359 return [], [], [], [], [], [], []
360
360
361 def diff(self, diffopts, node2, match, prefix, **opts):
361 def diff(self, diffopts, node2, match, prefix, **opts):
362 try:
362 try:
363 node1 = node.bin(self._state[1])
363 node1 = node.bin(self._state[1])
364 # We currently expect node2 to come from substate and be
364 # We currently expect node2 to come from substate and be
365 # in hex format
365 # in hex format
366 if node2 is not None:
366 if node2 is not None:
367 node2 = node.bin(node2)
367 node2 = node.bin(node2)
368 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
368 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
369 node1, node2, match,
369 node1, node2, match,
370 prefix=os.path.join(prefix, self._path),
370 prefix=os.path.join(prefix, self._path),
371 listsubrepos=True, **opts)
371 listsubrepos=True, **opts)
372 except error.RepoLookupError, inst:
372 except error.RepoLookupError, inst:
373 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
373 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
374 % (inst, subrelpath(self)))
374 % (inst, subrelpath(self)))
375
375
376 def archive(self, archiver, prefix):
376 def archive(self, archiver, prefix):
377 abstractsubrepo.archive(self, archiver, prefix)
377 abstractsubrepo.archive(self, archiver, prefix)
378
378
379 rev = self._state[1]
379 rev = self._state[1]
380 ctx = self._repo[rev]
380 ctx = self._repo[rev]
381 for subpath in ctx.substate:
381 for subpath in ctx.substate:
382 s = subrepo(ctx, subpath)
382 s = subrepo(ctx, subpath)
383 s.archive(archiver, os.path.join(prefix, self._path))
383 s.archive(archiver, os.path.join(prefix, self._path))
384
384
385 def dirty(self):
385 def dirty(self):
386 r = self._state[1]
386 r = self._state[1]
387 if r == '':
387 if r == '':
388 return True
388 return True
389 w = self._repo[None]
389 w = self._repo[None]
390 if w.p1() != self._repo[r]: # version checked out change
390 if w.p1() != self._repo[r]: # version checked out change
391 return True
391 return True
392 return w.dirty() # working directory changed
392 return w.dirty() # working directory changed
393
393
394 def checknested(self, path):
394 def checknested(self, path):
395 return self._repo._checknested(self._repo.wjoin(path))
395 return self._repo._checknested(self._repo.wjoin(path))
396
396
397 def commit(self, text, user, date):
397 def commit(self, text, user, date):
398 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
398 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
399 n = self._repo.commit(text, user, date)
399 n = self._repo.commit(text, user, date)
400 if not n:
400 if not n:
401 return self._repo['.'].hex() # different version checked out
401 return self._repo['.'].hex() # different version checked out
402 return node.hex(n)
402 return node.hex(n)
403
403
404 def remove(self):
404 def remove(self):
405 # we can't fully delete the repository as it may contain
405 # we can't fully delete the repository as it may contain
406 # local-only history
406 # local-only history
407 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
407 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
408 hg.clean(self._repo, node.nullid, False)
408 hg.clean(self._repo, node.nullid, False)
409
409
410 def _get(self, state):
410 def _get(self, state):
411 source, revision, kind = state
411 source, revision, kind = state
412 try:
412 try:
413 self._repo.lookup(revision)
413 self._repo.lookup(revision)
414 except error.RepoError:
414 except error.RepoError:
415 self._repo._subsource = source
415 self._repo._subsource = source
416 srcurl = _abssource(self._repo)
416 srcurl = _abssource(self._repo)
417 self._repo.ui.status(_('pulling subrepo %s from %s\n')
417 self._repo.ui.status(_('pulling subrepo %s from %s\n')
418 % (subrelpath(self), srcurl))
418 % (subrelpath(self), srcurl))
419 other = hg.repository(self._repo.ui, srcurl)
419 other = hg.repository(self._repo.ui, srcurl)
420 self._repo.pull(other)
420 self._repo.pull(other)
421
421
422 def get(self, state):
422 def get(self, state, overwrite=False):
423 self._get(state)
423 self._get(state)
424 source, revision, kind = state
424 source, revision, kind = state
425 self._repo.ui.debug("getting subrepo %s\n" % self._path)
425 self._repo.ui.debug("getting subrepo %s\n" % self._path)
426 hg.clean(self._repo, revision, False)
426 hg.clean(self._repo, revision, False)
427
427
428 def merge(self, state):
428 def merge(self, state):
429 self._get(state)
429 self._get(state)
430 cur = self._repo['.']
430 cur = self._repo['.']
431 dst = self._repo[state[1]]
431 dst = self._repo[state[1]]
432 anc = dst.ancestor(cur)
432 anc = dst.ancestor(cur)
433 if anc == cur:
433 if anc == cur:
434 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
434 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
435 hg.update(self._repo, state[1])
435 hg.update(self._repo, state[1])
436 elif anc == dst:
436 elif anc == dst:
437 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
437 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
438 else:
438 else:
439 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
439 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
440 hg.merge(self._repo, state[1], remind=False)
440 hg.merge(self._repo, state[1], remind=False)
441
441
442 def push(self, force):
442 def push(self, force):
443 # push subrepos depth-first for coherent ordering
443 # push subrepos depth-first for coherent ordering
444 c = self._repo['']
444 c = self._repo['']
445 subs = c.substate # only repos that are committed
445 subs = c.substate # only repos that are committed
446 for s in sorted(subs):
446 for s in sorted(subs):
447 if not c.sub(s).push(force):
447 if not c.sub(s).push(force):
448 return False
448 return False
449
449
450 dsturl = _abssource(self._repo, True)
450 dsturl = _abssource(self._repo, True)
451 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
451 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
452 (subrelpath(self), dsturl))
452 (subrelpath(self), dsturl))
453 other = hg.repository(self._repo.ui, dsturl)
453 other = hg.repository(self._repo.ui, dsturl)
454 return self._repo.push(other, force)
454 return self._repo.push(other, force)
455
455
456 def outgoing(self, ui, dest, opts):
456 def outgoing(self, ui, dest, opts):
457 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
457 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
458
458
459 def incoming(self, ui, source, opts):
459 def incoming(self, ui, source, opts):
460 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
460 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
461
461
462 def files(self):
462 def files(self):
463 rev = self._state[1]
463 rev = self._state[1]
464 ctx = self._repo[rev]
464 ctx = self._repo[rev]
465 return ctx.manifest()
465 return ctx.manifest()
466
466
467 def filedata(self, name):
467 def filedata(self, name):
468 rev = self._state[1]
468 rev = self._state[1]
469 return self._repo[rev][name].data()
469 return self._repo[rev][name].data()
470
470
471 def fileflags(self, name):
471 def fileflags(self, name):
472 rev = self._state[1]
472 rev = self._state[1]
473 ctx = self._repo[rev]
473 ctx = self._repo[rev]
474 return ctx.flags(name)
474 return ctx.flags(name)
475
475
476
476
477 class svnsubrepo(abstractsubrepo):
477 class svnsubrepo(abstractsubrepo):
478 def __init__(self, ctx, path, state):
478 def __init__(self, ctx, path, state):
479 self._path = path
479 self._path = path
480 self._state = state
480 self._state = state
481 self._ctx = ctx
481 self._ctx = ctx
482 self._ui = ctx._repo.ui
482 self._ui = ctx._repo.ui
483
483
484 def _svncommand(self, commands, filename=''):
484 def _svncommand(self, commands, filename=''):
485 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
485 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
486 cmd = ['svn'] + commands + [path]
486 cmd = ['svn'] + commands + [path]
487 cmd = [util.shellquote(arg) for arg in cmd]
487 cmd = [util.shellquote(arg) for arg in cmd]
488 cmd = util.quotecommand(' '.join(cmd))
488 cmd = util.quotecommand(' '.join(cmd))
489 env = dict(os.environ)
489 env = dict(os.environ)
490 # Avoid localized output, preserve current locale for everything else.
490 # Avoid localized output, preserve current locale for everything else.
491 env['LC_MESSAGES'] = 'C'
491 env['LC_MESSAGES'] = 'C'
492 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
492 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
493 close_fds=util.closefds,
493 close_fds=util.closefds,
494 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
494 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
495 universal_newlines=True, env=env)
495 universal_newlines=True, env=env)
496 stdout, stderr = p.communicate()
496 stdout, stderr = p.communicate()
497 stderr = stderr.strip()
497 stderr = stderr.strip()
498 if stderr:
498 if stderr:
499 raise util.Abort(stderr)
499 raise util.Abort(stderr)
500 return stdout
500 return stdout
501
501
502 def _wcrevs(self):
502 def _wcrevs(self):
503 # Get the working directory revision as well as the last
503 # Get the working directory revision as well as the last
504 # commit revision so we can compare the subrepo state with
504 # commit revision so we can compare the subrepo state with
505 # both. We used to store the working directory one.
505 # both. We used to store the working directory one.
506 output = self._svncommand(['info', '--xml'])
506 output = self._svncommand(['info', '--xml'])
507 doc = xml.dom.minidom.parseString(output)
507 doc = xml.dom.minidom.parseString(output)
508 entries = doc.getElementsByTagName('entry')
508 entries = doc.getElementsByTagName('entry')
509 lastrev, rev = '0', '0'
509 lastrev, rev = '0', '0'
510 if entries:
510 if entries:
511 rev = str(entries[0].getAttribute('revision')) or '0'
511 rev = str(entries[0].getAttribute('revision')) or '0'
512 commits = entries[0].getElementsByTagName('commit')
512 commits = entries[0].getElementsByTagName('commit')
513 if commits:
513 if commits:
514 lastrev = str(commits[0].getAttribute('revision')) or '0'
514 lastrev = str(commits[0].getAttribute('revision')) or '0'
515 return (lastrev, rev)
515 return (lastrev, rev)
516
516
517 def _wcrev(self):
517 def _wcrev(self):
518 return self._wcrevs()[0]
518 return self._wcrevs()[0]
519
519
520 def _wcchanged(self):
520 def _wcchanged(self):
521 """Return (changes, extchanges) where changes is True
521 """Return (changes, extchanges) where changes is True
522 if the working directory was changed, and extchanges is
522 if the working directory was changed, and extchanges is
523 True if any of these changes concern an external entry.
523 True if any of these changes concern an external entry.
524 """
524 """
525 output = self._svncommand(['status', '--xml'])
525 output = self._svncommand(['status', '--xml'])
526 externals, changes = [], []
526 externals, changes = [], []
527 doc = xml.dom.minidom.parseString(output)
527 doc = xml.dom.minidom.parseString(output)
528 for e in doc.getElementsByTagName('entry'):
528 for e in doc.getElementsByTagName('entry'):
529 s = e.getElementsByTagName('wc-status')
529 s = e.getElementsByTagName('wc-status')
530 if not s:
530 if not s:
531 continue
531 continue
532 item = s[0].getAttribute('item')
532 item = s[0].getAttribute('item')
533 props = s[0].getAttribute('props')
533 props = s[0].getAttribute('props')
534 path = e.getAttribute('path')
534 path = e.getAttribute('path')
535 if item == 'external':
535 if item == 'external':
536 externals.append(path)
536 externals.append(path)
537 if (item not in ('', 'normal', 'unversioned', 'external')
537 if (item not in ('', 'normal', 'unversioned', 'external')
538 or props not in ('', 'none')):
538 or props not in ('', 'none')):
539 changes.append(path)
539 changes.append(path)
540 for path in changes:
540 for path in changes:
541 for ext in externals:
541 for ext in externals:
542 if path == ext or path.startswith(ext + os.sep):
542 if path == ext or path.startswith(ext + os.sep):
543 return True, True
543 return True, True
544 return bool(changes), False
544 return bool(changes), False
545
545
546 def dirty(self):
546 def dirty(self):
547 if self._state[1] in self._wcrevs() and not self._wcchanged()[0]:
547 if self._state[1] in self._wcrevs() and not self._wcchanged()[0]:
548 return False
548 return False
549 return True
549 return True
550
550
551 def commit(self, text, user, date):
551 def commit(self, text, user, date):
552 # user and date are out of our hands since svn is centralized
552 # user and date are out of our hands since svn is centralized
553 changed, extchanged = self._wcchanged()
553 changed, extchanged = self._wcchanged()
554 if not changed:
554 if not changed:
555 return self._wcrev()
555 return self._wcrev()
556 if extchanged:
556 if extchanged:
557 # Do not try to commit externals
557 # Do not try to commit externals
558 raise util.Abort(_('cannot commit svn externals'))
558 raise util.Abort(_('cannot commit svn externals'))
559 commitinfo = self._svncommand(['commit', '-m', text])
559 commitinfo = self._svncommand(['commit', '-m', text])
560 self._ui.status(commitinfo)
560 self._ui.status(commitinfo)
561 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
561 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
562 if not newrev:
562 if not newrev:
563 raise util.Abort(commitinfo.splitlines()[-1])
563 raise util.Abort(commitinfo.splitlines()[-1])
564 newrev = newrev.groups()[0]
564 newrev = newrev.groups()[0]
565 self._ui.status(self._svncommand(['update', '-r', newrev]))
565 self._ui.status(self._svncommand(['update', '-r', newrev]))
566 return newrev
566 return newrev
567
567
568 def remove(self):
568 def remove(self):
569 if self.dirty():
569 if self.dirty():
570 self._ui.warn(_('not removing repo %s because '
570 self._ui.warn(_('not removing repo %s because '
571 'it has changes.\n' % self._path))
571 'it has changes.\n' % self._path))
572 return
572 return
573 self._ui.note(_('removing subrepo %s\n') % self._path)
573 self._ui.note(_('removing subrepo %s\n') % self._path)
574
574
575 def onerror(function, path, excinfo):
575 def onerror(function, path, excinfo):
576 if function is not os.remove:
576 if function is not os.remove:
577 raise
577 raise
578 # read-only files cannot be unlinked under Windows
578 # read-only files cannot be unlinked under Windows
579 s = os.stat(path)
579 s = os.stat(path)
580 if (s.st_mode & stat.S_IWRITE) != 0:
580 if (s.st_mode & stat.S_IWRITE) != 0:
581 raise
581 raise
582 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
582 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
583 os.remove(path)
583 os.remove(path)
584
584
585 path = self._ctx._repo.wjoin(self._path)
585 path = self._ctx._repo.wjoin(self._path)
586 shutil.rmtree(path, onerror=onerror)
586 shutil.rmtree(path, onerror=onerror)
587 try:
587 try:
588 os.removedirs(os.path.dirname(path))
588 os.removedirs(os.path.dirname(path))
589 except OSError:
589 except OSError:
590 pass
590 pass
591
591
592 def get(self, state):
592 def get(self, state, overwrite=False):
593 if overwrite:
594 self._svncommand(['revert', '--recursive', self._path])
593 status = self._svncommand(['checkout', state[0], '--revision', state[1]])
595 status = self._svncommand(['checkout', state[0], '--revision', state[1]])
594 if not re.search('Checked out revision [0-9]+.', status):
596 if not re.search('Checked out revision [0-9]+.', status):
595 raise util.Abort(status.splitlines()[-1])
597 raise util.Abort(status.splitlines()[-1])
596 self._ui.status(status)
598 self._ui.status(status)
597
599
598 def merge(self, state):
600 def merge(self, state):
599 old = int(self._state[1])
601 old = int(self._state[1])
600 new = int(state[1])
602 new = int(state[1])
601 if new > old:
603 if new > old:
602 self.get(state)
604 self.get(state)
603
605
604 def push(self, force):
606 def push(self, force):
605 # push is a no-op for SVN
607 # push is a no-op for SVN
606 return True
608 return True
607
609
608 def files(self):
610 def files(self):
609 output = self._svncommand(['list'])
611 output = self._svncommand(['list'])
610 # This works because svn forbids \n in filenames.
612 # This works because svn forbids \n in filenames.
611 return output.splitlines()
613 return output.splitlines()
612
614
613 def filedata(self, name):
615 def filedata(self, name):
614 return self._svncommand(['cat'], name)
616 return self._svncommand(['cat'], name)
615
617
616
618
617 types = {
619 types = {
618 'hg': hgsubrepo,
620 'hg': hgsubrepo,
619 'svn': svnsubrepo,
621 'svn': svnsubrepo,
620 }
622 }
@@ -1,266 +1,298 b''
1 $ "$TESTDIR/hghave" svn || exit 80
1 $ "$TESTDIR/hghave" svn || exit 80
2
2
3 $ fix_path()
3 $ fix_path()
4 > {
4 > {
5 > tr '\\' /
5 > tr '\\' /
6 > }
6 > }
7
7
8 SVN wants all paths to start with a slash. Unfortunately, Windows ones
8 SVN wants all paths to start with a slash. Unfortunately, Windows ones
9 don't. Handle that.
9 don't. Handle that.
10
10
11 $ escapedwd=`pwd | fix_path`
11 $ escapedwd=`pwd | fix_path`
12 $ expr "$escapedwd" : '\/' > /dev/null || escapedwd="/$escapedwd"
12 $ expr "$escapedwd" : '\/' > /dev/null || escapedwd="/$escapedwd"
13 $ escapedwd=`python -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$escapedwd"`
13 $ escapedwd=`python -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$escapedwd"`
14
14
15 create subversion repo
15 create subversion repo
16
16
17 $ SVNREPO="file://$escapedwd/svn-repo"
17 $ SVNREPO="file://$escapedwd/svn-repo"
18 $ WCROOT="`pwd`/svn-wc"
18 $ WCROOT="`pwd`/svn-wc"
19 $ svnadmin create svn-repo
19 $ svnadmin create svn-repo
20 $ svn co "$SVNREPO" svn-wc
20 $ svn co "$SVNREPO" svn-wc
21 Checked out revision 0.
21 Checked out revision 0.
22 $ cd svn-wc
22 $ cd svn-wc
23 $ mkdir src
23 $ mkdir src
24 $ echo alpha > src/alpha
24 $ echo alpha > src/alpha
25 $ svn add src
25 $ svn add src
26 A src
26 A src
27 A src/alpha
27 A src/alpha
28 $ mkdir externals
28 $ mkdir externals
29 $ echo other > externals/other
29 $ echo other > externals/other
30 $ svn add externals
30 $ svn add externals
31 A externals
31 A externals
32 A externals/other
32 A externals/other
33 $ svn ci -m 'Add alpha'
33 $ svn ci -m 'Add alpha'
34 Adding externals
34 Adding externals
35 Adding externals/other
35 Adding externals/other
36 Adding src
36 Adding src
37 Adding src/alpha
37 Adding src/alpha
38 Transmitting file data ..
38 Transmitting file data ..
39 Committed revision 1.
39 Committed revision 1.
40 $ svn up
40 $ svn up
41 At revision 1.
41 At revision 1.
42 $ echo "externals -r1 $SVNREPO/externals" > extdef
42 $ echo "externals -r1 $SVNREPO/externals" > extdef
43 $ svn propset -F extdef svn:externals src
43 $ svn propset -F extdef svn:externals src
44 property 'svn:externals' set on 'src'
44 property 'svn:externals' set on 'src'
45 $ svn ci -m 'Setting externals'
45 $ svn ci -m 'Setting externals'
46 Sending src
46 Sending src
47
47
48 Committed revision 2.
48 Committed revision 2.
49 $ cd ..
49 $ cd ..
50
50
51 create hg repo
51 create hg repo
52
52
53 $ mkdir sub
53 $ mkdir sub
54 $ cd sub
54 $ cd sub
55 $ hg init t
55 $ hg init t
56 $ cd t
56 $ cd t
57
57
58 first revision, no sub
58 first revision, no sub
59
59
60 $ echo a > a
60 $ echo a > a
61 $ hg ci -Am0
61 $ hg ci -Am0
62 adding a
62 adding a
63
63
64 add first svn sub with leading whitespaces
64 add first svn sub with leading whitespaces
65
65
66 $ echo "s = [svn] $SVNREPO/src" >> .hgsub
66 $ echo "s = [svn] $SVNREPO/src" >> .hgsub
67 $ echo "subdir/s = [svn] $SVNREPO/src" >> .hgsub
67 $ echo "subdir/s = [svn] $SVNREPO/src" >> .hgsub
68 $ svn co --quiet "$SVNREPO"/src s
68 $ svn co --quiet "$SVNREPO"/src s
69 $ mkdir subdir
69 $ mkdir subdir
70 $ svn co --quiet "$SVNREPO"/src subdir/s
70 $ svn co --quiet "$SVNREPO"/src subdir/s
71 $ hg add .hgsub
71 $ hg add .hgsub
72 $ hg ci -m1
72 $ hg ci -m1
73 committing subrepository s
73 committing subrepository s
74 committing subrepository subdir/s
74 committing subrepository subdir/s
75
75
76 make sure we avoid empty commits (issue2445)
76 make sure we avoid empty commits (issue2445)
77
77
78 $ hg sum
78 $ hg sum
79 parent: 1:* tip (glob)
79 parent: 1:* tip (glob)
80 1
80 1
81 branch: default
81 branch: default
82 commit: (clean)
82 commit: (clean)
83 update: (current)
83 update: (current)
84 $ hg ci -moops
84 $ hg ci -moops
85 nothing changed
85 nothing changed
86 [1]
86 [1]
87
87
88 debugsub
88 debugsub
89
89
90 $ hg debugsub
90 $ hg debugsub
91 path s
91 path s
92 source file://*/svn-repo/src (glob)
92 source file://*/svn-repo/src (glob)
93 revision 2
93 revision 2
94 path subdir/s
94 path subdir/s
95 source file://*/svn-repo/src (glob)
95 source file://*/svn-repo/src (glob)
96 revision 2
96 revision 2
97
97
98 change file in svn and hg, commit
98 change file in svn and hg, commit
99
99
100 $ echo a >> a
100 $ echo a >> a
101 $ echo alpha >> s/alpha
101 $ echo alpha >> s/alpha
102 $ hg sum
102 $ hg sum
103 parent: 1:* tip (glob)
103 parent: 1:* tip (glob)
104 1
104 1
105 branch: default
105 branch: default
106 commit: 1 modified, 1 subrepos
106 commit: 1 modified, 1 subrepos
107 update: (current)
107 update: (current)
108 $ hg commit -m 'Message!'
108 $ hg commit -m 'Message!'
109 committing subrepository s
109 committing subrepository s
110 Sending*s/alpha (glob)
110 Sending*s/alpha (glob)
111 Transmitting file data .
111 Transmitting file data .
112 Committed revision 3.
112 Committed revision 3.
113
113
114 Fetching external item into '$TESTTMP/sub/t/s/externals'
114 Fetching external item into '$TESTTMP/sub/t/s/externals'
115 External at revision 1.
115 External at revision 1.
116
116
117 At revision 3.
117 At revision 3.
118 $ hg debugsub
118 $ hg debugsub
119 path s
119 path s
120 source file://*/svn-repo/src (glob)
120 source file://*/svn-repo/src (glob)
121 revision 3
121 revision 3
122 path subdir/s
122 path subdir/s
123 source file://*/svn-repo/src (glob)
123 source file://*/svn-repo/src (glob)
124 revision 2
124 revision 2
125
125
126 add an unrelated revision in svn and update the subrepo to without
126 add an unrelated revision in svn and update the subrepo to without
127 bringing any changes.
127 bringing any changes.
128
128
129 $ svn mkdir --parents "$SVNREPO/unrelated" -m 'create unrelated'
129 $ svn mkdir --parents "$SVNREPO/unrelated" -m 'create unrelated'
130
130
131 Committed revision 4.
131 Committed revision 4.
132 $ svn up s
132 $ svn up s
133
133
134 Fetching external item into 's/externals'
134 Fetching external item into 's/externals'
135 External at revision 1.
135 External at revision 1.
136
136
137 At revision 4.
137 At revision 4.
138 $ hg sum
138 $ hg sum
139 parent: 2:* tip (glob)
139 parent: 2:* tip (glob)
140 Message!
140 Message!
141 branch: default
141 branch: default
142 commit: (clean)
142 commit: (clean)
143 update: (current)
143 update: (current)
144
144
145 $ echo a > s/a
145 $ echo a > s/a
146
146
147 should be empty despite change to s/a
147 should be empty despite change to s/a
148
148
149 $ hg st
149 $ hg st
150
150
151 add a commit from svn
151 add a commit from svn
152
152
153 $ cd "$WCROOT"/src
153 $ cd "$WCROOT"/src
154 $ svn up
154 $ svn up
155 U alpha
155 U alpha
156
156
157 Fetching external item into 'externals'
157 Fetching external item into 'externals'
158 A externals/other
158 A externals/other
159 Updated external to revision 1.
159 Updated external to revision 1.
160
160
161 Updated to revision 4.
161 Updated to revision 4.
162 $ echo xyz >> alpha
162 $ echo xyz >> alpha
163 $ svn propset svn:mime-type 'text/xml' alpha
163 $ svn propset svn:mime-type 'text/xml' alpha
164 property 'svn:mime-type' set on 'alpha'
164 property 'svn:mime-type' set on 'alpha'
165 $ svn ci -m 'amend a from svn'
165 $ svn ci -m 'amend a from svn'
166 Sending src/alpha
166 Sending src/alpha
167 Transmitting file data .
167 Transmitting file data .
168 Committed revision 5.
168 Committed revision 5.
169 $ cd ../../sub/t
169 $ cd ../../sub/t
170
170
171 this commit from hg will fail
171 this commit from hg will fail
172
172
173 $ echo zzz >> s/alpha
173 $ echo zzz >> s/alpha
174 $ hg ci -m 'amend alpha from hg'
174 $ hg ci -m 'amend alpha from hg'
175 committing subrepository s
175 committing subrepository s
176 abort: svn: Commit failed (details follow):
176 abort: svn: Commit failed (details follow):
177 svn: (Out of date)?.*/src/alpha.*(is out of date)? (re)
177 svn: (Out of date)?.*/src/alpha.*(is out of date)? (re)
178 [255]
178 [255]
179 $ svn revert -q s/alpha
179 $ svn revert -q s/alpha
180
180
181 this commit fails because of meta changes
181 this commit fails because of meta changes
182
182
183 $ svn propset svn:mime-type 'text/html' s/alpha
183 $ svn propset svn:mime-type 'text/html' s/alpha
184 property 'svn:mime-type' set on 's/alpha'
184 property 'svn:mime-type' set on 's/alpha'
185 $ hg ci -m 'amend alpha from hg'
185 $ hg ci -m 'amend alpha from hg'
186 committing subrepository s
186 committing subrepository s
187 abort: svn: Commit failed (details follow):
187 abort: svn: Commit failed (details follow):
188 svn: (Out of date)?.*/src/alpha.*(is out of date)? (re)
188 svn: (Out of date)?.*/src/alpha.*(is out of date)? (re)
189 [255]
189 [255]
190 $ svn revert -q s/alpha
190 $ svn revert -q s/alpha
191
191
192 this commit fails because of externals changes
192 this commit fails because of externals changes
193
193
194 $ echo zzz > s/externals/other
194 $ echo zzz > s/externals/other
195 $ hg ci -m 'amend externals from hg'
195 $ hg ci -m 'amend externals from hg'
196 committing subrepository s
196 committing subrepository s
197 abort: cannot commit svn externals
197 abort: cannot commit svn externals
198 [255]
198 [255]
199 $ hg diff --subrepos -r 1:2 | grep -v diff
199 $ hg diff --subrepos -r 1:2 | grep -v diff
200 --- a/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
200 --- a/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
201 +++ b/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
201 +++ b/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
202 @@ -1,2 +1,2 @@
202 @@ -1,2 +1,2 @@
203 -2 s
203 -2 s
204 +3 s
204 +3 s
205 2 subdir/s
205 2 subdir/s
206 --- a/a Thu Jan 01 00:00:00 1970 +0000
206 --- a/a Thu Jan 01 00:00:00 1970 +0000
207 +++ b/a Thu Jan 01 00:00:00 1970 +0000
207 +++ b/a Thu Jan 01 00:00:00 1970 +0000
208 @@ -1,1 +1,2 @@
208 @@ -1,1 +1,2 @@
209 a
209 a
210 +a
210 +a
211 $ svn revert -q s/externals/other
211 $ svn revert -q s/externals/other
212
212
213 this commit fails because of externals meta changes
213 this commit fails because of externals meta changes
214
214
215 $ svn propset svn:mime-type 'text/html' s/externals/other
215 $ svn propset svn:mime-type 'text/html' s/externals/other
216 property 'svn:mime-type' set on 's/externals/other'
216 property 'svn:mime-type' set on 's/externals/other'
217 $ hg ci -m 'amend externals from hg'
217 $ hg ci -m 'amend externals from hg'
218 committing subrepository s
218 committing subrepository s
219 abort: cannot commit svn externals
219 abort: cannot commit svn externals
220 [255]
220 [255]
221 $ svn revert -q s/externals/other
221 $ svn revert -q s/externals/other
222
222
223 clone
223 clone
224
224
225 $ cd ..
225 $ cd ..
226 $ hg clone t tc | fix_path
226 $ hg clone t tc | fix_path
227 updating to branch default
227 updating to branch default
228 A tc/subdir/s/alpha
228 A tc/subdir/s/alpha
229 U tc/subdir/s
229 U tc/subdir/s
230
230
231 Fetching external item into 'tc/subdir/s/externals'
231 Fetching external item into 'tc/subdir/s/externals'
232 A tc/subdir/s/externals/other
232 A tc/subdir/s/externals/other
233 Checked out external at revision 1.
233 Checked out external at revision 1.
234
234
235 Checked out revision 2.
235 Checked out revision 2.
236 A tc/s/alpha
236 A tc/s/alpha
237 U tc/s
237 U tc/s
238
238
239 Fetching external item into 'tc/s/externals'
239 Fetching external item into 'tc/s/externals'
240 A tc/s/externals/other
240 A tc/s/externals/other
241 Checked out external at revision 1.
241 Checked out external at revision 1.
242
242
243 Checked out revision 3.
243 Checked out revision 3.
244 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
244 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
245 $ cd tc
245 $ cd tc
246
246
247 debugsub in clone
247 debugsub in clone
248
248
249 $ hg debugsub
249 $ hg debugsub
250 path s
250 path s
251 source file://*/svn-repo/src (glob)
251 source file://*/svn-repo/src (glob)
252 revision 3
252 revision 3
253 path subdir/s
253 path subdir/s
254 source file://*/svn-repo/src (glob)
254 source file://*/svn-repo/src (glob)
255 revision 2
255 revision 2
256
256
257 verify subrepo is contained within the repo directory
257 verify subrepo is contained within the repo directory
258
258
259 $ python -c "import os.path; print os.path.exists('s')"
259 $ python -c "import os.path; print os.path.exists('s')"
260 True
260 True
261
261
262 update to nullrev (must delete the subrepo)
262 update to nullrev (must delete the subrepo)
263
263
264 $ hg up null
264 $ hg up null
265 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
265 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
266 $ ls
266 $ ls
267
268 Check hg update --clean
269 $ cd $TESTTMP/sub/t
270 $ cd s
271 $ echo c0 > alpha
272 $ echo c1 > f1
273 $ echo c1 > f2
274 $ svn add f1 -q
275 $ svn status
276 ? a
277 X externals
278 ? f2
279 M alpha
280 A f1
281
282 Performing status on external item at 'externals'
283 $ cd ..
284 $ hg update -C
285
286 Fetching external item into '$TESTTMP/sub/t/s/externals'
287 Checked out external at revision 1.
288
289 Checked out revision 3.
290 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
291 $ cd s
292 $ svn status
293 ? a
294 X externals
295 ? f1
296 ? f2
297
298 Performing status on external item at 'externals'
@@ -1,677 +1,705 b''
1 $ rm -rf sub
1 $ rm -rf sub
2 $ mkdir sub
2 $ mkdir sub
3 $ cd sub
3 $ cd sub
4 $ hg init t
4 $ hg init t
5 $ cd t
5 $ cd t
6
6
7 first revision, no sub
7 first revision, no sub
8
8
9 $ echo a > a
9 $ echo a > a
10 $ hg ci -Am0
10 $ hg ci -Am0
11 adding a
11 adding a
12
12
13 add first sub
13 add first sub
14
14
15 $ echo s = s > .hgsub
15 $ echo s = s > .hgsub
16 $ hg add .hgsub
16 $ hg add .hgsub
17 $ hg init s
17 $ hg init s
18 $ echo a > s/a
18 $ echo a > s/a
19
19
20 Issue2232: committing a subrepo without .hgsub
20 Issue2232: committing a subrepo without .hgsub
21
21
22 $ hg ci -mbad s
22 $ hg ci -mbad s
23 abort: can't commit subrepos without .hgsub
23 abort: can't commit subrepos without .hgsub
24 [255]
24 [255]
25
25
26 $ hg -R s ci -Ams0
26 $ hg -R s ci -Ams0
27 adding a
27 adding a
28 $ hg sum
28 $ hg sum
29 parent: 0:f7b1eb17ad24 tip
29 parent: 0:f7b1eb17ad24 tip
30 0
30 0
31 branch: default
31 branch: default
32 commit: 1 added, 1 subrepos
32 commit: 1 added, 1 subrepos
33 update: (current)
33 update: (current)
34 $ hg ci -m1
34 $ hg ci -m1
35 committing subrepository s
35 committing subrepository s
36
36
37 Issue2022: update -C
37 Issue2022: update -C
38
38
39 $ echo b > s/a
39 $ echo b > s/a
40 $ hg sum
40 $ hg sum
41 parent: 1:7cf8cfea66e4 tip
41 parent: 1:7cf8cfea66e4 tip
42 1
42 1
43 branch: default
43 branch: default
44 commit: 1 subrepos
44 commit: 1 subrepos
45 update: (current)
45 update: (current)
46 $ hg co -C 1
46 $ hg co -C 1
47 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
47 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
48 $ hg sum
48 $ hg sum
49 parent: 1:7cf8cfea66e4 tip
49 parent: 1:7cf8cfea66e4 tip
50 1
50 1
51 branch: default
51 branch: default
52 commit: (clean)
52 commit: (clean)
53 update: (current)
53 update: (current)
54
54
55 add sub sub
55 add sub sub
56
56
57 $ echo ss = ss > s/.hgsub
57 $ echo ss = ss > s/.hgsub
58 $ hg init s/ss
58 $ hg init s/ss
59 $ echo a > s/ss/a
59 $ echo a > s/ss/a
60 $ hg -R s add s/.hgsub
60 $ hg -R s add s/.hgsub
61 $ hg -R s/ss add s/ss/a
61 $ hg -R s/ss add s/ss/a
62 $ hg sum
62 $ hg sum
63 parent: 1:7cf8cfea66e4 tip
63 parent: 1:7cf8cfea66e4 tip
64 1
64 1
65 branch: default
65 branch: default
66 commit: 1 subrepos
66 commit: 1 subrepos
67 update: (current)
67 update: (current)
68 $ hg ci -m2
68 $ hg ci -m2
69 committing subrepository s
69 committing subrepository s
70 committing subrepository s/ss
70 committing subrepository s/ss
71 $ hg sum
71 $ hg sum
72 parent: 2:df30734270ae tip
72 parent: 2:df30734270ae tip
73 2
73 2
74 branch: default
74 branch: default
75 commit: (clean)
75 commit: (clean)
76 update: (current)
76 update: (current)
77
77
78 bump sub rev
78 bump sub rev
79
79
80 $ echo b > s/a
80 $ echo b > s/a
81 $ hg -R s ci -ms1
81 $ hg -R s ci -ms1
82 $ hg ci -m3
82 $ hg ci -m3
83 committing subrepository s
83 committing subrepository s
84
84
85 leave sub dirty
85 leave sub dirty
86
86
87 $ echo c > s/a
87 $ echo c > s/a
88 $ hg ci -m4
88 $ hg ci -m4
89 committing subrepository s
89 committing subrepository s
90 $ hg tip -R s
90 $ hg tip -R s
91 changeset: 3:1c833a7a9e3a
91 changeset: 3:1c833a7a9e3a
92 tag: tip
92 tag: tip
93 user: test
93 user: test
94 date: Thu Jan 01 00:00:00 1970 +0000
94 date: Thu Jan 01 00:00:00 1970 +0000
95 summary: 4
95 summary: 4
96
96
97
97
98 check caching
98 check caching
99
99
100 $ hg co 0
100 $ hg co 0
101 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
101 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
102 $ hg debugsub
102 $ hg debugsub
103
103
104 restore
104 restore
105
105
106 $ hg co
106 $ hg co
107 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
107 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
108 $ hg debugsub
108 $ hg debugsub
109 path s
109 path s
110 source s
110 source s
111 revision 1c833a7a9e3a4445c711aaf0f012379cd0d4034e
111 revision 1c833a7a9e3a4445c711aaf0f012379cd0d4034e
112
112
113 new branch for merge tests
113 new branch for merge tests
114
114
115 $ hg co 1
115 $ hg co 1
116 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
116 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
117 $ echo t = t >> .hgsub
117 $ echo t = t >> .hgsub
118 $ hg init t
118 $ hg init t
119 $ echo t > t/t
119 $ echo t > t/t
120 $ hg -R t add t
120 $ hg -R t add t
121 adding t/t
121 adding t/t
122
122
123 5
123 5
124
124
125 $ hg ci -m5 # add sub
125 $ hg ci -m5 # add sub
126 committing subrepository t
126 committing subrepository t
127 created new head
127 created new head
128 $ echo t2 > t/t
128 $ echo t2 > t/t
129
129
130 6
130 6
131
131
132 $ hg st -R s
132 $ hg st -R s
133 $ hg ci -m6 # change sub
133 $ hg ci -m6 # change sub
134 committing subrepository t
134 committing subrepository t
135 $ hg debugsub
135 $ hg debugsub
136 path s
136 path s
137 source s
137 source s
138 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
138 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
139 path t
139 path t
140 source t
140 source t
141 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
141 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
142 $ echo t3 > t/t
142 $ echo t3 > t/t
143
143
144 7
144 7
145
145
146 $ hg ci -m7 # change sub again for conflict test
146 $ hg ci -m7 # change sub again for conflict test
147 committing subrepository t
147 committing subrepository t
148 $ hg rm .hgsub
148 $ hg rm .hgsub
149
149
150 8
150 8
151
151
152 $ hg ci -m8 # remove sub
152 $ hg ci -m8 # remove sub
153
153
154 merge tests
154 merge tests
155
155
156 $ hg co -C 3
156 $ hg co -C 3
157 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
157 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
158 $ hg merge 5 # test adding
158 $ hg merge 5 # test adding
159 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
159 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
160 (branch merge, don't forget to commit)
160 (branch merge, don't forget to commit)
161 $ hg debugsub
161 $ hg debugsub
162 path s
162 path s
163 source s
163 source s
164 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
164 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
165 path t
165 path t
166 source t
166 source t
167 revision 60ca1237c19474e7a3978b0dc1ca4e6f36d51382
167 revision 60ca1237c19474e7a3978b0dc1ca4e6f36d51382
168 $ hg ci -m9
168 $ hg ci -m9
169 created new head
169 created new head
170 $ hg merge 6 --debug # test change
170 $ hg merge 6 --debug # test change
171 searching for copies back to rev 2
171 searching for copies back to rev 2
172 resolving manifests
172 resolving manifests
173 overwrite None partial False
173 overwrite None partial False
174 ancestor 1f14a2e2d3ec local f0d2028bf86d+ remote 1831e14459c4
174 ancestor 1f14a2e2d3ec local f0d2028bf86d+ remote 1831e14459c4
175 .hgsubstate: versions differ -> m
175 .hgsubstate: versions differ -> m
176 updating: .hgsubstate 1/1 files (100.00%)
176 updating: .hgsubstate 1/1 files (100.00%)
177 subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec
177 subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec
178 subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad:hg
178 subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad:hg
179 getting subrepo t
179 getting subrepo t
180 resolving manifests
180 resolving manifests
181 overwrite True partial False
181 overwrite True partial False
182 ancestor 60ca1237c194+ local 60ca1237c194+ remote 6747d179aa9a
182 ancestor 60ca1237c194+ local 60ca1237c194+ remote 6747d179aa9a
183 t: remote is newer -> g
183 t: remote is newer -> g
184 updating: t 1/1 files (100.00%)
184 updating: t 1/1 files (100.00%)
185 getting t
185 getting t
186 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
186 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
187 (branch merge, don't forget to commit)
187 (branch merge, don't forget to commit)
188 $ hg debugsub
188 $ hg debugsub
189 path s
189 path s
190 source s
190 source s
191 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
191 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
192 path t
192 path t
193 source t
193 source t
194 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
194 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
195 $ echo conflict > t/t
195 $ echo conflict > t/t
196 $ hg ci -m10
196 $ hg ci -m10
197 committing subrepository t
197 committing subrepository t
198 $ HGMERGE=internal:merge hg merge --debug 7 # test conflict
198 $ HGMERGE=internal:merge hg merge --debug 7 # test conflict
199 searching for copies back to rev 2
199 searching for copies back to rev 2
200 resolving manifests
200 resolving manifests
201 overwrite None partial False
201 overwrite None partial False
202 ancestor 1831e14459c4 local e45c8b14af55+ remote f94576341bcf
202 ancestor 1831e14459c4 local e45c8b14af55+ remote f94576341bcf
203 .hgsubstate: versions differ -> m
203 .hgsubstate: versions differ -> m
204 updating: .hgsubstate 1/1 files (100.00%)
204 updating: .hgsubstate 1/1 files (100.00%)
205 subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4
205 subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4
206 subrepo t: both sides changed, merge with t:7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4:hg
206 subrepo t: both sides changed, merge with t:7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4:hg
207 merging subrepo t
207 merging subrepo t
208 searching for copies back to rev 2
208 searching for copies back to rev 2
209 resolving manifests
209 resolving manifests
210 overwrite None partial False
210 overwrite None partial False
211 ancestor 6747d179aa9a local 20a0db6fbf6c+ remote 7af322bc1198
211 ancestor 6747d179aa9a local 20a0db6fbf6c+ remote 7af322bc1198
212 t: versions differ -> m
212 t: versions differ -> m
213 preserving t for resolve of t
213 preserving t for resolve of t
214 updating: t 1/1 files (100.00%)
214 updating: t 1/1 files (100.00%)
215 picked tool 'internal:merge' for t (binary False symlink False)
215 picked tool 'internal:merge' for t (binary False symlink False)
216 merging t
216 merging t
217 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
217 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
218 warning: conflicts during merge.
218 warning: conflicts during merge.
219 merging t failed!
219 merging t failed!
220 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
220 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
221 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
221 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
222 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
222 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
223 (branch merge, don't forget to commit)
223 (branch merge, don't forget to commit)
224
224
225 should conflict
225 should conflict
226
226
227 $ cat t/t
227 $ cat t/t
228 <<<<<<< local
228 <<<<<<< local
229 conflict
229 conflict
230 =======
230 =======
231 t3
231 t3
232 >>>>>>> other
232 >>>>>>> other
233
233
234 clone
234 clone
235
235
236 $ cd ..
236 $ cd ..
237 $ hg clone t tc
237 $ hg clone t tc
238 updating to branch default
238 updating to branch default
239 pulling subrepo s from $TESTTMP/sub/t/s
239 pulling subrepo s from $TESTTMP/sub/t/s
240 requesting all changes
240 requesting all changes
241 adding changesets
241 adding changesets
242 adding manifests
242 adding manifests
243 adding file changes
243 adding file changes
244 added 4 changesets with 5 changes to 3 files
244 added 4 changesets with 5 changes to 3 files
245 pulling subrepo s/ss from $TESTTMP/sub/t/s/ss
245 pulling subrepo s/ss from $TESTTMP/sub/t/s/ss
246 requesting all changes
246 requesting all changes
247 adding changesets
247 adding changesets
248 adding manifests
248 adding manifests
249 adding file changes
249 adding file changes
250 added 1 changesets with 1 changes to 1 files
250 added 1 changesets with 1 changes to 1 files
251 pulling subrepo t from $TESTTMP/sub/t/t
251 pulling subrepo t from $TESTTMP/sub/t/t
252 requesting all changes
252 requesting all changes
253 adding changesets
253 adding changesets
254 adding manifests
254 adding manifests
255 adding file changes
255 adding file changes
256 added 4 changesets with 4 changes to 1 files (+1 heads)
256 added 4 changesets with 4 changes to 1 files (+1 heads)
257 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
257 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
258 $ cd tc
258 $ cd tc
259 $ hg debugsub
259 $ hg debugsub
260 path s
260 path s
261 source s
261 source s
262 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
262 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
263 path t
263 path t
264 source t
264 source t
265 revision 20a0db6fbf6c3d2836e6519a642ae929bfc67c0e
265 revision 20a0db6fbf6c3d2836e6519a642ae929bfc67c0e
266
266
267 push
267 push
268
268
269 $ echo bah > t/t
269 $ echo bah > t/t
270 $ hg ci -m11
270 $ hg ci -m11
271 committing subrepository t
271 committing subrepository t
272 $ hg push
272 $ hg push
273 pushing to $TESTTMP/sub/t
273 pushing to $TESTTMP/sub/t
274 pushing subrepo s/ss to $TESTTMP/sub/t/s/ss
274 pushing subrepo s/ss to $TESTTMP/sub/t/s/ss
275 searching for changes
275 searching for changes
276 no changes found
276 no changes found
277 pushing subrepo s to $TESTTMP/sub/t/s
277 pushing subrepo s to $TESTTMP/sub/t/s
278 searching for changes
278 searching for changes
279 no changes found
279 no changes found
280 pushing subrepo t to $TESTTMP/sub/t/t
280 pushing subrepo t to $TESTTMP/sub/t/t
281 searching for changes
281 searching for changes
282 adding changesets
282 adding changesets
283 adding manifests
283 adding manifests
284 adding file changes
284 adding file changes
285 added 1 changesets with 1 changes to 1 files
285 added 1 changesets with 1 changes to 1 files
286 searching for changes
286 searching for changes
287 adding changesets
287 adding changesets
288 adding manifests
288 adding manifests
289 adding file changes
289 adding file changes
290 added 1 changesets with 1 changes to 1 files
290 added 1 changesets with 1 changes to 1 files
291
291
292 push -f
292 push -f
293
293
294 $ echo bah > s/a
294 $ echo bah > s/a
295 $ hg ci -m12
295 $ hg ci -m12
296 committing subrepository s
296 committing subrepository s
297 $ hg push
297 $ hg push
298 pushing to $TESTTMP/sub/t
298 pushing to $TESTTMP/sub/t
299 pushing subrepo s/ss to $TESTTMP/sub/t/s/ss
299 pushing subrepo s/ss to $TESTTMP/sub/t/s/ss
300 searching for changes
300 searching for changes
301 no changes found
301 no changes found
302 pushing subrepo s to $TESTTMP/sub/t/s
302 pushing subrepo s to $TESTTMP/sub/t/s
303 searching for changes
303 searching for changes
304 abort: push creates new remote heads on branch 'default'!
304 abort: push creates new remote heads on branch 'default'!
305 (did you forget to merge? use push -f to force)
305 (did you forget to merge? use push -f to force)
306 [255]
306 [255]
307 $ hg push -f
307 $ hg push -f
308 pushing to $TESTTMP/sub/t
308 pushing to $TESTTMP/sub/t
309 pushing subrepo s/ss to $TESTTMP/sub/t/s/ss
309 pushing subrepo s/ss to $TESTTMP/sub/t/s/ss
310 searching for changes
310 searching for changes
311 no changes found
311 no changes found
312 pushing subrepo s to $TESTTMP/sub/t/s
312 pushing subrepo s to $TESTTMP/sub/t/s
313 searching for changes
313 searching for changes
314 adding changesets
314 adding changesets
315 adding manifests
315 adding manifests
316 adding file changes
316 adding file changes
317 added 1 changesets with 1 changes to 1 files (+1 heads)
317 added 1 changesets with 1 changes to 1 files (+1 heads)
318 pushing subrepo t to $TESTTMP/sub/t/t
318 pushing subrepo t to $TESTTMP/sub/t/t
319 searching for changes
319 searching for changes
320 no changes found
320 no changes found
321 searching for changes
321 searching for changes
322 adding changesets
322 adding changesets
323 adding manifests
323 adding manifests
324 adding file changes
324 adding file changes
325 added 1 changesets with 1 changes to 1 files
325 added 1 changesets with 1 changes to 1 files
326
326
327 update
327 update
328
328
329 $ cd ../t
329 $ cd ../t
330 $ hg up -C # discard our earlier merge
330 $ hg up -C # discard our earlier merge
331 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
331 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
332 $ echo blah > t/t
332 $ echo blah > t/t
333 $ hg ci -m13
333 $ hg ci -m13
334 committing subrepository t
334 committing subrepository t
335
335
336 pull
336 pull
337
337
338 $ cd ../tc
338 $ cd ../tc
339 $ hg pull
339 $ hg pull
340 pulling from $TESTTMP/sub/t
340 pulling from $TESTTMP/sub/t
341 searching for changes
341 searching for changes
342 adding changesets
342 adding changesets
343 adding manifests
343 adding manifests
344 adding file changes
344 adding file changes
345 added 1 changesets with 1 changes to 1 files
345 added 1 changesets with 1 changes to 1 files
346 (run 'hg update' to get a working copy)
346 (run 'hg update' to get a working copy)
347
347
348 should pull t
348 should pull t
349
349
350 $ hg up
350 $ hg up
351 pulling subrepo t from $TESTTMP/sub/t/t
351 pulling subrepo t from $TESTTMP/sub/t/t
352 searching for changes
352 searching for changes
353 adding changesets
353 adding changesets
354 adding manifests
354 adding manifests
355 adding file changes
355 adding file changes
356 added 1 changesets with 1 changes to 1 files
356 added 1 changesets with 1 changes to 1 files
357 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
357 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
358 $ cat t/t
358 $ cat t/t
359 blah
359 blah
360
360
361 bogus subrepo path aborts
361 bogus subrepo path aborts
362
362
363 $ echo 'bogus=[boguspath' >> .hgsub
363 $ echo 'bogus=[boguspath' >> .hgsub
364 $ hg ci -m 'bogus subrepo path'
364 $ hg ci -m 'bogus subrepo path'
365 abort: missing ] in subrepo source
365 abort: missing ] in subrepo source
366 [255]
366 [255]
367
367
368 Issue1986: merge aborts when trying to merge a subrepo that
368 Issue1986: merge aborts when trying to merge a subrepo that
369 shouldn't need merging
369 shouldn't need merging
370
370
371 # subrepo layout
371 # subrepo layout
372 #
372 #
373 # o 5 br
373 # o 5 br
374 # /|
374 # /|
375 # o | 4 default
375 # o | 4 default
376 # | |
376 # | |
377 # | o 3 br
377 # | o 3 br
378 # |/|
378 # |/|
379 # o | 2 default
379 # o | 2 default
380 # | |
380 # | |
381 # | o 1 br
381 # | o 1 br
382 # |/
382 # |/
383 # o 0 default
383 # o 0 default
384
384
385 $ cd ..
385 $ cd ..
386 $ rm -rf sub
386 $ rm -rf sub
387 $ hg init main
387 $ hg init main
388 $ cd main
388 $ cd main
389 $ hg init s
389 $ hg init s
390 $ cd s
390 $ cd s
391 $ echo a > a
391 $ echo a > a
392 $ hg ci -Am1
392 $ hg ci -Am1
393 adding a
393 adding a
394 $ hg branch br
394 $ hg branch br
395 marked working directory as branch br
395 marked working directory as branch br
396 $ echo a >> a
396 $ echo a >> a
397 $ hg ci -m1
397 $ hg ci -m1
398 $ hg up default
398 $ hg up default
399 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
399 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
400 $ echo b > b
400 $ echo b > b
401 $ hg ci -Am1
401 $ hg ci -Am1
402 adding b
402 adding b
403 $ hg up br
403 $ hg up br
404 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
404 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
405 $ hg merge tip
405 $ hg merge tip
406 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
406 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
407 (branch merge, don't forget to commit)
407 (branch merge, don't forget to commit)
408 $ hg ci -m1
408 $ hg ci -m1
409 $ hg up 2
409 $ hg up 2
410 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
410 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
411 $ echo c > c
411 $ echo c > c
412 $ hg ci -Am1
412 $ hg ci -Am1
413 adding c
413 adding c
414 $ hg up 3
414 $ hg up 3
415 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
415 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
416 $ hg merge 4
416 $ hg merge 4
417 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
417 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
418 (branch merge, don't forget to commit)
418 (branch merge, don't forget to commit)
419 $ hg ci -m1
419 $ hg ci -m1
420
420
421 # main repo layout:
421 # main repo layout:
422 #
422 #
423 # * <-- try to merge default into br again
423 # * <-- try to merge default into br again
424 # .`|
424 # .`|
425 # . o 5 br --> substate = 5
425 # . o 5 br --> substate = 5
426 # . |
426 # . |
427 # o | 4 default --> substate = 4
427 # o | 4 default --> substate = 4
428 # | |
428 # | |
429 # | o 3 br --> substate = 2
429 # | o 3 br --> substate = 2
430 # |/|
430 # |/|
431 # o | 2 default --> substate = 2
431 # o | 2 default --> substate = 2
432 # | |
432 # | |
433 # | o 1 br --> substate = 3
433 # | o 1 br --> substate = 3
434 # |/
434 # |/
435 # o 0 default --> substate = 2
435 # o 0 default --> substate = 2
436
436
437 $ cd ..
437 $ cd ..
438 $ echo 's = s' > .hgsub
438 $ echo 's = s' > .hgsub
439 $ hg -R s up 2
439 $ hg -R s up 2
440 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
440 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
441 $ hg ci -Am1
441 $ hg ci -Am1
442 adding .hgsub
442 adding .hgsub
443 committing subrepository s
443 committing subrepository s
444 $ hg branch br
444 $ hg branch br
445 marked working directory as branch br
445 marked working directory as branch br
446 $ echo b > b
446 $ echo b > b
447 $ hg -R s up 3
447 $ hg -R s up 3
448 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
448 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
449 $ hg ci -Am1
449 $ hg ci -Am1
450 adding b
450 adding b
451 committing subrepository s
451 committing subrepository s
452 $ hg up default
452 $ hg up default
453 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
453 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
454 $ echo c > c
454 $ echo c > c
455 $ hg ci -Am1
455 $ hg ci -Am1
456 adding c
456 adding c
457 $ hg up 1
457 $ hg up 1
458 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
458 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
459 $ hg merge 2
459 $ hg merge 2
460 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
460 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
461 (branch merge, don't forget to commit)
461 (branch merge, don't forget to commit)
462 $ hg ci -m1
462 $ hg ci -m1
463 $ hg up 2
463 $ hg up 2
464 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
464 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
465 $ hg -R s up 4
465 $ hg -R s up 4
466 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
466 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
467 $ echo d > d
467 $ echo d > d
468 $ hg ci -Am1
468 $ hg ci -Am1
469 adding d
469 adding d
470 committing subrepository s
470 committing subrepository s
471 $ hg up 3
471 $ hg up 3
472 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
472 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
473 $ hg -R s up 5
473 $ hg -R s up 5
474 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
474 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
475 $ echo e > e
475 $ echo e > e
476 $ hg ci -Am1
476 $ hg ci -Am1
477 adding e
477 adding e
478 committing subrepository s
478 committing subrepository s
479
479
480 $ hg up 5
480 $ hg up 5
481 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
481 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
482 $ hg merge 4 # try to merge default into br again
482 $ hg merge 4 # try to merge default into br again
483 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
483 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
484 (branch merge, don't forget to commit)
484 (branch merge, don't forget to commit)
485 $ cd ..
485 $ cd ..
486
486
487 test subrepo delete from .hgsubstate
487 test subrepo delete from .hgsubstate
488
488
489 $ hg init testdelete
489 $ hg init testdelete
490 $ mkdir testdelete/nested testdelete/nested2
490 $ mkdir testdelete/nested testdelete/nested2
491 $ hg init testdelete/nested
491 $ hg init testdelete/nested
492 $ hg init testdelete/nested2
492 $ hg init testdelete/nested2
493 $ echo test > testdelete/nested/foo
493 $ echo test > testdelete/nested/foo
494 $ echo test > testdelete/nested2/foo
494 $ echo test > testdelete/nested2/foo
495 $ hg -R testdelete/nested add
495 $ hg -R testdelete/nested add
496 adding testdelete/nested/foo
496 adding testdelete/nested/foo
497 $ hg -R testdelete/nested2 add
497 $ hg -R testdelete/nested2 add
498 adding testdelete/nested2/foo
498 adding testdelete/nested2/foo
499 $ hg -R testdelete/nested ci -m test
499 $ hg -R testdelete/nested ci -m test
500 $ hg -R testdelete/nested2 ci -m test
500 $ hg -R testdelete/nested2 ci -m test
501 $ echo nested = nested > testdelete/.hgsub
501 $ echo nested = nested > testdelete/.hgsub
502 $ echo nested2 = nested2 >> testdelete/.hgsub
502 $ echo nested2 = nested2 >> testdelete/.hgsub
503 $ hg -R testdelete add
503 $ hg -R testdelete add
504 adding testdelete/.hgsub
504 adding testdelete/.hgsub
505 $ hg -R testdelete ci -m "nested 1 & 2 added"
505 $ hg -R testdelete ci -m "nested 1 & 2 added"
506 committing subrepository nested
506 committing subrepository nested
507 committing subrepository nested2
507 committing subrepository nested2
508 $ echo nested = nested > testdelete/.hgsub
508 $ echo nested = nested > testdelete/.hgsub
509 $ hg -R testdelete ci -m "nested 2 deleted"
509 $ hg -R testdelete ci -m "nested 2 deleted"
510 $ cat testdelete/.hgsubstate
510 $ cat testdelete/.hgsubstate
511 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
511 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
512 $ hg -R testdelete remove testdelete/.hgsub
512 $ hg -R testdelete remove testdelete/.hgsub
513 $ hg -R testdelete ci -m ".hgsub deleted"
513 $ hg -R testdelete ci -m ".hgsub deleted"
514 $ cat testdelete/.hgsubstate
514 $ cat testdelete/.hgsubstate
515
515
516 test repository cloning
516 test repository cloning
517
517
518 $ mkdir mercurial mercurial2
518 $ mkdir mercurial mercurial2
519 $ hg init nested_absolute
519 $ hg init nested_absolute
520 $ echo test > nested_absolute/foo
520 $ echo test > nested_absolute/foo
521 $ hg -R nested_absolute add
521 $ hg -R nested_absolute add
522 adding nested_absolute/foo
522 adding nested_absolute/foo
523 $ hg -R nested_absolute ci -mtest
523 $ hg -R nested_absolute ci -mtest
524 $ cd mercurial
524 $ cd mercurial
525 $ hg init nested_relative
525 $ hg init nested_relative
526 $ echo test2 > nested_relative/foo2
526 $ echo test2 > nested_relative/foo2
527 $ hg -R nested_relative add
527 $ hg -R nested_relative add
528 adding nested_relative/foo2
528 adding nested_relative/foo2
529 $ hg -R nested_relative ci -mtest2
529 $ hg -R nested_relative ci -mtest2
530 $ hg init main
530 $ hg init main
531 $ echo "nested_relative = ../nested_relative" > main/.hgsub
531 $ echo "nested_relative = ../nested_relative" > main/.hgsub
532 $ echo "nested_absolute = `pwd`/nested_absolute" >> main/.hgsub
532 $ echo "nested_absolute = `pwd`/nested_absolute" >> main/.hgsub
533 $ hg -R main add
533 $ hg -R main add
534 adding main/.hgsub
534 adding main/.hgsub
535 $ hg -R main ci -m "add subrepos"
535 $ hg -R main ci -m "add subrepos"
536 committing subrepository nested_absolute
536 committing subrepository nested_absolute
537 committing subrepository nested_relative
537 committing subrepository nested_relative
538 $ cd ..
538 $ cd ..
539 $ hg clone mercurial/main mercurial2/main
539 $ hg clone mercurial/main mercurial2/main
540 updating to branch default
540 updating to branch default
541 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
541 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
542 $ cat mercurial2/main/nested_absolute/.hg/hgrc \
542 $ cat mercurial2/main/nested_absolute/.hg/hgrc \
543 > mercurial2/main/nested_relative/.hg/hgrc
543 > mercurial2/main/nested_relative/.hg/hgrc
544 [paths]
544 [paths]
545 default = $TESTTMP/sub/mercurial/nested_absolute
545 default = $TESTTMP/sub/mercurial/nested_absolute
546 [paths]
546 [paths]
547 default = $TESTTMP/sub/mercurial/nested_relative
547 default = $TESTTMP/sub/mercurial/nested_relative
548 $ rm -rf mercurial mercurial2
548 $ rm -rf mercurial mercurial2
549
549
550 Issue1977: multirepo push should fail if subrepo push fails
550 Issue1977: multirepo push should fail if subrepo push fails
551
551
552 $ hg init repo
552 $ hg init repo
553 $ hg init repo/s
553 $ hg init repo/s
554 $ echo a > repo/s/a
554 $ echo a > repo/s/a
555 $ hg -R repo/s ci -Am0
555 $ hg -R repo/s ci -Am0
556 adding a
556 adding a
557 $ echo s = s > repo/.hgsub
557 $ echo s = s > repo/.hgsub
558 $ hg -R repo ci -Am1
558 $ hg -R repo ci -Am1
559 adding .hgsub
559 adding .hgsub
560 committing subrepository s
560 committing subrepository s
561 $ hg clone repo repo2
561 $ hg clone repo repo2
562 updating to branch default
562 updating to branch default
563 pulling subrepo s from $TESTTMP/sub/repo/s
563 pulling subrepo s from $TESTTMP/sub/repo/s
564 requesting all changes
564 requesting all changes
565 adding changesets
565 adding changesets
566 adding manifests
566 adding manifests
567 adding file changes
567 adding file changes
568 added 1 changesets with 1 changes to 1 files
568 added 1 changesets with 1 changes to 1 files
569 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
569 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
570 $ hg -q -R repo2 pull -u
570 $ hg -q -R repo2 pull -u
571 $ echo 1 > repo2/s/a
571 $ echo 1 > repo2/s/a
572 $ hg -R repo2/s ci -m2
572 $ hg -R repo2/s ci -m2
573 $ hg -q -R repo2/s push
573 $ hg -q -R repo2/s push
574 $ hg -R repo2/s up -C 0
574 $ hg -R repo2/s up -C 0
575 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
575 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
576 $ echo 2 > repo2/s/a
576 $ echo 2 > repo2/s/a
577 $ hg -R repo2/s ci -m3
577 $ hg -R repo2/s ci -m3
578 created new head
578 created new head
579 $ hg -R repo2 ci -m3
579 $ hg -R repo2 ci -m3
580 committing subrepository s
580 committing subrepository s
581 $ hg -q -R repo2 push
581 $ hg -q -R repo2 push
582 abort: push creates new remote heads on branch 'default'!
582 abort: push creates new remote heads on branch 'default'!
583 (did you forget to merge? use push -f to force)
583 (did you forget to merge? use push -f to force)
584 [255]
584 [255]
585 $ hg -R repo update
585 $ hg -R repo update
586 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
586 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
587 $ rm -rf repo2 repo
587 $ rm -rf repo2 repo
588
588
589
589
590 Issue1852 subrepos with relative paths always push/pull relative to default
590 Issue1852 subrepos with relative paths always push/pull relative to default
591
591
592 Prepare a repo with subrepo
592 Prepare a repo with subrepo
593
593
594 $ hg init issue1852a
594 $ hg init issue1852a
595 $ cd issue1852a
595 $ cd issue1852a
596 $ hg init sub/repo
596 $ hg init sub/repo
597 $ echo test > sub/repo/foo
597 $ echo test > sub/repo/foo
598 $ hg -R sub/repo add sub/repo/foo
598 $ hg -R sub/repo add sub/repo/foo
599 $ echo sub/repo = sub/repo > .hgsub
599 $ echo sub/repo = sub/repo > .hgsub
600 $ hg add .hgsub
600 $ hg add .hgsub
601 $ hg ci -mtest
601 $ hg ci -mtest
602 committing subrepository sub/repo
602 committing subrepository sub/repo
603 $ echo test >> sub/repo/foo
603 $ echo test >> sub/repo/foo
604 $ hg ci -mtest
604 $ hg ci -mtest
605 committing subrepository sub/repo
605 committing subrepository sub/repo
606 $ cd ..
606 $ cd ..
607
607
608 Create repo without default path, pull top repo, and see what happens on update
608 Create repo without default path, pull top repo, and see what happens on update
609
609
610 $ hg init issue1852b
610 $ hg init issue1852b
611 $ hg -R issue1852b pull issue1852a
611 $ hg -R issue1852b pull issue1852a
612 pulling from issue1852a
612 pulling from issue1852a
613 requesting all changes
613 requesting all changes
614 adding changesets
614 adding changesets
615 adding manifests
615 adding manifests
616 adding file changes
616 adding file changes
617 added 2 changesets with 3 changes to 2 files
617 added 2 changesets with 3 changes to 2 files
618 (run 'hg update' to get a working copy)
618 (run 'hg update' to get a working copy)
619 $ hg -R issue1852b update
619 $ hg -R issue1852b update
620 abort: default path for subrepository sub/repo not found
620 abort: default path for subrepository sub/repo not found
621 [255]
621 [255]
622
622
623 Pull -u now doesn't help
623 Pull -u now doesn't help
624
624
625 $ hg -R issue1852b pull -u issue1852a
625 $ hg -R issue1852b pull -u issue1852a
626 pulling from issue1852a
626 pulling from issue1852a
627 searching for changes
627 searching for changes
628 no changes found
628 no changes found
629
629
630 Try the same, but with pull -u
630 Try the same, but with pull -u
631
631
632 $ hg init issue1852c
632 $ hg init issue1852c
633 $ hg -R issue1852c pull -r0 -u issue1852a
633 $ hg -R issue1852c pull -r0 -u issue1852a
634 pulling from issue1852a
634 pulling from issue1852a
635 adding changesets
635 adding changesets
636 adding manifests
636 adding manifests
637 adding file changes
637 adding file changes
638 added 1 changesets with 2 changes to 2 files
638 added 1 changesets with 2 changes to 2 files
639 pulling subrepo sub/repo from issue1852a/sub/repo
639 pulling subrepo sub/repo from issue1852a/sub/repo
640 requesting all changes
640 requesting all changes
641 adding changesets
641 adding changesets
642 adding manifests
642 adding manifests
643 adding file changes
643 adding file changes
644 added 2 changesets with 2 changes to 1 files
644 added 2 changesets with 2 changes to 1 files
645 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
645 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
646
646
647 Try to push from the other side
647 Try to push from the other side
648
648
649 $ hg -R issue1852a push `pwd`/issue1852c
649 $ hg -R issue1852a push `pwd`/issue1852c
650 pushing to $TESTTMP/sub/issue1852c
650 pushing to $TESTTMP/sub/issue1852c
651 pushing subrepo sub/repo to $TESTTMP/sub/issue1852c/sub/repo
651 pushing subrepo sub/repo to $TESTTMP/sub/issue1852c/sub/repo
652 searching for changes
652 searching for changes
653 no changes found
653 no changes found
654 searching for changes
654 searching for changes
655 adding changesets
655 adding changesets
656 adding manifests
656 adding manifests
657 adding file changes
657 adding file changes
658 added 1 changesets with 1 changes to 1 files
658 added 1 changesets with 1 changes to 1 files
659
659
660 Check status of files when none of them belong to the first
660 Check status of files when none of them belong to the first
661 subrepository:
661 subrepository:
662
662
663 $ hg init subrepo-status
663 $ hg init subrepo-status
664 $ cd subrepo-status
664 $ cd subrepo-status
665 $ hg init subrepo-1
665 $ hg init subrepo-1
666 $ hg init subrepo-2
666 $ hg init subrepo-2
667 $ cd subrepo-2
667 $ cd subrepo-2
668 $ touch file
668 $ touch file
669 $ hg add file
669 $ hg add file
670 $ cd ..
670 $ cd ..
671 $ echo subrepo-1 = subrepo-1 > .hgsub
671 $ echo subrepo-1 = subrepo-1 > .hgsub
672 $ echo subrepo-2 = subrepo-2 >> .hgsub
672 $ echo subrepo-2 = subrepo-2 >> .hgsub
673 $ hg add .hgsub
673 $ hg add .hgsub
674 $ hg ci -m 'Added subrepos'
674 $ hg ci -m 'Added subrepos'
675 committing subrepository subrepo-1
675 committing subrepository subrepo-1
676 committing subrepository subrepo-2
676 committing subrepository subrepo-2
677 $ hg st subrepo-2/file
677 $ hg st subrepo-2/file
678
679 Check hg update --clean
680 $ cd $TESTTMP/sub/t
681 $ rm -r t/t.orig
682 $ hg status -S --all
683 C .hgsub
684 C .hgsubstate
685 C a
686 C s/.hgsub
687 C s/.hgsubstate
688 C s/a
689 C s/ss/a
690 C t/t
691 $ echo c1 > s/a
692 $ cd s
693 $ echo c1 > b
694 $ echo c1 > c
695 $ hg add b
696 $ cd ..
697 $ hg status -S
698 M s/a
699 A s/b
700 ? s/c
701 $ hg update -C
702 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
703 $ hg status -S
704 ? s/b
705 ? s/c
General Comments 0
You need to be logged in to leave comments. Login now