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 for config_repo in &wok_config.repos {
13 if let Some(subrepo) = umbrella.get_subrepo_by_path(&config_repo.path) {
14 subrepo.switch(&config_repo.head)?;
16 }
17 }
18
19 let mut index = umbrella.git_repo.index()?;
21 for submodule in umbrella.git_repo.submodules()? {
22 let submodule_path = submodule.path();
23
24 if let Some(_submodule_oid) = submodule.head_id() {
26 index.add_path(submodule_path)?;
28 }
29 }
30 index.write()?;
31
32 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 tree.id() == parent_tree.id() {
43 writeln!(stdout, "No submodule changes detected; nothing to lock")?;
44 return Ok(());
45 }
46
47 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
64fn 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 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 for delta in diff.deltas() {
82 if let Some(file_path) = delta.new_file().path() {
83 if let Some(file_path_str) = file_path.to_str() {
85 match umbrella.git_repo.find_submodule(file_path_str) {
87 std::result::Result::Ok(submodule) => {
88 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 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 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 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}