summaryrefslogtreecommitdiffstats
path: root/vendor/gix/src/clone/fetch/util.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gix/src/clone/fetch/util.rs')
-rw-r--r--vendor/gix/src/clone/fetch/util.rs229
1 files changed, 229 insertions, 0 deletions
diff --git a/vendor/gix/src/clone/fetch/util.rs b/vendor/gix/src/clone/fetch/util.rs
new file mode 100644
index 000000000..ac8943f6e
--- /dev/null
+++ b/vendor/gix/src/clone/fetch/util.rs
@@ -0,0 +1,229 @@
+use std::{borrow::Cow, convert::TryInto, io::Write};
+
+use gix_odb::Find;
+use gix_ref::{
+ transaction::{LogChange, RefLog},
+ FullNameRef,
+};
+
+use super::Error;
+use crate::{
+ bstr::{BStr, BString, ByteSlice},
+ Repository,
+};
+
+enum WriteMode {
+ Overwrite,
+ Append,
+}
+
+#[allow(clippy::result_large_err)]
+pub fn write_remote_to_local_config_file(
+ remote: &mut crate::Remote<'_>,
+ remote_name: BString,
+) -> Result<gix_config::File<'static>, Error> {
+ let mut config = gix_config::File::new(local_config_meta(remote.repo));
+ remote.save_as_to(remote_name, &mut config)?;
+
+ write_to_local_config(&config, WriteMode::Append)?;
+ Ok(config)
+}
+
+fn local_config_meta(repo: &Repository) -> gix_config::file::Metadata {
+ let meta = repo.config.resolved.meta().clone();
+ assert_eq!(
+ meta.source,
+ gix_config::Source::Local,
+ "local path is the default for new sections"
+ );
+ meta
+}
+
+fn write_to_local_config(config: &gix_config::File<'static>, mode: WriteMode) -> std::io::Result<()> {
+ assert_eq!(
+ config.meta().source,
+ gix_config::Source::Local,
+ "made for appending to local configuration file"
+ );
+ let mut local_config = std::fs::OpenOptions::new()
+ .create(false)
+ .write(matches!(mode, WriteMode::Overwrite))
+ .append(matches!(mode, WriteMode::Append))
+ .open(config.meta().path.as_deref().expect("local config with path set"))?;
+ local_config.write_all(config.detect_newline_style())?;
+ config.write_to_filter(&mut local_config, |s| s.meta().source == gix_config::Source::Local)
+}
+
+pub fn append_config_to_repo_config(repo: &mut Repository, config: gix_config::File<'static>) {
+ let repo_config = gix_features::threading::OwnShared::make_mut(&mut repo.config.resolved);
+ repo_config.append(config);
+}
+
+/// HEAD cannot be written by means of refspec by design, so we have to do it manually here. Also create the pointed-to ref
+/// if we have to, as it might not have been naturally included in the ref-specs.
+pub fn update_head(
+ repo: &mut Repository,
+ remote_refs: &[gix_protocol::handshake::Ref],
+ reflog_message: &BStr,
+ remote_name: &BStr,
+) -> Result<(), Error> {
+ use gix_ref::{
+ transaction::{PreviousValue, RefEdit},
+ Target,
+ };
+ let (head_peeled_id, head_ref) = match remote_refs.iter().find_map(|r| {
+ Some(match r {
+ gix_protocol::handshake::Ref::Symbolic {
+ full_ref_name,
+ target,
+ object,
+ } if full_ref_name == "HEAD" => (Some(object.as_ref()), Some(target)),
+ gix_protocol::handshake::Ref::Direct { full_ref_name, object } if full_ref_name == "HEAD" => {
+ (Some(object.as_ref()), None)
+ }
+ gix_protocol::handshake::Ref::Unborn { full_ref_name, target } if full_ref_name == "HEAD" => {
+ (None, Some(target))
+ }
+ _ => return None,
+ })
+ }) {
+ Some(t) => t,
+ None => return Ok(()),
+ };
+
+ let head: gix_ref::FullName = "HEAD".try_into().expect("valid");
+ let reflog_message = || LogChange {
+ mode: RefLog::AndReference,
+ force_create_reflog: false,
+ message: reflog_message.to_owned(),
+ };
+ match head_ref {
+ Some(referent) => {
+ let referent: gix_ref::FullName = referent.try_into().map_err(|err| Error::InvalidHeadRef {
+ head_ref_name: referent.to_owned(),
+ source: err,
+ })?;
+ repo.refs
+ .transaction()
+ .packed_refs(gix_ref::file::transaction::PackedRefs::DeletionsAndNonSymbolicUpdates(
+ Box::new(|oid, buf| {
+ repo.objects
+ .try_find(oid, buf)
+ .map(|obj| obj.map(|obj| obj.kind))
+ .map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync + 'static>)
+ }),
+ ))
+ .prepare(
+ {
+ let mut edits = vec![RefEdit {
+ change: gix_ref::transaction::Change::Update {
+ log: reflog_message(),
+ expected: PreviousValue::Any,
+ new: Target::Symbolic(referent.clone()),
+ },
+ name: head.clone(),
+ deref: false,
+ }];
+ if let Some(head_peeled_id) = head_peeled_id {
+ edits.push(RefEdit {
+ change: gix_ref::transaction::Change::Update {
+ log: reflog_message(),
+ expected: PreviousValue::Any,
+ new: Target::Peeled(head_peeled_id.to_owned()),
+ },
+ name: referent.clone(),
+ deref: false,
+ });
+ };
+ edits
+ },
+ gix_lock::acquire::Fail::Immediately,
+ gix_lock::acquire::Fail::Immediately,
+ )
+ .map_err(crate::reference::edit::Error::from)?
+ .commit(
+ repo.committer()
+ .transpose()
+ .map_err(|err| Error::HeadUpdate(crate::reference::edit::Error::ParseCommitterTime(err)))?,
+ )
+ .map_err(crate::reference::edit::Error::from)?;
+
+ if let Some(head_peeled_id) = head_peeled_id {
+ let mut log = reflog_message();
+ log.mode = RefLog::Only;
+ repo.edit_reference(RefEdit {
+ change: gix_ref::transaction::Change::Update {
+ log,
+ expected: PreviousValue::Any,
+ new: Target::Peeled(head_peeled_id.to_owned()),
+ },
+ name: head,
+ deref: false,
+ })?;
+ }
+
+ setup_branch_config(repo, referent.as_ref(), head_peeled_id, remote_name)?;
+ }
+ None => {
+ repo.edit_reference(RefEdit {
+ change: gix_ref::transaction::Change::Update {
+ log: reflog_message(),
+ expected: PreviousValue::Any,
+ new: Target::Peeled(
+ head_peeled_id
+ .expect("detached heads always point to something")
+ .to_owned(),
+ ),
+ },
+ name: head,
+ deref: false,
+ })?;
+ }
+ };
+ Ok(())
+}
+
+/// Setup the remote configuration for `branch` so that it points to itself, but on the remote, if and only if currently
+/// saved refspecs are able to match it.
+/// For that we reload the remote of `remote_name` and use its ref_specs for match.
+fn setup_branch_config(
+ repo: &mut Repository,
+ branch: &FullNameRef,
+ branch_id: Option<&gix_hash::oid>,
+ remote_name: &BStr,
+) -> Result<(), Error> {
+ let short_name = match branch.category_and_short_name() {
+ Some((cat, shortened)) if cat == gix_ref::Category::LocalBranch => match shortened.to_str() {
+ Ok(s) => s,
+ Err(_) => return Ok(()),
+ },
+ _ => return Ok(()),
+ };
+ let remote = repo
+ .find_remote(remote_name)
+ .expect("remote was just created and must be visible in config");
+ let group = gix_refspec::MatchGroup::from_fetch_specs(remote.fetch_specs.iter().map(|s| s.to_ref()));
+ let null = gix_hash::ObjectId::null(repo.object_hash());
+ let res = group.match_remotes(
+ Some(gix_refspec::match_group::Item {
+ full_ref_name: branch.as_bstr(),
+ target: branch_id.unwrap_or(&null),
+ object: None,
+ })
+ .into_iter(),
+ );
+ if !res.mappings.is_empty() {
+ let mut config = repo.config_snapshot_mut();
+ let mut section = config
+ .new_section("branch", Some(Cow::Owned(short_name.into())))
+ .expect("section header name is always valid per naming rules, our input branch name is valid");
+ section.push("remote".try_into().expect("valid at compile time"), Some(remote_name));
+ section.push(
+ "merge".try_into().expect("valid at compile time"),
+ Some(branch.as_bstr()),
+ );
+ write_to_local_config(&config, WriteMode::Overwrite)?;
+ config.commit().expect("configuration we set is valid");
+ }
+ Ok(())
+}