Commit e0d616a2 authored by Brad King's avatar Brad King Committed by Kitware Robot

Merge topic 'checkout-method'

304a7738 prepare: expose a method to cd to the work tree
9a2f9299 prepare: split the checkout function
0d170839 docs: prefer "workarea" to "work area"
077bed18 prepare: support checking out paths into the work tree
b72bdf00 git: expose methods to allow commands to run in the environment
Acked-by: Kitware Robot's avatarKitware Robot <kwrobot@kitware.com>
Reviewed-by: Brad King's avatarBrad King <brad.king@kitware.com>
Merge-request: !34
parents e8fc577b 304a7738
......@@ -115,13 +115,22 @@ impl GitContext {
pub fn git(&self) -> Command {
let mut git = Command::new("git");
git.env("GIT_DIR", &self.gitdir);
self.apply_environment(&mut git);
git
}
/// Apply the git environment to a command.
///
/// This allows a command which needs to access this git context to be run.
pub fn apply_environment<'a>(&self, cmd: &'a mut Command) -> &'a mut Command {
cmd.env("GIT_DIR", &self.gitdir);
self.config
.as_ref()
.map(|config| git.env("GIT_CONFIG", config));
.map(|config| cmd.env("GIT_CONFIG", config));
git
cmd
}
/// Fetch references from the given remote.
......
......@@ -16,6 +16,7 @@ use super::error::*;
use super::git::{CommitId, GitContext};
use std::collections::hash_map::HashMap;
use std::ffi::OsStr;
use std::fs::{self, File};
use std::io::{Read, Write};
use std::marker::PhantomData;
......@@ -78,14 +79,14 @@ pub enum MergeResult<'a> {
/// The configuration for submodules within the tree.
pub type SubmoduleConfig = HashMap<String, HashMap<String, String>>;
// Intermediate type for setting up the work area. Does not include submodules.
// Intermediate type for setting up the workarea. Does not include submodules.
struct PreparingGitWorkArea {
context: GitContext,
dir: TempDir,
}
#[derive(Debug)]
/// A representation of an empty work area where actions which require a work tree and an index may
/// A representation of an empty workarea where actions which require a work tree and an index may
/// be preformed.
pub struct PreparedGitWorkArea {
context: GitContext,
......@@ -100,6 +101,83 @@ lazy_static! {
Regex::new(r"^submodule\.(?P<name>.*)\.(?P<key>[^=]*)=(?P<value>.*)$").unwrap();
}
trait WorkareaGitContext {
fn cmd(&self) -> Command;
}
fn checkout<P>(ctx: &WorkareaGitContext, paths: &[P]) -> Result<()>
where P: AsRef<OsStr>,
{
// Checkout .gitmodules so that submodules work.
let ls_files = try!(ctx.cmd()
.arg("ls-files")
.arg("--")
.args(paths)
.output()
.chain_err(|| "failed to construct ls-files command"));
if !ls_files.status.success() {
bail!(ErrorKind::Git(format!("listing paths in the index: {}",
String::from_utf8_lossy(&ls_files.stderr))));
}
checkout_files(ctx, &ls_files.stdout)
}
fn checkout_files(ctx: &WorkareaGitContext, files: &[u8]) -> Result<()> {
let mut checkout_index = try!(ctx.cmd()
.arg("checkout-index")
.arg("-f")
.arg("-q")
.arg("--stdin")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.chain_err(|| "failed to construct checkout-index command"));
try!(checkout_index.stdin
.as_mut()
.unwrap()
.write_all(files)
.chain_err(|| ErrorKind::Git("writing to checkout-index".to_string())));
let res = checkout_index.wait().unwrap();
if !res.success() {
let mut stderr = String::new();
try!(checkout_index.stderr
.as_mut()
.unwrap()
.read_to_string(&mut stderr)
.chain_err(|| "failed to read from checkout-index"));
bail!(ErrorKind::Git(format!("running checkout-index: {}", stderr)));
}
// Update the index for the files we put into the context
let mut update_index = try!(ctx.cmd()
.arg("update-index")
.arg("--stdin")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.chain_err(|| "failed to construct update-index command"));
try!(update_index.stdin
.as_mut()
.unwrap()
.write_all(files)
.chain_err(|| ErrorKind::Git("writing to update-index".to_string())));
let res = update_index.wait().unwrap();
if !res.success() {
let mut stderr = String::new();
try!(update_index.stderr
.as_mut()
.unwrap()
.read_to_string(&mut stderr)
.chain_err(|| "failed to read from update-index"));
bail!(ErrorKind::Git(format!("running update-index: {}", stderr)));
}
Ok(())
}
impl PreparingGitWorkArea {
// Create an area for performing actions which require a work tree.
fn new(context: GitContext, rev: &CommitId) -> Result<Self> {
......@@ -116,7 +194,7 @@ impl PreparingGitWorkArea {
workarea.dir.path());
try!(fs::create_dir_all(workarea.work_tree())
.chain_err(|| "failed to create the work area directory"));
.chain_err(|| "failed to create the workarea directory"));
try!(workarea.prepare(rev));
debug!(target: "git.workarea",
......@@ -155,71 +233,10 @@ impl PreparingGitWorkArea {
// Explicitly do not check the return code; it is a failure.
// Checkout .gitmodules so that submodules work.
let ls_files = try!(self.git()
.arg("ls-files")
.arg("--")
.arg(".gitmodules")
.output()
.chain_err(|| "failed to construct ls-files command for .gitmodules"));
if !ls_files.status.success() {
bail!(ErrorKind::Git(format!("listing .gitmodules files in the index: {}",
String::from_utf8_lossy(&ls_files.stderr))));
}
let mut checkout_index = try!(self.git()
.arg("checkout-index")
.arg("-f")
.arg("-q")
.arg("--stdin")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.chain_err(|| "failed to construct checkout-index command"));
try!(checkout_index.stdin
.as_mut()
.unwrap()
.write_all(&ls_files.stdout)
.chain_err(|| ErrorKind::Git("writing to checkout-index".to_string())));
let res = checkout_index.wait().unwrap();
if !res.success() {
let mut stderr = String::new();
try!(checkout_index.stderr
.as_mut()
.unwrap()
.read_to_string(&mut stderr)
.chain_err(|| "failed to read from checkout-index"));
bail!(ErrorKind::Git(format!("running checkout-index for .gitmodules: {}", stderr)));
}
// Update the index for the files we put into the context
let mut update_index = try!(self.git()
.arg("update-index")
.arg("--stdin")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.chain_err(|| "failed to construct update-index command"));
try!(update_index.stdin
.as_mut()
.unwrap()
.write_all(&ls_files.stdout)
.chain_err(|| ErrorKind::Git("writing to update-index".to_string())));
let res = update_index.wait().unwrap();
if !res.success() {
let mut stderr = String::new();
try!(update_index.stderr
.as_mut()
.unwrap()
.read_to_string(&mut stderr)
.chain_err(|| "failed to read from update-index"));
bail!(ErrorKind::Git(format!("running update-index for .gitmodules: {}", stderr)));
}
Ok(())
checkout(self, &[".gitmodules"])
}
// Run a git command in the work area.
// Run a git command in the workarea.
fn git(&self) -> Command {
let mut git = self.context.git();
......@@ -281,6 +298,12 @@ impl PreparingGitWorkArea {
}
}
impl WorkareaGitContext for PreparingGitWorkArea {
fn cmd(&self) -> Command {
self.git()
}
}
impl PreparedGitWorkArea {
/// Create an area for performing actions which require a work tree.
pub fn new(context: GitContext, rev: &CommitId) -> Result<Self> {
......@@ -344,7 +367,7 @@ impl PreparedGitWorkArea {
Ok(())
}
/// Run a git command in the work area.
/// Run a git command in the workarea.
pub fn git(&self) -> Command {
let mut git = self.context.git();
......@@ -493,6 +516,19 @@ impl PreparedGitWorkArea {
Ok(conflict_info)
}
/// Checkout paths from the index to the filesystem.
///
/// Normally, files are not placed into the worktree, so checks which use other tools to
/// inspect file contents do not work. This method checks out files to the working directory
/// and fixes up Git's knowledge that they are there.
///
/// All paths supported by Git's globbing and searching mechanisms are supported.
pub fn checkout<P>(&self, paths: &[P]) -> Result<()>
where P: AsRef<OsStr>,
{
checkout(self, paths)
}
/// Prepare a command to create a merge commit.
///
/// The merge is performed, but only as a tree object. In order to create the actual commit
......@@ -544,16 +580,34 @@ impl PreparedGitWorkArea {
Ok(MergeResult::Ready(commit_tree))
}
// The path to the index file for the work area.
// The path to the index file for the workarea.
fn index(&self) -> PathBuf {
self.dir.path().join("index")
}
// The path to the working directory for the work area.
// The path to the working directory for the workarea.
fn work_tree(&self) -> PathBuf {
self.dir.path().join("work")
}
fn apply_workarea_environment<'a>(&self, cmd: &'a mut Command) -> &'a mut Command {
cmd.env("GIT_WORK_TREE", self.work_tree())
.env("GIT_INDEX_FILE", self.index())
}
/// Apply the git environment to a command.
///
/// This allows a command which needs to access this git workarea to be run.
pub fn apply_environment<'a>(&self, cmd: &'a mut Command) -> &'a mut Command {
self.apply_workarea_environment(self.context
.apply_environment(cmd))
}
/// Run a command from the work tree root.
pub fn cd_to_work_tree<'a>(&self, cmd: &'a mut Command) -> &'a mut Command {
cmd.current_dir(self.work_tree())
}
/// The path to the git repository.
pub fn gitdir(&self) -> &Path {
self.context.gitdir()
......@@ -565,4 +619,15 @@ impl PreparedGitWorkArea {
pub fn submodule_config(&self) -> &SubmoduleConfig {
&self.submodule_config
}
#[cfg(test)]
pub fn __work_tree(&self) -> PathBuf {
self.work_tree()
}
}
impl WorkareaGitContext for PreparedGitWorkArea {
fn cmd(&self) -> Command {
self.git()
}
}
......@@ -705,3 +705,30 @@ fn test_setup_merge_submodule_not_present() {
status);
}
}
#[test]
fn test_work_area_checkout() {
let tempdir = test_workspace_dir("test_work_area_checkout");
let base = CommitId::new(ARBITRARY_COMMIT);
let ctx = git_context(tempdir.path());
let workarea = ctx.prepare(&base).unwrap();
assert!(workarea.submodule_config().is_empty());
assert!(!workarea.__work_tree().join("rustfmt.toml").exists());
workarea.checkout(&["rustfmt.toml"]).unwrap();
assert!(workarea.__work_tree().join("rustfmt.toml").exists());
assert!(!workarea.__work_tree().join("Cargo.toml").exists());
workarea.checkout(&["*.toml"]).unwrap();
assert!(workarea.__work_tree().join("Cargo.toml").exists());
assert!(!workarea.__work_tree().join("src").exists());
workarea.checkout(&["src/"]).unwrap();
assert!(workarea.__work_tree().join("src").exists());
assert!(workarea.__work_tree().join("src/lib.rs").exists());
assert!(!workarea.__work_tree().join("README.md").exists());
workarea.checkout(&["*.md", "does-not-exist"]).unwrap();
assert!(workarea.__work_tree().join("README.md").exists());
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment