public void CanReplaceAnExistingTreeWithAGitLink() { var commitId = (ObjectId)"480095882d281ed676fe5b863569520e54a7d5c0"; const string targetPath = "just_a_dir"; var path = SandboxSubmoduleTestRepo(); using (var repo = new Repository(path)) { TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); Assert.Equal(TreeEntryTargetType.Tree, td[targetPath].TargetType); Assert.NotNull(td["just_a_dir/contents"]); td.AddGitLink(targetPath, commitId); TreeEntryDefinition fetched = td[targetPath]; Assert.NotNull(fetched); Assert.Equal(commitId, fetched.TargetId); Assert.Equal(TreeEntryTargetType.GitLink, fetched.TargetType); Assert.Equal(Mode.GitLink, fetched.Mode); Assert.Null(td["just_a_dir/contents"]); } }
private static void CopyLink(TreeDefinition definition, string name, GitLink link) { definition.AddGitLink(name, link.Id); }
private static IEnumerable <Tuple <ObjectId, ObjectId> > SplitRepository(Repository baseRepo, ICommitLog commits, string name, string path, IEnumerable <SharedRepo> sharedRepos, IEnumerable <MergedRepo> mergedRepos) { var shared = sharedRepos.Where(r => r.Paths.Any(p => p.StartsWith(path + "/"))).Select(r => new { Repo = "../" + r.Name + ".git", Path = r.Paths.First(p => p.StartsWith(path + "/")).Substring(path.Length + 1), Commits = r.Commits }); var merged = mergedRepos.Where(r => r.Name == name); using (var repo = new Repository(Repository.Init(Path.Combine("output", name)))) { File.Copy(Path.Combine(Config.Instance.MainRepo, ".gitignore"), Path.Combine("output", name, ".gitignore")); repo.Index.Add(".gitignore"); File.Copy(Path.Combine(Config.Instance.MainRepo, ".gitattributes"), Path.Combine("output", name, ".gitattributes")); repo.Index.Add(".gitattributes"); foreach (var toMerge in merged) { using (var mergeRepo = new Repository(toMerge.Repo)) { foreach (var c in mergeRepo.Commits.QueryBy(new CommitFilter { SortBy = CommitSortStrategies.Topological | CommitSortStrategies.Reverse })) { bool any = false; foreach (var m in toMerge.Mapping) { if (Merge(repo, c, m.Key, m.Value)) { any = true; } } if (any) { var rewrittenCommit = repo.Commit(c.Message, new Signature(c.Author.Name, Config.Instance.MapEmail(c.Author.Email), c.Author.When), new Signature(c.Committer.Name, Config.Instance.MapEmail(c.Author.Email), c.Author.When), new CommitOptions { AllowEmptyCommit = true }); yield return(new Tuple <ObjectId, ObjectId>(c.Id, rewrittenCommit.Id)); } } } repo.Index.Clear(); repo.Index.Add(".gitignore"); repo.Index.Add(".gitattributes"); } if (shared.Any()) { File.WriteAllText(Path.Combine("output", name, ".gitmodules"), string.Join("", shared.Select(s => $"[submodule \"{s.Path}\"]\n path = {s.Path}\n url = {s.Repo}\n"))); repo.Index.Add(".gitmodules"); } foreach (var c in commits) { if (c.Tree[path] == null) { continue; } TreeDefinition tree = null; foreach (var s in shared) { var mapping = s.Commits.FirstOrDefault(commit => c.Id == commit.Item1); if (mapping != null) { if (tree == null) { var tempCommit = repo.Commit("temp", new Signature("temp", "*****@*****.**", DateTimeOffset.UtcNow), new Signature("temp", "*****@*****.**", DateTimeOffset.UtcNow), new CommitOptions { AllowEmptyCommit = true }); tree = TreeDefinition.From(tempCommit); repo.Reset(ResetMode.Soft, tempCommit.Parents.First()); } tree.AddGitLink(s.Path, mapping.Item2); } } if (tree != null) { repo.Index.Replace(repo.ObjectDatabase.CreateTree(tree)); } var oldTree = c.Parents.FirstOrDefault()?.Tree?[path]?.Target as Tree; if (oldTree != null) { var diff = baseRepo.Diff.Compare <TreeChanges>(oldTree, (Tree)c.Tree[path].Target); if (!diff.Any()) { continue; } foreach (var file in diff) { if (file.Mode == Mode.Directory) { continue; } if (!file.Exists || (file.OldPath != file.Path && file.Status != ChangeKind.Copied)) { if (!shared.Any(s => file.OldPath.Replace('\\', '/').StartsWith(s.Path + '/'))) { File.Delete(Path.Combine("output", name, file.OldPath)); repo.Index.Remove(file.OldPath); } } if (file.Exists) { if (!shared.Any(s => file.Path.Replace('\\', '/').StartsWith(s.Path + '/'))) { Directory.CreateDirectory(Path.GetDirectoryName(Path.Combine("output", name, file.Path))); using (var input = baseRepo.Lookup <Blob>(file.Oid).GetContentStream()) using (var output = File.Create(Path.Combine("output", name, file.Path))) { input.CopyTo(output); } repo.Index.Add(file.Path); } } } } else { foreach (var entry in RecursiveTree((Tree)c.Tree[path].Target, path)) { if (shared.Any(s => entry.Item1.Replace('\\', '/').StartsWith(s.Path + '/'))) { continue; } var fullPath = Path.Combine("output", name, entry.Item1); Directory.CreateDirectory(Path.GetDirectoryName(fullPath)); using (var content = entry.Item2.GetContentStream()) using (var output = File.Create(fullPath)) { content.CopyTo(output); } repo.Index.Add(entry.Item1); } } var email = Config.Instance.MapEmail(c.Author.Email); var rewrittenCommit = repo.Commit(c.Message, new Signature(c.Author.Name, email, c.Author.When), new Signature(c.Committer.Name, email, c.Author.When), new CommitOptions { AllowEmptyCommit = true }); yield return(new Tuple <ObjectId, ObjectId>(c.Id, rewrittenCommit.Id)); } repo.Network.Remotes.Add("origin", Config.Instance.Origin(name)); Console.WriteLine("Copying LFS files..."); var lfsCount = CopyLfsFiles(repo, Path.Combine("output", name, ".git", "lfs", "objects"), new[] { Path.Combine(baseRepo.Info.WorkingDirectory, ".git", "lfs", "objects") }.Concat(merged.Where(r => r.Name == name).Select(r => Path.Combine(r.Repo, ".git", "lfs", "objects")))); Console.WriteLine($"Copied {lfsCount} files."); // LibGit2Sharp doesn't support git gc, so we use the command line: using (var gc = Process.Start(new ProcessStartInfo("git", "gc --aggressive") { WorkingDirectory = repo.Info.WorkingDirectory, UseShellExecute = false })) { gc.WaitForExit(); } } }