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