Merge pull request #3830 from gitbutlerapp/core-util-duration-updates

Replace integer timestamps with Duration
This commit is contained in:
Caleb Owens 2024-05-23 15:01:14 +02:00 committed by GitHub
commit f6421aab3c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 205 additions and 300 deletions

View File

@ -72,7 +72,7 @@ const diff1 = `
const hunk1 = plainToInstance(Hunk, {
id: 'asdf',
diff: diff1,
modifiedAt: new Date().toISOString(),
modifiedAt: new Date(),
filePath: 'foo/bar/baz.ts',
locked: false,
lockedTo: undefined,
@ -92,7 +92,7 @@ const diff2 = `
const hunk2 = plainToInstance(Hunk, {
id: 'asdf',
diff: diff2,
modifiedAt: new Date().toISOString(),
modifiedAt: new Date(),
filePath: 'random.ts',
locked: false,
lockedTo: undefined,

View File

@ -31,8 +31,8 @@
>
{#if $baseServiceBusy$}
<div class="sync-btn__busy-label">busy…</div>
{:else if $baseBranch?.lastFetched}
<TimeAgo date={$baseBranch?.lastFetched} />
{:else if $baseBranch?.lastFetchedAt}
<TimeAgo date={$baseBranch?.lastFetchedAt} />
{/if}
</Tag>

View File

@ -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;
}

View File

@ -33,21 +33,21 @@ export const remoteCommit0 = {
id: 'fe30876278739f7182effd27e9d9debde648b4de',
author,
description: 'fix: updated files',
createdAt: 1714902366
createdAt: { secs: 1714902366, nanos: 0 }
};
export const remoteCommit1 = {
id: 'fe30876278739f7182effd27e9d9debde648b4dd',
author,
description: 'fix: updated files',
createdAt: 1714902366
createdAt: { secs: 1714902366, nanos: 0 }
};
export const remoteBranch0 = {
sha: '90c225edcc74b31718a9cd8963c1bc89c17d8863',
name: '',
upstream: '',
lastCommitTimestampMs: 1714902366140,
lastCommitAt: { secs: 1714902366, nanos: 25 },
lastCommitAuthor: 'John Snow'
};
@ -60,7 +60,7 @@ export const baseBranch = {
behind: 0,
upstreamCommits: [],
recentCommits: [remoteCommit0],
lastFetchedMs: 1714843209991
lastFetchedAt: { secs: 1714843209, nanos: 123 }
};
export const user: User = {
@ -86,8 +86,8 @@ export const remoteBranchData = {
upstream: 'abc123',
authors: [author],
displayName: 'test',
lastCommitTs: new Date(),
firstCommitAt: new Date(),
lastCommitTs: { secs: 1714902366, nanos: 0 },
firstCommitAt: { secs: 1714902366, nanos: 0 },
ahead: 0,
behind: 0,
commits: [remoteCommit0],
@ -114,7 +114,7 @@ export const fileHunk = {
id: '63-70',
locked: false,
lockedTo: null,
modifiedAt: 1714829527993,
modifiedAt: { secs: 1714902366, nanos: 0 },
oldStart: 63,
start: 63
};
@ -125,7 +125,7 @@ export const file0 = {
hunks: [fileHunk],
id: 'package.json',
large: false,
modifiedAt: 1714829589111,
modifiedAt: { secs: 1714902366, nanos: 0 },
path: 'package.json'
};
@ -143,7 +143,7 @@ export const virtualBranch = {
ownership:
'package.json:63-70-dc79c984a36b2f8a29007633bde4daf3-1714829528116,23-58-fbf18cec4afef8aafbbc2dddef3e3391-1714829528116,79-85-c4d0a57fca736c384cde2a68009ffcb3-1714829503193',
requiresForce: false,
updatedAt: 1714829503190,
updatedAt: { secs: 1714902366, nanos: 0 },
upstream: null,
upstreamName: null
};

View File

@ -10,7 +10,7 @@ test('creates a file tree', () => {
path: 'test/foo.py',
hunks: [],
expanded: true,
modifiedAt: Date.now(),
modifiedAt: new Date(),
conflicted: false,
content: undefined,
binary: false
@ -20,7 +20,7 @@ test('creates a file tree', () => {
path: 'test/bar.rs',
hunks: [],
expanded: true,
modifiedAt: Date.now(),
modifiedAt: new Date(),
conflicted: false,
content: undefined,
binary: false
@ -30,7 +30,7 @@ test('creates a file tree', () => {
path: 'src/hello/world.txt',
hunks: [],
expanded: true,
modifiedAt: Date.now(),
modifiedAt: new Date(),
conflicted: false,
content: undefined,
binary: false

View File

@ -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 (params.value instanceof Date) return params.value;
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.
@ -122,7 +141,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<boolean>;
@Transform((obj) => new Date(obj.value))
@Transform(dateFromDuration)
updatedAt!: Date;
// Indicates that branch is default target for new changes
selectedForChanges!: boolean;
@ -153,7 +172,7 @@ export class Commit {
id!: string;
author!: Author;
description!: string;
@Transform((obj) => new Date(obj.value))
@Transform(dateFromDuration)
createdAt!: Date;
isRemote!: boolean;
isIntegrated!: boolean;
@ -198,7 +217,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;
@ -365,16 +384,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);
}

View File

@ -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);

View File

@ -1,3 +1,5 @@
use std::time::Duration;
use super::{Oid, Result, Signature, Tree};
use bstr::BStr;
@ -55,8 +57,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<'_> {

View File

@ -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;
@ -18,8 +19,8 @@ use serde::Serialize;
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,
/// Snapshot creation time as a duration since the Unix epoch
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(),

View File

@ -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;
}

View File

@ -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 =

View File

@ -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<Project>;

View File

@ -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_int_string_serde {
use std::time::Duration;
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&duration.as_millis().to_string())
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: Deserializer<'de>,
{
let millis_str = String::deserialize(deserializer)?;
let millis = millis_str
.parse::<u64>()
.map_err(serde::de::Error::custom)?;
Ok(Duration::from_millis(millis))
}
}

View File

@ -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<RemoteCommit>,
pub recent_commits: Vec<RemoteCommit>,
pub last_fetched_ms: Option<u128>,
pub last_fetched_at: Option<Duration>,
}
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)
}

View File

@ -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<HunkHash>,
pub timestamp_ms: Option<u128>,
pub timestamp: Option<Duration>,
pub start: u32,
pub end: u32,
pub locked_to: Vec<diff::HunkLock>,
@ -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<RangeInclusive<u32>> 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::<u128>()
.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::<u64>()
.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_secs())
}
(Some(hash), None) => write!(f, "-{:x}", hash),
(None, Some(timestamp_ms)) => write!(f, "--{}", timestamp_ms),
(None, Some(timestamp)) => write!(f, "--{}", timestamp.as_secs()),
(None, None) => Ok(()),
}
}
@ -114,14 +116,14 @@ impl Hunk {
start: u32,
end: u32,
hash: Option<HunkHash>,
timestamp_ms: Option<u128>,
timestamp: Option<Duration>,
) -> Result<Self> {
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<u128> {
self.timestamp_ms
pub fn timestamp(&self) -> Option<Duration> {
self.timestamp
}
pub fn contains(&self, line: u32) -> bool {

View File

@ -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_int_string_serde;
use crate::{git, id::Id};
pub type BranchId = Id<Branch>;
@ -25,16 +27,10 @@ pub struct Branch {
pub upstream: Option<git::RemoteRefname>,
// upstream_head is the last commit on we've pushed to the upstream branch
pub upstream_head: Option<git::Oid>,
#[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_int_string_serde")]
pub created_at: Duration,
#[serde(rename = "updated_timestamp_ms", with = "duration_int_string_serde")]
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<i64>,
}
fn serialize_u128<S>(x: &u128, s: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
s.serialize_str(&x.to_string())
}
fn deserialize_u128<'de, D>(d: D) -> Result<u128, D::Error>
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<usize>,
pub selected_for_changes: Option<bool>,
}
impl Branch {
pub fn from_reader(reader: &crate::reader::Reader<'_>) -> Result<Self, crate::reader::Error> {
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::<git::RemoteRefname>()
.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,
})
}
}

View File

@ -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};
@ -897,10 +897,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(),
},
};

View File

@ -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<git::RemoteRefname>,
pub last_commit_timestamp_ms: Option<u128>,
pub last_commit_at: Option<Duration>,
pub last_commit_author: Option<String>,
}
@ -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,
pub change_id: Option<String>,
}
@ -129,12 +129,7 @@ pub fn branch_to_remote_branch(branch: &git::Branch) -> Result<Option<RemoteBran
None
},
name,
last_commit_timestamp_ms: commit
.time()
.seconds()
.try_into()
.map(|t: u128| t * 1000)
.ok(),
last_commit_at: Some(commit.time()),
last_commit_author: commit
.author()
.name()
@ -186,7 +181,7 @@ pub fn commit_to_remote_commit(commit: &git::Commit) -> 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(),
change_id: commit.change_id(),
}

View File

@ -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,
@ -66,7 +66,7 @@ pub struct VirtualBranch {
pub upstream_name: Option<String>, // 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,
}
@ -92,7 +92,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<VirtualBranchFile>,
@ -118,7 +118,7 @@ pub struct VirtualBranchFile {
pub id: String,
pub path: PathBuf,
pub hunks: Vec<VirtualBranchHunk>,
pub modified_at: u128,
pub modified_at: Duration,
pub conflicted: bool,
pub binary: bool,
pub large: bool,
@ -138,7 +138,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,
@ -907,7 +907,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,
};
@ -1000,7 +1000,7 @@ fn commit_to_vbranch_commit(
is_integrated: bool,
is_remote: bool,
) -> Result<VirtualBranchCommit> {
let timestamp = u128::try_from(commit.time().seconds())?;
let timestamp = commit.time();
let signature = commit.author();
let message = commit.message().to_owned();
@ -1011,7 +1011,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,
@ -1101,7 +1101,7 @@ pub fn create_virtual_branch(
}
}
let now = crate::time::now_ms();
let now = crate::time::now();
let mut branch = Branch {
id: BranchId::generate(),
@ -1112,8 +1112,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,
@ -1537,10 +1537,10 @@ fn set_ownership(
}
#[derive(Default)]
struct MTimeCache(HashMap<PathBuf, u128>);
struct MTimeCache(HashMap<PathBuf, Duration>);
impl MTimeCache {
fn mtime_by_path<P: AsRef<Path>>(&mut self, path: P) -> u128 {
fn mtime_by_path<P: AsRef<Path>>(&mut self, path: P) -> Duration {
let path = path.as_ref();
if let Some(mtime) = self.0.get(path) {
@ -1559,7 +1559,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
}
@ -1817,7 +1817,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()
@ -1842,7 +1842,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(),
};
@ -1968,7 +1968,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,
@ -4073,7 +4077,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 {
@ -4135,8 +4139,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,

View File

@ -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
);
}

View File

@ -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::<Hunk>().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()
);
}

View File

@ -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![],
},
],

View File

@ -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)