-
Notifications
You must be signed in to change notification settings - Fork 124
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #164 from github/content-hash-versioning
Add content hashing as a versioning strategy
- Loading branch information
Showing
12 changed files
with
271 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
# frozen_string_literal: true | ||
|
||
require "ruby-xxhash" | ||
|
||
module Licensed | ||
module Sources | ||
module ContentVersioning | ||
GIT = "git".freeze | ||
CONTENTS = "contents".freeze | ||
|
||
# Find the version for a list of paths using the version strategy | ||
# specified for the source from the configuration | ||
# | ||
# paths - list of paths to find version | ||
# | ||
# Returns a version identifier for the given files | ||
def contents_version(*paths) | ||
case version_strategy | ||
when CONTENTS | ||
contents_hash(paths) | ||
when GIT | ||
git_version(paths) | ||
end | ||
end | ||
|
||
# Returns the version strategy configured for the source | ||
def version_strategy | ||
# default to git for backwards compatible behavior | ||
@version_strategy ||= begin | ||
case config.fetch("version_strategy", nil) | ||
when CONTENTS | ||
CONTENTS | ||
when GIT | ||
GIT | ||
else | ||
Licensed::Git.available? ? GIT : CONTENTS | ||
end | ||
end | ||
end | ||
|
||
# Find the version for a list of paths using Git commit information | ||
# | ||
# paths - list of paths to find version | ||
# | ||
# Returns the most recent git SHA from the given paths | ||
def git_version(paths) | ||
return if paths.nil? | ||
|
||
paths.map { |path| Licensed::Git.version(path) } | ||
.reject { |sha| sha.to_s.empty? } | ||
.max_by { |sha| Licensed::Git.commit_date(sha) } | ||
end | ||
|
||
# Find the version for a list of paths using their file contents | ||
# | ||
# paths - list of paths to find version | ||
# | ||
# Returns a hash of the path contents as an identifier for the group | ||
def contents_hash(paths) | ||
return if paths.nil? | ||
|
||
paths = paths.compact.select { |path| File.file?(path) } | ||
return if paths.empty? | ||
|
||
paths.sort | ||
.reduce(Digest::XXHash64.new, :file) | ||
.digest | ||
.to_s(16) # convert to hex | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#include <stdio.h> | ||
|
||
int main() | ||
{ | ||
printf("I'm a test!"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
# frozen_string_literal: true | ||
require "test_helper" | ||
|
||
describe Licensed::Sources::ContentVersioning do | ||
let(:fixtures) { File.expand_path("../../../fixtures/command", __FILE__) } | ||
let(:config) { Licensed::Configuration.new } | ||
let(:helper) do | ||
obj = mock.extend Licensed::Sources::ContentVersioning | ||
obj.stubs(:config).returns(config) | ||
obj | ||
end | ||
|
||
|
||
describe "#contents_version" do | ||
it "handles a content hashing strategy" do | ||
config["version_strategy"] = Licensed::Sources::ContentVersioning::CONTENTS | ||
helper.expects(:contents_hash).with(["path1", "path2"]).returns("version") | ||
helper.expects(:git_version).never | ||
assert_equal "version", helper.contents_version("path1", "path2") | ||
end | ||
|
||
it "handles a git commit SHA strategy" do | ||
config["version_strategy"] = Licensed::Sources::ContentVersioning::GIT | ||
helper.expects(:contents_hash).never | ||
helper.expects(:git_version).with(["path1", "path2"]).returns("version") | ||
assert_equal "version", helper.contents_version("path1", "path2") | ||
end | ||
end | ||
|
||
describe "#version_strategy" do | ||
it "specifies content hashing if configured" do | ||
config["version_strategy"] = Licensed::Sources::ContentVersioning::CONTENTS | ||
assert_equal Licensed::Sources::ContentVersioning::CONTENTS, helper.version_strategy | ||
end | ||
|
||
it "specifies git version if configured" do | ||
config["version_strategy"] = Licensed::Sources::ContentVersioning::GIT | ||
assert_equal Licensed::Sources::ContentVersioning::GIT, helper.version_strategy | ||
end | ||
|
||
it "defaults to git version if not configured and git is available" do | ||
Licensed::Git.stubs(:available?).returns(true) | ||
assert_equal Licensed::Sources::ContentVersioning::GIT, helper.version_strategy | ||
end | ||
|
||
it "defaults to content hashing if not configured and git is not available" do | ||
Licensed::Git.stubs(:available?).returns(false) | ||
assert_equal Licensed::Sources::ContentVersioning::CONTENTS, helper.version_strategy | ||
end | ||
end | ||
|
||
describe "#git_version" do | ||
it "gets a hash for the latest commit for the set of paths" do | ||
Dir.chdir fixtures do | ||
# the hash for "." in a folder should identify the latest commit | ||
# regardless of what other files from that folder are included | ||
assert_equal Licensed::Git.version("."), helper.git_version(Dir["*"].concat(["."])) | ||
end | ||
end | ||
|
||
it "handles files not tracked by git" do | ||
Dir.chdir File.expand_path("../../../bin", fixtures) do | ||
assert_nil helper.git_version(Dir["*"]) | ||
end | ||
end | ||
|
||
it "handles empty arrays" do | ||
assert_nil helper.git_version([]) | ||
end | ||
|
||
it "handles nil input" do | ||
assert_nil helper.git_version(nil) | ||
end | ||
end | ||
|
||
describe "#contents_hash" do | ||
it "gets a hash representing the contents of relative paths" do | ||
Dir.chdir fixtures do | ||
refute_nil helper.contents_hash(Dir["*"]) | ||
end | ||
end | ||
|
||
it "gets a hash representing the contents of absolute paths" do | ||
refute_nil helper.contents_hash(Dir["#{fixtures}/*"]) | ||
end | ||
|
||
it "is agnostic to the order of paths provided" do | ||
Dir.chdir fixtures do | ||
assert_equal helper.contents_hash(["bower.yml", "bundler.yml", "cabal.yml"]), | ||
helper.contents_hash(["cabal.yml", "bundler.yml", "bower.yml"]) | ||
end | ||
end | ||
|
||
it "handles empty arrays" do | ||
assert_nil helper.contents_hash([]) | ||
end | ||
|
||
it "handles nil input" do | ||
assert_nil helper.contents_hash(nil) | ||
end | ||
|
||
it "handles nil paths" do | ||
assert_nil helper.contents_hash([nil]) | ||
end | ||
|
||
it "handles non-existant paths" do | ||
assert_nil helper.contents_hash(["#{fixtures}-bad"]) | ||
end | ||
|
||
it "handles non-file paths" do | ||
assert_nil helper.contents_hash([fixtures]) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters