修复win11上编译问题
authorsnickersun <snickersun@163.com>
Sat, 11 Apr 2026 07:47:45 +0000 (15:47 +0800)
committersnickersun <snickersun@163.com>
Sat, 11 Apr 2026 07:47:45 +0000 (15:47 +0800)
2.sdk/Helios_SDK/Makefile
2.sdk/Helios_SDK/compile.py
2.sdk/Helios_SDK/link.mk
2.sdk/build.bat
2.sdk/clean.bat
2.sdk/new_clean.bat
2.sdk/project/7PIN/new.bat
2.sdk/project/7PIN_1_5/new.bat
gitdiff.py [new file with mode: 0644]

index f1b5029..2a34b4f 100644 (file)
@@ -467,7 +467,7 @@ common_clean:
 
 clean: private_clean common_clean
 
-
+COMSPEC:=$(shell echo %COMSPEC%)
 
 #################################################################
 #                                                               #
@@ -486,13 +486,8 @@ endef
 
 # Else, in BASH environment
 else
-
-# Get helios version
-ifeq ($(shell type -P helios),)
-    HELIOS_VERSION :=
-else
-    HELIOS_VERSION := $(shell helios --version | grep -Po 'Helios \(released by Quectel\) \K[^\s]+')
-endif
+# Get helios version (reuse the existing Windows batch for consistent parsing)
+HELIOS_VERSION := $(shell cmd /c get_helios_version.bat)
 # Define a macro to check if a string represents a number
 define is_number
 $(shell echo "$1" | grep -q '^[0-9]\+$' && echo "true" || echo "false")
@@ -506,19 +501,10 @@ ifeq ($(HELIOS_VERSION),)
 else ifeq ($(HELIOS_VERSION),1.4.1)
     IS_NEWER_THAN_1_4_1 := true
 else
-    # Split the version into parts
-    HELIOS_VERSION_PARTS := $(subst ., ,$(HELIOS_VERSION))
-    # Check if major, minor, and patch are numbers
-    IS_MAJOR_NUMBER := $(call is_number,$(word 1,$(HELIOS_VERSION_PARTS)))
-    IS_MINOR_NUMBER := $(call is_number,$(word 2,$(HELIOS_VERSION_PARTS)))
-    IS_PATCH_NUMBER := $(call is_number,$(word 3,$(HELIOS_VERSION_PARTS)))
-
-    # If any part is not a number, report an error
-    ifneq ($(IS_MAJOR_NUMBER)$(IS_MINOR_NUMBER)$(IS_PATCH_NUMBER),truetruetrue)
+    # Parse and compare semantic version in one step (avoid shell quoting issues).
+    IS_NEWER_THAN_1_4_1 := $(shell $(PY) -c "import sys; v=sys.argv[1].strip(); parts=v.split('.'); ok=(len(parts)==3 and all(p.isdigit() for p in parts)); print('invalid' if not ok else ('true' if tuple(map(int,parts)) >= (1,4,1) else 'false'))" "$(HELIOS_VERSION)")
+    ifeq ($(IS_NEWER_THAN_1_4_1),invalid)
         $(error "Unexpected version format: $(HELIOS_VERSION)")
-    else
-        # Compare the version with 1.4.1
-        IS_NEWER_THAN_1_4_1 := $(shell echo "$(HELIOS_VERSION)" | awk -F. '{if ($$1 > 1 || ($$1 == 1 && $$2 >= 4 && $$3 >= 1)) print "true"; else print "false"}')
     endif
 endif
 
index 5fcf025..06d3a35 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import json, os, re, sys
+import json, os, re, sys, shutil, subprocess
 
 dep_dict = {}
+SYSTEMROOT = os.environ.get('SystemRoot', r'C:\Windows')
+# 不信任 COMSPEC/ComSpec(批处理/工具链可能把它设成相对路径 cmd.exe),直接固定用系统 cmd.exe 绝对路径
+CMD_EXE = os.path.join(SYSTEMROOT, 'System32', 'cmd.exe')
+
+def _inject_make_abs_path(cmd: str) -> str:
+    """把命令开头的 make 替换成绝对路径,避免 PATH/编码导致找不到可执行文件。"""
+    make_exe = shutil.which('make')
+    if make_exe and cmd.lstrip().startswith('make '):
+        # 只替换开头的 'make ',保留后续参数原样
+        prefix_len = len(cmd) - len(cmd.lstrip())
+        return cmd[:prefix_len] + make_exe + cmd[prefix_len + 4:]
+    return cmd
+
+def run_cmd_capture(cmd: str) -> bytes:
+    """
+    显式用 cmd.exe 执行(不要依赖 COMSPEC/默认 shell),并捕获 stdout+stderr。
+    """
+    cmd = _inject_make_abs_path(cmd)
+    p = subprocess.Popen(
+        [CMD_EXE, '/d', '/s', '/c', cmd],
+        shell=False,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.STDOUT,
+    )
+    out, _ = p.communicate()
+    return out or b''
+
+def system_cmd(cmd: str) -> int:
+    """用 subprocess.call 显式 cmd.exe 执行。"""
+    cmd = _inject_make_abs_path(cmd)
+    return subprocess.call([CMD_EXE, '/d', '/s', '/c', cmd], shell=False)
 
 def not_empty(s):
     if not s == '':
@@ -43,9 +74,7 @@ def search_for_deps(comp_path):
     
     comp_mk = comp_path + '/' + comp_path.split('/')[-1] + '.mk'
     cmd = 'make --no-print-directory -f comp_info_tell.mk' + ' COMP_MK=' + comp_mk
-    res = os.popen(cmd)
-    ret = res.buffer.read().decode(encoding='utf-8')
-    res.close()
+    ret = run_cmd_capture(cmd).decode(encoding='utf-8', errors='ignore')
     if not ret:
         raise RuntimeError('cmd [%s] execution error' % cmd)
     regex = re.compile(r'NAME: (.*)\nGLOBAL_INCS: (.*)\nGLOBAL_DEFINE: (.*)\nLOCAL_CFLAGS: (.*)\nLOCAL_ARCHIVES: (.*)\nLOCAL_SRCS: (.*)\nLOCAL_SRCS_2: (.*)\nLOCAL_SRCS_3: (.*)\nLOCAL_INCS: (.*)\nLOCAL_DEFINE: (.*)\nCOMP_DEPS: (.*)\n')
@@ -193,20 +222,25 @@ def compile_proc():
             if 'DEF_COLLECT' in v and v['DEF_COLLECT']:
                 comp_dflags = ' '.join(v['DEF_COLLECT'])
                 os.environ['COMP_DFLAGS'] = comp_dflags
-            if os.system(cmd):
+            if system_cmd(cmd):
                 raise RuntimeError('cmd [%s] execution error' % cmd)
         if 'LOCAL_ARCHIVES' in v and v['LOCAL_ARCHIVES']:
             user_archive_list.extend(list(map(lambda x : k + '/' + x, v['LOCAL_ARCHIVES'])))
     
     target_bin = os.environ['TARGET_BIN']
-    cmd = 'make --no-print-directory -f link.mk ' + target_bin + ' COMP_ARCHIVES="' + ' '.join(comp_archive_list) + '"' + ' USER_ARCHIVES="' + ' '.join(user_archive_list) + '"'
-    if os.system(cmd):
-        raise RuntimeError('cmd [%s] execution error' % cmd)
+    make_exe = shutil.which('make') or 'make'
+    env = os.environ.copy()
+    env['COMP_ARCHIVES'] = ' '.join(comp_archive_list)
+    env['USER_ARCHIVES'] = ' '.join(user_archive_list)
+    if subprocess.call([make_exe, '--no-print-directory', '-f', 'link.mk', target_bin], env=env):
+        raise RuntimeError('cmd [make -f link.mk] execution error')
     if os.environ['PLAT'] != 'RDA' and os.environ['PLAT'] != 'Qualcomm':
         target_hex = os.environ['TARGET_HEX']
-        cmd = 'make --no-print-directory -f link.mk ' + target_hex + ' COMP_ARCHIVES="' + ' '.join(comp_archive_list) + '"' + ' USER_ARCHIVES="' + ' '.join(user_archive_list) + '"'
-        if os.system(cmd):
-            raise RuntimeError('cmd [%s] execution error' % cmd)
+        env = os.environ.copy()
+        env['COMP_ARCHIVES'] = ' '.join(comp_archive_list)
+        env['USER_ARCHIVES'] = ' '.join(user_archive_list)
+        if subprocess.call([make_exe, '--no-print-directory', '-f', 'link.mk', target_hex], env=env):
+            raise RuntimeError('cmd [make -f link.mk] execution error')
 
 def build_proc(app):
     print('\n[searching for all components ...]   ', end = '', flush=True)
index 9ad2bcf..854c0b3 100644 (file)
@@ -12,6 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+USE_WHOLE_ARCHIVE ?= y
+
 $(TARGET_BIN): $(TARGET_ELF)
 ifeq ($(strip $(PLAT)),Qualcomm)
        $(Q)$(OBJCOPY_PY) --bin $^ --output $@
@@ -36,12 +38,29 @@ $(OUTPUT_LD_FILE): $(LD_FILE)
 $(TARGET_ELF): $(OUTPUT_LD_FILE) $(COMP_ARCHIVES) $(USER_ARCHIVES) $(STD_LIBS)
        @echo
 ifeq ($(strip $(PLAT)),ECR6600)
+ifeq ($(strip $(USE_WHOLE_ARCHIVE)),n)
+       # ROM overflow 时,保留 microPython 与 USER_ARCHIVES 的 whole-archive,
+       # 其余 COMP_ARCHIVES 走普通按需拉取,避免输出空壳。
+       $(Q)$(CC) -Wl,--whole-archive \
+               $(filter %/services/microPython/libmicroPython.a,$(COMP_ARCHIVES)) $(USER_ARCHIVES) \
+               -Wl,--no-whole-archive $(filter-out %/services/microPython/libmicroPython.a,$(COMP_ARCHIVES)) $(STD_LIBS) \
+               -o $@ $(LDFLAGS)
+else
        $(Q)$(CC) -Wl,--whole-archive \
-       $(COMP_ARCHIVES) $(USER_ARCHIVES) -Wl,--no-whole-archive $(STD_LIBS) \
-       -o $@ $(LDFLAGS)
+               $(COMP_ARCHIVES) $(USER_ARCHIVES) -Wl,--no-whole-archive $(STD_LIBS) \
+               -o $@ $(LDFLAGS)
+endif
 else
+ifeq ($(strip $(USE_WHOLE_ARCHIVE)),n)
+       # 同上:只对 microPython 与 USER_ARCHIVES 使用 whole-archive
        $(LD) --whole-archive \
-       $(COMP_ARCHIVES) $(USER_ARCHIVES) --no-whole-archive $(STD_LIBS) \
-       -o $@ $(LDFLAGS)
+               $(filter %/services/microPython/libmicroPython.a,$(COMP_ARCHIVES)) $(USER_ARCHIVES) \
+               --no-whole-archive $(filter-out %/services/microPython/libmicroPython.a,$(COMP_ARCHIVES)) $(STD_LIBS) \
+               -o $@ $(LDFLAGS)
+else
+       $(LD) --whole-archive \
+               $(COMP_ARCHIVES) $(USER_ARCHIVES) --no-whole-archive $(STD_LIBS) \
+               -o $@ $(LDFLAGS)
+endif
 endif
        @echo ----- $@ updated
index ef3c24d..f0aa9ee 100644 (file)
@@ -1,4 +1,7 @@
 @echo off
+set PATH=D:\helios\bin;%PATH%
+@REM set COMSPEC=cmd.exe
+set "COMSPEC=%SystemRoot%\System32\cmd.exe"
 cd Helios_SDK
 helios make services/microPython @EG810MCN_GA_ALIPAY EG810MCN_GA_ALIPAY
 cd ..
index eb57536..ab4aa08 100644 (file)
@@ -1,5 +1,7 @@
 @echo off
-
+set PATH=D:\helios\bin;%PATH%
+@REM set COMSPEC=cmd.exe
+set "COMSPEC=%SystemRoot%\System32\cmd.exe"
 cd Helios_SDK
 helios clean
 cd ..
index 4c55cdb..f2e19e6 100644 (file)
@@ -1,5 +1,6 @@
 @echo off
-
+set PATH=D:\helios\bin;%PATH%
+set "COMSPEC=%SystemRoot%\System32\cmd.exe"
 cd Helios_SDK
 helios clean
 cd ..
index bd2d9ea..853a796 100644 (file)
@@ -5,31 +5,31 @@ del images /q
 @GOTO :STEP1
 ..\..\Helios_SDK\tools\ASR_1606\win32\mklfs.exe -c ext -b 4096 -r 4096 -p 4096 -s 1048576 -i .\images\ext_fs.bin
 if errorlevel 1 (
-    echo "ext_fs.binÉú³Éʧ°Ü !!!!!!!!!!!!!!! "
+    echo "ext_fs.bin����ʧ�� !!!!!!!!!!!!!!! "
     goto exit
 )
 if errorlevel 0 (
-    echo "ext_fs.binÉú³É³É¹¦ !!!!!!!!!!!!!!! "
+    echo "ext_fs.bin���ɳɹ� !!!!!!!!!!!!!!! "
 )
 :STEP1
 
 ..\..\Helios_SDK\tools\ASR_1606\win32\mklfs.exe -c usr -b 4096 -r 4096 -p 4096 -s 1376256 -i .\images\customer_fs.bin
 if errorlevel 1 (
-    echo "customer_fs.binÉú³Éʧ°Ü Çë¼ì²éusrĿ¼ÊÇ·ñ´óÓÚ316K!!!!!!!!!!!!!!!"
+    echo "customer_fs.bin����ʧ�� ����usrĿ¼�Ƿ����316K!!!!!!!!!!!!!!!"
     goto exit
 )
 if errorlevel 0 (
-    echo "customer_fs.binÉú³É³É¹¦ !!!!!!!!!!!!!!! "
+    echo "customer_fs.bin���ɳɹ� !!!!!!!!!!!!!!! "
 )
 
 %copy font\ext_font.bin images\ext_font.bin%
 
 ..\..\Helios_SDK\tools\common\win32\7z\7z a sdk\system\platform\ASR_1606\boards\EG810MCN_GA_VOLTE\images\firmware_base_release_pack.zip .\images\
 if errorlevel 0 (
-    echo imagesѹËõ³É¹¦!!!!!!!!!!!!!!
+    echo imagesѹ���ɹ�!!!!!!!!!!!!!!
 )
 if errorlevel 1 (
-    echo imagesѹËõʧ°Ü!!!!!!!!!!!!!!!!!!
+    echo imagesѹ��ʧ��!!!!!!!!!!!!!!!!!!
     goto exit
 )
 xcopy  sdk\ ..\..\Helios_SDK\ /e/h/y
index 65596b9..396c014 100644 (file)
@@ -1,39 +1,38 @@
 @echo off\r
-\r
 md images\r
 del images /q\r
 @GOTO :STEP1\r
 ..\..\Helios_SDK\tools\ASR_1606\win32\mklfs.exe -c ext -b 4096 -r 4096 -p 4096 -s 1048576 -i .\images\ext_fs.bin\r
 if errorlevel 1 (\r
-    echo "ext_fs.binÉú³Éʧ°Ü !!!!!!!!!!!!!!! "\r
+    echo "ext_fs.bin生成失败 !!!!!!!!!!!!!!! "\r
     goto exit\r
 )\r
 if errorlevel 0 (\r
-    echo "ext_fs.binÉú³É³É¹¦ !!!!!!!!!!!!!!! "\r
+    echo "ext_fs.bin生成成功 !!!!!!!!!!!!!!! "\r
 )\r
 :STEP1\r
 \r
 ..\..\Helios_SDK\tools\ASR_1606\win32\mklfs.exe -c usr -b 4096 -r 4096 -p 4096 -s 1376256 -i .\images\customer_fs.bin\r
 if errorlevel 1 (\r
-    echo "customer_fs.binÉú³Éʧ°Ü Çë¼ì²éusrĿ¼ÊÇ·ñ´óÓÚ316K!!!!!!!!!!!!!!!"\r
+    echo "customer_fs.bin生成失败 请检查usr目录是否大于316K!!!!!!!!!!!!!!!"\r
     goto exit\r
 )\r
 if errorlevel 0 (\r
-    echo "customer_fs.binÉú³É³É¹¦ !!!!!!!!!!!!!!! "\r
+    echo "customer_fs.bin生成成功 !!!!!!!!!!!!!!! "\r
 )\r
 \r
 %copy font\ext_font.bin images\ext_font.bin%\r
 \r
 ..\..\Helios_SDK\tools\common\win32\7z\7z a sdk\system\platform\ASR_1606\boards\EG810MCN_GA_VOLTE\images\firmware_base_release_pack.zip .\images\\r
 if errorlevel 0 (\r
-    echo imagesѹËõ³É¹¦!!!!!!!!!!!!!!\r
+    echo images压缩成功!!!!!!!!!!!!!!\r
 )\r
 if errorlevel 1 (\r
-    echo imagesѹËõʧ°Ü!!!!!!!!!!!!!!!!!!\r
+    echo images压缩失败!!!!!!!!!!!!!!!!!!\r
     goto exit\r
 )\r
 xcopy  sdk\ ..\..\Helios_SDK\ /e/h/y\r
 cd ..\..\Helios_SDK\r
-helios make services/microPython @EG810MCN_GA_VOLTE EG810MCN_GA_VOLTE\r
+helios.exe make services/microPython @EG810MCN_GA_VOLTE EG810MCN_GA_VOLTE\r
 cd ..\r
 :exit
\ No newline at end of file
diff --git a/gitdiff.py b/gitdiff.py
new file mode 100644 (file)
index 0000000..e580d4f
--- /dev/null
@@ -0,0 +1,234 @@
+import os
+import subprocess
+import datetime
+import sys
+from pathlib import Path
+
+def run_git_command(command):
+    try:
+        # 设置环境变量以正确处理中文文件名
+        env = os.environ.copy()
+        env['LC_ALL'] = 'C.UTF-8'
+        env['LANG'] = 'C.UTF-8'
+        
+        result = subprocess.run(
+            command,
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE,
+            encoding='utf-8',
+            errors='replace',
+            check=True,
+            env=env
+        )
+        return result.stdout.strip()
+    except subprocess.CalledProcessError as e:
+        print(f"Error executing git command: {e.stderr}")
+        # 不要立即退出,让调用者处理错误
+        raise
+    except UnicodeDecodeError as e:
+        print(f"Encoding error: {e}")
+        raise
+
+def main():
+    if len(sys.argv) > 3:
+        print("Usage: python git_changes_extractor.py [commit1 [commit2]]")
+        print("       If no arguments are provided, uncommitted changes will be extracted")
+        print("       If only commit1 is provided, it defaults to comparing with HEAD")
+        sys.exit(1)
+
+    # 处理不同参数情况
+    if len(sys.argv) == 1:
+        commit1 = None
+        commit2 = None
+        mode = "uncommitted"
+    elif len(sys.argv) == 2:
+        commit1 = sys.argv[1]
+        commit2 = "HEAD"
+        mode = "commits"
+    else:
+        commit1 = sys.argv[1]
+        commit2 = sys.argv[2]
+        mode = "commits"
+
+    # 获取当前仓库路径和名称
+    repo_path = Path(os.getcwd())
+    repo_name = repo_path.name
+
+    # 创建输出目录 (格式: 仓库名_时间戳)
+    timestamp = datetime.datetime.now().strftime("%y%m%d%H%M%S")
+    output_dir_name = f"{repo_name}_{timestamp}"
+    patches_dir = repo_path.parent / "PATCHES"
+    output_dir = patches_dir / output_dir_name
+    output_dir.mkdir(parents=True, exist_ok=True)
+    
+    # 创建diff子目录
+    diff_dir = output_dir / "diff"
+    diff_dir.mkdir(exist_ok=True)
+
+    # 获取变更文件列表
+    if mode == "uncommitted":
+        # 获取暂存区变更
+        staged_changes = run_git_command(
+            ["git", "diff", "--name-status", "--cached", "--diff-filter=ACDMRTUXB"]
+        ).splitlines()
+        
+        # 获取工作区变更
+        unstaged_changes = run_git_command(
+            ["git", "diff", "--name-status", "--diff-filter=ACDMRTUXB"]
+        ).splitlines()
+        
+        # 获取未跟踪文件 (新增但未git add的文件)
+        untracked_files = run_git_command(
+            ["git", "ls-files", "--others", "--exclude-standard"]
+        ).splitlines()
+        untracked_changes = [f"A\t{file}" for file in untracked_files]
+        
+        # 合并变更 (暂存区优先,然后工作区,最后未跟踪)
+        change_list = staged_changes.copy()
+        
+        # 添加工作区变更中不在暂存区的文件
+        staged_files = {line.split('\t', 1)[1] for line in staged_changes}
+        for line in unstaged_changes:
+            file_path = line.split('\t', 1)[1]
+            if file_path not in staged_files:
+                change_list.append(line)
+        
+        # 添加未跟踪文件
+        all_existing_files = {line.split('\t', 1)[1] for line in change_list}
+        for line in untracked_changes:
+            file_path = line.split('\t', 1)[1]
+            if file_path not in all_existing_files:
+                change_list.append(line)
+        
+        # 生成完整的未提交变更diff
+        full_diff = run_git_command(
+            ["git", "diff", "--patch", "--cached"]
+        )
+        with open(diff_dir / "uncommitted_changes.diff", "w", encoding='utf-8') as f:
+            f.write(full_diff)
+        
+        # 生成工作区与暂存区差异
+        unstaged_diff = run_git_command(
+            ["git", "diff", "--patch"]
+        )
+        with open(diff_dir / "unstaged_changes.diff", "w", encoding='utf-8') as f:
+            f.write(unstaged_diff)
+    else:
+        # 获取两个提交之间的变更
+        change_list = run_git_command(
+            ["git", "diff", "--name-status", commit1, commit2]
+        ).splitlines()
+
+        # 生成每个commit的详细diff
+        commit_hashes = run_git_command(
+            ["git", "rev-list", "--reverse", f"{commit1}..{commit2}"]
+        ).splitlines()
+        
+        if not commit_hashes:
+            commit_diff = run_git_command(
+                ["git", "diff", "--patch", commit1, commit2]
+            )
+            diff_file = diff_dir / f"{commit1[:8]}_{commit2[:8]}.diff"
+            with open(diff_file, "w", encoding='utf-8') as f:
+                f.write(commit_diff)
+        else:
+            all_commits = [commit1] + commit_hashes
+            for i in range(len(all_commits) - 1):
+                parent = all_commits[i]
+                child = all_commits[i + 1]
+                
+                commit_diff = run_git_command(
+                    ["git", "diff", "--patch", parent, child]
+                )
+                diff_file = diff_dir / f"{parent[:8]}_{child[:8]}.diff"
+                with open(diff_file, "w", encoding='utf-8') as f:
+                    f.write(commit_diff)
+
+    # 分类变更文件
+    added_files = []
+    modified_files = []
+    deleted_files = []
+
+    for line in change_list:
+        parts = line.split('\t')
+        if len(parts) < 2:
+            continue
+        status, file_path = parts[0], '\t'.join(parts[1:])
+        
+        if status == 'A':
+            added_files.append(file_path)
+        elif status == 'M':
+            modified_files.append(file_path)
+        elif status == 'D':
+            deleted_files.append(file_path)
+
+    # 写入变更列表文件
+    with open(output_dir / "gitchangelist.txt", "w", encoding='utf-8') as f:
+        f.write("Added Files:\n")
+        for file in added_files:
+            f.write(f"  {file}\n")
+        
+        f.write("\nModified Files:\n")
+        for file in modified_files:
+            f.write(f"  {file}\n")
+        
+        f.write("\nDeleted Files:\n")
+        for file in deleted_files:
+            f.write(f"  {file}\n")
+
+    # 写入git log(如果有提交范围)
+    if mode == "commits":
+        git_log = run_git_command(
+            ["git", "log", "--name-status", f"{commit1}..{commit2}"]
+        )
+        with open(output_dir / "gitlog.txt", "w", encoding='utf-8') as f:
+            f.write(git_log)
+    else:
+        # 未提交变更的简短信息
+        with open(output_dir / "gitlog.txt", "w", encoding='utf-8') as f:
+            f.write("Summary of uncommitted changes\n")
+            f.write("================================\n\n")
+            f.write("Changes are relative to HEAD:\n")
+            f.write(run_git_command(["git", "status", "--short"]))
+
+    # 复制变更文件(不包括已删除的)
+    for file_path in added_files + modified_files:
+        source_file = repo_path / file_path
+        target_file = output_dir / file_path
+        
+        target_file.parent.mkdir(parents=True, exist_ok=True)
+        
+        try:
+            # 直接从工作区复制,因为未跟踪文件在工作区中存在
+            if os.path.exists(source_file):
+                with open(source_file, "rb") as src, open(target_file, "wb") as dst:
+                    dst.write(src.read())
+            else:
+                # 从暂存区提取文件内容
+                # 对于包含特殊字符的文件名,使用更安全的方式
+                try:
+                    blob_content = run_git_command(
+                        ["git", "show", f":{file_path}"]
+                    )
+                except:
+                    # 如果pathspec失败,尝试使用git cat-file
+                    try:
+                        # 获取文件的blob hash
+                        blob_hash = run_git_command(
+                            ["git", "ls-files", "--stage", file_path]
+                        ).split()[1]
+                        blob_content = run_git_command(
+                            ["git", "cat-file", "blob", blob_hash]
+                        )
+                    except:
+                        print(f"Warning: Could not extract staged content for {file_path}")
+                        continue
+                with open(target_file, "w", encoding='utf-8') as dst:
+                    dst.write(blob_content)
+        except Exception as e:
+            print(f"Error copying file {file_path}: {e}")
+
+    print(f"Changes saved to: {output_dir}")
+
+if __name__ == "__main__":
+    main()    
\ No newline at end of file