├── .gitignore ├── .travis.yml ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── lib ├── pully.rb └── pully │ └── version.rb ├── logo.png ├── pully.gemspec └── spec ├── assets ├── gh_key.private ├── gh_key.public ├── git_ssh └── test.yml ├── env_spec.rb ├── lib_spec.rb └── lib_test_spec.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | *.bundle 19 | *.so 20 | *.o 21 | *.a 22 | mkmf.log 23 | .run 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.1.2 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in pully.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Seo Townsend 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![pully: A ruby library for managing GitHub pull requests](https://raw.githubusercontent.com/sotownsend/pully/master/logo.png) 2 | 3 | [![Gem Version](https://badge.fury.io/rb/pully.svg)](http://badge.fury.io/rb/pully) 4 | [![Build Status](https://travis-ci.org/sotownsend/Pully.svg?branch=master)](https://travis-ci.org/sotownsend/Pully) 5 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/sotownsend/pully/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 6 | [![License](http://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://github.com/sotownsend/pully/blob/master/LICENSE) 7 | 8 | # What is this? 9 | Pully is a ruby library for managing GitHub pull requests; purpose built for our continuous integration & deployment infrastructure at [Fittr®](http://www.fittr.com). 10 | 11 | # Selling points 12 | 1. It's easy to use 13 | 2. It has full code coverage on tests involving GitHub's API 14 | 3. While it uses the pull request interface on GitHub, **it does not use the big green button** to perform a merge. 15 | 16 | # Basic usage 17 | ```ruby 18 | require 'pully' 19 | 20 | #Create a new pully object, each pully object targets (1) repository. 21 | pully = Pully.new(user:"github_username", pass:"github_password", repo:"my_repository") 22 | 23 | #Get a list of all open pull requests (An array of pull numbers) 24 | open = pully.pull_requests 25 | 26 | #Get a list of all open pull requests marked as pending or success 27 | open_and_pending_or_success = pully.open_or_pending_pull_requests 28 | 29 | #Create a new pull request to merge 'my_branch' into 'master' with the title 'My pull request' and the message 'Hey XXX...' 30 | pull_number = pully.create_pull_request(from:"my_branch", to:"master", subject:"My pull request", message:"Hey XXXX, can you merge this for me?") 31 | 32 | #Comment on that pull request 33 | pully.write_comment_to_pull_request(pull_number, "Test Comment") 34 | 35 | #Get all comments 36 | comments = pully.comments_for_pull_request(pull_number) 37 | 38 | #Get the SHA of the 'from' branch of a certain pull request 39 | pully.sha_for_pull_request(pull_number) 40 | 41 | #Set the status of a pull request to pending (Other options include 'error', 'failed', and 'success') 42 | pully.set_pull_request_status(pull_number, "pending") 43 | 44 | #Set the status of a pull request to ready 45 | pully.set_pull_request_status(pull_number, "success") 46 | 47 | #Merge the request (Will NOT use GitHub's pull request merge, will merge commits into history as-is) 48 | pully.merge_pull_request(pull_number) 49 | ``` 50 | 51 | # Organization Repositories 52 | If your repositories are not owner by you, i.e. they are owned by an organization or another user who has granted you permissions, you will need to 53 | pass the `owner` field for the other individual or organization. 54 | 55 | ```ruby 56 | #Create a new pully object, each pully object targets (1) repository in an organization. 57 | pully = Pully.new(user:"github_username", pass:"github_password", repo:"my_repository", owner:"my_organization") 58 | 59 | ``` 60 | 61 | ## Requirements 62 | 63 | - Ruby 2.1 or Higher 64 | 65 | ## Communication 66 | 67 | - If you **found a bug**, submit a pull request. 68 | - If you **have a feature request**, submit a pull request. 69 | - If you **want to contribute**, submit a pull request. 70 | 71 | ## Installation 72 | 73 | Run `sudo gem install pully` 74 | 75 | ## Known issues 76 | 77 | 1. GitHub does not register commits immediately after a push is received. Things like `sha_for_pull_request` will return old values if you don't wait 78 | several seconds 79 | 2. GitHub's status API for pull requests returns 'pending' even if the UI says 'success'. We account for this bug, but if it is fixed in the future, 80 | then our specs will catch it 81 | 82 | --- 83 | 84 | ## FAQ 85 | 86 | ### When should I use pully? 87 | 88 | When you want to automate GitHub pull requests. Pully provides the necessary facilities for you to authenticate and control GitHub pull requests in 89 | any way you wish. Duplicate the functionality of many popular CI solutions. 90 | 91 | ### What's Fittr? 92 | 93 | Fittr is a SaaS company that focuses on providing personalized workouts and health information to individuals and corporations through phenomenal interfaces and algorithmic data-collection and processing. 94 | 95 | * * * 96 | 97 | ### Creator 98 | 99 | - [Seo Townsend](http://github.com/sotownsend) ([@seotownsend](https://twitter.com/seotownsend)) 100 | 101 | ## License 102 | 103 | pully is released under the MIT license. See LICENSE for details. 104 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rspec/core/rake_task' 2 | require "bundler/gem_tasks" 3 | 4 | # Default directory to look in is `/specs` 5 | # Run with `rake spec` 6 | RSpec::Core::RakeTask.new(:spec) 7 | 8 | task :set_private_key_permissions do 9 | File.chmod(0400, "./spec/assets/gh_key.private") 10 | end 11 | 12 | task :spec => :set_private_key_permissions 13 | task :default => :spec 14 | -------------------------------------------------------------------------------- /lib/pully.rb: -------------------------------------------------------------------------------- 1 | require "pully/version" 2 | require 'octokit' 3 | require 'git' 4 | require 'tempfile' 5 | 6 | module Pully 7 | class Pully 8 | module Error 9 | class NonExistantRepository < StandardError; end 10 | class BadLogin < StandardError; end 11 | end 12 | 13 | def initialize(user:, pass:, repo:, owner:) 14 | @user = user 15 | @pass = pass 16 | @repo = repo 17 | @owner = owner 18 | @repo_selector = @owner ? "#{@owner}/#{@repo}" : "#{@user}/#{@repo}" 19 | 20 | @gh_client = Octokit::Client.new(:login => @user, :password => @pass) 21 | 22 | begin 23 | @gh_client.user #throw exception if auth is bad 24 | rescue Octokit::Unauthorized 25 | raise Error::BadLogin 26 | end 27 | raise Error::NonExistantRepository unless @gh_client.repository?(@repo_selector) 28 | end 29 | 30 | def create_pull_request(from:, to:, subject:, message:) 31 | @gh_client.create_pull_request(@repo_selector, to, from, subject, message)["number"] 32 | end 33 | 34 | def comments_for_pull_request pull_number 35 | @gh_client.issue_comments(@repo_selector, pull_number) 36 | end 37 | 38 | def write_comment_to_pull_request pull_number, comment 39 | @gh_client.add_comment(@repo_selector, pull_number, comment) 40 | end 41 | 42 | def sha_for_pull_request pull_number 43 | @gh_client.pull_request(@repo_selector, pull_number).head.sha 44 | end 45 | 46 | def pull_request_status(pull_number) 47 | sha = sha_for_pull_request pull_number 48 | @gh_client.combined_status(@repo_selector, sha)["state"] 49 | end 50 | 51 | def pull_requests 52 | return @gh_client.pull_requests(@repo_selector, :state => 'open').map{|e| e["number"]} 53 | end 54 | 55 | def open_or_pending_pull_requests 56 | requests = @gh_client.pull_requests(@repo_selector, :state => 'open').map{|e| e["number"]} 57 | requests.select!{|e| ["open", "pending"].include? pull_request_status(e) } 58 | requests 59 | end 60 | 61 | def set_pull_request_status(pull_number, status) 62 | sha = sha_for_pull_request pull_number 63 | @gh_client.create_status(@repo_selector, sha, status) 64 | end 65 | 66 | def merge_pull_request(pull_number, message='Merged Pull Request') 67 | @gh_client.merge_pull_request(@repo_selector, pull_number, message) 68 | end 69 | 70 | def pull_request_branches(pull_number) 71 | from_name = @gh_client.pull_request(@repo_selector, pull_number).head.ref 72 | to_name = @gh_client.pull_request(@repo_selector, pull_number).base.ref 73 | 74 | return {:from => from_name, :to => to_name} 75 | end 76 | 77 | def pull_request_is_open?(pull_number) 78 | @gh_client.pull_request(@repo_selector, pull_number)["state"] == 'open' 79 | end 80 | end 81 | 82 | module TestHelpers 83 | class Branch 84 | module Error 85 | class NoSuchRepository < StandardError; end 86 | class BadRepoSelector < StandardError; end 87 | class BadLogin < StandardError; end 88 | class NoSuchCloneURL< StandardError; end 89 | end 90 | 91 | #repo_selector is like 'my_user/repo' 92 | def initialize(user:, pass:, repo_selector:, clone_url:) 93 | @user = user 94 | @pass = pass 95 | @repo_selector = repo_selector 96 | @clone_url = clone_url 97 | 98 | #Setup the local git client 99 | ############################################################## 100 | Git.configure do |config| 101 | config.git_ssh = "./spec/assets/git_ssh" 102 | end 103 | ############################################################## 104 | 105 | clone_repo 106 | 107 | #Setup Octocat client 108 | ############################################################## 109 | @gh_client = Octokit::Client.new(:login => @user, :password => @pass) 110 | begin 111 | @gh_client.user #throw exception if auth is bad 112 | rescue Octokit::Unauthorized 113 | raise Error::BadLogin 114 | end 115 | 116 | begin 117 | @repo = @gh_client.repo(repo_selector) 118 | rescue ArgumentError 119 | raise Error::BadRepoSelector 120 | rescue Octokit::NotFound 121 | raise Error::NoSuchRepository 122 | end 123 | ############################################################## 124 | end 125 | 126 | #Will clone down repo and set @gh_client to the new repo 127 | def clone_repo 128 | #Create a temp path 129 | temp_file = Tempfile.new('pully') 130 | @path = temp_file.path 131 | temp_file.close 132 | temp_file.unlink 133 | 134 | #Clone repo 135 | begin 136 | @git_client = Git.clone(@clone_url, 'pully', :path => @path) 137 | @git_client.config("user.name", "pully-test-account") 138 | @git_client.config("user.email", "pully-test-account@gmail.com") 139 | rescue Git::GitExecuteError => e 140 | raise Error::NoSuchCloneURL if e.message =~ /fatal: repository.*does not exist/ 141 | raise "Unknown git execute error: #{e}" 142 | end 143 | end 144 | 145 | def create_branch(new_branch_name) 146 | #Checkout what ever real master is 147 | @git_client.branch(master_branch).checkout 148 | 149 | #Create a new branch 150 | @git_client.branch(new_branch_name) 151 | @git_client.branch(new_branch_name).checkout 152 | end 153 | 154 | def delete_branch(branch_name) 155 | @git_client.push("origin", ":#{branch_name}") 156 | end 157 | 158 | def list_branches 159 | #Re-pull repo from github 160 | clone_repo 161 | @git_client.branches.remote.map{|e| e.name} 162 | end 163 | 164 | def latest_sha branch_name 165 | @git_client.checkout(branch_name) 166 | @git_client.object("HEAD").sha 167 | end 168 | 169 | def latest_message branch_name 170 | @git_client.checkout(branch_name) 171 | @git_client.object("HEAD").message 172 | end 173 | 174 | def master_branch 175 | @repo.default_branch 176 | end 177 | 178 | #Create, commit, and push to github 179 | def commit_new_random_file(branch_name) 180 | #Create a new file 181 | Dir.chdir "#{@path}/pully" do 182 | File.write "#{branch_name}.#{SecureRandom.hex}", branch_name 183 | end 184 | 185 | #Commit 186 | @git_client.add(:all => true) 187 | @git_client.commit_all("New branch from Pully Test Suite #{SecureRandom.hex}") 188 | local_sha = @git_client.object("HEAD").sha 189 | @git_client.push("origin", branch_name) 190 | 191 | return local_sha 192 | end 193 | end 194 | end 195 | 196 | def self.new(user:, pass:, repo:, owner: nil) 197 | Pully.new(user: user, pass: pass, repo: repo, owner: owner) 198 | end 199 | end 200 | -------------------------------------------------------------------------------- /lib/pully/version.rb: -------------------------------------------------------------------------------- 1 | module Pully 2 | VERSION = "0.1.2" 3 | end 4 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seosgithub/Pully/783896fc30d85c03f06571aa99e1fdc3341848db/logo.png -------------------------------------------------------------------------------- /pully.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'pully/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "pully" 8 | spec.version = Pully::VERSION 9 | spec.authors = ["Seo Townsend"] 10 | spec.email = ["seotownsend@icloud.com"] 11 | spec.summary = %q{A ruby library for managing GitHub pull requests} 12 | spec.description = %q{Create your own pull request GitHub bot with ease} 13 | spec.homepage = "https://github.com/sotownsend/pully" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files -z`.split("\x0") 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_development_dependency "bundler", "~> 1.6" 22 | spec.add_development_dependency "rake", "~> 10.3" 23 | spec.add_development_dependency "rspec", "~> 3.2" 24 | spec.add_runtime_dependency "thor", "~> 0.19" 25 | spec.add_runtime_dependency "ghee", "~> 0.12.17" 26 | spec.add_runtime_dependency "octokit", "~> 3.0" 27 | spec.add_runtime_dependency "git", "~> 1.2.9.1" 28 | end 29 | -------------------------------------------------------------------------------- /spec/assets/gh_key.private: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAsz09BY1HZNOjMH3XmP0NArT6LWmvWOUHgUA2pVO9RrxekJwa 3 | OMoS3HQVDwONn1e7p8ROhSr7U9hYRO9ZvPR3pr8olboWDg8IyYXXiy+tY29tZXqb 4 | M1tAQinj2qlnZhEn9b1HqwrsLy/fYQaWvlhdEQ690+z7/pHQ1Gy89Ewmvlr7Bl6D 5 | KYfe7JIAICYUQeZI2AyaJ3u9ltNE9kA6eHuVJUcFziQrQjKwuWoS0GWfhlbWnhRJ 6 | JHH/IEYDg8KUBjFu0nBs2ObLrhLu9XJsMIV8MrwCRL+8hr1VeLhydjP3qcQ44YQx 7 | urGmcbgX7km3dVFkPTPPbCxHhv1eVwK2Kqbp8wIDAQABAoIBAG2hNjKwIAvemsDt 8 | J7KOjKesNB0UuEiCBV4b/ZCPsISUcK64nB1CE9k9Myg0vynICqJporE3AfRdrlez 9 | okq9qHWYFGXIJPdCYNiKAbVDWyUnoVzhag42cQ5G+vPii7jVI4Dd3fkTPDT8bf+1 10 | fPw9pebqAleeat9hJr7XJ5exAD2K9s6Wg/VRnhfIFFna3gIb+h8rCVb94npEzfQw 11 | yZ9virDBjBIcUJuHKMZjDNHnxwMBFdSf+jwmWDnXmuiSZiCEoMQq0foRRg1lA8bg 12 | W6onEZ6pqZlNqr/uWf4/c5452CftGMIznIURPieXHhgodTFk3RfGE5+c7d4NXyqC 13 | lGaSvHECgYEA7qdDnCYafQ0jFzGKavIzWUJipCpHF7JwYjhPZs4oftxhKDGWi6Pq 14 | J2zutr+20jmvSRUXZsaz6ijTh6n6TbAs8uS2YBJnXWnZsMAqqIlbId86K9k68xat 15 | WeaN0sbgj6jl0A7l32tPEpKRBNhMkAggFpizbiK1byoc7e3jyP+YWHkCgYEAwERt 16 | SkxBIm8QG+CEqIhGxHgK6poWO5uzVYm9Xb0ODiprnIKt6KEDB+f+Fqhh2jbU2lVx 17 | tkrpX4OhYYSxp+vRtCs3ypfyDBVqwyRwPpActNynaEK9WeN4j5rEJEAB5NI7xK7n 18 | NixtYc2bfQdSdjD5ft13pA8e5QOx8ctGhDgSUssCgYAnJH39VW6Qxh/URQbb5Z+w 19 | ayf8nwaMB3NRWMUFKpkPvZud2QxFGPxlvQMmLCmTL2zlgrUU4VBO/qU7IhDwvw2Q 20 | RRz4guw7MQpIaCxW+jhdRNyopnd5uWVzUlI+mOnqBhyzVE41p8cPV8S7U7KzCPIi 21 | YqdDUCQL+I9IIvVyWSGmmQKBgHEf0a4Fb81yy2ebknIWvIyclM+0GaC426tYf7LH 22 | qZ1sGCTePrTBB6d/OGDccbaSzGoqG93F9cndCPb3vA+xEJGRXCI3hJLXhxmKNwCY 23 | iktgCJ+vpPlefXEA/qbVgke/6qquvhuByFnjP7kwtbQU3LdoVxyQSP02T1yU3cit 24 | tRpBAoGBAMrHa/DN4x5y+5fyn3qdg9R0mfDm/UVpXTepLIxRjNw/kcmCF8c7/LJZ 25 | fRs4sAKruDI1aA/PuydyQUl9Bwx+e4vmBTcBrLOGEcOmFz9Vbs4M5mSjUaDhdBxu 26 | iDG0r4yYvCv0N2InWdgOMU2JPaK78WnPKyiVR6P3uJjuE1Kmj75O 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /spec/assets/gh_key.public: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCzPT0FjUdk06MwfdeY/Q0CtPotaa9Y5QeBQDalU71GvF6QnBo4yhLcdBUPA42fV7unxE6FKvtT2FhE71m89HemvyiVuhYODwjJhdeLL61jb21lepszW0BCKePaqWdmESf1vUerCuwvL99hBpa+WF0RDr3T7Pv+kdDUbLz0TCa+WvsGXoMph97skgAgJhRB5kjYDJone72W00T2QDp4e5UlRwXOJCtCMrC5ahLQZZ+GVtaeFEkkcf8gRgODwpQGMW7ScGzY5suuEu71cmwwhXwyvAJEv7yGvVV4uHJ2M/epxDjhhDG6saZxuBfuSbd1UWQ9M89sLEeG/V5XArYqpunz Seo@imac.local 2 | -------------------------------------------------------------------------------- /spec/assets/git_ssh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #Execute SSH with the private keyfile stored in ./spec/assets/ 4 | #Used by lib_test_spec when pushing back to github 5 | ssh -i "./spec/assets/gh_key.private" "$@" 6 | -------------------------------------------------------------------------------- /spec/assets/test.yml: -------------------------------------------------------------------------------- 1 | github: 2 | user: pully-test-account 3 | pass: pully-test-account0 4 | repo: pully-test-account 5 | clone_url: git@github.com:pully-test-account/pully-test-account.git 6 | org_owner: pully-test-organization 7 | org_repo: pully-test-organization 8 | org_clone_url: git@github.com:pully-test-organization/pully-test-organization.git 9 | -------------------------------------------------------------------------------- /spec/env_spec.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | require 'ghee' 3 | require './lib/pully.rb' 4 | require 'securerandom' 5 | 6 | RSpec.describe "Test Environment" do 7 | it "Can write to a tempfile" do 8 | #Create a temp path 9 | temp_file = Tempfile.new('pully') 10 | @path = temp_file.path 11 | temp_file.close 12 | temp_file.unlink 13 | 14 | File.write temp_file, "test" 15 | str = File.read temp_file 16 | expect(str).to eq("test") 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/lib_spec.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | require 'ghee' 3 | require './lib/pully.rb' 4 | 5 | #Get github information 6 | def gh_info 7 | yaml = YAML.load_file("./spec/assets/test.yml") 8 | return yaml["github"] 9 | end 10 | 11 | RSpec.describe "Library" do 12 | def repo_selector(user:, repo:, owner:) 13 | return "#{user}/#{repo}" unless owner 14 | return "#{owner}/#{repo}" 15 | end 16 | 17 | it "Does throw an exception with INcorrect credentials while creating an object" do 18 | expect { pully = Pully.new(user: "abcdefgh", pass: "abcdefgh", repo: "abcdefgh") }.to raise_error(Pully::Pully::Error::BadLogin) 19 | end 20 | 21 | it "Does NOT throw an exception with correct credentials while creating an object" do 22 | pully = Pully.new(user: gh_info["user"], pass: gh_info["pass"], repo: gh_info["repo"]) 23 | end 24 | 25 | it "DOES throw an exception with correct credentials while creating an object but with a non-existant repository" do 26 | expect { pully = Pully.new(user: gh_info["user"], pass: gh_info["pass"], repo: SecureRandom.hex) }.to raise_error(Pully::Pully::Error::NonExistantRepository) 27 | end 28 | 29 | it "Does throw an exception with correct credentials while creating an object with an alternate owner but without specifying that owner" do 30 | expect { pully = Pully.new(user: gh_info["user"], pass: gh_info["pass"], repo: gh_info["org_repo"])}.to raise_error 31 | end 32 | 33 | it "Can call create a new pull request and returns an integer for the pull request #" do 34 | sleep 10 35 | #test branch creator 36 | new_branch_name = SecureRandom.hex 37 | th = Pully::TestHelpers::Branch.new(user: gh_info["user"], pass: gh_info["pass"], repo_selector: repo_selector(user: gh_info["user"], repo: gh_info["repo"], owner:nil), clone_url: gh_info["clone_url"]) 38 | th.create_branch(new_branch_name) 39 | th.commit_new_random_file(new_branch_name) 40 | 41 | pully = Pully.new(user: gh_info["user"], pass: gh_info["pass"], repo: gh_info["repo"]) 42 | n = pully.create_pull_request(from:new_branch_name, to:"master", subject:"My pull request", message:"Hey XXXX, can you merge this for me?") 43 | expect(n.class).to be(Fixnum) 44 | 45 | th.delete_branch(new_branch_name) 46 | end 47 | 48 | it "Can call create a new pull request for an organization repo and returns an integer for the pull request #" do 49 | sleep 10 50 | #test branch creator 51 | new_branch_name = SecureRandom.hex 52 | th = Pully::TestHelpers::Branch.new(user: gh_info["user"], pass: gh_info["pass"], repo_selector: repo_selector(user: gh_info["user"], repo: gh_info["org_repo"], owner:gh_info["org_owner"]), clone_url: gh_info["org_clone_url"]) 53 | th.create_branch(new_branch_name) 54 | th.commit_new_random_file(new_branch_name) 55 | 56 | pully = Pully.new(user: gh_info["user"], pass: gh_info["pass"], repo: gh_info["org_repo"], owner: gh_info["org_owner"]) 57 | n = pully.create_pull_request(from:new_branch_name, to:"master", subject:"My pull request", message:"Hey XXXX, can you merge this for me?") 58 | expect(n.class).to be(Fixnum) 59 | 60 | th.delete_branch(new_branch_name) 61 | end 62 | 63 | it "Can call create a new pull request and write a comment on that pull request" do 64 | #test branch creator 65 | new_branch_name = SecureRandom.hex 66 | th = Pully::TestHelpers::Branch.new(user: gh_info["user"], pass: gh_info["pass"], repo_selector: repo_selector(user: gh_info["user"], repo: gh_info["repo"], owner:nil), clone_url: gh_info["clone_url"]) 67 | th.create_branch(new_branch_name) 68 | th.commit_new_random_file(new_branch_name) 69 | 70 | pully = Pully.new(user: gh_info["user"], pass: gh_info["pass"], repo: gh_info["repo"]) 71 | pull_number = pully.create_pull_request(from:new_branch_name, to:"master", subject:"My pull request", message:"Hey XXXX, can you merge this for me?") 72 | 73 | before_create = pully.comments_for_pull_request(pull_number).length 74 | pully.write_comment_to_pull_request(pull_number, "Test Comment") 75 | after_create = pully.comments_for_pull_request(pull_number).length 76 | 77 | expect(after_create).to eq(before_create+1) 78 | 79 | th.delete_branch(new_branch_name) 80 | end 81 | 82 | it "Can call create a new pull request and write a comment on that pull request for an organization" do 83 | #test branch creator 84 | new_branch_name = SecureRandom.hex 85 | th = Pully::TestHelpers::Branch.new(user: gh_info["user"], pass: gh_info["pass"], repo_selector: repo_selector(user: gh_info["user"], repo: gh_info["org_repo"], owner:gh_info["org_owner"]), clone_url: gh_info["org_clone_url"]) 86 | th.create_branch(new_branch_name) 87 | th.commit_new_random_file(new_branch_name) 88 | 89 | pully = Pully.new(user: gh_info["user"], pass: gh_info["pass"], repo: gh_info["org_repo"], owner: gh_info["org_owner"]) 90 | pull_number = pully.create_pull_request(from:new_branch_name, to:"master", subject:"My pull request", message:"Hey XXXX, can you merge this for me?") 91 | 92 | before_create = pully.comments_for_pull_request(pull_number).length 93 | pully.write_comment_to_pull_request(pull_number, "Test Comment") 94 | after_create = pully.comments_for_pull_request(pull_number).length 95 | 96 | expect(after_create).to eq(before_create+1) 97 | 98 | th.delete_branch(new_branch_name) 99 | end 100 | 101 | it "Can call create a new pull request, get the SHA of that pull request, and compare it to the SHA of the from branch" do 102 | sleep 10 103 | #test branch creator 104 | new_branch_name = SecureRandom.hex 105 | th = Pully::TestHelpers::Branch.new(user: gh_info["user"], pass: gh_info["pass"], repo_selector: repo_selector(user: gh_info["user"], repo: gh_info["repo"], owner:nil), clone_url: gh_info["clone_url"]) 106 | th.create_branch(new_branch_name) 107 | local_sha = th.commit_new_random_file(new_branch_name) 108 | 109 | pully = Pully.new(user: gh_info["user"], pass: gh_info["pass"], repo: gh_info["repo"]) 110 | pull_number = pully.create_pull_request(from:new_branch_name, to:"master", subject:"My pull request", message:"Hey XXXX, can you merge this for me?") 111 | 112 | from_sha = pully.sha_for_pull_request(pull_number) 113 | 114 | expect(local_sha).to eq(from_sha) 115 | 116 | th.delete_branch(new_branch_name) 117 | end 118 | 119 | it "Changes the SHA when commits are added after a pull request" do 120 | sleep 10 121 | #test branch creator 122 | new_branch_name = SecureRandom.hex 123 | th = Pully::TestHelpers::Branch.new(user: gh_info["user"], pass: gh_info["pass"], repo_selector: repo_selector(user: gh_info["user"], repo: gh_info["repo"], owner:nil), clone_url: gh_info["clone_url"]) 124 | th.create_branch(new_branch_name) 125 | local_sha_a = th.commit_new_random_file(new_branch_name) 126 | 127 | pully = Pully.new(user: gh_info["user"], pass: gh_info["pass"], repo: gh_info["repo"]) 128 | pull_number = pully.create_pull_request(from:new_branch_name, to:"master", subject:"My pull request", message:"Hey XXXX, can you merge this for me?") 129 | 130 | #Get the SHA from the pull request 131 | from_sha_a = pully.sha_for_pull_request(pull_number) 132 | expect(local_sha_a).to eq(from_sha_a) 133 | 134 | local_sha_b = th.commit_new_random_file(new_branch_name) 135 | sleep 5 #Wait for GitHub to update 136 | from_sha_b = pully.sha_for_pull_request(pull_number) 137 | expect(local_sha_b).not_to eq(local_sha_a) 138 | expect(local_sha_b).to eq(from_sha_b) 139 | 140 | th.delete_branch(new_branch_name) 141 | end 142 | 143 | it "Can set the pull request status to success, assumes BUG IN GITHUB SETS TO PENDING" do 144 | sleep 10 145 | #test branch creator 146 | new_branch_name = SecureRandom.hex 147 | th = Pully::TestHelpers::Branch.new(user: gh_info["user"], pass: gh_info["pass"], repo_selector: repo_selector(user: gh_info["user"], repo: gh_info["repo"], owner:nil), clone_url: gh_info["clone_url"]) 148 | th.create_branch(new_branch_name) 149 | th.commit_new_random_file(new_branch_name) 150 | 151 | pully = Pully.new(user: gh_info["user"], pass: gh_info["pass"], repo: gh_info["repo"]) 152 | pull_number = pully.create_pull_request(from:new_branch_name, to:"master", subject:"My pull request", message:"Hey XXXX, can you merge this for me?") 153 | pully.sha_for_pull_request(pull_number) 154 | 155 | status = pully.pull_request_status(pull_number) 156 | expect(status).to eq("pending") 157 | 158 | pully.set_pull_request_status(pull_number, "success") 159 | status = pully.pull_request_status(pull_number) 160 | expect(status).to eq("success") 161 | 162 | th.delete_branch(new_branch_name) 163 | end 164 | 165 | it "Can get the from and to names of the pull request" do 166 | sleep 10 167 | #test branch creator 168 | new_branch_name = SecureRandom.hex 169 | th = Pully::TestHelpers::Branch.new(user: gh_info["user"], pass: gh_info["pass"], repo_selector: repo_selector(user: gh_info["user"], repo: gh_info["repo"], owner:nil), clone_url: gh_info["clone_url"]) 170 | th.create_branch(new_branch_name) 171 | th.commit_new_random_file(new_branch_name) 172 | 173 | pully = Pully.new(user: gh_info["user"], pass: gh_info["pass"], repo: gh_info["repo"]) 174 | pull_number = pully.create_pull_request(from:new_branch_name, to:"master", subject:"My pull request", message:"Hey XXXX, can you merge this for me?") 175 | branches = pully.pull_request_branches(pull_number) 176 | 177 | expect(branches[:from]).to eq(new_branch_name) 178 | expect(branches[:to]).to eq("master") 179 | 180 | th.delete_branch(new_branch_name) 181 | end 182 | 183 | it "Can get the from and to names of the pull request from an organization" do 184 | sleep 10 185 | #test branch creator 186 | new_branch_name = SecureRandom.hex 187 | th = Pully::TestHelpers::Branch.new(user: gh_info["user"], pass: gh_info["pass"], repo_selector: repo_selector(user: gh_info["user"], repo: gh_info["org_repo"], owner:gh_info["org_owner"]), clone_url: gh_info["org_clone_url"]) 188 | th.create_branch(new_branch_name) 189 | th.commit_new_random_file(new_branch_name) 190 | 191 | pully = Pully.new(user: gh_info["user"], pass: gh_info["pass"], repo: gh_info["org_repo"], owner: gh_info["org_owner"]) 192 | pull_number = pully.create_pull_request(from:new_branch_name, to:"master", subject:"My pull request", message:"Hey XXXX, can you merge this for me?") 193 | branches = pully.pull_request_branches(pull_number) 194 | 195 | expect(branches[:from]).to eq(new_branch_name) 196 | expect(branches[:to]).to eq("master") 197 | 198 | th.delete_branch(new_branch_name) 199 | end 200 | 201 | it "Can merge the pull request into master" do 202 | sleep 10 203 | #test branch creator 204 | new_branch_name = SecureRandom.hex 205 | th = Pully::TestHelpers::Branch.new(user: gh_info["user"], pass: gh_info["pass"], repo_selector: repo_selector(user: gh_info["user"], repo: gh_info["repo"], owner:nil), clone_url: gh_info["clone_url"]) 206 | th.create_branch(new_branch_name) 207 | last_sha = th.commit_new_random_file(new_branch_name) 208 | 209 | pully = Pully.new(user: gh_info["user"], pass: gh_info["pass"], repo: gh_info["repo"]) 210 | pull_number = pully.create_pull_request(from:new_branch_name, to:"master", subject:"My pull request", message:"Hey XXXX, can you merge this for me?") 211 | msg = SecureRandom.hex 212 | pully.merge_pull_request(pull_number, msg) 213 | 214 | #Commit message contains message given 215 | th = Pully::TestHelpers::Branch.new(user: gh_info["user"], pass: gh_info["pass"], repo_selector: repo_selector(user: gh_info["user"], repo: gh_info["repo"], owner:nil), clone_url: gh_info["clone_url"]) 216 | expect(th.latest_message("master") =~ /#{msg}/).not_to eq(nil) 217 | 218 | #We expect the pull request to be closed 219 | expect(pully.pull_request_is_open?(pull_number)).to eq(false) 220 | 221 | th.delete_branch(new_branch_name) 222 | end 223 | 224 | it "Can list pull requests" do 225 | sleep 10 226 | #test branch creator 227 | new_branch_name = SecureRandom.hex 228 | th = Pully::TestHelpers::Branch.new(user: gh_info["user"], pass: gh_info["pass"], repo_selector: repo_selector(user: gh_info["user"], repo: gh_info["repo"], owner:nil), clone_url: gh_info["clone_url"]) 229 | th.create_branch(new_branch_name) 230 | last_sha = th.commit_new_random_file(new_branch_name) 231 | 232 | pully = Pully.new(user: gh_info["user"], pass: gh_info["pass"], repo: gh_info["repo"]) 233 | pull_number = pully.create_pull_request(from:new_branch_name, to:"master", subject:"My pull request", message:"Hey XXXX, can you merge this for me?") 234 | 235 | expect(pully.pull_requests).to include(pull_number) 236 | 237 | th.delete_branch(new_branch_name) 238 | 239 | end 240 | 241 | it "Can list all success and pending pull requests" do 242 | sleep 10 243 | new_branch_name = SecureRandom.hex 244 | new_branch_name2 = SecureRandom.hex 245 | th = Pully::TestHelpers::Branch.new(user: gh_info["user"], pass: gh_info["pass"], repo_selector: repo_selector(user: gh_info["user"], repo: gh_info["repo"], owner:nil), clone_url: gh_info["clone_url"]) 246 | th.create_branch(new_branch_name) 247 | th.commit_new_random_file(new_branch_name) 248 | 249 | th.create_branch(new_branch_name2) 250 | th.commit_new_random_file(new_branch_name2) 251 | 252 | pully = Pully.new(user: gh_info["user"], pass: gh_info["pass"], repo: gh_info["repo"]) 253 | pull_number = pully.create_pull_request(from:new_branch_name, to:"master", subject:"My pull request", message:"Hey XXXX, can you merge this for me?") 254 | pull_number2 = pully.create_pull_request(from:new_branch_name2, to:"master", subject:"My pull request", message:"Hey XXXX, can you merge this for me?") 255 | pully.set_pull_request_status(pull_number, 'error') 256 | 257 | expect(pully.open_or_pending_pull_requests).not_to include(pull_number) 258 | expect(pully.open_or_pending_pull_requests).to include(pull_number2) 259 | 260 | th.delete_branch(new_branch_name) 261 | 262 | end 263 | end 264 | -------------------------------------------------------------------------------- /spec/lib_test_spec.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | require 'ghee' 3 | require './lib/pully.rb' 4 | require 'securerandom' 5 | 6 | 7 | RSpec.describe "Test Library" do 8 | #Get github information 9 | def gh_info 10 | yaml = YAML.load_file("./spec/assets/test.yml") 11 | return yaml["github"] 12 | end 13 | 14 | def repo_selector 15 | return "#{gh_info["user"]}/#{gh_info["repo"]}" 16 | end 17 | 18 | def rand_repo_selector 19 | return "#{gh_info["user"]}/#{SecureRandom.hex}" 20 | end 21 | 22 | it "Fails creation with incorrect credentials" do 23 | expect { Pully::TestHelpers::Branch.new(user: SecureRandom.hex, pass: SecureRandom.hex, repo_selector: repo_selector, clone_url: gh_info["clone_url"]) }.to raise_error(Pully::TestHelpers::Branch::Error::BadLogin) 24 | end 25 | 26 | it "does not fail creation with incorrect credentials" do 27 | Pully::TestHelpers::Branch.new(user: gh_info["user"], pass: gh_info["pass"], repo_selector: repo_selector, clone_url: gh_info["clone_url"]) 28 | end 29 | 30 | it "does fail creation with bad repo selector if passed a repo_selector without a /" do 31 | expect { Pully::TestHelpers::Branch.new(user: gh_info["user"], pass: gh_info["pass"], repo_selector: SecureRandom.hex, clone_url: gh_info["clone_url"]) }.to raise_error(Pully::TestHelpers::Branch::Error::BadRepoSelector) 32 | end 33 | 34 | it "does fail creation with unreal repository selector" do 35 | expect { Pully::TestHelpers::Branch.new(user: gh_info["user"], pass: gh_info["pass"], repo_selector: rand_repo_selector, clone_url: gh_info["clone_url"]) }.to raise_error(Pully::TestHelpers::Branch::Error::NoSuchRepository) 36 | end 37 | 38 | it "does fail creation with bad clone url" do 39 | expect { Pully::TestHelpers::Branch.new(user: gh_info["user"], pass: gh_info["pass"], repo_selector: rand_repo_selector, clone_url: "#{SecureRandom.hex}.com") }.to raise_error(Pully::TestHelpers::Branch::Error::NoSuchCloneURL) 40 | end 41 | 42 | it "Can create a new branch from our repository and delete it" do 43 | new_branch_name = SecureRandom.hex 44 | th = Pully::TestHelpers::Branch.new(user: gh_info["user"], pass: gh_info["pass"], repo_selector: repo_selector, clone_url: gh_info["clone_url"]) 45 | 46 | th.create_branch(new_branch_name) 47 | th.commit_new_random_file(new_branch_name) 48 | 49 | #Make sure the branch is listed in the names 50 | expect(th.list_branches).to include(new_branch_name) 51 | 52 | #Delete the branch 53 | th.delete_branch(new_branch_name) 54 | expect(th.list_branches).not_to include(new_branch_name) 55 | end 56 | 57 | it "Can create a new branch from our repository, grab something that looks like a SHA of the commit, and delete it" do 58 | new_branch_name = SecureRandom.hex 59 | th = Pully::TestHelpers::Branch.new(user: gh_info["user"], pass: gh_info["pass"], repo_selector: repo_selector, clone_url: gh_info["clone_url"]) 60 | 61 | th.create_branch(new_branch_name) 62 | sha = th.commit_new_random_file(new_branch_name) 63 | expect(sha.class).to be(String) 64 | expect(sha.length).to be(40) 65 | 66 | #Delete the branch 67 | th.delete_branch(new_branch_name) 68 | end 69 | end 70 | --------------------------------------------------------------------------------