You can get into trouble if you commit, update back to an older
changeset, and then rollback. The update removes your valuable changes
from the working dir, then rollback removes them history. Oops: you've
just irretrievably lost data running nothing but core Mercurial
commands. (More subtly: rollback from a shared clone that was already
at an older changeset -- no update required, just rollback from the
wrong directory.)
The fix assumes that only "commit" transactions have irreplaceable
data, and allows rolling back non-commit transactions as always. But
when rolling back a commit, check that the working dir is checked out
to tip, i.e. the changeset we're about to destroy. If not, abort. You
can get back the old (dangerous) behaviour with --force.
If the working dir parent was destroyed by rollback, then the old
behaviour is perfectly reasonable: restore dirstate, branch, and
bookmarks. That way the working dir moves back to an existing
changeset rather than becoming an orphan.
But if the working dir parent was unaffected -- say, you updated to an
older changeset and then did rollback -- then it's silly to restore
dirstate and branch. So don't do that. Leave the status of the working
dir alone. (But always restore bookmarks, because that file refers to
changeset IDs that may have been destroyed.)
- clarify how we parse undo.desc
- fix bad grammar in an error message
- factor out ui local
- rename some local variables
- standardize string quoting
This is extremely handy for those occasional circumstances where you
need to edit .hg/sharedpath manually, since modern Unix text editors
make it surprisingly difficult to create a text file with no trailing
newline.
The usual contract is that close() makes your writes permanent, so
atomictempfile's use of close() to *discard* writes (and rename() to
keep them) is rather unexpected. Thus, change it so close() makes
things permanent and add a new discard() method to throw them away.
discard() is only used internally, in __del__(), to ensure that writes
are discarded when an atomictempfile object goes out of scope.
I audited mercurial.*, hgext.*, and ~80 third-party extensions, and
found no one using the existing semantics of close() to discard
writes, so this should be safe.
We refresh the stat info when releasing repo.wlock(), right after writing it.
Also, invalidate the dirstate by deleting its attribute. This will force a
stat by the decorator that actually checks if anything changed, rather than
reading it again every time.
Note that prior to this, there was a single dirstate instance created for a
localrepo. It was invalidated by calling dirstate.invalidated(), clearing
its internal attributes.
As a consequence, the following construct is no longer safe:
ds = repo.dirstate # keep a reference to the repo's dirstate
wlock = repo.wlock()
try:
ds.setparents(...)
finally:
wlock.release() # dirstate should be written here
Since it's possible that the dirstate was modified between lines #1 and #2,
therefore changes to the old dirstate won't get written when the lock releases,
because a new instance was created by the decorator.
util is never imported by any other name than util, so this is mostly just a
simple search and replace from util.localpath to util.urllocalpath (assuming
other uses of util.localpath already has been renamed).
It has substantially different semantics from forget at the command
layer, so change it to avoid confusion.
We can't simply combine it with remove because we need to explicitly
drop non-added files in some cases like commit.
This greatly improves the speed of the bundling process, and often reduces the
bundle size considerably. (Although if the repository is already ordered, this
has little effect on both time and bundle size.)
For non-generaldelta clients, the reduced bundle size translates to a reduced
repository size, similar to shrinking the revlogs (which uses the exact same
algorithm). For generaldelta clients the difference is minor.
When the new bundle format comes, reordering will not be necessary since we
can then store the deltaparent relationsships directly. The eventual default
behavior for clients and servers is presented in the table below, where "new"
implies support for GD as well as the new bundle format:
old client new client
old server old bundle, no reorder old bundle, no reorder
new server, non-GD old bundle, no reorder[1] old bundle, no reorder[2]
new server, GD old bundle, reorder[3] new bundle, no reorder[4]
[1] reordering is expensive on the server in this case, skip it
[2] client can choose to do its own redelta here
[3] reordering is needed because otherwise the pull does a lot of extra
work on the server
[4] reordering isn't needed because client can get deltabase in bundle
format
Currently, the default is to reorder on GD-servers, and not otherwise. A new
setting, bundle.reorder, has been added to override the default reordering
behavior. It can be set to either 'auto' (the default), or any true or false
value as a standard boolean setting, to either force the reordering on or off
regardless of generaldelta.
Some timing data from a relatively branch test repository follows. All
bundling is done with --all --type none options.
Non-generaldelta, non-shrunk repo:
-----------------------------------
Size: 276M
Without reorder (default):
Bundle time: 14.4 seconds
Bundle size: 939M
With reorder:
Bundle time: 1 minute, 29.3 seconds
Bundle size: 381M
Generaldelta, non-shrunk repo:
-----------------------------------
Size: 87M
Without reorder:
Bundle time: 2 minutes, 1.4 seconds
Bundle size: 939M
With reorder (default):
Bundle time: 25.5 seconds
Bundle size: 381M
defversion was a property (later option) on the store opener, used to propagate
the changelog revlog format to the other revlogs, so they would be created with
the same format.
This required that the changelog instance was created before any other revlog;
an invariant that wasn't directly enforced (or documented) anywhere.
We now use the revlogv1 requirement instead, which is transfered to the store
opener options. If this option is missing, v0 revlogs are created.
With generaldelta switched on, deltas are always computed against the first
parent when adding revisions. This is done regardless of what revision the
incoming bundle, if any, is deltaed against.
The exact delta building strategy is subject to change, but this will not
affect compatibility.
Generaldelta is switched off by default.
The Mercurial 1.9 release is moving a lot of stuff around anyway and we are
already moving path_auditor from util.py to scmutil.py for that release.
So this seems like a good opportunity to do such a rename. It also strengthens
the current project policy to avoid underbars in names.
Before this patch undo.bookmarks was created on bookmarks write and
not with other transaction-related files. There were two issues: first
is that if you have changed bookmarks few times after a transaction
happened, rollback will give you a state which can point to
non-existing revision. Second is that if you have not changed
bookmarks after a transaction, rollback will touch your state anyway.
This change also adds `localrepo._writejournal` method, which can be
used by other extensions to save their transaction-related backup in
right time.
This improves the misleading error message
$ hg identify
abort: there is no Mercurial repository here (.hg not found)!
to the more explicit
$ hg identify
abort: requirement 'fake' not supported!
for all commands in commands.optionalrepo, which includes the identify
and serve commands in particular.
This is for the case when a new entry in .hg/requires will be defined
in a future Mercurial release.
Previously, when rolling back a transaction, some users could be confused
between the level to which the store is rolled back, and the new parents
of the working directory.
$ hg rollback
rolling back to revision 4 (undo commit)
With this change:
$ hg rollback
repository tip rolled back to tip revision 4 (undo commit)
working directory now based on revision 2 and 1
So now the user can realize that the store has been rolled back to an older
tip, but also that the working directory may not on the tip (here we are
rolling back the merge of the heads 2 and 1)
The default behaviour is to commit subrepositories with uncommitted changes. In
my experience this is usually undesirable:
- Changes to dependencies are often debugging leftovers
- Real changes should generally be applied on the source project directly,
tested then committed. This is not always possible, subversion subrepos may
include only a small part of the source project, without the tests.
Setting ui.commitsubrepos=no will now abort commits containing such modified
subrepositories like:
$ hg --config ui.commitsubrepos=no ci -m msg
abort: uncommitted changes in subrepo sub
I ruled out the hook solution because it does not easily take --include/exclude
options in account. Also, my main concern is whether this flag could cause
problems with extensions. If there are legitimate reasons for callers to
override this behaviour (I could not find any), they might either override at ui
level, or we could add an argument to localrepo.commit() later.
v2:
- Renamed ui.commitsubs to ui.commitsubrepos
- Mention the configuration entry in hg help subrepos