rebase: skip resolved but emptied revisions

When rebasing, if a conflict occurs and is resolved in a way the rebased
revision becomes empty, it is not skipped, unlike revisions being emptied
without conflicts.

The reason is:
- File 'x' is merged and resolved, merge.update() marks it as 'm' in the
  dirstate.
- rebase.concludenode() calls localrepo.commit(), which calls
  localrepo.status() which calls dirstate.status(). 'x' shows up as 'm' and is
  unconditionnally added to the modified files list, instead of being checked
  again.
- localrepo.commit() detects 'x' as changed an create a new revision where only
  the manifest parents and linkrev differ.

Marking 'x' as modified without checking it makes sense for regular merges. But
in rebase case, the merge looks normal but the second parent is usually
discarded. When this happens, 'm' files in dirstate are a bit irrelevant and
should be considered 'n' possibly dirty instead. That is what the current patch
does.

Another approach, maybe more efficient, would be to pass another flag to
merge.update() saying the 'branchmerge' is a bit of a lie and recordupdate()
should call dirstate.normallookup() instead of merge().

It is also tempting to add this logic to dirstate.setparents(), moving from two
to one parent is what invalidates the 'm' markers. But this is a far bigger
change to make.

v2: succumb to the temptation and move the logic in dirstate.setparents(). mpm
suggested trying _filecommit() first but it is called by commitctx() which
knows nothing about the dirstate and comes too late into the game. A second
approach was to rewrite the 'm' state into 'n' on the fly in dirstate.status()
which failed for graft in the following case:

  $ hg init repo
  $ cd repo
  $ echo a > a
  $ hg ci -qAm0
  $ echo a >> a
  $ hg ci -m1
  $ hg up 0
  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
  $ hg mv a b
  $ echo c > b
  $ hg ci -m2
  created new head
  $ hg graft 1 --tool internal:local
  grafting revision 1
  $ hg --config extensions.graphlog= glog --template '{rev} {desc|firstline}\n'
  @  3 1
  |
  o  2 2
  |
  | o  1 1
  |/
  o  0 0

  $ hg log -r 3 --debug --patch --git --copies
  changeset:   3:19cd7d1417952af13161b94c32e901769104560c
  tag:         tip
  phase:       draft
  parent:      2:b5c505595c9e9a12d5dd457919c143e05fc16fb8
  parent:      -1:0000000000000000000000000000000000000000
  manifest:    3:3d27ce8d02241aa59b60804805edf103c5c0cda4
  user:        test
  date:        Thu Jan 01 00:00:00 1970 +0000
  extra:       branch=default
  extra:       source=a03df74c41413a75c0a42997fc36c2de97b26658
  description:
  1

Here, revision 3 is created because there is a copy record for 'b' in the
dirstate and thus 'b' is considered modified. But this information is discarded
at commit time since 'b' content is unchanged. I do not know if discarding this
information is correct or not, but at this time we cannot represent it anyway.

This patch therefore implements the last solution of moving the logic into
dirstate.setparents(). It does not sound crazy as 'm' files makes no sense with
only one parent. It also makes dirstate.merge() calls .lookupnormal() if there
is one parent, to preserve the invariant.

I am a bit concerned about introducing this kind of stateful behaviour to
existing code which historically treated setparents() as a basic setter without
side-effects. And doing that during the code freeze.
This commit is contained in:
Patrick Mezard 2012-04-22 20:06:36 +02:00
parent 9906519ee5
commit 3c6f7e9f84
4 changed files with 21 additions and 46 deletions

View File

@ -238,7 +238,13 @@ class dirstate(object):
def setparents(self, p1, p2=nullid):
self._dirty = self._dirtypl = True
oldp2 = self._pl[1]
self._pl = p1, p2
if oldp2 != nullid and p2 == nullid:
# Discard 'm' markers when moving away from a merge state
for f, s in self._map.iteritems():
if s[0] == 'm':
self.normallookup(f)
def setbranch(self, branch):
if branch in ['tip', '.', 'null']:
@ -386,6 +392,8 @@ class dirstate(object):
def merge(self, f):
'''Mark a file merged.'''
if self._pl[1] == nullid:
return self.normallookup(f)
self._dirty = True
s = os.lstat(self._join(f))
self._addpath(f)

View File

@ -132,14 +132,11 @@ Graft out of order, skipping a merge and a duplicate
b: local copied/moved to a -> m
preserving b for resolve of b
updating: b 1/1 files (100.00%)
b
b: searching for copy revision for a
b: copy a:b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3
grafting revision 5
searching for copies back to rev 1
resolving manifests
overwrite: False, partial: False
ancestor: 4c60f11aa304, local: 6f5ea6ac8b70+, remote: 97f8bfe72746
ancestor: 4c60f11aa304, local: d2e44c99fd3f+, remote: 97f8bfe72746
e: remote is newer -> g
updating: e 1/1 files (100.00%)
getting e
@ -148,7 +145,7 @@ Graft out of order, skipping a merge and a duplicate
searching for copies back to rev 1
resolving manifests
overwrite: False, partial: False
ancestor: 4c60f11aa304, local: 77eb504366ab+, remote: 9c233e8e184d
ancestor: 4c60f11aa304, local: 839a7e8fcf80+, remote: 9c233e8e184d
e: versions differ -> m
d: remote is newer -> g
preserving e for resolve of e
@ -157,7 +154,7 @@ Graft out of order, skipping a merge and a duplicate
updating: e 2/2 files (100.00%)
picked tool 'internal:merge' for e (binary False symlink False)
merging e
my e@77eb504366ab+ other e@9c233e8e184d ancestor e@68795b066622
my e@839a7e8fcf80+ other e@9c233e8e184d ancestor e@68795b066622
warning: conflicts during merge.
merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
abort: unresolved conflicts, can't continue
@ -203,13 +200,11 @@ Compare with original:
View graph:
$ hg --config extensions.graphlog= log -G --template '{author}@{rev}.{phase}: {desc}\n'
@ test@11.draft: 3
@ test@10.draft: 3
|
o test@10.draft: 4
o test@9.draft: 4
|
o test@9.draft: 5
|
o bar@8.draft: 1
o test@8.draft: 5
|
o foo@7.draft: 2
|
@ -227,14 +222,6 @@ View graph:
|/
o test@0.public: 0
$ hg export --git 8
# HG changeset patch
# User bar
# Date 0 0
# Node ID 6f5ea6ac8b705521c6d5f49a04ed142e3f76645d
# Parent d2e44c99fd3f31c176ea4efb9eca9f6306c81756
1
Graft again onto another branch should preserve the original source
$ hg up -q 0
$ echo 'g'>g
@ -250,12 +237,12 @@ Graft again onto another branch should preserve the original source
2:5c095ad7e90f871700f02dd1fa5012cb4498a2d4
$ hg log --debug -r tip
changeset: 13:95adbe5de6b10f376b699ece9ed5a57cd7b4b0f6
changeset: 12:95adbe5de6b10f376b699ece9ed5a57cd7b4b0f6
tag: tip
phase: draft
parent: 12:b592ea63bb0c19a6c5c44685ee29a2284f9f1b8f
parent: 11:b592ea63bb0c19a6c5c44685ee29a2284f9f1b8f
parent: -1:0000000000000000000000000000000000000000
manifest: 13:9944044f82a462bbaccc9bdf7e0ac5b811db7d1b
manifest: 12:9944044f82a462bbaccc9bdf7e0ac5b811db7d1b
user: foo
date: Thu Jan 01 00:00:00 1970 +0000
files+: b
@ -273,7 +260,7 @@ Disallow grafting an already grafted cset onto its original branch
[255]
Disallow grafting already grafted csets with the same origin onto each other
$ hg up -q 13
$ hg up -q 12
$ hg graft 2
skipping already grafted revision 2
[255]
@ -286,5 +273,5 @@ Disallow grafting already grafted csets with the same origin onto each other
skipping already grafted revision 2
[255]
$ hg graft tip
skipping already grafted revision 13 (same origin 2)
skipping already grafted revision 12 (same origin 2)
[255]

View File

@ -380,9 +380,7 @@ Ensure --continue restores a correct state (issue3046) and phase:
$ hg rebase -c
saved backup bundle to $TESTTMP/a7/.hg/strip-backup/6215fafa5447-backup.hg (glob)
$ hg log -G --template "{rev}:{phase} '{desc}' {branches}\n"
@ 8:secret 'H2'
|
o 7:draft 'H'
@ 7:draft 'H'
|
| o 6:draft 'G'
|/|
@ -398,11 +396,3 @@ Ensure --continue restores a correct state (issue3046) and phase:
|/
o 0:draft 'A'
$ hg export --git 8
# HG changeset patch
# User test
# Date 0 0
# Node ID 248209b40064fe67181915fa7a4f3395520f700a
# Parent 02de42196ebee42ef284b6780a87cdc96e8eaab6
H2

View File

@ -117,9 +117,7 @@ already has one local mq patch
saved backup bundle to $TESTTMP/b/.hg/strip-backup/*-backup.hg (glob)
$ hg tglog
@ 9: 'r5' tags: 5.diff qtip tip
|
o 8: 'r4' tags: 4.diff
@ 8: 'r5' tags: 5.diff qtip tip
|
o 7: 'r2' tags: 2.diff qbase
|
@ -137,11 +135,3 @@ already has one local mq patch
|
o 0: 'r0' tags:
$ hg export --git 4.diff
# HG changeset patch
# User test
# Date 0 0
# Node ID 315eb21a13c2b06e787f5d0000e36f8f8f3a1768
# Parent 1660ab13ce9aea3da22ea54926bd49aeff8a4e20
r4