Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/bin/git-rsl-init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ fn main() {
None => panic!("Must supply a REMOTE argument"),
Some(v) => v.to_owned(),
};
// TODO - reduce code duplication across the top level of the binaries

let mut repo = git::discover_repo()
.expect("You don't appear to be in a git project. Please check yourself and try again");

Expand Down
16 changes: 8 additions & 8 deletions src/bin/git-secure-fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ use std::process;
use clap::{App, Arg};
use git_rsl::errors::*;
use git_rsl::utils::git;
use git_rsl::{secure_fetch_with_cleanup, BranchName, RemoteName};
use git_rsl::{secure_fetch_with_cleanup, ReferenceName, RemoteName};

fn main() {
let matches = App::new("git-secure-fetch")
.bin_name("git secure-fetch")
.about("Securely fetch <BRANCH> from <REMOTE> checking the reference state log to protect against metadata attacks")
.about("Securely fetch <REFERENCE> from <REMOTE> checking the reference state log to protect against metadata attacks")
.arg(Arg::with_name("REMOTE")
.help("The remote repository that is the source of the fetch operation.")
.takes_value(false)
.required(true))
.arg(Arg::with_name("BRANCH")
.help("The target branch to fetch.")
.arg(Arg::with_name("REFERENCE")
.help("The target ref (branch or tag) to fetch.")
.takes_value(false)
.required(true))
.version(crate_version!())
Expand All @@ -32,18 +32,18 @@ fn main() {
Some(v) => v.to_owned(),
};

let branch = match matches.value_of("BRANCH") {
None => panic!("Must supply a BRANCH argument"),
let reference = match matches.value_of("REFERENCE") {
None => panic!("Must supply a REFERENCE argument"),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to avoid panics. You could use bail! from error chain if you moved the logic in main to another function and then called handle_error from main?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this specific case should probably go into the issue tracker and be addressed as part of a different PR, since this one is really just supposed to be adding tag support.

Some(v) => v.to_owned(),
};
// TODO - reduce code duplication across the top level of the binaries

let mut repo = git::discover_repo()
.expect("You don't appear to be in a git project. Please check yourself and try again");

if let Err(ref e) = secure_fetch_with_cleanup(
&mut repo,
&RemoteName::new(&remote),
&BranchName::new(&branch),
&ReferenceName::new(&reference),
) {
handle_error(e);
process::exit(1);
Expand Down
16 changes: 8 additions & 8 deletions src/bin/git-secure-push.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@ extern crate git_rsl;
use clap::{App, Arg};
use git_rsl::errors::*;
use git_rsl::utils::git;
use git_rsl::{secure_push_with_cleanup, BranchName, RemoteName};
use git_rsl::{secure_push_with_cleanup, ReferenceName, RemoteName};
use std::process;

fn main() {
let matches = App::new("git-secure-push")
.bin_name("git secure-push")
.about("Securely push <BRANCH> to <REMOTE> while checking and updating the reference state log to protect against metadata attacks")
.about("Securely push <REFERENCE> to <REMOTE> while checking and updating the reference state log to protect against metadata attacks")
.arg(Arg::with_name("REMOTE")
.help("The remote repository that is the target of the push operation. (example: origin)")
.takes_value(false)
.required(true))
.arg(Arg::with_name("BRANCH")
.help("The target branch to push. (example: master)")
.arg(Arg::with_name("REFERENCE")
.help("The target reference (branch or tag) to push.")
.takes_value(false)
.required(true))
.version(crate_version!())
Expand All @@ -31,18 +31,18 @@ fn main() {
Some(v) => v.to_owned(),
};

let branch = match matches.value_of("BRANCH") {
None => panic!("Must supply a BRANCH argument"),
let reference = match matches.value_of("REFERENCE") {
None => panic!("Must supply a REFERENCE argument"),
Some(v) => v.to_owned(),
};
// TODO - reduce code duplication across the top level of the binaries

let mut repo = git::discover_repo()
.expect("You don't appear to be in a git project. Please check yourself and try again");

if let Err(ref e) = secure_push_with_cleanup(
&mut repo,
&RemoteName::new(&remote),
&BranchName::new(&branch),
&ReferenceName::new(&reference),
) {
handle_error(e);
process::exit(1);
Expand Down
4 changes: 2 additions & 2 deletions src/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ pub fn secure_fetch<'remote, 'repo: 'remote>(
let mut rsl = RSL::read(repo, &mut remote_2).chain_err(|| "couldn't read RSL")?;

// reject if one of the branches has no rsl push entry
for branch in ref_names {
match rsl.find_last_remote_push_entry_for_branch(&branch) {
for reference in ref_names {
match rsl.find_last_remote_push_entry_for_reference(&reference) {
Ok(None) => bail!("no push records for the ref you are attempting to fetch"),
Err(e) => {
return Err(e.chain_err(|| "couldn't check that provided refs are valid"))
Expand Down
20 changes: 10 additions & 10 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,18 @@ use git2::{Oid, Repository};
use std::env;
use std::path::PathBuf;

/// Wrapper around a string reference to a branch name to reduce the odds of
/// Wrapper around a string reference to a typed reference to reduce the odds of
/// parameter mismatch
#[derive(Clone, Debug)]
pub struct BranchName<'a>(&'a str);
pub struct ReferenceName<'a>(&'a str);

impl<'a> BranchName<'a> {
pub fn new(source: &'a str) -> BranchName<'a> {
BranchName(source)
impl<'a> ReferenceName<'a> {
pub fn new(source: &'a str) -> ReferenceName<'a> {
ReferenceName(source)
}
}

impl<'a> AsRef<str> for BranchName<'a> {
impl<'a> AsRef<str> for ReferenceName<'a> {
fn as_ref(&self) -> &str {
self.0
}
Expand Down Expand Up @@ -80,27 +80,27 @@ pub fn rsl_init_with_cleanup(repo: &mut Repository, remote_name: &RemoteName) ->
pub fn secure_fetch_with_cleanup(
repo: &mut Repository,
remote_name: &RemoteName,
branch: &BranchName,
ref_name: &ReferenceName,
) -> Result<()> {
ensure!(remote_name.0 == "origin", "Remote name must be \"origin\"");
let ws = Workspace::new(repo)?;
let mut remote = ws.repo
.find_remote(remote_name.as_ref())
.chain_err(|| format!("unable to find remote named {}", remote_name.as_ref()))?;
fetch::secure_fetch(ws.repo, &mut remote, &[branch.as_ref()])
fetch::secure_fetch(ws.repo, &mut remote, &[ref_name.as_ref()])
}

pub fn secure_push_with_cleanup(
repo: &mut Repository,
remote_name: &RemoteName,
branch: &BranchName,
ref_name: &ReferenceName,
) -> Result<()> {
ensure!(remote_name.0 == "origin", "Remote name must be \"origin\"");
let ws = Workspace::new(repo)?;
let mut remote = ws.repo
.find_remote(remote_name.as_ref())
.chain_err(|| format!("unable to find remote named {}", remote_name.as_ref()))?;
push::secure_push(ws.repo, &mut remote, &[branch.as_ref()])
push::secure_push(ws.repo, &mut remote, &[ref_name.as_ref()])
}

pub struct Workspace<'repo> {
Expand Down
34 changes: 18 additions & 16 deletions src/push_entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ use std::fmt;

use crypto::digest::Digest;
use crypto::sha3::Sha3;
use git2::{self, BranchType, Oid, Reference, Repository};
//use libgit2_sys::GIT_OID_RAWSZ;
use git2::{self, Oid, Reference, Repository};

use nonce_bag::NonceBag;

Expand All @@ -14,8 +13,11 @@ use utils;
#[serde(remote = "Oid")]
#[derive(Serialize, Deserialize)]
struct OidDef {
#[serde(serialize_with = "utils::buffer_to_hex", deserialize_with = "utils::hex_to_buffer",
getter = "get_raw_oid")]
#[serde(
serialize_with = "utils::buffer_to_hex",
deserialize_with = "utils::hex_to_buffer",
getter = "get_raw_oid"
)]
raw: Vec<u8>,
}

Expand Down Expand Up @@ -52,19 +54,19 @@ impl PushEntry {
branch_str: &str,
prev: String,
nonce_bag: NonceBag,
) -> PushEntry {
let branch_head = repo.find_branch(branch_str, BranchType::Local)
.unwrap()
.get()
.target()
.unwrap();

PushEntry {
ref_name: format!("refs/heads/{}", branch_str),
oid: branch_head,
) -> Result<PushEntry> {
// NONE OF THIS IS FINE bc this method doesn't return an error :P
let full_ref =
utils::git::get_ref_from_name(repo, branch_str).ok_or("failed to get reference")?;
let target_oid = full_ref.target().ok_or("failed to get target oid")?;
let ref_name = String::from(full_ref.name().ok_or("failed to get ref's full name")?);

Ok(PushEntry {
ref_name: ref_name,
oid: target_oid,
prev_hash: prev,
nonce_bag,
}
})
}

pub fn prev_hash(&self) -> String {
Expand Down Expand Up @@ -186,7 +188,7 @@ mod tests {
&"master",
String::from("fwjjk42ofw093j"),
NonceBag::default(),
);
).unwrap();
let oid = repo.commit_push_entry(&entry, "refs/heads/RSL").unwrap();

assert_eq!(PushEntry::from_oid(&repo, &oid).unwrap().unwrap(), entry);
Expand Down
26 changes: 15 additions & 11 deletions src/rsl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,28 @@ pub enum RSLType {
}

pub fn all_push_entries_in_fetch_head(repo: &Repository, rsl: &RSL, ref_names: &[&str]) -> bool {
// find the last push entry for each branch
// find the last push entry for each reference
let latest_push_entries: Vec<Oid> = ref_names
.into_iter()
.filter_map(
|ref_name| match rsl.find_last_remote_push_entry_for_branch(ref_name).ok() {
|ref_name| match rsl.find_last_remote_push_entry_for_reference(ref_name).ok() {
Some(Some(pe)) => Some(pe.oid()),
Some(None) | None => None,
},
)
.collect();

// find the Oid of the tip of each remote fetched branch
// find the Oid of the tip of each remote fetched reference
let fetch_heads: Vec<Oid> = ref_names
.into_iter()
.filter_map(|ref_name| {
println!("ref_name: {:?}", ref_name);
match repo.find_branch(&format!("origin/{}", ref_name), BranchType::Remote) {
Ok(branch) => branch.get().target(),
Err(_) => None,
Err(_) => match repo.find_reference(&format!("refs/tags/{}", ref_name)) {
Ok(tag_ref) => tag_ref.target(),
Err(_) => None,
},
}
})
.collect();
Expand Down Expand Up @@ -170,7 +173,7 @@ impl<'remote, 'repo> RSL<'remote, 'repo> {
ref_names.first().unwrap(),
prev_hash,
self.nonce_bag.clone(),
);
)?;

// commit new pushentry (TODO commit to detached HEAD instead of local RSL branch, in case someone else has updated and a fastforward is not possible)
self.repo
Expand Down Expand Up @@ -225,14 +228,15 @@ impl<'remote, 'repo> RSL<'remote, 'repo> {
Ok(())
}

pub fn find_last_remote_push_entry_for_branch(
pub fn find_last_remote_push_entry_for_reference(
&self,
branch: &str,
reference: &str,
) -> Result<Option<PushEntry>> {
let mut revwalk: Revwalk = self.repo.revwalk()?;
revwalk.push(self.remote_head)?;
let mut current = Some(self.remote_head);
let ref_name = format!("refs/heads/{}", branch);
let reference = git::get_ref_from_name(self.repo, reference).expect("failed to get ref");
let ref_name = reference.name().expect("failed to get ref name");
while current != None {
if let Some(pe) = PushEntry::from_oid(self.repo, &current.unwrap())? {
if pe.ref_name() == ref_name {
Expand Down Expand Up @@ -359,7 +363,7 @@ impl<'repo> HasRSL<'repo> for Repository {
self.commit_nonce_bag()?;

// create initial bootstrapping push entry
let initial_pe = PushEntry::new(self, "RSL", String::from("First Push Entry"), nonce_bag);
let initial_pe = PushEntry::new(self, "RSL", String::from("First Push Entry"), nonce_bag)?;
self.commit_push_entry(&initial_pe, "refs/heads/RSL")?;

// push new rsl branch
Expand Down Expand Up @@ -559,7 +563,7 @@ mod tests {
&"master", // branch
String::from("hash_of_last_pushentry"), // prev
NonceBag::default(),
);
).unwrap();
let oid = repo.commit_push_entry(&entry, "refs/heads/RSL").unwrap();

// we are on the correct branch with new commit at the tip
Expand Down Expand Up @@ -605,7 +609,7 @@ mod tests {

// create push entry manuallly and commit it to the remote rsl branch
let prev_hash = rsl.last_remote_push_entry.hash();
let push_entry = PushEntry::new(&repo, &"master", prev_hash, rsl.nonce_bag);
let push_entry = PushEntry::new(&repo, &"master", prev_hash, rsl.nonce_bag).unwrap();
let oid = repo.commit_push_entry(&push_entry, "refs/remotes/origin/RSL")
.unwrap();

Expand Down
44 changes: 26 additions & 18 deletions src/utils/git.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use std::env;
use std::path::Path;
use std::{env, str};

use git2;
use git2::BranchType;
use git2::build::CheckoutBuilder;
use git2::{Commit, DiffOptions, FetchOptions, Oid, PushOptions, Remote, RemoteCallbacks,
Repository, RepositoryState, Signature, Tree};
use git2::BranchType;
use git2::{Commit, DiffOptions, FetchOptions, Oid, PushOptions, Reference, Remote,
RemoteCallbacks, Repository, RepositoryState, Signature, Tree};

use git2::CredentialType;
use git2::MergeAnalysis;
Expand Down Expand Up @@ -267,24 +267,32 @@ pub fn fetch(
})
}

pub fn get_ref_from_name<'repo>(repo: &'repo Repository, name: &str) -> Option<Reference<'repo>> {
match repo.find_branch(name, BranchType::Local) {
Ok(branch) => Some(branch.into_reference()),
Err(_) => {
let tag_ref = repo.find_reference(&format!("refs/tags/{}", name))
.expect("reference not found");
if tag_ref.is_tag() {
Some(tag_ref)
} else {
// not a branch or a tag
None
}
}
}
}

/// Push the branches or tags given in ref_names
pub fn push(repo: &Repository, remote: &mut Remote, ref_names: &[&str]) -> Result<()> {
let refs: Vec<String> = ref_names
.iter()
.map(|name: &&str| {
format!(
"refs/heads/{}:refs/heads/{}",
name.to_string(),
name.to_string()
)
})
.collect();

let mut refspecs: Vec<&str> = vec![];
for name in &refs {
refspecs.push(name)
let mut refs: Vec<String> = vec![];
for name in ref_names {
let reference = get_ref_from_name(&repo, name).ok_or("couldn't find reference")?;
refs.push(reference.name().ok_or("couldn't get name from reference")?.to_string())
}

let refspecs: Vec<&str> = refs.iter().map(AsRef::as_ref).collect();

push_refspecs(repo, remote, &refspecs)
}

Expand Down
Loading