Mercurial > hg
view tests/test-copy.t @ 39764:e4e881572382
localrepo: iteratively derive local repository type
This commit implements the dynamic local repository type derivation
that was explained in the recent commit
bfeab472e3c0 "localrepo: create new function for instantiating a local
repo object."
Instead of a static localrepository class/type which must be customized
after construction, we now dynamically construct a type by building up
base classes/types to represent specific repository interfaces.
Conceptually, the end state is similar to what was happening when
various extensions would monkeypatch the __class__ of newly-constructed
repo instances. However, the approach is inverted. Instead of making
the instance then customizing it, we do the customization up front
by influencing the behavior of the type then we instantiate that
custom type.
This approach gives us much more flexibility. For example, we can
use completely separate classes for implementing different aspects
of the repository. For example, we could have one class representing
revlog-based file storage and another representing non-revlog based
file storage. When then choose which implementation to use based on
the presence of repo requirements.
A concern with this approach is that it creates a lot more types
and complexity and that complexity adds overhead. Yes, it is true that
this approach will result in more types being created. Yes, this is
more complicated than traditional "instantiate a static type." However,
I believe the alternatives to supporting alternate storage backends
are just as complicated. (Before I arrived at this solution, I had
patches storing factory functions on local repo instances for e.g.
constructing a file storage instance. We ended up having a handful
of these. And this was logically identical to assigning custom
methods. Since we were logically changing the type of the instance,
I figured it would be better to just use specialized types instead
of introducing levels of abstraction at run-time.)
On the performance front, I don't believe that having N base classes
has any significant performance overhead compared to just a single base
class. Intuition says that Python will need to iterate the base classes
to find an attribute. However, CPython caches method lookups: as long as
the __class__ or MRO isn't changing, method attribute lookup should be
constant time after first access. And non-method attributes are stored
in __dict__, of which there is only 1 per object, so the number of
base classes for __dict__ is irrelevant.
Anyway, this commit splits up the monolithic completelocalrepository
interface into sub-interfaces: 1 for file storage and 1 representing
everything else.
We've taught ``makelocalrepository()`` to call a series of factory
functions which will produce types implementing specific interfaces.
It then calls type() to create a new type from the built-up list of
base types.
This commit should be considered a start and not the end state. I
suspect we'll hit a number of problems as we start to implement
alternate storage backends:
* Passing custom arguments to __init__ and setting custom attributes
on __dict__.
* Customizing the set of interfaces that are needed. e.g. the
"readonly" intent could translate to not requesting an interface
providing methods related to writing.
* More ergonomic way for extensions to insert themselves so their
callbacks aren't unconditionally called.
* Wanting to modify vfs instances, other arguments passed to __init__.
That being said, this code is usable in its current state and I'm
convinced future commits will demonstrate the value in this approach.
Differential Revision: https://phab.mercurial-scm.org/D4642
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Tue, 18 Sep 2018 15:29:42 -0700 |
parents | f1186c292d03 |
children | e41449818bee |
line wrap: on
line source
$ mkdir part1 $ cd part1 $ hg init $ echo a > a $ hg add a $ hg commit -m "1" $ hg status $ hg copy a b $ hg --config ui.portablefilenames=abort copy a con.xml abort: filename contains 'con', which is reserved on Windows: con.xml [255] $ hg status A b $ hg sum parent: 0:c19d34741b0a tip 1 branch: default commit: 1 copied update: (current) phases: 1 draft $ hg --debug commit -m "2" committing files: b b: copy a:b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 committing manifest committing changelog updating the branch cache committed changeset 1:93580a2c28a50a56f63526fb305067e6fbf739c4 we should see two history entries $ hg history -v changeset: 1:93580a2c28a5 tag: tip user: test date: Thu Jan 01 00:00:00 1970 +0000 files: b description: 2 changeset: 0:c19d34741b0a user: test date: Thu Jan 01 00:00:00 1970 +0000 files: a description: 1 we should see one log entry for a $ hg log a changeset: 0:c19d34741b0a user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: 1 this should show a revision linked to changeset 0 $ hg debugindex a rev linkrev nodeid p1 p2 0 0 b789fdd96dc2 000000000000 000000000000 we should see one log entry for b $ hg log b changeset: 1:93580a2c28a5 tag: tip user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: 2 this should show a revision linked to changeset 1 $ hg debugindex b rev linkrev nodeid p1 p2 0 1 37d9b5d994ea 000000000000 000000000000 this should show the rename information in the metadata $ hg debugdata b 0 | head -3 | tail -2 copy: a copyrev: b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 #if reporevlogstore $ md5sum.py .hg/store/data/b.i 44913824c8f5890ae218f9829535922e .hg/store/data/b.i #endif $ hg cat b > bsum $ md5sum.py bsum 60b725f10c9c85c70d97880dfe8191b3 bsum $ hg cat a > asum $ md5sum.py asum 60b725f10c9c85c70d97880dfe8191b3 asum $ hg verify checking changesets checking manifests crosschecking files in changesets and manifests checking files checked 2 changesets with 2 changes to 2 files $ cd .. $ mkdir part2 $ cd part2 $ hg init $ echo foo > foo should fail - foo is not managed $ hg mv foo bar foo: not copying - file is not managed abort: no files to copy [255] $ hg st -A ? foo $ hg add foo dry-run; print a warning that this is not a real copy; foo is added $ hg mv --dry-run foo bar foo has not been committed yet, so no copy data will be stored for bar. $ hg st -A A foo should print a warning that this is not a real copy; bar is added $ hg mv foo bar foo has not been committed yet, so no copy data will be stored for bar. $ hg st -A A bar should print a warning that this is not a real copy; foo is added $ hg cp bar foo bar has not been committed yet, so no copy data will be stored for foo. $ hg rm -f bar $ rm bar $ hg st -A A foo $ hg commit -m1 moving a missing file $ rm foo $ hg mv foo foo3 foo: deleted in working directory foo3 does not exist! $ hg up -qC . copy --after to a nonexistent target filename $ hg cp -A foo dummy foo: not recording copy - dummy does not exist [1] dry-run; should show that foo is clean $ hg copy --dry-run foo bar $ hg st -A C foo should show copy $ hg copy foo bar $ hg st -C A bar foo shouldn't show copy $ hg commit -m2 $ hg st -C should match $ hg debugindex foo rev linkrev nodeid p1 p2 0 0 2ed2a3912a0b 000000000000 000000000000 $ hg debugrename bar bar renamed from foo:2ed2a3912a0b24502043eae84ee4b279c18b90dd $ echo bleah > foo $ echo quux > bar $ hg commit -m3 should not be renamed $ hg debugrename bar bar not renamed $ hg copy -f foo bar should show copy $ hg st -C M bar foo XXX: filtering lfilesrepo.status() in 3.3-rc causes the copy source to not be displayed. $ hg st -C --config extensions.largefiles= The fsmonitor extension is incompatible with the largefiles extension and has been disabled. (fsmonitor !) M bar foo $ hg commit -m3 should show no parents for tip $ hg debugindex bar rev linkrev nodeid p1 p2 0 1 7711d36246cc 000000000000 000000000000 1 2 bdf70a2b8d03 7711d36246cc 000000000000 2 3 b2558327ea8d 000000000000 000000000000 should match $ hg debugindex foo rev linkrev nodeid p1 p2 0 0 2ed2a3912a0b 000000000000 000000000000 1 2 dd12c926cf16 2ed2a3912a0b 000000000000 $ hg debugrename bar bar renamed from foo:dd12c926cf165e3eb4cf87b084955cb617221c17 should show no copies $ hg st -C copy --after on an added file $ cp bar baz $ hg add baz $ hg cp -A bar baz $ hg st -C A baz bar foo was clean: $ hg st -AC foo C foo Trying to copy on top of an existing file fails, $ hg copy -A bar foo foo: not overwriting - file already committed ('hg copy --after --force' to replace the file by recording a copy) [1] same error without the --after, so the user doesn't have to go through two hints: $ hg copy bar foo foo: not overwriting - file already committed ('hg copy --force' to replace the file by recording a copy) [1] but it's considered modified after a copy --after --force $ hg copy -Af bar foo $ hg st -AC foo M foo bar The hint for a file that exists but is not in file history doesn't mention --force: $ touch xyzzy $ hg cp bar xyzzy xyzzy: not overwriting - file exists ('hg copy --after' to record the copy) [1] $ cd ..