Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG: jj checkout and HEAD@git out of sync #2011

Closed
shavitmichael opened this issue Aug 8, 2023 · 10 comments
Closed

BUG: jj checkout and HEAD@git out of sync #2011

shavitmichael opened this issue Aug 8, 2023 · 10 comments

Comments

@shavitmichael
Copy link

jj can enter a state where its currently checked out commit get's out of sync with the git HEAD in a colocated repository. This looks similar to #1495 but doesn't seem to involve unborn branches (AFAICT).

My jj log looks like:

❯ jj log
@  zmvmkzxk [email protected] 2023-08-09 00:30:18.000 +08:00 393d6c13
│  (empty) (no description set)
◉  xxttkkus [email protected] 2023-08-04 02:11:47.000 +08:00 HEAD@git c09cbd8e
│  iommu/arm-smmu-v3: Cleanup arm_smmu_domain_finalise
◉  xqmpkklm [email protected] 2023-08-04 02:11:46.000 +08:00 2279e212
│  iommu/arm-smmu-v3: Free pasid domains on iommu release

and git status

❯ git checkout HEAD~1
Previous HEAD position was c09cbd8ed86ef iommu/arm-smmu-v3: Cleanup arm_smmu_domain_finalise
HEAD is now at 2279e21297f5c iommu/arm-smmu-v3: Free pasid domains on iommu release

This looks ok at a glance, but updating the currently checked out commit in either jj or git is broken

Checking out a commit in git should be reflected in jj with an updated HEAD@git label, and a new checked-out commit @. Neither of those get updated however.

❯ git checkout HEAD~1
Previous HEAD position was c09cbd8ed86ef iommu/arm-smmu-v3: Cleanup arm_smmu_domain_finalise
HEAD is now at 2279e21297f5c iommu/arm-smmu-v3: Free pasid domains on iommu release
❯ jj log
@  npytpmkq [email protected] 2023-08-09 00:40:15.000 +08:00 10f210ed
│  (no description set)
◉  xxttkkus [email protected] 2023-08-04 02:11:47.000 +08:00 HEAD@git c09cbd8e
│  iommu/arm-smmu-v3: Cleanup arm_smmu_domain_finalise
◉  xqmpkklm [email protected] 2023-08-04 02:11:46.000 +08:00 2279e212
│  iommu/arm-smmu-v3: Free pasid domains on iommu release

Similarly, updating the checked out commit in jj should be reflected on the git side; but git is instead still checked out at the previous commit and thus sees changes in jj's checked out commit as diffs.

// Undo previous git checkout for a cleaner experiment
❯ git checkout c09cbd8ed86ef
Previous HEAD position was 2279e21297f5c iommu/arm-smmu-v3: Free pasid domains on iommu release
HEAD is now at c09cbd8ed86ef iommu/arm-smmu-v3: Cleanup arm_smmu_domain_finalise
❯  jj co xqmpkklm
❯ jj log
@  wvnrupnq [email protected] 2023-08-09 00:42:05.000 +08:00 18804d9c
│  (empty) (no description set)
│ ◉  xxttkkus [email protected] 2023-08-04 02:11:47.000 +08:00 HEAD@git c09cbd8e
├─╯  iommu/arm-smmu-v3: Cleanup arm_smmu_domain_finalise
◉  xqmpkklm [email protected] 2023-08-04 02:11:46.000 +08:00 2279e212
│  iommu/arm-smmu-v3: Free pasid domains on iommu release
❯ git status
HEAD detached at c09cbd8ed86ef
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c

I haven't yet figured out how I got into this state, let alone reproduction steps. Let me know if you have any suggestions on how I could investigate further.

@martinvonz
Copy link
Member

Thanks for the report.

Maybe you can get some hints from running jj op log. You should see some operations saying "import git refs". You can also try jj debug operation <operation id> for various operations there and see where the git_head and wc_commit_ids point. Let us know what you find.

@ilyagr
Copy link
Contributor

ilyagr commented Aug 8, 2023

Also, what version of jj are you using? Is it 0.8? Newer unreleased version? jj version should tell you the exact commit.

@shavitmichael
Copy link
Author

Also, what version of jj are you using? Is it 0.8? Newer unreleased version? jj version should tell you the exact commit.

Running on jj 0.8.0-b6c65c12aca94c0574b629424931ea7f772babed

Maybe you can get some hints from running jj op log. You should see some operations saying "import git refs". You can also try jj debug operation <operation id> for various operations there and see where the git_head and wc_commit_ids point. Let us know what you find.

1. Git->jj direction

Ok, trying out the git checkout HEAD~1 test, and the first possibly unexpected observation is that jj log does not result in an "import git refs" operation:

@  553776c13193 [email protected] 15 seconds ago, lasted 1 second
│  snapshot working copy
│  args: jj log

The same operation on a fresh checkout of the repo (also collocated) has a different behavior

@  3c0a985116c0 [email protected] 7 seconds ago, lasted 2 seconds                                          │ git branch <new-branch-name> 149ba7d0c399
│  import git refs                                                                                                           │
│  args: jj log  

Perhaps expected then, the git_head isn't updated, and the wc points to an (empty) wc child of the stale git_head

    git_head: RefTarget {
        conflict: Conflict {
            removes: [],
            adds: [
                Some(
                    CommitId(
                        "c09cbd8ed86ef376fba5ee842a9d1925eaff6644",
                    ),
                ),
            ],
        },
    },
    wc_commit_ids: {
        WorkspaceId(
            "default",
        ): CommitId(
            "bf24c5c944fe0b3a20f60c12009fcfe1d2832718",
        ),
    },

Explicitly calling jj git import correctly updates the git_head in jj to reflect the one in git, but wc_commit_ids isn't updated.

2. jj->Git

Running jj co @-- doesn't update the git_head:

❯ jj op log

@  07ae677156ae [email protected] 8 seconds ago, lasted 1 second
│  check out commit 2279e21297f5c8540810288a2d3023c2b7ff5b94
│  args: jj co @--

❯ jj debug operation @

    git_head: RefTarget {
        conflict: Conflict {
            removes: [],
            adds: [
                Some(
                    CommitId(
                        "c09cbd8ed86ef376fba5ee842a9d1925eaff6644",
                    ),
                ),
            ],
        },
    },

In this direction, jj git export has no effect.

❯ jj git export
Nothing changed.

Meanwhile, the same operation on the fresh checkout does update the git_head.

@  c639e2dba547 [email protected] 21 seconds ago, lasted 1 second
│  check out commit d92b86f672a42d9d74a24a63a1e59793c4116830
│  args: jj co @--

    git_head: RefTarget {
        conflict: Conflict {
            removes: [],
            adds: [
                Some(
                    CommitId(
                        "d92b86f672a42d9d74a24a63a1e59793c4116830",
                    ),
                ),
            ],
        },
    },

@shavitmichael
Copy link
Author

Going further back the operation log, the repo was initially OK as changes were only being made on the Git side and jj was correctly tracking changes to the git_head on each call to jj log. The first jj checkout operation ever performed on the repo did not update the git_head, and I suspect things just got worse from there. The first such operation is shown below:

**jj op log**
◉  893f3050d14f [email protected] 6 days ago, lasted 775 milliseconds
│  check out commit 46b367e9640a74cb57e9555eaa78328162adaa31
│  args: jj co patman
◉  d7b3ef476cfb [email protected] 6 days ago, lasted 1 second
│  snapshot working copy
│  args: jj log

## Everything ok at and before d7b3ef476cfb

**jj log --at-operation d7b3ef476cfb**
@  rousutqr (no name configured) <(no email configured)> 2023-08-03 10:43:57.000 +08:00 19ba21da
│  (empty) (no description set)
◉  ktnrkutt Michael Shavit <[email protected]> 2023-08-03 02:10:54.000 +08:00 patman_upload HEAD@git b39e2a81
│  smmu test-engine fix

**  jj debug operation d7b3ef476cfb**
    git_head: RefTarget {
        conflict: Conflict {
            removes: [],
            adds: [
                Some(
                    CommitId(
                        "b39e2a81732d3c48071aef990052de9c66af136f",
                    ),
                ),
            ],
        },
    },
    wc_commit_ids: {
        WorkspaceId(
            "default",
        ): CommitId(
            "19ba21da1a131186d997bd06c216d368ef3f37bd",
        ),
    },

## Now things go wrong at 893f3050d14f

** jj log --at-operation 893f3050d14f**
@  omzqkxwu (no name configured) <(no email configured)> 2023-08-03 10:59:23.000 +08:00 6c9deb0f
│  (empty) (no description set)
◉  rnuuouvt Michael Shavit <[email protected]> 2023-08-03 02:07:23.000 +08:00 patman 46b367e9
│  smmu test-engine fix

## (Note: "smmu test-engine fix" 46b367e9 has a different sha/change-id than b39e2a81)*

**  jj debug operation 893f3050d14f**
    git_head: RefTarget {
        conflict: Conflict {
            removes: [],
            adds: [
                Some(
                    CommitId(
                        "b39e2a81732d3c48071aef990052de9c66af136f",
                    ),
                ),
            ],
        },
    },
    wc_commit_ids: {
        WorkspaceId(
            "default",
        ): CommitId(
            "6c9deb0f6823b9c72f011052363795bf061275b7",
        ),
    },
}

I did early on also create a non-collocated jj workspace backed by the same repo; could that have confused the collocated one?

@yuja
Copy link
Contributor

yuja commented Aug 9, 2023

I did early on also create a non-collocated jj workspace backed by the same repo; could that have confused the collocated one?

The check for "colocated" repo is implemented as follows. I'm not pretty sure, but if Git's worktree is involved, weird thing might occur.
https://github.com/martinvonz/jj/blob/b6c65c12aca94c0574b629424931ea7f772babed/src/cli_util.rs#L695-L700
(note: workspace_root() is canonicalized)

@shavitmichael
Copy link
Author

shavitmichael commented Aug 9, 2023

Thanks for the pointer. Added some hacky prints

        if let Some(git_workdir) = maybe_git_backend
            .and_then(|git_backend| {tracing::info!("maybe_git_backend"); git_backend.git_repo().workdir().map(ToOwned::to_owned)})
            .and_then(|workdir| {tracing::info!("mapped");workdir.canonicalize().ok()})
        {
            working_copy_shared_with_git = git_workdir == workspace.workspace_root().as_path();
            tracing::info!(git_workdir=git_workdir.to_str(), workspace_root=workspace.workspace_root().as_path().to_str());  
        }
        tracing::info!(wc_shared_with_git=working_copy_shared_with_git);

Which results in

❯ jj co c09cbd8ed86e --verbose
2023-08-09T15:43:36.268691Z  INFO jj_cli::cli_util: verbose logging enabled
2023-08-09T15:43:36.353494Z  INFO run_command:cmd_checkout:workspace_helper_internal{snapshot=true}:for_loaded_repo:new: jj_cli::cli_util: maybe_git_backend
2023-08-09T15:43:36.353514Z  INFO run_command:cmd_checkout:workspace_helper_internal{snapshot=true}:for_loaded_repo:new: jj_cli::cli_util: mapped
2023-08-09T15:43:36.353545Z  INFO run_command:cmd_checkout:workspace_helper_internal{snapshot=true}:for_loaded_repo:new: jj_cli::cli_util: git_workdir="<repo_root>/.repo/projects/linux/" workspace_root="<repo_root>/linux/common"
2023-08-09T15:43:36.353564Z  INFO run_command:cmd_checkout:workspace_helper_internal{snapshot=true}:for_loaded_repo:new: jj_cli::cli_util: working_copy_shared_with_git=false

Notably, this git repo is managed by the repo tool, with jj initialized. I think we can conclude that this issue was caused by initializing jj in a git repo managed by the repo tool.

I haven't looked too deep at what repo is doing under the hood yet, but <repo_root>/linux/common appears to be neither a symlink nor a git worktree:

From <repo_root>/linux/common (where jj init --git-repo=. and all of my development was made from)

❯ git worktree list -v
<repo_root>/.repo/projects/linux/common.git  c09cbd8ed86ef (detached HEAD)

@shavitmichael
Copy link
Author

I haven't looked too deep at what repo is doing under the hood yet, but <repo_root>/linux/common appears to be neither a symlink nor a git worktree:

Oh, the .git directory is a symlink to the common.git folder found in .repo .

@martinvonz
Copy link
Member

Interesting. Maybe we should add a config controlling whether we consider the workspace colocated. Then you would be able to override it in your case. We could set the config when we initialize the workspace. I don't know if there's a better solution.

yuja added a commit to yuja/jj that referenced this issue Aug 10, 2023
Maybe we could load GitBackend without resolving .git symlink, but that would
introduce more subtle bugs. Instead, we calculate the expected Git workdir path
from the canonical ".git" path.

Fixes jj-vcs#2011
@shavitmichael
Copy link
Author

(Can confirm that forcing working_copy_shared_with_git = true resolved all the issues from the original comment.)

@yuja yuja closed this as completed in b2101d1 Aug 10, 2023
@shavitmichael
Copy link
Author

That was quick; tried out your fix and it worked :). Thanks Yuya!

netbsd-srcmastr pushed a commit to NetBSD/pkgsrc that referenced this issue Oct 12, 2023
[0.10.0] - 2023-10-04

### Breaking changes

* A default revset-alias function `trunk()` now exists. If you previously defined
  your own `trunk()` alias it will continue to overwrite the built-in one.
  Check [revsets.toml](cli/src/config/revsets.toml) and [revsets.md](docs/revsets.md)
  to understand how the function can be adapted.

### New features

* The `ancestors()` revset function now takes an optional `depth` argument
  to limit the depth of the ancestor set. For example, use `jj log -r
  'ancestors(@, 5)` to view the last 5 commits.

* Support for the Watchman filesystem monitor is now bundled by default. Set
  `core.fsmonitor = "watchman"` in your repo to enable.

* You can now configure the set of immutable commits via
  `revset-aliases.immutable_heads()`. For example, set it to
  `"remote_branches() | tags()"` to prevent rewriting those those. Their
  ancestors are implicitly also immutable.

* `jj op log` now supports `--no-graph`.

* Templates now support an additional escape: `\0`. This will output a literal
  null byte. This may be useful for e.g.
  `jj log -T 'description ++ "\0"' --no-graph` to output descriptions only, but
  be able to tell where the boundaries are

* jj now bundles a TUI tool to use as the default diff and merge editors. (The
  previous default was `meld`.)

* `jj split` supports the `--interactive` flag. (This is already the default if
  no paths are provided.)

* `jj commit` accepts an optional list of paths indicating a subset of files to
  include in the first commit

* `jj commit` accepts the `--interactive` flag.

### Fixed bugs

### Contributors

Thanks to the people who made this release happen!

* Austin Seipp (@thoughtpolice)
* Emily Kyle Fox (@emilykfox)
* glencbz (@glencbz)
* Hong Shin (@honglooker)
* Ilya Grigoriev (@ilyagr)
* James Sully (@sullyj3)
* Martin von Zweigbergk (@martinvonz)
* Philip Metzger (@PhilipMetzger)
* Ruben Slabbert (@rslabbert)
* Vamsi Avula (@avamsi)
* Waleed Khan (@arxanas)
* Willian Mori (@wmrmrx))
* Yuya Nishihara (@yuja)
* Zachary Dremann (@Dr-Emann)


[0.9.0] - 2023-09-06

### Breaking changes

* The minimum supported Rust version (MSRV) is now 1.71.0.

* The storage format of branches, tags, and git refs has changed. Newly-stored
  repository data will no longer be loadable by older binaries.

* The `:` revset operator is deprecated. Use `::` instead. We plan to delete the
  `:` form in jj 0.15+.

* The `--allow-large-revsets` flag for `jj rebase` and `jj new` was replaced by
  a `all:` before the revset. For example, use `jj rebase -d 'all:foo-'`
  instead of `jj rebase --allow-large-revsets -d 'foo-'`.

* The `--allow-large-revsets` flag for `jj rebase` and `jj new` can no longer be
  used for allowing duplicate destinations. Include the potential duplicates
  in a single expression instead (e.g. `jj new 'all:x|y'`).

* The `push.branch-prefix` option was renamed to `git.push-branch-prefix`.

* The default editor on Windows is now `Notepad` instead of `pico`.

* `jj` will fail attempts to snapshot new files larger than 1MiB by default.
  This behavior can be customized with the `snapshot.max-new-file-size`
  config option.

* Author and committer signatures now use empty strings to represent unset
  names and email addresses. The `author`/`committer` template keywords and
  methods also return empty strings.
  Older binaries may not warn user when attempting to `git push` commits
  with such signatures.

* In revsets, the working-copy or remote symbols (such as `@`, `workspace_id@`,
  and `branch@remote`) can no longer be quoted as a unit. If a workspace or
  branch name contains whitespace, quote the name like `"branch name"@remote`.
  Also, these symbols will not be resolved as revset aliases or function
  parameters. For example, `author(foo@)` is now an error, and the revset alias
  `'revset-aliases.foo@' = '@'` will be failed to parse.

* The `root` revset symbol has been converted to function `root()`.

* The `..x` revset is now evaluated to `root()..x`, which means the root commit
  is no longer included.

* `jj git push` will now push all branches in the range `remote_branches()..@`
  instead of only branches pointing to `@` or `@-`.

* It's no longer allowed to create a Git remote named "git". Use `jj git remote
  rename` to rename the existing remote.
  [#1690](jj-vcs/jj#1690)

* Revset expression like `origin/main` will no longer resolve to a
  remote-tracking branch. Use `main@origin` instead.

### New features

* Default template for `jj log` now does not show irrelevant information
  (timestamp, empty, message placeholder etc.) about the root commit.

* Commit templates now support the `root` keyword, which is `true` for the root
  commit and `false` for every other commit.

* `jj init --git-repo` now works with bare repositories.

* `jj config edit --user` and `jj config set --user` will now pick a default
  config location if no existing file is found, potentially creating parent
  directories.

* `jj log` output is now topologically grouped.
  [#242](jj-vcs/jj#242)

* `jj git clone` now supports the `--colocate` flag to create the git repo
  in the same directory as the jj repo.

* `jj restore` gained a new option `--changes-in` to restore files
  from a merge revision's parents. This undoes the changes that `jj diff -r`
  would show.

* `jj diff`/`log` now supports `--tool <name>` option to generate diffs by
  external program. For configuration, see [the documentation](docs/config.md).
  [#1886](jj-vcs/jj#1886)

* A new experimental diff editor `meld-3` is introduced that sets up Meld to
  allow you to see both sides of the original diff while editing. This can be
  used with `jj split`, `jj move -i`, etc.

* `jj log`/`obslog`/`op log` now supports `--limit N` option to show the first
  `N` entries.

* Added the `ui.paginate` option to enable/disable pager usage in commands

* `jj checkout`/`jj describe`/`jj commit`/`jj new`/`jj squash` can take repeated
  `-m/--message` arguments. Each passed message will be combined into paragraphs
  (separated by a blank line)

* It is now possible to set a default description using the new
  `ui.default-description` option, to use when describing changes with an empty
  description.

* `jj split` will now leave the description empty on the second part if the
  description was empty on the input commit.

* `branches()`/`remote_branches()`/`author()`/`committer()`/`description()`
  revsets now support exact matching. For example, `branch(exact:main)`
  selects the branch named "main", but not "maint". `description(exact:"")`
  selects commits whose description is empty.

* Revsets gained a new function `mine()` that aliases `author(exact:"your_email")`.

* Added support for `::` and `..` revset operators with both left and right
  operands omitted. These expressions are equivalent to `all()` and `~root()`
  respectively.

* `jj log` timestamp format now accepts `.utc()` to convert a timestamp to UTC.

* templates now support additional string methods `.starts_with(x)`, `.ends_with(x)`
  `.remove_prefix(x)`, `.remove_suffix(x)`, and `.substr(start, end)`.

* `jj next` and `jj prev` are added, these allow you to traverse the history
  in a linear style. For people coming from Sapling and `git-branchles`
  see [#2126](jj-vcs/jj#2126) for
  further pending improvements.

* `jj diff --stat` has been implemented. It shows a histogram of the changes,
  same as `git diff --stat`. Fixes [#2066](jj-vcs/jj#2066)

* `jj git fetch --all-remotes` has been implemented. It fetches all remotes
  instead of just the default remote

### Fixed bugs

* Fix issues related to .gitignore handling of untracked directories
  [#2051](jj-vcs/jj#2051).

* `jj config set --user` and `jj config edit --user` can now be used outside of
  any repository.

* SSH authentication could hang when ssh-agent couldn't be reached
  [#1970](jj-vcs/jj#1970)

* SSH authentication can now use ed25519 and ed25519-sk keys. They still need
  to be password-less.

* Git repository managed by the repo tool can now be detected as a "colocated"
  repository.
  [#2011](jj-vcs/jj#2011)

### Contributors

Thanks to the people who made this release happen!

* Alexander Potashev (@aspotashev)
* Anton Bulakh (@necauqua)
* Austin Seipp (@thoughtpolice)
* Benjamin Brittain (@benbrittain)
* Benjamin Saunders (@Ralith)
* Christophe Poucet (@poucet)
* Emily Kyle Fox (@emilykfox)
* Glen Choo (@chooglen)
* Ilya Grigoriev (@ilyagr)
* Kevin Liao (@kevincliao)
* Linus Arver (@listx)
* Martin Clausen (@maacl)
* Martin von Zweigbergk (@martinvonz)
* Matt Freitas-Stavola (@mbStavola)
* Oscar Bonilla (@ob)
* Philip Metzger (@PhilipMetzger)
* Piotr Kufel (@qfel)
* Preston Van Loon (@prestonvanloon)
* Tal Pressman (@talpr)
* Vamsi Avula (@avamsi)
* Vincent Breitmoser (@Valodim)
* Vladimir (@0xdeafbeef)
* Waleed Khan (@arxanas)
* Yuya Nishihara (@yuja)
* Zachary Dremann (@Dr-Emann)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants