Struct git_repository::remote::fetch::Prepare
source · pub struct Prepare<'remote, 'repo, T, P>where
T: Transport,{ /* private fields */ }
async-network-client
or blocking-network-client
) and (crate features blocking-network-client
or async-network-client
) only.Expand description
A structure to hold the result of the handshake with the remote and configure the upcoming fetch operation.
Implementations§
source§impl<'remote, 'repo, T, P> Prepare<'remote, 'repo, T, P>where
T: Transport,
P: Progress,
P::SubProgress: 'static,
impl<'remote, 'repo, T, P> Prepare<'remote, 'repo, T, P>where
T: Transport,
P: Progress,
P::SubProgress: 'static,
sourcepub fn receive(self, should_interrupt: &AtomicBool) -> Result<Outcome, Error>
pub fn receive(self, should_interrupt: &AtomicBool) -> Result<Outcome, Error>
Receive the pack and perform the operation as configured by git via git-config
or overridden by various builder methods.
Return Ok(None)
if there was nothing to do because all remote refs are at the same state as they are locally, or Ok(Some(outcome))
to inform about all the changes that were made.
Negotiation
“fetch.negotiationAlgorithm” describes algorithms git
uses currently, with the default being consecutive
and skipping
being
experimented with. We currently implement something we could call ‘naive’ which works for now.
Pack .keep
files
That packs that are freshly written to the object database are vulnerable to garbage collection for the brief time that it takes between them being placed and the respective references to be written to disk which binds their objects to the commit graph, making them reachable.
To circumvent this issue, a .keep
file is created before any pack related file (i.e. .pack
or .idx
) is written, which indicates the
garbage collector (like git maintenance
, git gc
) to leave the corresponding pack file alone.
If there were any ref updates or the received pack was empty, the .keep
file will be deleted automatically leaving in its place at
write_pack_bundle.keep_path
a None
.
However, if no ref-update happened the path will still be present in write_pack_bundle.keep_path
and is expected to be handled by the caller.
A known application for this behaviour is in remote-helper
implementations which should send this path via lock <path>
to stdout
to inform git about the file that it will remove once it updated the refs accordingly.
Deviation
When updating refs, the git-fetch
docs state that the following:
Unlike when pushing with git-push, any updates outside of refs/{tags,heads}/* will be accepted without + in the refspec (or –force), whether that’s swapping e.g. a tree object for a blob, or a commit for another commit that’s doesn’t have the previous commit as an ancestor etc.
We explicitly don’t special case those refs and expect the user to take control. Note that by its nature, force only applies to refs pointing to commits and if they don’t, they will be updated either way in our implementation as well.
Async Mode Shortcoming
Currently the entire process of resolving a pack is blocking the executor. This can be fixed using the blocking
crate, but it
didn’t seem worth the tradeoff of having more complex code.
Configuration
gitoxide.userAgent
is read to obtain the application user agent for git servers and for HTTP servers as well.
Examples found in repository?
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
pub fn fetch_only<P>(
&mut self,
progress: P,
should_interrupt: &std::sync::atomic::AtomicBool,
) -> Result<(Repository, crate::remote::fetch::Outcome), Error>
where
P: crate::Progress,
P::SubProgress: 'static,
{
use crate::remote;
use crate::{bstr::ByteVec, remote::fetch::RefLogMessage};
let repo = self
.repo
.as_mut()
.expect("user error: multiple calls are allowed only until it succeeds");
let remote_name = match self.remote_name.as_ref() {
Some(name) => name.to_owned(),
None => repo
.config
.resolved
.string_by_key("clone.defaultRemoteName")
.map(|n| remote::name::validated(n.into_owned()))
.unwrap_or_else(|| Ok("origin".into()))?,
};
let mut remote = repo
.remote_at(self.url.clone())?
.with_refspecs(
Some(format!("+refs/heads/*:refs/remotes/{remote_name}/*").as_str()),
remote::Direction::Fetch,
)
.expect("valid static spec");
let mut clone_fetch_tags = None;
if let Some(f) = self.configure_remote.as_mut() {
remote = f(remote).map_err(|err| Error::RemoteConfiguration(err))?;
} else {
clone_fetch_tags = remote::fetch::Tags::All.into();
}
let config = util::write_remote_to_local_config_file(&mut remote, remote_name.clone())?;
// Now we are free to apply remote configuration we don't want to be written to disk.
if let Some(fetch_tags) = clone_fetch_tags {
remote = remote.with_fetch_tags(fetch_tags);
}
// Add HEAD after the remote was written to config, we need it to know what to checkout later, and assure
// the ref that HEAD points to is present no matter what.
let head_refspec = git_refspec::parse(
format!("HEAD:refs/remotes/{remote_name}/HEAD").as_str().into(),
git_refspec::parse::Operation::Fetch,
)
.expect("valid")
.to_owned();
let pending_pack: remote::fetch::Prepare<'_, '_, _, _> =
remote.connect(remote::Direction::Fetch, progress)?.prepare_fetch({
let mut opts = self.fetch_options.clone();
if !opts.extra_refspecs.contains(&head_refspec) {
opts.extra_refspecs.push(head_refspec)
}
opts
})?;
if pending_pack.ref_map().object_hash != repo.object_hash() {
unimplemented!("configure repository to expect a different object hash as advertised by the server")
}
let reflog_message = {
let mut b = self.url.to_bstring();
b.insert_str(0, "clone: from ");
b
};
let outcome = pending_pack
.with_write_packed_refs_only(true)
.with_reflog_message(RefLogMessage::Override {
message: reflog_message.clone(),
})
.receive(should_interrupt)?;
util::replace_changed_local_config_file(repo, config);
util::update_head(
repo,
&outcome.ref_map.remote_refs,
reflog_message.as_ref(),
remote_name.as_ref(),
)?;
Ok((self.repo.take().expect("still present"), outcome))
}
source§impl<'remote, 'repo, T, P> Prepare<'remote, 'repo, T, P>where
T: Transport,
impl<'remote, 'repo, T, P> Prepare<'remote, 'repo, T, P>where
T: Transport,
sourcepub fn ref_map(&self) -> &RefMap
pub fn ref_map(&self) -> &RefMap
Return the ref_map (that includes the server handshake) which was part of listing refs prior to fetching a pack.
Examples found in repository?
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
pub fn fetch_only<P>(
&mut self,
progress: P,
should_interrupt: &std::sync::atomic::AtomicBool,
) -> Result<(Repository, crate::remote::fetch::Outcome), Error>
where
P: crate::Progress,
P::SubProgress: 'static,
{
use crate::remote;
use crate::{bstr::ByteVec, remote::fetch::RefLogMessage};
let repo = self
.repo
.as_mut()
.expect("user error: multiple calls are allowed only until it succeeds");
let remote_name = match self.remote_name.as_ref() {
Some(name) => name.to_owned(),
None => repo
.config
.resolved
.string_by_key("clone.defaultRemoteName")
.map(|n| remote::name::validated(n.into_owned()))
.unwrap_or_else(|| Ok("origin".into()))?,
};
let mut remote = repo
.remote_at(self.url.clone())?
.with_refspecs(
Some(format!("+refs/heads/*:refs/remotes/{remote_name}/*").as_str()),
remote::Direction::Fetch,
)
.expect("valid static spec");
let mut clone_fetch_tags = None;
if let Some(f) = self.configure_remote.as_mut() {
remote = f(remote).map_err(|err| Error::RemoteConfiguration(err))?;
} else {
clone_fetch_tags = remote::fetch::Tags::All.into();
}
let config = util::write_remote_to_local_config_file(&mut remote, remote_name.clone())?;
// Now we are free to apply remote configuration we don't want to be written to disk.
if let Some(fetch_tags) = clone_fetch_tags {
remote = remote.with_fetch_tags(fetch_tags);
}
// Add HEAD after the remote was written to config, we need it to know what to checkout later, and assure
// the ref that HEAD points to is present no matter what.
let head_refspec = git_refspec::parse(
format!("HEAD:refs/remotes/{remote_name}/HEAD").as_str().into(),
git_refspec::parse::Operation::Fetch,
)
.expect("valid")
.to_owned();
let pending_pack: remote::fetch::Prepare<'_, '_, _, _> =
remote.connect(remote::Direction::Fetch, progress)?.prepare_fetch({
let mut opts = self.fetch_options.clone();
if !opts.extra_refspecs.contains(&head_refspec) {
opts.extra_refspecs.push(head_refspec)
}
opts
})?;
if pending_pack.ref_map().object_hash != repo.object_hash() {
unimplemented!("configure repository to expect a different object hash as advertised by the server")
}
let reflog_message = {
let mut b = self.url.to_bstring();
b.insert_str(0, "clone: from ");
b
};
let outcome = pending_pack
.with_write_packed_refs_only(true)
.with_reflog_message(RefLogMessage::Override {
message: reflog_message.clone(),
})
.receive(should_interrupt)?;
util::replace_changed_local_config_file(repo, config);
util::update_head(
repo,
&outcome.ref_map.remote_refs,
reflog_message.as_ref(),
remote_name.as_ref(),
)?;
Ok((self.repo.take().expect("still present"), outcome))
}
source§impl<'remote, 'repo, T, P> Prepare<'remote, 'repo, T, P>where
T: Transport,
impl<'remote, 'repo, T, P> Prepare<'remote, 'repo, T, P>where
T: Transport,
Builder
sourcepub fn with_dry_run(self, enabled: bool) -> Self
pub fn with_dry_run(self, enabled: bool) -> Self
If dry run is enabled, no change to the repository will be made.
This works by not actually fetching the pack after negotiating it, nor will refs be updated.
sourcepub fn with_write_packed_refs_only(self, enabled: bool) -> Self
pub fn with_write_packed_refs_only(self, enabled: bool) -> Self
If enabled, don’t write ref updates to loose refs, but put them exclusively to packed-refs.
This improves performance and allows case-sensitive filesystems to deal with ref names that would otherwise collide.
Examples found in repository?
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
pub fn fetch_only<P>(
&mut self,
progress: P,
should_interrupt: &std::sync::atomic::AtomicBool,
) -> Result<(Repository, crate::remote::fetch::Outcome), Error>
where
P: crate::Progress,
P::SubProgress: 'static,
{
use crate::remote;
use crate::{bstr::ByteVec, remote::fetch::RefLogMessage};
let repo = self
.repo
.as_mut()
.expect("user error: multiple calls are allowed only until it succeeds");
let remote_name = match self.remote_name.as_ref() {
Some(name) => name.to_owned(),
None => repo
.config
.resolved
.string_by_key("clone.defaultRemoteName")
.map(|n| remote::name::validated(n.into_owned()))
.unwrap_or_else(|| Ok("origin".into()))?,
};
let mut remote = repo
.remote_at(self.url.clone())?
.with_refspecs(
Some(format!("+refs/heads/*:refs/remotes/{remote_name}/*").as_str()),
remote::Direction::Fetch,
)
.expect("valid static spec");
let mut clone_fetch_tags = None;
if let Some(f) = self.configure_remote.as_mut() {
remote = f(remote).map_err(|err| Error::RemoteConfiguration(err))?;
} else {
clone_fetch_tags = remote::fetch::Tags::All.into();
}
let config = util::write_remote_to_local_config_file(&mut remote, remote_name.clone())?;
// Now we are free to apply remote configuration we don't want to be written to disk.
if let Some(fetch_tags) = clone_fetch_tags {
remote = remote.with_fetch_tags(fetch_tags);
}
// Add HEAD after the remote was written to config, we need it to know what to checkout later, and assure
// the ref that HEAD points to is present no matter what.
let head_refspec = git_refspec::parse(
format!("HEAD:refs/remotes/{remote_name}/HEAD").as_str().into(),
git_refspec::parse::Operation::Fetch,
)
.expect("valid")
.to_owned();
let pending_pack: remote::fetch::Prepare<'_, '_, _, _> =
remote.connect(remote::Direction::Fetch, progress)?.prepare_fetch({
let mut opts = self.fetch_options.clone();
if !opts.extra_refspecs.contains(&head_refspec) {
opts.extra_refspecs.push(head_refspec)
}
opts
})?;
if pending_pack.ref_map().object_hash != repo.object_hash() {
unimplemented!("configure repository to expect a different object hash as advertised by the server")
}
let reflog_message = {
let mut b = self.url.to_bstring();
b.insert_str(0, "clone: from ");
b
};
let outcome = pending_pack
.with_write_packed_refs_only(true)
.with_reflog_message(RefLogMessage::Override {
message: reflog_message.clone(),
})
.receive(should_interrupt)?;
util::replace_changed_local_config_file(repo, config);
util::update_head(
repo,
&outcome.ref_map.remote_refs,
reflog_message.as_ref(),
remote_name.as_ref(),
)?;
Ok((self.repo.take().expect("still present"), outcome))
}
sourcepub fn with_reflog_message(self, reflog_message: RefLogMessage) -> Self
pub fn with_reflog_message(self, reflog_message: RefLogMessage) -> Self
Set the reflog message to use when updating refs after fetching a pack.
Examples found in repository?
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
pub fn fetch_only<P>(
&mut self,
progress: P,
should_interrupt: &std::sync::atomic::AtomicBool,
) -> Result<(Repository, crate::remote::fetch::Outcome), Error>
where
P: crate::Progress,
P::SubProgress: 'static,
{
use crate::remote;
use crate::{bstr::ByteVec, remote::fetch::RefLogMessage};
let repo = self
.repo
.as_mut()
.expect("user error: multiple calls are allowed only until it succeeds");
let remote_name = match self.remote_name.as_ref() {
Some(name) => name.to_owned(),
None => repo
.config
.resolved
.string_by_key("clone.defaultRemoteName")
.map(|n| remote::name::validated(n.into_owned()))
.unwrap_or_else(|| Ok("origin".into()))?,
};
let mut remote = repo
.remote_at(self.url.clone())?
.with_refspecs(
Some(format!("+refs/heads/*:refs/remotes/{remote_name}/*").as_str()),
remote::Direction::Fetch,
)
.expect("valid static spec");
let mut clone_fetch_tags = None;
if let Some(f) = self.configure_remote.as_mut() {
remote = f(remote).map_err(|err| Error::RemoteConfiguration(err))?;
} else {
clone_fetch_tags = remote::fetch::Tags::All.into();
}
let config = util::write_remote_to_local_config_file(&mut remote, remote_name.clone())?;
// Now we are free to apply remote configuration we don't want to be written to disk.
if let Some(fetch_tags) = clone_fetch_tags {
remote = remote.with_fetch_tags(fetch_tags);
}
// Add HEAD after the remote was written to config, we need it to know what to checkout later, and assure
// the ref that HEAD points to is present no matter what.
let head_refspec = git_refspec::parse(
format!("HEAD:refs/remotes/{remote_name}/HEAD").as_str().into(),
git_refspec::parse::Operation::Fetch,
)
.expect("valid")
.to_owned();
let pending_pack: remote::fetch::Prepare<'_, '_, _, _> =
remote.connect(remote::Direction::Fetch, progress)?.prepare_fetch({
let mut opts = self.fetch_options.clone();
if !opts.extra_refspecs.contains(&head_refspec) {
opts.extra_refspecs.push(head_refspec)
}
opts
})?;
if pending_pack.ref_map().object_hash != repo.object_hash() {
unimplemented!("configure repository to expect a different object hash as advertised by the server")
}
let reflog_message = {
let mut b = self.url.to_bstring();
b.insert_str(0, "clone: from ");
b
};
let outcome = pending_pack
.with_write_packed_refs_only(true)
.with_reflog_message(RefLogMessage::Override {
message: reflog_message.clone(),
})
.receive(should_interrupt)?;
util::replace_changed_local_config_file(repo, config);
util::update_head(
repo,
&outcome.ref_map.remote_refs,
reflog_message.as_ref(),
remote_name.as_ref(),
)?;
Ok((self.repo.take().expect("still present"), outcome))
}