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

Switching between branches accumulates empty commits #1854

Closed
GaryBoone opened this issue Jul 11, 2023 · 6 comments
Closed

Switching between branches accumulates empty commits #1854

GaryBoone opened this issue Jul 11, 2023 · 6 comments

Comments

@GaryBoone
Copy link

jj avoids creating empty commits, but does so in the following transcript. In it, a branch is created and commits are added to it and to main. The 'main' branch label reset to the main branch using jj branch set to after the branch is created, enabling jj co 'heads(main:)' to work as expected.

The issue appears to surface if you jj co to a new branch after creating it.

This behavior does not occur if you don't jj co to the new branch after creating it.

Repro:

# Create a git-based repo and commit some files to it.
$ git init && jj init --git-repo=.
Found existing alias for "git". You should use: "g"
Initialized empty Git repository in /Users/.../.git/
Initialized repo in "."
$ touch m1 && jj ci -m"+m1" && touch m2 && jj ci -m"+m2"

# Now add another file, then create a branch, and co to the branch. 
$ touch b1 && jj branch create br && jj co br
Working copy now at: 0644f351b8d5 (no description set)
Parent commit      : 313ae46abd1b (no description set)
$ jj log -p
@  xxwkwuwkunsk gary@ 2023-07-11 10:30:15.000 -07:00 0644f351b8d5
│  (empty) (no description set)
◉  klkyknrwuspo gary@ 2023-07-11 10:30:15.000 -07:00 br HEAD@git 313ae46abd1b
│  (no description set)
│  Added regular file b1:
◉  qtqxwqozzzol gary@ 2023-07-11 10:29:46.000 -07:00 0e1d88277209
│  +m2
│  Added regular file m2:
◉  nostqtmyrmux gary@ 2023-07-11 10:29:46.000 -07:00 main 4c2e0027c096
│  +m1
│  Added regular file m1:
◉  zzzzzzzzzzzz 1970-01-01 00:00:00.000 +00:00 000000000000
   (empty) (no description set)

# Add more files to the branch. We see above that the last change in main was
# 'q', so co to that.
$ touch b2 && jj ci -m"+b2"
Working copy now at: e76f7977e628 (no description set)
Parent commit      : 7a1f867514c7 +b2
$ jj co q
Working copy now at: eb6e75b64a42 (no description set)
Parent commit      : 0e1d88277209 +m2
Added 0 files, modified 0 files, removed 2 files

# Move 'main' so that it no longer has a branch in it, enabling `jj co 'heads(main:)'` 
# to work as expected.
$ jj branch set main
$ jj st
Parent commit: 0e1d88277209 +m2
Working copy : eb6e75b64a42 (no description set)
The working copy is clean
$ jj log -p
@  qtxskxorwuut gary@ 2023-07-11 10:31:02.000 -07:00 main eb6e75b64a42
│  (empty) (no description set)
│ ◉  xxwkwuwkunsk gary@ 2023-07-11 10:30:49.000 -07:00 7a1f867514c7
│ │  +b2
│ │  Added regular file b2:
│ ◉  klkyknrwuspo gary@ 2023-07-11 10:30:15.000 -07:00 br 313ae46abd1b
├─╯  (no description set)
│    Added regular file b1:
◉  qtqxwqozzzol gary@ 2023-07-11 10:29:46.000 -07:00 HEAD@git 0e1d88277209
│  +m2
│  Added regular file m2:
◉  nostqtmyrmux gary@ 2023-07-11 10:29:46.000 -07:00 4c2e0027c096
│  +m1
│  Added regular file m1:
◉  zzzzzzzzzzzz 1970-01-01 00:00:00.000 +00:00 000000000000
   (empty) (no description set)

# Now repeatedly switch between the two branches and see that working copy
# commits are accumulating.
$ jj co 'heads(br:)'
Working copy now at: f866d9d1576d (no description set)
Parent commit      : 7a1f867514c7 +b2
Added 2 files, modified 0 files, removed 0 files
$ jj co 'heads(main:)'
Working copy now at: 22dab38e2bef (no description set)
Parent commit      : f866d9d1576d (no description set)
$ jj log -p
@  mwqyzprvqppp gary@ 2023-07-11 10:31:58.000 -07:00 22dab38e2bef
│  (empty) (no description set)
◉  wlvpqryxuqlz gary@ 2023-07-11 10:31:53.000 -07:00 HEAD@git f866d9d1576d
│  (empty) (no description set)
◉  xxwkwuwkunsk gary@ 2023-07-11 10:30:49.000 -07:00 7a1f867514c7
│  +b2
│  Added regular file b2:
◉  klkyknrwuspo gary@ 2023-07-11 10:30:15.000 -07:00 br 313ae46abd1b
│  (no description set)
│  Added regular file b1:
◉  qtqxwqozzzol gary@ 2023-07-11 10:29:46.000 -07:00 main 0e1d88277209
│  +m2
│  Added regular file m2:
◉  nostqtmyrmux gary@ 2023-07-11 10:29:46.000 -07:00 4c2e0027c096
│  +m1
│  Added regular file m1:
◉  zzzzzzzzzzzz 1970-01-01 00:00:00.000 +00:00 000000000000
   (empty) (no description set)
$ jj co 'heads(br:)'
Working copy now at: dbbd5d2a706d (no description set)
Parent commit      : 22dab38e2bef (no description set)
$ jj co 'heads(main:)'
Working copy now at: 25bfbf46353e (no description set)
Parent commit      : dbbd5d2a706d (no description set)
$ jj co 'heads(br:)'
Working copy now at: 6db6ca7d1b94 (no description set)
Parent commit      : 25bfbf46353e (no description set)
$ jj co 'heads(main:)'
Working copy now at: 7efbfd83fd97 (no description set)
Parent commit      : 6db6ca7d1b94 (no description set)
$ jj log -p
@  zzrnnukrxlnl gary@ 2023-07-11 10:32:19.000 -07:00 7efbfd83fd97
│  (empty) (no description set)
◉  qlnwrslrmlkv gary@ 2023-07-11 10:32:17.000 -07:00 HEAD@git 6db6ca7d1b94
│  (empty) (no description set)
◉  rtsvxzqqwwpq gary@ 2023-07-11 10:32:14.000 -07:00 25bfbf46353e
│  (empty) (no description set)
◉  onwqxxxtoouz gary@ 2023-07-11 10:32:08.000 -07:00 dbbd5d2a706d
│  (empty) (no description set)
◉  mwqyzprvqppp gary@ 2023-07-11 10:31:58.000 -07:00 22dab38e2bef
│  (empty) (no description set)
◉  wlvpqryxuqlz gary@ 2023-07-11 10:31:53.000 -07:00 f866d9d1576d
│  (empty) (no description set)
◉  xxwkwuwkunsk gary@ 2023-07-11 10:30:49.000 -07:00 7a1f867514c7
│  +b2
│  Added regular file b2:
◉  klkyknrwuspo gary@ 2023-07-11 10:30:15.000 -07:00 br 313ae46abd1b
│  (no description set)
│  Added regular file b1:
◉  qtqxwqozzzol gary@ 2023-07-11 10:29:46.000 -07:00 main 0e1d88277209
│  +m2
│  Added regular file m2:
◉  nostqtmyrmux gary@ 2023-07-11 10:29:46.000 -07:00 4c2e0027c096
│  +m1
│  Added regular file m1:
◉  zzzzzzzzzzzz 1970-01-01 00:00:00.000 +00:00 000000000000
   (empty) (no description set)

# jj version:
$ jj -V
jj 0.7.0-7acdc8f01111f800cc49648d4ee00a8d06116747
@necauqua
Copy link
Collaborator

necauqua commented Jul 11, 2023

I hate to be that guy, but this is completely expected behavior and you are just not using jj correctly, because you are trying to use a git branching model which doesn't work as jj specifically has a different philosophy.

There are no branches in jj like there are in git, you do not "checkout" a branch, jj co is merely an alias for jj new which kind of works as you'd expect checkout to work (that's why it's there, although there are arguments to remove it) - except in a case like this where you want to create and keep a branch because you are used to "must have a branch" from git.

What happens in your case is:

  • you create two commits (+m1 and +m2) - now you are editing a working copy commit (also called @) which is a child of +m2
  • you point a branch br at @ (it's the default target of jj branch set)
  • and now you "check out" br, which points at @, so you "check out @", creating a new @ that's a child of was-just-@

If you call jj co @ (aka jj new @) repeatedly you'd get a string of empty commits, that's normal.

A reminder of literally the main design choice/benefit of jj - there is no index, there is always this @ commit that has your changes amended to it every single time you run any jj command (aka snapshotting).

You can just do the following to get the feel of it slightly, notice how there are absolutely no branches:

git init && jj init --git-repo=.

touch a
jj commit -m '+a' # commit is just an alias for jj describe -m".." && jj new
touch b
jj commit -m '+b'

jj co @- # "check out" the +a commit
touch c
jj commit -m '+c'

jj log -r 'all()' # now look at the log

touch some-dirty-work
jj co <id of +b> # whoops, look, we didn't "commit" dirty work

jj log -r 'all()' # now look at the log again - it will have a child of +c with no message, which has the state of dirty work :)

Also jj edit can be a funny command.

edit:

There are no branches in jj like there are in git

Well there obviously are branches, but they behave identically to tags, basically, and are only present for pushing to remotes and git compat

@yuja
Copy link
Collaborator

yuja commented Jul 11, 2023

Maybe better to not discard empty wc commit if it has a branch?

# Move 'main' so that it no longer has a branch in it, enabling `jj co 'heads(main:)'` 
# to work as expected.
$ jj branch set main
$ jj st
Parent commit: 0e1d88277209 +m2
Working copy : eb6e75b64a42 (no description set)
The working copy is clean
$ jj log -p
@  qtxskxorwuut gary@ 2023-07-11 10:31:02.000 -07:00 main eb6e75b64a42
│  (empty) (no description set)
│ ◉  xxwkwuwkunsk gary@ 2023-07-11 10:30:49.000 -07:00 7a1f867514c7
│ │  +b2
│ │  Added regular file b2:

At this point, main points to the working-copy commit, and it's empty. So the next checkout will abandon the empty commit, and main will move to the wc parent. I think moving branch ref backwards should be avoided unless user explicitly abandon the commit.

# Now repeatedly switch between the two branches and see that working copy
# commits are accumulating.
$ jj co 'heads(br:)'
Working copy now at: f866d9d1576d (no description set)
Parent commit      : 7a1f867514c7 +b2
Added 2 files, modified 0 files, removed 0 files

@GaryBoone
Copy link
Author

I hate to be that guy, but this is completely expected behavior and you are just not using jj correctly, because you are trying to use a git branching model which doesn't work as jj specifically has a different philosophy.

I appreciate your being that guy; I'm exactly interested in understanding the philosophy to use it correctly.

Here's an nearly identical sequence as the one I showed above:

git init && jj init --git-repo=.
touch m1 && jj ci -m"+m1" && touch m2 && jj ci -m"+m2"
touch b1 && jj branch create br && jj ci -m"+b1"
jj co <id of +m2>
touch m3 && jj ci -m"+m3"
jj branch set main

The main difference being that this sequence doesn't include a jj co br after the branch is created. However, after this sequence, switching with jj co 'heads(br:)' and jj co 'heads(main:)' does not generate the extra series of commits shown above.

That makes sense to me: While jj does make commits with jj co and jj new and any other jj command, it also prunes completely empty commits. They don't any any value to the repo change list. So I think the output shown in the issue description is a bug.

BTW, perhaps just for this example where there a no descriptions, I like jj log -p which will show where some-dirty-work was added.

@martinvonz
Copy link
Member

Maybe better to not discard empty wc commit if it has a branch?

I agree.

Well there obviously are branches, but they behave identically to tags, basically

However, branches are updated when you rewrite a commit. For example, if you change the description of some commit, then all descendants will automatically be rewritten, and branches pointing to any of them will be updated to point to the rewritten commit.

yuja added a commit to yuja/jj that referenced this issue Jul 12, 2023
… branch

It would be confusing if a branch moved backwards by checking out unrelated
commit.

jj-vcs#1854
yuja added a commit that referenced this issue Jul 12, 2023
… branch

It would be confusing if a branch moved backwards by checking out unrelated
commit.

#1854
@necauqua
Copy link
Collaborator

necauqua commented Jul 12, 2023

jj co 'heads(br:)' and jj co 'heads(main:)'

Oh, I might've not fully understood what exactly you were describing.

So you've set main to a different commit so that it won't be included in br, but the commit was empty and it dropped, moving main back to it's parent included in br.

Basically what you're saying in # Move 'main' so that it no longer has a branch in it, enabling jj co 'heads(main:)' to work as expected., which it didn't, I need to learn how to read 😂

Well the fix for that is already in jjs main :)

branches are updated when you rewrite a commit

Hm, right, so git tags (which we only show and don't even have a way of creating yet) are pointers to a commit_id, and branches are more like pointers to a change_id I guess, the main difference from git I needed to highlight is that there's no "checked out" branch that moves when you make commits.

@martinvonz
Copy link
Member

I skimmed this thread and didn't find any action items for us. Please let me know if I missed anything.

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