1919from devbase .errors import DevbaseError
2020from devbase .log import get_logger
2121
22- from devbase .env .store import EnvEntry , EnvFile
22+ from devbase .env .store import EnvFile
2323
2424logger = get_logger (__name__ )
2525
@@ -85,9 +85,13 @@ def filter_members(
8585 continue
8686 m = _PROJECT_ENV_RE .match (arcname )
8787 if not m :
88- # 他の形式は manifest 検証で拒否されているはずだが念のため。
89- logger .debug ("未対応の arcname を無視します: %s" , arcname )
90- continue
88+ # manifest 検証 (bundle._validate_manifest) は path のパターンを制限していないため、
89+ # 未対応 arcname がここに来た場合は黙って捨てると "manifest と適用結果が食い違う"
90+ # 整合性問題になる。明示的にエラーで止める (PR #13 codex 指摘)。
91+ raise MergeError (
92+ f"バンドルに未対応の arcname が含まれています: { arcname } "
93+ "(対応形式: env/global.env / env/sources.yml / env/projects/<name>/.env)"
94+ )
9195 name = m .group (1 )
9296 if name in excluded :
9397 continue
@@ -104,24 +108,35 @@ def _merge_into_existing_bytes(existing_bytes: bytes,
104108 既存に無いキーは末尾に sorted 順で append。``merged`` から除外されたキーは
105109 出力からも除外する (現状の merge ロジック上発生しないが、安全側で対応)。
106110
111+ 値が変更されていないキーは ``raw`` 行をそのまま温存して出力する。これにより
112+ 例えば ``PATH=$HOME/bin`` のような未クオート値が ``PATH="\\ $HOME/bin"`` に
113+ 勝手にエスケープされて source 時の意味が変わるのを防ぐ (PR #13 codex 指摘)。
114+ 値が変わったキーと新規キーのみ ``EnvFile._format_kv_line`` でフォーマットする。
115+
107116 ``EnvFile.dump_bytes`` で再シリアライズするとコメント・空行が失われるため、
108117 ``EnvFile.parse_entries`` ベースで再構成している (PR #15 gemini 指摘)。
109118 """
110119 seen : set [str ] = set ()
111- out_entries : List [EnvEntry ] = []
120+ out_lines : List [str ] = []
112121 for e in EnvFile .parse_entries (existing_bytes ):
113122 if e .kind != 'kv' or e .key is None :
114- out_entries .append (e )
123+ out_lines .append (e . raw + ' \n ' )
115124 continue
116125 if e .key in merged :
117- out_entries .append (EnvEntry (
118- kind = 'kv' , raw = e .raw , key = e .key , value = merged [e .key ]
119- ))
120126 seen .add (e .key )
127+ new_value = merged [e .key ]
128+ if e .value == new_value :
129+ # 値が変わっていないキーは元の raw 行を温存する (escape 形式や
130+ # クオート有無を保持して source 時の意味が変わらないように)
131+ out_lines .append (e .raw + '\n ' )
132+ else :
133+ out_lines .append (
134+ EnvFile ._format_kv_line (e .key , new_value )
135+ )
121136 # merged から除外されているキーは entries からも落とす
122137 for key in sorted (k for k in merged if k not in seen ):
123- out_entries .append (EnvEntry ( kind = 'kv' , raw = '' , key = key , value = merged [key ]))
124- return EnvFile . dump_entries_bytes ( out_entries )
138+ out_lines .append (EnvFile . _format_kv_line ( key , merged [key ]))
139+ return '' . join ( out_lines ). encode ( 'utf-8' )
125140
126141
127142def _plan_replace (target : Path , arcname : str , incoming : Dict [str , str ],
0 commit comments