Compare commits

..

104 Commits

Author SHA1 Message Date
Jose Diaz-Gonzalez
fac8e4274f Release version 0.26.0 2019-09-23 11:45:01 -04:00
Jose Diaz-Gonzalez
17fee66f31 Merge pull request #128 from Snawoot/master
Workaround gist clone in `--prefer-ssh` mode
2019-09-23 11:44:21 -04:00
Vladislav Yarmak
a56d27dd8b workaround gist clone in --prefer-ssh mode 2019-09-21 19:22:27 +03:00
Jose Diaz-Gonzalez
e57873b6dd Create PULL_REQUEST.md 2019-08-14 17:51:19 -04:00
Jose Diaz-Gonzalez
2658b039a1 Create ISSUE_TEMPLATE.md 2019-08-14 17:47:47 -04:00
Jose Diaz-Gonzalez
fd684a71fb Update README.rst 2019-07-11 13:40:25 -07:00
Jose Diaz-Gonzalez
bacd77030b Update README.rst 2019-07-11 13:39:41 -07:00
Jose Diaz-Gonzalez
b73079daf2 Release version 0.25.0 2019-07-03 17:46:12 -04:00
Jose Diaz-Gonzalez
eca8a70666 Merge pull request #120 from 8h2a/patch-1
Issue 119: Change retrieve_data to be a generator
2019-07-03 17:45:40 -04:00
2a
e74765ba7f Issue 119: Change retrieve_data to be a generator
See issue #119.
2019-07-03 23:01:00 +02:00
Jose Diaz-Gonzalez
6db5bd731b Release version 0.24.0 2019-06-27 11:24:43 -04:00
Jose Diaz-Gonzalez
7305871c20 Merge pull request #117 from QuicketSolutions/master
Add option for Releases
2019-06-27 11:15:02 -04:00
Ethan Timm
baf7b1a9b4 Merge pull request #5 from QuicketSolutions/QKT-45
QKT-45: include assets - update readme
2019-06-25 15:41:11 -05:00
Ethan Timm
121fa68294 QKT-45: include assets - update readme
update readme with flag information for including assets alongside their respective releases
2019-06-25 15:41:02 -05:00
Ethan Timm
44dfc79edc Merge pull request #4 from whwright/wip-releases
Download github assets
2019-06-25 15:35:39 -05:00
Harrison Wright
89f59cc7a2 Make assets it's own flag 2019-06-24 15:49:19 -05:00
Jose Diaz-Gonzalez
ad8c5b8768 Merge pull request #118 from whwright/115-fix-pull-details
Fix pull details
2019-06-24 14:51:10 -04:00
Harrison Wright
921aab3729 Fix pull details 2019-06-22 13:19:45 -05:00
Harrison Wright
ea4c3d0f6f Fix super call for python2 2019-06-22 13:05:54 -05:00
Harrison Wright
9b6400932d Fix redirect to s3 2019-06-22 13:00:42 -05:00
Harrison Wright
de0c3f46c6 WIP: download assets 2019-06-21 20:03:14 -05:00
Ethan Timm
73b069f872 Merge pull request #3 from QuicketSolutions/QKT-42
QKT-42: releases - add readme info
2019-06-21 16:54:28 -05:00
ethan
3d3f512074 QKT-42: releases - add readme info 2019-06-21 16:53:40 -05:00
Ethan Timm
1c3078992d Merge pull request #2 from QuicketSolutions/QKT-42
QKT-42 update: shorter command flag
2019-06-21 16:49:40 -05:00
ethan
4b40ae94d7 QKT-42 update: shorter command flag 2019-06-21 16:48:25 -05:00
Ethan Timm
a18fda9faf Merge pull request #1 from QuicketSolutions/QKT-42
QKT-42: support saving release information
2019-06-21 16:43:48 -05:00
ethan
41130fc8b0 QKT-42: support saving release information 2019-06-21 11:20:32 -05:00
Jose Diaz-Gonzalez
2340a02fc6 Release version 0.23.0 2019-06-04 14:43:32 -04:00
Jose Diaz-Gonzalez
cafff4ae80 Merge pull request #113 from kleag/master
Avoid to crash in case of HTTP 502 error
2019-06-04 14:43:10 -04:00
Gael de Chalendar
3193d120e5 Avoid to crash in case of HTTP 502 error
Survive also on socket.error connections like on HTTPError or URLError.

This should solve issue #110.
2019-06-04 18:53:58 +02:00
Jose Diaz-Gonzalez
da4b29a2d6 Release version 0.22.2 2019-02-21 15:41:11 -05:00
Jose Diaz-Gonzalez
d05c96ecef Merge pull request #107 from josegonzalez/patch-1
fix: warn instead of error
2019-02-21 15:40:59 -05:00
Jose Diaz-Gonzalez
c86163bfe6 fix: warn instead of error
Refs #106
2019-02-21 15:40:39 -05:00
Jose Diaz-Gonzalez
eff6e36974 Release version 0.22.1 2019-02-21 15:13:31 -05:00
Jose Diaz-Gonzalez
63e458bafb Merge pull request #106 from jstetic/master
Log URL error
2019-02-21 15:13:02 -05:00
JOHN STETIC
57ab5ce1a2 Log URL error https://github.com/josegonzalez/python-github-backup/issues/105 2019-02-20 20:43:00 -05:00
Jose Diaz-Gonzalez
d148f9b900 Release version 0.22.0 2019-02-01 09:50:42 -05:00
Jose Diaz-Gonzalez
89ee22c2be Merge pull request #103 from whwright/98-better-logging
Fix accidental system exit with better logging strategy
2018-12-27 15:12:26 -05:00
W. Harrison Wright
9e472b74e6 Remove unnecessary sys.exit call 2018-12-27 13:07:13 -06:00
W. Harrison Wright
4b459f9af8 Add org check to avoid incorrect log output 2018-12-27 12:58:57 -06:00
W. Harrison Wright
b70ea87db7 Fix accidental system exit with better logging strategy 2018-12-27 12:53:21 -06:00
Jose Diaz-Gonzalez
f8be34562b Release version 0.21.1 2018-12-25 06:28:28 -05:00
Jose Diaz-Gonzalez
ec05204aa9 Merge pull request #101 from ecki/patch-2
Mark options which are not included in --all
2018-12-25 06:27:58 -05:00
Bernd
628f2cbf73 Mark options which are not included in --all
As discussed in Issue #100
2018-12-24 04:19:29 +01:00
Jose Diaz-Gonzalez
38bf438d2f Release version 0.21.0 2018-11-28 01:59:03 -05:00
Jose Diaz-Gonzalez
899cf42b57 Merge pull request #97 from whwright/94-fix-user-repos
Correctly download repos when user arg != authenticated user
2018-11-28 01:58:37 -05:00
W. Harrison Wright
b5972aaaf0 Correctly download repos when user arg != authenticated user 2018-11-11 19:40:46 -06:00
Jose Diaz-Gonzalez
d860f369e9 Release version 0.20.1 2018-09-29 00:16:48 -04:00
Jose Diaz-Gonzalez
77ab1bda15 Merge pull request #92 from whwright/87-fix-starred-bug
Clone the specified user's starred repos/gists, not the authenticated user
2018-09-29 00:16:32 -04:00
W. Harrison Wright
4a4a317331 Clone the specified user's gists, not the authenticated user 2018-09-28 21:59:50 -05:00
W. Harrison Wright
5a8e1ac275 Clone the specified user's starred repos, not the authenticated user 2018-09-28 21:46:28 -05:00
Jose Diaz-Gonzalez
0de341eab4 Release version 0.20.0 2018-03-24 15:00:26 -04:00
Jose Diaz-Gonzalez
b0130fdf94 chore: drop Python 2.6 2018-03-24 15:00:05 -04:00
Jose Diaz-Gonzalez
b49f399037 feat: simplify release script 2018-03-24 14:59:56 -04:00
Jose Diaz-Gonzalez
321414d352 Release version 0.19.2 2018-03-24 01:16:34 -04:00
Jose Diaz-Gonzalez
413d4381cc fix: cleanup pep8 violations 2018-03-24 01:16:28 -04:00
Jose Diaz-Gonzalez
0110ea40ed Release version 0.19.1 2018-03-24 01:04:35 -04:00
Jose Diaz-Gonzalez
8d2ef2f528 Release version 0.19.0 2018-03-24 00:54:34 -04:00
Jose Diaz-Gonzalez
1a79f755a5 Merge pull request #77 from mayflower/pull-details
Pull Details
2018-03-23 23:40:22 -04:00
Jose Diaz-Gonzalez
abf45d5b54 Merge pull request #84 from johbo/fix-python36-skip-existing
Mark string as binary in comparison for skip_existing
2018-02-26 10:44:12 -05:00
Johannes Bornhold
fd33037b1c Mark string as binary in comparison for skip_existing
Found out that the flag "--skip-existing" did not work out as expected on Python
3.6. Tracked it down to the comparison which has to be against a string of bytes
in Python3.
2018-02-26 11:21:25 +01:00
Jose Diaz-Gonzalez
87dab293ed Release version 0.18.0 2018-02-22 12:13:06 -05:00
Jose Diaz-Gonzalez
0244af4e05 Merge pull request #82 from sgreene570/add-followers
Add option to fetch followers/following JSON data
2018-02-22 12:11:48 -05:00
Stephen Greene
eca9f0f7df Add option to fetch followers/following JSON data 2018-02-21 19:29:59 -08:00
Jose Diaz-Gonzalez
afa2a6d587 Release version 0.17.0 2018-02-20 13:06:48 -05:00
Jose Diaz-Gonzalez
b77ea48d74 Merge pull request #81 from whwright/gists
Add ability to back up gists
2018-02-19 15:29:20 -05:00
W. Harrison Wright
f378254188 Short circuit gists backup process 2018-02-07 21:46:59 -06:00
W. Harrison Wright
83128e986a Formatting 2018-02-07 21:30:55 -06:00
W. Harrison Wright
17e4f9a125 Add ability to backup gists 2018-02-07 21:29:49 -06:00
Jose Diaz-Gonzalez
e59d1e3a68 Release version 0.16.0 2018-01-22 12:49:31 -05:00
Jose Diaz-Gonzalez
de860ee5a9 Merge pull request #78 from whwright/clone-starred-repos
Clone starred repos
2018-01-22 12:36:42 -05:00
Jose Diaz-Gonzalez
cb054c2631 Update README.rst 2018-01-22 12:36:32 -05:00
W. Harrison Wright
c142707a90 Update documentation 2018-01-22 11:34:27 -06:00
W. Harrison Wright
7cccd42ec9 Change option to --all-starred 2018-01-14 10:22:10 -06:00
W. Harrison Wright
9a539b1d6b JK don't update documentation 2018-01-14 10:18:51 -06:00
W. Harrison Wright
cd2372183e Update documentation 2018-01-13 17:44:09 -06:00
W. Harrison Wright
bd346de898 Put starred clone repoistories under a new option 2018-01-13 17:43:00 -06:00
W. Harrison Wright
6e3cbe841a Add comment 2018-01-13 14:12:26 -06:00
W. Harrison Wright
8b95f187ad Add ability to clone starred repos 2018-01-13 14:08:36 -06:00
Robin Gloster
ef88248c41 Add additional output for the current request
This is useful to have some progress indication for huge repositories.
2017-12-29 23:33:53 +01:00
Robin Gloster
0a4decfb3b Add option to backup additional PR details
Some payload is only included when requesting a single pull request
2017-12-29 21:39:59 +01:00
Jose Diaz-Gonzalez
2b9549ffde Release version 0.15.0 2017-12-11 11:46:16 -05:00
Jose Diaz-Gonzalez
fb2c3ca921 Merge pull request #75 from slibby/slibby-patch-windows
update check_io() to allow scripts to run on Windows
2017-12-11 11:45:58 -05:00
Sam Libby
4f4785085d update logging_subprocess function
1. added newline for return
2. added one-time warning (once per subprocess)
2017-12-11 09:25:49 -07:00
Sam Libby
76895dcf69 update check_io() to allow scripts to run on Windows 2017-12-10 21:44:26 -07:00
Jose Diaz-Gonzalez
1d50a4038b Release version 0.14.1 2017-10-11 16:18:21 -04:00
Jose Diaz-Gonzalez
9d31ccfba9 Merge pull request #70 from epfremmer/patch-1
Fix arg not defined error
2017-10-11 16:17:58 -04:00
Edward Pfremmer
27a1ba2d04 Fix arg not defined error
Ref: https://github.com/josegonzalez/python-github-backup/issues/69
2017-10-11 15:12:34 -05:00
Jose Diaz-Gonzalez
f157ea107f Release version 0.14.0 2017-10-11 11:52:16 -04:00
Jose Diaz-Gonzalez
a129cc759a Merge pull request #68 from pieterclaerhout/master
Added support for LFS clones
2017-10-11 11:51:57 -04:00
pieterclaerhout
bb551a83f4 Updated the readme 2017-10-11 15:14:13 +02:00
pieterclaerhout
9b1b4a9ebc Added a check to see if git-lfs is installed when doing an LFS clone 2017-10-11 15:11:14 +02:00
pieterclaerhout
e6b6eb8bef Added support for LFS clones 2017-10-10 19:52:07 +02:00
Jose Diaz-Gonzalez
0b3f120e2b Merge pull request #66 from albertyw/python3
Explicitly support python 3
2017-09-30 21:01:27 -04:00
Albert Wang
990249b80b Add pypi info to readme 2017-09-30 17:16:38 -07:00
Albert Wang
cefb226545 Explicitly support python 3 in package description 2017-09-30 17:13:47 -07:00
Jose Diaz-Gonzalez
ea22ffdf26 Merge pull request #65 from mumblez/master
add couple examples to help new users
2017-05-30 11:58:42 -06:00
Yusuf Tran
0f21d7b8a4 add couple examples to help new users 2017-05-30 18:52:11 +01:00
Jose Diaz-Gonzalez
cb33b9bab7 Release version 0.13.2 2017-05-06 14:14:08 -06:00
Jose Diaz-Gonzalez
68c48cb0b3 Merge pull request #64 from karlicoss/fix-remotes
Fix remotes while updating repository
2017-05-06 14:13:46 -06:00
Dima Gerasimov
922a3c5a6e Fix remotes while updating repository 2017-05-06 12:58:42 +01:00
Jose Diaz-Gonzalez
d4055eb99c Release version 0.13.1 2017-04-11 09:40:13 -06:00
Jose Diaz-Gonzalez
d8a330559c Merge pull request #61 from McNetic/fix_empty_updated_at
Fix error when repository has no updated_at value
2017-04-11 09:37:15 -06:00
Nicolai Ehemann
de93824498 Fix error when repository has no updated_at value 2017-04-11 11:10:03 +02:00
8 changed files with 572 additions and 150 deletions

View File

@@ -1,9 +1,169 @@
Changelog
=========
0.13.0 (2017-04-05)
0.26.0 (2019-09-23)
-------------------
------------------------
- Workaround gist clone in `--prefer-ssh` mode. [Vladislav Yarmak]
- Create PULL_REQUEST.md. [Jose Diaz-Gonzalez]
- Create ISSUE_TEMPLATE.md. [Jose Diaz-Gonzalez]
0.25.0 (2019-07-03)
-------------------
- Issue 119: Change retrieve_data to be a generator. [2a]
See issue #119.
0.24.0 (2019-06-27)
-------------------
- QKT-45: include assets - update readme. [Ethan Timm]
update readme with flag information for including assets alongside their respective releases
- Make assets it's own flag. [Harrison Wright]
- Fix super call for python2. [Harrison Wright]
- Fix redirect to s3. [Harrison Wright]
- WIP: download assets. [Harrison Wright]
- QKT-42: releases - add readme info. [ethan]
- QKT-42 update: shorter command flag. [ethan]
- QKT-42: support saving release information. [ethan]
- Fix pull details. [Harrison Wright]
0.23.0 (2019-06-04)
-------------------
- Avoid to crash in case of HTTP 502 error. [Gael de Chalendar]
Survive also on socket.error connections like on HTTPError or URLError.
This should solve issue #110.
0.22.2 (2019-02-21)
-------------------
Fix
~~~
- Warn instead of error. [Jose Diaz-Gonzalez]
Refs #106
0.22.1 (2019-02-21)
-------------------
- Log URL error https://github.com/josegonzalez/python-github-
backup/issues/105. [JOHN STETIC]
0.22.0 (2019-02-01)
-------------------
- Remove unnecessary sys.exit call. [W. Harrison Wright]
- Add org check to avoid incorrect log output. [W. Harrison Wright]
- Fix accidental system exit with better logging strategy. [W. Harrison
Wright]
0.21.1 (2018-12-25)
-------------------
- Mark options which are not included in --all. [Bernd]
As discussed in Issue #100
0.21.0 (2018-11-28)
-------------------
- Correctly download repos when user arg != authenticated user. [W.
Harrison Wright]
0.20.1 (2018-09-29)
-------------------
- Clone the specified user's gists, not the authenticated user. [W.
Harrison Wright]
- Clone the specified user's starred repos, not the authenticated user.
[W. Harrison Wright]
0.20.0 (2018-03-24)
-------------------
- Chore: drop Python 2.6. [Jose Diaz-Gonzalez]
- Feat: simplify release script. [Jose Diaz-Gonzalez]
0.19.2 (2018-03-24)
-------------------
Fix
~~~
- Cleanup pep8 violations. [Jose Diaz-Gonzalez]
0.19.0 (2018-03-24)
-------------------
- Add additional output for the current request. [Robin Gloster]
This is useful to have some progress indication for huge repositories.
- Add option to backup additional PR details. [Robin Gloster]
Some payload is only included when requesting a single pull request
- Mark string as binary in comparison for skip_existing. [Johannes
Bornhold]
Found out that the flag "--skip-existing" did not work out as expected on Python
3.6. Tracked it down to the comparison which has to be against a string of bytes
in Python3.
0.18.0 (2018-02-22)
-------------------
- Add option to fetch followers/following JSON data. [Stephen Greene]
0.17.0 (2018-02-20)
-------------------
- Short circuit gists backup process. [W. Harrison Wright]
- Formatting. [W. Harrison Wright]
- Add ability to backup gists. [W. Harrison Wright]
0.16.0 (2018-01-22)
-------------------
- Change option to --all-starred. [W. Harrison Wright]
- JK don't update documentation. [W. Harrison Wright]
- Put starred clone repoistories under a new option. [W. Harrison
Wright]
- Add comment. [W. Harrison Wright]
- Add ability to clone starred repos. [W. Harrison Wright]
0.14.1 (2017-10-11)
-------------------
- Fix arg not defined error. [Edward Pfremmer]
0.14.0 (2017-10-11)
-------------------
- Added a check to see if git-lfs is installed when doing an LFS clone.
[pieterclaerhout]
- Added support for LFS clones. [pieterclaerhout]
- Add pypi info to readme. [Albert Wang]
- Explicitly support python 3 in package description. [Albert Wang]
- Add couple examples to help new users. [Yusuf Tran]
0.13.2 (2017-05-06)
-------------------
- Fix remotes while updating repository. [Dima Gerasimov]
0.13.1 (2017-04-11)
-------------------
- Fix error when repository has no updated_at value. [Nicolai Ehemann]
0.13.0 (2017-04-05)
-------------------
- Add OS check for OSX specific keychain args. [Martin O'Reilly]
Keychain arguments are only supported on Mac OSX.
@@ -12,8 +172,6 @@ Changelog
error message rather than a "No password item matching the
provided name and account could be found in the osx keychain"
error message
- Add support for storing PAT in OSX keychain. [Martin O'Reilly]
Added additional optional arguments and README guidance for storing
@@ -23,62 +181,48 @@ Changelog
0.12.1 (2017-03-27)
-------------------
- Avoid remote branch name churn. [Chris Adams]
This avoids the backup output having lots of "[new branch]" messages
because removing the old remote name removed all of the existing branch
references.
- Fix detection of bare git directories. [Andrzej Maczuga]
0.12.0 (2016-11-22)
-------------------
Fix
~~~
- Properly import version from github_backup package. [Jose Diaz-
Gonzalez]
- Support alternate git status output. [Jose Diaz-Gonzalez]
Other
~~~~~
- Pep8: E501 line too long (83 > 79 characters) [Jose Diaz-Gonzalez]
- Pep8: E128 continuation line under-indented for visual indent. [Jose
Diaz-Gonzalez]
- Support archivization using bare git clones. [Andrzej Maczuga]
- Fix typo, 3x. [Terrell Russell]
0.11.0 (2016-10-26)
-------------------
- Support --token file:///home/user/token.txt (fixes gh-51) [Björn
Dahlgren]
- Fix some linting. [Albert Wang]
- Fix byte/string conversion for python 3. [Albert Wang]
- Support python 3. [Albert Wang]
- Encode special characters in password. [Remi Rampin]
- Don't pretend program name is "Github Backup" [Remi Rampin]
- Don't install over insecure connection. [Remi Rampin]
The git:// protocol is unauthenticated and unencrypted, and no longer advertised by GitHub. Using HTTPS shouldn't impact performance.
0.10.3 (2016-08-21)
-------------------
- Fixes #29. [Jonas Michel]
Reporting an error when the user's rate limit is exceeded causes
@@ -86,8 +230,6 @@ Other
sleep. Instead of generating an explicit error we just want to
inform the user that the script is going to sleep until their rate
limit count resets.
- Fixes #29. [Jonas Michel]
The errors list was not being cleared out after resuming a backup
@@ -98,14 +240,13 @@ Other
0.10.2 (2016-08-21)
-------------------
- Add a note regarding git version requirement. [Jose Diaz-Gonzalez]
Closes #37
0.10.0 (2016-08-18)
-------------------
- Implement incremental updates. [Robert Bradshaw]
Guarded with an --incremental flag.
@@ -118,12 +259,11 @@ Other
0.9.0 (2016-03-29)
------------------
- Fix cloning private repos with basic auth or token. [Kazuki Suda]
0.8.0 (2016-02-14)
------------------
- Don't store issues which are actually pull requests. [Enrico Tröger]
This prevents storing pull requests twice since the Github API returns
@@ -134,43 +274,31 @@ Other
0.7.0 (2016-02-02)
------------------
- Softly fail if not able to read hooks. [Albert Wang]
- Add note about 2-factor auth. [Albert Wang]
- Make user repository search go through endpoint capable of reading
private repositories. [Albert Wang]
- Prompt for password if only username given. [Alex Hall]
0.6.0 (2015-11-10)
------------------
- Force proper remote url. [Jose Diaz-Gonzalez]
- Improve error handling in case of HTTP errors. [Enrico Tröger]
In case of a HTTP status code 404, the returned 'r' was never assigned.
In case of URL errors which are not timeouts, we probably should bail
out.
- Add --hooks to also include web hooks into the backup. [Enrico Tröger]
- Create the user specified output directory if it does not exist.
[Enrico Tröger]
Fixes #17.
- Add missing auth argument to _get_response() [Enrico Tröger]
When running unauthenticated and Github starts rate-limiting the client,
github-backup crashes because the used auth variable in _get_response()
was not available. This change should fix it.
- Add repository URL to error message for non-existing repositories.
[Enrico Tröger]
@@ -181,40 +309,28 @@ Other
0.5.0 (2015-10-10)
------------------
- Add release script. [Jose Diaz-Gonzalez]
- Refactor to both simplify codepath as well as follow PEP8 standards.
[Jose Diaz-Gonzalez]
- Retry 3 times when the connection times out. [Mathijs Jonker]
- Made unicode output defalut. [Kirill Grushetsky]
- Import alphabetised. [Kirill Grushetsky]
- Preserve Unicode characters in the output file. [Kirill Grushetsky]
Added option to preserve Unicode characters in the output file
- Josegonzales/python-github-backup#12 Added backup of labels and
milestones. [aensley]
- Fixed indent. [Mathijs Jonker]
- Skip unitialized repo's. [mjonker-embed]
These gave me errors which caused mails from crontab.
- Added prefer-ssh. [mjonker-embed]
Was needed for my back-up setup, code includes this but readme wasn't updated
- Retry API requests which failed due to rate-limiting. [Chris Adams]
This allows operation to continue, albeit at a slower pace,
if you have enough data to trigger the API rate limits
- Logging_subprocess: always log when a command fails. [Chris Adams]
Previously git clones could fail without any indication
@@ -224,21 +340,15 @@ Other
Now a non-zero return code will always output a message to
stderr and will display the executed command so it can be
rerun for troubleshooting.
- Switch to using ssh_url. [Chris Adams]
The previous commit used the wrong URL for a private repo. This was
masked by the lack of error loging in logging_subprocess (which will be
in a separate branch)
- Add an option to prefer checkouts over SSH. [Chris Adams]
This is really useful with private repos to avoid being nagged
for credentials for every repository
- Add pull request support. [Kevin Laude]
Back up reporitory pull requests by passing the --include-pulls
@@ -250,8 +360,6 @@ Other
Pull requests are automatically backed up when the --all argument is
uesd.
- Add GitHub Enterprise support. [Kevin Laude]
Pass the -H or --github-host argument with a GitHub Enterprise hostname
@@ -261,35 +369,21 @@ Other
0.2.0 (2014-09-22)
------------------
- Add support for retrieving repositories. Closes #1. [Jose Diaz-
Gonzalez]
- Fix PEP8 violations. [Jose Diaz-Gonzalez]
- Add authorization to header only if specified by user. [Ioannis
Filippidis]
- Fill out readme more. [Jose Diaz-Gonzalez]
- Fix import. [Jose Diaz-Gonzalez]
- Properly name readme. [Jose Diaz-Gonzalez]
- Create MANIFEST.in. [Jose Diaz-Gonzalez]
- Create .gitignore. [Jose Diaz-Gonzalez]
- Create setup.py. [Jose Diaz-Gonzalez]
- Create requirements.txt. [Jose Diaz-Gonzalez]
- Create __init__.py. [Jose Diaz-Gonzalez]
- Create LICENSE.txt. [Jose Diaz-Gonzalez]
- Create README.md. [Jose Diaz-Gonzalez]
- Create github-backup. [Jose Diaz-Gonzalez]

13
ISSUE_TEMPLATE.md Normal file
View File

@@ -0,0 +1,13 @@
# Important notice regarding filed issues
This project already fills my needs, and as such I have no real reason to continue it's development. This project is otherwise provided as is, and no support is given.
If pull requests implementing bug fixes or enhancements are pushed, I am happy to review and merge them (time permitting).
If you wish to have a bug fixed, you have a few options:
- Fix it yourself.
- File a bug and hope someone else fixes it for you.
- Pay me to fix it (my rate is $200 an hour, minimum 1 hour, contact me via my [github email address](https://github.com/josegonzalez) if you want to go this route).
In all cases, feel free to file an issue, they may be of help to others in the future.

7
PULL_REQUEST.md Normal file
View File

@@ -0,0 +1,7 @@
# Important notice regarding filed pull requests
This project already fills my needs, and as such I have no real reason to continue it's development. This project is otherwise provided as is, and no support is given.
I will attempt to review pull requests at _my_ earliest convenience. If I am unable to get to your pull request in a timely fashion, it is what it is. This repository does not pay any bills, and I am not required to merge any pull request from any individual.
If you wish to jump my personal priority queue, you may pay me for my time to review. My rate is $200 an hour - minimum 1 hour - feel free contact me via my github email address if you want to go this route.

View File

@@ -2,6 +2,10 @@
github-backup
=============
|PyPI| |Python Versions|
This project is considered feature complete for the primary maintainer. If you would like a bugfix or enhancement and cannot sponsor the work, pull requests are welcome. Feel free to contact the maintainer for consulting estimates if desired.
backup a github user or organization
Requirements
@@ -26,11 +30,13 @@ Usage
CLI Usage is as follows::
github-backup [-h] [-u USERNAME] [-p PASSWORD] [-t TOKEN]
[-o OUTPUT_DIRECTORY] [-i] [--starred] [--watched]
[--all] [--issues] [--issue-comments] [--issue-events]
[--pulls] [--pull-comments] [--pull-commits] [--labels]
[--hooks] [--milestones] [--repositories] [--bare]
[--wikis] [--skip-existing]
[-o OUTPUT_DIRECTORY] [-i] [--starred] [--all-starred]
[--watched] [--followers] [--following] [--all]
[--issues] [--issue-comments] [--issue-events] [--pulls]
[--pull-comments] [--pull-commits] [--labels] [--hooks]
[--milestones] [--repositories] [--releases] [--assets]
[--bare] [--lfs] [--wikis] [--gists] [--starred-gists]
[--skip-existing]
[-L [LANGUAGES [LANGUAGES ...]]] [-N NAME_REGEX]
[-H GITHUB_HOST] [-O] [-R REPOSITORY] [-P] [-F]
[--prefer-ssh] [-v]
@@ -51,12 +57,16 @@ CLI Usage is as follows::
password for basic auth. If a username is given but
not a password, the password will be prompted for.
-t TOKEN, --token TOKEN
personal access or OAuth token
personal access or OAuth token, or path to token
(file://...)
-o OUTPUT_DIRECTORY, --output-directory OUTPUT_DIRECTORY
directory at which to backup the repositories
-i, --incremental incremental backup
--starred include starred repositories in backup
--starred include JSON output of starred repositories in backup
--all-starred include starred repositories in backup
--watched include watched repositories in backup
--followers include JSON output of followers in backup
--following include JSON output of following users in backup
--all include everything in backup
--issues include issues in backup
--issue-comments include issue comments in backup
@@ -69,8 +79,14 @@ CLI Usage is as follows::
authenticated)
--milestones include milestones in backup
--repositories include repository clone in backup
--releases include repository releases' information without assets or binaries
--assets include assets alongside release information; only applies if including releases
--bare clone bare repositories
--lfs clone LFS repositories (requires Git LFS to be
installed, https://git-lfs.github.com)
--wikis include wiki clone in backup
--gists include gists in backup
--starred-gists include starred gists in backup
--skip-existing skip project if a backup directory exists
-L [LANGUAGES [LANGUAGES ...]], --languages [LANGUAGES [LANGUAGES ...]]
only allow these languages
@@ -114,3 +130,31 @@ Note: When you run github-backup, you will be asked whether you want to allow "
1. **Allow:** In this case you will need to click "Allow" each time you run `github-backup`
2. **Always Allow:** In this case, you will not be asked for permission when you run `github-backup` in future. This is less secure, but is required if you want to schedule `github-backup` to run automatically
About Git LFS
=============
When you use the "--lfs" option, you will need to make sure you have Git LFS installed.
Instructions on how to do this can be found on https://git-lfs.github.com.
Examples
========
Backup all repositories::
export ACCESS_TOKEN=SOME-GITHUB-TOKEN
github-backup WhiteHouse --token $ACCESS_TOKEN --organization --output-directory /tmp/white-house --repositories
Backup a single organization repository with everything else (wiki, pull requests, comments, issues etc)::
export ACCESS_TOKEN=SOME-GITHUB-TOKEN
ORGANIZATION=docker
REPO=cli
# e.g. git@github.com:docker/cli.git
github-backup $ORGANIZATION -P -t $ACCESS_TOKEN -o . --all -O -R $REPO
.. |PyPI| image:: https://img.shields.io/pypi/v/github-backup.svg
:target: https://pypi.python.org/pypi/github-backup/
.. |Python Versions| image:: https://img.shields.io/pypi/pyversions/github-backup.svg
:target: https://github.com/albertyw/github-backup

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env python
from __future__ import print_function
import socket
import argparse
import base64
@@ -17,6 +18,7 @@ import subprocess
import sys
import time
import platform
PY2 = False
try:
# python 3
from urllib.parse import urlparse
@@ -25,14 +27,19 @@ try:
from urllib.error import HTTPError, URLError
from urllib.request import urlopen
from urllib.request import Request
from urllib.request import HTTPRedirectHandler
from urllib.request import build_opener
except ImportError:
# python 2
PY2 = True
from urlparse import urlparse
from urllib import quote as urlquote
from urllib import urlencode
from urllib2 import HTTPError, URLError
from urllib2 import urlopen
from urllib2 import Request
from urllib2 import HTTPRedirectHandler
from urllib2 import build_opener
from github_backup import __version__
@@ -40,16 +47,17 @@ FNULL = open(os.devnull, 'w')
def log_error(message):
if type(message) == str:
message = [message]
for msg in message:
sys.stderr.write("{0}\n".format(msg))
"""
Log message (str) or messages (List[str]) to stderr and exit with status 1
"""
log_warning(message)
sys.exit(1)
def log_info(message):
"""
Log message (str) or messages (List[str]) to stdout
"""
if type(message) == str:
message = [message]
@@ -57,6 +65,17 @@ def log_info(message):
sys.stdout.write("{0}\n".format(msg))
def log_warning(message):
"""
Log message (str) or messages (List[str]) to stderr
"""
if type(message) == str:
message = [message]
for msg in message:
sys.stderr.write("{0}\n".format(msg))
def logging_subprocess(popenargs,
logger,
stdout_log_level=logging.DEBUG,
@@ -69,11 +88,15 @@ def logging_subprocess(popenargs,
"""
child = subprocess.Popen(popenargs, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, **kwargs)
if sys.platform == 'win32':
log_info("Windows operating system detected - no subprocess logging will be returned")
log_level = {child.stdout: stdout_log_level,
child.stderr: stderr_log_level}
def check_io():
if sys.platform == 'win32':
return
ready_to_read = select.select([child.stdout, child.stderr],
[],
[],
@@ -155,15 +178,27 @@ def parse_args():
parser.add_argument('--starred',
action='store_true',
dest='include_starred',
help='include starred repositories in backup')
help='include JSON output of starred repositories in backup')
parser.add_argument('--all-starred',
action='store_true',
dest='all_starred',
help='include starred repositories in backup [*]')
parser.add_argument('--watched',
action='store_true',
dest='include_watched',
help='include watched repositories in backup')
help='include JSON output of watched repositories in backup')
parser.add_argument('--followers',
action='store_true',
dest='include_followers',
help='include JSON output of followers in backup')
parser.add_argument('--following',
action='store_true',
dest='include_following',
help='include JSON output of following users in backup')
parser.add_argument('--all',
action='store_true',
dest='include_everything',
help='include everything in backup')
help='include everything in backup (not including [*])')
parser.add_argument('--issues',
action='store_true',
dest='include_issues',
@@ -188,6 +223,10 @@ def parse_args():
action='store_true',
dest='include_pull_commits',
help='include pull request commits in backup')
parser.add_argument('--pull-details',
action='store_true',
dest='include_pull_details',
help='include more pull request details in backup [*]')
parser.add_argument('--labels',
action='store_true',
dest='include_labels',
@@ -208,10 +247,22 @@ def parse_args():
action='store_true',
dest='bare_clone',
help='clone bare repositories')
parser.add_argument('--lfs',
action='store_true',
dest='lfs_clone',
help='clone LFS repositories (requires Git LFS to be installed, https://git-lfs.github.com) [*]')
parser.add_argument('--wikis',
action='store_true',
dest='include_wiki',
help='include wiki clone in backup')
parser.add_argument('--gists',
action='store_true',
dest='include_gists',
help='include gists in backup [*]')
parser.add_argument('--starred-gists',
action='store_true',
dest='include_starred_gists',
help='include starred gists in backup [*]')
parser.add_argument('--skip-existing',
action='store_true',
dest='skip_existing',
@@ -241,11 +292,11 @@ def parse_args():
parser.add_argument('-P', '--private',
action='store_true',
dest='private',
help='include private repositories')
help='include private repositories [*]')
parser.add_argument('-F', '--fork',
action='store_true',
dest='fork',
help='include forked repositories')
help='include forked repositories [*]')
parser.add_argument('--prefer-ssh',
action='store_true',
help='Clone repositories using SSH instead of HTTPS')
@@ -258,6 +309,15 @@ def parse_args():
parser.add_argument('--keychain-account',
dest='osx_keychain_item_account',
help='OSX ONLY: account field of password item in OSX keychain that holds the personal access or OAuth token')
parser.add_argument('--releases',
action='store_true',
dest='include_releases',
help='include release information, not including assets or binaries'
)
parser.add_argument('--assets',
action='store_true',
dest='include_assets',
help='include assets alongside release information; only applies if including releases')
return parser.parse_args()
@@ -271,12 +331,12 @@ def get_auth(args, encode=True):
if platform.system() != 'Darwin':
log_error("Keychain arguments are only supported on Mac OSX")
try:
with open(os.devnull,'w') as devnull:
with open(os.devnull, 'w') as devnull:
token = (subprocess.check_output([
'security','find-generic-password',
'-s',args.osx_keychain_item_name,
'-a',args.osx_keychain_item_account,
'-w' ], stderr=devnull).strip())
'security', 'find-generic-password',
'-s', args.osx_keychain_item_name,
'-a', args.osx_keychain_item_account,
'-w'], stderr=devnull).strip())
auth = token + ':' + 'x-oauth-basic'
except:
log_error('No password item matching the provided name and account could be found in the osx keychain.')
@@ -327,6 +387,9 @@ def get_github_host(args):
def get_github_repo_url(args, repository):
if repository.get('is_gist'):
return repository['git_pull_url']
if args.prefer_ssh:
return repository['ssh_url']
@@ -335,7 +398,7 @@ def get_github_repo_url(args, repository):
repo_url = 'https://{0}@{1}/{2}/{3}.git'.format(
auth,
get_github_host(args),
args.user,
repository['owner']['login'],
repository['name'])
else:
repo_url = repository['clone_url']
@@ -343,12 +406,11 @@ def get_github_repo_url(args, repository):
return repo_url
def retrieve_data(args, template, query_args=None, single_request=False):
def retrieve_data_gen(args, template, query_args=None, single_request=False):
auth = get_auth(args)
query_args = get_query_args(query_args)
per_page = 100
page = 0
data = []
while True:
page = page + 1
@@ -357,6 +419,16 @@ def retrieve_data(args, template, query_args=None, single_request=False):
status_code = int(r.getcode())
retries = 0
while retries < 3 and status_code == 502:
print('API request returned HTTP 502: Bad Gateway. Retrying in 5 seconds')
retries += 1
time.sleep(5)
request = _construct_request(per_page, page, query_args, template, auth) # noqa
r, errors = _get_response(request, auth, template)
status_code = int(r.getcode())
if status_code != 200:
template = 'API request returned HTTP {0}: {1}'
errors.append(template.format(status_code, r.reason))
@@ -365,11 +437,12 @@ def retrieve_data(args, template, query_args=None, single_request=False):
response = json.loads(r.read().decode('utf-8'))
if len(errors) == 0:
if type(response) == list:
data.extend(response)
for resp in response:
yield resp
if len(response) < per_page:
break
elif type(response) == dict and single_request:
data.append(response)
yield response
if len(errors) > 0:
log_error(errors)
@@ -377,8 +450,8 @@ def retrieve_data(args, template, query_args=None, single_request=False):
if single_request:
break
return data
def retrieve_data(args, template, query_args=None, single_request=False):
return list(retrieve_data_gen(args, template, query_args, single_request))
def get_query_args(query_args=None):
if not query_args:
@@ -398,7 +471,13 @@ def _get_response(request, auth, template):
except HTTPError as exc:
errors, should_continue = _request_http_error(exc, auth, errors) # noqa
r = exc
except URLError:
except URLError as e:
log_warning(e.reason)
should_continue = _request_url_error(template, retry_timeout)
if not should_continue:
raise
except socket.error as e:
log_warning(e.strerror)
should_continue = _request_url_error(template, retry_timeout)
if not should_continue:
raise
@@ -419,6 +498,7 @@ def _construct_request(per_page, page, query_args, template, auth):
request = Request(template + '?' + querystring)
if auth is not None:
request.add_header('Authorization', 'Basic '.encode('ascii') + auth)
log_info('Requesting {}?{}'.format(template, querystring))
return request
@@ -467,11 +547,65 @@ def _request_url_error(template, retry_timeout):
return False
def retrieve_repositories(args):
class S3HTTPRedirectHandler(HTTPRedirectHandler):
"""
A subclassed redirect handler for downloading Github assets from S3.
urllib will add the Authorization header to the redirected request to S3, which will result in a 400,
so we should remove said header on redirect.
"""
def redirect_request(self, req, fp, code, msg, headers, newurl):
if PY2:
# HTTPRedirectHandler is an old style class
request = HTTPRedirectHandler.redirect_request(self, req, fp, code, msg, headers, newurl)
else:
request = super(S3HTTPRedirectHandler, self).redirect_request(req, fp, code, msg, headers, newurl)
del request.headers['Authorization']
return request
def download_file(url, path, auth):
request = Request(url)
request.add_header('Accept', 'application/octet-stream')
request.add_header('Authorization', 'Basic '.encode('ascii') + auth)
opener = build_opener(S3HTTPRedirectHandler)
response = opener.open(request)
chunk_size = 16 * 1024
with open(path, 'wb') as f:
while True:
chunk = response.read(chunk_size)
if not chunk:
break
f.write(chunk)
def get_authenticated_user(args):
template = 'https://{0}/user'.format(get_github_api_host(args))
data = retrieve_data(args, template, single_request=True)
return data[0]
def check_git_lfs_install():
exit_code = subprocess.call(['git', 'lfs', 'version'])
if exit_code != 0:
log_error('The argument --lfs requires you to have Git LFS installed.\nYou can get it from https://git-lfs.github.com.')
def retrieve_repositories(args, authenticated_user):
log_info('Retrieving repositories')
single_request = False
template = 'https://{0}/user/repos'.format(
get_github_api_host(args))
if args.user == authenticated_user['login']:
# we must use the /user/repos API to be able to access private repos
template = 'https://{0}/user/repos'.format(
get_github_api_host(args))
else:
if args.private and not args.organization:
log_warning('Authenticated user is different from user being backed up, thus private repositories cannot be accessed')
template = 'https://{0}/users/{1}/repos'.format(
get_github_api_host(args),
args.user)
if args.organization:
template = 'https://{0}/orgs/{1}/repos'.format(
get_github_api_host(args),
@@ -484,7 +618,34 @@ def retrieve_repositories(args):
args.user,
args.repository)
return retrieve_data(args, template, single_request=single_request)
repos = retrieve_data(args, template, single_request=single_request)
if args.all_starred:
starred_template = 'https://{0}/users/{1}/starred'.format(get_github_api_host(args), args.user)
starred_repos = retrieve_data(args, starred_template, single_request=False)
# flag each repo as starred for downstream processing
for item in starred_repos:
item.update({'is_starred': True})
repos.extend(starred_repos)
if args.include_gists:
gists_template = 'https://{0}/users/{1}/gists'.format(get_github_api_host(args), args.user)
gists = retrieve_data(args, gists_template, single_request=False)
# flag each repo as a gist for downstream processing
for item in gists:
item.update({'is_gist': True})
repos.extend(gists)
if args.include_starred_gists:
starred_gists_template = 'https://{0}/gists/starred'.format(get_github_api_host(args))
starred_gists = retrieve_data(args, starred_gists_template, single_request=False)
# flag each repo as a starred gist for downstream processing
for item in starred_gists:
item.update({'is_gist': True,
'is_starred': True})
repos.extend(starred_gists)
return repos
def filter_repositories(args, unfiltered_repositories):
@@ -492,7 +653,8 @@ def filter_repositories(args, unfiltered_repositories):
repositories = []
for r in unfiltered_repositories:
if r['owner']['login'] == args.user:
# gists can be anonymous, so need to safely check owner
if r.get('owner', {}).get('login') == args.user or r.get('is_starred'):
repositories.append(r)
name_regex = None
@@ -504,11 +666,11 @@ def filter_repositories(args, unfiltered_repositories):
languages = [x.lower() for x in args.languages]
if not args.fork:
repositories = [r for r in repositories if not r['fork']]
repositories = [r for r in repositories if not r.get('fork')]
if not args.private:
repositories = [r for r in repositories if not r['private']]
repositories = [r for r in repositories if not r.get('private') or r.get('public')]
if languages:
repositories = [r for r in repositories if r['language'] and r['language'].lower() in languages] # noqa
repositories = [r for r in repositories if r.get('language') and r.get('language').lower() in languages] # noqa
if name_regex:
repositories = [r for r in repositories if name_regex.match(r['name'])]
@@ -520,7 +682,7 @@ def backup_repositories(args, output_directory, repositories):
repos_template = 'https://{0}/repos'.format(get_github_api_host(args))
if args.incremental:
last_update = max(repository['updated_at'] for repository in repositories) # noqa
last_update = max(list(repository['updated_at'] for repository in repositories) or [time.strftime('%Y-%m-%dT%H:%M:%SZ', time.localtime())]) # noqa
last_update_path = os.path.join(output_directory, 'last_update')
if os.path.exists(last_update_path):
args.since = open(last_update_path).read().strip()
@@ -530,17 +692,36 @@ def backup_repositories(args, output_directory, repositories):
args.since = None
for repository in repositories:
backup_cwd = os.path.join(output_directory, 'repositories')
repo_cwd = os.path.join(backup_cwd, repository['name'])
if repository.get('is_gist'):
repo_cwd = os.path.join(output_directory, 'gists', repository['id'])
elif repository.get('is_starred'):
# put starred repos in -o/starred/${owner}/${repo} to prevent collision of
# any repositories with the same name
repo_cwd = os.path.join(output_directory, 'starred', repository['owner']['login'], repository['name'])
else:
repo_cwd = os.path.join(output_directory, 'repositories', repository['name'])
repo_dir = os.path.join(repo_cwd, 'repository')
repo_url = get_github_repo_url(args, repository)
if args.include_repository or args.include_everything:
fetch_repository(repository['name'],
include_gists = (args.include_gists or args.include_starred_gists)
if (args.include_repository or args.include_everything) \
or (include_gists and repository.get('is_gist')):
repo_name = repository.get('name') if not repository.get('is_gist') else repository.get('id')
fetch_repository(repo_name,
repo_url,
repo_dir,
skip_existing=args.skip_existing,
bare_clone=args.bare_clone)
bare_clone=args.bare_clone,
lfs_clone=args.lfs_clone)
if repository.get('is_gist'):
# dump gist information to a file as well
output_file = '{0}/gist.json'.format(repo_cwd)
with codecs.open(output_file, 'w', encoding='utf-8') as f:
json_dump(repository, f)
continue # don't try to back anything else for a gist; it doesn't exist
download_wiki = (args.include_wiki or args.include_everything)
if repository['has_wiki'] and download_wiki:
@@ -548,7 +729,8 @@ def backup_repositories(args, output_directory, repositories):
repo_url.replace('.git', '.wiki.git'),
os.path.join(repo_cwd, 'wiki'),
skip_existing=args.skip_existing,
bare_clone=args.bare_clone)
bare_clone=args.bare_clone,
lfs_clone=args.lfs_clone)
if args.include_issues or args.include_everything:
backup_issues(args, repo_cwd, repository, repos_template)
@@ -565,6 +747,10 @@ def backup_repositories(args, output_directory, repositories):
if args.include_hooks or args.include_everything:
backup_hooks(args, repo_cwd, repository, repos_template)
if args.include_releases or args.include_everything:
backup_releases(args, repo_cwd, repository, repos_template,
include_assets=args.include_assets or args.include_everything)
if args.incremental:
open(last_update_path, 'w').write(last_update)
@@ -639,23 +825,38 @@ def backup_pulls(args, repo_cwd, repository, repos_template):
pulls = {}
_pulls_template = '{0}/{1}/pulls'.format(repos_template,
repository['full_name'])
query_args = {
'filter': 'all',
'state': 'all',
'sort': 'updated',
'direction': 'desc',
}
pull_states = ['open', 'closed']
for pull_state in pull_states:
query_args = {
'filter': 'all',
'state': pull_state,
'sort': 'updated',
'direction': 'desc',
}
# It'd be nice to be able to apply the args.since filter here...
_pulls = retrieve_data(args,
if not args.include_pull_details:
pull_states = ['open', 'closed']
for pull_state in pull_states:
query_args['state'] = pull_state
_pulls = retrieve_data_gen(args,
_pulls_template,
query_args=query_args)
for pull in _pulls:
if args.since and pull['updated_at'] < args.since:
break
if not args.since or pull['updated_at'] >= args.since:
pulls[pull['number']] = pull
else:
_pulls = retrieve_data_gen(args,
_pulls_template,
query_args=query_args)
for pull in _pulls:
if args.since and pull['updated_at'] < args.since:
break
if not args.since or pull['updated_at'] >= args.since:
pulls[pull['number']] = pull
pulls[pull['number']] = retrieve_data(
args,
_pulls_template + '/{}'.format(pull['number']),
single_request=True
)[0]
log_info('Saving {0} pull requests to disk'.format(
len(list(pulls.keys()))))
@@ -734,17 +935,45 @@ def backup_hooks(args, repo_cwd, repository, repos_template):
log_info("Unable to read hooks, skipping")
def backup_releases(args, repo_cwd, repository, repos_template, include_assets=False):
repository_fullname = repository['full_name']
# give release files somewhere to live & log intent
release_cwd = os.path.join(repo_cwd, 'releases')
log_info('Retrieving {0} releases'.format(repository_fullname))
mkdir_p(repo_cwd, release_cwd)
query_args = {}
release_template = '{0}/{1}/releases'.format(repos_template, repository_fullname)
releases = retrieve_data(args, release_template, query_args=query_args)
# for each release, store it
log_info('Saving {0} releases to disk'.format(len(releases)))
for release in releases:
release_name = release['tag_name']
output_filepath = os.path.join(release_cwd, '{0}.json'.format(release_name))
with codecs.open(output_filepath, 'w+', encoding='utf-8') as f:
json_dump(release, f)
if include_assets:
assets = retrieve_data(args, release['assets_url'])
for asset in assets:
download_file(asset['url'], os.path.join(release_cwd, asset['name']), get_auth(args))
def fetch_repository(name,
remote_url,
local_dir,
skip_existing=False,
bare_clone=False):
bare_clone=False,
lfs_clone=False):
if bare_clone:
if os.path.exists(local_dir):
clone_exists = subprocess.check_output(['git',
'rev-parse',
'--is-bare-repository'],
cwd=local_dir) == "true\n"
cwd=local_dir) == b"true\n"
else:
clone_exists = False
else:
@@ -769,7 +998,7 @@ def fetch_repository(name,
remotes = subprocess.check_output(['git', 'remote', 'show'],
cwd=local_dir)
remotes = [i.strip() for i in remotes.decode('utf-8')]
remotes = [i.strip() for i in remotes.decode('utf-8').splitlines()]
if 'origin' not in remotes:
git_command = ['git', 'remote', 'rm', 'origin']
@@ -780,7 +1009,10 @@ def fetch_repository(name,
git_command = ['git', 'remote', 'set-url', 'origin', remote_url]
logging_subprocess(git_command, None, cwd=local_dir)
git_command = ['git', 'fetch', '--all', '--force', '--tags', '--prune']
if lfs_clone:
git_command = ['git', 'lfs', 'fetch', '--all', '--force', '--tags', '--prune']
else:
git_command = ['git', 'fetch', '--all', '--force', '--tags', '--prune']
logging_subprocess(git_command, None, cwd=local_dir)
else:
log_info('Cloning {0} repository from {1} to {2}'.format(
@@ -788,9 +1020,15 @@ def fetch_repository(name,
masked_remote_url,
local_dir))
if bare_clone:
git_command = ['git', 'clone', '--mirror', remote_url, local_dir]
if lfs_clone:
git_command = ['git', 'lfs', 'clone', '--mirror', remote_url, local_dir]
else:
git_command = ['git', 'clone', '--mirror', remote_url, local_dir]
else:
git_command = ['git', 'clone', remote_url, local_dir]
if lfs_clone:
git_command = ['git', 'lfs', 'clone', remote_url, local_dir]
else:
git_command = ['git', 'clone', remote_url, local_dir]
logging_subprocess(git_command, None)
@@ -798,21 +1036,37 @@ def backup_account(args, output_directory):
account_cwd = os.path.join(output_directory, 'account')
if args.include_starred or args.include_everything:
output_file = '{0}/starred.json'.format(account_cwd)
template = "https://{0}/users/{1}/starred"
template = template.format(get_github_api_host(args), args.user)
output_file = "{0}/starred.json".format(account_cwd)
template = "https://{0}/users/{1}/starred".format(get_github_api_host(args), args.user)
_backup_data(args,
'starred repositories',
"starred repositories",
template,
output_file,
account_cwd)
if args.include_watched or args.include_everything:
output_file = '{0}/watched.json'.format(account_cwd)
template = "https://{0}/users/{1}/subscriptions"
template = template.format(get_github_api_host(args), args.user)
output_file = "{0}/watched.json".format(account_cwd)
template = "https://{0}/users/{1}/subscriptions".format(get_github_api_host(args), args.user)
_backup_data(args,
'watched repositories',
"watched repositories",
template,
output_file,
account_cwd)
if args.include_followers or args.include_everything:
output_file = "{0}/followers.json".format(account_cwd)
template = "https://{0}/users/{1}/followers".format(get_github_api_host(args), args.user)
_backup_data(args,
"followers",
template,
output_file,
account_cwd)
if args.include_following or args.include_everything:
output_file = "{0}/following.json".format(account_cwd)
template = "https://{0}/users/{1}/following".format(get_github_api_host(args), args.user)
_backup_data(args,
"following",
template,
output_file,
account_cwd)
@@ -847,9 +1101,13 @@ def main():
log_info('Create output directory {0}'.format(output_directory))
mkdir_p(output_directory)
if args.lfs_clone:
check_git_lfs_install()
log_info('Backing up user {0} to {1}'.format(args.user, output_directory))
repositories = retrieve_repositories(args)
authenticated_user = get_authenticated_user(args)
repositories = retrieve_repositories(args, authenticated_user)
repositories = filter_repositories(args, repositories)
backup_repositories(args, output_directory, repositories)
backup_account(args, output_directory)

View File

@@ -1 +1 @@
__version__ = '0.13.0'
__version__ = '0.26.0'

View File

@@ -1,8 +1,13 @@
#!/usr/bin/env bash
set -eo pipefail; [[ $RELEASE_TRACE ]] && set -x
PACKAGE_NAME='github-backup'
INIT_PACKAGE_NAME='github_backup'
if [[ ! -f setup.py ]]; then
echo -e "${RED}WARNING: Missing setup.py${COLOR_OFF}\n"
exit 1
fi
PACKAGE_NAME="$(cat setup.py | grep "name='" | head | cut -d "'" -f2)"
INIT_PACKAGE_NAME="$(echo "${PACKAGE_NAME//-/_}")"
PUBLIC="true"
# Colors

View File

@@ -37,8 +37,9 @@ setup(
'Development Status :: 5 - Production/Stable',
'Topic :: System :: Archiving :: Backup',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
],
description='backup a github user or organization',
long_description=open_file('README.rst').read(),