up_rs/tasks/git/
checkout.rs1use crate::tasks::git::fetch::remote_callbacks;
3use crate::tasks::git::status::ensure_repo_clean;
4use color_eyre::eyre::bail;
5use color_eyre::eyre::eyre;
6use color_eyre::eyre::Result;
7use git2::build::CheckoutBuilder;
8use git2::BranchType;
9use git2::ErrorCode;
10use git2::FetchOptions;
11use git2::Repository;
12use git2::SubmoduleUpdateOptions;
13use std::convert::Into;
14use std::str;
15use tracing::debug;
16use tracing::trace;
17
18pub(super) fn checkout_branch(
24 repo: &Repository,
25 branch_name: &str,
26 short_branch: &str,
27 upstream_remote_name: &str,
28 force: bool,
29) -> Result<()> {
30 match repo.find_branch(short_branch, BranchType::Local) {
31 Ok(_) => (),
32 Err(e) if e.code() == ErrorCode::NotFound => {
33 debug!("Branch {short_branch} doesn't exist, creating it...",);
34 let branch_target = format!("{upstream_remote_name}/{short_branch}");
35 let branch_commit = repo
36 .find_branch(&branch_target, BranchType::Remote)?
37 .get()
38 .peel_to_commit()?;
39 let mut branch = repo.branch(short_branch, &branch_commit, false)?;
40 branch.set_upstream(Some(&branch_target))?;
41 }
42 Err(e) => return Err(e.into()),
43 };
44 match repo.head() {
45 Ok(current_head) => {
46 let current_head = current_head.name();
48 trace!("Current head is {current_head:?}, branch_name is {branch_name}",);
49 if !force && !repo.head_detached()? && current_head == Some(branch_name) {
50 debug!("Repo head is already {branch_name}, skipping branch checkout...",);
51 return Ok(());
52 }
53 }
54 Err(e) if e.code() == ErrorCode::UnbornBranch => {
55 trace!("No current head, continuing with branch checkout...");
57 }
58 Err(e) => {
59 bail!(e);
60 }
61 }
62 if !force {
63 ensure_repo_clean(repo)?;
64 }
65 debug!("Setting head to {branch_name}");
66 set_and_checkout_head(repo, branch_name, force)?;
67 Ok(())
68}
69
70pub(super) fn set_and_checkout_head(
80 repo: &Repository,
81 branch_name: &str,
82 force: bool,
83) -> Result<()> {
84 if force {
85 debug!("Force checking out {branch_name}");
86 } else {
87 ensure_repo_clean(repo)?;
88 }
89 repo.set_head(branch_name)?;
90 force_checkout_head(repo)?;
91 Ok(())
92}
93
94fn force_checkout_head(repo: &Repository) -> Result<()> {
103 debug!("Force checking out HEAD.");
104 repo.checkout_head(Some(
105 CheckoutBuilder::new()
106 .force()
107 .allow_conflicts(true)
108 .recreate_missing(true)
109 .conflict_style_diff3(true)
110 .conflict_style_merge(true),
111 ))?;
112
113 for mut submodule in repo.submodules()? {
114 trace!("Updating submodule: {:?}", submodule.name());
115
116 let mut checkout_builder = CheckoutBuilder::new();
117 checkout_builder
118 .force()
119 .allow_conflicts(true)
120 .recreate_missing(true)
121 .conflict_style_diff3(true)
122 .conflict_style_merge(true);
123
124 let mut count = 0;
126 let mut fetch_options = FetchOptions::new();
127 fetch_options.remote_callbacks(remote_callbacks(&mut count));
128
129 submodule.update(
130 false,
131 Some(
132 SubmoduleUpdateOptions::new()
133 .fetch(fetch_options)
134 .checkout(checkout_builder),
135 ),
136 )?;
137
138 let submodule_repo = submodule.open()?;
140 force_checkout_head(&submodule_repo)?;
141 }
142 Ok(())
143}
144
145pub(super) fn needs_checkout(repo: &Repository, branch_name: &str) -> bool {
147 match repo.head().map_err(Into::into).and_then(|h| {
148 h.shorthand()
149 .map(ToOwned::to_owned)
150 .ok_or_else(|| eyre!("Current branch is not valid UTF-8"))
151 }) {
152 Ok(current_branch) if current_branch == branch_name => {
153 debug!("Already on branch: '{branch_name}'");
154 false
155 }
156 Ok(current_branch) => {
157 debug!("Current branch: {current_branch}");
158 true
159 }
160 Err(e) => {
161 debug!("Current branch errored: {e}");
162 true
163 }
164 }
165}