├── .gitignore ├── README.md ├── git-gui.py ├── gitfiles.py ├── install-pyqt.sh └── requirements.py /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/linux,emacs,sublimetext 3 | 4 | ### Linux ### 5 | *~ 6 | 7 | # temporary files which can be created if a process still has a handle open of a deleted file 8 | .fuse_hidden* 9 | 10 | # KDE directory preferences 11 | .directory 12 | 13 | # Linux trash folder which might appear on any partition or disk 14 | .Trash-* 15 | 16 | 17 | ### Emacs ### 18 | # -*- mode: gitignore; -*- 19 | *~ 20 | \#*\# 21 | /.emacs.desktop 22 | /.emacs.desktop.lock 23 | *.elc 24 | auto-save-list 25 | tramp 26 | .\#* 27 | 28 | # Org-mode 29 | .org-id-locations 30 | *_archive 31 | 32 | # flymake-mode 33 | *_flymake.* 34 | 35 | # eshell files 36 | /eshell/history 37 | /eshell/lastdir 38 | 39 | # elpa packages 40 | /elpa/ 41 | 42 | # reftex files 43 | *.rel 44 | 45 | # AUCTeX auto folder 46 | /auto/ 47 | 48 | # cask packages 49 | .cask/ 50 | dist/ 51 | 52 | # Flycheck 53 | flycheck_*.el 54 | 55 | # server auth directory 56 | /server/ 57 | 58 | # projectiles files 59 | .projectile 60 | 61 | ### SublimeText ### 62 | # cache files for sublime text 63 | *.tmlanguage.cache 64 | *.tmPreferences.cache 65 | *.stTheme.cache 66 | 67 | # workspace files are user-specific 68 | *.sublime-workspace 69 | 70 | # project files should be checked into the repository, unless a significant 71 | # proportion of contributors will probably not be using SublimeText 72 | # *.sublime-project 73 | 74 | # sftp configuration file 75 | sftp-config.json 76 | 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # git-repo 2 | A python script that tells about github user or any particular repository! 3 | ## Getting Started 4 | These instructions will get you a copy of the project up and running on your local machine. 5 | First of all, download the zip file of the repository or clone it locally. 6 | ### Prerequisities 7 | To install all the prerequisities run 8 | ``` 9 | pip install -r requirements.py 10 | ``` 11 | To install PyQt package 12 | ``` 13 | $ chmod +x install-pyqt.sh 14 | $ ./install-pyqt.sh 15 | ``` 16 | in the project folder. 17 | ## How to use the script 18 | To execute the script just run 19 | ``` 20 | python git-gui.py 21 | ``` 22 | for the GUI version of the script or 23 | ``` 24 | python gitfiles.py 25 | ``` 26 | for the console version. 27 | ### Main functions : 28 | 1. A menu driven app that asks about you want user information or a repository information 29 | 2. If user is selected, then asks for username and provides user information. 30 | 3. If Repository is selected, then shows the repository related information. 31 | 32 | ### Screenshots 33 | Console version Screenshots
34 | 35 | 36 | 37 | # Please contribute 38 | Contribute to make a complete GIT Management System. Send pull requests. 39 | ## Things to do: 40 | 1. Add more fields in repository section. 41 | 2. Add more fields in user section. 42 | 3. Have a look to the Issue section for more. 43 | -------------------------------------------------------------------------------- /git-gui.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | import sys 4 | from bs4 import BeautifulSoup 5 | import requests 6 | from PyQt4.QtCore import * 7 | from PyQt4.QtGui import * 8 | 9 | repo_name = None 10 | user_name = None 11 | 12 | 13 | class Main(QMainWindow): 14 | def __init__(self, parent=None): 15 | QMainWindow.__init__(self, parent) 16 | self.initUI() 17 | 18 | def initUI(self): 19 | self.win = QWidget() 20 | 21 | self.repo_view = False 22 | 23 | self.l1 = QLabel("Github Username") 24 | 25 | self.nm = QLineEdit(self) 26 | self.nm.setMinimumWidth(200) 27 | self.nm.editingFinished.connect(self.enterPress) 28 | 29 | self.l2 = QLabel("Repository") 30 | 31 | self.add1 = QLineEdit(self) 32 | self.add1.setMinimumWidth(200) 33 | 34 | self.fbox = QFormLayout() 35 | self.fbox.addRow(self.l1, self.nm) 36 | 37 | self.vbox = QVBoxLayout() 38 | 39 | self.vbox.addWidget(self.add1) 40 | self.fbox.addRow(self.l2, self.vbox) 41 | 42 | self.submitButton = QPushButton("Submit") 43 | self.cancelButton = QPushButton("Cancel") 44 | self.fbox.addRow(self.submitButton, self.cancelButton) 45 | self.submitButton.clicked.connect(self.submit) 46 | 47 | self.win.setLayout(self.fbox) 48 | self.setWindowTitle("Github GUI") 49 | self.setCentralWidget(self.win) 50 | self.resize(self.win.minimumSize()) 51 | self.show() 52 | 53 | def enterPress(self): 54 | self.add1.setFocus() 55 | 56 | def submit(self): 57 | global repo_name 58 | global user_name 59 | repo_name = str(self.add1.text()).strip() 60 | user_name = str(self.nm.text()).strip() 61 | if(len(repo_name) == 0 or len(user_name) == 0): 62 | msg = QMessageBox() 63 | msg.setIcon(QMessageBox.Critical) 64 | msg.setText("User or repo name can't be empty") 65 | msg.setWindowTitle("Error") 66 | msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) 67 | msg.exec_() 68 | return 69 | self.switch_view() 70 | 71 | def switch_view(self): 72 | if(self.repo_view is False): 73 | self.repo_view = True 74 | global user_name 75 | global repo_name 76 | user_page = "http://github.com/" + user_name 77 | repo_page = user_page + "/" + repo_name 78 | res = requests.get(user_page) 79 | if(res.status_code == 404): 80 | msg = QMessageBox() 81 | msg.setIcon(QMessageBox.Critical) 82 | msg.setText("User doesn't exists.") 83 | msg.setWindowTitle("Error") 84 | msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) 85 | msg.exec_() 86 | self.repo_view = False 87 | return 88 | else: 89 | soup = BeautifulSoup(res.text, "html.parser") 90 | followers = soup.find('a', {'href': '/' + user_name + '?tab=followers'}).find('span').text.strip() 91 | following = soup.find('a', {'href': '/' + user_name + '?tab=following'}).find('span').text.strip() 92 | res = requests.get(repo_page) 93 | if(res.status_code == 404): 94 | msg = QMessageBox() 95 | msg.setIcon(QMessageBox.Critical) 96 | msg.setText("Repository doesn't exists.") 97 | msg.setWindowTitle("Error") 98 | msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) 99 | msg.exec_() 100 | self.repo_view = False 101 | return 102 | 103 | soup = BeautifulSoup(res.text, "html.parser") 104 | try: 105 | short_desc = soup.find("span", {"itemprop": "about"}).text 106 | except: 107 | short_desc = "No description available" 108 | 109 | issue_tag = soup.find('a', {"data-hotkey": "g i"}) 110 | issues = issue_tag.find('span', {'class': 'counter'}).text 111 | 112 | pr_tag = soup.find('a', {"data-hotkey": "g p"}) 113 | prs = pr_tag.find('span', {'class': 'counter'}).text 114 | 115 | main_tag = soup.find('li', {'class': 'commits'}) 116 | 117 | commits = main_tag.find('span').text.strip() 118 | branches = main_tag.find_next("li").find('span').text.strip() 119 | releases = main_tag.find_next("li").find_next("li").find('span').text.strip() 120 | contributors = main_tag.find_next("li").find_next("li").find_next("li").find('span').text.strip() 121 | 122 | overview_files = soup.findAll('tr', {'class': 'js-navigation-item'}) 123 | files = list() 124 | 125 | for file in overview_files: 126 | files.append([file.find('a').text.strip(), 127 | file.find('td', {'class': 'message'}).find('a').attrs["title"], 128 | file.find('td', {'class': 'age'}).text.strip() 129 | ]) 130 | 131 | try: 132 | watchers = soup.find('a', {'href': '/' + user_name + '/' + repo_name + '/watchers'}).text.strip() 133 | except: 134 | watchers = "0" 135 | try: 136 | stars = soup.find('a', {'href': '/' + user_name + '/' + repo_name + '/stargazers'}).text.strip() 137 | except: 138 | stars = "0" 139 | try: 140 | forks = soup.find('a', {'href': '/' + user_name + '/' + repo_name + '/network'}).text.strip() 141 | except: 142 | forks = "0" 143 | 144 | self.font = QFont("Times", 16, QFont.Bold) 145 | 146 | self.user = QLabel(user_name) 147 | self.user.setFont(self.font) 148 | self.repo_name = QLabel(repo_name) 149 | self.repo_name.setFont(self.font) 150 | self.title = QLabel(short_desc) 151 | self.title.setFont(self.font) 152 | 153 | self.issue = QLabel("Issues | " + issues) 154 | self.pr = QLabel("Pull Requests | " + prs) 155 | 156 | self.follower = QLabel("Followers | " + followers) 157 | self.followee = QLabel("Following | " + following) 158 | 159 | self.watcher = QLabel("Watchers | " + watchers) 160 | self.star = QLabel("Stars | " + stars) 161 | self.fork = QLabel("Forks | " + forks) 162 | 163 | self.commit = QLabel(commits + " commits") 164 | self.branch = QLabel(branches + " branches") 165 | self.release = QLabel(releases + " releases") 166 | self.contributor = QLabel(contributors + " contributors") 167 | 168 | self.file_overview = QLabel("Files Overview") 169 | 170 | self.main_grid = QGridLayout() 171 | self.main_grid.setSpacing(0) 172 | 173 | self.area = QScrollArea(self) 174 | self.area.setWidgetResizable(True) 175 | 176 | self.main_grid.addWidget(self.user, 0, 0) 177 | self.main_grid.addWidget(self.follower, 0, 2) 178 | self.main_grid.addWidget(self.followee, 0, 3) 179 | self.main_grid.addWidget(self.repo_name, 1, 0) 180 | self.main_grid.addWidget(self.watcher, 2, 1) 181 | self.main_grid.addWidget(self.star, 2, 2) 182 | self.main_grid.addWidget(self.fork, 2, 3) 183 | self.main_grid.addWidget(self.title, 3, 0) 184 | 185 | self.main_grid.addWidget(self.issue, 4, 0) 186 | self.main_grid.addWidget(self.pr, 4, 2) 187 | 188 | for i in range(len(files)): 189 | self.main_grid.addWidget(QLabel(files[i][0]), i + 6, 0) 190 | self.main_grid.addWidget(QLabel(files[i][1]), i + 6, 1) 191 | self.main_grid.addWidget(QLabel(files[i][2]), i + 6, 3) 192 | 193 | self.central_widget = QWidget() 194 | self.central_widget.setLayout(self.main_grid) 195 | self.area.setWidget(self.central_widget) 196 | 197 | self.vbar = self.area.verticalScrollBar() 198 | self.vbar.setValue(self.vbar.maximum()) 199 | 200 | self.setCentralWidget(self.area) 201 | 202 | self.resize(self.central_widget.width() + 20, self.central_widget.height() + 20) 203 | self.show() 204 | 205 | else: 206 | self.repo_view = False 207 | self.initUI() 208 | 209 | def keyPressEvent(self, event): 210 | key = event.key() 211 | if(key == Qt.Key_Backspace): 212 | self.switch_view() 213 | 214 | 215 | if __name__ == '__main__': 216 | app = QApplication(sys.argv) 217 | window = Main() 218 | sys.exit(app.exec_()) 219 | -------------------------------------------------------------------------------- /gitfiles.py: -------------------------------------------------------------------------------- 1 | """ 2 | Program to find information about given Github username or 3 | repository from the terminal. 4 | """ 5 | import urllib2 6 | from bs4 import BeautifulSoup 7 | import requests 8 | import re 9 | 10 | def infoAboutRepo(): 11 | """ 12 | Given username and repository, Returns information about the repository. 13 | """ 14 | user = raw_input('Enter the user name ') 15 | url = 'https://github.com/'+user 16 | # Check If username is invalid 17 | try: 18 | soup = BeautifulSoup(urllib2.urlopen(url).read(), "html.parser") 19 | except Exception: 20 | print 'User "%s" does not exist! Please try again.' %(user) 21 | exit() 22 | 23 | popularRepo = soup.find_all('span' , {'class': 'repo js-repo'}) 24 | print "These are the some popular repo of user",user 25 | for repo in popularRepo: 26 | print repo.string 27 | 28 | repo = raw_input('Enter the repository name : ') 29 | url = "https://github.com/"+user+'/'+repo 30 | try: 31 | urllib2.urlopen(url) 32 | except urllib2.HTTPError, e: 33 | print 'Sorry, there is no such repository named "%s" for user "%s"'%(repo, user) 34 | exit() 35 | 36 | 37 | def pulse(url): 38 | """ 39 | """ 40 | url += '/pulse/monthly' 41 | page = urllib2.urlopen(url) 42 | soup = BeautifulSoup(page.read(), "html.parser") 43 | div_all = soup.findAll('div',{'class':'section diffstat-summary'}) 44 | if not div_all: 45 | print 'No Recent activities in the repository.' 46 | return 47 | print '\nThe whole information about the repository is as follows :\n' 48 | for each_div in div_all: 49 | print ' '.join(each_div.get_text().split()) 50 | 51 | def readme(url): 52 | """ 53 | """ 54 | url+= '/blob/master/README.md' 55 | # Check if ReadMe exists. 56 | try: 57 | soup = BeautifulSoup(urllib2.urlopen(url).read(), "html.parser") 58 | paragraphs = soup.find('article', {"class" : "markdown-body entry-content"}).get_text() 59 | except Exception: 60 | print 'ReadMe file for the repository doesn\'t exist' 61 | return 62 | 63 | print '\nREADME\n' 64 | print paragraphs 65 | 66 | 67 | def watching(url): 68 | """ 69 | """ 70 | # TODO: watching not working as of now. Only giving 0 as Watcher... 71 | soup = BeautifulSoup(urllib2.urlopen(url).read(), "html.parser") 72 | watch = soup.find('a' , {"class" : "social-count js-social-count"}).text 73 | print 'Watchers: %s' %(watch.split()[0]) 74 | 75 | 76 | def statistics(url): 77 | """ 78 | """ 79 | soup = BeautifulSoup(urllib2.urlopen(url).read(), "html.parser") 80 | ultags_all= soup.find_all('ul', {'class' : 'numbers-summary'}) 81 | if not ultags_all: 82 | print 'No activities in the repository.' 83 | return 84 | print "\nUsers Activities in Repo" 85 | for ultag in ultags_all : 86 | for litag in ultag.find_all('li'): 87 | if ' '.join(litag.text.split()) != "Fetching contributors": 88 | print ' '.join(litag.text.split()) 89 | 90 | statistics(url) 91 | pulse(url) 92 | readme(url) 93 | watching(url) 94 | #more features to be added... 95 | 96 | 97 | def infoAboutUser(): 98 | """ 99 | Given username, Returns information about user if exists. 100 | """ 101 | user = raw_input('Enter the user name of the person you want to see : ') 102 | url = 'https://github.com/'+user 103 | # Check If username is invalid 104 | try: 105 | soup = BeautifulSoup(urllib2.urlopen(url).read(), "html.parser") 106 | except Exception: 107 | print 'User "%s" does not exist! Please try again.' %(user) 108 | exit() 109 | 110 | 111 | def profileInfo(soup): 112 | """ 113 | Returns the Profile specific information for the User. 114 | """ 115 | # TODO: remove unwanted code 116 | 117 | #Give users full name 118 | fullName = soup.find('span', attrs = {'class': "vcard-fullname"}).text 119 | print "Full name: ",fullName 120 | 121 | #Give users username 122 | userName = soup.find('span', attrs = {'class': "vcard-username"}).text 123 | print "username: ",userName 124 | 125 | #Give users home town 126 | try: 127 | homeTown = soup.find('li',{'aria-label':"Home location"}).text 128 | print "Home Town: ",homeTown 129 | except: 130 | print "User does not add his/her hometown on github!" 131 | #Give user Email-Id 132 | try: 133 | email_id = soup.find('li',{'aria-label' : "Email"}).text 134 | print "email-id: ",email_id 135 | except: 136 | print "User does not add his/her email-id on github!" 137 | 138 | #Give Joining date 139 | join = soup.find('li',{'aria-label':"Member since" }).text 140 | print "Joining date of github: ",join[10:] 141 | 142 | #Give users organisation 143 | try: 144 | organization = soup.find('li',{'aria-label' : "Organization"}).text 145 | print "Organization: ",organization 146 | except: 147 | print "User does not add his/her working Organization on github!" 148 | 149 | #Give users Blog or Website 150 | try: 151 | website = soup.find('li',{'aria-label' : "Blog or website"}).text 152 | print "Personal website: ",website 153 | except: 154 | print "User does not add his/her personal website on github!" 155 | 156 | # Get followers 157 | for followersCount in soup.findAll('span', attrs = {'class': "counter"}): 158 | parent = followersCount.parent 159 | if parent.name == 'a' and 'followers' in parent['href']: 160 | count = followersCount.text.replace(" ",'').replace("\n", "") 161 | #count = int(re.search(r'\d+', str(followersCount.text)).group()) 162 | print "Followers: ",count 163 | count = int(re.search(r'\d+', str(followersCount.text)).group()) 164 | if (count > 0): 165 | follow(url,'followers') 166 | 167 | for followingCount in soup.findAll('span', attrs = {'class': "counter"}): 168 | parent = followingCount.parent 169 | if parent.name == 'a' and 'following' in parent['href']: 170 | count = followersCount.text.replace(" ", '').replace("\n", "") 171 | print "Following: ", count 172 | count = int(re.search(r'\d+', str(followingCount.text)).group()) 173 | if (count > 0): 174 | follow(url,'following') 175 | 176 | #Give user bio 177 | userdesc(soup) 178 | 179 | def follow(url,str): 180 | url_new = url + '?tab=' + str 181 | 182 | try: 183 | soup = BeautifulSoup(urllib2.urlopen(url_new).read(), 'html.parser') 184 | except Exception: 185 | print 'Connection Error!' 186 | exit() 187 | 188 | user_names = soup.find_all('span', {'class': 'f4 link-gray-dark'}) 189 | 190 | for uname in user_names: 191 | ustring = '-> ' + uname.string 192 | try: 193 | print ustring 194 | except: 195 | continue 196 | print '' 197 | 198 | 199 | 200 | def contributions(soup): 201 | """ 202 | Returns the contributions done by user in given Period. 203 | """ 204 | # TODO: Generates error. Needs modification 205 | print "\nContributions of User\n" 206 | 207 | totalContributions = soup.find('div' , {'class' : 'js-contribution-graph'}).find('h2',{'class' : 'f4 text-normal mb-3'}).text 208 | print "Total contributions last year",totalContributions.split()[0] 209 | 210 | 211 | Streaks = soup.find('svg' , {'class' : 'js-calendar-graph-svg'}).find_all('rect') 212 | longestStreak = 0 213 | streakList = [] 214 | for streak in Streaks: 215 | streakList.append(int(streak['data-count'])) 216 | longestStreak = max(int(streak['data-count']),longestStreak) 217 | print "Longest Streak: ",longestStreak 218 | 219 | print "Total contributions last weeks: ",sum(streakList[-7:]) 220 | 221 | 222 | def popularRepos(soup): 223 | """ 224 | Returns Public repositories of the user. 225 | """ 226 | popularRepo = soup.find_all('span' , {'class': 'repo js-repo'}) 227 | 228 | if not popularRepo: 229 | print 'No public repositories for the given user.' 230 | return 231 | desc= soup.findAll('p',{'class':'pinned-repo-desc text-gray text-small d-block mt-2 mb-3'}) 232 | stars= soup.findAll('p',{'class':'mb-0 f6 text-gray'}) 233 | length= len(popularRepo) 234 | countPopularRepo =1 235 | for i in range(0,length): 236 | print str(countPopularRepo) + ': ' 237 | countPopularRepo = countPopularRepo+1 238 | print 'Name :' + popularRepo[i].text 239 | print 'Description :' + desc[i].text 240 | descriptions = list(stars[i].stripped_strings) 241 | description = "\nLanguage : ".join(descriptions) if descriptions else "" 242 | print "Stars :" +description 243 | 244 | 245 | def userdesc(soup): 246 | try: 247 | desc= soup.find('div',{'class':'user-profile-bio'}).text 248 | print "User's Bio:",desc 249 | except Exception: 250 | print "User's Bio: This User doesn\'t have a bio" 251 | 252 | print "\nUsers Info\n" 253 | profileInfo(soup) 254 | # followers(url) 255 | contributions(soup) 256 | print "\nUsers Popular Repositories\n" 257 | popularRepos(soup) 258 | 259 | if __name__ == "__main__": 260 | print "Welcome to the Python Interface of GitHub!" 261 | print '''Please enter your choice :\n 262 | 1. Get information about user 263 | 2. Get information about a particular repository\n''' 264 | 265 | #Small changes here for ask user for his choice 266 | while True: 267 | choice = raw_input('Enter your choice here: ') 268 | if choice == '1': 269 | infoAboutUser() 270 | break 271 | elif choice == '2': 272 | infoAboutRepo() 273 | break 274 | else: 275 | print "Sorry, It is not a valid choice.Please select from 1 and 2!\n\n" 276 | -------------------------------------------------------------------------------- /install-pyqt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #This script is for installing PyQt package 3 | sudo apt-get install python-qt4 -y 4 | -------------------------------------------------------------------------------- /requirements.py: -------------------------------------------------------------------------------- 1 | bs4 2 | requests 3 | BeautifulSoup 4 | --------------------------------------------------------------------------------