///| Snapshot operations: commit, checkout, rollback
///|
pub fn Fs::snapshot(
self : Fs,
backing_fs : &@bit.FileSystem,
rfs : &@bit.RepoFileSystem,
message : String,
author : String,
timestamp : Int64,
) -> Snapshot raise @bit.GitError {
if !self.working.is_dirty() {
raise @bit.GitError::InvalidObject("No changes to commit")
}
let tree_id = build_tree_from_working(
backing_fs,
rfs,
self.git_dir,
self.base_tree,
self.working,
)
let parents = match self.base_commit {
Some(id) => [id]
None => []
}
let commit = @bit.Commit::new(
tree_id, parents, author, timestamp, "+0000", author, timestamp, "+0000", message,
)
let (commit_id, compressed) = @bit.create_commit(commit)
@lib.write_object_bytes(backing_fs, self.git_dir, commit_id, compressed)
self.base_tree = tree_id
self.base_commit = Some(commit_id)
self.working.clear()
self.cache.clear()
self.cached_db = None // Invalidate ObjectDb cache since new objects were written
Snapshot::new(commit_id, tree_id, message, author, timestamp)
}
///|
pub fn Fs::checkout_snapshot(
self : Fs,
backing_fs : &@bit.RepoFileSystem,
commit_id : @bit.ObjectId,
) -> Unit raise @bit.GitError {
let db = self.get_db(backing_fs)
let obj = db.get(backing_fs, commit_id)
guard obj is Some(o) else {
raise @bit.GitError::InvalidObject("Commit not found: \{commit_id}")
}
if o.obj_type != @bit.ObjectType::Commit {
raise @bit.GitError::InvalidObject("Not a commit: \{commit_id}")
}
let info = @bit.parse_commit(o.data)
self.base_tree = info.tree
self.base_commit = Some(commit_id)
self.working.clear()
self.cache.clear()
}
///|
pub fn Fs::rollback(self : Fs) -> Unit {
self.working.clear()
self.cache.clear()
}
///|
pub fn Fs::list_snapshots(
self : Fs,
backing_fs : &@bit.RepoFileSystem,
max_count? : Int = 100,
) -> Array[Snapshot] raise @bit.GitError {
let result : Array[Snapshot] = []
let mut current = self.base_commit
let db = self.get_db(backing_fs)
let mut count = 0
while count < max_count {
match current {
None => break
Some(commit_id) => {
let obj = db.get(backing_fs, commit_id)
match obj {
None => break
Some(o) => {
if o.obj_type != @bit.ObjectType::Commit {
break
}
let info = @bit.parse_commit(o.data)
let (author, timestamp, message) = parse_commit_details(o.data)
result.push(
Snapshot::new(commit_id, info.tree, message, author, timestamp),
)
current = if info.parents.length() > 0 {
Some(info.parents[0])
} else {
None
}
}
}
}
}
count += 1
}
result
}
///|
fn parse_commit_details(content : Bytes) -> (String, Int64, String) {
let text = @utf8.decode_lossy(content[:])
let mut author = ""
let mut timestamp = 0L
let mut in_message = false
let message_buf = StringBuilder::new()
for line_view in text.split("\n") {
let line = line_view.to_owned()
if in_message {
if message_buf.to_string().length() > 0 {
message_buf.write_char('\n')
}
message_buf.write_string(line)
} else if line.length() == 0 {
in_message = true
} else if line.has_prefix("author ") {
let rest = String::unsafe_substring(line, start=7, end=line.length())
match rest.rev_find(" ") {
Some(space2) => {
let before_tz = String::unsafe_substring(rest, start=0, end=space2)
match before_tz.rev_find(" ") {
Some(space1) => {
author = String::unsafe_substring(before_tz, start=0, end=space1)
let ts_str = String::unsafe_substring(
before_tz,
start=space1 + 1,
end=before_tz.length(),
)
timestamp = parse_int64(ts_str)
}
None => author = rest
}
}
None => author = rest
}
}
} nobreak {
()
}
(author, timestamp, message_buf.to_string().trim().to_owned())
}
///|
fn parse_int64(s : String) -> Int64 {
let mut result = 0L
for c in s {
if c >= '0' && c <= '9' {
result = result * 10L + (c.to_int() - '0'.to_int()).to_int64()
}
}
result
}
///|
pub fn Fs::discard_changes(self : Fs) -> Unit {
self.rollback()
}
///|
pub fn Fs::get_working_files(self : Fs) -> Array[String] {
let out = self.working.files.keys().collect()
out.sort()
out
}
///|
pub fn Fs::get_deleted_files(self : Fs) -> Array[String] {
let out = self.working.deleted.keys().collect()
out.sort()
out
}