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