diff --git a/app/src/lib/components/SyncButton.svelte b/app/src/lib/components/SyncButton.svelte index 9806943d9..94e9bbb1f 100644 --- a/app/src/lib/components/SyncButton.svelte +++ b/app/src/lib/components/SyncButton.svelte @@ -31,8 +31,8 @@ > {#if $baseServiceBusy$}
busy…
- {:else if $baseBranch?.lastFetched} - + {:else if $baseBranch?.lastFetchedAt} + {/if} diff --git a/app/src/lib/history/types.ts b/app/src/lib/history/types.ts index 60ced1f4a..1255dfa24 100644 --- a/app/src/lib/history/types.ts +++ b/app/src/lib/history/types.ts @@ -1,4 +1,4 @@ -import { RemoteHunk } from '$lib/vbranches/types'; +import { RemoteHunk, dateFromDuration } from '$lib/vbranches/types'; import { Transform, Type } from 'class-transformer'; export type Operation = @@ -61,6 +61,6 @@ export class Snapshot { linesRemoved!: number; filesChanged!: string[]; details?: SnapshotDetails; - @Transform((obj) => new Date(obj.value * 1000)) + @Transform(dateFromDuration) createdAt!: Date; } diff --git a/app/src/lib/testing/fixtures.ts b/app/src/lib/testing/fixtures.ts index 7473c8e43..7c84b739e 100644 --- a/app/src/lib/testing/fixtures.ts +++ b/app/src/lib/testing/fixtures.ts @@ -60,7 +60,7 @@ export const baseBranch = { behind: 0, upstreamCommits: [], recentCommits: [remoteCommit0], - lastFetchedMs: 1714843209991 + lastFetchedAt: new Date(1714843209991) }; export const user: User = { diff --git a/app/src/lib/vbranches/types.ts b/app/src/lib/vbranches/types.ts index 43385685f..f84308e1e 100644 --- a/app/src/lib/vbranches/types.ts +++ b/app/src/lib/vbranches/types.ts @@ -3,7 +3,28 @@ import { splitMessage } from '$lib/utils/commitMessage'; import { hashCode } from '$lib/utils/string'; import { isDefined, notNull } from '$lib/utils/typeguards'; import { convertRemoteToWebUrl } from '$lib/utils/url'; -import { Type, Transform } from 'class-transformer'; +import { Type, Transform, type TransformFnParams } from 'class-transformer'; + +interface Duration { + secs: number; + nanos: number; +} + +function isDuration(object: any): object is Duration { + const secsIsNumber = typeof object.secs === 'number'; + const nanosIsNumber = typeof object.nanos === 'number'; + + return secsIsNumber && nanosIsNumber; +} + +export function dateFromDuration(params: TransformFnParams): Date | undefined { + if (!params.value) return; + + if (!isDuration(params.value)) { + throw Error('Expected a Duration object'); + } + return new Date(params.value.secs * 1000 + params.value.nanos / 1000000); +} export type ChangeType = /// Entry does not exist in old version @@ -16,9 +37,7 @@ export type ChangeType = export class Hunk { id!: string; diff!: string; - @Transform((obj) => { - return new Date(obj.value); - }) + @Transform(dateFromDuration) modifiedAt!: Date; filePath!: string; hash?: string; @@ -43,7 +62,7 @@ export class LocalFile { @Type(() => Hunk) hunks!: Hunk[]; expanded?: boolean; - @Transform((obj) => new Date(obj.value)) + @Transform(dateFromDuration) modifiedAt!: Date; // This indicates if a file has merge conflict markers generated and not yet resolved. // This is true for files after a branch which does not apply cleanly (Branch.isMergeable == false) is applied. @@ -121,7 +140,7 @@ export class Branch { // If the branch has been already applied, then it was either performed cleanly or we generated conflict markers in the diffs. // (therefore this field is applicable for stashed/unapplied or remote branches, i.e. active == false) isMergeable!: Promise; - @Transform((obj) => new Date(obj.value)) + @Transform(dateFromDuration) updatedAt!: Date; // Indicates that branch is default target for new changes selectedForChanges!: boolean; @@ -152,7 +171,7 @@ export class Commit { id!: string; author!: Author; description!: string; - @Transform((obj) => new Date(obj.value)) + @Transform(dateFromDuration) createdAt!: Date; isRemote!: boolean; isIntegrated!: boolean; @@ -197,7 +216,7 @@ export class RemoteCommit { id!: string; author!: Author; description!: string; - @Transform((obj) => new Date(obj.value * 1000)) + @Transform(dateFromDuration) createdAt!: Date; changeId!: string; isSigned!: boolean; @@ -361,16 +380,13 @@ export class BaseBranch { upstreamCommits!: RemoteCommit[]; @Type(() => RemoteCommit) recentCommits!: RemoteCommit[]; - lastFetchedMs?: number; + @Transform(dateFromDuration) + lastFetchedAt?: Date; actualPushRemoteName(): string { return this.pushRemoteName || this.remoteName; } - get lastFetched(): Date | undefined { - return this.lastFetchedMs ? new Date(this.lastFetchedMs) : undefined; - } - get pushRepoBaseUrl(): string { return convertRemoteToWebUrl(this.pushRemoteUrl); } diff --git a/crates/gitbutler-cli/src/main.rs b/crates/gitbutler-cli/src/main.rs index 71111b5e2..35b133b7d 100644 --- a/crates/gitbutler-cli/src/main.rs +++ b/crates/gitbutler-cli/src/main.rs @@ -51,7 +51,8 @@ fn list_snapshots(repo_dir: &str) -> Result<()> { let project = project_from_path(repo_dir); let snapshots = project.list_snapshots(100, None)?; for snapshot in snapshots { - let ts = chrono::DateTime::from_timestamp(snapshot.created_at / 1000, 0); + // Using chrono for formatting the timestamp + let ts = chrono::DateTime::from_timestamp(snapshot.created_at.as_secs() as i64, 0); let details = snapshot.details; if let (Some(ts), Some(details)) = (ts, details) { println!("{} {} {}", ts, snapshot.id, details.operation); diff --git a/crates/gitbutler-core/src/git/commit.rs b/crates/gitbutler-core/src/git/commit.rs index 665d468ce..b3c8c7d51 100644 --- a/crates/gitbutler-core/src/git/commit.rs +++ b/crates/gitbutler-core/src/git/commit.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use super::{Oid, Result, Signature, Tree}; use bstr::BStr; @@ -54,8 +56,8 @@ impl<'repo> Commit<'repo> { self.commit.parent(n).map(Into::into).map_err(Into::into) } - pub fn time(&self) -> git2::Time { - self.commit.time() + pub fn time(&self) -> Duration { + Duration::from_secs(self.commit.time().seconds().try_into().unwrap()) } pub fn author(&self) -> Signature<'_> { diff --git a/crates/gitbutler-core/src/ops/entry.rs b/crates/gitbutler-core/src/ops/entry.rs index 9d43d6e74..2808a964e 100644 --- a/crates/gitbutler-core/src/ops/entry.rs +++ b/crates/gitbutler-core/src/ops/entry.rs @@ -7,6 +7,7 @@ use std::fmt::Display; use std::fmt::Formatter; use std::path::PathBuf; use std::str::FromStr; +use std::time::Duration; use strum::EnumString; use serde::Serialize; @@ -19,7 +20,7 @@ pub struct Snapshot { /// The sha of the commit that represents the snapshot pub id: String, /// Snapshot creation time in epoch milliseconds - pub created_at: i64, + pub created_at: Duration, /// The number of working directory lines added in the snapshot pub lines_added: usize, /// The number of working directory lines removed in the snapshot @@ -304,7 +305,7 @@ mod tests { let commit_sha = "1234567890".to_string(); let commit_message = "Create a new snapshot\n\nBody text 1\nBody text2\n\nBody text 3\n\nVersion: 1\nOperation: CreateCommit\nFoo: Bar\n".to_string(); - let created_at = 1234567890; + let created_at = Duration::from_secs(1234567890); let details = SnapshotDetails::from_str(&commit_message.clone()).unwrap(); let snapshot = Snapshot { id: commit_sha.clone(), diff --git a/crates/gitbutler-core/src/ops/oplog.rs b/crates/gitbutler-core/src/ops/oplog.rs index 9e4442a65..a3dbd2486 100644 --- a/crates/gitbutler-core/src/ops/oplog.rs +++ b/crates/gitbutler-core/src/ops/oplog.rs @@ -3,6 +3,7 @@ use git2::FileMode; use itertools::Itertools; use std::collections::HashMap; use std::str::FromStr; +use std::time::Duration; use std::{fs, path::PathBuf}; use anyhow::Result; @@ -349,7 +350,7 @@ impl Oplog for Project { lines_added, lines_removed, files_changed, - created_at: commit.time().seconds(), + created_at: Duration::from_secs(commit.time().seconds().try_into().unwrap()), }); if snapshots.len() >= limit { @@ -363,7 +364,7 @@ impl Oplog for Project { lines_added: 0, lines_removed: 0, files_changed: Vec::new(), // Fix: Change 0 to an empty vector - created_at: commit.time().seconds(), + created_at: Duration::from_secs(commit.time().seconds().try_into().unwrap()), }); break; } diff --git a/crates/gitbutler-core/src/project_repository/repository.rs b/crates/gitbutler-core/src/project_repository/repository.rs index d2f5d96b4..55f1bcd1f 100644 --- a/crates/gitbutler-core/src/project_repository/repository.rs +++ b/crates/gitbutler-core/src/project_repository/repository.rs @@ -7,7 +7,6 @@ use std::{ use anyhow::{Context, Result}; use super::conflicts; -use crate::error::{AnyhowContextExt, Code, ErrorWithContext}; use crate::{ askpass, error, git::{self, credentials::HelpError, Url}, @@ -15,6 +14,10 @@ use crate::{ ssh, users, virtual_branches::{Branch, BranchId}, }; +use crate::{ + error::{AnyhowContextExt, Code, ErrorWithContext}, + time, +}; pub struct Repository { pub git_repository: git::Repository, @@ -175,7 +178,7 @@ impl Repository { let branch = self.git_repository.find_branch(&target_branch_refname)?; let commit_id = branch.peel_to_commit()?.id(); - let now = crate::time::now_ms(); + let now = time::now_ms(); let branch_name = format!("test-push-{now}"); let refname = diff --git a/crates/gitbutler-core/src/projects/project.rs b/crates/gitbutler-core/src/projects/project.rs index e59e70a64..22576c329 100644 --- a/crates/gitbutler-core/src/projects/project.rs +++ b/crates/gitbutler-core/src/projects/project.rs @@ -1,6 +1,6 @@ use std::{ path::{self, PathBuf}, - time, + time::SystemTime, }; use serde::{Deserialize, Serialize}; @@ -38,16 +38,16 @@ pub struct ApiProject { #[serde(rename_all = "camelCase")] pub enum FetchResult { Fetched { - timestamp: time::SystemTime, + timestamp: SystemTime, }, Error { - timestamp: time::SystemTime, + timestamp: SystemTime, error: String, }, } impl FetchResult { - pub fn timestamp(&self) -> &time::SystemTime { + pub fn timestamp(&self) -> &SystemTime { match self { FetchResult::Fetched { timestamp } | FetchResult::Error { timestamp, .. } => timestamp, } @@ -57,7 +57,7 @@ impl FetchResult { #[derive(Debug, Deserialize, Serialize, Copy, Clone)] pub struct CodePushState { pub id: git::Oid, - pub timestamp: time::SystemTime, + pub timestamp: SystemTime, } pub type ProjectId = Id; diff --git a/crates/gitbutler-core/src/time.rs b/crates/gitbutler-core/src/time.rs index 50d9c28e7..35e6f11cb 100644 --- a/crates/gitbutler-core/src/time.rs +++ b/crates/gitbutler-core/src/time.rs @@ -1,12 +1,43 @@ -use std::time::UNIX_EPOCH; +use std::time::{Duration, UNIX_EPOCH}; + +/// Gets the duration of time since the Unix epoch. +/// +/// # Panics +/// Panics if the system time is set before the Unix epoch. +pub fn now() -> Duration { + UNIX_EPOCH + .elapsed() + .expect("system time is set before the Unix epoch") +} /// Gets the number of milliseconds since the Unix epoch. /// /// # Panics /// Panics if the system time is set before the Unix epoch. pub fn now_ms() -> u128 { - UNIX_EPOCH - .elapsed() - .expect("system time is set before the Unix epoch") - .as_millis() + now().as_millis() +} + +pub mod duration_serializer { + use std::time::Duration; + + use serde::{Deserialize, Deserializer, Serializer}; + + pub fn serialize(duration: &Duration, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&duration.as_millis().to_string()) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let millis_str = String::deserialize(deserializer)?; + let millis = millis_str + .parse::() + .map_err(serde::de::Error::custom)?; + Ok(Duration::from_millis(millis)) + } } diff --git a/crates/gitbutler-core/src/virtual_branches/base.rs b/crates/gitbutler-core/src/virtual_branches/base.rs index d43157895..22099f50b 100644 --- a/crates/gitbutler-core/src/virtual_branches/base.rs +++ b/crates/gitbutler-core/src/virtual_branches/base.rs @@ -1,4 +1,7 @@ -use std::{path::Path, time}; +use std::{ + path::Path, + time::{self, Duration}, +}; use anyhow::{Context, Result}; use serde::Serialize; @@ -31,7 +34,7 @@ pub struct BaseBranch { pub behind: usize, pub upstream_commits: Vec, pub recent_commits: Vec, - pub last_fetched_ms: Option, + pub last_fetched_at: Option, } pub fn get_base_branch_data( @@ -210,7 +213,7 @@ pub fn set_base_branch( }, ); - let now_ms = crate::time::now_ms(); + let now = crate::time::now(); let (upstream, upstream_head) = if let git::Refname::Local(head_name) = &head_name { let upstream_name = target_branch_ref.with_branch(head_name.branch()); @@ -244,8 +247,8 @@ pub fn set_base_branch( applied: true, upstream, upstream_head, - created_timestamp_ms: now_ms, - updated_timestamp_ms: now_ms, + created_at: now, + updated_at: now, head: current_head_commit.id(), tree: super::write_tree_onto_commit( project_repository, @@ -646,13 +649,13 @@ pub fn target_to_base_branch( behind: upstream_commits.len(), upstream_commits, recent_commits, - last_fetched_ms: project_repository + last_fetched_at: project_repository .project() .project_data_last_fetch .as_ref() .map(FetchResult::timestamp) .copied() - .map(|t| t.duration_since(time::UNIX_EPOCH).unwrap().as_millis()), + .map(|t| t.duration_since(time::UNIX_EPOCH).unwrap()), }; Ok(base) } diff --git a/crates/gitbutler-core/src/virtual_branches/branch/hunk.rs b/crates/gitbutler-core/src/virtual_branches/branch/hunk.rs index 0379698b4..59b0f0062 100644 --- a/crates/gitbutler-core/src/virtual_branches/branch/hunk.rs +++ b/crates/gitbutler-core/src/virtual_branches/branch/hunk.rs @@ -2,6 +2,7 @@ use std::{fmt::Display, ops::RangeInclusive, str::FromStr}; use anyhow::{anyhow, Context, Result}; use bstr::ByteSlice; +use std::time::Duration; use crate::git::diff; @@ -10,7 +11,7 @@ pub type HunkHash = md5::Digest; #[derive(Debug, Eq, Clone)] pub struct Hunk { pub hash: Option, - pub timestamp_ms: Option, + pub timestamp: Option, pub start: u32, pub end: u32, pub locked_to: Vec, @@ -22,7 +23,7 @@ impl From<&diff::GitHunk> for Hunk { start: hunk.new_start, end: hunk.new_start + hunk.new_lines, hash: Some(Hunk::hash_diff(&hunk.diff_lines)), - timestamp_ms: None, + timestamp: None, locked_to: hunk.locked_to.to_vec(), } } @@ -44,7 +45,7 @@ impl From> for Hunk { start: *range.start(), end: *range.end(), hash: None, - timestamp_ms: None, + timestamp: None, locked_to: vec![], } } @@ -83,27 +84,28 @@ impl FromStr for Hunk { None }; - let timestamp_ms = if let Some(raw_timestamp_ms) = range.next() { - Some( - raw_timestamp_ms - .parse::() - .context(format!("failed to parse timestamp_ms of range: {}", s))?, - ) + let timestamp = if let Some(raw_timestamp_ms) = range.next() { + let parsed_millis = raw_timestamp_ms + .parse::() + .context(format!("failed to parse timestamp_ms of range: {}", s))?; + Some(Duration::from_millis(parsed_millis)) } else { None }; - Hunk::new(start, end, hash, timestamp_ms) + Hunk::new(start, end, hash, timestamp) } } impl Display for Hunk { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}-{}", self.start, self.end)?; - match (&self.hash, &self.timestamp_ms) { - (Some(hash), Some(timestamp_ms)) => write!(f, "-{:x}-{}", hash, timestamp_ms), + match (&self.hash, &self.timestamp) { + (Some(hash), Some(timestamp)) => { + write!(f, "-{:x}-{}", hash, timestamp.as_millis()) + } (Some(hash), None) => write!(f, "-{:x}", hash), - (None, Some(timestamp_ms)) => write!(f, "--{}", timestamp_ms), + (None, Some(timestamp)) => write!(f, "--{}", timestamp.as_millis()), (None, None) => Ok(()), } } @@ -114,14 +116,14 @@ impl Hunk { start: u32, end: u32, hash: Option, - timestamp_ms: Option, + timestamp: Option, ) -> Result { if start > end { Err(anyhow!("invalid range: {}-{}", start, end)) } else { Ok(Hunk { hash, - timestamp_ms, + timestamp, start, end, locked_to: vec![], @@ -134,13 +136,13 @@ impl Hunk { self } - pub fn with_timestamp(mut self, timestamp_ms: u128) -> Self { - self.timestamp_ms = Some(timestamp_ms); + pub fn with_timestamp(mut self, timestamp: Duration) -> Self { + self.timestamp = Some(timestamp); self } - pub fn timestamp_ms(&self) -> Option { - self.timestamp_ms + pub fn timestamp(&self) -> Option { + self.timestamp } pub fn contains(&self, line: u32) -> bool { diff --git a/crates/gitbutler-core/src/virtual_branches/branch/mod.rs b/crates/gitbutler-core/src/virtual_branches/branch/mod.rs index f01badac4..43657e669 100644 --- a/crates/gitbutler-core/src/virtual_branches/branch/mod.rs +++ b/crates/gitbutler-core/src/virtual_branches/branch/mod.rs @@ -2,12 +2,14 @@ mod file_ownership; mod hunk; mod ownership; -use anyhow::Result; +use std::time::Duration; + pub use file_ownership::OwnershipClaim; pub use hunk::{Hunk, HunkHash}; pub use ownership::{reconcile_claims, BranchOwnershipClaims}; use serde::{Deserialize, Serialize}; +use crate::time::duration_serializer; use crate::{git, id::Id}; pub type BranchId = Id; @@ -25,16 +27,10 @@ pub struct Branch { pub upstream: Option, // upstream_head is the last commit on we've pushed to the upstream branch pub upstream_head: Option, - #[serde( - serialize_with = "serialize_u128", - deserialize_with = "deserialize_u128" - )] - pub created_timestamp_ms: u128, - #[serde( - serialize_with = "serialize_u128", - deserialize_with = "deserialize_u128" - )] - pub updated_timestamp_ms: u128, + #[serde(rename = "created_timestamp_ms", with = "duration_serializer")] + pub created_at: Duration, + #[serde(rename = "updated_timestamp_ms", with = "duration_serializer")] + pub updated_at: Duration, /// tree is the last git tree written to a session, or merge base tree if this is new. use this for delta calculation from the session data pub tree: git::Oid, /// head is id of the last "virtual" commit in this branch @@ -47,22 +43,6 @@ pub struct Branch { pub selected_for_changes: Option, } -fn serialize_u128(x: &u128, s: S) -> Result -where - S: serde::Serializer, -{ - s.serialize_str(&x.to_string()) -} - -fn deserialize_u128<'de, D>(d: D) -> Result -where - D: serde::Deserializer<'de>, -{ - let s = String::deserialize(d)?; - let x: u128 = s.parse().map_err(serde::de::Error::custom)?; - Ok(x) -} - impl Branch { pub fn refname(&self) -> git::VirtualRefname { self.into() @@ -87,144 +67,3 @@ pub struct BranchCreateRequest { pub order: Option, pub selected_for_changes: Option, } - -impl Branch { - pub fn from_reader(reader: &crate::reader::Reader<'_>) -> Result { - let results = reader.batch(&[ - "id", - "meta/name", - "meta/notes", - "meta/applied", - "meta/order", - "meta/upstream", - "meta/upstream_head", - "meta/tree", - "meta/head", - "meta/created_timestamp_ms", - "meta/updated_timestamp_ms", - "meta/ownership", - "meta/selected_for_changes", - ])?; - - let id: String = results[0].clone()?.try_into()?; - let id: BranchId = id.parse().map_err(|e| { - crate::reader::Error::Io( - std::io::Error::new(std::io::ErrorKind::Other, format!("id: {}", e)).into(), - ) - })?; - let name: String = results[1].clone()?.try_into()?; - - let notes: String = match results[2].clone() { - Ok(notes) => Ok(notes.try_into()?), - Err(crate::reader::Error::NotFound) => Ok(String::new()), - Err(e) => Err(e), - }?; - - let applied = match results[3].clone() { - Ok(applied) => applied.try_into(), - _ => Ok(false), - } - .unwrap_or(false); - - let order: usize = match results[4].clone() { - Ok(order) => Ok(order.try_into()?), - Err(crate::reader::Error::NotFound) => Ok(0), - Err(e) => Err(e), - }?; - - let upstream = match results[5].clone() { - Ok(crate::reader::Content::UTF8(upstream)) => { - if upstream.is_empty() { - Ok(None) - } else { - upstream - .parse::() - .map(Some) - .map_err(|e| { - crate::reader::Error::Io( - std::io::Error::new( - std::io::ErrorKind::Other, - format!("meta/upstream: {}", e), - ) - .into(), - ) - }) - } - } - Ok(_) | Err(crate::reader::Error::NotFound) => Ok(None), - Err(e) => Err(e), - }?; - - let upstream_head = match results[6].clone() { - Ok(crate::reader::Content::UTF8(upstream_head)) => { - upstream_head.parse().map(Some).map_err(|e| { - crate::reader::Error::Io( - std::io::Error::new( - std::io::ErrorKind::Other, - format!("meta/upstream_head: {}", e), - ) - .into(), - ) - }) - } - Ok(_) | Err(crate::reader::Error::NotFound) => Ok(None), - Err(e) => Err(e), - }?; - - let tree: String = results[7].clone()?.try_into()?; - let head: String = results[8].clone()?.try_into()?; - let created_timestamp_ms = results[9].clone()?.try_into()?; - let updated_timestamp_ms = results[10].clone()?.try_into()?; - - let ownership_string: String = results[11].clone()?.try_into()?; - let ownership = ownership_string.parse().map_err(|e| { - crate::reader::Error::Io( - std::io::Error::new(std::io::ErrorKind::Other, format!("meta/ownership: {}", e)) - .into(), - ) - })?; - - let selected_for_changes = match results[12].clone() { - Ok(raw_ts) => { - let ts = raw_ts.try_into().map_err(|e| { - crate::reader::Error::Io( - std::io::Error::new( - std::io::ErrorKind::Other, - format!("meta/selected_for_changes: {}", e), - ) - .into(), - ) - })?; - Ok(Some(ts)) - } - Err(crate::reader::Error::NotFound) => Ok(None), - Err(e) => Err(e), - }?; - - Ok(Self { - id, - name, - notes, - applied, - upstream, - upstream_head, - tree: tree.parse().map_err(|e| { - crate::reader::Error::Io( - std::io::Error::new(std::io::ErrorKind::Other, format!("meta/tree: {}", e)) - .into(), - ) - })?, - head: head.parse().map_err(|e| { - crate::reader::Error::Io( - std::io::Error::new(std::io::ErrorKind::Other, format!("meta/head: {}", e)) - .into(), - ) - })?, - created_timestamp_ms, - updated_timestamp_ms, - ownership, - order, - selected_for_changes, - }) - } -} diff --git a/crates/gitbutler-core/src/virtual_branches/controller.rs b/crates/gitbutler-core/src/virtual_branches/controller.rs index 10efcaf83..a59f221d0 100644 --- a/crates/gitbutler-core/src/virtual_branches/controller.rs +++ b/crates/gitbutler-core/src/virtual_branches/controller.rs @@ -6,7 +6,7 @@ use crate::{ snapshot::Snapshot, }, }; -use std::{collections::HashMap, path::Path, sync::Arc}; +use std::{collections::HashMap, path::Path, sync::Arc, time::SystemTime}; use anyhow::Context; use tokio::{sync::Semaphore, task::JoinHandle}; @@ -930,10 +930,10 @@ impl ControllerInner { .map_err(errors::FetchFromTargetError::Remote) { Ok(()) => projects::FetchResult::Fetched { - timestamp: std::time::SystemTime::now(), + timestamp: SystemTime::now(), }, Err(error) => projects::FetchResult::Error { - timestamp: std::time::SystemTime::now(), + timestamp: SystemTime::now(), error: error.to_string(), }, }; diff --git a/crates/gitbutler-core/src/virtual_branches/remote.rs b/crates/gitbutler-core/src/virtual_branches/remote.rs index fb4dede60..ee7914047 100644 --- a/crates/gitbutler-core/src/virtual_branches/remote.rs +++ b/crates/gitbutler-core/src/virtual_branches/remote.rs @@ -1,4 +1,4 @@ -use std::path::Path; +use std::{path::Path, time::Duration}; use anyhow::{Context, Result}; use bstr::BString; @@ -25,7 +25,7 @@ pub struct RemoteBranch { pub sha: git::Oid, pub name: git::Refname, pub upstream: Option, - pub last_commit_timestamp_ms: Option, + pub last_commit_at: Option, pub last_commit_author: Option, } @@ -45,7 +45,7 @@ pub struct RemoteCommit { pub id: String, #[serde(serialize_with = "crate::serde::as_string_lossy")] pub description: BString, - pub created_at: u128, + pub created_at: Duration, pub author: Author, } @@ -128,12 +128,7 @@ pub fn branch_to_remote_branch(branch: &git::Branch) -> Result RemoteCommit { RemoteCommit { id: commit.id().to_string(), description: commit.message().to_owned(), - created_at: commit.time().seconds().try_into().unwrap(), + created_at: commit.time(), author: commit.author().into(), } } diff --git a/crates/gitbutler-core/src/virtual_branches/virtual.rs b/crates/gitbutler-core/src/virtual_branches/virtual.rs index 3ae80c673..742c2d579 100644 --- a/crates/gitbutler-core/src/virtual_branches/virtual.rs +++ b/crates/gitbutler-core/src/virtual_branches/virtual.rs @@ -1,7 +1,7 @@ use std::borrow::Borrow; #[cfg(target_family = "unix")] use std::os::unix::prelude::PermissionsExt; -use std::time::SystemTime; +use std::time::{Duration, SystemTime}; use std::{ collections::HashMap, hash::Hash, @@ -65,7 +65,7 @@ pub struct VirtualBranch { pub upstream_name: Option, // the upstream branch where this branch will push to on next push pub base_current: bool, // is this vbranch based on the current base branch? if false, this needs to be manually merged with conflicts pub ownership: BranchOwnershipClaims, - pub updated_at: u128, + pub updated_at: Duration, pub selected_for_changes: bool, pub head: git::Oid, } @@ -91,7 +91,7 @@ pub struct VirtualBranchCommit { pub id: git::Oid, #[serde(serialize_with = "crate::serde::as_string_lossy")] pub description: BString, - pub created_at: u128, + pub created_at: Duration, pub author: Author, pub is_remote: bool, pub files: Vec, @@ -117,7 +117,7 @@ pub struct VirtualBranchFile { pub id: String, pub path: PathBuf, pub hunks: Vec, - pub modified_at: u128, + pub modified_at: Duration, pub conflicted: bool, pub binary: bool, pub large: bool, @@ -137,7 +137,7 @@ pub struct VirtualBranchHunk { pub id: String, #[serde(serialize_with = "crate::serde::as_string_lossy")] pub diff: BString, - pub modified_at: u128, + pub modified_at: Duration, pub file_path: PathBuf, #[serde(serialize_with = "crate::serde::hash_to_hex")] pub hash: HunkHash, @@ -903,7 +903,7 @@ pub fn list_virtual_branches( conflicted: conflicts::is_resolving(project_repository), base_current, ownership: branch.ownership, - updated_at: branch.updated_timestamp_ms, + updated_at: branch.updated_at, selected_for_changes: branch.selected_for_changes == Some(max_selected_for_changes), head: branch.head, }; @@ -996,7 +996,7 @@ fn commit_to_vbranch_commit( is_integrated: bool, is_remote: bool, ) -> Result { - let timestamp = u128::try_from(commit.time().seconds())?; + let timestamp = commit.time(); let signature = commit.author(); let message = commit.message().to_owned(); @@ -1007,7 +1007,7 @@ fn commit_to_vbranch_commit( let commit = VirtualBranchCommit { id: commit.id(), - created_at: timestamp * 1000, + created_at: timestamp, author: Author::from(signature), description: message, is_remote, @@ -1092,7 +1092,7 @@ pub fn create_virtual_branch( .unwrap_or(&"Virtual branch".to_string()), ); - let now = crate::time::now_ms(); + let now = crate::time::now(); let mut branch = Branch { id: BranchId::generate(), @@ -1103,8 +1103,8 @@ pub fn create_virtual_branch( upstream_head: None, tree: tree.id(), head: default_target.sha, - created_timestamp_ms: now, - updated_timestamp_ms: now, + created_at: now, + updated_at: now, ownership: BranchOwnershipClaims::default(), order, selected_for_changes, @@ -1525,10 +1525,10 @@ fn set_ownership( } #[derive(Default)] -struct MTimeCache(HashMap); +struct MTimeCache(HashMap); impl MTimeCache { - fn mtime_by_path>(&mut self, path: P) -> u128 { + fn mtime_by_path>(&mut self, path: P) -> Duration { let path = path.as_ref(); if let Some(mtime) = self.0.get(path) { @@ -1547,7 +1547,7 @@ impl MTimeCache { }, ) .duration_since(time::UNIX_EPOCH) - .map_or(0, |d| d.as_millis()); + .unwrap_or(Duration::ZERO); self.0.insert(path.into(), mtime); mtime } @@ -1805,7 +1805,7 @@ fn get_applied_status( return None; // Defer allocation to unclaimed hunks processing } if claimed_hunk.eq(&Hunk::from(git_diff_hunk)) { - let timestamp = claimed_hunk.timestamp_ms().unwrap_or(mtime); + let timestamp = claimed_hunk.timestamp().unwrap_or(mtime); diffs_by_branch .entry(branch.id) .or_default() @@ -1830,7 +1830,7 @@ fn get_applied_status( let updated_hunk = Hunk { start: git_diff_hunk.new_start, end: git_diff_hunk.new_start + git_diff_hunk.new_lines, - timestamp_ms: Some(mtime), + timestamp: Some(mtime), hash: Some(hash), locked_to: git_diff_hunk.locked_to.to_vec(), }; @@ -1956,7 +1956,11 @@ fn virtual_hunks_into_virtual_files( let conflicted = conflicts::is_conflicting(project_repository, Some(&id)).unwrap_or(false); let binary = hunks.iter().any(|h| h.binary); - let modified_at = hunks.iter().map(|h| h.modified_at).max().unwrap_or(0); + let modified_at = hunks + .iter() + .map(|h| h.modified_at) + .max() + .unwrap_or(Duration::ZERO); debug_assert!(hunks.iter().all(|hunk| hunk.file_path == path)); VirtualBranchFile { id, @@ -4052,7 +4056,7 @@ pub fn create_virtual_branch_from_branch( .any(|b| b.selected_for_changes.is_some())) .then_some(chrono::Utc::now().timestamp_millis()); - let now = crate::time::now_ms(); + let now = crate::time::now(); // only set upstream if it's not the default target let upstream_branch = match upstream { @@ -4118,8 +4122,8 @@ pub fn create_virtual_branch_from_branch( upstream: upstream_branch, tree: head_commit_tree.id(), head: head_commit.id(), - created_timestamp_ms: now, - updated_timestamp_ms: now, + created_at: now, + updated_at: now, ownership, order, selected_for_changes, diff --git a/crates/gitbutler-core/tests/suite/virtual_branches/fetch_from_target.rs b/crates/gitbutler-core/tests/suite/virtual_branches/fetch_from_target.rs index 845b437af..d2f9b302c 100644 --- a/crates/gitbutler-core/tests/suite/virtual_branches/fetch_from_target.rs +++ b/crates/gitbutler-core/tests/suite/virtual_branches/fetch_from_target.rs @@ -14,29 +14,29 @@ async fn should_update_last_fetched() { .unwrap(); let before_fetch = controller.get_base_branch_data(project_id).await.unwrap(); - assert!(before_fetch.last_fetched_ms.is_none()); + assert!(before_fetch.last_fetched_at.is_none()); let fetch = controller .fetch_from_target(project_id, None) .await .unwrap(); - assert!(fetch.last_fetched_ms.is_some()); + assert!(fetch.last_fetched_at.is_some()); let after_fetch = controller.get_base_branch_data(project_id).await.unwrap(); - assert!(after_fetch.last_fetched_ms.is_some()); - assert_eq!(fetch.last_fetched_ms, after_fetch.last_fetched_ms); + assert!(after_fetch.last_fetched_at.is_some()); + assert_eq!(fetch.last_fetched_at, after_fetch.last_fetched_at); let second_fetch = controller .fetch_from_target(project_id, None) .await .unwrap(); - assert!(second_fetch.last_fetched_ms.is_some()); - assert_ne!(fetch.last_fetched_ms, second_fetch.last_fetched_ms); + assert!(second_fetch.last_fetched_at.is_some()); + assert_ne!(fetch.last_fetched_at, second_fetch.last_fetched_at); let after_second_fetch = controller.get_base_branch_data(project_id).await.unwrap(); - assert!(after_second_fetch.last_fetched_ms.is_some()); + assert!(after_second_fetch.last_fetched_at.is_some()); assert_eq!( - second_fetch.last_fetched_ms, - after_second_fetch.last_fetched_ms + second_fetch.last_fetched_at, + after_second_fetch.last_fetched_at ); } diff --git a/crates/gitbutler-core/tests/virtual_branches/branch/hunk.rs b/crates/gitbutler-core/tests/virtual_branches/branch/hunk.rs index 3f5a6efa3..32adb6de7 100644 --- a/crates/gitbutler-core/tests/virtual_branches/branch/hunk.rs +++ b/crates/gitbutler-core/tests/virtual_branches/branch/hunk.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use gitbutler_core::virtual_branches::branch::Hunk; #[test] @@ -24,7 +26,7 @@ fn parse_with_hash() { fn parse_with_timestamp() { assert_eq!( "2-3--123".parse::().unwrap(), - Hunk::new(2, 3, None, Some(123)).unwrap() + Hunk::new(2, 3, None, Some(Duration::from_secs(123))).unwrap() ); } @@ -37,7 +39,9 @@ fn parse_invalid_2() { fn to_string_no_hash() { assert_eq!( "1-2--123", - Hunk::new(1, 2, None, Some(123)).unwrap().to_string() + Hunk::new(1, 2, None, Some(Duration::from_secs(123))) + .unwrap() + .to_string() ); } diff --git a/crates/gitbutler-core/tests/virtual_branches/branch/ownership.rs b/crates/gitbutler-core/tests/virtual_branches/branch/ownership.rs index 74db19731..7e7a113d4 100644 --- a/crates/gitbutler-core/tests/virtual_branches/branch/ownership.rs +++ b/crates/gitbutler-core/tests/virtual_branches/branch/ownership.rs @@ -17,14 +17,14 @@ fn reconcile_ownership_simple() { start: 1, end: 3, hash: Some(Hunk::hash("1,3")), - timestamp_ms: None, + timestamp: None, locked_to: vec![], }, Hunk { start: 4, end: 6, hash: Some(Hunk::hash("4,6")), - timestamp_ms: None, + timestamp: None, locked_to: vec![], }, ], @@ -42,7 +42,7 @@ fn reconcile_ownership_simple() { start: 7, end: 9, hash: Some(Hunk::hash("7,9")), - timestamp_ms: None, + timestamp: None, locked_to: vec![], }], }], @@ -58,14 +58,14 @@ fn reconcile_ownership_simple() { start: 4, end: 6, hash: Some(Hunk::hash("4,6")), - timestamp_ms: None, + timestamp: None, locked_to: vec![], }, Hunk { start: 7, end: 9, hash: Some(Hunk::hash("9,7")), - timestamp_ms: None, + timestamp: None, locked_to: vec![], }, ], @@ -84,7 +84,7 @@ fn reconcile_ownership_simple() { start: 1, end: 3, hash: Some(Hunk::hash("1,3")), - timestamp_ms: None, + timestamp: None, locked_to: vec![], },], }], @@ -101,14 +101,14 @@ fn reconcile_ownership_simple() { start: 4, end: 6, hash: Some(Hunk::hash("4,6")), - timestamp_ms: None, + timestamp: None, locked_to: vec![], }, Hunk { start: 7, end: 9, hash: Some(Hunk::hash("9,7")), - timestamp_ms: None, + timestamp: None, locked_to: vec![], }, ], diff --git a/crates/gitbutler-core/tests/virtual_branches/iterator.rs b/crates/gitbutler-core/tests/virtual_branches/iterator.rs index 1fb7fa43a..24ae8d7ce 100644 --- a/crates/gitbutler-core/tests/virtual_branches/iterator.rs +++ b/crates/gitbutler-core/tests/virtual_branches/iterator.rs @@ -1,4 +1,7 @@ -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::{ + sync::atomic::{AtomicUsize, Ordering}, + time::Duration, +}; use anyhow::Result; use gitbutler_core::virtual_branches; @@ -25,8 +28,8 @@ fn new_test_branch() -> virtual_branches::branch::Branch { .unwrap(), ), upstream_head: None, - created_timestamp_ms: TEST_INDEX.load(Ordering::Relaxed) as u128, - updated_timestamp_ms: (TEST_INDEX.load(Ordering::Relaxed) + 100) as u128, + created_at: Duration::from_millis(TEST_INDEX.load(Ordering::Relaxed) as u64), + updated_at: Duration::from_millis((TEST_INDEX.load(Ordering::Relaxed) + 100) as u64), head: format!( "0123456789abcdef0123456789abcdef0123456{}", TEST_INDEX.load(Ordering::Relaxed)