using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using LibGit2Sharp.Core;
using LibGit2Sharp.Core.Handles;
namespace LibGit2Sharp
{
///
/// Holds the meta data of a .
///
public class TreeDefinition
{
private readonly Dictionary entries = new Dictionary();
private readonly Dictionary unwrappedTrees = new Dictionary();
///
/// Builds a from an existing .
///
/// The to be processed.
/// A new holding the meta data of the .
public static TreeDefinition From(Tree tree)
{
Ensure.ArgumentNotNull(tree, "tree");
var td = new TreeDefinition();
foreach (TreeEntry treeEntry in tree)
{
td.Add(treeEntry.Name, treeEntry);
}
return td;
}
///
/// Builds a from a 's .
///
/// The whose tree is to be processed
/// A new holding the meta data of the 's .
public static TreeDefinition From(Commit commit)
{
Ensure.ArgumentNotNull(commit, "commit");
return From(commit.Tree);
}
private void AddEntry(string targetTreeEntryName, TreeEntryDefinition treeEntryDefinition)
{
if (entries.ContainsKey(targetTreeEntryName))
{
WrapTree(targetTreeEntryName, treeEntryDefinition);
return;
}
entries.Add(targetTreeEntryName, treeEntryDefinition);
}
///
/// Removes a located the specified path.
///
/// The path within this .
/// The current .
public virtual TreeDefinition Remove(string treeEntryPath)
{
Ensure.ArgumentNotNullOrEmptyString(treeEntryPath, "treeEntryPath");
if (this[treeEntryPath] == null)
{
return this;
}
Tuple segments = ExtractPosixLeadingSegment(treeEntryPath);
if (segments.Item2 == null)
{
entries.Remove(segments.Item1);
}
if (!unwrappedTrees.ContainsKey(segments.Item1))
{
return this;
}
if (segments.Item2 != null)
{
unwrappedTrees[segments.Item1].Remove(segments.Item2);
}
if (unwrappedTrees[segments.Item1].entries.Count == 0)
{
unwrappedTrees.Remove(segments.Item1);
entries.Remove(segments.Item1);
}
return this;
}
///
/// Adds or replaces a at the specified location.
///
/// The path within this .
/// The to be stored at the described location.
/// The current .
public virtual TreeDefinition Add(string targetTreeEntryPath, TreeEntryDefinition treeEntryDefinition)
{
Ensure.ArgumentNotNullOrEmptyString(targetTreeEntryPath, "targetTreeEntryPath");
Ensure.ArgumentNotNull(treeEntryDefinition, "treeEntryDefinition");
if (Path.IsPathRooted(targetTreeEntryPath))
{
throw new ArgumentException("The provided path is an absolute path.");
}
if (treeEntryDefinition is TransientTreeTreeEntryDefinition)
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture,
"The {0} references a target which hasn't been created in the {1} yet. " +
"This situation can occur when the target is a whole new {2} being created, or when an existing {2} is being updated because some of its children were added/removed.",
typeof(TreeEntryDefinition).Name, typeof(ObjectDatabase).Name, typeof(Tree).Name));
}
Tuple segments = ExtractPosixLeadingSegment(targetTreeEntryPath);
if (segments.Item2 != null)
{
TreeDefinition td = RetrieveOrBuildTreeDefinition(segments.Item1, true);
td.Add(segments.Item2, treeEntryDefinition);
}
else
{
AddEntry(segments.Item1, treeEntryDefinition);
}
return this;
}
///
/// Adds or replaces a , built from the provided , at the specified location.
///
/// The path within this .
/// The to be stored at the described location.
/// The current .
public virtual TreeDefinition Add(string targetTreeEntryPath, TreeEntry treeEntry)
{
Ensure.ArgumentNotNull(treeEntry, "treeEntry");
TreeEntryDefinition ted = TreeEntryDefinition.From(treeEntry);
return Add(targetTreeEntryPath, ted);
}
///
/// Adds or replaces a , dynamically built from the provided , at the specified location.
///
/// The path within this .
/// The to be stored at the described location.
/// The file related attributes.
/// The current .
public virtual TreeDefinition Add(string targetTreeEntryPath, Blob blob, Mode mode)
{
Ensure.ArgumentNotNull(blob, "blob");
Ensure.ArgumentConformsTo(mode, m => m.HasAny(TreeEntryDefinition.BlobModes), "mode");
TreeEntryDefinition ted = TreeEntryDefinition.From(blob, mode);
return Add(targetTreeEntryPath, ted);
}
///
/// Adds or replaces a , dynamically built from the content of the file, at the specified location.
///
/// The path within this .
/// The path to the file from which a will be built and stored at the described location. A relative path is allowed to be passed if the target
/// is a standard, non-bare, repository. The path will then be considered as a path relative to the root of the working directory.
/// The file related attributes.
/// The current .
public virtual TreeDefinition Add(string targetTreeEntryPath, string filePath, Mode mode)
{
Ensure.ArgumentNotNullOrEmptyString(filePath, "filePath");
TreeEntryDefinition ted = TreeEntryDefinition.TransientBlobFrom(filePath, mode);
return Add(targetTreeEntryPath, ted);
}
///
/// Adds or replaces a , dynamically built from the provided , at the specified location.
///
/// The path within this .
/// The to be stored at the described location.
/// The current .
public virtual TreeDefinition Add(string targetTreeEntryPath, Tree tree)
{
Ensure.ArgumentNotNull(tree, "tree");
TreeEntryDefinition ted = TreeEntryDefinition.From(tree);
return Add(targetTreeEntryPath, ted);
}
///
/// Adds or replaces a gitlink equivalent to .
///
/// The to be linked.
/// The current .
public virtual TreeDefinition Add(Submodule submodule)
{
Ensure.ArgumentNotNull(submodule, "submodule");
return AddGitLink(submodule.Path, submodule.HeadCommitId);
}
///
/// Adds or replaces a gitlink ,
/// referencing the commit identified by ,
/// at the specified location.
///
/// The path within this .
/// The of the commit to be linked at the described location.
/// The current .
public virtual TreeDefinition AddGitLink(string targetTreeEntryPath, ObjectId objectId)
{
Ensure.ArgumentNotNull(objectId, "objectId");
var ted = TreeEntryDefinition.From(objectId);
return Add(targetTreeEntryPath, ted);
}
private TreeDefinition RetrieveOrBuildTreeDefinition(string treeName, bool shouldOverWrite)
{
TreeDefinition td;
if (unwrappedTrees.TryGetValue(treeName, out td))
{
return td;
}
TreeEntryDefinition treeEntryDefinition;
bool hasAnEntryBeenFound = entries.TryGetValue(treeName, out treeEntryDefinition);
if (hasAnEntryBeenFound)
{
switch (treeEntryDefinition.TargetType)
{
case TreeEntryTargetType.Tree:
td = From(treeEntryDefinition.Target as Tree);
break;
case TreeEntryTargetType.Blob:
case TreeEntryTargetType.GitLink:
if (shouldOverWrite)
{
td = new TreeDefinition();
break;
}
return null;
default:
throw new NotImplementedException();
}
}
else
{
if (!shouldOverWrite)
{
return null;
}
td = new TreeDefinition();
}
entries[treeName] = new TransientTreeTreeEntryDefinition();
unwrappedTrees.Add(treeName, td);
return td;
}
internal Tree Build(Repository repository)
{
WrapAllTreeDefinitions(repository);
using (var builder = new TreeBuilder(repository))
{
var builtTreeEntryDefinitions = new List>(entries.Count);
foreach (KeyValuePair kvp in entries)
{
string name = kvp.Key;
TreeEntryDefinition ted = kvp.Value;
var transient = ted as TransientBlobTreeEntryDefinition;
if (transient == null)
{
builder.Insert(name, ted);
continue;
}
Blob blob = transient.Builder(repository.ObjectDatabase);
TreeEntryDefinition ted2 = TreeEntryDefinition.From(blob, ted.Mode);
builtTreeEntryDefinitions.Add(new Tuple(name, ted2));
builder.Insert(name, ted2);
}
builtTreeEntryDefinitions.ForEach(t => entries[t.Item1] = t.Item2);
ObjectId treeId = builder.Write();
var result = repository.Lookup(treeId);
if (result == null)
{
throw new LibGit2SharpException("Unable to read created tree");
}
return result;
}
}
private void WrapAllTreeDefinitions(Repository repository)
{
foreach (KeyValuePair pair in unwrappedTrees)
{
Tree tree = pair.Value.Build(repository);
entries[pair.Key] = TreeEntryDefinition.From(tree);
}
unwrappedTrees.Clear();
}
private void WrapTree(string entryName, TreeEntryDefinition treeEntryDefinition)
{
entries[entryName] = treeEntryDefinition;
unwrappedTrees.Remove(entryName);
}
///
/// Retrieves the located the specified path.
///
/// The path within this .
/// The found if any; null otherwise.
public virtual TreeEntryDefinition this[string treeEntryPath]
{
get
{
Ensure.ArgumentNotNullOrEmptyString(treeEntryPath, "treeEntryPath");
Tuple segments = ExtractPosixLeadingSegment(treeEntryPath);
if (segments.Item2 != null)
{
TreeDefinition td = RetrieveOrBuildTreeDefinition(segments.Item1, false);
return td == null ? null : td[segments.Item2];
}
TreeEntryDefinition treeEntryDefinition;
return !entries.TryGetValue(segments.Item1, out treeEntryDefinition) ? null : treeEntryDefinition;
}
}
private static Tuple ExtractPosixLeadingSegment(FilePath targetPath)
{
string[] segments = targetPath.Posix.Split(new[] { '/' }, 2);
if (segments[0] == string.Empty || (segments.Length == 2 && (segments[1] == string.Empty || segments[1].StartsWith("/", StringComparison.Ordinal))))
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "'{0}' is not a valid path.", targetPath));
}
return new Tuple(segments[0], segments.Length == 2 ? segments[1] : null);
}
private class TreeBuilder : IDisposable
{
private readonly TreeBuilderSafeHandle handle;
public TreeBuilder(Repository repo)
{
handle = Proxy.git_treebuilder_new(repo.Handle);
}
public void Insert(string name, TreeEntryDefinition treeEntryDefinition)
{
Proxy.git_treebuilder_insert(handle, name, treeEntryDefinition);
}
public ObjectId Write()
{
return Proxy.git_treebuilder_write(handle);
}
public void Dispose()
{
handle.SafeDispose();
}
}
}
}