##// END OF EJS Templates
Make fast-forward merges full merges
Brendan Cully -
r4623:66ed92ed default
parent child Browse files
Show More
@@ -1,544 +1,543 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, context
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 105 def nonoverlap(d1, d2, d3):
106 106 "Return list of elements in d1 not in d2 or d3"
107 107 l = [d for d in d1 if d not in d3 and d not in d2]
108 108 l.sort()
109 109 return l
110 110
111 111 def dirname(f):
112 112 s = f.rfind("/")
113 113 if s == -1:
114 114 return ""
115 115 return f[:s]
116 116
117 117 def dirs(files):
118 118 d = {}
119 119 for f in files:
120 120 f = dirname(f)
121 121 while f not in d:
122 122 d[f] = True
123 123 f = dirname(f)
124 124 return d
125 125
126 126 wctx = repo.workingctx()
127 127
128 128 def makectx(f, n):
129 129 if len(n) == 20:
130 130 return repo.filectx(f, fileid=n)
131 131 return wctx.filectx(f)
132 132 ctx = util.cachefunc(makectx)
133 133
134 134 def findold(fctx):
135 135 "find files that path was copied from, back to linkrev limit"
136 136 old = {}
137 137 seen = {}
138 138 orig = fctx.path()
139 139 visit = [fctx]
140 140 while visit:
141 141 fc = visit.pop()
142 142 s = str(fc)
143 143 if s in seen:
144 144 continue
145 145 seen[s] = 1
146 146 if fc.path() != orig and fc.path() not in old:
147 147 old[fc.path()] = 1
148 148 if fc.rev() < limit:
149 149 continue
150 150 visit += fc.parents()
151 151
152 152 old = old.keys()
153 153 old.sort()
154 154 return old
155 155
156 156 copy = {}
157 157 fullcopy = {}
158 158
159 159 def checkcopies(c, man):
160 160 '''check possible copies for filectx c'''
161 161 for of in findold(c):
162 162 if of not in man: # original file not in other manifest?
163 163 continue
164 164 c2 = ctx(of, man[of])
165 165 ca = c.ancestor(c2)
166 166 if not ca: # unrelated?
167 167 continue
168 168 # named changed on only one side?
169 169 if ca.path() == c.path() or ca.path() == c2.path():
170 170 fullcopy[c.path()] = of # remember for dir rename detection
171 171 if c == ca or c2 == ca: # no merge needed, ignore copy
172 172 continue
173 173 copy[c.path()] = of
174 174
175 175 if not repo.ui.configbool("merge", "followcopies", True):
176 176 return {}
177 177
178 178 # avoid silly behavior for update from empty dir
179 179 if not m1 or not m2 or not ma:
180 180 return {}
181 181
182 182 u1 = nonoverlap(m1, m2, ma)
183 183 u2 = nonoverlap(m2, m1, ma)
184 184
185 185 for f in u1:
186 186 checkcopies(ctx(f, m1[f]), m2)
187 187
188 188 for f in u2:
189 189 checkcopies(ctx(f, m2[f]), m1)
190 190
191 191 if not fullcopy or not repo.ui.configbool("merge", "followdirs", True):
192 192 return copy
193 193
194 194 # generate a directory move map
195 195 d1, d2 = dirs(m1), dirs(m2)
196 196 invalid = {}
197 197 dirmove = {}
198 198
199 199 # examine each file copy for a potential directory move, which is
200 200 # when all the files in a directory are moved to a new directory
201 201 for dst, src in fullcopy.items():
202 202 dsrc, ddst = dirname(src), dirname(dst)
203 203 if dsrc in invalid:
204 204 # already seen to be uninteresting
205 205 continue
206 206 elif dsrc in d1 and ddst in d1:
207 207 # directory wasn't entirely moved locally
208 208 invalid[dsrc] = True
209 209 elif dsrc in d2 and ddst in d2:
210 210 # directory wasn't entirely moved remotely
211 211 invalid[dsrc] = True
212 212 elif dsrc in dirmove and dirmove[dsrc] != ddst:
213 213 # files from the same directory moved to two different places
214 214 invalid[dsrc] = True
215 215 else:
216 216 # looks good so far
217 217 dirmove[dsrc + "/"] = ddst + "/"
218 218
219 219 for i in invalid:
220 220 if i in dirmove:
221 221 del dirmove[i]
222 222
223 223 del d1, d2, invalid
224 224
225 225 if not dirmove:
226 226 return copy
227 227
228 228 # check unaccounted nonoverlapping files against directory moves
229 229 for f in u1 + u2:
230 230 if f not in fullcopy:
231 231 for d in dirmove:
232 232 if f.startswith(d):
233 233 # new file added in a directory that was moved, move it
234 234 copy[f] = dirmove[d] + f[len(d):]
235 235 break
236 236
237 237 return copy
238 238
239 239 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
240 240 """
241 241 Merge p1 and p2 with ancestor ma and generate merge action list
242 242
243 243 overwrite = whether we clobber working files
244 244 partial = function to filter file lists
245 245 """
246 246
247 247 repo.ui.note(_("resolving manifests\n"))
248 248 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
249 249 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
250 250
251 251 m1 = p1.manifest()
252 252 m2 = p2.manifest()
253 253 ma = pa.manifest()
254 254 backwards = (pa == p2)
255 255 action = []
256 256 copy = {}
257 257
258 258 def fmerge(f, f2=None, fa=None):
259 259 """merge flags"""
260 260 if not f2:
261 261 f2 = f
262 262 fa = f
263 263 a, b, c = ma.execf(fa), m1.execf(f), m2.execf(f2)
264 264 if ((a^b) | (a^c)) ^ a:
265 265 return 'x'
266 266 a, b, c = ma.linkf(fa), m1.linkf(f), m2.linkf(f2)
267 267 if ((a^b) | (a^c)) ^ a:
268 268 return 'l'
269 269 return ''
270 270
271 271 def act(msg, m, f, *args):
272 272 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
273 273 action.append((f, m) + args)
274 274
275 275 if not (backwards or overwrite):
276 276 copy = findcopies(repo, m1, m2, ma, pa.rev())
277 277 copied = dict.fromkeys(copy.values())
278 278
279 279 # Compare manifests
280 280 for f, n in m1.iteritems():
281 281 if partial and not partial(f):
282 282 continue
283 283 if f in m2:
284 284 # are files different?
285 285 if n != m2[f]:
286 286 a = ma.get(f, nullid)
287 287 # are both different from the ancestor?
288 288 if not overwrite and n != a and m2[f] != a:
289 289 act("versions differ", "m", f, f, f, fmerge(f), False)
290 290 # are we clobbering?
291 291 # is remote's version newer?
292 292 # or are we going back in time and clean?
293 293 elif overwrite or m2[f] != a or (backwards and not n[20:]):
294 294 act("remote is newer", "g", f, m2.flags(f))
295 295 # local is newer, not overwrite, check mode bits
296 296 elif fmerge(f) != m1.flags(f):
297 297 act("update permissions", "e", f, m2.flags(f))
298 298 # contents same, check mode bits
299 299 elif m1.flags(f) != m2.flags(f):
300 300 if overwrite or fmerge(f) != m1.flags(f):
301 301 act("update permissions", "e", f, m2.flags(f))
302 302 elif f in copied:
303 303 continue
304 304 elif f in copy:
305 305 f2 = copy[f]
306 306 if f2 not in m2: # directory rename
307 307 act("remote renamed directory to " + f2, "d",
308 308 f, None, f2, m1.flags(f))
309 309 elif f2 in m1: # case 2 A,B/B/B
310 310 act("local copied to " + f2, "m",
311 311 f, f2, f, fmerge(f, f2, f2), False)
312 312 else: # case 4,21 A/B/B
313 313 act("local moved to " + f2, "m",
314 314 f, f2, f, fmerge(f, f2, f2), False)
315 315 elif f in ma:
316 316 if n != ma[f] and not overwrite:
317 317 if repo.ui.prompt(
318 318 (_(" local changed %s which remote deleted\n") % f) +
319 319 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
320 320 act("prompt delete", "r", f)
321 321 else:
322 322 act("other deleted", "r", f)
323 323 else:
324 324 # file is created on branch or in working directory
325 325 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
326 326 act("remote deleted", "r", f)
327 327
328 328 for f, n in m2.iteritems():
329 329 if partial and not partial(f):
330 330 continue
331 331 if f in m1:
332 332 continue
333 333 if f in copied:
334 334 continue
335 335 if f in copy:
336 336 f2 = copy[f]
337 337 if f2 not in m1: # directory rename
338 338 act("local renamed directory to " + f2, "d",
339 339 None, f, f2, m2.flags(f))
340 340 elif f2 in m2: # rename case 1, A/A,B/A
341 341 act("remote copied to " + f, "m",
342 342 f2, f, f, fmerge(f2, f, f2), False)
343 343 else: # case 3,20 A/B/A
344 344 act("remote moved to " + f, "m",
345 345 f2, f, f, fmerge(f2, f, f2), True)
346 346 elif f in ma:
347 347 if overwrite or backwards:
348 348 act("recreating", "g", f, m2.flags(f))
349 349 elif n != ma[f]:
350 350 if repo.ui.prompt(
351 351 (_("remote changed %s which local deleted\n") % f) +
352 352 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"):
353 353 act("prompt recreating", "g", f, m2.flags(f))
354 354 else:
355 355 act("remote created", "g", f, m2.flags(f))
356 356
357 357 return action
358 358
359 359 def applyupdates(repo, action, wctx, mctx):
360 360 "apply the merge action list to the working directory"
361 361
362 362 updated, merged, removed, unresolved = 0, 0, 0, 0
363 363 action.sort()
364 364 for a in action:
365 365 f, m = a[:2]
366 366 if f and f[0] == "/":
367 367 continue
368 368 if m == "r": # remove
369 369 repo.ui.note(_("removing %s\n") % f)
370 370 util.audit_path(f)
371 371 try:
372 372 util.unlink(repo.wjoin(f))
373 373 except OSError, inst:
374 374 if inst.errno != errno.ENOENT:
375 375 repo.ui.warn(_("update failed to remove %s: %s!\n") %
376 376 (f, inst.strerror))
377 377 removed += 1
378 378 elif m == "m": # merge
379 379 f2, fd, flags, move = a[2:]
380 380 r = filemerge(repo, f, f2, wctx, mctx)
381 381 if r > 0:
382 382 unresolved += 1
383 383 else:
384 384 if r is None:
385 385 updated += 1
386 386 else:
387 387 merged += 1
388 388 if f != fd:
389 389 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
390 390 repo.wwrite(fd, repo.wread(f), flags)
391 391 if move:
392 392 repo.ui.debug(_("removing %s\n") % f)
393 393 os.unlink(repo.wjoin(f))
394 394 util.set_exec(repo.wjoin(fd), "x" in flags)
395 395 elif m == "g": # get
396 396 flags = a[2]
397 397 repo.ui.note(_("getting %s\n") % f)
398 398 t = mctx.filectx(f).data()
399 399 repo.wwrite(f, t, flags)
400 400 updated += 1
401 401 elif m == "d": # directory rename
402 402 f2, fd, flags = a[2:]
403 403 if f:
404 404 repo.ui.note(_("moving %s to %s\n") % (f, fd))
405 405 t = wctx.filectx(f).data()
406 406 repo.wwrite(fd, t, flags)
407 407 util.unlink(repo.wjoin(f))
408 408 if f2:
409 409 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
410 410 t = mctx.filectx(f2).data()
411 411 repo.wwrite(fd, t, flags)
412 412 updated += 1
413 413 elif m == "e": # exec
414 414 flags = a[2]
415 415 util.set_exec(repo.wjoin(f), flags)
416 416
417 417 return updated, merged, removed, unresolved
418 418
419 419 def recordupdates(repo, action, branchmerge):
420 420 "record merge actions to the dirstate"
421 421
422 422 for a in action:
423 423 f, m = a[:2]
424 424 if m == "r": # remove
425 425 if branchmerge:
426 426 repo.dirstate.update([f], 'r')
427 427 else:
428 428 repo.dirstate.forget([f])
429 429 elif m == "f": # forget
430 430 repo.dirstate.forget([f])
431 431 elif m == "g": # get
432 432 if branchmerge:
433 433 repo.dirstate.update([f], 'n', st_mtime=-1)
434 434 else:
435 435 repo.dirstate.update([f], 'n')
436 436 elif m == "m": # merge
437 437 f2, fd, flag, move = a[2:]
438 438 if branchmerge:
439 439 # We've done a branch merge, mark this file as merged
440 440 # so that we properly record the merger later
441 441 repo.dirstate.update([fd], 'm')
442 442 if f != f2: # copy/rename
443 443 if move:
444 444 repo.dirstate.update([f], 'r')
445 445 if f != fd:
446 446 repo.dirstate.copy(f, fd)
447 447 else:
448 448 repo.dirstate.copy(f2, fd)
449 449 else:
450 450 # We've update-merged a locally modified file, so
451 451 # we set the dirstate to emulate a normal checkout
452 452 # of that file some time in the past. Thus our
453 453 # merge will appear as a normal local file
454 454 # modification.
455 455 repo.dirstate.update([fd], 'n', st_size=-1, st_mtime=-1)
456 456 if move:
457 457 repo.dirstate.forget([f])
458 458 elif m == "d": # directory rename
459 459 f2, fd, flag = a[2:]
460 460 if branchmerge:
461 461 repo.dirstate.update([fd], 'a')
462 462 if f:
463 463 repo.dirstate.update([f], 'r')
464 464 repo.dirstate.copy(f, fd)
465 465 if f2:
466 466 repo.dirstate.copy(f2, fd)
467 467 else:
468 468 repo.dirstate.update([fd], 'n')
469 469 if f:
470 470 repo.dirstate.forget([f])
471 471
472 472 def update(repo, node, branchmerge, force, partial, wlock):
473 473 """
474 474 Perform a merge between the working directory and the given node
475 475
476 476 branchmerge = whether to merge between branches
477 477 force = whether to force branch merging or file overwriting
478 478 partial = a function to filter file lists (dirstate not updated)
479 479 wlock = working dir lock, if already held
480 480 """
481 481
482 482 if not wlock:
483 483 wlock = repo.wlock()
484 484
485 485 wc = repo.workingctx()
486 486 if node is None:
487 487 # tip of current branch
488 488 try:
489 489 node = repo.branchtags()[wc.branch()]
490 490 except KeyError:
491 491 raise util.Abort(_("branch %s not found") % wc.branch())
492 492 overwrite = force and not branchmerge
493 493 forcemerge = force and branchmerge
494 494 pl = wc.parents()
495 495 p1, p2 = pl[0], repo.changectx(node)
496 496 pa = p1.ancestor(p2)
497 497 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
498 498 fastforward = False
499 499
500 500 ### check phase
501 501 if not overwrite and len(pl) > 1:
502 502 raise util.Abort(_("outstanding uncommitted merges"))
503 503 if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
504 504 if branchmerge:
505 505 if p1.branch() != p2.branch():
506 506 fastforward = True
507 branchmerge = False
508 507 else:
509 508 raise util.Abort(_("there is nothing to merge, just use "
510 509 "'hg update' or look at 'hg heads'"))
511 510 elif not (overwrite or branchmerge):
512 511 raise util.Abort(_("update spans branches, use 'hg merge' "
513 512 "or 'hg update -C' to lose changes"))
514 513 if branchmerge and not forcemerge:
515 514 if wc.files():
516 515 raise util.Abort(_("outstanding uncommitted changes"))
517 516
518 517 ### calculate phase
519 518 action = []
520 519 if not force:
521 520 checkunknown(wc, p2)
522 521 if not util.checkfolding(repo.path):
523 522 checkcollision(p2)
524 523 if not branchmerge:
525 524 action += forgetremoved(wc, p2)
526 525 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
527 526
528 527 ### apply phase
529 528 if not branchmerge: # just jump to the new rev
530 529 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
531 530 if not partial:
532 531 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
533 532
534 533 stats = applyupdates(repo, action, wc, p2)
535 534
536 535 if not partial:
537 536 recordupdates(repo, action, branchmerge)
538 537 repo.dirstate.setparents(fp1, fp2)
539 538 if not branchmerge and not fastforward:
540 539 repo.dirstate.setbranch(p2.branch())
541 540 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
542 541
543 542 return stats
544 543
@@ -1,106 +1,108 b''
1 1 marked working directory as branch foo
2 2 foo
3 3 marked working directory as branch bar
4 4 % branch shadowing
5 5 abort: a branch of the same name already exists (use --force to override)
6 6 marked working directory as branch default
7 7 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
8 8 foo
9 9 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
10 10 (branch merge, don't forget to commit)
11 11 foo
12 12 changeset: 5:5f8fb06e083e
13 13 branch: foo
14 14 tag: tip
15 15 parent: 4:4909a3732169
16 16 parent: 3:bf1bc2f45e83
17 17 user: test
18 18 date: Mon Jan 12 13:46:40 1970 +0000
19 19 summary: merge
20 20
21 21 changeset: 4:4909a3732169
22 22 branch: foo
23 23 parent: 1:b699b1cec9c2
24 24 user: test
25 25 date: Mon Jan 12 13:46:40 1970 +0000
26 26 summary: modify a branch
27 27
28 28 changeset: 3:bf1bc2f45e83
29 29 user: test
30 30 date: Mon Jan 12 13:46:40 1970 +0000
31 31 summary: clear branch name
32 32
33 33 changeset: 2:67ec16bde7f1
34 34 branch: bar
35 35 user: test
36 36 date: Mon Jan 12 13:46:40 1970 +0000
37 37 summary: change branch name
38 38
39 39 changeset: 1:b699b1cec9c2
40 40 branch: foo
41 41 user: test
42 42 date: Mon Jan 12 13:46:40 1970 +0000
43 43 summary: add branch name
44 44
45 45 changeset: 0:be8523e69bf8
46 46 user: test
47 47 date: Mon Jan 12 13:46:40 1970 +0000
48 48 summary: initial
49 49
50 50 foo 5:5f8fb06e083e
51 51 default 3:bf1bc2f45e83
52 52 bar 2:67ec16bde7f1
53 53 foo
54 54 default
55 55 bar
56 56 % test for invalid branch cache
57 57 rolling back last transaction
58 58 changeset: 4:4909a3732169
59 59 branch: foo
60 60 tag: tip
61 61 parent: 1:b699b1cec9c2
62 62 user: test
63 63 date: Mon Jan 12 13:46:40 1970 +0000
64 64 summary: modify a branch
65 65
66 66 Invalid branch cache: unknown tip
67 67 changeset: 4:4909a3732169c0c20011c4f4b8fdff4e3d89b23f
68 68 branch: foo
69 69 tag: tip
70 70 parent: 1:b699b1cec9c2966b3700de4fef0dc123cd754c31
71 71 parent: -1:0000000000000000000000000000000000000000
72 72 manifest: 4:d01b250baaa05909152f7ae07d7a649deea0df9a
73 73 user: test
74 74 date: Mon Jan 12 13:46:40 1970 +0000
75 75 files: a
76 76 extra: branch=foo
77 77 description:
78 78 modify a branch
79 79
80 80
81 81 4:4909a3732169
82 82 4909a3732169c0c20011c4f4b8fdff4e3d89b23f 4
83 83 bf1bc2f45e834c75404d0ddab57d53beab56e2f8 default
84 84 4909a3732169c0c20011c4f4b8fdff4e3d89b23f foo
85 85 67ec16bde7f1575d523313b9bca000f6a6f12dca bar
86 86 % update with no arguments: tipmost revision of the current branch
87 87 bf1bc2f45e83
88 88 4909a3732169 (foo) tip
89 89 marked working directory as branch foobar
90 90 abort: branch foobar not found
91 91 % fastforward merge
92 92 marked working directory as branch ff
93 93 adding ff
94 94 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
95 95 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
96 96 (branch merge, don't forget to commit)
97 97 foo
98 changeset: 6:9cc105113eeb
98 changeset: 6:f0c74f92a385
99 99 branch: foo
100 100 tag: tip
101 parent: 4:4909a3732169
102 parent: 5:c420d2121b71
101 103 user: test
102 104 date: Mon Jan 12 13:46:40 1970 +0000
103 105 summary: Merge ff into foo
104 106
105 107 a
106 108 ff
General Comments 0
You need to be logged in to leave comments. Login now