##// END OF EJS Templates
Merge with stable
Matt Mackall -
r4404:47371e1c merge default
parent child Browse files
Show More
@@ -1,47 +1,71 b''
1 The standalone Windows installer for Mercurial is built in a somewhat
1 The standalone Windows installer for Mercurial is built in a somewhat
2 jury-rigged fashion.
2 jury-rigged fashion.
3
3
4 It has the following prerequisites, at least as I build it:
4 It has the following prerequisites, at least as I build it:
5
5
6 Python for Windows
6 Python for Windows
7 http://www.python.org/ftp/python/2.4.1/python-2.4.1.msi
7 http://www.python.org/ftp/python/2.4.3/python-2.4.3.msi
8
8
9 MinGW
9 MinGW
10 http://www.mingw.org/
10 http://www.mingw.org/
11
11
12 Python for Windows Extensions
12 Python for Windows Extensions
13 http://sourceforge.net/projects/pywin32/
13 http://sourceforge.net/projects/pywin32/
14
14
15 mfc71.dll (just download, don't install)
15 mfc71.dll (just download, don't install)
16 http://starship.python.net/crew/mhammond/win32/
16 http://starship.python.net/crew/mhammond/win32/
17
17
18 The py2exe distutils extension
18 The py2exe distutils extension
19 http://sourceforge.net/projects/py2exe/
19 http://sourceforge.net/projects/py2exe/
20
20
21 Inno Setup
21 Inno Setup
22 http://www.jrsoftware.org/isinfo.php
22 http://www.jrsoftware.org/isinfo.php
23
23
24 ISTool
24 ISTool - optional
25 http://www.istool.org/default.aspx/
25 http://www.istool.org/default.aspx/
26
26
27 add_path (you need only add_path.exe in the zip file)
27 add_path (you need only add_path.exe in the zip file)
28 http://www.barisione.org/apps.html#add_path
28 http://www.barisione.org/apps.html#add_path
29
29
30 And, of course, Mercurial itself.
30 And, of course, Mercurial itself.
31
31
32 Once you have all this installed and built, clone a copy of the
32 Once you have all this installed and built, clone a copy of the
33 Mercurial repository you want to package, and name the repo
33 Mercurial repository you want to package, and name the repo
34 C:\hg\hg-release.
34 C:\hg\hg-release.
35
35
36 In a shell, build a standalone copy of the hg.exe program:
36 In a shell, build a standalone copy of the hg.exe program:
37
37
38 python setup.py build -c mingw32 py2exe -b 1
38 python setup.py build -c mingw32
39 python setup.py py2exe -b 1
40
41 Note: the previously suggested combined command of "python setup.py build -c
42 mingw32 py2exe -b 1" doesn't work correctly anymore as it doesn't include the
43 extensions in the mercurial subdirectory.
39
44
40 Copy mfc71.dll and add_path.exe into the dist directory that just
45 If you want to create a file named setup.cfg with the contents:
41 got created.
46
47 [build]
48 compiler=mingw32
49
50 you can skip the first build step.
51
52 Copy mfc71.dll and add_path.exe into the dist directory that just got created.
42
53
43 Run ISTool, and open the C:\hg\hg-release\contrib\win32\mercurial.iss
54 If you use ISTool, you open the C:\hg\hg-release\contrib\win32\mercurial.iss
44 file.
55 file and type Ctrl-F9 to compile the installer file.
56
57 Otherwise you run the Inno Setup compiler. Assuming it's on the path you run:
58
59 iscc contrib\win32\mercurial.iss
60
61 The actual installer will be in the C:\hg\hg-release\Output directory.
45
62
46 In ISTool, type Ctrl-F9 to compile the installer file. The actual
63 To automate the steps above you may want to create a batchfile based on the
47 installer will be in the C:\hg\hg-release\Output directory.
64 following:
65
66 echo [build] > setup.cfg
67 echo compiler=mingw32 >> setup.cfg
68 python setup.py py2exe -b 1
69 iscc contrib\win32\mercurial.iss
70
71 and run it from the root of the hg repository (c:\hg\hg-release).
@@ -1,509 +1,533 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 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from node import *
8 from node import *
9 from i18n import _
9 from i18n import _
10 import errno, util, os, tempfile
10 import errno, util, os, tempfile
11
11
12 def filemerge(repo, fw, fo, wctx, mctx):
12 def filemerge(repo, fw, fo, wctx, mctx):
13 """perform a 3-way merge in the working directory
13 """perform a 3-way merge in the working directory
14
14
15 fw = filename in the working directory
15 fw = filename in the working directory
16 fo = filename in other parent
16 fo = filename in other parent
17 wctx, mctx = working and merge changecontexts
17 wctx, mctx = working and merge changecontexts
18 """
18 """
19
19
20 def temp(prefix, ctx):
20 def temp(prefix, ctx):
21 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
21 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
22 (fd, name) = tempfile.mkstemp(prefix=pre)
22 (fd, name) = tempfile.mkstemp(prefix=pre)
23 data = repo.wwritedata(ctx.path(), ctx.data())
23 data = repo.wwritedata(ctx.path(), ctx.data())
24 f = os.fdopen(fd, "wb")
24 f = os.fdopen(fd, "wb")
25 f.write(data)
25 f.write(data)
26 f.close()
26 f.close()
27 return name
27 return name
28
28
29 fcm = wctx.filectx(fw)
29 fcm = wctx.filectx(fw)
30 fco = mctx.filectx(fo)
30 fco = mctx.filectx(fo)
31
31
32 if not fco.cmp(fcm.data()): # files identical?
32 if not fco.cmp(fcm.data()): # files identical?
33 return None
33 return None
34
34
35 fca = fcm.ancestor(fco)
35 fca = fcm.ancestor(fco)
36 if not fca:
36 if not fca:
37 fca = repo.filectx(fw, fileid=nullrev)
37 fca = repo.filectx(fw, fileid=nullrev)
38 a = repo.wjoin(fw)
38 a = repo.wjoin(fw)
39 b = temp("base", fca)
39 b = temp("base", fca)
40 c = temp("other", fco)
40 c = temp("other", fco)
41
41
42 if fw != fo:
42 if fw != fo:
43 repo.ui.status(_("merging %s and %s\n") % (fw, fo))
43 repo.ui.status(_("merging %s and %s\n") % (fw, fo))
44 else:
44 else:
45 repo.ui.status(_("merging %s\n") % fw)
45 repo.ui.status(_("merging %s\n") % fw)
46
46
47 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca))
47 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca))
48
48
49 cmd = (os.environ.get("HGMERGE") or repo.ui.config("ui", "merge")
49 cmd = (os.environ.get("HGMERGE") or repo.ui.config("ui", "merge")
50 or "hgmerge")
50 or "hgmerge")
51 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=repo.root,
51 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=repo.root,
52 environ={'HG_FILE': fw,
52 environ={'HG_FILE': fw,
53 'HG_MY_NODE': str(wctx.parents()[0]),
53 'HG_MY_NODE': str(wctx.parents()[0]),
54 'HG_OTHER_NODE': str(mctx)})
54 'HG_OTHER_NODE': str(mctx)})
55 if r:
55 if r:
56 repo.ui.warn(_("merging %s failed!\n") % fw)
56 repo.ui.warn(_("merging %s failed!\n") % fw)
57
57
58 os.unlink(b)
58 os.unlink(b)
59 os.unlink(c)
59 os.unlink(c)
60 return r
60 return r
61
61
62 def checkunknown(wctx, mctx):
62 def checkunknown(wctx, mctx):
63 "check for collisions between unknown files and files in mctx"
63 "check for collisions between unknown files and files in mctx"
64 man = mctx.manifest()
64 man = mctx.manifest()
65 for f in wctx.unknown():
65 for f in wctx.unknown():
66 if f in man:
66 if f in man:
67 if mctx.filectx(f).cmp(wctx.filectx(f).data()):
67 if mctx.filectx(f).cmp(wctx.filectx(f).data()):
68 raise util.Abort(_("untracked local file '%s' differs"\
68 raise util.Abort(_("untracked local file '%s' differs"\
69 " from remote version") % f)
69 " from remote version") % f)
70
70
71 def checkcollision(mctx):
71 def checkcollision(mctx):
72 "check for case folding collisions in the destination context"
72 "check for case folding collisions in the destination context"
73 folded = {}
73 folded = {}
74 for fn in mctx.manifest():
74 for fn in mctx.manifest():
75 fold = fn.lower()
75 fold = fn.lower()
76 if fold in folded:
76 if fold in folded:
77 raise util.Abort(_("case-folding collision between %s and %s")
77 raise util.Abort(_("case-folding collision between %s and %s")
78 % (fn, folded[fold]))
78 % (fn, folded[fold]))
79 folded[fold] = fn
79 folded[fold] = fn
80
80
81 def forgetremoved(wctx, mctx):
81 def forgetremoved(wctx, mctx):
82 """
82 """
83 Forget removed files
83 Forget removed files
84
84
85 If we're jumping between revisions (as opposed to merging), and if
85 If we're jumping between revisions (as opposed to merging), and if
86 neither the working directory nor the target rev has the file,
86 neither the working directory nor the target rev has the file,
87 then we need to remove it from the dirstate, to prevent the
87 then we need to remove it from the dirstate, to prevent the
88 dirstate from listing the file when it is no longer in the
88 dirstate from listing the file when it is no longer in the
89 manifest.
89 manifest.
90 """
90 """
91
91
92 action = []
92 action = []
93 man = mctx.manifest()
93 man = mctx.manifest()
94 for f in wctx.deleted() + wctx.removed():
94 for f in wctx.deleted() + wctx.removed():
95 if f not in man:
95 if f not in man:
96 action.append((f, "f"))
96 action.append((f, "f"))
97
97
98 return action
98 return action
99
99
100 def findcopies(repo, m1, m2, ma, limit):
100 def findcopies(repo, m1, m2, ma, limit):
101 """
101 """
102 Find moves and copies between m1 and m2 back to limit linkrev
102 Find moves and copies between m1 and m2 back to limit linkrev
103 """
103 """
104
104
105 def nonoverlap(d1, d2, d3):
106 "Return list of elements in d1 not in d2 or d3"
107 l = [d for d in d1 if d not in d3 and d not in d2]
108 l.sort()
109 return l
110
111 def dirname(f):
112 s = f.rfind("/")
113 if s == -1:
114 return ""
115 return f[:s]
116
117 def dirs(files):
118 d = {}
119 for f in files:
120 f = dirname(f)
121 while f not in d:
122 d[f] = True
123 f = dirname(f)
124 return d
125
105 def findold(fctx):
126 def findold(fctx):
106 "find files that path was copied from, back to linkrev limit"
127 "find files that path was copied from, back to linkrev limit"
107 old = {}
128 old = {}
108 seen = {}
129 seen = {}
109 orig = fctx.path()
130 orig = fctx.path()
110 visit = [fctx]
131 visit = [fctx]
111 while visit:
132 while visit:
112 fc = visit.pop()
133 fc = visit.pop()
113 s = str(fc)
134 s = str(fc)
114 if s in seen:
135 if s in seen:
115 continue
136 continue
116 seen[s] = 1
137 seen[s] = 1
117 if fc.path() != orig and fc.path() not in old:
138 if fc.path() != orig and fc.path() not in old:
118 old[fc.path()] = 1
139 old[fc.path()] = 1
119 if fc.rev() < limit:
140 if fc.rev() < limit:
120 continue
141 continue
121 visit += fc.parents()
142 visit += fc.parents()
122
143
123 old = old.keys()
144 old = old.keys()
124 old.sort()
145 old.sort()
125 return old
146 return old
126
147
127 def nonoverlap(d1, d2, d3):
148 copy = {}
128 "Return list of elements in d1 not in d2 or d3"
149 fullcopy = {}
129 l = [d for d in d1 if d not in d3 and d not in d2]
130 l.sort()
131 return l
132
150
133 def checkcopies(c, man):
151 def checkcopies(c, man):
134 '''check possible copies for filectx c'''
152 '''check possible copies for filectx c'''
135 for of in findold(c):
153 for of in findold(c):
136 if of not in man:
154 if of not in man: # original file not in other manifest?
137 continue
155 continue
138 c2 = ctx(of, man[of])
156 c2 = ctx(of, man[of])
139 ca = c.ancestor(c2)
157 ca = c.ancestor(c2)
140 if not ca: # unrelated
158 if not ca: # unrelated?
141 continue
159 continue
160 # named changed on only one side?
142 if ca.path() == c.path() or ca.path() == c2.path():
161 if ca.path() == c.path() or ca.path() == c2.path():
143 fullcopy[c.path()] = of
162 fullcopy[c.path()] = of # remember for dir rename detection
144 if c == ca and c2 == ca: # no merge needed, ignore copy
163 if c == c2: # no merge needed, ignore copy
145 continue
164 continue
146 copy[c.path()] = of
165 copy[c.path()] = of
147
166
148 def dirs(files):
149 d = {}
150 for f in files:
151 d[os.path.dirname(f)] = True
152 return d
153
154 if not repo.ui.configbool("merge", "followcopies", True):
167 if not repo.ui.configbool("merge", "followcopies", True):
155 return {}
168 return {}
156
169
157 # avoid silly behavior for update from empty dir
170 # avoid silly behavior for update from empty dir
158 if not m1 or not m2 or not ma:
171 if not m1 or not m2 or not ma:
159 return {}
172 return {}
160
173
161 dcopies = repo.dirstate.copies()
174 dcopies = repo.dirstate.copies()
162 copy = {}
163 fullcopy = {}
164 u1 = nonoverlap(m1, m2, ma)
175 u1 = nonoverlap(m1, m2, ma)
165 u2 = nonoverlap(m2, m1, ma)
176 u2 = nonoverlap(m2, m1, ma)
166 ctx = util.cachefunc(lambda f, n: repo.filectx(f, fileid=n[:20]))
177 ctx = util.cachefunc(lambda f, n: repo.filectx(f, fileid=n[:20]))
167
178
168 for f in u1:
179 for f in u1:
169 checkcopies(ctx(dcopies.get(f, f), m1[f]), m2)
180 checkcopies(ctx(dcopies.get(f, f), m1[f]), m2)
170
181
171 for f in u2:
182 for f in u2:
172 checkcopies(ctx(f, m2[f]), m1)
183 checkcopies(ctx(f, m2[f]), m1)
173
184
174 if not fullcopy or not repo.ui.configbool("merge", "followdirs", True):
185 if not fullcopy or not repo.ui.configbool("merge", "followdirs", True):
175 return copy
186 return copy
176
187
177 # generate a directory move map
188 # generate a directory move map
178 d1, d2 = dirs(m1), dirs(m2)
189 d1, d2 = dirs(m1), dirs(m2)
179 invalid = {}
190 invalid = {}
180 dirmove = {}
191 dirmove = {}
181
192
193 # examine each file copy for a potential directory move, which is
194 # when all the files in a directory are moved to a new directory
182 for dst, src in fullcopy.items():
195 for dst, src in fullcopy.items():
183 dsrc, ddst = os.path.dirname(src), os.path.dirname(dst)
196 dsrc, ddst = dirname(src), dirname(dst)
184 if dsrc in invalid:
197 if dsrc in invalid:
198 # already seen to be uninteresting
185 continue
199 continue
186 elif (dsrc in d1 and ddst in d1) or (dsrc in d2 and ddst in d2):
200 elif dsrc in d1 and ddst in d1:
201 # directory wasn't entirely moved locally
202 invalid[dsrc] = True
203 elif dsrc in d2 and ddst in d2:
204 # directory wasn't entirely moved remotely
187 invalid[dsrc] = True
205 invalid[dsrc] = True
188 elif dsrc in dirmove and dirmove[dsrc] != ddst:
206 elif dsrc in dirmove and dirmove[dsrc] != ddst:
207 # files from the same directory moved to two different places
189 invalid[dsrc] = True
208 invalid[dsrc] = True
190 del dirmove[dsrc]
191 else:
209 else:
210 # looks good so far
192 dirmove[dsrc + "/"] = ddst + "/"
211 dirmove[dsrc + "/"] = ddst + "/"
193
212
213 for i in invalid:
214 if i in dirmove:
215 del dirmove[i]
216
194 del d1, d2, invalid
217 del d1, d2, invalid
195
218
196 if not dirmove:
219 if not dirmove:
197 return copy
220 return copy
198
221
199 # check unaccounted nonoverlapping files
222 # check unaccounted nonoverlapping files against directory moves
200 for f in u1 + u2:
223 for f in u1 + u2:
201 if f not in fullcopy:
224 if f not in fullcopy:
202 for d in dirmove:
225 for d in dirmove:
203 if f.startswith(d):
226 if f.startswith(d):
227 # new file added in a directory that was moved, move it
204 copy[f] = dirmove[d] + f[len(d):]
228 copy[f] = dirmove[d] + f[len(d):]
205 break
229 break
206
230
207 return copy
231 return copy
208
232
209 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
233 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
210 """
234 """
211 Merge p1 and p2 with ancestor ma and generate merge action list
235 Merge p1 and p2 with ancestor ma and generate merge action list
212
236
213 overwrite = whether we clobber working files
237 overwrite = whether we clobber working files
214 partial = function to filter file lists
238 partial = function to filter file lists
215 """
239 """
216
240
217 repo.ui.note(_("resolving manifests\n"))
241 repo.ui.note(_("resolving manifests\n"))
218 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
242 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
219 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
243 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
220
244
221 m1 = p1.manifest()
245 m1 = p1.manifest()
222 m2 = p2.manifest()
246 m2 = p2.manifest()
223 ma = pa.manifest()
247 ma = pa.manifest()
224 backwards = (pa == p2)
248 backwards = (pa == p2)
225 action = []
249 action = []
226 copy = {}
250 copy = {}
227
251
228 def fmerge(f, f2=None, fa=None):
252 def fmerge(f, f2=None, fa=None):
229 """merge flags"""
253 """merge flags"""
230 if not f2:
254 if not f2:
231 f2 = f
255 f2 = f
232 fa = f
256 fa = f
233 a, b, c = ma.execf(fa), m1.execf(f), m2.execf(f2)
257 a, b, c = ma.execf(fa), m1.execf(f), m2.execf(f2)
234 if ((a^b) | (a^c)) ^ a:
258 if ((a^b) | (a^c)) ^ a:
235 return 'x'
259 return 'x'
236 a, b, c = ma.linkf(fa), m1.linkf(f), m2.linkf(f2)
260 a, b, c = ma.linkf(fa), m1.linkf(f), m2.linkf(f2)
237 if ((a^b) | (a^c)) ^ a:
261 if ((a^b) | (a^c)) ^ a:
238 return 'l'
262 return 'l'
239 return ''
263 return ''
240
264
241 def act(msg, m, f, *args):
265 def act(msg, m, f, *args):
242 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
266 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
243 action.append((f, m) + args)
267 action.append((f, m) + args)
244
268
245 if not (backwards or overwrite):
269 if not (backwards or overwrite):
246 copy = findcopies(repo, m1, m2, ma, pa.rev())
270 copy = findcopies(repo, m1, m2, ma, pa.rev())
247 copied = dict.fromkeys(copy.values())
271 copied = dict.fromkeys(copy.values())
248
272
249 # Compare manifests
273 # Compare manifests
250 for f, n in m1.iteritems():
274 for f, n in m1.iteritems():
251 if partial and not partial(f):
275 if partial and not partial(f):
252 continue
276 continue
253 if f in m2:
277 if f in m2:
254 # are files different?
278 # are files different?
255 if n != m2[f]:
279 if n != m2[f]:
256 a = ma.get(f, nullid)
280 a = ma.get(f, nullid)
257 # are both different from the ancestor?
281 # are both different from the ancestor?
258 if not overwrite and n != a and m2[f] != a:
282 if not overwrite and n != a and m2[f] != a:
259 act("versions differ", "m", f, f, f, fmerge(f), False)
283 act("versions differ", "m", f, f, f, fmerge(f), False)
260 # are we clobbering?
284 # are we clobbering?
261 # is remote's version newer?
285 # is remote's version newer?
262 # or are we going back in time and clean?
286 # or are we going back in time and clean?
263 elif overwrite or m2[f] != a or (backwards and not n[20:]):
287 elif overwrite or m2[f] != a or (backwards and not n[20:]):
264 act("remote is newer", "g", f, m2.flags(f))
288 act("remote is newer", "g", f, m2.flags(f))
265 # local is newer, not overwrite, check mode bits
289 # local is newer, not overwrite, check mode bits
266 elif fmerge(f) != m1.flags(f):
290 elif fmerge(f) != m1.flags(f):
267 act("update permissions", "e", f, m2.flags(f))
291 act("update permissions", "e", f, m2.flags(f))
268 # contents same, check mode bits
292 # contents same, check mode bits
269 elif m1.flags(f) != m2.flags(f):
293 elif m1.flags(f) != m2.flags(f):
270 if overwrite or fmerge(f) != m1.flags(f):
294 if overwrite or fmerge(f) != m1.flags(f):
271 act("update permissions", "e", f, m2.flags(f))
295 act("update permissions", "e", f, m2.flags(f))
272 elif f in copied:
296 elif f in copied:
273 continue
297 continue
274 elif f in copy:
298 elif f in copy:
275 f2 = copy[f]
299 f2 = copy[f]
276 if f2 not in m2: # directory rename
300 if f2 not in m2: # directory rename
277 act("remote renamed directory to " + f2, "d",
301 act("remote renamed directory to " + f2, "d",
278 f, None, f2, m1.flags(f))
302 f, None, f2, m1.flags(f))
279 elif f2 in m1: # case 2 A,B/B/B
303 elif f2 in m1: # case 2 A,B/B/B
280 act("local copied to " + f2, "m",
304 act("local copied to " + f2, "m",
281 f, f2, f, fmerge(f, f2, f2), False)
305 f, f2, f, fmerge(f, f2, f2), False)
282 else: # case 4,21 A/B/B
306 else: # case 4,21 A/B/B
283 act("local moved to " + f2, "m",
307 act("local moved to " + f2, "m",
284 f, f2, f, fmerge(f, f2, f2), False)
308 f, f2, f, fmerge(f, f2, f2), False)
285 elif f in ma:
309 elif f in ma:
286 if n != ma[f] and not overwrite:
310 if n != ma[f] and not overwrite:
287 if repo.ui.prompt(
311 if repo.ui.prompt(
288 (_(" local changed %s which remote deleted\n") % f) +
312 (_(" local changed %s which remote deleted\n") % f) +
289 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
313 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
290 act("prompt delete", "r", f)
314 act("prompt delete", "r", f)
291 else:
315 else:
292 act("other deleted", "r", f)
316 act("other deleted", "r", f)
293 else:
317 else:
294 # file is created on branch or in working directory
318 # file is created on branch or in working directory
295 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
319 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
296 act("remote deleted", "r", f)
320 act("remote deleted", "r", f)
297
321
298 for f, n in m2.iteritems():
322 for f, n in m2.iteritems():
299 if partial and not partial(f):
323 if partial and not partial(f):
300 continue
324 continue
301 if f in m1:
325 if f in m1:
302 continue
326 continue
303 if f in copied:
327 if f in copied:
304 continue
328 continue
305 if f in copy:
329 if f in copy:
306 f2 = copy[f]
330 f2 = copy[f]
307 if f2 not in m1: # directory rename
331 if f2 not in m1: # directory rename
308 act("local renamed directory to " + f2, "d",
332 act("local renamed directory to " + f2, "d",
309 None, f, f2, m2.flags(f))
333 None, f, f2, m2.flags(f))
310 elif f2 in m2: # rename case 1, A/A,B/A
334 elif f2 in m2: # rename case 1, A/A,B/A
311 act("remote copied to " + f, "m",
335 act("remote copied to " + f, "m",
312 f2, f, f, fmerge(f2, f, f2), False)
336 f2, f, f, fmerge(f2, f, f2), False)
313 else: # case 3,20 A/B/A
337 else: # case 3,20 A/B/A
314 act("remote moved to " + f, "m",
338 act("remote moved to " + f, "m",
315 f2, f, f, fmerge(f2, f, f2), True)
339 f2, f, f, fmerge(f2, f, f2), True)
316 elif f in ma:
340 elif f in ma:
317 if overwrite or backwards:
341 if overwrite or backwards:
318 act("recreating", "g", f, m2.flags(f))
342 act("recreating", "g", f, m2.flags(f))
319 elif n != ma[f]:
343 elif n != ma[f]:
320 if repo.ui.prompt(
344 if repo.ui.prompt(
321 (_("remote changed %s which local deleted\n") % f) +
345 (_("remote changed %s which local deleted\n") % f) +
322 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"):
346 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"):
323 act("prompt recreating", "g", f, m2.flags(f))
347 act("prompt recreating", "g", f, m2.flags(f))
324 else:
348 else:
325 act("remote created", "g", f, m2.flags(f))
349 act("remote created", "g", f, m2.flags(f))
326
350
327 return action
351 return action
328
352
329 def applyupdates(repo, action, wctx, mctx):
353 def applyupdates(repo, action, wctx, mctx):
330 "apply the merge action list to the working directory"
354 "apply the merge action list to the working directory"
331
355
332 updated, merged, removed, unresolved = 0, 0, 0, 0
356 updated, merged, removed, unresolved = 0, 0, 0, 0
333 action.sort()
357 action.sort()
334 for a in action:
358 for a in action:
335 f, m = a[:2]
359 f, m = a[:2]
336 if f and f[0] == "/":
360 if f and f[0] == "/":
337 continue
361 continue
338 if m == "r": # remove
362 if m == "r": # remove
339 repo.ui.note(_("removing %s\n") % f)
363 repo.ui.note(_("removing %s\n") % f)
340 util.audit_path(f)
364 util.audit_path(f)
341 try:
365 try:
342 util.unlink(repo.wjoin(f))
366 util.unlink(repo.wjoin(f))
343 except OSError, inst:
367 except OSError, inst:
344 if inst.errno != errno.ENOENT:
368 if inst.errno != errno.ENOENT:
345 repo.ui.warn(_("update failed to remove %s: %s!\n") %
369 repo.ui.warn(_("update failed to remove %s: %s!\n") %
346 (f, inst.strerror))
370 (f, inst.strerror))
347 removed += 1
371 removed += 1
348 elif m == "m": # merge
372 elif m == "m": # merge
349 f2, fd, flags, move = a[2:]
373 f2, fd, flags, move = a[2:]
350 r = filemerge(repo, f, f2, wctx, mctx)
374 r = filemerge(repo, f, f2, wctx, mctx)
351 if r > 0:
375 if r > 0:
352 unresolved += 1
376 unresolved += 1
353 else:
377 else:
354 if r is None:
378 if r is None:
355 updated += 1
379 updated += 1
356 else:
380 else:
357 merged += 1
381 merged += 1
358 if f != fd:
382 if f != fd:
359 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
383 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
360 repo.wwrite(fd, repo.wread(f), flags)
384 repo.wwrite(fd, repo.wread(f), flags)
361 if move:
385 if move:
362 repo.ui.debug(_("removing %s\n") % f)
386 repo.ui.debug(_("removing %s\n") % f)
363 os.unlink(repo.wjoin(f))
387 os.unlink(repo.wjoin(f))
364 util.set_exec(repo.wjoin(fd), "x" in flags)
388 util.set_exec(repo.wjoin(fd), "x" in flags)
365 elif m == "g": # get
389 elif m == "g": # get
366 flags = a[2]
390 flags = a[2]
367 repo.ui.note(_("getting %s\n") % f)
391 repo.ui.note(_("getting %s\n") % f)
368 t = mctx.filectx(f).data()
392 t = mctx.filectx(f).data()
369 repo.wwrite(f, t, flags)
393 repo.wwrite(f, t, flags)
370 updated += 1
394 updated += 1
371 elif m == "d": # directory rename
395 elif m == "d": # directory rename
372 f2, fd, flags = a[2:]
396 f2, fd, flags = a[2:]
373 if f:
397 if f:
374 repo.ui.note(_("moving %s to %s\n") % (f, fd))
398 repo.ui.note(_("moving %s to %s\n") % (f, fd))
375 t = wctx.filectx(f).data()
399 t = wctx.filectx(f).data()
376 repo.wwrite(fd, t, flags)
400 repo.wwrite(fd, t, flags)
377 util.unlink(repo.wjoin(f))
401 util.unlink(repo.wjoin(f))
378 if f2:
402 if f2:
379 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
403 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
380 t = mctx.filectx(f2).data()
404 t = mctx.filectx(f2).data()
381 repo.wwrite(fd, t, flags)
405 repo.wwrite(fd, t, flags)
382 updated += 1
406 updated += 1
383 elif m == "e": # exec
407 elif m == "e": # exec
384 flags = a[2]
408 flags = a[2]
385 util.set_exec(repo.wjoin(f), flags)
409 util.set_exec(repo.wjoin(f), flags)
386
410
387 return updated, merged, removed, unresolved
411 return updated, merged, removed, unresolved
388
412
389 def recordupdates(repo, action, branchmerge):
413 def recordupdates(repo, action, branchmerge):
390 "record merge actions to the dirstate"
414 "record merge actions to the dirstate"
391
415
392 for a in action:
416 for a in action:
393 f, m = a[:2]
417 f, m = a[:2]
394 if m == "r": # remove
418 if m == "r": # remove
395 if branchmerge:
419 if branchmerge:
396 repo.dirstate.update([f], 'r')
420 repo.dirstate.update([f], 'r')
397 else:
421 else:
398 repo.dirstate.forget([f])
422 repo.dirstate.forget([f])
399 elif m == "f": # forget
423 elif m == "f": # forget
400 repo.dirstate.forget([f])
424 repo.dirstate.forget([f])
401 elif m == "g": # get
425 elif m == "g": # get
402 if branchmerge:
426 if branchmerge:
403 repo.dirstate.update([f], 'n', st_mtime=-1)
427 repo.dirstate.update([f], 'n', st_mtime=-1)
404 else:
428 else:
405 repo.dirstate.update([f], 'n')
429 repo.dirstate.update([f], 'n')
406 elif m == "m": # merge
430 elif m == "m": # merge
407 f2, fd, flag, move = a[2:]
431 f2, fd, flag, move = a[2:]
408 if branchmerge:
432 if branchmerge:
409 # We've done a branch merge, mark this file as merged
433 # We've done a branch merge, mark this file as merged
410 # so that we properly record the merger later
434 # so that we properly record the merger later
411 repo.dirstate.update([fd], 'm')
435 repo.dirstate.update([fd], 'm')
412 if f != f2: # copy/rename
436 if f != f2: # copy/rename
413 if move:
437 if move:
414 repo.dirstate.update([f], 'r')
438 repo.dirstate.update([f], 'r')
415 if f != fd:
439 if f != fd:
416 repo.dirstate.copy(f, fd)
440 repo.dirstate.copy(f, fd)
417 else:
441 else:
418 repo.dirstate.copy(f2, fd)
442 repo.dirstate.copy(f2, fd)
419 else:
443 else:
420 # We've update-merged a locally modified file, so
444 # We've update-merged a locally modified file, so
421 # we set the dirstate to emulate a normal checkout
445 # we set the dirstate to emulate a normal checkout
422 # of that file some time in the past. Thus our
446 # of that file some time in the past. Thus our
423 # merge will appear as a normal local file
447 # merge will appear as a normal local file
424 # modification.
448 # modification.
425 repo.dirstate.update([fd], 'n', st_size=-1, st_mtime=-1)
449 repo.dirstate.update([fd], 'n', st_size=-1, st_mtime=-1)
426 if move:
450 if move:
427 repo.dirstate.forget([f])
451 repo.dirstate.forget([f])
428 elif m == "d": # directory rename
452 elif m == "d": # directory rename
429 f2, fd, flag = a[2:]
453 f2, fd, flag = a[2:]
430 if branchmerge:
454 if branchmerge:
431 repo.dirstate.update([fd], 'a')
455 repo.dirstate.update([fd], 'a')
432 if f:
456 if f:
433 repo.dirstate.update([f], 'r')
457 repo.dirstate.update([f], 'r')
434 repo.dirstate.copy(f, fd)
458 repo.dirstate.copy(f, fd)
435 if f2:
459 if f2:
436 repo.dirstate.copy(f2, fd)
460 repo.dirstate.copy(f2, fd)
437 else:
461 else:
438 repo.dirstate.update([fd], 'n')
462 repo.dirstate.update([fd], 'n')
439 if f:
463 if f:
440 repo.dirstate.forget([f])
464 repo.dirstate.forget([f])
441
465
442 def update(repo, node, branchmerge, force, partial, wlock):
466 def update(repo, node, branchmerge, force, partial, wlock):
443 """
467 """
444 Perform a merge between the working directory and the given node
468 Perform a merge between the working directory and the given node
445
469
446 branchmerge = whether to merge between branches
470 branchmerge = whether to merge between branches
447 force = whether to force branch merging or file overwriting
471 force = whether to force branch merging or file overwriting
448 partial = a function to filter file lists (dirstate not updated)
472 partial = a function to filter file lists (dirstate not updated)
449 wlock = working dir lock, if already held
473 wlock = working dir lock, if already held
450 """
474 """
451
475
452 if not wlock:
476 if not wlock:
453 wlock = repo.wlock()
477 wlock = repo.wlock()
454
478
455 wc = repo.workingctx()
479 wc = repo.workingctx()
456 if node is None:
480 if node is None:
457 # tip of current branch
481 # tip of current branch
458 try:
482 try:
459 node = repo.branchtags()[wc.branch()]
483 node = repo.branchtags()[wc.branch()]
460 except KeyError:
484 except KeyError:
461 raise util.Abort(_("branch %s not found") % wc.branch())
485 raise util.Abort(_("branch %s not found") % wc.branch())
462 overwrite = force and not branchmerge
486 overwrite = force and not branchmerge
463 forcemerge = force and branchmerge
487 forcemerge = force and branchmerge
464 pl = wc.parents()
488 pl = wc.parents()
465 p1, p2 = pl[0], repo.changectx(node)
489 p1, p2 = pl[0], repo.changectx(node)
466 pa = p1.ancestor(p2)
490 pa = p1.ancestor(p2)
467 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
491 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
468
492
469 ### check phase
493 ### check phase
470 if not overwrite and len(pl) > 1:
494 if not overwrite and len(pl) > 1:
471 raise util.Abort(_("outstanding uncommitted merges"))
495 raise util.Abort(_("outstanding uncommitted merges"))
472 if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
496 if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
473 if branchmerge:
497 if branchmerge:
474 raise util.Abort(_("there is nothing to merge, just use "
498 raise util.Abort(_("there is nothing to merge, just use "
475 "'hg update' or look at 'hg heads'"))
499 "'hg update' or look at 'hg heads'"))
476 elif not (overwrite or branchmerge):
500 elif not (overwrite or branchmerge):
477 raise util.Abort(_("update spans branches, use 'hg merge' "
501 raise util.Abort(_("update spans branches, use 'hg merge' "
478 "or 'hg update -C' to lose changes"))
502 "or 'hg update -C' to lose changes"))
479 if branchmerge and not forcemerge:
503 if branchmerge and not forcemerge:
480 if wc.files():
504 if wc.files():
481 raise util.Abort(_("outstanding uncommitted changes"))
505 raise util.Abort(_("outstanding uncommitted changes"))
482
506
483 ### calculate phase
507 ### calculate phase
484 action = []
508 action = []
485 if not force:
509 if not force:
486 checkunknown(wc, p2)
510 checkunknown(wc, p2)
487 if not util.checkfolding(repo.path):
511 if not util.checkfolding(repo.path):
488 checkcollision(p2)
512 checkcollision(p2)
489 if not branchmerge:
513 if not branchmerge:
490 action += forgetremoved(wc, p2)
514 action += forgetremoved(wc, p2)
491 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
515 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
492
516
493 ### apply phase
517 ### apply phase
494 if not branchmerge: # just jump to the new rev
518 if not branchmerge: # just jump to the new rev
495 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
519 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
496 if not partial:
520 if not partial:
497 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
521 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
498
522
499 stats = applyupdates(repo, action, wc, p2)
523 stats = applyupdates(repo, action, wc, p2)
500
524
501 if not partial:
525 if not partial:
502 recordupdates(repo, action, branchmerge)
526 recordupdates(repo, action, branchmerge)
503 repo.dirstate.setparents(fp1, fp2)
527 repo.dirstate.setparents(fp1, fp2)
504 if not branchmerge:
528 if not branchmerge:
505 repo.dirstate.setbranch(p2.branch())
529 repo.dirstate.setbranch(p2.branch())
506 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
530 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
507
531
508 return stats
532 return stats
509
533
General Comments 0
You need to be logged in to leave comments. Login now