1use anyhow::*;
2use std::io::Write;
3
4use crate::{config, repo};
5
6pub fn update<W: Write>(
7 wok_config: &mut config::Config,
8 umbrella: &repo::Repo,
9 stdout: &mut W,
10 no_commit: bool,
11) -> Result<()> {
12 writeln!(stdout, "Updating submodules...")?;
13
14 let mut saw_updates = false;
15 let mut saw_conflicts = false;
16
17 for config_repo in &wok_config.repos {
19 if config_repo.is_skipped_for("update") {
20 continue;
21 }
22
23 if let Some(subrepo) = umbrella.get_subrepo_by_path(&config_repo.path) {
24 subrepo.switch(&config_repo.head)?;
26
27 let merge_result = subrepo.merge(&config_repo.head)?;
29
30 let current_commit = get_current_commit_hash(&subrepo.git_repo)?;
32
33 match merge_result {
35 repo::MergeResult::UpToDate => {
36 writeln!(
37 stdout,
38 "- '{}': already up to date on '{}' ({})",
39 config_repo.path.display(),
40 config_repo.head,
41 ¤t_commit[..8]
42 )?;
43 },
44 repo::MergeResult::FastForward => {
45 saw_updates = true;
46 writeln!(
47 stdout,
48 "- '{}': fast-forwarded '{}' to {}",
49 config_repo.path.display(),
50 config_repo.head,
51 ¤t_commit[..8]
52 )?;
53 },
54 repo::MergeResult::Merged => {
55 saw_updates = true;
56 writeln!(
57 stdout,
58 "- '{}': merged '{}' to {}",
59 config_repo.path.display(),
60 config_repo.head,
61 ¤t_commit[..8]
62 )?;
63 },
64 repo::MergeResult::Rebased => {
65 saw_updates = true;
66 writeln!(
67 stdout,
68 "- '{}': rebased '{}' to {}",
69 config_repo.path.display(),
70 config_repo.head,
71 ¤t_commit[..8]
72 )?;
73 },
74 repo::MergeResult::Conflicts => {
75 saw_conflicts = true;
76 writeln!(
77 stdout,
78 "- '{}': merge conflicts in '{}' ({}), manual resolution required",
79 config_repo.path.display(),
80 config_repo.head,
81 ¤t_commit[..8]
82 )?;
83 },
84 }
85 }
86 }
87
88 let staged_changes = stage_submodule_changes(&umbrella.git_repo)?;
90
91 if saw_conflicts {
92 writeln!(
93 stdout,
94 "Skipped committing umbrella repo due to merge conflicts in subrepos"
95 )?;
96 return Ok(());
97 }
98
99 if no_commit {
100 if staged_changes || saw_updates {
101 writeln!(
102 stdout,
103 "Changes staged; commit skipped because --no-commit was provided"
104 )?;
105 } else {
106 writeln!(stdout, "No submodule updates detected; nothing to commit")?;
107 }
108 return Ok(());
109 }
110
111 if !staged_changes {
113 writeln!(stdout, "No submodule updates detected; nothing to commit")?;
114 return Ok(());
115 }
116
117 commit_submodule_updates(&umbrella.git_repo)?;
118
119 writeln!(stdout, "Updated submodule state committed")?;
120 Ok(())
121}
122
123fn get_current_commit_hash(git_repo: &git2::Repository) -> Result<String> {
124 let head = git_repo.head()?;
125 let commit = head.peel_to_commit()?;
126 Ok(commit.id().to_string())
127}
128
129fn stage_submodule_changes(git_repo: &git2::Repository) -> Result<bool> {
130 let head_tree = git_repo
131 .head()
132 .ok()
133 .and_then(|head| head.peel_to_tree().ok());
134 let mut index = git_repo.index()?;
135
136 for submodule in git_repo.submodules()? {
137 let submodule_path = submodule.path();
138
139 if let Some(_submodule_oid) = submodule.head_id() {
141 index.add_path(submodule_path)?;
142 }
143 }
144
145 index.write()?;
146
147 if let Some(tree) = head_tree.as_ref() {
148 let diff = git_repo.diff_tree_to_index(Some(tree), Some(&index), None)?;
149 Ok(diff.deltas().len() > 0)
150 } else {
151 Ok(!index.is_empty())
152 }
153}
154
155fn commit_submodule_updates(git_repo: &git2::Repository) -> Result<()> {
156 let commit_message = "Update submodules to latest";
157 let signature = git_repo.signature()?;
158 let tree_id = git_repo.index()?.write_tree()?;
159 let tree = git_repo.find_tree(tree_id)?;
160
161 let head_ref = git_repo.head()?;
162 let parent_commit = head_ref.peel_to_commit()?;
163
164 git_repo.commit(
165 Some("HEAD"),
166 &signature,
167 &signature,
168 commit_message,
169 &tree,
170 &[&parent_commit],
171 )?;
172
173 Ok(())
174}