##// END OF EJS Templates
flags: actually merge flags in simplemerge...
marmoute -
r45398:84614212 stable
parent child Browse files
Show More
@@ -1,510 +1,535
1 1 # Copyright (C) 2004, 2005 Canonical Ltd
2 2 #
3 3 # This program is free software; you can redistribute it and/or modify
4 4 # it under the terms of the GNU General Public License as published by
5 5 # the Free Software Foundation; either version 2 of the License, or
6 6 # (at your option) any later version.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU General Public License
14 14 # along with this program; if not, see <http://www.gnu.org/licenses/>.
15 15
16 16 # mbp: "you know that thing where cvs gives you conflict markers?"
17 17 # s: "i hate that."
18 18
19 19 from __future__ import absolute_import
20 20
21 21 from .i18n import _
22 22 from . import (
23 23 error,
24 24 mdiff,
25 node as nodemod,
25 26 pycompat,
27 util,
26 28 )
27 29 from .utils import stringutil
28 30
29 31
30 32 class CantReprocessAndShowBase(Exception):
31 33 pass
32 34
33 35
34 36 def intersect(ra, rb):
35 37 """Given two ranges return the range where they intersect or None.
36 38
37 39 >>> intersect((0, 10), (0, 6))
38 40 (0, 6)
39 41 >>> intersect((0, 10), (5, 15))
40 42 (5, 10)
41 43 >>> intersect((0, 10), (10, 15))
42 44 >>> intersect((0, 9), (10, 15))
43 45 >>> intersect((0, 9), (7, 15))
44 46 (7, 9)
45 47 """
46 48 assert ra[0] <= ra[1]
47 49 assert rb[0] <= rb[1]
48 50
49 51 sa = max(ra[0], rb[0])
50 52 sb = min(ra[1], rb[1])
51 53 if sa < sb:
52 54 return sa, sb
53 55 else:
54 56 return None
55 57
56 58
57 59 def compare_range(a, astart, aend, b, bstart, bend):
58 60 """Compare a[astart:aend] == b[bstart:bend], without slicing.
59 61 """
60 62 if (aend - astart) != (bend - bstart):
61 63 return False
62 64 for ia, ib in zip(
63 65 pycompat.xrange(astart, aend), pycompat.xrange(bstart, bend)
64 66 ):
65 67 if a[ia] != b[ib]:
66 68 return False
67 69 else:
68 70 return True
69 71
70 72
71 73 class Merge3Text(object):
72 74 """3-way merge of texts.
73 75
74 76 Given strings BASE, OTHER, THIS, tries to produce a combined text
75 77 incorporating the changes from both BASE->OTHER and BASE->THIS."""
76 78
77 79 def __init__(self, basetext, atext, btext, base=None, a=None, b=None):
78 80 self.basetext = basetext
79 81 self.atext = atext
80 82 self.btext = btext
81 83 if base is None:
82 84 base = mdiff.splitnewlines(basetext)
83 85 if a is None:
84 86 a = mdiff.splitnewlines(atext)
85 87 if b is None:
86 88 b = mdiff.splitnewlines(btext)
87 89 self.base = base
88 90 self.a = a
89 91 self.b = b
90 92
91 93 def merge_lines(
92 94 self,
93 95 name_a=None,
94 96 name_b=None,
95 97 name_base=None,
96 98 start_marker=b'<<<<<<<',
97 99 mid_marker=b'=======',
98 100 end_marker=b'>>>>>>>',
99 101 base_marker=None,
100 102 localorother=None,
101 103 minimize=False,
102 104 ):
103 105 """Return merge in cvs-like form.
104 106 """
105 107 self.conflicts = False
106 108 newline = b'\n'
107 109 if len(self.a) > 0:
108 110 if self.a[0].endswith(b'\r\n'):
109 111 newline = b'\r\n'
110 112 elif self.a[0].endswith(b'\r'):
111 113 newline = b'\r'
112 114 if name_a and start_marker:
113 115 start_marker = start_marker + b' ' + name_a
114 116 if name_b and end_marker:
115 117 end_marker = end_marker + b' ' + name_b
116 118 if name_base and base_marker:
117 119 base_marker = base_marker + b' ' + name_base
118 120 merge_regions = self.merge_regions()
119 121 if minimize:
120 122 merge_regions = self.minimize(merge_regions)
121 123 for t in merge_regions:
122 124 what = t[0]
123 125 if what == b'unchanged':
124 126 for i in range(t[1], t[2]):
125 127 yield self.base[i]
126 128 elif what == b'a' or what == b'same':
127 129 for i in range(t[1], t[2]):
128 130 yield self.a[i]
129 131 elif what == b'b':
130 132 for i in range(t[1], t[2]):
131 133 yield self.b[i]
132 134 elif what == b'conflict':
133 135 if localorother == b'local':
134 136 for i in range(t[3], t[4]):
135 137 yield self.a[i]
136 138 elif localorother == b'other':
137 139 for i in range(t[5], t[6]):
138 140 yield self.b[i]
139 141 else:
140 142 self.conflicts = True
141 143 if start_marker is not None:
142 144 yield start_marker + newline
143 145 for i in range(t[3], t[4]):
144 146 yield self.a[i]
145 147 if base_marker is not None:
146 148 yield base_marker + newline
147 149 for i in range(t[1], t[2]):
148 150 yield self.base[i]
149 151 if mid_marker is not None:
150 152 yield mid_marker + newline
151 153 for i in range(t[5], t[6]):
152 154 yield self.b[i]
153 155 if end_marker is not None:
154 156 yield end_marker + newline
155 157 else:
156 158 raise ValueError(what)
157 159
158 160 def merge_groups(self):
159 161 """Yield sequence of line groups. Each one is a tuple:
160 162
161 163 'unchanged', lines
162 164 Lines unchanged from base
163 165
164 166 'a', lines
165 167 Lines taken from a
166 168
167 169 'same', lines
168 170 Lines taken from a (and equal to b)
169 171
170 172 'b', lines
171 173 Lines taken from b
172 174
173 175 'conflict', base_lines, a_lines, b_lines
174 176 Lines from base were changed to either a or b and conflict.
175 177 """
176 178 for t in self.merge_regions():
177 179 what = t[0]
178 180 if what == b'unchanged':
179 181 yield what, self.base[t[1] : t[2]]
180 182 elif what == b'a' or what == b'same':
181 183 yield what, self.a[t[1] : t[2]]
182 184 elif what == b'b':
183 185 yield what, self.b[t[1] : t[2]]
184 186 elif what == b'conflict':
185 187 yield (
186 188 what,
187 189 self.base[t[1] : t[2]],
188 190 self.a[t[3] : t[4]],
189 191 self.b[t[5] : t[6]],
190 192 )
191 193 else:
192 194 raise ValueError(what)
193 195
194 196 def merge_regions(self):
195 197 """Return sequences of matching and conflicting regions.
196 198
197 199 This returns tuples, where the first value says what kind we
198 200 have:
199 201
200 202 'unchanged', start, end
201 203 Take a region of base[start:end]
202 204
203 205 'same', astart, aend
204 206 b and a are different from base but give the same result
205 207
206 208 'a', start, end
207 209 Non-clashing insertion from a[start:end]
208 210
209 211 'conflict', zstart, zend, astart, aend, bstart, bend
210 212 Conflict between a and b, with z as common ancestor
211 213
212 214 Method is as follows:
213 215
214 216 The two sequences align only on regions which match the base
215 217 and both descendants. These are found by doing a two-way diff
216 218 of each one against the base, and then finding the
217 219 intersections between those regions. These "sync regions"
218 220 are by definition unchanged in both and easily dealt with.
219 221
220 222 The regions in between can be in any of three cases:
221 223 conflicted, or changed on only one side.
222 224 """
223 225
224 226 # section a[0:ia] has been disposed of, etc
225 227 iz = ia = ib = 0
226 228
227 229 for region in self.find_sync_regions():
228 230 zmatch, zend, amatch, aend, bmatch, bend = region
229 231 # print 'match base [%d:%d]' % (zmatch, zend)
230 232
231 233 matchlen = zend - zmatch
232 234 assert matchlen >= 0
233 235 assert matchlen == (aend - amatch)
234 236 assert matchlen == (bend - bmatch)
235 237
236 238 len_a = amatch - ia
237 239 len_b = bmatch - ib
238 240 len_base = zmatch - iz
239 241 assert len_a >= 0
240 242 assert len_b >= 0
241 243 assert len_base >= 0
242 244
243 245 # print 'unmatched a=%d, b=%d' % (len_a, len_b)
244 246
245 247 if len_a or len_b:
246 248 # try to avoid actually slicing the lists
247 249 equal_a = compare_range(
248 250 self.a, ia, amatch, self.base, iz, zmatch
249 251 )
250 252 equal_b = compare_range(
251 253 self.b, ib, bmatch, self.base, iz, zmatch
252 254 )
253 255 same = compare_range(self.a, ia, amatch, self.b, ib, bmatch)
254 256
255 257 if same:
256 258 yield b'same', ia, amatch
257 259 elif equal_a and not equal_b:
258 260 yield b'b', ib, bmatch
259 261 elif equal_b and not equal_a:
260 262 yield b'a', ia, amatch
261 263 elif not equal_a and not equal_b:
262 264 yield b'conflict', iz, zmatch, ia, amatch, ib, bmatch
263 265 else:
264 266 raise AssertionError(b"can't handle a=b=base but unmatched")
265 267
266 268 ia = amatch
267 269 ib = bmatch
268 270 iz = zmatch
269 271
270 272 # if the same part of the base was deleted on both sides
271 273 # that's OK, we can just skip it.
272 274
273 275 if matchlen > 0:
274 276 assert ia == amatch
275 277 assert ib == bmatch
276 278 assert iz == zmatch
277 279
278 280 yield b'unchanged', zmatch, zend
279 281 iz = zend
280 282 ia = aend
281 283 ib = bend
282 284
283 285 def minimize(self, merge_regions):
284 286 """Trim conflict regions of lines where A and B sides match.
285 287
286 288 Lines where both A and B have made the same changes at the beginning
287 289 or the end of each merge region are eliminated from the conflict
288 290 region and are instead considered the same.
289 291 """
290 292 for region in merge_regions:
291 293 if region[0] != b"conflict":
292 294 yield region
293 295 continue
294 296 # pytype thinks this tuple contains only 3 things, but
295 297 # that's clearly not true because this code successfully
296 298 # executes. It might be wise to rework merge_regions to be
297 299 # some kind of attrs type.
298 300 (
299 301 issue,
300 302 z1,
301 303 z2,
302 304 a1,
303 305 a2,
304 306 b1,
305 307 b2,
306 308 ) = region # pytype: disable=bad-unpacking
307 309 alen = a2 - a1
308 310 blen = b2 - b1
309 311
310 312 # find matches at the front
311 313 ii = 0
312 314 while (
313 315 ii < alen and ii < blen and self.a[a1 + ii] == self.b[b1 + ii]
314 316 ):
315 317 ii += 1
316 318 startmatches = ii
317 319
318 320 # find matches at the end
319 321 ii = 0
320 322 while (
321 323 ii < alen
322 324 and ii < blen
323 325 and self.a[a2 - ii - 1] == self.b[b2 - ii - 1]
324 326 ):
325 327 ii += 1
326 328 endmatches = ii
327 329
328 330 if startmatches > 0:
329 331 yield b'same', a1, a1 + startmatches
330 332
331 333 yield (
332 334 b'conflict',
333 335 z1,
334 336 z2,
335 337 a1 + startmatches,
336 338 a2 - endmatches,
337 339 b1 + startmatches,
338 340 b2 - endmatches,
339 341 )
340 342
341 343 if endmatches > 0:
342 344 yield b'same', a2 - endmatches, a2
343 345
344 346 def find_sync_regions(self):
345 347 """Return a list of sync regions, where both descendants match the base.
346 348
347 349 Generates a list of (base1, base2, a1, a2, b1, b2). There is
348 350 always a zero-length sync region at the end of all the files.
349 351 """
350 352
351 353 ia = ib = 0
352 354 amatches = mdiff.get_matching_blocks(self.basetext, self.atext)
353 355 bmatches = mdiff.get_matching_blocks(self.basetext, self.btext)
354 356 len_a = len(amatches)
355 357 len_b = len(bmatches)
356 358
357 359 sl = []
358 360
359 361 while ia < len_a and ib < len_b:
360 362 abase, amatch, alen = amatches[ia]
361 363 bbase, bmatch, blen = bmatches[ib]
362 364
363 365 # there is an unconflicted block at i; how long does it
364 366 # extend? until whichever one ends earlier.
365 367 i = intersect((abase, abase + alen), (bbase, bbase + blen))
366 368 if i:
367 369 intbase = i[0]
368 370 intend = i[1]
369 371 intlen = intend - intbase
370 372
371 373 # found a match of base[i[0], i[1]]; this may be less than
372 374 # the region that matches in either one
373 375 assert intlen <= alen
374 376 assert intlen <= blen
375 377 assert abase <= intbase
376 378 assert bbase <= intbase
377 379
378 380 asub = amatch + (intbase - abase)
379 381 bsub = bmatch + (intbase - bbase)
380 382 aend = asub + intlen
381 383 bend = bsub + intlen
382 384
383 385 assert self.base[intbase:intend] == self.a[asub:aend], (
384 386 self.base[intbase:intend],
385 387 self.a[asub:aend],
386 388 )
387 389
388 390 assert self.base[intbase:intend] == self.b[bsub:bend]
389 391
390 392 sl.append((intbase, intend, asub, aend, bsub, bend))
391 393
392 394 # advance whichever one ends first in the base text
393 395 if (abase + alen) < (bbase + blen):
394 396 ia += 1
395 397 else:
396 398 ib += 1
397 399
398 400 intbase = len(self.base)
399 401 abase = len(self.a)
400 402 bbase = len(self.b)
401 403 sl.append((intbase, intbase, abase, abase, bbase, bbase))
402 404
403 405 return sl
404 406
405 407 def find_unconflicted(self):
406 408 """Return a list of ranges in base that are not conflicted."""
407 409 am = mdiff.get_matching_blocks(self.basetext, self.atext)
408 410 bm = mdiff.get_matching_blocks(self.basetext, self.btext)
409 411
410 412 unc = []
411 413
412 414 while am and bm:
413 415 # there is an unconflicted block at i; how long does it
414 416 # extend? until whichever one ends earlier.
415 417 a1 = am[0][0]
416 418 a2 = a1 + am[0][2]
417 419 b1 = bm[0][0]
418 420 b2 = b1 + bm[0][2]
419 421 i = intersect((a1, a2), (b1, b2))
420 422 if i:
421 423 unc.append(i)
422 424
423 425 if a2 < b2:
424 426 del am[0]
425 427 else:
426 428 del bm[0]
427 429
428 430 return unc
429 431
430 432
431 433 def _verifytext(text, path, ui, opts):
432 434 """verifies that text is non-binary (unless opts[text] is passed,
433 435 then we just warn)"""
434 436 if stringutil.binary(text):
435 437 msg = _(b"%s looks like a binary file.") % path
436 438 if not opts.get(b'quiet'):
437 439 ui.warn(_(b'warning: %s\n') % msg)
438 440 if not opts.get(b'text'):
439 441 raise error.Abort(msg)
440 442 return text
441 443
442 444
443 445 def _picklabels(defaults, overrides):
444 446 if len(overrides) > 3:
445 447 raise error.Abort(_(b"can only specify three labels."))
446 448 result = defaults[:]
447 449 for i, override in enumerate(overrides):
448 450 result[i] = override
449 451 return result
450 452
451 453
454 def _bytes_to_set(b):
455 """turns a multiple bytes (usually flags) into a set of individual byte"""
456 return set(b[x : x + 1] for x in range(len(b)))
457
458
459 def is_null(ctx):
460 if not util.safehasattr(ctx, "node"):
461 return False
462 return ctx.node() != nodemod.nullid
463
464
452 465 def simplemerge(ui, localctx, basectx, otherctx, **opts):
453 466 """Performs the simplemerge algorithm.
454 467
455 468 The merged result is written into `localctx`.
456 469 """
457 470 opts = pycompat.byteskwargs(opts)
458 471
459 472 def readctx(ctx):
460 473 # Merges were always run in the working copy before, which means
461 474 # they used decoded data, if the user defined any repository
462 475 # filters.
463 476 #
464 477 # Maintain that behavior today for BC, though perhaps in the future
465 478 # it'd be worth considering whether merging encoded data (what the
466 479 # repository usually sees) might be more useful.
467 480 return _verifytext(ctx.decodeddata(), ctx.path(), ui, opts)
468 481
469 482 mode = opts.get(b'mode', b'merge')
470 483 name_a, name_b, name_base = None, None, None
471 484 if mode != b'union':
472 485 name_a, name_b, name_base = _picklabels(
473 486 [localctx.path(), otherctx.path(), None], opts.get(b'label', [])
474 487 )
475 488
476 489 try:
477 490 localtext = readctx(localctx)
478 491 basetext = readctx(basectx)
479 492 othertext = readctx(otherctx)
480 493 except error.Abort:
481 494 return 1
482 495
483 496 m3 = Merge3Text(basetext, localtext, othertext)
484 497 extrakwargs = {
485 498 b"localorother": opts.get(b"localorother", None),
486 499 b'minimize': True,
487 500 }
488 501 if mode == b'union':
489 502 extrakwargs[b'start_marker'] = None
490 503 extrakwargs[b'mid_marker'] = None
491 504 extrakwargs[b'end_marker'] = None
492 505 elif name_base is not None:
493 506 extrakwargs[b'base_marker'] = b'|||||||'
494 507 extrakwargs[b'name_base'] = name_base
495 508 extrakwargs[b'minimize'] = False
496 509
497 510 mergedtext = b""
498 511 for line in m3.merge_lines(
499 512 name_a=name_a, name_b=name_b, **pycompat.strkwargs(extrakwargs)
500 513 ):
501 514 if opts.get(b'print'):
502 515 ui.fout.write(line)
503 516 else:
504 517 mergedtext += line
505 518
519 # merge flags if necessary
520 flags = localctx.flags()
521 localflags = _bytes_to_set(flags)
522 otherflags = _bytes_to_set(otherctx.flags())
523 if is_null(basectx) and localflags != otherflags:
524 baseflags = _bytes_to_set(basectx.flags())
525 flags = localflags & otherflags
526 for f in localflags.symmetric_difference(otherflags):
527 if f not in baseflags:
528 flags.add(f)
529 flags = b''.join(sorted(flags))
530
506 531 if not opts.get(b'print'):
507 localctx.write(mergedtext, localctx.flags())
532 localctx.write(mergedtext, flags)
508 533
509 534 if m3.conflicts and not mode == b'union':
510 535 return 1
@@ -1,215 +1,214
1 1 ===============================================
2 2 Testing merge involving change to the exec flag
3 3 ===============================================
4 4
5 5 #require execbit
6 6
7 7
8 8 Initial setup
9 9 ==============
10 10
11 11
12 12 $ hg init base-repo
13 13 $ cd base-repo
14 14 $ cat << EOF > a
15 15 > 1
16 16 > 2
17 17 > 3
18 18 > 4
19 19 > 5
20 20 > 6
21 21 > 7
22 22 > 8
23 23 > 9
24 24 > EOF
25 25 $ touch b
26 26 $ hg add a b
27 27 $ hg commit -m "initial commit"
28 28 $ cd ..
29 29
30 30 Testing merging mode change
31 31 ===========================
32 32
33 33 setup
34 34
35 35 Change on one side, executable bit on the other
36 36
37 37 $ hg clone base-repo simple-merge-repo
38 38 updating to branch default
39 39 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
40 40 $ cd simple-merge-repo
41 41 $ chmod +x a
42 42 $ hg ci -m "make a executable, no change"
43 43 $ [ -x a ] || echo "executable bit not recorded"
44 44 $ hg up ".^"
45 45 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
46 46 $ cat << EOF > a
47 47 > 1
48 48 > 2
49 49 > 3
50 50 > 4
51 51 > 5
52 52 > 6
53 53 > 7
54 54 > x
55 55 > 9
56 56 > EOF
57 57 $ hg commit -m "edit end of file"
58 58 created new head
59 59
60 60 merge them (from the update side)
61 61
62 62 $ hg merge 'desc("make a executable, no change")'
63 63 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
64 64 (branch merge, don't forget to commit)
65 65 $ hg st
66 66 M a
67 67 $ [ -x a ] || echo "executable bit lost"
68 68
69 69 merge them (from the chmod side)
70 70
71 71 $ hg up -C 'desc("make a executable, no change")'
72 72 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
73 73 $ hg merge 'desc("edit end of file")'
74 74 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
75 75 (branch merge, don't forget to commit)
76 76 $ hg st
77 77 M a
78 78 $ [ -x a ] || echo "executable bit lost"
79 79
80 80
81 81 $ cd ..
82 82
83 83 Testing merging mode change with rename
84 84 =======================================
85 85
86 86 $ hg clone base-repo rename-merge-repo
87 87 updating to branch default
88 88 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
89 89 $ cd rename-merge-repo
90 90
91 91 make "a" executable on one side
92 92
93 93 $ chmod +x a
94 94 $ hg status
95 95 M a
96 96 $ hg ci -m "make a executable"
97 97 $ [ -x a ] || echo "executable bit not recorded"
98 98 $ hg up ".^"
99 99 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
100 100
101 101 make "a" renamed on the other side
102 102
103 103 $ hg mv a z
104 104 $ hg st --copies
105 105 A z
106 106 a
107 107 R a
108 108 $ hg ci -m "rename a to z"
109 109 created new head
110 110
111 111 merge them (from the rename side)
112 112
113 113 $ hg merge 'desc("make a executable")'
114 114 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
115 115 (branch merge, don't forget to commit)
116 116 $ hg st --copies
117 117 M z
118 118 a
119 119 $ [ -x z ] || echo "executable bit lost"
120 120
121 121 merge them (from the chmod side)
122 122
123 123 $ hg up -C 'desc("make a executable")'
124 124 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
125 125 $ hg merge 'desc("rename a to z")'
126 126 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
127 127 (branch merge, don't forget to commit)
128 128 $ hg st --copies
129 129 M z
130 130 a
131 131 R a
132 132 $ [ -x z ] || echo "executable bit lost"
133 133
134 134
135 135 $ cd ..
136 136
137 137 Testing merging mode change with rename + modification on both side
138 138 ===================================================================
139 139
140 140
141 141 $ hg clone base-repo rename+mod-merge-repo
142 142 updating to branch default
143 143 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
144 144 $ cd rename+mod-merge-repo
145 145
146 146 make "a" executable on one side
147 147
148 148 $ chmod +x a
149 149 $ cat << EOF > a
150 150 > 1
151 151 > x
152 152 > 3
153 153 > 4
154 154 > 5
155 155 > 6
156 156 > 7
157 157 > 8
158 158 > 9
159 159 > EOF
160 160 $ hg status
161 161 M a
162 162 $ hg ci -m "make a executable, and change start"
163 163 $ [ -x a ] || echo "executable bit not recorded"
164 164 $ hg up ".^"
165 165 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
166 166
167 167 make "a" renamed on the other side
168 168
169 169 $ hg mv a z
170 170 $ hg st --copies
171 171 A z
172 172 a
173 173 R a
174 174 $ cat << EOF > z
175 175 > 1
176 176 > 2
177 177 > 3
178 178 > 4
179 179 > 5
180 180 > 6
181 181 > 7
182 182 > x
183 183 > 9
184 184 > EOF
185 185 $ hg ci -m "rename a to z, and change end"
186 186 created new head
187 187
188 188 merge them (from the rename side)
189 189
190 190 $ hg merge 'desc("make a executable")'
191 191 merging z and a to z
192 192 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
193 193 (branch merge, don't forget to commit)
194 194 $ hg st --copies
195 195 M z
196 196 a
197 197 $ [ -x z ] || echo "executable bit lost"
198 executable bit lost
199 198
200 199 merge them (from the chmod side)
201 200
202 201 $ hg up -C 'desc("make a executable")'
203 202 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
204 203 $ hg merge 'desc("rename a to z")'
205 204 merging a and z to z
206 205 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
207 206 (branch merge, don't forget to commit)
208 207 $ hg st --copies
209 208 M z
210 209 a
211 210 R a
212 211 $ [ -x z ] || echo "executable bit lost"
213 212 executable bit lost
214 213
215 214 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now