///|
/// SubdirRepo の構築と基本操作
///|
/// Bytes を String に変換(ASCII/UTF-8 として解釈)
fn bytes_to_string(data : Bytes) -> String {
let result = StringBuilder::new()
for b in data {
result.write_char(b.to_int().unsafe_to_char())
}
result.to_string()
}
///|
/// 文字列の末尾の改行を除去
fn trim_newlines(s : String) -> String {
let chars = s.to_array()
let mut end = chars.length()
while end > 0 {
let c = chars[end - 1]
if c == '\n' || c == '\r' {
end -= 1
} else {
break
}
}
let result = StringBuilder::new()
for i in 0.. String {
let chars = s.to_array()
if n >= chars.length() {
return ""
}
let result = StringBuilder::new()
for i in n.. String {
let chars = path.to_array()
let mut start = 0
let mut end = chars.length()
// 先頭のスラッシュと "./" を除去
while start < end && chars[start] == '/' {
start += 1
}
if start + 1 < end && chars[start] == '.' && chars[start + 1] == '/' {
start += 2
}
// 末尾のスラッシュを除去
while end > start && chars[end - 1] == '/' {
end -= 1
}
// 結果を構築
let result = StringBuilder::new()
for i in start.. Array[String] {
if path == "" {
return []
}
let parts : Array[String] = []
let mut current = StringBuilder::new()
for c in path {
if c == '/' {
if current.to_string().length() > 0 {
parts.push(current.to_string())
current = StringBuilder::new()
}
} else {
current.write_char(c)
}
}
if current.to_string().length() > 0 {
parts.push(current.to_string())
}
parts
}
///|
/// コミットからサブディレクトリのツリーIDを抽出
pub fn extract_subdir_tree(
db : @lib.ObjectDb,
rfs : &@bit.RepoFileSystem,
commit_id : @bit.ObjectId,
subdir_path : String,
) -> @bit.ObjectId raise SubdirError {
// コミットを取得
let commit_obj = db.get(rfs, commit_id) catch {
_ => raise CommitNotFound(commit_id)
}
let obj = match commit_obj {
Some(o) => o
None => raise CommitNotFound(commit_id)
}
let commit = @bit.parse_commit(obj.data) catch {
_ => raise CommitNotFound(commit_id)
}
// ルートツリーから開始
let mut current_tree = commit.tree
let path_parts = subdir_path |> normalize_subdir_path |> split_path
if path_parts.length() == 0 {
// ルートディレクトリの場合
return current_tree
}
// パスを辿る
for part in path_parts {
let tree_obj = db.get(rfs, current_tree) catch {
_ => raise TreeNotFound(current_tree)
}
let tobj = match tree_obj {
Some(o) => o
None => raise TreeNotFound(current_tree)
}
let entries = @bit.parse_tree(tobj.data) catch {
_ => raise TreeNotFound(current_tree)
}
let mut found = false
for entry in entries {
if entry.name == part {
// ツリーモード(ディレクトリ)かチェック
if entry.mode == "040000" || entry.mode == "40000" {
current_tree = entry.id
found = true
break
} else {
// ファイルを指している場合はエラー
raise SubdirNotFound(subdir_path)
}
}
}
if !found {
raise SubdirNotFound(subdir_path)
}
}
current_tree
}
///|
/// コミットからサブディレクトリリポジトリを作成
pub fn SubdirRepo::from_commit(
rfs : &@bit.RepoFileSystem,
git_dir : String,
commit_id : @bit.ObjectId,
subdir_path : String,
config? : SubdirConfig = SubdirConfig::default(),
) -> SubdirRepo raise SubdirError {
let normalized_path = normalize_subdir_path(subdir_path)
if normalized_path == "" {
raise InvalidPath("subdir_path cannot be empty or root")
}
// ObjectDb を読み込む
let db = @lib.ObjectDb::load(rfs, git_dir) catch {
_ => raise IoError("failed to load object db")
}
// サブディレクトリのツリーを抽出
let tree_id = extract_subdir_tree(db, rfs, commit_id, normalized_path) catch {
e => raise e
}
// サブディレクトリのツリーをベースとした Fs を作成
let bitfs = @fs.Fs::from_tree(git_dir, tree_id)
{
git_dir,
subdir_path: normalized_path,
current_commit: Some(commit_id),
subdir_tree: Some(tree_id),
bitfs,
config,
}
}
///|
/// HEAD からサブディレクトリリポジトリを作成
pub fn SubdirRepo::from_head(
rfs : &@bit.RepoFileSystem,
git_dir : String,
subdir_path : String,
config? : SubdirConfig = SubdirConfig::default(),
) -> SubdirRepo raise SubdirError {
// HEAD を読み取る
let head_content = rfs.read_file(git_dir + "/HEAD") catch {
_ => raise IoError("failed to read HEAD")
}
let head_str = head_content |> bytes_to_string |> trim_newlines
let head_id : @bit.ObjectId = if head_str.has_prefix("ref: ") {
// シンボリックリファレンス
let ref_path = skip_chars(head_str, 5)
let ref_content = rfs.read_file(git_dir + "/" + ref_path) catch {
_ => raise IoError("failed to read ref: " + ref_path)
}
(ref_content |> bytes_to_string |> trim_newlines |> @bit.ObjectId::from_hex) catch {
_ => raise IoError("invalid commit id in ref")
}
} else {
@bit.ObjectId::from_hex(head_str) catch {
_ => raise IoError("invalid commit id in HEAD")
}
}
SubdirRepo::from_commit(rfs, git_dir, head_id, subdir_path, config~) catch {
e => raise e
}
}
///|
/// ブランチからサブディレクトリリポジトリを作成
pub fn SubdirRepo::from_branch(
rfs : &@bit.RepoFileSystem,
git_dir : String,
branch : String,
subdir_path : String,
config? : SubdirConfig = SubdirConfig::default(),
) -> SubdirRepo raise SubdirError {
// ブランチリファレンスを読み取る
let ref_path = git_dir + "/refs/heads/" + branch
let ref_content = rfs.read_file(ref_path) catch {
_ => raise IoError("branch not found: " + branch)
}
let commit_id = @bit.ObjectId::from_hex(
trim_newlines(bytes_to_string(ref_content)),
) catch {
_ => raise IoError("invalid commit id in branch ref")
}
SubdirRepo::from_commit(rfs, git_dir, commit_id, subdir_path, config~) catch {
e => raise e
}
}
///|
/// サブディレクトリのパスを取得
pub fn SubdirRepo::path(self : SubdirRepo) -> String {
self.subdir_path
}
///|
/// 現在のベースコミットを取得
pub fn SubdirRepo::base_commit(self : SubdirRepo) -> @bit.ObjectId? {
self.current_commit
}
///|
/// サブディレクトリのツリーIDを取得
pub fn SubdirRepo::tree_id(self : SubdirRepo) -> @bit.ObjectId? {
self.subdir_tree
}
///|
/// 変更があるかどうか
pub fn SubdirRepo::is_dirty(self : SubdirRepo) -> Bool {
self.bitfs.is_dirty()
}
///|
/// Fs を取得(内部アクセス用)
pub fn SubdirRepo::fs(self : SubdirRepo) -> @fs.Fs {
self.bitfs
}
///|
/// ワーキングディレクトリの変更を bitfs に読み込む
/// rfs: 実際のファイルシステム(OsFs など)
/// workdir: ワーキングディレクトリのルートパス
pub fn SubdirRepo::load_workdir(
self : SubdirRepo,
rfs : &@bit.RepoFileSystem,
workdir : String,
) -> Unit {
// サブディレクトリのパスを取得
let subdir_abs = workdir + "/" + self.subdir_path
// ディレクトリスタック(base_path, rel_path)
let stack : Array[(String, String)] = [(subdir_abs, "/")]
while stack.length() > 0 {
let (base_path, rel_path) = stack.pop().unwrap()
let dir_path = if rel_path == "/" {
base_path
} else {
base_path + rel_path
}
let entries = rfs.readdir(dir_path) catch {
_ => continue // ディレクトリが読めなければスキップ
}
for entry in entries {
// .git ファイル/ディレクトリはスキップ
if entry == ".git" {
continue
}
let full_path = dir_path + "/" + entry
let bitfs_path = if rel_path == "/" {
"/" + entry
} else {
rel_path + "/" + entry
}
if rfs.is_dir(full_path) {
// ディレクトリの場合はスタックに追加
stack.push((base_path, bitfs_path))
} else {
// ファイルの場合は読み込んで比較
let content = rfs.read_file(full_path) catch {
_ => continue // 読めなければスキップ
}
// 既存の内容と比較
let existing = self.bitfs.read_file(rfs, bitfs_path) catch {
_ => b"" // ファイルが存在しない場合は空
}
// 異なる場合のみ書き込み
if content != existing {
self.bitfs.write_file(bitfs_path, content)
}
}
}
}
}