repair.v

3.7 KB · raw

1//! Doctor's repairs that copy records between this repo and its remote.2//!3//! Each one only puts back what another copy of the repo verifiably holds;4//! none changes what a save contains.56module exchange78import compress.zlib9import src.events10import src.history11import src.remote12import src.storage1314/// Fetch saves this repo is missing, with any missing ancestors and objects,15/// in one transaction.16pub fn fetch_missing(store &storage.Store, mut backend remote.Backend, repo string, ids []string) !int {17	mut fetched := Fetched{}18	mut wanted := ids.clone()19	mut seen := map[string]bool{}20	for wanted.len > 0 {21		id := wanted.pop()22		if id == '' || id in seen || history.by_id(store, id) != none {23			continue24		}25		seen[id] = true26		object := backend.get(save_key(repo, id))!27		if !object.found {28			return error('the remote does not have save ${id} either')29		}30		record := decode_save(object.body)!31		if record.id != id {32			return error('remote save ${id} holds the record of ${record.id}')33		}34		for file in record.files {35			if !store.has_object(file.hash)! && file.hash !in fetched.repo_objects {36				blob := backend.get(object_key(repo, file.hash))!37				if !blob.found {38					return error('the remote is missing object ${file.hash} that save ${id} needs')39				}40				fetched.repo_objects[file.hash] = blob.body41			}42		}43		fetched.saves << record44		wanted << record.parent45		wanted << record.restores46	}47	// The saves table refers to each parent, so parents land first.48	mut ordered := []SaveRecord{}49	mut placed := map[string]bool{}50	for ordered.len < fetched.saves.len {51		before := ordered.len52		for record in fetched.saves {53			if record.id !in placed && (record.parent !in seen || record.parent in placed) {54				ordered << record55				placed[record.id] = true56			}57		}58		if ordered.len == before {59			return error('the remote history has a parent cycle')60		}61	}62	fetched.saves = ordered63	store.begin()!64	write_fetched_records(store, fetched) or {65		store.rollback()66		return err67	}68	store.end()!69	return ordered.len70}7172/// Upload verified local copies of remote objects that are missing or damaged,73/// and of missing save records with their objects.74pub fn resend(store &storage.Store, mut backend remote.Backend, repo string, hashes []string, save_ids []string) !int {75	// A record sent before this repo applies a remote strip would bring the76	// stripped path back.77	if unapplied_strips(store, mut backend, repo)!.len > 0 {78		return error(strip_not_applied)79	}80	mut sent := 081	for hash in hashes {82		key := object_key(repo, hash)83		existing := backend.get(key)!84		if existing.found {85			data := zlib.decompress(existing.body) or { []u8{} }86			// Another machine may have put it right since the audit.87			if storage.hash_hex(data) == hash {88				continue89			}90			backend.delete(key)!91		}92		store.get_object(hash)!93		if backend.put_if_absent(key, store.object_blob(hash)!)! {94			sent++95		}96	}97	for id in save_ids {98		save := history.by_id(store, id) or { return error('save ${id} is missing here too') }99		for file in history.files_of(store, id)! {100			store.get_object(file.hash)!101			if backend.put_if_absent(object_key(repo, file.hash), store.object_blob(file.hash)!)! {102				sent++103			}104		}105		if backend.put_if_absent(save_key(repo, id), encode_save(save_record(store, save)!).bytes())! {106			sent++107		}108	}109	return sent110}111112/// Remove what recorded strips cover but a save still holds, without113/// recording a new strip.114pub fn reapply_strips(store &storage.Store) !int {115	store.begin()!116	mut removed := 0117	for event in events.strip_events(store) or {118		store.rollback()119		return err120	} {121		removed += remove_path(store, event.path, event.save_id) or {122			store.rollback()123			return err124		}125	}126	store.drop_unreferenced_objects() or {127		store.rollback()128		return err129	}130	store.end()!131	return removed132}