Skip to content

Commit

Permalink
Add project metadata for local saved state
Browse files Browse the repository at this point in the history
Summary:
Add a project metadata field to all saved state implementations that
can be used to differentiate multiple saved states for a given project at a
given source control revision. For example, a tool using saved states might
want multiple saved states for a particular revision, one for each supported
version of the tool.

Update the local saved state interface to use the project metadata, if
specified, by appending it, separated by an underscore, to the end of the hash
in the filename.

Reviewed By: wez

Differential Revision: D14789784

fbshipit-source-id: d92d730e4362ff0d8eb1791b840c632ba14be539
  • Loading branch information
kcoons authored and facebook-github-bot committed May 2, 2019
1 parent bdd1dda commit 1a5d214
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 5 deletions.
8 changes: 7 additions & 1 deletion saved_state/LocalSavedStateInterface.cpp
Expand Up @@ -80,6 +80,12 @@ LocalSavedStateInterface::getMostRecentSavedStateImpl(
}

w_string LocalSavedStateInterface::getLocalPath(w_string_piece commitId) const {
return w_string::pathCat({localStoragePath_, project_, commitId});
w_string filename;
if (!projectMetadata_) {
filename = w_string::build(commitId);
} else {
filename = w_string::build(commitId, w_string("_"), projectMetadata_);
}
return w_string::pathCat({localStoragePath_, project_, filename});
}
} // namespace watchman
5 changes: 4 additions & 1 deletion saved_state/LocalSavedStateInterface.h
Expand Up @@ -10,7 +10,10 @@ namespace watchman {
// stored on the local filesystem. The local storage path must contain a
// subdirectory for the project, and within the project directory the saved
// state for a given commit must be in a file whose name is the source control
// commit hash.
// commit hash. If project metadata is not specified, then only saved states
// with no project metadata will be returned. If project metadata is specified,
// then the most recent saved state with the specified project metadata will be
// returned.
//
// Checks the most recent n commits to find a saved state, if available. If a
// saved state is not available, returns an error message in the saved state
Expand Down
9 changes: 9 additions & 0 deletions saved_state/SavedStateInterface.cpp
Expand Up @@ -39,6 +39,15 @@ SavedStateInterface::SavedStateInterface(const json_ref& savedStateConfig) {
throw QueryParseError("'project' must be a string");
}
project_ = json_to_w_string(project);
auto projectMetadata = savedStateConfig.get_default("project-metadata");
if (projectMetadata) {
if (!projectMetadata.isString()) {
throw QueryParseError("'project-metadata' must be a string");
}
projectMetadata_ = json_to_w_string(projectMetadata);
} else {
projectMetadata_ = w_string();
}
}

SavedStateInterface::SavedStateResult
Expand Down
1 change: 1 addition & 0 deletions saved_state/SavedStateInterface.h
Expand Up @@ -46,6 +46,7 @@ class SavedStateInterface {

protected:
w_string project_;
w_string projectMetadata_;

explicit SavedStateInterface(const json_ref& savedStateConfig);
virtual SavedStateResult getMostRecentSavedStateImpl(
Expand Down
29 changes: 28 additions & 1 deletion tests/LocalSavedStateInterfaceTest.cpp
Expand Up @@ -89,6 +89,21 @@ void test_project() {
ok(true, "expected constructor to succeed");
}

void test_project_metadata() {
auto localStoragePath = w_string_to_json("/absolute/path");
expect_query_parse_error(
json_object({{"local-storage-path", localStoragePath},
{"project", w_string_to_json("relative/path")},
{"project-metadata", json_integer(5)}}),
"failed to parse query: 'project-metadata' must be a string");
LocalSavedStateInterface interface(
json_object({{"local-storage-path", localStoragePath},
{"project", w_string_to_json("foo")},
{"project-metadata", w_string_to_json("meta")}}),
nullptr);
ok(true, "expected constructor to succeed");
}

void test_path() {
auto localStoragePath = w_string_to_json("/absolute/path");
LocalSavedStateInterface interface(
Expand All @@ -101,13 +116,25 @@ void test_path() {
"Expected path to be \"%s\" but observed \"%s\"",
expectedPath,
path.c_str());
interface = LocalSavedStateInterface(
json_object({{"local-storage-path", localStoragePath},
{"project", w_string_to_json("foo")},
{"project-metadata", w_string_to_json("meta")}}),
nullptr);
path = interface.getLocalPath("hash");
expectedPath = "/absolute/path/foo/hash_meta";
ok(!strcmp(path.c_str(), expectedPath),
"Expected path to be \"%s\" but observed \"%s\"",
expectedPath,
path.c_str());
}

int main(int, char**) {
plan_tests(22);
plan_tests(26);
test_max_commits();
test_localStoragePath();
test_project();
test_project_metadata();
test_path();

return exit_status();
Expand Down
74 changes: 72 additions & 2 deletions tests/integration/test_local_saved_state.py
Expand Up @@ -119,14 +119,20 @@ def getQuery(self, config):
},
}

def getLocalFilename(self, saved_state_rev, metadata):
if metadata:
return saved_state_rev + "_" + metadata
return saved_state_rev

# Creates a saved state (with no content) for the specified project at the
# specified bookmark within the specified local storage path.
def saveState(self, project, bookmark, local_storage):
def saveState(self, project, bookmark, local_storage, metadata=None):
saved_state_rev = self.resolveCommitHash(bookmark, cwd=self.root)
project_dir = os.path.join(local_storage, project)
if not os.path.isdir(project_dir):
os.mkdir(project_dir)
self.touchRelative(project_dir, saved_state_rev)
filename = self.getLocalFilename(saved_state_rev, metadata)
self.touchRelative(project_dir, filename)
return saved_state_rev

def getConfig(self, result):
Expand Down Expand Up @@ -229,6 +235,70 @@ def test_localSavedStateLookupSuccess(self):
self.assertSavedStateInfo(res, expected_path, saved_state_rev_feature3)
self.assertFileListsEqual(res["files"], ["f1", "bar", "car"])

def test_localSavedStateLookupSuccessWithMetadata(self):
local_storage = self.mkdtemp()
metadata = "metadata"
saved_state_rev_feature3 = self.saveState(
"example_project", "feature3", local_storage, metadata
)
self.saveState("example_project", "feature0", local_storage, metadata)
config = {
"local-storage-path": local_storage,
"project": "example_project",
"project-metadata": metadata,
"max-commits": 10,
}
test_query = self.getQuery(config)
res = self.watchmanCommand("query", self.root, test_query)
expected_mergebase = self.resolveCommitHash("TheMaster", cwd=self.root)
self.assertMergebaseEquals(res, expected_mergebase)
self.assertStorageTypeLocal(res)
self.assertCommitIDEquals(res, saved_state_rev_feature3)
self.assertEqual(self.getConfig(res), config)
project_dir = os.path.join(local_storage, "example_project")
filepath = self.getLocalFilename(saved_state_rev_feature3, metadata)
expected_path = os.path.join(project_dir, filepath)
self.assertSavedStateInfo(res, expected_path, saved_state_rev_feature3)
self.assertFileListsEqual(res["files"], ["f1", "bar", "car"])

def test_localSavedStateFailureIfMetadataDoesNotMatch(self):
local_storage = self.mkdtemp()
self.saveState("example_project", "feature3", local_storage)
self.saveState("example_project", "feature0", local_storage)
config = {
"local-storage-path": local_storage,
"project": "example_project",
"project-metadata": "meta",
"max-commits": 10,
}
test_query = self.getQuery(config)
res = self.watchmanCommand("query", self.root, test_query)
self.assertSavedStateErrorEquals(res, "No suitable saved state found")
self.assertEqual(self.getConfig(res), config)
self.assertStorageTypeLocal(res)
expected_mergebase = self.resolveCommitHash("TheMaster", cwd=self.root)
self.assertMergebaseEquals(res, expected_mergebase)
self.assertFileListsEqual(res["files"], ["foo", "p1", "m2", "bar", "car", "f1"])

def test_localSavedStateFailureIfNoMetadataForFileThatHasIt(self):
local_storage = self.mkdtemp()
metadata = "metadata"
self.saveState("example_project", "feature3", local_storage, metadata)
self.saveState("example_project", "feature0", local_storage, metadata)
config = {
"local-storage-path": local_storage,
"project": "example_project",
"max-commits": 10,
}
test_query = self.getQuery(config)
res = self.watchmanCommand("query", self.root, test_query)
self.assertSavedStateErrorEquals(res, "No suitable saved state found")
self.assertEqual(self.getConfig(res), config)
self.assertStorageTypeLocal(res)
expected_mergebase = self.resolveCommitHash("TheMaster", cwd=self.root)
self.assertMergebaseEquals(res, expected_mergebase)
self.assertFileListsEqual(res["files"], ["foo", "p1", "m2", "bar", "car", "f1"])

def test_localSavedStateSubscription(self):
local_storage = self.mkdtemp()
saved_state_rev_feature3 = self.saveState(
Expand Down

0 comments on commit 1a5d214

Please sign in to comment.