private static void CleanRepoAndSubmodules(LibGit2Sharp.Repository repo, ILogger log) { using (log.BeginScope($"Beginning clean of {repo.Info.WorkingDirectory} and {repo.Submodules.Count()} submodules")) { log.LogDebug($"Beginning clean of {repo.Info.WorkingDirectory} and {repo.Submodules.Count()} submodules"); LibGit2Sharp.StatusOptions options = new LibGit2Sharp.StatusOptions { IncludeUntracked = true, RecurseUntrackedDirs = true, }; int count = 0; foreach (LibGit2Sharp.StatusEntry item in repo.RetrieveStatus(options)) { if (item.State == LibGit2Sharp.FileStatus.NewInWorkdir) { File.Delete(Path.Combine(repo.Info.WorkingDirectory, item.FilePath)); ++count; } } log.LogDebug($"Deleted {count} untracked files"); foreach (LibGit2Sharp.Submodule sub in repo.Submodules) { string normalizedSubPath = sub.Path.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar); string subRepoPath = Path.Combine(repo.Info.WorkingDirectory, normalizedSubPath); string subRepoGitFilePath = Path.Combine(subRepoPath, ".git"); if (!File.Exists(subRepoGitFilePath)) { log.LogDebug($"Submodule {sub.Name} in {subRepoPath} does not appear to be initialized (no file at {subRepoGitFilePath}), attempting to initialize now."); // hasn't been initialized yet, can happen when different hashes have new or moved submodules try { repo.Submodules.Update(sub.Name, new LibGit2Sharp.SubmoduleUpdateOptions { Init = true }); } catch { log.LogDebug($"Submodule {sub.Name} in {subRepoPath} is already initialized, trying to adopt from super-repo {repo.Info.Path}"); // superrepo thinks it is initialized, but it's orphaned. Go back to the master repo to find out where this is supposed to point. using (LibGit2Sharp.Repository masterRepo = new LibGit2Sharp.Repository(repo.Info.WorkingDirectory)) { LibGit2Sharp.Submodule masterSubModule = masterRepo.Submodules.Single(s => s.Name == sub.Name); string masterSubPath = Path.Combine(repo.Info.Path, "modules", masterSubModule.Path); log.LogDebug($"Writing .gitdir redirect {masterSubPath} to {subRepoGitFilePath}"); Directory.CreateDirectory(Path.GetDirectoryName(subRepoGitFilePath)); File.WriteAllText(subRepoGitFilePath, $"gitdir: {masterSubPath}"); } } } using (log.BeginScope($"Beginning clean of submodule {sub.Name}")) { log.LogDebug($"Beginning clean of submodule {sub.Name} in {subRepoPath}"); // The worktree is stored in the .gitdir/config file, so we have to change it // to get it to check out to the correct place. LibGit2Sharp.ConfigurationEntry <string> oldWorkTree = null; using (LibGit2Sharp.Repository subRepo = new LibGit2Sharp.Repository(subRepoPath)) { oldWorkTree = subRepo.Config.Get <string>("core.worktree"); if (oldWorkTree != null) { log.LogDebug($"{subRepoPath} old worktree is {oldWorkTree.Value}, setting to {subRepoPath}"); subRepo.Config.Set("core.worktree", subRepoPath); } // This branch really shouldn't happen but just in case. else { log.LogDebug($"{subRepoPath} has default worktree, leaving unchanged"); } } using (LibGit2Sharp.Repository subRepo = new LibGit2Sharp.Repository(subRepoPath)) { log.LogDebug($"Resetting {sub.Name} to {sub.HeadCommitId.Sha}"); subRepo.Reset(LibGit2Sharp.ResetMode.Hard, subRepo.Commits.QueryBy(new LibGit2Sharp.CommitFilter { IncludeReachableFrom = subRepo.Refs }).Single(c => c.Sha == sub.HeadCommitId.Sha)); // Now we reset the worktree back so that when we can initialize a Repository // from it, instead of having to figure out which hash of the repo was most recently checked out. if (oldWorkTree != null) { log.LogDebug($"resetting {subRepoPath} worktree to {oldWorkTree.Value}"); subRepo.Config.Set("core.worktree", oldWorkTree.Value); } else { log.LogDebug($"leaving {subRepoPath} worktree as default"); } log.LogDebug($"Done resetting {subRepoPath}, checking submodules"); CleanRepoAndSubmodules(subRepo, log); } } if (File.Exists(subRepoGitFilePath)) { log.LogDebug($"Deleting {subRepoGitFilePath} to orphan submodule {sub.Name}"); File.Delete(subRepoGitFilePath); } else { log.LogDebug($"{sub.Name} doesn't have a .gitdir redirect at {subRepoGitFilePath}, skipping delete"); } } } }
private static void CheckoutSubmodules(LibGit2Sharp.Repository repo, LibGit2Sharp.CloneOptions submoduleCloneOptions, string gitDirParentPath, ILogger log) { foreach (LibGit2Sharp.Submodule sub in repo.Submodules) { log.LogDebug($"Updating submodule {sub.Name} at {sub.Path} for {repo.Info.WorkingDirectory}. GitDirParent: {gitDirParentPath}"); repo.Submodules.Update(sub.Name, new LibGit2Sharp.SubmoduleUpdateOptions { CredentialsProvider = submoduleCloneOptions.CredentialsProvider, Init = true }); string normalizedSubPath = sub.Path.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar); string subRepoPath = Path.Combine(repo.Info.WorkingDirectory, normalizedSubPath); string relativeGitDirPath = File.ReadAllText(Path.Combine(subRepoPath, ".git")).Substring(8); log.LogDebug($"Submodule {sub.Name} has .gitdir {relativeGitDirPath}"); string absoluteGitDirPath = Path.GetFullPath(Path.Combine(subRepoPath, relativeGitDirPath)); string relocatedGitDirPath = absoluteGitDirPath.Replace(repo.Info.Path.TrimEnd(new[] { '/', '\\' }), gitDirParentPath.TrimEnd(new[] { '/', '\\' })); string subRepoGitFilePath = Path.Combine(subRepoPath, ".git"); log.LogDebug($"Writing new .gitdir path {relocatedGitDirPath} to submodule at {subRepoPath}"); // File.WriteAllText gets access denied for some reason using (FileStream s = File.OpenWrite(subRepoGitFilePath)) using (StreamWriter w = new StreamWriter(s)) { w.Write($"gitdir: {relocatedGitDirPath}"); w.Flush(); s.SetLength(s.Position); } // The worktree is stored in the .gitdir/config file, so we have to change it // to get it to check out to the correct place. LibGit2Sharp.ConfigurationEntry <string> oldWorkTree = null; using (LibGit2Sharp.Repository subRepo = new LibGit2Sharp.Repository(subRepoPath)) { oldWorkTree = subRepo.Config.Get <string>("core.worktree"); if (oldWorkTree != null) { log.LogDebug($"{subRepoPath} old worktree is {oldWorkTree.Value}, setting to {subRepoPath}"); subRepo.Config.Set("core.worktree", subRepoPath); } else { log.LogDebug($"{subRepoPath} has default worktree, leaving unchanged"); } } using (LibGit2Sharp.Repository subRepo = new LibGit2Sharp.Repository(subRepoPath)) { log.LogDebug($"Resetting {sub.Name} to {sub.HeadCommitId.Sha}"); subRepo.Reset(LibGit2Sharp.ResetMode.Hard, subRepo.Commits.QueryBy(new LibGit2Sharp.CommitFilter { IncludeReachableFrom = subRepo.Refs }).Single(c => c.Sha == sub.HeadCommitId.Sha)); // Now we reset the worktree back so that when we can initialize a Repository // from it, instead of having to figure out which hash of the repo was most recently checked out. if (oldWorkTree != null) { log.LogDebug($"resetting {subRepoPath} worktree to {oldWorkTree.Value}"); subRepo.Config.Set("core.worktree", oldWorkTree.Value); } else { log.LogDebug($"leaving {subRepoPath} worktree as default"); } log.LogDebug($"Done checking out {subRepoPath}, checking submodules"); CheckoutSubmodules(subRepo, submoduleCloneOptions, absoluteGitDirPath, log); } if (File.Exists(subRepoGitFilePath)) { log.LogDebug($"Deleting {subRepoGitFilePath} to orphan submodule {sub.Name}"); File.Delete(subRepoGitFilePath); } else { log.LogDebug($"{sub.Name} doesn't have a .gitdir redirect at {subRepoGitFilePath}, skipping delete"); } } }
private static LibGit2Sharp.ConfigurationEntry <string> GetGitDiffToolPathEntry(List <LibGit2Sharp.ConfigurationEntry <string> > configurationEntries, LibGit2Sharp.ConfigurationEntry <string> diffToolEntry) { var diffToolCmdKey = string.Format("difftool.{0}.path", diffToolEntry.Value); return(GetMostLocalGitConfigurationEntryByKey(configurationEntries, diffToolCmdKey)); }