์ ๋ Github Enterprise์์ ์๋ํํ ์ ์๊ธฐ๋ฅผ ํฌ๋งํ๋ฉด์ ์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๊ฐ์ง๊ณ ๋๊ณ ์์ต๋๋ค. ๊ฐ์ฅ ๋จผ์ ํด์ผ ํ ์ผ ์ค ํ๋๋ ์ ์ฅ์์ ๋ํ ๋ถ๊ธฐ ๋ณดํธ๋ฅผ ์ค์ ํ๋ ๊ฒ์ ๋๋ค. ๊ทธ๋ฌ๋ ํ ์คํธ ์ ์ฅ์(์ด๋ฏธ ๋ถ๊ธฐ ๋ณดํธ๊ฐ ์ค์ ๋์ด ์์)์์ ๋ถ๊ธฐ ๊ฐ์ฒด๋ฅผ ์์ํ ๋ ํ ๋น๋ ์ ์ผํ ์์ฑ์ "์ด๋ฆ"์ด๋ฉฐ ์ค๋ช ์์ ๋์ด๋ ๋๋ถ๋ถ์ ๋ฉ์๋๋ ๋ฌผ์ฒด:
import github
github.enable_console_debug_logging()
Github = github.Github
Github("myuser",
'mycreds',
base_url="https://ghe.workworkwork.com/api/v3")
master = g.get_user("myuser").get_repo('cookbook1').get_branch('master')
print master
print master.protection_url
์ถ๋ ฅ:
...
GET https://ghe.workworkwork.com/api/v3/repos/myuser/cookbook1/branches/master {'Authorization': 'Basic (login and password removed)', 'User-Agent': 'PyGithub/Python'} None ==> 200 {'status': '200 OK', 'content-length': '2925', 'x-github-media-type': 'github.v3', 'x-content-type-options': 'nosniff', 'content-security-policy': "default-src 'none'", 'access-control-expose-headers': 'ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval', 'x-github-request-id': '0ec6f4e8-2355-41dc-859d-0e7076a294b1', 'strict-transport-security': 'max-age=31536000; includeSubdomains', 'vary': 'Accept, Authorization, Cookie, X-GitHub-OTP', 'server': 'GitHub.com', 'access-control-allow-origin': '*', 'etag': '"965449c848ed3319d7e028c2a587f414"', 'x-xss-protection': '1; mode=block', 'cache-control': 'private, max-age=60, s-maxage=60', 'date': 'Thu, 16 Aug 2018 23:41:19 GMT', 'x-frame-options': 'deny', 'x-oauth-scopes': 'admin:org_hook, admin:pre_receive_hook, admin:repo_hook, repo', 'content-type': 'application/json; charset=utf-8', 'x-accepted-oauth-scopes': ''} {"name":"master","commit":{"sha":"3548233b67c8ec4cad703ff947fa0ba022df7c66","commit":{"author":{"name":"Garrett Anderson","email":"[email protected]","date":"2018-08-16T18:44:44Z"},"committer":{"name":"GitHub Enterprise","email":"[email protected]","date":"2018-08-16T18:44:44Z"},"message":"Merge pull request #8 from myuser/newfork\n\nwhat","tree":{"sha":"44b3a300094b2ccbbfb79ea7df5f82e627c12b42","url":"https://ghe.workworkwork.com/api/v3/repos/myuser/cookbook1/git/trees/44b3a300094b2ccbbfb79ea7df5f82e627c12b42"},"url":"https://ghe.workworkwork.com/api/v3/repos/myuser/cookbook1/git/commits/3548233b67c8ec4cad703ff947fa0ba022df7c66","comment_count":0},"url":"https://ghe.workworkwork.com/api/v3/repos/myuser/cookbook1/commits/3548233b67c8ec4cad703ff947fa0ba022df7c66","html_url":"https://ghe.workworkwork.com/myuser/cookbook1/commit/3548233b67c8ec4cad703ff947fa0ba022df7c66","comments_url":"https://ghe.workworkwork.com/api/v3/repos/myuser/cookbook1/commits/3548233b67c8ec4cad703ff947fa0ba022df7c66/comments","author":{"login":"myuser","id":593,"avatar_url":"https://ghe.workworkwork.com/avatars/u/593?","gravatar_id":"","url":"https://ghe.workworkwork.com/api/v3/users/myuser","html_url":"https://ghe.workworkwork.com/myuser","followers_url":"https://ghe.workworkwork.com/api/v3/users/myuser/followers","following_url":"https://ghe.workworkwork.com/api/v3/users/myuser/following{/other_user}","gists_url":"https://ghe.workworkwork.com/api/v3/users/myuser/gists{/gist_id}","starred_url":"https://ghe.workworkwork.com/api/v3/users/myuser/starred{/owner}{/repo}","subscriptions_url":"https://ghe.workworkwork.com/api/v3/users/myuser/subscriptions","organizations_url":"https://ghe.workworkwork.com/api/v3/users/myuser/orgs","repos_url":"https://ghe.workworkwork.com/api/v3/users/myuser/repos","events_url":"https://ghe.workworkwork.com/api/v3/users/myuser/events{/privacy}","received_events_url":"https://ghe.workworkwork.com/api/v3/users/myuser/received_events","type":"User","site_admin":true},"committer":null,"parents":[{"sha":"067642113fe45ee77160ee28cafc4870f2c06b6a","url":"https://ghe.workworkwork.com/api/v3/repos/myuser/cookbook1/commits/067642113fe45ee77160ee28cafc4870f2c06b6a","html_url":"https://ghe.workworkwork.com/myuser/cookbook1/commit/067642113fe45ee77160ee28cafc4870f2c06b6a"},{"sha":"3a9479c605617a9035cd986a4bf77880ac5dde64","url":"https://ghe.workworkwork.com/api/v3/repos/myuser/cookbook1/commits/3a9479c605617a9035cd986a4bf77880ac5dde64","html_url":"https://ghe.workworkwork.com/myuser/cookbook1/commit/3a9479c605617a9035cd986a4bf77880ac5dde64"}]},"_links":{"self":"https://ghe.workworkwork.com/api/v3/repos/myuser/cookbook1/branches/master","html":"https://ghe.workworkwork.com/myuser/cookbook1/tree/master"}}
Branch(name="master")
AttributeError: 'Branch' object has no attribute 'protection_url'
๊ฑฐ๊ธฐ์๋ ์ด๋ค ๋ฐฉ๋ฒ๋ ์์ต๋๋ค.
print master.get_required_status_checks()
OUTPUT:
AttributeError: 'Branch' object has no attribute 'get_required_status_checks'
๋๋ ์ด ์์ฑ:
print master.protection
OUTPUT:
return self._protected.value
AttributeError: 'Branch' object has no attribute '_protected'
๊ฐ์ฒด๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
print dir(master)
OUTPUT:
['CHECK_AFTER_INIT_FLAG', '_GithubObject__makeSimpleAttribute', '_GithubObject__makeSimpleListAttribute', '_GithubObject__makeTransformedAttribute', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_commit', '_completeIfNeeded', '_headers', '_initAttributes', '_makeBoolAttribute', '_makeClassAttribute', '_makeDatetimeAttribute', '_makeDictAttribute', '_makeDictOfStringsToClassesAttribute', '_makeIntAttribute', '_makeListOfClassesAttribute', '_makeListOfIntsAttribute', '_makeListOfListOfStringsAttribute', '_makeListOfStringsAttribute', '_makeStringAttribute', '_makeTimestampAttribute', '_name', '_parentUrl', '_rawData', '_requester', '_storeAndUseAttributes', '_useAttributes', 'commit', 'contexts', 'enforcement_level', 'etag', 'get__repr__', 'last_modified', 'name', 'protected', 'raw_data', 'raw_headers', 'setCheckAfterInitFlag']
๊ฐ์ฒด๊ฐ ์ ๋๋ก ์์ฑ๋์ง ์์ ๊ฒ ๊ฐ์ต๋๋ค.
์ด๋ค PyGithub ๋ฒ์ ์ ์ฌ์ฉํ๊ณ ์์ต๋๊น? #790์ ์ต๊ทผ์ ๋ณํฉ๋์์ง๋ง ์์ง ๋ฆด๋ฆฌ์ค๋์ง ์์์ต๋๋ค.
์ ๋ PyGithub-1.40๊ณผ Github Enterprise 2.8.6์ ์ฌ์ฉํ๊ณ ์์ต๋๋ค.
https://github.com/PyGithub/PyGithub/pull/790์ ํ์คํ ์ ๋งํด ๋ณด์ด์ง๋ง ์ฐ๋ฆฌ ํ์ฌ์ GHE ๋ฒ์ ์ด ์ฝ๊ฐ ์ต์ ๋ฒ์ ์ด๋ฏ๋ก API ๋ณ๊ฒฝ์ด ์ํฅ์ ๋ฏธ์น๋์ง ํ์คํ์ง ์์ต๋๋ค.
์ ๋ฐ์ดํธ: ์ต์ ๋ง์คํฐ๋ฅผ ํ ์คํธํ๋ ์ค
์ต์ ๋ณํฉ์ผ๋ก ๋๋ฝ๋ ์์ฑ ๋ฐ ๋ฉ์๋ ๋ฌธ์ ๊ฐ ํด๊ฒฐ๋ ๊ฒ ๊ฐ์ง๋ง ๋ด API ๋ฒ์ ๊ณผ ํธํ๋์ง ์๋ ๊ฒ ๊ฐ์ต๋๋ค.
master = g.get_user("ganderson").get_repo('cookbook1').get_branch('master')
print master.protection_url
master.get_protection()
์ฐ์ถ:
None
Traceback (most recent call last):
File "test_pygithub.py", line 12, in <module>
master.get_protection()
File "/Users/me/tmp/ghe-python/venv/lib/python2.7/site-packages/github/Branch.py", line 102, in get_protection
self.protection_url
File "/Users/me/tmp/ghe-python/venv/lib/python2.7/site-packages/github/Requester.py", line 260, in requestJsonAndCheck
return self.__check(*self.requestJson(verb, url, parameters, headers, input, self.__customConnection(url)))
File "/Users/me/tmp/ghe-python/venv/lib/python2.7/site-packages/github/Requester.py", line 276, in __customConnection
if not url.startswith("/"):
AttributeError: 'NoneType' object has no attribute 'startswith'
์ด๊ฒ์ ๋ณ๊ฐ์ ๋ฌธ์ ์ธ ๊ฒ ๊ฐ์ผ๋ ์ด ํฐ์ผ์ ๋ซ๊ฒ ์ต๋๋ค. ๊ฐ์ฌํฉ๋๋ค.
๋ง์คํฐ๊ฐ ๊ณ ์ฅ ๋๋ฉด ์๋ก์ด ๋ฌธ์ ๋ฅผ ์ ๊ธฐํ์ญ์์ค.
์ด๊ฒ์ ๋ํ ํ์ค api.github.com์ 1.43.2์์ ๋์๊ฒ ๊นจ์ง ๊ฒ ๊ฐ์ต๋๋ค.
๋ณดํธ๋๋ 2๊ฐ์ ๋ถ๊ธฐ๊ฐ ์์ง๋ง ๋ถ๊ธฐ ๊ฐ์ฒด๋ protection_url์ ์ค์ ํ์ง ์์ต๋๋ค.
๋ณดํธ URL์ด ์ค์ ๋์ง ์๊ณ URL์ ๋น๋ํ๋ ๋ฐ ์ฌ์ฉํ๋ ๋ชจ๋ ํญ๋ชฉ์ ๋ํด ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.
[
{
"name": "develop",
"commit": {
"sha": "XXXX",
"url": "https://api.github.com/repos/X/X/commits/X"
}
},
{
"name": "master",
"commit": {
"sha": "X",
"url": "https://api.github.com/repos/X/X/commits/X"
}
}
]
branches?protected=true
์ฌ์ฉํ์ฌ ์ปฌํ๋ฉด ํ์ด๋ก๋์ protection_url์ด ๋ฐํ๋ฉ๋๋ค.
[
{
"name": "develop",
"commit": {
"sha": "X",
"url": "https://api.github.com/repos/X/X/commits/X"
},
"protected": true,
"protection": {
"enabled": true,
"required_status_checks": {
"enforcement_level": "off",
"contexts": [
]
}
},
"protection_url": "https://api.github.com/repos/X/X/branches/develop/protection"
},
{
"name": "master",
"commit": {
"sha": "X",
"url": "https://api.github.com/repos/X/X/commits/X"
},
"protected": true,
"protection": {
"enabled": true,
"required_status_checks": {
"enforcement_level": "off",
"contexts": [
]
}
},
"protection_url": "https://api.github.com/repos/X/X/branches/master/protection"
}
]
๋ง์ง๋ง ๋๊ธ ์์ฑ์๋ฅผ ๋์ ํ์ฌ ๋ฌธ์ ๋ฅผ ๋ค์ ์ฝ๋๋ค.
get_branch("master")
๋จ์ผ ์ง์ ์ ์ป์ ์ ์์ต๋๊น? ๋ฌธ์ ๋ ํ์ฌ get_branches
๋ฉ์๋๊ฐ protected
๋งค๊ฐ๋ณ์๋ฅผ ์ง์ํ์ง ์๊ธฐ ๋๋ฌธ์ผ ์ ์์ต๋๋ค.
์ด ๋ฌธ์ ๋ฅผ ๋๋ฒ๊น ํ๋ ๋ฐ ์ฝ๊ฐ์ ์๊ฐ์ ํ ์ ํ๊ณ GitHub v3 API์ ๋ํด ๋ฒ๊ทธ๋ฅผ ์ ๊ธฐํ์ต๋๋ค.
๋ถ๊ธฐ ๋ณดํธ ์ฝ๋๋ฅผ ๊ฐ๋ฐํ ๋ ์ด ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค. -- Repository.get_branches()๊ฐ ๋ชฉ๋ก์ ๋ฐํํฉ๋๋ค.
๊ทธ๋ฌ๋ GitHub๋ Branch์ ๋ฉ์๋๊ฐ ์ ์ฉํ ๋งํผ ์ถฉ๋ถํ ์ ๋ณด๋ฅผ ๋ฐํํ์ง ์์ต๋๋ค. ๋ณดํธ ๊ธฐ๋ฅ์ ํ์ฉํ๋ ค๋ฉด Repository.get_branch('name')๋ฅผ ํธ์ถํด์ผ ํฉ๋๋ค.
๋ถ๊ธฐ ๋ณดํธ ์ฝ๋๋ฅผ ๊ฐ๋ฐํ ๋ ์ด ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค. -- Repository.get_branches()๊ฐ ๋ชฉ๋ก์ ๋ฐํํฉ๋๋ค.
๊ทธ๋ฌ๋ GitHub๋ Branch์ ๋ฉ์๋๊ฐ ์ ์ฉํ ๋งํผ ์ถฉ๋ถํ ์ ๋ณด๋ฅผ ๋ฐํํ์ง ์์ต๋๋ค. ๋ณดํธ ๊ธฐ๋ฅ์ ํ์ฉํ๋ ค๋ฉด Repository.get_branch('name')๋ฅผ ํธ์ถํด์ผ ํฉ๋๋ค.
๊ฐ์ฌํฉ๋๋ค. ์ค๋ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์ต๋๋ค. ์ด ํด๊ฒฐ ๋ฐฉ๋ฒ์ ํตํด ๋ง์ ์๊ฐ๊ณผ ์ข์ ์ ์ค์ผ ์ ์์์ต๋๋ค!
์ด ๋ฌธ์ ๋ ์ต๊ทผ ํ๋์ด ์์๊ธฐ ๋๋ฌธ์ ์๋์ผ๋ก ์ค๋๋ ๊ฒ์ผ๋ก ํ์๋์์ต๋๋ค. ๋ ์ด์ ํ๋์ด ์์ผ๋ฉด ํ์๋ฉ๋๋ค. ๊ทํ์ ๊ธฐ์ฌ์ ๊ฐ์ฌ๋๋ฆฝ๋๋ค.
๊ฐ์ฅ ์ ์ฉํ ๋๊ธ
๋ถ๊ธฐ ๋ณดํธ ์ฝ๋๋ฅผ ๊ฐ๋ฐํ ๋ ์ด ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค. -- Repository.get_branches()๊ฐ ๋ชฉ๋ก์ ๋ฐํํฉ๋๋ค.
๊ทธ๋ฌ๋ GitHub๋ Branch์ ๋ฉ์๋๊ฐ ์ ์ฉํ ๋งํผ ์ถฉ๋ถํ ์ ๋ณด๋ฅผ ๋ฐํํ์ง ์์ต๋๋ค. ๋ณดํธ ๊ธฐ๋ฅ์ ํ์ฉํ๋ ค๋ฉด Repository.get_branch('name')๋ฅผ ํธ์ถํด์ผ ํฉ๋๋ค.