summaryrefslogtreecommitdiff
path: root/crates/git-tracker/src/managed_repository.rs
blob: 0a41bd05d5719b833a76cf880715b46864085c04 (plain)
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
use git2::{AutotagOption, FetchOptions, RemoteCallbacks, RemoteUpdateFlags, Repository};
use log::{debug, info, trace, warn};
use std::{io::Write, path::PathBuf};

// much of this is shamelessly lifted from
// https://github.com/rust-lang/git2-rs/blob/9a5c9706ff578c936be644dd1e8fe155bdc4d129/examples/pull.rs

#[derive(Debug, thiserror::Error)]
pub enum Error {
	#[error("libgit2 error")]
	Git(#[from] git2::Error),
}

pub struct ManagedRepository {
	pub path: PathBuf,
	pub tracked_branches: Vec<String>,
	pub upstream_remote_url: String,
	pub upstream_remote_name: String,
}

impl ManagedRepository {
	/// basic set of options for fetching from remotes
	fn fetch_options<'a>() -> FetchOptions<'a> {
		let mut remote_callbacks = RemoteCallbacks::new();
		remote_callbacks.transfer_progress(|progress| {
			if progress.received_objects() == progress.total_objects() {
				trace!(
					"Resolving deltas {}/{}\r",
					progress.indexed_deltas(),
					progress.total_deltas()
				);
			} else {
				trace!(
					"Received {}/{} objects ({}) in {} bytes\r",
					progress.received_objects(),
					progress.total_objects(),
					progress.indexed_objects(),
					progress.received_bytes()
				);
			}
			std::io::stdout().flush().ok();
			true
		});

		let mut fetch_opts = FetchOptions::new();
		fetch_opts.remote_callbacks(remote_callbacks);

		fetch_opts
	}

	/// Update the given branches in the [`repository`] using the nixpkgs remote
	fn update_branches_in(&self, repository: &Repository) -> Result<(), Error> {
		let mut remote = repository.find_remote(&self.upstream_remote_name)?;
		// download all the refs
		remote.download(&self.tracked_branches, Some(&mut Self::fetch_options()))?;
		remote.disconnect()?;
		// and (hopefully) update what they refer to for later
		remote.update_tips(
			None,
			RemoteUpdateFlags::UPDATE_FETCHHEAD,
			AutotagOption::Auto,
			None,
		)?;

		Ok(())
	}

	/// Fetch the repository or update it if it exists
	///
	/// # Errors
	/// Will return [`Err`] if the repository cannot be opened, cloned, or updated
	pub fn fetch_or_update(&self) -> Result<(), Error> {
		// Open our repository or clone it if it doesn't exist
		let repository = if self.path.exists() {
			Repository::open(self.path.as_path())?
		} else {
			warn!(
				"Couldn't find repository at {}! Cloning a fresh one from {}",
				self.path.display(),
				self.upstream_remote_url
			);
			Repository::clone(&self.upstream_remote_url, self.path.as_path())?;
			info!("Finished cloning to {}", self.path.display());

			// bail early as we already have a fresh copy
			return Ok(());
		};

		debug!("Updating repository at {}", self.path.display());
		self.update_branches_in(&repository)?;
		debug!("Finished updating!");

		Ok(())
	}
}