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(); } } } }