When using Git as a store, new commits created in the underlying Git
repo are only made visible by making changes on top of them (e.g by
checking them out, so a working copy commit is created on top). That's
especially confusing when creating a new repo backed by an existing
Git repo, because the commits from that repo don't show up.
This commit prepares for fixing that by adding a function for updating
heads based on git refs. Since we don't yet track git refs (or
anything similar), the function just makes sure the refs are visible
in the Jujube repo by making them (anonymous) heads.
`Transaction::add_head()` and others would let the caller add
non-heads to the set (i.e. ancestors of others heads) and the the
non-heads were filterd out when the transaction was committed. That's
a little surprising, so let's try to keep the set valid even within a
transaction. That will surely make commands that add many commits
noticeably slower in large repos. Hopefully we can improve that
later.
Returning the store's internal `git2::Repository` instance wrapped in
a `Mutex` makes it easy to run into deadlocks. Let's return a freshly
loaded repo instance instead.
It's annoying to have to have the Git repo and Jujube repo in separate
directories. This commit adds `jj init --git`, which creates a new
Jujube repo with an empty, bare git repo in `.jj/git/`. Hopefully the
`jj git` subcommands will eventually provide enough functionality for
working with the Git repo that the user won't have to use Git commands
directly. If they still do, they can run them from inside `.jj/git/`,
or create a new worktree based on that bare repo.
The implementation is quite straight-forward. One thing to note is
that I made `.jj/store` support relative paths to the Git repo. That's
mostly so the Jujube repo can be moved around freely.
This commit starts adding support for working with a Jujube repo's
underlyng Git repo (if there is one). It does so by adding a command
for pushing from the Git repo to a remote, so you can work with your
anonymous branches in Jujube and push to a remote Git repo without
having to switch repos and copy commit hashes.
For example, `jj git push origin main` will push to the "main" branch
on the remote called "origin". The remote name (such as "origin") is
resolved in that repo. Unlike most commands, it defaults to pushing
the working copy's parent, since it is probably a mistake to push a
working copy commit to a Git repo.
I plan to add more `jj git` subcommands later. There will probably be
at least a command (or several?) for making the Git repo's refs
available in the Jujube repo.
This is just a little refactoring to prepare for making `jj split` ask
the user for commit descriptions. It's not actually needed, but it
doesn't make logical sense for the function to be about editing the
description of a particular commit.
This adds `jj edit`, which lets the user edit the content changes in a
commit. It behaves similar to `jj restore` when restoring the parent
commit, except that it edits the change compared to the re-merged
parents if the commit is a merge commit.
Changes to the "before" side of the diff will have no effect, so let's
clarify that by marking it readonly. At least Meld checks the
permissions and shows in the UI that the left side is readonly.
This adds an interactive mode for `jj restore`. It works by first
creating two temporary directories with the contents of the subset of
files that differ between the two trees, and then letting the user
edit the directory representing the right/after side. This has some
advantages compared to the interactive modes in Git and Mercurial:
* It lets the user edit the final state as opposed to the diff itself
(depending on the diff tool, of course). I think most users find it
easier to edit the file contents than to edit the patch
format.
* It delegates the hard work to a tool that is already written (this
is a big advantage for an immature tool like Jujube, but it is not
an advantage from the user's point of view).
Almost all of the work in this commit went into adding a function that
takes two trees, lets the user edit the diff, and returns a new tree
id. I plan to reuse that function for other interactive commands. One
planned command is `jj edit`, which will let the user edit the changes
in a commit. `jj edit -r abc123` will be mostly about providing a more
intuitive name for `jj restore --source abc123^ --destination abc123`,
plus it will be different for merge commits (it will edit only the
changes in the merge commit). I also plan to add `jj split` by letting
the user edit the full diff, leaving only the parts that should go
into the first commit. Perhaps there will also be commands for moving
part of a commit out of or into a parent commit.
This extracts a bit from `Transaction::check_out()` for taking a
Conflict, materializing it, and writing the resulting plain file to
the store. It will soon be reused.
Before this commit, running Git's GC in a Git repo backing a Jujube
repo would risk deleting the conflict data we store as blobs in the
Git repo. This commit fixes that by adding a Git note pointing to the
conflict blob.
I wasn't able to add a test case for this because libgit2 doesn't
support gc [1]. Just testing that the ref is there doesn't seem very
useful.
[1] https://github.com/libgit2/libgit2/issues/3247
Very little code was saved by reusing `write_file()` and it made it
confusing (needed to provide an unused filename). Also, I'll soon want
access to the `locked_repo` variable in `write_conflict()`.
I have never run into this being a problem in practice, but this
change is a stepping stone for two things:
1. Using exponential backoff for other locks (in particular the
working copy).
2. Making the Git store write a ref for conflict objects, so they
don't get GC'd (I want to do that before even I start dogfooding).
I had tried to generate the protobuf code at build time many months
ago, but decided against it because it slowed down the build too
much. I didn't realize there was the
"cargo:rerun-if-changed=<filename>" feature that time. Given that that
exists, it seems like an obvious win to generate the source code at
build time.
I put the generated sources in `$OUT_DIR` (where [1] says they should
be), then include them in the `protos` module by using the `include!`
macro. The biggest problem with that is that I couldn't get IntelliJ
to understand it, even after enabling the experimental features
described in [2].
[1] https://doc.rust-lang.org/cargo/reference/build-script-examples.html#code-generation
[2] https://github.com/intellij-rust/intellij-rust/issues/1908#issuecomment-592773865
We currently recalculate the entire evolution state whenever a new
commit is added within a transaction. That's clearly wasteful. This
commit makes the state-update incremental.
I'm about to make the evolution state updated incrementally. To be
able to tell if a new commit is divergent, we'll need to keep track of
already existing, non-obsolete commits in the change, even if they're
not divergent before the new commit is added.
A pruned commit just indicates that its predecessors should be evolved
onto the pruned commit's parent instead of onto the pruned commit
itself. The pruned commit itself can be divergent. For example, if
there are several pruned sucessors of a commit, then it's unclear
where the predecessor's children should be rebased to.
I'm about to add more fields to the type and this will help to
slightly reduce the boilerplate for initializing and the maps and sets
and creating the struct.
Before this commit, when the `evolve()` evolved a stack of orphans, it
would use the evolve state from the beginning of the function to
calculate where they should go. That meant that only the bottom-most
orphan(s) would get evolved to their right place. This commit fixes
that by use the Transaction's evolution state.
Before this commit, it could share its state with the
`ReadonlyEvolution`. That makes no sense when the state is modified,
and would crash if we tried to get a mutable reference to the
state. It only "worked" because the state is not yet updated within a
transaction (a known bug). I'm about to fix that bug, so we need to
fix the ownership, which this commit does.
There was some duplicate between `ReadonlyEvolution` and
`MutableEvolution` that could be extracted. It will help to have this
shared code on the `State` object for the next few patches.
The point of having the `modified` and `removed` files in the test was
to show that they don't get untracked, but I forgot to include them in
the `.gitignores`, so there was no reason they would have gotten
untracked anyway.
The project's source of truth is now in Git and I really miss support
for anonymous heads and evolution (compared to when the code was in
Mercurial). I'm therefore more motivated to make the tool useful for
day-to-day work on small repos, so I can use it myself. Until now, I
had been more focused on improving performance when it was used as a
read-only client for medium-to-large repos.
One important feature for my day-to-day work is support for
ignores. This commit adds simple and effective, but somewhat hacky
support for that. libgit2 requires a repo to check if a file should be
ignored (presumably so it can respect `.git/info/excludes`). To work
around that, we create a temporary git repo in `/tmp/` whenever the
working copy is committed. We set that temporary git repo's working
copy to be shared with our own working copy. Due to
https://github.com/libgit2/libgit2sharp/issues/1716 (which seems to
apply to the non-.NET version as well), this workaround unfortunately
leaves a .git file (pointing to the deleted temporary git repo) around
in every Jujube repo. That's always ignored by libgit2, so it's not
much of a problem.
This removes one level of indirection, which is nice because it was
visible to the callers. The `Index` struct is now empty. The next step
is obviously to delete it (and perhaps rename `IndexFile` to `Index`
or `ReadonlyIndex`).