git_wok/cmd/
lock.rs

1use anyhow::*;
2use std::io::Write;
3
4use crate::{config, repo};
5
6pub fn lock<W: Write>(
7    wok_config: &mut config::Config,
8    umbrella: &repo::Repo,
9    stdout: &mut W,
10) -> Result<()> {
11    // Ensure each repo is switched to its configured branch
12    for config_repo in &wok_config.repos {
13        if let Some(subrepo) = umbrella.get_subrepo_by_path(&config_repo.path) {
14            // Switch subrepo to its configured branch
15            subrepo.switch(&config_repo.head)?;
16        }
17    }
18
19    // Add all submodule changes to the index
20    let mut index = umbrella.git_repo.index()?;
21    for submodule in umbrella.git_repo.submodules()? {
22        let submodule_path = submodule.path();
23
24        // Only add submodules that have a head (are initialized)
25        if let Some(_submodule_oid) = submodule.head_id() {
26            // Add the submodule entry to the index
27            index.add_path(submodule_path)?;
28        }
29    }
30    index.write()?;
31
32    // Check if there are any changes to commit
33    let signature = umbrella.git_repo.signature()?;
34    let tree_id = umbrella.git_repo.index()?.write_tree()?;
35    let tree = umbrella.git_repo.find_tree(tree_id)?;
36
37    let head_ref = umbrella.git_repo.head()?;
38    let parent_commit = head_ref.peel_to_commit()?;
39    let parent_tree = parent_commit.tree()?;
40
41    // If nothing changed, don't create a commit
42    if tree.id() == parent_tree.id() {
43        writeln!(stdout, "No submodule changes detected; nothing to lock")?;
44        return Ok(());
45    }
46
47    // Build commit message with changed submodule summary
48    let (commit_message, _changed_submodules) =
49        build_lock_commit_message(umbrella, &parent_tree, &tree)?;
50
51    umbrella.git_repo.commit(
52        Some("HEAD"),
53        &signature,
54        &signature,
55        &commit_message,
56        &tree,
57        &[&parent_commit],
58    )?;
59
60    writeln!(stdout, "Locked submodule state")?;
61    Ok(())
62}
63
64/// Build a commit message for lock operation and return changed submodule info.
65/// Returns (commit_message, changed_submodules_list)
66fn build_lock_commit_message(
67    umbrella: &repo::Repo,
68    parent_tree: &git2::Tree,
69    index_tree: &git2::Tree,
70) -> Result<(String, Vec<(String, String)>)> {
71    // Get diff between parent tree and staged index
72    let diff = umbrella.git_repo.diff_tree_to_tree(
73        Some(parent_tree),
74        Some(index_tree),
75        None,
76    )?;
77
78    let mut changed_submodules = Vec::new();
79
80    // Iterate through changed files in the diff
81    for delta in diff.deltas() {
82        if let Some(file_path) = delta.new_file().path() {
83            // Convert Path to str for find_submodule
84            if let Some(file_path_str) = file_path.to_str() {
85                // Check if this file is a submodule
86                match umbrella.git_repo.find_submodule(file_path_str) {
87                    std::result::Result::Ok(submodule) => {
88                        // Get the submodule repository and its HEAD
89                        let submodule_repo_path =
90                            umbrella.work_dir.join(submodule.path());
91                        match git2::Repository::open(&submodule_repo_path) {
92                            std::result::Result::Ok(subrepo_git) => {
93                                // Get the actual HEAD of the submodule (not from umbrella's index)
94                                match subrepo_git.head() {
95                                    std::result::Result::Ok(head_ref) => {
96                                        match head_ref.peel_to_commit() {
97                                            std::result::Result::Ok(commit) => {
98                                                let message = commit
99                                                    .message()
100                                                    .unwrap_or("(no message)");
101
102                                                // Get first line of commit message and truncate to 50 chars
103                                                let first_line = message
104                                                    .lines()
105                                                    .next()
106                                                    .unwrap_or("(no message)");
107                                                let truncated = if first_line.len() > 50
108                                                {
109                                                    format!("{}...", &first_line[..47])
110                                                } else {
111                                                    first_line.to_string()
112                                                };
113
114                                                let submodule_name = submodule
115                                                    .path()
116                                                    .to_string_lossy()
117                                                    .to_string();
118                                                changed_submodules
119                                                    .push((submodule_name, truncated));
120                                            },
121                                            std::result::Result::Err(_) => continue,
122                                        }
123                                    },
124                                    std::result::Result::Err(_) => continue,
125                                }
126                            },
127                            std::result::Result::Err(_) => continue,
128                        }
129                    },
130                    std::result::Result::Err(_) => continue,
131                }
132            }
133        }
134    }
135
136    // Build the commit message
137    let mut message = String::from("Lock submodule state");
138
139    if !changed_submodules.is_empty() {
140        message.push_str("\n\nChanged submodules:");
141        for (name, commit_msg) in &changed_submodules {
142            message.push_str(&format!("\n- {}: {}", name, commit_msg));
143        }
144    }
145
146    std::result::Result::Ok((message, changed_submodules))
147}