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