##// END OF EJS Templates
merge with stable
Matt Mackall -
r12010:ce818cf2 merge default
parent child Browse files
Show More
@@ -1,259 +1,256 b''
1 1 # filemerge.py - file-level merge handling for Mercurial
2 2 #
3 3 # Copyright 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import short
9 9 from i18n import _
10 10 import util, simplemerge, match, error
11 11 import os, tempfile, re, filecmp
12 12
13 13 def _toolstr(ui, tool, part, default=""):
14 14 return ui.config("merge-tools", tool + "." + part, default)
15 15
16 16 def _toolbool(ui, tool, part, default=False):
17 17 return ui.configbool("merge-tools", tool + "." + part, default)
18 18
19 19 def _toollist(ui, tool, part, default=[]):
20 20 return ui.configlist("merge-tools", tool + "." + part, default)
21 21
22 22 _internal = ['internal:' + s
23 23 for s in 'fail local other merge prompt dump'.split()]
24 24
25 25 def _findtool(ui, tool):
26 26 if tool in _internal:
27 27 return tool
28 28 k = _toolstr(ui, tool, "regkey")
29 29 if k:
30 30 p = util.lookup_reg(k, _toolstr(ui, tool, "regname"))
31 31 if p:
32 32 p = util.find_exe(p + _toolstr(ui, tool, "regappend"))
33 33 if p:
34 34 return p
35 35 return util.find_exe(_toolstr(ui, tool, "executable", tool))
36 36
37 37 def _picktool(repo, ui, path, binary, symlink):
38 38 def check(tool, pat, symlink, binary):
39 39 tmsg = tool
40 40 if pat:
41 41 tmsg += " specified for " + pat
42 42 if not _findtool(ui, tool):
43 43 if pat: # explicitly requested tool deserves a warning
44 44 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
45 45 else: # configured but non-existing tools are more silent
46 46 ui.note(_("couldn't find merge tool %s\n") % tmsg)
47 47 elif symlink and not _toolbool(ui, tool, "symlink"):
48 48 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
49 49 elif binary and not _toolbool(ui, tool, "binary"):
50 50 ui.warn(_("tool %s can't handle binary\n") % tmsg)
51 51 elif not util.gui() and _toolbool(ui, tool, "gui"):
52 52 ui.warn(_("tool %s requires a GUI\n") % tmsg)
53 53 else:
54 54 return True
55 55 return False
56 56
57 57 # HGMERGE takes precedence
58 58 hgmerge = os.environ.get("HGMERGE")
59 59 if hgmerge:
60 60 return (hgmerge, hgmerge)
61 61
62 62 # then patterns
63 63 for pat, tool in ui.configitems("merge-patterns"):
64 64 mf = match.match(repo.root, '', [pat])
65 65 if mf(path) and check(tool, pat, symlink, False):
66 66 toolpath = _findtool(ui, tool)
67 67 return (tool, '"' + toolpath + '"')
68 68
69 69 # then merge tools
70 70 tools = {}
71 71 for k, v in ui.configitems("merge-tools"):
72 72 t = k.split('.')[0]
73 73 if t not in tools:
74 74 tools[t] = int(_toolstr(ui, t, "priority", "0"))
75 75 names = tools.keys()
76 76 tools = sorted([(-p, t) for t, p in tools.items()])
77 77 uimerge = ui.config("ui", "merge")
78 78 if uimerge:
79 79 if uimerge not in names:
80 80 return (uimerge, uimerge)
81 81 tools.insert(0, (None, uimerge)) # highest priority
82 82 tools.append((None, "hgmerge")) # the old default, if found
83 83 for p, t in tools:
84 84 if check(t, None, symlink, binary):
85 85 toolpath = _findtool(ui, t)
86 86 return (t, '"' + toolpath + '"')
87 87 # internal merge as last resort
88 88 return (not (symlink or binary) and "internal:merge" or None, None)
89 89
90 90 def _eoltype(data):
91 91 "Guess the EOL type of a file"
92 92 if '\0' in data: # binary
93 93 return None
94 94 if '\r\n' in data: # Windows
95 95 return '\r\n'
96 96 if '\r' in data: # Old Mac
97 97 return '\r'
98 98 if '\n' in data: # UNIX
99 99 return '\n'
100 100 return None # unknown
101 101
102 102 def _matcheol(file, origfile):
103 103 "Convert EOL markers in a file to match origfile"
104 104 tostyle = _eoltype(open(origfile, "rb").read())
105 105 if tostyle:
106 106 data = open(file, "rb").read()
107 107 style = _eoltype(data)
108 108 if style:
109 109 newdata = data.replace(style, tostyle)
110 110 if newdata != data:
111 111 open(file, "wb").write(newdata)
112 112
113 113 def filemerge(repo, mynode, orig, fcd, fco, fca):
114 114 """perform a 3-way merge in the working directory
115 115
116 116 mynode = parent node before merge
117 117 orig = original local filename before merge
118 118 fco = other file context
119 119 fca = ancestor file context
120 120 fcd = local file context for current/destination file
121 121 """
122 122
123 123 def temp(prefix, ctx):
124 124 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
125 125 (fd, name) = tempfile.mkstemp(prefix=pre)
126 126 data = repo.wwritedata(ctx.path(), ctx.data())
127 127 f = os.fdopen(fd, "wb")
128 128 f.write(data)
129 129 f.close()
130 130 return name
131 131
132 132 def isbin(ctx):
133 133 try:
134 134 return util.binary(ctx.data())
135 135 except IOError:
136 136 return False
137 137
138 138 if not fco.cmp(fcd): # files identical?
139 139 return None
140 140
141 if fca == fco: # backwards, use working dir parent as ancestor
142 fca = fcd.parents()[0]
143
144 141 ui = repo.ui
145 142 fd = fcd.path()
146 143 binary = isbin(fcd) or isbin(fco) or isbin(fca)
147 144 symlink = 'l' in fcd.flags() + fco.flags()
148 145 tool, toolpath = _picktool(repo, ui, fd, binary, symlink)
149 146 ui.debug("picked tool '%s' for %s (binary %s symlink %s)\n" %
150 147 (tool, fd, binary, symlink))
151 148
152 149 if not tool or tool == 'internal:prompt':
153 150 tool = "internal:local"
154 151 if ui.promptchoice(_(" no tool found to merge %s\n"
155 152 "keep (l)ocal or take (o)ther?") % fd,
156 153 (_("&Local"), _("&Other")), 0):
157 154 tool = "internal:other"
158 155 if tool == "internal:local":
159 156 return 0
160 157 if tool == "internal:other":
161 158 repo.wwrite(fd, fco.data(), fco.flags())
162 159 return 0
163 160 if tool == "internal:fail":
164 161 return 1
165 162
166 163 # do the actual merge
167 164 a = repo.wjoin(fd)
168 165 b = temp("base", fca)
169 166 c = temp("other", fco)
170 167 out = ""
171 168 back = a + ".orig"
172 169 util.copyfile(a, back)
173 170
174 171 if orig != fco.path():
175 172 ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
176 173 else:
177 174 ui.status(_("merging %s\n") % fd)
178 175
179 176 ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
180 177
181 178 # do we attempt to simplemerge first?
182 179 try:
183 180 premerge = _toolbool(ui, tool, "premerge", not (binary or symlink))
184 181 except error.ConfigError:
185 182 premerge = _toolstr(ui, tool, "premerge").lower()
186 183 valid = 'keep'.split()
187 184 if premerge not in valid:
188 185 _valid = ', '.join(["'" + v + "'" for v in valid])
189 186 raise error.ConfigError(_("%s.premerge not valid "
190 187 "('%s' is neither boolean nor %s)") %
191 188 (tool, premerge, _valid))
192 189
193 190 if premerge:
194 191 r = simplemerge.simplemerge(ui, a, b, c, quiet=True)
195 192 if not r:
196 193 ui.debug(" premerge successful\n")
197 194 os.unlink(back)
198 195 os.unlink(b)
199 196 os.unlink(c)
200 197 return 0
201 198 if premerge != 'keep':
202 199 util.copyfile(back, a) # restore from backup and try again
203 200
204 201 env = dict(HG_FILE=fd,
205 202 HG_MY_NODE=short(mynode),
206 203 HG_OTHER_NODE=str(fco.changectx()),
207 204 HG_BASE_NODE=str(fca.changectx()),
208 205 HG_MY_ISLINK='l' in fcd.flags(),
209 206 HG_OTHER_ISLINK='l' in fco.flags(),
210 207 HG_BASE_ISLINK='l' in fca.flags())
211 208
212 209 if tool == "internal:merge":
213 210 r = simplemerge.simplemerge(ui, a, b, c, label=['local', 'other'])
214 211 elif tool == 'internal:dump':
215 212 a = repo.wjoin(fd)
216 213 util.copyfile(a, a + ".local")
217 214 repo.wwrite(fd + ".other", fco.data(), fco.flags())
218 215 repo.wwrite(fd + ".base", fca.data(), fca.flags())
219 216 return 1 # unresolved
220 217 else:
221 218 args = _toolstr(ui, tool, "args", '$local $base $other')
222 219 if "$output" in args:
223 220 out, a = a, back # read input from backup, write to original
224 221 replace = dict(local=a, base=b, other=c, output=out)
225 222 args = util.interpolate(r'\$', replace, args,
226 223 lambda s: '"%s"' % util.localpath(s))
227 224 r = util.system(toolpath + ' ' + args, cwd=repo.root, environ=env)
228 225
229 226 if not r and (_toolbool(ui, tool, "checkconflicts") or
230 227 'conflicts' in _toollist(ui, tool, "check")):
231 228 if re.match("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data()):
232 229 r = 1
233 230
234 231 checked = False
235 232 if 'prompt' in _toollist(ui, tool, "check"):
236 233 checked = True
237 234 if ui.promptchoice(_("was merge of '%s' successful (yn)?") % fd,
238 235 (_("&Yes"), _("&No")), 1):
239 236 r = 1
240 237
241 238 if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
242 239 'changed' in _toollist(ui, tool, "check")):
243 240 if filecmp.cmp(repo.wjoin(fd), back):
244 241 if ui.promptchoice(_(" output file %s appears unchanged\n"
245 242 "was merge successful (yn)?") % fd,
246 243 (_("&Yes"), _("&No")), 1):
247 244 r = 1
248 245
249 246 if _toolbool(ui, tool, "fixeol"):
250 247 _matcheol(repo.wjoin(fd), back)
251 248
252 249 if r:
253 250 ui.warn(_("merging %s failed!\n") % fd)
254 251 else:
255 252 os.unlink(back)
256 253
257 254 os.unlink(b)
258 255 os.unlink(c)
259 256 return r
@@ -1,528 +1,533 b''
1 1 # merge.py - directory-level update/merge handling for Mercurial
2 2 #
3 3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import nullid, nullrev, hex, bin
9 9 from i18n import _
10 10 import util, filemerge, copies, subrepo
11 11 import errno, os, shutil
12 12
13 13 class mergestate(object):
14 14 '''track 3-way merge state of individual files'''
15 15 def __init__(self, repo):
16 16 self._repo = repo
17 17 self._read()
18 18 def reset(self, node=None):
19 19 self._state = {}
20 20 if node:
21 21 self._local = node
22 22 shutil.rmtree(self._repo.join("merge"), True)
23 23 def _read(self):
24 24 self._state = {}
25 25 try:
26 26 f = self._repo.opener("merge/state")
27 27 for i, l in enumerate(f):
28 28 if i == 0:
29 29 self._local = bin(l[:-1])
30 30 else:
31 31 bits = l[:-1].split("\0")
32 32 self._state[bits[0]] = bits[1:]
33 33 except IOError, err:
34 34 if err.errno != errno.ENOENT:
35 35 raise
36 36 def _write(self):
37 37 f = self._repo.opener("merge/state", "w")
38 38 f.write(hex(self._local) + "\n")
39 39 for d, v in self._state.iteritems():
40 40 f.write("\0".join([d] + v) + "\n")
41 41 def add(self, fcl, fco, fca, fd, flags):
42 42 hash = util.sha1(fcl.path()).hexdigest()
43 43 self._repo.opener("merge/" + hash, "w").write(fcl.data())
44 44 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
45 45 hex(fca.filenode()), fco.path(), flags]
46 46 self._write()
47 47 def __contains__(self, dfile):
48 48 return dfile in self._state
49 49 def __getitem__(self, dfile):
50 50 return self._state[dfile][0]
51 51 def __iter__(self):
52 52 l = self._state.keys()
53 53 l.sort()
54 54 for f in l:
55 55 yield f
56 56 def mark(self, dfile, state):
57 57 self._state[dfile][0] = state
58 58 self._write()
59 59 def resolve(self, dfile, wctx, octx):
60 60 if self[dfile] == 'r':
61 61 return 0
62 62 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
63 63 f = self._repo.opener("merge/" + hash)
64 64 self._repo.wwrite(dfile, f.read(), flags)
65 65 fcd = wctx[dfile]
66 66 fco = octx[ofile]
67 67 fca = self._repo.filectx(afile, fileid=anode)
68 68 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
69 69 if not r:
70 70 self.mark(dfile, 'r')
71 71 return r
72 72
73 73 def _checkunknown(wctx, mctx):
74 74 "check for collisions between unknown files and files in mctx"
75 75 for f in wctx.unknown():
76 76 if f in mctx and mctx[f].cmp(wctx[f]):
77 77 raise util.Abort(_("untracked file in working directory differs"
78 78 " from file in requested revision: '%s'") % f)
79 79
80 80 def _checkcollision(mctx):
81 81 "check for case folding collisions in the destination context"
82 82 folded = {}
83 83 for fn in mctx:
84 84 fold = fn.lower()
85 85 if fold in folded:
86 86 raise util.Abort(_("case-folding collision between %s and %s")
87 87 % (fn, folded[fold]))
88 88 folded[fold] = fn
89 89
90 90 def _forgetremoved(wctx, mctx, branchmerge):
91 91 """
92 92 Forget removed files
93 93
94 94 If we're jumping between revisions (as opposed to merging), and if
95 95 neither the working directory nor the target rev has the file,
96 96 then we need to remove it from the dirstate, to prevent the
97 97 dirstate from listing the file when it is no longer in the
98 98 manifest.
99 99
100 100 If we're merging, and the other revision has removed a file
101 101 that is not present in the working directory, we need to mark it
102 102 as removed.
103 103 """
104 104
105 105 action = []
106 106 state = branchmerge and 'r' or 'f'
107 107 for f in wctx.deleted():
108 108 if f not in mctx:
109 109 action.append((f, state))
110 110
111 111 if not branchmerge:
112 112 for f in wctx.removed():
113 113 if f not in mctx:
114 114 action.append((f, "f"))
115 115
116 116 return action
117 117
118 118 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
119 119 """
120 120 Merge p1 and p2 with ancestor pa and generate merge action list
121 121
122 122 overwrite = whether we clobber working files
123 123 partial = function to filter file lists
124 124 """
125 125
126 126 def fmerge(f, f2, fa):
127 127 """merge flags"""
128 128 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
129 129 if m == n: # flags agree
130 130 return m # unchanged
131 131 if m and n and not a: # flags set, don't agree, differ from parent
132 132 r = repo.ui.promptchoice(
133 133 _(" conflicting flags for %s\n"
134 134 "(n)one, e(x)ec or sym(l)ink?") % f,
135 135 (_("&None"), _("E&xec"), _("Sym&link")), 0)
136 136 if r == 1:
137 137 return "x" # Exec
138 138 if r == 2:
139 139 return "l" # Symlink
140 140 return ""
141 141 if m and m != a: # changed from a to m
142 142 return m
143 143 if n and n != a: # changed from a to n
144 144 return n
145 145 return '' # flag was cleared
146 146
147 147 def act(msg, m, f, *args):
148 148 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
149 149 action.append((f, m) + args)
150 150
151 151 action, copy = [], {}
152 152
153 153 if overwrite:
154 154 pa = p1
155 155 elif pa == p2: # backwards
156 156 pa = p1.p1()
157 157 elif pa and repo.ui.configbool("merge", "followcopies", True):
158 158 dirs = repo.ui.configbool("merge", "followdirs", True)
159 159 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
160 160 for of, fl in diverge.iteritems():
161 161 act("divergent renames", "dr", of, fl)
162 162
163 163 repo.ui.note(_("resolving manifests\n"))
164 164 repo.ui.debug(" overwrite %s partial %s\n" % (overwrite, bool(partial)))
165 165 repo.ui.debug(" ancestor %s local %s remote %s\n" % (pa, p1, p2))
166 166
167 167 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
168 168 copied = set(copy.values())
169 169
170 170 if '.hgsubstate' in m1:
171 171 # check whether sub state is modified
172 172 for s in p1.substate:
173 173 if p1.sub(s).dirty():
174 174 m1['.hgsubstate'] += "+"
175 175 break
176 176
177 177 # Compare manifests
178 178 for f, n in m1.iteritems():
179 179 if partial and not partial(f):
180 180 continue
181 181 if f in m2:
182 182 rflags = fmerge(f, f, f)
183 183 a = ma.get(f, nullid)
184 184 if n == m2[f] or m2[f] == a: # same or local newer
185 185 # is file locally modified or flags need changing?
186 186 # dirstate flags may need to be made current
187 187 if m1.flags(f) != rflags or n[20:]:
188 188 act("update permissions", "e", f, rflags)
189 189 elif n == a: # remote newer
190 190 act("remote is newer", "g", f, rflags)
191 191 else: # both changed
192 192 act("versions differ", "m", f, f, f, rflags, False)
193 193 elif f in copied: # files we'll deal with on m2 side
194 194 pass
195 195 elif f in copy:
196 196 f2 = copy[f]
197 197 if f2 not in m2: # directory rename
198 198 act("remote renamed directory to " + f2, "d",
199 199 f, None, f2, m1.flags(f))
200 200 else: # case 2 A,B/B/B or case 4,21 A/B/B
201 201 act("local copied/moved to " + f2, "m",
202 202 f, f2, f, fmerge(f, f2, f2), False)
203 203 elif f in ma: # clean, a different, no remote
204 204 if n != ma[f]:
205 205 if repo.ui.promptchoice(
206 206 _(" local changed %s which remote deleted\n"
207 207 "use (c)hanged version or (d)elete?") % f,
208 208 (_("&Changed"), _("&Delete")), 0):
209 209 act("prompt delete", "r", f)
210 210 else:
211 211 act("prompt keep", "a", f)
212 212 elif n[20:] == "a": # added, no remote
213 213 act("remote deleted", "f", f)
214 214 elif n[20:] != "u":
215 215 act("other deleted", "r", f)
216 216
217 217 for f, n in m2.iteritems():
218 218 if partial and not partial(f):
219 219 continue
220 220 if f in m1 or f in copied: # files already visited
221 221 continue
222 222 if f in copy:
223 223 f2 = copy[f]
224 224 if f2 not in m1: # directory rename
225 225 act("local renamed directory to " + f2, "d",
226 226 None, f, f2, m2.flags(f))
227 227 elif f2 in m2: # rename case 1, A/A,B/A
228 228 act("remote copied to " + f, "m",
229 229 f2, f, f, fmerge(f2, f, f2), False)
230 230 else: # case 3,20 A/B/A
231 231 act("remote moved to " + f, "m",
232 232 f2, f, f, fmerge(f2, f, f2), True)
233 233 elif f not in ma:
234 234 act("remote created", "g", f, m2.flags(f))
235 235 elif n != ma[f]:
236 236 if repo.ui.promptchoice(
237 237 _("remote changed %s which local deleted\n"
238 238 "use (c)hanged version or leave (d)eleted?") % f,
239 239 (_("&Changed"), _("&Deleted")), 0) == 0:
240 240 act("prompt recreating", "g", f, m2.flags(f))
241 241
242 242 return action
243 243
244 244 def actionkey(a):
245 245 return a[1] == 'r' and -1 or 0, a
246 246
247 247 def applyupdates(repo, action, wctx, mctx, actx):
248 248 """apply the merge action list to the working directory
249 249
250 250 wctx is the working copy context
251 251 mctx is the context to be merged into the working copy
252 252 actx is the context of the common ancestor
253 253 """
254 254
255 255 updated, merged, removed, unresolved = 0, 0, 0, 0
256 256 ms = mergestate(repo)
257 257 ms.reset(wctx.parents()[0].node())
258 258 moves = []
259 259 action.sort(key=actionkey)
260 260 substate = wctx.substate # prime
261 261
262 262 # prescan for merges
263 263 u = repo.ui
264 264 for a in action:
265 265 f, m = a[:2]
266 266 if m == 'm': # merge
267 267 f2, fd, flags, move = a[2:]
268 268 if f == '.hgsubstate': # merged internally
269 269 continue
270 270 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
271 271 fcl = wctx[f]
272 272 fco = mctx[f2]
273 fca = fcl.ancestor(fco, actx) or repo.filectx(f, fileid=nullrev)
273 if mctx == actx: # backwards, use working dir parent as ancestor
274 fca = fcl.parents()[0]
275 else:
276 fca = fcl.ancestor(fco, actx)
277 if not fca:
278 fca = repo.filectx(f, fileid=nullrev)
274 279 ms.add(fcl, fco, fca, fd, flags)
275 280 if f != fd and move:
276 281 moves.append(f)
277 282
278 283 # remove renamed files after safely stored
279 284 for f in moves:
280 285 if util.lexists(repo.wjoin(f)):
281 286 repo.ui.debug("removing %s\n" % f)
282 287 os.unlink(repo.wjoin(f))
283 288
284 289 audit_path = util.path_auditor(repo.root)
285 290
286 291 numupdates = len(action)
287 292 for i, a in enumerate(action):
288 293 f, m = a[:2]
289 294 u.progress(_('updating'), i + 1, item=f, total=numupdates, unit='files')
290 295 if f and f[0] == "/":
291 296 continue
292 297 if m == "r": # remove
293 298 repo.ui.note(_("removing %s\n") % f)
294 299 audit_path(f)
295 300 if f == '.hgsubstate': # subrepo states need updating
296 301 subrepo.submerge(repo, wctx, mctx, wctx)
297 302 try:
298 303 util.unlink(repo.wjoin(f))
299 304 except OSError, inst:
300 305 if inst.errno != errno.ENOENT:
301 306 repo.ui.warn(_("update failed to remove %s: %s!\n") %
302 307 (f, inst.strerror))
303 308 removed += 1
304 309 elif m == "m": # merge
305 310 if f == '.hgsubstate': # subrepo states need updating
306 311 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx))
307 312 continue
308 313 f2, fd, flags, move = a[2:]
309 314 r = ms.resolve(fd, wctx, mctx)
310 315 if r is not None and r > 0:
311 316 unresolved += 1
312 317 else:
313 318 if r is None:
314 319 updated += 1
315 320 else:
316 321 merged += 1
317 322 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
318 323 if f != fd and move and util.lexists(repo.wjoin(f)):
319 324 repo.ui.debug("removing %s\n" % f)
320 325 os.unlink(repo.wjoin(f))
321 326 elif m == "g": # get
322 327 flags = a[2]
323 328 repo.ui.note(_("getting %s\n") % f)
324 329 t = mctx.filectx(f).data()
325 330 repo.wwrite(f, t, flags)
326 331 t = None
327 332 updated += 1
328 333 if f == '.hgsubstate': # subrepo states need updating
329 334 subrepo.submerge(repo, wctx, mctx, wctx)
330 335 elif m == "d": # directory rename
331 336 f2, fd, flags = a[2:]
332 337 if f:
333 338 repo.ui.note(_("moving %s to %s\n") % (f, fd))
334 339 t = wctx.filectx(f).data()
335 340 repo.wwrite(fd, t, flags)
336 341 util.unlink(repo.wjoin(f))
337 342 if f2:
338 343 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
339 344 t = mctx.filectx(f2).data()
340 345 repo.wwrite(fd, t, flags)
341 346 updated += 1
342 347 elif m == "dr": # divergent renames
343 348 fl = a[2]
344 349 repo.ui.warn(_("warning: detected divergent renames of %s to:\n") % f)
345 350 for nf in fl:
346 351 repo.ui.warn(" %s\n" % nf)
347 352 elif m == "e": # exec
348 353 flags = a[2]
349 354 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
350 355 u.progress(_('updating'), None, total=numupdates, unit='files')
351 356
352 357 return updated, merged, removed, unresolved
353 358
354 359 def recordupdates(repo, action, branchmerge):
355 360 "record merge actions to the dirstate"
356 361
357 362 for a in action:
358 363 f, m = a[:2]
359 364 if m == "r": # remove
360 365 if branchmerge:
361 366 repo.dirstate.remove(f)
362 367 else:
363 368 repo.dirstate.forget(f)
364 369 elif m == "a": # re-add
365 370 if not branchmerge:
366 371 repo.dirstate.add(f)
367 372 elif m == "f": # forget
368 373 repo.dirstate.forget(f)
369 374 elif m == "e": # exec change
370 375 repo.dirstate.normallookup(f)
371 376 elif m == "g": # get
372 377 if branchmerge:
373 378 repo.dirstate.otherparent(f)
374 379 else:
375 380 repo.dirstate.normal(f)
376 381 elif m == "m": # merge
377 382 f2, fd, flag, move = a[2:]
378 383 if branchmerge:
379 384 # We've done a branch merge, mark this file as merged
380 385 # so that we properly record the merger later
381 386 repo.dirstate.merge(fd)
382 387 if f != f2: # copy/rename
383 388 if move:
384 389 repo.dirstate.remove(f)
385 390 if f != fd:
386 391 repo.dirstate.copy(f, fd)
387 392 else:
388 393 repo.dirstate.copy(f2, fd)
389 394 else:
390 395 # We've update-merged a locally modified file, so
391 396 # we set the dirstate to emulate a normal checkout
392 397 # of that file some time in the past. Thus our
393 398 # merge will appear as a normal local file
394 399 # modification.
395 400 if f2 == fd: # file not locally copied/moved
396 401 repo.dirstate.normallookup(fd)
397 402 if move:
398 403 repo.dirstate.forget(f)
399 404 elif m == "d": # directory rename
400 405 f2, fd, flag = a[2:]
401 406 if not f2 and f not in repo.dirstate:
402 407 # untracked file moved
403 408 continue
404 409 if branchmerge:
405 410 repo.dirstate.add(fd)
406 411 if f:
407 412 repo.dirstate.remove(f)
408 413 repo.dirstate.copy(f, fd)
409 414 if f2:
410 415 repo.dirstate.copy(f2, fd)
411 416 else:
412 417 repo.dirstate.normal(fd)
413 418 if f:
414 419 repo.dirstate.forget(f)
415 420
416 421 def update(repo, node, branchmerge, force, partial):
417 422 """
418 423 Perform a merge between the working directory and the given node
419 424
420 425 node = the node to update to, or None if unspecified
421 426 branchmerge = whether to merge between branches
422 427 force = whether to force branch merging or file overwriting
423 428 partial = a function to filter file lists (dirstate not updated)
424 429
425 430 The table below shows all the behaviors of the update command
426 431 given the -c and -C or no options, whether the working directory
427 432 is dirty, whether a revision is specified, and the relationship of
428 433 the parent rev to the target rev (linear, on the same named
429 434 branch, or on another named branch).
430 435
431 436 This logic is tested by test-update-branches.
432 437
433 438 -c -C dirty rev | linear same cross
434 439 n n n n | ok (1) x
435 440 n n n y | ok ok ok
436 441 n n y * | merge (2) (2)
437 442 n y * * | --- discard ---
438 443 y n y * | --- (3) ---
439 444 y n n * | --- ok ---
440 445 y y * * | --- (4) ---
441 446
442 447 x = can't happen
443 448 * = don't-care
444 449 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
445 450 2 = abort: crosses branches (use 'hg merge' to merge or
446 451 use 'hg update -C' to discard changes)
447 452 3 = abort: uncommitted local changes
448 453 4 = incompatible options (checked in commands.py)
449 454 """
450 455
451 456 onode = node
452 457 wlock = repo.wlock()
453 458 try:
454 459 wc = repo[None]
455 460 if node is None:
456 461 # tip of current branch
457 462 try:
458 463 node = repo.branchtags()[wc.branch()]
459 464 except KeyError:
460 465 if wc.branch() == "default": # no default branch!
461 466 node = repo.lookup("tip") # update to tip
462 467 else:
463 468 raise util.Abort(_("branch %s not found") % wc.branch())
464 469 overwrite = force and not branchmerge
465 470 pl = wc.parents()
466 471 p1, p2 = pl[0], repo[node]
467 472 pa = p1.ancestor(p2)
468 473 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
469 474 fastforward = False
470 475
471 476 ### check phase
472 477 if not overwrite and len(pl) > 1:
473 478 raise util.Abort(_("outstanding uncommitted merges"))
474 479 if branchmerge:
475 480 if pa == p2:
476 481 raise util.Abort(_("merging with a working directory ancestor"
477 482 " has no effect"))
478 483 elif pa == p1:
479 484 if p1.branch() != p2.branch():
480 485 fastforward = True
481 486 else:
482 487 raise util.Abort(_("nothing to merge (use 'hg update'"
483 488 " or check 'hg heads')"))
484 489 if not force and (wc.files() or wc.deleted()):
485 490 raise util.Abort(_("outstanding uncommitted changes "
486 491 "(use 'hg status' to list changes)"))
487 492 elif not overwrite:
488 493 if pa == p1 or pa == p2: # linear
489 494 pass # all good
490 495 elif wc.files() or wc.deleted():
491 496 raise util.Abort(_("crosses branches (use 'hg merge' to merge "
492 497 "or use 'hg update -C' to discard changes)"))
493 498 elif onode is None:
494 499 raise util.Abort(_("crosses branches (use 'hg merge' or use "
495 500 "'hg update -c')"))
496 501 else:
497 502 # Allow jumping branches if clean and specific rev given
498 503 overwrite = True
499 504
500 505 ### calculate phase
501 506 action = []
502 507 wc.status(unknown=True) # prime cache
503 508 if not force:
504 509 _checkunknown(wc, p2)
505 510 if not util.checkcase(repo.path):
506 511 _checkcollision(p2)
507 512 action += _forgetremoved(wc, p2, branchmerge)
508 513 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
509 514
510 515 ### apply phase
511 516 if not branchmerge: # just jump to the new rev
512 517 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
513 518 if not partial:
514 519 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
515 520
516 521 stats = applyupdates(repo, action, wc, p2, pa)
517 522
518 523 if not partial:
519 524 repo.dirstate.setparents(fp1, fp2)
520 525 recordupdates(repo, action, branchmerge)
521 526 if not branchmerge and not fastforward:
522 527 repo.dirstate.setbranch(p2.branch())
523 528 finally:
524 529 wlock.release()
525 530
526 531 if not partial:
527 532 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
528 533 return stats
@@ -1,321 +1,321 b''
1 1 % init
2 2 % committing changes
3 3 adding a
4 4 % committed changeset 0
5 5 % committed changeset 1
6 6 % committed changeset 2
7 7 % committed changeset 3
8 8 % committed changeset 4
9 9 % committed changeset 5
10 10 % committed changeset 6
11 11 % committed changeset 7
12 12 % committed changeset 8
13 13 % committed changeset 9
14 14 % committed changeset 10
15 15 % committed changeset 11
16 16 % committed changeset 12
17 17 % committed changeset 13
18 18 % committed changeset 14
19 19 % committed changeset 15
20 20 % committed changeset 16
21 21 % committed changeset 17
22 22 % committed changeset 18
23 23 % committed changeset 19
24 24 % committed changeset 20
25 25 % committed changeset 21
26 26 % committed changeset 22
27 27 % committed changeset 23
28 28 % committed changeset 24
29 29 % committed changeset 25
30 30 % committed changeset 26
31 31 % committed changeset 27
32 32 % committed changeset 28
33 33 % committed changeset 29
34 34 % committed changeset 30
35 35 % committed changeset 31
36 36 % log
37 37 changeset: 31:58c80a7c8a40
38 38 tag: tip
39 39 user: test
40 40 date: Thu Jan 01 00:00:31 1970 +0000
41 41 summary: msg 31
42 42
43 43 changeset: 30:ed2d2f24b11c
44 44 user: test
45 45 date: Thu Jan 01 00:00:30 1970 +0000
46 46 summary: msg 30
47 47
48 48 changeset: 29:b5bd63375ab9
49 49 user: test
50 50 date: Thu Jan 01 00:00:29 1970 +0000
51 51 summary: msg 29
52 52
53 53 changeset: 28:8e0c2264c8af
54 54 user: test
55 55 date: Thu Jan 01 00:00:28 1970 +0000
56 56 summary: msg 28
57 57
58 58 changeset: 27:288867a866e9
59 59 user: test
60 60 date: Thu Jan 01 00:00:27 1970 +0000
61 61 summary: msg 27
62 62
63 63 changeset: 26:3efc6fd51aeb
64 64 user: test
65 65 date: Thu Jan 01 00:00:26 1970 +0000
66 66 summary: msg 26
67 67
68 68 changeset: 25:02a84173a97a
69 69 user: test
70 70 date: Thu Jan 01 00:00:25 1970 +0000
71 71 summary: msg 25
72 72
73 73 changeset: 24:10e0acd3809e
74 74 user: test
75 75 date: Thu Jan 01 00:00:24 1970 +0000
76 76 summary: msg 24
77 77
78 78 changeset: 23:5ec79163bff4
79 79 user: test
80 80 date: Thu Jan 01 00:00:23 1970 +0000
81 81 summary: msg 23
82 82
83 83 changeset: 22:06c7993750ce
84 84 user: test
85 85 date: Thu Jan 01 00:00:22 1970 +0000
86 86 summary: msg 22
87 87
88 88 changeset: 21:e5db6aa3fe2a
89 89 user: test
90 90 date: Thu Jan 01 00:00:21 1970 +0000
91 91 summary: msg 21
92 92
93 93 changeset: 20:7128fb4fdbc9
94 94 user: test
95 95 date: Thu Jan 01 00:00:20 1970 +0000
96 96 summary: msg 20
97 97
98 98 changeset: 19:52798545b482
99 99 user: test
100 100 date: Thu Jan 01 00:00:19 1970 +0000
101 101 summary: msg 19
102 102
103 103 changeset: 18:86977a90077e
104 104 user: test
105 105 date: Thu Jan 01 00:00:18 1970 +0000
106 106 summary: msg 18
107 107
108 108 changeset: 17:03515f4a9080
109 109 user: test
110 110 date: Thu Jan 01 00:00:17 1970 +0000
111 111 summary: msg 17
112 112
113 113 changeset: 16:a2e6ea4973e9
114 114 user: test
115 115 date: Thu Jan 01 00:00:16 1970 +0000
116 116 summary: msg 16
117 117
118 118 changeset: 15:e7fa0811edb0
119 119 user: test
120 120 date: Thu Jan 01 00:00:15 1970 +0000
121 121 summary: msg 15
122 122
123 123 changeset: 14:ce8f0998e922
124 124 user: test
125 125 date: Thu Jan 01 00:00:14 1970 +0000
126 126 summary: msg 14
127 127
128 128 changeset: 13:9d7d07bc967c
129 129 user: test
130 130 date: Thu Jan 01 00:00:13 1970 +0000
131 131 summary: msg 13
132 132
133 133 changeset: 12:1941b52820a5
134 134 user: test
135 135 date: Thu Jan 01 00:00:12 1970 +0000
136 136 summary: msg 12
137 137
138 138 changeset: 11:7b4cd9578619
139 139 user: test
140 140 date: Thu Jan 01 00:00:11 1970 +0000
141 141 summary: msg 11
142 142
143 143 changeset: 10:7c5eff49a6b6
144 144 user: test
145 145 date: Thu Jan 01 00:00:10 1970 +0000
146 146 summary: msg 10
147 147
148 148 changeset: 9:eb44510ef29a
149 149 user: test
150 150 date: Thu Jan 01 00:00:09 1970 +0000
151 151 summary: msg 9
152 152
153 153 changeset: 8:453eb4dba229
154 154 user: test
155 155 date: Thu Jan 01 00:00:08 1970 +0000
156 156 summary: msg 8
157 157
158 158 changeset: 7:03750880c6b5
159 159 user: test
160 160 date: Thu Jan 01 00:00:07 1970 +0000
161 161 summary: msg 7
162 162
163 163 changeset: 6:a3d5c6fdf0d3
164 164 user: test
165 165 date: Thu Jan 01 00:00:06 1970 +0000
166 166 summary: msg 6
167 167
168 168 changeset: 5:7874a09ea728
169 169 user: test
170 170 date: Thu Jan 01 00:00:05 1970 +0000
171 171 summary: msg 5
172 172
173 173 changeset: 4:9b2ba8336a65
174 174 user: test
175 175 date: Thu Jan 01 00:00:04 1970 +0000
176 176 summary: msg 4
177 177
178 178 changeset: 3:b53bea5e2fcb
179 179 user: test
180 180 date: Thu Jan 01 00:00:03 1970 +0000
181 181 summary: msg 3
182 182
183 183 changeset: 2:db07c04beaca
184 184 user: test
185 185 date: Thu Jan 01 00:00:02 1970 +0000
186 186 summary: msg 2
187 187
188 188 changeset: 1:5cd978ea5149
189 189 user: test
190 190 date: Thu Jan 01 00:00:01 1970 +0000
191 191 summary: msg 1
192 192
193 193 changeset: 0:b99c7b9c8e11
194 194 user: test
195 195 date: Thu Jan 01 00:00:00 1970 +0000
196 196 summary: msg 0
197 197
198 198 % hg up -C
199 199 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
200 200 % bisect test
201 201 Testing changeset 16:a2e6ea4973e9 (30 changesets remaining, ~4 tests)
202 202 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
203 203 Testing changeset 23:5ec79163bff4 (15 changesets remaining, ~3 tests)
204 204 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
205 205 skip
206 206 Testing changeset 24:10e0acd3809e (15 changesets remaining, ~3 tests)
207 207 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
208 208 Testing changeset 27:288867a866e9 (7 changesets remaining, ~2 tests)
209 209 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
210 210 Testing changeset 29:b5bd63375ab9 (4 changesets remaining, ~2 tests)
211 211 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
212 212 Testing changeset 28:8e0c2264c8af (2 changesets remaining, ~1 tests)
213 213 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
214 214 The first bad revision is:
215 215 changeset: 29:b5bd63375ab9
216 216 user: test
217 217 date: Thu Jan 01 00:00:29 1970 +0000
218 218 summary: msg 29
219 219
220 220 % bisect reverse test
221 221 Testing changeset 15:e7fa0811edb0 (32 changesets remaining, ~5 tests)
222 222 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
223 223 Testing changeset 7:03750880c6b5 (16 changesets remaining, ~4 tests)
224 224 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
225 225 skip
226 226 Testing changeset 6:a3d5c6fdf0d3 (16 changesets remaining, ~4 tests)
227 227 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
228 228 Testing changeset 2:db07c04beaca (7 changesets remaining, ~2 tests)
229 229 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
230 230 Testing changeset 0:b99c7b9c8e11 (3 changesets remaining, ~1 tests)
231 231 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
232 232 Testing changeset 1:5cd978ea5149 (2 changesets remaining, ~1 tests)
233 233 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
234 234 The first good revision is:
235 235 changeset: 1:5cd978ea5149
236 236 user: test
237 237 date: Thu Jan 01 00:00:01 1970 +0000
238 238 summary: msg 1
239 239
240 abort: Inconsistent state, 31:58c80a7c8a40 is good and bad
240 abort: starting revisions are not directly related
241 241 error
242 242 Testing changeset 15:e7fa0811edb0 (32 changesets remaining, ~5 tests)
243 243 5cd978ea5149
244 244 % reproduce AssertionError, issue1228 and issue1182
245 245 Testing changeset 2:db07c04beaca (4 changesets remaining, ~2 tests)
246 246 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
247 247 Testing changeset 1:5cd978ea5149 (4 changesets remaining, ~2 tests)
248 248 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
249 249 Testing changeset 3:b53bea5e2fcb (4 changesets remaining, ~2 tests)
250 250 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
251 251 Due to skipped revisions, the first bad revision could be any of:
252 252 changeset: 1:5cd978ea5149
253 253 user: test
254 254 date: Thu Jan 01 00:00:01 1970 +0000
255 255 summary: msg 1
256 256
257 257 changeset: 2:db07c04beaca
258 258 user: test
259 259 date: Thu Jan 01 00:00:02 1970 +0000
260 260 summary: msg 2
261 261
262 262 changeset: 3:b53bea5e2fcb
263 263 user: test
264 264 date: Thu Jan 01 00:00:03 1970 +0000
265 265 summary: msg 3
266 266
267 267 changeset: 4:9b2ba8336a65
268 268 user: test
269 269 date: Thu Jan 01 00:00:04 1970 +0000
270 270 summary: msg 4
271 271
272 272 % reproduce non converging bisect, issue1182
273 273 Testing changeset 1:5cd978ea5149 (2 changesets remaining, ~1 tests)
274 274 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
275 275 Due to skipped revisions, the first bad revision could be any of:
276 276 changeset: 1:5cd978ea5149
277 277 user: test
278 278 date: Thu Jan 01 00:00:01 1970 +0000
279 279 summary: msg 1
280 280
281 281 changeset: 2:db07c04beaca
282 282 user: test
283 283 date: Thu Jan 01 00:00:02 1970 +0000
284 284 summary: msg 2
285 285
286 286 % test no action
287 287 abort: cannot bisect (no known good revisions)
288 288 failure
289 289 % reproduce AssertionError, issue1445
290 290 Testing changeset 3:b53bea5e2fcb (6 changesets remaining, ~2 tests)
291 291 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
292 292 Testing changeset 2:db07c04beaca (6 changesets remaining, ~2 tests)
293 293 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
294 294 Testing changeset 4:9b2ba8336a65 (6 changesets remaining, ~2 tests)
295 295 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
296 296 Testing changeset 1:5cd978ea5149 (6 changesets remaining, ~2 tests)
297 297 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
298 298 Testing changeset 5:7874a09ea728 (6 changesets remaining, ~2 tests)
299 299 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
300 300 The first bad revision is:
301 301 changeset: 6:a3d5c6fdf0d3
302 302 user: test
303 303 date: Thu Jan 01 00:00:06 1970 +0000
304 304 summary: msg 6
305 305
306 306 % test invalid command
307 307 abort: failed to execute exit 127
308 308 % test bisecting command
309 309 Testing changeset 15:e7fa0811edb0 (31 changesets remaining, ~4 tests)
310 310 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
311 311 Changeset 15:e7fa0811edb0: good
312 312 Changeset 7:03750880c6b5: good
313 313 Changeset 3:b53bea5e2fcb: bad
314 314 Changeset 5:7874a09ea728: bad
315 315 Changeset 6:a3d5c6fdf0d3: good
316 316 The first good revision is:
317 317 changeset: 6:a3d5c6fdf0d3
318 318 user: test
319 319 date: Thu Jan 01 00:00:06 1970 +0000
320 320 summary: msg 6
321 321
General Comments 0
You need to be logged in to leave comments. Login now