<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>~&#x2F;notes - Jujutsu</title>
    <subtitle>Thoughts on technology, life, and everything in between.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://rinx.fyi/tags/jujutsu/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://rinx.fyi"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-10-09T00:00:00+00:00</updated>
    <id>https://rinx.fyi/tags/jujutsu/atom.xml</id>
    <entry xml:lang="en">
        <title>How I Keep Private Changes Clean in a Jujutsu Megamerge Workflow</title>
        <published>2026-10-09T00:00:00+00:00</published>
        <updated>2026-10-09T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              rinx
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://rinx.fyi/posts/20261009-how-i-keep-private-changes-clean-in-a-jujutsu-megamerge-workflow-en/"/>
        <id>https://rinx.fyi/posts/20261009-how-i-keep-private-changes-clean-in-a-jujutsu-megamerge-workflow-en/</id>
        
        <content type="html" xml:base="https://rinx.fyi/posts/20261009-how-i-keep-private-changes-clean-in-a-jujutsu-megamerge-workflow-en/">&lt;p&gt;日本語版記事は&lt;a href=&quot;https:&#x2F;&#x2F;rinx.fyi&#x2F;posts&#x2F;20261006-jujutsu-megamerge-and-private-changes&#x2F;&quot;&gt;こちら&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;&#x2F;h2&gt;
&lt;p&gt;Recently, I briefly introduced the Jujutsu Megamerge Workflow. &lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#fn:1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;p&gt;In this post, I will share examples of how I handle private changes using the Megamerge Workflow.
The intended readers are those who have a general understanding of the Megamerge Workflow but cannot quite imagine the specific situations where it becomes useful.
If you want to learn more about the Megamerge Workflow, please refer to the following articles.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;isaaccorbrey.com&#x2F;notes&#x2F;jujutsu-megamerges-for-fun-and-profit&quot;&gt;Jujutsu megamerges for fun and profit - Isaac Corbrey&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;v5.chriskrycho.com&#x2F;journal&#x2F;jujutsu-megamerges-and-jj-absorb&#x2F;&quot;&gt;Jujutsu Megamerges and jj absorb - Sym·poly·mathesy&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;steveklabnik.github.io&#x2F;jujutsu-tutorial&#x2F;advanced&#x2F;simultaneous-edits.html&quot;&gt;Working on all of your branches simultaneously - Steve&#x27;s Jujutsu Tutorial&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;arialdo.codeberg.page&#x2F;ju-ju-tsu&#x2F;tutorial&#x2F;moving&#x2F;absorbing.html&quot;&gt;Dispatching Edits from Megamerges - Ju! Ju! Tsu!&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;By using Megamerge Workflow, you can keep &quot;messy commits in progress&quot; or &quot;local-only changes&quot; while pushing only cleaned-up, organized changes to the remote.
While this requires complex state management in Git, Jujutsu allows you to eliminate it entirely.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;megamerge-workflow-and-private-changes&quot;&gt;Megamerge Workflow and Private Changes&lt;&#x2F;h2&gt;
&lt;p&gt;As mentioned in the previous article &lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#fn:1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;, I often commit changes to the repository as &quot;private changes&quot; when I want them reflected in the working copy but not on the remote (or don&#x27;t need them to be).&lt;&#x2F;p&gt;
&lt;p&gt;Specifically, these include Nix Flake-related files (&lt;code&gt;flake.nix&lt;&#x2F;code&gt;, &lt;code&gt;flake.lock&lt;&#x2F;code&gt;) and repository-local dictionary files for &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;automattic&#x2F;harper&quot;&gt;Harper&lt;&#x2F;a&gt; (&lt;code&gt;.harper-dictionary&lt;&#x2F;code&gt;).
Regarding &lt;code&gt;flake.nix&lt;&#x2F;code&gt; and &lt;code&gt;flake.lock&lt;&#x2F;code&gt;, there is a constraint on the Nix side where evaluation is not possible unless the files are in a state tracked by Git. &lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#fn:2&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;
I often use Nix devShells to prepare the tools necessary for development.
However, since other team members do not use Nix, the communication cost of introducing files that only I use into the team repository requiring them to be reviewed is too high.
Therefore, I would prefer to commit these as &quot;private changes&quot; confined to my local environment.&lt;&#x2F;p&gt;
&lt;p&gt;If using only Git, such changes would need to be stashed using &lt;code&gt;git stash&lt;&#x2F;code&gt; before pushing.
With the Megamerge Workflow, you can keep all changes in the working copy and only carve out the parts you want to push to the remote.&lt;&#x2F;p&gt;
&lt;p&gt;In the Megamerge Workflow, the working copy contains all of the following:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;A series of changes being stacked to be pushed to the remote&lt;&#x2F;li&gt;
&lt;li&gt;Private changes (commits) for &lt;code&gt;flake.nix&lt;&#x2F;code&gt; and &lt;code&gt;flake.lock&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Private changes (commits) for &lt;code&gt;.harper-dictionary&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;etc.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;figure&gt;
  &lt;img src=&quot;&#x2F;images&#x2F;posts&#x2F;20261006_jj_mega.png&quot; alt=&quot;&quot; &#x2F;&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;This way, &lt;code&gt;flake.nix&lt;&#x2F;code&gt; and &lt;code&gt;flake.lock&lt;&#x2F;code&gt; are always tracked by Git (Jujutsu), while being managed only in my local environment without being pushed to the remote. They are truly &quot;private changes.&quot;&lt;&#x2F;p&gt;
&lt;p&gt;When I want to reflect changes on the remote, I create a bookmark for the relevant changes as follows:&lt;&#x2F;p&gt;
&lt;figure&gt;
  &lt;img src=&quot;&#x2F;images&#x2F;posts&#x2F;20261006_jj_create_bookmark_for_app_impl.png&quot; alt=&quot;&quot; &#x2F;&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;Then, I simply push it using &lt;code&gt;jj git push --bookmark &amp;lt;bookmark&amp;gt;&lt;&#x2F;code&gt;.
In this way, by creating changes intended for the remote on a branch separate from private changes, I can manage them so that private changes are not pushed.
However, there is still a risk that private changes could be pushed to the remote due to operational error.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;jj-configurations&quot;&gt;jj configurations&lt;&#x2F;h2&gt;
&lt;p&gt;To prevent pushing private changes to the remote, &lt;code&gt;git.private-commits&lt;&#x2F;code&gt; &lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#fn:3&quot;&gt;3&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; is useful.
This option takes a revset. Any changes matching this revset will be blocked from being pushed.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;revset-aliases&quot;&gt;revset-aliases&lt;&#x2F;h3&gt;
&lt;p&gt;To identify changes that should not be pushed to the remote, I use revset aliases.&lt;&#x2F;p&gt;
&lt;p&gt;I manage the types of changes based on prefixes in the description (commit message) such as &quot;private:&quot;, &quot;wip:&quot;, &quot;todo:&quot;, &quot;mega&quot;, etc.
Here, I treat changes whose description starts with &quot;private&quot; as private changes. To support this rule, I create a revset alias as follows:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;revset-aliases&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;#39;private()&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;description(glob:&amp;quot;private*&amp;quot;)&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Evaluating &lt;code&gt;private()&lt;&#x2F;code&gt; lists only the changes with descriptions starting with &quot;private&quot;, as intended.&lt;&#x2F;p&gt;
&lt;figure&gt;
  &lt;img src=&quot;&#x2F;images&#x2F;posts&#x2F;20261006_jj_private_changes.png&quot; alt=&quot;&quot; &#x2F;&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;In addition to private changes (&lt;code&gt;private&lt;&#x2F;code&gt;), I also define &lt;code&gt;wip&lt;&#x2F;code&gt; (work in progress), &lt;code&gt;todo&lt;&#x2F;code&gt; (incomplete), and &lt;code&gt;mega&lt;&#x2F;code&gt; (formal changes for Megamerge) as revset aliases.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;revset-aliases&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;#39;wip()&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;description(glob:&amp;quot;wip*&amp;quot;)&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;#39;todo()&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;description(glob:&amp;quot;todo*&amp;quot;)&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;#39;private()&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;description(glob:&amp;quot;private*&amp;quot;)&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;#39;mega()&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;description(glob:&amp;quot;mega*&amp;quot;)&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;#39;local()&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;wip() | todo() | private() | mega()&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I also define a &lt;code&gt;local()&lt;&#x2F;code&gt; revset alias to handle all of these together.&lt;&#x2F;p&gt;
&lt;p&gt;Using this revset alias in &lt;code&gt;git.private-commits&lt;&#x2F;code&gt;, it prevents the pushing of changes that match &lt;code&gt;local()&lt;&#x2F;code&gt; and their descendants.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;git&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;private-commits&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;local()&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;While &lt;code&gt;git.private-commits&lt;&#x2F;code&gt; is a robust way to prevent accidental pushes, it only handles the &quot;what not to push&quot; part. Sometimes, you want to perform additional checks—such as running a linter or a test suite—immediately before the push happens.&lt;&#x2F;p&gt;
&lt;p&gt;This is where a custom &lt;code&gt;jj push&lt;&#x2F;code&gt; alias becomes useful. Before I discovered the &lt;code&gt;git.private-commits&lt;&#x2F;code&gt; option, I relied entirely on this alias as my primary guardrail. Even now, I continue to use it to integrate tools like pre-commit into my workflow.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;jj-push-alias&quot;&gt;jj push alias&lt;&#x2F;h2&gt;
&lt;p&gt;In the Jujutsu configuration file, you can set aliases &lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#fn:4&quot;&gt;4&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;. You can assign &lt;code&gt;jj&lt;&#x2F;code&gt; subcommands and arguments to any alias.
Additionally, using the &lt;code&gt;jj util exec&lt;&#x2F;code&gt; subcommand &lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#fn:5&quot;&gt;5&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;, you can execute arbitrary external commands via &lt;code&gt;jj&lt;&#x2F;code&gt;.
By combining these, I created a &lt;code&gt;jj push&lt;&#x2F;code&gt; alias for pushing to the remote.&lt;&#x2F;p&gt;
&lt;p&gt;The functionality of &lt;code&gt;jj push&lt;&#x2F;code&gt; is as follows:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Verify that changes that should not be pushed to the remote are not included in the push target&lt;&#x2F;li&gt;
&lt;li&gt;Run &lt;code&gt;pre-commit&lt;&#x2F;code&gt; before pushing&lt;&#x2F;li&gt;
&lt;li&gt;Execute &lt;code&gt;jj git push&lt;&#x2F;code&gt; to push changes to the remote&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;One minor drawback of Jujutsu is that it doesn&#x27;t natively support Git hooks, such as &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;pre-commit.com&#x2F;&quot;&gt;pre-commit&lt;&#x2F;a&gt;.
Therefore, I have configured &lt;code&gt;pre-commit&lt;&#x2F;code&gt; to run within the &lt;code&gt;jj push&lt;&#x2F;code&gt; alias.&lt;&#x2F;p&gt;
&lt;p&gt;Essentially, it&#x27;s no longer a &quot;Pre commit&quot; but rather a &quot;Pre push&quot; check.
I&#x27;ve set it up so that if the &lt;code&gt;pre-commit&lt;&#x2F;code&gt; command fails, the push is stopped; otherwise, the push proceeds.&lt;&#x2F;p&gt;
&lt;figure&gt;
  &lt;img src=&quot;&#x2F;images&#x2F;posts&#x2F;20261006_jj_pre_commit.png&quot; alt=&quot;&quot; &#x2F;&gt;
  
&lt;&#x2F;figure&gt;
&lt;h3 id=&quot;jj-push-alias-example&quot;&gt;jj push Alias Example&lt;&#x2F;h3&gt;
&lt;p&gt;Based on the above, I have configured the &lt;code&gt;jj push&lt;&#x2F;code&gt; alias as follows:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rinx&#x2F;dotfiles&#x2F;blob&#x2F;d1033370a8cb2142a2c506d3131da5cb28633f6d&#x2F;jjconfig.toml#L45-L81&quot;&gt;https:&#x2F;&#x2F;github.com&#x2F;rinx&#x2F;dotfiles&#x2F;blob&#x2F;d1033370a8cb2142a2c506d3131da5cb28633f6d&#x2F;jjconfig.toml#L45-L81&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;aliases&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;push&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  &amp;#39;util&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  &amp;#39;exec&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  &amp;#39;--&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  &amp;#39;bash&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  &amp;#39;-c&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  &amp;#39;&amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;## Run check if pre-commit is available&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;if type pre-commit &amp;gt;&#x2F;dev&#x2F;null 2&amp;gt;&amp;amp;1; then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    echo &amp;quot;==&amp;gt; Running pre-commit before push...&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    pre-commit run --all-files&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    if [ $? -ne 0 ]; then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;        echo &amp;quot;❌ pre-commit failed. Aborting push.&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;        exit 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    fi&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;else&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    echo &amp;quot;⚠️  pre-commit not found. Skipping checks.&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;fi&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;## Extract the target Revset from command line arguments. Use default `::@` if not specified&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;echo &amp;quot;==&amp;gt; Checking for forbidden markers in pushable commits...&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;targets=$(echo &amp;quot;$@&amp;quot; | grep -oP &amp;#39;(?&amp;lt;=(-b|--bookmark|-c|--change|-r|--revision)\s)[^ ]+&amp;#39;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;if [ -z &amp;quot;$targets&amp;quot; ]; then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  target_revset=&amp;quot;::@&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;else&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  target_revset=&amp;quot;::(${targets&#x2F;&#x2F;$&amp;#39;\n&amp;#39;&#x2F;|})&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;fi&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;## Verify that no unintended changes are included in the push target&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;if [ -n &amp;quot;$(jj log -r &amp;quot;$target_revset &amp;amp; mutable() &amp;amp; local()&amp;quot; --no-graph --ignore-working-copy -T &amp;#39;commit_id&amp;#39;)&amp;quot; ]; then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    echo &amp;quot;❌ Commits to be pushed contain wip&#x2F;todo&#x2F;private&#x2F;mega markers. Aborting push.&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    exit 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;fi&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;## Execute push to remote&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;echo &amp;quot;==&amp;gt; Pushing changes to remote...&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;echo &amp;quot;==&amp;gt; jj git push $@&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;jj git push $@&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;&amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  &amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;By preparing simple guardrails as aliases, you can use Jujutsu with more peace of mind.
I encourage you to build the optimal workflow and aliases that suit your own usage.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;This article was translated with AI assistance and reviewed by the author.&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;fn:1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;rinx.fyi&#x2F;posts&#x2F;20260922-jujutsu-workflow&#x2F;&quot;&gt;Jujutsu を用いた開発ワークフロー: Squash Workflow と Megamerge Workflow&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;fn:2&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;zenn.dev&#x2F;asa1984&#x2F;books&#x2F;nix-hands-on&#x2F;viewer&#x2F;ch01-03-flakes#git%E3%81%A8flake&quot;&gt;GitとFlake - Nix入門: ハンズオン編&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;fn:3&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;3&lt;&#x2F;sup&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.jj-vcs.dev&#x2F;latest&#x2F;config&#x2F;#set-of-private-commits&quot;&gt;Set of private commits - Jujutsu docs&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;fn:4&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;4&lt;&#x2F;sup&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.jj-vcs.dev&#x2F;latest&#x2F;config&#x2F;#aliases&quot;&gt;Aliases - Jujutsu docs&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;fn:5&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;5&lt;&#x2F;sup&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.jj-vcs.dev&#x2F;latest&#x2F;cli-reference&#x2F;#jj-util-exec&quot;&gt;jj util exec&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Jujutsu Megamerge でプライベートな変更をリモートにプッシュしないようにする</title>
        <published>2026-10-06T00:00:00+00:00</published>
        <updated>2026-10-06T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              rinx
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://rinx.fyi/posts/20261006-jujutsu-megamerge-and-private-changes/"/>
        <id>https://rinx.fyi/posts/20261006-jujutsu-megamerge-and-private-changes/</id>
        
        <content type="html" xml:base="https://rinx.fyi/posts/20261006-jujutsu-megamerge-and-private-changes/">&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;rinx.fyi&#x2F;posts&#x2F;20261009-how-i-keep-private-changes-clean-in-a-jujutsu-megamerge-workflow-en&#x2F;&quot;&gt;English translation&lt;&#x2F;a&gt; is available.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;はじめに&quot;&gt;はじめに&lt;&#x2F;h2&gt;
&lt;p&gt;先日は Jujutsu の Megamerge Workflow を簡単に紹介した。 &lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#fn:1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;p&gt;そこではプライベートな変更を管理する例を取り上げたが、今回はそれをどのように Megamerge Workflow と組み合わせているかの具体例を紹介したい。
Megamerge Workflow がどのようなものかはある程度理解しているが、どのようなシチュエーションで便利なのかがイメージできていない人が想定読者だ。
Megamerge Workflow について詳しく知りたい方はまず &lt;a href=&quot;https:&#x2F;&#x2F;rinx.fyi&#x2F;posts&#x2F;20260922-jujutsu-workflow&#x2F;&quot;&gt;Jujutsu を用いた開発ワークフロー: Squash Workflow と Megamerge Workflow&lt;&#x2F;a&gt;&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#fn:1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; や記事中のリンク先を参考にしていただきたい。&lt;&#x2F;p&gt;
&lt;p&gt;Megamerge Workflow を導入することで「作業中の汚いコミット」や「ローカル専用の変更」を保持したまま、リモートには整理されたキレイな変更だけをプッシュすることができるようになる。
Git では複雑な状態管理が必要になる一方、 Jujutsu を用いればこの煩雑さを一気になくすことができる。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;megamerge-workflow-とプライベートな変更&quot;&gt;Megamerge Workflow とプライベートな変更&lt;&#x2F;h2&gt;
&lt;p&gt;先日の記事 &lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#fn:1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; でも触れたように、私はよく「ワーキングコピーには反映しておきたいが、リモートには反映したくない(しなくてもいい)変更」を「プライベートな変更」としてリポジトリにコミットすることがある。&lt;&#x2F;p&gt;
&lt;p&gt;具体的には Nix の Flake 関連ファイル (&lt;code&gt;flake.nix&lt;&#x2F;code&gt;, &lt;code&gt;flake.lock&lt;&#x2F;code&gt;) や &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;automattic&#x2F;harper&quot;&gt;Harper&lt;&#x2F;a&gt; のリポジトリローカルな辞書ファイル (&lt;code&gt;.harper-dictionary&lt;&#x2F;code&gt;) だ。
特に &lt;code&gt;flake.nix&lt;&#x2F;code&gt;, &lt;code&gt;flake.lock&lt;&#x2F;code&gt; に関しては Git で追跡されている状態でないと評価ができないという Nix 側の制約がある。 &lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#fn:2&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;
私は開発に必要なツールを Nix の devShell で用意することが多い。
一方、他のチームメンバーは Nix を利用していないため、自分しか利用しないファイルをわざわざレビューしてもらってまでチームのリポジトリに導入するのはコミュニケーションコストが高すぎる。
そのため、これらを「プライベートな変更」としてローカル環境に閉じてコミットしておけるとうれしい。&lt;&#x2F;p&gt;
&lt;p&gt;Git のみを利用している場合、このような変更はプッシュ前に &lt;code&gt;git stash&lt;&#x2F;code&gt; で退避させるなどの必要がある。
Megamerge Workflow を用いれば、すべての変更をワーキングコピー上に残したまま、プッシュしたい部分だけを切り出してリモートへプッシュできる。&lt;&#x2F;p&gt;
&lt;p&gt;Megamerge Workflow では、ワーキングコピーは次のような変更がすべて含まれた状態となる。&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;リモートにプッシュする変更を積んでいくための変更群&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;flake.nix&lt;&#x2F;code&gt;, &lt;code&gt;flake.lock&lt;&#x2F;code&gt; のプライベートな変更(コミット)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;.harper-dictionary&lt;&#x2F;code&gt; のプライベートな変更(コミット)&lt;&#x2F;li&gt;
&lt;li&gt;etc..&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;figure&gt;
  &lt;img src=&quot;&#x2F;images&#x2F;posts&#x2F;20261006_jj_mega.png&quot; alt=&quot;&quot; &#x2F;&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;これなら &lt;code&gt;flake.nix&lt;&#x2F;code&gt;, &lt;code&gt;flake.lock&lt;&#x2F;code&gt; は常に Git (Jujutsu) で追跡されている状態となるし、一方でリモートにプッシュせずに自分の手元の環境だけで管理しておける。まさに「プライベートな変更」と言える。&lt;&#x2F;p&gt;
&lt;p&gt;変更をリモートに反映したいときは、次のようにして該当する変更に bookmark を作成する。&lt;&#x2F;p&gt;
&lt;figure&gt;
  &lt;img src=&quot;&#x2F;images&#x2F;posts&#x2F;20261006_jj_create_bookmark_for_app_impl.png&quot; alt=&quot;&quot; &#x2F;&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;あとはこれを &lt;code&gt;jj git push --bookmark &amp;lt;bookmark&amp;gt;&lt;&#x2F;code&gt; でプッシュすれば良い。
このように、リモートに反映したい変更はプライベートな変更とは別のブランチ上に作成していくことで、プライベートな変更がリモートにプッシュされないように管理する。
しかし、これだけでは操作を誤ってプライベートな変更をリモートにプッシュしてしまう可能性が依然残る。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;jj-push-エイリアス&quot;&gt;jj push エイリアス&lt;&#x2F;h2&gt;
&lt;p&gt;Jujutsu の設定ファイルではエイリアス &lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#fn:3&quot;&gt;3&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; を設定できる。任意のエイリアスに &lt;code&gt;jj&lt;&#x2F;code&gt; のサブコマンドと引数を割り当てられる。
また、 &lt;code&gt;jj util exec&lt;&#x2F;code&gt; サブコマンド &lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#fn:4&quot;&gt;4&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; を用いると任意の外部コマンドを &lt;code&gt;jj&lt;&#x2F;code&gt; 経由で実行できる。
これらを組み合わせて、 リモートへのプッシュ用に &lt;code&gt;jj push&lt;&#x2F;code&gt; エイリアスを作成する。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;jj push&lt;&#x2F;code&gt; の機能は次のとおり。&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;リモートにプッシュしたくない変更がプッシュ対象に含まれないことを確認する&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;jj git push&lt;&#x2F;code&gt; を実行して変更をリモートにプッシュ&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;code&gt;jj git push&lt;&#x2F;code&gt; の代わりに &lt;code&gt;jj push&lt;&#x2F;code&gt; エイリアスを利用するようにすれば、操作を誤ったとしてもプライベートな変更をリモートにプッシュすることがないようにできる。
誤って Megamerge のマージコミットをリモートにプッシュしようとすると、次のようにエラーで弾かれる。&lt;&#x2F;p&gt;
&lt;figure&gt;
  &lt;img src=&quot;&#x2F;images&#x2F;posts&#x2F;20261006_jj_aborting_push.png&quot; alt=&quot;&quot; &#x2F;&gt;
  
&lt;&#x2F;figure&gt;
&lt;h3 id=&quot;revset-aliases&quot;&gt;revset-aliases&lt;&#x2F;h3&gt;
&lt;p&gt;リモートにプッシュしたくない変更を識別するため、 revset aliases を利用する。&lt;&#x2F;p&gt;
&lt;p&gt;私は、変更の Description (コミットメッセージ) の接頭辞 (&quot;private:&quot;, &quot;wip:&quot;, &quot;todo:&quot;, &quot;mega&quot; など) により変更の種類を管理するようにしている。
ここでも、変更の Description が &quot;private&quot; からはじまるものをプライベートな変更として扱うことにする。このルールに対応するように、次のとおりに revset alias を作成する。&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;revset-aliases&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;#39;private()&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;description(glob:&amp;quot;private*&amp;quot;)&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;private()&lt;&#x2F;code&gt; を参照すると、意図したとおり &quot;private&quot; からはじまる Description を持つ変更のみがリストアップされる。&lt;&#x2F;p&gt;
&lt;figure&gt;
  &lt;img src=&quot;&#x2F;images&#x2F;posts&#x2F;20261006_jj_private_changes.png&quot; alt=&quot;&quot; &#x2F;&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;ここでは、プライベートな変更 (&lt;code&gt;private&lt;&#x2F;code&gt;) 以外にも &lt;code&gt;wip&lt;&#x2F;code&gt; (作業中), &lt;code&gt;todo&lt;&#x2F;code&gt; (未完了), &lt;code&gt;mega&lt;&#x2F;code&gt; (Megamerge 用の形式的な変更) も revset aliases として定義しておく。&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;revset-aliases&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;#39;wip()&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;description(glob:&amp;quot;wip*&amp;quot;)&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;#39;todo()&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;description(glob:&amp;quot;todo*&amp;quot;)&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;#39;private()&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;description(glob:&amp;quot;private*&amp;quot;)&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;#39;mega()&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;description(glob:&amp;quot;mega*&amp;quot;)&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;#39;local()&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;wip() | todo() | private() | mega()&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;これらにあてはまるものをまとめて扱えるように &lt;code&gt;local()&lt;&#x2F;code&gt; という revset alias も定義しておく。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;jj log -r &quot;$target_revset &amp;amp; mutable() &amp;amp; local()&quot;&lt;&#x2F;code&gt; のようにプッシュ対象の revset と組み合わせて &lt;code&gt;jj log&lt;&#x2F;code&gt; コマンドを実行すると、「プッシュ対象」かつ「未プッシュの変更 (&lt;code&gt;mutable()&lt;&#x2F;code&gt;)」 (リモートにプッシュ済みの変更はチェック対象外とする) かつ「 &lt;code&gt;local()&lt;&#x2F;code&gt; に該当」する変更のみを抽出できる。
この実行結果が空であれば &lt;code&gt;local()&lt;&#x2F;code&gt; に該当する変更がプッシュ対象に含まれていないと確認できる。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;pre-commit&quot;&gt;pre-commit&lt;&#x2F;h3&gt;
&lt;p&gt;Jujutsu を利用する上で少し困るのが、 &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;pre-commit.com&#x2F;&quot;&gt;pre-commit&lt;&#x2F;a&gt; をはじめとした Git hooks をうまく扱えないことだ。
そこで私は、 &lt;code&gt;jj push&lt;&#x2F;code&gt; エイリアスの中で &lt;code&gt;pre-commit&lt;&#x2F;code&gt; を実行するように設定しているので、これもついでに紹介しておく。&lt;&#x2F;p&gt;
&lt;p&gt;本質的には &quot;Pre commit&quot; ではなくなってしまうが、 &quot;Pre push&quot; なチェックとして &lt;code&gt;pre-commit&lt;&#x2F;code&gt; ツールを実行するようにしている。
&lt;code&gt;pre-commit&lt;&#x2F;code&gt; コマンドの実行結果が失敗ならプッシュを止め、成功ならプッシュを実行するようにしておく。&lt;&#x2F;p&gt;
&lt;figure&gt;
  &lt;img src=&quot;&#x2F;images&#x2F;posts&#x2F;20261006_jj_pre_commit.png&quot; alt=&quot;&quot; &#x2F;&gt;
  
&lt;&#x2F;figure&gt;
&lt;h3 id=&quot;jj-push-エイリアス例&quot;&gt;jj push エイリアス例&lt;&#x2F;h3&gt;
&lt;p&gt;以上の内容を踏まえて、私は &lt;code&gt;jj push&lt;&#x2F;code&gt; エイリアスを次のように設定している。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rinx&#x2F;dotfiles&#x2F;blob&#x2F;d1033370a8cb2142a2c506d3131da5cb28633f6d&#x2F;jjconfig.toml#L45-L81&quot;&gt;https:&#x2F;&#x2F;github.com&#x2F;rinx&#x2F;dotfiles&#x2F;blob&#x2F;d1033370a8cb2142a2c506d3131da5cb28633f6d&#x2F;jjconfig.toml#L45-L81&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;aliases&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;push&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  &amp;#39;util&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  &amp;#39;exec&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  &amp;#39;--&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  &amp;#39;bash&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  &amp;#39;-c&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  &amp;#39;&amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;## pre-commit が利用できるときはチェックを実行&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;if type pre-commit &amp;gt;&#x2F;dev&#x2F;null 2&amp;gt;&amp;amp;1; then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    echo &amp;quot;==&amp;gt; Running pre-commit before push...&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    pre-commit run --all-files&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    if [ $? -ne 0 ]; then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;        echo &amp;quot;❌ pre-commit failed. Aborting push.&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;        exit 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    fi&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;else&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    echo &amp;quot;⚠️  pre-commit not found. Skipping checks.&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;fi&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;## コマンドライン引数からプッシュ対象の Revset を抽出。指定されていないときはデフォルトプッシュ対象の `::@` を使用&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;echo &amp;quot;==&amp;gt; Checking for forbidden markers in pushable commits...&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;targets=$(echo &amp;quot;$@&amp;quot; | grep -oP &amp;#39;(?&amp;lt;=(-b|--bookmark|-c|--change|-r|--revision)\s)[^ ]+&amp;#39;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;if [ -z &amp;quot;$targets&amp;quot; ]; then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  target_revset=&amp;quot;::@&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;else&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  target_revset=&amp;quot;::(${targets&#x2F;&#x2F;$&amp;#39;\n&amp;#39;&#x2F;|})&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;fi&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;## プッシュ対象に意図しない変更が含まれないかを確認&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;if [ -n &amp;quot;$(jj log -r &amp;quot;$target_revset &amp;amp; mutable() &amp;amp; local()&amp;quot; --no-graph --ignore-working-copy -T &amp;#39;commit_id&amp;#39;)&amp;quot; ]; then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    echo &amp;quot;❌ Commits to be pushed contain wip&#x2F;todo&#x2F;private&#x2F;mega markers. Aborting push.&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    exit 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;fi&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;## リモートへのプッシュを実行&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;echo &amp;quot;==&amp;gt; Pushing changes to remote...&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;echo &amp;quot;==&amp;gt; jj git push $@&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;jj git push $@&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;&amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  &amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;エイリアスとして簡単なガードレールを用意することで、より安心して Jujutsu を利用することができるようになる。
自身の使い方に合わせて最適なワークフローやエイリアスを構築してみてほしい。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;追記&quot;&gt;追記&lt;&#x2F;h2&gt;
&lt;p&gt;この記事を公開した後に気付いたが、 Jujutsu 自体に &lt;code&gt;git.private-commits&lt;&#x2F;code&gt; &lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#fn:5&quot;&gt;5&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; という設定項目があった。
「プライベートな変更をリモートにプッシュしないようにする」という目的のためには、公式に用意されたこの仕組みを利用する方が筋が良い。
本稿の設定例にあわせて書くと、次のようになる。&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;git&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;private-commits&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;local()&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;これにより、 &lt;code&gt;local()&lt;&#x2F;code&gt; に該当する変更とその子孫にあたる変更のプッシュを防止できる。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;jj push&lt;&#x2F;code&gt; エイリアス方式は pre-commit のようにプッシュ操作にフックして何かしらのチェックを実施したい場合には依然有効なので、これはこれで残しておこうと思う。&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;fn:1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;rinx.fyi&#x2F;posts&#x2F;20260922-jujutsu-workflow&#x2F;&quot;&gt;Jujutsu を用いた開発ワークフロー: Squash Workflow と Megamerge Workflow&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;fn:2&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;zenn.dev&#x2F;asa1984&#x2F;books&#x2F;nix-hands-on&#x2F;viewer&#x2F;ch01-03-flakes#git%E3%81%A8flake&quot;&gt;GitとFlake - Nix入門: ハンズオン編&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;fn:3&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;3&lt;&#x2F;sup&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.jj-vcs.dev&#x2F;latest&#x2F;config&#x2F;#aliases&quot;&gt;Aliases - Jujutsu docs&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;fn:4&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;4&lt;&#x2F;sup&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.jj-vcs.dev&#x2F;latest&#x2F;cli-reference&#x2F;#jj-util-exec&quot;&gt;jj util exec&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;fn:5&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;5&lt;&#x2F;sup&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.jj-vcs.dev&#x2F;latest&#x2F;config&#x2F;#set-of-private-commits&quot;&gt;Set of private commits - Jujutsu docs&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Jujutsu を用いた開発ワークフロー: Squash Workflow と Megamerge Workflow</title>
        <published>2026-09-22T00:00:00+00:00</published>
        <updated>2026-09-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              rinx
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://rinx.fyi/posts/20260922-jujutsu-workflow/"/>
        <id>https://rinx.fyi/posts/20260922-jujutsu-workflow/</id>
        
        <content type="html" xml:base="https://rinx.fyi/posts/20260922-jujutsu-workflow/">&lt;p&gt;業務を含めた開発フロー全般で &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;jj-vcs&#x2F;jj&quot;&gt;Jujutsu (jj-vcs&#x2F;jj, &lt;code&gt;jj&lt;&#x2F;code&gt;)&lt;&#x2F;a&gt; を使うようになってからもうすぐ半年になる。
現在の個人的な &lt;code&gt;jj&lt;&#x2F;code&gt; 利用ワークフローと、 &lt;code&gt;jj&lt;&#x2F;code&gt; について便利だと感じている部分をまとめておこうと思う。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;jujutsu-について&quot;&gt;Jujutsu について&lt;&#x2F;h2&gt;
&lt;p&gt;あらためて紹介する必要もないが、 &lt;code&gt;jj&lt;&#x2F;code&gt; は Git 互換のバージョン管理システムだ。&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;jj-vcs&#x2F;jj&quot;&gt;jj-vcs&#x2F;jj&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.jj-vcs.dev&#x2F;latest&#x2F;&quot;&gt;Jujutsu docs&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Git でできることのほとんどは &lt;code&gt;jj&lt;&#x2F;code&gt; でもできる。 &lt;code&gt;jj&lt;&#x2F;code&gt; でできることも大抵は Git でできる。
しかし、同じことをやろうとしても &lt;code&gt;jj&lt;&#x2F;code&gt; のほうが圧倒的に簡単にできる場合が多い。&lt;&#x2F;p&gt;
&lt;p&gt;たとえば、過去の特定のコミットだけを修正したいときに Git なら &lt;code&gt;git rebase -i&lt;&#x2F;code&gt; を利用し、該当のコミットに &lt;code&gt;edit&lt;&#x2F;code&gt; フラグを立てて編集モードに入り…と複雑な手順を踏む必要があるが、 &lt;code&gt;jj&lt;&#x2F;code&gt; の場合は &lt;code&gt;jj edit &amp;lt;rev&amp;gt;&lt;&#x2F;code&gt; で該当の変更を選択し、ファイルを編集するだけで簡単に修正できる。&lt;&#x2F;p&gt;
&lt;p&gt;Rebase 操作を例にとっても、 &lt;code&gt;jj rebase --before &amp;lt;before&amp;gt; --after &amp;lt;after&amp;gt; --revision &amp;lt;rev&amp;gt;&lt;&#x2F;code&gt; のように、どの変更をどの位置に Rebase するかが直感的にわかる形で操作ができる。&lt;&#x2F;p&gt;
&lt;p&gt;それらに加えて &lt;code&gt;jj undo&lt;&#x2F;code&gt;, &lt;code&gt;jj redo&lt;&#x2F;code&gt; により操作の Undo &#x2F; Redo ができるため、変更履歴の操作を行うことへのハードルが非常に低い。
この操作感に慣れてしまうと、もう素の Git に戻る気はなくなってしまう。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;jj&lt;&#x2F;code&gt; そのものの紹介は既にさまざまなリソースがあるので、ここでは参考文献を紹介するだけにとどめておく。&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;steveklabnik.github.io&#x2F;jujutsu-tutorial&#x2F;introduction&#x2F;introduction.html&quot;&gt;Steve&#x27;s Jujutsu Tutorial&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;arialdo.codeberg.page&#x2F;ju-ju-tsu&#x2F;&quot;&gt;Ju! Ju! Tsu!&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;zenn.dev&#x2F;zetamatta&#x2F;books&#x2F;c1e309aea68960&quot;&gt;君のレポジトリを領域展開 - 次世代バージョン管理システム Jujutsu (jj-vcs&#x2F;jj)の世界&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;squash-workflow&quot;&gt;Squash Workflow&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;code&gt;jj&lt;&#x2F;code&gt; を用いたワークフローの中でも特に Git ユーザーにとって馴染みやすいのが Squash Workflow だ。
私も基本的にはこのワークフローに則って作業をしている。&lt;&#x2F;p&gt;
&lt;p&gt;Git ではコミットに含めたい変更内容を index にステージング (&lt;code&gt;git add&lt;&#x2F;code&gt;) してからコミット (&lt;code&gt;git commit&lt;&#x2F;code&gt;) するが、 &lt;code&gt;jj&lt;&#x2F;code&gt; の Squash Workflow ではワーキングコピーにある変更内容を &lt;code&gt;jj squash&lt;&#x2F;code&gt; により過去の変更に移動させる。 &lt;code&gt;jj&lt;&#x2F;code&gt; には Git の index に対応するエリアはないが、このワークフローにおいてはワーキングコピーにある変更がステージングされた変更に該当すると捉えると理解しやすい。&lt;&#x2F;p&gt;
&lt;p&gt;このワークフローについては次のページの解説がわかりやすい。&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;steveklabnik.github.io&#x2F;jujutsu-tutorial&#x2F;real-world-workflows&#x2F;the-squash-workflow.html&quot;&gt;The Squash Workflow - Steve&#x27;s Jujutsu Tutorial&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;arialdo.codeberg.page&#x2F;ju-ju-tsu&#x2F;tutorial&#x2F;moving&#x2F;squash-workflow.html&quot;&gt;The Squash Workflow - Ju! Ju! Tsu!&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;冒頭で過去のコミットの修正を &lt;code&gt;jj edit &amp;lt;rev&amp;gt;&lt;&#x2F;code&gt; で行う例を挙げたが、 Squash Workflow を用いると次のようになる。&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;jj new &amp;lt;rev&amp;gt;&lt;&#x2F;code&gt; で修正したい変更から新しい変更を作成する&lt;&#x2F;li&gt;
&lt;li&gt;修正内容をワーキングコピーに反映する&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;jj squash&lt;&#x2F;code&gt; により、ワーキングコピーの変更を元の &lt;code&gt;&amp;lt;rev&amp;gt;&lt;&#x2F;code&gt; に移動させる&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;対象の変更に直接 &lt;code&gt;jj edit&lt;&#x2F;code&gt; を実行するよりも、このように Squash Workflow を用いたほうが、元の変更に対してどのような修正をしたかを確認してから反映できるため好ましい。
このあたりは &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;arialdo.codeberg.page&#x2F;ju-ju-tsu&#x2F;tutorial&#x2F;moving&#x2F;dont-edit.html&quot;&gt;Don’t Edit - Ju! Ju! Tsu!&lt;&#x2F;a&gt; で解説されている。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;megamerge-workflow&quot;&gt;Megamerge Workflow&lt;&#x2F;h2&gt;
&lt;p&gt;このワークフローは、ある意味では Squash Workflow の派生版とも言える。
詳しくは参考ページを読んでもらえると良いが、同じリポジトリ上で複数の作業を行うとき、ブランチを切り替える必要なく同じワーキングコピー上ですべての変更を行うことができる。&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;isaaccorbrey.com&#x2F;notes&#x2F;jujutsu-megamerges-for-fun-and-profit&quot;&gt;Jujutsu megamerges for fun and profit - Isaac Corbrey&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;v5.chriskrycho.com&#x2F;journal&#x2F;jujutsu-megamerges-and-jj-absorb&#x2F;&quot;&gt;Jujutsu Megamerges and jj absorb - Sym·poly·mathesy&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;steveklabnik.github.io&#x2F;jujutsu-tutorial&#x2F;advanced&#x2F;simultaneous-edits.html&quot;&gt;Working on all of your branches simultaneously - Steve&#x27;s Jujutsu Tutorial&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;arialdo.codeberg.page&#x2F;ju-ju-tsu&#x2F;tutorial&#x2F;moving&#x2F;absorbing.html&quot;&gt;Dispatching Edits from Megamerges - Ju! Ju! Tsu!&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;モノリポ構成で複数のコンポーネントに対する変更を並行で進める場合などは特にこのワークフローにフィットする。私も業務で触るリポジトリはだいたいこのパターンにあてはまるため、このワークフローをよく利用している。
たとえば、次のような変更を積んだリポジトリを考えてみる。&lt;&#x2F;p&gt;
&lt;figure&gt;
  &lt;img src=&quot;&#x2F;images&#x2F;posts&#x2F;20260922_jujutsu_workflow_megamerge.png&quot; alt=&quot;&quot; &#x2F;&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;変更グラフのすぐ右に表示されている8桁の文字列が Change-ID (変更ID) を表している。 Change-ID は太字になっている部分まででユニークに扱われる。
この例では、 ID が &lt;code&gt;r&lt;&#x2F;code&gt; の変更から4つの変更 (&lt;code&gt;u&lt;&#x2F;code&gt;, &lt;code&gt;v&lt;&#x2F;code&gt;, &lt;code&gt;sx&lt;&#x2F;code&gt;, &lt;code&gt;sr&lt;&#x2F;code&gt;) に派生して、それらを &lt;code&gt;n&lt;&#x2F;code&gt; でマージ (megamerge) している。&lt;&#x2F;p&gt;
&lt;p&gt;この仮想リポジトリでは、 Server 側の変更と Client 側の変更をローカル環境で同期させながら、リモート側には別ブランチとして Push してそれぞれ別の Pull Request として変更を提出することを想定している。
また、他メンバーに共有する必要のない &lt;code&gt;flake.nix&lt;&#x2F;code&gt;, &lt;code&gt;flake.lock&lt;&#x2F;code&gt; や &lt;code&gt;.harper-dictionary&lt;&#x2F;code&gt; を含む変更を Private な変更として扱い、リモートには Push しない方針としている。 (私の場合チームメンバーで Nix や Harper を利用している人はいないので、手元に置いておけば十分)
&lt;code&gt;jj&lt;&#x2F;code&gt; にはブランチがなく、代わりにコミットに対するブックマーク (bookmark) を指定するようになっている。そのため、 &lt;code&gt;sx&lt;&#x2F;code&gt;, &lt;code&gt;sr&lt;&#x2F;code&gt; のみに bookmark を指定して Push することで、リモートには不要な変更を Push せずに済む。&lt;&#x2F;p&gt;
&lt;p&gt;各ブランチに変更を加えたいときは、Squash Workflow と同じくワーキングコピー上で変更内容を作成し、 &lt;code&gt;jj squash&lt;&#x2F;code&gt; で該当する変更に移動させれば良い。
さらに、 &lt;code&gt;jj squash&lt;&#x2F;code&gt; の代わりに &lt;code&gt;jj absorb&lt;&#x2F;code&gt; を利用すれば、多くの場合で変更内容を自動的に適切な変更に Squash してくれる。先の例では、 &lt;code&gt;.harper-dictionary&lt;&#x2F;code&gt; に関する変更や &lt;code&gt;flake.nix&lt;&#x2F;code&gt; に関する変更がワーキングコピーに一緒に含まれていたとしても、 &lt;code&gt;jj absorb&lt;&#x2F;code&gt; を実行するだけで自動的に &lt;code&gt;v&lt;&#x2F;code&gt; や &lt;code&gt;u&lt;&#x2F;code&gt; の変更に取り込んでくれる。編集しているファイルや編集箇所をもとに適切な取り込み先を判別しているのだ。 Megamerge Workflow を利用する際はこれが非常に便利なので、覚えておくと良い。&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
