<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mengchao Ren</title>
    <description>The latest articles on DEV Community by Mengchao Ren (@mengchaoren).</description>
    <link>https://dev.to/mengchaoren</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4109936%2Fddffe2ba-de2b-4374-a9f1-51dc98317702.jpg</url>
      <title>DEV Community: Mengchao Ren</title>
      <link>https://dev.to/mengchaoren</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mengchaoren"/>
    <language>en</language>
    <item>
      <title>Two Git Worktrees, Two Django Migrations, One Coordination Problem</title>
      <dc:creator>Mengchao Ren</dc:creator>
      <pubDate>Fri, 04 Sep 2026 14:18:01 +0000</pubDate>
      <link>https://dev.to/mengchaoren/two-git-worktrees-two-django-migrations-one-coordination-problem-38c5</link>
      <guid>https://dev.to/mengchaoren/two-git-worktrees-two-django-migrations-one-coordination-problem-38c5</guid>
      <description>&lt;p&gt;Git worktrees are a great fit for parallel development: one repository can have several branches checked out into separate directories at the same time.&lt;/p&gt;

&lt;p&gt;That convenience creates a coordination gap.&lt;/p&gt;

&lt;p&gt;Imagine two tasks starting from the same commit. One worktree handles invoices; another handles payments. Both tasks need a Django migration in the same &lt;code&gt;billing&lt;/code&gt; app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;invoice worktree:
billing/migrations/0002_invoice.py

payment worktree:
billing/migrations/0002_payment.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Neither file has to be committed yet. Each task looks reasonable in isolation, but the two branches now deserve a joint review before they meet.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://github.com/mengchar-cmu-F25/branchradar" rel="noopener noreferrer"&gt;BranchRadar&lt;/a&gt; to flag that situation while the work is still in progress. It reads Git metadata and compares changed paths across worktrees, including committed, staged, unstaged, and untracked files.&lt;/p&gt;

&lt;p&gt;One important caveat up front: this is a &lt;strong&gt;path-level warning&lt;/strong&gt;, not proof that the Django migration graph is broken. The goal is to surface a coordination decision early enough for a human to inspect it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install it
&lt;/h2&gt;

&lt;p&gt;BranchRadar v0.1.1 requires Python 3.11+ and Git. You can install the release wheel directly into a virtual environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://github.com/mengchar-cmu-F25/branchradar/releases/download/v0.1.1/branchradar-0.1.1-py3-none-any.whl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The basic command is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;branchradar &lt;span class="nt"&gt;--repo&lt;/span&gt; /path/to/repo &lt;span class="nt"&gt;--base&lt;/span&gt; main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Reproduce the problem in a disposable repository
&lt;/h2&gt;

&lt;p&gt;This demo creates an isolated Git repository and two worktrees. The migration files are intentionally empty because BranchRadar is testing path-level coordination, not executing Django.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;br_demo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
git init &lt;span class="nt"&gt;-b&lt;/span&gt; main &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$br_demo&lt;/span&gt;&lt;span class="s2"&gt;/repo"&lt;/span&gt;
git &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$br_demo&lt;/span&gt;&lt;span class="s2"&gt;/repo"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; user.name&lt;span class="o"&gt;=&lt;/span&gt;Demo &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; user.email&lt;span class="o"&gt;=&lt;/span&gt;demo@example.test &lt;span class="se"&gt;\&lt;/span&gt;
  commit &lt;span class="nt"&gt;--allow-empty&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; base

git &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$br_demo&lt;/span&gt;&lt;span class="s2"&gt;/repo"&lt;/span&gt; worktree add &lt;span class="nt"&gt;-b&lt;/span&gt; invoice &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$br_demo&lt;/span&gt;&lt;span class="s2"&gt;/invoice"&lt;/span&gt;
git &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$br_demo&lt;/span&gt;&lt;span class="s2"&gt;/repo"&lt;/span&gt; worktree add &lt;span class="nt"&gt;-b&lt;/span&gt; payment &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$br_demo&lt;/span&gt;&lt;span class="s2"&gt;/payment"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first, both worktrees are clean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;branchradar &lt;span class="nt"&gt;--repo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$br_demo&lt;/span&gt;&lt;span class="s2"&gt;/repo"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result ends with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Risks: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create one untracked migration in each worktree:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$br_demo&lt;/span&gt;&lt;span class="s2"&gt;/invoice/billing/migrations"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$br_demo&lt;/span&gt;&lt;span class="s2"&gt;/payment/billing/migrations"&lt;/span&gt;

&lt;span class="nb"&gt;touch&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$br_demo&lt;/span&gt;&lt;span class="s2"&gt;/invoice/billing/migrations/0002_invoice.py"&lt;/span&gt;
&lt;span class="nb"&gt;touch&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$br_demo&lt;/span&gt;&lt;span class="s2"&gt;/payment/billing/migrations/0002_payment.py"&lt;/span&gt;

branchradar &lt;span class="nt"&gt;--repo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$br_demo&lt;/span&gt;&lt;span class="s2"&gt;/repo"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the relevant output. I shortened only the temporary path and base commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BranchRadar: 2 branches against main (&amp;lt;base-commit&amp;gt;)
- invoice (1 files) @ &amp;lt;tmp&amp;gt;/invoice; migrations=billing; dirty=1
- payment (1 files) @ &amp;lt;tmp&amp;gt;/payment; migrations=billing; dirty=1
Risks: 1
- [HIGH] parallel_django_migrations: invoice [refs/heads/invoice] &amp;lt;-&amp;gt; payment [refs/heads/payment]: both branches change Django migrations in 'billing'
  invoice [refs/heads/invoice]: billing/migrations/0002_invoice.py
  payment [refs/heads/payment]: billing/migrations/0002_payment.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both files are still untracked. The worktrees can even point to the same commit; BranchRadar includes their current working-tree state when it calculates the paths to compare.&lt;/p&gt;

&lt;p&gt;A finding exits with status &lt;code&gt;1&lt;/code&gt;, so it can be used as a local guard or CI check. Run this demo interactively: a script using &lt;code&gt;set -e&lt;/code&gt; would stop at that expected warning.&lt;/p&gt;

&lt;h2&gt;
  
  
  A negative control
&lt;/h2&gt;

&lt;p&gt;A useful detector also needs an example it should &lt;em&gt;not&lt;/em&gt; flag.&lt;/p&gt;

&lt;p&gt;Remove the payment migration from &lt;code&gt;billing&lt;/code&gt;, then create one under a different Django app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$br_demo&lt;/span&gt;&lt;span class="s2"&gt;/payment/billing/migrations/0002_payment.py"&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$br_demo&lt;/span&gt;&lt;span class="s2"&gt;/payment/orders/migrations"&lt;/span&gt;
&lt;span class="nb"&gt;touch&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$br_demo&lt;/span&gt;&lt;span class="s2"&gt;/payment/orders/migrations/0002_order.py"&lt;/span&gt;

branchradar &lt;span class="nt"&gt;--repo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$br_demo&lt;/span&gt;&lt;span class="s2"&gt;/repo"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now both worktrees contain migration changes, but their app paths do not overlap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BranchRadar: 2 branches against main (&amp;lt;base-commit&amp;gt;)
- invoice (1 files) @ &amp;lt;tmp&amp;gt;/invoice; migrations=billing; dirty=1
- payment (1 files) @ &amp;lt;tmp&amp;gt;/payment; migrations=orders; dirty=1
Risks: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command exits with status &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This matters because BranchRadar is not warning merely because two branches contain migration files. It groups migration paths by the directory before &lt;code&gt;/migrations/&lt;/code&gt; and reports a risk when both sides of a branch pair change migrations in the same app.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the comparison works
&lt;/h2&gt;

&lt;p&gt;By default, BranchRadar analyzes branches currently checked out in worktrees. &lt;code&gt;--all-local-branches&lt;/code&gt; opts other local branches into the scan.&lt;/p&gt;

&lt;p&gt;For every pair of candidates, it finds their mutual merge base and collects the paths exclusive to each side after that point. This helps avoid treating stacked or shared history as two independent changes. Checked-out worktrees also contribute staged, unstaged, and untracked non-ignored paths.&lt;/p&gt;

&lt;p&gt;For automation, JSON output is available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;branchradar &lt;span class="nt"&gt;--repo&lt;/span&gt; /path/to/repo &lt;span class="nt"&gt;--base&lt;/span&gt; main &lt;span class="nt"&gt;--format&lt;/span&gt; json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exit statuses are intentionally simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0  no configured path overlap found
1  one or more risks found
2  usage or Git error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;BranchRadar also has a configurable detector for API producer/consumer overlap. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[contracts.public_api]&lt;/span&gt;
&lt;span class="py"&gt;producer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"backend/api/**"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"openapi/**"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;consumer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"frontend/src/api/**"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If one branch changes a configured producer path while another changes its consumer path, the report names both branches, their merge base, and the evidence paths. That is still a request for joint review, not a claim that the API is definitely incompatible.&lt;/p&gt;

&lt;h2&gt;
  
  
  What v0.1.1 intentionally does not do
&lt;/h2&gt;

&lt;p&gt;BranchRadar does not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;inspect migration operations or dependency declarations;&lt;/li&gt;
&lt;li&gt;prove that two migrations are incompatible;&lt;/li&gt;
&lt;li&gt;validate API semantics;&lt;/li&gt;
&lt;li&gt;run Django commands or project tests;&lt;/li&gt;
&lt;li&gt;merge or modify branches;&lt;/li&gt;
&lt;li&gt;run coding agents or build a general code graph.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two same-app migrations may be compatible and still produce a warning. Migrations in different apps may have relationships that path matching cannot see. Squash-equivalent work without shared Git ancestry can also produce a warning.&lt;/p&gt;

&lt;p&gt;I kept this first version narrow because an explainable warning with exact paths is useful only if the signal remains easy to verify.&lt;/p&gt;

&lt;h2&gt;
  
  
  The feedback I want
&lt;/h2&gt;

&lt;p&gt;If you use Git worktrees—or run coding agents in parallel—I would especially value answers to these questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Is “two independent migration changes in the same Django app” useful in your workflow, or too noisy?&lt;/li&gt;
&lt;li&gt;Should a later version inspect Django migration dependencies, or is path-level evidence easier to trust?&lt;/li&gt;
&lt;li&gt;Should checked-out worktrees remain the default scope, or should every local branch be scanned automatically?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The repository, fixed v0.1.1 wheel, full synthetic demo, and issue tracker are here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/mengchar-cmu-F25/branchradar" rel="noopener noreferrer"&gt;github.com/mengchar-cmu-F25/branchradar&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: this post was produced with AI assistance. Its commands and claims were checked against the linked repository, its passing test suite, and a fresh disposable two-worktree run. I remain responsible for the content.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>git</category>
      <category>python</category>
      <category>django</category>
    </item>
  </channel>
</rss>
