{"action":"created","comment":{"url":"https://api.github.com/repos/opencontainers/specs/pulls/comments/39085634","id":39085634,"diff_hunk":"@@ -0,0 +1,196 @@\n+package main\n+\n+import (\n+\t\"bytes\"\n+\t\"encoding/json\"\n+\t\"flag\"\n+\t\"fmt\"\n+\t\"log\"\n+\t\"os\"\n+\t\"os/exec\"\n+\t\"regexp\"\n+\t\"strings\"\n+)\n+\n+var (\n+\tflCommitRange = flag.String(\"range\", \"\", \"use this commit range instead\")\n+\n+\tvalidDCO = regexp.MustCompile(`^Signed-off-by: ([^<]+) <([^<>@]+@[^<>]+)>$`)\n+)\n+\n+var DefaultRules = []ValidateRule{\n+\tfunc(c CommitEntry) error {\n+\t\thasValid := false\n+\t\tfor _, line := range strings.Split(c.Body, \"\\n\") {\n+\t\t\tif validDCO.MatchString(line) {\n+\t\t\t\thasValid = true\n+\t\t\t}\n+\t\t}\n+\t\tif !hasValid {\n+\t\t\treturn fmt.Errorf(\"%q does not have a valid DCO\", c.Commit)\n+\t\t}\n+\t\treturn nil\n+\t},\n+\t// TODO add something for the cleanliness of the c.Subject\n+}\n+\n+func main() {\n+\tflag.Parse()\n+\n+\t/*\n+\t\tif len(os.Getenv(\"TRAVIS\")) > 0 {\n+\t\t\tfmt.Println(os.Getenv(\"TRAVIS_COMMIT_RANGE\"))\n+\t\t\tif len(os.Getenv(\"TRAVIS_PULL_REQUEST\")) > 0 {\n+\t\t\t\tfmt.Println(os.Getenv(\"TRAVIS_BRANCH\"))\n+\t\t\t}\n+\t\t}\n+\t*/\n+\n+\tinvalids := []InvalidCommit{}\n+\tif *flCommitRange != \"\" {\n+\t\tc, err := GitCommits(*flCommitRange)\n+\t\tif err != nil {\n+\t\t\tlog.Fatal(err)\n+\t\t}\n+\n+\t\tfor _, commit := range c {\n+\t\t\tfmt.Printf(\" * %s %s ... \", commit.AbbreviatedCommit, commit.Subject)\n+\t\t\tif err := ValidateCommit(commit, DefaultRules); err != nil {\n+\t\t\t\tinvalids = append(invalids, InvalidCommit{commit, err})\n+\t\t\t\tfmt.Printf(\"FAIL: %s\\n\", err)\n+\t\t\t} else {\n+\t\t\t\tfmt.Println(\"PASS\")\n+\t\t\t}\n+\t\t}\n+\t} else if os.Getenv(\"TRAVIS_COMMIT_RANGE\") != \"\" {\n+\t\tc, err := GitCommits(os.Getenv(\"TRAVIS_COMMIT_RANGE\"))\n+\t\tif err != nil {\n+\t\t\tlog.Fatal(err)\n+\t\t}\n+\n+\t\tfor _, commit := range c {\n+\t\t\tfmt.Printf(\" * %s %s ... \", commit.AbbreviatedCommit, commit.Subject)\n+\t\t\tif err := ValidateCommit(commit, DefaultRules); err != nil {\n+\t\t\t\tinvalids = append(invalids, InvalidCommit{commit, err})\n+\t\t\t\tfmt.Printf(\"FAIL: %s\\n\", err)\n+\t\t\t} else {\n+\t\t\t\tfmt.Println(\"PASS\")\n+\t\t\t}\n+\t\t}\n+\t}\n+\tif len(invalids) > 0 {\n+\t\tfmt.Printf(\"%d issues to fix\\n\", len(invalids))\n+\t\tos.Exit(1)\n+\t}\n+}\n+\n+type ValidateRule func(CommitEntry) error\n+\n+func ValidateCommit(c CommitEntry, rules []ValidateRule) error {\n+\tfor _, r := range rules {\n+\t\tif err := r(c); err != nil {\n+\t\t\treturn err\n+\t\t}\n+\t}\n+\treturn nil\n+}\n+\n+type InvalidCommit struct {\n+\tCommitEntry CommitEntry\n+\tError       error\n+}\n+\n+type CommitEntry struct {\n+\tCommit               string\n+\tAbbreviatedCommit    string `json:\"abbreviated_commit\"`\n+\tTree                 string\n+\tAbbreviatedTree      string `json:\"abbreviated_tree\"`\n+\tParent               string\n+\tAbbreviatedParent    string `json:\"abbreviated_parent\"`\n+\tRefs                 string\n+\tEncoding             string\n+\tSubject              string\n+\tSanitizedSubjectLine string `json:\"sanitized_subject_line\"`\n+\tBody                 string\n+\tCommitNotes          string `json:\"commit_notes\"`\n+\tVerificationFlag     string `json:\"verification_flag\"`\n+\tShortMsg             string\n+\tSigner               string\n+\tSignerKey            string `json:\"signer_key\"`\n+\tAuthor               Person `json:\"author,omitempty\"`\n+\tCommiter             Person `json:\"commiter,omitempty\"`\n+}\n+\n+type Person struct {\n+\tName  string `json:\"name\"`\n+\tEmail string `json:\"email\"`\n+\tDate  string `json:\"date\"` // this could maybe be an actual time.Time\n+}\n+\n+var (\n+\tprettyLogSubject = `--pretty=format:%s`\n+\tprettyLogBody    = `--pretty=format:%b`\n+\tprettyLogCommit  = `--pretty=format:%H`\n+\tprettyLogFormat  = `--pretty=format:{\"commit\": \"%H\", \"abbreviated_commit\": \"%h\", \"tree\": \"%T\", \"abbreviated_tree\": \"%t\", \"parent\": \"%P\", \"abbreviated_parent\": \"%p\", \"refs\": \"%D\", \"encoding\": \"%e\", \"sanitized_subject_line\": \"%f\", \"commit_notes\": \"%N\", \"verification_flag\": \"%G?\", \"signer\": \"%GS\", \"signer_key\": \"%GK\", \"author\": { \"name\": \"%aN\", \"email\": \"%aE\", \"date\": \"%aD\" }, \"commiter\": { \"name\": \"%cN\", \"email\": \"%cE\", \"date\": \"%cD\" }}`\n+)\n+\n+func GitLogCommit(commit string) (*CommitEntry, error) {\n+\tbuf := bytes.NewBuffer([]byte{})\n+\tcmd := exec.Command(\"git\", \"log\", \"-1\", prettyLogFormat, commit)\n+\tcmd.Stdout = buf\n+\tcmd.Stderr = os.Stderr\n+\n+\tif err := cmd.Run(); err != nil {\n+\t\tlog.Println(strings.Join(cmd.Args, \" \"))\n+\t\treturn nil, err\n+\t}\n+\tc := CommitEntry{}\n+\toutput := buf.Bytes()\n+\tif err := json.Unmarshal(output, &c); err != nil {\n+\t\tfmt.Println(string(output))\n+\t\treturn nil, err\n+\t}\n+\toutput, err := exec.Command(\"git\", \"log\", \"-1\", prettyLogSubject, commit).Output()\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\tc.Subject = strings.TrimSpace(string(output))\n+\toutput, err = exec.Command(\"git\", \"log\", \"-1\", prettyLogBody, commit).Output()\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\tc.Body = strings.TrimSpace(string(output))\n+\treturn &c, nil\n+}\n+\n+func GitCommits(commitrange string) ([]CommitEntry, error) {\n+\toutput, err := exec.Command(\"git\", \"log\", prettyLogCommit, commitrange).Output()\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\tcommitHashes := strings.Split(strings.TrimSpace(string(output)), \"\\n\")\n+\tcommits := make([]CommitEntry, len(commitHashes))\n+\tfor i, commitHash := range commitHashes {\n+\t\tc, err := GitLogCommit(commitHash)\n+\t\tif err != nil {\n+\t\t\treturn commits, err\n+\t\t}\n+\t\tcommits[i] = *c\n+\t}\n+\treturn commits, nil\n+}\n+\n+func GitFetchHeadCommit() (string, error) {","path":".tools/validate.go","position":183,"original_position":183,"commit_id":"583b15f7c5b1fed268f4a197d2ea36be97e47d8a","original_commit_id":"583b15f7c5b1fed268f4a197d2ea36be97e47d8a","user":{"login":"wking","id":209920,"avatar_url":"https://avatars.githubusercontent.com/u/209920?v=3","gravatar_id":"","url":"https://api.github.com/users/wking","html_url":"https://github.com/wking","followers_url":"https://api.github.com/users/wking/followers","following_url":"https://api.github.com/users/wking/following{/other_user}","gists_url":"https://api.github.com/users/wking/gists{/gist_id}","starred_url":"https://api.github.com/users/wking/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wking/subscriptions","organizations_url":"https://api.github.com/users/wking/orgs","repos_url":"https://api.github.com/users/wking/repos","events_url":"https://api.github.com/users/wking/events{/privacy}","received_events_url":"https://api.github.com/users/wking/received_events","type":"User","site_admin":false},"body":"This doesn't seem to be used anywhere. Maybe leave it out until we need it?","created_at":"2015-09-09T19:30:10Z","updated_at":"2015-09-09T19:30:10Z","html_url":"https://github.com/opencontainers/specs/pull/167#discussion_r39085634","pull_request_url":"https://api.github.com/repos/opencontainers/specs/pulls/167","_links":{"self":{"href":"https://api.github.com/repos/opencontainers/specs/pulls/comments/39085634"},"html":{"href":"https://github.com/opencontainers/specs/pull/167#discussion_r39085634"},"pull_request":{"href":"https://api.github.com/repos/opencontainers/specs/pulls/167"}}},"pull_request":{"url":"https://api.github.com/repos/opencontainers/specs/pulls/167","id":44587779,"html_url":"https://github.com/opencontainers/specs/pull/167","diff_url":"https://github.com/opencontainers/specs/pull/167.diff","patch_url":"https://github.com/opencontainers/specs/pull/167.patch","issue_url":"https://api.github.com/repos/opencontainers/specs/issues/167","number":167,"state":"open","locked":false,"title":".tools: repo validation tool","user":{"login":"vbatts","id":67049,"avatar_url":"https://avatars.githubusercontent.com/u/67049?v=3","gravatar_id":"","url":"https://api.github.com/users/vbatts","html_url":"https://github.com/vbatts","followers_url":"https://api.github.com/users/vbatts/followers","following_url":"https://api.github.com/users/vbatts/following{/other_user}","gists_url":"https://api.github.com/users/vbatts/gists{/gist_id}","starred_url":"https://api.github.com/users/vbatts/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/vbatts/subscriptions","organizations_url":"https://api.github.com/users/vbatts/orgs","repos_url":"https://api.github.com/users/vbatts/repos","events_url":"https://api.github.com/users/vbatts/events{/privacy}","received_events_url":"https://api.github.com/users/vbatts/received_events","type":"User","site_admin":false},"body":"Initially only a DCO validation for travis\r\n\r\nhttps://github.com/opencontainers/specs/issues/90\r\n\r\nSigned-off-by: Vincent Batts <vbatts@hashbangbash.com>","created_at":"2015-09-09T19:05:45Z","updated_at":"2015-09-09T19:30:10Z","closed_at":null,"merged_at":null,"merge_commit_sha":"eb314930b7f4261d5efa295a352e96b613ed4ed1","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/opencontainers/specs/pulls/167/commits","review_comments_url":"https://api.github.com/repos/opencontainers/specs/pulls/167/comments","review_comment_url":"https://api.github.com/repos/opencontainers/specs/pulls/comments{/number}","comments_url":"https://api.github.com/repos/opencontainers/specs/issues/167/comments","statuses_url":"https://api.github.com/repos/opencontainers/specs/statuses/583b15f7c5b1fed268f4a197d2ea36be97e47d8a","head":{"label":"vbatts:validate-dco","ref":"validate-dco","sha":"583b15f7c5b1fed268f4a197d2ea36be97e47d8a","user":{"login":"vbatts","id":67049,"avatar_url":"https://avatars.githubusercontent.com/u/67049?v=3","gravatar_id":"","url":"https://api.github.com/users/vbatts","html_url":"https://github.com/vbatts","followers_url":"https://api.github.com/users/vbatts/followers","following_url":"https://api.github.com/users/vbatts/following{/other_user}","gists_url":"https://api.github.com/users/vbatts/gists{/gist_id}","starred_url":"https://api.github.com/users/vbatts/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/vbatts/subscriptions","organizations_url":"https://api.github.com/users/vbatts/orgs","repos_url":"https://api.github.com/users/vbatts/repos","events_url":"https://api.github.com/users/vbatts/events{/privacy}","received_events_url":"https://api.github.com/users/vbatts/received_events","type":"User","site_admin":false},"repo":{"id":39969450,"name":"specs","full_name":"vbatts/specs","owner":{"login":"vbatts","id":67049,"avatar_url":"https://avatars.githubusercontent.com/u/67049?v=3","gravatar_id":"","url":"https://api.github.com/users/vbatts","html_url":"https://github.com/vbatts","followers_url":"https://api.github.com/users/vbatts/followers","following_url":"https://api.github.com/users/vbatts/following{/other_user}","gists_url":"https://api.github.com/users/vbatts/gists{/gist_id}","starred_url":"https://api.github.com/users/vbatts/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/vbatts/subscriptions","organizations_url":"https://api.github.com/users/vbatts/orgs","repos_url":"https://api.github.com/users/vbatts/repos","events_url":"https://api.github.com/users/vbatts/events{/privacy}","received_events_url":"https://api.github.com/users/vbatts/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/vbatts/specs","description":"Open Container Specifications","fork":true,"url":"https://api.github.com/repos/vbatts/specs","forks_url":"https://api.github.com/repos/vbatts/specs/forks","keys_url":"https://api.github.com/repos/vbatts/specs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/vbatts/specs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/vbatts/specs/teams","hooks_url":"https://api.github.com/repos/vbatts/specs/hooks","issue_events_url":"https://api.github.com/repos/vbatts/specs/issues/events{/number}","events_url":"https://api.github.com/repos/vbatts/specs/events","assignees_url":"https://api.github.com/repos/vbatts/specs/assignees{/user}","branches_url":"https://api.github.com/repos/vbatts/specs/branches{/branch}","tags_url":"https://api.github.com/repos/vbatts/specs/tags","blobs_url":"https://api.github.com/repos/vbatts/specs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/vbatts/specs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/vbatts/specs/git/refs{/sha}","trees_url":"https://api.github.com/repos/vbatts/specs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/vbatts/specs/statuses/{sha}","languages_url":"https://api.github.com/repos/vbatts/specs/languages","stargazers_url":"https://api.github.com/repos/vbatts/specs/stargazers","contributors_url":"https://api.github.com/repos/vbatts/specs/contributors","subscribers_url":"https://api.github.com/repos/vbatts/specs/subscribers","subscription_url":"https://api.github.com/repos/vbatts/specs/subscription","commits_url":"https://api.github.com/repos/vbatts/specs/commits{/sha}","git_commits_url":"https://api.github.com/repos/vbatts/specs/git/commits{/sha}","comments_url":"https://api.github.com/repos/vbatts/specs/comments{/number}","issue_comment_url":"https://api.github.com/repos/vbatts/specs/issues/comments{/number}","contents_url":"https://api.github.com/repos/vbatts/specs/contents/{+path}","compare_url":"https://api.github.com/repos/vbatts/specs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/vbatts/specs/merges","archive_url":"https://api.github.com/repos/vbatts/specs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/vbatts/specs/downloads","issues_url":"https://api.github.com/repos/vbatts/specs/issues{/number}","pulls_url":"https://api.github.com/repos/vbatts/specs/pulls{/number}","milestones_url":"https://api.github.com/repos/vbatts/specs/milestones{/number}","notifications_url":"https://api.github.com/repos/vbatts/specs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/vbatts/specs/labels{/name}","releases_url":"https://api.github.com/repos/vbatts/specs/releases{/id}","created_at":"2015-07-30T19:50:53Z","updated_at":"2015-07-30T19:50:54Z","pushed_at":"2015-09-09T19:05:35Z","git_url":"git://github.com/vbatts/specs.git","ssh_url":"git@github.com:vbatts/specs.git","clone_url":"https://github.com/vbatts/specs.git","svn_url":"https://github.com/vbatts/specs","homepage":"http://www.opencontainers.org","size":274,"stargazers_count":0,"watchers_count":0,"language":"Go","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"opencontainers:master","ref":"master","sha":"fd0ff56c8e37e0dd20e135458c72582efcbfd46a","user":{"login":"opencontainers","id":12563465,"avatar_url":"https://avatars.githubusercontent.com/u/12563465?v=3","gravatar_id":"","url":"https://api.github.com/users/opencontainers","html_url":"https://github.com/opencontainers","followers_url":"https://api.github.com/users/opencontainers/followers","following_url":"https://api.github.com/users/opencontainers/following{/other_user}","gists_url":"https://api.github.com/users/opencontainers/gists{/gist_id}","starred_url":"https://api.github.com/users/opencontainers/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/opencontainers/subscriptions","organizations_url":"https://api.github.com/users/opencontainers/orgs","repos_url":"https://api.github.com/users/opencontainers/repos","events_url":"https://api.github.com/users/opencontainers/events{/privacy}","received_events_url":"https://api.github.com/users/opencontainers/received_events","type":"Organization","site_admin":false},"repo":{"id":36960293,"name":"specs","full_name":"opencontainers/specs","owner":{"login":"opencontainers","id":12563465,"avatar_url":"https://avatars.githubusercontent.com/u/12563465?v=3","gravatar_id":"","url":"https://api.github.com/users/opencontainers","html_url":"https://github.com/opencontainers","followers_url":"https://api.github.com/users/opencontainers/followers","following_url":"https://api.github.com/users/opencontainers/following{/other_user}","gists_url":"https://api.github.com/users/opencontainers/gists{/gist_id}","starred_url":"https://api.github.com/users/opencontainers/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/opencontainers/subscriptions","organizations_url":"https://api.github.com/users/opencontainers/orgs","repos_url":"https://api.github.com/users/opencontainers/repos","events_url":"https://api.github.com/users/opencontainers/events{/privacy}","received_events_url":"https://api.github.com/users/opencontainers/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/opencontainers/specs","description":"Open Container Specifications","fork":false,"url":"https://api.github.com/repos/opencontainers/specs","forks_url":"https://api.github.com/repos/opencontainers/specs/forks","keys_url":"https://api.github.com/repos/opencontainers/specs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/opencontainers/specs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/opencontainers/specs/teams","hooks_url":"https://api.github.com/repos/opencontainers/specs/hooks","issue_events_url":"https://api.github.com/repos/opencontainers/specs/issues/events{/number}","events_url":"https://api.github.com/repos/opencontainers/specs/events","assignees_url":"https://api.github.com/repos/opencontainers/specs/assignees{/user}","branches_url":"https://api.github.com/repos/opencontainers/specs/branches{/branch}","tags_url":"https://api.github.com/repos/opencontainers/specs/tags","blobs_url":"https://api.github.com/repos/opencontainers/specs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/opencontainers/specs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/opencontainers/specs/git/refs{/sha}","trees_url":"https://api.github.com/repos/opencontainers/specs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/opencontainers/specs/statuses/{sha}","languages_url":"https://api.github.com/repos/opencontainers/specs/languages","stargazers_url":"https://api.github.com/repos/opencontainers/specs/stargazers","contributors_url":"https://api.github.com/repos/opencontainers/specs/contributors","subscribers_url":"https://api.github.com/repos/opencontainers/specs/subscribers","subscription_url":"https://api.github.com/repos/opencontainers/specs/subscription","commits_url":"https://api.github.com/repos/opencontainers/specs/commits{/sha}","git_commits_url":"https://api.github.com/repos/opencontainers/specs/git/commits{/sha}","comments_url":"https://api.github.com/repos/opencontainers/specs/comments{/number}","issue_comment_url":"https://api.github.com/repos/opencontainers/specs/issues/comments{/number}","contents_url":"https://api.github.com/repos/opencontainers/specs/contents/{+path}","compare_url":"https://api.github.com/repos/opencontainers/specs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/opencontainers/specs/merges","archive_url":"https://api.github.com/repos/opencontainers/specs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/opencontainers/specs/downloads","issues_url":"https://api.github.com/repos/opencontainers/specs/issues{/number}","pulls_url":"https://api.github.com/repos/opencontainers/specs/pulls{/number}","milestones_url":"https://api.github.com/repos/opencontainers/specs/milestones{/number}","notifications_url":"https://api.github.com/repos/opencontainers/specs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/opencontainers/specs/labels{/name}","releases_url":"https://api.github.com/repos/opencontainers/specs/releases{/id}","created_at":"2015-06-05T23:30:10Z","updated_at":"2015-09-09T15:45:51Z","pushed_at":"2015-09-09T19:05:45Z","git_url":"git://github.com/opencontainers/specs.git","ssh_url":"git@github.com:opencontainers/specs.git","clone_url":"https://github.com/opencontainers/specs.git","svn_url":"https://github.com/opencontainers/specs","homepage":"http://www.opencontainers.org","size":1152,"stargazers_count":421,"watchers_count":421,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":66,"mirror_url":null,"open_issues_count":46,"forks":66,"open_issues":46,"watchers":421,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/opencontainers/specs/pulls/167"},"html":{"href":"https://github.com/opencontainers/specs/pull/167"},"issue":{"href":"https://api.github.com/repos/opencontainers/specs/issues/167"},"comments":{"href":"https://api.github.com/repos/opencontainers/specs/issues/167/comments"},"review_comments":{"href":"https://api.github.com/repos/opencontainers/specs/pulls/167/comments"},"review_comment":{"href":"https://api.github.com/repos/opencontainers/specs/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/opencontainers/specs/pulls/167/commits"},"statuses":{"href":"https://api.github.com/repos/opencontainers/specs/statuses/583b15f7c5b1fed268f4a197d2ea36be97e47d8a"}}}}