When running in a working copy collocated with git's, we export the
working copy's commit's parent to git after every command. However, we
forgot to update our own record of git's HEAD. That means that on
subsequent imports from git, it'll look like the user had updated HEAD
using a git command. When we detect that, we trust that the user had
taken care of the changes in the working copy and we simply abandon
our old working copy commit. That led to the bug reported in $54,
where the second commit of a `jj split` got lost.
The fix is to also update our record of where git's HEAD is when we
tell git to update it.
Closes#54.
We no longer need the commit ID, so we shouldn't make the callers pass
it. This lets us simplify several tests, because they no longer to
create commits just to check out a tree in the working copy.
We used to use the value to detect races, but we use the tree ID and
the operation ID these days, so we don't need the commit ID.
By changing this, we can avoid creating some commit IDs in tests,
which is why I tackled this issue now.
There are only two callers of `LockedWorkingCopy::check_out()`. One is
in `commands.rs`. That caller already checks after taking the lock
that the old commit ID is as expected. The other caller is
`WorkingCopy::check_out()`. We can simply move the check to that level
since it's the only caller that cares now.
A few commands (`restore`, `diff`, and `untrack` so far) accept path
arguments, but they only support files. Let's make them work with
directories too.
It's harmless but potentially confusing to have multiple workspaces
with the same ID (it would mean that they always have the same
checkout). Let's just prevent it for now. We can add an override later
if people think of usecases for it.
When you run `jj co abc123` and that commit is already checked out, we
just print a message. The condition for that assumed that the checkout
existed, which it won't if you just ran `jj workspace forget`. Let's
avoid that crash, especially since `jj co` is an easy way to restore
the working copy if you had accidentally run `jj workspace forget`
(though `jj undo` is even easier).
It seems helpful to show in the log output which commit is checked out
in which workspace, so let's try that. I made it only show the
information if there are multiple checkouts for now.
In workspaces added after the initial one, the idea is to have
`.jj/repo` be a file whose contents is a path to the location of the
repo directory in some other workspace.
Because we record each workspace's checkout in the repo view, we can
-- unlike other VCSs -- let the user refer to any workspace's checkout
in revsets. This patch adds syntax for that, so you can show the
contents of the checkout in workspace "foo" with `jj show foo@`. That
won't automatically commit that workspace's working copy, however.
If the workspace is shared with a Git repo, we sometimes update Git's
HEAD ref. We should get the new checkout from the right workspace ID
when doing that (though I'm not sure we'll ever support sharing the
working copy with Git in a non-default workspace).
When importing Git HEAD, we already use the right workspace ID for the
new checkout, but the old checkout we abandon is always the default
workspace's. We should fix that even if we will never support sharing
a working copy with Git in a non-default workspace.
Before committing the working copy, we check if the working copy is
checked out to the commit we expect based on the repo's view. We
always use the default workspace's checkout, so we need to fix that.
We detect concurrent working copy changes by checking that the old
commit matches the repo's view. We should use the current workspace
when looking up the checkout in the view.
This adds a `jj move [--from <rev>] [--to <rev>] [-i]` command, which
lets you move some changes from one commit into another. `jj
squash/amend` is just a special case of this new command. Except for
that command's more specialized help text, instructions, etc., it
could be implemented as simply `jj move --to @-`.
I thought it was a bit unclear which part of the process was
interactive (it's only choosing parts of the diffs that is
interactive, not choosing destination or anything else).
We allow rebasing to a descendant, but that causes divergence because
the old commit remains visible. You could imagine making it work so
`jj rebase -r B -d D` on a linear chain "A-B-C-D" reorders it to
"A-C-D-B", but we don't do that yet, so let's just prevent the
divergence for now.
Now that we have the operation ID recorded in the working copy state,
we can tell if the working copy is stale. When it is, we update it to
the repo view's checkout.
When there are concurrent operations that want to update the working
copy, it's useful to know which operation was the last to successfully
update the working copy. That can help use decide how to resolve a
mismatch between the repo view's record and the working copy's
record. If we detect such a difference, we can look at the working
copy's operation ID to see if it was updated by an operation before or
after we loaded the repo.
If the working copy's record says that it was updated at operation A
and we have loaded the repo at operation B (after A), we know that the
working copy is stale, so we can automatically update it (or tell the
user to run some command to update it if we think that's more
user-friendly).
Conversely, if we have loaded the repo at operation A and the working
copy's record says that it was updated at operation B, we know that
there was some concurrent operation that updated it. We can then
decide to print a warning telling the user that we skipped updating
because of the conflict. We already have logic for not updating the
working copy if the repo is loaded at an earlier operation, but maybe
we can drop that if we record the operation in the working copy (as
this patch does).
When importing git HEAD in a working copy shared with git, we reset
the working copy to the new commit at the end. If we fail to reset the
working copy, we shouldn't commit the operation. This patch mostly
fixes that by locking the working copy while we commit the
operation. There's still a small risk that the operation commits and
we fail to write the working copy state, but there's not much we can
do about that (or it's not worth the effort anyway).
`WorkingCopy::check_out()` currently fails if the commit recorded on
disk has changed since it was last read. It fails with a "concurrent
checkout" error. That usually works well in practice, but one can
imagine cases where it's not correct. For an example where the current
behavior is wrong, consider this sequence of events:
1. Process A loads the repo and working copy.
2. Process B loads the repo at operation A. It has not loaded the
working copy yet.
3. Process A writes an operation and updates the working copy.
4. Process B loads the working copy and sees that it is checked out
to the commit process B set it to. We don't currently have any
checks that the working copy commit matches the view's checkout
(though I plan to add that).
5. Process B finishes its operation (which is now divergent with the
operation written by process A). It updates the working copy to
the checkout set in the repo view by process B. There's no data
loss here, but the behavior is surprising because we would usually
tell the user that we detected a concurrent update to the working
copy.
We should instead check that the working copy's commit on disk matches
what the previous repo view said, i.e. the view at the start of the
operation we just committed. This patch does that by having the caller
pass in the expected old commit ID.