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");
                    }
                }
            }
        }
Example #2
0
        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));
        }