Compare commits

..

512 Commits

Author SHA1 Message Date
Unknwon
b5f6206a65 prepare release 2015-12-06 15:34:17 -05:00
Unknwon
3de9c11ea7 Merge pull request #2110 from msoedov/misspellings
Fix misspelled words
2015-12-06 15:27:11 -05:00
Alex Myasoedov
ae54d878c0 Fix misspelled words 2015-12-06 16:42:23 +02:00
Unknwon
89244b74c6 remember page number when delete repo 2015-12-05 17:49:46 -05:00
Unknwon
ca8ce793d1 #2063 Ability to delete repo from admin panel 2015-12-05 17:39:29 -05:00
Unknwon
978dc00305 APIs: admin users 2015-12-05 17:13:13 -05:00
Unknwon
bf26808fb3 update README 2015-12-05 13:50:43 -05:00
Unknwon
404867f206 fix #2105 and fix #1857 2015-12-05 13:24:13 -05:00
Unknwon
f0ee33267c fix #2102 2015-12-05 11:46:42 -05:00
Unknwon
f3eaa4c592 Set default language for i18n 2015-12-05 01:20:11 -05:00
Unknwon
f41360d864 #2052 advanced select ops for system notices 2015-12-05 01:09:14 -05:00
Unknwon
e82ee40e9e init with all enabled 2015-12-04 21:34:37 -05:00
Unknwon
1ee7c33e93 template fix 2015-12-04 21:32:33 -05:00
Unknwon
e538ff2770 fix #1829 and fix #890 2015-12-04 21:30:33 -05:00
Unknwon
76d4b9288b #2045 have fallback but empty value 2015-12-04 19:01:34 -05:00
Unknwon
05ba8622f0 #2045 move fallback to empty string 2015-12-04 18:31:45 -05:00
Unknwon
4795fa01d8 fix #2101 2015-12-04 17:30:32 -05:00
Unknwon
942fd6be53 fix panic for #2045 2015-12-04 17:20:23 -05:00
Unknwon
56dd430a10 refactor API routes and some work for #976 2015-12-04 17:16:42 -05:00
Unknwon
e0bae9547a more fixes on #2045 2015-12-04 15:41:56 -05:00
Unknwon
bfe6027266 Merge branch 'develop' of github.com:gogits/gogs into develop 2015-12-03 18:10:54 -05:00
Unknwon
4d9499c2d3 fix broken link 2015-12-03 18:10:45 -05:00
Unknwon
98e989d52c minor JS and UI fix 2015-12-03 15:01:15 -05:00
Unknwon
5742f9fe69 fix #2095 2015-12-03 14:31:31 -05:00
Unknwon
1802d52362 Merge pull request #2094 from nanoant/patch/less-pronounced-sha-labels
UI: Use more subtle grey SHA1 labels
2015-12-03 14:28:30 -05:00
Adam Strzelecki
cab2911f23 UI: More subtle strips on commits list
With grey SHA1 labels, we should consider having also more subtle strips on
commits list. As current strips blend too much with grey SHA1 labels and top
bar, making hard to distinguish headers from content.
2015-12-03 20:26:06 +01:00
Unknwon
81133d45a1 work on #2093 2015-12-03 14:21:13 -05:00
Unknwon
a51acf1751 Merge pull request #2092 from nanoant/patch/non-bold-last-commit-sha
UI: Remove CSS rule making last-commit SHA bold
2015-12-03 13:14:57 -05:00
Adam Strzelecki
edbb67cb3f UI: Use more subtle grey SHA1 labels
Current green SHA1 labels are more pronounced than other UI elements attracting
attention as if they were most important thing in the UI, while they are not as
important, especially without real Git client.

Using grey SHA1 labels makes the UI more balanced, less aggressive and lets
user to focus on other content elements.

NOTE: Neither GitHub or Bitbucket uses so heavy pronunciation as Gogs.
2015-12-03 14:21:20 +01:00
Adam Strzelecki
c5e249c0be UI: Remove CSS rule making last-commit SHA bold
This removes remains from old design, that was not cleaned by previous #2068 PR.
2015-12-03 13:30:16 +01:00
Unknwon
37a372f6f5 500 when wiki not exists 2015-12-03 02:08:25 -05:00
Unknwon
f122d0856e fix #2090 2015-12-03 01:59:32 -05:00
Unknwon
4a6016f5af fix #2087 2015-12-03 01:03:06 -05:00
Unknwon
cc8f5add6e fix #976 2015-12-03 00:24:37 -05:00
Unknwon
ec2423ad7c more UI minor fixes 2015-12-02 20:56:26 -05:00
Unknwon
c4bab163cb Merge pull request #2088 from nanoant/patch/further-layout-fixes
Further layout fixes
2015-12-02 20:26:48 -05:00
Adam Strzelecki
0068b8106b CSS: Octicons 16px fix outside of _octicons.less
Otherwise the fix will be overwritten by next _octicons.less update.

This is follow-up for 22b0dfbb35.
2015-12-03 02:18:38 +01:00
Adam Strzelecki
2580e7b57e UI: Always show menu on repo pages
Merges repo/sidebar.tmpl with repo/header.tmpl and makes every repo page use
middleware.RepoRef() necessary to display information on this menu.
2015-12-03 02:16:18 +01:00
Unknwon
3d3498bda1 clean test data 2015-12-02 20:10:47 -05:00
Unknwon
29375059e1 minor CSS fix 2015-12-02 20:10:00 -05:00
Unknwon
85449b2f11 minor CSS fix for #2068 2015-12-02 19:53:39 -05:00
Unknwon
b83cb36049 minor fix for #2086 2015-12-02 19:45:43 -05:00
Unknwon
73d9eebf01 Merge pull request #2068 from nanoant/patch/repo-file-list-layout
Repo file list layout & misc fixes
2015-12-02 19:44:16 -05:00
Adam Strzelecki
b73241ceb1 UI: Display last-commit header without 2nd column
This uses a CSS trick making first th to be relative block with width equal to
first two columns, effectively working around inability to use colspan="2" on
first row that was breaking "fixed-layout" for tables.

Also use grey header for last-commit SHA1 tag.
2015-12-03 01:15:40 +01:00
Unknwon
e350d74c8a fix #2085 2015-12-02 18:58:13 -05:00
Unknwon
149e540322 Merge pull request #2086 from nanoant/patch/translation-add-code-entry
Translation: Add missing entry for new "code" tab
2015-12-02 18:53:38 -05:00
Adam Strzelecki
314664892c UI: Keep repo URL action right of ref combo & path
Just use secondary menu instead custom ".head.meta", which simplifies code.

Also do not display repo URL action when we are in subdirectory or viewing a
file.
2015-12-02 23:48:36 +01:00
Adam Strzelecki
a9a386a1e5 Translation: Add missing entry for new "code" tab
We have new tab, but we had no entry. That's why it was showing "code"
(lowercase) as this is text id, where we were expecting properly title cased
"Code" to be shown in English version.

Also add Polish translation "code=Kod".
2015-12-02 23:00:23 +01:00
Adam Strzelecki
3eae4ecde7 UI: Make repository menu divide header and content
This is more inline with way GitHub looks like and feels much more natural and
in style with rest of the interface.
2015-12-02 22:40:22 +01:00
Adam Strzelecki
ec98deeb8c UI: Keep repository settings menu button right 2015-12-02 22:06:50 +01:00
Adam Strzelecki
61fdd8c571 Commits & files UI: SUI fixed single line table
Instead using own ellipsis, uses Semantic UI fixed single line table which
effectively applies ellipsis to all overflowing table cells.

NOTE: File list cannot use colspan="2" for 1st "Last commit" elements,
otherwise layout breaks with fixed table.
2015-12-02 21:57:39 +01:00
Adam Strzelecki
4813665d0a CSS: Reduce .sha.label font size to 13px 2015-12-02 21:43:30 +01:00
Adam Strzelecki
640dce12a8 CSS: .repository .sha.label -> .ui .sha.label
This is because SHA1 label is used in many other places, not only inside
.repository container.
2015-12-02 21:42:31 +01:00
Adam Strzelecki
99b958db43 UI: Mark top menu icons blue only when non-zero 2015-12-02 21:38:52 +01:00
Adam Strzelecki
22b0dfbb35 CSS: Ensure Octicons are used with 16px font size
Semantic UI .icon 1em font-size has priority over .octicon 16px, resulting
octicons rendered at 14px font-size, which is not okay since Octicons are meant
to be shown sizes that are multiples of 16px.
2015-12-02 21:33:32 +01:00
Unknwon
4a64ae4abf fix #2083 2015-12-02 13:47:22 -05:00
Unknwon
926e91820a #2071 Diff is not showing full content when has super long one line 2015-12-02 01:10:13 -05:00
Unknwon
91ae2ad28b Merge pull request #2081 from angus-g/en-trans
Update English translations
2015-12-01 23:46:05 -05:00
Angus Gibson
db30ea03d8 Fix casing in English translation 2015-12-02 15:43:56 +11:00
Unknwon
0be8b1b1a1 #2052 Ability to batch delete system notices 2015-12-01 23:33:08 -05:00
Angus Gibson
d45302a6ba Update English translations
Just some phrasing changes to make the English translations sound more natural.
2015-12-02 15:27:08 +11:00
Unknwon
834d38a8fb #2045 add short version as fallback to Slack payload 2015-12-01 21:16:19 -05:00
Unknwon
5572884c6b fix #2057 2015-12-01 20:51:31 -05:00
Unknwon
3460ec1039 update REMADE and locale 2015-12-01 19:53:19 -05:00
Unknwon
53bf23d965 Merge pull request #2079 from nanoant/patch/ldap-custom-username-attr
LDAP: Optional user name attribute specification
2015-12-01 18:23:12 -05:00
Adam Strzelecki
573305f3d3 LDAP: Optional user name attribute specification
Consider following LDAP search query example:

    (&(objectClass=Person)(|(uid=%s)(mail=%s)))

Right now on first login attempt Gogs will use the text supplied on login form
as the newly created user name. In example query above the text matches against
both e-mail or user name. So if user puts the e-mail then the new Gogs user
name will be e-mail which may be undesired.

Using optional user name attribute setting we can explicitly say we want Gogs
user name to be certain LDAP attribute eg. `uid`, so even user will use e-mail
to login 1st time, the new account will receive correct user name.
2015-12-02 00:20:14 +01:00
Unknwon
7ccce4d110 Merge pull request #2078 from nanoant/patch/makefile-improvements
Makefile improvements
2015-12-01 17:26:25 -05:00
Unknwon
9ed60d96a9 fix API 2015-12-01 16:33:45 -05:00
Unknwon
b6d2b96259 Merge pull request #2076 from Gibheer/new_mirror
add new mirror button to dashboard
2015-12-01 16:33:00 -05:00
Adam Strzelecki
e5fe367b82 scripts: Remove less.sh superseded by Makefile
We no longer need to manually build CSS files as Makefile keeps track of it.
2015-12-01 22:28:21 +01:00
Adam Strzelecki
19e8ce0354 Makefile: Remove trailing whitespace & add last LF
This is pure cleanup commit.
2015-12-01 22:20:21 +01:00
Adam Strzelecki
f907a5c98b Makefile: Auto-build CSS & bin-data when necessary
This will ensure that running `make` we will get all necessary files built and
we do not need manually remember to rebuild them.
2015-12-01 22:18:30 +01:00
Adam Strzelecki
da607c611d Makefile: Copy installed binary instead 2nd build
This speeds up single build/rebuild rather than install & build which compiles
everything twice, we just copy installed binary back to the project root.
2015-12-01 22:16:00 +01:00
Gibheer
3d54f6c0a4 add new mirror button to dashboard
This adds the button to create a new mirror on the dashboard at the same
place where "new repository" and "new organization" already exist.
2015-12-01 21:10:36 +01:00
Unknwon
2093586241 Merge pull request #2016 from raxetul/develop
Dockerfile for RaspberryPi is added.
2015-12-01 14:46:03 -05:00
Unknwon
117afe7620 Merge pull request #2069 from nanoant/patch/admin-see-all-organizations
Admin should be able to see all organizations
2015-11-30 21:18:33 -05:00
Unknwon
d3a5ff7b6b fix #2042 2015-11-30 20:50:40 -05:00
Unknwon
dcb391d341 Merge branch 'feature/wiki' into develop 2015-11-30 20:46:19 -05:00
Unknwon
830d000667 finish wiki 2015-11-30 20:45:55 -05:00
Unknwon
5a14c3cf98 Merge pull request #2053 from kakwa/develop
various fixes in gogs dump command
2015-11-30 16:18:18 -05:00
Adam Strzelecki
e57b2dffa4 Admin should be able to see all organizations
This is follow-up for 56c66ee486 allowing admin
to see private repositories, even when not being member of them.
2015-11-30 21:46:01 +01:00
Unknwon
ca96e04e5f #1681 carry --config flag for builtin SSH 2015-11-30 15:40:05 -05:00
Unknwon
9950f5a5bd add line break after SSH error message 2015-11-30 10:00:52 -05:00
kakwa
1d7a1b6034 add name of the dump file in last log message 2015-11-28 15:22:10 +01:00
kakwa
a59b1fcc21 Fix dump of log and custom directory in dump cmd
Now, the dump cmd uses setting.CustomPath and setting.LogRootPath
instead of setting.WorkDir which was kind of broken if the gogs
binary was in a different directory than gogs data.
Additionally, the backup of setting.CustomPath directory is only done
if it exists.
2015-11-28 15:08:50 +01:00
kakwa
c5a9be9115 Using a tmp dir to generate db and repo dumps
Using a tmp dir makes gogs dump more robust to concurrent runs.

It also permits an easier cleaning of the tmp files (gogs-db.sql and
gog-repo.zip) by just removing the tmp dir.

As a side effect, it partially fix bugs on workdir.
Previously, 'gogs dump' created the archives in the current directory,
and tried to include these archives from the directory where the
gogs binary lies.
ex: if gogs binary is in /usr/bin/gogs, and gogs dump is run from /tmp/,
/tmp/gog-repo.zip is created, but gogs dump tried to include
/usr/bin/gogs-repo.zip.
2015-11-28 14:07:51 +01:00
kakwa
f86afb04a2 Adding more error handling in dump cmd
The dump cmd did not check the return value of the z.AddFile or
z.AddDir when building the final archive.
It caused the dump command to succeed even if an error occurred.
The resulted dump archive could be corrupted/empty.
(errors could be various: removal by a concurrent process, disk full,
bugs in the dump cmd itself)
2015-11-28 12:11:38 +01:00
Unknwon
5d1f5f32d0 wiki: finish pages 2015-11-27 02:16:12 -05:00
Unknwon
e42fcb033d wiki: finish edit 2015-11-27 01:50:38 -05:00
Unknwon
392f3ee210 wiki: finish new 2015-11-27 00:24:24 -05:00
Unknwon
c50a3503e6 introduce git-shell 2015-11-26 17:33:45 -05:00
Unknwon
aaa3f1b2b9 Use better LDAP lib and should fix #1139 2015-11-26 14:04:58 -05:00
Unknwon
2b10fdc4dc Wiki: UI for page new 2015-11-25 20:10:25 -05:00
Unknwon
2f28a0310b Merge branch '0.8.0' of github.com:gogits/gogs into develop 2015-11-25 11:01:40 -05:00
Unknwon
253513cedd prepare for release 2015-11-25 09:36:26 -05:00
Unknwon
eb30cbab81 add unsupported migration prompt 2015-11-25 09:27:27 -05:00
Unknwon
144663a3cf allow admin to migrate for any user/org 2015-11-25 00:55:37 -05:00
Unknwon
ba92f4687e minor fix markdown post process 2015-11-24 19:29:35 -05:00
Unknwon
968edb3e44 more link fix 2015-11-24 19:28:24 -05:00
Unknwon
3ca544912f #1944 Drop /org/ URL path prefix in organization home page 2015-11-24 19:14:00 -05:00
Unknwon
7f9598141b fix #2020 2015-11-24 18:49:34 -05:00
Unknwon
56c66ee486 #2008 more supported git hooks 2015-11-24 16:30:47 -05:00
Unknwon
21ad4bf0fe print error log to client side when dev mode 2015-11-23 22:33:24 -05:00
Unknwon
0128036514 #1681 some fixes for builtin SSH server on Windows 2015-11-23 22:32:07 -05:00
Unknwon
ec8d41765d some fix to #2026 2015-11-23 20:43:04 -05:00
Unknwon
ffbeda077c Merge pull request #2024 from andreynering/dropzone-allow-all-files
Fixing Dropzone should accept all files when config is "*/*".
2015-11-23 10:54:48 -05:00
Andrey Nering
880849a283 Fixing Dropzone should accept all files when config is "*/*". 2015-11-23 13:46:58 -02:00
Unknwon
b2fb7e3fd2 more HTTP clone word fix 2015-11-22 13:01:42 -05:00
Emrah URHAN
737da1a374 Latest develop updates is merged with my RaspberryPi Dockerfile version.
Merge branch 'develop' of https://github.com/gogits/gogs into develop
2015-11-22 19:40:18 +02:00
Emrah URHAN
f63a468dfc Dockerfile for RaspberryPi is added. 2015-11-22 17:14:08 +02:00
Unknwon
efaf60ba5a fix #2013 2015-11-22 02:42:39 -05:00
Unknwon
e6b2a01e5d minor JS fix 2015-11-22 02:29:20 -05:00
Unknwon
52c8f69163 fix #650 2015-11-22 01:32:09 -05:00
Unknwon
b80e848d02 upgrade libs 2015-11-21 21:09:18 -05:00
Unknwon
f12832c61e fix possible panic 2015-11-21 21:06:11 -05:00
Unknwon
dcc740fd26 fix incorrect 2015-11-21 19:30:11 -05:00
Unknwon
8966750fd4 add some log 2015-11-21 19:11:57 -05:00
Unknwon
3623b0927e remove tags redis and memcache 2015-11-21 17:21:22 -05:00
Unknwon
d37da1f392 prepare for release 2015-11-21 14:40:29 -05:00
Unknwon
cefc50b278 update locales 2015-11-21 14:08:23 -05:00
Unknwon
b4877b1e06 fix for #2012 2015-11-21 14:02:37 -05:00
Unknwon
eea2e05da6 minor fix on #1694 2015-11-21 12:58:31 -05:00
Unknwon
2b1e955f91 Merge pull request #1694 from sapk/fix-admin-configuration-new-ui
Fix admin configuration new ui
2015-11-21 12:53:33 -05:00
Antoine GIRARD
63cdee84d1 Fix admin configuration new ui 2015-11-21 12:57:28 +01:00
Unknwon
6a6a7512c2 notice 2015-11-20 11:37:17 -05:00
Unknwon
6b30b20765 add more debug info 2015-11-20 08:43:15 -05:00
Unknwon
126228d146 HTML render fix 2015-11-20 05:37:51 -05:00
Unknwon
74dfe439c2 more fix on #2002 2015-11-20 04:08:08 -05:00
Unknwon
1d4a5b1825 fix #2002 2015-11-20 02:53:54 -05:00
Unknwon
987dcc5372 fix #1383 2015-11-20 02:38:41 -05:00
Unknwon
9b6c835715 fix #1873 2015-11-20 01:52:11 -05:00
Unknwon
902b578465 better escape char handle 2015-11-20 01:18:50 -05:00
Unknwon
3d14e73fd8 fix #1119 and data race in timming tasks 2015-11-20 00:47:35 -05:00
Unknwon
9bcc3c1ea3 fix minor dashboard CSS issue 2015-11-19 23:52:23 -05:00
Unknwon
6a66e5fc98 better error message 2015-11-19 13:45:07 -05:00
Unknwon
c0b0ce7b1a #1971 more general rule to detect error 2015-11-19 13:04:07 -05:00
Unknwon
dc0c0dc06b fix typo for #1996 2015-11-19 11:52:39 -05:00
Unknwon
2158e6fc43 fix #1997 2015-11-19 11:40:00 -05:00
Unknwon
ee686f6231 minor fix on auto sign in 2015-11-19 00:22:16 -05:00
Unknwon
481be9b5c9 update locale #1965 2015-11-19 00:08:31 -05:00
Unknwon
9330c943cd work on #1891 2015-11-18 23:52:09 -05:00
Unknwon
915bf1d2e3 Merge pull request #1994 from arthuroy/develop
Fix #1965 - the hyperlink and the display name of the branch
2015-11-18 23:01:06 -05:00
Unknwon
f455125d4d fix #878 2015-11-18 21:21:47 -05:00
Unknwon
df339ad8b0 #1401 #493 Admin should be able to see private repositories 2015-11-18 19:49:11 -05:00
Unknwon
2c653141a8 #1742 Update default branch in git repository while change in web view 2015-11-18 19:32:23 -05:00
Arthur Ouyang
fc56f42dc3 Use refStr[len("refs/heads/"):] instead of refStr[11:] and fix error
Fix #1965
2015-11-19 08:10:44 +08:00
Arthur Ouyang
0bd4d15e47 Use refStr[11:] instead of TrimPrefix
Fix #1965
2015-11-19 08:05:27 +08:00
Arthur Ouyang
e04c97b9fa Fix #1965 - the hyperlink and the display name of the branch
The hyperlink and the display name of the branch if the branch is in a folder or the branch name has '#'
2015-11-19 07:31:55 +08:00
Unknwon
f04d773f4f UI: long organization name in create repository owner list 2015-11-18 17:42:20 -05:00
Unknwon
4325b01a58 minor fix for #1992 2015-11-18 17:19:36 -05:00
Unknwon
052519a7d7 Merge pull request #1992 from Gibheer/disable_version_display
fix #1957 - disable version display
2015-11-18 17:15:14 -05:00
Gibheer
e347736c9e fix old naming
This is the old naming of the variable and I forgot to fix it. It now
works and is tested.
2015-11-18 23:12:42 +01:00
Gibheer
56006da34b fix #1957 - disable version display
This allows the admin to disable the version information about gogs and
go in use in the footer.
2015-11-18 22:32:31 +01:00
Unknwon
efea642d6c add admin op: delete missing repos 2015-11-18 15:37:48 -05:00
Unknwon
81d7359fdd fix #1981 2015-11-18 15:11:37 -05:00
Unknwon
9a0902523b fix #1987 2015-11-18 15:01:11 -05:00
Unknwon
d2808e38fe fix #1979 2015-11-18 14:51:38 -05:00
Unknwon
7a9777ae36 fix #1990 2015-11-18 14:12:10 -05:00
Unknwon
62533560ce fix #1988 2015-11-18 13:14:46 -05:00
Unknwon
dc7e74ebb1 Merge pull request #1769 from sapk/fix-admin-dashboard-new-ui
Fix admin dashboard new ui
2015-11-18 10:24:35 -05:00
Unknwon
9a27e1b90c minor css fix 2015-11-17 02:20:49 -05:00
Unknwon
ff5f14431e fix #1448 2015-11-17 02:18:05 -05:00
Unknwon
ab9411be2a clean up code 2015-11-16 23:33:40 -05:00
Unknwon
114e6790f8 fix CSS width 2015-11-16 23:31:35 -05:00
Unknwon
ec5f881384 fix CSS 2015-11-16 23:30:05 -05:00
Unknwon
9ab96172fc new watchers, stars and forks UI 2015-11-16 23:28:46 -05:00
Unknwon
e06558e208 #1922 Pull request fail to merge with BIN 2015-11-16 21:18:04 -05:00
Unknwon
54fd4cc5fb Merge pull request #1962 from 0rax/develop
Update S6 Path after Alpine Package update
2015-11-16 12:11:06 -05:00
Jean-Philippe Roemer
3deddabfd8 Add set -x & set -e to docker/build.sh for better debugging 2015-11-16 16:49:40 +00:00
Jean-Philippe Roemer
0cbf56855a Update s6 path following package update 2015-11-16 16:48:09 +00:00
Unknwon
917d334ebd only show user's activities in profile 2015-11-16 11:43:23 -05:00
Unknwon
bb1fbe4e70 octicon fix in feeds 2015-11-16 11:39:48 -05:00
Unknwon
cceb3364bb CSS fix 2015-11-16 11:33:46 -05:00
Unknwon
d370effca5 minor fix for #1961 2015-11-16 11:20:11 -05:00
Unknwon
29ed7872f8 repo sidebar active class 2015-11-16 11:16:52 -05:00
Unknwon
5dc3dd1704 fix #1960 2015-11-16 11:11:59 -05:00
Unknwon
134d8e7681 work on #1961 2015-11-16 10:14:12 -05:00
Unknwon
c9b65f0fdc fix #1959 2015-11-16 00:26:20 -05:00
Unknwon
951037c0ae remove test code 2015-11-16 00:16:43 -05:00
Unknwon
7046df6028 UI fix 2015-11-16 00:16:03 -05:00
Unknwon
1db3ae6601 UI fix 2015-11-16 00:03:23 -05:00
Unknwon
612d0d6d25 minor HTML fix 2015-11-15 23:59:39 -05:00
Unknwon
18de67380c fix #1958 2015-11-15 23:52:46 -05:00
Unknwon
1a901433e2 minor fix for #1949 2015-11-15 17:37:26 -05:00
Unknwon
e030109b5a fix api broken 2015-11-15 17:07:44 -05:00
Unknwon
35d49d3b34 Merge pull request #1949 from TheHowl/develop
Fix bad issue links in non-readme markdown files
2015-11-15 16:23:55 -05:00
Howl
ca5678da32 Safely detect urlPrefix in the format /:owner/:repo 2015-11-15 22:22:25 +01:00
Unknwon
4d3138cd10 fix #1953 2015-11-15 14:55:12 -05:00
Unknwon
942284648e fix markdown padding 2015-11-15 14:50:35 -05:00
Unknwon
4f03b81ec7 #1931 Test patch does not checkout correct base branch 2015-11-15 14:41:36 -05:00
Unknwon
b4970b3cc3 fix #1948 2015-11-15 14:05:51 -05:00
Howl
85c58eba90 Fix bad issue links in non-readme markdown files 2015-11-15 12:04:43 +01:00
Unknwon
84a43b38cf remove unused code 2015-11-14 13:22:24 -05:00
Unknwon
7c80eba77f minor UI fix and fix ssh race 2015-11-14 13:21:31 -05:00
Unknwon
9c12ed3b6e clean up 2015-11-14 04:54:27 -05:00
Unknwon
7b1c10ea7e new repo ui
- copy link button: #1396, #1168, #1668,
- synxtax highlight: #1712, #1549, #1315, #670
- z-index: #1942
2015-11-14 04:34:01 -05:00
Unknwon
679af4ddea clean up 2015-11-13 22:45:33 -05:00
Unknwon
f8ae161c74 fix #1302 2015-11-13 17:37:02 -05:00
Unknwon
1d57f0d64f Show custom avatars in commits 2015-11-13 17:10:25 -05:00
Unknwon
1559bd58e7 save custom avatar as PNG 2015-11-13 16:43:43 -05:00
Unknwon
6a664e88c7 #1854 show issue content 2015-11-13 12:11:45 -05:00
Unknwon
0f438ef0b3 new dashboard ui 2015-11-13 12:05:48 -05:00
Unknwon
a6c7716742 minor fix for #1935 and fix #1854 2015-11-13 10:05:50 -05:00
Unknwon
1c3754bcec Merge pull request #1935 from makhov/issue-title-at-dashboard
Show issue title at dashboard
2015-11-13 10:00:38 -05:00
Alexey Makhov
ee645af107 #1854 change issueId to issueIndex 2015-11-13 09:21:22 +03:00
Alexey Makhov
3e7695ae91 #1854 improves 2015-11-13 00:16:51 +03:00
Unknwon
2268d28189 Merge pull request #1936 from fanningert/patch-1
Update .gopmfile
2015-11-12 16:11:12 -05:00
Thomas Fanninger
eee6e4206a Update .gopmfile
Add missing dependencies
2015-11-12 22:08:41 +01:00
Alexey Makhov
1bfebdcdf6 #1854 improves 2015-11-13 00:01:51 +03:00
Alexey Makhov
588a0db218 #1854 issue title at dashboard 2015-11-12 23:09:48 +03:00
Unknwon
d1e28ac013 Merge pull request #1933 from renatoaquino/develop
HOME fix into FreeBSD init script
2015-11-12 12:50:45 -05:00
Unknwon
523dc8b613 prepare release 2015-11-12 12:42:39 -05:00
Renato Aquino
923c45d721 HOME fix into FreeBSD init script 2015-11-12 15:36:35 -02:00
Unknwon
10427b2178 fix #1930 2015-11-12 12:32:40 -05:00
Unknwon
020fb43b77 space bar as click 2015-11-11 18:44:39 -05:00
Unknwon
675cd997d8 minor tabindex fix 2015-11-11 16:24:13 -05:00
Unknwon
908f2924ce fix #1824 and fix #1917 2015-11-11 16:19:28 -05:00
Unknwon
f28173bf50 Merge pull request #1927 from makhov/bug/1824
Fix #1824 add tabindex
2015-11-11 15:30:55 -05:00
Alexey Makhov
7835c2212c issue #1824 add tabindex 2015-11-11 22:59:06 +03:00
Unknwon
3b62a0fe0e fix #1572 fix file histrory paging issue 2015-11-10 16:46:17 -05:00
Unknwon
2db785b3ed convert missing name 2015-11-09 11:39:03 -05:00
Unknwon
647688bd06 #1266 convert name to lower when handle SSH 2015-11-09 11:34:04 -05:00
Unknwon
0d4498429c work on #1904 2015-11-09 02:13:25 -05:00
Unknwon
3ec650b0ef update version 2015-11-08 17:03:21 -05:00
Unknwon
18c841050b fix 1540 and experimental SSH server support 2015-11-08 16:59:56 -05:00
Unknwon
b55499d039 go vet and fix #1890 2015-11-08 14:31:49 -05:00
Unknwon
58436b5ea5 prepare 0.7.0 2015-11-08 11:08:13 -05:00
Unknwon
d85a1d478e admin op with notice 2015-11-07 20:20:51 -05:00
Unknwon
676d774d88 fix #1902 2015-11-07 00:39:45 -05:00
Unknwon
e7aabf70dc #1905 Triming the issue title 2015-11-06 11:17:46 -05:00
Unknwon
6f929dcd9e #1895 Case sensitive clone URL 2015-11-06 11:14:58 -05:00
Unknwon
54ca0b2f09 #1433 images with links in Markdown
- #1904 minor fix on image link
2015-11-06 11:10:27 -05:00
Unknwon
2bd64621fc #1900 last updatede time not update after push 2015-11-05 19:18:59 -05:00
无闻
05b419b219 Merge pull request #1898 from 0rax/develop
Docker Improvmement and small fix
2015-11-05 15:17:45 -05:00
Unknwon
b163d79a2e DO sponsor 2015-11-05 14:40:22 -05:00
Unknwon
f255b1e86d #1896 fatal when no needed update task 2015-11-04 21:57:10 -05:00
Unknwon
9372eedf2e README 2015-11-04 18:34:08 -05:00
Jean-Philippe Roemer
14a8a46bec Add logging when socat link creation is deactivated 2015-11-04 19:43:25 +00:00
Jean-Philippe Roemer
7679aa1a21 Fix typo & cleanup start.sh code 2015-11-04 19:34:26 +00:00
Jean-Philippe Roemer
9a8aeef478 Add syslog & fix /data/git rights & SOCAT_LINK env var
- Resolve #1893
- Add syslogd to output sshd log on stdout (via `docker logs`)
- Enforce directory rights on `/data/git`, `/data/gogs` & `/data/ssh`
- Add `SOCAT_LINK` environment variable to prevent the creation of scout links when they are not needed (see #1815)
2015-11-04 19:22:15 +00:00
Unknwon
603c7389b8 #1459 Dashboard issues lacks sorting 2015-11-04 12:50:02 -05:00
Unknwon
00eb2b221f fix #1876 2015-11-04 10:16:50 -05:00
Unknwon
1b5e1bebc2 fix #1886 2015-11-04 00:32:25 -05:00
Unknwon
3a81fdf092 rename fields 2015-11-03 22:49:06 -05:00
Unknwon
6f0a41b8b2 #1511 Allow local import only for admin users 2015-11-03 18:40:52 -05:00
Unknwon
25ec20d525 #1838 update merge base before generate new patch 2015-11-03 17:25:39 -05:00
Unknwon
8e262f3ec4 update locales 2015-11-03 14:34:21 -05:00
Unknwon
2cee0f84c0 Revert "fix CI..."
This reverts commit 94b2816446.
2015-11-03 13:19:45 -05:00
Unknwon
94b2816446 fix CI... 2015-11-03 13:15:07 -05:00
Unknwon
8411b50f5d work on #1882 2015-11-03 13:00:04 -05:00
Unknwon
8a87bee434 what's wrong with go tip? 2015-11-03 12:46:23 -05:00
Unknwon
1dfa693a5c fix CI!! 2015-11-03 12:42:21 -05:00
Unknwon
d5b92b61d7 fix CI 2015-11-03 12:29:14 -05:00
Unknwon
a374751eb8 add Makefile 2015-11-03 12:16:43 -05:00
Unknwon
0af035c37e #1078 not show bin when rename a file 2015-11-03 09:52:17 -05:00
Unknwon
bc82157216 fix #1078 2015-11-02 19:55:24 -05:00
Unknwon
8eb4c3121a work on #1880 2015-11-02 18:54:47 -05:00
Unknwon
75aab86a8d fix problem with #1879 2015-11-02 14:01:19 -05:00
Unknwon
92535c9df0 update some libs 2015-11-02 02:00:12 -05:00
无闻
50058b3c6d Merge pull request #1871 from Gibheer/config_minimum_key_sizes
move minimum key sizes to config
2015-11-01 12:39:12 -05:00
Unknwon
b0226a1d05 fix #1831 2015-11-01 12:30:27 -05:00
Unknwon
67ced4aaca safe check 2015-10-31 23:25:08 -04:00
Unknwon
04806b614e more on #1705 2015-10-31 23:22:28 -04:00
Unknwon
cb100c7781 work #1705 2015-10-31 23:18:58 -04:00
Unknwon
5cad124704 fix #1697 2015-10-31 19:10:44 -04:00
Unknwon
fad31ca302 work on #1748 2015-10-31 18:59:07 -04:00
无闻
6b8bef3cf6 Merge pull request #1869 from tjyang/tjyang
adding extra check on logpath
2015-10-31 13:17:02 -04:00
Unknwon
940898a3ff minor fix for #1865 2015-10-31 12:35:26 -04:00
无闻
75fe134072 Merge pull request #1865 from mattarnster/develop
Translation for invalid milestone due date format ISO 8601 fix.
2015-10-31 12:22:29 -04:00
Unknwon
16feb5b655 minor fix on #1863 2015-10-31 12:04:04 -04:00
无闻
659bd29bc5 Merge pull request #1863 from nathan7/go-source-meta
Add go-source directive to repo pages
2015-10-31 11:45:40 -04:00
无闻
91c9069c4d Merge pull request #1862 from nathan7/fix-private
Fix the JSON field names for MigrateRepoForm
2015-10-31 11:14:38 -04:00
T.J. Yang
b992deae92 adding extra check on logpath 2015-10-31 07:32:34 -05:00
Matt Arnold
c7eaf96b37 Translation for invalid milestone due date format ISO 8601 fix. 2015-10-30 14:44:03 +00:00
Nathan Zadoks
4323a89c03 Add go-source directive to repo pages
This makes Gogs repos work with godoc.org: https://github.com/golang/gddo/wiki/Source-Code-Links
2015-10-30 14:00:09 +01:00
Gibheer
b90b0c1191 move minimum key sizes to config
This moves the minimum key sizes into the config file, so that anyone
can modify the restrictions.
2015-10-30 13:53:06 +01:00
Nathan Zadoks
f1aa4c0524 Fix the JSON field names for MigrateRepoForm
For some reason, the field names for Private and Mirror got swapped.
2015-10-30 02:44:45 +01:00
Unknwon
102b675f96 remove test code 😵 2015-10-29 21:19:51 -04:00
Unknwon
073da3c49d #470 fix max length of email 2015-10-29 21:12:41 -04:00
Unknwon
0fe6fe663e fix e-mail type 2015-10-29 21:10:01 -04:00
Unknwon
7d72c8333e work on #470 and fix miror JS issue when choose targets on compare and pull 2015-10-29 21:09:48 -04:00
Unknwon
706b0f72e2 fix issue comment mention and autofix count when start 2015-10-29 20:40:57 -04:00
无闻
1f4beb530c Merge pull request #1858 from ChasingLogic/patch-1
Fixed broken link in README
2015-10-29 16:50:11 -04:00
Mathew Robinson
89bf56a6ac Fixed broken link in README
The link http://gogs.io/docs/intro/ produces a 404 error and is used in the README updated it to http://gogs.io/docs/intro which correctly links to the introductory documentation
2015-10-29 15:34:59 -04:00
无闻
31b375782b Merge pull request #1853 from andreynering/windows-service
Adding simple script to install Gogs as a Windows service.
2015-10-29 13:20:27 -04:00
Andrey Nering
0252629956 Adding simple script to install Gogs as a Windows service. 2015-10-29 08:05:50 -02:00
Unknwon
4f0e31e96d update README 2015-10-29 03:21:59 -04:00
Unknwon
f5689ee3a5 update locale 2015-10-28 02:30:24 -04:00
无闻
a020cf803b Merge pull request #1845 from SergioBenitez/develop
Sanitize input to LDAP authentication module
2015-10-28 02:24:50 -04:00
Sergio Benitez
630ebbe6c2 Sanitizing input to LDAP authentication module. 2015-10-26 18:08:59 -07:00
Unknwon
b9f5def5dc fix insecure tls when trigger task 2015-10-26 09:16:24 -04:00
Unknwon
87c3c8172a #1711 try to indicate sqlite session issue when merge 2015-10-25 18:35:27 -04:00
Unknwon
022820103d #1657 allow forcing all private repos 2015-10-25 04:26:26 -04:00
Unknwon
d5fab7f1b9 update README 2015-10-25 03:30:16 -04:00
Unknwon
c3ba5590c9 Reopen PR need retest patch 2015-10-25 03:10:22 -04:00
无闻
379629d28a Merge pull request #1836 from likeit92/develop
workaround for reverse proxy, ssh, submodule
2015-10-24 16:51:59 -04:00
Matthias Pioch
8b92f9cca6 fix workaround for reverse proxy, ssh, submodule 2015-10-24 22:46:13 +02:00
Matthias Pioch
78a4e71245 workaround for reverse proxy, ssh, submodule 2015-10-24 22:03:08 +02:00
Unknwon
63e6e31271 add config and update locale 2015-10-24 15:35:26 -04:00
Unknwon
2be5837cb0 New push to base repo of base branch: retest all corresponding patches 2015-10-24 14:48:11 -04:00
Unknwon
71d8ff247d update some locales 2015-10-24 03:40:47 -04:00
Unknwon
0fbb8c8826 New push to head repo of head branch: regenerate patch and retest apply 2015-10-24 03:36:47 -04:00
Unknwon
e0aab4a7f6 #1830 new comment with status change overwrites issue content 2015-10-23 12:54:19 -04:00
Unknwon
db7ac8bc1d use notice instead of suspend delete archives 2015-10-23 11:03:15 -04:00
Unknwon
c6ce6bd4c2 work on #1830 2015-10-23 10:31:13 -04:00
Unknwon
b5fdf0947b reduce code 2015-10-22 17:47:08 -04:00
Unknwon
35a65736fa add get repo API 2015-10-22 17:46:07 -04:00
Unknwon
04af4b24fd work on #1818 2015-10-22 14:47:32 -04:00
无闻
a301c7ed26 Merge pull request #1814 from 0rax/develop
Update docker `socat` link creation
2015-10-21 13:08:42 -04:00
Jean-Philippe Roemer
f7c7837fc8 Docker socat link: default port fix
- Remove port 80 & 443 unused by the container
- Add port 3000 used by gogs application
2015-10-21 16:55:55 +01:00
Jean-Philippe Roemer
02289479ef Docker socat link creation
- `start.sh` will now verify that the port is not already used by another service
- Resolve #1807
- Log when a service is created, or could not be created
- Keep track of which port is already used, including goes & sshd port
2015-10-21 16:27:56 +01:00
Unknwon
eac91a74d8 Merge branch 'develop' of github.com:gogits/gogs into develop 2015-10-21 06:40:56 -04:00
Unknwon
f17b746a01 update locale 2015-10-21 06:40:44 -04:00
无闻
d3f67d341f Merge pull request #1811 from likeit92/develop
fix build error with PKGBUILD (archlinux, x64)
2015-10-21 04:50:05 -04:00
Matthias Pioch
0f4f81f1fb fix import path on x64 systems, fix #1795 2015-10-21 09:00:35 +02:00
无闻
d0c17adfea Merge pull request #1809 from likeit92/master
fix build error with PKGBUILD (archlinux)
2015-10-20 18:01:35 -04:00
Matthias Pioch
87c05c386f fix import path, fix #1795 2015-10-20 23:13:06 +02:00
无闻
8fc8848ce2 Merge pull request #1803 from kenno/develop
Fix import path
2015-10-19 10:13:39 -04:00
Soputtra San
1f1abb17e2 Fix import path 2015-10-19 23:43:11 +11:00
Unknwon
fc7959d3bc New/reopen PR checks if there is any unmerged and open PR 2015-10-18 19:30:39 -04:00
Unknwon
4dc6285715 add new status: checking 2015-10-18 17:18:54 -04:00
无闻
9825760817 Merge pull request #1799 from legoktm/patch-1
Simplify disable Gravatar preference wording
2015-10-18 14:16:32 -04:00
Kunal Mehta
9573f9afe9 Simplify disable Gravatar preference wording
"Enable this to disable..." is needlessly confusing, simplify it by being
upfront that this setting disables the Gravatar fetching.
2015-10-17 20:46:52 -07:00
Unknwon
6599869f28 #1790 fast return for too large diff 2015-10-17 17:25:45 -04:00
Unknwon
932dbccb67 fix import path, fix #1782 2015-10-15 21:28:12 -04:00
Antoine GIRARD
5edc2f6d6c Fix indent tmpl 2015-10-14 01:09:33 +02:00
Antoine GIRARD
4dd731cb53 Fix indent .less 2015-10-14 01:05:32 +02:00
无闻
a749e6adcf Merge pull request #1778 from limianwang/develop
fix typo in README for Docker
2015-10-13 18:23:44 -04:00
Limian Wang
b854d3ba40 fix typo in README for Docker 2015-10-13 15:22:33 -07:00
Unknwon
6a6e43f964 print out git version 2015-10-13 16:01:57 -04:00
Antoine GIRARD
ae0fadeb0e To quick to copy-paste 2015-10-13 02:08:13 +02:00
Antoine GIRARD
2717ada14c Little fix 2015-10-13 02:04:04 +02:00
Antoine GIRARD
e1c04f043b Implement new ui to dashboard 2015-10-13 01:40:35 +02:00
无闻
a42514613f Merge pull request #1767 from 0rax/develop
Fix bug introduced in #1759 preventing ssh server to launch
2015-10-12 17:37:14 -04:00
Jean-Philippe Roemer
9acf02ad7f Fix bug introduced in #1759 preventing ssh server to launch
- ssh/setup: test directive now check if a file exist in key path instead of a folder
- ssh/setup script was hanging waiting for an input about rewriting the key
  as the test case was returning true, when it should have been false
  (check if file is a folder instead of a file)
2015-10-12 22:13:22 +01:00
无闻
5c6df9f31b Merge pull request #1766 from 0rax/develop
Docker repository path backward compatibility fix
2015-10-12 16:59:41 -04:00
Jean-Philippe Roemer
533c6a8e08 Edit comment to make it clear that we are talking about container version 2015-10-12 21:52:20 +01:00
Jean-Philippe Roemer
bfed40eec4 Docker repository path backward compatibility fix:
- resolve #1765
- create link to old git repository path: `/home/git/gogs-repository`
2015-10-12 21:28:26 +01:00
无闻
575300cd57 Merge pull request #1764 from 0rax/develop
Volumed data setup changed to allow #1759
2015-10-12 14:52:08 -04:00
Jean-Philippe Roemer
9cba6ff84b Volumed data setup changed to allow #1759
- Volumed subfolder now created up in the ENTRYPOINT script, this way
  they are created before S6 even starts making VOLUME.
- The subfolder will be created during VOLUME creation too as ENTRYPOINT
  script will be run before /bin/true
- SSH Keys will now be created on a single key basis not replying on the
  existence of /data/ssh folder
2015-10-12 18:46:45 +01:00
Unknwon
570ddefc32 print log every time regulater timezone 2015-10-10 19:04:26 -04:00
无闻
5676fa5b5d Merge pull request #1758 from 0rax/develop
Docker Container Restart Fix
2015-10-10 16:28:59 -04:00
Jean-Philippe Roemer
fc427432ed Docker Container Restart Fix
- Fix s6 fifodir error on container restart
- Add .tags* to .gitignore (Atom auto ctags generation)
2015-10-10 21:03:19 +01:00
无闻
d7f390a3b0 Merge pull request #1757 from alvaroaleman/bugfix/docker_gosu
Update gosu, this fixes #1756
2015-10-10 14:54:05 -04:00
Alvaro Aleman
2671c86ba7 Update gosu, this fixes #1756 2015-10-10 20:48:26 +02:00
Unknwon
f1c2276c8d add log when cannot regulate time 2015-10-09 23:05:20 -04:00
Unknwon
2020bafee1 fix typo 2015-10-09 22:09:07 -04:00
Unknwon
bc2f546023 fix README links 2015-10-09 15:41:08 -04:00
无闻
ef6d12844c Merge pull request #1752 from soudy/develop
fixed dead links in README
2015-10-09 15:26:49 -04:00
Steven Oud
a443fcf33a fixed dead links in README 2015-10-09 11:09:58 +02:00
Unknwon
b5e6af9587 update locale 2015-10-08 22:49:08 -04:00
Unknwon
01dc8f8a4f fix change visivility of non-org does not affect forks 2015-10-08 22:38:42 -04:00
Unknwon
aff49b1c9e unified API error response 2015-10-08 20:36:07 -04:00
无闻
b1941f1da1 Merge pull request #1736 from soudy/develop
Added repository remove option to API
2015-10-08 20:01:56 -04:00
Unknwon
19c3745488 fix quick guide style 2015-10-07 23:11:32 -04:00
Unknwon
ea6c6bc20a work on 1714 2015-10-05 09:54:55 -04:00
Unknwon
db00aa7653 add log for trigger hook when push through SSH 2015-10-05 09:46:00 -04:00
Unknwon
215920772a save PR info as patch and minor fix on PR 2015-10-04 20:54:06 -04:00
Steven Oud
6fe868a4d5 added repository remove option to api 2015-10-04 23:29:23 +02:00
无闻
02d3b66265 Merge pull request #1686 from preovaleo/develop
Fix #1669 on develop
2015-10-03 12:57:24 -04:00
无闻
fe8495e4a5 Merge pull request #1735 from soudy/develop
Add links to forks/watchers/stars in new layout header
2015-10-03 12:46:16 -04:00
Steven Oud
280fde9b7c added links to forks/watchers/stars in new layout header 2015-10-03 18:04:44 +02:00
无闻
79fb24a397 Merge pull request #1732 from 0rax/develop
Docker Container: Init 1 & Initialisation
2015-10-02 20:02:03 -04:00
Unknwon
4465c58f4b new quick start page 2015-10-02 19:58:36 -04:00
Jean-Philippe Roemer
5981f1edcd Remove fixed issue 2015-10-02 23:32:46 +01:00
Jean-Philippe Roemer
ad5e0b833c Docker Container: Init 1 & Initialisation
- Now using a setup script before starting the app. The separation of
the run script and the setup script will make service initialisation a
little bit clearer
- Now calling start.sh script as ENTRYPOINT and S6 as CMD. This way
when running the container with just a shell script, the start.sh
script will be launched before, making debugging easier
- Added note about `.dockerignore` ignored during Docker Hub Automated
Build
2015-10-02 23:13:39 +01:00
无闻
e5310cdbc1 Merge pull request #1731 from ogarcia/develop
Several bugfixes in Docker build
2015-10-02 15:59:10 -04:00
Unknwon
e34d0063c3 Update Docker README for #1708 2015-10-02 15:44:59 -04:00
Óscar García Amor
3e7d8db7a2 Several bugfixes in Docker build
- Removed unnecessary variables
- Fixed symbolic links creation
- Fixed enter point
- Less intermediate containers
2015-10-02 21:18:13 +02:00
无闻
655b69cb1f Merge pull request #1730 from 0rax/develop
New approach to Gogs Docker Container
2015-10-02 11:31:12 -04:00
Jean-Philippe Roemer
fcb1f4ec07 Add bash to the image so bash git hooks can be used 2015-10-02 12:01:02 +01:00
Jean-Philippe Roemer
3cad8d9492 Use app/docker folder for SSH Configureation 2015-10-02 11:31:05 +01:00
Jean-Philippe Roemer
e63e0b3105 New approach to Gogs Docker Container
- VOLUME for ‘/data’
- Usage of S6 as PID 1 Process
- Usage of ‘socat’ so linked container (like databases) are binded to
localhost
- OpenSSH, Socat Link and Gogs are supervised using S6
- Size of container reduced to ~75Mo
2015-10-02 10:56:36 +01:00
无闻
d86c785410 Merge pull request #1725 from soudy/develop
Implemented #1721: see users who forked/starred/watched a repository
2015-10-02 04:55:02 -04:00
kendaru
b05c7b3faa moved routes to RouterRef group 2015-10-02 10:48:31 +02:00
kendaru
5c39d3fa7d changed integrated page number to GET 2015-10-02 10:04:11 +02:00
Unknwon
c60d8bc069 update locales 2015-10-02 02:55:52 -04:00
Steven
87d64acc9f fixed HTML indenting 2015-10-01 16:16:11 +02:00
Steven
c8aa9c6cb1 implemented #1721: see users who forked/starred/watched a repository 2015-10-01 15:51:46 +02:00
Steven
ecd59deb27 implemented #1721: see users who forked/starred/watched a repository 2015-10-01 15:17:27 +02:00
Unknwon
e0a099ec11 fix link again 2015-10-01 01:58:59 -04:00
Unknwon
5af872955b fix README links 2015-10-01 01:58:30 -04:00
无闻
e00268895c Merge pull request #1710 from geerlingguy/patch-1
Set USER env variable so installation can complete successfully.
2015-09-28 14:21:52 -04:00
Jeff Geerling
4a05c4a759 Set USER env variable so installation can complete successfully. 2015-09-26 23:33:53 -05:00
Unknwon
a205acf829 drop go 1.2 support 2015-09-26 17:54:02 -04:00
Unknwon
d5ad4e1141 prepare release 2015-09-26 14:09:09 -04:00
Unknwon
2dde2a8ad3 fix error detect when push commits 2015-09-25 21:06:31 -04:00
Unknwon
6dfee30bf0 #1602 Wrong commit order on issue page when pushing multiple commits 2015-09-25 20:35:56 -04:00
Unknwon
21e13cb51e #1525 Triggere mailer for admin created accounts 2015-09-25 19:45:44 -04:00
Unknwon
986447335d #1693 minor fix 2015-09-25 19:07:21 -04:00
无闻
84c727ae66 Merge pull request #1693 from sapk/fix-admin-repo-new-ui
Fix admin repos new ui
2015-09-25 19:05:27 -04:00
无闻
ee1256cf74 Merge pull request #1691 from sapk/fix-admin-organization-new-ui
Fix admin organization new ui
2015-09-25 19:03:53 -04:00
Antoine GIRARD
dfc16d0879 Fix admin repos new ui 2015-09-26 00:38:43 +02:00
Antoine GIRARD
a8fd615adc Fix indent in notice router 2015-09-25 19:56:23 +02:00
Antoine GIRARD
c98dad1cf3 New admin organization UI 2015-09-25 19:54:52 +02:00
Unknwon
0d5e57e4ae #1689 minor fixes and update locale 2015-09-25 12:58:39 -04:00
无闻
1c35380c2c Merge pull request #1689 from sapk/fix-admin-notice-new-ui
New admin notice UI based on user list example
2015-09-25 12:53:18 -04:00
Théo Bougé
bb7ddb45ff Fix typo 2015-09-25 18:46:50 +02:00
Théo Bougé
939d2054d8 fix typo 2015-09-25 18:45:35 +02:00
Antoine GIRARD
3544dafb64 Fix indent 2015-09-25 18:39:31 +02:00
Antoine GIRARD
fefce965f9 Add total next to system notice 2015-09-25 18:36:05 +02:00
Antoine GIRARD
14a1101139 New admin notice UI based on user list example 2015-09-25 18:13:38 +02:00
Théo Bougé
5a2093b053 fix the restart 2015-09-25 17:58:46 +02:00
Théo BOUGE
2f23cf98ea Fix #1669 2015-09-25 09:51:01 +02:00
无闻
4f8b209956 Merge pull request #1678 from TheHowl/TheHowl/fix-go-get-not-working-with-user-or-repo-names-with-uppercase-letters
Fix `go get` not working when there is a username or repository name with some uppercase letters.
2015-09-24 18:51:02 -04:00
Unknwon
043ded0896 fix #1683 2015-09-24 18:42:29 -04:00
Unknwon
e07675b480 fix test custom import path 2015-09-24 18:03:08 -04:00
Unknwon
4c30caad1c fix test import path 2015-09-24 17:58:40 -04:00
Unknwon
216f0477b5 fix subrepo break change 2015-09-24 17:55:01 -04:00
无闻
befed9c20c Merge pull request #1662 from SergioBenitez/develop
Commit messages now rendered with line breaks and prefix spacing.
2015-09-24 17:23:53 -04:00
Unknwon
e787e73e2f fix URL match 2015-09-24 14:20:07 -04:00
Howl
f8c09dc1ff Fix go get not working when there is a username or repository name with some uppercase letters. 2015-09-23 16:16:52 +02:00
Unknwon
42a38dfca3 #1665 improve docs 2015-09-21 10:44:40 -04:00
Unknwon
91220a2501 #1665 improve Docker docs 2015-09-21 10:13:47 -04:00
Unknwon
700ac8dea7 fix #1664 2015-09-21 10:09:21 -04:00
Unknwon
69b1d65c9b remove unused file and update locale 2015-09-20 23:29:59 -04:00
Sergio Benitez
7acbcf9ddd Commit messages now rendered with line breaks and prefix spacing. 2015-09-18 19:02:23 -07:00
Unknwon
2340bb1ed2 update locales 2015-09-18 10:02:17 -04:00
Unknwon
d9c5b3bcee #1659 fix change label title resets color 2015-09-18 06:18:31 -04:00
Unknwon
86d3c5cbb3 update Docker info 2015-09-17 19:18:49 -04:00
Unknwon
acf428863c fix #981 2015-09-17 17:21:27 -04:00
Unknwon
3fb1b6a608 drop oauth2 feature support 2015-09-17 16:11:44 -04:00
Unknwon
562e47f31c finish all new template for current ones 2015-09-17 15:45:36 -04:00
Unknwon
9d36fc6986 finish new auth e-mails 2015-09-17 14:57:24 -04:00
Unknwon
923873db85 fix possible XSS in view issue page 2015-09-17 14:22:46 -04:00
Unknwon
373731f5e8 user gomail and new activate account email tpl
- #1496: fallback plain text
- #1002: add date header
- #913: fix encoding of header
2015-09-17 01:54:12 -04:00
Unknwon
e75fd2f783 simplify names 2015-09-16 23:08:46 -04:00
Unknwon
a517cfdf7b fix codekit 2015-09-16 15:36:21 -04:00
Unknwon
2729eb998c #1646 and other minor fixes 2015-09-16 15:34:46 -04:00
Unknwon
b003b18788 #1649 Using commas to delineate emails 2015-09-16 15:04:54 -04:00
Unknwon
6a1907d994 revert simple LDAP userDN and update example 2015-09-16 12:15:14 -04:00
Unknwon
e303d74ab6 #1643 fix delete milestone 2015-09-15 10:03:50 -04:00
Unknwon
e4ecbcdf4a new sign in page 2015-09-14 22:50:44 -04:00
Unknwon
f5c7f22cc8 #1637 able to skip verify for LDAP 2015-09-14 15:48:51 -04:00
Unknwon
2bc3e83e1c fix simple LDAP userDN 2015-09-14 12:24:37 -04:00
Unknwon
d600530c20 #1635 PAM return error bug 2015-09-14 11:03:42 -04:00
Unknwon
2d1bb0cf49 #1633 admin delete user 2015-09-13 13:26:20 -04:00
Unknwon
5f1183cecf #697 add install option 2015-09-13 12:14:32 -04:00
Unknwon
b0bf4cc1cb fix #1632 and #1606 2015-09-13 11:26:25 -04:00
Unknwon
98108e379d #697 update locale and admin panel 2015-09-13 11:16:29 -04:00
Unknwon
83e747bfda #697 and #1606 and new admin edit user UI 2015-09-13 11:07:21 -04:00
Unknwon
e5ed5904c6 #1606 GUI bug while adding ldap user 2015-09-13 10:05:18 -04:00
Unknwon
1fa5b6711b fix html logic 2015-09-13 09:56:50 -04:00
Unknwon
8e0a69f86a #697 disable captcha and new admin create user UI 2015-09-13 09:51:51 -04:00
Unknwon
e2d6b0116e #1500 only regulateTimeZone for MySQL 2015-09-13 07:32:00 -04:00
Unknwon
cd37fccdfb #1625 remove auto_register and makes it default 2015-09-12 20:58:51 -04:00
Unknwon
6969c20afd #1625 LDAP not working 2015-09-12 19:20:37 -04:00
Unknwon
f4e54aafa5 typo in logo 2015-09-12 17:01:10 -04:00
Unknwon
d185f601d3 #1622 comment with whitespace 2015-09-12 16:58:18 -04:00
Unknwon
aff773f1b9 add tidb support 2015-09-12 15:31:36 -04:00
Unknwon
10de16beb0 typo and README 2015-09-12 09:21:09 -04:00
Unknwon
ed5a61153f fix #1629 2015-09-12 07:17:37 -04:00
Unknwon
47df562ced #1626 default db_type 2015-09-11 21:00:12 -04:00
Unknwon
c7ac237b57 fix css 2015-09-11 20:49:28 -04:00
Unknwon
be89802bd8 new admin user list UI 2015-09-11 20:42:26 -04:00
Unknwon
247017d9ff clean log 2015-09-11 13:33:36 -04:00
Unknwon
362d64df04 #1620 add allowed domains for SMTP auth 2015-09-11 13:32:33 -04:00
Unknwon
373ef5d15e #1500 timezone minutes offset 2015-09-11 12:33:27 -04:00
Unknwon
121a81a2c5 finish new edit auth UI 2015-09-11 12:03:08 -04:00
Unknwon
0617448282 finish new add auth UI 2015-09-10 17:11:41 -04:00
Unknwon
fa728d8dff more minor fix on 1581 2015-09-10 15:45:03 -04:00
Unknwon
26ac016b9f minor fix on #1581 2015-09-10 15:03:14 -04:00
Unknwon
cbd6276200 Merge branch 'develop' of https://github.com/SergioBenitez/gogs into develop
# Conflicts:
#	modules/bindata/bindata.go
2015-09-10 14:55:29 -04:00
Unknwon
52ec80fa18 finish all new user settings UI 2015-09-10 11:40:34 -04:00
Unknwon
c8d92fad30 #1595 pushing new branch will rereference issues in previous branch 2015-09-10 07:53:40 -04:00
无闻
c3061c61a7 Merge pull request #1609 from denghongcai/develop
Add allow attribute `class` to `<code>` tag
2015-09-10 05:51:17 -04:00
Hongcai Deng
e4d4662074 add regexp to restrict <code class=""></code> 2015-09-10 09:06:09 +08:00
Unknwon
9899ea71e8 #1611 fix bool type in sqlite query 2015-09-09 18:07:10 -04:00
Sergio Benitez
b954a22ce2 Updated binary data. 2015-09-09 13:45:38 -07:00
Sergio Benitez
964d0262ff Merged conflicts. 2015-09-09 13:44:51 -07:00
Unknwon
b75d0378cb clean log 2015-09-09 12:42:24 -04:00
Hongcai Deng
357c002c03 add allow attribute class to <code> 2015-09-09 17:31:14 +08:00
Unknwon
f432f1f41c #1604 suburl for release md preview 2015-09-08 10:05:57 -04:00
Unknwon
6f6f38e7c3 #1603 suburl for comment attachments 2015-09-08 09:09:40 -04:00
Unknwon
49e120a67c #1602 change status after comment issue 2015-09-08 06:34:02 -04:00
Unknwon
af324a6165 adjust icon css 2015-09-07 16:12:58 -04:00
Unknwon
7b0ae27549 #1599 org dashboard icon css 2015-09-07 15:37:34 -04:00
Unknwon
31e7b0f588 #1599 status button mrigin-right 2015-09-07 15:22:52 -04:00
Unknwon
03ea29eb36 minor fix on icon 2015-09-07 15:03:31 -04:00
Unknwon
32c12d553c Able to use GOGS_WORK_DIR environment variable to specify work directory 2015-09-07 14:06:05 -04:00
Unknwon
cc83043edc minor fix on template 2015-09-07 14:02:09 -04:00
Unknwon
3d9b98fae4 #1585 order owners list by last changed time 2015-09-07 13:58:23 -04:00
无闻
36405d0faa Merge pull request #1596 from denghongcai/develop
implemented #1528
2015-09-07 13:10:59 -04:00
Unknwon
5020576e80 minor regexp fix on #1593 2015-09-07 11:56:06 -04:00
Unknwon
56eb252098 #1593 fix repo name router bug 2015-09-07 11:51:53 -04:00
Hongcai Deng
fbb3486c95 fix syntax error 2015-09-07 18:28:56 +08:00
Hongcai Deng
7b92dc3d9d implemented #1528 2015-09-07 18:25:15 +08:00
Unknwon
645d4d0c5b #1594 Collaborative Repositories missing suburl on Dashboard 2015-09-07 05:57:19 -04:00
Unknwon
232c22208c remove rsync 2015-09-06 18:53:59 -04:00
Unknwon
0806725ca5 minor fix on #1589
Signed-off-by: Unknwon <u@gogs.io>
2015-09-06 18:19:30 -04:00
无闻
d504ee0417 Merge pull request #1589 from siadat/migrating-empty-repository
Set IsBare true if migrated repo is empty
2015-09-06 18:17:58 -04:00
Sina Siadat
ae1650824c Set IsBare true if migrated repo is empty.
Executes `git log -1` and sets IsBare to true if stderr contains this
string:

  fatal: bad default revision 'HEAD'
2015-09-07 02:20:51 +04:30
Unknwon
c38754d432 #1191 allow upload avatar for org
Signed-off-by: Unknwon <u@gogs.io>
2015-09-06 17:12:02 -04:00
Unknwon
85f34ba538 new user profile settings UI
Signed-off-by: Unknwon <u@gogs.io>
2015-09-06 16:31:22 -04:00
Unknwon
00767a0522 finish new org settings page 2015-09-06 11:25:08 -04:00
Unknwon
4dbc322859 fix org header order 2015-09-06 11:01:05 -04:00
Unknwon
edc99bc8a4 new org options UI 2015-09-06 10:56:31 -04:00
Unknwon
80701d45bb #1193 Make organization emails non-mandatory 2015-09-06 10:08:14 -04:00
Unknwon
aa67de910a fix #1558 2015-09-06 08:54:08 -04:00
Unknwon
24658fcbdd #633 dix suburl 2015-09-06 06:57:25 -04:00
Unknwon
705224353b minor fix 2015-09-05 19:58:44 -04:00
Sergio Benitez
079a2d68db Minor fixes to the LDAP module readme 2015-09-05 15:26:31 -07:00
Unknwon
119dec51f2 Mirror repository should not allow fork 2015-09-05 14:31:52 -04:00
Unknwon
46dce2d653 fix emoji edit not rendered 2015-09-05 14:18:02 -04:00
Unknwon
d48cde6ec8 #633 emoji 2015-09-05 14:14:03 -04:00
Unknwon
3b0c2cb480 #633 Add Emoji support 2015-09-05 14:11:05 -04:00
无闻
d464d3b0c3 Merge pull request #1584 from manfer/dashboardtableheader
remove dashboard operations table header
2015-09-05 11:33:27 -04:00
Gogs
b80aef0fa6 remove dashboard operations table header 2015-09-05 16:57:25 +02:00
Sergio Benitez
0f07a5cb84 Updated the LDAP auth module README. 2015-09-04 21:14:20 -07:00
Sergio Benitez
7cb4aa8d82 Updated binary data. 2015-09-04 20:52:35 -07:00
Sergio Benitez
aa9c36514f Merged conflicts. 2015-09-04 20:49:07 -07:00
Sergio Benitez
2d1db4bf05 Added LDAP simple auth support. 2015-09-04 20:48:34 -07:00
1268 changed files with 29752 additions and 40906 deletions

View File

@@ -13,8 +13,7 @@ watch_dirs = [
watch_exts = [".go"]
build_delay = 1500
cmds = [
#["go-bindata", "-o=modules/bindata/bindata.go", "-ignore=\\.DS_Store|README", "-pkg=bindata", "conf/..."],
["go", "install", "-tags", "sqlite"],# redis memcache cert pam
["go", "build", "-tags", "sqlite"],
["go", "install", "-race"], # sqlite redis memcache cert pam tidb
["go", "build", "-race"],
["./gogs", "web"]
]

View File

@@ -1,6 +1,14 @@
.git
.git/
.git/*
conf
conf/
conf/*
packager
packager/
packager/*
scripts
scripts/
scripts/*
*.yml
*.md
@@ -9,4 +17,4 @@ scripts/*
.gitignore
.gopmfile
config.codekit
LICENSE
LICENSE

2
.gitignore vendored
View File

@@ -35,3 +35,5 @@ docker/docker/Dockerfile
docker/docker/init_gogs.sh
gogs.sublime-project
gogs.sublime-workspace
.tags*
release

View File

@@ -3,37 +3,46 @@ path = github.com/gogits/gogs
[deps]
github.com/bradfitz/gomemcache = commit:72a68649ba
github.com/Unknwon/cae = commit:2e70a1351b
github.com/Unknwon/com = commit:47d7d2b81a
github.com/Unknwon/i18n = commit:7457d88830
github.com/Unknwon/macaron = commit:635c89ac74
github.com/Unknwon/paginater = commit:cab2d086fa
github.com/codegangsta/cli = commit:142e6cd241
github.com/go-sql-driver/mysql = commit:3dd7008ac1
github.com/go-xorm/core = commit:515edd92c1
github.com/go-xorm/xorm = commit:ce23797899
github.com/codegangsta/cli = commit:0302d39
github.com/go-macaron/binding = commit:2502aaf
github.com/go-macaron/cache = commit:5617353
github.com/go-macaron/captcha = commit:8aa5919
github.com/go-macaron/csrf = commit:715bca0
github.com/go-macaron/gzip = commit:4938e9b
github.com/go-macaron/i18n = commit:d2d3329
github.com/go-macaron/inject = commit:c5ab7bf
github.com/go-macaron/session = commit:66031fc
github.com/go-macaron/toolbox = commit:ab30a81
github.com/go-sql-driver/mysql = commit:d512f20
github.com/go-xorm/core = commit:acb6f00
github.com/go-xorm/xorm = commit:a8fba4d
github.com/gogits/chardet = commit:2404f77725
github.com/gogits/go-gogs-client = commit:519eee0af0
github.com/lib/pq = commit:b269bd035a
github.com/macaron-contrib/binding = commit:de6ed78668
github.com/macaron-contrib/cache = commit:a139ea1eee
github.com/macaron-contrib/captcha = commit:9a0a0b1468
github.com/macaron-contrib/csrf = commit:98ddf5a710
github.com/macaron-contrib/i18n = commit:da2b19e90b
github.com/macaron-contrib/oauth2 = commit:1adb5ce072
github.com/macaron-contrib/session = commit:e48134e803
github.com/macaron-contrib/toolbox = commit:acbfe36e16
github.com/mattn/go-sqlite3 = commit:897b8800a7
github.com/mcuadros/go-version = commit:d52711f8d6
github.com/microcosm-cc/bluemonday = commit:2b7763a06c
github.com/mssola/user_agent = commit:a163d6a569
github.com/msteinert/pam = commit:6534f23b39
github.com/gogits/git-shell = commit:de77627
github.com/gogits/go-gogs-client = commit:4b541fa
github.com/issue9/identicon = commit:f8c0d2c
github.com/klauspost/compress = commit:bcd0709
github.com/klauspost/cpuid = commit:8d9fe96
github.com/klauspost/crc32 = commit:0aff1ea
github.com/lib/pq = commit:11fc39a
github.com/mattn/go-sqlite3 = commit:5651a9d
github.com/mcuadros/go-version = commit:d52711f
github.com/microcosm-cc/bluemonday = commit:4ac6f27
github.com/msteinert/pam = commit:02ccfbf
github.com/nfnt/resize = commit:dc93e1b98c
github.com/russross/blackfriday = commit:8cec3a854e
github.com/shurcooL/sanitized_anchor_name = commit:244f5ac324
github.com/russross/blackfriday = commit:300106c
github.com/shurcooL/sanitized_anchor_name = commit:10ef21a
github.com/Unknwon/cae = commit:7f5e046
github.com/Unknwon/com = commit:28b053d
github.com/Unknwon/i18n = commit:3b48b66
github.com/Unknwon/paginater = commit:7748a72
golang.org/x/net =
golang.org/x/text =
gopkg.in/ini.v1 = commit:463307112d
golang.org/x/text =
golang.org/x/crypto =
gopkg.in/asn1-ber.v1 = commit:4e86f43
gopkg.in/gomail.v2 = commit:df6fc79
gopkg.in/ini.v1 = commit:65f8c74
gopkg.in/ldap.v2 = e9a325d
gopkg.in/macaron.v1 = commit:1c6dd87
gopkg.in/redis.v2 = commit:e617904962
[res]

View File

@@ -1,17 +1,18 @@
language: go
go:
- 1.2
- 1.3
- 1.4
- 1.5
- tip
before_install:
- sudo apt-get update -qq
- sudo apt-get install -y libpam-dev
- go get github.com/msteinert/pam
install:
- go get -t -v ./...
script: go build -v -tags "pam"
notifications:

View File

@@ -42,7 +42,7 @@ There is no standard form of making a feature request. Just try to describe the
### Pull Request
Pull requests are always welcome, but note that **ALL PULL REQUESTS MUST APPLY TO THE `DEV` BRANCH**.
Pull requests are always welcome, but note that **ALL PULL REQUESTS MUST APPLY TO THE `develop` BRANCH**.
We are always thrilled to receive pull requests, and do our best to process them as fast as possible. Not sure if that typo is worth a pull request? Do it! We will appreciate it.

View File

@@ -1,54 +1,22 @@
FROM google/debian:wheezy
MAINTAINER u@gogs.io
FROM alpine:3.2
MAINTAINER roemer.jp@gmail.com
RUN echo "deb http://ftp.debian.org/debian/ wheezy-backports main" >> /etc/apt/sources.list && \
apt-get update -qqy && \
apt-get install --no-install-recommends -qqy \
curl build-essential ca-certificates git \
openssh-server rsync libpam-dev && \
apt-get autoclean && \
apt-get autoremove && \
rm -rf /var/lib/apt/lists/*
# Install system utils & Gogs runtime dependencies
ADD https://github.com/tianon/gosu/releases/download/1.6/gosu-amd64 /usr/sbin/gosu
RUN echo "@edge http://dl-4.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories \
&& echo "@community http://dl-4.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories \
&& apk -U --no-progress upgrade \
&& apk -U --no-progress add ca-certificates bash git linux-pam s6@edge curl openssh socat \
&& chmod +x /usr/sbin/gosu
ENV GOROOT /goroot
ENV GOPATH /gopath
ENV PATH $PATH:$GOROOT/bin:$GOPATH/bin
COPY . /gopath/src/github.com/gogits/gogs/
WORKDIR /gopath/src/github.com/gogits/gogs/
# Build binary and clean up useless files
RUN mkdir /goroot && \
curl https://storage.googleapis.com/golang/go1.5.linux-amd64.tar.gz | tar xzf - -C /goroot --strip-components=1 && \
go get -v -tags "sqlite redis memcache cert pam" && \
go build -tags "sqlite redis memcache cert pam" && \
mkdir /app/ && \
mv /gopath/src/github.com/gogits/gogs/ /app/gogs/ && \
rm -r $GOROOT $GOPATH
WORKDIR /app/gogs/
RUN useradd --shell /bin/bash --system --comment gogits git
# SSH login fix, otherwise user is kicked off after login
RUN mkdir /var/run/sshd && \
sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd && \
sed 's@UsePrivilegeSeparation yes@UsePrivilegeSeparation no@' -i /etc/ssh/sshd_config && \
echo "export VISIBLE=now" >> /etc/profile && \
echo "PermitUserEnvironment yes" >> /etc/ssh/sshd_config
# Setup server keys on startup
RUN sed 's@^HostKey@\#HostKey@' -i /etc/ssh/sshd_config && \
echo "HostKey /data/ssh/ssh_host_key" >> /etc/ssh/sshd_config && \
echo "HostKey /data/ssh/ssh_host_rsa_key" >> /etc/ssh/sshd_config && \
echo "HostKey /data/ssh/ssh_host_dsa_key" >> /etc/ssh/sshd_config && \
echo "HostKey /data/ssh/ssh_host_ecdsa_key" >> /etc/ssh/sshd_config && \
echo "HostKey /data/ssh/ssh_host_ed25519_key" >> /etc/ssh/sshd_config
# Prepare data
ENV GOGS_CUSTOM /data/gogs
RUN echo "export GOGS_CUSTOM=/data/gogs" >> /etc/profile
COPY . /app/gogs/
WORKDIR /app/gogs/
RUN ./docker/build.sh
# Configure Docker Container
VOLUME ["/data"]
EXPOSE 22 3000
ENTRYPOINT []
CMD ["./docker/start.sh"]
ENTRYPOINT ["docker/start.sh"]
CMD ["/bin/s6-svscan", "/app/gogs/docker/s6/"]

22
Dockerfile.rpi Normal file
View File

@@ -0,0 +1,22 @@
FROM sander85/rpi-alpine:latest
MAINTAINER roemer.jp@gmail.com, raxetul@gmail.com
# Install system utils & Gogs runtime dependencies
ADD https://github.com/tianon/gosu/releases/download/1.6/gosu-armhf /usr/sbin/gosu
RUN echo "@edge http://dl-4.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories \
&& echo "@community http://dl-4.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories \
&& apk -U --no-progress upgrade \
&& apk -U --no-progress add ca-certificates bash git linux-pam s6@edge curl openssh socat \
&& chmod +x /usr/sbin/gosu
ENV GOGS_CUSTOM /data/gogs
COPY . /app/gogs/
WORKDIR /app/gogs/
RUN ./docker/build.sh
# Configure Docker Container
VOLUME ["/data"]
EXPOSE 22 3000
ENTRYPOINT ["docker/start.sh"]
CMD ["/usr/bin/s6-svscan", "/app/gogs/docker/s6/"]

46
Makefile Normal file
View File

@@ -0,0 +1,46 @@
LDFLAGS += -X "github.com/gogits/gogs/modules/setting.BuildTime=$(shell date -u '+%Y-%m-%d %I:%M:%S %Z')"
LDFLAGS += -X "github.com/gogits/gogs/modules/setting.BuildGitHash=$(shell git rev-parse HEAD)"
DATA_FILES := $(shell find conf | sed 's/ /\\ /g')
LESS_FILES := $(wildcard public/less/gogs.less public/less/_*.less)
GENERATED := modules/bindata/bindata.go public/css/gogs.css
TAGS = ""
RELEASE_ROOT = "release"
RELEASE_GOGS = "release/gogs"
NOW = $(shell date -u '+%Y%m%d%I%M%S')
.PHONY: build pack release bindata clean
build: $(GENERATED)
go install -ldflags '$(LDFLAGS)' -tags '$(TAGS)'
cp '$(GOPATH)/bin/gogs' .
govet:
go tool vet -composites=false -methods=false -structtags=false .
pack:
rm -rf $(RELEASE_GOGS)
mkdir -p $(RELEASE_GOGS)
cp -r gogs LICENSE README.md README_ZH.md templates public scripts $(RELEASE_GOGS)
rm -rf $(RELEASE_GOGS)/public/config.codekit $(RELEASE_GOGS)/public/less
cd $(RELEASE_ROOT) && zip -r gogs.$(NOW).zip "gogs"
release: build pack
bindata: modules/bindata/bindata.go
modules/bindata/bindata.go: $(DATA_FILES)
go-bindata -o=$@ -ignore="\\.DS_Store|README.md" -pkg=bindata conf/...
less: public/css/gogs.css
public/css/gogs.css: $(LESS_FILES)
lessc $< $@
clean:
go clean -i ./...
clean-mac: clean
find . -name ".DS_Store" -print0 | xargs -0 rm

View File

@@ -3,72 +3,61 @@ Gogs - Go Git Service [![Build Status](https://travis-ci.org/gogits/gogs.svg?bra
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gogits/gogs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Gogs (Go Git Service) is a painless self-hosted Git service.
![](public/img/gogs-large-resize.png)
##### Current version: 0.6.9 Beta
##### Current version: 0.7.33 Beta
<table>
<tr>
<td width="33%"><img src="http://gogs.io/imgs/screenshoots/1.png"></td>
<td width="33%"><img src="http://gogs.io/imgs/screenshoots/2.png"></td>
<td width="33%"><img src="http://gogs.io/imgs/screenshoots/3.png"></td>
</tr>
<tr>
<td><img src="http://gogs.io/imgs/screenshoots/4.png"></td>
<td><img src="http://gogs.io/imgs/screenshoots/5.png"></td>
<td><img src="http://gogs.io/imgs/screenshoots/6.png"></td>
</tr>
<tr>
<td><img src="http://gogs.io/imgs/screenshoots/7.png"></td>
<td><img src="http://gogs.io/imgs/screenshoots/8.png"></td>
<td><img src="http://gogs.io/imgs/screenshoots/9.png"></td>
</tr>
</table>
| Web | UI | Preview |
|:-------------:|:-------:|:-------:|
|![Dashboard](https://gogs.io/img/screenshots/1.png)|![Repository](https://gogs.io/img/screenshots/2.png)|![Commits History](https://gogs.io/img/screenshots/3.png)|
|![Profile](https://gogs.io/img/screenshots/4.png)|![Admin Dashboard](https://gogs.io/img/screenshots/5.png)|![Diff](https://gogs.io/img/screenshots/6.png)|
|![Issues](https://gogs.io/img/screenshots/7.png)|![Releases](https://gogs.io/img/screenshots/8.png)|![Organization](https://gogs.io/img/screenshots/9.png)|
### NOTICES
- Due to testing purpose, data of [try.gogs.io](https://try.gogs.io) has been reset in **Jan 28, 2015** and will reset multiple times after. Please do **NOT** put your important data on the site.
- The demo site [try.gogs.io](https://try.gogs.io) is running under `develop` branch.
- :exclamation::exclamation::exclamation:<span style="color: red">You **MUST** read [CONTRIBUTING.md](CONTRIBUTING.md) before you start filing an issue or making a Pull Request, and **MUST** discuss with us on [Gitter](https://gitter.im/gogits/gogs) for UI changes and feature Pull Requests, otherwise it's high possibilities that we are not going to merge it.</span>:exclamation::exclamation::exclamation:
- :bangbang:<span style="color: red">You **MUST** read [CONTRIBUTING.md](CONTRIBUTING.md) before you start filing an issue or making a Pull Request, and **MUST** discuss with us on [Gitter](https://gitter.im/gogits/gogs) for UI changes, otherwise it's high possibilities that we are not going to merge it.</span>:bangbang:
- Please [start discussion](http://forum.gogs.io/category/2/general-discussion) or [ask a question](http://forum.gogs.io/category/4/getting-help) on [the forum](http://forum.gogs.io/). GitHub issue tracker only keeps **bugs** and **feature requests**, all other topics will be closed without reason.
- If you think there are vulnerabilities in the project, please talk privately to **u@gogs.io**. Thanks!
- If you're interested in using APIs, we have experimental support with [documentation](https://github.com/gogits/go-gogs-client/wiki).
- If your team/company is using Gogs and would like to put your logo on [our website](http://gogs.io), contact us by any means.
#### Other language version
- [简体中文](README_ZH.md)
[简体中文](README_ZH.md)
## Purpose
The goal of this project is to make the easiest, fastest, and most painless way to set up a self-hosted Git service. With Go, this can be done via an independent binary distribution across **ALL platforms** that Go supports, including Linux, Mac OS X, and Windows.
The goal of this project is to make the easiest, fastest, and most painless way of setting up a self-hosted Git service. With Go, this can be done with an independent binary distribution across **ALL platforms** that Go supports, including Linux, Mac OS X, Windows and ARM.
## Overview
- Please see the [Documentation](http://gogs.io/docs/intro/) for project design, known issues, and change log.
- Please see the [Documentation](http://gogs.io/docs/intro) for common usages and change log.
- See the [Trello Board](https://trello.com/b/uxAoeLUl/gogs-go-git-service) to follow the develop team.
- Want to try it before doing anything else? Do it [online](https://try.gogs.io/gogs/gogs) or go down to the **Installation -> Install from binary** section!
- Having trouble? Get help with [Troubleshooting](http://gogs.io/docs/intro/troubleshooting.md).
- Want to try it before doing anything else? Do it [online](https://try.gogs.io/gogs/gogs)!
- Having trouble? Get help with [Troubleshooting](http://gogs.io/docs/intro/troubleshooting.html).
- Want to help with localization? Check out the [guide](http://gogs.io/docs/features/i18n.html)!
## Features
- Activity timeline
- SSH/HTTP(S) protocol support
- SMTP/LDAP/reverse proxy authentication support
- Reverse proxy suburl support
- Account/Organization(with team)/Repository management
- Repository/Organization webhooks(including Slack)
- SSH and HTTP/HTTPS protocols
- SMTP/LDAP/Reverse proxy authentication
- Reverse proxy with sub-path
- Account/Organization/Repository management
- Repository/Organization webhooks (including Slack)
- Repository Git hooks/deploy keys
- Add/remove repository collaborators
- Gravatar and custom source support
- Repository issues and pull requests
- Add/Remove repository collaborators
- Gravatar and custom source
- Mail service
- Administration panel
- Supports MySQL, PostgreSQL and SQLite3
- Social account login (GitHub, Google, QQ, Weibo)
- Supports MySQL, PostgreSQL, SQLite3 and [TiDB](https://github.com/pingcap/tidb) (experimental)
- Multi-language support ([14 languages](https://crowdin.com/project/gogs))
## System Requirements
- A cheap Raspberry Pi is powerful enough for basic functionality.
- At least 2 CPU cores and 1GB RAM would be the baseline for teamwork.
- 2 CPU cores and 1GB RAM would be the baseline for teamwork.
## Browser Support
@@ -77,13 +66,13 @@ The goal of this project is to make the easiest, fastest, and most painless way
## Installation
Make sure you install the [prerequisites](http://gogs.io/docs/installation/) first.
Make sure you install the [prerequisites](http://gogs.io/docs/installation) first.
There are 5 ways to install Gogs:
- [Install from binary](http://gogs.io/docs/installation/install_from_binary.md)
- [Install from source](http://gogs.io/docs/installation/install_from_source.md)
- [Install from packages](http://gogs.io/docs/installation/install_from_packages.md)
- [Install from binary](http://gogs.io/docs/installation/install_from_binary.html)
- [Install from source](http://gogs.io/docs/installation/install_from_source.html)
- [Install from packages](http://gogs.io/docs/installation/install_from_packages.html)
- [Ship with Docker](https://github.com/gogits/gogs/tree/master/docker)
- [Install with Vagrant](https://github.com/geerlingguy/ansible-vagrant-examples/tree/master/gogs)
@@ -91,6 +80,7 @@ There are 5 ways to install Gogs:
- [How To Set Up Gogs on Ubuntu 14.04](https://www.digitalocean.com/community/tutorials/how-to-set-up-gogs-on-ubuntu-14-04)
- [Run your own GitHub-like service with the help of Docker](http://blog.hypriot.com/post/run-your-own-github-like-service-with-docker/)
- [使用 Gogs 搭建自己的 Git 服务器](https://mynook.info/blog/post/host-your-own-git-server-using-gogs) (Chinese)
- [阿里云上 Ubuntu 14.04 64 位安装 Gogs](http://my.oschina.net/luyao/blog/375654) (Chinese)
- [Installing Gogs on FreeBSD](https://www.codejam.info/2015/03/installing-gogs-on-freebsd.html)
- [Gogs on Raspberry Pi](http://blog.meinside.pe.kr/Gogs-on-Raspberry-Pi/)
@@ -99,17 +89,38 @@ There are 5 ways to install Gogs:
- [Instalando Gogs no Ubuntu](http://blog.linuxpro.com.br/2015/08/14/instalando-gogs-no-ubuntu/) (Português)
### Deploy to Cloud
- [OpenShift](https://github.com/tkisme/gogs-openshift)
- [Cloudron](https://cloudron.io/appstore.html#io.gogs.cloudronapp)
- [Scaleway](https://www.scaleway.com/imagehub/gogs/)
- [Portal](https://portaldemo.xyz/cloud/)
- [Sandstorm](https://github.com/cem/gogs-sandstorm)
- [sloppy.io](https://github.com/sloppyio/quickstarters/tree/master/gogs)
## Software and Service Support
- [Drone](https://github.com/drone/drone) (CI)
- [Fabric8](http://fabric8.io/) (DevOps)
- [Taiga](https://taiga.io/) (Project Management)
### Product Support
- [Synology](https://www.synology.com) (Docker)
- [One Space](http://www.onespace.cc) (App Store)
## Acknowledgments
- Router and middleware mechanism of [Macaron](https://github.com/Unknwon/macaron).
- Mail Service, modules design is inspired by [WeTalk](https://github.com/beego/wetalk).
- Router and middleware mechanism of [Macaron](https://github.com/go-macaron/macaron).
- Modules design is inspired by [WeTalk](https://github.com/beego/wetalk).
- System Monitor Status is inspired by [GoBlog](https://github.com/fuxiaohei/goblog).
- Thanks [lavachen](http://www.lavachen.cn/) and [Rocker](http://weibo.com/rocker1989) for designing Logo.
- Thanks [Crowdin](https://crowdin.com/project/gogs) for providing open source translation plan.
- Thanks [DigitalOcean](https://www.digitalocean.com) for hosting home and demo sites.
## Contributors
- Ex-team members [@lunny](https://github.com/lunny) and [@fuxiaohei](https://github.com/fuxiaohei).
- Ex-team members [@lunny](https://github.com/lunny), [@fuxiaohei](https://github.com/fuxiaohei) and [@slene](https://github.com/slene).
- See [contributors page](https://github.com/gogits/gogs/graphs/contributors) for full list of contributors.
- See [TRANSLATORS](conf/locale/TRANSLATORS) for public list of translators.

View File

@@ -1,35 +1,35 @@
Gogs - Go Git Service [![Build Status](https://travis-ci.org/gogits/gogs.svg?branch=master)](https://travis-ci.org/gogits/gogs)
=====================
Gogs (Go Git Service) 是一款可轻易搭建的自助 Git 服务。
Gogs (Go Git Service) 是一款易搭建的自助 Git 服务。
## 开发目的
Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自助 Git 服务。使用 Go 语言开发使得 Gogs 能够通过独立的二进制分发,并且支持 Go 语言支持的 **所有平台**,包括 Linux、Mac OS X 以及 Windows。
Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自助 Git 服务。使用 Go 语言开发使得 Gogs 能够通过独立的二进制分发,并且支持 Go 语言支持的 **所有平台**,包括 Linux、Mac OS XWindows 以及 ARM 平台
## 项目概览
- 有关项目设计、已知问题和变更日志,请通过 [使用手册](http://gogs.io/docs/intro/) 查看。
- 有关基本用法和变更日志,请通过 [使用手册](http://gogs.io/docs/intro/) 查看。
- 您可以到 [Trello Board](https://trello.com/b/uxAoeLUl/gogs-go-git-service) 跟随开发团队的脚步。
- 想要先睹为快?通过 [在线体验](https://try.gogs.io/gogs/gogs) 或查看 **安装部署 -> 二进制安装** 小节。
- 使用过程中遇到问题?尝试从 [故障排查](http://gogs.io/docs/intro/troubleshooting.md) 页面获取帮助。
- 使用过程中遇到问题?尝试从 [故障排查](http://gogs.io/docs/intro/troubleshooting.html) 页面获取帮助。
- 希望帮助多国语言界面的翻译吗?请立即访问 [详情页面](http://gogs.io/docs/features/i18n.html)
## 功能特性
- 支持活动时间线
- 支持 SSH/HTTP(S) 协议
- 支持 SMTP/LDAP/反向代理的用户认证
- 支持 SSH 以及 HTTP/HTTPS 协议
- 支持 SMTPLDAP反向代理的用户认证
- 支持反向代理子路径
- 支持用户、组织和仓库管理系统
- 支持仓库和组织级别 Web 钩子(包括 Slack 集成)
- 支持仓库 Git 钩子和部署密钥
- 支持 添加/删除 仓库协作者
- 支持仓库工单Issue和合并请求Pull Request
- 支持添加和删除仓库协作者
- 支持 Gravatar 以及自定义源
- 支持邮件服务
- 支持后台管理面板
- 支持 MySQL、PostgreSQL 以及 SQLite3 数据库
- 支持社交帐号登录GitHub、Google、QQ、微博
- 支持 MySQL、PostgreSQLSQLite3 和 [TiDB](https://github.com/pingcap/tidb)(实验性支持) 数据库
- 支持多语言本地化([14 种语言]([more](https://crowdin.com/project/gogs))
## 系统要求
@@ -44,27 +44,53 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
## 安装部署
在安装 Gogs 之前,您需要先安装 [基本环境](http://gogs.io/docs/installation/)。
在安装 Gogs 之前,您需要先安装 [基本环境](http://gogs.io/docs/installation)。
然后,您可以通过以下 5 种方式来安装 Gogs
- [二进制安装](http://gogs.io/docs/installation/install_from_binary.md)
- [源码安装](http://gogs.io/docs/installation/install_from_source.md)
- [包管理安装](http://gogs.io/docs/installation/install_from_packages.md)
- [二进制安装](http://gogs.io/docs/installation/install_from_binary.html)
- [源码安装](http://gogs.io/docs/installation/install_from_source.html)
- [包管理安装](http://gogs.io/docs/installation/install_from_packages.html)
- [采用 Docker 部署](https://github.com/gogits/gogs/tree/master/docker)
- [通过 Vagrant 安装](https://github.com/geerlingguy/ansible-vagrant-examples/tree/master/gogs)
### 使用教程
- [使用 Gogs 搭建自己的 Git 服务器](https://mynook.info/blog/post/host-your-own-git-server-using-gogs)
- [阿里云上 Ubuntu 14.04 64 位安装 Gogs](http://my.oschina.net/luyao/blog/375654)
### 云端部署
- [OpenShift](https://github.com/tkisme/gogs-openshift)
- [Cloudron](https://cloudron.io/appstore.html#io.gogs.cloudronapp)
- [Scaleway](https://www.scaleway.com/imagehub/gogs/)
- [Portal](https://portaldemo.xyz/cloud/)
- [Sandstorm](https://github.com/cem/gogs-sandstorm)
- [sloppy.io](https://github.com/sloppyio/quickstarters/tree/master/gogs)
## 软件及服务支持
- [Drone](https://github.com/drone/drone)CI
- [Fabric8](http://fabric8.io/)DevOps
- [Taiga](https://taiga.io/)(项目管理)
### 产品支持
- [Synology](https://www.synology.com)Docker
- [One Space](http://www.onespace.cc)(应用商店)
## 特别鸣谢
- 基于 [Macaron](https://github.com/Unknwon/macaron) 的路由与中间件机制。
- 基于 [WeTalk](https://github.com/beego/wetalk) 修改的邮件服务和模块设计。
- 基于 [Macaron](https://github.com/go-macaron/macaron) 的路由与中间件机制。
- 基于 [WeTalk](https://github.com/beego/wetalk) 修改的模块设计。
- 基于 [GoBlog](https://github.com/fuxiaohei/goblog) 修改的系统监视状态。
- 感谢 [lavachen](http://www.lavachen.cn/) 和 [Rocker](http://weibo.com/rocker1989) 设计的 Logo。
- 感谢 [Crowdin](https://crowdin.com/project/gogs) 提供免费的开源项目本地化支持。
- 感谢 [DigitalOcean](https://www.digitalocean.com) 提供主站和体验站点的服务器赞助。
## 贡献成员
- 前团队成员 [@lunny](https://github.com/lunny)[@fuxiaohei](https://github.com/fuxiaohei)。
- 前团队成员 [@lunny](https://github.com/lunny)[@fuxiaohei](https://github.com/fuxiaohei) 和 [@slene](https://github.com/slene)
- 您可以通过查看 [贡献者页面](https://github.com/gogits/gogs/graphs/contributors) 获取完整的贡献者列表。
- 您可以通过查看 [TRANSLATORS](conf/locale/TRANSLATORS) 文件获取公开的翻译人员列表。

View File

@@ -32,12 +32,12 @@ var CmdCert = cli.Command{
Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`,
Action: runCert,
Flags: []cli.Flag{
cli.StringFlag{"host", "", "Comma-separated hostnames and IPs to generate a certificate for", ""},
cli.StringFlag{"ecdsa-curve", "", "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521", ""},
cli.IntFlag{"rsa-bits", 2048, "Size of RSA key to generate. Ignored if --ecdsa-curve is set", ""},
cli.StringFlag{"start-date", "", "Creation date formatted as Jan 1 15:04:05 2011", ""},
cli.DurationFlag{"duration", 365 * 24 * time.Hour, "Duration that certificate is valid for", ""},
cli.BoolFlag{"ca", "whether this cert should be its own Certificate Authority", ""},
stringFlag("host", "", "Comma-separated hostnames and IPs to generate a certificate for"),
stringFlag("ecdsa-curve", "", "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521"),
intFlag("rsa-bits", 2048, "Size of RSA key to generate. Ignored if --ecdsa-curve is set"),
stringFlag("start-date", "", "Creation date formatted as Jan 1 15:04:05 2011"),
durationFlag("duration", 365*24*time.Hour, "Duration that certificate is valid for"),
boolFlag("ca", "whether this cert should be its own Certificate Authority"),
},
}
@@ -114,7 +114,7 @@ func runCert(ctx *cli.Context) {
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Acme Co"},
CommonName: "Gogs",
CommonName: "Gogs",
},
NotBefore: notBefore,
NotAfter: notAfter,

42
cmd/cmd.go Normal file
View File

@@ -0,0 +1,42 @@
// Copyright 2015 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cmd
import (
"time"
"github.com/codegangsta/cli"
)
func stringFlag(name, value, usage string) cli.StringFlag {
return cli.StringFlag{
Name: name,
Value: value,
Usage: usage,
}
}
func boolFlag(name, usage string) cli.BoolFlag {
return cli.BoolFlag{
Name: name,
Usage: usage,
}
}
func intFlag(name string, value int, usage string) cli.IntFlag {
return cli.IntFlag{
Name: name,
Value: value,
Usage: usage,
}
}
func durationFlag(name string, value time.Duration, usage string) cli.DurationFlag {
return cli.DurationFlag{
Name: name,
Value: value,
Usage: usage,
}
}

View File

@@ -11,6 +11,8 @@ import (
"path"
"time"
"io/ioutil"
"github.com/Unknwon/cae/zip"
"github.com/codegangsta/cli"
@@ -25,8 +27,8 @@ var CmdDump = cli.Command{
It can be used for backup and capture Gogs server image to send to maintainer`,
Action: runDump,
Flags: []cli.Flag{
cli.StringFlag{"config, c", "custom/conf/app.ini", "Custom configuration file path", ""},
cli.BoolFlag{"verbose, v", "show process details", ""},
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"),
boolFlag("verbose, v", "show process details"),
},
}
@@ -34,20 +36,27 @@ func runDump(ctx *cli.Context) {
if ctx.IsSet("config") {
setting.CustomConf = ctx.String("config")
}
setting.NewConfigContext()
models.LoadModelsConfig()
setting.NewContext()
models.LoadConfigs()
models.SetEngine()
TmpWorkDir, err := ioutil.TempDir(os.TempDir(), "gogs-dump-")
if err != nil {
log.Fatalf("Fail to create tmp work directory: %v", err)
}
log.Printf("Creating tmp work dir: %s", TmpWorkDir)
reposDump := path.Join(TmpWorkDir, "gogs-repo.zip")
dbDump := path.Join(TmpWorkDir, "gogs-db.sql")
log.Printf("Dumping local repositories...%s", setting.RepoRootPath)
zip.Verbose = ctx.Bool("verbose")
defer os.Remove("gogs-repo.zip")
if err := zip.PackTo(setting.RepoRootPath, "gogs-repo.zip", true); err != nil {
if err := zip.PackTo(setting.RepoRootPath, reposDump, true); err != nil {
log.Fatalf("Fail to dump local repositories: %v", err)
}
log.Printf("Dumping database...")
defer os.Remove("gogs-db.sql")
if err := models.DumpDatabase("gogs-db.sql"); err != nil {
if err := models.DumpDatabase(dbDump); err != nil {
log.Fatalf("Fail to dump database: %v", err)
}
@@ -59,16 +68,30 @@ func runDump(ctx *cli.Context) {
log.Fatalf("Fail to create %s: %v", fileName, err)
}
workDir, _ := setting.WorkDir()
z.AddFile("gogs-repo.zip", path.Join(workDir, "gogs-repo.zip"))
z.AddFile("gogs-db.sql", path.Join(workDir, "gogs-db.sql"))
z.AddDir("custom", path.Join(workDir, "custom"))
z.AddDir("log", path.Join(workDir, "log"))
if err := z.AddFile("gogs-repo.zip", reposDump); err !=nil {
log.Fatalf("Fail to include gogs-repo.zip: %v", err)
}
if err := z.AddFile("gogs-db.sql", dbDump); err !=nil {
log.Fatalf("Fail to include gogs-db.sql: %v", err)
}
customDir, err := os.Stat(setting.CustomPath)
if err == nil && customDir.IsDir() {
if err := z.AddDir("custom", setting.CustomPath); err !=nil {
log.Fatalf("Fail to include custom: %v", err)
}
} else {
log.Printf("Custom dir %s doesn't exist, skipped", setting.CustomPath)
}
if err := z.AddDir("log", setting.LogRootPath); err !=nil {
log.Fatalf("Fail to include log: %v", err)
}
// FIXME: SSH key file.
if err = z.Close(); err != nil {
os.Remove(fileName)
log.Fatalf("Fail to save %s: %v", fileName, err)
}
log.Println("Finish dumping!")
log.Printf("Removing tmp work dir: %s", TmpWorkDir)
os.RemoveAll(TmpWorkDir)
log.Printf("Finish dumping in file %s", fileName)
}

View File

@@ -5,6 +5,7 @@
package cmd
import (
"crypto/tls"
"fmt"
"os"
"os/exec"
@@ -32,12 +33,12 @@ var CmdServ = cli.Command{
Description: `Serv provide access auth for repositories`,
Action: runServ,
Flags: []cli.Flag{
cli.StringFlag{"config, c", "custom/conf/app.ini", "Custom configuration file path", ""},
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"),
},
}
func setup(logPath string) {
setting.NewConfigContext()
setting.NewContext()
log.NewGitLogger(filepath.Join(setting.LogRootPath, logPath))
if setting.DisableSSH {
@@ -45,9 +46,9 @@ func setup(logPath string) {
os.Exit(1)
}
models.LoadModelsConfig()
models.LoadConfigs()
if setting.UseSQLite3 {
if setting.UseSQLite3 || setting.UseTiDB {
workDir, _ := setting.WorkDir()
os.Chdir(workDir)
}
@@ -64,7 +65,7 @@ func parseCmd(cmd string) (string, string) {
}
var (
COMMANDS = map[string]models.AccessMode{
allowedCommands = map[string]models.AccessMode{
"git-upload-pack": models.ACCESS_MODE_READ,
"git-upload-archive": models.ACCESS_MODE_READ,
"git-receive-pack": models.ACCESS_MODE_WRITE,
@@ -73,7 +74,56 @@ var (
func fail(userMessage, logMessage string, args ...interface{}) {
fmt.Fprintln(os.Stderr, "Gogs:", userMessage)
log.GitLogger.Fatal(3, logMessage, args...)
if len(logMessage) > 0 {
if !setting.ProdMode {
fmt.Fprintf(os.Stderr, logMessage+"\n", args...)
}
log.GitLogger.Fatal(3, logMessage, args...)
return
}
log.GitLogger.Close()
os.Exit(1)
}
func handleUpdateTask(uuid string, user *models.User, username, reponame string, isWiki bool) {
task, err := models.GetUpdateTaskByUUID(uuid)
if err != nil {
if models.IsErrUpdateTaskNotExist(err) {
log.GitLogger.Trace("No update task is presented: %s", uuid)
return
}
log.GitLogger.Fatal(2, "GetUpdateTaskByUUID: %v", err)
} else if err = models.DeleteUpdateTaskByUUID(uuid); err != nil {
log.GitLogger.Fatal(2, "DeleteUpdateTaskByUUID: %v", err)
}
if isWiki {
return
}
if err = models.Update(task.RefName, task.OldCommitID, task.NewCommitID,
user.Name, username, reponame, user.Id); err != nil {
log.GitLogger.Error(2, "Update: %v", err)
}
// Ask for running deliver hook and test pull request tasks.
reqURL := setting.AppUrl + username + "/" + reponame + "/tasks/trigger?branch=" +
strings.TrimPrefix(task.RefName, "refs/heads/")
log.GitLogger.Trace("Trigger task: %s", reqURL)
resp, err := httplib.Head(reqURL).SetTLSClientConfig(&tls.Config{
InsecureSkipVerify: true,
}).Response()
if err == nil {
resp.Body.Close()
if resp.StatusCode/100 != 2 {
log.GitLogger.Error(2, "Fail to trigger task: not 2xx response code")
}
} else {
log.GitLogger.Error(2, "Fail to trigger task: %v", err)
}
}
func runServ(c *cli.Context) {
@@ -94,35 +144,46 @@ func runServ(c *cli.Context) {
}
verb, args := parseCmd(cmd)
repoPath := strings.Trim(args, "'")
repoPath := strings.ToLower(strings.Trim(args, "'"))
rr := strings.SplitN(repoPath, "/", 2)
if len(rr) != 2 {
fail("Invalid repository path", "Invalid repository path: %v", args)
}
repoUserName := rr[0]
repoName := strings.TrimSuffix(rr[1], ".git")
username := strings.ToLower(rr[0])
reponame := strings.ToLower(strings.TrimSuffix(rr[1], ".git"))
repoUser, err := models.GetUserByName(repoUserName)
if err != nil {
if models.IsErrUserNotExist(err) {
fail("Repository owner does not exist", "Unregistered owner: %s", repoUserName)
}
fail("Internal error", "Failed to get repository owner(%s): %v", repoUserName, err)
isWiki := false
if strings.HasSuffix(reponame, ".wiki") {
isWiki = true
reponame = reponame[:len(reponame)-5]
}
repo, err := models.GetRepositoryByName(repoUser.Id, repoName)
repoUser, err := models.GetUserByName(username)
if err != nil {
if models.IsErrUserNotExist(err) {
fail("Repository owner does not exist", "Unregistered owner: %s", username)
}
fail("Internal error", "Failed to get repository owner(%s): %v", username, err)
}
repo, err := models.GetRepositoryByName(repoUser.Id, reponame)
if err != nil {
if models.IsErrRepoNotExist(err) {
fail(_ACCESS_DENIED_MESSAGE, "Repository does not exist: %s/%s", repoUser.Name, repoName)
fail(_ACCESS_DENIED_MESSAGE, "Repository does not exist: %s/%s", repoUser.Name, reponame)
}
fail("Internal error", "Failed to get repository: %v", err)
}
requestedMode, has := COMMANDS[verb]
requestedMode, has := allowedCommands[verb]
if !has {
fail("Unknown git command", "Unknown git command %s", verb)
}
// Prohibit push to mirror repositories.
if requestedMode > models.ACCESS_MODE_READ && repo.IsMirror {
fail("mirror repository is read-only", "")
}
// Allow anonymous clone for public repositories.
var (
keyID int64
@@ -131,12 +192,12 @@ func runServ(c *cli.Context) {
if requestedMode == models.ACCESS_MODE_WRITE || repo.IsPrivate {
keys := strings.Split(c.Args()[0], "-")
if len(keys) != 2 {
fail("Key ID format error", "Invalid key ID: %s", c.Args()[0])
fail("Key ID format error", "Invalid key argument: %s", c.Args()[0])
}
key, err := models.GetPublicKeyByID(com.StrTo(keys[1]).MustInt64())
if err != nil {
fail("Key ID format error", "Invalid key ID[%s]: %v", c.Args()[0], err)
fail("Invalid key ID", "Invalid key ID[%s]: %v", c.Args()[0], err)
}
keyID = key.ID
@@ -161,7 +222,7 @@ func runServ(c *cli.Context) {
fail("Internal error", "UpdateDeployKey: %v", err)
}
} else {
user, err = models.GetUserByKeyId(key.ID)
user, err = models.GetUserByKeyID(key.ID)
if err != nil {
fail("internal error", "Failed to get user by key ID(%d): %v", keyID, err)
}
@@ -184,6 +245,11 @@ func runServ(c *cli.Context) {
uuid := uuid.NewV4().String()
os.Setenv("uuid", uuid)
// Special handle for Windows.
if setting.IsWindows {
verb = strings.Replace(verb, "-", " ", 1)
}
var gitcmd *exec.Cmd
verbs := strings.Split(verb, " ")
if len(verbs) == 2 {
@@ -200,28 +266,7 @@ func runServ(c *cli.Context) {
}
if requestedMode == models.ACCESS_MODE_WRITE {
tasks, err := models.GetUpdateTasksByUuid(uuid)
if err != nil {
log.GitLogger.Fatal(2, "GetUpdateTasksByUuid: %v", err)
}
for _, task := range tasks {
err = models.Update(task.RefName, task.OldCommitId, task.NewCommitId,
user.Name, repoUserName, repoName, user.Id)
if err != nil {
log.GitLogger.Error(2, "Failed to update: %v", err)
}
}
if err = models.DelUpdateTasksByUuid(uuid); err != nil {
log.GitLogger.Fatal(2, "DelUpdateTasksByUuid: %v", err)
}
}
// Send deliver hook request.
resp, err := httplib.Head(setting.AppUrl + setting.AppSubUrl + repoUserName + "/" + repoName + "/hooks/trigger").Response()
if err == nil {
resp.Body.Close()
handleUpdateTask(uuid, user, username, reponame, isWiki)
}
// Update user key activity.

View File

@@ -20,7 +20,7 @@ var CmdUpdate = cli.Command{
Description: `Update get pushed info and insert into database`,
Action: runUpdate,
Flags: []cli.Flag{
cli.StringFlag{"config, c", "custom/conf/app.ini", "Custom configuration file path", ""},
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"),
},
}
@@ -42,16 +42,14 @@ func runUpdate(c *cli.Context) {
log.GitLogger.Fatal(2, "refName is empty, shouldn't use")
}
uuid := os.Getenv("uuid")
task := models.UpdateTask{
Uuid: uuid,
UUID: os.Getenv("uuid"),
RefName: args[0],
OldCommitId: args[1],
NewCommitId: args[2],
OldCommitID: args[1],
NewCommitID: args[2],
}
if err := models.AddUpdateTask(&task); err != nil {
log.GitLogger.Fatal(2, err.Error())
log.GitLogger.Fatal(2, "AddUpdateTask: %v", err)
}
}

View File

@@ -7,7 +7,7 @@ package cmd
import (
"crypto/tls"
"fmt"
"html/template"
gotmpl "html/template"
"io/ioutil"
"net/http"
"net/http/fcgi"
@@ -15,34 +15,31 @@ import (
"path"
"strings"
"github.com/Unknwon/macaron"
"github.com/codegangsta/cli"
"github.com/go-macaron/binding"
"github.com/go-macaron/cache"
"github.com/go-macaron/captcha"
"github.com/go-macaron/csrf"
"github.com/go-macaron/gzip"
"github.com/go-macaron/i18n"
"github.com/go-macaron/session"
"github.com/go-macaron/toolbox"
"github.com/go-xorm/xorm"
"github.com/macaron-contrib/binding"
"github.com/macaron-contrib/cache"
"github.com/macaron-contrib/captcha"
"github.com/macaron-contrib/csrf"
"github.com/macaron-contrib/i18n"
"github.com/macaron-contrib/oauth2"
"github.com/macaron-contrib/session"
"github.com/macaron-contrib/toolbox"
"github.com/mcuadros/go-version"
"gopkg.in/ini.v1"
api "github.com/gogits/go-gogs-client"
"gopkg.in/macaron.v1"
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/auth"
"github.com/gogits/gogs/modules/auth/apiv1"
"github.com/gogits/gogs/modules/avatar"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/bindata"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/middleware"
"github.com/gogits/gogs/modules/setting"
"github.com/gogits/gogs/modules/template"
"github.com/gogits/gogs/routers"
"github.com/gogits/gogs/routers/admin"
"github.com/gogits/gogs/routers/api/v1"
apiv1 "github.com/gogits/gogs/routers/api/v1"
"github.com/gogits/gogs/routers/dev"
"github.com/gogits/gogs/routers/org"
"github.com/gogits/gogs/routers/repo"
@@ -56,8 +53,8 @@ var CmdWeb = cli.Command{
and it takes care of all the other things for you`,
Action: runWeb,
Flags: []cli.Flag{
cli.StringFlag{"port, p", "3000", "Temporary port number to prevent conflict", ""},
cli.StringFlag{"config, c", "custom/conf/app.ini", "Custom configuration file path", ""},
stringFlag("port, p", "3000", "Temporary port number to prevent conflict"),
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"),
},
}
@@ -80,13 +77,14 @@ func checkVersion() {
// Check dependency version.
checkers := []VerChecker{
{"github.com/go-xorm/xorm", func() string { return xorm.Version }, "0.4.3.0806"},
{"github.com/go-xorm/xorm", func() string { return xorm.Version }, "0.4.4.1029"},
{"github.com/Unknwon/macaron", macaron.Version, "0.5.4"},
{"github.com/macaron-contrib/binding", binding.Version, "0.1.0"},
{"github.com/macaron-contrib/cache", cache.Version, "0.1.2"},
{"github.com/macaron-contrib/csrf", csrf.Version, "0.0.3"},
{"github.com/macaron-contrib/i18n", i18n.Version, "0.0.7"},
{"github.com/macaron-contrib/session", session.Version, "0.1.6"},
{"github.com/go-macaron/binding", binding.Version, "0.1.0"},
{"github.com/go-macaron/cache", cache.Version, "0.1.2"},
{"github.com/go-macaron/csrf", csrf.Version, "0.0.3"},
{"github.com/go-macaron/i18n", i18n.Version, "0.2.0"},
{"github.com/go-macaron/session", session.Version, "0.1.6"},
{"github.com/go-macaron/toolbox", toolbox.Version, "0.1.0"},
{"gopkg.in/ini.v1", ini.Version, "1.3.4"},
}
for _, c := range checkers {
@@ -104,7 +102,7 @@ func newMacaron() *macaron.Macaron {
}
m.Use(macaron.Recovery())
if setting.EnableGzip {
m.Use(macaron.Gziper())
m.Use(gzip.Gziper())
}
if setting.Protocol == setting.FCGI {
m.SetURLPrefix(setting.AppSubUrl)
@@ -124,7 +122,7 @@ func newMacaron() *macaron.Macaron {
))
m.Use(macaron.Renderer(macaron.RenderOptions{
Directory: path.Join(setting.StaticRootPath, "templates"),
Funcs: []template.FuncMap{base.TemplateFuncs},
Funcs: []gotmpl.FuncMap{template.Funcs},
IndentJSON: macaron.Env != macaron.PROD,
}))
@@ -142,6 +140,7 @@ func newMacaron() *macaron.Macaron {
CustomDirectory: path.Join(setting.CustomPath, "conf/locale"),
Langs: setting.Langs,
Names: setting.Names,
DefaultLang: "en-US",
Redirect: true,
}))
m.Use(cache.Cacher(cache.Options{
@@ -167,13 +166,6 @@ func newMacaron() *macaron.Macaron {
},
},
}))
// OAuth 2.
if setting.OauthService != nil {
for _, info := range setting.OauthService.OauthInfos {
m.Use(oauth2.NewOAuth2Provider(info.Options, info.AuthUrl, info.TokenUrl))
}
}
m.Use(middleware.Contexter())
return m
}
@@ -192,7 +184,6 @@ func runWeb(ctx *cli.Context) {
ignSignInAndCsrf := middleware.Toggle(&middleware.ToggleOptions{DisableCsrf: true})
reqSignOut := middleware.Toggle(&middleware.ToggleOptions{SignOutRequire: true})
bind := binding.Bind
bindIgnErr := binding.BindIgnErr
// Routers.
@@ -200,55 +191,11 @@ func runWeb(ctx *cli.Context) {
m.Get("/explore", ignSignIn, routers.Explore)
m.Combo("/install", routers.InstallInit).Get(routers.Install).
Post(bindIgnErr(auth.InstallForm{}), routers.InstallPost)
m.Get("/:type(issues|pulls)", reqSignIn, user.Issues)
m.Get("/^:type(issues|pulls)$", reqSignIn, user.Issues)
// ***** START: API *****
// FIXME: custom form error response.
m.Group("/api", func() {
m.Group("/v1", func() {
// Miscellaneous.
m.Post("/markdown", bindIgnErr(apiv1.MarkdownForm{}), v1.Markdown)
m.Post("/markdown/raw", v1.MarkdownRaw)
// Users.
m.Group("/users", func() {
m.Get("/search", v1.SearchUsers)
m.Group("/:username", func() {
m.Get("", v1.GetUserInfo)
m.Group("/tokens", func() {
m.Combo("").Get(v1.ListAccessTokens).
Post(bind(v1.CreateAccessTokenForm{}), v1.CreateAccessToken)
}, middleware.ApiReqBasicAuth())
})
})
// Repositories.
m.Combo("/user/repos", middleware.ApiReqToken()).Get(v1.ListMyRepos).
Post(bind(api.CreateRepoOption{}), v1.CreateRepo)
m.Post("/org/:org/repos", middleware.ApiReqToken(), bind(api.CreateRepoOption{}), v1.CreateOrgRepo)
m.Group("/repos", func() {
m.Get("/search", v1.SearchRepos)
m.Group("", func() {
m.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), v1.MigrateRepo)
}, middleware.ApiReqToken())
m.Group("/:username/:reponame", func() {
m.Combo("/hooks").Get(v1.ListRepoHooks).
Post(bind(api.CreateHookOption{}), v1.CreateRepoHook)
m.Patch("/hooks/:id:int", bind(api.EditHookOption{}), v1.EditRepoHook)
m.Get("/raw/*", middleware.RepoRef(), v1.GetRepoRawFile)
m.Get("/archive/*", v1.GetRepoArchive)
}, middleware.ApiRepoAssignment(), middleware.ApiReqToken())
})
m.Any("/*", func(ctx *middleware.Context) {
ctx.HandleAPI(404, "Page not found")
})
})
apiv1.RegisterRoutes(m)
}, ignSignIn)
// ***** END: API *****
@@ -256,7 +203,6 @@ func runWeb(ctx *cli.Context) {
m.Group("/user", func() {
m.Get("/login", user.SignIn)
m.Post("/login", bindIgnErr(auth.SignInForm{}), user.SignInPost)
m.Get("/info/:name", user.SocialSignIn)
m.Get("/sign_up", user.SignUp)
m.Post("/sign_up", bindIgnErr(auth.RegisterForm{}), user.SignUpPost)
m.Get("/reset_password", user.ResetPasswd)
@@ -267,21 +213,20 @@ func runWeb(ctx *cli.Context) {
m.Get("", user.Settings)
m.Post("", bindIgnErr(auth.UpdateProfileForm{}), user.SettingsPost)
m.Post("/avatar", binding.MultipartForm(auth.UploadAvatarForm{}), user.SettingsAvatar)
m.Get("/email", user.SettingsEmails)
m.Post("/email", bindIgnErr(auth.AddEmailForm{}), user.SettingsEmailPost)
m.Combo("/email").Get(user.SettingsEmails).
Post(bindIgnErr(auth.AddEmailForm{}), user.SettingsEmailPost)
m.Post("/email/delete", user.DeleteEmail)
m.Get("/password", user.SettingsPassword)
m.Post("/password", bindIgnErr(auth.ChangePasswordForm{}), user.SettingsPasswordPost)
m.Combo("/ssh").Get(user.SettingsSSHKeys).
Post(bindIgnErr(auth.AddSSHKeyForm{}), user.SettingsSSHKeysPost)
m.Post("/ssh/delete", user.DeleteSSHKey)
m.Get("/social", user.SettingsSocial)
m.Combo("/applications").Get(user.SettingsApplications).
Post(bindIgnErr(auth.NewAccessTokenForm{}), user.SettingsApplicationsPost)
m.Post("/applications/delete", user.SettingsDeleteApplication)
m.Route("/delete", "GET,POST", user.SettingsDelete)
}, reqSignIn, func(ctx *middleware.Context) {
ctx.Data["PageIsUserSettings"] = true
ctx.Data["HasOAuthService"] = setting.OauthService != nil
})
m.Group("/user", func() {
@@ -311,7 +256,7 @@ func runWeb(ctx *cli.Context) {
m.Group("/users", func() {
m.Get("", admin.Users)
m.Get("/new", admin.NewUser)
m.Post("/new", bindIgnErr(auth.RegisterForm{}), admin.NewUserPost)
m.Post("/new", bindIgnErr(auth.AdminCrateUserForm{}), admin.NewUserPost)
m.Get("/:userid", admin.EditUser)
m.Post("/:userid", bindIgnErr(auth.AdminEditUserForm{}), admin.EditUserPost)
m.Post("/:userid/delete", admin.DeleteUser)
@@ -322,21 +267,23 @@ func runWeb(ctx *cli.Context) {
})
m.Group("/repos", func() {
m.Get("", admin.Repositories)
m.Get("", admin.Repos)
m.Post("/delete", admin.DeleteRepo)
})
m.Group("/auths", func() {
m.Get("", admin.Authentications)
m.Get("/new", admin.NewAuthSource)
m.Post("/new", bindIgnErr(auth.AuthenticationForm{}), admin.NewAuthSourcePost)
m.Get("/:authid", admin.EditAuthSource)
m.Post("/:authid", bindIgnErr(auth.AuthenticationForm{}), admin.EditAuthSourcePost)
m.Combo("/:authid").Get(admin.EditAuthSource).
Post(bindIgnErr(auth.AuthenticationForm{}), admin.EditAuthSourcePost)
m.Post("/:authid/delete", admin.DeleteAuthSource)
})
m.Group("/notices", func() {
m.Get("", admin.Notices)
m.Get("/:id:int/delete", admin.DeleteNotice)
m.Post("/delete", admin.DeleteNotices)
m.Get("/empty", admin.EmptyNotices)
})
}, adminReq)
// ***** END: Admin *****
@@ -377,6 +324,7 @@ func runWeb(ctx *cli.Context) {
}
reqRepoAdmin := middleware.RequireRepoAdmin()
reqRepoPusher := middleware.RequireRepoPusher()
// ***** START: Organization *****
m.Group("/org", func() {
@@ -385,16 +333,16 @@ func runWeb(ctx *cli.Context) {
m.Group("/:org", func() {
m.Get("/dashboard", user.Dashboard)
m.Get("/:type(issues|pulls)", user.Issues)
m.Get("/^:type(issues|pulls)$", user.Issues)
m.Get("/members", org.Members)
m.Get("/members/action/:action", org.MembersAction)
m.Get("/teams", org.Teams)
m.Get("/teams/:team", org.TeamMembers)
m.Get("/teams/:team/repositories", org.TeamRepositories)
m.Get("/teams/:team/action/:action", org.TeamsAction)
m.Get("/teams/:team/action/repo/:action", org.TeamsRepoAction)
}, middleware.OrgAssignment(true, true))
m.Route("/teams/:team/action/:action", "GET,POST", org.TeamsAction)
m.Route("/teams/:team/action/repo/:action", "GET,POST", org.TeamsRepoAction)
}, middleware.OrgAssignment(true))
m.Group("/:org", func() {
m.Get("/teams/new", org.NewTeam)
@@ -406,6 +354,7 @@ func runWeb(ctx *cli.Context) {
m.Group("/settings", func() {
m.Combo("").Get(org.Settings).
Post(bindIgnErr(auth.UpdateOrgSettingForm{}), org.SettingsPost)
m.Post("/avatar", binding.MultipartForm(auth.UploadAvatarForm{}), org.SettingsAvatar)
m.Group("/hooks", func() {
m.Get("", org.Webhooks)
@@ -422,11 +371,8 @@ func runWeb(ctx *cli.Context) {
})
m.Route("/invitations/new", "GET,POST", org.Invitation)
}, middleware.OrgAssignment(true, true, true))
}, middleware.OrgAssignment(true, true))
}, reqSignIn)
m.Group("/org", func() {
m.Get("/:org", org.Home)
}, ignSignIn, middleware.OrgAssignment(true))
// ***** END: Organization *****
// ***** START: Repository *****
@@ -452,6 +398,7 @@ func runWeb(ctx *cli.Context) {
m.Post("/gogs/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost)
m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
m.Get("/:id", repo.WebHooksEdit)
m.Post("/:id/test", repo.TestWebhook)
m.Post("/gogs/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
@@ -468,14 +415,16 @@ func runWeb(ctx *cli.Context) {
m.Post("/delete", repo.DeleteDeployKey)
})
}, func(ctx *middleware.Context) {
ctx.Data["PageIsSettings"] = true
})
}, reqSignIn, middleware.RepoAssignment(true), reqRepoAdmin)
}, reqSignIn, middleware.RepoAssignment(), reqRepoAdmin, middleware.RepoRef())
m.Group("/:username/:reponame", func() {
m.Get("/action/:action", repo.Action)
m.Group("/issues", func() {
m.Combo("/new").Get(repo.NewIssue).
m.Combo("/new", repo.MustEnableIssues).Get(middleware.RepoRef(), repo.NewIssue).
Post(bindIgnErr(auth.CreateIssueForm{}), repo.NewIssuePost)
m.Combo("/:index/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment)
@@ -495,61 +444,81 @@ func runWeb(ctx *cli.Context) {
m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
m.Post("/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel)
m.Post("/delete", repo.DeleteLabel)
}, reqRepoAdmin)
}, reqRepoAdmin, middleware.RepoRef())
m.Group("/milestones", func() {
m.Get("/new", repo.NewMilestone)
m.Post("/new", bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost)
m.Combo("/new").Get(repo.NewMilestone).
Post(bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost)
m.Get("/:id/edit", repo.EditMilestone)
m.Post("/:id/edit", bindIgnErr(auth.CreateMilestoneForm{}), repo.EditMilestonePost)
m.Get("/:id/:action", repo.ChangeMilestonStatus)
m.Post("/delete", repo.DeleteMilestone)
}, reqRepoAdmin)
}, reqRepoAdmin, middleware.RepoRef())
m.Group("/releases", func() {
m.Get("/new", repo.NewRelease)
m.Post("/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost)
m.Get("/edit/:tagname", repo.EditRelease)
m.Post("/edit/:tagname", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost)
m.Post("/delete", repo.DeleteRelease)
}, reqRepoAdmin, middleware.RepoRef())
m.Combo("/compare/*").Get(repo.CompareAndPullRequest).
m.Combo("/compare/*", repo.MustEnablePulls).Get(repo.CompareAndPullRequest).
Post(bindIgnErr(auth.CreateIssueForm{}), repo.CompareAndPullRequestPost)
}, reqSignIn, middleware.RepoAssignment(true))
}, reqSignIn, middleware.RepoAssignment())
m.Group("/:username/:reponame", func() {
m.Get("/releases", middleware.RepoRef(), repo.Releases)
m.Get("/:type(issues|pulls)", repo.RetrieveLabels, repo.Issues)
m.Get("/:type(issues|pulls)/:index", repo.ViewIssue)
m.Get("/labels/", repo.RetrieveLabels, repo.Labels)
m.Get("/milestones", repo.Milestones)
m.Get("/branches", repo.Branches)
m.Group("", func() {
m.Get("/releases", repo.Releases)
m.Get("/^:type(issues|pulls)$", repo.RetrieveLabels, repo.Issues)
m.Get("/^:type(issues|pulls)$/:index", repo.ViewIssue)
m.Get("/labels/", repo.RetrieveLabels, repo.Labels)
m.Get("/milestones", repo.Milestones)
}, middleware.RepoRef())
// m.Get("/branches", repo.Branches)
m.Group("/wiki", func() {
m.Get("/?:page", repo.Wiki)
m.Get("/_pages", repo.WikiPages)
m.Group("", func() {
m.Combo("/_new").Get(repo.NewWiki).
Post(bindIgnErr(auth.NewWikiForm{}), repo.NewWikiPost)
m.Combo("/:page/_edit").Get(repo.EditWiki).
Post(bindIgnErr(auth.NewWikiForm{}), repo.EditWikiPost)
}, reqSignIn, reqRepoPusher)
}, repo.MustEnableWiki, middleware.RepoRef())
m.Get("/archive/*", repo.Download)
m.Group("/pulls/:index", func() {
m.Get("/commits", repo.ViewPullCommits)
m.Get("/files", repo.ViewPullFiles)
m.Post("/merge", reqRepoAdmin, repo.MergePullRequest)
})
}, repo.MustEnablePulls)
m.Group("", func() {
m.Get("/src/*", repo.Home)
m.Get("/raw/*", repo.SingleDownload)
m.Get("/commits/*", repo.RefCommits)
m.Get("/commit/*", repo.Diff)
m.Get("/stars", repo.Stars)
m.Get("/watchers", repo.Watchers)
m.Get("/forks", repo.Forks)
}, middleware.RepoRef())
m.Get("/compare/:before([a-z0-9]{40})...:after([a-z0-9]{40})", repo.CompareDiff)
}, ignSignIn, middleware.RepoAssignment(true))
}, ignSignIn, middleware.RepoAssignment())
m.Group("/:username", func() {
m.Group("/:reponame", func() {
m.Get("", repo.Home)
m.Get(".git", repo.Home)
}, ignSignIn, middleware.RepoAssignment(true, true), middleware.RepoRef())
m.Get("\\.git$", repo.Home)
}, ignSignIn, middleware.RepoAssignment(true), middleware.RepoRef())
m.Group("/:reponame", func() {
m.Any("/*", ignSignInAndCsrf, repo.Http)
m.Head("/hooks/trigger", repo.TriggerHook)
m.Any("/*", ignSignInAndCsrf, repo.HTTP)
m.Head("/tasks/trigger", repo.TriggerTask)
})
})
// ***** END: Repository *****

View File

@@ -11,12 +11,30 @@ RUN_MODE = dev
[repository]
ROOT =
SCRIPT_TYPE = bash
; Default ANSI charset
ANSI_CHARSET =
; Force every new repository to be private
FORCE_PRIVATE = false
; Patch test queue length, make it as large as possible
PULL_REQUEST_QUEUE_LENGTH = 10000
[ui]
; Number of repositories that are showed in one explore page
EXPLORE_PAGING_NUM = 20
; Number of issues that are showed in one page
ISSUE_PAGING_NUM = 10
; Number of maximum commits showed in one activity feed
FEED_MAX_COMMIT_NUM = 5
[ui.admin]
; Number of users that are showed in one page
USER_PAGING_NUM = 50
; Number of repos that are showed in one page
REPO_PAGING_NUM = 50
; Number of notices that are showed in one page
NOTICE_PAGING_NUM = 25
; Number of organization that are showed in one page
ORG_PAGING_NUM = 50
[markdown]
; Enable hard line break extension
@@ -30,6 +48,8 @@ HTTP_ADDR =
HTTP_PORT = 3000
; Disable SSH feature when not available
DISABLE_SSH = false
; Whether use builtin SSH server or not.
START_SSH_SERVER = false
SSH_PORT = 22
; Disable CDN even in "prod" mode
OFFLINE_MODE = false
@@ -61,7 +81,7 @@ USER = root
PASSWD =
; For "postgres" only, either "disable", "require" or "verify-full"
SSL_MODE = disable
; For "sqlite3" only
; For "sqlite3" and "tidb"
PATH = data/gogs.db
[admin]
@@ -95,6 +115,18 @@ ENABLE_REVERSE_PROXY_AUTHENTICATION = false
ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = false
; Do not check minimum key size with corresponding type
DISABLE_MINIMUM_KEY_SIZE_CHECK = false
; Enable captcha validation for registration
ENABLE_CAPTCHA = true
; used to filter keys which are too short
[service.minimum_key_sizes]
ED25519 = 256
ECDSA = 256
NTRU = 1087
MCE = 1702
McE = 1702
RSA = 1024
DSA = 1024
[webhook]
; Hook task queue length
@@ -109,7 +141,7 @@ PAGING_NUM = 10
[mailer]
ENABLED = false
; Buffer length of channel, keep it as it is if you don't know what it is.
SEND_BUFFER_LEN = 10
SEND_BUFFER_LEN = 100
; Name displayed in mail title
SUBJECT = %(APP_NAME)s
; Mail server
@@ -133,44 +165,6 @@ FROM =
USER =
PASSWD =
[oauth]
ENABLED = false
[oauth.github]
ENABLED = false
CLIENT_ID =
CLIENT_SECRET =
SCOPES = https://api.github.com/user
AUTH_URL = https://github.com/login/oauth/authorize
TOKEN_URL = https://github.com/login/oauth/access_token
; Get client id and secret from
; https://console.developers.google.com/project
[oauth.google]
ENABLED = false
CLIENT_ID =
CLIENT_SECRET =
SCOPES = https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile
AUTH_URL = https://accounts.google.com/o/oauth2/auth
TOKEN_URL = https://accounts.google.com/o/oauth2/token
[oauth.qq]
ENABLED = false
CLIENT_ID =
CLIENT_SECRET =
SCOPES = get_user_info
; QQ 互联
AUTH_URL = https://graph.qq.com/oauth2.0/authorize
TOKEN_URL = https://graph.qq.com/oauth2.0/token
[oauth.weibo]
ENABLED = false
CLIENT_ID =
CLIENT_SECRET =
SCOPES = all
AUTH_URL = https://api.weibo.com/oauth2/authorize
TOKEN_URL = https://api.weibo.com/oauth2/access_token
[cache]
; Either "memory", "redis", or "memcache", default is "memory"
ADAPTER = memory
@@ -340,3 +334,5 @@ it-IT = it
[other]
SHOW_FOOTER_BRANDING = false
; Show version information about gogs and go in the footer
SHOW_FOOTER_VERSION = true

View File

@@ -1 +0,0 @@
Leiningen.gitignore

View File

@@ -1 +0,0 @@
C++.gitignore

View File

@@ -1,20 +1,30 @@
# This file lists all PUBLIC individuals having contributed content to the translation.
# Order of name is meaningless.
# Entries are in alphabetical order.
Akihiro YAGASAKI <yaggytter@momiage.com>
Alexander Steinhöfer <kontakt@lx-s.de>
Alexandre Magno <alexandre.mbm@gmail.com>
Barış Arda Yılmaz <ardayilmazgamer@gmail.com>
Christoph Kisfeld <christoph.kisfeld@gmail.com>
Daniel Speichert <daniel@speichert.pl>
Gregor Santner <gdev@live.de>
Huimin Wang <wanghm2009@hotmail.co.jp>
ilko <email>
Thomas Fanninger <gogs.thomas@fanninger.at>
Łukasz Jan Niemier <lukasz@niemier.pl>
Lafriks <lafriks@gmail.com>
Luc Stepniewski <luc@stepniewski.fr>
Miguel de la Cruz <miguel@mcrx.me>
Marc Schiller <marc@schiller.im>
Morten Sørensen <klim8d@gmail.com>
Natan Albuquerque <natanalbuquerque5@gmail.com>
Akihiro YAGASAKI <yaggytter AT momiage DOT com>
Alexander Steinhöfer <kontakt AT lx-s DOT de>
Alexandre Magno <alexandre DOT mbm AT gmail DOT com>
Andrey Nering <andrey AT nering DOT com DOT br>
Arthur Aslanyan <arthur DOT e DOT aslanyan AT gmail DOT com>
Barış Arda Yılmaz <ardayilmazgamer AT gmail DOT com>
Christoph Kisfeld <christoph DOT kisfeld AT gmail DOT com>
Daniel Speichert <daniel AT speichert DOT pl>
Dmitriy Nogay <me AT catwhocode DOT ga>
Gregor Santner <gdev AT live DOT de>
Hamid Feizabadi <hamidfzm AT gmail DOT com>
Huimin Wang <wanghm2009 AT hotmail DOT co DOT jp>
ilko
Lafriks <lafriks AT gmail DOT com>
Lauri Ojansivu <x AT xet7 DOT org>
Luc Stepniewski <luc AT stepniewski DOT fr>
Marc Schiller <marc AT schiller DOT im>
Miguel de la Cruz <miguel AT mcrx DOT me>
Morten Sørensen <klim8d AT gmail DOT com>
Nakao Takamasa <at.mattenn AT gmail DOT com>
Natan Albuquerque <natanalbuquerque5 AT gmail DOT com>
Odilon Junior <odilon DOT junior93 AT gmail DOT com>
Thomas Fanninger <gogs DOT thomas AT fanninger DOT at>
Tilmann Bach <tilmann AT outlook DOT com>
Vladimir Vissoultchev <wqweto AT gmail DOT com>
YJSoft <yjsoft AT yjsoft DOT pe DOT kr>
Łukasz Jan Niemier <lukasz AT niemier DOT pl>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,6 @@ dashboard = Dashboard
explore = Explore
help = Help
sign_in = Sign In
social_sign_in = Social Sign In: 2nd Step <small>associate account</small>
sign_out = Sign Out
sign_up = Sign Up
register = Register
@@ -14,12 +13,12 @@ version = Version
page = Page
template = Template
language = Language
create_new = Create new...
create_new = Create...
user_profile_and_more = User profile and more
signed_in_as = Signed in as
username = Username
email = E-mail
email = Email
password = Password
re_type = Re-Type
captcha = Captcha
@@ -54,7 +53,8 @@ code = Code
[install]
install = Installation
title = Install Steps For First-time Run
requite_db_desc = Gogs requires MySQL, PostgreSQL or SQLite3.
docker_helper = If you're running Gogs inside Docker, please read <a target="_blank" href="%s">Guidelines</a> carefully before you change anything in this page!
requite_db_desc = Gogs requires MySQL, PostgreSQL, SQLite3 or TiDB.
db_title = Database Settings
db_type = Database Type
host = Host
@@ -64,8 +64,11 @@ db_name = Database Name
db_helper = Please use INNODB engine with utf8_general_ci charset for MySQL.
ssl_mode = SSL Mode
path = Path
sqlite_helper = The file path of SQLite3 database.
err_empty_sqlite_path = SQLite3 database path cannot be empty.
sqlite_helper = The file path of SQLite3 or TiDB database.
err_empty_db_path = SQLite3 or TiDB database path cannot be empty.
err_invalid_tidb_name = TiDB database name does not allow characters "." and "-".
no_admin_and_disable_registration = You cannot disable registration without creating an admin account.
err_empty_admin_password = Admin password cannot be empty.
general_title = Application General Settings
app_name = Application Name
@@ -81,14 +84,14 @@ ssh_port_helper = Port number which your SSH server is using, leave it empty to
http_port = HTTP Port
http_port_helper = Port number which application will listen on.
app_url = Application URL
app_url_helper = This affects HTTP/HTTPS clone URL and somewhere in e-mail.
app_url_helper = This affects HTTP/HTTPS clone URL and somewhere in email.
optional_title = Optional Settings
email_title = E-mail Service Settings
email_title = Email Service Settings
smtp_host = SMTP Host
smtp_from = From
smtp_from_helper = Mail from address, RFC 5322. It can be just an email address, or the "Name" <email@example.com> format.
mailer_user = Sender E-mail
mailer_user = Sender Email
mailer_password = Sender Password
register_confirm = Enable Register Confirmation
mail_notify = Enable Mail Notification
@@ -99,6 +102,8 @@ disable_gravatar = Disable Gravatar Service
disable_gravatar_popup = Disable Gravatar and custom sources, all avatars are uploaded by users or default.
disable_registration = Disable Self-registration
disable_registration_popup = Disable user self-registration, only admin can create accounts.
enable_captcha = Enable Captcha
enable_captcha_popup = Require validate captcha for user self-registration.
require_sign_in_view = Enable Require Sign In to View Pages
require_sign_in_view_popup = Only signed in users can view pages, visitors will only be able to see sign in/up pages.
admin_setting_desc = You do not have to create an admin account right now, user whoever ID=1 will gain admin access automatically.
@@ -106,7 +111,7 @@ admin_title = Admin Account Settings
admin_name = Username
admin_password = Password
confirm_password = Confirm Password
admin_email = E-mail
admin_email = Admin Email
install_gogs = Install Gogs
test_git_failed = Fail to test 'git' command: %v
sqlite3_not_available = Your release version does not support SQLite3, please download the official binary version from %s, NOT the gobuild version.
@@ -118,7 +123,7 @@ invalid_admin_setting = Admin account setting is invalid: %v
install_success = Welcome! We're glad that you chose Gogs, have fun and take care.
[home]
uname_holder = Username or E-mail
uname_holder = Username or email
password_holder = Password
switch_dashboard_context = Switch Dashboard Context
my_repos = My Repositories
@@ -142,19 +147,25 @@ remember_me = Remember Me
forgot_password= Forgot Password
forget_password = Forgot password?
sign_up_now = Need an account? Sign up now.
confirmation_mail_sent_prompt = A new confirmation e-mail has been sent to <b>%s</b>, please check your inbox within the next %d hours to complete the registration process.
sign_in_email = Sign in to your e-mail
confirmation_mail_sent_prompt = A new confirmation email has been sent to <b>%s</b>, please check your inbox within the next %d hours to complete the registration process.
active_your_account = Activate Your Account
resent_limit_prompt = Sorry, you already requested an activation email recently. Please wait 3 minutes then try again.
has_unconfirmed_mail = Hi %s, you have an unconfirmed e-mail address (<b>%s</b>). If you haven't received a confirmation e-mail or need to resend a new one, please click on the button below.
resend_mail = Click here to resend your activation e-mail
email_not_associate = This e-mail address is not associated with any account.
send_reset_mail = Click here to (re)send your password reset e-mail
has_unconfirmed_mail = Hi %s, you have an unconfirmed email address (<b>%s</b>). If you haven't received a confirmation email or need to resend a new one, please click on the button below.
resend_mail = Click here to resend your activation email
email_not_associate = This email address is not associated with any account.
send_reset_mail = Click here to (re)send your password reset email
reset_password = Reset Your Password
invalid_code = Sorry, your confirmation code has expired or not valid.
reset_password_helper = Click here to reset your password
password_too_short = Password length cannot be less then 6.
[mail]
activate_account = Please activate your account
activate_email = Verify your email address
reset_password = Reset your password
register_success = Registration successful, welcome
register_notify = Welcome on board
[modal]
yes = Yes
no = No
@@ -163,7 +174,7 @@ modify = Modify
[form]
UserName = Username
RepoName = Repository name
Email = E-mail address
Email = Email address
Password = Password
Retype = Re-type password
SSHTitle = SSH key name
@@ -171,7 +182,7 @@ HttpsUrl = HTTPS URL
PayloadUrl = Payload URL
TeamName = Team name
AuthName = Authorization name
AdminEmail = Admin E-mail
AdminEmail = Admin email
require_error = ` cannot be empty.`
alpha_dash_error = ` must be valid alpha or numeric or dash(-_) characters.`
@@ -179,17 +190,18 @@ alpha_dash_dot_error = ` must be valid alpha or numeric or dash(-_) or dot chara
size_error = ` must be size %s.`
min_size_error = ` must contain at least %s characters.`
max_size_error = ` must contain at most %s characters.`
email_error = ` is not a valid e-mail address.`
email_error = ` is not a valid email address.`
url_error = ` is not a valid URL.`
include_error = ` must contain substring '%s'.`
unknown_error = Unknown error:
captcha_incorrect = Captcha didn't match.
password_not_match = Password and confirm password are not same.
username_been_taken = Username has been already taken.
repo_name_been_taken = Repository name has been already taken.
org_name_been_taken = Organization name has been already taken.
team_name_been_taken = Team name has been already taken.
email_been_used = E-mail address has been already used.
username_been_taken = Username has already been taken.
repo_name_been_taken = Repository name has already been taken.
org_name_been_taken = Organization name has already been taken.
team_name_been_taken = Team name has already been taken.
email_been_used = Email address has already been used.
illegal_team_name = Team name contains illegal characters.
username_password_incorrect = Username or password is not correct.
enterred_invalid_repo_name = Please make sure that the repository name you entered is correct.
@@ -206,7 +218,7 @@ still_own_repo = Your account still has ownership over at least one repository,
still_has_org = Your account still has membership in at least one organization, you have to leave or delete your memberships first.
org_still_own_repo = This organization still have ownership of repository, you have to delete or transfer them first.
still_own_user = This authentication still is in use by at least one user, please remove them from the authentication and try again.
still_own_user = This authentication is still in use by at least one user, please remove them from the authentication and try again.
target_branch_not_exist = Target branch does not exist.
@@ -234,19 +246,19 @@ delete = Delete Account
uid = Uid
public_profile = Public Profile
profile_desc = Your E-mail address is public and will be used for any account related notifications, and any web based operations made via the site.
profile_desc = Your email address is public and will be used for any account related notifications, and any web based operations made via the site.
full_name = Full Name
website = Website
location = Location
update_profile = Update Profile
update_profile_success = Your profile has been updated successfully.
change_username = Username Changed
change_username_desc = You changed your username. This will affect the way how links relate to your account. Do you want to continue?
change_username_prompt = This change will affect the way how links relate to your account.
continue = Continue
cancel = Cancel
enable_custom_avatar = Enable Custom Avatar
enable_custom_avatar_helper = Enable this to disable fetch from Gravatar
enable_custom_avatar_helper = Disable fetch from Gravatar
choose_new_avatar = Choose new avatar
update_avatar = Update Avatar Setting
uploaded_avatar_not_a_image = Uploaded file is not a image.
@@ -256,19 +268,23 @@ update_avatar_success = Your avatar setting has been updated successfully.
change_password = Change Password
old_password = Current Password
new_password = New Password
retype_new_password = Retype New Password
password_incorrect = Current password is not correct.
change_password_success = Your password was successfully changed. You can now sign using this new password.
emails = E-mail Addresses
manage_emails = Manage e-mail addresses
email_desc = Your primary e-mail address will be used for notifications and other operations.
emails = Email Addresses
manage_emails = Manage email addresses
email_desc = Your primary email address will be used for notifications and other operations.
primary = Primary
primary_email = Set as primary
delete_email = Delete
add_new_email = Add new e-mail address
add_email = Add e-mail
add_email_confirmation_sent = A new confirmation e-mail has been sent to <b>%s</b>, please check your inbox within the next %d hours to complete the confirmation process.
add_email_success = Your new E-mail address was successfully added.
email_deletion = Email Deletion
email_deletion_desc = Deleting this email address will remove related information from your account. Do you want to continue?
email_deletion_success = Email has been deleted successfully!
add_new_email = Add new email address
add_email = Add email
add_email_confirmation_sent = A new confirmation email has been sent to '%s', please check your inbox within the next %d hours to complete the confirmation process.
add_email_success = Your new email address was successfully added.
manage_ssh_keys = Manage SSH Keys
add_key = Add Key
@@ -319,7 +335,9 @@ repo_name = Repository Name
repo_name_helper = A good repository name is usually composed of short, memorable and unique keywords.
visibility = Visibility
visiblity_helper = This repository is <span class="ui red text">Private</span>
visiblity_helper_forced = Site admin has forced all new repositories to be <span class="ui red text">Private</span>
visiblity_fork_helper = (Change of this value will affect all forks)
clone_helper = Need help cloning? Visit <a target="_blank" href="%s">Help</a>!
fork_repo = Fork Repository
fork_from = Fork From
fork_visiblity_helper = You cannot alter the visibility of a forked repository.
@@ -330,10 +348,13 @@ license = License
license_helper = Select a license file
readme = Readme
readme_helper = Select a readme template
auto_init = Initialize this repository selected files and template
auto_init = Initialize this repository with selected files and template
create_repo = Create Repository
default_branch = Default Branch
mirror_interval = Mirror Interval (hour)
watchers = Watchers
stargazers = Stargazers
forks = Forks
form.name_reserved = Repository name '%s' is reserved.
form.name_pattern_not_allowed = Repository name pattern '%s' is not allowed.
@@ -344,14 +365,16 @@ migrate_type_helper = This repository will be a <span class="text blue">mirror</
migrate_repo = Migrate Repository
migrate.clone_address = Clone Address
migrate.clone_address_desc = This can be a HTTP/HTTPS/GIT URL or local server path.
migrate.permission_denied = You are not allowed to import local repositories.
migrate.invalid_local_path = Invalid local path, it does not exist or not a directory.
migrate.failed = Migration failed: %v
forked_from = forked from
fork_from_self = You cannot fork repository you already owned!
fork_from_self = You cannot fork a repository you already own!
copy_link = Copy
click_to_copy = Copy to clipboard
copy_link_success = Copied!
copy_link_error = Press ⌘-C or Ctrl-C to copy
copied = Copied OK
clone_helper = Need help cloning? Visit <a target="_blank" href="%s">Help</a>!
unwatch = Unwatch
watch = Watch
unstar = Unstar
@@ -363,10 +386,12 @@ quick_guide = Quick Guide
clone_this_repo = Clone this repository
create_new_repo_command = Create a new repository on the command line
push_exist_repo = Push an existing repository from the command line
repo_is_empty = This repository is empty, please come back later!
code = Code
branch = Branch
tree = Tree
branch_and_tags = Branches & Tags
filter_branch_and_tag = Filter branch or tag
branches = Branches
tags = Tags
issues = Issues
@@ -435,9 +460,9 @@ issues.num_comments = %d comments
issues.commented_at = `commented <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.no_content = There is no content yet.
issues.close_issue = Close
issues.close_comment_issue = Close and comment
issues.close_comment_issue = Comment and close
issues.reopen_issue = Reopen
issues.reopen_comment_issue = Reopen and comment
issues.reopen_comment_issue = Comment and reopen
issues.create_comment = Comment
issues.closed_at = `closed <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.reopened_at = `reopened <a id="%[1]s" href="#%[1]s">%[2]s</a>`
@@ -458,9 +483,10 @@ issues.label_edit = Edit
issues.label_delete = Delete
issues.label_modify = Label Modification
issues.label_deletion = Label Deletion
issues.label_deletion_desc = Delete this label will remove its information in all related issues. Do you want to continue?
issues.label_deletion_desc = Deleting this label will remove its information in all related issues. Do you want to continue?
issues.label_deletion_success = Label has been deleted successfully!
pulls.new = New Pull Request
pulls.compare_changes = Compare Changes
pulls.compare_changes_desc = Compare two branches and make a pull request for changes.
pulls.compare_base = base
@@ -479,10 +505,12 @@ pulls.reopen_to_merge = Please reopen this pull request to perform merge operati
pulls.merged = Merged
pulls.has_merged = This pull request has been merged successfully!
pulls.data_broken = Data of this pull request has been broken due to deletion of fork information.
pulls.is_checking = The conflict checking is still in progress, please refresh page in few moments.
pulls.can_auto_merge_desc = You can perform auto-merge operation on this pull request.
pulls.cannot_auto_merge_desc = You can't perform auto-merge operation because there are conflicts between commits.
pulls.cannot_auto_merge_helper = Please use command line tool to solve it.
pulls.merge_pull_request = Merge Pull Request
pulls.open_unmerged_pull_exists = `You can't perform reopen operation because there is already an open pull request (#%d) from same repository with same merge information and is waiting for merging.`
milestones.new = New Milestone
milestones.open_tab = %d Open
@@ -497,27 +525,50 @@ milestones.title = Title
milestones.desc = Description
milestones.due_date = Due Date (optional)
milestones.clear = Clear
milestones.invalid_due_date_format = Due date format is invalid, must be 'year-mm-dd'.
milestones.invalid_due_date_format = Due date format is invalid, must be 'yyyy-mm-dd'.
milestones.create_success = Milestone '%s' has been created successfully!
milestones.edit = Edit Milestone
milestones.edit_subheader = Use better description for milestones so people won't be confused.
milestones.edit_subheader = Use a better description for milestones so people won't be confused.
milestones.cancel = Cancel
milestones.modify = Modify Milestone
milestones.edit_success = Changes of milestone '%s' has been saved successfully!
milestones.deletion = Milestone Deletion
milestones.deletion_desc = Delete this milestone will remove its information in all related issues. Do you want to continue?
milestones.deletion_desc = Deleting this milestone will remove its information in all related issues. Do you want to continue?
milestones.deletion_success = Milestone has been deleted successfully!
wiki = Wiki
wiki.welcome = Welcome to Wiki!
wiki.welcome_desc = Wiki is the place where you would like to document your project together and make it better.
wiki.create_first_page = Create the first page
wiki.page = Page
wiki.filter_page = Filter page
wiki.new_page = Create New Page
wiki.default_commit_message = Write a note about this update (optional).
wiki.save_page = Save Page
wiki.last_commit_info = %s edited this page %s
wiki.edit_page_button = Edit
wiki.new_page_button = New Page
wiki.page_already_exists = Wiki page with same name already exists.
wiki.pages = Pages
wiki.last_updated = Last updated %s
settings = Settings
settings.options = Options
settings.collaboration = Collaboration
settings.hooks = Webhooks
settings.githooks = Git Hooks
settings.basic_settings = Basic Settings
settings.danger_zone = Danger Zone
settings.site = Official Site
settings.update_settings = Update Settings
settings.change_reponame_prompt = This change will affect how links relate to the repository.
settings.advanced_settings = Advanced Settings
settings.wiki_desc = Enable wiki to allow people write documents
settings.issues_desc = Enable builtin lightweight issue tracker
settings.use_external_issue_tracker = Use external issue tracker
settings.tracker_url_format = External Issue Tracker URL Format
settings.tracker_url_format_desc = You can use placeholder <code>{user} {repo} {index}</code> for user name, repository name and issue index.
settings.pulls_desc = Enable pull requests to accept public contributions
settings.danger_zone = Danger Zone
settings.transfer = Transfer Ownership
settings.transfer_desc = Transfer this repository to another user or to an organization in which you have admin rights.
settings.new_owner_has_same_repo = The new owner already has a repository with same name. Please choose another name.
@@ -531,6 +582,7 @@ settings.delete_notices_2 = - This operation will permanently delete the everyth
settings.delete_notices_fork_1 = - If this repository is public, all forks will be became independent after deletion.
settings.delete_notices_fork_2 = - If this repository is private, all forks will be removed at the same time.
settings.delete_notices_fork_3 = - If you want to keep all forks after deletion, please change visibility of this repository to public first.
settings.deletion_success = Repository has been deleted successfully!
settings.update_settings_success = Repository options has been updated successfully.
settings.transfer_owner = New Owner
settings.make_transfer = Make Transfer
@@ -539,12 +591,16 @@ settings.confirm_delete = Confirm Deletion
settings.add_collaborator = Add New Collaborator
settings.add_collaborator_success = New collaborator has been added.
settings.remove_collaborator_success = Collaborator has been removed.
settings.search_user_placeholder = Search user...
settings.user_is_org_member = User is organization member who cannot be added as a collaborator.
settings.add_webhook = Add Webhook
settings.hooks_desc = Webhooks are much like basic HTTP POST event triggers. Whenever something occurs in Gogs, we will handle the notification to the target host you specify. Learn more in this <a target="_blank" href="%s">Webhooks Guide</a>.
settings.webhook_deletion = Delete Webhook
settings.webhook_deletion_desc = Delete this webhook will remove its information and all delivery history. Do you want to continue?
settings.webhook_deletion_success = Webhook has been deleted successfully!
settings.webhook.test_delivery = Test Delivery
settings.webhook.test_delivery_desc = Send a fake push event delivery to test your webhook settings
settings.webhook.test_delivery_success = Test webhook has been added to delivery queue. It may taks few seconds before it shows up in the delivery history.
settings.webhook.request = Request
settings.webhook.response = Response
settings.webhook.headers = Headers
@@ -584,14 +640,15 @@ settings.slack_domain = Domain
settings.slack_channel = Channel
settings.deploy_keys = Deploy Keys
settings.add_deploy_key = Add Deploy Key
settings.deploy_key_desc = Deploy key only has read-only access. It is not same as personal account SSH keys.
settings.no_deploy_keys = You haven't added any deploy key.
settings.title = Title
settings.deploy_key_content = Content
settings.key_been_used = Deploy key content has been used.
settings.key_name_used = Deploy key with same name has already existed.
settings.key_name_used = Deploy key with the same name already exists.
settings.add_key_success = New deploy key '%s' has been added successfully!
settings.deploy_key_deletion = Delete Deploy Key
settings.deploy_key_deletion_desc = Delete this deploy key will remove all related accesses for this repository. Do you want to continue?
settings.deploy_key_deletion_desc = Deleting this deploy key will remove all related accesses for this repository. Do you want to continue?
settings.deploy_key_deletion_success = Deploy key has been deleted successfully!
diff.browse_source = Browse Source
@@ -611,26 +668,33 @@ release.stable = Stable
release.edit = edit
release.ahead = <strong>%d</strong> commits to %s since this release
release.source_code = Source Code
release.new_subheader = Publish releases to iterate product.
release.edit_subheader = Detailed change log can help users understand what has been improved.
release.tag_name = Tag name
release.target = Target
release.tag_helper = Choose an existing tag, or create a new tag on publish.
release.release_title = Release title
release.content_with_md = Content with <a href="%s">Markdown</a>
release.title = Title
release.content = Content
release.write = Write
release.preview = Preview
release.content_placeholder = Write some content
release.loading = Loading...
release.prerelease_desc = This is a pre-release
release.prerelease_helper = Well point out that this release is not production-ready.
release.prerelease_helper = We'll point out that this release is not production-ready.
release.cancel = Cancel
release.publish = Publish Release
release.save_draft = Save Draft
release.edit_release = Edit Release
release.tag_name_already_exist = Release with this tag name has already existed.
release.delete_release = Delete This Release
release.deletion = Release Deletion
release.deletion_desc = Deleting this release will delete the corresponding Git tag. Do you want to continue?
release.deletion_success = Release has been deleted successfully!
release.tag_name_already_exist = Release with this tag name already exists.
release.downloads = Downloads
[org]
org_name_holder = Organization Name
org_full_name_holder = Organization Full Name
org_name_helper = Great organization names are short and memorable.
org_email_helper = Organization's E-mail receives all notifications and confirmations.
create_org = Create Organization
repo_updated = Updated
people = People
@@ -655,9 +719,9 @@ settings.full_name = Full Name
settings.website = Website
settings.location = Location
settings.update_settings = Update Settings
settings.change_orgname = Organization Name Changed
settings.change_orgname_desc = Organization name has been changed. This will affect how links relate to the organization. Do you want to continue?
settings.update_setting_success = Organization settings were successfully updated.
settings.update_setting_success = Organization settings has been updated successfully.
settings.change_orgname_prompt = This change will affect how links relate to the organization.
settings.update_avatar_success = Organization avatar setting has been updated successfully.
settings.delete = Delete Organization
settings.delete_account = Delete This Organization
settings.delete_prompt = The organization will be permanently removed, and this <strong>CANNOT</strong> be undone!
@@ -666,16 +730,17 @@ settings.delete_org_title = Organization Deletion
settings.delete_org_desc = This organization is going to be deleted permanently, do you want to continue?
settings.hooks_desc = Add webhooks that will be triggered for <strong>all repositories</strong> under this organization.
members.membership_visibility = Membership Visibility:
members.public = Public
members.public_helper = make private
members.private = Private
members.private_helper = make public
members.member_role = Member Role:
members.owner = Owner
members.member = Member
members.conceal = Conceal
members.remove = Remove
members.leave = Leave
members.invite_desc = Start typing a username to invite a new member to %s:
members.invite_desc = Add a new member to %s:
members.invite_now = Invite Now
teams.join = Join
@@ -700,6 +765,7 @@ teams.read_permission_desc = This team grants <strong>Read</strong> access: memb
teams.write_permission_desc = This team grants <strong>Write</strong> access: members can read from and push to the team's repositories.
teams.admin_permission_desc = This team grants <strong>Admin</strong> access: members can read from, push to, and add collaborators to the team's repositories.
teams.repositories = Team Repositories
teams.search_repo_placeholder = Search repository...
teams.add_team_repository = Add Team Repository
teams.remove_repo = Remove
teams.add_nonexistent_repo = The repository you're trying to add does not exist, please create it first.
@@ -713,8 +779,9 @@ authentication = Authentications
config = Configuration
notices = System Notices
monitor = Monitoring
prev = Prev.
next = Next
first_page = First
last_page = Last
total = Total: %d
dashboard.statistic = Statistic
dashboard.operations = Operations
@@ -729,6 +796,8 @@ dashboard.delete_inactivate_accounts = Delete all inactive accounts
dashboard.delete_inactivate_accounts_success = All inactivate accounts have been deleted successfully.
dashboard.delete_repo_archives = Delete all repositories archives
dashboard.delete_repo_archives_success = All repositories archives have been deleted successfully.
dashboard.delete_missing_repos = Delete all repository records that lost Git files
dashboard.delete_missing_repos_success = All repository records that lost Git files have been deleted successfully.
dashboard.git_gc_repos = Do garbage collection on repositories
dashboard.git_gc_repos_success = All repositories have done garbage collection successfully.
dashboard.resync_all_sshkeys = Rewrite '.ssh/authorized_keys' file (caution: non-Gogs keys will be lost)
@@ -773,19 +842,24 @@ users.activated = Activated
users.admin = Admin
users.repos = Repos
users.created = Created
users.send_register_notify = Send Registration Notification To User
users.new_success = New account '%s' has been created successfully.
users.edit = Edit
users.auth_source = Authorization Source
users.auth_source = Authentication Source
users.local = Local
users.auth_login_name = Authorization Login Name
users.auth_login_name = Authentication Login Name
users.password_helper = Leave it empty to remain unchanged.
users.update_profile_success = Account profile has been updated successfully.
users.edit_account = Edit Account
users.is_activated = This account is activated
users.is_admin = This account has administrator permissions
users.allow_git_hook = This account has permissions to create Git hooks
users.allow_import_local = This account has permissions to import local repositories
users.update_profile = Update Account Profile
users.delete_account = Delete This Account
users.still_own_repo = This account still has ownership over at least one repository, you have to delete or transfer them first.
users.still_has_org = This account still has membership in at least one organization, you have to leave or delete the organizations first.
users.deletion_success = Account has been deleted successfully!
orgs.org_manage_panel = Organization Manage Panel
orgs.name = Name
@@ -800,41 +874,49 @@ repos.watches = Watches
repos.stars = Stars
repos.issues = Issues
auths.auth_manage_panel = Authorization Manage Panel
auths.new = Add New Authorization Source
auths.auth_manage_panel = Authentication Manage Panel
auths.new = Add New Source
auths.name = Name
auths.type = Type
auths.enabled = Enabled
auths.updated = Updated
auths.auth_type = Authorization Type
auths.auth_name = Authorization Name
auths.auth_type = Authentication Type
auths.auth_name = Authentication Name
auths.domain = Domain
auths.host = Host
auths.port = Port
auths.bind_dn = Bind DN
auths.bind_password = Bind Password
auths.bind_password_helper = Warning: This password is stored in plain text. Do not use a high privileged account.
auths.user_base = User Search Base
auths.user_dn = User DN
auths.attribute_username = Username attribute
auths.attribute_username_placeholder = Leave empty to use sign-in form field value for user name.
auths.attribute_name = First name attribute
auths.attribute_surname = Surname attribute
auths.attribute_mail = E-mail attribute
auths.attribute_mail = Email attribute
auths.filter = User Filter
auths.admin_filter = Admin Filter
auths.ms_ad_sa = Ms Ad SA
auths.smtp_auth = SMTP Authorization Type
auths.smtp_auth = SMTP Authentication Type
auths.smtphost = SMTP Host
auths.smtpport = SMTP Port
auths.allowed_domains = Allowed Domains
auths.allowed_domains_helper = Leave it empty to not restrict any domains. Multiple domains should be separated by comma ','.
auths.enable_tls = Enable TLS Encryption
auths.skip_tls_verify = Skip TLS Verify
auths.pam_service_name = PAM Service Name
auths.enable_auto_register = Enable Auto Registration
auths.tips = Tips
auths.edit = Edit Authorization Setting
auths.activated = This authentication has activated
auths.update_success = Authorization setting has been updated successfully.
auths.update = Update Authorization Setting
auths.delete = Delete This Authorization
auths.delete_auth_title = Authorization Deletion
auths.delete_auth_desc = This authorization is going to be deleted, do you want to continue?
auths.edit = Edit Authentication Setting
auths.activated = This authentication is activate
auths.new_success = New authentication '%s' has been added successfully.
auths.update_success = Authentication setting has been updated successfully.
auths.update = Update Authentication Setting
auths.delete = Delete This Authentication
auths.delete_auth_title = Authentication Deletion
auths.delete_auth_desc = This authentication is going to be deleted, do you want to continue?
auths.deletion_success = Authentication has been deleted successfully!
config.server_config = Server Configuration
config.app_name = Application Name
@@ -858,14 +940,16 @@ config.db_user = User
config.db_ssl_mode = SSL Mode
config.db_ssl_mode_helper = (for "postgres" only)
config.db_path = Path
config.db_path_helper = (for "sqlite3" only)
config.db_path_helper = (for "sqlite3" and "tidb")
config.service_config = Service Configuration
config.register_email_confirm = Require E-mail Confirmation
config.register_email_confirm = Require Email Confirmation
config.disable_register = Disable Registration
config.show_registration_button = Show Register Button
config.require_sign_in_view = Require Sign In View
config.mail_notify = Mail Notification
config.enable_cache_avatar = Enable Cache Avatar
config.mail_notify = Mail Notification
config.disable_key_size_check = Disable Minimum Key Size Check
config.enable_captcha = Enable Captcha
config.active_code_lives = Active Code Lives
config.reset_password_code_lives = Reset Password Code Lives
config.webhook_config = Webhook Configuration
@@ -911,16 +995,23 @@ monitor.start = Start Time
monitor.execute_time = Execution Time
notices.system_notice_list = System Notices
notices.view_detail_header = View Notice Detail
notices.actions = Actions
notices.select_all = Select All
notices.deselect_all = Deselect All
notices.inverse_selection = Inverse Selection
notices.delete_selected = Delete Selected
notices.delete_all = Delete All Notices
notices.type = Type
notices.type_1 = Repository
notices.desc = Description
notices.op = Op.
notices.delete_success = System notice has been deleted successfully.
notices.delete_success = System notices have been deleted successfully.
[action]
create_repo = created repository <a href="%s">%s</a>
rename_repo = renamed repository from <code>%[1]s</code> to <a href="%[2]s">%[3]s</a>
commit_repo = pushed to <a href="%s/src/%s">%[2]s</a> at <a href="%[1]s">%[3]s</a>
commit_repo = pushed to <a href="%[1]s/src/%[2]s">%[3]s</a> at <a href="%[1]s">%[4]s</a>
create_issue = `opened issue <a href="%s/issues/%s">%s#%[2]s</a>`
create_pull_request = `created pull request <a href="%s/pulls/%s">%s#%[2]s</a>`
comment_issue = `commented on issue <a href="%s/issues/%s">%s#%[2]s</a>`

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +0,0 @@
web:
build: .
links:
- mysql
ports:
- "3000:3000"
mysql:
image: mysql
environment:
- MYSQL_ROOT_PASSWORD=gogs
- MYSQL_DATABASE=gogs

View File

@@ -17,12 +17,12 @@ $ mkdir -p /var/gogs
$ docker run --name=gogs -p 10022:22 -p 10080:3000 -v /var/gogs:/data gogs/gogs
# Use `docker start` if you have stopped it.
$ docker start gogs
$ docker start gogs
```
Files will be store in local path `/var/gogs` in my case.
Directory `/var/gogs` keeps Git repoistories and Gogs data:
Directory `/var/gogs` keeps Git repositories and Gogs data:
/var/gogs
|-- git
@@ -33,7 +33,17 @@ Directory `/var/gogs` keeps Git repoistories and Gogs data:
|-- conf
|-- data
|-- log
|-- templates
### Volume with data container
If you're more comfortable with mounting data to a data container, the commands you execute at the first time will look like as follows:
```
# Create data container
docker run --name=gogs-data --entrypoint /bin/true gogs/gogs
# Use `docker run` for the first time.
docker run --name=gogs --volumes-from gogs-data -p 10022:22 -p 10080:3000 gogs/gogs
```
## Settings
@@ -41,23 +51,24 @@ Most of settings are obvious and easy to understand, but there are some settings
- **Repository Root Path**: keep it as default value `/home/git/gogs-repositories` because `start.sh` already made a symbolic link for you.
- **Run User**: keep it as default value `git` because `start.sh` already setup a user with name `git`.
- **Domain**: fill in with Docker container IP(e.g. `192.168.99.100`).
- **Domain**: fill in with Docker container IP(e.g. `192.168.99.100`). But if you want to access your Gogs instance from a different physical machine, please fill in with the hostname or IP address of the Docker host machine.
- **SSH Port**: Use the exposed port from Docker container. For example, your SSH server listens on `22` inside Docker, but you expose it by `10022:22`, then use `10022` for this value.
- **HTTP Port**: Use port you want Gogs to listen on inside Docker container. For example, your Gogs listens on `3000` inside Docker, and you expose it by `10080:3000`, but you still use `3000` for this value.
- **Application URL**: Use combination of **Domain** and **exposed HTTP Port** values(e.g. `http://192.168.99.100:10080/`).
- **Application URL**: Use combination of **Domain** and **exposed HTTP Port** values(e.g. `http://192.168.99.100:10080/`).
Full documentation of settings can be found [here](http://gogs.io/docs/advanced/configuration_cheat_sheet.html).
## Troubleshooting
## Upgrade
If you see the following error:
:exclamation::exclamation::exclamation:<span style="color: red">**Make sure you have volumed data to somewhere outside Docker container**</span>:exclamation::exclamation::exclamation:
```
checkVersion()] [E] Binary and template file version does not match
```
Steps to upgrade Gogs with Docker:
Run `rm -fr /var/gogs/gogs/templates/` should fix this it. Just remember to backup templates file if you have made modifications youself.
- `docker pull gogs/gogs`
- `docker stop gogs`
- `docker rm gogs`
- Finally, create container as the first time and don't forget to do same volume and port mapping.
## Known Issues
- [Use ctrl+c when clone through SSH makes Docker exit unexpectedly](https://github.com/gogits/gogs/issues/1499)
- `.dockerignore` seems to be ignored during Docker Hub Automated build

27
docker/build.sh Executable file
View File

@@ -0,0 +1,27 @@
#!/bin/sh
set -x
set -e
# Set temp environment vars
export GOPATH=/tmp/go
export PATH=${PATH}:${GOPATH}/bin
# Install build deps
apk -U --no-progress add linux-pam-dev go@community gcc musl-dev
# Init go environment to build Gogs
mkdir -p ${GOPATH}/src/github.com/gogits/
ln -s /app/gogs/ ${GOPATH}/src/github.com/gogits/gogs
cd ${GOPATH}/src/github.com/gogits/gogs
go get -v -tags "sqlite redis memcache cert pam"
go build -tags "sqlite redis memcache cert pam"
# Cleanup GOPATH
rm -r $GOPATH
# Remove build deps
apk --no-progress del linux-pam-dev go gcc musl-dev
# Create git user for Gogs
adduser -H -D -g 'Gogs Git User' git -h /data/git -s /bin/bash && passwd -u git
echo "export GOGS_CUSTOM=${GOGS_CUSTOM}" >> /etc/profile

5
docker/s6/.s6-svscan/finish Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/sh
# Cleanup SOCAT services and s6 event folder
rm -rf $(find /app/gogs/docker/s6/ -name 'event')
rm -rf /app/gogs/docker/s6/SOCAT_*

8
docker/s6/gogs/run Executable file
View File

@@ -0,0 +1,8 @@
#!/bin/sh
if test -f ./setup; then
source ./setup
fi
export USER=git
exec gosu $USER /app/gogs/gogs web

23
docker/s6/gogs/setup Executable file
View File

@@ -0,0 +1,23 @@
#!/bin/sh
if ! test -d ~git/.ssh; then
mkdir -p ~git/.ssh
chmod 700 ~git/.ssh
fi
if ! test -f ~git/.ssh/environment; then
echo "GOGS_CUSTOM=${GOGS_CUSTOM}" > ~git/.ssh/environment
chmod 600 ~git/.ssh/environment
fi
cd /app/gogs
# Link volumed data with app data
ln -sf /data/gogs/log ./log
ln -sf /data/gogs/data ./data
# Backward Compatibility with Gogs Container v0.6.15
ln -sf /data/git /home/git
chown -R git:git /data /app/gogs ~git/
chmod 0755 /data /data/gogs ~git/

7
docker/s6/openssh/run Executable file
View File

@@ -0,0 +1,7 @@
#!/bin/sh
if test -f ./setup; then
source ./setup
fi
exec gosu root /usr/sbin/sshd -D -f /app/gogs/docker/sshd_config

27
docker/s6/openssh/setup Executable file
View File

@@ -0,0 +1,27 @@
#!/bin/sh
# Check if host keys are present, else create them
if ! test -f /data/ssh/ssh_host_key; then
ssh-keygen -q -f /data/ssh/ssh_host_key -N '' -t rsa1
fi
if ! test -f /data/ssh/ssh_host_rsa_key; then
ssh-keygen -q -f /data/ssh/ssh_host_rsa_key -N '' -t rsa
fi
if ! test -f /data/ssh/ssh_host_dsa_key; then
ssh-keygen -q -f /data/ssh/ssh_host_dsa_key -N '' -t dsa
fi
if ! test -f /data/ssh/ssh_host_ecdsa_key; then
ssh-keygen -q -f /data/ssh/ssh_host_ecdsa_key -N '' -t ecdsa
fi
if ! test -f /data/ssh/ssh_host_ed25519_key; then
ssh-keygen -q -f /data/ssh/ssh_host_ed25519_key -N '' -t ed25519
fi
# Set correct right to ssh keys
chown -R root:root /data/ssh/*
chmod 0700 /data/ssh
chmod 0600 /data/ssh/*

7
docker/s6/syslogd/run Executable file
View File

@@ -0,0 +1,7 @@
#!/bin/sh
if test -f ./setup; then
source ./setup
fi
exec gosu root /sbin/syslogd -nS -O-

17
docker/sshd_config Normal file
View File

@@ -0,0 +1,17 @@
Port 22
AddressFamily any
ListenAddress 0.0.0.0
ListenAddress ::
Protocol 2
LogLevel INFO
HostKey /data/ssh/ssh_host_key
HostKey /data/ssh/ssh_host_rsa_key
HostKey /data/ssh/ssh_host_dsa_key
HostKey /data/ssh/ssh_host_ecdsa_key
HostKey /data/ssh/ssh_host_ed25519_key
PermitRootLogin no
AuthorizedKeysFile .ssh/authorized_keys
PasswordAuthentication no
UsePrivilegeSeparation no
PermitUserEnvironment yes
AllowUsers git

View File

@@ -1,47 +1,56 @@
#!/bin/bash -
#
#!/bin/sh
if ! test -d /data/gogs
then
mkdir -p /var/run/sshd
mkdir -p /data/gogs/data /data/gogs/conf /data/gogs/log /data/git
create_socat_links() {
# Bind linked docker container to localhost socket using socat
USED_PORT="3000:22"
while read NAME ADDR PORT; do
if test -z "$NAME$ADDR$PORT"; then
continue
elif echo $USED_PORT | grep -E "(^|:)$PORT($|:)" > /dev/null; then
echo "init:socat | Can't bind linked container ${NAME} to localhost, port ${PORT} already in use" 1>&2
else
SERV_FOLDER=/app/gogs/docker/s6/SOCAT_${NAME}_${PORT}
mkdir -p ${SERV_FOLDER}
CMD="socat -ls TCP4-LISTEN:${PORT},fork,reuseaddr TCP4:${ADDR}:${PORT}"
echo -e "#!/bin/sh\nexec $CMD" > ${SERV_FOLDER}/run
chmod +x ${SERV_FOLDER}/run
USED_PORT="${USED_PORT}:${PORT}"
echo "init:socat | Linked container ${NAME} will be binded to localhost on port ${PORT}" 1>&2
fi
done << EOT
$(env | sed -En 's|(.*)_PORT_([0-9]+)_TCP=tcp://(.*):([0-9]+)|\1 \3 \4|p')
EOT
}
cleanup() {
# Cleanup SOCAT services and s6 event folder
# On start and on shutdown in case container has been killed
rm -rf $(find /app/gogs/docker/s6/ -name 'event')
rm -rf /app/gogs/docker/s6/SOCAT_*
}
create_volume_subfolder() {
# Create VOLUME subfolder
for f in /data/gogs/data /data/gogs/conf /data/gogs/log /data/git /data/ssh; do
if ! test -d $f; then
mkdir -p $f
fi
done
}
cleanup
create_volume_subfolder
LINK=$(echo "$SOCAT_LINK" | tr '[:upper:]' '[:lower:]')
if [ "$LINK" = "false" -o "$LINK" = "0" ]; then
echo "init:socat | Will not try to create socat links as requested" 1>&2
else
create_socat_links
fi
if ! test -d /data/ssh
then
mkdir /data/ssh
ssh-keygen -q -f /data/ssh/ssh_host_key -N '' -t rsa1
ssh-keygen -q -f /data/ssh/ssh_host_rsa_key -N '' -t rsa
ssh-keygen -q -f /data/ssh/ssh_host_dsa_key -N '' -t dsa
ssh-keygen -q -f /data/ssh/ssh_host_ecdsa_key -N '' -t ecdsa
ssh-keygen -q -f /data/ssh/ssh_host_ed25519_key -N '' -t ed25519
chown -R root:root /data/ssh/*
chmod 600 /data/ssh/*
# Exec CMD or S6 by default if nothing present
if [ $# -gt 0 ];then
exec "$@"
else
exec /bin/s6-svscan /app/gogs/docker/s6/
fi
service ssh start
# sync templates
test -d /data/gogs/templates || cp -ar ./templates /data/gogs/
rsync -rtv /data/gogs/templates/ ./templates/
ln -sf /data/gogs/log ./log
ln -sf /data/gogs/data ./data
ln -sf /data/git /home/git
if ! test -d ~git/.ssh
then
mkdir ~git/.ssh
chmod 700 ~git/.ssh
fi
if ! test -f ~git/.ssh/environment
then
echo "GOGS_CUSTOM=/data/gogs" > ~git/.ssh/environment
chown git:git ~git/.ssh/environment
chown 600 ~git/.ssh/environment
fi
chown -R git:git /data .
exec su git -c "./gogs web"

View File

@@ -1,10 +1,10 @@
// +build go1.2
// +build go1.3
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Gogs(Go Git Service) is a painless self-hosted Git Service written in Go.
// Gogs (Go Git Service) is a painless self-hosted Git Service.
package main
import (
@@ -17,7 +17,7 @@ import (
"github.com/gogits/gogs/modules/setting"
)
const APP_VER = "0.6.9.0903 Beta"
const APP_VER = "0.7.33.1205 Beta"
func init() {
runtime.GOMAXPROCS(runtime.NumCPU())

View File

@@ -36,19 +36,19 @@ func accessLevel(e Engine, u *User, repo *Repository) (AccessMode, error) {
mode = ACCESS_MODE_READ
}
if u != nil {
if u.Id == repo.OwnerID {
return ACCESS_MODE_OWNER, nil
}
a := &Access{UserID: u.Id, RepoID: repo.ID}
if has, err := e.Get(a); !has || err != nil {
return mode, err
}
return a.Mode, nil
if u == nil {
return mode, nil
}
return mode, nil
if u.Id == repo.OwnerID {
return ACCESS_MODE_OWNER, nil
}
a := &Access{UserID: u.Id, RepoID: repo.ID}
if has, err := e.Get(a); !has || err != nil {
return mode, err
}
return a.Mode, nil
}
// AccessLevel returns the Access a user has to a repository. Will return NoneAccess if the
@@ -67,9 +67,8 @@ func HasAccess(u *User, repo *Repository, testMode AccessMode) (bool, error) {
return hasAccess(x, u, repo, testMode)
}
// GetAccessibleRepositories finds all repositories where a user has access to,
// besides he/she owns.
func (u *User) GetAccessibleRepositories() (map[*Repository]AccessMode, error) {
// GetRepositoryAccesses finds all repositories with their access mode where a user has access but does not own.
func (u *User) GetRepositoryAccesses() (map[*Repository]AccessMode, error) {
accesses := make([]*Access, 0, 10)
if err := x.Find(&accesses, &Access{UserID: u.Id}); err != nil {
return nil, err
@@ -80,7 +79,7 @@ func (u *User) GetAccessibleRepositories() (map[*Repository]AccessMode, error) {
repo, err := GetRepositoryByID(access.RepoID)
if err != nil {
if IsErrRepoNotExist(err) {
log.Error(4, "%v", err)
log.Error(4, "GetRepositoryByID: %v", err)
continue
}
return nil, err
@@ -92,11 +91,28 @@ func (u *User) GetAccessibleRepositories() (map[*Repository]AccessMode, error) {
}
repos[repo] = access.Mode
}
// FIXME: should we generate an ordered list here? Random looks weird.
return repos, nil
}
// GetAccessibleRepositories finds all repositories where a user has access but does not own.
func (u *User) GetAccessibleRepositories() ([]*Repository, error) {
accesses := make([]*Access, 0, 10)
if err := x.Find(&accesses, &Access{UserID: u.Id}); err != nil {
return nil, err
}
if len(accesses) == 0 {
return []*Repository{}, nil
}
repoIDs := make([]int64, 0, len(accesses))
for _, access := range accesses {
repoIDs = append(repoIDs, access.RepoID)
}
repos := make([]*Repository, 0, len(repoIDs))
return repos, x.Where("owner_id != ?", u.Id).In("id", repoIDs).Desc("updated").Find(&repos)
}
func maxAccessMode(modes ...AccessMode) AccessMode {
max := ACCESS_MODE_NONE
for _, mode := range modes {

View File

@@ -14,6 +14,7 @@ import (
"time"
"unicode"
"github.com/Unknwon/com"
"github.com/go-xorm/xorm"
api "github.com/gogits/go-gogs-client"
@@ -136,6 +137,26 @@ func (a Action) GetIssueInfos() []string {
return strings.SplitN(a.Content, "|", 2)
}
func (a Action) GetIssueTitle() string {
index := com.StrTo(a.GetIssueInfos()[0]).MustInt64()
issue, err := GetIssueByIndex(a.RepoID, index)
if err != nil {
log.Error(4, "GetIssueByIndex: %v", err)
return "500 when get issue"
}
return issue.Name
}
func (a Action) GetIssueContent() string {
index := com.StrTo(a.GetIssueInfos()[0]).MustInt64()
issue, err := GetIssueByIndex(a.RepoID, index)
if err != nil {
log.Error(4, "GetIssueByIndex: %v", err)
return "500 when get issue"
}
return issue.Content
}
func newRepoAction(e Engine, u *User, repo *Repository) (err error) {
if err = notifyWatchers(e, &Action{
ActUserID: u.Id,
@@ -147,7 +168,7 @@ func newRepoAction(e Engine, u *User, repo *Repository) (err error) {
RepoName: repo.Name,
IsPrivate: repo.IsPrivate,
}); err != nil {
return fmt.Errorf("notify watchers '%d/%s': %v", u.Id, repo.ID, err)
return fmt.Errorf("notify watchers '%d/%d': %v", u.Id, repo.ID, err)
}
log.Trace("action.newRepoAction: %s/%s", u.Name, repo.Name)
@@ -187,9 +208,52 @@ func issueIndexTrimRight(c rune) bool {
return !unicode.IsDigit(c)
}
type PushCommit struct {
Sha1 string
Message string
AuthorEmail string
AuthorName string
}
type PushCommits struct {
Len int
Commits []*PushCommit
CompareUrl string
avatars map[string]string
}
func NewPushCommits() *PushCommits {
return &PushCommits{
avatars: make(map[string]string),
}
}
// AvatarLink tries to match user in database with e-mail
// in order to show custom avatar, and falls back to general avatar link.
func (push *PushCommits) AvatarLink(email string) string {
_, ok := push.avatars[email]
if !ok {
u, err := GetUserByEmail(email)
if err != nil {
push.avatars[email] = base.AvatarLink(email)
if !IsErrUserNotExist(err) {
log.Error(4, "GetUserByEmail: %v", err)
}
} else {
push.avatars[email] = u.AvatarLink()
}
}
return push.avatars[email]
}
// updateIssuesCommit checks if issues are manipulated by commit message.
func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string, commits []*base.PushCommit) error {
for _, c := range commits {
func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string, commits []*PushCommit) error {
// Commits are appended in the reverse order.
for i := len(commits) - 1; i >= 0; i-- {
c := commits[i]
refMarked := make(map[int64]bool)
for _, ref := range IssueReferenceKeywordsPat.FindAllString(c.Message, -1) {
ref = ref[strings.IndexByte(ref, byte(' '))+1:]
@@ -210,6 +274,9 @@ func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string
issue, err := GetIssueByRef(ref)
if err != nil {
if IsErrIssueNotExist(err) {
continue
}
return err
}
@@ -220,7 +287,7 @@ func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string
url := fmt.Sprintf("%s/%s/%s/commit/%s", setting.AppSubUrl, repoUserName, repoName, c.Sha1)
message := fmt.Sprintf(`<a href="%s">%s</a>`, url, c.Message)
if _, err = CreateComment(u, repo, issue, 0, 0, COMMENT_TYPE_COMMIT_REF, message, nil); err != nil {
if err = CreateRefComment(u, repo, issue, message, c.Sha1); err != nil {
return err
}
}
@@ -246,6 +313,9 @@ func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string
issue, err := GetIssueByRef(ref)
if err != nil {
if IsErrIssueNotExist(err) {
continue
}
return err
}
@@ -283,6 +353,9 @@ func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string
issue, err := GetIssueByRef(ref)
if err != nil {
if IsErrIssueNotExist(err) {
continue
}
return err
}
@@ -310,7 +383,7 @@ func CommitRepoAction(
repoID int64,
repoUserName, repoName string,
refFullName string,
commit *base.PushCommits,
commit *PushCommits,
oldCommitID string, newCommitID string) error {
u, err := GetUserByID(userID)
@@ -325,12 +398,18 @@ func CommitRepoAction(
return fmt.Errorf("GetOwner: %v", err)
}
// Change repository bare status and update last updated time.
repo.IsBare = false
if err = UpdateRepository(repo, false); err != nil {
return fmt.Errorf("UpdateRepository: %v", err)
}
isNewBranch := false
opType := COMMIT_REPO
// Check it's tag push or branch.
if strings.HasPrefix(refFullName, "refs/tags/") {
opType = PUSH_TAG
commit = &base.PushCommits{}
commit = &PushCommits{}
} else {
// if not the first commit, set the compareUrl
if !strings.HasPrefix(oldCommitID, "0000000") {
@@ -339,17 +418,19 @@ func CommitRepoAction(
isNewBranch = true
}
// Change repository bare status and update last updated time.
repo.IsBare = false
if err = UpdateRepository(repo, false); err != nil {
return fmt.Errorf("UpdateRepository: %v", err)
// NOTE: limit to detect latest 100 commits.
if len(commit.Commits) > 100 {
commit.Commits = commit.Commits[len(commit.Commits)-100:]
}
if err = updateIssuesCommit(u, repo, repoUserName, repoName, commit.Commits); err != nil {
log.Debug("updateIssuesCommit: %v", err)
log.Error(4, "updateIssuesCommit: %v", err)
}
}
if len(commit.Commits) > setting.FeedMaxCommitNum {
commit.Commits = commit.Commits[:setting.FeedMaxCommitNum]
}
bs, err := json.Marshal(commit)
if err != nil {
return fmt.Errorf("Marshal: %v", err)
@@ -370,24 +451,9 @@ func CommitRepoAction(
IsPrivate: repo.IsPrivate,
}); err != nil {
return fmt.Errorf("NotifyWatchers: %v", err)
}
repoLink := fmt.Sprintf("%s%s/%s", setting.AppUrl, repoUserName, repoName)
payloadRepo := &api.PayloadRepo{
ID: repo.ID,
Name: repo.LowerName,
URL: repoLink,
Description: repo.Description,
Website: repo.Website,
Watchers: repo.NumWatches,
Owner: &api.PayloadAuthor{
Name: repo.Owner.DisplayName(),
Email: repo.Owner.Email,
UserName: repo.Owner.Name,
},
Private: repo.IsPrivate,
}
payloadRepo := repo.ComposePayload()
pusher_email, pusher_name := "", ""
pusher, err := GetUserByName(userName)
@@ -413,7 +479,7 @@ func CommitRepoAction(
commits[i] = &api.PayloadCommit{
ID: cmt.Sha1,
Message: cmt.Message,
URL: fmt.Sprintf("%s/commit/%s", repoLink, cmt.Sha1),
URL: fmt.Sprintf("%s/commit/%s", repo.RepoLink(), cmt.Sha1),
Author: &api.PayloadAuthor{
Name: cmt.AuthorName,
Email: cmt.AuthorEmail,
@@ -472,7 +538,7 @@ func transferRepoAction(e Engine, actUser, oldOwner, newOwner *User, repo *Repos
IsPrivate: repo.IsPrivate,
Content: path.Join(oldOwner.LowerName, repo.LowerName),
}); err != nil {
return fmt.Errorf("notify watchers '%d/%s': %v", actUser.Id, repo.ID, err)
return fmt.Errorf("notify watchers '%d/%d': %v", actUser.Id, repo.ID, err)
}
// Remove watch for organization.

View File

@@ -5,9 +5,12 @@
package models
import (
"strings"
"time"
"github.com/Unknwon/com"
"github.com/gogits/gogs/modules/base"
)
type NoticeType int
@@ -18,7 +21,7 @@ const (
// Notice represents a system notice for admin.
type Notice struct {
Id int64
ID int64 `xorm:"pk autoincr"`
Type NoticeType
Description string `xorm:"TEXT"`
Created time.Time `xorm:"CREATED"`
@@ -50,11 +53,10 @@ func CountNotices() int64 {
return count
}
// GetNotices returns given number of notices with offset.
func GetNotices(num, offset int) ([]*Notice, error) {
notices := make([]*Notice, 0, num)
err := x.Limit(num, offset).Desc("id").Find(&notices)
return notices, err
// Notices returns number of notices in given page.
func Notices(page, pageSize int) ([]*Notice, error) {
notices := make([]*Notice, 0, pageSize)
return notices, x.Limit(pageSize, (page-1)*pageSize).Desc("id").Find(&notices)
}
// DeleteNotice deletes a system notice by given ID.
@@ -62,3 +64,22 @@ func DeleteNotice(id int64) error {
_, err := x.Id(id).Delete(new(Notice))
return err
}
// DeleteNotices deletes all notices with ID from start to end (inclusive).
func DeleteNotices(start, end int64) error {
sess := x.Where("id >= ?", start)
if end > 0 {
sess.And("id <= ?", end)
}
_, err := sess.Delete(new(Notice))
return err
}
// DeleteNoticesByIDs deletes notices by given IDs.
func DeleteNoticesByIDs(ids []int64) error {
if len(ids) == 0 {
return nil
}
_, err := x.Where("id IN (" + strings.Join(base.Int64sToStrings(ids), ",") + ")").Delete(new(Notice))
return err
}

View File

@@ -15,7 +15,7 @@ import (
var c = cron.New()
func NewCronContext() {
func NewContext() {
var (
entry *cron.Entry
err error

View File

@@ -18,7 +18,7 @@ func IsErrNameReserved(err error) bool {
}
func (err ErrNameReserved) Error() string {
return fmt.Sprintf("name is reserved: [name: %s]", err.Name)
return fmt.Sprintf("name is reserved [name: %s]", err.Name)
}
type ErrNamePatternNotAllowed struct {
@@ -31,7 +31,7 @@ func IsErrNamePatternNotAllowed(err error) bool {
}
func (err ErrNamePatternNotAllowed) Error() string {
return fmt.Sprintf("name pattern is not allowed: [pattern: %s]", err.Pattern)
return fmt.Sprintf("name pattern is not allowed [pattern: %s]", err.Pattern)
}
// ____ ___
@@ -51,7 +51,7 @@ func IsErrUserAlreadyExist(err error) bool {
}
func (err ErrUserAlreadyExist) Error() string {
return fmt.Sprintf("user already exists: [name: %s]", err.Name)
return fmt.Sprintf("user already exists [name: %s]", err.Name)
}
type ErrUserNotExist struct {
@@ -65,7 +65,7 @@ func IsErrUserNotExist(err error) bool {
}
func (err ErrUserNotExist) Error() string {
return fmt.Sprintf("user does not exist: [uid: %d, name: %s]", err.UID, err.Name)
return fmt.Sprintf("user does not exist [uid: %d, name: %s]", err.UID, err.Name)
}
type ErrEmailAlreadyUsed struct {
@@ -78,7 +78,7 @@ func IsErrEmailAlreadyUsed(err error) bool {
}
func (err ErrEmailAlreadyUsed) Error() string {
return fmt.Sprintf("e-mail has been used: [email: %s]", err.Email)
return fmt.Sprintf("e-mail has been used [email: %s]", err.Email)
}
type ErrUserOwnRepos struct {
@@ -91,7 +91,7 @@ func IsErrUserOwnRepos(err error) bool {
}
func (err ErrUserOwnRepos) Error() string {
return fmt.Sprintf("user still has ownership of repositories: [uid: %d]", err.UID)
return fmt.Sprintf("user still has ownership of repositories [uid: %d]", err.UID)
}
type ErrUserHasOrgs struct {
@@ -104,7 +104,27 @@ func IsErrUserHasOrgs(err error) bool {
}
func (err ErrUserHasOrgs) Error() string {
return fmt.Sprintf("user still has membership of organizations: [uid: %d]", err.UID)
return fmt.Sprintf("user still has membership of organizations [uid: %d]", err.UID)
}
// __ __.__ __ .__
// / \ / \__| | _|__|
// \ \/\/ / | |/ / |
// \ /| | <| |
// \__/\ / |__|__|_ \__|
// \/ \/
type ErrWikiAlreadyExist struct {
Title string
}
func IsErrWikiAlreadyExist(err error) bool {
_, ok := err.(ErrWikiAlreadyExist)
return ok
}
func (err ErrWikiAlreadyExist) Error() string {
return fmt.Sprintf("wiki page already exists [title: %s]", err.Title)
}
// __________ ___. .__ .__ ____ __.
@@ -114,6 +134,19 @@ func (err ErrUserHasOrgs) Error() string {
// |____| |____/|___ /____/__|\___ > |____|__ \___ > ____|
// \/ \/ \/ \/\/
type ErrKeyUnableVerify struct {
Result string
}
func IsErrKeyUnableVerify(err error) bool {
_, ok := err.(ErrKeyUnableVerify)
return ok
}
func (err ErrKeyUnableVerify) Error() string {
return fmt.Sprintf("Unable to verify key content [result: %s]", err.Result)
}
type ErrKeyNotExist struct {
ID int64
}
@@ -124,7 +157,7 @@ func IsErrKeyNotExist(err error) bool {
}
func (err ErrKeyNotExist) Error() string {
return fmt.Sprintf("public key does not exist: [id: %d]", err.ID)
return fmt.Sprintf("public key does not exist [id: %d]", err.ID)
}
type ErrKeyAlreadyExist struct {
@@ -138,7 +171,7 @@ func IsErrKeyAlreadyExist(err error) bool {
}
func (err ErrKeyAlreadyExist) Error() string {
return fmt.Sprintf("public key already exists: [owner_id: %d, content: %s]", err.OwnerID, err.Content)
return fmt.Sprintf("public key already exists [owner_id: %d, content: %s]", err.OwnerID, err.Content)
}
type ErrKeyNameAlreadyUsed struct {
@@ -152,7 +185,38 @@ func IsErrKeyNameAlreadyUsed(err error) bool {
}
func (err ErrKeyNameAlreadyUsed) Error() string {
return fmt.Sprintf("public key already exists: [owner_id: %d, name: %s]", err.OwnerID, err.Name)
return fmt.Sprintf("public key already exists [owner_id: %d, name: %s]", err.OwnerID, err.Name)
}
type ErrKeyAccessDenied struct {
UserID int64
KeyID int64
Note string
}
func IsErrKeyAccessDenied(err error) bool {
_, ok := err.(ErrKeyAccessDenied)
return ok
}
func (err ErrKeyAccessDenied) Error() string {
return fmt.Sprintf("user does not have access to the key [user_id: %d, key_id: %d, note: %s]",
err.UserID, err.KeyID, err.Note)
}
type ErrDeployKeyNotExist struct {
ID int64
KeyID int64
RepoID int64
}
func IsErrDeployKeyNotExist(err error) bool {
_, ok := err.(ErrDeployKeyNotExist)
return ok
}
func (err ErrDeployKeyNotExist) Error() string {
return fmt.Sprintf("Deploy key does not exist [id: %d, key_id: %d, repo_id: %d]", err.ID, err.KeyID, err.RepoID)
}
type ErrDeployKeyAlreadyExist struct {
@@ -166,7 +230,7 @@ func IsErrDeployKeyAlreadyExist(err error) bool {
}
func (err ErrDeployKeyAlreadyExist) Error() string {
return fmt.Sprintf("public key already exists: [key_id: %d, repo_id: %d]", err.KeyID, err.RepoID)
return fmt.Sprintf("public key already exists [key_id: %d, repo_id: %d]", err.KeyID, err.RepoID)
}
type ErrDeployKeyNameAlreadyUsed struct {
@@ -180,7 +244,7 @@ func IsErrDeployKeyNameAlreadyUsed(err error) bool {
}
func (err ErrDeployKeyNameAlreadyUsed) Error() string {
return fmt.Sprintf("public key already exists: [repo_id: %d, name: %s]", err.RepoID, err.Name)
return fmt.Sprintf("public key already exists [repo_id: %d, name: %s]", err.RepoID, err.Name)
}
// _____ ___________ __
@@ -200,7 +264,7 @@ func IsErrAccessTokenNotExist(err error) bool {
}
func (err ErrAccessTokenNotExist) Error() string {
return fmt.Sprintf("access token does not exist: [sha: %s]", err.SHA)
return fmt.Sprintf("access token does not exist [sha: %s]", err.SHA)
}
// ________ .__ __ .__
@@ -220,7 +284,7 @@ func IsErrLastOrgOwner(err error) bool {
}
func (err ErrLastOrgOwner) Error() string {
return fmt.Sprintf("user is the last member of owner team: [uid: %d]", err.UID)
return fmt.Sprintf("user is the last member of owner team [uid: %d]", err.UID)
}
// __________ .__ __
@@ -259,6 +323,62 @@ func (err ErrRepoAlreadyExist) Error() string {
return fmt.Sprintf("repository already exists [uname: %s, name: %s]", err.Uname, err.Name)
}
type ErrInvalidCloneAddr struct {
IsURLError bool
IsInvalidPath bool
IsPermissionDenied bool
}
func IsErrInvalidCloneAddr(err error) bool {
_, ok := err.(ErrInvalidCloneAddr)
return ok
}
func (err ErrInvalidCloneAddr) Error() string {
return fmt.Sprintf("invalid clone address [is_url_error: %v, is_invalid_path: %v, is_permission_denied: %v]",
err.IsURLError, err.IsInvalidPath, err.IsPermissionDenied)
}
type ErrUpdateTaskNotExist struct {
UUID string
}
func IsErrUpdateTaskNotExist(err error) bool {
_, ok := err.(ErrUpdateTaskNotExist)
return ok
}
func (err ErrUpdateTaskNotExist) Error() string {
return fmt.Sprintf("update task does not exist [uuid: %s]", err.UUID)
}
type ErrReleaseAlreadyExist struct {
TagName string
}
func IsErrReleaseAlreadyExist(err error) bool {
_, ok := err.(ErrReleaseAlreadyExist)
return ok
}
func (err ErrReleaseAlreadyExist) Error() string {
return fmt.Sprintf("Release tag already exist [tag_name: %s]", err.TagName)
}
type ErrReleaseNotExist struct {
ID int64
TagName string
}
func IsErrReleaseNotExist(err error) bool {
_, ok := err.(ErrReleaseNotExist)
return ok
}
func (err ErrReleaseNotExist) Error() string {
return fmt.Sprintf("Release tag does not exist [id: %d, tag_name: %s]", err.ID, err.TagName)
}
// __ __ ___. .__ __
// / \ / \ ____\_ |__ | |__ ____ ____ | | __
// \ \/\/ // __ \| __ \| | \ / _ \ / _ \| |/ /
@@ -310,7 +430,7 @@ func (err ErrIssueNotExist) Error() string {
type ErrPullRequestNotExist struct {
ID int64
PullID int64
IssueID int64
HeadRepoID int64
BaseRepoID int64
HeadBarcnh string
@@ -323,8 +443,8 @@ func IsErrPullRequestNotExist(err error) bool {
}
func (err ErrPullRequestNotExist) Error() string {
return fmt.Sprintf("pull request does not exist [id: %d, pull_id: %d, head_repo_id: %d, base_repo_id: %d, head_branch: %s, base_branch: %s]",
err.ID, err.PullID, err.HeadRepoID, err.BaseRepoID, err.HeadBarcnh, err.BaseBranch)
return fmt.Sprintf("pull request does not exist [id: %d, issue_id: %d, head_repo_id: %d, base_repo_id: %d, head_branch: %s, base_branch: %s]",
err.ID, err.IssueID, err.HeadRepoID, err.BaseRepoID, err.HeadBarcnh, err.BaseBranch)
}
// _________ __
@@ -408,3 +528,23 @@ func IsErrAttachmentNotExist(err error) bool {
func (err ErrAttachmentNotExist) Error() string {
return fmt.Sprintf("attachment does not exist [id: %d, uuid: %s]", err.ID, err.UUID)
}
// _____ __ .__ __ .__ __ .__
// / _ \ __ ___/ |_| |__ ____ _____/ |_|__| ____ _____ _/ |_|__| ____ ____
// / /_\ \| | \ __\ | \_/ __ \ / \ __\ |/ ___\\__ \\ __\ |/ _ \ / \
// / | \ | /| | | Y \ ___/| | \ | | \ \___ / __ \| | | ( <_> ) | \
// \____|__ /____/ |__| |___| /\___ >___| /__| |__|\___ >____ /__| |__|\____/|___| /
// \/ \/ \/ \/ \/ \/ \/
type ErrAuthenticationNotExist struct {
ID int64
}
func IsErrAuthenticationNotExist(err error) bool {
_, ok := err.(ErrAuthenticationNotExist)
return ok
}
func (err ErrAuthenticationNotExist) Error() string {
return fmt.Sprintf("Authentication does not exist [id: %d]", err.ID)
}

View File

@@ -12,15 +12,14 @@ import (
"os"
"os/exec"
"strings"
"time"
"github.com/Unknwon/com"
"golang.org/x/net/html/charset"
"golang.org/x/text/transform"
"github.com/Unknwon/com"
"github.com/gogits/git-shell"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/git"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/process"
)
@@ -37,6 +36,7 @@ const (
DIFF_FILE_ADD = iota + 1
DIFF_FILE_CHANGE
DIFF_FILE_DEL
DIFF_FILE_RENAME
)
type DiffLine struct {
@@ -57,12 +57,14 @@ type DiffSection struct {
type DiffFile struct {
Name string
OldName string
Index int
Addition, Deletion int
Type int
IsCreated bool
IsDeleted bool
IsBin bool
IsRenamed bool
Sections []*DiffSection
}
@@ -77,39 +79,53 @@ func (diff *Diff) NumFiles() int {
const DIFF_HEAD = "diff --git "
func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff, error) {
scanner := bufio.NewScanner(reader)
func ParsePatch(maxlines int, reader io.Reader) (*Diff, error) {
var (
diff = &Diff{Files: make([]*DiffFile, 0)}
curFile *DiffFile
curSection = &DiffSection{
Lines: make([]*DiffLine, 0, 10),
}
leftLine, rightLine int
isTooLong bool
// FIXME: Should use cache in the future.
buf bytes.Buffer
lineCount int
)
diff := &Diff{Files: make([]*DiffFile, 0)}
var i int
for scanner.Scan() {
line := scanner.Text()
// fmt.Println(i, line)
input := bufio.NewReader(reader)
isEOF := false
for {
if isEOF {
break
}
line, err := input.ReadString('\n')
if err != nil {
if err == io.EOF {
isEOF = true
} else {
return nil, fmt.Errorf("ReadString: %v", err)
}
}
if len(line) > 0 && line[len(line)-1] == '\n' {
// Remove line break.
line = line[:len(line)-1]
}
if strings.HasPrefix(line, "+++ ") || strings.HasPrefix(line, "--- ") {
continue
}
if line == "" {
} else if len(line) == 0 {
continue
}
i = i + 1
lineCount++
// Diff data too large, we only show the first about maxlines lines
if i == maxlines {
isTooLong = true
if lineCount >= maxlines {
log.Warn("Diff data too large")
diff.Files = nil
return diff, nil
}
switch {
@@ -120,10 +136,6 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
curSection.Lines = append(curSection.Lines, diffLine)
continue
case line[0] == '@':
if isTooLong {
break
}
curSection = &DiffSection{}
curFile.Sections = append(curFile.Sections, curSection)
ss := strings.Split(line, "@@")
@@ -162,21 +174,27 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
// Get new file.
if strings.HasPrefix(line, DIFF_HEAD) {
if isTooLong {
break
middle := -1
// Note: In case file name is surrounded by double quotes (it happens only in git-shell).
// e.g. diff --git "a/xxx" "b/xxx"
hasQuote := line[len(DIFF_HEAD)] == '"'
if hasQuote {
middle = strings.Index(line, ` "b/`)
} else {
middle = strings.Index(line, " b/")
}
beg := len(DIFF_HEAD)
a := line[beg : (len(line)-beg)/2+beg]
// In case file name is surrounded by double quotes(it happens only in git-shell).
if a[0] == '"' {
a = a[1 : len(a)-1]
a = strings.Replace(a, `\"`, `"`, -1)
a := line[beg+2 : middle]
b := line[middle+3:]
if hasQuote {
a = string(git.UnescapeChars([]byte(a[1 : len(a)-1])))
b = string(git.UnescapeChars([]byte(b[1 : len(b)-1])))
}
curFile = &DiffFile{
Name: a[strings.Index(a, "/")+1:],
Name: a,
Index: len(diff.Files) + 1,
Type: DIFF_FILE_CHANGE,
Sections: make([]*DiffSection, 0, 10),
@@ -184,20 +202,30 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
diff.Files = append(diff.Files, curFile)
// Check file diff type.
for scanner.Scan() {
for {
line, err := input.ReadString('\n')
if err != nil {
if err == io.EOF {
isEOF = true
} else {
return nil, fmt.Errorf("ReadString: %v", err)
}
}
switch {
case strings.HasPrefix(scanner.Text(), "new file"):
case strings.HasPrefix(line, "new file"):
curFile.Type = DIFF_FILE_ADD
curFile.IsDeleted = false
curFile.IsCreated = true
case strings.HasPrefix(scanner.Text(), "deleted"):
case strings.HasPrefix(line, "deleted"):
curFile.Type = DIFF_FILE_DEL
curFile.IsCreated = false
curFile.IsDeleted = true
case strings.HasPrefix(scanner.Text(), "index"):
case strings.HasPrefix(line, "index"):
curFile.Type = DIFF_FILE_CHANGE
curFile.IsCreated = false
curFile.IsDeleted = false
case strings.HasPrefix(line, "similarity index 100%"):
curFile.Type = DIFF_FILE_RENAME
curFile.IsRenamed = true
curFile.OldName = curFile.Name
curFile.Name = b
}
if curFile.Type > 0 {
break
@@ -206,6 +234,8 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
}
}
// FIXME: detect encoding while parsing.
var buf bytes.Buffer
for _, f := range diff.Files {
buf.Reset()
for _, sec := range f.Sections {
@@ -232,61 +262,55 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
return diff, nil
}
func GetDiffRange(repoPath, beforeCommitId string, afterCommitId string, maxlines int) (*Diff, error) {
func GetDiffRange(repoPath, beforeCommitID string, afterCommitID string, maxlines int) (*Diff, error) {
repo, err := git.OpenRepository(repoPath)
if err != nil {
return nil, err
}
commit, err := repo.GetCommit(afterCommitId)
commit, err := repo.GetCommit(afterCommitID)
if err != nil {
return nil, err
}
rd, wr := io.Pipe()
var cmd *exec.Cmd
// if "after" commit given
if beforeCommitId == "" {
if len(beforeCommitID) == 0 {
// First commit of repository.
if commit.ParentCount() == 0 {
cmd = exec.Command("git", "show", afterCommitId)
cmd = exec.Command("git", "show", afterCommitID)
} else {
c, _ := commit.Parent(0)
cmd = exec.Command("git", "diff", c.Id.String(), afterCommitId)
cmd = exec.Command("git", "diff", "-M", c.ID.String(), afterCommitID)
}
} else {
cmd = exec.Command("git", "diff", beforeCommitId, afterCommitId)
cmd = exec.Command("git", "diff", "-M", beforeCommitID, afterCommitID)
}
cmd.Dir = repoPath
cmd.Stdout = wr
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
done := make(chan error)
go func() {
cmd.Start()
done <- cmd.Wait()
wr.Close()
}()
defer rd.Close()
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, fmt.Errorf("StdoutPipe: %v", err)
}
desc := fmt.Sprintf("GetDiffRange(%s)", repoPath)
pid := process.Add(desc, cmd)
go func() {
// In case process became zombie.
select {
case <-time.After(5 * time.Minute):
if errKill := process.Kill(pid); errKill != nil {
log.Error(4, "git_diff.ParsePatch(Kill): %v", err)
}
<-done
// return "", ErrExecTimeout.Error(), ErrExecTimeout
case err = <-done:
process.Remove(pid)
}
}()
if err = cmd.Start(); err != nil {
return nil, fmt.Errorf("Start: %v", err)
}
return ParsePatch(pid, maxlines, cmd, rd)
pid := process.Add(fmt.Sprintf("GetDiffRange (%s)", repoPath), cmd)
defer process.Remove(pid)
diff, err := ParsePatch(maxlines, stdout)
if err != nil {
return nil, fmt.Errorf("ParsePatch: %v", err)
}
if err = cmd.Wait(); err != nil {
return nil, fmt.Errorf("Wait: %v", err)
}
return diff, nil
}
func GetDiffCommit(repoPath, commitId string, maxlines int) (*Diff, error) {

View File

@@ -9,7 +9,6 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"os"
"path"
@@ -20,9 +19,7 @@ import (
"github.com/go-xorm/xorm"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/git"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/process"
"github.com/gogits/gogs/modules/setting"
gouuid "github.com/gogits/gogs/modules/uuid"
)
@@ -95,15 +92,6 @@ func (i *Issue) AfterSet(colName string, _ xorm.Cell) {
if err != nil {
log.Error(3, "GetUserByID[%d]: %v", i.ID, err)
}
case "is_pull":
if !i.IsPull {
return
}
i.PullRequest, err = GetPullRequestByPullID(i.ID)
if err != nil {
log.Error(3, "GetPullRequestByPullID[%d]: %v", i.ID, err)
}
case "created":
i.Created = regulateTimeZone(i.Created)
}
@@ -236,7 +224,7 @@ func (i *Issue) changeStatus(e *xorm.Session, doer *User, isClosed bool) (err er
}
i.IsClosed = isClosed
if err = updateIssue(e, i); err != nil {
if err = updateIssueCols(e, i, "is_closed"); err != nil {
return err
} else if err = updateIssueUsersByStatus(e, i.ID, isClosed); err != nil {
return err
@@ -285,6 +273,15 @@ func (i *Issue) ChangeStatus(doer *User, isClosed bool) (err error) {
return sess.Commit()
}
func (i *Issue) GetPullRequest() (err error) {
if i.PullRequest != nil {
return nil
}
i.PullRequest, err = GetPullRequestByIssueID(i.ID)
return err
}
// It's caller's responsibility to create action.
func newIssue(e *xorm.Session, repo *Repository, issue *Issue, labelIDs []int64, uuids []string, isPull bool) (err error) {
if _, err = e.Insert(issue); err != nil {
@@ -546,7 +543,7 @@ func GetIssueCountByPoster(uid, rid int64, isClosed bool) int64 {
// IssueUser represents an issue-user relation.
type IssueUser struct {
ID int64 `xorm:"pk autoincr"`
UID int64 `xorm:"uid INDEX"` // User ID.
UID int64 `xorm:"INDEX"` // User ID.
IssueID int64
RepoID int64 `xorm:"INDEX"`
MilestoneID int64
@@ -721,32 +718,28 @@ func GetIssueStats(opts *IssueStatsOptions) *IssueStats {
if opts.AssigneeID > 0 {
baseCond += " AND assignee_id=" + com.ToStr(opts.AssigneeID)
}
if opts.IsPull {
baseCond += " AND issue.is_pull=1"
} else {
baseCond += " AND issue.is_pull=0"
}
baseCond += " AND issue.is_pull=?"
switch opts.FilterMode {
case FM_ALL, FM_ASSIGN:
results, _ := x.Query(queryStr+baseCond, false)
results, _ := x.Query(queryStr+baseCond, false, opts.IsPull)
stats.OpenCount = parseCountResult(results)
results, _ = x.Query(queryStr+baseCond, true)
results, _ = x.Query(queryStr+baseCond, true, opts.IsPull)
stats.ClosedCount = parseCountResult(results)
case FM_CREATE:
baseCond += " AND poster_id=?"
results, _ := x.Query(queryStr+baseCond, false, opts.UserID)
results, _ := x.Query(queryStr+baseCond, false, opts.IsPull, opts.UserID)
stats.OpenCount = parseCountResult(results)
results, _ = x.Query(queryStr+baseCond, true, opts.UserID)
results, _ = x.Query(queryStr+baseCond, true, opts.IsPull, opts.UserID)
stats.ClosedCount = parseCountResult(results)
case FM_MENTION:
queryStr += " INNER JOIN `issue_user` ON `issue`.id=`issue_user`.issue_id"
baseCond += " AND `issue_user`.uid=? AND `issue_user`.is_mentioned=?"
results, _ := x.Query(queryStr+baseCond, false, opts.UserID, true)
results, _ := x.Query(queryStr+baseCond, false, opts.IsPull, opts.UserID, true)
stats.OpenCount = parseCountResult(results)
results, _ = x.Query(queryStr+baseCond, true, opts.UserID, true)
results, _ = x.Query(queryStr+baseCond, true, opts.IsPull, opts.UserID, true)
stats.ClosedCount = parseCountResult(results)
}
return stats
@@ -758,7 +751,7 @@ func GetUserIssueStats(repoID, uid int64, repoIDs []int64, filterMode int, isPul
queryStr := "SELECT COUNT(*) FROM `issue` "
baseCond := " WHERE issue.is_closed=?"
if repoID > 0 {
if repoID > 0 || len(repoIDs) == 0 {
baseCond += " AND issue.repo_id=" + com.ToStr(repoID)
} else {
baseCond += " AND issue.repo_id IN (" + strings.Join(base.Int64sToStrings(repoIDs), ",") + ")"
@@ -821,11 +814,17 @@ func updateIssue(e Engine, issue *Issue) error {
return err
}
// UpdateIssue updates information of issue.
// UpdateIssue updates all fields of given issue.
func UpdateIssue(issue *Issue) error {
return updateIssue(x, issue)
}
// updateIssueCols updates specific fields of given issue.
func updateIssueCols(e Engine, issue *Issue, cols ...string) error {
_, err := e.Id(issue.ID).Cols(cols...).Update(issue)
return err
}
func updateIssueUsersByStatus(e Engine, issueID int64, isClosed bool) error {
_, err := e.Exec("UPDATE `issue_user` SET is_closed=? WHERE issue_id=?", isClosed, issueID)
return err
@@ -894,233 +893,6 @@ func UpdateIssueUsersByMentions(uids []int64, iid int64) error {
return nil
}
// __________ .__ .__ __________ __
// \______ \__ __| | | |\______ \ ____ ________ __ ____ _______/ |_
// | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\
// | | | | / |_| |_| | \ ___< <_| | | /\ ___/ \___ \ | |
// |____| |____/|____/____/____|_ /\___ >__ |____/ \___ >____ > |__|
// \/ \/ |__| \/ \/
type PullRequestType int
const (
PULL_REQUEST_GOGS = iota
PLLL_ERQUEST_GIT
)
// PullRequest represents relation between pull request and repositories.
type PullRequest struct {
ID int64 `xorm:"pk autoincr"`
PullID int64 `xorm:"INDEX"`
Pull *Issue `xorm:"-"`
PullIndex int64
HeadRepoID int64
HeadRepo *Repository `xorm:"-"`
BaseRepoID int64
HeadUserName string
HeadBarcnh string
BaseBranch string
MergeBase string `xorm:"VARCHAR(40)"`
MergedCommitID string `xorm:"VARCHAR(40)"`
Type PullRequestType
CanAutoMerge bool
HasMerged bool
Merged time.Time
MergerID int64
Merger *User `xorm:"-"`
}
func (pr *PullRequest) AfterSet(colName string, _ xorm.Cell) {
var err error
switch colName {
case "head_repo_id":
pr.HeadRepo, err = GetRepositoryByID(pr.HeadRepoID)
if err != nil {
log.Error(3, "GetRepositoryByID[%d]: %v", pr.ID, err)
}
case "merger_id":
if !pr.HasMerged {
return
}
pr.Merger, err = GetUserByID(pr.MergerID)
if err != nil {
if IsErrUserNotExist(err) {
pr.MergerID = -1
pr.Merger = NewFakeUser()
} else {
log.Error(3, "GetUserByID[%d]: %v", pr.ID, err)
}
}
case "merged":
if !pr.HasMerged {
return
}
pr.Merged = regulateTimeZone(pr.Merged)
}
}
// Merge merges pull request to base repository.
func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error) {
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if err = pr.Pull.changeStatus(sess, doer, true); err != nil {
return fmt.Errorf("Pull.changeStatus: %v", err)
}
headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name)
headGitRepo, err := git.OpenRepository(headRepoPath)
if err != nil {
return fmt.Errorf("OpenRepository: %v", err)
}
pr.MergedCommitID, err = headGitRepo.GetCommitIdOfBranch(pr.HeadBarcnh)
if err != nil {
return fmt.Errorf("GetCommitIdOfBranch: %v", err)
}
if err = mergePullRequestAction(sess, doer, pr.Pull.Repo, pr.Pull); err != nil {
return fmt.Errorf("mergePullRequestAction: %v", err)
}
pr.HasMerged = true
pr.Merged = time.Now()
pr.MergerID = doer.Id
if _, err = sess.Id(pr.ID).AllCols().Update(pr); err != nil {
return fmt.Errorf("update pull request: %v", err)
}
// Clone base repo.
tmpBasePath := path.Join("data/tmp/repos", com.ToStr(time.Now().Nanosecond())+".git")
os.MkdirAll(path.Dir(tmpBasePath), os.ModePerm)
defer os.RemoveAll(path.Dir(tmpBasePath))
var stderr string
if _, stderr, err = process.ExecTimeout(5*time.Minute,
fmt.Sprintf("PullRequest.Merge(git clone): %s", tmpBasePath),
"git", "clone", baseGitRepo.Path, tmpBasePath); err != nil {
return fmt.Errorf("git clone: %s", stderr)
}
// Check out base branch.
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge(git checkout): %s", tmpBasePath),
"git", "checkout", pr.BaseBranch); err != nil {
return fmt.Errorf("git checkout: %s", stderr)
}
// Pull commits.
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge(git pull): %s", tmpBasePath),
"git", "pull", headRepoPath, pr.HeadBarcnh); err != nil {
return fmt.Errorf("git pull: %s", stderr)
}
// Push back to upstream.
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge(git push): %s", tmpBasePath),
"git", "push", baseGitRepo.Path, pr.BaseBranch); err != nil {
return fmt.Errorf("git push: %s", stderr)
}
return sess.Commit()
}
// NewPullRequest creates new pull request with labels for repository.
func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []string, pr *PullRequest, patch []byte) (err error) {
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if err = newIssue(sess, repo, pull, labelIDs, uuids, true); err != nil {
return fmt.Errorf("newIssue: %v", err)
}
// Notify watchers.
act := &Action{
ActUserID: pull.Poster.Id,
ActUserName: pull.Poster.Name,
ActEmail: pull.Poster.Email,
OpType: CREATE_PULL_REQUEST,
Content: fmt.Sprintf("%d|%s", pull.Index, pull.Name),
RepoID: repo.ID,
RepoUserName: repo.Owner.Name,
RepoName: repo.Name,
IsPrivate: repo.IsPrivate,
}
if err = notifyWatchers(sess, act); err != nil {
return err
}
// Test apply patch.
repoPath, err := repo.RepoPath()
if err != nil {
return fmt.Errorf("RepoPath: %v", err)
}
patchPath := path.Join(repoPath, "pulls", com.ToStr(pr.ID)+".patch")
os.MkdirAll(path.Dir(patchPath), os.ModePerm)
if err = ioutil.WriteFile(patchPath, patch, 0644); err != nil {
return fmt.Errorf("save patch: %v", err)
}
defer os.Remove(patchPath)
stdout, stderr, err := process.ExecDir(-1, repoPath,
fmt.Sprintf("NewPullRequest(git apply --check): %d", repo.ID),
"git", "apply", "--check", "-v", patchPath)
if err != nil {
if strings.Contains(stderr, "fatal:") {
return fmt.Errorf("git apply --check: %v - %s", err, stderr)
}
}
pr.CanAutoMerge = !strings.Contains(stdout, "error: patch failed:")
pr.PullID = pull.ID
pr.PullIndex = pull.Index
if _, err = sess.Insert(pr); err != nil {
return fmt.Errorf("insert pull repo: %v", err)
}
return sess.Commit()
}
// GetUnmergedPullRequest returnss a pull request hasn't been merged by given info.
func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch string) (*PullRequest, error) {
pr := &PullRequest{
HeadRepoID: headRepoID,
BaseRepoID: baseRepoID,
HeadBarcnh: headBranch,
BaseBranch: baseBranch,
}
has, err := x.Where("has_merged=?", false).Get(pr)
if err != nil {
return nil, err
} else if !has {
return nil, ErrPullRequestNotExist{0, 0, headRepoID, baseRepoID, headBranch, baseBranch}
}
return pr, nil
}
// GetPullRequestByPullID returns pull repo by given pull ID.
func GetPullRequestByPullID(pullID int64) (*PullRequest, error) {
pr := new(PullRequest)
has, err := x.Where("pull_id=?", pullID).Get(pr)
if err != nil {
return nil, err
} else if !has {
return nil, ErrPullRequestNotExist{0, pullID, 0, 0, "", ""}
}
return pr, nil
}
// .____ ___. .__
// | | _____ \_ |__ ____ | |
// | | \__ \ | __ \_/ __ \| |
@@ -1525,7 +1297,7 @@ func changeMilestoneIssueStats(e *xorm.Session, issue *Issue) error {
}
// ChangeMilestoneIssueStats updates the open/closed issues counter and progress
// for the milestone associated witht the given issue.
// for the milestone associated with the given issue.
func ChangeMilestoneIssueStats(issue *Issue) (err error) {
sess := x.NewSession()
defer sessionRelease(sess)
@@ -1599,8 +1371,8 @@ func ChangeMilestoneAssign(oldMid int64, issue *Issue) (err error) {
}
// DeleteMilestoneByID deletes a milestone by given ID.
func DeleteMilestoneByID(mid int64) error {
m, err := GetMilestoneByID(mid)
func DeleteMilestoneByID(id int64) error {
m, err := GetMilestoneByID(id)
if err != nil {
if IsErrMilestoneNotExist(err) {
return nil
@@ -1619,7 +1391,7 @@ func DeleteMilestoneByID(mid int64) error {
return err
}
if _, err = sess.Id(m.ID).Delete(m); err != nil {
if _, err = sess.Id(m.ID).Delete(new(Milestone)); err != nil {
return err
}
@@ -1685,6 +1457,9 @@ type Comment struct {
RenderedContent string `xorm:"-"`
Created time.Time `xorm:"CREATED"`
// Reference issue in commit message
CommitSHA string `xorm:"VARCHAR(40)"`
Attachments []*Attachment `xorm:"-"`
// For view issue page.
@@ -1733,14 +1508,15 @@ func (c *Comment) EventTag() string {
return "event-" + com.ToStr(c.ID)
}
func createComment(e *xorm.Session, u *User, repo *Repository, issue *Issue, commitID, line int64, cmtType CommentType, content string, uuids []string) (_ *Comment, err error) {
func createComment(e *xorm.Session, u *User, repo *Repository, issue *Issue, commitID, line int64, cmtType CommentType, content, commitSHA string, uuids []string) (_ *Comment, err error) {
comment := &Comment{
PosterID: u.Id,
Type: cmtType,
IssueID: issue.ID,
CommitID: commitID,
Line: line,
Content: content,
PosterID: u.Id,
Type: cmtType,
IssueID: issue.ID,
CommitID: commitID,
Line: line,
Content: content,
CommitSHA: commitSHA,
}
if _, err = e.Insert(comment); err != nil {
return nil, err
@@ -1819,18 +1595,18 @@ func createStatusComment(e *xorm.Session, doer *User, repo *Repository, issue *I
if !issue.IsClosed {
cmtType = COMMENT_TYPE_REOPEN
}
return createComment(e, doer, repo, issue, 0, 0, cmtType, "", nil)
return createComment(e, doer, repo, issue, 0, 0, cmtType, "", "", nil)
}
// CreateComment creates comment of issue or commit.
func CreateComment(doer *User, repo *Repository, issue *Issue, commitID, line int64, cmtType CommentType, content string, attachments []string) (comment *Comment, err error) {
func CreateComment(doer *User, repo *Repository, issue *Issue, commitID, line int64, cmtType CommentType, content, commitSHA string, attachments []string) (comment *Comment, err error) {
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return nil, err
}
comment, err = createComment(sess, doer, repo, issue, commitID, line, cmtType, content, attachments)
comment, err = createComment(sess, doer, repo, issue, commitID, line, cmtType, content, commitSHA, attachments)
if err != nil {
return nil, err
}
@@ -1840,7 +1616,29 @@ func CreateComment(doer *User, repo *Repository, issue *Issue, commitID, line in
// CreateIssueComment creates a plain issue comment.
func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content string, attachments []string) (*Comment, error) {
return CreateComment(doer, repo, issue, 0, 0, COMMENT_TYPE_COMMENT, content, attachments)
return CreateComment(doer, repo, issue, 0, 0, COMMENT_TYPE_COMMENT, content, "", attachments)
}
// CreateRefComment creates a commit reference comment to issue.
func CreateRefComment(doer *User, repo *Repository, issue *Issue, content, commitSHA string) error {
if len(commitSHA) == 0 {
return fmt.Errorf("cannot create reference with empty commit SHA")
}
// Check if same reference from same commit has already existed.
has, err := x.Get(&Comment{
Type: COMMENT_TYPE_COMMIT_REF,
IssueID: issue.ID,
CommitSHA: commitSHA,
})
if err != nil {
return fmt.Errorf("check reference comment: %v", err)
} else if has {
return nil
}
_, err = CreateComment(doer, repo, issue, 0, 0, COMMENT_TYPE_COMMIT_REF, content, commitSHA, nil)
return err
}
// GetCommentByID returns the comment by given ID.

View File

@@ -13,6 +13,7 @@ import (
"strings"
"time"
"github.com/Unknwon/com"
"github.com/go-xorm/core"
"github.com/go-xorm/xorm"
@@ -23,24 +24,26 @@ import (
type LoginType int
// Note: new type must be added at the end of list to maintain compatibility.
const (
NOTYPE LoginType = iota
PLAIN
LDAP
SMTP
PAM
DLDAP
)
var (
ErrAuthenticationAlreadyExist = errors.New("Authentication already exist")
ErrAuthenticationNotExist = errors.New("Authentication does not exist")
ErrAuthenticationUserUsed = errors.New("Authentication has been used by some users")
)
var LoginTypes = map[LoginType]string{
LDAP: "LDAP",
SMTP: "SMTP",
PAM: "PAM",
var LoginNames = map[LoginType]string{
LDAP: "LDAP (via BindDN)",
DLDAP: "LDAP (simple auth)",
SMTP: "SMTP",
PAM: "PAM",
}
// Ensure structs implemented interface.
@@ -51,23 +54,24 @@ var (
)
type LDAPConfig struct {
ldap.Ldapsource
*ldap.Source
}
func (cfg *LDAPConfig) FromDB(bs []byte) error {
return json.Unmarshal(bs, &cfg.Ldapsource)
return json.Unmarshal(bs, &cfg)
}
func (cfg *LDAPConfig) ToDB() ([]byte, error) {
return json.Marshal(cfg.Ldapsource)
return json.Marshal(cfg)
}
type SMTPConfig struct {
Auth string
Host string
Port int
TLS bool
SkipVerify bool
Auth string
Host string
Port int
AllowedDomains string `xorm:"TEXT"`
TLS bool
SkipVerify bool
}
func (cfg *SMTPConfig) FromDB(bs []byte) error {
@@ -91,32 +95,71 @@ func (cfg *PAMConfig) ToDB() ([]byte, error) {
}
type LoginSource struct {
ID int64 `xorm:"pk autoincr"`
Type LoginType
Name string `xorm:"UNIQUE"`
IsActived bool `xorm:"NOT NULL DEFAULT false"`
Cfg core.Conversion `xorm:"TEXT"`
AllowAutoRegister bool `xorm:"NOT NULL DEFAULT false"`
Created time.Time `xorm:"CREATED"`
Updated time.Time `xorm:"UPDATED"`
ID int64 `xorm:"pk autoincr"`
Type LoginType
Name string `xorm:"UNIQUE"`
IsActived bool `xorm:"NOT NULL DEFAULT false"`
Cfg core.Conversion `xorm:"TEXT"`
Created time.Time `xorm:"CREATED"`
Updated time.Time `xorm:"UPDATED"`
}
func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) {
switch colName {
case "type":
switch LoginType((*val).(int64)) {
case LDAP:
case LDAP, DLDAP:
source.Cfg = new(LDAPConfig)
case SMTP:
source.Cfg = new(SMTPConfig)
case PAM:
source.Cfg = new(PAMConfig)
default:
panic("unrecognized login source type: " + com.ToStr(*val))
}
}
}
func (source *LoginSource) TypeString() string {
return LoginTypes[source.Type]
func (source *LoginSource) TypeName() string {
return LoginNames[source.Type]
}
func (source *LoginSource) IsLDAP() bool {
return source.Type == LDAP
}
func (source *LoginSource) IsDLDAP() bool {
return source.Type == DLDAP
}
func (source *LoginSource) IsSMTP() bool {
return source.Type == SMTP
}
func (source *LoginSource) IsPAM() bool {
return source.Type == PAM
}
func (source *LoginSource) UseTLS() bool {
switch source.Type {
case LDAP, DLDAP:
return source.LDAP().UseSSL
case SMTP:
return source.SMTP().TLS
}
return false
}
func (source *LoginSource) SkipVerify() bool {
switch source.Type {
case LDAP, DLDAP:
return source.LDAP().SkipVerify
case SMTP:
return source.SMTP().SkipVerify
}
return false
}
func (source *LoginSource) LDAP() *LDAPConfig {
@@ -131,23 +174,30 @@ func (source *LoginSource) PAM() *PAMConfig {
return source.Cfg.(*PAMConfig)
}
// CountLoginSources returns number of login sources.
func CountLoginSources() int64 {
count, _ := x.Count(new(LoginSource))
return count
}
func CreateSource(source *LoginSource) error {
_, err := x.Insert(source)
return err
}
func GetAuths() ([]*LoginSource, error) {
func LoginSources() ([]*LoginSource, error) {
auths := make([]*LoginSource, 0, 5)
return auths, x.Find(&auths)
}
// GetLoginSourceByID returns login source by given ID.
func GetLoginSourceByID(id int64) (*LoginSource, error) {
source := new(LoginSource)
has, err := x.Id(id).Get(source)
if err != nil {
return nil, err
} else if !has {
return nil, ErrAuthenticationNotExist
return nil, ErrAuthenticationNotExist{id}
}
return source, nil
}
@@ -157,110 +207,34 @@ func UpdateSource(source *LoginSource) error {
return err
}
func DelLoginSource(source *LoginSource) error {
cnt, err := x.Count(&User{LoginSource: source.ID})
func DeleteSource(source *LoginSource) error {
count, err := x.Count(&User{LoginSource: source.ID})
if err != nil {
return err
}
if cnt > 0 {
} else if count > 0 {
return ErrAuthenticationUserUsed
}
_, err = x.Id(source.ID).Delete(&LoginSource{})
_, err = x.Id(source.ID).Delete(new(LoginSource))
return err
}
// UserSignIn validates user name and password.
func UserSignIn(uname, passwd string) (*User, error) {
u := new(User)
if strings.Contains(uname, "@") {
u = &User{Email: uname}
} else {
u = &User{LowerName: strings.ToLower(uname)}
}
// .____ ________ _____ __________
// | | \______ \ / _ \\______ \
// | | | | \ / /_\ \| ___/
// | |___ | ` \/ | \ |
// |_______ \/_______ /\____|__ /____|
// \/ \/ \/
has, err := x.Get(u)
if err != nil {
return nil, err
}
if u.LoginType == NOTYPE && has {
u.LoginType = PLAIN
}
// For plain login, user must exist to reach this line.
// Now verify password.
if u.LoginType == PLAIN {
if !u.ValidatePassword(passwd) {
return nil, ErrUserNotExist{u.Id, u.Name}
}
return u, nil
}
if !has {
var sources []LoginSource
if err = x.UseBool().Find(&sources,
&LoginSource{IsActived: true, AllowAutoRegister: true}); err != nil {
return nil, err
}
for _, source := range sources {
if source.Type == LDAP {
u, err := LoginUserLdapSource(nil, uname, passwd,
source.ID, source.Cfg.(*LDAPConfig), true)
if err == nil {
return u, nil
}
log.Warn("Fail to login(%s) by LDAP(%s): %v", uname, source.Name, err)
} else if source.Type == SMTP {
u, err := LoginUserSMTPSource(nil, uname, passwd,
source.ID, source.Cfg.(*SMTPConfig), true)
if err == nil {
return u, nil
}
log.Warn("Fail to login(%s) by SMTP(%s): %v", uname, source.Name, err)
} else if source.Type == PAM {
u, err := LoginUserPAMSource(nil, uname, passwd,
source.ID, source.Cfg.(*PAMConfig), true)
if err == nil {
return u, nil
}
log.Warn("Fail to login(%s) by PAM(%s): %v", uname, source.Name, err)
}
}
return nil, ErrUserNotExist{u.Id, u.Name}
}
var source LoginSource
hasSource, err := x.Id(u.LoginSource).Get(&source)
if err != nil {
return nil, err
} else if !hasSource {
return nil, ErrLoginSourceNotExist
} else if !source.IsActived {
return nil, ErrLoginSourceNotActived
}
switch u.LoginType {
case LDAP:
return LoginUserLdapSource(u, u.LoginName, passwd, source.ID, source.Cfg.(*LDAPConfig), false)
case SMTP:
return LoginUserSMTPSource(u, u.LoginName, passwd, source.ID, source.Cfg.(*SMTPConfig), false)
case PAM:
return LoginUserPAMSource(u, u.LoginName, passwd, source.ID, source.Cfg.(*PAMConfig), false)
}
return nil, ErrUnsupportedLoginType
}
// Query if name/passwd can login against the LDAP directory pool
// Create a local user if success
// Return the same LoginUserPlain semantic
// FIXME: https://github.com/gogits/gogs/issues/672
func LoginUserLdapSource(u *User, name, passwd string, sourceId int64, cfg *LDAPConfig, autoRegister bool) (*User, error) {
fn, sn, mail, admin, logged := cfg.Ldapsource.SearchEntry(name, passwd)
// LoginUserLDAPSource queries if loginName/passwd can login against the LDAP directory pool,
// and create a local user if success when enabled.
// It returns the same LoginUserPlain semantic.
func LoginUserLDAPSource(u *User, loginName, passwd string, source *LoginSource, autoRegister bool) (*User, error) {
cfg := source.Cfg.(*LDAPConfig)
directBind := (source.Type == DLDAP)
name, fn, sn, mail, admin, logged := cfg.SearchEntry(loginName, passwd, directBind)
if !logged {
// User not in LDAP, do nothing
return nil, ErrUserNotExist{0, name}
return nil, ErrUserNotExist{0, loginName}
}
if !autoRegister {
@@ -268,6 +242,9 @@ func LoginUserLdapSource(u *User, name, passwd string, sourceId int64, cfg *LDAP
}
// Fallback.
if len(name) == 0 {
name = loginName
}
if len(mail) == 0 {
mail = fmt.Sprintf("%s@localhost", name)
}
@@ -275,11 +252,10 @@ func LoginUserLdapSource(u *User, name, passwd string, sourceId int64, cfg *LDAP
u = &User{
LowerName: strings.ToLower(name),
Name: name,
FullName: fn + " " + sn,
LoginType: LDAP,
LoginSource: sourceId,
LoginName: name,
Passwd: passwd,
FullName: composeFullName(fn, sn, name),
LoginType: source.Type,
LoginSource: source.ID,
LoginName: loginName,
Email: mail,
IsAdmin: admin,
IsActive: true,
@@ -287,6 +263,26 @@ func LoginUserLdapSource(u *User, name, passwd string, sourceId int64, cfg *LDAP
return u, CreateUser(u)
}
func composeFullName(firstName, surename, userName string) string {
switch {
case len(firstName) == 0 && len(surename) == 0:
return userName
case len(firstName) == 0:
return surename
case len(surename) == 0:
return firstName
default:
return firstName + " " + surename
}
}
// _________ __________________________
// / _____/ / \__ ___/\______ \
// \_____ \ / \ / \| | | ___/
// / \/ Y \ | | |
// /_______ /\____|__ /____| |____|
// \/ \/
type loginAuth struct {
username, password string
}
@@ -316,9 +312,7 @@ const (
SMTP_LOGIN = "LOGIN"
)
var (
SMTPAuths = []string{SMTP_PLAIN, SMTP_LOGIN}
)
var SMTPAuths = []string{SMTP_PLAIN, SMTP_LOGIN}
func SMTPAuth(a smtp.Auth, cfg *SMTPConfig) error {
c, err := smtp.Dial(fmt.Sprintf("%s:%d", cfg.Host, cfg.Port))
@@ -357,6 +351,16 @@ func SMTPAuth(a smtp.Auth, cfg *SMTPConfig) error {
// Create a local user if success
// Return the same LoginUserPlain semantic
func LoginUserSMTPSource(u *User, name, passwd string, sourceId int64, cfg *SMTPConfig, autoRegister bool) (*User, error) {
// Verify allowed domains.
if len(cfg.AllowedDomains) > 0 {
idx := strings.Index(name, "@")
if idx == -1 {
return nil, ErrUserNotExist{0, name}
} else if !com.IsSliceContainsStr(strings.Split(cfg.AllowedDomains, ","), name[idx+1:]) {
return nil, ErrUserNotExist{0, name}
}
}
var auth smtp.Auth
if cfg.Auth == SMTP_PLAIN {
auth = smtp.PlainAuth("", name, passwd, cfg.Host)
@@ -368,7 +372,7 @@ func LoginUserSMTPSource(u *User, name, passwd string, sourceId int64, cfg *SMTP
if err := SMTPAuth(auth, cfg); err != nil {
if strings.Contains(err.Error(), "Username and Password not accepted") {
return nil, ErrUserNotExist{u.Id, u.Name}
return nil, ErrUserNotExist{0, name}
}
return nil, err
}
@@ -397,13 +401,20 @@ func LoginUserSMTPSource(u *User, name, passwd string, sourceId int64, cfg *SMTP
return u, err
}
// __________ _____ _____
// \______ \/ _ \ / \
// | ___/ /_\ \ / \ / \
// | | / | \/ Y \
// |____| \____|__ /\____|__ /
// \/ \/
// Query if name/passwd can login against PAM
// Create a local user if success
// Return the same LoginUserPlain semantic
func LoginUserPAMSource(u *User, name, passwd string, sourceId int64, cfg *PAMConfig, autoRegister bool) (*User, error) {
if err := pam.PAMAuth(cfg.ServiceName, name, passwd); err != nil {
if strings.Contains(err.Error(), "Authentication failure") {
return nil, ErrUserNotExist{u.Id, u.Name}
return nil, ErrUserNotExist{0, name}
}
return nil, err
}
@@ -415,7 +426,7 @@ func LoginUserPAMSource(u *User, name, passwd string, sourceId int64, cfg *PAMCo
// fake a local user creation
u = &User{
LowerName: strings.ToLower(name),
Name: strings.ToLower(name),
Name: name,
LoginType: PAM,
LoginSource: sourceId,
LoginName: name,
@@ -423,6 +434,75 @@ func LoginUserPAMSource(u *User, name, passwd string, sourceId int64, cfg *PAMCo
Passwd: passwd,
Email: name,
}
err := CreateUser(u)
return u, err
return u, CreateUser(u)
}
func ExternalUserLogin(u *User, name, passwd string, source *LoginSource, autoRegister bool) (*User, error) {
if !source.IsActived {
return nil, ErrLoginSourceNotActived
}
switch source.Type {
case LDAP, DLDAP:
return LoginUserLDAPSource(u, name, passwd, source, autoRegister)
case SMTP:
return LoginUserSMTPSource(u, name, passwd, source.ID, source.Cfg.(*SMTPConfig), autoRegister)
case PAM:
return LoginUserPAMSource(u, name, passwd, source.ID, source.Cfg.(*PAMConfig), autoRegister)
}
return nil, ErrUnsupportedLoginType
}
// UserSignIn validates user name and password.
func UserSignIn(uname, passwd string) (*User, error) {
var u *User
if strings.Contains(uname, "@") {
u = &User{Email: strings.ToLower(uname)}
} else {
u = &User{LowerName: strings.ToLower(uname)}
}
userExists, err := x.Get(u)
if err != nil {
return nil, err
}
if userExists {
switch u.LoginType {
case NOTYPE, PLAIN:
if u.ValidatePassword(passwd) {
return u, nil
}
return nil, ErrUserNotExist{u.Id, u.Name}
default:
var source LoginSource
hasSource, err := x.Id(u.LoginSource).Get(&source)
if err != nil {
return nil, err
} else if !hasSource {
return nil, ErrLoginSourceNotExist
}
return ExternalUserLogin(u, u.LoginName, passwd, &source, false)
}
}
var sources []LoginSource
if err = x.UseBool().Find(&sources, &LoginSource{IsActived: true}); err != nil {
return nil, err
}
for _, source := range sources {
u, err := ExternalUserLogin(nil, uname, passwd, &source, true)
if err == nil {
return u, nil
}
log.Warn("Failed to login '%s' via '%s': %v", uname, source.Name, err)
}
return nil, ErrUserNotExist{u.Id, u.Name}
}

View File

@@ -11,6 +11,7 @@ import (
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"time"
@@ -65,6 +66,8 @@ var migrations = []Migration{
NewMigration("trim action compare URL prefix", trimCommitActionAppUrlPrefix), // V5 -> V6:v0.6.3
NewMigration("generate issue-label from issue", issueToIssueLabel), // V6 -> V7:v0.6.4
NewMigration("refactor attachment table", attachmentRefactor), // V7 -> V8:v0.6.4
NewMigration("rename pull request fields", renamePullRequestFields), // V8 -> V9:v0.6.16
NewMigration("clean up migrate repo info", cleanUpMigrateRepoInfo), // V9 -> V10:v0.6.20
}
// Migrate database to current version
@@ -103,6 +106,11 @@ func Migrate(x *xorm.Engine) error {
}
v := currentVersion.Version
if _MIN_DB_VER > v {
log.Fatal(4, "Gogs no longer supports auto-migration from your previously installed version. Please try to upgrade to a lower version first, then upgrade to current version.")
return nil
}
if int(v-_MIN_DB_VER) > len(migrations) {
// User downgraded Gogs.
currentVersion.Version = int64(len(migrations) + _MIN_DB_VER)
@@ -453,7 +461,7 @@ func trimCommitActionAppUrlPrefix(x *xorm.Engine) error {
pushCommits = new(PushCommits)
if err = json.Unmarshal(action["content"], pushCommits); err != nil {
return fmt.Errorf("unmarshal action content[%s]: %v", actID, err)
return fmt.Errorf("unmarshal action content[%d]: %v", actID, err)
}
infos := strings.Split(pushCommits.CompareUrl, "/")
@@ -464,7 +472,7 @@ func trimCommitActionAppUrlPrefix(x *xorm.Engine) error {
p, err := json.Marshal(pushCommits)
if err != nil {
return fmt.Errorf("marshal action content[%s]: %v", actID, err)
return fmt.Errorf("marshal action content[%d]: %v", actID, err)
}
if _, err = sess.Id(actID).Update(&Action{
@@ -606,3 +614,96 @@ func attachmentRefactor(x *xorm.Engine) error {
return sess.Commit()
}
func renamePullRequestFields(x *xorm.Engine) (err error) {
type PullRequest struct {
ID int64 `xorm:"pk autoincr"`
PullID int64 `xorm:"INDEX"`
PullIndex int64
HeadBarcnh string
IssueID int64 `xorm:"INDEX"`
Index int64
HeadBranch string
}
if err = x.Sync(new(PullRequest)); err != nil {
return fmt.Errorf("sync: %v", err)
}
results, err := x.Query("SELECT `id`,`pull_id`,`pull_index`,`head_barcnh` FROM `pull_request`")
if err != nil {
if strings.Contains(err.Error(), "no such column") {
return nil
}
return fmt.Errorf("select pull requests: %v", err)
}
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
var pull *PullRequest
for _, pr := range results {
pull = &PullRequest{
ID: com.StrTo(pr["id"]).MustInt64(),
IssueID: com.StrTo(pr["pull_id"]).MustInt64(),
Index: com.StrTo(pr["pull_index"]).MustInt64(),
HeadBranch: string(pr["head_barcnh"]),
}
if _, err = sess.Id(pull.ID).Update(pull); err != nil {
return err
}
}
return sess.Commit()
}
func cleanUpMigrateRepoInfo(x *xorm.Engine) (err error) {
type (
User struct {
ID int64 `xorm:"pk autoincr"`
LowerName string
}
Repository struct {
ID int64 `xorm:"pk autoincr"`
OwnerID int64
LowerName string
}
)
repos := make([]*Repository, 0, 25)
if err = x.Where("is_mirror=?", false).Find(&repos); err != nil {
return fmt.Errorf("select all non-mirror repositories: %v", err)
}
var user *User
for _, repo := range repos {
user = &User{ID: repo.OwnerID}
has, err := x.Get(user)
if err != nil {
return fmt.Errorf("get owner of repository[%d - %d]: %v", repo.ID, repo.OwnerID, err)
} else if !has {
continue
}
configPath := filepath.Join(setting.RepoRootPath, user.LowerName, repo.LowerName+".git/config")
// In case repository file is somehow missing.
if !com.IsFile(configPath) {
continue
}
cfg, err := ini.Load(configPath)
if err != nil {
return fmt.Errorf("open config file: %v", err)
}
cfg.DeleteSection("remote \"origin\"")
if err = cfg.SaveToIndent(configPath, "\t"); err != nil {
return fmt.Errorf("save config file: %v", err)
}
}
return nil
}

View File

@@ -20,6 +20,7 @@ import (
_ "github.com/lib/pq"
"github.com/gogits/gogs/models/migrations"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/setting"
)
@@ -46,20 +47,22 @@ func sessionRelease(sess *xorm.Session) {
// Note: get back time.Time from database Go sees it at UTC where they are really Local.
// So this function makes correct timezone offset.
func regulateTimeZone(t time.Time) time.Time {
if setting.UseSQLite3 {
if !setting.UseMySQL {
return t
}
zone := t.Local().Format("-0700")
if len(zone) != 5 {
log.Error(4, "Unprocessable timezone: %s - %s", t.Local(), zone)
return t
}
offset := com.StrTo(zone[2:3]).MustInt()
hour := com.StrTo(zone[2:3]).MustInt()
minutes := com.StrTo(zone[3:5]).MustInt()
if zone[0] == '-' {
return t.Add(time.Duration(offset) * time.Hour)
return t.Add(time.Duration(hour) * time.Hour).Add(time.Duration(minutes) * time.Minute)
}
return t.Add(-1 * time.Duration(offset) * time.Hour)
return t.Add(-1 * time.Duration(hour) * time.Hour).Add(-1 * time.Duration(minutes) * time.Minute)
}
var (
@@ -72,11 +75,12 @@ var (
}
EnableSQLite3 bool
EnableTidb bool
)
func init() {
tables = append(tables,
new(User), new(PublicKey), new(Oauth2), new(AccessToken),
new(User), new(PublicKey), new(AccessToken),
new(Repository), new(DeployKey), new(Collaboration), new(Access),
new(Watch), new(Star), new(Follow), new(Action),
new(Issue), new(PullRequest), new(Comment), new(Attachment), new(IssueUser),
@@ -92,7 +96,7 @@ func init() {
}
}
func LoadModelsConfig() {
func LoadConfigs() {
sec := setting.Cfg.Section("database")
DbCfg.Type = sec.Key("DB_TYPE").String()
switch DbCfg.Type {
@@ -102,6 +106,8 @@ func LoadModelsConfig() {
setting.UseMySQL = true
case "postgres":
setting.UsePostgreSQL = true
case "tidb":
setting.UseTiDB = true
}
DbCfg.Host = sec.Key("HOST").String()
DbCfg.Name = sec.Key("NAME").String()
@@ -143,6 +149,14 @@ func getEngine() (*xorm.Engine, error) {
return nil, fmt.Errorf("Fail to create directories: %v", err)
}
cnnstr = "file:" + DbCfg.Path + "?cache=shared&mode=rwc"
case "tidb":
if !EnableTidb {
return nil, fmt.Errorf("Unknown database type: %s", DbCfg.Type)
}
if err := os.MkdirAll(path.Dir(DbCfg.Path), os.ModePerm); err != nil {
return nil, fmt.Errorf("Fail to create directories: %v", err)
}
cnnstr = "goleveldb://" + DbCfg.Path
default:
return nil, fmt.Errorf("Unknown database type: %s", DbCfg.Type)
}
@@ -224,11 +238,11 @@ func GetStatistic() (stats Statistic) {
stats.Counter.Access, _ = x.Count(new(Access))
stats.Counter.Issue, _ = x.Count(new(Issue))
stats.Counter.Comment, _ = x.Count(new(Comment))
stats.Counter.Oauth, _ = x.Count(new(Oauth2))
stats.Counter.Oauth = 0
stats.Counter.Follow, _ = x.Count(new(Follow))
stats.Counter.Mirror, _ = x.Count(new(Mirror))
stats.Counter.Release, _ = x.Count(new(Release))
stats.Counter.LoginSource, _ = x.Count(new(LoginSource))
stats.Counter.LoginSource = CountLoginSources()
stats.Counter.Webhook, _ = x.Count(new(Webhook))
stats.Counter.Milestone, _ = x.Count(new(Milestone))
stats.Counter.Label, _ = x.Count(new(Label))

18
models/models_tidb.go Normal file
View File

@@ -0,0 +1,18 @@
// +build tidb go1.4.2
// Copyright 2015 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
_ "github.com/go-xorm/tidb"
"github.com/ngaut/log"
_ "github.com/pingcap/tidb"
)
func init() {
EnableTidb = true
log.SetLevelByString("error")
}

View File

@@ -1,106 +0,0 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"errors"
"time"
)
type OauthType int
const (
GITHUB OauthType = iota + 1
GOOGLE
TWITTER
QQ
WEIBO
BITBUCKET
FACEBOOK
)
var (
ErrOauth2RecordNotExist = errors.New("OAuth2 record does not exist")
ErrOauth2NotAssociated = errors.New("OAuth2 is not associated with user")
)
type Oauth2 struct {
Id int64
Uid int64 `xorm:"unique(s)"` // userId
User *User `xorm:"-"`
Type int `xorm:"unique(s) unique(oauth)"` // twitter,github,google...
Identity string `xorm:"unique(s) unique(oauth)"` // id..
Token string `xorm:"TEXT not null"`
Created time.Time `xorm:"CREATED"`
Updated time.Time
HasRecentActivity bool `xorm:"-"`
}
func BindUserOauth2(userId, oauthId int64) error {
_, err := x.Id(oauthId).Update(&Oauth2{Uid: userId})
return err
}
func AddOauth2(oa *Oauth2) error {
_, err := x.Insert(oa)
return err
}
func GetOauth2(identity string) (oa *Oauth2, err error) {
oa = &Oauth2{Identity: identity}
isExist, err := x.Get(oa)
if err != nil {
return
} else if !isExist {
return nil, ErrOauth2RecordNotExist
} else if oa.Uid == -1 {
return oa, ErrOauth2NotAssociated
}
oa.User, err = GetUserByID(oa.Uid)
return oa, err
}
func GetOauth2ById(id int64) (oa *Oauth2, err error) {
oa = new(Oauth2)
has, err := x.Id(id).Get(oa)
if err != nil {
return nil, err
} else if !has {
return nil, ErrOauth2RecordNotExist
}
return oa, nil
}
// UpdateOauth2 updates given OAuth2.
func UpdateOauth2(oa *Oauth2) error {
_, err := x.Id(oa.Id).AllCols().Update(oa)
return err
}
// GetOauthByUserId returns list of oauthes that are related to given user.
func GetOauthByUserId(uid int64) ([]*Oauth2, error) {
socials := make([]*Oauth2, 0, 5)
err := x.Find(&socials, Oauth2{Uid: uid})
if err != nil {
return nil, err
}
for _, social := range socials {
social.HasRecentActivity = social.Updated.Add(7 * 24 * time.Hour).After(time.Now())
}
return socials, err
}
// DeleteOauth2ById deletes a oauth2 by ID.
func DeleteOauth2ById(id int64) error {
_, err := x.Delete(&Oauth2{Id: id})
return err
}
// CleanUnbindOauth deletes all unbind OAuthes.
func CleanUnbindOauth() error {
_, err := x.Delete(&Oauth2{Uid: -1})
return err
}

View File

@@ -10,7 +10,7 @@ import (
"os"
"strings"
"github.com/gogits/gogs/modules/base"
"github.com/go-xorm/xorm"
)
var (
@@ -93,17 +93,6 @@ func (org *User) RemoveOrgRepo(repoID int64) error {
return org.removeOrgRepo(x, repoID)
}
// IsOrgEmailUsed returns true if the e-mail has been used in organization account.
func IsOrgEmailUsed(email string) (bool, error) {
if len(email) == 0 {
return false, nil
}
return x.Get(&User{
Email: email,
Type: ORGANIZATION,
})
}
// CreateOrganization creates record of a new organization.
func CreateOrganization(org, owner *User) (err error) {
if err = IsUsableName(org.Name); err != nil {
@@ -117,18 +106,9 @@ func CreateOrganization(org, owner *User) (err error) {
return ErrUserAlreadyExist{org.Name}
}
isExist, err = IsOrgEmailUsed(org.Email)
if err != nil {
return err
} else if isExist {
return ErrEmailAlreadyUsed{org.Email}
}
org.LowerName = strings.ToLower(org.Name)
org.FullName = org.Name
org.Avatar = base.EncodeMd5(org.Email)
org.AvatarEmail = org.Email
// No password for organization.
org.UseCustomAvatar = true
org.NumTeams = 1
org.NumMembers = 1
@@ -141,6 +121,17 @@ func CreateOrganization(org, owner *User) (err error) {
if _, err = sess.Insert(org); err != nil {
return fmt.Errorf("insert organization: %v", err)
}
org.GenerateRandomAvatar()
// Add initial creator to organization and owner team.
if _, err = sess.Insert(&OrgUser{
Uid: owner.Id,
OrgID: org.Id,
IsOwner: true,
NumTeams: 1,
}); err != nil {
return fmt.Errorf("insert org-user relation: %v", err)
}
// Create default owner team.
t := &Team{
@@ -154,23 +145,11 @@ func CreateOrganization(org, owner *User) (err error) {
return fmt.Errorf("insert owner team: %v", err)
}
// Add initial creator to organization and owner team.
ou := &OrgUser{
Uid: owner.Id,
OrgID: org.Id,
IsOwner: true,
NumTeams: 1,
}
if _, err = sess.Insert(ou); err != nil {
return fmt.Errorf("insert org-user relation: %v", err)
}
tu := &TeamUser{
if _, err = sess.Insert(&TeamUser{
Uid: owner.Id,
OrgID: org.Id,
TeamID: t.ID,
}
if _, err = sess.Insert(tu); err != nil {
}); err != nil {
return fmt.Errorf("insert team-user relation: %v", err)
}
@@ -205,14 +184,12 @@ func CountOrganizations() int64 {
return count
}
// GetOrganizations returns given number of organizations with offset.
func GetOrganizations(num, offset int) ([]*User, error) {
orgs := make([]*User, 0, num)
err := x.Limit(num, offset).Where("type=1").Asc("id").Find(&orgs)
return orgs, err
// Organizations returns number of organizations in given page.
func Organizations(page, pageSize int) ([]*User, error) {
orgs := make([]*User, 0, pageSize)
return orgs, x.Limit(pageSize, (page-1)*pageSize).Where("type=1").Asc("id").Find(&orgs)
}
// TODO: need some kind of mechanism to record failure.
// DeleteOrganization completely and permanently deletes everything of organization.
func DeleteOrganization(org *User) (err error) {
if err := DeleteUser(org); err != nil {
@@ -220,23 +197,23 @@ func DeleteOrganization(org *User) (err error) {
}
sess := x.NewSession()
defer sess.Close()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.Delete(&Team{OrgID: org.Id}); err != nil {
sess.Rollback()
return err
if err = deleteBeans(sess,
&Team{OrgID: org.Id},
&OrgUser{OrgID: org.Id},
&TeamUser{OrgID: org.Id},
); err != nil {
return fmt.Errorf("deleteBeans: %v", err)
}
if _, err = sess.Delete(&OrgUser{OrgID: org.Id}); err != nil {
sess.Rollback()
return err
}
if _, err = sess.Delete(&TeamUser{OrgID: org.Id}); err != nil {
sess.Rollback()
return err
if err = deleteUser(sess, org); err != nil {
return fmt.Errorf("deleteUser: %v", err)
}
return sess.Commit()
}
@@ -275,6 +252,25 @@ func IsPublicMembership(orgId, uid int64) bool {
return has
}
func getOwnedOrgsByUserID(sess *xorm.Session, userID int64) ([]*User, error) {
orgs := make([]*User, 0, 10)
return orgs, sess.Where("`org_user`.uid=?", userID).And("`org_user`.is_owner=?", true).
Join("INNER", "`org_user`", "`org_user`.org_id=`user`.id").Find(&orgs)
}
// GetOwnedOrgsByUserID returns a list of organizations are owned by given user ID.
func GetOwnedOrgsByUserID(userID int64) ([]*User, error) {
sess := x.NewSession()
return getOwnedOrgsByUserID(sess, userID)
}
// GetOwnedOrganizationsByUserIDDesc returns a list of organizations are owned by
// given user ID and descring order by given condition.
func GetOwnedOrgsByUserIDDesc(userID int64, desc string) ([]*User, error) {
sess := x.NewSession()
return getOwnedOrgsByUserID(sess.Desc(desc), userID)
}
// GetOrgUsersByUserId returns all organization-user relations by user ID.
func GetOrgUsersByUserId(uid int64) ([]*OrgUser, error) {
ous := make([]*OrgUser, 0, 10)

View File

@@ -13,7 +13,6 @@ import (
"io"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
@@ -33,25 +32,8 @@ const (
_TPL_PUBLICK_KEY = `command="%s serv key-%d --config='%s'",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s` + "\n"
)
var (
ErrKeyUnableVerify = errors.New("Unable to verify public key")
)
var sshOpLocker = sync.Mutex{}
var (
SSHPath string // SSH directory.
appPath string // Execution(binary) path.
)
// exePath returns the executable path.
func exePath() (string, error) {
file, err := exec.LookPath(os.Args[0])
if err != nil {
return "", err
}
return filepath.Abs(file)
}
var SSHPath string // SSH directory.
// homeDir returns the home directory of current user.
func homeDir() string {
@@ -63,16 +45,9 @@ func homeDir() string {
}
func init() {
var err error
if appPath, err = exePath(); err != nil {
log.Fatal(4, "fail to get app path: %v\n", err)
}
appPath = strings.Replace(appPath, "\\", "/", -1)
// Determine and create .ssh path.
SSHPath = filepath.Join(homeDir(), ".ssh")
if err = os.MkdirAll(SSHPath, 0700); err != nil {
if err := os.MkdirAll(SSHPath, 0700); err != nil {
log.Fatal(4, "fail to create '%s': %v", SSHPath, err)
}
}
@@ -114,17 +89,7 @@ func (k *PublicKey) OmitEmail() string {
// GetAuthorizedString generates and returns formatted public key string for authorized_keys file.
func (key *PublicKey) GetAuthorizedString() string {
return fmt.Sprintf(_TPL_PUBLICK_KEY, appPath, key.ID, setting.CustomConf, key.Content)
}
var minimumKeySizes = map[string]int{
"(ED25519)": 256,
"(ECDSA)": 256,
"(NTRU)": 1087,
"(MCE)": 1702,
"(McE)": 1702,
"(RSA)": 1024,
"(DSA)": 1024,
return fmt.Sprintf(_TPL_PUBLICK_KEY, setting.AppPath, key.ID, setting.CustomConf, key.Content)
}
func extractTypeFromBase64Key(key string) (string, error) {
@@ -153,7 +118,7 @@ func parseKeyString(content string) (string, error) {
if len(lines) == 1 {
// Parse openssh format
parts := strings.Fields(lines[0])
parts := strings.SplitN(lines[0], " ", 3)
switch len(parts) {
case 0:
return "", errors.New("Empty key")
@@ -228,9 +193,9 @@ func CheckPublicKeyString(content string) (_ string, err error) {
tmpFile.Close()
// Check if ssh-keygen recognizes its contents.
stdout, stderr, err := process.Exec("CheckPublicKeyString", "ssh-keygen", "-l", "-f", tmpPath)
stdout, stderr, err := process.Exec("CheckPublicKeyString", "ssh-keygen", "-lf", tmpPath)
if err != nil {
return "", errors.New("ssh-keygen -l -f: " + stderr)
return "", errors.New("ssh-keygen -lf: " + stderr)
} else if len(stdout) < 2 {
return "", errors.New("ssh-keygen returned not enough output to evaluate the key: " + stdout)
}
@@ -242,7 +207,7 @@ func CheckPublicKeyString(content string) (_ string, err error) {
sshKeygenOutput := strings.Split(stdout, " ")
if len(sshKeygenOutput) < 4 {
return content, ErrKeyUnableVerify
return content, ErrKeyUnableVerify{stdout}
}
// Check if key type and key size match.
@@ -251,9 +216,10 @@ func CheckPublicKeyString(content string) (_ string, err error) {
if keySize == 0 {
return "", errors.New("cannot get key size of the given key")
}
keyType := strings.TrimSpace(sshKeygenOutput[len(sshKeygenOutput)-1])
if minimumKeySize := minimumKeySizes[keyType]; minimumKeySize == 0 {
return "", errors.New("sorry, unrecognized public key type")
keyType := strings.Trim(sshKeygenOutput[len(sshKeygenOutput)-1], " ()\n")
if minimumKeySize := setting.Service.MinimumKeySizes[keyType]; minimumKeySize == 0 {
return "", fmt.Errorf("unrecognized public key type: %s", keyType)
} else if keySize < minimumKeySize {
return "", fmt.Errorf("the minimum accepted size of a public key %s is %d", keyType, minimumKeySize)
}
@@ -321,9 +287,9 @@ func addKey(e Engine, key *PublicKey) (err error) {
if err = ioutil.WriteFile(tmpPath, []byte(key.Content), 0644); err != nil {
return err
}
stdout, stderr, err := process.Exec("AddPublicKey", "ssh-keygen", "-l", "-f", tmpPath)
stdout, stderr, err := process.Exec("AddPublicKey", "ssh-keygen", "-lf", tmpPath)
if err != nil {
return errors.New("ssh-keygen -l -f: " + stderr)
return errors.New("ssh-keygen -lf: " + stderr)
} else if len(stdout) < 2 {
return errors.New("not enough output for calculating fingerprint: " + stdout)
}
@@ -337,23 +303,23 @@ func addKey(e Engine, key *PublicKey) (err error) {
}
// AddPublicKey adds new public key to database and authorized_keys file.
func AddPublicKey(ownerID int64, name, content string) (err error) {
if err = checkKeyContent(content); err != nil {
return err
func AddPublicKey(ownerID int64, name, content string) (*PublicKey, error) {
if err := checkKeyContent(content); err != nil {
return nil, err
}
// Key name of same user cannot be duplicated.
has, err := x.Where("owner_id=? AND name=?", ownerID, name).Get(new(PublicKey))
if err != nil {
return err
return nil, err
} else if has {
return ErrKeyNameAlreadyUsed{ownerID, name}
return nil, ErrKeyNameAlreadyUsed{ownerID, name}
}
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
return nil, err
}
key := &PublicKey{
@@ -364,10 +330,10 @@ func AddPublicKey(ownerID int64, name, content string) (err error) {
Type: KEY_TYPE_USER,
}
if err = addKey(sess, key); err != nil {
return fmt.Errorf("addKey: %v", err)
return nil, fmt.Errorf("addKey: %v", err)
}
return sess.Commit()
return key, sess.Commit()
}
// GetPublicKeyByID returns public key by given ID.
@@ -382,6 +348,19 @@ func GetPublicKeyByID(keyID int64) (*PublicKey, error) {
return key, nil
}
// SearchPublicKeyByContent searches content as prefix (leak e-mail part)
// and returns public key found.
func SearchPublicKeyByContent(content string) (*PublicKey, error) {
key := new(PublicKey)
has, err := x.Where("content like ?", content+"%").Get(key)
if err != nil {
return nil, err
} else if !has {
return nil, ErrKeyNotExist{}
}
return key, nil
}
// ListPublicKeys returns a list of public keys belongs to given user.
func ListPublicKeys(uid int64) ([]*PublicKey, error) {
keys := make([]*PublicKey, 0, 5)
@@ -471,12 +450,18 @@ func deletePublicKey(e *xorm.Session, keyID int64) error {
}
// DeletePublicKey deletes SSH key information both in database and authorized_keys file.
func DeletePublicKey(id int64) (err error) {
has, err := x.Id(id).Get(new(PublicKey))
func DeletePublicKey(doer *User, id int64) (err error) {
key, err := GetPublicKeyByID(id)
if err != nil {
return err
} else if !has {
return nil
if IsErrKeyNotExist(err) {
return nil
}
return fmt.Errorf("GetPublicKeyByID: %v", err)
}
// Check if user has access to delete this key.
if !doer.IsAdmin && doer.Id != key.OwnerID {
return ErrKeyAccessDenied{doer.Id, key.ID, "public"}
}
sess := x.NewSession()
@@ -540,6 +525,7 @@ type DeployKey struct {
RepoID int64 `xorm:"UNIQUE(s) INDEX"`
Name string
Fingerprint string
Content string `xorm:"-"`
Created time.Time `xorm:"CREATED"`
Updated time.Time // Note: Updated must below Created for AfterSet.
HasRecentActivity bool `xorm:"-"`
@@ -554,6 +540,16 @@ func (k *DeployKey) AfterSet(colName string, _ xorm.Cell) {
}
}
// GetContent gets associated public key content.
func (k *DeployKey) GetContent() error {
pkey, err := GetPublicKeyByID(k.KeyID)
if err != nil {
return err
}
k.Content = pkey.Content
return nil
}
func checkDeployKey(e Engine, keyID, repoID int64, name string) error {
// Note: We want error detail, not just true or false here.
has, err := e.Where("key_id=? AND repo_id=?", keyID, repoID).Get(new(DeployKey))
@@ -574,18 +570,19 @@ func checkDeployKey(e Engine, keyID, repoID int64, name string) error {
}
// addDeployKey adds new key-repo relation.
func addDeployKey(e *xorm.Session, keyID, repoID int64, name, fingerprint string) (err error) {
if err = checkDeployKey(e, keyID, repoID, name); err != nil {
return err
func addDeployKey(e *xorm.Session, keyID, repoID int64, name, fingerprint string) (*DeployKey, error) {
if err := checkDeployKey(e, keyID, repoID, name); err != nil {
return nil, err
}
_, err = e.Insert(&DeployKey{
key := &DeployKey{
KeyID: keyID,
RepoID: repoID,
Name: name,
Fingerprint: fingerprint,
})
return err
}
_, err := e.Insert(key)
return key, err
}
// HasDeployKey returns true if public key is a deploy key of given repository.
@@ -595,39 +592,52 @@ func HasDeployKey(keyID, repoID int64) bool {
}
// AddDeployKey add new deploy key to database and authorized_keys file.
func AddDeployKey(repoID int64, name, content string) (err error) {
if err = checkKeyContent(content); err != nil {
return err
func AddDeployKey(repoID int64, name, content string) (*DeployKey, error) {
if err := checkKeyContent(content); err != nil {
return nil, err
}
key := &PublicKey{
pkey := &PublicKey{
Content: content,
Mode: ACCESS_MODE_READ,
Type: KEY_TYPE_DEPLOY,
}
has, err := x.Get(key)
has, err := x.Get(pkey)
if err != nil {
return err
return nil, err
}
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
return nil, err
}
// First time use this deploy key.
if !has {
if err = addKey(sess, key); err != nil {
return nil
if err = addKey(sess, pkey); err != nil {
return nil, fmt.Errorf("addKey: %v", err)
}
}
if err = addDeployKey(sess, key.ID, repoID, name, key.Fingerprint); err != nil {
return err
key, err := addDeployKey(sess, pkey.ID, repoID, name, pkey.Fingerprint)
if err != nil {
return nil, fmt.Errorf("addDeployKey: %v", err)
}
return sess.Commit()
return key, sess.Commit()
}
// GetDeployKeyByID returns deploy key by given ID.
func GetDeployKeyByID(id int64) (*DeployKey, error) {
key := new(DeployKey)
has, err := x.Id(id).Get(key)
if err != nil {
return nil, err
} else if !has {
return nil, ErrDeployKeyNotExist{id, 0, 0}
}
return key, nil
}
// GetDeployKeyByRepo returns deploy key by given public key ID and repository ID.
@@ -636,8 +646,13 @@ func GetDeployKeyByRepo(keyID, repoID int64) (*DeployKey, error) {
KeyID: keyID,
RepoID: repoID,
}
_, err := x.Get(key)
return key, err
has, err := x.Get(key)
if err != nil {
return nil, err
} else if !has {
return nil, ErrDeployKeyNotExist{0, keyID, repoID}
}
return key, nil
}
// UpdateDeployKey updates deploy key information.
@@ -647,13 +662,27 @@ func UpdateDeployKey(key *DeployKey) error {
}
// DeleteDeployKey deletes deploy key from its repository authorized_keys file if needed.
func DeleteDeployKey(id int64) error {
key := &DeployKey{ID: id}
has, err := x.Id(key.ID).Get(key)
func DeleteDeployKey(doer *User, id int64) error {
key, err := GetDeployKeyByID(id)
if err != nil {
return err
} else if !has {
return nil
if IsErrDeployKeyNotExist(err) {
return nil
}
return fmt.Errorf("GetDeployKeyByID: %v", err)
}
// Check if user has access to delete this key.
if !doer.IsAdmin {
repo, err := GetRepositoryByID(key.RepoID)
if err != nil {
return fmt.Errorf("GetRepositoryByID: %v", err)
}
yes, err := HasAccess(doer, repo, ACCESS_MODE_ADMIN)
if err != nil {
return fmt.Errorf("HasAccess: %v", err)
} else if !yes {
return ErrKeyAccessDenied{doer.Id, key.ID, "deploy"}
}
}
sess := x.NewSession()
@@ -667,7 +696,7 @@ func DeleteDeployKey(id int64) error {
}
// Check if this is the last reference to same key content.
has, err = sess.Where("key_id=?", key.KeyID).Get(new(DeployKey))
has, err := sess.Where("key_id=?", key.KeyID).Get(new(DeployKey))
if err != nil {
return err
} else if !has {

559
models/pull.go Normal file
View File

@@ -0,0 +1,559 @@
// Copyright 2015 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"fmt"
"os"
"path"
"strings"
"time"
"github.com/Unknwon/com"
"github.com/go-xorm/xorm"
"github.com/gogits/gogs/modules/git"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/process"
"github.com/gogits/gogs/modules/setting"
)
type PullRequestType int
const (
PULL_REQUEST_GOGS PullRequestType = iota
PLLL_ERQUEST_GIT
)
type PullRequestStatus int
const (
PULL_REQUEST_STATUS_CONFLICT PullRequestStatus = iota
PULL_REQUEST_STATUS_CHECKING
PULL_REQUEST_STATUS_MERGEABLE
)
// PullRequest represents relation between pull request and repositories.
type PullRequest struct {
ID int64 `xorm:"pk autoincr"`
Type PullRequestType
Status PullRequestStatus
IssueID int64 `xorm:"INDEX"`
Issue *Issue `xorm:"-"`
Index int64
HeadRepoID int64
HeadRepo *Repository `xorm:"-"`
BaseRepoID int64
BaseRepo *Repository `xorm:"-"`
HeadUserName string
HeadBranch string
BaseBranch string
MergeBase string `xorm:"VARCHAR(40)"`
HasMerged bool
MergedCommitID string `xorm:"VARCHAR(40)"`
Merged time.Time
MergerID int64
Merger *User `xorm:"-"`
}
// Note: don't try to get Pull because will end up recursive querying.
func (pr *PullRequest) AfterSet(colName string, _ xorm.Cell) {
switch colName {
case "merged":
if !pr.HasMerged {
return
}
pr.Merged = regulateTimeZone(pr.Merged)
}
}
func (pr *PullRequest) getHeadRepo(e Engine) (err error) {
pr.HeadRepo, err = getRepositoryByID(e, pr.HeadRepoID)
if err != nil && !IsErrRepoNotExist(err) {
return fmt.Errorf("getRepositoryByID(head): %v", err)
}
return nil
}
func (pr *PullRequest) GetHeadRepo() (err error) {
return pr.getHeadRepo(x)
}
func (pr *PullRequest) GetBaseRepo() (err error) {
if pr.BaseRepo != nil {
return nil
}
pr.BaseRepo, err = GetRepositoryByID(pr.BaseRepoID)
if err != nil {
return fmt.Errorf("GetRepositoryByID(base): %v", err)
}
return nil
}
func (pr *PullRequest) GetMerger() (err error) {
if !pr.HasMerged || pr.Merger != nil {
return nil
}
pr.Merger, err = GetUserByID(pr.MergerID)
if IsErrUserNotExist(err) {
pr.MergerID = -1
pr.Merger = NewFakeUser()
} else if err != nil {
return fmt.Errorf("GetUserByID: %v", err)
}
return nil
}
// IsChecking returns true if this pull request is still checking conflict.
func (pr *PullRequest) IsChecking() bool {
return pr.Status == PULL_REQUEST_STATUS_CHECKING
}
// CanAutoMerge returns true if this pull request can be merged automatically.
func (pr *PullRequest) CanAutoMerge() bool {
return pr.Status == PULL_REQUEST_STATUS_MERGEABLE
}
// Merge merges pull request to base repository.
func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error) {
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if err = pr.Issue.changeStatus(sess, doer, true); err != nil {
return fmt.Errorf("Issue.changeStatus: %v", err)
}
if err = pr.getHeadRepo(sess); err != nil {
return fmt.Errorf("getHeadRepo: %v", err)
}
headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name)
headGitRepo, err := git.OpenRepository(headRepoPath)
if err != nil {
return fmt.Errorf("OpenRepository: %v", err)
}
pr.MergedCommitID, err = headGitRepo.GetCommitIdOfBranch(pr.HeadBranch)
if err != nil {
return fmt.Errorf("GetCommitIdOfBranch: %v", err)
}
if err = mergePullRequestAction(sess, doer, pr.Issue.Repo, pr.Issue); err != nil {
return fmt.Errorf("mergePullRequestAction: %v", err)
}
pr.HasMerged = true
pr.Merged = time.Now()
pr.MergerID = doer.Id
if _, err = sess.Id(pr.ID).AllCols().Update(pr); err != nil {
return fmt.Errorf("update pull request: %v", err)
}
// Clone base repo.
tmpBasePath := path.Join(setting.AppDataPath, "tmp/repos", com.ToStr(time.Now().Nanosecond())+".git")
os.MkdirAll(path.Dir(tmpBasePath), os.ModePerm)
defer os.RemoveAll(path.Dir(tmpBasePath))
var stderr string
if _, stderr, err = process.ExecTimeout(5*time.Minute,
fmt.Sprintf("PullRequest.Merge (git clone): %s", tmpBasePath),
"git", "clone", baseGitRepo.Path, tmpBasePath); err != nil {
return fmt.Errorf("git clone: %s", stderr)
}
// Check out base branch.
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath),
"git", "checkout", pr.BaseBranch); err != nil {
return fmt.Errorf("git checkout: %s", stderr)
}
// Add head repo remote.
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git remote add): %s", tmpBasePath),
"git", "remote", "add", "head_repo", headRepoPath); err != nil {
return fmt.Errorf("git remote add [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
}
// Merge commits.
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git fetch): %s", tmpBasePath),
"git", "fetch", "head_repo"); err != nil {
return fmt.Errorf("git fetch [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
}
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git merge --no-ff --no-commit): %s", tmpBasePath),
"git", "merge", "--no-ff", "--no-commit", "head_repo/"+pr.HeadBranch); err != nil {
return fmt.Errorf("git merge --no-ff --no-commit [%s]: %v - %s", tmpBasePath, err, stderr)
}
sig := doer.NewGitSig()
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git merge): %s", tmpBasePath),
"git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
"-m", fmt.Sprintf("Merge branch '%s' of %s/%s into %s", pr.HeadBranch, pr.HeadUserName, pr.HeadRepo.Name, pr.BaseBranch)); err != nil {
return fmt.Errorf("git commit [%s]: %v - %s", tmpBasePath, err, stderr)
}
// Push back to upstream.
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git push): %s", tmpBasePath),
"git", "push", baseGitRepo.Path, pr.BaseBranch); err != nil {
return fmt.Errorf("git push: %s", stderr)
}
return sess.Commit()
}
// patchConflicts is a list of conflit description from Git.
var patchConflicts = []string{
"patch does not apply",
"already exists in working directory",
"unrecognized input",
}
// testPatch checks if patch can be merged to base repository without conflit.
// FIXME: make a mechanism to clean up stable local copies.
func (pr *PullRequest) testPatch() (err error) {
if pr.BaseRepo == nil {
pr.BaseRepo, err = GetRepositoryByID(pr.BaseRepoID)
if err != nil {
return fmt.Errorf("GetRepositoryByID: %v", err)
}
}
patchPath, err := pr.BaseRepo.PatchPath(pr.Index)
if err != nil {
return fmt.Errorf("BaseRepo.PatchPath: %v", err)
}
// Fast fail if patch does not exist, this assumes data is cruppted.
if !com.IsFile(patchPath) {
log.Trace("PullRequest[%d].testPatch: ignored cruppted data", pr.ID)
return nil
}
log.Trace("PullRequest[%d].testPatch(patchPath): %s", pr.ID, patchPath)
if err := pr.BaseRepo.UpdateLocalCopy(); err != nil {
return fmt.Errorf("UpdateLocalCopy: %v", err)
}
// Checkout base branch.
_, stderr, err := process.ExecDir(-1, pr.BaseRepo.LocalCopyPath(),
fmt.Sprintf("PullRequest.Merge(git checkout): %v", pr.BaseRepo.ID),
"git", "checkout", pr.BaseBranch)
if err != nil {
return fmt.Errorf("git checkout: %s", stderr)
}
pr.Status = PULL_REQUEST_STATUS_CHECKING
_, stderr, err = process.ExecDir(-1, pr.BaseRepo.LocalCopyPath(),
fmt.Sprintf("testPatch(git apply --check): %d", pr.BaseRepo.ID),
"git", "apply", "--check", patchPath)
if err != nil {
for i := range patchConflicts {
if strings.Contains(stderr, patchConflicts[i]) {
log.Trace("PullRequest[%d].testPatch(apply): has conflit", pr.ID)
fmt.Println(stderr)
pr.Status = PULL_REQUEST_STATUS_CONFLICT
return nil
}
}
return fmt.Errorf("git apply --check: %v - %s", err, stderr)
}
return nil
}
// NewPullRequest creates new pull request with labels for repository.
func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []string, pr *PullRequest, patch []byte) (err error) {
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if err = newIssue(sess, repo, pull, labelIDs, uuids, true); err != nil {
return fmt.Errorf("newIssue: %v", err)
}
// Notify watchers.
act := &Action{
ActUserID: pull.Poster.Id,
ActUserName: pull.Poster.Name,
ActEmail: pull.Poster.Email,
OpType: CREATE_PULL_REQUEST,
Content: fmt.Sprintf("%d|%s", pull.Index, pull.Name),
RepoID: repo.ID,
RepoUserName: repo.Owner.Name,
RepoName: repo.Name,
IsPrivate: repo.IsPrivate,
}
if err = notifyWatchers(sess, act); err != nil {
return err
}
pr.Index = pull.Index
if err = repo.SavePatch(pr.Index, patch); err != nil {
return fmt.Errorf("SavePatch: %v", err)
}
pr.BaseRepo = repo
if err = pr.testPatch(); err != nil {
return fmt.Errorf("testPatch: %v", err)
}
if pr.Status == PULL_REQUEST_STATUS_CHECKING {
pr.Status = PULL_REQUEST_STATUS_MERGEABLE
}
pr.IssueID = pull.ID
if _, err = sess.Insert(pr); err != nil {
return fmt.Errorf("insert pull repo: %v", err)
}
return sess.Commit()
}
// GetUnmergedPullRequest returnss a pull request that is open and has not been merged
// by given head/base and repo/branch.
func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch string) (*PullRequest, error) {
pr := new(PullRequest)
has, err := x.Where("head_repo_id=? AND head_branch=? AND base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?",
headRepoID, headBranch, baseRepoID, baseBranch, false, false).
Join("INNER", "issue", "issue.id=pull_request.issue_id").Get(pr)
if err != nil {
return nil, err
} else if !has {
return nil, ErrPullRequestNotExist{0, 0, headRepoID, baseRepoID, headBranch, baseBranch}
}
return pr, nil
}
// GetUnmergedPullRequestsByHeadInfo returnss all pull requests that are open and has not been merged
// by given head information (repo and branch).
func GetUnmergedPullRequestsByHeadInfo(repoID int64, branch string) ([]*PullRequest, error) {
prs := make([]*PullRequest, 0, 2)
return prs, x.Where("head_repo_id=? AND head_branch=? AND has_merged=? AND issue.is_closed=?",
repoID, branch, false, false).
Join("INNER", "issue", "issue.id=pull_request.issue_id").Find(&prs)
}
// GetUnmergedPullRequestsByBaseInfo returnss all pull requests that are open and has not been merged
// by given base information (repo and branch).
func GetUnmergedPullRequestsByBaseInfo(repoID int64, branch string) ([]*PullRequest, error) {
prs := make([]*PullRequest, 0, 2)
return prs, x.Where("base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?",
repoID, branch, false, false).
Join("INNER", "issue", "issue.id=pull_request.issue_id").Find(&prs)
}
// GetPullRequestByID returns a pull request by given ID.
func GetPullRequestByID(id int64) (*PullRequest, error) {
pr := new(PullRequest)
has, err := x.Id(id).Get(pr)
if err != nil {
return nil, err
} else if !has {
return nil, ErrPullRequestNotExist{id, 0, 0, 0, "", ""}
}
return pr, nil
}
// GetPullRequestByIssueID returns pull request by given issue ID.
func GetPullRequestByIssueID(issueID int64) (*PullRequest, error) {
pr := &PullRequest{
IssueID: issueID,
}
has, err := x.Get(pr)
if err != nil {
return nil, err
} else if !has {
return nil, ErrPullRequestNotExist{0, issueID, 0, 0, "", ""}
}
return pr, nil
}
// Update updates all fields of pull request.
func (pr *PullRequest) Update() error {
_, err := x.Id(pr.ID).AllCols().Update(pr)
return err
}
// Update updates specific fields of pull request.
func (pr *PullRequest) UpdateCols(cols ...string) error {
_, err := x.Id(pr.ID).Cols(cols...).Update(pr)
return err
}
var PullRequestQueue = NewUniqueQueue(setting.Repository.PullRequestQueueLength)
// UpdatePatch generates and saves a new patch.
func (pr *PullRequest) UpdatePatch() (err error) {
if err = pr.GetHeadRepo(); err != nil {
return fmt.Errorf("GetHeadRepo: %v", err)
} else if pr.HeadRepo == nil {
log.Trace("PullRequest[%d].UpdatePatch: ignored cruppted data", pr.ID)
return nil
}
if err = pr.GetBaseRepo(); err != nil {
return fmt.Errorf("GetBaseRepo: %v", err)
} else if err = pr.BaseRepo.GetOwner(); err != nil {
return fmt.Errorf("GetOwner: %v", err)
}
headGitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath())
if err != nil {
return fmt.Errorf("OpenRepository: %v", err)
}
// Add a temporary remote.
tmpRemote := com.ToStr(time.Now().UnixNano())
if err = headGitRepo.AddRemote(tmpRemote, RepoPath(pr.BaseRepo.Owner.Name, pr.BaseRepo.Name)); err != nil {
return fmt.Errorf("AddRemote: %v", err)
}
defer func() {
headGitRepo.RemoveRemote(tmpRemote)
}()
remoteBranch := "remotes/" + tmpRemote + "/" + pr.BaseBranch
pr.MergeBase, err = headGitRepo.GetMergeBase(remoteBranch, pr.HeadBranch)
if err != nil {
return fmt.Errorf("GetMergeBase: %v", err)
} else if err = pr.Update(); err != nil {
return fmt.Errorf("Update: %v", err)
}
patch, err := headGitRepo.GetPatch(pr.MergeBase, pr.HeadBranch)
if err != nil {
return fmt.Errorf("GetPatch: %v", err)
}
if err = pr.BaseRepo.SavePatch(pr.Index, patch); err != nil {
return fmt.Errorf("BaseRepo.SavePatch: %v", err)
}
return nil
}
// AddToTaskQueue adds itself to pull request test task queue.
func (pr *PullRequest) AddToTaskQueue() {
go PullRequestQueue.AddFunc(pr.ID, func() {
pr.Status = PULL_REQUEST_STATUS_CHECKING
if err := pr.UpdateCols("status"); err != nil {
log.Error(5, "AddToTaskQueue.UpdateCols[%d].(add to queue): %v", pr.ID, err)
}
})
}
func addHeadRepoTasks(prs []*PullRequest) {
for _, pr := range prs {
log.Trace("addHeadRepoTasks[%d]: composing new test task", pr.ID)
if err := pr.UpdatePatch(); err != nil {
log.Error(4, "UpdatePatch: %v", err)
continue
}
pr.AddToTaskQueue()
}
}
// AddTestPullRequestTask adds new test tasks by given head/base repository and head/base branch,
// and generate new patch for testing as needed.
func AddTestPullRequestTask(repoID int64, branch string) {
log.Trace("AddTestPullRequestTask[head_repo_id: %d, head_branch: %s]: finding pull requests", repoID, branch)
prs, err := GetUnmergedPullRequestsByHeadInfo(repoID, branch)
if err != nil {
log.Error(4, "Find pull requests[head_repo_id: %d, head_branch: %s]: %v", repoID, branch, err)
return
}
addHeadRepoTasks(prs)
log.Trace("AddTestPullRequestTask[base_repo_id: %d, base_branch: %s]: finding pull requests", repoID, branch)
prs, err = GetUnmergedPullRequestsByBaseInfo(repoID, branch)
if err != nil {
log.Error(4, "Find pull requests[base_repo_id: %d, base_branch: %s]: %v", repoID, branch, err)
return
}
for _, pr := range prs {
pr.AddToTaskQueue()
}
}
// checkAndUpdateStatus checks if pull request is possible to levaing checking status,
// and set to be either conflict or mergeable.
func (pr *PullRequest) checkAndUpdateStatus() {
// Status is not changed to conflict means mergeable.
if pr.Status == PULL_REQUEST_STATUS_CHECKING {
pr.Status = PULL_REQUEST_STATUS_MERGEABLE
}
// Make sure there is no waiting test to process before levaing the checking status.
if !PullRequestQueue.Exist(pr.ID) {
if err := pr.UpdateCols("status"); err != nil {
log.Error(4, "Update[%d]: %v", pr.ID, err)
}
}
}
// TestPullRequests checks and tests untested patches of pull requests.
// TODO: test more pull requests at same time.
func TestPullRequests() {
prs := make([]*PullRequest, 0, 10)
x.Iterate(PullRequest{
Status: PULL_REQUEST_STATUS_CHECKING,
},
func(idx int, bean interface{}) error {
pr := bean.(*PullRequest)
if err := pr.GetBaseRepo(); err != nil {
log.Error(3, "GetBaseRepo: %v", err)
return nil
}
if err := pr.testPatch(); err != nil {
log.Error(3, "testPatch: %v", err)
return nil
}
prs = append(prs, pr)
return nil
})
// Update pull request status.
for _, pr := range prs {
pr.checkAndUpdateStatus()
}
// Start listening on new test requests.
for prID := range PullRequestQueue.Queue() {
log.Trace("TestPullRequests[%v]: processing test task", prID)
PullRequestQueue.Remove(prID)
pr, err := GetPullRequestByID(com.StrTo(prID).MustInt64())
if err != nil {
log.Error(4, "GetPullRequestByID[%d]: %v", prID, err)
continue
} else if err = pr.testPatch(); err != nil {
log.Error(4, "testPatch[%d]: %v", pr.ID, err)
continue
}
pr.checkAndUpdateStatus()
}
}
func InitTestPullRequests() {
go TestPullRequests()
}

View File

@@ -5,7 +5,7 @@
package models
import (
"errors"
"fmt"
"sort"
"strings"
"time"
@@ -13,18 +13,14 @@ import (
"github.com/go-xorm/xorm"
"github.com/gogits/gogs/modules/git"
)
var (
ErrReleaseAlreadyExist = errors.New("Release already exist")
ErrReleaseNotExist = errors.New("Release does not exist")
"github.com/gogits/gogs/modules/process"
)
// Release represents a release of repository.
type Release struct {
Id int64
RepoId int64
PublisherId int64
ID int64 `xorm:"pk autoincr"`
RepoID int64
PublisherID int64
Publisher *User `xorm:"-"`
TagName string
LowerTagName string
@@ -47,12 +43,16 @@ func (r *Release) AfterSet(colName string, _ xorm.Cell) {
}
// IsReleaseExist returns true if release with given tag name already exists.
func IsReleaseExist(repoId int64, tagName string) (bool, error) {
func IsReleaseExist(repoID int64, tagName string) (bool, error) {
if len(tagName) == 0 {
return false, nil
}
return x.Get(&Release{RepoId: repoId, LowerTagName: strings.ToLower(tagName)})
return x.Get(&Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)})
}
func init() {
git.GetVersion()
}
func createTag(gitRepo *git.Repository, rel *Release) error {
@@ -64,7 +64,7 @@ func createTag(gitRepo *git.Repository, rel *Release) error {
return err
}
if err = gitRepo.CreateTag(rel.TagName, commit.Id.String()); err != nil {
if err = gitRepo.CreateTag(rel.TagName, commit.ID.String()); err != nil {
return err
}
} else {
@@ -84,11 +84,11 @@ func createTag(gitRepo *git.Repository, rel *Release) error {
// CreateRelease creates a new release of repository.
func CreateRelease(gitRepo *git.Repository, rel *Release) error {
isExist, err := IsReleaseExist(rel.RepoId, rel.TagName)
isExist, err := IsReleaseExist(rel.RepoID, rel.TagName)
if err != nil {
return err
} else if isExist {
return ErrReleaseAlreadyExist
return ErrReleaseAlreadyExist{rel.TagName}
}
if err = createTag(gitRepo, rel); err != nil {
@@ -100,22 +100,35 @@ func CreateRelease(gitRepo *git.Repository, rel *Release) error {
}
// GetRelease returns release by given ID.
func GetRelease(repoId int64, tagName string) (*Release, error) {
isExist, err := IsReleaseExist(repoId, tagName)
func GetRelease(repoID int64, tagName string) (*Release, error) {
isExist, err := IsReleaseExist(repoID, tagName)
if err != nil {
return nil, err
} else if !isExist {
return nil, ErrReleaseNotExist
return nil, ErrReleaseNotExist{0, tagName}
}
rel := &Release{RepoId: repoId, LowerTagName: strings.ToLower(tagName)}
rel := &Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)}
_, err = x.Get(rel)
return rel, err
}
// GetReleasesByRepoId returns a list of releases of repository.
func GetReleasesByRepoId(repoId int64) (rels []*Release, err error) {
err = x.Desc("created").Find(&rels, Release{RepoId: repoId})
// GetReleaseByID returns release with given ID.
func GetReleaseByID(id int64) (*Release, error) {
rel := new(Release)
has, err := x.Id(id).Get(rel)
if err != nil {
return nil, err
} else if !has {
return nil, ErrReleaseNotExist{id, ""}
}
return rel, nil
}
// GetReleasesByRepoID returns a list of releases of repository.
func GetReleasesByRepoID(repoID int64) (rels []*Release, err error) {
err = x.Desc("created").Find(&rels, Release{RepoID: repoID})
return rels, err
}
@@ -150,6 +163,32 @@ func UpdateRelease(gitRepo *git.Repository, rel *Release) (err error) {
if err = createTag(gitRepo, rel); err != nil {
return err
}
_, err = x.Id(rel.Id).AllCols().Update(rel)
_, err = x.Id(rel.ID).AllCols().Update(rel)
return err
}
// DeleteReleaseByID deletes a release and corresponding Git tag by given ID.
func DeleteReleaseByID(id int64) error {
rel, err := GetReleaseByID(id)
if err != nil {
return fmt.Errorf("GetReleaseByID: %v", err)
}
repo, err := GetRepositoryByID(rel.RepoID)
if err != nil {
return fmt.Errorf("GetRepositoryByID: %v", err)
}
_, stderr, err := process.ExecDir(-1, repo.RepoPath(),
fmt.Sprintf("DeleteReleaseByID (git tag -d): %d", rel.ID),
"git", "tag", "-d", rel.TagName)
if err != nil && !strings.Contains(stderr, "not found") {
return fmt.Errorf("git tag -d: %v - %s", err, stderr)
}
if _, err = x.Id(rel.ID).Delete(new(Release)); err != nil {
return fmt.Errorf("Delete: %v", err)
}
return nil
}

View File

@@ -17,16 +17,21 @@ import (
"regexp"
"sort"
"strings"
"sync"
"time"
"unicode/utf8"
"github.com/Unknwon/cae/zip"
"github.com/Unknwon/com"
"github.com/go-xorm/xorm"
"github.com/mcuadros/go-version"
"gopkg.in/ini.v1"
"github.com/gogits/git-shell"
api "github.com/gogits/go-gogs-client"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/bindata"
"github.com/gogits/gogs/modules/git"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/process"
"github.com/gogits/gogs/modules/setting"
@@ -46,6 +51,9 @@ var (
var (
Gitignores, Licenses, Readmes []string
// Maximum items per page in forks, watchers and stars of a repo
ItemsPerPage = 40
)
func LoadRepoConfig() {
@@ -90,16 +98,13 @@ func NewRepoContext() {
}
// Check Git version.
ver, err := git.GetVersion()
gitVer, err := git.Version()
if err != nil {
log.Fatal(4, "Fail to get Git version: %v", err)
}
reqVer, err := git.ParseVersion("1.7.1")
if err != nil {
log.Fatal(4, "Fail to parse required Git version: %v", err)
}
if ver.LessThan(reqVer) {
log.Info("Git Version: %s", gitVer)
if version.Compare("1.7.1", gitVer, ">") {
log.Fatal(4, "Gogs requires Git version greater or equal to 1.7.1")
}
@@ -157,6 +162,14 @@ type Repository struct {
IsMirror bool
*Mirror `xorm:"-"`
// Advanced settings
EnableWiki bool `xorm:"NOT NULL DEFAULT true"`
EnableIssues bool `xorm:"NOT NULL DEFAULT true"`
EnableExternalTracker bool
ExternalTrackerFormat string
ExternalMetas map[string]string `xorm:"-"`
EnablePulls bool `xorm:"NOT NULL DEFAULT true"`
IsFork bool `xorm:"NOT NULL DEFAULT false"`
ForkID int64
BaseRepo *Repository `xorm:"-"`
@@ -179,9 +192,11 @@ func (repo *Repository) AfterSet(colName string, _ xorm.Cell) {
}
func (repo *Repository) getOwner(e Engine) (err error) {
if repo.Owner == nil {
repo.Owner, err = getUserByID(e, repo.OwnerID)
if repo.Owner != nil {
return nil
}
repo.Owner, err = getUserByID(e, repo.OwnerID)
return err
}
@@ -189,6 +204,39 @@ func (repo *Repository) GetOwner() error {
return repo.getOwner(x)
}
func (repo *Repository) mustOwner(e Engine) *User {
if err := repo.getOwner(e); err != nil {
return &User{
Name: "error",
FullName: err.Error(),
}
}
return repo.Owner
}
// MustOwner always returns a valid *User object to avoid
// conceptually impossible error handling.
// It creates a fake object that contains error deftail
// when error occurs.
func (repo *Repository) MustOwner() *User {
return repo.mustOwner(x)
}
// ComposeMetas composes a map of metas for rendering external issue tracker URL.
func (repo *Repository) ComposeMetas() map[string]string {
if !repo.EnableExternalTracker {
return nil
} else if repo.ExternalMetas == nil {
repo.ExternalMetas = map[string]string{
"format": repo.ExternalTrackerFormat,
"user": repo.MustOwner().Name,
"repo": repo.Name,
}
}
return repo.ExternalMetas
}
// GetAssignees returns all users that have write access of repository.
func (repo *Repository) GetAssignees() (_ []*User, err error) {
if err = repo.GetOwner(); err != nil {
@@ -245,22 +293,16 @@ func (repo *Repository) GetBaseRepo() (err error) {
return err
}
func (repo *Repository) repoPath(e Engine) (string, error) {
if err := repo.getOwner(e); err != nil {
return "", err
}
return RepoPath(repo.Owner.Name, repo.Name), nil
func (repo *Repository) repoPath(e Engine) string {
return RepoPath(repo.mustOwner(e).Name, repo.Name)
}
func (repo *Repository) RepoPath() (string, error) {
func (repo *Repository) RepoPath() string {
return repo.repoPath(x)
}
func (repo *Repository) RepoLink() (string, error) {
if err := repo.GetOwner(); err != nil {
return "", err
}
return setting.AppSubUrl + "/" + repo.Owner.Name + "/" + repo.Name, nil
func (repo *Repository) RepoLink() string {
return setting.AppSubUrl + "/" + repo.MustOwner().Name + "/" + repo.Name
}
func (repo *Repository) HasAccess(u *User) bool {
@@ -272,6 +314,11 @@ func (repo *Repository) IsOwnedBy(userID int64) bool {
return repo.OwnerID == userID
}
// CanBeForked returns true if repository meets the requirements of being forked.
func (repo *Repository) CanBeForked() bool {
return !repo.IsBare && !repo.IsMirror
}
func (repo *Repository) NextIssueIndex() int64 {
return int64(repo.NumIssues+repo.NumPulls) + 1
}
@@ -288,6 +335,73 @@ func (repo *Repository) DescriptionHtml() template.HTML {
return template.HTML(DescPattern.ReplaceAllStringFunc(base.Sanitizer.Sanitize(repo.Description), sanitize))
}
func (repo *Repository) LocalCopyPath() string {
return path.Join(setting.AppDataPath, "tmp/local", com.ToStr(repo.ID))
}
func updateLocalCopy(repoPath, localPath string) error {
if !com.IsExist(localPath) {
if err := git.Clone(repoPath, localPath); err != nil {
return fmt.Errorf("Clone: %v", err)
}
} else {
if err := git.Pull(localPath, true); err != nil {
return fmt.Errorf("Pull: %v", err)
}
}
return nil
}
// UpdateLocalCopy makes sure the local copy of repository is up-to-date.
func (repo *Repository) UpdateLocalCopy() error {
return updateLocalCopy(repo.RepoPath(), repo.LocalCopyPath())
}
// PatchPath returns corresponding patch file path of repository by given issue ID.
func (repo *Repository) PatchPath(index int64) (string, error) {
if err := repo.GetOwner(); err != nil {
return "", err
}
return filepath.Join(RepoPath(repo.Owner.Name, repo.Name), "pulls", com.ToStr(index)+".patch"), nil
}
// SavePatch saves patch data to corresponding location by given issue ID.
func (repo *Repository) SavePatch(index int64, patch []byte) error {
patchPath, err := repo.PatchPath(index)
if err != nil {
return fmt.Errorf("PatchPath: %v", err)
}
os.MkdirAll(filepath.Dir(patchPath), os.ModePerm)
if err = ioutil.WriteFile(patchPath, patch, 0644); err != nil {
return fmt.Errorf("WriteFile: %v", err)
}
return nil
}
// ComposePayload composes and returns *api.PayloadRepo corresponding to the repository.
func (repo *Repository) ComposePayload() *api.PayloadRepo {
cl := repo.CloneLink()
return &api.PayloadRepo{
ID: repo.ID,
Name: repo.LowerName,
URL: repo.RepoLink(),
SSHURL: cl.SSH,
CloneURL: cl.HTTPS,
Description: repo.Description,
Website: repo.Website,
Watchers: repo.NumWatches,
Owner: &api.PayloadAuthor{
Name: repo.MustOwner().DisplayName(),
Email: repo.MustOwner().Email,
UserName: repo.MustOwner().Name,
},
Private: repo.IsPrivate,
}
}
func isRepositoryExist(e Engine, u *User, repoName string) (bool, error) {
has, err := e.Get(&Repository{
OwnerID: u.Id,
@@ -308,24 +422,31 @@ type CloneLink struct {
Git string
}
// CloneLink returns clone URLs of repository.
func (repo *Repository) CloneLink() (cl CloneLink, err error) {
if err = repo.GetOwner(); err != nil {
return cl, err
func (repo *Repository) cloneLink(isWiki bool) *CloneLink {
repoName := repo.Name
if isWiki {
repoName += ".wiki"
}
repo.Owner = repo.MustOwner()
cl := new(CloneLink)
if setting.SSHPort != 22 {
cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", setting.RunUser, setting.SSHDomain, setting.SSHPort, repo.Owner.LowerName, repo.LowerName)
cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", setting.RunUser, setting.SSHDomain, setting.SSHPort, repo.Owner.Name, repoName)
} else {
cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", setting.RunUser, setting.SSHDomain, repo.Owner.LowerName, repo.LowerName)
cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", setting.RunUser, setting.SSHDomain, repo.Owner.Name, repoName)
}
cl.HTTPS = fmt.Sprintf("%s%s/%s.git", setting.AppUrl, repo.Owner.LowerName, repo.LowerName)
return cl, nil
cl.HTTPS = fmt.Sprintf("%s%s/%s.git", setting.AppUrl, repo.Owner.Name, repoName)
return cl
}
// CloneLink returns clone URLs of repository.
func (repo *Repository) CloneLink() (cl *CloneLink) {
return repo.cloneLink(false)
}
var (
reservedNames = []string{"debug", "raw", "install", "api", "avatar", "user", "org", "help", "stars", "issues", "pulls", "commits", "repo", "template", "admin", "new"}
reservedPatterns = []string{"*.git", "*.keys"}
reservedPatterns = []string{"*.git", "*.keys", "*.wiki"}
)
// IsUsableName checks if name is reserved or pattern of name is not allowed.
@@ -397,6 +518,11 @@ func UpdateMirror(m *Mirror) error {
return updateMirror(x, m)
}
func createUpdateHook(repoPath string) error {
return git.SetUpdateHook(repoPath,
fmt.Sprintf(_TPL_UPDATE_HOOK, setting.ScriptType, "\""+setting.AppPath+"\"", setting.CustomConf))
}
// MirrorRepository creates a mirror repository from source.
func MirrorRepository(repoId int64, userName, repoName, repoPath, url string) error {
_, stderr, err := process.ExecTimeout(10*time.Minute,
@@ -416,13 +542,21 @@ func MirrorRepository(repoId int64, userName, repoName, repoPath, url string) er
return nil
}
type MigrateRepoOptions struct {
Name string
Description string
IsPrivate bool
IsMirror bool
RemoteAddr string
}
// MigrateRepository migrates a existing repository from other project hosting.
func MigrateRepository(u *User, name, desc string, private, mirror bool, url string) (*Repository, error) {
func MigrateRepository(u *User, opts MigrateRepoOptions) (*Repository, error) {
repo, err := CreateRepository(u, CreateRepoOptions{
Name: name,
Description: desc,
IsPrivate: private,
IsMirror: mirror,
Name: opts.Name,
Description: opts.Description,
IsPrivate: opts.IsPrivate,
IsMirror: opts.IsMirror,
})
if err != nil {
return nil, err
@@ -432,7 +566,7 @@ func MigrateRepository(u *User, name, desc string, private, mirror bool, url str
tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()))
os.MkdirAll(tmpDir, os.ModePerm)
repoPath := RepoPath(u.Name, name)
repoPath := RepoPath(u.Name, opts.Name)
if u.IsOrganization() {
t, err := u.GetOwnerTeam()
@@ -445,8 +579,8 @@ func MigrateRepository(u *User, name, desc string, private, mirror bool, url str
}
repo.IsBare = false
if mirror {
if err = MirrorRepository(repo.ID, u.Name, repo.Name, repoPath, url); err != nil {
if opts.IsMirror {
if err = MirrorRepository(repo.ID, u.Name, repo.Name, repoPath, opts.RemoteAddr); err != nil {
return repo, err
}
repo.IsMirror = true
@@ -458,20 +592,47 @@ func MigrateRepository(u *User, name, desc string, private, mirror bool, url str
// FIXME: this command could for both migrate and mirror
_, stderr, err := process.ExecTimeout(10*time.Minute,
fmt.Sprintf("MigrateRepository: %s", repoPath),
"git", "clone", "--mirror", "--bare", "--quiet", url, repoPath)
"git", "clone", "--mirror", "--bare", "--quiet", opts.RemoteAddr, repoPath)
if err != nil {
return repo, fmt.Errorf("git clone --mirror --bare --quiet: %v", stderr)
} else if err = createUpdateHook(repoPath); err != nil {
return repo, fmt.Errorf("create update hook: %v", err)
}
// Check if repository has master branch, if so set it to default branch.
// Clean up mirror info which prevents "push --all".
configPath := filepath.Join(repoPath, "/config")
cfg, err := ini.Load(configPath)
if err != nil {
return repo, fmt.Errorf("open config file: %v", err)
}
cfg.DeleteSection("remote \"origin\"")
if err = cfg.SaveToIndent(configPath, "\t"); err != nil {
return repo, fmt.Errorf("save config file: %v", err)
}
// Check if repository is empty.
_, stderr, err = com.ExecCmdDir(repoPath, "git", "log", "-1")
if err != nil {
if strings.Contains(stderr, "fatal: bad default revision 'HEAD'") {
repo.IsBare = true
} else {
return repo, fmt.Errorf("check bare: %v - %s", err, stderr)
}
}
// Try to get HEAD branch and set it as default branch.
gitRepo, err := git.OpenRepository(repoPath)
if err != nil {
return repo, fmt.Errorf("open git repository: %v", err)
log.Error(4, "OpenRepository: %v", err)
return repo, nil
}
if gitRepo.IsBranchExist("master") {
repo.DefaultBranch = "master"
headBranch, err := gitRepo.GetHEADBranch()
if err != nil {
log.Error(4, "GetHEADBranch: %v", err)
return repo, nil
}
if headBranch != nil {
repo.DefaultBranch = headBranch.Name
}
return repo, UpdateRepository(repo, false)
@@ -481,33 +642,26 @@ func MigrateRepository(u *User, name, desc string, private, mirror bool, url str
func initRepoCommit(tmpPath string, sig *git.Signature) (err error) {
var stderr string
if _, stderr, err = process.ExecDir(-1,
tmpPath, fmt.Sprintf("initRepoCommit(git add): %s", tmpPath),
tmpPath, fmt.Sprintf("initRepoCommit (git add): %s", tmpPath),
"git", "add", "--all"); err != nil {
return fmt.Errorf("git add: %s", stderr)
}
if _, stderr, err = process.ExecDir(-1,
tmpPath, fmt.Sprintf("initRepoCommit(git commit): %s", tmpPath),
tmpPath, fmt.Sprintf("initRepoCommit (git commit): %s", tmpPath),
"git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
"-m", "initial commit"); err != nil {
return fmt.Errorf("git commit: %s", stderr)
}
if _, stderr, err = process.ExecDir(-1,
tmpPath, fmt.Sprintf("initRepoCommit(git push): %s", tmpPath),
tmpPath, fmt.Sprintf("initRepoCommit (git push): %s", tmpPath),
"git", "push", "origin", "master"); err != nil {
return fmt.Errorf("git push: %s", stderr)
}
return nil
}
func createUpdateHook(repoPath string) error {
hookPath := path.Join(repoPath, "hooks/update")
os.MkdirAll(path.Dir(hookPath), os.ModePerm)
return ioutil.WriteFile(hookPath,
[]byte(fmt.Sprintf(_TPL_UPDATE_HOOK, setting.ScriptType, "\""+appPath+"\"", setting.CustomConf)), 0777)
}
type CreateRepoOptions struct {
Name string
Description string
@@ -544,10 +698,7 @@ func prepareRepoCommit(repo *Repository, tmpDir, repoPath string, opts CreateRep
return fmt.Errorf("getRepoInitFile[%s]: %v", opts.Readme, err)
}
cloneLink, err := repo.CloneLink()
if err != nil {
return fmt.Errorf("CloneLink: %v", err)
}
cloneLink := repo.CloneLink()
match := map[string]string{
"Name": repo.Name,
"Description": repo.Description,
@@ -596,26 +747,21 @@ func prepareRepoCommit(repo *Repository, tmpDir, repoPath string, opts CreateRep
}
// InitRepository initializes README and .gitignore if needed.
func initRepository(e Engine, repoPath string, u *User, repo *Repository, opts CreateRepoOptions) error {
func initRepository(e Engine, repoPath string, u *User, repo *Repository, opts CreateRepoOptions) (err error) {
// Somehow the directory could exist.
if com.IsExist(repoPath) {
return fmt.Errorf("initRepository: path already exists: %s", repoPath)
}
// Init bare new repository.
os.MkdirAll(repoPath, os.ModePerm)
_, stderr, err := process.ExecDir(-1, repoPath,
fmt.Sprintf("initRepository(git init --bare): %s", repoPath), "git", "init", "--bare")
if err != nil {
return fmt.Errorf("git init --bare: %v - %s", err, stderr)
}
if err := createUpdateHook(repoPath); err != nil {
return err
if err = git.InitRepository(repoPath, true); err != nil {
return fmt.Errorf("InitRepository: %v", err)
} else if err = createUpdateHook(repoPath); err != nil {
return fmt.Errorf("createUpdateHook: %v", err)
}
tmpDir := filepath.Join(os.TempDir(), "gogs-"+repo.Name+"-"+com.ToStr(time.Now().Nanosecond()))
fmt.Println(tmpDir)
// Initialize repository according to user's choice.
if opts.AutoInit {
os.MkdirAll(tmpDir, os.ModePerm)
@@ -699,12 +845,15 @@ func createRepository(e *xorm.Session, u *User, repo *Repository) (err error) {
// CreateRepository creates a repository for given user or organization.
func CreateRepository(u *User, opts CreateRepoOptions) (_ *Repository, err error) {
repo := &Repository{
OwnerID: u.Id,
Owner: u,
Name: opts.Name,
LowerName: strings.ToLower(opts.Name),
Description: opts.Description,
IsPrivate: opts.IsPrivate,
OwnerID: u.Id,
Owner: u,
Name: opts.Name,
LowerName: strings.ToLower(opts.Name),
Description: opts.Description,
IsPrivate: opts.IsPrivate,
EnableWiki: true,
EnableIssues: true,
EnablePulls: true,
}
sess := x.NewSession()
@@ -761,21 +910,16 @@ func CountPublicRepositories() int64 {
return countRepositories(false)
}
// GetRepositoriesWithUsers returns given number of repository objects with offset.
// It also auto-gets corresponding users.
func GetRepositoriesWithUsers(num, offset int) ([]*Repository, error) {
repos := make([]*Repository, 0, num)
if err := x.Limit(num, offset).Asc("id").Find(&repos); err != nil {
// RepositoriesWithUsers returns number of repos in given page.
func RepositoriesWithUsers(page, pageSize int) (_ []*Repository, err error) {
repos := make([]*Repository, 0, pageSize)
if err = x.Limit(pageSize, (page-1)*pageSize).Asc("id").Find(&repos); err != nil {
return nil, err
}
for _, repo := range repos {
repo.Owner = &User{Id: repo.OwnerID}
has, err := x.Get(repo.Owner)
if err != nil {
for i := range repos {
if err = repos[i].GetOwner(); err != nil {
return nil, err
} else if !has {
return nil, ErrUserNotExist{repo.OwnerID, ""}
}
}
@@ -821,9 +965,9 @@ func TransferOwnership(u *User, newOwnerName string, repo *Repository) error {
}
// Remove redundant collaborators.
collaborators, err := repo.GetCollaborators()
collaborators, err := repo.getCollaborators(sess)
if err != nil {
return fmt.Errorf("GetCollaborators: %v", err)
return fmt.Errorf("getCollaborators: %v", err)
}
// Dummy object.
@@ -859,9 +1003,9 @@ func TransferOwnership(u *User, newOwnerName string, repo *Repository) error {
}
if newOwner.IsOrganization() {
t, err := newOwner.GetOwnerTeam()
t, err := newOwner.getOwnerTeam(sess)
if err != nil {
return fmt.Errorf("GetOwnerTeam: %v", err)
return fmt.Errorf("getOwnerTeam: %v", err)
} else if err = t.addRepository(sess, repo); err != nil {
return fmt.Errorf("add to owner team: %v", err)
}
@@ -887,7 +1031,14 @@ func TransferOwnership(u *User, newOwnerName string, repo *Repository) error {
// Change repository directory name.
if err = os.Rename(RepoPath(owner.Name, repo.Name), RepoPath(newOwner.Name, repo.Name)); err != nil {
return fmt.Errorf("rename directory: %v", err)
return fmt.Errorf("rename repository directory: %v", err)
}
wikiPath := WikiPath(owner.Name, repo.Name)
if com.IsExist(wikiPath) {
if err = os.Rename(wikiPath, WikiPath(newOwner.Name, repo.Name)); err != nil {
return fmt.Errorf("rename repository wiki: %v", err)
}
}
return sess.Commit()
@@ -909,7 +1060,18 @@ func ChangeRepositoryName(u *User, oldRepoName, newRepoName string) (err error)
}
// Change repository directory name.
return os.Rename(RepoPath(u.LowerName, oldRepoName), RepoPath(u.LowerName, newRepoName))
if err = os.Rename(RepoPath(u.Name, oldRepoName), RepoPath(u.Name, newRepoName)); err != nil {
return fmt.Errorf("rename repository directory: %v", err)
}
wikiPath := WikiPath(u.Name, oldRepoName)
if com.IsExist(wikiPath) {
if err = os.Rename(wikiPath, WikiPath(u.Name, newRepoName)); err != nil {
return fmt.Errorf("rename repository wiki: %v", err)
}
}
return nil
}
func getRepositoriesByForkID(e Engine, forkID int64) ([]*Repository, error) {
@@ -940,13 +1102,11 @@ func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err e
if err = repo.getOwner(e); err != nil {
return fmt.Errorf("getOwner: %v", err)
}
if !repo.Owner.IsOrganization() {
return nil
}
// Organization repository need to recalculate access table when visivility is changed.
if err = repo.recalculateTeamAccesses(e, 0); err != nil {
return fmt.Errorf("recalculateTeamAccesses: %v", err)
if repo.Owner.IsOrganization() {
// Organization repository need to recalculate access table when visivility is changed.
if err = repo.recalculateTeamAccesses(e, 0); err != nil {
return fmt.Errorf("recalculateTeamAccesses: %v", err)
}
}
forkRepos, err := getRepositoriesByForkID(e, repo.ID)
@@ -1015,26 +1175,20 @@ func DeleteRepository(uid, repoID int64) error {
}
}
if _, err = sess.Delete(&Repository{ID: repoID}); err != nil {
return err
} else if _, err = sess.Delete(&Access{RepoID: repo.ID}); err != nil {
return err
} else if _, err = sess.Delete(&Action{RepoID: repo.ID}); err != nil {
return err
} else if _, err = sess.Delete(&Watch{RepoID: repoID}); err != nil {
return err
} else if _, err = sess.Delete(&Mirror{RepoID: repoID}); err != nil {
return err
} else if _, err = sess.Delete(&IssueUser{RepoID: repoID}); err != nil {
return err
} else if _, err = sess.Delete(&Milestone{RepoID: repoID}); err != nil {
return err
} else if _, err = sess.Delete(&Release{RepoId: repoID}); err != nil {
return err
} else if _, err = sess.Delete(&Collaboration{RepoID: repoID}); err != nil {
return err
} else if _, err = sess.Delete(&PullRequest{BaseRepoID: repoID}); err != nil {
return err
if err = deleteBeans(sess,
&Repository{ID: repoID},
&Access{RepoID: repo.ID},
&Action{RepoID: repo.ID},
&Watch{RepoID: repoID},
&Star{RepoID: repoID},
&Mirror{RepoID: repoID},
&IssueUser{RepoID: repoID},
&Milestone{RepoID: repoID},
&Release{RepoID: repoID},
&Collaboration{RepoID: repoID},
&PullRequest{BaseRepoID: repoID},
); err != nil {
return fmt.Errorf("deleteBeans: %v", err)
}
// Delete comments and attachments.
@@ -1076,15 +1230,21 @@ func DeleteRepository(uid, repoID int64) error {
}
// Remove repository files.
repoPath, err := repo.repoPath(sess)
if err != nil {
return fmt.Errorf("RepoPath: %v", err)
}
repoPath := repo.repoPath(sess)
if err = os.RemoveAll(repoPath); err != nil {
desc := fmt.Sprintf("delete repository files[%s]: %v", repoPath, err)
desc := fmt.Sprintf("delete repository files [%s]: %v", repoPath, err)
log.Warn(desc)
if err = CreateRepositoryNotice(desc); err != nil {
log.Error(4, "add notice: %v", err)
log.Error(4, "CreateRepositoryNotice: %v", err)
}
}
wikiPath := repo.WikiPath()
if err = os.RemoveAll(wikiPath); err != nil {
desc := fmt.Sprintf("delete repository wiki [%s]: %v", wikiPath, err)
log.Warn(desc)
if err = CreateRepositoryNotice(desc); err != nil {
log.Error(4, "CreateRepositoryNotice: %v", err)
}
}
@@ -1185,9 +1345,13 @@ func GetRecentUpdatedRepositories(page int) (repos []*Repository, err error) {
Where("is_private=?", false).Limit(setting.ExplorePagingNum).Desc("updated").Find(&repos)
}
func getRepositoryCount(e Engine, u *User) (int64, error) {
return x.Count(&Repository{OwnerID: u.Id})
}
// GetRepositoryCount returns the total number of repositories of user.
func GetRepositoryCount(u *User) (int64, error) {
return x.Count(&Repository{OwnerID: u.Id})
return getRepositoryCount(x, u)
}
type SearchOption struct {
@@ -1212,7 +1376,7 @@ func SearchRepositoryByName(opt SearchOption) (repos []*Repository, err error) {
sess.Where("owner_id=?", opt.Uid)
}
if !opt.Private {
sess.And("is_private=false")
sess.And("is_private=?", false)
}
sess.And("lower_name like ?", "%"+opt.Keyword+"%").Find(&repos)
return repos, err
@@ -1223,39 +1387,99 @@ func DeleteRepositoryArchives() error {
return x.Where("id > 0").Iterate(new(Repository),
func(idx int, bean interface{}) error {
repo := bean.(*Repository)
if err := repo.GetOwner(); err != nil {
return err
}
return os.RemoveAll(filepath.Join(RepoPath(repo.Owner.Name, repo.Name), "archives"))
return os.RemoveAll(filepath.Join(repo.RepoPath(), "archives"))
})
}
// DeleteMissingRepositories deletes all repository records that lost Git files.
func DeleteMissingRepositories() error {
repos := make([]*Repository, 0, 5)
if err := x.Where("id > 0").Iterate(new(Repository),
func(idx int, bean interface{}) error {
repo := bean.(*Repository)
if !com.IsDir(repo.RepoPath()) {
repos = append(repos, repo)
}
return nil
}); err != nil {
if err2 := CreateRepositoryNotice(fmt.Sprintf("DeleteMissingRepositories: %v", err)); err2 != nil {
log.Error(4, "CreateRepositoryNotice: %v", err2)
}
return nil
}
if len(repos) == 0 {
return nil
}
for _, repo := range repos {
log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID)
if err := DeleteRepository(repo.OwnerID, repo.ID); err != nil {
if err2 := CreateRepositoryNotice(fmt.Sprintf("DeleteRepository [%d]: %v", repo.ID, err)); err2 != nil {
log.Error(4, "CreateRepositoryNotice: %v", err2)
}
}
}
return nil
}
// RewriteRepositoryUpdateHook rewrites all repositories' update hook.
func RewriteRepositoryUpdateHook() error {
return x.Where("id > 0").Iterate(new(Repository),
func(idx int, bean interface{}) error {
repo := bean.(*Repository)
if err := repo.GetOwner(); err != nil {
return err
}
return createUpdateHook(RepoPath(repo.Owner.Name, repo.Name))
return createUpdateHook(repo.RepoPath())
})
}
var (
// Prevent duplicate running tasks.
isMirrorUpdating = false
isGitFscking = false
isCheckingRepos = false
// statusPool represents a pool of status with true/false.
type statusPool struct {
lock sync.RWMutex
pool map[string]bool
}
// Start sets value of given name to true in the pool.
func (p *statusPool) Start(name string) {
p.lock.Lock()
defer p.lock.Unlock()
p.pool[name] = true
}
// Stop sets value of given name to false in the pool.
func (p *statusPool) Stop(name string) {
p.lock.Lock()
defer p.lock.Unlock()
p.pool[name] = false
}
// IsRunning checks if value of given name is set to true in the pool.
func (p *statusPool) IsRunning(name string) bool {
p.lock.RLock()
defer p.lock.RUnlock()
return p.pool[name]
}
// Prevent duplicate running tasks.
var taskStatusPool = &statusPool{
pool: make(map[string]bool),
}
const (
_MIRROR_UPDATE = "mirror_update"
_GIT_FSCK = "git_fsck"
_CHECK_REPOs = "check_repos"
)
// MirrorUpdate checks and updates mirror repositories.
func MirrorUpdate() {
if isMirrorUpdating {
if taskStatusPool.IsRunning(_MIRROR_UPDATE) {
return
}
isMirrorUpdating = true
defer func() { isMirrorUpdating = false }()
taskStatusPool.Start(_MIRROR_UPDATE)
defer taskStatusPool.Stop(_MIRROR_UPDATE)
log.Trace("Doing: MirrorUpdate")
@@ -1271,11 +1495,7 @@ func MirrorUpdate() {
return nil
}
repoPath, err := m.Repo.RepoPath()
if err != nil {
return fmt.Errorf("Repo.RepoPath: %v", err)
}
repoPath := m.Repo.RepoPath()
if _, stderr, err := process.ExecDir(10*time.Minute,
repoPath, fmt.Sprintf("MirrorUpdate: %s", repoPath),
"git", "remote", "update", "--prune"); err != nil {
@@ -1303,11 +1523,11 @@ func MirrorUpdate() {
// GitFsck calls 'git fsck' to check repository health.
func GitFsck() {
if isGitFscking {
if taskStatusPool.IsRunning(_GIT_FSCK) {
return
}
isGitFscking = true
defer func() { isGitFscking = false }()
taskStatusPool.Start(_GIT_FSCK)
defer taskStatusPool.Stop(_GIT_FSCK)
log.Trace("Doing: GitFsck")
@@ -1315,12 +1535,8 @@ func GitFsck() {
if err := x.Where("id>0").Iterate(new(Repository),
func(idx int, bean interface{}) error {
repo := bean.(*Repository)
repoPath, err := repo.RepoPath()
if err != nil {
return fmt.Errorf("RepoPath: %v", err)
}
_, _, err = process.ExecDir(-1, repoPath, "Repository health check", "git", args...)
repoPath := repo.RepoPath()
_, _, err := process.ExecDir(-1, repoPath, "Repository health check", "git", args...)
if err != nil {
desc := fmt.Sprintf("Fail to health check repository(%s)", repoPath)
log.Warn(desc)
@@ -1372,11 +1588,11 @@ func repoStatsCheck(checker *repoChecker) {
}
func CheckRepoStats() {
if isCheckingRepos {
if taskStatusPool.IsRunning(_CHECK_REPOs) {
return
}
isCheckingRepos = true
defer func() { isCheckingRepos = false }()
taskStatusPool.Start(_CHECK_REPOs)
defer taskStatusPool.Stop(_CHECK_REPOs)
log.Trace("Doing: CheckRepoStats")
@@ -1405,12 +1621,18 @@ func CheckRepoStats() {
"UPDATE `user` SET num_repos=(SELECT COUNT(*) FROM `repository` WHERE owner_id=?) WHERE id=?",
"user count 'num_repos'",
},
// Issue.NumComments
{
"SELECT `issue`.id FROM `issue` WHERE `issue`.num_comments!=(SELECT COUNT(*) FROM `comment` WHERE issue_id=`issue`.id AND type=0)",
"UPDATE `issue` SET num_comments=(SELECT COUNT(*) FROM `comment` WHERE issue_id=? AND type=0) WHERE id=?",
"issue count 'num_comments'",
},
}
for i := range checkers {
repoStatsCheck(checkers[i])
}
// FIXME: use checker when v0.8, stop supporting old fork repo format.
// FIXME: use checker when v0.9, stop supporting old fork repo format.
// ***** START: Repository.NumForks *****
results, err := x.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_forks!=(SELECT COUNT(*) FROM `repository` WHERE fork_id=repo.id)")
if err != nil {
@@ -1555,15 +1777,19 @@ type Watch struct {
RepoID int64 `xorm:"UNIQUE(watch)"`
}
func isWatching(e Engine, uid, repoId int64) bool {
has, _ := e.Get(&Watch{0, uid, repoId})
return has
}
// IsWatching checks if user has watched given repository.
func IsWatching(uid, repoId int64) bool {
has, _ := x.Get(&Watch{0, uid, repoId})
return has
return isWatching(x, uid, repoId)
}
func watchRepo(e Engine, uid, repoId int64, watch bool) (err error) {
if watch {
if IsWatching(uid, repoId) {
if isWatching(e, uid, repoId) {
return nil
}
if _, err = e.Insert(&Watch{RepoID: repoId, UserID: uid}); err != nil {
@@ -1571,7 +1797,7 @@ func watchRepo(e Engine, uid, repoId int64, watch bool) (err error) {
}
_, err = e.Exec("UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?", repoId)
} else {
if !IsWatching(uid, repoId) {
if !isWatching(e, uid, repoId) {
return nil
}
if _, err = e.Delete(&Watch{0, uid, repoId}); err != nil {
@@ -1587,15 +1813,21 @@ func WatchRepo(uid, repoId int64, watch bool) (err error) {
return watchRepo(x, uid, repoId, watch)
}
func getWatchers(e Engine, rid int64) ([]*Watch, error) {
func getWatchers(e Engine, repoID int64) ([]*Watch, error) {
watches := make([]*Watch, 0, 10)
err := e.Find(&watches, &Watch{RepoID: rid})
return watches, err
return watches, e.Find(&watches, &Watch{RepoID: repoID})
}
// GetWatchers returns all watchers of given repository.
func GetWatchers(rid int64) ([]*Watch, error) {
return getWatchers(x, rid)
func GetWatchers(repoID int64) ([]*Watch, error) {
return getWatchers(x, repoID)
}
// Repository.GetWatchers returns range of users watching given repository.
func (repo *Repository) GetWatchers(page int) ([]*User, error) {
users := make([]*User, 0, ItemsPerPage)
return users, x.Limit(ItemsPerPage, (page-1)*ItemsPerPage).
Where("repo_id=?", repo.ID).Join("LEFT", "watch", "user.id=watch.user_id").Find(&users)
}
func notifyWatchers(e Engine, act *Action) error {
@@ -1639,7 +1871,7 @@ func NotifyWatchers(act *Action) error {
type Star struct {
ID int64 `xorm:"pk autoincr"`
UID int64 `xorm:"uid UNIQUE(s)"`
UID int64 `xorm:"UNIQUE(s)"`
RepoID int64 `xorm:"UNIQUE(s)"`
}
@@ -1675,6 +1907,12 @@ func IsStaring(uid, repoId int64) bool {
return has
}
func (repo *Repository) GetStargazers(page int) ([]*User, error) {
users := make([]*User, 0, ItemsPerPage)
return users, x.Limit(ItemsPerPage, (page-1)*ItemsPerPage).
Where("repo_id=?", repo.ID).Join("LEFT", "star", "user.id=star.uid").Find(&users)
}
// ___________ __
// \_ _____/__________| | __
// | __)/ _ \_ __ \ |/ /
@@ -1716,15 +1954,10 @@ func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Reposit
return nil, err
}
oldRepoPath, err := oldRepo.RepoPath()
if err != nil {
return nil, fmt.Errorf("get old repository path: %v", err)
}
repoPath := RepoPath(u.Name, repo.Name)
_, stderr, err := process.ExecTimeout(10*time.Minute,
fmt.Sprintf("ForkRepository(git clone): %s/%s", u.Name, repo.Name),
"git", "clone", "--bare", oldRepoPath, repoPath)
"git", "clone", "--bare", oldRepo.RepoPath(), repoPath)
if err != nil {
return nil, fmt.Errorf("git clone: %v", stderr)
}
@@ -1742,3 +1975,8 @@ func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Reposit
return repo, sess.Commit()
}
func (repo *Repository) GetForks() ([]*Repository, error) {
forks := make([]*Repository, 0, repo.NumForks)
return forks, x.Find(&forks, &Repository{ForkID: repo.ID})
}

View File

@@ -14,7 +14,7 @@ import (
// AccessToken represents a personal access token.
type AccessToken struct {
ID int64 `xorm:"pk autoincr"`
UID int64 `xorm:"uid INDEX"`
UID int64 `xorm:"INDEX"`
Name string
Sha1 string `xorm:"UNIQUE VARCHAR(40)"`
Created time.Time `xorm:"CREATED"`

View File

@@ -10,49 +10,46 @@ import (
"os/exec"
"strings"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/git"
"github.com/gogits/gogs/modules/log"
)
type UpdateTask struct {
Id int64
Uuid string `xorm:"index"`
ID int64 `xorm:"pk autoincr"`
UUID string `xorm:"index"`
RefName string
OldCommitId string
NewCommitId string
OldCommitID string
NewCommitID string
}
const (
MAX_COMMITS int = 5
)
func AddUpdateTask(task *UpdateTask) error {
_, err := x.Insert(task)
return err
}
func GetUpdateTasksByUuid(uuid string) ([]*UpdateTask, error) {
// GetUpdateTaskByUUID returns update task by given UUID.
func GetUpdateTaskByUUID(uuid string) (*UpdateTask, error) {
task := &UpdateTask{
Uuid: uuid,
UUID: uuid,
}
tasks := make([]*UpdateTask, 0)
err := x.Find(&tasks, task)
has, err := x.Get(task)
if err != nil {
return nil, err
} else if !has {
return nil, ErrUpdateTaskNotExist{uuid}
}
return tasks, nil
return task, nil
}
func DelUpdateTasksByUuid(uuid string) error {
_, err := x.Delete(&UpdateTask{Uuid: uuid})
func DeleteUpdateTaskByUUID(uuid string) error {
_, err := x.Delete(&UpdateTask{UUID: uuid})
return err
}
func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName string, userId int64) error {
isNew := strings.HasPrefix(oldCommitId, "0000000")
func Update(refName, oldCommitID, newCommitID, userName, repoUserName, repoName string, userID int64) error {
isNew := strings.HasPrefix(oldCommitID, "0000000")
if isNew &&
strings.HasPrefix(newCommitId, "0000000") {
strings.HasPrefix(newCommitID, "0000000") {
return fmt.Errorf("old rev and new rev both 000000")
}
@@ -62,23 +59,23 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
gitUpdate.Dir = f
gitUpdate.Run()
isDel := strings.HasPrefix(newCommitId, "0000000")
isDel := strings.HasPrefix(newCommitID, "0000000")
if isDel {
log.GitLogger.Info("del rev", refName, "from", userName+"/"+repoName+".git", "by", userId)
log.GitLogger.Info("del rev", refName, "from", userName+"/"+repoName+".git", "by", userID)
return nil
}
repo, err := git.OpenRepository(f)
gitRepo, err := git.OpenRepository(f)
if err != nil {
return fmt.Errorf("runUpdate.Open repoId: %v", err)
}
ru, err := GetUserByName(repoUserName)
user, err := GetUserByName(repoUserName)
if err != nil {
return fmt.Errorf("runUpdate.GetUserByName: %v", err)
}
repos, err := GetRepositoryByName(ru.Id, repoName)
repo, err := GetRepositoryByName(user.Id, repoName)
if err != nil {
return fmt.Errorf("runUpdate.GetRepositoryByName userId: %v", err)
}
@@ -86,7 +83,7 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
// Push tags.
if strings.HasPrefix(refName, "refs/tags/") {
tagName := git.RefEndName(refName)
tag, err := repo.GetTag(tagName)
tag, err := gitRepo.GetTag(tagName)
if err != nil {
log.GitLogger.Fatal(4, "runUpdate.GetTag: %v", err)
}
@@ -102,16 +99,16 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
actEmail = cmt.Committer.Email
}
commit := &base.PushCommits{}
commit := &PushCommits{}
if err = CommitRepoAction(userId, ru.Id, userName, actEmail,
repos.ID, repoUserName, repoName, refName, commit, oldCommitId, newCommitId); err != nil {
if err = CommitRepoAction(userID, user.Id, userName, actEmail,
repo.ID, repoUserName, repoName, refName, commit, oldCommitID, newCommitID); err != nil {
log.GitLogger.Fatal(4, "CommitRepoAction: %s/%s:%v", repoUserName, repoName, err)
}
return err
}
newCommit, err := repo.GetCommit(newCommitId)
newCommit, err := gitRepo.GetCommit(newCommitID)
if err != nil {
return fmt.Errorf("runUpdate GetCommit of newCommitId: %v", err)
}
@@ -121,12 +118,12 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
if isNew {
l, err = newCommit.CommitsBefore()
if err != nil {
return fmt.Errorf("Find CommitsBefore erro: %v", err)
return fmt.Errorf("CommitsBefore: %v", err)
}
} else {
l, err = newCommit.CommitsBeforeUntil(oldCommitId)
l, err = newCommit.CommitsBeforeUntil(oldCommitID)
if err != nil {
return fmt.Errorf("Find CommitsBeforeUntil erro: %v", err)
return fmt.Errorf("CommitsBeforeUntil: %v", err)
}
}
@@ -135,26 +132,23 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
}
// Push commits.
commits := make([]*base.PushCommit, 0)
commits := make([]*PushCommit, 0)
var actEmail string
for e := l.Front(); e != nil; e = e.Next() {
commit := e.Value.(*git.Commit)
if actEmail == "" {
actEmail = commit.Committer.Email
}
commits = append(commits,
&base.PushCommit{commit.Id.String(),
&PushCommit{commit.ID.String(),
commit.Message(),
commit.Author.Email,
commit.Author.Name})
if len(commits) >= MAX_COMMITS {
break
}
commit.Author.Name,
})
}
if err = CommitRepoAction(userId, ru.Id, userName, actEmail,
repos.ID, repoUserName, repoName, refName, &base.PushCommits{l.Len(), commits, ""}, oldCommitId, newCommitId); err != nil {
if err = CommitRepoAction(userID, user.Id, userName, actEmail,
repo.ID, repoUserName, repoName, refName, &PushCommits{l.Len(), commits, "", nil}, oldCommitID, newCommitID); err != nil {
return fmt.Errorf("runUpdate.models.CommitRepoAction: %s/%s:%v", repoUserName, repoName, err)
}
return nil

View File

@@ -14,6 +14,7 @@ import (
"image"
"image/jpeg"
_ "image/jpeg"
"image/png"
"os"
"path"
"path/filepath"
@@ -24,9 +25,11 @@ import (
"github.com/go-xorm/xorm"
"github.com/nfnt/resize"
"github.com/gogits/git-shell"
"github.com/gogits/gogs/modules/avatar"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/git"
oldgit "github.com/gogits/gogs/modules/git"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/setting"
)
@@ -55,12 +58,13 @@ type User struct {
Name string `xorm:"UNIQUE NOT NULL"`
FullName string
// Email is the primary email address (to be used for communication).
Email string `xorm:"UNIQUE(s) NOT NULL"`
Email string `xorm:"NOT NULL"`
Passwd string `xorm:"NOT NULL"`
LoginType LoginType
LoginSource int64 `xorm:"NOT NULL DEFAULT 0"`
LoginName string
Type UserType `xorm:"UNIQUE(s)"`
Type UserType
OwnedOrgs []*User `xorm:"-"`
Orgs []*User `xorm:"-"`
Repos []*Repository `xorm:"-"`
Location string
@@ -70,13 +74,14 @@ type User struct {
Created time.Time `xorm:"CREATED"`
Updated time.Time `xorm:"UPDATED"`
// Remember visibility choice for convenience.
// Remember visibility choice for convenience, true for private
LastRepoVisibility bool
// Permissions.
IsActive bool
IsAdmin bool
AllowGitHook bool
IsActive bool
IsAdmin bool
AllowGitHook bool
AllowImportLocal bool // Allow migrate repository by local path
// Avatar.
Avatar string `xorm:"VARCHAR(2048) NOT NULL"`
@@ -106,11 +111,27 @@ func (u *User) AfterSet(colName string, _ xorm.Cell) {
}
}
// HasForkedRepo checks if user has already forked a repository with given ID.
func (u *User) HasForkedRepo(repoID int64) bool {
_, has := HasForkedRepo(u.Id, repoID)
return has
}
// CanEditGitHook returns true if user can edit Git hooks.
func (u *User) CanEditGitHook() bool {
return u.IsAdmin || u.AllowGitHook
}
// CanImportLocal returns true if user can migrate repository by local path.
func (u *User) CanImportLocal() bool {
return u.IsAdmin || u.AllowImportLocal
}
// EmailAdresses is the list of all email addresses of a user. Can contain the
// primary email address, but is not obligatory
type EmailAddress struct {
Id int64
Uid int64 `xorm:"INDEX NOT NULL"`
ID int64 `xorm:"pk autoincr"`
UID int64 `xorm:"INDEX NOT NULL"`
Email string `xorm:"UNIQUE NOT NULL"`
IsActivated bool
IsPrimary bool `xorm:"-"`
@@ -126,48 +147,75 @@ func (u *User) DashboardLink() string {
// HomeLink returns the user or organization home page link.
func (u *User) HomeLink() string {
if u.IsOrganization() {
return setting.AppSubUrl + "/org/" + u.Name
}
return setting.AppSubUrl + "/" + u.Name
}
// GenerateEmailActivateCode generates an activate code based on user information and given e-mail.
func (u *User) GenerateEmailActivateCode(email string) string {
code := base.CreateTimeLimitCode(
com.ToStr(u.Id)+email+u.LowerName+u.Passwd+u.Rands,
setting.Service.ActiveCodeLives, nil)
// Add tail hex username
code += hex.EncodeToString([]byte(u.LowerName))
return code
}
// GenerateActivateCode generates an activate code based on user information.
func (u *User) GenerateActivateCode() string {
return u.GenerateEmailActivateCode(u.Email)
}
// CustomAvatarPath returns user custom avatar file path.
func (u *User) CustomAvatarPath() string {
return filepath.Join(setting.AvatarUploadPath, com.ToStr(u.Id))
}
// GenerateRandomAvatar generates a random avatar for user.
func (u *User) GenerateRandomAvatar() error {
seed := u.Email
if len(seed) == 0 {
seed = u.Name
}
img, err := avatar.RandomImage([]byte(seed))
if err != nil {
return fmt.Errorf("RandomImage: %v", err)
}
if err = os.MkdirAll(path.Dir(u.CustomAvatarPath()), os.ModePerm); err != nil {
return fmt.Errorf("MkdirAll: %v", err)
}
fw, err := os.Create(u.CustomAvatarPath())
if err != nil {
return fmt.Errorf("Create: %v", err)
}
defer fw.Close()
if err = jpeg.Encode(fw, img, nil); err != nil {
return fmt.Errorf("Encode: %v", err)
}
log.Info("New random avatar created: %d", u.Id)
return nil
}
func (u *User) RelAvatarLink() string {
defaultImgUrl := "/img/avatar_default.jpg"
if u.Id == -1 {
return defaultImgUrl
}
imgPath := path.Join(setting.AvatarUploadPath, com.ToStr(u.Id))
switch {
case u.UseCustomAvatar:
if !com.IsExist(imgPath) {
if !com.IsExist(u.CustomAvatarPath()) {
return defaultImgUrl
}
return "/avatars/" + com.ToStr(u.Id)
case setting.DisableGravatar, setting.OfflineMode:
if !com.IsExist(imgPath) {
img, err := avatar.RandomImage([]byte(u.Email))
if err != nil {
log.Error(3, "RandomImage: %v", err)
return defaultImgUrl
if !com.IsExist(u.CustomAvatarPath()) {
if err := u.GenerateRandomAvatar(); err != nil {
log.Error(3, "GenerateRandomAvatar: %v", err)
}
if err = os.MkdirAll(path.Dir(imgPath), os.ModePerm); err != nil {
log.Error(3, "MkdirAll: %v", err)
return defaultImgUrl
}
fw, err := os.Create(imgPath)
if err != nil {
log.Error(3, "Create: %v", err)
return defaultImgUrl
}
defer fw.Close()
if err = jpeg.Encode(fw, img, nil); err != nil {
log.Error(3, "Encode: %v", err)
return defaultImgUrl
}
log.Info("New random avatar created: %d", u.Id)
}
return "/avatars/" + com.ToStr(u.Id)
@@ -208,43 +256,36 @@ func (u *User) ValidatePassword(passwd string) bool {
return u.Passwd == newUser.Passwd
}
// CustomAvatarPath returns user custom avatar file path.
func (u *User) CustomAvatarPath() string {
return filepath.Join(setting.AvatarUploadPath, com.ToStr(u.Id))
}
// UploadAvatar saves custom avatar for user.
// FIXME: split uploads to different subdirs in case we have massive users.
func (u *User) UploadAvatar(data []byte) error {
u.UseCustomAvatar = true
img, _, err := image.Decode(bytes.NewReader(data))
if err != nil {
return err
return fmt.Errorf("Decode: %v", err)
}
m := resize.Resize(234, 234, img, resize.NearestNeighbor)
m := resize.Resize(290, 290, img, resize.NearestNeighbor)
sess := x.NewSession()
defer sess.Close()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.Id(u.Id).AllCols().Update(u); err != nil {
sess.Rollback()
return err
u.UseCustomAvatar = true
if err = updateUser(sess, u); err != nil {
return fmt.Errorf("updateUser: %v", err)
}
os.MkdirAll(setting.AvatarUploadPath, os.ModePerm)
fw, err := os.Create(u.CustomAvatarPath())
if err != nil {
sess.Rollback()
return err
return fmt.Errorf("Create: %v", err)
}
defer fw.Close()
if err = jpeg.Encode(fw, m, nil); err != nil {
sess.Rollback()
return err
if err = png.Encode(fw, m); err != nil {
return fmt.Errorf("Encode: %v", err)
}
return sess.Commit()
@@ -284,9 +325,13 @@ func (u *User) IsPublicMember(orgId int64) bool {
return IsPublicMembership(orgId, u.Id)
}
func (u *User) getOrganizationCount(e Engine) (int64, error) {
return e.Where("uid=?", u.Id).Count(new(OrgUser))
}
// GetOrganizationCount returns count of membership of organization of user.
func (u *User) GetOrganizationCount() (int64, error) {
return x.Where("uid=?", u.Id).Count(new(OrgUser))
return u.getOrganizationCount(x)
}
// GetRepositories returns all repositories that user owns, including private repositories.
@@ -295,6 +340,12 @@ func (u *User) GetRepositories() (err error) {
return err
}
// GetOwnedOrganizations returns all organizations that user owns.
func (u *User) GetOwnedOrganizations() (err error) {
u.OwnedOrgs, err = GetOwnedOrgsByUserID(u.Id)
return err
}
// GetOrganizations returns all organizations that user belongs to.
func (u *User) GetOrganizations() error {
ous, err := GetOrgUsersByUserId(u.Id)
@@ -321,6 +372,15 @@ func (u *User) DisplayName() string {
return u.Name
}
// ShortName returns shorted user name with given maximum length,
// it adds "..." at the end if user name has more length than maximum.
func (u *User) ShortName(length int) string {
if len(u.Name) < length {
return u.Name
}
return u.Name[:length] + "..."
}
// IsUserExist checks if given user name exist,
// the user name should be noncased unique.
// If uid is presented, then check will rule out that one,
@@ -372,6 +432,7 @@ func CreateUser(u *User) (err error) {
return ErrUserAlreadyExist{u.Name}
}
u.Email = strings.ToLower(u.Email)
isExist, err = IsEmailUsed(u.Email)
if err != nil {
return err
@@ -413,11 +474,10 @@ func CountUsers() int64 {
return countUsers(x)
}
// GetUsers returns given number of user objects with offset.
func GetUsers(num, offset int) ([]*User, error) {
users := make([]*User, 0, num)
err := x.Limit(num, offset).Where("type=0").Asc("id").Find(&users)
return users, err
// Users returns number of users in given page.
func Users(page, pageSize int) ([]*User, error) {
users := make([]*User, 0, pageSize)
return users, x.Limit(pageSize, (page-1)*pageSize).Where("type=0").Asc("id").Find(&users)
}
// get user by erify code
@@ -490,12 +550,20 @@ func ChangeUserName(u *User, newUserName string) (err error) {
}
func updateUser(e Engine, u *User) error {
u.Email = strings.ToLower(u.Email)
has, err := e.Where("id!=?", u.Id).And("type=?", u.Type).And("email=?", u.Email).Get(new(User))
if err != nil {
return err
} else if has {
return ErrEmailAlreadyUsed{u.Email}
// Organization does not need e-mail.
if !u.IsOrganization() {
u.Email = strings.ToLower(u.Email)
has, err := e.Where("id!=?", u.Id).And("type=?", u.Type).And("email=?", u.Email).Get(new(User))
if err != nil {
return err
} else if has {
return ErrEmailAlreadyUsed{u.Email}
}
if len(u.AvatarEmail) == 0 {
u.AvatarEmail = u.Email
}
u.Avatar = avatar.HashEmail(u.AvatarEmail)
}
u.LowerName = strings.ToLower(u.Name)
@@ -510,13 +578,8 @@ func updateUser(e Engine, u *User) error {
u.Description = u.Description[:255]
}
if u.AvatarEmail == "" {
u.AvatarEmail = u.Email
}
u.Avatar = avatar.HashEmail(u.AvatarEmail)
u.FullName = base.Sanitizer.Sanitize(u.FullName)
_, err = e.Id(u.Id).AllCols().Update(u)
_, err := e.Id(u.Id).AllCols().Update(u)
return err
}
@@ -525,8 +588,8 @@ func UpdateUser(u *User) error {
return updateUser(x, u)
}
// DeleteBeans deletes all given beans, beans should contain delete conditions.
func DeleteBeans(e Engine, beans ...interface{}) (err error) {
// deleteBeans deletes all given beans, beans should contain delete conditions.
func deleteBeans(e Engine, beans ...interface{}) (err error) {
for i := range beans {
if _, err = e.Delete(beans[i]); err != nil {
return err
@@ -536,14 +599,12 @@ func DeleteBeans(e Engine, beans ...interface{}) (err error) {
}
// FIXME: need some kind of mechanism to record failure. HINT: system notice
// DeleteUser completely and permanently deletes everything of a user,
// but issues/comments/pulls will be kept and shown as someone has been deleted.
func DeleteUser(u *User) error {
func deleteUser(e *xorm.Session, u *User) error {
// Note: A user owns any repository or belongs to any organization
// cannot perform delete operation.
// Check ownership of repository.
count, err := GetRepositoryCount(u)
count, err := getRepositoryCount(e, u)
if err != nil {
return fmt.Errorf("GetRepositoryCount: %v", err)
} else if count > 0 {
@@ -551,26 +612,20 @@ func DeleteUser(u *User) error {
}
// Check membership of organization.
count, err = u.GetOrganizationCount()
count, err = u.getOrganizationCount(e)
if err != nil {
return fmt.Errorf("GetOrganizationCount: %v", err)
} else if count > 0 {
return ErrUserHasOrgs{UID: u.Id}
}
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
// ***** START: Watch *****
watches := make([]*Watch, 0, 10)
if err = x.Find(&watches, &Watch{UserID: u.Id}); err != nil {
if err = e.Find(&watches, &Watch{UserID: u.Id}); err != nil {
return fmt.Errorf("get all watches: %v", err)
}
for i := range watches {
if _, err = sess.Exec("UPDATE `repository` SET num_watches=num_watches-1 WHERE id=?", watches[i].RepoID); err != nil {
if _, err = e.Exec("UPDATE `repository` SET num_watches=num_watches-1 WHERE id=?", watches[i].RepoID); err != nil {
return fmt.Errorf("decrease repository watch number[%d]: %v", watches[i].RepoID, err)
}
}
@@ -578,11 +633,11 @@ func DeleteUser(u *User) error {
// ***** START: Star *****
stars := make([]*Star, 0, 10)
if err = x.Find(&stars, &Star{UID: u.Id}); err != nil {
if err = e.Find(&stars, &Star{UID: u.Id}); err != nil {
return fmt.Errorf("get all stars: %v", err)
}
for i := range stars {
if _, err = sess.Exec("UPDATE `repository` SET num_stars=num_stars-1 WHERE id=?", stars[i].RepoID); err != nil {
if _, err = e.Exec("UPDATE `repository` SET num_stars=num_stars-1 WHERE id=?", stars[i].RepoID); err != nil {
return fmt.Errorf("decrease repository star number[%d]: %v", stars[i].RepoID, err)
}
}
@@ -590,18 +645,17 @@ func DeleteUser(u *User) error {
// ***** START: Follow *****
followers := make([]*Follow, 0, 10)
if err = x.Find(&followers, &Follow{UserID: u.Id}); err != nil {
if err = e.Find(&followers, &Follow{UserID: u.Id}); err != nil {
return fmt.Errorf("get all followers: %v", err)
}
for i := range followers {
if _, err = sess.Exec("UPDATE `user` SET num_followers=num_followers-1 WHERE id=?", followers[i].UserID); err != nil {
if _, err = e.Exec("UPDATE `user` SET num_followers=num_followers-1 WHERE id=?", followers[i].UserID); err != nil {
return fmt.Errorf("decrease user follower number[%d]: %v", followers[i].UserID, err)
}
}
// ***** END: Follow *****
if err = DeleteBeans(sess,
&Oauth2{Uid: u.Id},
if err = deleteBeans(e,
&AccessToken{UID: u.Id},
&Collaboration{UserID: u.Id},
&Access{UserID: u.Id},
@@ -610,36 +664,32 @@ func DeleteUser(u *User) error {
&Follow{FollowID: u.Id},
&Action{UserID: u.Id},
&IssueUser{UID: u.Id},
&EmailAddress{Uid: u.Id},
&EmailAddress{UID: u.Id},
); err != nil {
return fmt.Errorf("DeleteBeans: %v", err)
return fmt.Errorf("deleteBeans: %v", err)
}
// ***** START: PublicKey *****
keys := make([]*PublicKey, 0, 10)
if err = sess.Find(&keys, &PublicKey{OwnerID: u.Id}); err != nil {
if err = e.Find(&keys, &PublicKey{OwnerID: u.Id}); err != nil {
return fmt.Errorf("get all public keys: %v", err)
}
for _, key := range keys {
if err = deletePublicKey(sess, key.ID); err != nil {
if err = deletePublicKey(e, key.ID); err != nil {
return fmt.Errorf("deletePublicKey: %v", err)
}
}
// ***** END: PublicKey *****
// Clear assignee.
if _, err = sess.Exec("UPDATE `issue` SET assignee_id=0 WHERE assignee_id=?", u.Id); err != nil {
if _, err = e.Exec("UPDATE `issue` SET assignee_id=0 WHERE assignee_id=?", u.Id); err != nil {
return fmt.Errorf("clear assignee: %v", err)
}
if _, err = sess.Id(u.Id).Delete(new(User)); err != nil {
if _, err = e.Id(u.Id).Delete(new(User)); err != nil {
return fmt.Errorf("Delete: %v", err)
}
if err = sess.Commit(); err != nil {
return fmt.Errorf("Commit: %v", err)
}
// FIXME: system notice
// Note: There are something just cannot be roll back,
// so just keep error logs of those operations.
@@ -651,6 +701,23 @@ func DeleteUser(u *User) error {
return nil
}
// DeleteUser completely and permanently deletes everything of a user,
// but issues/comments/pulls will be kept and shown as someone has been deleted.
func DeleteUser(u *User) (err error) {
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if err = deleteUser(sess, u); err != nil {
// Note: don't wrapper error here.
return err
}
return sess.Commit()
}
// DeleteInactivateUsers deletes all inactivate users and email addresses.
func DeleteInactivateUsers() (err error) {
users := make([]*User, 0, 10)
@@ -676,9 +743,9 @@ func UserPath(userName string) string {
return filepath.Join(setting.RepoRootPath, strings.ToLower(userName))
}
func GetUserByKeyId(keyId int64) (*User, error) {
func GetUserByKeyID(keyID int64) (*User, error) {
user := new(User)
has, err := x.Sql("SELECT a.* FROM `user` AS a, public_key AS b WHERE a.id = b.owner_id AND b.id=?", keyId).Get(user)
has, err := x.Sql("SELECT a.* FROM `user` AS a, public_key AS b WHERE a.id = b.owner_id AND b.id=?", keyID).Get(user)
if err != nil {
return nil, err
} else if !has {
@@ -805,11 +872,11 @@ func AddEmailAddress(email *EmailAddress) error {
func (email *EmailAddress) Activate() error {
email.IsActivated = true
if _, err := x.Id(email.Id).AllCols().Update(email); err != nil {
if _, err := x.Id(email.ID).AllCols().Update(email); err != nil {
return err
}
if user, err := GetUserByID(email.Uid); err != nil {
if user, err := GetUserByID(email.UID); err != nil {
return err
} else {
user.Rands = GetUserSalt()
@@ -825,7 +892,7 @@ func DeleteEmailAddress(email *EmailAddress) error {
return ErrEmailNotExist
}
if _, err = x.Id(email.Id).Delete(email); err != nil {
if _, err = x.Id(email.ID).Delete(email); err != nil {
return err
}
@@ -845,12 +912,12 @@ func MakeEmailPrimary(email *EmailAddress) error {
return ErrEmailNotActivated
}
user := &User{Id: email.Uid}
user := &User{Id: email.UID}
has, err = x.Get(user)
if err != nil {
return err
} else if !has {
return ErrUserNotExist{email.Uid, ""}
return ErrUserNotExist{email.UID, ""}
}
// Make sure the former primary email doesn't disappear
@@ -859,7 +926,7 @@ func MakeEmailPrimary(email *EmailAddress) error {
if err != nil {
return err
} else if !has {
former_primary_email.Uid = user.Id
former_primary_email.UID = user.Id
former_primary_email.IsActivated = user.IsActive
x.Insert(former_primary_email)
}
@@ -873,11 +940,11 @@ func MakeEmailPrimary(email *EmailAddress) error {
// UserCommit represents a commit with validation of user.
type UserCommit struct {
User *User
*git.Commit
*oldgit.Commit
}
// ValidateCommitWithEmail chceck if author's e-mail of commit is corresponsind to a user.
func ValidateCommitWithEmail(c *git.Commit) *User {
func ValidateCommitWithEmail(c *oldgit.Commit) *User {
u, err := GetUserByEmail(c.Author.Email)
if err != nil {
return nil
@@ -894,7 +961,7 @@ func ValidateCommitsWithEmails(oldCommits *list.List) *list.List {
e = oldCommits.Front()
)
for e != nil {
c := e.Value.(*git.Commit)
c := e.Value.(*oldgit.Commit)
if v, ok := emails[c.Author.Email]; !ok {
u, _ = GetUserByEmail(c.Author.Email)
@@ -936,10 +1003,10 @@ func GetUserByEmail(email string) (*User, error) {
return nil, err
}
if has {
return GetUserByID(emailAddress.Uid)
return GetUserByID(emailAddress.UID)
}
return nil, ErrUserNotExist{0, "email"}
return nil, ErrUserNotExist{0, email}
}
// SearchUserByName returns given number of users whose name contains keyword.

View File

@@ -13,6 +13,7 @@ import (
"sync"
"time"
"github.com/Unknwon/com"
"github.com/go-xorm/xorm"
api "github.com/gogits/go-gogs-client"
@@ -177,8 +178,8 @@ func GetActiveWebhooksByRepoID(repoID int64) (ws []*Webhook, err error) {
return ws, err
}
// GetWebhooksByRepoId returns all webhooks of repository.
func GetWebhooksByRepoId(repoID int64) (ws []*Webhook, err error) {
// GetWebhooksByRepoID returns all webhooks of repository.
func GetWebhooksByRepoID(repoID int64) (ws []*Webhook, err error) {
err = x.Find(&ws, &Webhook{RepoID: repoID})
return ws, err
}
@@ -334,7 +335,7 @@ func (t *HookTask) AfterSet(colName string, _ xorm.Cell) {
t.ResponseInfo = &HookResponse{}
if err = json.Unmarshal([]byte(t.ResponseContent), t.ResponseInfo); err != nil {
log.Error(3, "Unmarshal[%d]: %v", t.ID, err)
log.Error(3, "Unmarshal [%d]: %v", t.ID, err)
}
}
}
@@ -342,7 +343,7 @@ func (t *HookTask) AfterSet(colName string, _ xorm.Cell) {
func (t *HookTask) MarshalJSON(v interface{}) string {
p, err := json.Marshal(v)
if err != nil {
log.Error(3, "Marshal[%d]: %v", t.ID, err)
log.Error(3, "Marshal [%d]: %v", t.ID, err)
}
return string(p)
}
@@ -435,39 +436,65 @@ func PrepareWebhooks(repo *Repository, event HookEventType, p api.Payloader) err
return nil
}
type hookQueue struct {
// Make sure one repository only occur once in the queue.
lock sync.Mutex
repoIDs map[int64]bool
// UniqueQueue represents a queue that guarantees only one instance of same ID is in the line.
type UniqueQueue struct {
lock sync.Mutex
ids map[string]bool
queue chan int64
queue chan string
}
func (q *hookQueue) removeRepoID(id int64) {
func (q *UniqueQueue) Queue() <-chan string {
return q.queue
}
func NewUniqueQueue(queueLength int) *UniqueQueue {
if queueLength <= 0 {
queueLength = 100
}
return &UniqueQueue{
ids: make(map[string]bool),
queue: make(chan string, queueLength),
}
}
func (q *UniqueQueue) Remove(id interface{}) {
q.lock.Lock()
defer q.lock.Unlock()
delete(q.repoIDs, id)
delete(q.ids, com.ToStr(id))
}
func (q *hookQueue) addRepoID(id int64) {
q.lock.Lock()
if q.repoIDs[id] {
q.lock.Unlock()
func (q *UniqueQueue) AddFunc(id interface{}, fn func()) {
newid := com.ToStr(id)
if q.Exist(id) {
return
}
q.repoIDs[id] = true
q.lock.Lock()
q.ids[newid] = true
if fn != nil {
fn()
}
q.lock.Unlock()
q.queue <- id
q.queue <- newid
}
// AddRepoID adds repository ID to hook delivery queue.
func (q *hookQueue) AddRepoID(id int64) {
go q.addRepoID(id)
func (q *UniqueQueue) Add(id interface{}) {
q.AddFunc(id, nil)
}
var HookQueue *hookQueue
func (q *UniqueQueue) Exist(id interface{}) bool {
q.lock.Lock()
defer q.lock.Unlock()
func deliverHook(t *HookTask) {
return q.ids[com.ToStr(id)]
}
var HookQueue = NewUniqueQueue(setting.Webhook.QueueLength)
func (t *HookTask) deliver() {
t.IsDelivered = true
timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second
@@ -499,6 +526,8 @@ func deliverHook(t *HookTask) {
t.Delivered = time.Now().UTC().UnixNano()
if t.IsSucceed {
log.Trace("Hook delivered: %s", t.UUID)
} else {
log.Trace("Hook delivery failed: %s", t.UUID)
}
// Update webhook last delivery status.
@@ -549,12 +578,13 @@ func deliverHook(t *HookTask) {
}
// DeliverHooks checks and delivers undelivered hooks.
// TODO: shoot more hooks at same time.
func DeliverHooks() {
tasks := make([]*HookTask, 0, 10)
x.Where("is_delivered=?", false).Iterate(new(HookTask),
func(idx int, bean interface{}) error {
t := bean.(*HookTask)
deliverHook(t)
t.deliver()
tasks = append(tasks, t)
return nil
})
@@ -562,29 +592,25 @@ func DeliverHooks() {
// Update hook task status.
for _, t := range tasks {
if err := UpdateHookTask(t); err != nil {
log.Error(4, "UpdateHookTask(%d): %v", t.ID, err)
log.Error(4, "UpdateHookTask [%d]: %v", t.ID, err)
}
}
HookQueue = &hookQueue{
lock: sync.Mutex{},
repoIDs: make(map[int64]bool),
queue: make(chan int64, setting.Webhook.QueueLength),
}
// Start listening on new hook requests.
for repoID := range HookQueue.queue {
HookQueue.removeRepoID(repoID)
for repoID := range HookQueue.Queue() {
log.Trace("DeliverHooks [%v]: processing delivery hooks", repoID)
HookQueue.Remove(repoID)
tasks = make([]*HookTask, 0, 5)
if err := x.Where("repo_id=? AND is_delivered=?", repoID, false).Find(&tasks); err != nil {
log.Error(4, "Get repository(%d) hook tasks: %v", repoID, err)
log.Error(4, "Get repository [%d] hook tasks: %v", repoID, err)
continue
}
for _, t := range tasks {
deliverHook(t)
t.deliver()
if err := UpdateHookTask(t); err != nil {
log.Error(4, "UpdateHookTask(%d): %v", t.ID, err)
log.Error(4, "UpdateHookTask [%d]: %v", t.ID, err)
continue
}
}
}

View File

@@ -33,8 +33,9 @@ type SlackPayload struct {
}
type SlackAttachment struct {
Color string `json:"color"`
Text string `json:"text"`
Fallback string `json:"fallback"`
Color string `json:"color"`
Text string `json:"text"`
}
func (p *SlackPayload) SetSecret(_ string) {}
@@ -82,19 +83,19 @@ func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, e
// n new commits
var (
branchName = git.RefEndName(p.Ref)
commitDesc string
commitString string
)
if len(p.Commits) == 1 {
commitString = "1 new commit"
if len(p.CompareUrl) > 0 {
commitString = SlackLinkFormatter(p.CompareUrl, commitString)
}
commitDesc = "1 new commit"
} else {
commitString = fmt.Sprintf("%d new commits", len(p.Commits))
if p.CompareUrl != "" {
commitString = SlackLinkFormatter(p.CompareUrl, commitString)
}
commitDesc = fmt.Sprintf("%d new commits", len(p.Commits))
}
if len(p.CompareUrl) > 0 {
commitString = SlackLinkFormatter(p.CompareUrl, commitDesc)
} else {
commitString = commitDesc
}
repoLink := SlackLinkFormatter(p.Repo.URL, p.Repo.Name)
@@ -111,7 +112,10 @@ func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, e
}
}
slackAttachments := []SlackAttachment{{Color: slack.Color, Text: attachmentText}}
slackAttachments := []SlackAttachment{{
Color: slack.Color,
Text: attachmentText,
}}
return &SlackPayload{
Channel: slack.Channel,

178
models/wiki.go Normal file
View File

@@ -0,0 +1,178 @@
// Copyright 2015 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"sync"
"github.com/Unknwon/com"
"github.com/gogits/git-shell"
"github.com/gogits/gogs/modules/setting"
)
// workingPool represents a pool of working status which makes sure
// that only one instance of same task is performing at a time.
// However, different type of tasks can performing at the same time.
type workingPool struct {
lock sync.Mutex
pool map[string]*sync.Mutex
count map[string]int
}
// CheckIn checks in a task and waits if others are running.
func (p *workingPool) CheckIn(name string) {
p.lock.Lock()
lock, has := p.pool[name]
if !has {
lock = &sync.Mutex{}
p.pool[name] = lock
}
p.count[name]++
p.lock.Unlock()
lock.Lock()
}
// CheckOut checks out a task to let other tasks run.
func (p *workingPool) CheckOut(name string) {
p.lock.Lock()
defer p.lock.Unlock()
p.pool[name].Unlock()
if p.count[name] == 1 {
delete(p.pool, name)
delete(p.count, name)
} else {
p.count[name]--
}
}
var wikiWorkingPool = &workingPool{
pool: make(map[string]*sync.Mutex),
count: make(map[string]int),
}
// ToWikiPageURL formats a string to corresponding wiki URL name.
func ToWikiPageURL(name string) string {
return strings.Replace(name, " ", "-", -1)
}
// ToWikiPageName formats a URL back to corresponding wiki page name.
func ToWikiPageName(name string) string {
return strings.Replace(name, "-", " ", -1)
}
// WikiCloneLink returns clone URLs of repository wiki.
func (repo *Repository) WikiCloneLink() (cl *CloneLink) {
return repo.cloneLink(true)
}
// WikiPath returns wiki data path by given user and repository name.
func WikiPath(userName, repoName string) string {
return filepath.Join(UserPath(userName), strings.ToLower(repoName)+".wiki.git")
}
func (repo *Repository) WikiPath() string {
return WikiPath(repo.MustOwner().Name, repo.Name)
}
// HasWiki returns true if repository has wiki.
func (repo *Repository) HasWiki() bool {
return com.IsDir(repo.WikiPath())
}
// InitWiki initializes a wiki for repository,
// it does nothing when repository already has wiki.
func (repo *Repository) InitWiki() error {
if repo.HasWiki() {
return nil
}
if err := git.InitRepository(repo.WikiPath(), true); err != nil {
return fmt.Errorf("InitRepository: %v", err)
}
return nil
}
func (repo *Repository) LocalWikiPath() string {
return path.Join(setting.AppDataPath, "tmp/local-wiki", com.ToStr(repo.ID))
}
// UpdateLocalWiki makes sure the local copy of repository wiki is up-to-date.
func (repo *Repository) UpdateLocalWiki() error {
return updateLocalCopy(repo.WikiPath(), repo.LocalWikiPath())
}
// updateWikiPage adds new page to repository wiki.
func (repo *Repository) updateWikiPage(doer *User, oldTitle, title, content, message string, isNew bool) (err error) {
wikiWorkingPool.CheckIn(com.ToStr(repo.ID))
defer wikiWorkingPool.CheckOut(com.ToStr(repo.ID))
if err = repo.InitWiki(); err != nil {
return fmt.Errorf("InitWiki: %v", err)
}
localPath := repo.LocalWikiPath()
// Discard local commits make sure even to remote when local copy exists.
if com.IsExist(localPath) {
// No need to check if nothing in the repository.
if git.IsBranchExist(localPath, "master") {
if err = git.ResetHEAD(localPath, true, "origin/master"); err != nil {
return fmt.Errorf("Reset: %v", err)
}
}
}
if err = repo.UpdateLocalWiki(); err != nil {
return fmt.Errorf("UpdateLocalWiki: %v", err)
}
title = ToWikiPageName(strings.Replace(title, "/", " ", -1))
filename := path.Join(localPath, title+".md")
// If not a new file, show perform update not create.
if isNew {
if com.IsExist(filename) {
return ErrWikiAlreadyExist{filename}
}
} else {
os.Remove(path.Join(localPath, oldTitle+".md"))
}
if err = ioutil.WriteFile(filename, []byte(content), 0666); err != nil {
return fmt.Errorf("WriteFile: %v", err)
}
if len(message) == 0 {
message = "Update page '" + title + "'"
}
if err = git.AddChanges(localPath, true); err != nil {
return fmt.Errorf("AddChanges: %v", err)
} else if err = git.CommitChanges(localPath, message, doer.NewGitSig()); err != nil {
return fmt.Errorf("CommitChanges: %v", err)
} else if err = git.Push(localPath, "origin", "master"); err != nil {
return fmt.Errorf("Push: %v", err)
}
return nil
}
func (repo *Repository) AddWikiPage(doer *User, title, content, message string) error {
return repo.updateWikiPage(doer, "", title, content, message, true)
}
func (repo *Repository) EditWikiPage(doer *User, oldTitle, title, content, message string) error {
return repo.updateWikiPage(doer, oldTitle, title, content, message, false)
}

View File

@@ -1,27 +0,0 @@
Copyright (c) 2012 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,11 +0,0 @@
# Copyright 2009 The Go Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
include $(GOROOT)/src/Make.inc
TARG=github.com/mmitton/asn1-ber
GOFILES=\
ber.go\
include $(GOROOT)/src/Make.pkg

View File

@@ -1,14 +0,0 @@
ASN1 BER Encoding / Decoding Library for the GO programming language.
Required Librarys:
None
Working:
Very basic encoding / decoding needed for LDAP protocol
Tests Implemented:
None
TODO:
Fix all encoding / decoding to conform to ASN1 BER spec
Implement Tests / Benchmarks

View File

@@ -1,497 +0,0 @@
package ber
import (
"bytes"
"fmt"
"io"
"reflect"
"errors"
)
type Packet struct {
ClassType uint8
TagType uint8
Tag uint8
Value interface{}
ByteValue []byte
Data *bytes.Buffer
Children []*Packet
Description string
}
const (
TagEOC = 0x00
TagBoolean = 0x01
TagInteger = 0x02
TagBitString = 0x03
TagOctetString = 0x04
TagNULL = 0x05
TagObjectIdentifier = 0x06
TagObjectDescriptor = 0x07
TagExternal = 0x08
TagRealFloat = 0x09
TagEnumerated = 0x0a
TagEmbeddedPDV = 0x0b
TagUTF8String = 0x0c
TagRelativeOID = 0x0d
TagSequence = 0x10
TagSet = 0x11
TagNumericString = 0x12
TagPrintableString = 0x13
TagT61String = 0x14
TagVideotexString = 0x15
TagIA5String = 0x16
TagUTCTime = 0x17
TagGeneralizedTime = 0x18
TagGraphicString = 0x19
TagVisibleString = 0x1a
TagGeneralString = 0x1b
TagUniversalString = 0x1c
TagCharacterString = 0x1d
TagBMPString = 0x1e
TagBitmask = 0x1f // xxx11111b
)
var TagMap = map[uint8]string{
TagEOC: "EOC (End-of-Content)",
TagBoolean: "Boolean",
TagInteger: "Integer",
TagBitString: "Bit String",
TagOctetString: "Octet String",
TagNULL: "NULL",
TagObjectIdentifier: "Object Identifier",
TagObjectDescriptor: "Object Descriptor",
TagExternal: "External",
TagRealFloat: "Real (float)",
TagEnumerated: "Enumerated",
TagEmbeddedPDV: "Embedded PDV",
TagUTF8String: "UTF8 String",
TagRelativeOID: "Relative-OID",
TagSequence: "Sequence and Sequence of",
TagSet: "Set and Set OF",
TagNumericString: "Numeric String",
TagPrintableString: "Printable String",
TagT61String: "T61 String",
TagVideotexString: "Videotex String",
TagIA5String: "IA5 String",
TagUTCTime: "UTC Time",
TagGeneralizedTime: "Generalized Time",
TagGraphicString: "Graphic String",
TagVisibleString: "Visible String",
TagGeneralString: "General String",
TagUniversalString: "Universal String",
TagCharacterString: "Character String",
TagBMPString: "BMP String",
}
const (
ClassUniversal = 0 // 00xxxxxxb
ClassApplication = 64 // 01xxxxxxb
ClassContext = 128 // 10xxxxxxb
ClassPrivate = 192 // 11xxxxxxb
ClassBitmask = 192 // 11xxxxxxb
)
var ClassMap = map[uint8]string{
ClassUniversal: "Universal",
ClassApplication: "Application",
ClassContext: "Context",
ClassPrivate: "Private",
}
const (
TypePrimitive = 0 // xx0xxxxxb
TypeConstructed = 32 // xx1xxxxxb
TypeBitmask = 32 // xx1xxxxxb
)
var TypeMap = map[uint8]string{
TypePrimitive: "Primative",
TypeConstructed: "Constructed",
}
var Debug bool = false
func PrintBytes(buf []byte, indent string) {
data_lines := make([]string, (len(buf)/30)+1)
num_lines := make([]string, (len(buf)/30)+1)
for i, b := range buf {
data_lines[i/30] += fmt.Sprintf("%02x ", b)
num_lines[i/30] += fmt.Sprintf("%02d ", (i+1)%100)
}
for i := 0; i < len(data_lines); i++ {
fmt.Print(indent + data_lines[i] + "\n")
fmt.Print(indent + num_lines[i] + "\n\n")
}
}
func PrintPacket(p *Packet) {
printPacket(p, 0, false)
}
func printPacket(p *Packet, indent int, printBytes bool) {
indent_str := ""
for len(indent_str) != indent {
indent_str += " "
}
class_str := ClassMap[p.ClassType]
tagtype_str := TypeMap[p.TagType]
tag_str := fmt.Sprintf("0x%02X", p.Tag)
if p.ClassType == ClassUniversal {
tag_str = TagMap[p.Tag]
}
value := fmt.Sprint(p.Value)
description := ""
if p.Description != "" {
description = p.Description + ": "
}
fmt.Printf("%s%s(%s, %s, %s) Len=%d %q\n", indent_str, description, class_str, tagtype_str, tag_str, p.Data.Len(), value)
if printBytes {
PrintBytes(p.Bytes(), indent_str)
}
for _, child := range p.Children {
printPacket(child, indent+1, printBytes)
}
}
func resizeBuffer(in []byte, new_size uint64) (out []byte) {
out = make([]byte, new_size)
copy(out, in)
return
}
func readBytes(reader io.Reader, buf []byte) error {
idx := 0
buflen := len(buf)
if reader == nil {
return errors.New("reader was nil, aborting")
}
for idx < buflen {
n, err := reader.Read(buf[idx:])
if err != nil {
return err
}
idx += n
}
return nil
}
func ReadPacket(reader io.Reader) (*Packet, error) {
buf := make([]byte, 2)
err := readBytes(reader, buf)
if err != nil {
return nil, err
}
idx := uint64(2)
datalen := uint64(buf[1])
if Debug {
fmt.Printf("Read: datalen = %d len(buf) = %d ", datalen, len(buf))
for _, b := range buf {
fmt.Printf("%02X ", b)
}
fmt.Printf("\n")
}
if datalen&128 != 0 {
a := datalen - 128
idx += a
buf = resizeBuffer(buf, 2+a)
err := readBytes(reader, buf[2:])
if err != nil {
return nil, err
}
datalen = DecodeInteger(buf[2 : 2+a])
if Debug {
fmt.Printf("Read: a = %d idx = %d datalen = %d len(buf) = %d", a, idx, datalen, len(buf))
for _, b := range buf {
fmt.Printf("%02X ", b)
}
fmt.Printf("\n")
}
}
buf = resizeBuffer(buf, idx+datalen)
err = readBytes(reader, buf[idx:])
if err != nil {
return nil, err
}
if Debug {
fmt.Printf("Read: len( buf ) = %d idx=%d datalen=%d idx+datalen=%d\n", len(buf), idx, datalen, idx+datalen)
for _, b := range buf {
fmt.Printf("%02X ", b)
}
}
p := DecodePacket(buf)
return p, nil
}
func DecodeString(data []byte) (ret string) {
// for _, c := range data {
// ret += fmt.Sprintf("%c", c)
// }
return string(data)
}
func DecodeInteger(data []byte) (ret uint64) {
for _, i := range data {
ret = ret * 256
ret = ret + uint64(i)
}
return
}
func EncodeInteger(val uint64) []byte {
var out bytes.Buffer
found := false
shift := uint(56)
mask := uint64(0xFF00000000000000)
for mask > 0 {
if !found && (val&mask != 0) {
found = true
}
if found || (shift == 0) {
out.Write([]byte{byte((val & mask) >> shift)})
}
shift -= 8
mask = mask >> 8
}
return out.Bytes()
}
func DecodePacket(data []byte) *Packet {
p, _ := decodePacket(data)
return p
}
func decodePacket(data []byte) (*Packet, []byte) {
if Debug {
fmt.Printf("decodePacket: enter %d\n", len(data))
}
p := new(Packet)
p.ClassType = data[0] & ClassBitmask
p.TagType = data[0] & TypeBitmask
p.Tag = data[0] & TagBitmask
datalen := DecodeInteger(data[1:2])
datapos := uint64(2)
if datalen&128 != 0 {
datalen -= 128
datapos += datalen
datalen = DecodeInteger(data[2 : 2+datalen])
}
p.Data = new(bytes.Buffer)
p.Children = make([]*Packet, 0, 2)
p.Value = nil
value_data := data[datapos : datapos+datalen]
if p.TagType == TypeConstructed {
for len(value_data) != 0 {
var child *Packet
child, value_data = decodePacket(value_data)
p.AppendChild(child)
}
} else if p.ClassType == ClassUniversal {
p.Data.Write(data[datapos : datapos+datalen])
p.ByteValue = value_data
switch p.Tag {
case TagEOC:
case TagBoolean:
val := DecodeInteger(value_data)
p.Value = val != 0
case TagInteger:
p.Value = DecodeInteger(value_data)
case TagBitString:
case TagOctetString:
p.Value = DecodeString(value_data)
case TagNULL:
case TagObjectIdentifier:
case TagObjectDescriptor:
case TagExternal:
case TagRealFloat:
case TagEnumerated:
p.Value = DecodeInteger(value_data)
case TagEmbeddedPDV:
case TagUTF8String:
case TagRelativeOID:
case TagSequence:
case TagSet:
case TagNumericString:
case TagPrintableString:
p.Value = DecodeString(value_data)
case TagT61String:
case TagVideotexString:
case TagIA5String:
case TagUTCTime:
case TagGeneralizedTime:
case TagGraphicString:
case TagVisibleString:
case TagGeneralString:
case TagUniversalString:
case TagCharacterString:
case TagBMPString:
}
} else {
p.Data.Write(data[datapos : datapos+datalen])
}
return p, data[datapos+datalen:]
}
func (p *Packet) DataLength() uint64 {
return uint64(p.Data.Len())
}
func (p *Packet) Bytes() []byte {
var out bytes.Buffer
out.Write([]byte{p.ClassType | p.TagType | p.Tag})
packet_length := EncodeInteger(p.DataLength())
if p.DataLength() > 127 || len(packet_length) > 1 {
out.Write([]byte{byte(len(packet_length) | 128)})
out.Write(packet_length)
} else {
out.Write(packet_length)
}
out.Write(p.Data.Bytes())
return out.Bytes()
}
func (p *Packet) AppendChild(child *Packet) {
p.Data.Write(child.Bytes())
if len(p.Children) == cap(p.Children) {
newChildren := make([]*Packet, cap(p.Children)*2)
copy(newChildren, p.Children)
p.Children = newChildren[0:len(p.Children)]
}
p.Children = p.Children[0 : len(p.Children)+1]
p.Children[len(p.Children)-1] = child
}
func Encode(ClassType, TagType, Tag uint8, Value interface{}, Description string) *Packet {
p := new(Packet)
p.ClassType = ClassType
p.TagType = TagType
p.Tag = Tag
p.Data = new(bytes.Buffer)
p.Children = make([]*Packet, 0, 2)
p.Value = Value
p.Description = Description
if Value != nil {
v := reflect.ValueOf(Value)
if ClassType == ClassUniversal {
switch Tag {
case TagOctetString:
sv, ok := v.Interface().(string)
if ok {
p.Data.Write([]byte(sv))
}
}
}
}
return p
}
func NewSequence(Description string) *Packet {
return Encode(ClassUniversal, TypePrimitive, TagSequence, nil, Description)
}
func NewBoolean(ClassType, TagType, Tag uint8, Value bool, Description string) *Packet {
intValue := 0
if Value {
intValue = 1
}
p := Encode(ClassType, TagType, Tag, nil, Description)
p.Value = Value
p.Data.Write(EncodeInteger(uint64(intValue)))
return p
}
func NewInteger(ClassType, TagType, Tag uint8, Value uint64, Description string) *Packet {
p := Encode(ClassType, TagType, Tag, nil, Description)
p.Value = Value
p.Data.Write(EncodeInteger(Value))
return p
}
func NewString(ClassType, TagType, Tag uint8, Value, Description string) *Packet {
p := Encode(ClassType, TagType, Tag, nil, Description)
p.Value = Value
p.Data.Write([]byte(Value))
return p
}

View File

@@ -5,22 +5,36 @@
package auth
import (
"github.com/Unknwon/macaron"
"gopkg.in/macaron.v1"
"github.com/macaron-contrib/binding"
"github.com/go-macaron/binding"
)
type AdminCrateUserForm struct {
LoginType string `binding:"Required"`
LoginName string
UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"`
Email string `binding:"Required;Email;MaxSize(254)"`
Password string `binding:"MaxSize(255)"`
SendNotify bool
}
func (f *AdminCrateUserForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
type AdminEditUserForm struct {
FullName string `form:"fullname" binding:"MaxSize(100)"`
Email string `binding:"Required;Email;MaxSize(254)"`
Password string `binding:"OmitEmpty;MinSize(6);MaxSize(255)"`
Website string `binding:"MaxSize(50)"`
Location string `binding:"MaxSize(50)"`
Avatar string `binding:"Required;Email;MaxSize(50)"`
Active bool
Admin bool
AllowGitHook bool
LoginType int
LoginType string `binding:"Required"`
LoginName string
FullName string `binding:"MaxSize(100)"`
Email string `binding:"Required;Email;MaxSize(254)"`
Password string `binding:"MaxSize(255)"`
Website string `binding:"MaxSize(50)"`
Location string `binding:"MaxSize(50)"`
Active bool
Admin bool
AllowGitHook bool
AllowImportLocal bool
}
func (f *AdminEditUserForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {

View File

@@ -1,73 +0,0 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package apiv1
import (
"reflect"
"github.com/Unknwon/macaron"
"github.com/macaron-contrib/binding"
"github.com/gogits/gogs/modules/auth"
)
type MarkdownForm struct {
Text string
Mode string
Context string
}
func (f *MarkdownForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validateApiReq(errs, ctx.Data, f)
}
func validateApiReq(errs binding.Errors, data map[string]interface{}, f auth.Form) binding.Errors {
if errs.Len() == 0 {
return errs
}
data["HasError"] = true
typ := reflect.TypeOf(f)
val := reflect.ValueOf(f)
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
val = val.Elem()
}
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
fieldName := field.Tag.Get("form")
// Allow ignored fields in the struct
if fieldName == "-" {
continue
}
if errs[0].FieldNames[0] == field.Name {
switch errs[0].Classification {
case binding.ERR_REQUIRED:
data["ErrorMsg"] = fieldName + " cannot be empty"
case binding.ERR_ALPHA_DASH:
data["ErrorMsg"] = fieldName + " must be valid alpha or numeric or dash(-_) characters"
case binding.ERR_ALPHA_DASH_DOT:
data["ErrorMsg"] = fieldName + " must be valid alpha or numeric or dash(-_) or dot characters"
case binding.ERR_MIN_SIZE:
data["ErrorMsg"] = fieldName + " must contain at least " + auth.GetMinSize(field) + " characters"
case binding.ERR_MAX_SIZE:
data["ErrorMsg"] = fieldName + " must contain at most " + auth.GetMaxSize(field) + " characters"
case binding.ERR_EMAIL:
data["ErrorMsg"] = fieldName + " is not a valid e-mail address"
case binding.ERR_URL:
data["ErrorMsg"] = fieldName + " is not a valid URL"
default:
data["ErrorMsg"] = "Unknown error: " + errs[0].Classification
}
return errs
}
}
return errs
}

View File

@@ -10,9 +10,9 @@ import (
"time"
"github.com/Unknwon/com"
"github.com/Unknwon/macaron"
"github.com/macaron-contrib/binding"
"github.com/macaron-contrib/session"
"github.com/go-macaron/binding"
"github.com/go-macaron/session"
"gopkg.in/macaron.v1"
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/base"
@@ -181,7 +181,7 @@ func AssignForm(form interface{}, data map[string]interface{}) {
}
}
func getSize(field reflect.StructField, prefix string) string {
func getRuleBody(field reflect.StructField, prefix string) string {
for _, rule := range strings.Split(field.Tag.Get("binding"), ";") {
if strings.HasPrefix(rule, prefix) {
return rule[len(prefix) : len(rule)-1]
@@ -191,15 +191,19 @@ func getSize(field reflect.StructField, prefix string) string {
}
func GetSize(field reflect.StructField) string {
return getSize(field, "Size(")
return getRuleBody(field, "Size(")
}
func GetMinSize(field reflect.StructField) string {
return getSize(field, "MinSize(")
return getRuleBody(field, "MinSize(")
}
func GetMaxSize(field reflect.StructField) string {
return getSize(field, "MaxSize(")
return getRuleBody(field, "MaxSize(")
}
func GetInclude(field reflect.StructField) string {
return getRuleBody(field, "Include(")
}
// FIXME: struct contains a struct
@@ -260,6 +264,8 @@ func validate(errs binding.Errors, data map[string]interface{}, f Form, l macaro
data["ErrorMsg"] = trName + l.Tr("form.email_error")
case binding.ERR_URL:
data["ErrorMsg"] = trName + l.Tr("form.url_error")
case binding.ERR_INCLUDE:
data["ErrorMsg"] = trName + l.Tr("form.include_error", GetInclude(field))
default:
data["ErrorMsg"] = l.Tr("form.unknown_error") + " " + errs[0].Classification
}

View File

@@ -5,32 +5,33 @@
package auth
import (
"github.com/Unknwon/macaron"
"github.com/macaron-contrib/binding"
"github.com/go-macaron/binding"
"gopkg.in/macaron.v1"
)
type AuthenticationForm struct {
ID int64 `form:"id"`
Type int
Name string `binding:"Required;MaxSize(50)"`
ID int64
Type int `binding:"Range(2,5)"`
Name string `binding:"Required;MaxSize(30)"`
Host string
Port int
UseSSL bool `form:"use_ssl"`
BindDN string `form:"bind_dn"`
BindDN string
BindPassword string
UserBase string
UserDN string
AttributeUsername string
AttributeName string
AttributeSurname string
AttributeMail string
Filter string
AdminFilter string
IsActived bool
SMTPAuth string `form:"smtp_auth"`
SMTPHost string `form:"smtp_host"`
SMTPPort int `form:"smtp_port"`
TLS bool `form:"tls"`
IsActive bool
SMTPAuth string
SMTPHost string
SMTPPort int
AllowedDomains string
TLS bool
SkipVerify bool
AllowAutoRegister bool `form:"allowautoregister"`
PAMServiceName string
}

View File

@@ -4,61 +4,98 @@ Gogs LDAP Authentication Module
## About
This authentication module attempts to authorize and authenticate a user
against an LDAP server. Like most LDAP authentication systems, this module does
this in two steps. First, it queries the LDAP server using a Bind DN and
searches for the user that is attempting to sign in. If the user is found, the
module attempts to bind to the server using the user's supplied credentials. If
this succeeds, the user has been authenticated, and his account information is
retrieved and passed to the Gogs login infrastructure.
against an LDAP server. It provides two methods of authentication: LDAP via
BindDN, and LDAP simple authentication.
LDAP via BindDN functions like most LDAP authentication systems. First, it
queries the LDAP server using a Bind DN and searches for the user that is
attempting to sign in. If the user is found, the module attempts to bind to the
server using the user's supplied credentials. If this succeeds, the user has
been authenticated, and his account information is retrieved and passed to the
Gogs login infrastructure.
LDAP simple authentication does not utilize a Bind DN. Instead, it binds
directly with the LDAP server using the user's supplied credentials. If the bind
succeeds and no filter rules out the user, the user is authenticated.
LDAP via BindDN is recommended for most users. By using a Bind DN, the server
can perform authorization by restricting which entries the Bind DN account can
read. Further, using a Bind DN with reduced permissions can reduce security risk
in the face of application bugs.
## Usage
To use this module, add an LDAP authentication source via the Authentications
section in the admin panel. The fields should be set as follows:
section in the admin panel. Both the LDAP via BindDN and the simple auth LDAP
share the following fields:
* Authorization Name **(required)**
* A name to assign to the new method of authorization.
* A name to assign to the new method of authorization.
* Host **(required)**
* The address where the LDAP server can be reached.
* Example: mydomain.com
* The address where the LDAP server can be reached.
* Example: mydomain.com
* Port **(required)**
* The port to use when connecting to the server.
* Example: 636
* The port to use when connecting to the server.
* Example: 636
* Enable TLS Encryption (optional)
* Whether to use TLS when connecting to the LDAP server.
* Whether to use TLS when connecting to the LDAP server.
* Bind DN (optional)
* The DN to bind to the LDAP server with when searching for the user.
This may be left blank to perform an anonymous search.
* Example: cn=Search,dc=mydomain,dc=com
* Bind Password (optional)
* The password for the Bind DN specified above, if any.
* User Search Base **(required)**
* The LDAP base at which user accounts will be searched for.
* Example: ou=Users,dc=mydomain,dc=com
* User Filter **(required)**
* An LDAP filter declaring how to find the user record that is attempting
to authenticate. The '%s' matching parameter will be substituted with
the user's username.
* Example: (&(objectClass=posixAccount)(uid=%s))
* Admin Filter (optional)
* An LDAP filter specifying if a user should be given administrator
privileges. If a user accounts passes the filter, the user will be
privileged as an administrator.
* Example: (objectClass=adminAccount)
* First name attribute (optional)
* The attribute of the user's LDAP record containing the user's first
name. This will be used to populate their account information.
* Example: givenName
* The attribute of the user's LDAP record containing the user's first name.
This will be used to populate their account information.
* Example: givenName
* Surname name attribute (optional)
* The attribute of the user's LDAP record containing the user's surname
This will be used to populate their account information.
* Example: sn
* Surname attribute (optional)
* The attribute of the user's LDAP record containing the user's surname This
will be used to populate their account information.
* Example: sn
* E-mail attribute **(required)**
* The attribute of the user's LDAP record containing the user's email
address. This will be used to populate their account information.
* Example: mail
* The attribute of the user's LDAP record containing the user's email
address. This will be used to populate their account information.
* Example: mail
**LDAP via BindDN** adds the following fields:
* Bind DN (optional)
* The DN to bind to the LDAP server with when searching for the user. This
may be left blank to perform an anonymous search.
* Example: cn=Search,dc=mydomain,dc=com
* Bind Password (optional)
* The password for the Bind DN specified above, if any. _Note: The password
is stored in plaintext at the server. As such, ensure that your Bind DN
has as few privileges as possible._
* User Search Base **(required)**
* The LDAP base at which user accounts will be searched for.
* Example: ou=Users,dc=mydomain,dc=com
* User Filter **(required)**
* An LDAP filter declaring how to find the user record that is attempting to
authenticate. The '%s' matching parameter will be substituted with the
user's username.
* Example: (&(objectClass=posixAccount)(uid=%s))
**LDAP using simple auth** adds the following fields:
* User DN **(required)**
* A template to use as the user's DN. The `%s` matching parameter will be
substituted with the user's username.
* Example: cn=%s,ou=Users,dc=mydomain,dc=com
* Example: uid=%s,ou=Users,dc=mydomain,dc=com
* User Filter **(required)**
* An LDAP filter declaring when a user should be allowed to log in. The `%s`
matching parameter will be substituted with the user's username.
* Example: (&(objectClass=posixAccount)(cn=%s))
* Example: (&(objectClass=posixAccount)(uid=%s))

View File

@@ -7,30 +7,58 @@
package ldap
import (
"crypto/tls"
"fmt"
"strings"
"gopkg.in/ldap.v2"
"github.com/gogits/gogs/modules/ldap"
"github.com/gogits/gogs/modules/log"
)
// Basic LDAP authentication service
type Ldapsource struct {
Name string // canonical name (ie. corporate.ad)
Host string // LDAP host
Port int // port number
UseSSL bool // Use SSL
BindDN string // DN to bind with
BindPassword string // Bind DN password
UserBase string // Base search path for users
AttributeName string // First name attribute
AttributeSurname string // Surname attribute
AttributeMail string // E-mail attribute
Filter string // Query filter to validate entry
AdminFilter string // Query filter to check if user is admin
Enabled bool // if this source is disabled
type Source struct {
Name string // canonical name (ie. corporate.ad)
Host string // LDAP host
Port int // port number
UseSSL bool // Use SSL
SkipVerify bool
BindDN string // DN to bind with
BindPassword string // Bind DN password
UserBase string // Base search path for users
UserDN string // Template for the DN of the user for simple auth
AttributeUsername string // Username attribute
AttributeName string // First name attribute
AttributeSurname string // Surname attribute
AttributeMail string // E-mail attribute
Filter string // Query filter to validate entry
AdminFilter string // Query filter to check if user is admin
Enabled bool // if this source is disabled
}
func (ls Ldapsource) FindUserDN(name string) (string, bool) {
func (ls *Source) sanitizedUserQuery(username string) (string, bool) {
// See http://tools.ietf.org/search/rfc4515
badCharacters := "\x00()*\\"
if strings.ContainsAny(username, badCharacters) {
log.Debug("'%s' contains invalid query characters. Aborting.", username)
return "", false
}
return fmt.Sprintf(ls.Filter, username), true
}
func (ls *Source) sanitizedUserDN(username string) (string, bool) {
// See http://tools.ietf.org/search/rfc4514: "special characters"
badCharacters := "\x00()*\\,='\"#+;<> "
if strings.ContainsAny(username, badCharacters) {
log.Debug("'%s' contains invalid DN characters. Aborting.", username)
return "", false
}
return fmt.Sprintf(ls.UserDN, username), true
}
func (ls *Source) FindUserDN(name string) (string, bool) {
l, err := ldapDial(ls)
if err != nil {
log.Error(4, "LDAP Connect error, %s:%v", ls.Host, err)
@@ -52,7 +80,11 @@ func (ls Ldapsource) FindUserDN(name string) (string, bool) {
}
// A search for the user.
userFilter := fmt.Sprintf(ls.Filter, name)
userFilter, ok := ls.sanitizedUserQuery(name)
if !ok {
return "", false
}
log.Trace("Searching using filter %s", userFilter)
search := ldap.NewSearchRequest(
ls.UserBase, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0,
@@ -70,7 +102,7 @@ func (ls Ldapsource) FindUserDN(name string) (string, bool) {
userDN := sr.Entries[0].DN
if userDN == "" {
log.Error(4, "LDAP search was succesful, but found no DN!")
log.Error(4, "LDAP search was successful, but found no DN!")
return "", false
}
@@ -78,30 +110,47 @@ func (ls Ldapsource) FindUserDN(name string) (string, bool) {
}
// searchEntry : search an LDAP source if an entry (name, passwd) is valid and in the specific filter
func (ls Ldapsource) SearchEntry(name, passwd string) (string, string, string, bool, bool) {
userDN, found := ls.FindUserDN(name)
if !found {
return "", "", "", false, false
func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, string, string, string, bool, bool) {
var userDN string
if directBind {
log.Trace("LDAP will bind directly via UserDN template: %s", ls.UserDN)
var ok bool
userDN, ok = ls.sanitizedUserDN(name)
if !ok {
return "", "", "", "", false, false
}
} else {
log.Trace("LDAP will use BindDN.")
var found bool
userDN, found = ls.FindUserDN(name)
if !found {
return "", "", "", "", false, false
}
}
l, err := ldapDial(ls)
if err != nil {
log.Error(4, "LDAP Connect error, %s:%v", ls.Host, err)
log.Error(4, "LDAP Connect error (%s): %v", ls.Host, err)
ls.Enabled = false
return "", "", "", false, false
return "", "", "", "", false, false
}
defer l.Close()
log.Trace("Binding with userDN: %s", userDN)
err = l.Bind(userDN, passwd)
if err != nil {
log.Debug("LDAP auth. failed for %s, reason: %v", userDN, err)
return "", "", "", false, false
return "", "", "", "", false, false
}
log.Trace("Bound successfully with userDN: %s", userDN)
userFilter := fmt.Sprintf(ls.Filter, name)
userFilter, ok := ls.sanitizedUserQuery(name)
if !ok {
return "", "", "", "", false, false
}
search := ldap.NewSearchRequest(
userDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, userFilter,
[]string{ls.AttributeName, ls.AttributeSurname, ls.AttributeMail},
@@ -110,12 +159,18 @@ func (ls Ldapsource) SearchEntry(name, passwd string) (string, string, string, b
sr, err := l.Search(search)
if err != nil {
log.Error(4, "LDAP Search failed unexpectedly! (%v)", err)
return "", "", "", false, false
return "", "", "", "", false, false
} else if len(sr.Entries) < 1 {
log.Error(4, "LDAP Search failed unexpectedly! (0 entries)")
return "", "", "", false, false
if directBind {
log.Error(4, "User filter inhibited user login.")
} else {
log.Error(4, "LDAP Search failed unexpectedly! (0 entries)")
}
return "", "", "", "", false, false
}
username_attr := sr.Entries[0].GetAttributeValue(ls.AttributeUsername)
name_attr := sr.Entries[0].GetAttributeValue(ls.AttributeName)
sn_attr := sr.Entries[0].GetAttributeValue(ls.AttributeSurname)
mail_attr := sr.Entries[0].GetAttributeValue(ls.AttributeMail)
@@ -137,13 +192,15 @@ func (ls Ldapsource) SearchEntry(name, passwd string) (string, string, string, b
}
}
return name_attr, sn_attr, mail_attr, admin_attr, true
return username_attr, name_attr, sn_attr, mail_attr, admin_attr, true
}
func ldapDial(ls Ldapsource) (*ldap.Conn, error) {
func ldapDial(ls *Source) (*ldap.Conn, error) {
if ls.UseSSL {
log.Debug("Using TLS for LDAP")
return ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port), nil)
log.Debug("Using TLS for LDAP without verifying: %v", ls.SkipVerify)
return ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port), &tls.Config{
InsecureSkipVerify: ls.SkipVerify,
})
} else {
return ldap.Dial("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port))
}

View File

@@ -5,8 +5,8 @@
package auth
import (
"github.com/Unknwon/macaron"
"github.com/macaron-contrib/binding"
"github.com/go-macaron/binding"
"gopkg.in/macaron.v1"
)
// ________ .__ __ .__
@@ -17,8 +17,7 @@ import (
// \/ /_____/ \/ \/ \/ \/ \/
type CreateOrgForm struct {
OrgName string `form:"org_name" binding:"Required;AlphaDashDot;MaxSize(30)"`
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
OrgName string `binding:"Required;AlphaDashDot;MaxSize(35)" locale:"org.org_name_holder"`
}
func (f *CreateOrgForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
@@ -26,13 +25,11 @@ func (f *CreateOrgForm) Validate(ctx *macaron.Context, errs binding.Errors) bind
}
type UpdateOrgSettingForm struct {
OrgUserName string `form:"uname" binding:"Required;AlphaDashDot;MaxSize(30)" locale:"org.org_name_holder"`
OrgFullName string `form:"fullname" binding:"MaxSize(100)"`
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
Description string `form:"desc" binding:"MaxSize(255)"`
Website string `form:"website" binding:"Url;MaxSize(100)"`
Location string `form:"location" binding:"MaxSize(50)"`
Avatar string `form:"avatar" binding:"Required;Email;MaxSize(50)"`
Name string `binding:"Required;AlphaDashDot;MaxSize(35)" locale:"org.org_name_holder"`
FullName string `binding:"MaxSize(100)"`
Description string `binding:"MaxSize(255)"`
Website string `binding:"Url;MaxSize(100)"`
Location string `binding:"MaxSize(50)"`
}
func (f *UpdateOrgSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {

View File

@@ -5,8 +5,14 @@
package auth
import (
"github.com/Unknwon/macaron"
"github.com/macaron-contrib/binding"
"net/url"
"strings"
"github.com/Unknwon/com"
"github.com/go-macaron/binding"
"gopkg.in/macaron.v1"
"github.com/gogits/gogs/models"
)
// _______________________________________ _________.______________________ _______________.___.
@@ -37,8 +43,8 @@ type MigrateRepoForm struct {
AuthPassword string `json:"auth_password"`
Uid int64 `json:"uid" binding:"Required"`
RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"`
Private bool `json:"mirror"`
Mirror bool `json:"private"`
Mirror bool `json:"mirror"`
Private bool `json:"private"`
Description string `json:"description" binding:"MaxSize(255)"`
}
@@ -46,6 +52,34 @@ func (f *MigrateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) bi
return validate(errs, ctx.Data, f, ctx.Locale)
}
// ParseRemoteAddr checks if given remote address is valid,
// and returns composed URL with needed username and passowrd.
// It also checks if given user has permission when remote address
// is actually a local path.
func (f MigrateRepoForm) ParseRemoteAddr(user *models.User) (string, error) {
remoteAddr := f.CloneAddr
// Remote address can be HTTP/HTTPS/Git URL or local path.
if strings.HasPrefix(remoteAddr, "http://") ||
strings.HasPrefix(remoteAddr, "https://") ||
strings.HasPrefix(remoteAddr, "git://") {
u, err := url.Parse(remoteAddr)
if err != nil {
return "", models.ErrInvalidCloneAddr{IsURLError: true}
}
if len(f.AuthUsername)+len(f.AuthPassword) > 0 {
u.User = url.UserPassword(f.AuthUsername, f.AuthPassword)
}
remoteAddr = u.String()
} else if !user.CanImportLocal() {
return "", models.ErrInvalidCloneAddr{IsPermissionDenied: true}
} else if !com.IsDir(remoteAddr) {
return "", models.ErrInvalidCloneAddr{IsInvalidPath: true}
}
return remoteAddr, nil
}
type RepoSettingForm struct {
RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"`
Description string `binding:"MaxSize(255)"`
@@ -53,6 +87,13 @@ type RepoSettingForm struct {
Branch string
Interval int
Private bool
// Advanced settings
EnableWiki bool
EnableIssues bool
EnableExternalTracker bool
TrackerURLFormat string
EnablePulls bool
}
func (f *RepoSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
@@ -181,12 +222,12 @@ func (f *CreateLabelForm) Validate(ctx *macaron.Context, errs binding.Errors) bi
// \/ \/ \/ \/ \/ \/
type NewReleaseForm struct {
TagName string `form:"tag_name" binding:"Required"`
TagName string `binding:"Required"`
Target string `form:"tag_target" binding:"Required"`
Title string `form:"title" binding:"Required"`
Content string `form:"content" binding:"Required"`
Draft string `form:"draft"`
Prerelease bool `form:"prerelease"`
Title string `binding:"Required"`
Content string
Draft string
Prerelease bool
}
func (f *NewReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
@@ -203,3 +244,22 @@ type EditReleaseForm struct {
func (f *EditReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
// __ __.__ __ .__
// / \ / \__| | _|__|
// \ \/\/ / | |/ / |
// \ /| | <| |
// \__/\ / |__|__|_ \__|
// \/ \/
type NewWikiForm struct {
OldTitle string
Title string `binding:"Required"`
Content string `binding:"Required"`
Message string
}
// FIXME: use code generation to generate this method.
func (f *NewWikiForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}

View File

@@ -7,8 +7,8 @@ package auth
import (
"mime/multipart"
"github.com/Unknwon/macaron"
"github.com/macaron-contrib/binding"
"github.com/go-macaron/binding"
"gopkg.in/macaron.v1"
)
type InstallForm struct {
@@ -30,7 +30,7 @@ type InstallForm struct {
SMTPHost string
SMTPFrom string
SMTPEmail string `binding:"OmitEmpty;Email;MaxSize(50)" locale:"install.mailer_user"`
SMTPEmail string `binding:"OmitEmpty;Email;MaxSize(254)" locale:"install.mailer_user"`
SMTPPasswd string
RegisterConfirm bool
MailNotify bool
@@ -38,12 +38,13 @@ type InstallForm struct {
OfflineMode bool
DisableGravatar bool
DisableRegistration bool
EnableCaptcha bool
RequireSignInView bool
AdminName string `binding:"OmitEmpty;AlphaDashDot;MaxSize(30)" locale:"install.admin_name"`
AdminPasswd string `binding:"OmitEmpty;MaxSize(255)" locale:"install.admin_password"`
AdminConfirmPasswd string
AdminEmail string `binding:"OmitEmpty;Email;MaxSize(50)" locale:"install.admin_email"`
AdminEmail string `binding:"OmitEmpty;MinSize(3);MaxSize(254);Include(@)" locale:"install.admin_email"`
}
func (f *InstallForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
@@ -58,12 +59,10 @@ func (f *InstallForm) Validate(ctx *macaron.Context, errs binding.Errors) bindin
// \/ \/
type RegisterForm struct {
UserName string `form:"uname" binding:"Required;AlphaDashDot;MaxSize(35)"`
Email string `form:"email" binding:"Required;Email;MaxSize(254)"`
Password string `form:"password" binding:"Required;MaxSize(255)"`
Retype string `form:"retype"`
LoginType string `form:"logintype"`
LoginName string `form:"loginname"`
UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"`
Email string `binding:"Required;Email;MaxSize(254)"`
Password string `binding:"Required;MaxSize(255)"`
Retype string
}
func (f *RegisterForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
@@ -71,9 +70,9 @@ func (f *RegisterForm) Validate(ctx *macaron.Context, errs binding.Errors) bindi
}
type SignInForm struct {
UserName string `form:"uname" binding:"Required;MaxSize(254)"`
Password string `form:"password" binding:"Required;MaxSize(255)"`
Remember bool `form:"remember"`
UserName string `binding:"Required;MaxSize(254)"`
Password string `binding:"Required;MaxSize(255)"`
Remember bool
}
func (f *SignInForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
@@ -88,12 +87,12 @@ func (f *SignInForm) Validate(ctx *macaron.Context, errs binding.Errors) binding
// \/ \/ \/ \/ \/
type UpdateProfileForm struct {
UserName string `form:"uname" binding:"Required;MaxSize(35)"`
FullName string `form:"fullname" binding:"MaxSize(100)"`
Email string `form:"email" binding:"Required;Email;MaxSize(254)"`
Website string `form:"website" binding:"Url;MaxSize(100)"`
Location string `form:"location" binding:"MaxSize(50)"`
Avatar string `form:"avatar" binding:"Required;Email;MaxSize(254)"`
Name string `binding:"Required;MaxSize(35)"`
FullName string `binding:"MaxSize(100)"`
Email string `binding:"Required;Email;MaxSize(254)"`
Website string `binding:"Url;MaxSize(100)"`
Location string `binding:"MaxSize(50)"`
Gravatar string `binding:"Required;Email;MaxSize(254)"`
}
func (f *UpdateProfileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
@@ -101,8 +100,8 @@ func (f *UpdateProfileForm) Validate(ctx *macaron.Context, errs binding.Errors)
}
type UploadAvatarForm struct {
Enable bool `form:"enable"`
Avatar *multipart.FileHeader `form:"avatar"`
Enable bool
Avatar *multipart.FileHeader
}
func (f *UploadAvatarForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
@@ -110,7 +109,7 @@ func (f *UploadAvatarForm) Validate(ctx *macaron.Context, errs binding.Errors) b
}
type AddEmailForm struct {
Email string `binding:"Required;Email;MaxSize(50)"`
Email string `binding:"Required;Email;MaxSize(254)"`
}
func (f *AddEmailForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {

View File

@@ -32,13 +32,15 @@ import (
"sync"
"time"
"github.com/issue9/identicon"
"github.com/nfnt/resize"
"github.com/gogits/gogs/modules/identicon"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/setting"
)
//FIXME: remove cache module
var gravatarSource string
func UpdateGravatarSource() {
@@ -102,7 +104,7 @@ func New(hash string, cacheDir string) *Avatar {
expireDuration: time.Minute * 10,
reqParams: url.Values{
"d": {"retro"},
"size": {"200"},
"size": {"290"},
"r": {"pg"}}.Encode(),
imagePath: filepath.Join(cacheDir, hash+".image"), //maybe png or jpeg
}
@@ -153,7 +155,7 @@ func (this *Avatar) Encode(wr io.Writer, size int) (err error) {
if img, err = decodeImageFile(imgPath); err != nil {
return
}
m := resize.Resize(uint(size), 0, img, resize.NearestNeighbor)
m := resize.Resize(uint(size), 0, img, resize.Lanczos3)
return jpeg.Encode(wr, m, nil)
}
@@ -192,7 +194,7 @@ func (this *service) mustInt(r *http.Request, defaultValue int, keys ...string)
func (this *service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
urlPath := r.URL.Path
hash := urlPath[strings.LastIndex(urlPath, "/")+1:]
size := this.mustInt(r, 80, "s", "size") // default size = 80*80
size := this.mustInt(r, 290, "s", "size") // default size = 290*290
avatar := New(hash, this.cacheDir)
avatar.AlterImage = this.altImage

View File

@@ -4,15 +4,29 @@
package base
import (
"os"
"os/exec"
"path/filepath"
)
const DOC_URL = "https://github.com/gogits/go-gogs-client/wiki"
type (
TplName string
ApiJsonErr struct {
Message string `json:"message"`
DocUrl string `json:"url"`
}
)
var GoGetMetas = make(map[string]bool)
// ExecPath returns the executable path.
func ExecPath() (string, error) {
file, err := exec.LookPath(os.Args[0])
if err != nil {
return "", err
}
p, err := filepath.Abs(file)
if err != nil {
return "", err
}
return p, nil
}

View File

@@ -14,6 +14,7 @@ import (
"regexp"
"strings"
"github.com/Unknwon/com"
"github.com/russross/blackfriday"
"golang.org/x/net/html"
@@ -99,12 +100,33 @@ func (options *CustomRender) Link(out *bytes.Buffer, link []byte, title []byte,
options.Renderer.Link(out, link, title, content)
}
var (
svgSuffix = []byte(".svg")
svgSuffixWithMark = []byte(".svg?")
)
func (options *CustomRender) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
if len(link) > 0 && !isLink(link) {
link = []byte(path.Join(strings.Replace(options.urlPrefix, "/src/", "/raw/", 1), string(link)))
prefix := strings.Replace(options.urlPrefix, "/src/", "/raw/", 1)
if len(link) > 0 {
if isLink(link) {
// External link with .svg suffix usually means CI status.
if bytes.HasSuffix(link, svgSuffix) || bytes.Contains(link, svgSuffixWithMark) {
options.Renderer.Image(out, link, title, alt)
return
}
} else {
if link[0] != '/' {
prefix += "/"
}
link = []byte(prefix + string(link))
}
}
out.WriteString(`<a href="`)
out.Write(link)
out.WriteString(`">`)
options.Renderer.Image(out, link, title, alt)
out.WriteString("</a>")
}
var (
@@ -115,7 +137,43 @@ var (
sha1CurrentPattern = regexp.MustCompile(`\b[0-9a-f]{40}\b`)
)
func RenderSpecialLink(rawBytes []byte, urlPrefix string) []byte {
func cutoutVerbosePrefix(prefix string) string {
count := 0
for i := 0; i < len(prefix); i++ {
if prefix[i] == '/' {
count++
}
if count >= 3 {
return prefix[:i]
}
}
return prefix
}
func RenderIssueIndexPattern(rawBytes []byte, urlPrefix string, metas map[string]string) []byte {
urlPrefix = cutoutVerbosePrefix(urlPrefix)
ms := issueIndexPattern.FindAll(rawBytes, -1)
for _, m := range ms {
var space string
m2 := m
if m2[0] == ' ' {
space = " "
m2 = m2[1:]
}
if metas == nil {
rawBytes = bytes.Replace(rawBytes, m, []byte(fmt.Sprintf(`%s<a href="%s/issues/%s">%s</a>`,
space, urlPrefix, m2[1:], m2)), 1)
} else {
// Support for external issue tracker
metas["index"] = string(m2[1:])
rawBytes = bytes.Replace(rawBytes, m, []byte(fmt.Sprintf(`%s<a href="%s">%s</a>`,
space, com.Expand(metas["format"], metas), m2)), 1)
}
}
return rawBytes
}
func RenderSpecialLink(rawBytes []byte, urlPrefix string, metas map[string]string) []byte {
ms := MentionPattern.FindAll(rawBytes, -1)
for _, m := range ms {
m = bytes.TrimSpace(m)
@@ -123,29 +181,7 @@ func RenderSpecialLink(rawBytes []byte, urlPrefix string) []byte {
[]byte(fmt.Sprintf(`<a href="%s/%s">%s</a>`, setting.AppSubUrl, m[1:], m)), -1)
}
ms = commitPattern.FindAll(rawBytes, -1)
for _, m := range ms {
m = bytes.TrimSpace(m)
i := strings.Index(string(m), "commit/")
j := strings.Index(string(m), "#")
if j == -1 {
j = len(m)
}
rawBytes = bytes.Replace(rawBytes, m, []byte(fmt.Sprintf(
` <code><a href="%s">%s</a></code>`, m, ShortSha(string(m[i+7:j])))), -1)
}
ms = issueFullPattern.FindAll(rawBytes, -1)
for _, m := range ms {
m = bytes.TrimSpace(m)
i := strings.Index(string(m), "issues/")
j := strings.Index(string(m), "#")
if j == -1 {
j = len(m)
}
rawBytes = bytes.Replace(rawBytes, m, []byte(fmt.Sprintf(
` <a href="%s">#%s</a>`, m, ShortSha(string(m[i+7:j])))), -1)
}
rawBytes = RenderIssueIndexPattern(rawBytes, urlPrefix)
rawBytes = RenderIssueIndexPattern(rawBytes, urlPrefix, metas)
rawBytes = RenderSha1CurrentPattern(rawBytes, urlPrefix)
return rawBytes
}
@@ -159,33 +195,10 @@ func RenderSha1CurrentPattern(rawBytes []byte, urlPrefix string) []byte {
return rawBytes
}
func RenderIssueIndexPattern(rawBytes []byte, urlPrefix string) []byte {
ms := issueIndexPattern.FindAll(rawBytes, -1)
for _, m := range ms {
var space string
m2 := m
if m2[0] == ' ' {
space = " "
m2 = m2[1:]
}
rawBytes = bytes.Replace(rawBytes, m, []byte(fmt.Sprintf(`%s<a href="%s/issues/%s">%s</a>`,
space, urlPrefix, m2[1:], m2)), 1)
}
return rawBytes
}
func RenderRawMarkdown(body []byte, urlPrefix string) []byte {
htmlFlags := 0
// htmlFlags |= blackfriday.HTML_USE_XHTML
// htmlFlags |= blackfriday.HTML_USE_SMARTYPANTS
// htmlFlags |= blackfriday.HTML_SMARTYPANTS_FRACTIONS
// htmlFlags |= blackfriday.HTML_SMARTYPANTS_LATEX_DASHES
// htmlFlags |= blackfriday.HTML_SKIP_HTML
htmlFlags |= blackfriday.HTML_SKIP_STYLE
// htmlFlags |= blackfriday.HTML_SKIP_SCRIPT
// htmlFlags |= blackfriday.HTML_GITHUB_BLOCKCODE
htmlFlags |= blackfriday.HTML_OMIT_CONTENTS
// htmlFlags |= blackfriday.HTML_COMPLETE_PAGE
renderer := &CustomRender{
Renderer: blackfriday.HtmlRenderer(htmlFlags, "", ""),
urlPrefix: urlPrefix,
@@ -209,33 +222,96 @@ func RenderRawMarkdown(body []byte, urlPrefix string) []byte {
return body
}
var (
leftAngleBracket = []byte("</")
rightAngleBracket = []byte(">")
)
var noEndTags = []string{"img", "input", "br", "hr"}
// PreProcessMarkdown renders full links of commits, issues and pulls to shorter version.
func PreProcessMarkdown(rawHTML []byte, urlPrefix string) []byte {
ms := commitPattern.FindAll(rawHTML, -1)
for _, m := range ms {
m = bytes.TrimSpace(m)
i := strings.Index(string(m), "commit/")
j := strings.Index(string(m), "#")
if j == -1 {
j = len(m)
}
rawHTML = bytes.Replace(rawHTML, m, []byte(fmt.Sprintf(
` <code><a href="%s">%s</a></code>`, m, ShortSha(string(m[i+7:j])))), -1)
}
ms = issueFullPattern.FindAll(rawHTML, -1)
for _, m := range ms {
m = bytes.TrimSpace(m)
i := strings.Index(string(m), "issues/")
j := strings.Index(string(m), "#")
if j == -1 {
j = len(m)
}
rawHTML = bytes.Replace(rawHTML, m, []byte(fmt.Sprintf(
` <a href="%s">#%s</a>`, m, ShortSha(string(m[i+7:j])))), -1)
}
return rawHTML
}
// PostProcessMarkdown treats different types of HTML differently,
// and only renders special links for plain text blocks.
func PostProcessMarkdown(rawHtml []byte, urlPrefix string) []byte {
func PostProcessMarkdown(rawHtml []byte, urlPrefix string, metas map[string]string) []byte {
startTags := make([]string, 0, 5)
var buf bytes.Buffer
tokenizer := html.NewTokenizer(bytes.NewReader(rawHtml))
OUTER_LOOP:
for html.ErrorToken != tokenizer.Next() {
token := tokenizer.Token()
switch token.Type {
case html.TextToken:
buf.Write(RenderSpecialLink([]byte(token.String()), urlPrefix))
buf.Write(RenderSpecialLink([]byte(token.String()), urlPrefix, metas))
case html.StartTagToken:
buf.WriteString(token.String())
tagName := token.Data
// If this is an excluded tag, we skip processing all output until a close tag is encountered.
if strings.EqualFold("a", tagName) || strings.EqualFold("code", tagName) || strings.EqualFold("pre", tagName) {
stackNum := 1
for html.ErrorToken != tokenizer.Next() {
token = tokenizer.Token()
// Copy the token to the output verbatim
buf.WriteString(token.String())
// If this is the close tag, we are done
if html.EndTagToken == token.Type && strings.EqualFold(tagName, token.Data) {
break
if token.Type == html.StartTagToken {
stackNum++
}
// If this is the close tag to the outer-most, we are done
if token.Type == html.EndTagToken && strings.EqualFold(tagName, token.Data) {
stackNum--
if stackNum == 0 {
break
}
}
}
continue OUTER_LOOP
}
if !com.IsSliceContainsStr(noEndTags, token.Data) {
startTags = append(startTags, token.Data)
}
case html.EndTagToken:
if len(startTags) == 0 {
buf.WriteString(token.String())
break
}
buf.Write(leftAngleBracket)
buf.WriteString(startTags[len(startTags)-1])
buf.Write(rightAngleBracket)
startTags = startTags[:len(startTags)-1]
default:
buf.WriteString(token.String())
}
@@ -250,13 +326,14 @@ func PostProcessMarkdown(rawHtml []byte, urlPrefix string) []byte {
return rawHtml
}
func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte {
result := RenderRawMarkdown(rawBytes, urlPrefix)
result = PostProcessMarkdown(result, urlPrefix)
func RenderMarkdown(rawBytes []byte, urlPrefix string, metas map[string]string) []byte {
result := PreProcessMarkdown(rawBytes, urlPrefix)
result = RenderRawMarkdown(result, urlPrefix)
result = PostProcessMarkdown(result, urlPrefix, metas)
result = Sanitizer.SanitizeBytes(result)
return result
}
func RenderMarkdownString(raw, urlPrefix string) string {
return string(RenderMarkdown([]byte(raw), urlPrefix))
func RenderMarkdownString(raw, urlPrefix string, metas map[string]string) string {
return string(RenderMarkdown([]byte(raw), urlPrefix, metas))
}

View File

@@ -15,6 +15,7 @@ import (
"hash"
"html/template"
"math"
"regexp"
"strings"
"time"
@@ -22,14 +23,16 @@ import (
"github.com/Unknwon/i18n"
"github.com/microcosm-cc/bluemonday"
"github.com/gogits/chardet"
"github.com/gogits/gogs/modules/avatar"
"github.com/gogits/gogs/modules/setting"
)
var Sanitizer = bluemonday.UGCPolicy()
var Sanitizer = bluemonday.UGCPolicy().AllowAttrs("class").Matching(regexp.MustCompile(`[\p{L}\p{N}\s\-_',:\[\]!\./\\\(\)&]*`)).OnElements("code")
// Encode string to md5 hex value.
func EncodeMd5(str string) string {
// EncodeMD5 encodes string to md5 hex value.
func EncodeMD5(str string) string {
m := md5.New()
m.Write([]byte(str))
return hex.EncodeToString(m.Sum(nil))
@@ -42,6 +45,22 @@ func EncodeSha1(str string) string {
return hex.EncodeToString(h.Sum(nil))
}
func ShortSha(sha1 string) string {
if len(sha1) == 40 {
return sha1[:10]
}
return sha1
}
func DetectEncoding(content []byte) (string, error) {
detector := chardet.NewTextDetector()
result, err := detector.DetectBest(content)
if result.Charset != "UTF-8" && len(setting.Repository.AnsiCharset) > 0 {
return setting.Repository.AnsiCharset, err
}
return result.Charset, err
}
func BasicAuthDecode(encoded string) (string, string, error) {
s, err := base64.StdEncoding.DecodeString(encoded)
if err != nil {

File diff suppressed because one or more lines are too long

View File

@@ -111,7 +111,7 @@ func TestSpecSchedule(t *testing.T) {
t.Error(err)
}
if !reflect.DeepEqual(actual, c.expected) {
t.Errorf("%s => (expected) %b != %b (actual)", c.expr, c.expected, actual)
t.Errorf("%s => (expected) %v != %v (actual)", c.expr, c.expected, actual)
}
}
}

View File

@@ -18,7 +18,7 @@ type Blob struct {
}
func (b *Blob) Data() (io.Reader, error) {
stdout, stderr, err := com.ExecCmdDirBytes(b.repo.Path, "git", "show", b.Id.String())
stdout, stderr, err := com.ExecCmdDirBytes(b.repo.Path, "git", "show", b.ID.String())
if err != nil {
return nil, errors.New(string(stderr))
}

View File

@@ -14,7 +14,7 @@ import (
// Commit represents a git commit.
type Commit struct {
Tree
Id sha1 // The id of this commit object
ID sha1 // The id of this commit object
Author *Signature
Committer *Signature
CommitMessage string
@@ -35,7 +35,7 @@ func (c *Commit) Summary() string {
// Return oid of the parent number n (0-based index). Return nil if no such parent exists.
func (c *Commit) ParentId(n int) (id sha1, err error) {
if n >= len(c.parents) {
err = IdNotExist
err = IDNotExist
return
}
return c.parents[n], nil
@@ -61,7 +61,7 @@ func (c *Commit) ParentCount() int {
}
func (c *Commit) CommitsBefore() (*list.List, error) {
return c.repo.getCommitsBefore(c.Id)
return c.repo.getCommitsBefore(c.ID)
}
func (c *Commit) CommitsBeforeUntil(commitId string) (*list.List, error) {
@@ -73,19 +73,19 @@ func (c *Commit) CommitsBeforeUntil(commitId string) (*list.List, error) {
}
func (c *Commit) CommitsCount() (int, error) {
return c.repo.commitsCount(c.Id)
return c.repo.commitsCount(c.ID)
}
func (c *Commit) SearchCommits(keyword string) (*list.List, error) {
return c.repo.searchCommits(c.Id, keyword)
return c.repo.searchCommits(c.ID, keyword)
}
func (c *Commit) CommitsByRange(page int) (*list.List, error) {
return c.repo.commitsByRange(c.Id, page)
return c.repo.commitsByRange(c.ID, page)
}
func (c *Commit) GetCommitOfRelPath(relPath string) (*Commit, error) {
return c.repo.getCommitOfRelPath(c.Id, relPath)
return c.repo.getCommitOfRelPath(c.ID, relPath)
}
func (c *Commit) GetSubModule(entryname string) (*SubModule, error) {

View File

@@ -28,7 +28,7 @@ func (c *Commit) CreateArchive(path string, archiveType ArchiveType) error {
return fmt.Errorf("unknown format: %v", archiveType)
}
_, stderr, err := com.ExecCmdDir(c.repo.Path, "git", "archive", "--format="+format, "-o", path, c.Id.String())
_, stderr, err := com.ExecCmdDir(c.repo.Path, "git", "archive", "--format="+format, "-o", path, c.ID.String())
if err != nil {
return fmt.Errorf("%s", stderr)
}

22
modules/git/error.go Normal file
View File

@@ -0,0 +1,22 @@
// Copyright 2015 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package git
import (
"fmt"
)
type ErrUnsupportedVersion struct {
Required string
}
func IsErrUnsupportedVersion(err error) bool {
_, ok := err.(ErrUnsupportedVersion)
return ok
}
func (err ErrUnsupportedVersion) Error() string {
return fmt.Sprintf("Operation requires higher version [required: %s]", err.Required)
}

View File

@@ -16,15 +16,23 @@ import (
// hookNames is a list of Git hooks' name that are supported.
var hookNames = []string{
"pre-applypatch",
"applypatch-msg",
"pre-applypatch",
"post-applypatch",
"pre-commit",
"prepare-commit-msg",
"commit-msg",
"pre-commit",
"pre-rebase",
"post-commit",
"pre-rebase",
"post-checkout",
"post-merge",
"pre-push",
// "update",
"post-receive",
"post-update",
"push-to-checkout",
"pre-auto-gc",
"post-rewrite",
}
var (

View File

@@ -5,6 +5,7 @@
package git
import (
"errors"
"path/filepath"
)
@@ -21,6 +22,8 @@ func OpenRepository(repoPath string) (*Repository, error) {
repoPath, err := filepath.Abs(repoPath)
if err != nil {
return nil, err
} else if !isDir(repoPath) {
return nil, errors.New("no such file or directory")
}
return &Repository{Path: repoPath}, nil

View File

@@ -35,3 +35,16 @@ func (repo *Repository) GetBranches() ([]string, error) {
}
return branches, nil
}
// SetDefaultBranch sets default branch of repository.
func (repo *Repository) SetDefaultBranch(branchName string) error {
if gitVer.LessThan(MustParseVersion("1.7.10")) {
return ErrUnsupportedVersion{"1.7.10"}
}
_, stderr, err := com.ExecCmdDir(repo.Path, "git", "symbolic-ref", "HEAD", "refs/heads/"+branchName)
if err != nil {
return concatenateError(err, stderr)
}
return nil
}

View File

@@ -70,7 +70,7 @@ l:
if err != nil {
return nil, err
}
commit.Tree.Id = id
commit.Tree.ID = id
case "parent":
// A commit can have one or more parents
oid, err := NewIdFromString(string(line[spacepos+1:]))
@@ -111,9 +111,9 @@ func (repo *Repository) getCommit(id sha1) (*Commit, error) {
repo.commitCache = make(map[sha1]*Commit, 10)
}
data, bytErr, err := com.ExecCmdDirBytes(repo.Path, "git", "cat-file", "-p", id.String())
data, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "cat-file", "-p", id.String())
if err != nil {
return nil, errors.New(err.Error() + ": " + string(bytErr))
return nil, concatenateError(err, string(stderr))
}
commit, err := parseCommitData(data)
@@ -121,7 +121,7 @@ func (repo *Repository) getCommit(id sha1) (*Commit, error) {
return nil, err
}
commit.repo = repo
commit.Id = id
commit.ID = id
repo.commitCache[id] = commit
return commit, nil
@@ -211,7 +211,7 @@ func (repo *Repository) CommitsBetween(last *Commit, before *Commit) (*list.List
var err error
cur := last
for {
if cur.Id.Equal(before.Id) {
if cur.ID.Equal(before.ID) {
break
}
l.PushBack(cur)
@@ -229,7 +229,7 @@ func (repo *Repository) CommitsBetween(last *Commit, before *Commit) (*list.List
func (repo *Repository) commitsBefore(lock *sync.Mutex, l *list.List, parent *list.Element, id sha1, limit int) error {
commit, err := repo.getCommit(id)
if err != nil {
return err
return fmt.Errorf("getCommit: %v", err)
}
var e *list.Element
@@ -240,7 +240,7 @@ func (repo *Repository) commitsBefore(lock *sync.Mutex, l *list.List, parent *li
for {
if in == nil {
break
} else if in.Value.(*Commit).Id.Equal(commit.Id) {
} else if in.Value.(*Commit).ID.Equal(commit.ID) {
return nil
} else {
if in.Next() == nil {
@@ -301,8 +301,7 @@ func (repo *Repository) CommitsByFileAndRange(branch, file string, page int) (*l
func (repo *Repository) getCommitsBefore(id sha1) (*list.List, error) {
l := list.New()
lock := new(sync.Mutex)
err := repo.commitsBefore(lock, l, nil, id, 0)
return l, err
return l, repo.commitsBefore(lock, l, nil, id, 0)
}
func (repo *Repository) searchCommits(id sha1, keyword string) (*list.List, error) {

View File

@@ -20,31 +20,55 @@ type PullRequestInfo struct {
NumFiles int
}
// GetMergeBase checks and returns merge base of two branches.
func (repo *Repository) GetMergeBase(remoteBranch, headBranch string) (string, error) {
// Get merge base commit.
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "merge-base", remoteBranch, headBranch)
if err != nil {
return "", fmt.Errorf("get merge base: %v", concatenateError(err, stderr))
}
return strings.TrimSpace(stdout), nil
}
// AddRemote adds a remote to repository.
func (repo *Repository) AddRemote(name, path string) error {
_, stderr, err := com.ExecCmdDir(repo.Path, "git", "remote", "add", "-f", name, path)
if err != nil {
return fmt.Errorf("add remote(%s - %s): %v", name, path, concatenateError(err, stderr))
}
return nil
}
// RemoveRemote removes a remote from repository.
func (repo *Repository) RemoveRemote(name string) error {
_, stderr, err := com.ExecCmdDir(repo.Path, "git", "remote", "remove", name)
if err != nil {
return fmt.Errorf("remove remote(%s): %v", name, concatenateError(err, stderr))
}
return nil
}
// GetPullRequestInfo generates and returns pull request information
// between base and head branches of repositories.
func (repo *Repository) GetPullRequestInfo(basePath, baseBranch, headBranch string) (*PullRequestInfo, error) {
func (repo *Repository) GetPullRequestInfo(basePath, baseBranch, headBranch string) (_ *PullRequestInfo, err error) {
// Add a temporary remote.
tmpRemote := com.ToStr(time.Now().UnixNano())
_, stderr, err := com.ExecCmdDir(repo.Path, "git", "remote", "add", "-f", tmpRemote, basePath)
if err != nil {
return nil, fmt.Errorf("add base as remote: %v", concatenateError(err, stderr))
if err = repo.AddRemote(tmpRemote, basePath); err != nil {
return nil, fmt.Errorf("AddRemote: %v", err)
}
defer func() {
com.ExecCmdDir(repo.Path, "git", "remote", "remove", tmpRemote)
repo.RemoveRemote(tmpRemote)
}()
prInfo := new(PullRequestInfo)
var stdout string
remoteBranch := "remotes/" + tmpRemote + "/" + baseBranch
// Get merge base commit.
stdout, stderr, err = com.ExecCmdDir(repo.Path, "git", "merge-base", remoteBranch, headBranch)
if err != nil {
return nil, fmt.Errorf("get merge base: %v", concatenateError(err, stderr))
}
prInfo.MergeBase = strings.TrimSpace(stdout)
stdout, stderr, err = com.ExecCmdDir(repo.Path, "git", "log", remoteBranch+"..."+headBranch, prettyLogFormat)
prInfo := new(PullRequestInfo)
prInfo.MergeBase, err = repo.GetMergeBase(remoteBranch, headBranch)
if err != nil {
return nil, fmt.Errorf("GetMergeBase: %v", err)
}
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "log", prInfo.MergeBase+"..."+headBranch, prettyLogFormat)
if err != nil {
return nil, fmt.Errorf("list diff logs: %v", concatenateError(err, stderr))
}
@@ -64,20 +88,8 @@ func (repo *Repository) GetPullRequestInfo(basePath, baseBranch, headBranch stri
}
// GetPatch generates and returns patch data between given branches.
func (repo *Repository) GetPatch(basePath, baseBranch, headBranch string) ([]byte, error) {
// Add a temporary remote.
tmpRemote := com.ToStr(time.Now().UnixNano())
_, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "remote", "add", "-f", tmpRemote, basePath)
if err != nil {
return nil, fmt.Errorf("add base as remote: %v", concatenateError(err, string(stderr)))
}
defer func() {
com.ExecCmdDir(repo.Path, "git", "remote", "remove", tmpRemote)
}()
var stdout []byte
remoteBranch := "remotes/" + tmpRemote + "/" + baseBranch
stdout, stderr, err = com.ExecCmdDirBytes(repo.Path, "git", "diff", "-p", remoteBranch, headBranch)
func (repo *Repository) GetPatch(mergeBase, headBranch string) ([]byte, error) {
stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "diff", "-p", "--binary", mergeBase, headBranch)
if err != nil {
return nil, concatenateError(err, string(stderr))
}

View File

@@ -69,7 +69,7 @@ func (repo *Repository) getTag(id sha1) (*Tag, error) {
// Tag is a commit.
if ObjectType(tp) == COMMIT {
tag := &Tag{
Id: id,
ID: id,
Object: id,
Type: string(COMMIT),
repo: repo,
@@ -89,7 +89,7 @@ func (repo *Repository) getTag(id sha1) (*Tag, error) {
return nil, err
}
tag.Id = id
tag.ID = id
tag.repo = repo
repo.tagCache[id] = tag

Some files were not shown because too many files have changed in this diff Show More