Openembedded Core Discussions
 help / color / mirror / Atom feed
From: Jamin Lin <jamin_lin@aspeedtech.com>
To: "openembedded-core@lists.openembedded.org"
	<openembedded-core@lists.openembedded.org>,
	"alex.kanavin@gmail.com" <alex.kanavin@gmail.com>,
	"paul@pbarker.dev" <paul@pbarker.dev>
Cc: Troy Lee <troy_lee@aspeedtech.com>, Jamin Lin <jamin_lin@aspeedtech.com>
Subject: [PATCH v3 1/5] devtool: Detect nested git repos before the initial workspace commit
Date: Thu, 23 Jul 2026 08:11:20 +0000	[thread overview]
Message-ID: <20260723081118.1558249-2-jamin_lin@aspeedtech.com> (raw)
In-Reply-To: <20260723081118.1558249-1-jamin_lin@aspeedtech.com>

setup_git_repo() is meant to convert a git repo that a recipe unpacks
inside S (e.g. via multiple git SRC_URI entries with different
destsuffix values) into a regular git submodule, so devtool can later
tag branches on it and extract patches from it via finish/update.

That detection never actually triggers, because of the order the
function runs things in:

  1. 'git init'
  2. 'git add -A .' + initial commit  <- commits the nested repo as a
                                         bare, unregistered gitlink
  3. checkout devbranch, tag basetag
  4. scan 'git status --porcelain' for still-untracked directories
     ("?? <dir>/") and convert any that are git repos into submodules

By the time step 4 runs, the nested repo was already swept up by step
2's 'git add -A .': git treats a directory containing its own .git as
an embedded repo and stages it as a gitlink pointing at its current
HEAD, without registering it as a submodule. Once that gitlink is
committed, 'git status --porcelain' reports it as e.g. " M leveldir"
(already tracked) rather than "?? leveldir/" (untracked), so step 4's
"line.endswith('/')" check can never match it, and the conversion to a
real submodule silently never happens.

This isn't just a missed feature: an unregistered gitlink that has its
own untracked content (e.g. a further nested git repo underneath it)
shows up as "dirty" to git status even though the tracked commit hash
hasn't changed. patch.bbclass's patch_task_postfunc sees that dirtiness
after do_patch and tries to commit it, but 'git add' has nothing new to
stage for a gitlink whose hash is unchanged, so the follow-up 'git
commit' fails with "nothing to commit" and do_patch fails outright.

Fix this by moving the nested-repo detection and submodule conversion
to run right after 'git init', before 'git add -A .' and the initial
commit. At that point the nested repo is still untracked and reported
with a trailing "/", so it's correctly picked up and registered via
'git submodule add' before anything commits it as a bare gitlink.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
 scripts/lib/devtool/__init__.py | 64 +++++++++++++++++++++------------
 1 file changed, 41 insertions(+), 23 deletions(-)

diff --git a/scripts/lib/devtool/__init__.py b/scripts/lib/devtool/__init__.py
index 58b02eb460..0003b7b107 100644
--- a/scripts/lib/devtool/__init__.py
+++ b/scripts/lib/devtool/__init__.py
@@ -200,6 +200,47 @@ def setup_git_repo(repodir, version, devbranch, basetag='devtool-base', d=None):
     if not os.path.exists(os.path.join(repodir, '.git')):
         bb.process.run('git init', cwd=repodir)
         bb.process.run('git config --local gc.autodetach 0', cwd=repodir)
+
+        # If the recipe unpacks another git repo inside S (e.g. multiple git
+        # SRC_URI entries with destsuffix), declare it as a regular git
+        # submodule now, so we will be able to tag branches on it and extract
+        # patches when doing finish/update on the recipe. This has to happen
+        # before 'git add -A .' below: once that runs, the nested repo is
+        # committed as a bare, unregistered gitlink and 'git status' no longer
+        # reports it as untracked ("?? <dir>/"), so this detection can never
+        # find it.
+        #
+        # Discover nested repos top-down (so we can still skip descending into
+        # a repo that manages its own submodules via .gitmodules), but do the
+        # actual 'git submodule add' + commit bottom-up (deepest repo first):
+        # a parent's commit recording its child's current HEAD must happen
+        # after that child is fully finalized, otherwise a deeper repo added
+        # later on gets its own registration commit, moving the child's HEAD
+        # forward again and leaving the parent's already-made commit pointing
+        # at a stale, superseded revision of it.
+        stdout, _ = bb.process.run("git status --porcelain", cwd=repodir)
+        nested_repos = []
+        for line in stdout.splitlines():
+            if line.endswith("/"):
+                new_dir = line.split()[1]
+                for root, dirs, files in os.walk(os.path.join(repodir, new_dir)):
+                    if ".git" in dirs + files:
+                        nested_repos.append(root)
+                        # Do not descend into nested git repos that have submodules themselves.
+                        if ".gitmodules" in files:
+                            logger.warning('Nested git repository with submodules %s; devtool will not recurse into it', root)
+                            dirs[:] = []
+
+        for root in reversed(nested_repos):
+            parentdir = os.path.join(root, "..")
+            (stdout, _) = bb.process.run('git remote', cwd=root)
+            remote = stdout.splitlines()[0]
+            (stdout, _) = bb.process.run('git remote get-url %s' % remote, cwd=root)
+            remote_url = stdout.splitlines()[0]
+            logger.error(os.path.relpath(parentdir, root))
+            bb.process.run('git submodule add %s %s' % (remote_url, os.path.relpath(root, parentdir)), cwd=parentdir)
+            oe.patch.GitApplyTree.commitIgnored("Add additional submodule from SRC_URI", dir=parentdir, d=d)
+
         bb.process.run('git add -f -A .', cwd=repodir)
         commit_cmd = ['git']
         oe.patch.GitApplyTree.gitCommandUserOptions(commit_cmd, d=d)
@@ -237,29 +278,6 @@ def setup_git_repo(repodir, version, devbranch, basetag='devtool-base', d=None):
     bb.process.run('git checkout -b %s' % devbranch, cwd=repodir)
     bb.process.run('git tag -f --no-sign %s' % basetag, cwd=repodir)
 
-    # if recipe unpacks another git repo inside S, we need to declare it as a regular git submodule now,
-    # so we will be able to tag branches on it and extract patches when doing finish/update on the recipe
-    stdout, _ = bb.process.run("git status --porcelain", cwd=repodir)
-    found = False
-    for line in stdout.splitlines():
-        if line.endswith("/"):
-            new_dir = line.split()[1]
-            for root, dirs, files in os.walk(os.path.join(repodir, new_dir)):
-                if ".git" in dirs + files:
-                    (stdout, _) = bb.process.run('git remote', cwd=root)
-                    remote = stdout.splitlines()[0]
-                    (stdout, _) = bb.process.run('git remote get-url %s' % remote, cwd=root)
-                    remote_url = stdout.splitlines()[0]
-                    logger.error(os.path.relpath(os.path.join(root, ".."), root))
-                    bb.process.run('git submodule add %s %s' % (remote_url, os.path.relpath(root, os.path.join(root, ".."))), cwd=os.path.join(root, ".."))
-                    # Do not descend into nested git repos that have submodules themselves.
-                    if ".gitmodules" in files:
-                        logger.warning('Nested git repository with submodules %s; devtool will not recurse into it', root)
-                        dirs[:] = []
-                    found = True
-                if found:
-                    oe.patch.GitApplyTree.commitIgnored("Add additional submodule from SRC_URI", dir=os.path.join(root, ".."), d=d)
-                    found = False
     if os.path.exists(os.path.join(repodir, '.gitmodules')):
         bb.process.run('git submodule foreach --recursive  "git tag -f --no-sign %s"' % basetag, cwd=repodir)
 
-- 
2.43.0


  reply	other threads:[~2026-07-23  8:11 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-23  8:11 [PATCH v3 0/5] devtool: fix standalone clone conversion for nested git repos Jamin Lin
2026-07-23  8:11 ` Jamin Lin [this message]
2026-07-23  8:11 ` [PATCH v3 2/5] oe/patch: Skip commitIgnored when nothing is actually staged Jamin Lin
2026-07-23  8:11 ` [PATCH v3 3/5] devtool-source: Convert nested git SRC_URI destsuffix repos to standalone clones Jamin Lin
2026-07-23  8:11 ` [PATCH v3 4/5] meta-selftest: Add devtool-test-multi-destsuffix recipe Jamin Lin
2026-07-23  8:11 ` [PATCH v3 5/5] oeqa/selftest/devtool: Add test for multiple nested git destsuffix repos Jamin Lin

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260723081118.1558249-2-jamin_lin@aspeedtech.com \
    --to=jamin_lin@aspeedtech.com \
    --cc=alex.kanavin@gmail.com \
    --cc=openembedded-core@lists.openembedded.org \
    --cc=paul@pbarker.dev \
    --cc=troy_lee@aspeedtech.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox