2017-03-31 21:33:48 +03:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
#
|
|
|
|
# Copyright (c) 2004-present, Facebook, Inc.
|
|
|
|
# All rights reserved.
|
|
|
|
#
|
|
|
|
# This source code is licensed under the BSD-style license found in the
|
|
|
|
# LICENSE file in the root directory of this source tree. An additional grant
|
|
|
|
# of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
|
2017-11-08 04:42:43 +03:00
|
|
|
import os
|
2017-11-30 01:23:57 +03:00
|
|
|
from eden.integration.hg.lib.hg_extension_test_base import (
|
Change how the UNTRACKED_ADDED conflict and merges are handled.
Summary:
Previously, we used the Mercurial code `g` when faced with an `UNTRACKED_ADDED`
file conflict, but that was allowing merges to silently succeed that should not
have. This revision changes our logic to use the code `m` for merge, which
unearthed that we were not honoring the user's `update.check` setting properly.
Because we use `update.check=noconflict` internally at Facebook, we changed the
Eden integration tests to default to verifying Hg running with this setting. To
support it properly, we had to port this code from `update.py` in Mercurial to
our own `_determine_actions_for_conflicts()` function:
```
if updatecheck == 'noconflict':
for f, (m, args, msg) in actionbyfile.iteritems():
if m not in ('g', 'k', 'e', 'r', 'pr'):
msg = _("conflicting changes")
hint = _("commit or update --clean to discard changes")
raise error.Abort(msg, hint=hint)
```
However, this introduced an interesting issue where the `checkOutRevision()`
Thrift call from Hg would update the `SNAPSHOT` file on the server, but
`.hg/dirstate` would not get updated with the new parents until the update
completed on the client. With the new call to `raise error.Abort` on the client,
we could get in a state where the `SNAPSHOT` file had the hash of the commit
assuming the update succeeded, but `.hg/dirstate` reflected the reality where it
failed.
To that end, we changed `checkOutRevision()` to take a new parameter,
`checkoutMode`, which can take on one of three values: `NORMAL`, `DRY_RUN`, and
`FORCE`. Now if the user tries to do an ordinary `hg update` with
`update.check=noconflict`, we first do a `DRY_RUN` and examine the potential
conflicts. Only if the conflicts should not block the update do we proceed with
a call to `checkOutRevision()` in `NORMAL` mode.
To make this work, we had to make a number of changes to `CheckoutAction`,
`CheckoutContext`, `EdenMount`, and `TreeInode` to keep track of the
`checkoutMode` and ensure that no changes are made to the working copy when a
`DRY_RUN` is in effect.
One minor issue (for which there is a `TODO`) is that a `DRY_RUN` will not
report any `DIRECTORY_NOT_EMPTY` conflicts that may exist. As `TreeInode` is
implemented today, it is a bit messy to report this type of conflict without
modifying the working copy along the way.
Finally, any `UNTRACKED_ADDED` conflict should cause an update to
abort to match the behavior in stock Mercurial if the user has the following
config setting:
```
[commands]
update.check = noconflict
```
Though the original name for this setting was:
```
[experimental]
updatecheck = noconflict
```
Although I am on Mercurial 4.4.1, the `update.check` setting does not seem to
take effect when I run the integration tests, but the `updatecheck` setting
does, so for now, I set both in `hg_extension_test_base.py` with a `TODO` to
remove `updatecheck` once I can get `update.check` to do its job.
Reviewed By: simpkins
Differential Revision: D6366007
fbshipit-source-id: bb3ecb1270e77d59d7d9e7baa36ada61971bbc49
2017-11-30 08:38:12 +03:00
|
|
|
EdenHgTestCase, hg_test
|
2017-11-30 01:23:57 +03:00
|
|
|
)
|
|
|
|
from eden.integration.lib import hgrepo
|
2017-10-31 22:10:55 +03:00
|
|
|
from textwrap import dedent
|
2017-11-30 01:23:57 +03:00
|
|
|
from typing import Dict
|
2017-03-31 21:33:48 +03:00
|
|
|
|
|
|
|
|
2017-08-24 04:38:31 +03:00
|
|
|
@hg_test
|
2017-11-08 05:47:42 +03:00
|
|
|
class UpdateTest(EdenHgTestCase):
|
2017-11-30 01:23:57 +03:00
|
|
|
def edenfs_logging_settings(self) -> Dict[str, str]:
|
2017-07-08 01:41:53 +03:00
|
|
|
return {
|
|
|
|
'eden.fs.inodes.TreeInode': 'DBG5',
|
|
|
|
'eden.fs.inodes.CheckoutAction': 'DBG5',
|
|
|
|
}
|
2017-04-14 03:24:13 +03:00
|
|
|
|
2017-11-30 01:23:57 +03:00
|
|
|
def populate_backing_repo(self, repo: hgrepo.HgRepository) -> None:
|
2017-03-31 21:33:49 +03:00
|
|
|
repo.write_file('hello.txt', 'hola')
|
2017-03-31 23:58:26 +03:00
|
|
|
repo.write_file('.gitignore', 'ignoreme\n')
|
|
|
|
repo.write_file('foo/.gitignore', '*.log\n')
|
|
|
|
repo.write_file('foo/bar.txt', 'test\n')
|
|
|
|
repo.write_file('foo/subdir/test.txt', 'test\n')
|
|
|
|
self.commit1 = repo.commit('Initial commit.')
|
|
|
|
|
|
|
|
repo.write_file('foo/.gitignore', '*.log\n/_*\n')
|
|
|
|
self.commit2 = repo.commit('Update foo/.gitignore')
|
2017-03-31 21:33:48 +03:00
|
|
|
|
2017-04-07 03:44:06 +03:00
|
|
|
repo.write_file('foo/bar.txt', 'updated in commit 3\n')
|
|
|
|
self.commit3 = repo.commit('Update foo/.gitignore')
|
|
|
|
|
2017-11-30 01:23:57 +03:00
|
|
|
def test_update_clean_reverts_modified_files(self) -> None:
|
2017-03-31 21:33:48 +03:00
|
|
|
'''Test using `hg update --clean .` to revert file modifications.'''
|
2017-04-01 04:13:27 +03:00
|
|
|
self.assert_status_empty()
|
2017-03-31 21:33:48 +03:00
|
|
|
|
|
|
|
self.write_file('hello.txt', 'saluton')
|
2017-04-01 04:13:27 +03:00
|
|
|
self.assert_status({'hello.txt': 'M'})
|
2017-03-31 21:33:48 +03:00
|
|
|
|
2017-03-31 21:33:49 +03:00
|
|
|
self.repo.update('.', clean=True)
|
2017-03-31 21:33:48 +03:00
|
|
|
self.assertEqual('hola', self.read_file('hello.txt'))
|
2017-04-01 04:13:27 +03:00
|
|
|
self.assert_status_empty()
|
2017-03-31 23:58:26 +03:00
|
|
|
|
2017-11-30 01:23:57 +03:00
|
|
|
def test_update_clean_removes_added_and_removed_statuses(self) -> None:
|
2017-11-15 09:18:44 +03:00
|
|
|
'''Test using `hg update --clean .` in the presence of added and removed
|
|
|
|
files.'''
|
|
|
|
self.write_file('bar/some_new_file.txt', 'new file\n')
|
|
|
|
self.hg('add', 'bar/some_new_file.txt')
|
|
|
|
self.hg('remove', 'foo/bar.txt')
|
|
|
|
self.assertFalse(os.path.isfile(self.get_path('foo/bar.txt')))
|
|
|
|
self.assert_status({'foo/bar.txt': 'R', 'bar/some_new_file.txt': 'A'})
|
|
|
|
|
|
|
|
self.repo.update('.', clean=True)
|
|
|
|
self.assert_status({'bar/some_new_file.txt': '?'})
|
|
|
|
self.assertTrue(os.path.isfile(self.get_path('foo/bar.txt')))
|
2017-11-21 00:51:33 +03:00
|
|
|
self.assert_dirstate_empty()
|
2017-11-15 09:18:44 +03:00
|
|
|
|
2017-11-30 01:23:57 +03:00
|
|
|
def test_update_with_gitignores(self) -> None:
|
2017-03-31 23:58:26 +03:00
|
|
|
'''
|
|
|
|
Test `hg update` with gitignore files.
|
|
|
|
|
|
|
|
This exercises the normal checkout and ignore logic, but also exercises
|
|
|
|
some additional interesting cases: The `hg status` calls cause eden to
|
|
|
|
create FileInode objects for the .gitignore files, even though they
|
|
|
|
have never been requested via FUSE APIs. When we update them via
|
|
|
|
checkout, this triggers FUSE inode invalidation events. We want to
|
|
|
|
make sure the invalidation doesn't cause any errors even though the
|
|
|
|
kernel didn't previously know that these inode objects existed.
|
|
|
|
'''
|
|
|
|
# Call `hg status`, which causes eden to internally create FileInode
|
|
|
|
# objects for the .gitignore files.
|
2017-04-01 04:13:27 +03:00
|
|
|
self.assert_status_empty()
|
2017-03-31 23:58:26 +03:00
|
|
|
|
|
|
|
self.write_file('foo/subdir/test.log', 'log data')
|
|
|
|
self.write_file('foo/_data', 'data file')
|
2017-10-31 22:10:55 +03:00
|
|
|
self.assert_status_empty(
|
|
|
|
check_ignored=False, msg='test.log and _data should be ignored'
|
|
|
|
)
|
2017-04-01 04:13:27 +03:00
|
|
|
self.assert_status({
|
|
|
|
'foo/subdir/test.log': 'I',
|
|
|
|
'foo/_data': 'I',
|
|
|
|
})
|
2017-03-31 23:58:26 +03:00
|
|
|
|
|
|
|
# Call `hg update` to move from commit2 to commit1, which will
|
|
|
|
# change the contents of foo/.gitignore. This will cause edenfs
|
|
|
|
# to send an inode invalidation event to FUSE, but FUSE never knew
|
|
|
|
# about this inode in the first place. edenfs should ignore the
|
|
|
|
# resulting ENOENT error in response to the invalidation request.
|
|
|
|
self.repo.update(self.commit1)
|
2017-10-31 22:10:55 +03:00
|
|
|
self.assert_status(
|
|
|
|
{
|
|
|
|
'foo/_data': '?',
|
|
|
|
}, check_ignored=False
|
|
|
|
)
|
2017-04-01 04:13:27 +03:00
|
|
|
self.assert_status({
|
|
|
|
'foo/subdir/test.log': 'I',
|
|
|
|
'foo/_data': '?',
|
|
|
|
})
|
2017-03-31 23:58:26 +03:00
|
|
|
self.assertEqual('*.log\n', self.read_file('foo/.gitignore'))
|
2017-04-07 03:44:06 +03:00
|
|
|
self.assertEqual('test\n', self.read_file('foo/bar.txt'))
|
2017-03-31 23:58:27 +03:00
|
|
|
|
2017-11-30 01:23:57 +03:00
|
|
|
def test_update_with_new_commits(self) -> None:
|
2017-03-31 23:58:27 +03:00
|
|
|
'''
|
|
|
|
Test running `hg update` to check out commits that were created after
|
|
|
|
the edenfs daemon originally started.
|
|
|
|
|
|
|
|
This makes sure edenfs can correctly import new commits that appear in
|
|
|
|
the backing store repository.
|
|
|
|
'''
|
2017-04-07 03:44:06 +03:00
|
|
|
new_contents = 'New contents for bar.txt\n'
|
2017-03-31 23:58:27 +03:00
|
|
|
self.backing_repo.write_file('foo/bar.txt', new_contents)
|
|
|
|
new_commit = self.backing_repo.commit('Update foo/bar.txt')
|
|
|
|
|
2017-04-01 04:13:27 +03:00
|
|
|
self.assert_status_empty()
|
2017-04-07 03:44:06 +03:00
|
|
|
self.assertNotEqual(new_contents, self.read_file('foo/bar.txt'))
|
2017-03-31 23:58:27 +03:00
|
|
|
|
|
|
|
self.repo.update(new_commit)
|
|
|
|
self.assertEqual(new_contents, self.read_file('foo/bar.txt'))
|
2017-04-01 04:13:27 +03:00
|
|
|
self.assert_status_empty()
|
2017-04-07 03:44:06 +03:00
|
|
|
|
2017-11-30 01:23:57 +03:00
|
|
|
def test_reset(self) -> None:
|
2017-04-07 03:44:06 +03:00
|
|
|
'''
|
|
|
|
Test `hg reset`
|
|
|
|
'''
|
|
|
|
self.assert_status_empty()
|
|
|
|
self.assertEqual('updated in commit 3\n', self.read_file('foo/bar.txt'))
|
|
|
|
|
|
|
|
self.repo.reset(self.commit2, keep=True)
|
|
|
|
self.assert_status({'foo/bar.txt': 'M'})
|
|
|
|
self.assertEqual('updated in commit 3\n', self.read_file('foo/bar.txt'))
|
|
|
|
|
|
|
|
self.repo.update(self.commit2, clean=True)
|
|
|
|
self.assert_status_empty()
|
|
|
|
self.assertEqual('test\n', self.read_file('foo/bar.txt'))
|
2017-04-14 03:24:13 +03:00
|
|
|
|
2017-11-30 01:23:57 +03:00
|
|
|
def test_update_replace_untracked_dir(self) -> None:
|
2017-04-14 03:24:13 +03:00
|
|
|
'''
|
|
|
|
Create a local untracked directory, then run "hg update -C" to
|
|
|
|
checkout a commit where this directory exists in source control.
|
|
|
|
'''
|
|
|
|
self.assert_status_empty()
|
|
|
|
# Write some new files in the eden working directory
|
|
|
|
self.mkdir('new_project')
|
|
|
|
self.write_file('new_project/newcode.c', 'test\n')
|
|
|
|
self.write_file('new_project/Makefile', 'all:\n\techo done!\n')
|
|
|
|
self.write_file('new_project/.gitignore', '*.o\n')
|
|
|
|
self.write_file('new_project/newcode.o', '\x00\x01\x02\x03\x04')
|
|
|
|
|
|
|
|
# Add the same files to a commit in the backing repository
|
|
|
|
self.backing_repo.write_file('new_project/newcode.c', 'test\n')
|
2017-10-31 22:10:55 +03:00
|
|
|
self.backing_repo.write_file(
|
|
|
|
'new_project/Makefile', 'all:\n\techo done!\n'
|
|
|
|
)
|
2017-04-14 03:24:13 +03:00
|
|
|
self.backing_repo.write_file('new_project/.gitignore', '*.o\n')
|
|
|
|
new_commit = self.backing_repo.commit('Add new_project')
|
|
|
|
|
|
|
|
# Check the status before we update
|
2017-10-31 22:10:55 +03:00
|
|
|
self.assert_status(
|
|
|
|
{
|
|
|
|
'new_project/newcode.o': 'I',
|
|
|
|
'new_project/newcode.c': '?',
|
|
|
|
'new_project/Makefile': '?',
|
|
|
|
'new_project/.gitignore': '?',
|
|
|
|
}
|
|
|
|
)
|
2017-04-14 03:24:13 +03:00
|
|
|
|
|
|
|
# Now run "hg update -C new_commit"
|
|
|
|
self.repo.update(new_commit, clean=True)
|
|
|
|
self.assert_status({
|
|
|
|
'new_project/newcode.o': 'I',
|
|
|
|
})
|
2017-10-31 22:10:55 +03:00
|
|
|
|
2017-11-30 01:23:57 +03:00
|
|
|
def test_update_with_merge_flag_and_conflict(self) -> None:
|
2017-10-31 22:10:55 +03:00
|
|
|
self.write_file('foo/bar.txt', 'changing yet again\n')
|
|
|
|
with self.assertRaises(hgrepo.HgError) as context:
|
|
|
|
self.hg('update', '.^', '--merge')
|
|
|
|
self.assertIn(
|
|
|
|
b'conflicts while merging foo/bar.txt! '
|
2017-11-08 04:42:43 +03:00
|
|
|
b'(edit, then use \'hg resolve --mark\')', context.exception.stderr
|
2017-10-31 22:10:55 +03:00
|
|
|
)
|
|
|
|
self.assert_status({
|
|
|
|
'foo/bar.txt': 'M',
|
|
|
|
})
|
2017-11-08 04:42:43 +03:00
|
|
|
expected_contents = dedent(
|
|
|
|
'''\
|
2017-10-31 22:10:55 +03:00
|
|
|
<<<<<<< working copy
|
|
|
|
changing yet again
|
|
|
|
=======
|
|
|
|
test
|
|
|
|
>>>>>>> destination
|
2017-11-08 04:42:43 +03:00
|
|
|
'''
|
|
|
|
)
|
2017-10-31 22:10:55 +03:00
|
|
|
self.assertEqual(expected_contents, self.read_file('foo/bar.txt'))
|
2017-11-08 04:42:43 +03:00
|
|
|
|
Change how the UNTRACKED_ADDED conflict and merges are handled.
Summary:
Previously, we used the Mercurial code `g` when faced with an `UNTRACKED_ADDED`
file conflict, but that was allowing merges to silently succeed that should not
have. This revision changes our logic to use the code `m` for merge, which
unearthed that we were not honoring the user's `update.check` setting properly.
Because we use `update.check=noconflict` internally at Facebook, we changed the
Eden integration tests to default to verifying Hg running with this setting. To
support it properly, we had to port this code from `update.py` in Mercurial to
our own `_determine_actions_for_conflicts()` function:
```
if updatecheck == 'noconflict':
for f, (m, args, msg) in actionbyfile.iteritems():
if m not in ('g', 'k', 'e', 'r', 'pr'):
msg = _("conflicting changes")
hint = _("commit or update --clean to discard changes")
raise error.Abort(msg, hint=hint)
```
However, this introduced an interesting issue where the `checkOutRevision()`
Thrift call from Hg would update the `SNAPSHOT` file on the server, but
`.hg/dirstate` would not get updated with the new parents until the update
completed on the client. With the new call to `raise error.Abort` on the client,
we could get in a state where the `SNAPSHOT` file had the hash of the commit
assuming the update succeeded, but `.hg/dirstate` reflected the reality where it
failed.
To that end, we changed `checkOutRevision()` to take a new parameter,
`checkoutMode`, which can take on one of three values: `NORMAL`, `DRY_RUN`, and
`FORCE`. Now if the user tries to do an ordinary `hg update` with
`update.check=noconflict`, we first do a `DRY_RUN` and examine the potential
conflicts. Only if the conflicts should not block the update do we proceed with
a call to `checkOutRevision()` in `NORMAL` mode.
To make this work, we had to make a number of changes to `CheckoutAction`,
`CheckoutContext`, `EdenMount`, and `TreeInode` to keep track of the
`checkoutMode` and ensure that no changes are made to the working copy when a
`DRY_RUN` is in effect.
One minor issue (for which there is a `TODO`) is that a `DRY_RUN` will not
report any `DIRECTORY_NOT_EMPTY` conflicts that may exist. As `TreeInode` is
implemented today, it is a bit messy to report this type of conflict without
modifying the working copy along the way.
Finally, any `UNTRACKED_ADDED` conflict should cause an update to
abort to match the behavior in stock Mercurial if the user has the following
config setting:
```
[commands]
update.check = noconflict
```
Though the original name for this setting was:
```
[experimental]
updatecheck = noconflict
```
Although I am on Mercurial 4.4.1, the `update.check` setting does not seem to
take effect when I run the integration tests, but the `updatecheck` setting
does, so for now, I set both in `hg_extension_test_base.py` with a `TODO` to
remove `updatecheck` once I can get `update.check` to do its job.
Reviewed By: simpkins
Differential Revision: D6366007
fbshipit-source-id: bb3ecb1270e77d59d7d9e7baa36ada61971bbc49
2017-11-30 08:38:12 +03:00
|
|
|
def test_merge_update_added_file_with_same_contents_in_destination(
|
2017-11-30 01:23:57 +03:00
|
|
|
self
|
|
|
|
) -> None:
|
Change how the UNTRACKED_ADDED conflict and merges are handled.
Summary:
Previously, we used the Mercurial code `g` when faced with an `UNTRACKED_ADDED`
file conflict, but that was allowing merges to silently succeed that should not
have. This revision changes our logic to use the code `m` for merge, which
unearthed that we were not honoring the user's `update.check` setting properly.
Because we use `update.check=noconflict` internally at Facebook, we changed the
Eden integration tests to default to verifying Hg running with this setting. To
support it properly, we had to port this code from `update.py` in Mercurial to
our own `_determine_actions_for_conflicts()` function:
```
if updatecheck == 'noconflict':
for f, (m, args, msg) in actionbyfile.iteritems():
if m not in ('g', 'k', 'e', 'r', 'pr'):
msg = _("conflicting changes")
hint = _("commit or update --clean to discard changes")
raise error.Abort(msg, hint=hint)
```
However, this introduced an interesting issue where the `checkOutRevision()`
Thrift call from Hg would update the `SNAPSHOT` file on the server, but
`.hg/dirstate` would not get updated with the new parents until the update
completed on the client. With the new call to `raise error.Abort` on the client,
we could get in a state where the `SNAPSHOT` file had the hash of the commit
assuming the update succeeded, but `.hg/dirstate` reflected the reality where it
failed.
To that end, we changed `checkOutRevision()` to take a new parameter,
`checkoutMode`, which can take on one of three values: `NORMAL`, `DRY_RUN`, and
`FORCE`. Now if the user tries to do an ordinary `hg update` with
`update.check=noconflict`, we first do a `DRY_RUN` and examine the potential
conflicts. Only if the conflicts should not block the update do we proceed with
a call to `checkOutRevision()` in `NORMAL` mode.
To make this work, we had to make a number of changes to `CheckoutAction`,
`CheckoutContext`, `EdenMount`, and `TreeInode` to keep track of the
`checkoutMode` and ensure that no changes are made to the working copy when a
`DRY_RUN` is in effect.
One minor issue (for which there is a `TODO`) is that a `DRY_RUN` will not
report any `DIRECTORY_NOT_EMPTY` conflicts that may exist. As `TreeInode` is
implemented today, it is a bit messy to report this type of conflict without
modifying the working copy along the way.
Finally, any `UNTRACKED_ADDED` conflict should cause an update to
abort to match the behavior in stock Mercurial if the user has the following
config setting:
```
[commands]
update.check = noconflict
```
Though the original name for this setting was:
```
[experimental]
updatecheck = noconflict
```
Although I am on Mercurial 4.4.1, the `update.check` setting does not seem to
take effect when I run the integration tests, but the `updatecheck` setting
does, so for now, I set both in `hg_extension_test_base.py` with a `TODO` to
remove `updatecheck` once I can get `update.check` to do its job.
Reviewed By: simpkins
Differential Revision: D6366007
fbshipit-source-id: bb3ecb1270e77d59d7d9e7baa36ada61971bbc49
2017-11-30 08:38:12 +03:00
|
|
|
base_commit = self.repo.get_head_hash()
|
|
|
|
|
|
|
|
file_contents = 'new file\n'
|
|
|
|
self.write_file('bar/some_new_file.txt', file_contents)
|
|
|
|
self.hg('add', 'bar/some_new_file.txt')
|
|
|
|
self.write_file('foo/bar.txt', 'Modify existing file.\n')
|
|
|
|
new_commit = self.repo.commit('add some_new_file.txt')
|
|
|
|
self.assert_status_empty()
|
|
|
|
|
|
|
|
self.repo.update(base_commit)
|
|
|
|
self.assert_status_empty()
|
|
|
|
self.write_file('bar/some_new_file.txt', file_contents)
|
|
|
|
self.hg('add', 'bar/some_new_file.txt')
|
|
|
|
self.assert_status({'bar/some_new_file.txt': 'A'})
|
2017-11-21 02:46:54 +03:00
|
|
|
|
Change how the UNTRACKED_ADDED conflict and merges are handled.
Summary:
Previously, we used the Mercurial code `g` when faced with an `UNTRACKED_ADDED`
file conflict, but that was allowing merges to silently succeed that should not
have. This revision changes our logic to use the code `m` for merge, which
unearthed that we were not honoring the user's `update.check` setting properly.
Because we use `update.check=noconflict` internally at Facebook, we changed the
Eden integration tests to default to verifying Hg running with this setting. To
support it properly, we had to port this code from `update.py` in Mercurial to
our own `_determine_actions_for_conflicts()` function:
```
if updatecheck == 'noconflict':
for f, (m, args, msg) in actionbyfile.iteritems():
if m not in ('g', 'k', 'e', 'r', 'pr'):
msg = _("conflicting changes")
hint = _("commit or update --clean to discard changes")
raise error.Abort(msg, hint=hint)
```
However, this introduced an interesting issue where the `checkOutRevision()`
Thrift call from Hg would update the `SNAPSHOT` file on the server, but
`.hg/dirstate` would not get updated with the new parents until the update
completed on the client. With the new call to `raise error.Abort` on the client,
we could get in a state where the `SNAPSHOT` file had the hash of the commit
assuming the update succeeded, but `.hg/dirstate` reflected the reality where it
failed.
To that end, we changed `checkOutRevision()` to take a new parameter,
`checkoutMode`, which can take on one of three values: `NORMAL`, `DRY_RUN`, and
`FORCE`. Now if the user tries to do an ordinary `hg update` with
`update.check=noconflict`, we first do a `DRY_RUN` and examine the potential
conflicts. Only if the conflicts should not block the update do we proceed with
a call to `checkOutRevision()` in `NORMAL` mode.
To make this work, we had to make a number of changes to `CheckoutAction`,
`CheckoutContext`, `EdenMount`, and `TreeInode` to keep track of the
`checkoutMode` and ensure that no changes are made to the working copy when a
`DRY_RUN` is in effect.
One minor issue (for which there is a `TODO`) is that a `DRY_RUN` will not
report any `DIRECTORY_NOT_EMPTY` conflicts that may exist. As `TreeInode` is
implemented today, it is a bit messy to report this type of conflict without
modifying the working copy along the way.
Finally, any `UNTRACKED_ADDED` conflict should cause an update to
abort to match the behavior in stock Mercurial if the user has the following
config setting:
```
[commands]
update.check = noconflict
```
Though the original name for this setting was:
```
[experimental]
updatecheck = noconflict
```
Although I am on Mercurial 4.4.1, the `update.check` setting does not seem to
take effect when I run the integration tests, but the `updatecheck` setting
does, so for now, I set both in `hg_extension_test_base.py` with a `TODO` to
remove `updatecheck` once I can get `update.check` to do its job.
Reviewed By: simpkins
Differential Revision: D6366007
fbshipit-source-id: bb3ecb1270e77d59d7d9e7baa36ada61971bbc49
2017-11-30 08:38:12 +03:00
|
|
|
# Note the update fails even though some_new_file.txt is the same in
|
|
|
|
# both the working copy and the destination.
|
|
|
|
with self.assertRaises(hgrepo.HgError) as context:
|
|
|
|
self.repo.update(new_commit)
|
|
|
|
self.assertIn(b'abort: conflicting changes', context.exception.stderr)
|
|
|
|
self.assertEqual(
|
|
|
|
base_commit,
|
|
|
|
self.repo.get_head_hash(),
|
|
|
|
msg='We should still be on the base commit because '
|
|
|
|
'the merge was aborted.'
|
|
|
|
)
|
|
|
|
self.assert_dirstate(
|
|
|
|
{
|
|
|
|
'bar/some_new_file.txt': ('a', 0, 'MERGE_BOTH'),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
self.assert_status({'bar/some_new_file.txt': 'A'})
|
|
|
|
self.assertEqual(file_contents, self.read_file('bar/some_new_file.txt'))
|
|
|
|
|
|
|
|
# Now do the update with --merge specified.
|
|
|
|
self.repo.update(new_commit, merge=True)
|
|
|
|
self.assert_status_empty()
|
|
|
|
self.assertEqual(
|
|
|
|
new_commit,
|
|
|
|
self.repo.get_head_hash(),
|
|
|
|
msg='Should be expected commit hash because nothing has changed.'
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_merge_update_added_file_with_conflict_in_destination(self) -> None:
|
|
|
|
self._test_merge_update_file_with_conflict_in_destination(True)
|
|
|
|
|
|
|
|
def test_merge_update_untracked_file_with_conflict_in_destination(
|
2017-11-30 01:23:57 +03:00
|
|
|
self
|
|
|
|
) -> None:
|
Change how the UNTRACKED_ADDED conflict and merges are handled.
Summary:
Previously, we used the Mercurial code `g` when faced with an `UNTRACKED_ADDED`
file conflict, but that was allowing merges to silently succeed that should not
have. This revision changes our logic to use the code `m` for merge, which
unearthed that we were not honoring the user's `update.check` setting properly.
Because we use `update.check=noconflict` internally at Facebook, we changed the
Eden integration tests to default to verifying Hg running with this setting. To
support it properly, we had to port this code from `update.py` in Mercurial to
our own `_determine_actions_for_conflicts()` function:
```
if updatecheck == 'noconflict':
for f, (m, args, msg) in actionbyfile.iteritems():
if m not in ('g', 'k', 'e', 'r', 'pr'):
msg = _("conflicting changes")
hint = _("commit or update --clean to discard changes")
raise error.Abort(msg, hint=hint)
```
However, this introduced an interesting issue where the `checkOutRevision()`
Thrift call from Hg would update the `SNAPSHOT` file on the server, but
`.hg/dirstate` would not get updated with the new parents until the update
completed on the client. With the new call to `raise error.Abort` on the client,
we could get in a state where the `SNAPSHOT` file had the hash of the commit
assuming the update succeeded, but `.hg/dirstate` reflected the reality where it
failed.
To that end, we changed `checkOutRevision()` to take a new parameter,
`checkoutMode`, which can take on one of three values: `NORMAL`, `DRY_RUN`, and
`FORCE`. Now if the user tries to do an ordinary `hg update` with
`update.check=noconflict`, we first do a `DRY_RUN` and examine the potential
conflicts. Only if the conflicts should not block the update do we proceed with
a call to `checkOutRevision()` in `NORMAL` mode.
To make this work, we had to make a number of changes to `CheckoutAction`,
`CheckoutContext`, `EdenMount`, and `TreeInode` to keep track of the
`checkoutMode` and ensure that no changes are made to the working copy when a
`DRY_RUN` is in effect.
One minor issue (for which there is a `TODO`) is that a `DRY_RUN` will not
report any `DIRECTORY_NOT_EMPTY` conflicts that may exist. As `TreeInode` is
implemented today, it is a bit messy to report this type of conflict without
modifying the working copy along the way.
Finally, any `UNTRACKED_ADDED` conflict should cause an update to
abort to match the behavior in stock Mercurial if the user has the following
config setting:
```
[commands]
update.check = noconflict
```
Though the original name for this setting was:
```
[experimental]
updatecheck = noconflict
```
Although I am on Mercurial 4.4.1, the `update.check` setting does not seem to
take effect when I run the integration tests, but the `updatecheck` setting
does, so for now, I set both in `hg_extension_test_base.py` with a `TODO` to
remove `updatecheck` once I can get `update.check` to do its job.
Reviewed By: simpkins
Differential Revision: D6366007
fbshipit-source-id: bb3ecb1270e77d59d7d9e7baa36ada61971bbc49
2017-11-30 08:38:12 +03:00
|
|
|
self._test_merge_update_file_with_conflict_in_destination(False)
|
2017-11-21 02:46:54 +03:00
|
|
|
|
Change how the UNTRACKED_ADDED conflict and merges are handled.
Summary:
Previously, we used the Mercurial code `g` when faced with an `UNTRACKED_ADDED`
file conflict, but that was allowing merges to silently succeed that should not
have. This revision changes our logic to use the code `m` for merge, which
unearthed that we were not honoring the user's `update.check` setting properly.
Because we use `update.check=noconflict` internally at Facebook, we changed the
Eden integration tests to default to verifying Hg running with this setting. To
support it properly, we had to port this code from `update.py` in Mercurial to
our own `_determine_actions_for_conflicts()` function:
```
if updatecheck == 'noconflict':
for f, (m, args, msg) in actionbyfile.iteritems():
if m not in ('g', 'k', 'e', 'r', 'pr'):
msg = _("conflicting changes")
hint = _("commit or update --clean to discard changes")
raise error.Abort(msg, hint=hint)
```
However, this introduced an interesting issue where the `checkOutRevision()`
Thrift call from Hg would update the `SNAPSHOT` file on the server, but
`.hg/dirstate` would not get updated with the new parents until the update
completed on the client. With the new call to `raise error.Abort` on the client,
we could get in a state where the `SNAPSHOT` file had the hash of the commit
assuming the update succeeded, but `.hg/dirstate` reflected the reality where it
failed.
To that end, we changed `checkOutRevision()` to take a new parameter,
`checkoutMode`, which can take on one of three values: `NORMAL`, `DRY_RUN`, and
`FORCE`. Now if the user tries to do an ordinary `hg update` with
`update.check=noconflict`, we first do a `DRY_RUN` and examine the potential
conflicts. Only if the conflicts should not block the update do we proceed with
a call to `checkOutRevision()` in `NORMAL` mode.
To make this work, we had to make a number of changes to `CheckoutAction`,
`CheckoutContext`, `EdenMount`, and `TreeInode` to keep track of the
`checkoutMode` and ensure that no changes are made to the working copy when a
`DRY_RUN` is in effect.
One minor issue (for which there is a `TODO`) is that a `DRY_RUN` will not
report any `DIRECTORY_NOT_EMPTY` conflicts that may exist. As `TreeInode` is
implemented today, it is a bit messy to report this type of conflict without
modifying the working copy along the way.
Finally, any `UNTRACKED_ADDED` conflict should cause an update to
abort to match the behavior in stock Mercurial if the user has the following
config setting:
```
[commands]
update.check = noconflict
```
Though the original name for this setting was:
```
[experimental]
updatecheck = noconflict
```
Although I am on Mercurial 4.4.1, the `update.check` setting does not seem to
take effect when I run the integration tests, but the `updatecheck` setting
does, so for now, I set both in `hg_extension_test_base.py` with a `TODO` to
remove `updatecheck` once I can get `update.check` to do its job.
Reviewed By: simpkins
Differential Revision: D6366007
fbshipit-source-id: bb3ecb1270e77d59d7d9e7baa36ada61971bbc49
2017-11-30 08:38:12 +03:00
|
|
|
def _test_merge_update_file_with_conflict_in_destination(
|
2017-11-21 02:46:54 +03:00
|
|
|
self, add_before_updating: bool
|
2017-11-30 01:23:57 +03:00
|
|
|
) -> None:
|
2017-11-21 02:46:54 +03:00
|
|
|
base_commit = self.repo.get_head_hash()
|
2017-11-08 04:42:43 +03:00
|
|
|
original_contents = 'Original contents.\n'
|
|
|
|
self.write_file('some_new_file.txt', original_contents)
|
|
|
|
self.hg('add', 'some_new_file.txt')
|
Change how the UNTRACKED_ADDED conflict and merges are handled.
Summary:
Previously, we used the Mercurial code `g` when faced with an `UNTRACKED_ADDED`
file conflict, but that was allowing merges to silently succeed that should not
have. This revision changes our logic to use the code `m` for merge, which
unearthed that we were not honoring the user's `update.check` setting properly.
Because we use `update.check=noconflict` internally at Facebook, we changed the
Eden integration tests to default to verifying Hg running with this setting. To
support it properly, we had to port this code from `update.py` in Mercurial to
our own `_determine_actions_for_conflicts()` function:
```
if updatecheck == 'noconflict':
for f, (m, args, msg) in actionbyfile.iteritems():
if m not in ('g', 'k', 'e', 'r', 'pr'):
msg = _("conflicting changes")
hint = _("commit or update --clean to discard changes")
raise error.Abort(msg, hint=hint)
```
However, this introduced an interesting issue where the `checkOutRevision()`
Thrift call from Hg would update the `SNAPSHOT` file on the server, but
`.hg/dirstate` would not get updated with the new parents until the update
completed on the client. With the new call to `raise error.Abort` on the client,
we could get in a state where the `SNAPSHOT` file had the hash of the commit
assuming the update succeeded, but `.hg/dirstate` reflected the reality where it
failed.
To that end, we changed `checkOutRevision()` to take a new parameter,
`checkoutMode`, which can take on one of three values: `NORMAL`, `DRY_RUN`, and
`FORCE`. Now if the user tries to do an ordinary `hg update` with
`update.check=noconflict`, we first do a `DRY_RUN` and examine the potential
conflicts. Only if the conflicts should not block the update do we proceed with
a call to `checkOutRevision()` in `NORMAL` mode.
To make this work, we had to make a number of changes to `CheckoutAction`,
`CheckoutContext`, `EdenMount`, and `TreeInode` to keep track of the
`checkoutMode` and ensure that no changes are made to the working copy when a
`DRY_RUN` is in effect.
One minor issue (for which there is a `TODO`) is that a `DRY_RUN` will not
report any `DIRECTORY_NOT_EMPTY` conflicts that may exist. As `TreeInode` is
implemented today, it is a bit messy to report this type of conflict without
modifying the working copy along the way.
Finally, any `UNTRACKED_ADDED` conflict should cause an update to
abort to match the behavior in stock Mercurial if the user has the following
config setting:
```
[commands]
update.check = noconflict
```
Though the original name for this setting was:
```
[experimental]
updatecheck = noconflict
```
Although I am on Mercurial 4.4.1, the `update.check` setting does not seem to
take effect when I run the integration tests, but the `updatecheck` setting
does, so for now, I set both in `hg_extension_test_base.py` with a `TODO` to
remove `updatecheck` once I can get `update.check` to do its job.
Reviewed By: simpkins
Differential Revision: D6366007
fbshipit-source-id: bb3ecb1270e77d59d7d9e7baa36ada61971bbc49
2017-11-30 08:38:12 +03:00
|
|
|
self.write_file('foo/bar.txt', 'Modify existing file.\n')
|
2017-11-08 04:42:43 +03:00
|
|
|
commit = self.repo.commit('Commit a new file.')
|
2017-11-21 02:46:54 +03:00
|
|
|
self.assert_status_empty()
|
2017-11-08 04:42:43 +03:00
|
|
|
|
|
|
|
# Do an `hg prev` and re-create the new file with different contents.
|
2017-11-21 02:46:54 +03:00
|
|
|
self.repo.update(base_commit)
|
2017-11-08 04:42:43 +03:00
|
|
|
self.assert_status_empty()
|
2017-11-30 01:23:57 +03:00
|
|
|
self.assertFalse(os.path.exists(self.get_path('some_new_file.txt')))
|
2017-11-08 04:42:43 +03:00
|
|
|
modified_contents = 'Re-create the file with different contents.\n'
|
|
|
|
self.write_file('some_new_file.txt', modified_contents)
|
2017-11-21 02:46:54 +03:00
|
|
|
|
|
|
|
if add_before_updating:
|
|
|
|
self.hg('add', 'some_new_file.txt')
|
|
|
|
self.assert_status({
|
|
|
|
'some_new_file.txt': 'A',
|
|
|
|
})
|
|
|
|
else:
|
|
|
|
self.assert_status({
|
|
|
|
'some_new_file.txt': '?',
|
|
|
|
})
|
2017-11-08 04:42:43 +03:00
|
|
|
|
|
|
|
# Verify `hg next` updates such that the original contents and commit
|
|
|
|
# hash are restored. No conflicts should be reported.
|
|
|
|
path_to_backup = '.hg/origbackups/some_new_file.txt'
|
|
|
|
expected_backup_file = os.path.join(self.mount, path_to_backup)
|
|
|
|
self.assertFalse(os.path.isfile(expected_backup_file))
|
Change how the UNTRACKED_ADDED conflict and merges are handled.
Summary:
Previously, we used the Mercurial code `g` when faced with an `UNTRACKED_ADDED`
file conflict, but that was allowing merges to silently succeed that should not
have. This revision changes our logic to use the code `m` for merge, which
unearthed that we were not honoring the user's `update.check` setting properly.
Because we use `update.check=noconflict` internally at Facebook, we changed the
Eden integration tests to default to verifying Hg running with this setting. To
support it properly, we had to port this code from `update.py` in Mercurial to
our own `_determine_actions_for_conflicts()` function:
```
if updatecheck == 'noconflict':
for f, (m, args, msg) in actionbyfile.iteritems():
if m not in ('g', 'k', 'e', 'r', 'pr'):
msg = _("conflicting changes")
hint = _("commit or update --clean to discard changes")
raise error.Abort(msg, hint=hint)
```
However, this introduced an interesting issue where the `checkOutRevision()`
Thrift call from Hg would update the `SNAPSHOT` file on the server, but
`.hg/dirstate` would not get updated with the new parents until the update
completed on the client. With the new call to `raise error.Abort` on the client,
we could get in a state where the `SNAPSHOT` file had the hash of the commit
assuming the update succeeded, but `.hg/dirstate` reflected the reality where it
failed.
To that end, we changed `checkOutRevision()` to take a new parameter,
`checkoutMode`, which can take on one of three values: `NORMAL`, `DRY_RUN`, and
`FORCE`. Now if the user tries to do an ordinary `hg update` with
`update.check=noconflict`, we first do a `DRY_RUN` and examine the potential
conflicts. Only if the conflicts should not block the update do we proceed with
a call to `checkOutRevision()` in `NORMAL` mode.
To make this work, we had to make a number of changes to `CheckoutAction`,
`CheckoutContext`, `EdenMount`, and `TreeInode` to keep track of the
`checkoutMode` and ensure that no changes are made to the working copy when a
`DRY_RUN` is in effect.
One minor issue (for which there is a `TODO`) is that a `DRY_RUN` will not
report any `DIRECTORY_NOT_EMPTY` conflicts that may exist. As `TreeInode` is
implemented today, it is a bit messy to report this type of conflict without
modifying the working copy along the way.
Finally, any `UNTRACKED_ADDED` conflict should cause an update to
abort to match the behavior in stock Mercurial if the user has the following
config setting:
```
[commands]
update.check = noconflict
```
Though the original name for this setting was:
```
[experimental]
updatecheck = noconflict
```
Although I am on Mercurial 4.4.1, the `update.check` setting does not seem to
take effect when I run the integration tests, but the `updatecheck` setting
does, so for now, I set both in `hg_extension_test_base.py` with a `TODO` to
remove `updatecheck` once I can get `update.check` to do its job.
Reviewed By: simpkins
Differential Revision: D6366007
fbshipit-source-id: bb3ecb1270e77d59d7d9e7baa36ada61971bbc49
2017-11-30 08:38:12 +03:00
|
|
|
with self.assertRaises(hgrepo.HgError) as context:
|
|
|
|
self.repo.update(commit, merge=True)
|
|
|
|
self.assertIn(
|
|
|
|
b'warning: conflicts while merging some_new_file.txt! '
|
|
|
|
b'(edit, then use \'hg resolve --mark\')', context.exception.stderr
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
commit,
|
|
|
|
self.repo.get_head_hash(),
|
|
|
|
msg='Even though we have a merge conflict, '
|
|
|
|
'we should still be at the new commit.'
|
|
|
|
)
|
2018-01-20 01:59:26 +03:00
|
|
|
self.assert_dirstate_empty()
|
Change how the UNTRACKED_ADDED conflict and merges are handled.
Summary:
Previously, we used the Mercurial code `g` when faced with an `UNTRACKED_ADDED`
file conflict, but that was allowing merges to silently succeed that should not
have. This revision changes our logic to use the code `m` for merge, which
unearthed that we were not honoring the user's `update.check` setting properly.
Because we use `update.check=noconflict` internally at Facebook, we changed the
Eden integration tests to default to verifying Hg running with this setting. To
support it properly, we had to port this code from `update.py` in Mercurial to
our own `_determine_actions_for_conflicts()` function:
```
if updatecheck == 'noconflict':
for f, (m, args, msg) in actionbyfile.iteritems():
if m not in ('g', 'k', 'e', 'r', 'pr'):
msg = _("conflicting changes")
hint = _("commit or update --clean to discard changes")
raise error.Abort(msg, hint=hint)
```
However, this introduced an interesting issue where the `checkOutRevision()`
Thrift call from Hg would update the `SNAPSHOT` file on the server, but
`.hg/dirstate` would not get updated with the new parents until the update
completed on the client. With the new call to `raise error.Abort` on the client,
we could get in a state where the `SNAPSHOT` file had the hash of the commit
assuming the update succeeded, but `.hg/dirstate` reflected the reality where it
failed.
To that end, we changed `checkOutRevision()` to take a new parameter,
`checkoutMode`, which can take on one of three values: `NORMAL`, `DRY_RUN`, and
`FORCE`. Now if the user tries to do an ordinary `hg update` with
`update.check=noconflict`, we first do a `DRY_RUN` and examine the potential
conflicts. Only if the conflicts should not block the update do we proceed with
a call to `checkOutRevision()` in `NORMAL` mode.
To make this work, we had to make a number of changes to `CheckoutAction`,
`CheckoutContext`, `EdenMount`, and `TreeInode` to keep track of the
`checkoutMode` and ensure that no changes are made to the working copy when a
`DRY_RUN` is in effect.
One minor issue (for which there is a `TODO`) is that a `DRY_RUN` will not
report any `DIRECTORY_NOT_EMPTY` conflicts that may exist. As `TreeInode` is
implemented today, it is a bit messy to report this type of conflict without
modifying the working copy along the way.
Finally, any `UNTRACKED_ADDED` conflict should cause an update to
abort to match the behavior in stock Mercurial if the user has the following
config setting:
```
[commands]
update.check = noconflict
```
Though the original name for this setting was:
```
[experimental]
updatecheck = noconflict
```
Although I am on Mercurial 4.4.1, the `update.check` setting does not seem to
take effect when I run the integration tests, but the `updatecheck` setting
does, so for now, I set both in `hg_extension_test_base.py` with a `TODO` to
remove `updatecheck` once I can get `update.check` to do its job.
Reviewed By: simpkins
Differential Revision: D6366007
fbshipit-source-id: bb3ecb1270e77d59d7d9e7baa36ada61971bbc49
2017-11-30 08:38:12 +03:00
|
|
|
self.assert_status({
|
|
|
|
'some_new_file.txt': 'M',
|
|
|
|
})
|
|
|
|
merge_contents = dedent(
|
|
|
|
'''\
|
|
|
|
<<<<<<< working copy
|
|
|
|
Re-create the file with different contents.
|
|
|
|
=======
|
|
|
|
Original contents.
|
|
|
|
>>>>>>> destination
|
|
|
|
'''
|
|
|
|
)
|
|
|
|
self.assertEqual(merge_contents, self.read_file('some_new_file.txt'))
|
2017-11-08 04:42:43 +03:00
|
|
|
|
|
|
|
# Verify the previous version of the file was backed up as expected.
|
|
|
|
self.assertTrue(os.path.isfile(expected_backup_file))
|
|
|
|
self.assertEqual(modified_contents, self.read_file(path_to_backup))
|
2017-11-08 05:36:35 +03:00
|
|
|
|
2017-11-30 01:23:57 +03:00
|
|
|
def test_update_modified_file_to_removed_file_taking_other(self) -> None:
|
2017-11-10 03:25:44 +03:00
|
|
|
self.write_file('some_new_file.txt', 'I am new!\n')
|
|
|
|
self.hg('add', 'some_new_file.txt')
|
|
|
|
self.repo.commit('Commit a new file.')
|
|
|
|
self.write_file(
|
|
|
|
'some_new_file.txt', 'Make some changes to that new file.\n'
|
|
|
|
)
|
|
|
|
|
|
|
|
self.hg('update', '.^', '--merge', '--tool', ':other')
|
|
|
|
self.assertFalse(os.path.exists(self.get_path('some_new_file.txt')))
|
|
|
|
self.assertFalse(
|
|
|
|
os.path.isfile(
|
|
|
|
os.path.join(self.mount, '.hg/origbackups/some_new_file.txt')
|
|
|
|
),
|
|
|
|
msg='There should not be a backup file because '
|
|
|
|
':other was specified explicitly.'
|
|
|
|
)
|
|
|
|
|
2017-11-30 01:23:57 +03:00
|
|
|
def test_update_modified_file_to_removed_file_taking_local(self) -> None:
|
New actions that resulted from applyupdates() in merge.py were not getting applied.
Summary:
In the course of verifying a fix for `hg update --merge` in D6270272, I
discovered a new bug in our merge logic in the Python code. As expained in the
test plan, there was a case where a file was listed as "untracked" instead of
"added" after a merge with `--tool :local`.
I traced through what happens in stock Mercurial. After the call to
`applyupdates()` in `update()` in `merge.py`, there is this code:
```
stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
wc.flushall()
if not partial:
with repo.dirstate.parentchange():
repo.setparents(fp1, fp2)
recordupdates(repo, actions, branchmerge)
# update completed, clear state
util.unlink(repo.vfs.join('updatestate'))
if not branchmerge:
repo.dirstate.setbranch(p2.branch())
```
It turns out that `applyupdates()` can have the side-effect of adding new
entries to the `actions` dict. In this case, we have a `'cd'` action for which
an `'am'` action is generated. Our `merge_update()` function in
`eden/hg/eden/__init__.py` did not have the `recordupdates()` call that the
stock implementation of Mercurial does, so the `'am'` (for "add/merge") was not
getting applied.
It seems likely that introducing this `recordupdates()` call may fix other
subtle bugs in Eden's Mercurial extension for which we do not yet have
integration tests.
Reviewed By: wez
Differential Revision: D6279971
fbshipit-source-id: 901c1bc563a7a3910dde18cf2f0d8b8ff9cd6fbe
2017-11-10 03:25:45 +03:00
|
|
|
self.write_file('some_new_file.txt', 'I am new!\n')
|
|
|
|
self.hg('add', 'some_new_file.txt')
|
|
|
|
self.repo.commit('Commit a new file.')
|
|
|
|
new_contents = 'Make some changes to that new file.\n'
|
Change how the UNTRACKED_ADDED conflict and merges are handled.
Summary:
Previously, we used the Mercurial code `g` when faced with an `UNTRACKED_ADDED`
file conflict, but that was allowing merges to silently succeed that should not
have. This revision changes our logic to use the code `m` for merge, which
unearthed that we were not honoring the user's `update.check` setting properly.
Because we use `update.check=noconflict` internally at Facebook, we changed the
Eden integration tests to default to verifying Hg running with this setting. To
support it properly, we had to port this code from `update.py` in Mercurial to
our own `_determine_actions_for_conflicts()` function:
```
if updatecheck == 'noconflict':
for f, (m, args, msg) in actionbyfile.iteritems():
if m not in ('g', 'k', 'e', 'r', 'pr'):
msg = _("conflicting changes")
hint = _("commit or update --clean to discard changes")
raise error.Abort(msg, hint=hint)
```
However, this introduced an interesting issue where the `checkOutRevision()`
Thrift call from Hg would update the `SNAPSHOT` file on the server, but
`.hg/dirstate` would not get updated with the new parents until the update
completed on the client. With the new call to `raise error.Abort` on the client,
we could get in a state where the `SNAPSHOT` file had the hash of the commit
assuming the update succeeded, but `.hg/dirstate` reflected the reality where it
failed.
To that end, we changed `checkOutRevision()` to take a new parameter,
`checkoutMode`, which can take on one of three values: `NORMAL`, `DRY_RUN`, and
`FORCE`. Now if the user tries to do an ordinary `hg update` with
`update.check=noconflict`, we first do a `DRY_RUN` and examine the potential
conflicts. Only if the conflicts should not block the update do we proceed with
a call to `checkOutRevision()` in `NORMAL` mode.
To make this work, we had to make a number of changes to `CheckoutAction`,
`CheckoutContext`, `EdenMount`, and `TreeInode` to keep track of the
`checkoutMode` and ensure that no changes are made to the working copy when a
`DRY_RUN` is in effect.
One minor issue (for which there is a `TODO`) is that a `DRY_RUN` will not
report any `DIRECTORY_NOT_EMPTY` conflicts that may exist. As `TreeInode` is
implemented today, it is a bit messy to report this type of conflict without
modifying the working copy along the way.
Finally, any `UNTRACKED_ADDED` conflict should cause an update to
abort to match the behavior in stock Mercurial if the user has the following
config setting:
```
[commands]
update.check = noconflict
```
Though the original name for this setting was:
```
[experimental]
updatecheck = noconflict
```
Although I am on Mercurial 4.4.1, the `update.check` setting does not seem to
take effect when I run the integration tests, but the `updatecheck` setting
does, so for now, I set both in `hg_extension_test_base.py` with a `TODO` to
remove `updatecheck` once I can get `update.check` to do its job.
Reviewed By: simpkins
Differential Revision: D6366007
fbshipit-source-id: bb3ecb1270e77d59d7d9e7baa36ada61971bbc49
2017-11-30 08:38:12 +03:00
|
|
|
self.write_file('some_new_file.txt', new_contents)
|
New actions that resulted from applyupdates() in merge.py were not getting applied.
Summary:
In the course of verifying a fix for `hg update --merge` in D6270272, I
discovered a new bug in our merge logic in the Python code. As expained in the
test plan, there was a case where a file was listed as "untracked" instead of
"added" after a merge with `--tool :local`.
I traced through what happens in stock Mercurial. After the call to
`applyupdates()` in `update()` in `merge.py`, there is this code:
```
stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
wc.flushall()
if not partial:
with repo.dirstate.parentchange():
repo.setparents(fp1, fp2)
recordupdates(repo, actions, branchmerge)
# update completed, clear state
util.unlink(repo.vfs.join('updatestate'))
if not branchmerge:
repo.dirstate.setbranch(p2.branch())
```
It turns out that `applyupdates()` can have the side-effect of adding new
entries to the `actions` dict. In this case, we have a `'cd'` action for which
an `'am'` action is generated. Our `merge_update()` function in
`eden/hg/eden/__init__.py` did not have the `recordupdates()` call that the
stock implementation of Mercurial does, so the `'am'` (for "add/merge") was not
getting applied.
It seems likely that introducing this `recordupdates()` call may fix other
subtle bugs in Eden's Mercurial extension for which we do not yet have
integration tests.
Reviewed By: wez
Differential Revision: D6279971
fbshipit-source-id: 901c1bc563a7a3910dde18cf2f0d8b8ff9cd6fbe
2017-11-10 03:25:45 +03:00
|
|
|
|
|
|
|
self.hg('update', '.^', '--merge', '--tool', ':local')
|
|
|
|
self.assertEqual(new_contents, self.read_file('some_new_file.txt'))
|
|
|
|
self.assert_status({'some_new_file.txt': 'A'})
|
|
|
|
|
2017-11-30 01:23:57 +03:00
|
|
|
def test_update_ignores_untracked_directory(self) -> None:
|
Change how the UNTRACKED_ADDED conflict and merges are handled.
Summary:
Previously, we used the Mercurial code `g` when faced with an `UNTRACKED_ADDED`
file conflict, but that was allowing merges to silently succeed that should not
have. This revision changes our logic to use the code `m` for merge, which
unearthed that we were not honoring the user's `update.check` setting properly.
Because we use `update.check=noconflict` internally at Facebook, we changed the
Eden integration tests to default to verifying Hg running with this setting. To
support it properly, we had to port this code from `update.py` in Mercurial to
our own `_determine_actions_for_conflicts()` function:
```
if updatecheck == 'noconflict':
for f, (m, args, msg) in actionbyfile.iteritems():
if m not in ('g', 'k', 'e', 'r', 'pr'):
msg = _("conflicting changes")
hint = _("commit or update --clean to discard changes")
raise error.Abort(msg, hint=hint)
```
However, this introduced an interesting issue where the `checkOutRevision()`
Thrift call from Hg would update the `SNAPSHOT` file on the server, but
`.hg/dirstate` would not get updated with the new parents until the update
completed on the client. With the new call to `raise error.Abort` on the client,
we could get in a state where the `SNAPSHOT` file had the hash of the commit
assuming the update succeeded, but `.hg/dirstate` reflected the reality where it
failed.
To that end, we changed `checkOutRevision()` to take a new parameter,
`checkoutMode`, which can take on one of three values: `NORMAL`, `DRY_RUN`, and
`FORCE`. Now if the user tries to do an ordinary `hg update` with
`update.check=noconflict`, we first do a `DRY_RUN` and examine the potential
conflicts. Only if the conflicts should not block the update do we proceed with
a call to `checkOutRevision()` in `NORMAL` mode.
To make this work, we had to make a number of changes to `CheckoutAction`,
`CheckoutContext`, `EdenMount`, and `TreeInode` to keep track of the
`checkoutMode` and ensure that no changes are made to the working copy when a
`DRY_RUN` is in effect.
One minor issue (for which there is a `TODO`) is that a `DRY_RUN` will not
report any `DIRECTORY_NOT_EMPTY` conflicts that may exist. As `TreeInode` is
implemented today, it is a bit messy to report this type of conflict without
modifying the working copy along the way.
Finally, any `UNTRACKED_ADDED` conflict should cause an update to
abort to match the behavior in stock Mercurial if the user has the following
config setting:
```
[commands]
update.check = noconflict
```
Though the original name for this setting was:
```
[experimental]
updatecheck = noconflict
```
Although I am on Mercurial 4.4.1, the `update.check` setting does not seem to
take effect when I run the integration tests, but the `updatecheck` setting
does, so for now, I set both in `hg_extension_test_base.py` with a `TODO` to
remove `updatecheck` once I can get `update.check` to do its job.
Reviewed By: simpkins
Differential Revision: D6366007
fbshipit-source-id: bb3ecb1270e77d59d7d9e7baa36ada61971bbc49
2017-11-30 08:38:12 +03:00
|
|
|
base_commit = self.repo.get_head_hash()
|
2017-11-08 05:36:35 +03:00
|
|
|
self.mkdir('foo/bar')
|
|
|
|
self.write_file('foo/bar/a.txt', 'File in directory two levels deep.\n')
|
|
|
|
self.write_file('foo/bar/b.txt', 'Another file.\n')
|
|
|
|
self.hg('add', 'foo/bar/a.txt')
|
|
|
|
self.assert_status({
|
|
|
|
'foo/bar/a.txt': 'A',
|
|
|
|
'foo/bar/b.txt': '?',
|
|
|
|
})
|
|
|
|
self.repo.commit('Commit only a.txt.')
|
|
|
|
self.assert_status({
|
|
|
|
'foo/bar/b.txt': '?',
|
|
|
|
})
|
Change how the UNTRACKED_ADDED conflict and merges are handled.
Summary:
Previously, we used the Mercurial code `g` when faced with an `UNTRACKED_ADDED`
file conflict, but that was allowing merges to silently succeed that should not
have. This revision changes our logic to use the code `m` for merge, which
unearthed that we were not honoring the user's `update.check` setting properly.
Because we use `update.check=noconflict` internally at Facebook, we changed the
Eden integration tests to default to verifying Hg running with this setting. To
support it properly, we had to port this code from `update.py` in Mercurial to
our own `_determine_actions_for_conflicts()` function:
```
if updatecheck == 'noconflict':
for f, (m, args, msg) in actionbyfile.iteritems():
if m not in ('g', 'k', 'e', 'r', 'pr'):
msg = _("conflicting changes")
hint = _("commit or update --clean to discard changes")
raise error.Abort(msg, hint=hint)
```
However, this introduced an interesting issue where the `checkOutRevision()`
Thrift call from Hg would update the `SNAPSHOT` file on the server, but
`.hg/dirstate` would not get updated with the new parents until the update
completed on the client. With the new call to `raise error.Abort` on the client,
we could get in a state where the `SNAPSHOT` file had the hash of the commit
assuming the update succeeded, but `.hg/dirstate` reflected the reality where it
failed.
To that end, we changed `checkOutRevision()` to take a new parameter,
`checkoutMode`, which can take on one of three values: `NORMAL`, `DRY_RUN`, and
`FORCE`. Now if the user tries to do an ordinary `hg update` with
`update.check=noconflict`, we first do a `DRY_RUN` and examine the potential
conflicts. Only if the conflicts should not block the update do we proceed with
a call to `checkOutRevision()` in `NORMAL` mode.
To make this work, we had to make a number of changes to `CheckoutAction`,
`CheckoutContext`, `EdenMount`, and `TreeInode` to keep track of the
`checkoutMode` and ensure that no changes are made to the working copy when a
`DRY_RUN` is in effect.
One minor issue (for which there is a `TODO`) is that a `DRY_RUN` will not
report any `DIRECTORY_NOT_EMPTY` conflicts that may exist. As `TreeInode` is
implemented today, it is a bit messy to report this type of conflict without
modifying the working copy along the way.
Finally, any `UNTRACKED_ADDED` conflict should cause an update to
abort to match the behavior in stock Mercurial if the user has the following
config setting:
```
[commands]
update.check = noconflict
```
Though the original name for this setting was:
```
[experimental]
updatecheck = noconflict
```
Although I am on Mercurial 4.4.1, the `update.check` setting does not seem to
take effect when I run the integration tests, but the `updatecheck` setting
does, so for now, I set both in `hg_extension_test_base.py` with a `TODO` to
remove `updatecheck` once I can get `update.check` to do its job.
Reviewed By: simpkins
Differential Revision: D6366007
fbshipit-source-id: bb3ecb1270e77d59d7d9e7baa36ada61971bbc49
2017-11-30 08:38:12 +03:00
|
|
|
self.repo.update(base_commit)
|
2017-11-08 05:36:35 +03:00
|
|
|
self.assert_status({
|
|
|
|
'foo/bar/b.txt': '?',
|
|
|
|
})
|
|
|
|
self.assertFalse(os.path.exists(self.get_path('foo/bar/a.txt')))
|
|
|
|
self.assertTrue(os.path.exists(self.get_path('foo/bar/b.txt')))
|