The fix introduced in 3509b9cf8f86 was only partially successful. It is correct
to turn dirstate 'm' merge records into normal/dirty ones but copy records are
lost in the process. To adjust them as well, we need to look in the first
parent manifest to know which files were added and preserve only related
records. But the dirstate does not have access to changesets, the logic has to
moved at another level, in localrepo.
'hg qnew' passes matching object to 'patch.diff()' to specify target
filenames, and it causes 'dirstate.walk()' via 'repo.status()' in
'patch.diff()'.
but target files are already known before 'patch.diff()' invocation.
to avoid useless 'dirstate.walk()' invocation, this patch uses
'changes' argument to pass already known target files to
'patch.diff()' instead of 'match' argument.
'changes' argument of 'patch.diff()' should have lists for modified,
added and removed files separately, so this patch saves status of
'.hgsubstate' before commit, and put it into appropriate list in
'changes'.
'hg qnew' with pattern/-I/-X creates matching object with them, and
uses it twice for 'dirstate.walk()': via 'repo.status()' and
'repo.commit()'.
this may cause full manifest scan in the second 'dirstate.walk()',
even though mq already knows complete target filenames at the first
'dirstate.walk()'.
this patch creates exact matching object also in this case, and use it
at 'repo.commit()' invocation to avoid full manifest scan in the
second 'dirstate.walk()'.
even though 'inclsubs' is added to 'pats' for original matching
object, it is also passed to exact matching object, because
subrepositories are deleted from result of 'dirstate.walk()' at the
end of it.
The 'start' variable pointed to qtip in self.series, but it was used for
indexing self.fullseries.
When fullseries had holes and the patch was moved to self.fullseries[start]
it would end up too early in self.series and it could thus not be
found in self.series[start:] and it would crash.
Now the 'fullstart' index in fullseries is found used instead.
In secret mode qfinished changeset were move to the draft phase in all case[1]
without regard to phases.new-commit value
This changeset ensure qfinish does not automatically promote a changeset
further than the phases.new-commit value.
Note: This may also result in qfinished changeset made public if
phases.new-commit is set to public.
[1] "In all case" where parent have a compatible phase. Qfinish keep never
altering phases of changeset not involved in the qfinish.
-n could be confused for --dry-run by foolhardy users, resulting in
permanent data loss.
As leaving a backup when none was requested is significantly less
disastrous, the short option is silently ignored. Old scripts continue
to work, users only get lightly burned.
Originally, mq.strip called repair.strip a single rev at a time.
repair.strip stores in a backup bundle any revision greater than
the revision being stripped, strips, then restores the backup with
repo.addchangegroup. So, when stripping revisions on more than one
topological branch, some could end up being restored from the backup
bundle, only to be later removed by a subsequent repair.strip call.
But repo.addchangegroup calls hooks for all those restore operations.
And 1671d21e8e41 changed it to delay all hook calls until the
repository lock were released - by mq.strip, after stripping all
revisions. Thus, the hooks could be called over revisions already
removed from the repository at that point.
By generating the revision lists at once inside repo.strip, we avoid
calling addchangegroup for temporary restores. Incidentally, this
also avoids creating many backup files for a single strip command.
Since c06eb45e85a7, mq saves the nodeid of the first applied patch to
cache/branchheads, which breaks the optimized cache handling introduced in
1808e27e1362. The problem is the revision being committed is appended to
mqrepo.applied after the commit succeeds, which means mqrepo._branchtags()
performs a regular update and write the first applied patch to the branch
cache.
One solution is to set a context variable _committingpatch on the mqrepo while
it is committing a patch and to take it in account when deciding to fast-path
mqrepo._branchtags(). Not really elegant but it works.
The changes to test-mq-caches.t reverse changes introduced by c06eb45e85a7. The
cache should not have been updated with mq records.
The changes to test-keyword.t are indirectly caused by c06eb45e85a7.
Reported and analyzed by Yuya Nishihara <yuya@tcha.org>
Notes:
- qpush still makes a slow path _branchtags() call when checking heads. Maybe
this can be optimized.
- be careful when merging this patch in default as secretcommit() was renamed
newcommit() right after the end of the code freeze.
The current behaviour is to return the previous one in the series but at the
same time the implementation is buggy because it does not take guarded patches
in account.
This was breaking my remotebranches extension in a completely
mystifying way, because repo.lookup was failing to resolve the
statusentry. I'm not sure how this works absent my remotebranches
extension, but doing it this way looks more correct and doesn't break
anything.
When mq changeset are secret, they don't appear in outgoing and won't be
pushed. So it's not necessary to abort the push.
The checkpush call is protected by lock to prevent race on phase.
As mq automatically sets changesets as secret, it should make them draft when he
is done with it. We do not move them automatically to draft when we detect that
something else have also set them as secret through their parents.
Before the code optimistically relied on savedirty not being called a cancelled
transaction. If it was called it could save incorrect data.
Instead we now start using the invalidate method introduced in 469ecb6e5f24.
It wasn't obvious from the code how qsave mocked around with .hg/patches and
.hg/patches.? and what was going on.
This makes it more explicit so it will survive future refactorings.
When renaming a patch A as B where B was previously qfolded into A and
therefore marked as removed, a versioned MQ would first restore B before
marking it as a copy of A, thus losing A changes. The undelete() call is
probably a left-over, wctx.copy() explicitely handles the case where the
destination is removed.
Also note that status command represents "hg rm b; hg mv a b" as:
A b
a
R a
which explains the first hunk in test-mq-qrename.t.
Patch specifications in mq is passed around as a string or None. None is
generally used when no patch has been specified and there thus is nothing to
lookup and the calling code should do something else. One code path did however
pass None all the way to lookup. That case was handled in lookup, but there was
really need for that, it was undocumented, and it used to cause trouble back
when patches was specified as integers.
Most of the code paths in mq would always pass patch specifications as a
string. Patches can be specified by their index, but one code path passed that
(through pop) to lookup as an integer - all other code paths used a string.
Unfortunately pop and lookup (like many other parts of mq) used the boolean
value of the patch specification to see if it was None, and they would thus
incorrectly handle patch 0 as None.
This patch makes the code comply with the actual internal duck typing of patch
specifications: patch indices must be encoded as strings. The (now) unused code
for partial and thus incorrect handling of indices as integers is removed.
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.
The new behavior was breaking existing tools that relied on a sequence such as
this:
1) start with a dirty working copy
2) qimport some patch
3) try to qpush it
4) old behavior would fail at this point due to outstanding changes.
(new behavior would only fail if the outstanding changes and the patches
changes intersect)
5) innocent user qrefreshes, gets his local changes in the imported patch
It's worth considering if we can move this behavior to -f in the future.
The most appropriate context is not always clearly defined. The obvious cases:
For working directory commands, we use None
For commands (eg annotate) with single revs, we use that revision
The less obvious cases:
For commands (eg status, diff) with a pair of revs, we use the second revision
For commands that take a range (like log), we use None
Because q.refresh() changes nodeid, .hg/patches/status gets invalid until
q.savedirty(). This patch changes mq not to unlock repository of incomplete
state.
The internal list representation of guards was leaking into the
output. The guards were always printed using repr(guard) and that
style was kept.
When "hg qguard -l" prints several guards for a patch, it does so by
joining the names with " " and that style was used for the error
messages too.
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.
When trying to import a url that ends with a slash
os.path.basename() would return an empty string. So
when seeing a leading slash, remove it and infer the
patch name from the remaining url.
e.g. `hg qimport http://paste.pocoo.org/raw/xxx/`
deduced '.' to be the patch name. Now we'll deduce 'xxx'.
These leaks may occur in environments that don't employ a reference
counting GC, i.e. PyPy.
This implies:
- changing opener(...).read() calls to opener.read(...)
- changing opener(...).write() calls to opener.write(...)
- changing open(...).read(...) to util.readfile(...)
- changing open(...).write(...) to util.writefile(...)
When mq status entry referencing a patches that is not in series `hg qfinish
-a` used to issue a traceback. This states is inconsistent but might happen
regularly when people misuse hg up -mq.
This changeset prevent hg from crashing. The faulty entry is finished anyway and
a warning is issued.
This patch prevent mq to crash when .hg/patches/status contains Malformed lines
(without ":"). Blank lines are ignored and other malformed lines issue a
warning.
Add missing calls to close() to many places where files are
opened. Relying on reference counting to catch them soon-ish is not
portable and fails in environments with a proper GC, such as PyPy.
We can't assume that all pushable patches early in the series have already been
applied. If a hg qselect is done while you already have patches applied, some
patches with guards may now be pushable, even though they come earlier in the
series.
So instead of checking only applied patches, explicitly check where we are in
the series against the position of the patch we want to qpush to.
Some extensions (e.g. hgsubversion) completely override push command. Because
extensions load order is unspecified, if hgsubversion loads before mq, mq
checks about not pushing applied patches will be bypassed. Short of finding a
way to fix load order, extracting the checking logic will allow
hgsubversion-like extensions to run the check themselves.
* parse branch and nodeid header lines
* remember the line number where diffs started
Combined, these make mq.patchheader() very useful for parsing and
preserving a patch header through edits. TortoiseHg will use the
nodeid and parent to display these header datums in the graph when
patches are unapplied, and uses diffstartline to parse patch files
using record.parsepatch().
This patch prevents MQ from creating an inconsistent subrepo state. If
the .hgsub file has been changed, and none of the subrepos have
uncommitted changes, creating or updating a patch (using qnew, qrefresh,
or qrecord) will update .hgsubstate accordingly.
If any subrepos _do_ have uncommitted changes, qnew/qrefresh/qrecord
will abort.
Thanks to pmezard for proposing this solution.
If MQ allows modifying .hgsub or .hgsubstate in a patch, it can easily
lead to an inconsistent subrepo state. This patch prevents qrefresh from
adding any modifications to .hgsub or .hgsubstate to a patch. The user
is warned that these files are not included in the patch.
The tests test both the slightly irrational and the pathological cases.
This patch adds an '--exact/-e' option to qpush that will try to push the
patches in the correct location in the DAG. Specifying this option does the
following:
* If --move is specified, abort. It makes no sense to move a patch to the front
of the queue and try to apply it to its parent, because its parent is one of
the patches we just moved it in front of!
* If patches are already applied, abort. We don't want patch changesets
scattered throughout the DAG.
* If local changes are present, abort unless --force is used, as usual.
* Find the first patch we're going to push (if we're pushing multiple patches
with a target or --all).
* If that patch doesn't have a parent, abort, obviously.
* If the parent doesn't exist in the repo, abort. Something is wrong.
* Update to the parent, then continue pushing the patches as normal.
The code eventually converts data through sets to ensure unicity:
do it earlier to allow faster __contains__ lookups and avoid
`del l[l.index(x)]` kind of code.
When using a versioned patch repository, you would get a spurious
warning when deleting and adding the same patch.
Before:
$ hg qdelete --keep 3.diff
$ hg qimport --existing 3.diff
adding 3.diff to series file
3.diff already tracked!
After:
$ hg qdelete --keep 3.diff
$ hg qimport --existing 3.diff
adding 3.diff to series file
Some callers to patchheader.qseries returned the value
further, but pathcheader.qseries does not explicitly
return anything. It was confusing this was returned
further
--force was not necessary when passing --rev since cb494d252290, but this
behaviour is usually harmful when branch names are passed instead of explicit
revisions.
Changeset 1e63d4dee4f6 deprecated qsave and qrestore. In the
deprecating comment, users were referred to 'rebase --mq' which -- at
the time the message was written -- didn't exist. Currently, on the
default branch, rebase *does* take a '--mq' option, but it probably
doesn't do what Dirkjan expected it to do when he wrote the message.
In the original, deprecating commit, little context was provided as to
why this change was made. Based on my recollection, concensus at the
Paris Sprint in February 2010 was that one of the problems with MQ was
that it exposed far too many commands. Notable among these were qsave
& qrestore: very few core developers understood what they did and even
fewer (none, IIRC) actually used them. However, they couldn't be
removed; not only do the usual backwards compatibility reasons apply,
but the hg book refers to them.
Configuration from the outer repo is inherited to the patches repo when --mq is
used.
In case the patches repo only has paths.default configured but the outer repo
has paths.default-push then the inherited default-push will win. Very
confusing.
Inheriting the default paths is however wrong in all sane cases, so now we
explicitly remove them.