#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously public override async IAsyncEnumerable <GitReference> GetAll(HashSet <string> alreadyReturned) #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { string baseDir = Path.GetFullPath(GitDir); foreach (string file in Directory.EnumerateFiles(Path.Combine(baseDir, "refs"), "*", SearchOption.AllDirectories)) { if (file.Length > baseDir.Length + 1 && file[baseDir.Length] == Path.DirectorySeparatorChar) { string name = file.Substring(baseDir.Length + 1).Replace(Path.DirectorySeparatorChar, '/'); yield return(new GitReference(this, name, (GitId?)null)); } } foreach (string file in Directory.EnumerateFiles(GitDir)) { string name = Path.GetFileName(file); if (GitReference.AllUpper(name) && !alreadyReturned.Contains(name)) { yield return(new GitSymbolicReference(this, file.Substring(GitDir.Length + 1))); } } }
private async ValueTask CommitUpdateFileReferences() { foreach (var v in Updates.Where(x => x.Type == UpdateType.Verify)) { var r = await _referenceRepository.GetAsync(v.Name).ConfigureAwait(false); if (r is null || r.Id != v.Id) { throw new GitException($"Reference {v.Name} is not {v.Id}, but {r?.Id ?? Zero}"); } } Action?unlock = null; string?hookData = null; bool? logRefUpdates = null; try { foreach (var v in Updates.Select(x => x.Name).Distinct()) { var name = v; if (GitReference.AllUpper(name)) { var rf = await _referenceRepository.GetAsync(v).ConfigureAwait(false); if (rf is GitSymbolicReference sr) { rf = await sr.ResolveAsync().ConfigureAwait(false); name = (rf as GitSymbolicReference)?.ReferenceName ?? rf.Name ?? name; } } string path = Path.Combine(Repository.GitDirectory, name); Directory.CreateDirectory(Path.GetDirectoryName(path) !); string p = path + ".lock"; #pragma warning disable CA2000 // Dispose objects before losing scope var f = new FileStream(path + ".lock", FileMode.CreateNew); #pragma warning restore CA2000 // Dispose objects before losing scope unlock += () => { f.Close(); File.Delete(p); }; } bool allowContinue = true; if (!string.IsNullOrEmpty(GitConfiguration.GitProgramPath) && await Repository.Configuration.HookExistsAsync("reference-transaction").ConfigureAwait(false)) { StringBuilder sb = new StringBuilder(); foreach (var v in Updates) { GitReference?rf; // We might record 'HEAD' when we really update something like 'refs/heads/main' // This might need fixing when things are fixed in git itself switch (v.Type) { case UpdateType.Create: sb.Append(Zero); sb.Append(' '); sb.Append(v.Id); sb.Append(' '); sb.Append(v.Name); sb.Append('\n'); break; case UpdateType.Update: rf = await _referenceRepository.GetAsync(v.Name).ConfigureAwait(false); sb.Append(rf?.Id ?? Zero); sb.Append(' '); sb.Append(v.Id); sb.Append(' '); sb.Append(v.Name); sb.Append('\n'); break; case UpdateType.Delete: rf = await _referenceRepository.GetAsync(v.Name).ConfigureAwait(false); if (rf?.Id != null) { sb.Append(rf.Id); sb.Append(' '); sb.Append(Zero); sb.Append(' '); sb.Append(v.Name); sb.Append('\n'); } break; } } if (sb.Length > 0) { hookData = sb.ToString(); } if (hookData is not null) { var r = await Repository.RunHookErrAsync("reference-transaction", new[] { "prepared" }, stdinText : hookData, expectedResults : Array.Empty <int>()).ConfigureAwait(false); if (r.ExitCode != 0) { throw new GitException($"Git reference-transaction denied update: {r.OutputText} ({r.ErrorText})"); } } } if (allowContinue) { var signature = Repository.Configuration.Identity.AsRecord(); foreach (var v in Updates) { GitId? originalId = null; GitReference?rf = null; switch (v.Type) { case UpdateType.Create: using (var fs = new FileStream(Path.Combine(Repository.GitDirectory, v.TargetName), FileMode.CreateNew)) using (var sw = new StreamWriter(fs)) { await sw.WriteLineAsync(v.Id !.ToString()).ConfigureAwait(false); } break; case UpdateType.Update: rf = await _referenceRepository.GetAsync(v.Name).ConfigureAwait(false); if (rf is GitSymbolicReference sr) { rf = await sr.ResolveAsync().ConfigureAwait(false); v.TargetName = (rf as GitSymbolicReference)?.ReferenceName ?? rf.Name ?? v.Name; } originalId = rf?.Id; using (var fs = new FileStream(Path.Combine(Repository.GitDirectory, v.TargetName), FileMode.Create)) using (var sw = new StreamWriter(fs)) { fs.SetLength(0); await sw.WriteLineAsync(v.Id !.ToString()).ConfigureAwait(false); } break; case UpdateType.Delete: rf = await _referenceRepository.GetAsync(v.Name).ConfigureAwait(false); if (rf is GitSymbolicReference sr2) { rf = await sr2.ResolveAsync().ConfigureAwait(false); v.TargetName = rf.Name ?? v.Name; } originalId = rf?.Id; File.Delete(Path.Combine(Repository.GitDirectory, v.TargetName)); // If failed here, we need to cleanup packed references!! break; default: continue; } logRefUpdates ??= await Repository.Configuration.GetBoolAsync("core", "logallrefupdates").ConfigureAwait(false) ?? false; if (logRefUpdates == true && (GitReference.AllUpper(v.Name) || v.Name.StartsWith("refs/heads/", StringComparison.OrdinalIgnoreCase) || v.Name.StartsWith("refs/remotes/", StringComparison.OrdinalIgnoreCase) || v.Name.StartsWith("refs/notes/", StringComparison.OrdinalIgnoreCase))) { var log = new GitReferenceLogRecord { Original = originalId ?? Zero, Target = v.Id ?? Zero, Signature = signature, Reason = Reason }; await AppendLog(v.Name, log).ConfigureAwait(false); if (rf is not null && rf.Name != v.Name) { await AppendLog(rf.Name !, log).ConfigureAwait(false); } } } if (hookData is not null) { var hd = hookData; hookData = null; // Ignore errors await Repository.RunHookErrAsync("run", new[] { "committed" }, stdinText : hd, expectedResults : Array.Empty <int>()).ConfigureAwait(false); } } } catch when(hookData is not null) { // Ignore errors await Repository.RunHookErrAsync("run", new[] { "abort" }, stdinText : hookData, expectedResults : Array.Empty <int>()).ConfigureAwait(false); throw; } finally { unlock?.Invoke(); } }