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