sapling/ctreemanifest/manifest_entry.cpp
Durham Goode e8554fc3d9 treemanifest: add concept of mutability to manifest, and use it during edits
This adds the concept of mutable and immutable Manifests. During a treemanifest
copy, any sub-manifests that are immutable (such as ones that had been loaded
from a store, or those that are in memory but have been mark immutable), do not
need to be copied. This dramatically reduces the amount of memory allocation
happening when copying trees during automatic tree creation during hg pull.
2016-10-14 16:01:12 -07:00

239 lines
6.4 KiB
C++

// manifest_entry.cpp - c++ implementation of a single manifest entry
//
// Copyright 2016 Facebook, Inc.
//
// This software may be used and distributed according to the terms of the
// GNU General Public License version 2 or any later version.
//
// no-check-code
#include "manifest_entry.h"
ManifestEntry::ManifestEntry() {
this->filename = NULL;
this->filenamelen = 0;
this->node = NULL;
this->flag = NULL;
this->ownedmemory = NULL;
}
void ManifestEntry::initialize(
const char *filename, const size_t filenamelen,
const char *node,
const char *flag) {
if (flag != NULL && *flag == MANIFEST_DIRECTORY_FLAG) {
this->resolved = ManifestPtr(new Manifest());
}
this->ownedmemory = new char[
filenamelen +
1 + // null character
HEX_NODE_SIZE + // node hash
1 + // flag
1 // NL
];
// set up the pointers.
this->filename = this->ownedmemory;
if (node == NULL) {
this->node = NULL;
} else {
this->node = this->filename + filenamelen + 1;
}
// set up the null character and NL.
this->filename[filenamelen] = '\0';
*(this->filename + filenamelen + 1 + HEX_NODE_SIZE + 1) = '\n';
// set up filenamelen
this->filenamelen = filenamelen;
// write the memory.
memcpy(this->filename, filename, filenamelen);
if (node != NULL) {
memcpy(this->node, node, HEX_NODE_SIZE);
}
if (flag == NULL) {
*(this->filename + filenamelen + 1 + HEX_NODE_SIZE) = '\n';
this->flag = NULL;
} else {
this->flag = this->filename + filenamelen + 1 + HEX_NODE_SIZE;
*this->flag = *flag;
}
}
char *ManifestEntry::initialize(char *entrystart) {
// Each entry is of the format:
//
// <filename>\0<40-byte hash><optional 1 byte flag>\n
//
// Where flags can be 't' to represent a sub directory
this->filename = entrystart;
char *nulldelimiter = strchr(entrystart, '\0');
this->filenamelen = nulldelimiter - entrystart;
this->node = nulldelimiter + 1;
this->flag = nulldelimiter + 41;
char *nextpointer;
if (*this->flag != '\n') {
nextpointer = this->flag + 2;
} else {
// No flag
nextpointer = this->flag + 1;
this->flag = NULL;
}
this->resolved = ManifestPtr();
this->ownedmemory = NULL;
return nextpointer;
}
void ManifestEntry::initialize(ManifestEntry *other) {
if (other->ownedmemory) {
this->initialize(other->filename, other->filenamelen,
other->node, other->flag);
if (other->resolved.isnull()) {
this->resolved = ManifestPtr();
}
} else {
// Else it points at a piece of memory owned by something else
this->initialize(other->filename);
}
if (!other->resolved.isnull()) {
if (other->resolved->isMutable()) {
this->resolved = other->resolved->copy();
} else {
this->resolved = other->resolved;
}
}
}
ManifestEntry::~ManifestEntry() {
if (this->ownedmemory != NULL) {
delete [] this->ownedmemory;
}
}
bool ManifestEntry::isdirectory() const {
return this->flag && *this->flag == MANIFEST_DIRECTORY_FLAG;
}
void ManifestEntry::appendtopath(std::string &path) {
path.append(this->filename, this->filenamelen);
if (this->isdirectory()) {
path.append(1, '/');
}
}
ManifestPtr ManifestEntry::get_manifest(
ManifestFetcher fetcher, const char *path, size_t pathlen) {
if (this->resolved.isnull()) {
std::string binnode = binfromhex(node);
this->resolved = fetcher.get(path, pathlen, binnode);
}
return this->resolved;
}
void ManifestEntry::update(const char *node, const char *flag) {
// we cannot flip between file and directory.
bool wasdir = this->flag != NULL && *this->flag == MANIFEST_DIRECTORY_FLAG;
bool willbedir = flag != NULL && *flag == MANIFEST_DIRECTORY_FLAG;
if (wasdir != willbedir) {
throw std::logic_error("changing to/from directory is not permitted");
}
// if we didn't previously own the memory, we should now.
if (this->ownedmemory == NULL) {
ManifestPtr oldresolved = this->resolved;
this->initialize(this->filename, this->filenamelen, node, flag);
this->resolved = oldresolved;
return;
}
// initialize node if it's not already done.
if (this->node == NULL) {
this->node = this->filename + this->filenamelen + 1;
}
memcpy(this->node, node, HEX_NODE_SIZE);
if (flag == NULL) {
*(this->filename + this->filenamelen + 1 + HEX_NODE_SIZE) = '\n';
this->flag = NULL;
} else {
this->flag = this->filename + filenamelen + 1 + HEX_NODE_SIZE;
*this->flag = *flag;
}
}
static size_t mercurialOrderFilenameLength(const ManifestEntry &entry) {
return entry.filenamelen +
((entry.flag != NULL && *entry.flag == MANIFEST_DIRECTORY_FLAG) ?
1 : 0);
}
static char mercurialOrderFilenameCharAt(
const ManifestEntry &entry, size_t offset) {
if (offset < entry.filenamelen) {
return entry.filename[offset];
} else if (offset == entry.filenamelen &&
(entry.flag != NULL && *entry.flag == MANIFEST_DIRECTORY_FLAG)) {
return '/';
}
throw std::out_of_range("Illegal index for manifest entry");
}
bool ManifestEntry::compareMercurialOrder(
ManifestEntry * const &left,
ManifestEntry * const &right) {
size_t leftlen = mercurialOrderFilenameLength(*left);
size_t rightlen = mercurialOrderFilenameLength(*right);
size_t minlen = (leftlen < rightlen) ? leftlen : rightlen;
for (size_t ix = 0; ix < minlen; ix ++) {
unsigned char leftchar = mercurialOrderFilenameCharAt(*left, ix);
unsigned char rightchar = mercurialOrderFilenameCharAt(*right, ix);
if (leftchar < rightchar) {
return true;
} else if (leftchar > rightchar) {
return false;
}
}
// same up to minlen.
if (leftlen < rightlen) {
return true;
}
return false;
}
int ManifestEntry::compareName(ManifestEntry *left, ManifestEntry *right) {
assert(left || right);
// If left is empty, then it is greater than right. This makes this function
// useful for iterating right after left has already finished.
if (!left) {
return 1;
}
else if (!right) {
return -1;
}
size_t minlen = left->filenamelen < right->filenamelen ?
left->filenamelen : right->filenamelen;
int cmp = strncmp(left->filename, right->filename, minlen);
if (cmp == 0 && left->filenamelen == right->filenamelen) {
return 0;
} else if (cmp > 0 ||
(cmp == 0 && left->filenamelen > right->filenamelen)) {
return 1;
} else {
return -1;
}
}