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