33This will create symlinks or clone git repos as needed.
44"""
55
6+ # Use this PR for testing to see all four types of change at once:
7+ # https://github.com/danvk/test-repo/pull/2/
8+
69import atexit
710from collections import OrderedDict
811import errno
1417
1518from github import Github , UnknownObjectException
1619
20+ from util import memoize
21+
22+
23+ class GitHubDiff (object ):
24+ def __init__ (self , pr , github_file ):
25+ self ._pr = pr
26+ self ._file = github_file
27+ self .type = {
28+ 'modified' : 'change' ,
29+ 'renamed' : 'move' ,
30+ 'added' : 'add' ,
31+ 'removed' : 'delete'
32+ }[github_file .status ]
33+ self ._a_path = None
34+ self ._b_path = None
35+
36+ @property
37+ def a (self ):
38+ if self .type == 'move' :
39+ return self ._file .raw_data ['previous_filename' ]
40+ elif self .type == 'add' :
41+ return None
42+ else :
43+ return self ._file .filename
1744
18- temp_dirs = []
45+ @property
46+ def b (self ):
47+ if self .type == 'delete' :
48+ return None
49+ else :
50+ return self ._file .filename
1951
20- _github_memo = None
21- def _github ():
22- global _github_memo
23- if _github_memo : return _github_memo
52+ # NB: these are @memoize'd via fetch()
53+ @property
54+ def a_path (self ):
55+ return fetch (self ._pr .base .repo , self .a , self ._pr .base .sha )
56+
57+ @property
58+ def b_path (self ):
59+ return fetch (self ._pr .head .repo , self .b , self ._pr .head .sha )
60+
61+ def __repr__ (self ):
62+ return '%s (%s)' % (self .a or self .b , self .type )
63+
64+ # TOOD: diffstats are accessible via file.{changes,additions,deletions}
65+
66+
67+ @memoize
68+ def fetch (repo , filename , sha ):
69+ if filename is None : return None
70+ data = repo .get_file_contents (filename , sha ).decoded_content
71+ _ , ext = os .path .splitext (filename )
72+ fd , path = tempfile .mkstemp (suffix = ext )
73+ open (path , 'wb' ).write (data )
74+ return path
2475
76+
77+ @memoize
78+ def _github ():
2579 def simple_fallback (message = None ):
26- global _github_memo
2780 if message : sys .stderr .write (message + '\n ' )
28- _github_memo = Github ()
29- return _github_memo
81+ return Github ()
3082
3183 github_rc = os .path .join (os .path .expanduser ('~' ), '.githubrc' )
3284 if os .path .exists (github_rc ):
@@ -45,53 +97,20 @@ def simple_fallback(message=None):
4597 return simple_fallback ('.githubrc missing user.login. Using anonymous API access.' )
4698 if not kvs .get ('user.password' ):
4799 return simple_fallback ('.githubrc missing user.password. Using anonymous API access.' )
48- _github_memo = Github (kvs ['user.login' ], kvs ['user.password' ])
100+ return Github (kvs ['user.login' ], kvs ['user.password' ])
49101 else :
50- _github_memo = Github ()
51- return _github_memo
52-
53-
54- def put_in_dir (dirname , filename , contents ):
55- """Puts contents into filename in dirname.
56-
57- This creates intermediate directories as needed, e.g. if filename is a/b/c.
58- """
59- d = os .path .join (dirname , os .path .dirname (filename ))
60- try :
61- os .makedirs (d )
62- except OSError , e :
63- # be happy if someone already created the path
64- if e .errno != errno .EEXIST :
65- raise
66- open (os .path .join (dirname , filename ), 'w' ).write (contents )
102+ return Github ()
67103
68104
69105def fetch_pull_request (owner , repo , num ):
70- """Pull down the pull request into two local directories.
71-
72- Returns before_dir, after_dir.
73- """
74- sys .stderr .write ('Loading pull request %s/%s#%s from github...\n ' % (owner , repo , num ))
106+ '''Return a list of Diff objects for a pull request.'''
107+ sys .stderr .write ('Loading pull request %s/%s#%s from github...\n ' % (
108+ owner , repo , num ))
75109 g = _github ()
76110 pr = g .get_user (owner ).get_repo (repo ).get_pull (num )
77- base_repo = pr .base .repo
78- head_repo = pr .head .repo
79- files = list (pr .get_files ())
80-
81- a_dir , b_dir = tempfile .mkdtemp (), tempfile .mkdtemp ()
82- temp_dirs .extend ([a_dir , b_dir ])
83-
84- for f in files :
85- sys .stderr .write (' %s...\n ' % f .filename )
86- if f .status == 'modified' or f .status == 'deleted' :
87- contents = base_repo .get_file_contents (f .filename , pr .base .sha ).decoded_content
88- put_in_dir (a_dir , f .filename , contents )
89- if f .status == 'modified' or f .status == 'added' :
90- contents = head_repo .get_file_contents (f .filename , pr .head .sha ).decoded_content
91- put_in_dir (b_dir , f .filename , contents )
92- # f.status == 'moved'?
93-
94- return a_dir , b_dir
111+ files = pr .get_files ()
112+
113+ return [GitHubDiff (pr , f ) for f in files ]
95114
96115
97116class NoRemoteError (Exception ):
0 commit comments