diff --git a/GitCommands/DateTimeUtils.cs b/GitCommands/DateTimeUtils.cs index 78706b990b4..16da6c00d13 100644 --- a/GitCommands/DateTimeUtils.cs +++ b/GitCommands/DateTimeUtils.cs @@ -7,11 +7,26 @@ public static class DateTimeUtils /// /// Midnight 1 January 1970. /// - public static readonly DateTime UnixEpoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + private static readonly DateTime UnixEpoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + /// + /// Parse unix time string + /// + /// Unix time string + /// DateTime (local time) public static DateTime ParseUnixTime(string unixTime) { return UnixEpoch.AddSeconds(long.Parse(unixTime)).ToLocalTime(); } + + /// + /// Convert from DateTime to native Git time format (unix time) + /// + /// DateTime + /// Unix time (seconds since 1970) + public static long ToUnixTime(DateTime dateTime) + { + return (long)(dateTime.ToUniversalTime() - UnixEpoch).TotalSeconds; + } } } diff --git a/GitCommands/Git/GitModule.cs b/GitCommands/Git/GitModule.cs index e14251259ae..41d326550b2 100644 --- a/GitCommands/Git/GitModule.cs +++ b/GitCommands/Git/GitModule.cs @@ -938,8 +938,8 @@ public GitRevision GetRevision(ObjectId? objectId = null, bool shortFormat = fal AuthorEmail = ReEncodeStringFromLossless(lines[4]), Committer = ReEncodeStringFromLossless(lines[6]), CommitterEmail = ReEncodeStringFromLossless(lines[7]), - AuthorDate = DateTimeUtils.ParseUnixTime(lines[5]), - CommitDate = DateTimeUtils.ParseUnixTime(lines[8]), + AuthorUnixTime = long.Parse(lines[5]), + CommitUnixTime = long.Parse(lines[8]), MessageEncoding = lines[9] }; diff --git a/GitCommands/RevisionReader.cs b/GitCommands/RevisionReader.cs index ede2a392f3d..793ebcd2902 100644 --- a/GitCommands/RevisionReader.cs +++ b/GitCommands/RevisionReader.cs @@ -117,6 +117,7 @@ private async Task ExecuteAsync( // This property is relatively expensive to call for every revision, so // cache it for the duration of the loop. var logOutputEncoding = module.LogOutputEncoding; + long sixMonths = new DateTimeOffset(DateTime.Now.ToUniversalTime() - TimeSpan.FromDays(30 * 6)).ToUnixTimeSeconds(); using (var process = module.GitCommandRunner.RunDetached(arguments, redirectOutput: true, outputEncoding: GitModule.LosslessEncoding)) { @@ -141,7 +142,7 @@ private async Task ExecuteAsync( // We keep full multiline message bodies within the last six months. // Commits earlier than that have their properties set to null and the // memory will be GCd. - if (DateTime.Now - revision.AuthorDate > TimeSpan.FromDays(30 * 6)) + if (sixMonths > revision.AuthorUnixTime) { revision.Body = null; } @@ -340,10 +341,10 @@ int CountParents(int baseOffset) #region Timestamps // Lines 2 and 3 are timestamps, as decimal ASCII seconds since the unix epoch, each terminated by `\n` - var authorDate = ParseUnixDateTime(); - var commitDate = ParseUnixDateTime(); + var authorUnixTime = ParseUnixDateTime(); + var commitUnixTime = ParseUnixDateTime(); - DateTime ParseUnixDateTime() + long ParseUnixDateTime() { long unixTime = 0; @@ -353,7 +354,7 @@ DateTime ParseUnixDateTime() if (c == '\n') { - return DateTimeUtils.UnixEpoch.AddTicks(unixTime * TimeSpan.TicksPerSecond).ToLocalTime(); + return unixTime; } unixTime = (unixTime * 10) + (c - '0'); @@ -435,10 +436,10 @@ DateTime ParseUnixDateTime() TreeGuid = treeId, Author = author, AuthorEmail = authorEmail, - AuthorDate = authorDate, + AuthorUnixTime = authorUnixTime, Committer = committer, CommitterEmail = committerEmail, - CommitDate = commitDate, + CommitUnixTime = commitUnixTime, MessageEncoding = encodingName, Subject = subject, Body = body, diff --git a/GitUI/UserControls/RevisionGrid/RevisionGridControl.cs b/GitUI/UserControls/RevisionGrid/RevisionGridControl.cs index bc45082933e..e8726edda9b 100644 --- a/GitUI/UserControls/RevisionGrid/RevisionGridControl.cs +++ b/GitUI/UserControls/RevisionGrid/RevisionGridControl.cs @@ -1046,10 +1046,10 @@ void AddArtificialRevisions(ObjectId filteredCurrentCheckout) GitRevision workTreeRev = new(ObjectId.WorkTreeId) { Author = userName, - AuthorDate = DateTime.MaxValue, + AuthorUnixTime = 0, AuthorEmail = userEmail, Committer = userName, - CommitDate = DateTime.MaxValue, + CommitUnixTime = 0, CommitterEmail = userEmail, Subject = ResourceManager.TranslatedStrings.Workspace, ParentIds = new[] { ObjectId.IndexId }, @@ -1061,10 +1061,10 @@ void AddArtificialRevisions(ObjectId filteredCurrentCheckout) GitRevision indexRev = new(ObjectId.IndexId) { Author = userName, - AuthorDate = DateTime.MaxValue, + AuthorUnixTime = 0, AuthorEmail = userEmail, Committer = userName, - CommitDate = DateTime.MaxValue, + CommitUnixTime = 0, CommitterEmail = userEmail, Subject = ResourceManager.TranslatedStrings.Index, ParentIds = new[] { filteredCurrentCheckout }, diff --git a/IntegrationTests/UI.IntegrationTests/UserControls/RevisionGrid/CopyContextMenuItemTests.cs b/IntegrationTests/UI.IntegrationTests/UserControls/RevisionGrid/CopyContextMenuItemTests.cs index 4f76c15bd4b..781cc91fefd 100644 --- a/IntegrationTests/UI.IntegrationTests/UserControls/RevisionGrid/CopyContextMenuItemTests.cs +++ b/IntegrationTests/UI.IntegrationTests/UserControls/RevisionGrid/CopyContextMenuItemTests.cs @@ -159,7 +159,7 @@ public void Should_should_show_info_for_multiple_commits() { Author = "Author1", AuthorEmail = "author1@foo.bla", - AuthorDate = new DateTime(2018, 10, 23, 11, 34, 21), + AuthorUnixTime = DateTimeUtils.ToUnixTime(new DateTime(2018, 10, 23, 11, 34, 21)), }; GitRevision rev2 = new(ObjectId.Random()) { @@ -167,7 +167,7 @@ public void Should_should_show_info_for_multiple_commits() AuthorEmail = "author2@foo.bla", Committer = "Committer2", CommitterEmail = "committer2@foo.bar", - CommitDate = new DateTime(2018, 10, 23, 11, 34, 21), + CommitUnixTime = DateTimeUtils.ToUnixTime(new DateTime(2018, 10, 23, 11, 34, 21)), }; GitRevision rev3 = new(ObjectId.Random()) { diff --git a/Plugins/GitUIPluginInterfaces/GitRevision.cs b/Plugins/GitUIPluginInterfaces/GitRevision.cs index 870caea5022..7fa84cb6e80 100644 --- a/Plugins/GitUIPluginInterfaces/GitRevision.cs +++ b/Plugins/GitUIPluginInterfaces/GitRevision.cs @@ -51,10 +51,17 @@ public GitRevision(ObjectId objectId) public string? Author { get; set; } public string? AuthorEmail { get; set; } - public DateTime AuthorDate { get; set; } + + // Git native datetime format + public long AuthorUnixTime { get; set; } + public DateTime AuthorDate => FromUnixTimeSeconds(AuthorUnixTime); public string? Committer { get; set; } public string? CommitterEmail { get; set; } - public DateTime CommitDate { get; set; } + public long CommitUnixTime { get; set; } + public DateTime CommitDate => FromUnixTimeSeconds(CommitUnixTime); + + private static DateTime FromUnixTimeSeconds(long unixTime) + => unixTime == 0 ? DateTime.MaxValue : DateTimeOffset.FromUnixTimeSeconds(unixTime).LocalDateTime; public BuildInfo? BuildStatus { diff --git a/UnitTests/GitUI.Tests/UserControls/RevisionGrid/Graph/LaneInfoProviderTests.cs b/UnitTests/GitUI.Tests/UserControls/RevisionGrid/Graph/LaneInfoProviderTests.cs index 66bffcdae90..04016c40553 100644 --- a/UnitTests/GitUI.Tests/UserControls/RevisionGrid/Graph/LaneInfoProviderTests.cs +++ b/UnitTests/GitUI.Tests/UserControls/RevisionGrid/Graph/LaneInfoProviderTests.cs @@ -101,7 +101,7 @@ public void Setup() GitRevision = new GitRevision(ObjectId.WorkTreeId) { Author = "John Doe", - AuthorDate = DateTime.Parse("2010-03-24 13:37:12"), + AuthorUnixTime = DateTimeUtils.ToUnixTime(DateTime.Parse("2010-03-24 13:37:12")), AuthorEmail = "j.doe@some.email.dotcom", Body = "WIP: fixing bugs" } @@ -112,7 +112,7 @@ public void Setup() GitRevision = new GitRevision(realCommitObjectId) { Author = "John Doe", - AuthorDate = DateTime.Parse("2010-03-24 13:37:12"), + AuthorUnixTime = DateTimeUtils.ToUnixTime(DateTime.Parse("2010-03-24 13:37:12")), AuthorEmail = "j.doe@some.email.dotcom", Subject = "fix: bugs", Body = "fix: bugs\r\n\r\nall bugs fixed" @@ -124,7 +124,7 @@ public void Setup() GitRevision = new GitRevision(mergeCommitObjectId) { Author = "John Doe", - AuthorDate = DateTime.Parse("2010-03-24 13:37:12"), + AuthorUnixTime = DateTimeUtils.ToUnixTime(DateTime.Parse("2010-03-24 13:37:12")), AuthorEmail = "j.doe@some.email.dotcom", Subject = "merge remote tracking branch upstream/branch", Body = "merge commit's subject here will not be parsed\r\n\r\nmerge commit's body might list details and/or conflicts...", @@ -137,7 +137,7 @@ public void Setup() GitRevision = new GitRevision(undetectedMergeCommitObjectId) { Author = "John Doe", - AuthorDate = DateTime.Parse("2010-03-24 13:37:12"), + AuthorUnixTime = DateTimeUtils.ToUnixTime(DateTime.Parse("2010-03-24 13:37:12")), AuthorEmail = "j.doe@some.email.dotcom", Subject = "special merge", Body = "merge commit's subject here will not be parsed\r\n\r\nmerge commit's body might list details and/or conflicts...", @@ -150,7 +150,7 @@ public void Setup() GitRevision = new GitRevision(innerCommitObjectId) { Author = "John Doe", - AuthorDate = DateTime.Parse("2010-03-24 13:37:12"), + AuthorUnixTime = DateTimeUtils.ToUnixTime(DateTime.Parse("2010-03-24 13:37:12")), AuthorEmail = "j.doe@some.email.dotcom", Subject = "fix: further bugs", Body = "fix: further bugs"