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
159
160
161
162
163
164
165
166
167
168
use git::bstr::BString;
use git_repository as git;
use crate::OutputFormat;
pub struct Options {
pub format: OutputFormat,
pub dry_run: bool,
pub remote: Option<String>,
pub ref_specs: Vec<BString>,
pub handshake_info: bool,
}
pub const PROGRESS_RANGE: std::ops::RangeInclusive<u8> = 1..=3;
pub(crate) mod function {
use anyhow::bail;
use git_repository as git;
use git_repository::{prelude::ObjectIdExt, refspec::match_group::validate::Fix, remote::fetch::Status};
use super::Options;
use crate::OutputFormat;
pub fn fetch<P>(
repo: git::Repository,
progress: P,
mut out: impl std::io::Write,
err: impl std::io::Write,
Options {
format,
dry_run,
remote,
handshake_info,
ref_specs,
}: Options,
) -> anyhow::Result<()>
where
P: git::Progress,
P::SubProgress: 'static,
{
if format != OutputFormat::Human {
bail!("JSON output isn't yet supported for fetching.");
}
let mut remote = crate::repository::remote::by_name_or_url(&repo, remote.as_deref())?;
if !ref_specs.is_empty() {
remote.replace_refspecs(ref_specs.iter(), git::remote::Direction::Fetch)?;
}
let res: git::remote::fetch::Outcome = remote
.connect(git::remote::Direction::Fetch, progress)?
.prepare_fetch(Default::default())?
.with_dry_run(dry_run)
.receive(&git::interrupt::IS_INTERRUPTED)?;
if handshake_info {
writeln!(out, "Handshake Information")?;
writeln!(out, "\t{:?}", res.ref_map.handshake)?;
}
let ref_specs = remote.refspecs(git::remote::Direction::Fetch);
match res.status {
Status::NoChange => {
let show_unmapped = false;
crate::repository::remote::refs::print_refmap(
&repo,
ref_specs,
res.ref_map,
show_unmapped,
&mut out,
err,
)
}
Status::DryRun { update_refs } => print_updates(&repo, update_refs, ref_specs, res.ref_map, &mut out, err),
Status::Change {
update_refs,
write_pack_bundle,
} => {
print_updates(&repo, update_refs, ref_specs, res.ref_map, &mut out, err)?;
if let Some(data_path) = write_pack_bundle.data_path {
writeln!(out, "pack file: \"{}\"", data_path.display()).ok();
}
if let Some(index_path) = write_pack_bundle.index_path {
writeln!(out, "index file: \"{}\"", index_path.display()).ok();
}
Ok(())
}
}?;
if dry_run {
writeln!(out, "DRY-RUN: No ref was updated and no pack was received.").ok();
}
Ok(())
}
pub(crate) fn print_updates(
repo: &git::Repository,
update_refs: git::remote::fetch::refs::update::Outcome,
refspecs: &[git::refspec::RefSpec],
mut map: git::remote::fetch::RefMap,
mut out: impl std::io::Write,
mut err: impl std::io::Write,
) -> anyhow::Result<()> {
let mut last_spec_index = usize::MAX;
let mut updates = update_refs
.iter_mapping_updates(&map.mappings, refspecs)
.filter_map(|(update, mapping, spec, edit)| spec.map(|spec| (update, mapping, spec, edit)))
.collect::<Vec<_>>();
updates.sort_by_key(|t| t.2);
for (update, mapping, spec, edit) in updates {
if mapping.spec_index != last_spec_index {
last_spec_index = mapping.spec_index;
spec.to_ref().write_to(&mut out)?;
writeln!(out)?;
}
write!(out, "\t")?;
match &mapping.remote {
git::remote::fetch::Source::ObjectId(id) => {
write!(out, "{}", id.attach(repo).shorten_or_id())?;
}
git::remote::fetch::Source::Ref(r) => {
crate::repository::remote::refs::print_ref(&mut out, r)?;
}
};
match edit {
Some(edit) => {
writeln!(out, " -> {} [{}]", edit.name, update.mode)
}
None => writeln!(out, " (fetch only)"),
}?;
}
if !map.fixes.is_empty() {
writeln!(
err,
"The following destination refs were removed as they didn't start with 'ref/'"
)?;
map.fixes.sort_by(|l, r| match (l, r) {
(
Fix::MappingWithPartialDestinationRemoved { spec: l, .. },
Fix::MappingWithPartialDestinationRemoved { spec: r, .. },
) => l.cmp(r),
});
let mut prev_spec = None;
for fix in &map.fixes {
match fix {
Fix::MappingWithPartialDestinationRemoved { name, spec } => {
if prev_spec.map_or(true, |prev_spec| prev_spec != spec) {
prev_spec = spec.into();
spec.to_ref().write_to(&mut err)?;
writeln!(err)?;
}
writeln!(err, "\t{name}")?;
}
}
}
}
if map.remote_refs.len() - map.mappings.len() != 0 {
writeln!(
err,
"server sent {} tips, {} were filtered due to {} refspec(s).",
map.remote_refs.len(),
map.remote_refs.len() - map.mappings.len(),
refspecs.len()
)?;
}
Ok(())
}
}