context: /*
   * Copyright (C) 2009, Henon <meinrad.recheis@gmail.com>
   * Copyright (C) 2010, Andrew Cooper <andymancooper@gmail.com>
   *
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or
   * without modification, are permitted provided that the following
   * conditions are met:
   *
   * - Redistributions of source code must retain the above copyright
   *   notice, this list of conditions and the following disclaimer.
   *
   * - Redistributions in binary form must reproduce the above
   *   copyright notice, this list of conditions and the following
   *   disclaimer in the documentation and/or other materials provided
   *   with the distribution.
   *
   * - Neither the name of the TicGit project nor the
   *   names of its contributors may be used to endorse or promote
   *   products derived from this software without specific prior
   *   written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Security;
  using System.Text;
  using System.IO;
  using System.Diagnostics;
  using GitSharp.Commands;
  using GitSharp.Core;
  using CoreRepository = GitSharp.Core.Repository;
  using System.Text.RegularExpressions;
  
  namespace GitSharp
  {
  	/// <summary>
  	/// Represents a git repository
  	/// </summary>
  	public class Repository : IDisposable
  	{
  		#region Constructors
  
  		internal CoreRepository _internal_repo;
  		
  		RemoteCollection _remoteCollection;
  		StashCollection _stashCollection;
  
  		internal Repository(CoreRepository repo)
  		{
  			//PreferredEncoding = Encoding.UTF8;
  			_internal_repo = repo;
  		}
  
  		/// <summary>
  		/// Initializes the Repository object.
  		/// </summary>
  		/// <param name="path">Path to the local git repository.</param>
  		public Repository(string path)
  			: this(GitSharp.Core.Repository.Open(path))
  		{
  		}
  
  
  		#endregion
  
  		#region Public properties
  
  
  		/// <summary>
  		/// Returns the path to the repository database (i.e. the .git directory).
  		/// </summary>
  		public string Directory
  		{
  			get
  			{
  				Debug.Assert(_internal_repo != null, "Repository not initialized correctly.");
  				return _internal_repo.Directory.FullName;
  			}
  		}
  
  		/// <summary>
  		/// Get the repository's head which is a symbolic reference to the active branch.
  		/// </summary>
  		public Branch Head
  		{
  			get
  			{
  				Debug.Assert(_internal_repo != null, "Repository not initialized correctly.");
  				return new Branch(this, "HEAD");
  			}
  			internal set // <-- Note that setting head does not automatically check out that branch into the repository's working directory. 
  			{
  				if (value == null)
  					throw new ArgumentException("Value can not be null.");
  				if (Head.Name != value.Name)
  				{
  					if (Branches.ContainsKey(value.Name))
  					{
  						var updateRef = _internal_repo.UpdateRef("HEAD");
  						updateRef.NewObjectId = value.Target._id;
  						updateRef.IsForceUpdate = true;
  						updateRef.update();
  						RefUpdate u = _internal_repo.UpdateRef(Constants.HEAD);
  						u.link(value.Name);
  					}
  					else
  						throw new ArgumentException("Trying to set HEAD to non existent branch: " + value.Name);
  				}
  
  			}
  		}
  
  		public Index Index
  		{
  			get
  			{
  				return new Index(this); // <--- this is just a wrapper around the internal repo's GitIndex instance so need not cache it here
  			}
  		}
  
  		/// <summary>
  		/// Returns true if this repository is a bare repository. Bare repositories don't have a working directory and thus do not support some operations.
  		/// </summary>
  		public bool IsBare
  		{
  			get
  			{
  				Debug.Assert(_internal_repo != null, "Repository not initialized correctly.");
  				return _internal_repo.Config.getBoolean("core", "bare", false);
  			}
  		}
  
  		/// <summary>
  		/// Returns the path to the working directory (i.e. the parent of the .git directory of a non-bare repo). Returns null if it is a bare repository.
  		/// </summary>
  		public string WorkingDirectory
  		{
  			get
  			{
  				Debug.Assert(_internal_repo != null, "Repository not initialized correctly.");
  				if (IsBare)
  					return null;
  				return _internal_repo.WorkingDirectory.FullName;
  			}
  		}
  
  		#endregion
  
  		/// <summary>
  		/// Check out the branch with the given name into the working directory and make it the current branch.
  		/// </summary>
  		/// <param name="name"></param>
  		public void SwitchToBranch(string name)
  		{
  			SwitchToBranch(new Branch(this, name));
  		}
  
  		/// <summary>
  		/// Check out the given branch into the working directory and make it the current branch.
  		/// </summary>
  		/// <param name="branch"></param>
  		public void SwitchToBranch(Branch branch)
  		{
  			branch.Checkout();
  		}
  
  		/// <summary>
  		/// Commit staged changes and update HEAD. The default git author from the config is used.
  		/// </summary>
  		/// <param name="message">The commit message</param>
  		/// <returns>Returns the newly created commit</returns>
  		public Commit Commit(string message)
  		{
  			return Commit(message, new Author(Config["user.name"] ?? "unknown", Config["user.email"] ?? "unknown@(none)."));
  		}
  
  		/// <summary>
  		/// Commit staged changes and update HEAD
  		/// </summary>
  		/// <param name="message">The commit message</param>
  		/// <param name="author">The author of the content to be committed</param>
  		/// <returns>Returns the newly created commit</returns>
  		public Commit Commit(string message, Author author)
  		{
  			return Index.CommitChanges(message, author);
  		}
  		
  		/// <summary>
  		/// Commit changes in the specified files and update HEAD
  		/// </summary>
  		/// <param name='message'>The commit message</param>
  		/// <param name="paths">List of files to commit</param>
  		/// <returns>Returns the newly created commit</returns>
  		public Commit Commit(string message, params string[] paths)
  		{
  			return Commit(message, new Author(Config["user.name"] ?? "unknown", Config["user.email"] ?? "unknown@(none)."), paths);
  		}
  		
  		/// <summary>
  		/// Commit changes in the specified files and update HEAD
  		/// </summary>
  		/// <param name='message'>The commit message</param>
  		/// <param name="author">The author of the content to be committed</param>
  		/// <param name="paths">List of files to commit</param>
  		/// <returns>Returns the newly created commit</returns>
  		public Commit Commit(string message, Author author, params string[] paths)
  		{
  			if (string.IsNullOrEmpty(message))
  				throw new ArgumentException("Commit message must not be null or empty!", "message");
  			if (string.IsNullOrEmpty(author.Name))
  				throw new ArgumentException("Author name must not be null or empty!", "author");
  			
  			int basePathLength = Path.GetFullPath (WorkingDirectory).TrimEnd ('/','\\').Length;
  			
  			// Expand directory paths into file paths. Convert paths to full paths.
  			List<string> filePaths = new List<string> ();
  			foreach (var path in paths) {
  				string fullPath = path;
  				if (!Path.IsPathRooted (fullPath))
  					fullPath = Path.Combine (WorkingDirectory, fullPath);
  				fullPath = Path.GetFullPath (fullPath).TrimEnd ('/','\\');
  				DirectoryInfo dir = new DirectoryInfo (fullPath);
  				if (dir.Exists)
  					filePaths.AddRange (GetDirectoryFiles (dir));
  				else
  					filePaths.Add (fullPath);
  			}
  			
  			// Read the tree of the last commit. We are going to update it.
  			GitSharp.Core.Tree tree = _internal_repo.MapTree (CurrentBranch.CurrentCommit._id);
  
  			// Keep a list of trees that have been modified, since they have to be written.
  			HashSet<GitSharp.Core.Tree> modifiedTrees = new HashSet<GitSharp.Core.Tree> ();
  			
  			// Update the tree
  			foreach (string fullPath in filePaths) {
  				string relPath = fullPath.Substring (basePathLength + 1).Replace ('\\','/');				
  				TreeEntry treeEntry = tree.FindBlobMember (relPath);
  				
  				if (File.Exists (fullPath)) {
  					// Looks like an old directory is now a file. Delete the subtree and create a new entry for the file.
  					if (treeEntry != null && !(treeEntry is FileTreeEntry))
  						treeEntry.Delete ();
  
  					FileTreeEntry fileEntry = treeEntry as FileTreeEntry;
  					var writer = new ObjectWriter (_internal_repo);
  					bool executable = GitSharp.Core.Util.FS.canExecute (new FileInfo (fullPath));
  					ObjectId id = writer.WriteBlob (new FileInfo (fullPath));
  					if (fileEntry == null) {
  						// It's a new file. Add it.
  						fileEntry = (FileTreeEntry) tree.AddFile (relPath);
  						treeEntry = fileEntry;
  					} else if (fileEntry.Id == id && executable == fileEntry.IsExecutable) {
  						// Same file, ignore it
  						continue;
  					}
  					
  					fileEntry.Id = id;
  					fileEntry.SetExecutable (executable);
  				}
  				else {
  					// Deleted file or directory. Remove from the tree
  					if (treeEntry != null) {
  						GitSharp.Core.Tree ptree = treeEntry.Parent;
  						treeEntry.Delete ();
  						// Remove the subtree if it's now empty
  						while (ptree != null && ptree.MemberCount == 0) {
  							GitSharp.Core.Tree nextParent = ptree.Parent;
  							ptree.Delete ();
  							ptree = nextParent;
  						}
  					}
  					else
  						continue; // Already deleted.
  				}
  				modifiedTrees.Add (treeEntry.Parent);
  			}
  
  			// check if tree is different from current commit's tree
  			if (modifiedTrees.Count == 0)
  				throw new InvalidOperationException("There are no changes to commit");
  			
  			// Create new trees if there is any change
  			ObjectId tree_id = SaveTree (tree, modifiedTrees);
  
  			// Create the commit
  			var parent = CurrentBranch.CurrentCommit;
  			var commit = GitSharp.Commit.Create(message, parent, new Tree(this, tree_id), author);
  			Ref.Update("HEAD", commit);
  			
  			// Unstage updated files
  			Index.Unstage (paths);
  			
  			return commit;
  		}
  		
  		IEnumerable<string> GetDirectoryFiles (DirectoryInfo dir)
  		{
  			// Recursively get the list of files in the directory (which are not ignored)
  			return dir.GetFiles ()
  						.Where (f => !Index.IgnoreHandler.IsIgnored (f.FullName))
  						.Select (f => f.FullName)
  				   .Concat (dir.GetDirectories ()
  						.Where (di => !Index.IgnoreHandler.IsIgnored (di.FullName) && di.Name != GitSharp.Core.Constants.DOT_GIT)
  						.SelectMany (di => GetDirectoryFiles (di)));
  		}
  		
  		ObjectId SaveTree (GitSharp.Core.Tree tree, HashSet<GitSharp.Core.Tree> modifiedTrees)
  		{
  			// Saves tree that have been modified (that is, which are in the provided list or
  			// which have child trees that have been modified)
  			
  			bool childModified = false;
  			foreach (var te in tree.Members) {
  				GitSharp.Core.Tree childTree = te as GitSharp.Core.Tree;
  				if (childTree != null) {
  					ObjectId newId = SaveTree (childTree, modifiedTrees);
  					if (newId != null) {
  						childTree.Id = newId;
  						childModified = true;
  					}
  				}
  			}
  			if (childModified || modifiedTrees.Contains (tree)) {
  				ObjectWriter writer = new ObjectWriter (_internal_repo);
  
gt: 				return writer.WriteTree (tree);