mirror of
https://github.com/gogs/gogs.git
synced 2026-02-28 09:10:57 +01:00
Compare commits
283 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4df378b892 | ||
|
|
351dfc95a9 | ||
|
|
837155577a | ||
|
|
7e88420bc6 | ||
|
|
5911fc3512 | ||
|
|
4108c12092 | ||
|
|
e444a67d59 | ||
|
|
0cce4439ce | ||
|
|
59c965a5ec | ||
|
|
76bdbcc969 | ||
|
|
477b4d3b50 | ||
|
|
4d31eb2c0d | ||
|
|
d0b0d24f22 | ||
|
|
5d95ffe3eb | ||
|
|
98da7241a0 | ||
|
|
bc17f2f759 | ||
|
|
40f3142264 | ||
|
|
b21160a13a | ||
|
|
5b2afd8ec8 | ||
|
|
7a3eccc709 | ||
|
|
2a8d71367d | ||
|
|
3d5d61778a | ||
|
|
ddcc8d998c | ||
|
|
99e9bbef6c | ||
|
|
0e96a46020 | ||
|
|
df5ed64cca | ||
|
|
2a0bb1fa90 | ||
|
|
c6083c335e | ||
|
|
2e9c4ddedb | ||
|
|
fa8bf0f1d7 | ||
|
|
9a2e43bff2 | ||
|
|
bd5dc626e8 | ||
|
|
626dc1f0bd | ||
|
|
1b0ef0ec0b | ||
|
|
e4a092fb5a | ||
|
|
b886fb4bf0 | ||
|
|
fa5e372f75 | ||
|
|
356f1438a6 | ||
|
|
a19aaa439d | ||
|
|
0d469f261e | ||
|
|
c3440c4dd3 | ||
|
|
718d3ae258 | ||
|
|
b8d48bdb62 | ||
|
|
15d62bba82 | ||
|
|
eb6c634475 | ||
|
|
eec06fb3df | ||
|
|
df05134494 | ||
|
|
9bd3ebe207 | ||
|
|
a576224d0e | ||
|
|
989f30eb41 | ||
|
|
06d293a84e | ||
|
|
120cd4e471 | ||
|
|
1cbd4c01fb | ||
|
|
2528c482e9 | ||
|
|
b1a53f6d8e | ||
|
|
fde9c69679 | ||
|
|
3df5dcc1dc | ||
|
|
14080dd61d | ||
|
|
da2585c11e | ||
|
|
dd8a06a397 | ||
|
|
4e0c697aaf | ||
|
|
3ffbb54337 | ||
|
|
67cfb6735b | ||
|
|
b7508b06fa | ||
|
|
2a0db47935 | ||
|
|
dce2a9e7e1 | ||
|
|
abb02889f2 | ||
|
|
e2ca53029e | ||
|
|
b5f6206a65 | ||
|
|
3de9c11ea7 | ||
|
|
ae54d878c0 | ||
|
|
89244b74c6 | ||
|
|
ca8ce793d1 | ||
|
|
978dc00305 | ||
|
|
bf26808fb3 | ||
|
|
404867f206 | ||
|
|
f0ee33267c | ||
|
|
f3eaa4c592 | ||
|
|
f41360d864 | ||
|
|
e82ee40e9e | ||
|
|
1ee7c33e93 | ||
|
|
e538ff2770 | ||
|
|
76d4b9288b | ||
|
|
05ba8622f0 | ||
|
|
4795fa01d8 | ||
|
|
942fd6be53 | ||
|
|
56dd430a10 | ||
|
|
e0bae9547a | ||
|
|
bfe6027266 | ||
|
|
4d9499c2d3 | ||
|
|
98e989d52c | ||
|
|
5742f9fe69 | ||
|
|
1802d52362 | ||
|
|
cab2911f23 | ||
|
|
81133d45a1 | ||
|
|
a51acf1751 | ||
|
|
edbb67cb3f | ||
|
|
c5e249c0be | ||
|
|
37a372f6f5 | ||
|
|
f122d0856e | ||
|
|
4a6016f5af | ||
|
|
cc8f5add6e | ||
|
|
ec2423ad7c | ||
|
|
c4bab163cb | ||
|
|
0068b8106b | ||
|
|
2580e7b57e | ||
|
|
3d3498bda1 | ||
|
|
29375059e1 | ||
|
|
85449b2f11 | ||
|
|
b83cb36049 | ||
|
|
73d9eebf01 | ||
|
|
b73241ceb1 | ||
|
|
e350d74c8a | ||
|
|
149e540322 | ||
|
|
314664892c | ||
|
|
a9a386a1e5 | ||
|
|
3eae4ecde7 | ||
|
|
ec98deeb8c | ||
|
|
61fdd8c571 | ||
|
|
4813665d0a | ||
|
|
640dce12a8 | ||
|
|
99b958db43 | ||
|
|
22b0dfbb35 | ||
|
|
4a64ae4abf | ||
|
|
926e91820a | ||
|
|
91ae2ad28b | ||
|
|
db30ea03d8 | ||
|
|
0be8b1b1a1 | ||
|
|
d45302a6ba | ||
|
|
834d38a8fb | ||
|
|
5572884c6b | ||
|
|
3460ec1039 | ||
|
|
53bf23d965 | ||
|
|
573305f3d3 | ||
|
|
7ccce4d110 | ||
|
|
9ed60d96a9 | ||
|
|
b6d2b96259 | ||
|
|
e5fe367b82 | ||
|
|
19e8ce0354 | ||
|
|
f907a5c98b | ||
|
|
da607c611d | ||
|
|
3d54f6c0a4 | ||
|
|
2093586241 | ||
|
|
117afe7620 | ||
|
|
d3a5ff7b6b | ||
|
|
dcb391d341 | ||
|
|
830d000667 | ||
|
|
5a14c3cf98 | ||
|
|
e57b2dffa4 | ||
|
|
ca96e04e5f | ||
|
|
9950f5a5bd | ||
|
|
1d7a1b6034 | ||
|
|
a59b1fcc21 | ||
|
|
c5a9be9115 | ||
|
|
f86afb04a2 | ||
|
|
5d1f5f32d0 | ||
|
|
e42fcb033d | ||
|
|
392f3ee210 | ||
|
|
c50a3503e6 | ||
|
|
aaa3f1b2b9 | ||
|
|
2b10fdc4dc | ||
|
|
2f28a0310b | ||
|
|
253513cedd | ||
|
|
eb30cbab81 | ||
|
|
144663a3cf | ||
|
|
ba92f4687e | ||
|
|
968edb3e44 | ||
|
|
3ca544912f | ||
|
|
7f9598141b | ||
|
|
56c66ee486 | ||
|
|
21ad4bf0fe | ||
|
|
0128036514 | ||
|
|
ec8d41765d | ||
|
|
ffbeda077c | ||
|
|
880849a283 | ||
|
|
b2fb7e3fd2 | ||
|
|
737da1a374 | ||
|
|
f63a468dfc | ||
|
|
efaf60ba5a | ||
|
|
e6b2a01e5d | ||
|
|
52c8f69163 | ||
|
|
b80e848d02 | ||
|
|
f12832c61e | ||
|
|
dcc740fd26 | ||
|
|
8966750fd4 | ||
|
|
3623b0927e | ||
|
|
d37da1f392 | ||
|
|
cefc50b278 | ||
|
|
b4877b1e06 | ||
|
|
eea2e05da6 | ||
|
|
2b1e955f91 | ||
|
|
63cdee84d1 | ||
|
|
6a6a7512c2 | ||
|
|
6b30b20765 | ||
|
|
126228d146 | ||
|
|
74dfe439c2 | ||
|
|
1d4a5b1825 | ||
|
|
987dcc5372 | ||
|
|
9b6c835715 | ||
|
|
902b578465 | ||
|
|
3d14e73fd8 | ||
|
|
9bcc3c1ea3 | ||
|
|
6a66e5fc98 | ||
|
|
c0b0ce7b1a | ||
|
|
dc0c0dc06b | ||
|
|
2158e6fc43 | ||
|
|
ee686f6231 | ||
|
|
481be9b5c9 | ||
|
|
9330c943cd | ||
|
|
915bf1d2e3 | ||
|
|
f455125d4d | ||
|
|
df339ad8b0 | ||
|
|
2c653141a8 | ||
|
|
fc56f42dc3 | ||
|
|
0bd4d15e47 | ||
|
|
e04c97b9fa | ||
|
|
f04d773f4f | ||
|
|
4325b01a58 | ||
|
|
052519a7d7 | ||
|
|
e347736c9e | ||
|
|
56006da34b | ||
|
|
efea642d6c | ||
|
|
81d7359fdd | ||
|
|
9a0902523b | ||
|
|
d2808e38fe | ||
|
|
7a9777ae36 | ||
|
|
62533560ce | ||
|
|
dc7e74ebb1 | ||
|
|
9a27e1b90c | ||
|
|
ff5f14431e | ||
|
|
ab9411be2a | ||
|
|
114e6790f8 | ||
|
|
ec5f881384 | ||
|
|
9ab96172fc | ||
|
|
e06558e208 | ||
|
|
54fd4cc5fb | ||
|
|
3deddabfd8 | ||
|
|
0cbf56855a | ||
|
|
917d334ebd | ||
|
|
bb1fbe4e70 | ||
|
|
cceb3364bb | ||
|
|
d370effca5 | ||
|
|
29ed7872f8 | ||
|
|
5dc3dd1704 | ||
|
|
134d8e7681 | ||
|
|
c9b65f0fdc | ||
|
|
951037c0ae | ||
|
|
7046df6028 | ||
|
|
1db3ae6601 | ||
|
|
612d0d6d25 | ||
|
|
18de67380c | ||
|
|
1a901433e2 | ||
|
|
e030109b5a | ||
|
|
35d49d3b34 | ||
|
|
ca5678da32 | ||
|
|
4d3138cd10 | ||
|
|
942284648e | ||
|
|
4f03b81ec7 | ||
|
|
b4970b3cc3 | ||
|
|
85c58eba90 | ||
|
|
84a43b38cf | ||
|
|
7c80eba77f | ||
|
|
9c12ed3b6e | ||
|
|
7b1c10ea7e | ||
|
|
679af4ddea | ||
|
|
f8ae161c74 | ||
|
|
1d57f0d64f | ||
|
|
1559bd58e7 | ||
|
|
6a664e88c7 | ||
|
|
0f438ef0b3 | ||
|
|
a6c7716742 | ||
|
|
1c3754bcec | ||
|
|
ee645af107 | ||
|
|
3e7695ae91 | ||
|
|
2268d28189 | ||
|
|
eee6e4206a | ||
|
|
1bfebdcdf6 | ||
|
|
588a0db218 | ||
|
|
5edc2f6d6c | ||
|
|
4dd731cb53 | ||
|
|
ae0fadeb0e | ||
|
|
2717ada14c | ||
|
|
e1c04f043b |
@@ -13,7 +13,7 @@ watch_dirs = [
|
||||
watch_exts = [".go"]
|
||||
build_delay = 1500
|
||||
cmds = [
|
||||
["go", "install"], # sqlite redis memcache cert pam tidb
|
||||
["go", "build"],
|
||||
["go", "install", "-race"], # sqlite redis memcache cert pam tidb
|
||||
["go", "build", "-race"],
|
||||
["./gogs", "web"]
|
||||
]
|
||||
50
.gopmfile
50
.gopmfile
@@ -3,42 +3,46 @@ path = github.com/gogits/gogs
|
||||
|
||||
[deps]
|
||||
github.com/bradfitz/gomemcache = commit:72a68649ba
|
||||
github.com/codegangsta/cli = commit:70e3fa5
|
||||
github.com/go-macaron/binding = commit:864a5ce
|
||||
github.com/codegangsta/cli = commit:b5232bb
|
||||
github.com/go-macaron/binding = commit:2502aaf
|
||||
github.com/go-macaron/cache = commit:5617353
|
||||
github.com/go-macaron/captcha = commit:875ff77
|
||||
github.com/go-macaron/csrf = commit:75c2b04
|
||||
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:5e728b6
|
||||
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:ddfcf96
|
||||
github.com/go-sql-driver/mysql = commit:527bcd55aa
|
||||
github.com/go-xorm/core = commit:3e10003353
|
||||
github.com/go-xorm/xorm = commit:8bf4405
|
||||
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:1030bf8
|
||||
github.com/issue9/identicon = commit:5a61672
|
||||
github.com/klauspost/compress = commit:0449b1c
|
||||
github.com/klauspost/cpuid = commit:8d9fe96
|
||||
github.com/klauspost/crc32 = commit:f8d2e12
|
||||
github.com/lib/pq = commit:83c4f41
|
||||
github.com/gogits/git-shell = commit:1ffc4bc
|
||||
github.com/gogits/go-gogs-client = commit:4b541fa
|
||||
github.com/issue9/identicon = commit:f8c0d2c
|
||||
github.com/kardianos/minwinsvc = commit:cad6b2b
|
||||
github.com/klauspost/compress = commit:42eb574
|
||||
github.com/klauspost/cpuid = commit:eebb3ea
|
||||
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/mssola/user_agent = commit:783ec61
|
||||
github.com/msteinert/pam = commit:6534f23b39
|
||||
github.com/msteinert/pam = commit:02ccfbf
|
||||
github.com/nfnt/resize = commit:dc93e1b98c
|
||||
github.com/russross/blackfriday = commit:510be64
|
||||
github.com/russross/blackfriday = commit:d18b67a
|
||||
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:7457d88830
|
||||
github.com/Unknwon/i18n = commit:3b48b66
|
||||
github.com/Unknwon/paginater = commit:7748a72
|
||||
golang.org/x/net =
|
||||
golang.org/x/text =
|
||||
gopkg.in/gomail.v2 = commit:df6fc79
|
||||
gopkg.in/ini.v1 = commit:060d7da
|
||||
golang.org/x/net = commit:d75b190
|
||||
golang.org/x/text = commit:458f474
|
||||
golang.org/x/crypto = commit:7b85b09
|
||||
gopkg.in/asn1-ber.v1 = commit:4e86f43
|
||||
gopkg.in/gomail.v2 = commit:fbb71dd
|
||||
gopkg.in/ini.v1 = commit:a4e5487
|
||||
gopkg.in/ldap.v2 = commit:e9a325d
|
||||
gopkg.in/macaron.v1 = commit:1c6dd87
|
||||
gopkg.in/redis.v2 = commit:e617904962
|
||||
|
||||
|
||||
@@ -24,4 +24,4 @@ before:
|
||||
- mv packager/.godir .
|
||||
after:
|
||||
- mv bin/main gogs
|
||||
after_install: ./packager/debian/postinst
|
||||
after_install: ./packager/hooks/postinst
|
||||
|
||||
@@ -42,13 +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 `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.
|
||||
|
||||
If your pull request is not accepted on the first try, don't be discouraged! If there's a problem with the implementation, hopefully you received feedback on what to improve.
|
||||
|
||||
We're trying very hard to keep Gogs lean and focused. We don't want it to do everything for everybody. This means that we might decide against incorporating a new feature. We believe you do like to discuss with us first in [Gitter](https://gitter.im/gogits/gogs).
|
||||
Please read detailed information on [Wiki](https://github.com/gogits/gogs/wiki/Contributing-Code).
|
||||
|
||||
### Ask For Help
|
||||
|
||||
|
||||
@@ -19,4 +19,4 @@ RUN ./docker/build.sh
|
||||
VOLUME ["/data"]
|
||||
EXPOSE 22 3000
|
||||
ENTRYPOINT ["docker/start.sh"]
|
||||
CMD ["/usr/bin/s6-svscan", "/app/gogs/docker/s6/"]
|
||||
CMD ["/bin/s6-svscan", "/app/gogs/docker/s6/"]
|
||||
|
||||
22
Dockerfile.rpi
Normal file
22
Dockerfile.rpi
Normal 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/"]
|
||||
25
Makefile
25
Makefile
@@ -1,17 +1,23 @@
|
||||
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
|
||||
.PHONY: build pack release bindata clean
|
||||
|
||||
build:
|
||||
.IGNORE: public/css/gogs.css
|
||||
|
||||
build: $(GENERATED)
|
||||
go install -ldflags '$(LDFLAGS)' -tags '$(TAGS)'
|
||||
go build -ldflags '$(LDFLAGS)' -tags '$(TAGS)'
|
||||
cp '$(GOPATH)/bin/gogs' .
|
||||
|
||||
govet:
|
||||
go tool vet -composites=false -methods=false -structtags=false .
|
||||
@@ -25,11 +31,18 @@ pack:
|
||||
|
||||
release: build pack
|
||||
|
||||
bindata:
|
||||
go-bindata -o=modules/bindata/bindata.go -ignore="\\.DS_Store|README.md" -pkg=bindata conf/...
|
||||
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
|
||||
find . -name ".DS_Store" -print0 | xargs -0 rm
|
||||
|
||||
47
README.md
47
README.md
@@ -1,35 +1,22 @@
|
||||
Gogs - Go Git Service [](https://travis-ci.org/gogits/gogs)
|
||||
Gogs - Go Git Service [](https://travis-ci.org/gogits/gogs) [](https://quay.io/repository/gogs/gogs) [](https://crowdin.com/project/gogs) [](https://gitter.im/gogits/gogs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
=====================
|
||||
|
||||
[](https://gitter.im/gogits/gogs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||

|
||||
|
||||

|
||||
##### Current version: 0.8.0
|
||||
|
||||
##### Current version: 0.7.6 Beta
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="33%"><img src="http://gogs.io/img/screenshots/1.png"></td>
|
||||
<td width="33%"><img src="http://gogs.io/img/screenshots/2.png"></td>
|
||||
<td width="33%"><img src="http://gogs.io/img/screenshots/3.png"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="http://gogs.io/img/screenshots/4.png"></td>
|
||||
<td><img src="http://gogs.io/img/screenshots/5.png"></td>
|
||||
<td><img src="http://gogs.io/img/screenshots/6.png"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="http://gogs.io/img/screenshots/7.png"></td>
|
||||
<td><img src="http://gogs.io/img/screenshots/8.png"></td>
|
||||
<td><img src="http://gogs.io/img/screenshots/9.png"></td>
|
||||
</tr>
|
||||
</table>
|
||||
| Web | UI | Preview |
|
||||
|:-------------:|:-------:|:-------:|
|
||||
||||
|
||||
||||
|
||||
||||
|
||||
|
||||
### 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.
|
||||
- :bangbang: You **MUST** read [CONTRIBUTING.md](CONTRIBUTING.md) for bug report and contributing code. :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.
|
||||
- Due to testing purpose, data of [try.gogs.io](https://try.gogs.io) was 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.
|
||||
- :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 and feature Pull Requests, otherwise it's high possibilities that we are not going to merge it.</span>:bangbang:
|
||||
- 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.
|
||||
@@ -44,7 +31,7 @@ The goal of this project is to make the easiest, fastest, and most painless way
|
||||
|
||||
- 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!
|
||||
- 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)!
|
||||
|
||||
@@ -57,12 +44,11 @@ The goal of this project is to make the easiest, fastest, and most painless way
|
||||
- Account/Organization/Repository management
|
||||
- Repository/Organization webhooks (including Slack)
|
||||
- Repository Git hooks/deploy keys
|
||||
- Repository issues and pull requests
|
||||
- Repository issues, pull requests and wiki
|
||||
- Add/Remove repository collaborators
|
||||
- Gravatar and custom source
|
||||
- Mail service
|
||||
- Administration panel
|
||||
- CI integration: [Drone](https://github.com/drone/drone)
|
||||
- Supports MySQL, PostgreSQL, SQLite3 and [TiDB](https://github.com/pingcap/tidb) (experimental)
|
||||
- Multi-language support ([14 languages](https://crowdin.com/project/gogs))
|
||||
|
||||
@@ -108,6 +94,13 @@ There are 5 ways to install Gogs:
|
||||
- [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
|
||||
|
||||
|
||||
12
README_ZH.md
12
README_ZH.md
@@ -11,7 +11,7 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
|
||||
|
||||
- 有关基本用法和变更日志,请通过 [使用手册](http://gogs.io/docs/intro/) 查看。
|
||||
- 您可以到 [Trello Board](https://trello.com/b/uxAoeLUl/gogs-go-git-service) 跟随开发团队的脚步。
|
||||
- 想要先睹为快?通过 [在线体验](https://try.gogs.io/gogs/gogs) 或查看 **安装部署 -> 二进制安装** 小节。
|
||||
- 想要先睹为快?直接去 [在线体验](https://try.gogs.io/gogs/gogs) 。
|
||||
- 使用过程中遇到问题?尝试从 [故障排查](http://gogs.io/docs/intro/troubleshooting.html) 页面获取帮助。
|
||||
- 希望帮助多国语言界面的翻译吗?请立即访问 [详情页面](http://gogs.io/docs/features/i18n.html)!
|
||||
|
||||
@@ -24,12 +24,11 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
|
||||
- 支持用户、组织和仓库管理系统
|
||||
- 支持仓库和组织级别 Web 钩子(包括 Slack 集成)
|
||||
- 支持仓库 Git 钩子和部署密钥
|
||||
- 支持仓库工单(Issue)和合并请求(Pull Request)
|
||||
- 支持仓库工单(Issue)、合并请求(Pull Request)以及 Wiki
|
||||
- 支持添加和删除仓库协作者
|
||||
- 支持 Gravatar 以及自定义源
|
||||
- 支持邮件服务
|
||||
- 支持后台管理面板
|
||||
- 支持 CI 集成:[Drone](https://github.com/drone/drone)
|
||||
- 支持 MySQL、PostgreSQL、SQLite3 和 [TiDB](https://github.com/pingcap/tidb)(实验性支持) 数据库
|
||||
- 支持多语言本地化([14 种语言]([more](https://crowdin.com/project/gogs)))
|
||||
|
||||
@@ -67,6 +66,13 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
|
||||
- [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/)(项目管理)
|
||||
|
||||
### 产品支持
|
||||
|
||||
|
||||
12
cmd/cert.go
12
cmd/cert.go
@@ -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"),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
42
cmd/cmd.go
Normal file
42
cmd/cmd.go
Normal 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,
|
||||
}
|
||||
}
|
||||
47
cmd/dump.go
47
cmd/dump.go
@@ -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"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -38,16 +40,23 @@ func runDump(ctx *cli.Context) {
|
||||
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)
|
||||
}
|
||||
|
||||
64
cmd/serve.go
64
cmd/serve.go
@@ -33,7 +33,7 @@ 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"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -65,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,
|
||||
@@ -76,6 +76,9 @@ func fail(userMessage, logMessage string, args ...interface{}) {
|
||||
fmt.Fprintln(os.Stderr, "Gogs:", userMessage)
|
||||
|
||||
if len(logMessage) > 0 {
|
||||
if !setting.ProdMode {
|
||||
fmt.Fprintf(os.Stderr, logMessage+"\n", args...)
|
||||
}
|
||||
log.GitLogger.Fatal(3, logMessage, args...)
|
||||
return
|
||||
}
|
||||
@@ -84,7 +87,7 @@ func fail(userMessage, logMessage string, args ...interface{}) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func handleUpdateTask(uuid string, user *models.User, repoUserName, repoName string) {
|
||||
func handleUpdateTask(uuid string, user *models.User, username, reponame string, isWiki bool) {
|
||||
task, err := models.GetUpdateTaskByUUID(uuid)
|
||||
if err != nil {
|
||||
if models.IsErrUpdateTaskNotExist(err) {
|
||||
@@ -92,19 +95,21 @@ func handleUpdateTask(uuid string, user *models.User, repoUserName, repoName str
|
||||
return
|
||||
}
|
||||
log.GitLogger.Fatal(2, "GetUpdateTaskByUUID: %v", err)
|
||||
}
|
||||
|
||||
if err = models.Update(task.RefName, task.OldCommitID, task.NewCommitID,
|
||||
user.Name, repoUserName, repoName, user.Id); err != nil {
|
||||
log.GitLogger.Error(2, "Update: %v", err)
|
||||
}
|
||||
|
||||
if err = models.DeleteUpdateTaskByUUID(uuid); err != nil {
|
||||
} 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 + repoUserName + "/" + repoName + "/tasks/trigger?branch=" +
|
||||
reqURL := setting.LocalUrl + username + "/" + reponame + "/tasks/trigger?branch=" +
|
||||
strings.TrimPrefix(task.RefName, "refs/heads/")
|
||||
log.GitLogger.Trace("Trigger task: %s", reqURL)
|
||||
|
||||
@@ -144,26 +149,32 @@ func runServ(c *cli.Context) {
|
||||
if len(rr) != 2 {
|
||||
fail("Invalid repository path", "Invalid repository path: %v", args)
|
||||
}
|
||||
repoUserName := strings.ToLower(rr[0])
|
||||
repoName := strings.ToLower(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)
|
||||
}
|
||||
@@ -234,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 {
|
||||
@@ -250,7 +266,7 @@ func runServ(c *cli.Context) {
|
||||
}
|
||||
|
||||
if requestedMode == models.ACCESS_MODE_WRITE {
|
||||
handleUpdateTask(uuid, user, repoUserName, repoName)
|
||||
handleUpdateTask(uuid, user, username, reponame, isWiki)
|
||||
}
|
||||
|
||||
// Update user key activity.
|
||||
|
||||
@@ -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"),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
156
cmd/web.go
156
cmd/web.go
@@ -7,7 +7,7 @@ package cmd
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"html/template"
|
||||
gotmpl "html/template"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/fcgi"
|
||||
@@ -29,20 +29,19 @@ import (
|
||||
"gopkg.in/ini.v1"
|
||||
"gopkg.in/macaron.v1"
|
||||
|
||||
api "github.com/gogits/go-gogs-client"
|
||||
"github.com/gogits/git-shell"
|
||||
|
||||
"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 +55,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"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -81,17 +80,19 @@ func checkVersion() {
|
||||
// Check dependency version.
|
||||
checkers := []VerChecker{
|
||||
{"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/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.0.7"},
|
||||
{"github.com/go-macaron/i18n", i18n.Version, "0.2.0"},
|
||||
{"github.com/go-macaron/session", session.Version, "0.1.6"},
|
||||
{"gopkg.in/ini.v1", ini.Version, "1.3.4"},
|
||||
{"github.com/go-macaron/toolbox", toolbox.Version, "0.1.0"},
|
||||
{"gopkg.in/ini.v1", ini.Version, "1.8.4"},
|
||||
{"gopkg.in/macaron.v1", macaron.Version, "0.8.0"},
|
||||
{"github.com/gogits/git-shell", git.Version, "0.1.0"},
|
||||
}
|
||||
for _, c := range checkers {
|
||||
if !version.Compare(c.Version(), c.Expected, ">=") {
|
||||
log.Fatal(4, "Package '%s' version is too old(%s -> %s), did you forget to update?", c.ImportPath, c.Version(), c.Expected)
|
||||
log.Fatal(4, "Package '%s' version is too old (%s -> %s), did you forget to update?", c.ImportPath, c.Version(), c.Expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,7 +125,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 +143,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{
|
||||
@@ -185,7 +187,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.
|
||||
@@ -196,54 +197,8 @@ func runWeb(ctx *cli.Context) {
|
||||
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("/repos", func() {
|
||||
m.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), v1.MigrateRepo)
|
||||
m.Combo("/:username/:reponame").Get(v1.GetRepo).
|
||||
Delete(v1.DeleteRepo)
|
||||
|
||||
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.Error(404)
|
||||
})
|
||||
})
|
||||
apiv1.RegisterRoutes(m)
|
||||
}, ignSignIn)
|
||||
// ***** END: API *****
|
||||
|
||||
@@ -303,10 +258,8 @@ func runWeb(ctx *cli.Context) {
|
||||
|
||||
m.Group("/users", func() {
|
||||
m.Get("", admin.Users)
|
||||
m.Get("/new", admin.NewUser)
|
||||
m.Post("/new", bindIgnErr(auth.AdminCrateUserForm{}), admin.NewUserPost)
|
||||
m.Get("/:userid", admin.EditUser)
|
||||
m.Post("/:userid", bindIgnErr(auth.AdminEditUserForm{}), admin.EditUserPost)
|
||||
m.Combo("/new").Get(admin.NewUser).Post(bindIgnErr(auth.AdminCrateUserForm{}), admin.NewUserPost)
|
||||
m.Combo("/:userid").Get(admin.EditUser).Post(bindIgnErr(auth.AdminEditUserForm{}), admin.EditUserPost)
|
||||
m.Post("/:userid/delete", admin.DeleteUser)
|
||||
})
|
||||
|
||||
@@ -315,13 +268,13 @@ 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.Combo("/new").Get(admin.NewAuthSource).Post(bindIgnErr(auth.AuthenticationForm{}), admin.NewAuthSourcePost)
|
||||
m.Combo("/:authid").Get(admin.EditAuthSource).
|
||||
Post(bindIgnErr(auth.AuthenticationForm{}), admin.EditAuthSourcePost)
|
||||
m.Post("/:authid/delete", admin.DeleteAuthSource)
|
||||
@@ -329,7 +282,8 @@ func runWeb(ctx *cli.Context) {
|
||||
|
||||
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 *****
|
||||
@@ -370,6 +324,7 @@ func runWeb(ctx *cli.Context) {
|
||||
}
|
||||
|
||||
reqRepoAdmin := middleware.RequireRepoAdmin()
|
||||
reqRepoPusher := middleware.RequireRepoPusher()
|
||||
|
||||
// ***** START: Organization *****
|
||||
m.Group("/org", func() {
|
||||
@@ -385,9 +340,9 @@ func runWeb(ctx *cli.Context) {
|
||||
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)
|
||||
@@ -416,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 *****
|
||||
@@ -446,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)
|
||||
|
||||
@@ -462,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)
|
||||
@@ -489,41 +444,58 @@ 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)
|
||||
@@ -536,13 +508,13 @@ func runWeb(ctx *cli.Context) {
|
||||
}, 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())
|
||||
}, ignSignIn, middleware.RepoAssignment(true), middleware.RepoRef())
|
||||
|
||||
m.Group("/:reponame", func() {
|
||||
m.Any("/*", ignSignInAndCsrf, repo.HTTP)
|
||||
|
||||
19
conf/app.ini
19
conf/app.ini
@@ -15,6 +15,8 @@ SCRIPT_TYPE = bash
|
||||
ANSI_CHARSET =
|
||||
; Force every new repository to be private
|
||||
FORCE_PRIVATE = false
|
||||
; Global maximum creation limit of repository per user, -1 means no limit
|
||||
MAX_CREATION_LIMIT = -1
|
||||
; Patch test queue length, make it as large as possible
|
||||
PULL_REQUEST_QUEUE_LENGTH = 10000
|
||||
|
||||
@@ -32,7 +34,7 @@ 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 = 50
|
||||
NOTICE_PAGING_NUM = 25
|
||||
; Number of organization that are showed in one page
|
||||
ORG_PAGING_NUM = 50
|
||||
|
||||
@@ -46,6 +48,10 @@ DOMAIN = localhost
|
||||
ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
|
||||
HTTP_ADDR =
|
||||
HTTP_PORT = 3000
|
||||
; Local (DMZ) URL for Gogs workers (such as SSH update) accessing web service.
|
||||
; In most cases you do not need to change the default value.
|
||||
; Alter it only if your SSH server node is not the same as HTTP node.
|
||||
LOCAL_ROOT_URL = http://localhost:%(HTTP_PORT)s/
|
||||
; Disable SSH feature when not available
|
||||
DISABLE_SSH = false
|
||||
; Whether use builtin SSH server or not.
|
||||
@@ -265,8 +271,8 @@ ADDR =
|
||||
; For "smtp" mode only
|
||||
[log.smtp]
|
||||
LEVEL =
|
||||
; Name displayed in mail title, default is "Diagnostic message from serve"
|
||||
SUBJECT = Diagnostic message from serve
|
||||
; Name displayed in mail title, default is "Diagnostic message from server"
|
||||
SUBJECT = Diagnostic message from server
|
||||
; Mail server
|
||||
HOST =
|
||||
; Mailer user name and password
|
||||
@@ -296,7 +302,8 @@ SCHEDULE = @every 1h
|
||||
; Repository health check
|
||||
[cron.repo_health_check]
|
||||
SCHEDULE = @every 24h
|
||||
; Arguments for command 'git fsck', e.g.: "--unreachable --tags"
|
||||
TIMEOUT = 60s
|
||||
; Arguments for command 'git fsck', e.g. "--unreachable --tags"
|
||||
; see more on http://git-scm.com/docs/git-fsck/1.7.5
|
||||
ARGS =
|
||||
|
||||
@@ -307,7 +314,7 @@ SCHEDULE = @every 24h
|
||||
|
||||
[git]
|
||||
MAX_GIT_DIFF_LINES = 10000
|
||||
; Arguments for command 'git gc', e.g.: "--aggressive --auto"
|
||||
; Arguments for command 'git gc', e.g. "--aggressive --auto"
|
||||
; see more on http://git-scm.com/docs/git-gc/1.7.5
|
||||
GC_ARGS =
|
||||
|
||||
@@ -334,3 +341,5 @@ it-IT = it
|
||||
|
||||
[other]
|
||||
SHOW_FOOTER_BRANDING = false
|
||||
; Show version information about gogs and go in the footer
|
||||
SHOW_FOOTER_VERSION = true
|
||||
|
||||
@@ -20,10 +20,11 @@ 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>
|
||||
Ł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
@@ -18,7 +18,7 @@ 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
|
||||
@@ -28,6 +28,7 @@ organization = Organization
|
||||
mirror = Mirror
|
||||
new_repo = New Repository
|
||||
new_migrate = New Migration
|
||||
new_mirror = New Mirror
|
||||
new_fork = New Fork Repository
|
||||
new_org = New Organization
|
||||
manage_org = Manage Organizations
|
||||
@@ -84,14 +85,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
|
||||
@@ -111,7 +112,7 @@ admin_title = Admin Account Settings
|
||||
admin_name = Username
|
||||
admin_password = Password
|
||||
confirm_password = Confirm Password
|
||||
admin_email = Admin 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.
|
||||
@@ -123,7 +124,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
|
||||
@@ -147,13 +148,13 @@ 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.
|
||||
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
|
||||
@@ -161,9 +162,10 @@ password_too_short = Password length cannot be less then 6.
|
||||
|
||||
[mail]
|
||||
activate_account = Please activate your account
|
||||
activate_email = Verify your e-mail address
|
||||
activate_email = Verify your email address
|
||||
reset_password = Reset your password
|
||||
register_success = Register success, Welcome
|
||||
register_success = Registration successful, welcome
|
||||
register_notify = Welcome on board
|
||||
|
||||
[modal]
|
||||
yes = Yes
|
||||
@@ -173,7 +175,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
|
||||
@@ -181,7 +183,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.`
|
||||
@@ -189,18 +191,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.
|
||||
@@ -217,7 +219,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.
|
||||
|
||||
@@ -245,7 +247,8 @@ 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.
|
||||
password_username_disabled = Non-local type users are not allowed to change their username.
|
||||
full_name = Full Name
|
||||
website = Website
|
||||
location = Location
|
||||
@@ -270,20 +273,21 @@ 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.
|
||||
password_change_disabled = Non-local type users are not allowed to change their 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
|
||||
email_deletion = E-mail Deletion
|
||||
email_deletion_desc = Delete this e-mail address will remove related information from your account. Do you want to continue?
|
||||
email_deletion_success = E-mail has been deleted successfully!
|
||||
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 '%s', 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
|
||||
@@ -336,6 +340,7 @@ 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.
|
||||
@@ -350,7 +355,13 @@ auto_init = Initialize this repository with selected files and template
|
||||
create_repo = Create Repository
|
||||
default_branch = Default Branch
|
||||
mirror_interval = Mirror Interval (hour)
|
||||
mirror_address = Mirror Address
|
||||
mirror_address_desc = Please include necessary user credentials in the address.
|
||||
watchers = Watchers
|
||||
stargazers = Stargazers
|
||||
forks = Forks
|
||||
|
||||
form.reach_limit_of_creation = The owner has reached maximum creation limit of %d repositories.
|
||||
form.name_reserved = Repository name '%s' is reserved.
|
||||
form.name_pattern_not_allowed = Repository name pattern '%s' is not allowed.
|
||||
|
||||
@@ -362,15 +373,15 @@ 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
|
||||
|
||||
mirror_from = mirror from
|
||||
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
|
||||
copy_link_success = Copied!
|
||||
copy_link_error = Press ⌘-C or Ctrl-C to copy
|
||||
click_to_copy = Copy to clipboard
|
||||
copied = Copied OK
|
||||
clone_helper = Need help cloning? Visit <a target="_blank" href="%s">Help</a>!
|
||||
unwatch = Unwatch
|
||||
watch = Watch
|
||||
unstar = Unstar
|
||||
@@ -384,10 +395,10 @@ 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
|
||||
@@ -479,9 +490,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
|
||||
@@ -501,9 +513,9 @@ 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.can_auto_merge_desc = This pull request can be merged automatically.
|
||||
pulls.cannot_auto_merge_desc = This pull request can't be merged automatically because there are conflicts.
|
||||
pulls.cannot_auto_merge_helper = Please merge manually in order to resolve the conflicts.
|
||||
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.`
|
||||
|
||||
@@ -523,24 +535,50 @@ milestones.clear = Clear
|
||||
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.use_external_wiki = Use external wiki
|
||||
settings.external_wiki_url = External Wiki URL
|
||||
settings.external_wiki_url_desc = Visitors will be redirected to URL when they click on the tab.
|
||||
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.
|
||||
@@ -554,6 +592,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
|
||||
@@ -562,12 +601,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
|
||||
@@ -607,14 +650,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
|
||||
@@ -634,21 +678,28 @@ 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 = We’ll 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
|
||||
@@ -689,16 +740,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
|
||||
@@ -723,6 +775,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.
|
||||
@@ -753,6 +806,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)
|
||||
@@ -806,6 +861,8 @@ 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.max_repo_creation = Maximum Repository Creation Limit
|
||||
users.max_repo_creation_desc = (Set -1 to use global default limit)
|
||||
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
|
||||
@@ -845,9 +902,11 @@ 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
|
||||
@@ -895,7 +954,7 @@ config.db_ssl_mode_helper = (for "postgres" only)
|
||||
config.db_path = Path
|
||||
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
|
||||
@@ -948,23 +1007,30 @@ 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>`
|
||||
merge_pull_request = `merged pull request <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
transfer_repo = transfered repository <code>%s</code> to <a href="%s">%s</a>
|
||||
push_tag = pushed tag <a href="%s/src/%s">%[2]s</a> to <a href="%[1]s">%[3]s</a>
|
||||
compare_2_commits = View comparison for these 2 commits
|
||||
compare_commits = View comparison for these %d commits
|
||||
|
||||
[tool]
|
||||
ago = ago
|
||||
|
||||
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
1956
config.codekit
1956
config.codekit
File diff suppressed because it is too large
Load Diff
@@ -1,12 +0,0 @@
|
||||
web:
|
||||
build: .
|
||||
links:
|
||||
- mysql
|
||||
ports:
|
||||
- "3000:3000"
|
||||
|
||||
mysql:
|
||||
image: mysql
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=gogs
|
||||
- MYSQL_DATABASE=gogs
|
||||
@@ -1,6 +1,6 @@
|
||||
# Docker for Gogs
|
||||
|
||||
Visit [Docker Hub](https://hub.docker.com/r/gogs/gogs/) see all available tags.
|
||||
Visit [Docker Hub](https://hub.docker.com/r/gogs/gogs/) or [Quay](https://quay.io/repository/gogs/gogs) see all available tags.
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -33,7 +33,6 @@ Directory `/var/gogs` keeps Git repositories and Gogs data:
|
||||
|-- conf
|
||||
|-- data
|
||||
|-- log
|
||||
|-- templates
|
||||
|
||||
### Volume with data container
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/bin/sh
|
||||
set -x
|
||||
set -e
|
||||
|
||||
# Set temp environment vars
|
||||
export GOPATH=/tmp/go
|
||||
@@ -11,8 +13,8 @@ apk -U --no-progress add linux-pam-dev go@community gcc musl-dev
|
||||
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"
|
||||
go get -v -tags "sqlite cert pam"
|
||||
go build -tags "sqlite cert pam"
|
||||
|
||||
# Cleanup GOPATH
|
||||
rm -r $GOPATH
|
||||
|
||||
@@ -52,5 +52,5 @@ fi
|
||||
if [ $# -gt 0 ];then
|
||||
exec "$@"
|
||||
else
|
||||
exec /usr/bin/s6-svscan /app/gogs/docker/s6/
|
||||
exec /bin/s6-svscan /app/gogs/docker/s6/
|
||||
fi
|
||||
|
||||
3
gogs.go
3
gogs.go
@@ -12,12 +12,13 @@ import (
|
||||
"runtime"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
_ "github.com/kardianos/minwinsvc"
|
||||
|
||||
"github.com/gogits/gogs/cmd"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
const APP_VER = "0.7.6.1112 Beta"
|
||||
const APP_VER = "0.8.0.1212"
|
||||
|
||||
func init() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
||||
@@ -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 {
|
||||
|
||||
130
models/action.go
130
models/action.go
@@ -14,12 +14,13 @@ import (
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
"github.com/gogits/git-shell"
|
||||
api "github.com/gogits/go-gogs-client"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/git"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
@@ -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,
|
||||
@@ -187,8 +208,70 @@ 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),
|
||||
}
|
||||
}
|
||||
|
||||
func (pc *PushCommits) ToApiPayloadCommits(repoLink string) []*api.PayloadCommit {
|
||||
commits := make([]*api.PayloadCommit, len(pc.Commits))
|
||||
for i, cmt := range pc.Commits {
|
||||
author_username := ""
|
||||
author, err := GetUserByEmail(cmt.AuthorEmail)
|
||||
if err == nil {
|
||||
author_username = author.Name
|
||||
}
|
||||
commits[i] = &api.PayloadCommit{
|
||||
ID: cmt.Sha1,
|
||||
Message: cmt.Message,
|
||||
URL: fmt.Sprintf("%s/commit/%s", repoLink, cmt.Sha1),
|
||||
Author: &api.PayloadAuthor{
|
||||
Name: cmt.AuthorName,
|
||||
Email: cmt.AuthorEmail,
|
||||
UserName: author_username,
|
||||
},
|
||||
}
|
||||
}
|
||||
return commits
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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]
|
||||
@@ -322,7 +405,7 @@ func CommitRepoAction(
|
||||
repoID int64,
|
||||
repoUserName, repoName string,
|
||||
refFullName string,
|
||||
commit *base.PushCommits,
|
||||
commit *PushCommits,
|
||||
oldCommitID string, newCommitID string) error {
|
||||
|
||||
u, err := GetUserByID(userID)
|
||||
@@ -348,11 +431,11 @@ func CommitRepoAction(
|
||||
// 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") {
|
||||
commit.CompareUrl = fmt.Sprintf("%s/%s/compare/%s...%s", repoUserName, repoName, oldCommitID, newCommitID)
|
||||
commit.CompareUrl = repo.ComposeCompareURL(oldCommitID, newCommitID)
|
||||
} else {
|
||||
isNewBranch = true
|
||||
}
|
||||
@@ -386,24 +469,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)
|
||||
@@ -419,30 +487,12 @@ func CommitRepoAction(
|
||||
|
||||
switch opType {
|
||||
case COMMIT_REPO: // Push
|
||||
commits := make([]*api.PayloadCommit, len(commit.Commits))
|
||||
for i, cmt := range commit.Commits {
|
||||
author_username := ""
|
||||
author, err := GetUserByEmail(cmt.AuthorEmail)
|
||||
if err == nil {
|
||||
author_username = author.Name
|
||||
}
|
||||
commits[i] = &api.PayloadCommit{
|
||||
ID: cmt.Sha1,
|
||||
Message: cmt.Message,
|
||||
URL: fmt.Sprintf("%s/commit/%s", repoLink, cmt.Sha1),
|
||||
Author: &api.PayloadAuthor{
|
||||
Name: cmt.AuthorName,
|
||||
Email: cmt.AuthorEmail,
|
||||
UserName: author_username,
|
||||
},
|
||||
}
|
||||
}
|
||||
p := &api.PushPayload{
|
||||
Ref: refFullName,
|
||||
Before: oldCommitID,
|
||||
After: newCommitID,
|
||||
CompareUrl: setting.AppUrl + commit.CompareUrl,
|
||||
Commits: commits,
|
||||
Commits: commit.ToApiPayloadCommits(repo.FullRepoLink()),
|
||||
Repo: payloadRepo,
|
||||
Pusher: &api.PayloadAuthor{
|
||||
Name: pusher_name,
|
||||
|
||||
@@ -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"`
|
||||
@@ -61,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
|
||||
}
|
||||
|
||||
124
models/error.go
124
models/error.go
@@ -107,6 +107,39 @@ func (err ErrUserHasOrgs) Error() string {
|
||||
return fmt.Sprintf("user still has membership of organizations [uid: %d]", err.UID)
|
||||
}
|
||||
|
||||
type ErrReachLimitOfRepo struct {
|
||||
Limit int
|
||||
}
|
||||
|
||||
func IsErrReachLimitOfRepo(err error) bool {
|
||||
_, ok := err.(ErrReachLimitOfRepo)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrReachLimitOfRepo) Error() string {
|
||||
return fmt.Sprintf("user has reached maximum limit of repositories [limit: %d]", err.Limit)
|
||||
}
|
||||
|
||||
// __ __.__ __ .__
|
||||
// / \ / \__| | _|__|
|
||||
// \ \/\/ / | |/ / |
|
||||
// \ /| | <| |
|
||||
// \__/\ / |__|__|_ \__|
|
||||
// \/ \/
|
||||
|
||||
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 +147,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
|
||||
}
|
||||
@@ -155,6 +201,37 @@ func (err ErrKeyNameAlreadyUsed) Error() string {
|
||||
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 {
|
||||
KeyID int64
|
||||
RepoID int64
|
||||
@@ -288,6 +365,33 @@ 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)
|
||||
}
|
||||
|
||||
// __ __ ___. .__ __
|
||||
// / \ / \ ____\_ |__ | |__ ____ ____ | | __
|
||||
// \ \/\/ // __ \| __ \| | \ / _ \ / _ \| |/ /
|
||||
@@ -437,3 +541,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)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
@@ -80,36 +79,50 @@ 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
|
||||
// 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()
|
||||
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 {
|
||||
if lineCount >= maxlines {
|
||||
log.Warn("Diff data too large")
|
||||
diff.Files = nil
|
||||
return diff, nil
|
||||
@@ -163,10 +176,10 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
|
||||
if strings.HasPrefix(line, DIFF_HEAD) {
|
||||
middle := -1
|
||||
|
||||
// Note: In case file name is surrounded by double quotes(it happens only in git-shell).
|
||||
hasQuote := strings.Index(line, `\"`) > -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 {
|
||||
line = strings.Replace(line, `\"`, `"`, -1)
|
||||
middle = strings.Index(line, ` "b/`)
|
||||
} else {
|
||||
middle = strings.Index(line, " b/")
|
||||
@@ -176,8 +189,8 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
|
||||
a := line[beg+2 : middle]
|
||||
b := line[middle+3:]
|
||||
if hasQuote {
|
||||
a = a[1 : len(a)-1]
|
||||
b = b[1 : len(b)-1]
|
||||
a = string(git.UnescapeChars([]byte(a[1 : len(a)-1])))
|
||||
b = string(git.UnescapeChars([]byte(b[1 : len(b)-1])))
|
||||
}
|
||||
|
||||
curFile = &DiffFile{
|
||||
@@ -189,17 +202,26 @@ 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.IsCreated = true
|
||||
case strings.HasPrefix(scanner.Text(), "deleted"):
|
||||
case strings.HasPrefix(line, "deleted"):
|
||||
curFile.Type = DIFF_FILE_DEL
|
||||
curFile.IsDeleted = true
|
||||
case strings.HasPrefix(scanner.Text(), "index"):
|
||||
case strings.HasPrefix(line, "index"):
|
||||
curFile.Type = DIFF_FILE_CHANGE
|
||||
case strings.HasPrefix(scanner.Text(), "similarity index 100%"):
|
||||
case strings.HasPrefix(line, "similarity index 100%"):
|
||||
curFile.Type = DIFF_FILE_RENAME
|
||||
curFile.IsRenamed = true
|
||||
curFile.OldName = curFile.Name
|
||||
@@ -212,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 {
|
||||
@@ -238,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", "-M", c.ID.String(), afterCommitId)
|
||||
cmd = exec.Command("git", "diff", "-M", c.ID.String(), afterCommitID)
|
||||
}
|
||||
} else {
|
||||
cmd = exec.Command("git", "diff", "-M", 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) {
|
||||
|
||||
@@ -718,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
|
||||
@@ -1301,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)
|
||||
@@ -1375,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
|
||||
|
||||
115
models/login.go
115
models/login.go
@@ -10,6 +10,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/smtp"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -26,25 +27,24 @@ 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
|
||||
LOGIN_NOTYPE LoginType = iota
|
||||
LOGIN_PLAIN
|
||||
LOGIN_LDAP
|
||||
LOGIN_SMTP
|
||||
LOGIN_PAM
|
||||
LOGIN_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 LoginNames = map[LoginType]string{
|
||||
LDAP: "LDAP (via BindDN)",
|
||||
DLDAP: "LDAP (simple auth)",
|
||||
SMTP: "SMTP",
|
||||
PAM: "PAM",
|
||||
LOGIN_LDAP: "LDAP (via BindDN)",
|
||||
LOGIN_DLDAP: "LDAP (simple auth)",
|
||||
LOGIN_SMTP: "SMTP",
|
||||
LOGIN_PAM: "PAM",
|
||||
}
|
||||
|
||||
// Ensure structs implemented interface.
|
||||
@@ -109,11 +109,11 @@ func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) {
|
||||
switch colName {
|
||||
case "type":
|
||||
switch LoginType((*val).(int64)) {
|
||||
case LDAP, DLDAP:
|
||||
case LOGIN_LDAP, LOGIN_DLDAP:
|
||||
source.Cfg = new(LDAPConfig)
|
||||
case SMTP:
|
||||
case LOGIN_SMTP:
|
||||
source.Cfg = new(SMTPConfig)
|
||||
case PAM:
|
||||
case LOGIN_PAM:
|
||||
source.Cfg = new(PAMConfig)
|
||||
default:
|
||||
panic("unrecognized login source type: " + com.ToStr(*val))
|
||||
@@ -126,26 +126,26 @@ func (source *LoginSource) TypeName() string {
|
||||
}
|
||||
|
||||
func (source *LoginSource) IsLDAP() bool {
|
||||
return source.Type == LDAP
|
||||
return source.Type == LOGIN_LDAP
|
||||
}
|
||||
|
||||
func (source *LoginSource) IsDLDAP() bool {
|
||||
return source.Type == DLDAP
|
||||
return source.Type == LOGIN_DLDAP
|
||||
}
|
||||
|
||||
func (source *LoginSource) IsSMTP() bool {
|
||||
return source.Type == SMTP
|
||||
return source.Type == LOGIN_SMTP
|
||||
}
|
||||
|
||||
func (source *LoginSource) IsPAM() bool {
|
||||
return source.Type == PAM
|
||||
return source.Type == LOGIN_PAM
|
||||
}
|
||||
|
||||
func (source *LoginSource) UseTLS() bool {
|
||||
switch source.Type {
|
||||
case LDAP, DLDAP:
|
||||
case LOGIN_LDAP, LOGIN_DLDAP:
|
||||
return source.LDAP().UseSSL
|
||||
case SMTP:
|
||||
case LOGIN_SMTP:
|
||||
return source.SMTP().TLS
|
||||
}
|
||||
|
||||
@@ -154,9 +154,9 @@ func (source *LoginSource) UseTLS() bool {
|
||||
|
||||
func (source *LoginSource) SkipVerify() bool {
|
||||
switch source.Type {
|
||||
case LDAP, DLDAP:
|
||||
case LOGIN_LDAP, LOGIN_DLDAP:
|
||||
return source.LDAP().SkipVerify
|
||||
case SMTP:
|
||||
case LOGIN_SMTP:
|
||||
return source.SMTP().SkipVerify
|
||||
}
|
||||
|
||||
@@ -191,13 +191,14 @@ func LoginSources() ([]*LoginSource, error) {
|
||||
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
|
||||
}
|
||||
@@ -225,17 +226,16 @@ func DeleteSource(source *LoginSource) error {
|
||||
// |_______ \/_______ /\____|__ /____|
|
||||
// \/ \/ \/
|
||||
|
||||
// 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, source *LoginSource, autoRegister bool) (*User, error) {
|
||||
// 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)
|
||||
fn, sn, mail, admin, logged := cfg.SearchEntry(name, passwd, directBind)
|
||||
directBind := (source.Type == LOGIN_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 {
|
||||
@@ -243,6 +243,9 @@ func LoginUserLDAPSource(u *User, name, passwd string, source *LoginSource, auto
|
||||
}
|
||||
|
||||
// Fallback.
|
||||
if len(name) == 0 {
|
||||
name = loginName
|
||||
}
|
||||
if len(mail) == 0 {
|
||||
mail = fmt.Sprintf("%s@localhost", name)
|
||||
}
|
||||
@@ -250,10 +253,10 @@ func LoginUserLDAPSource(u *User, name, passwd string, source *LoginSource, auto
|
||||
u = &User{
|
||||
LowerName: strings.ToLower(name),
|
||||
Name: name,
|
||||
FullName: strings.TrimSpace(fn + " " + sn),
|
||||
FullName: composeFullName(fn, sn, name),
|
||||
LoginType: source.Type,
|
||||
LoginSource: source.ID,
|
||||
LoginName: name,
|
||||
LoginName: loginName,
|
||||
Email: mail,
|
||||
IsAdmin: admin,
|
||||
IsActive: true,
|
||||
@@ -261,6 +264,19 @@ func LoginUserLDAPSource(u *User, name, passwd string, source *LoginSource, auto
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// _________ __________________________
|
||||
// / _____/ / \__ ___/\______ \
|
||||
// \_____ \ / \ / \| | | ___/
|
||||
@@ -335,7 +351,7 @@ func SMTPAuth(a smtp.Auth, cfg *SMTPConfig) error {
|
||||
// Query if name/passwd can login against the LDAP directory pool
|
||||
// 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) {
|
||||
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, "@")
|
||||
@@ -356,7 +372,11 @@ 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") {
|
||||
// Check standard error format first,
|
||||
// then fallback to worse case.
|
||||
tperr, ok := err.(*textproto.Error)
|
||||
if (ok && tperr.Code == 535) ||
|
||||
strings.Contains(err.Error(), "Username and Password not accepted") {
|
||||
return nil, ErrUserNotExist{0, name}
|
||||
}
|
||||
return nil, err
|
||||
@@ -375,8 +395,8 @@ func LoginUserSMTPSource(u *User, name, passwd string, sourceId int64, cfg *SMTP
|
||||
u = &User{
|
||||
LowerName: strings.ToLower(loginName),
|
||||
Name: strings.ToLower(loginName),
|
||||
LoginType: SMTP,
|
||||
LoginSource: sourceId,
|
||||
LoginType: LOGIN_SMTP,
|
||||
LoginSource: sourceID,
|
||||
LoginName: name,
|
||||
IsActive: true,
|
||||
Passwd: passwd,
|
||||
@@ -396,7 +416,7 @@ func LoginUserSMTPSource(u *User, name, passwd string, sourceId int64, cfg *SMTP
|
||||
// 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) {
|
||||
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{0, name}
|
||||
@@ -411,16 +431,15 @@ 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),
|
||||
LoginType: PAM,
|
||||
LoginSource: sourceId,
|
||||
Name: name,
|
||||
LoginType: LOGIN_PAM,
|
||||
LoginSource: sourceID,
|
||||
LoginName: name,
|
||||
IsActive: true,
|
||||
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) {
|
||||
@@ -429,11 +448,11 @@ func ExternalUserLogin(u *User, name, passwd string, source *LoginSource, autoRe
|
||||
}
|
||||
|
||||
switch source.Type {
|
||||
case LDAP, DLDAP:
|
||||
case LOGIN_LDAP, LOGIN_DLDAP:
|
||||
return LoginUserLDAPSource(u, name, passwd, source, autoRegister)
|
||||
case SMTP:
|
||||
case LOGIN_SMTP:
|
||||
return LoginUserSMTPSource(u, name, passwd, source.ID, source.Cfg.(*SMTPConfig), autoRegister)
|
||||
case PAM:
|
||||
case LOGIN_PAM:
|
||||
return LoginUserPAMSource(u, name, passwd, source.ID, source.Cfg.(*PAMConfig), autoRegister)
|
||||
}
|
||||
|
||||
@@ -444,7 +463,7 @@ func ExternalUserLogin(u *User, name, passwd string, source *LoginSource, autoRe
|
||||
func UserSignIn(uname, passwd string) (*User, error) {
|
||||
var u *User
|
||||
if strings.Contains(uname, "@") {
|
||||
u = &User{Email: uname}
|
||||
u = &User{Email: strings.ToLower(uname)}
|
||||
} else {
|
||||
u = &User{LowerName: strings.ToLower(uname)}
|
||||
}
|
||||
@@ -456,7 +475,7 @@ func UserSignIn(uname, passwd string) (*User, error) {
|
||||
|
||||
if userExists {
|
||||
switch u.LoginType {
|
||||
case NOTYPE, PLAIN:
|
||||
case LOGIN_NOTYPE, LOGIN_PLAIN:
|
||||
if u.ValidatePassword(passwd) {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-xorm/xorm"
|
||||
@@ -24,7 +23,7 @@ import (
|
||||
gouuid "github.com/gogits/gogs/modules/uuid"
|
||||
)
|
||||
|
||||
const _MIN_DB_VER = 0
|
||||
const _MIN_DB_VER = 4
|
||||
|
||||
type Migration interface {
|
||||
Description() string
|
||||
@@ -58,10 +57,6 @@ type Version struct {
|
||||
// If you want to "retire" a migration, remove it from the top of the list and
|
||||
// update _MIN_VER_DB accordingly
|
||||
var migrations = []Migration{
|
||||
NewMigration("generate collaboration from access", accessToCollaboration), // V0 -> V1:v0.5.13
|
||||
NewMigration("make authorize 4 if team is owners", ownerTeamUpdate), // V1 -> V2:v0.5.13
|
||||
NewMigration("refactor access table to use id's", accessRefactor), // V2 -> V3:v0.5.13
|
||||
NewMigration("generate team-repo from team", teamToTeamRepo), // V3 -> V4:v0.5.13
|
||||
NewMigration("fix locale file load panic", fixLocaleFileLoadPanic), // V4 -> V5:v0.6.0
|
||||
NewMigration("trim action compare URL prefix", trimCommitActionAppUrlPrefix), // V5 -> V6:v0.6.3
|
||||
NewMigration("generate issue-label from issue", issueToIssueLabel), // V6 -> V7:v0.6.4
|
||||
@@ -81,24 +76,9 @@ func Migrate(x *xorm.Engine) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("get: %v", err)
|
||||
} else if !has {
|
||||
// If the user table does not exist it is a fresh installation and we
|
||||
// can skip all migrations.
|
||||
needsMigration, err := x.IsTableExist("user")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if needsMigration {
|
||||
isEmpty, err := x.IsTableEmpty("user")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// If the user table is empty it is a fresh installation and we can
|
||||
// skip all migrations.
|
||||
needsMigration = !isEmpty
|
||||
}
|
||||
if !needsMigration {
|
||||
currentVersion.Version = int64(_MIN_DB_VER + len(migrations))
|
||||
}
|
||||
// If the version record does not exist we think
|
||||
// it is a fresh installation and we can skip all migrations.
|
||||
currentVersion.Version = int64(_MIN_DB_VER + len(migrations))
|
||||
|
||||
if _, err = x.InsertOne(currentVersion); err != nil {
|
||||
return fmt.Errorf("insert: %v", err)
|
||||
@@ -106,6 +86,12 @@ 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 (>= v0.6.0) 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)
|
||||
@@ -132,276 +118,6 @@ func sessionRelease(sess *xorm.Session) {
|
||||
sess.Close()
|
||||
}
|
||||
|
||||
func accessToCollaboration(x *xorm.Engine) (err error) {
|
||||
type Collaboration struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
Created time.Time
|
||||
}
|
||||
|
||||
if err = x.Sync(new(Collaboration)); err != nil {
|
||||
return fmt.Errorf("sync: %v", err)
|
||||
}
|
||||
|
||||
results, err := x.Query("SELECT u.id AS `uid`, a.repo_name AS `repo`, a.mode AS `mode`, a.created as `created` FROM `access` a JOIN `user` u ON a.user_name=u.lower_name")
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no such column") {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
offset := strings.Split(time.Now().String(), " ")[2]
|
||||
for _, result := range results {
|
||||
mode := com.StrTo(result["mode"]).MustInt64()
|
||||
// Collaborators must have write access.
|
||||
if mode < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
userID := com.StrTo(result["uid"]).MustInt64()
|
||||
repoRefName := string(result["repo"])
|
||||
|
||||
var created time.Time
|
||||
switch {
|
||||
case setting.UseSQLite3:
|
||||
created, _ = time.Parse(time.RFC3339, string(result["created"]))
|
||||
case setting.UseMySQL:
|
||||
created, _ = time.Parse("2006-01-02 15:04:05-0700", string(result["created"])+offset)
|
||||
case setting.UsePostgreSQL:
|
||||
created, _ = time.Parse("2006-01-02T15:04:05Z-0700", string(result["created"])+offset)
|
||||
}
|
||||
|
||||
// find owner of repository
|
||||
parts := strings.SplitN(repoRefName, "/", 2)
|
||||
ownerName := parts[0]
|
||||
repoName := parts[1]
|
||||
|
||||
results, err := sess.Query("SELECT u.id as `uid`, ou.uid as `memberid` FROM `user` u LEFT JOIN org_user ou ON ou.org_id=u.id WHERE u.lower_name=?", ownerName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(results) < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
ownerID := com.StrTo(results[0]["uid"]).MustInt64()
|
||||
if ownerID == userID {
|
||||
continue
|
||||
}
|
||||
|
||||
// test if user is member of owning organization
|
||||
isMember := false
|
||||
for _, member := range results {
|
||||
memberID := com.StrTo(member["memberid"]).MustInt64()
|
||||
// We can skip all cases that a user is member of the owning organization
|
||||
if memberID == userID {
|
||||
isMember = true
|
||||
}
|
||||
}
|
||||
if isMember {
|
||||
continue
|
||||
}
|
||||
|
||||
results, err = sess.Query("SELECT id FROM `repository` WHERE owner_id=? AND lower_name=?", ownerID, repoName)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if len(results) < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
collaboration := &Collaboration{
|
||||
UserID: userID,
|
||||
RepoID: com.StrTo(results[0]["id"]).MustInt64(),
|
||||
}
|
||||
has, err := sess.Get(collaboration)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if has {
|
||||
continue
|
||||
}
|
||||
|
||||
collaboration.Created = created
|
||||
if _, err = sess.InsertOne(collaboration); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
func ownerTeamUpdate(x *xorm.Engine) (err error) {
|
||||
if _, err := x.Exec("UPDATE `team` SET authorize=4 WHERE lower_name=?", "owners"); err != nil {
|
||||
return fmt.Errorf("update owner team table: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func accessRefactor(x *xorm.Engine) (err error) {
|
||||
type (
|
||||
AccessMode int
|
||||
Access struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UserID int64 `xorm:"UNIQUE(s)"`
|
||||
RepoID int64 `xorm:"UNIQUE(s)"`
|
||||
Mode AccessMode
|
||||
}
|
||||
UserRepo struct {
|
||||
UserID int64
|
||||
RepoID int64
|
||||
}
|
||||
)
|
||||
|
||||
// We consiously don't start a session yet as we make only reads for now, no writes
|
||||
|
||||
accessMap := make(map[UserRepo]AccessMode, 50)
|
||||
|
||||
results, err := x.Query("SELECT r.id AS `repo_id`, r.is_private AS `is_private`, r.owner_id AS `owner_id`, u.type AS `owner_type` FROM `repository` r LEFT JOIN `user` u ON r.owner_id=u.id")
|
||||
if err != nil {
|
||||
return fmt.Errorf("select repositories: %v", err)
|
||||
}
|
||||
for _, repo := range results {
|
||||
repoID := com.StrTo(repo["repo_id"]).MustInt64()
|
||||
isPrivate := com.StrTo(repo["is_private"]).MustInt() > 0
|
||||
ownerID := com.StrTo(repo["owner_id"]).MustInt64()
|
||||
ownerIsOrganization := com.StrTo(repo["owner_type"]).MustInt() > 0
|
||||
|
||||
results, err := x.Query("SELECT `user_id` FROM `collaboration` WHERE repo_id=?", repoID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("select collaborators: %v", err)
|
||||
}
|
||||
for _, user := range results {
|
||||
userID := com.StrTo(user["user_id"]).MustInt64()
|
||||
accessMap[UserRepo{userID, repoID}] = 2 // WRITE ACCESS
|
||||
}
|
||||
|
||||
if !ownerIsOrganization {
|
||||
continue
|
||||
}
|
||||
|
||||
// The minimum level to add a new access record,
|
||||
// because public repository has implicit open access.
|
||||
minAccessLevel := AccessMode(0)
|
||||
if !isPrivate {
|
||||
minAccessLevel = 1
|
||||
}
|
||||
|
||||
repoString := "$" + string(repo["repo_id"]) + "|"
|
||||
|
||||
results, err = x.Query("SELECT `id`,`authorize`,`repo_ids` FROM `team` WHERE org_id=? AND authorize>? ORDER BY `authorize` ASC", ownerID, int(minAccessLevel))
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no such column") {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("select teams from org: %v", err)
|
||||
}
|
||||
|
||||
for _, team := range results {
|
||||
if !strings.Contains(string(team["repo_ids"]), repoString) {
|
||||
continue
|
||||
}
|
||||
teamID := com.StrTo(team["id"]).MustInt64()
|
||||
mode := AccessMode(com.StrTo(team["authorize"]).MustInt())
|
||||
|
||||
results, err := x.Query("SELECT `uid` FROM `team_user` WHERE team_id=?", teamID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("select users from team: %v", err)
|
||||
}
|
||||
for _, user := range results {
|
||||
userID := com.StrTo(user["uid"]).MustInt64()
|
||||
accessMap[UserRepo{userID, repoID}] = mode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Drop table can't be in a session (at least not in sqlite)
|
||||
if _, err = x.Exec("DROP TABLE `access`"); err != nil {
|
||||
return fmt.Errorf("drop access table: %v", err)
|
||||
}
|
||||
|
||||
// Now we start writing so we make a session
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = sess.Sync2(new(Access)); err != nil {
|
||||
return fmt.Errorf("sync: %v", err)
|
||||
}
|
||||
|
||||
accesses := make([]*Access, 0, len(accessMap))
|
||||
for ur, mode := range accessMap {
|
||||
accesses = append(accesses, &Access{UserID: ur.UserID, RepoID: ur.RepoID, Mode: mode})
|
||||
}
|
||||
|
||||
if _, err = sess.Insert(accesses); err != nil {
|
||||
return fmt.Errorf("insert accesses: %v", err)
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
func teamToTeamRepo(x *xorm.Engine) error {
|
||||
type TeamRepo struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
OrgID int64 `xorm:"INDEX"`
|
||||
TeamID int64 `xorm:"UNIQUE(s)"`
|
||||
RepoID int64 `xorm:"UNIQUE(s)"`
|
||||
}
|
||||
|
||||
teamRepos := make([]*TeamRepo, 0, 50)
|
||||
|
||||
results, err := x.Query("SELECT `id`,`org_id`,`repo_ids` FROM `team`")
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no such column") {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("select teams: %v", err)
|
||||
}
|
||||
for _, team := range results {
|
||||
orgID := com.StrTo(team["org_id"]).MustInt64()
|
||||
teamID := com.StrTo(team["id"]).MustInt64()
|
||||
|
||||
// #1032: legacy code can have duplicated IDs for same repository.
|
||||
mark := make(map[int64]bool)
|
||||
for _, idStr := range strings.Split(string(team["repo_ids"]), "|") {
|
||||
repoID := com.StrTo(strings.TrimPrefix(idStr, "$")).MustInt64()
|
||||
if repoID == 0 || mark[repoID] {
|
||||
continue
|
||||
}
|
||||
|
||||
mark[repoID] = true
|
||||
teamRepos = append(teamRepos, &TeamRepo{
|
||||
OrgID: orgID,
|
||||
TeamID: teamID,
|
||||
RepoID: repoID,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = sess.Sync2(new(TeamRepo)); err != nil {
|
||||
return fmt.Errorf("sync2: %v", err)
|
||||
} else if _, err = sess.Insert(teamRepos); err != nil {
|
||||
return fmt.Errorf("insert team-repos: %v", err)
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
func fixLocaleFileLoadPanic(_ *xorm.Engine) error {
|
||||
cfg, err := ini.Load(setting.CustomConf)
|
||||
if err != nil {
|
||||
@@ -489,7 +205,8 @@ func issueToIssueLabel(x *xorm.Engine) error {
|
||||
issueLabels := make([]*IssueLabel, 0, 50)
|
||||
results, err := x.Query("SELECT `id`,`label_ids` FROM `issue`")
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no such column") {
|
||||
if strings.Contains(err.Error(), "no such column") ||
|
||||
strings.Contains(err.Error(), "Unknown column") {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("select issues: %v", err)
|
||||
@@ -648,6 +365,9 @@ func renamePullRequestFields(x *xorm.Engine) (err error) {
|
||||
Index: com.StrTo(pr["pull_index"]).MustInt64(),
|
||||
HeadBranch: string(pr["head_barcnh"]),
|
||||
}
|
||||
if pull.Index == 0 {
|
||||
continue
|
||||
}
|
||||
if _, err = sess.Id(pull.ID).Update(pull); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ func CreateOrganization(org, owner *User) (err error) {
|
||||
org.LowerName = strings.ToLower(org.Name)
|
||||
org.FullName = org.Name
|
||||
org.UseCustomAvatar = true
|
||||
org.MaxRepoCreation = -1
|
||||
org.NumTeams = 1
|
||||
org.NumMembers = 1
|
||||
|
||||
|
||||
104
models/pull.go
104
models/pull.go
@@ -14,7 +14,9 @@ import (
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
"github.com/gogits/gogs/modules/git"
|
||||
"github.com/gogits/git-shell"
|
||||
api "github.com/gogits/go-gogs-client"
|
||||
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/process"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
@@ -124,6 +126,12 @@ func (pr *PullRequest) CanAutoMerge() bool {
|
||||
|
||||
// Merge merges pull request to base repository.
|
||||
func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error) {
|
||||
if err = pr.GetHeadRepo(); err != nil {
|
||||
return fmt.Errorf("GetHeadRepo: %v", err)
|
||||
} else if err = pr.GetBaseRepo(); err != nil {
|
||||
return fmt.Errorf("GetBaseRepo: %v", err)
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
@@ -134,18 +142,14 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error
|
||||
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)
|
||||
pr.MergedCommitID, err = headGitRepo.GetBranchCommitID(pr.HeadBranch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetCommitIdOfBranch: %v", err)
|
||||
return fmt.Errorf("GetBranchCommitID: %v", err)
|
||||
}
|
||||
|
||||
if err = mergePullRequestAction(sess, doer, pr.Issue.Repo, pr.Issue); err != nil {
|
||||
@@ -166,48 +170,85 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error
|
||||
|
||||
var stderr string
|
||||
if _, stderr, err = process.ExecTimeout(5*time.Minute,
|
||||
fmt.Sprintf("PullRequest.Merge(git clone): %s", tmpBasePath),
|
||||
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),
|
||||
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),
|
||||
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)
|
||||
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),
|
||||
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)
|
||||
return fmt.Errorf("git fetch [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
|
||||
}
|
||||
|
||||
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
|
||||
fmt.Sprintf("PullRequest.Merge(git merge): %s", tmpBasePath),
|
||||
"git", "merge", "--no-ff", "-m",
|
||||
fmt.Sprintf("Merge branch '%s' of %s/%s into %s", pr.HeadBranch, pr.HeadUserName, pr.HeadRepo.Name, pr.BaseBranch),
|
||||
"head_repo/"+pr.HeadBranch); err != nil {
|
||||
return fmt.Errorf("git merge[%s]: %s", tmpBasePath, stderr)
|
||||
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),
|
||||
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()
|
||||
if err = sess.Commit(); err != nil {
|
||||
return fmt.Errorf("Commit: %v", err)
|
||||
}
|
||||
|
||||
// Compose commit repository action
|
||||
l, err := headGitRepo.CommitsBetweenIDs(pr.MergedCommitID, pr.MergeBase)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CommitsBetween: %v", err)
|
||||
}
|
||||
p := &api.PushPayload{
|
||||
Ref: "refs/heads/" + pr.BaseBranch,
|
||||
Before: pr.MergeBase,
|
||||
After: pr.MergedCommitID,
|
||||
CompareUrl: setting.AppUrl + pr.BaseRepo.ComposeCompareURL(pr.MergeBase, pr.MergedCommitID),
|
||||
Commits: ListToPushCommits(l).ToApiPayloadCommits(pr.BaseRepo.FullRepoLink()),
|
||||
Repo: pr.BaseRepo.ComposePayload(),
|
||||
Pusher: &api.PayloadAuthor{
|
||||
Name: pr.HeadRepo.MustOwner().DisplayName(),
|
||||
Email: pr.HeadRepo.MustOwner().Email,
|
||||
UserName: pr.HeadRepo.MustOwner().Name,
|
||||
},
|
||||
Sender: &api.PayloadUser{
|
||||
UserName: doer.Name,
|
||||
ID: doer.Id,
|
||||
AvatarUrl: setting.AppUrl + doer.RelAvatarLink(),
|
||||
},
|
||||
}
|
||||
if err = PrepareWebhooks(pr.BaseRepo, HOOK_EVENT_PUSH, p); err != nil {
|
||||
return fmt.Errorf("PrepareWebhooks: %v", err)
|
||||
}
|
||||
go HookQueue.Add(pr.BaseRepo.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// patchConflicts is a list of conflit description from Git.
|
||||
@@ -218,6 +259,7 @@ var patchConflicts = []string{
|
||||
}
|
||||
|
||||
// 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)
|
||||
@@ -243,14 +285,23 @@ func (pr *PullRequest) testPatch() (err error) {
|
||||
return fmt.Errorf("UpdateLocalCopy: %v", err)
|
||||
}
|
||||
|
||||
pr.Status = PULL_REQUEST_STATUS_CHECKING
|
||||
// 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
|
||||
}
|
||||
@@ -395,23 +446,16 @@ func (pr *PullRequest) UpdatePatch() (err error) {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
headRepoPath, err := pr.HeadRepo.RepoPath()
|
||||
if err != nil {
|
||||
return fmt.Errorf("HeadRepo.RepoPath: %v", err)
|
||||
}
|
||||
|
||||
headGitRepo, err := git.OpenRepository(headRepoPath)
|
||||
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 {
|
||||
if err = headGitRepo.AddRemote(tmpRemote, RepoPath(pr.BaseRepo.MustOwner().Name, pr.BaseRepo.Name), true); err != nil {
|
||||
return fmt.Errorf("AddRemote: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
|
||||
@@ -5,34 +5,31 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
"github.com/gogits/gogs/modules/git"
|
||||
)
|
||||
"github.com/gogits/git-shell"
|
||||
|
||||
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
|
||||
Target string
|
||||
Title string
|
||||
Sha1 string `xorm:"VARCHAR(40)"`
|
||||
NumCommits int
|
||||
NumCommitsBehind int `xorm:"-"`
|
||||
NumCommits int64
|
||||
NumCommitsBehind int64 `xorm:"-"`
|
||||
Note string `xorm:"TEXT"`
|
||||
IsDraft bool `xorm:"NOT NULL DEFAULT false"`
|
||||
IsPrerelease bool
|
||||
@@ -47,35 +44,35 @@ 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 createTag(gitRepo *git.Repository, rel *Release) error {
|
||||
// Only actual create when publish.
|
||||
if !rel.IsDraft {
|
||||
if !gitRepo.IsTagExist(rel.TagName) {
|
||||
commit, err := gitRepo.GetCommitOfBranch(rel.Target)
|
||||
commit, err := gitRepo.GetBranchCommit(rel.Target)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("GetBranchCommit: %v", err)
|
||||
}
|
||||
|
||||
if err = gitRepo.CreateTag(rel.TagName, commit.ID.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
commit, err := gitRepo.GetCommitOfTag(rel.TagName)
|
||||
commit, err := gitRepo.GetTagCommit(rel.TagName)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("GetTagCommit: %v", err)
|
||||
}
|
||||
|
||||
rel.NumCommits, err = commit.CommitsCount()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("CommitsCount: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,11 +81,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 +97,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 +160,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
|
||||
}
|
||||
|
||||
600
models/repo.go
600
models/repo.go
@@ -17,17 +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"
|
||||
@@ -49,7 +53,7 @@ var (
|
||||
Gitignores, Licenses, Readmes []string
|
||||
|
||||
// Maximum items per page in forks, watchers and stars of a repo
|
||||
ItemsPerPage = 54
|
||||
ItemsPerPage = 40
|
||||
)
|
||||
|
||||
func LoadRepoConfig() {
|
||||
@@ -94,19 +98,15 @@ func NewRepoContext() {
|
||||
}
|
||||
|
||||
// Check Git version.
|
||||
ver, err := git.GetVersion()
|
||||
gitVer, err := git.BinVersion()
|
||||
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")
|
||||
}
|
||||
log.Info("Git Version: %s", ver.String())
|
||||
|
||||
// Git requires setting user.name and user.email in order to commit changes.
|
||||
for configKey, defaultValue := range map[string]string{"user.name": "Gogs", "user.email": "gogs@fake.local"} {
|
||||
@@ -162,6 +162,16 @@ type Repository struct {
|
||||
IsMirror bool
|
||||
*Mirror `xorm:"-"`
|
||||
|
||||
// Advanced settings
|
||||
EnableWiki bool `xorm:"NOT NULL DEFAULT true"`
|
||||
EnableExternalWiki bool
|
||||
ExternalWikiURL string
|
||||
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:"-"`
|
||||
@@ -196,6 +206,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 {
|
||||
@@ -252,22 +295,28 @@ 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) GitConfigPath() string {
|
||||
return filepath.Join(repo.RepoPath(), "config")
|
||||
}
|
||||
|
||||
func (repo *Repository) RepoLink() string {
|
||||
return setting.AppSubUrl + "/" + repo.MustOwner().Name + "/" + repo.Name
|
||||
}
|
||||
|
||||
func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) string {
|
||||
return fmt.Sprintf("%s/%s/compare/%s...%s", repo.MustOwner().Name, repo.Name, oldCommitID, newCommitID)
|
||||
}
|
||||
|
||||
func (repo *Repository) FullRepoLink() string {
|
||||
return setting.AppUrl + repo.MustOwner().Name + "/" + repo.Name
|
||||
}
|
||||
|
||||
func (repo *Repository) HasAccess(u *User) bool {
|
||||
@@ -304,31 +353,24 @@ func (repo *Repository) LocalCopyPath() string {
|
||||
return path.Join(setting.AppDataPath, "tmp/local", com.ToStr(repo.ID))
|
||||
}
|
||||
|
||||
// UpdateLocalCopy makes sure the local copy of repository is up-to-date.
|
||||
func (repo *Repository) UpdateLocalCopy() error {
|
||||
repoPath, err := repo.RepoPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
localPath := repo.LocalCopyPath()
|
||||
func updateLocalCopy(repoPath, localPath string) error {
|
||||
if !com.IsExist(localPath) {
|
||||
_, stderr, err := process.Exec(
|
||||
fmt.Sprintf("UpdateLocalCopy(git clone): %s", repoPath), "git", "clone", repoPath, localPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("git clone: %v - %s", err, stderr)
|
||||
if err := git.Clone(repoPath, localPath, git.CloneRepoOptions{}); err != nil {
|
||||
return fmt.Errorf("Clone: %v", err)
|
||||
}
|
||||
} else {
|
||||
_, stderr, err := process.ExecDir(-1, localPath,
|
||||
fmt.Sprintf("UpdateLocalCopy(git pull): %s", repoPath), "git", "pull")
|
||||
if err != nil {
|
||||
return fmt.Errorf("git pull: %v - %s", err, stderr)
|
||||
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 {
|
||||
@@ -345,7 +387,7 @@ func (repo *Repository) SavePatch(index int64, patch []byte) error {
|
||||
return fmt.Errorf("PatchPath: %v", err)
|
||||
}
|
||||
|
||||
os.MkdirAll(path.Dir(patchPath), os.ModePerm)
|
||||
os.MkdirAll(filepath.Dir(patchPath), os.ModePerm)
|
||||
if err = ioutil.WriteFile(patchPath, patch, 0644); err != nil {
|
||||
return fmt.Errorf("WriteFile: %v", err)
|
||||
}
|
||||
@@ -353,6 +395,27 @@ func (repo *Repository) SavePatch(index int64, patch []byte) error {
|
||||
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.FullRepoLink(),
|
||||
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,
|
||||
@@ -373,24 +436,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.Name, repo.Name)
|
||||
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.Name, repo.Name)
|
||||
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.Name, repo.Name)
|
||||
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.
|
||||
@@ -424,6 +494,8 @@ type Mirror struct {
|
||||
Interval int // Hour.
|
||||
Updated time.Time `xorm:"UPDATED"`
|
||||
NextUpdate time.Time
|
||||
|
||||
address string `xorm:"-"`
|
||||
}
|
||||
|
||||
func (m *Mirror) AfterSet(colName string, _ xorm.Cell) {
|
||||
@@ -437,6 +509,61 @@ func (m *Mirror) AfterSet(colName string, _ xorm.Cell) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mirror) readAddress() {
|
||||
if len(m.address) > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
cfg, err := ini.Load(m.Repo.GitConfigPath())
|
||||
if err != nil {
|
||||
log.Error(4, "Load: %v", err)
|
||||
return
|
||||
}
|
||||
m.address = cfg.Section("remote \"origin\"").Key("url").Value()
|
||||
}
|
||||
|
||||
// HandleCloneUserCredentials replaces user credentials from HTTP/HTTPS URL
|
||||
// with placeholder <credentials>.
|
||||
// It will fail for any other forms of clone addresses.
|
||||
func HandleCloneUserCredentials(url string, mosaics bool) string {
|
||||
i := strings.Index(url, "@")
|
||||
if i == -1 {
|
||||
return url
|
||||
}
|
||||
start := strings.Index(url, "://")
|
||||
if start == -1 {
|
||||
return url
|
||||
}
|
||||
if mosaics {
|
||||
return url[:start+3] + "<credentials>" + url[i:]
|
||||
}
|
||||
return url[:start+3] + url[i+1:]
|
||||
}
|
||||
|
||||
// Address returns mirror address from Git repository config without credentials.
|
||||
func (m *Mirror) Address() string {
|
||||
m.readAddress()
|
||||
return HandleCloneUserCredentials(m.address, false)
|
||||
}
|
||||
|
||||
// FullAddress returns mirror address from Git repository config.
|
||||
func (m *Mirror) FullAddress() string {
|
||||
m.readAddress()
|
||||
return m.address
|
||||
}
|
||||
|
||||
// SaveAddress writes new address to Git repository config.
|
||||
func (m *Mirror) SaveAddress(addr string) error {
|
||||
configPath := m.Repo.GitConfigPath()
|
||||
cfg, err := ini.Load(configPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Load: %v", err)
|
||||
}
|
||||
|
||||
cfg.Section("remote \"origin\"").Key("url").SetValue(addr)
|
||||
return cfg.SaveToIndent(configPath, "\t")
|
||||
}
|
||||
|
||||
func getMirror(e Engine, repoId int64) (*Mirror, error) {
|
||||
m := &Mirror{RepoID: repoId}
|
||||
has, err := e.Get(m)
|
||||
@@ -462,23 +589,9 @@ func UpdateMirror(m *Mirror) error {
|
||||
return updateMirror(x, m)
|
||||
}
|
||||
|
||||
// MirrorRepository creates a mirror repository from source.
|
||||
func MirrorRepository(repoId int64, userName, repoName, repoPath, url string) error {
|
||||
_, stderr, err := process.ExecTimeout(10*time.Minute,
|
||||
fmt.Sprintf("MirrorRepository: %s/%s", userName, repoName),
|
||||
"git", "clone", "--mirror", url, repoPath)
|
||||
if err != nil {
|
||||
return errors.New("git clone --mirror: " + stderr)
|
||||
}
|
||||
|
||||
if _, err = x.InsertOne(&Mirror{
|
||||
RepoID: repoId,
|
||||
Interval: 24,
|
||||
NextUpdate: time.Now().Add(24 * time.Hour),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
func createUpdateHook(repoPath string) error {
|
||||
return git.SetUpdateHook(repoPath,
|
||||
fmt.Sprintf(_TPL_UPDATE_HOOK, setting.ScriptType, "\""+setting.AppPath+"\"", setting.CustomConf))
|
||||
}
|
||||
|
||||
type MigrateRepoOptions struct {
|
||||
@@ -517,29 +630,35 @@ func MigrateRepository(u *User, opts MigrateRepoOptions) (*Repository, error) {
|
||||
repo.NumWatches = 1
|
||||
}
|
||||
|
||||
repo.IsBare = false
|
||||
if opts.IsMirror {
|
||||
if err = MirrorRepository(repo.ID, u.Name, repo.Name, repoPath, opts.RemoteAddr); err != nil {
|
||||
return repo, err
|
||||
}
|
||||
repo.IsMirror = true
|
||||
return repo, UpdateRepository(repo, false)
|
||||
} else {
|
||||
os.RemoveAll(repoPath)
|
||||
os.RemoveAll(repoPath)
|
||||
if err = git.Clone(opts.RemoteAddr, repoPath, git.CloneRepoOptions{
|
||||
Mirror: true,
|
||||
Quiet: true,
|
||||
Timeout: 10 * time.Minute,
|
||||
}); err != nil {
|
||||
return repo, fmt.Errorf("Clone: %v", err)
|
||||
}
|
||||
|
||||
// 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", 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)
|
||||
if opts.IsMirror {
|
||||
if _, err = x.InsertOne(&Mirror{
|
||||
RepoID: repo.ID,
|
||||
Interval: 24,
|
||||
NextUpdate: time.Now().Add(24 * time.Hour),
|
||||
}); err != nil {
|
||||
return repo, fmt.Errorf("InsertOne: %v", err)
|
||||
}
|
||||
|
||||
repo.IsMirror = true
|
||||
return repo, UpdateRepository(repo, false)
|
||||
}
|
||||
|
||||
if err = createUpdateHook(repoPath); err != nil {
|
||||
return repo, fmt.Errorf("createUpdateHook: %v", err)
|
||||
}
|
||||
|
||||
// Clean up mirror info which prevents "push --all".
|
||||
configPath := filepath.Join(repoPath, "/config")
|
||||
// This also removes possible user credentials.
|
||||
configPath := repo.GitConfigPath()
|
||||
cfg, err := ini.Load(configPath)
|
||||
if err != nil {
|
||||
return repo, fmt.Errorf("open config file: %v", err)
|
||||
@@ -550,7 +669,7 @@ func MigrateRepository(u *User, opts MigrateRepoOptions) (*Repository, error) {
|
||||
}
|
||||
|
||||
// Check if repository is empty.
|
||||
_, stderr, err = com.ExecCmdDir(repoPath, "git", "log", "-1")
|
||||
_, stderr, err := com.ExecCmdDir(repoPath, "git", "log", "-1")
|
||||
if err != nil {
|
||||
if strings.Contains(stderr, "fatal: bad default revision 'HEAD'") {
|
||||
repo.IsBare = true
|
||||
@@ -559,13 +678,19 @@ func MigrateRepository(u *User, opts MigrateRepoOptions) (*Repository, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if repository has master branch, if so set it to default branch.
|
||||
// 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)
|
||||
@@ -575,33 +700,26 @@ func MigrateRepository(u *User, opts MigrateRepoOptions) (*Repository, error) {
|
||||
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, "\""+setting.AppPath+"\"", setting.CustomConf)), 0777)
|
||||
}
|
||||
|
||||
type CreateRepoOptions struct {
|
||||
Name string
|
||||
Description string
|
||||
@@ -638,10 +756,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,
|
||||
@@ -690,22 +805,17 @@ 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()))
|
||||
@@ -792,13 +902,20 @@ 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) {
|
||||
if !u.CanCreateRepo() {
|
||||
return nil, ErrReachLimitOfRepo{u.MaxRepoCreation}
|
||||
}
|
||||
|
||||
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()
|
||||
@@ -976,7 +1093,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()
|
||||
@@ -998,7 +1122,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) {
|
||||
@@ -1102,26 +1237,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.
|
||||
@@ -1163,12 +1292,18 @@ 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, "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)
|
||||
@@ -1314,47 +1449,99 @@ func DeleteRepositoryArchives() error {
|
||||
return x.Where("id > 0").Iterate(new(Repository),
|
||||
func(idx int, bean interface{}) error {
|
||||
repo := bean.(*Repository)
|
||||
repoPath, err := repo.RepoPath()
|
||||
if err != nil {
|
||||
if err2 := CreateRepositoryNotice(fmt.Sprintf("DeleteRepositoryArchives[%d]: %v", repo.ID, err)); err2 != nil {
|
||||
log.Error(4, "CreateRepositoryNotice: %v", err2)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return os.RemoveAll(filepath.Join(repoPath, "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)
|
||||
repoPath, err := repo.RepoPath()
|
||||
if err != nil {
|
||||
if err2 := CreateRepositoryNotice(fmt.Sprintf("RewriteRepositoryUpdateHook[%d]: %v", repo.ID, err)); err2 != nil {
|
||||
log.Error(4, "CreateRepositoryNotice: %v", err2)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return createUpdateHook(repoPath)
|
||||
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")
|
||||
|
||||
@@ -1370,11 +1557,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 {
|
||||
@@ -1402,25 +1585,19 @@ 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")
|
||||
|
||||
args := append([]string{"fsck"}, setting.Cron.RepoHealthCheck.Args...)
|
||||
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...)
|
||||
if err != nil {
|
||||
repoPath := repo.RepoPath()
|
||||
if err := git.Fsck(repoPath, setting.Cron.RepoHealthCheck.Timeout, setting.Cron.RepoHealthCheck.Args...); err != nil {
|
||||
desc := fmt.Sprintf("Fail to health check repository(%s)", repoPath)
|
||||
log.Warn(desc)
|
||||
if err = CreateRepositoryNotice(desc); err != nil {
|
||||
@@ -1471,11 +1648,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")
|
||||
|
||||
@@ -1515,7 +1692,7 @@ func CheckRepoStats() {
|
||||
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 {
|
||||
@@ -1696,25 +1873,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 all users watching given repository.
|
||||
func (repo *Repository) GetWatchers(offset int) ([]*User, error) {
|
||||
users := make([]*User, 0, 10)
|
||||
offset = (offset - 1) * ItemsPerPage
|
||||
|
||||
err := x.Limit(ItemsPerPage, offset).Where("repo_id=?", repo.ID).Join("LEFT", "watch", "user.id=watch.user_id").Find(&users)
|
||||
|
||||
return users, err
|
||||
// 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 {
|
||||
@@ -1794,13 +1967,10 @@ func IsStaring(uid, repoId int64) bool {
|
||||
return has
|
||||
}
|
||||
|
||||
func (repo *Repository) GetStars(offset int) ([]*User, error) {
|
||||
users := make([]*User, 0, 10)
|
||||
offset = (offset - 1) * ItemsPerPage
|
||||
|
||||
err := x.Limit(ItemsPerPage, offset).Where("repo_id=?", repo.ID).Join("LEFT", "star", "user.id=star.uid").Find(&users)
|
||||
|
||||
return users, err
|
||||
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)
|
||||
}
|
||||
|
||||
// ___________ __
|
||||
@@ -1844,15 +2014,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)
|
||||
}
|
||||
@@ -1872,9 +2037,6 @@ func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Reposit
|
||||
}
|
||||
|
||||
func (repo *Repository) GetForks() ([]*Repository, error) {
|
||||
forks := make([]*Repository, 0, 10)
|
||||
|
||||
err := x.Find(&forks, &Repository{ForkID: repo.ID})
|
||||
|
||||
return forks, err
|
||||
forks := make([]*Repository, 0, repo.NumForks)
|
||||
return forks, x.Find(&forks, &Repository{ForkID: repo.ID})
|
||||
}
|
||||
|
||||
@@ -32,10 +32,6 @@ 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.
|
||||
|
||||
@@ -211,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.
|
||||
@@ -303,27 +299,32 @@ func addKey(e Engine, key *PublicKey) (err error) {
|
||||
if _, err = e.Insert(key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Don't need to rewrite this file if builtin SSH server is enabled.
|
||||
if setting.StartSSHServer {
|
||||
return nil
|
||||
}
|
||||
return saveAuthorizedKeyFile(key)
|
||||
}
|
||||
|
||||
// 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{
|
||||
@@ -334,10 +335,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.
|
||||
@@ -443,6 +444,11 @@ func deletePublicKey(e *xorm.Session, keyID int64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Don't need to rewrite this file if builtin SSH server is enabled.
|
||||
if setting.StartSSHServer {
|
||||
return nil
|
||||
}
|
||||
|
||||
fpath := filepath.Join(SSHPath, "authorized_keys")
|
||||
tmpPath := filepath.Join(SSHPath, "authorized_keys.tmp")
|
||||
if err = rewriteAuthorizedKeys(key, fpath, tmpPath); err != nil {
|
||||
@@ -454,12 +460,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()
|
||||
@@ -523,6 +535,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:"-"`
|
||||
@@ -537,6 +550,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))
|
||||
@@ -557,18 +580,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.
|
||||
@@ -578,39 +602,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.
|
||||
@@ -619,8 +656,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.
|
||||
@@ -630,13 +672,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()
|
||||
@@ -650,7 +706,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 {
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/git"
|
||||
git "github.com/gogits/git-shell"
|
||||
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
)
|
||||
|
||||
@@ -47,6 +47,24 @@ func DeleteUpdateTaskByUUID(uuid string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func ListToPushCommits(l *list.List) *PushCommits {
|
||||
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,
|
||||
&PushCommit{commit.ID.String(),
|
||||
commit.Message(),
|
||||
commit.Author.Email,
|
||||
commit.Author.Name,
|
||||
})
|
||||
}
|
||||
return &PushCommits{l.Len(), commits, "", nil}
|
||||
}
|
||||
|
||||
func Update(refName, oldCommitID, newCommitID, userName, repoUserName, repoName string, userID int64) error {
|
||||
isNew := strings.HasPrefix(oldCommitID, "0000000")
|
||||
if isNew &&
|
||||
@@ -100,7 +118,7 @@ func Update(refName, oldCommitID, newCommitID, userName, repoUserName, repoName
|
||||
actEmail = cmt.Committer.Email
|
||||
}
|
||||
|
||||
commit := &base.PushCommits{}
|
||||
commit := &PushCommits{}
|
||||
|
||||
if err = CommitRepoAction(userID, user.Id, userName, actEmail,
|
||||
repo.ID, repoUserName, repoName, refName, commit, oldCommitID, newCommitID); err != nil {
|
||||
@@ -117,7 +135,7 @@ func Update(refName, oldCommitID, newCommitID, userName, repoUserName, repoName
|
||||
// Push new branch.
|
||||
var l *list.List
|
||||
if isNew {
|
||||
l, err = newCommit.CommitsBefore()
|
||||
l, err = newCommit.CommitsBeforeLimit(10)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CommitsBefore: %v", err)
|
||||
}
|
||||
@@ -132,24 +150,8 @@ func Update(refName, oldCommitID, newCommitID, userName, repoUserName, repoName
|
||||
return fmt.Errorf("runUpdate.Commit repoId: %v", err)
|
||||
}
|
||||
|
||||
// Push commits.
|
||||
commits := make([]*base.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(),
|
||||
commit.Message(),
|
||||
commit.Author.Email,
|
||||
commit.Author.Name,
|
||||
})
|
||||
}
|
||||
|
||||
if err = CommitRepoAction(userID, user.Id, userName, actEmail,
|
||||
repo.ID, repoUserName, repoName, refName, &base.PushCommits{l.Len(), commits, ""}, oldCommitID, newCommitID); err != nil {
|
||||
if err = CommitRepoAction(userID, user.Id, userName, user.Email,
|
||||
repo.ID, repoUserName, repoName, refName, ListToPushCommits(l), oldCommitID, newCommitID); err != nil {
|
||||
return fmt.Errorf("runUpdate.models.CommitRepoAction: %s/%s:%v", repoUserName, repoName, err)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"image"
|
||||
"image/jpeg"
|
||||
_ "image/jpeg"
|
||||
"image/png"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
@@ -24,9 +25,10 @@ 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"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
@@ -73,6 +75,8 @@ type User struct {
|
||||
|
||||
// Remember visibility choice for convenience, true for private
|
||||
LastRepoVisibility bool
|
||||
// Maximum repository creation limit, -1 means use gloabl default
|
||||
MaxRepoCreation int `xorm:"NOT NULL DEFAULT -1"`
|
||||
|
||||
// Permissions.
|
||||
IsActive bool
|
||||
@@ -99,6 +103,12 @@ type User struct {
|
||||
Members []*User `xorm:"-"`
|
||||
}
|
||||
|
||||
func (u *User) BeforeUpdate() {
|
||||
if u.MaxRepoCreation < -1 {
|
||||
u.MaxRepoCreation = -1
|
||||
}
|
||||
}
|
||||
|
||||
func (u *User) AfterSet(colName string, _ xorm.Cell) {
|
||||
switch colName {
|
||||
case "full_name":
|
||||
@@ -108,6 +118,34 @@ func (u *User) AfterSet(colName string, _ xorm.Cell) {
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if user login type is LOGIN_PLAIN.
|
||||
func (u *User) IsLocal() bool {
|
||||
return u.LoginType <= LOGIN_PLAIN
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func (u *User) RepoCreationNum() int {
|
||||
if u.MaxRepoCreation <= -1 {
|
||||
return setting.Repository.MaxCreationLimit
|
||||
}
|
||||
return u.MaxRepoCreation
|
||||
}
|
||||
|
||||
func (u *User) CanCreateRepo() bool {
|
||||
if u.MaxRepoCreation <= -1 {
|
||||
if setting.Repository.MaxCreationLimit <= -1 {
|
||||
return true
|
||||
}
|
||||
return u.NumRepos < setting.Repository.MaxCreationLimit
|
||||
}
|
||||
return u.NumRepos < u.MaxRepoCreation
|
||||
}
|
||||
|
||||
// CanEditGitHook returns true if user can edit Git hooks.
|
||||
func (u *User) CanEditGitHook() bool {
|
||||
return u.IsAdmin || u.AllowGitHook
|
||||
@@ -138,9 +176,6 @@ 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
|
||||
}
|
||||
|
||||
@@ -253,14 +288,12 @@ func (u *User) ValidatePassword(passwd string) bool {
|
||||
// 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 sessionRelease(sess)
|
||||
@@ -268,19 +301,20 @@ func (u *User) UploadAvatar(data []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = sess.Id(u.Id).AllCols().Update(u); err != nil {
|
||||
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 {
|
||||
return err
|
||||
return fmt.Errorf("Create: %v", err)
|
||||
}
|
||||
defer fw.Close()
|
||||
|
||||
if err = jpeg.Encode(fw, m, nil); err != nil {
|
||||
return err
|
||||
if err = png.Encode(fw, m); err != nil {
|
||||
return fmt.Errorf("Encode: %v", err)
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
@@ -367,6 +401,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,
|
||||
@@ -418,6 +461,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
|
||||
@@ -431,6 +475,7 @@ func CreateUser(u *User) (err error) {
|
||||
u.Rands = GetUserSalt()
|
||||
u.Salt = GetUserSalt()
|
||||
u.EncodePasswd()
|
||||
u.MaxRepoCreation = -1
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
@@ -651,7 +696,7 @@ func deleteUser(e *xorm.Session, u *User) error {
|
||||
&IssueUser{UID: u.Id},
|
||||
&EmailAddress{UID: u.Id},
|
||||
); err != nil {
|
||||
return fmt.Errorf("deleteUser: %v", err)
|
||||
return fmt.Errorf("deleteBeans: %v", err)
|
||||
}
|
||||
|
||||
// ***** START: PublicKey *****
|
||||
@@ -991,7 +1036,7 @@ func GetUserByEmail(email string) (*User, error) {
|
||||
return GetUserByID(emailAddress.UID)
|
||||
}
|
||||
|
||||
return nil, ErrUserNotExist{0, "email"}
|
||||
return nil, ErrUserNotExist{0, email}
|
||||
}
|
||||
|
||||
// SearchUserByName returns given number of users whose name contains keyword.
|
||||
|
||||
@@ -178,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
|
||||
}
|
||||
@@ -335,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -343,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)
|
||||
}
|
||||
@@ -526,6 +526,8 @@ func (t *HookTask) deliver() {
|
||||
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.
|
||||
@@ -590,24 +592,24 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
// Start listening on new hook requests.
|
||||
for repoID := range HookQueue.Queue() {
|
||||
log.Trace("DeliverHooks[%v]: processing delivery hooks", repoID)
|
||||
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 {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gogits/git-shell"
|
||||
api "github.com/gogits/go-gogs-client"
|
||||
|
||||
"github.com/gogits/gogs/modules/git"
|
||||
)
|
||||
|
||||
type SlackMeta struct {
|
||||
@@ -33,8 +32,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 +82,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 +111,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
178
models/wiki.go
Normal 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)
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -31,6 +31,7 @@ type AdminEditUserForm struct {
|
||||
Password string `binding:"MaxSize(255)"`
|
||||
Website string `binding:"MaxSize(50)"`
|
||||
Location string `binding:"MaxSize(50)"`
|
||||
MaxRepoCreation int
|
||||
Active bool
|
||||
Admin bool
|
||||
AllowGitHook bool
|
||||
|
||||
@@ -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/go-macaron/binding"
|
||||
"gopkg.in/macaron.v1"
|
||||
|
||||
"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
|
||||
}
|
||||
@@ -10,28 +10,29 @@ import (
|
||||
)
|
||||
|
||||
type AuthenticationForm struct {
|
||||
ID int64
|
||||
Type int `binding:"Range(2,5)"`
|
||||
Name string `binding:"Required;MaxSize(30)"`
|
||||
Host string
|
||||
Port int
|
||||
BindDN string
|
||||
BindPassword string
|
||||
UserBase string
|
||||
UserDN string `form:"user_dn"`
|
||||
AttributeName string
|
||||
AttributeSurname string
|
||||
AttributeMail string
|
||||
Filter string
|
||||
AdminFilter string
|
||||
IsActive bool
|
||||
SMTPAuth string
|
||||
SMTPHost string
|
||||
SMTPPort int
|
||||
AllowedDomains string
|
||||
TLS bool
|
||||
SkipVerify bool
|
||||
PAMServiceName string `form:"pam_service_name"`
|
||||
ID int64
|
||||
Type int `binding:"Range(2,5)"`
|
||||
Name string `binding:"Required;MaxSize(30)"`
|
||||
Host string
|
||||
Port int
|
||||
BindDN string
|
||||
BindPassword string
|
||||
UserBase string
|
||||
UserDN string
|
||||
AttributeUsername string
|
||||
AttributeName string
|
||||
AttributeSurname string
|
||||
AttributeMail string
|
||||
Filter string
|
||||
AdminFilter string
|
||||
IsActive bool
|
||||
SMTPAuth string
|
||||
SMTPHost string
|
||||
SMTPPort int
|
||||
AllowedDomains string
|
||||
TLS bool
|
||||
SkipVerify bool
|
||||
PAMServiceName string
|
||||
}
|
||||
|
||||
func (f *AuthenticationForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
|
||||
@@ -11,27 +11,29 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gogits/gogs/modules/ldap"
|
||||
"gopkg.in/ldap.v2"
|
||||
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
)
|
||||
|
||||
// Basic LDAP authentication service
|
||||
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
|
||||
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
|
||||
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 *Source) sanitizedUserQuery(username string) (string, bool) {
|
||||
@@ -100,7 +102,7 @@ func (ls *Source) 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
|
||||
}
|
||||
|
||||
@@ -108,7 +110,7 @@ func (ls *Source) FindUserDN(name string) (string, bool) {
|
||||
}
|
||||
|
||||
// searchEntry : search an LDAP source if an entry (name, passwd) is valid and in the specific filter
|
||||
func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, string, string, bool, bool) {
|
||||
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)
|
||||
@@ -116,7 +118,7 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str
|
||||
var ok bool
|
||||
userDN, ok = ls.sanitizedUserDN(name)
|
||||
if !ok {
|
||||
return "", "", "", false, false
|
||||
return "", "", "", "", false, false
|
||||
}
|
||||
} else {
|
||||
log.Trace("LDAP will use BindDN.")
|
||||
@@ -124,7 +126,7 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str
|
||||
var found bool
|
||||
userDN, found = ls.FindUserDN(name)
|
||||
if !found {
|
||||
return "", "", "", false, false
|
||||
return "", "", "", "", false, false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +134,7 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str
|
||||
if err != nil {
|
||||
log.Error(4, "LDAP Connect error (%s): %v", ls.Host, err)
|
||||
ls.Enabled = false
|
||||
return "", "", "", false, false
|
||||
return "", "", "", "", false, false
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
@@ -140,13 +142,13 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str
|
||||
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, ok := ls.sanitizedUserQuery(name)
|
||||
if !ok {
|
||||
return "", "", "", false, false
|
||||
return "", "", "", "", false, false
|
||||
}
|
||||
|
||||
search := ldap.NewSearchRequest(
|
||||
@@ -157,7 +159,7 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str
|
||||
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 {
|
||||
if directBind {
|
||||
log.Error(4, "User filter inhibited user login.")
|
||||
@@ -165,9 +167,10 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str
|
||||
log.Error(4, "LDAP Search failed unexpectedly! (0 entries)")
|
||||
}
|
||||
|
||||
return "", "", "", false, false
|
||||
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)
|
||||
@@ -189,7 +192,7 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str
|
||||
}
|
||||
}
|
||||
|
||||
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 *Source) (*ldap.Conn, error) {
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
// \/ /_____/ \/ \/ \/ \/ \/
|
||||
|
||||
type CreateOrgForm struct {
|
||||
OrgName string `binding:"Required;AlphaDashDot;MaxSize(30)" locale:"org.org_name_holder"`
|
||||
OrgName string `binding:"Required;AlphaDashDot;MaxSize(35)" locale:"org.org_name_holder"`
|
||||
}
|
||||
|
||||
func (f *CreateOrgForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
@@ -25,11 +25,12 @@ func (f *CreateOrgForm) Validate(ctx *macaron.Context, errs binding.Errors) bind
|
||||
}
|
||||
|
||||
type UpdateOrgSettingForm struct {
|
||||
Name string `binding:"Required;AlphaDashDot;MaxSize(30)" 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)"`
|
||||
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)"`
|
||||
MaxRepoCreation int
|
||||
}
|
||||
|
||||
func (f *UpdateOrgSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
|
||||
@@ -81,12 +81,22 @@ func (f MigrateRepoForm) ParseRemoteAddr(user *models.User) (string, error) {
|
||||
}
|
||||
|
||||
type RepoSettingForm struct {
|
||||
RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"`
|
||||
Description string `binding:"MaxSize(255)"`
|
||||
Website string `binding:"Url;MaxSize(100)"`
|
||||
Branch string
|
||||
Interval int
|
||||
Private bool
|
||||
RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"`
|
||||
Description string `binding:"MaxSize(255)"`
|
||||
Website string `binding:"Url;MaxSize(100)"`
|
||||
Branch string
|
||||
Interval int
|
||||
MirrorAddress string
|
||||
Private bool
|
||||
|
||||
// Advanced settings
|
||||
EnableWiki bool
|
||||
EnableExternalWiki bool
|
||||
ExternalWikiURL string
|
||||
EnableIssues bool
|
||||
EnableExternalTracker bool
|
||||
TrackerURLFormat string
|
||||
EnablePulls bool
|
||||
}
|
||||
|
||||
func (f *RepoSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
@@ -215,12 +225,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 {
|
||||
@@ -237,3 +247,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)
|
||||
}
|
||||
|
||||
@@ -87,12 +87,12 @@ func (f *SignInForm) Validate(ctx *macaron.Context, errs binding.Errors) binding
|
||||
// \/ \/ \/ \/ \/
|
||||
|
||||
type UpdateProfileForm struct {
|
||||
Name string `binding:"Required;MaxSize(35)"`
|
||||
Name string `binding:"OmitEmpty;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)"`
|
||||
Gravatar string `binding:"OmitEmpty;Email;MaxSize(254)"`
|
||||
}
|
||||
|
||||
func (f *UpdateProfileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
|
||||
@@ -39,6 +39,8 @@ import (
|
||||
"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
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/russross/blackfriday"
|
||||
"golang.org/x/net/html"
|
||||
|
||||
@@ -99,13 +100,26 @@ 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) {
|
||||
prefix := strings.Replace(options.urlPrefix, "/src/", "/raw/", 1)
|
||||
if len(link) > 0 && !isLink(link) {
|
||||
if link[0] != '/' {
|
||||
prefix += "/"
|
||||
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))
|
||||
}
|
||||
link = []byte(prefix + string(link))
|
||||
}
|
||||
|
||||
out.WriteString(`<a href="`)
|
||||
@@ -119,11 +133,47 @@ var (
|
||||
MentionPattern = regexp.MustCompile(`(\s|^)@[0-9a-zA-Z_\.]+`)
|
||||
commitPattern = regexp.MustCompile(`(\s|^)https?.*commit/[0-9a-zA-Z]+(#+[0-9a-zA-Z-]*)?`)
|
||||
issueFullPattern = regexp.MustCompile(`(\s|^)https?.*issues/[0-9]+(#+[0-9a-zA-Z-]*)?`)
|
||||
issueIndexPattern = regexp.MustCompile(`( |^)#[0-9]+\b`)
|
||||
issueIndexPattern = regexp.MustCompile(`( |^|\()#[0-9]+\b`)
|
||||
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 = string(m2[0])
|
||||
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)
|
||||
@@ -131,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
|
||||
}
|
||||
@@ -167,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,
|
||||
@@ -217,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())
|
||||
}
|
||||
@@ -258,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))
|
||||
}
|
||||
|
||||
@@ -23,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().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))
|
||||
@@ -43,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
@@ -1,26 +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 git
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
)
|
||||
|
||||
type Blob struct {
|
||||
repo *Repository
|
||||
*TreeEntry
|
||||
}
|
||||
|
||||
func (b *Blob) Data() (io.Reader, error) {
|
||||
stdout, stderr, err := com.ExecCmdDirBytes(b.repo.Path, "git", "show", b.ID.String())
|
||||
if err != nil {
|
||||
return nil, errors.New(string(stderr))
|
||||
}
|
||||
return bytes.NewBuffer(stdout), nil
|
||||
}
|
||||
@@ -1,162 +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 git
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"container/list"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Commit represents a git commit.
|
||||
type Commit struct {
|
||||
Tree
|
||||
ID sha1 // The id of this commit object
|
||||
Author *Signature
|
||||
Committer *Signature
|
||||
CommitMessage string
|
||||
|
||||
parents []sha1 // sha1 strings
|
||||
submodules map[string]*SubModule
|
||||
}
|
||||
|
||||
// Return the commit message. Same as retrieving CommitMessage directly.
|
||||
func (c *Commit) Message() string {
|
||||
return c.CommitMessage
|
||||
}
|
||||
|
||||
func (c *Commit) Summary() string {
|
||||
return strings.Split(c.CommitMessage, "\n")[0]
|
||||
}
|
||||
|
||||
// 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
|
||||
return
|
||||
}
|
||||
return c.parents[n], nil
|
||||
}
|
||||
|
||||
// Return parent number n (0-based index)
|
||||
func (c *Commit) Parent(n int) (*Commit, error) {
|
||||
id, err := c.ParentId(n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parent, err := c.repo.getCommit(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parent, nil
|
||||
}
|
||||
|
||||
// Return the number of parents of the commit. 0 if this is the
|
||||
// root commit, otherwise 1,2,...
|
||||
func (c *Commit) ParentCount() int {
|
||||
return len(c.parents)
|
||||
}
|
||||
|
||||
func (c *Commit) CommitsBefore() (*list.List, error) {
|
||||
return c.repo.getCommitsBefore(c.ID)
|
||||
}
|
||||
|
||||
func (c *Commit) CommitsBeforeUntil(commitId string) (*list.List, error) {
|
||||
ec, err := c.repo.GetCommit(commitId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.repo.CommitsBetween(c, ec)
|
||||
}
|
||||
|
||||
func (c *Commit) CommitsCount() (int, error) {
|
||||
return c.repo.commitsCount(c.ID)
|
||||
}
|
||||
|
||||
func (c *Commit) SearchCommits(keyword string) (*list.List, error) {
|
||||
return c.repo.searchCommits(c.ID, keyword)
|
||||
}
|
||||
|
||||
func (c *Commit) CommitsByRange(page int) (*list.List, error) {
|
||||
return c.repo.commitsByRange(c.ID, page)
|
||||
}
|
||||
|
||||
func (c *Commit) GetCommitOfRelPath(relPath string) (*Commit, error) {
|
||||
return c.repo.getCommitOfRelPath(c.ID, relPath)
|
||||
}
|
||||
|
||||
func (c *Commit) GetSubModule(entryname string) (*SubModule, error) {
|
||||
modules, err := c.GetSubModules()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return modules[entryname], nil
|
||||
}
|
||||
|
||||
func (c *Commit) GetSubModules() (map[string]*SubModule, error) {
|
||||
if c.submodules != nil {
|
||||
return c.submodules, nil
|
||||
}
|
||||
|
||||
entry, err := c.GetTreeEntryByPath(".gitmodules")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rd, err := entry.Blob().Data()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(rd)
|
||||
c.submodules = make(map[string]*SubModule)
|
||||
var ismodule bool
|
||||
var path string
|
||||
for scanner.Scan() {
|
||||
if strings.HasPrefix(scanner.Text(), "[submodule") {
|
||||
ismodule = true
|
||||
continue
|
||||
}
|
||||
if ismodule {
|
||||
fields := strings.Split(scanner.Text(), "=")
|
||||
k := strings.TrimSpace(fields[0])
|
||||
if k == "path" {
|
||||
path = strings.TrimSpace(fields[1])
|
||||
} else if k == "url" {
|
||||
c.submodules[path] = &SubModule{path, strings.TrimSpace(fields[1])}
|
||||
ismodule = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return c.submodules, nil
|
||||
}
|
||||
|
||||
func isImageFile(data []byte) (string, bool) {
|
||||
contentType := http.DetectContentType(data)
|
||||
if strings.Index(contentType, "image/") != -1 {
|
||||
return contentType, true
|
||||
}
|
||||
return contentType, false
|
||||
}
|
||||
|
||||
func (c *Commit) IsImageFile(name string) bool {
|
||||
blob, err := c.GetBlobByPath(name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
dataRc, err := blob.Data()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
buf := make([]byte, 1024)
|
||||
n, _ := dataRc.Read(buf)
|
||||
if n > 0 {
|
||||
buf = buf[:n]
|
||||
}
|
||||
_, isImage := isImageFile(buf)
|
||||
return isImage
|
||||
}
|
||||
@@ -1,36 +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 git
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
)
|
||||
|
||||
type ArchiveType int
|
||||
|
||||
const (
|
||||
ZIP ArchiveType = iota + 1
|
||||
TARGZ
|
||||
)
|
||||
|
||||
func (c *Commit) CreateArchive(path string, archiveType ArchiveType) error {
|
||||
var format string
|
||||
switch archiveType {
|
||||
case ZIP:
|
||||
format = "zip"
|
||||
case TARGZ:
|
||||
format = "tar.gz"
|
||||
default:
|
||||
return fmt.Errorf("unknown format: %v", archiveType)
|
||||
}
|
||||
|
||||
_, stderr, err := com.ExecCmdDir(c.repo.Path, "git", "archive", "--format="+format, "-o", path, c.ID.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s", stderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,116 +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 git
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
)
|
||||
|
||||
// hookNames is a list of Git hooks' name that are supported.
|
||||
var hookNames = []string{
|
||||
"pre-applypatch",
|
||||
"applypatch-msg",
|
||||
"prepare-commit-msg",
|
||||
"commit-msg",
|
||||
"pre-commit",
|
||||
"pre-rebase",
|
||||
"post-commit",
|
||||
"post-receive",
|
||||
"post-update",
|
||||
}
|
||||
|
||||
var (
|
||||
ErrNotValidHook = errors.New("not a valid Git hook")
|
||||
)
|
||||
|
||||
// IsValidHookName returns true if given name is a valid Git hook.
|
||||
func IsValidHookName(name string) bool {
|
||||
for _, hn := range hookNames {
|
||||
if hn == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Hook represents a Git hook.
|
||||
type Hook struct {
|
||||
name string
|
||||
IsActive bool // Indicates whether repository has this hook.
|
||||
Content string // Content of hook if it's active.
|
||||
Sample string // Sample content from Git.
|
||||
path string // Hook file path.
|
||||
}
|
||||
|
||||
// GetHook returns a Git hook by given name and repository.
|
||||
func GetHook(repoPath, name string) (*Hook, error) {
|
||||
if !IsValidHookName(name) {
|
||||
return nil, ErrNotValidHook
|
||||
}
|
||||
h := &Hook{
|
||||
name: name,
|
||||
path: path.Join(repoPath, "hooks", name),
|
||||
}
|
||||
if isFile(h.path) {
|
||||
data, err := ioutil.ReadFile(h.path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h.IsActive = true
|
||||
h.Content = string(data)
|
||||
} else if isFile(h.path + ".sample") {
|
||||
data, err := ioutil.ReadFile(h.path + ".sample")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h.Sample = string(data)
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (h *Hook) Name() string {
|
||||
return h.name
|
||||
}
|
||||
|
||||
// Update updates hook settings.
|
||||
func (h *Hook) Update() error {
|
||||
if len(strings.TrimSpace(h.Content)) == 0 {
|
||||
if com.IsExist(h.path) {
|
||||
return os.Remove(h.path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return ioutil.WriteFile(h.path, []byte(strings.Replace(h.Content, "\r", "", -1)), os.ModePerm)
|
||||
}
|
||||
|
||||
// ListHooks returns a list of Git hooks of given repository.
|
||||
func ListHooks(repoPath string) (_ []*Hook, err error) {
|
||||
if !isDir(path.Join(repoPath, "hooks")) {
|
||||
return nil, errors.New("hooks path does not exist")
|
||||
}
|
||||
|
||||
hooks := make([]*Hook, len(hookNames))
|
||||
for i, name := range hookNames {
|
||||
hooks[i], err = GetHook(repoPath, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return hooks, nil
|
||||
}
|
||||
|
||||
func (repo *Repository) GetHook(name string) (*Hook, error) {
|
||||
return GetHook(repo.Path, name)
|
||||
}
|
||||
|
||||
func (repo *Repository) Hooks() ([]*Hook, error) {
|
||||
return ListHooks(repo.Path)
|
||||
}
|
||||
@@ -1,27 +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 git
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Repository represents a Git repository.
|
||||
type Repository struct {
|
||||
Path string
|
||||
|
||||
commitCache map[sha1]*Commit
|
||||
tagCache map[sha1]*Tag
|
||||
}
|
||||
|
||||
// OpenRepository opens the repository at the given path.
|
||||
func OpenRepository(repoPath string) (*Repository, error) {
|
||||
repoPath, err := filepath.Abs(repoPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Repository{Path: repoPath}, nil
|
||||
}
|
||||
@@ -1,37 +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 git
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
)
|
||||
|
||||
func IsBranchExist(repoPath, branchName string) bool {
|
||||
_, _, err := com.ExecCmdDir(repoPath, "git", "show-ref", "--verify", "refs/heads/"+branchName)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (repo *Repository) IsBranchExist(branchName string) bool {
|
||||
return IsBranchExist(repo.Path, branchName)
|
||||
}
|
||||
|
||||
func (repo *Repository) GetBranches() ([]string, error) {
|
||||
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "show-ref", "--heads")
|
||||
if err != nil {
|
||||
return nil, concatenateError(err, stderr)
|
||||
}
|
||||
infos := strings.Split(stdout, "\n")
|
||||
branches := make([]string, len(infos)-1)
|
||||
for i, info := range infos[:len(infos)-1] {
|
||||
parts := strings.Split(info, " ")
|
||||
if len(parts) != 2 {
|
||||
continue // NOTE: I should believe git will not give me wrong string.
|
||||
}
|
||||
branches[i] = strings.TrimPrefix(parts[1], "refs/heads/")
|
||||
}
|
||||
return branches, nil
|
||||
}
|
||||
@@ -1,341 +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 git
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"container/list"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
)
|
||||
|
||||
func (repo *Repository) getCommitIdOfRef(refpath string) (string, error) {
|
||||
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "show-ref", "--verify", refpath)
|
||||
if err != nil {
|
||||
return "", errors.New(stderr)
|
||||
}
|
||||
return strings.Split(stdout, " ")[0], nil
|
||||
}
|
||||
|
||||
func (repo *Repository) GetCommitIdOfBranch(branchName string) (string, error) {
|
||||
return repo.getCommitIdOfRef("refs/heads/" + branchName)
|
||||
}
|
||||
|
||||
// get branch's last commit or a special commit by id string
|
||||
func (repo *Repository) GetCommitOfBranch(branchName string) (*Commit, error) {
|
||||
commitId, err := repo.GetCommitIdOfBranch(branchName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return repo.GetCommit(commitId)
|
||||
}
|
||||
|
||||
func (repo *Repository) GetCommitIdOfTag(tagName string) (string, error) {
|
||||
return repo.getCommitIdOfRef("refs/tags/" + tagName)
|
||||
}
|
||||
|
||||
func (repo *Repository) GetCommitOfTag(tagName string) (*Commit, error) {
|
||||
tag, err := repo.GetTag(tagName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tag.Commit()
|
||||
}
|
||||
|
||||
// Parse commit information from the (uncompressed) raw
|
||||
// data from the commit object.
|
||||
// \n\n separate headers from message
|
||||
func parseCommitData(data []byte) (*Commit, error) {
|
||||
commit := new(Commit)
|
||||
commit.parents = make([]sha1, 0, 1)
|
||||
// we now have the contents of the commit object. Let's investigate...
|
||||
nextline := 0
|
||||
l:
|
||||
for {
|
||||
eol := bytes.IndexByte(data[nextline:], '\n')
|
||||
switch {
|
||||
case eol > 0:
|
||||
line := data[nextline : nextline+eol]
|
||||
spacepos := bytes.IndexByte(line, ' ')
|
||||
reftype := line[:spacepos]
|
||||
switch string(reftype) {
|
||||
case "tree":
|
||||
id, err := NewIdFromString(string(line[spacepos+1:]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
commit.Tree.ID = id
|
||||
case "parent":
|
||||
// A commit can have one or more parents
|
||||
oid, err := NewIdFromString(string(line[spacepos+1:]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
commit.parents = append(commit.parents, oid)
|
||||
case "author":
|
||||
sig, err := newSignatureFromCommitline(line[spacepos+1:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
commit.Author = sig
|
||||
case "committer":
|
||||
sig, err := newSignatureFromCommitline(line[spacepos+1:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
commit.Committer = sig
|
||||
}
|
||||
nextline += eol + 1
|
||||
case eol == 0:
|
||||
commit.CommitMessage = string(data[nextline+1:])
|
||||
break l
|
||||
default:
|
||||
break l
|
||||
}
|
||||
}
|
||||
return commit, nil
|
||||
}
|
||||
|
||||
func (repo *Repository) getCommit(id sha1) (*Commit, error) {
|
||||
if repo.commitCache != nil {
|
||||
if c, ok := repo.commitCache[id]; ok {
|
||||
return c, nil
|
||||
}
|
||||
} else {
|
||||
repo.commitCache = make(map[sha1]*Commit, 10)
|
||||
}
|
||||
|
||||
data, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "cat-file", "-p", id.String())
|
||||
if err != nil {
|
||||
return nil, concatenateError(err, string(stderr))
|
||||
}
|
||||
|
||||
commit, err := parseCommitData(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
commit.repo = repo
|
||||
commit.ID = id
|
||||
|
||||
repo.commitCache[id] = commit
|
||||
return commit, nil
|
||||
}
|
||||
|
||||
// Find the commit object in the repository.
|
||||
func (repo *Repository) GetCommit(commitId string) (*Commit, error) {
|
||||
id, err := NewIdFromString(commitId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return repo.getCommit(id)
|
||||
}
|
||||
|
||||
func (repo *Repository) commitsCount(id sha1) (int, error) {
|
||||
if gitVer.LessThan(MustParseVersion("1.8.0")) {
|
||||
stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log",
|
||||
"--pretty=format:''", id.String())
|
||||
if err != nil {
|
||||
return 0, errors.New(string(stderr))
|
||||
}
|
||||
return len(bytes.Split(stdout, []byte("\n"))), nil
|
||||
}
|
||||
|
||||
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "rev-list", "--count", id.String())
|
||||
if err != nil {
|
||||
return 0, errors.New(stderr)
|
||||
}
|
||||
return com.StrTo(strings.TrimSpace(stdout)).Int()
|
||||
}
|
||||
|
||||
func (repo *Repository) CommitsCount(commitId string) (int, error) {
|
||||
id, err := NewIdFromString(commitId)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return repo.commitsCount(id)
|
||||
}
|
||||
|
||||
func (repo *Repository) commitsCountBetween(start, end sha1) (int, error) {
|
||||
if gitVer.LessThan(MustParseVersion("1.8.0")) {
|
||||
stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log",
|
||||
"--pretty=format:''", start.String()+"..."+end.String())
|
||||
if err != nil {
|
||||
return 0, errors.New(string(stderr))
|
||||
}
|
||||
return len(bytes.Split(stdout, []byte("\n"))), nil
|
||||
}
|
||||
|
||||
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "rev-list", "--count",
|
||||
start.String()+"..."+end.String())
|
||||
if err != nil {
|
||||
return 0, errors.New(stderr)
|
||||
}
|
||||
return com.StrTo(strings.TrimSpace(stdout)).Int()
|
||||
}
|
||||
|
||||
func (repo *Repository) CommitsCountBetween(startCommitID, endCommitID string) (int, error) {
|
||||
start, err := NewIdFromString(startCommitID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
end, err := NewIdFromString(endCommitID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return repo.commitsCountBetween(start, end)
|
||||
}
|
||||
|
||||
func (repo *Repository) FilesCountBetween(startCommitID, endCommitID string) (int, error) {
|
||||
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "diff", "--name-only",
|
||||
startCommitID+"..."+endCommitID)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("list changed files: %v", concatenateError(err, stderr))
|
||||
}
|
||||
return len(strings.Split(stdout, "\n")) - 1, nil
|
||||
}
|
||||
|
||||
// used only for single tree, (]
|
||||
func (repo *Repository) CommitsBetween(last *Commit, before *Commit) (*list.List, error) {
|
||||
l := list.New()
|
||||
if last == nil || last.ParentCount() == 0 {
|
||||
return l, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
cur := last
|
||||
for {
|
||||
if cur.ID.Equal(before.ID) {
|
||||
break
|
||||
}
|
||||
l.PushBack(cur)
|
||||
if cur.ParentCount() == 0 {
|
||||
break
|
||||
}
|
||||
cur, err = cur.Parent(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
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 fmt.Errorf("getCommit: %v", err)
|
||||
}
|
||||
|
||||
var e *list.Element
|
||||
if parent == nil {
|
||||
e = l.PushBack(commit)
|
||||
} else {
|
||||
var in = parent
|
||||
for {
|
||||
if in == nil {
|
||||
break
|
||||
} else if in.Value.(*Commit).ID.Equal(commit.ID) {
|
||||
return nil
|
||||
} else {
|
||||
if in.Next() == nil {
|
||||
break
|
||||
}
|
||||
if in.Value.(*Commit).Committer.When.Equal(commit.Committer.When) {
|
||||
break
|
||||
}
|
||||
|
||||
if in.Value.(*Commit).Committer.When.After(commit.Committer.When) &&
|
||||
in.Next().Value.(*Commit).Committer.When.Before(commit.Committer.When) {
|
||||
break
|
||||
}
|
||||
}
|
||||
in = in.Next()
|
||||
}
|
||||
|
||||
e = l.InsertAfter(commit, in)
|
||||
}
|
||||
|
||||
var pr = parent
|
||||
if commit.ParentCount() > 1 {
|
||||
pr = e
|
||||
}
|
||||
|
||||
for i := 0; i < commit.ParentCount(); i++ {
|
||||
id, err := commit.ParentId(i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = repo.commitsBefore(lock, l, pr, id, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repo *Repository) FileCommitsCount(branch, file string) (int, error) {
|
||||
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "rev-list", "--count",
|
||||
branch, "--", file)
|
||||
if err != nil {
|
||||
return 0, errors.New(stderr)
|
||||
}
|
||||
return com.StrTo(strings.TrimSpace(stdout)).Int()
|
||||
}
|
||||
|
||||
func (repo *Repository) CommitsByFileAndRange(branch, file string, page int) (*list.List, error) {
|
||||
stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log", branch,
|
||||
"--skip="+com.ToStr((page-1)*50), "--max-count=50", prettyLogFormat, "--", file)
|
||||
if err != nil {
|
||||
return nil, errors.New(string(stderr))
|
||||
}
|
||||
return parsePrettyFormatLog(repo, stdout)
|
||||
}
|
||||
|
||||
func (repo *Repository) getCommitsBefore(id sha1) (*list.List, error) {
|
||||
l := list.New()
|
||||
lock := new(sync.Mutex)
|
||||
return l, repo.commitsBefore(lock, l, nil, id, 0)
|
||||
}
|
||||
|
||||
func (repo *Repository) searchCommits(id sha1, keyword string) (*list.List, error) {
|
||||
stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log", id.String(), "-100",
|
||||
"-i", "--grep="+keyword, prettyLogFormat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(stderr) > 0 {
|
||||
return nil, errors.New(string(stderr))
|
||||
}
|
||||
return parsePrettyFormatLog(repo, stdout)
|
||||
}
|
||||
|
||||
var CommitsRangeSize = 50
|
||||
|
||||
func (repo *Repository) commitsByRange(id sha1, page int) (*list.List, error) {
|
||||
stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log", id.String(),
|
||||
"--skip="+com.ToStr((page-1)*CommitsRangeSize), "--max-count="+com.ToStr(CommitsRangeSize), prettyLogFormat)
|
||||
if err != nil {
|
||||
return nil, errors.New(string(stderr))
|
||||
}
|
||||
return parsePrettyFormatLog(repo, stdout)
|
||||
}
|
||||
|
||||
func (repo *Repository) getCommitOfRelPath(id sha1, relPath string) (*Commit, error) {
|
||||
stdout, _, err := com.ExecCmdDir(repo.Path, "git", "log", "-1", prettyLogFormat, id.String(), "--", relPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err = NewIdFromString(string(stdout))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return repo.getCommit(id)
|
||||
}
|
||||
@@ -1,14 +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 git
|
||||
|
||||
type ObjectType string
|
||||
|
||||
const (
|
||||
COMMIT ObjectType = "commit"
|
||||
TREE ObjectType = "tree"
|
||||
BLOB ObjectType = "blob"
|
||||
TAG ObjectType = "tag"
|
||||
)
|
||||
@@ -1,104 +0,0 @@
|
||||
// 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 (
|
||||
"container/list"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
)
|
||||
|
||||
type PullRequestInfo struct {
|
||||
MergeBase string
|
||||
Commits *list.List
|
||||
// Diff *Diff
|
||||
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, err error) {
|
||||
// Add a temporary remote.
|
||||
tmpRemote := com.ToStr(time.Now().UnixNano())
|
||||
if err = repo.AddRemote(tmpRemote, basePath); err != nil {
|
||||
return nil, fmt.Errorf("AddRemote: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
repo.RemoveRemote(tmpRemote)
|
||||
}()
|
||||
|
||||
remoteBranch := "remotes/" + tmpRemote + "/" + baseBranch
|
||||
|
||||
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))
|
||||
}
|
||||
prInfo.Commits, err = parsePrettyFormatLog(repo, []byte(stdout))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsePrettyFormatLog: %v", err)
|
||||
}
|
||||
|
||||
// Count number of changed files.
|
||||
stdout, stderr, err = com.ExecCmdDir(repo.Path, "git", "diff", "--name-only", remoteBranch+"..."+headBranch)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list changed files: %v", concatenateError(err, stderr))
|
||||
}
|
||||
prInfo.NumFiles = len(strings.Split(stdout, "\n")) - 1
|
||||
|
||||
return prInfo, nil
|
||||
}
|
||||
|
||||
// GetPatch generates and returns patch data between given branches.
|
||||
func (repo *Repository) GetPatch(mergeBase, headBranch string) ([]byte, error) {
|
||||
stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "diff", "-p", mergeBase, headBranch)
|
||||
if err != nil {
|
||||
return nil, concatenateError(err, string(stderr))
|
||||
}
|
||||
|
||||
return stdout, nil
|
||||
}
|
||||
|
||||
// Merge merges pull request from head repository and branch.
|
||||
func (repo *Repository) Merge(headRepoPath string, baseBranch, headBranch string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,117 +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 git
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
)
|
||||
|
||||
func IsTagExist(repoPath, tagName string) bool {
|
||||
_, _, err := com.ExecCmdDir(repoPath, "git", "show-ref", "--verify", "refs/tags/"+tagName)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (repo *Repository) IsTagExist(tagName string) bool {
|
||||
return IsTagExist(repo.Path, tagName)
|
||||
}
|
||||
|
||||
func (repo *Repository) getTagsReversed() ([]string, error) {
|
||||
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "tag", "-l", "--sort=-v:refname")
|
||||
if err != nil {
|
||||
return nil, concatenateError(err, stderr)
|
||||
}
|
||||
tags := strings.Split(stdout, "\n")
|
||||
return tags[:len(tags)-1], nil
|
||||
}
|
||||
|
||||
// GetTags returns all tags of given repository.
|
||||
func (repo *Repository) GetTags() ([]string, error) {
|
||||
if gitVer.AtLeast(MustParseVersion("2.0.0")) {
|
||||
return repo.getTagsReversed()
|
||||
}
|
||||
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "tag", "-l")
|
||||
if err != nil {
|
||||
return nil, concatenateError(err, stderr)
|
||||
}
|
||||
tags := strings.Split(stdout, "\n")
|
||||
return tags[:len(tags)-1], nil
|
||||
}
|
||||
|
||||
func (repo *Repository) CreateTag(tagName, idStr string) error {
|
||||
_, stderr, err := com.ExecCmdDir(repo.Path, "git", "tag", tagName, idStr)
|
||||
if err != nil {
|
||||
return errors.New(stderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repo *Repository) getTag(id sha1) (*Tag, error) {
|
||||
if repo.tagCache != nil {
|
||||
if t, ok := repo.tagCache[id]; ok {
|
||||
return t, nil
|
||||
}
|
||||
} else {
|
||||
repo.tagCache = make(map[sha1]*Tag, 10)
|
||||
}
|
||||
|
||||
// Get tag type.
|
||||
tp, stderr, err := com.ExecCmdDir(repo.Path, "git", "cat-file", "-t", id.String())
|
||||
if err != nil {
|
||||
return nil, errors.New(stderr)
|
||||
}
|
||||
tp = strings.TrimSpace(tp)
|
||||
|
||||
// Tag is a commit.
|
||||
if ObjectType(tp) == COMMIT {
|
||||
tag := &Tag{
|
||||
ID: id,
|
||||
Object: id,
|
||||
Type: string(COMMIT),
|
||||
repo: repo,
|
||||
}
|
||||
repo.tagCache[id] = tag
|
||||
return tag, nil
|
||||
}
|
||||
|
||||
// Tag with message.
|
||||
data, bytErr, err := com.ExecCmdDirBytes(repo.Path, "git", "cat-file", "-p", id.String())
|
||||
if err != nil {
|
||||
return nil, errors.New(string(bytErr))
|
||||
}
|
||||
|
||||
tag, err := parseTagData(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tag.ID = id
|
||||
tag.repo = repo
|
||||
|
||||
repo.tagCache[id] = tag
|
||||
return tag, nil
|
||||
}
|
||||
|
||||
// GetTag returns a Git tag by given name.
|
||||
func (repo *Repository) GetTag(tagName string) (*Tag, error) {
|
||||
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "show-ref", "--tags", tagName)
|
||||
if err != nil {
|
||||
return nil, errors.New(stderr)
|
||||
}
|
||||
|
||||
id, err := NewIdFromString(strings.Split(stdout, " ")[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tag, err := repo.getTag(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tag.Name = tagName
|
||||
return tag, nil
|
||||
}
|
||||
@@ -1,32 +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 git
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
)
|
||||
|
||||
// Find the tree object in the repository.
|
||||
func (repo *Repository) GetTree(idStr string) (*Tree, error) {
|
||||
id, err := NewIdFromString(idStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return repo.getTree(id)
|
||||
}
|
||||
|
||||
func (repo *Repository) getTree(id sha1) (*Tree, error) {
|
||||
treePath := filepathFromSHA1(repo.Path, id.String())
|
||||
if !com.IsFile(treePath) {
|
||||
_, _, err := com.ExecCmdDir(repo.Path, "git", "ls-tree", id.String())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("repo.getTree: %v", ErrNotExist)
|
||||
}
|
||||
}
|
||||
|
||||
return NewTree(repo, id), nil
|
||||
}
|
||||
@@ -1,87 +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 git
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
IDNotExist = errors.New("sha1 ID does not exist")
|
||||
)
|
||||
|
||||
type sha1 [20]byte
|
||||
|
||||
// Return true if s has the same sha1 as caller.
|
||||
// Support 40-length-string, []byte, sha1
|
||||
func (id sha1) Equal(s2 interface{}) bool {
|
||||
switch v := s2.(type) {
|
||||
case string:
|
||||
if len(v) != 40 {
|
||||
return false
|
||||
}
|
||||
return v == id.String()
|
||||
case []byte:
|
||||
if len(v) != 20 {
|
||||
return false
|
||||
}
|
||||
for i, v := range v {
|
||||
if id[i] != v {
|
||||
return false
|
||||
}
|
||||
}
|
||||
case sha1:
|
||||
for i, v := range v {
|
||||
if id[i] != v {
|
||||
return false
|
||||
}
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Return string (hex) representation of the Oid
|
||||
func (s sha1) String() string {
|
||||
result := make([]byte, 0, 40)
|
||||
hexvalues := []byte("0123456789abcdef")
|
||||
for i := 0; i < 20; i++ {
|
||||
result = append(result, hexvalues[s[i]>>4])
|
||||
result = append(result, hexvalues[s[i]&0xf])
|
||||
}
|
||||
return string(result)
|
||||
}
|
||||
|
||||
// Create a new sha1 from a 20 byte slice.
|
||||
func NewId(b []byte) (sha1, error) {
|
||||
var id sha1
|
||||
if len(b) != 20 {
|
||||
return id, errors.New("Length must be 20")
|
||||
}
|
||||
|
||||
for i := 0; i < 20; i++ {
|
||||
id[i] = b[i]
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// Create a new sha1 from a Sha1 string of length 40.
|
||||
func NewIdFromString(s string) (sha1, error) {
|
||||
s = strings.TrimSpace(s)
|
||||
var id sha1
|
||||
if len(s) != 40 {
|
||||
return id, fmt.Errorf("Length must be 40")
|
||||
}
|
||||
b, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
return id, err
|
||||
}
|
||||
|
||||
return NewId(b)
|
||||
}
|
||||
@@ -1,51 +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 git
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Author and Committer information
|
||||
type Signature struct {
|
||||
Email string
|
||||
Name string
|
||||
When time.Time
|
||||
}
|
||||
|
||||
// Helper to get a signature from the commit line, which looks like these:
|
||||
// author Patrick Gundlach <gundlach@speedata.de> 1378823654 +0200
|
||||
// author Patrick Gundlach <gundlach@speedata.de> Thu, 07 Apr 2005 22:13:13 +0200
|
||||
// but without the "author " at the beginning (this method should)
|
||||
// be used for author and committer.
|
||||
//
|
||||
// FIXME: include timezone for timestamp!
|
||||
func newSignatureFromCommitline(line []byte) (_ *Signature, err error) {
|
||||
sig := new(Signature)
|
||||
emailStart := bytes.IndexByte(line, '<')
|
||||
sig.Name = string(line[:emailStart-1])
|
||||
emailEnd := bytes.IndexByte(line, '>')
|
||||
sig.Email = string(line[emailStart+1 : emailEnd])
|
||||
|
||||
// Check date format.
|
||||
firstChar := line[emailEnd+2]
|
||||
if firstChar >= 48 && firstChar <= 57 {
|
||||
timestop := bytes.IndexByte(line[emailEnd+2:], ' ')
|
||||
timestring := string(line[emailEnd+2 : emailEnd+2+timestop])
|
||||
seconds, err := strconv.ParseInt(timestring, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sig.When = time.Unix(seconds, 0)
|
||||
} else {
|
||||
sig.When, err = time.Parse("Mon Jan _2 15:04:05 2006 -0700", string(line[emailEnd+2:]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return sig, nil
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
// 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 (
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func Test_newSignatureFromCommitline(t *testing.T) {
|
||||
Convey("Parse signature from commit line", t, func() {
|
||||
line := "Intern <intern@macbook-intern.(none)> 1445412825 +0200"
|
||||
sig, err := newSignatureFromCommitline([]byte(line))
|
||||
So(err, ShouldBeNil)
|
||||
So(sig, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
@@ -1,70 +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 git
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
type SubModule struct {
|
||||
Name string
|
||||
Url string
|
||||
}
|
||||
|
||||
// SubModuleFile represents a file with submodule type.
|
||||
type SubModuleFile struct {
|
||||
*Commit
|
||||
|
||||
refUrl string
|
||||
refId string
|
||||
}
|
||||
|
||||
func NewSubModuleFile(c *Commit, refUrl, refId string) *SubModuleFile {
|
||||
return &SubModuleFile{
|
||||
Commit: c,
|
||||
refUrl: refUrl,
|
||||
refId: refId,
|
||||
}
|
||||
}
|
||||
|
||||
// RefUrl guesses and returns reference URL.
|
||||
func (sf *SubModuleFile) RefUrl() string {
|
||||
if sf.refUrl == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
url := strings.TrimSuffix(sf.refUrl, ".git")
|
||||
|
||||
// git://xxx/user/repo
|
||||
if strings.HasPrefix(url, "git://") {
|
||||
return "http://" + strings.TrimPrefix(url, "git://")
|
||||
}
|
||||
|
||||
// http[s]://xxx/user/repo
|
||||
if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") {
|
||||
return url
|
||||
}
|
||||
|
||||
// sysuser@xxx:user/repo
|
||||
i := strings.Index(url, "@")
|
||||
j := strings.LastIndex(url, ":")
|
||||
if i > -1 && j > -1 {
|
||||
// fix problem with reverse proxy works only with local server
|
||||
if strings.Contains(setting.AppUrl, url[i+1:j]) {
|
||||
return setting.AppUrl + url[j+1:]
|
||||
} else {
|
||||
return "http://" + url[i+1:j] + "/" + url[j+1:]
|
||||
}
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
// RefId returns reference ID.
|
||||
func (sf *SubModuleFile) RefId() string {
|
||||
return sf.refId
|
||||
}
|
||||
@@ -1,67 +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 git
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
// Tag represents a Git tag.
|
||||
type Tag struct {
|
||||
Name string
|
||||
ID sha1
|
||||
repo *Repository
|
||||
Object sha1 // The id of this commit object
|
||||
Type string
|
||||
Tagger *Signature
|
||||
TagMessage string
|
||||
}
|
||||
|
||||
func (tag *Tag) Commit() (*Commit, error) {
|
||||
return tag.repo.getCommit(tag.Object)
|
||||
}
|
||||
|
||||
// Parse commit information from the (uncompressed) raw
|
||||
// data from the commit object.
|
||||
// \n\n separate headers from message
|
||||
func parseTagData(data []byte) (*Tag, error) {
|
||||
tag := new(Tag)
|
||||
// we now have the contents of the commit object. Let's investigate...
|
||||
nextline := 0
|
||||
l:
|
||||
for {
|
||||
eol := bytes.IndexByte(data[nextline:], '\n')
|
||||
switch {
|
||||
case eol > 0:
|
||||
line := data[nextline : nextline+eol]
|
||||
spacepos := bytes.IndexByte(line, ' ')
|
||||
reftype := line[:spacepos]
|
||||
switch string(reftype) {
|
||||
case "object":
|
||||
id, err := NewIdFromString(string(line[spacepos+1:]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tag.Object = id
|
||||
case "type":
|
||||
// A commit can have one or more parents
|
||||
tag.Type = string(line[spacepos+1:])
|
||||
case "tagger":
|
||||
sig, err := newSignatureFromCommitline(line[spacepos+1:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tag.Tagger = sig
|
||||
}
|
||||
nextline += eol + 1
|
||||
case eol == 0:
|
||||
tag.TagMessage = string(data[nextline+1:])
|
||||
break l
|
||||
default:
|
||||
break l
|
||||
}
|
||||
}
|
||||
return tag, nil
|
||||
}
|
||||
@@ -1,136 +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 git
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotExist = errors.New("error not exist")
|
||||
)
|
||||
|
||||
// A tree is a flat directory listing.
|
||||
type Tree struct {
|
||||
ID sha1
|
||||
repo *Repository
|
||||
|
||||
// parent tree
|
||||
ptree *Tree
|
||||
|
||||
entries Entries
|
||||
entriesParsed bool
|
||||
}
|
||||
|
||||
// Parse tree information from the (uncompressed) raw
|
||||
// data from the tree object.
|
||||
func parseTreeData(tree *Tree, data []byte) ([]*TreeEntry, error) {
|
||||
entries := make([]*TreeEntry, 0, 10)
|
||||
l := len(data)
|
||||
pos := 0
|
||||
for pos < l {
|
||||
entry := new(TreeEntry)
|
||||
entry.ptree = tree
|
||||
step := 6
|
||||
switch string(data[pos : pos+step]) {
|
||||
case "100644":
|
||||
entry.mode = ModeBlob
|
||||
entry.Type = BLOB
|
||||
case "100755":
|
||||
entry.mode = ModeExec
|
||||
entry.Type = BLOB
|
||||
case "120000":
|
||||
entry.mode = ModeSymlink
|
||||
entry.Type = BLOB
|
||||
case "160000":
|
||||
entry.mode = ModeCommit
|
||||
entry.Type = COMMIT
|
||||
|
||||
step = 8
|
||||
case "040000":
|
||||
entry.mode = ModeTree
|
||||
entry.Type = TREE
|
||||
default:
|
||||
return nil, errors.New("unknown type: " + string(data[pos:pos+step]))
|
||||
}
|
||||
pos += step + 6 // Skip string type of entry type.
|
||||
|
||||
step = 40
|
||||
id, err := NewIdFromString(string(data[pos : pos+step]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entry.ID = id
|
||||
pos += step + 1 // Skip half of sha1.
|
||||
|
||||
step = bytes.IndexByte(data[pos:], '\n')
|
||||
entry.name = string(data[pos : pos+step])
|
||||
|
||||
// In case entry name is surrounded by double quotes(it happens only in git-shell).
|
||||
if entry.name[0] == '"' {
|
||||
entry.name = string(data[pos+1 : pos+step-1])
|
||||
entry.name = strings.Replace(entry.name, `\"`, `"`, -1)
|
||||
}
|
||||
|
||||
pos += step + 1
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func (t *Tree) SubTree(rpath string) (*Tree, error) {
|
||||
if len(rpath) == 0 {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
paths := strings.Split(rpath, "/")
|
||||
var err error
|
||||
var g = t
|
||||
var p = t
|
||||
var te *TreeEntry
|
||||
for _, name := range paths {
|
||||
te, err = p.GetTreeEntryByPath(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
g, err = t.repo.getTree(te.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.ptree = p
|
||||
p = g
|
||||
}
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func (t *Tree) ListEntries(relpath string) (Entries, error) {
|
||||
if t.entriesParsed {
|
||||
return t.entries, nil
|
||||
}
|
||||
t.entriesParsed = true
|
||||
|
||||
stdout, stderr, err := com.ExecCmdDirBytes(t.repo.Path,
|
||||
"git", "ls-tree", t.ID.String())
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "exit status 128") {
|
||||
return nil, errors.New(strings.TrimSpace(string(stderr)))
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
t.entries, err = parseTreeData(t, stdout)
|
||||
return t.entries, err
|
||||
}
|
||||
|
||||
func NewTree(repo *Repository, id sha1) *Tree {
|
||||
tree := new(Tree)
|
||||
tree.ID = id
|
||||
tree.repo = repo
|
||||
return tree
|
||||
}
|
||||
@@ -1,59 +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 git
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
|
||||
if len(relpath) == 0 {
|
||||
return &TreeEntry{
|
||||
ID: t.ID,
|
||||
Type: TREE,
|
||||
mode: ModeTree,
|
||||
}, nil
|
||||
// return nil, fmt.Errorf("GetTreeEntryByPath(empty relpath): %v", ErrNotExist)
|
||||
}
|
||||
|
||||
relpath = path.Clean(relpath)
|
||||
parts := strings.Split(relpath, "/")
|
||||
var err error
|
||||
tree := t
|
||||
for i, name := range parts {
|
||||
if i == len(parts)-1 {
|
||||
entries, err := tree.ListEntries(path.Dir(relpath))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range entries {
|
||||
if v.name == name {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tree, err = tree.SubTree(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("GetTreeEntryByPath: %v", ErrNotExist)
|
||||
}
|
||||
|
||||
func (t *Tree) GetBlobByPath(rpath string) (*Blob, error) {
|
||||
entry, err := t.GetTreeEntryByPath(rpath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !entry.IsDir() {
|
||||
return entry.Blob(), nil
|
||||
}
|
||||
|
||||
return nil, ErrNotExist
|
||||
}
|
||||
@@ -1,113 +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 git
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
)
|
||||
|
||||
type EntryMode int
|
||||
|
||||
// There are only a few file modes in Git. They look like unix file modes, but they can only be
|
||||
// one of these.
|
||||
const (
|
||||
ModeBlob EntryMode = 0100644
|
||||
ModeExec EntryMode = 0100755
|
||||
ModeSymlink EntryMode = 0120000
|
||||
ModeCommit EntryMode = 0160000
|
||||
ModeTree EntryMode = 0040000
|
||||
)
|
||||
|
||||
type TreeEntry struct {
|
||||
ID sha1
|
||||
Type ObjectType
|
||||
|
||||
mode EntryMode
|
||||
name string
|
||||
|
||||
ptree *Tree
|
||||
|
||||
commited bool
|
||||
|
||||
size int64
|
||||
sized bool
|
||||
}
|
||||
|
||||
func (te *TreeEntry) Name() string {
|
||||
return te.name
|
||||
}
|
||||
|
||||
func (te *TreeEntry) Size() int64 {
|
||||
if te.IsDir() {
|
||||
return 0
|
||||
}
|
||||
|
||||
if te.sized {
|
||||
return te.size
|
||||
}
|
||||
|
||||
stdout, _, err := com.ExecCmdDir(te.ptree.repo.Path, "git", "cat-file", "-s", te.ID.String())
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
te.sized = true
|
||||
te.size = com.StrTo(strings.TrimSpace(stdout)).MustInt64()
|
||||
return te.size
|
||||
}
|
||||
|
||||
func (te *TreeEntry) IsSubModule() bool {
|
||||
return te.mode == ModeCommit
|
||||
}
|
||||
|
||||
func (te *TreeEntry) IsDir() bool {
|
||||
return te.mode == ModeTree
|
||||
}
|
||||
|
||||
func (te *TreeEntry) EntryMode() EntryMode {
|
||||
return te.mode
|
||||
}
|
||||
|
||||
func (te *TreeEntry) Blob() *Blob {
|
||||
return &Blob{
|
||||
repo: te.ptree.repo,
|
||||
TreeEntry: te,
|
||||
}
|
||||
}
|
||||
|
||||
type Entries []*TreeEntry
|
||||
|
||||
var sorter = []func(t1, t2 *TreeEntry) bool{
|
||||
func(t1, t2 *TreeEntry) bool {
|
||||
return (t1.IsDir() || t1.IsSubModule()) && !t2.IsDir() && !t2.IsSubModule()
|
||||
},
|
||||
func(t1, t2 *TreeEntry) bool {
|
||||
return t1.name < t2.name
|
||||
},
|
||||
}
|
||||
|
||||
func (bs Entries) Len() int { return len(bs) }
|
||||
func (bs Entries) Swap(i, j int) { bs[i], bs[j] = bs[j], bs[i] }
|
||||
func (bs Entries) Less(i, j int) bool {
|
||||
t1, t2 := bs[i], bs[j]
|
||||
var k int
|
||||
for k = 0; k < len(sorter)-1; k++ {
|
||||
sort := sorter[k]
|
||||
switch {
|
||||
case sort(t1, t2):
|
||||
return true
|
||||
case sort(t2, t1):
|
||||
return false
|
||||
}
|
||||
}
|
||||
return sorter[k](t1, t2)
|
||||
}
|
||||
|
||||
func (bs Entries) Sort() {
|
||||
sort.Sort(bs)
|
||||
}
|
||||
@@ -1,77 +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 git
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"container/list"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const prettyLogFormat = `--pretty=format:%H`
|
||||
|
||||
func parsePrettyFormatLog(repo *Repository, logByts []byte) (*list.List, error) {
|
||||
l := list.New()
|
||||
if len(logByts) == 0 {
|
||||
return l, nil
|
||||
}
|
||||
|
||||
parts := bytes.Split(logByts, []byte{'\n'})
|
||||
|
||||
for _, commitId := range parts {
|
||||
commit, err := repo.GetCommit(string(commitId))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l.PushBack(commit)
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func RefEndName(refStr string) string {
|
||||
index := strings.LastIndex(refStr, "/")
|
||||
if index != -1 {
|
||||
return refStr[index+1:]
|
||||
}
|
||||
return refStr
|
||||
}
|
||||
|
||||
// If the object is stored in its own file (i.e not in a pack file),
|
||||
// this function returns the full path to the object file.
|
||||
// It does not test if the file exists.
|
||||
func filepathFromSHA1(rootdir, sha1 string) string {
|
||||
return filepath.Join(rootdir, "objects", sha1[:2], sha1[2:])
|
||||
}
|
||||
|
||||
// isDir returns true if given path is a directory,
|
||||
// or returns false when it's a file or does not exist.
|
||||
func isDir(dir string) bool {
|
||||
f, e := os.Stat(dir)
|
||||
if e != nil {
|
||||
return false
|
||||
}
|
||||
return f.IsDir()
|
||||
}
|
||||
|
||||
// isFile returns true if given path is a file,
|
||||
// or returns false when it's a directory or does not exist.
|
||||
func isFile(filePath string) bool {
|
||||
f, e := os.Stat(filePath)
|
||||
if e != nil {
|
||||
return false
|
||||
}
|
||||
return !f.IsDir()
|
||||
}
|
||||
|
||||
func concatenateError(err error, stderr string) error {
|
||||
if len(stderr) == 0 {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("%v: %s", err, stderr)
|
||||
}
|
||||
@@ -1,104 +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 git
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
)
|
||||
|
||||
var (
|
||||
// Cached Git version.
|
||||
gitVer *Version
|
||||
)
|
||||
|
||||
// Version represents version of Git.
|
||||
type Version struct {
|
||||
Major, Minor, Patch int
|
||||
}
|
||||
|
||||
func ParseVersion(verStr string) (*Version, error) {
|
||||
infos := strings.Split(verStr, ".")
|
||||
if len(infos) < 3 {
|
||||
return nil, errors.New("incorrect version input")
|
||||
}
|
||||
|
||||
v := &Version{}
|
||||
for i, s := range infos {
|
||||
switch i {
|
||||
case 0:
|
||||
v.Major, _ = com.StrTo(s).Int()
|
||||
case 1:
|
||||
v.Minor, _ = com.StrTo(s).Int()
|
||||
case 2:
|
||||
v.Patch, _ = com.StrTo(strings.TrimSpace(s)).Int()
|
||||
}
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func MustParseVersion(verStr string) *Version {
|
||||
v, _ := ParseVersion(verStr)
|
||||
return v
|
||||
}
|
||||
|
||||
// Compare compares two versions,
|
||||
// it returns 1 if original is greater, -1 if original is smaller, 0 if equal.
|
||||
func (v *Version) Compare(that *Version) int {
|
||||
if v.Major > that.Major {
|
||||
return 1
|
||||
} else if v.Major < that.Major {
|
||||
return -1
|
||||
}
|
||||
|
||||
if v.Minor > that.Minor {
|
||||
return 1
|
||||
} else if v.Minor < that.Minor {
|
||||
return -1
|
||||
}
|
||||
|
||||
if v.Patch > that.Patch {
|
||||
return 1
|
||||
} else if v.Patch < that.Patch {
|
||||
return -1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (v *Version) LessThan(that *Version) bool {
|
||||
return v.Compare(that) < 0
|
||||
}
|
||||
|
||||
func (v *Version) AtLeast(that *Version) bool {
|
||||
return v.Compare(that) >= 0
|
||||
}
|
||||
|
||||
func (v *Version) String() string {
|
||||
return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
|
||||
}
|
||||
|
||||
// GetVersion returns current Git version installed.
|
||||
func GetVersion() (*Version, error) {
|
||||
if gitVer != nil {
|
||||
return gitVer, nil
|
||||
}
|
||||
|
||||
stdout, stderr, err := com.ExecCmd("git", "version")
|
||||
if err != nil {
|
||||
return nil, errors.New(stderr)
|
||||
}
|
||||
|
||||
infos := strings.Split(stdout, " ")
|
||||
if len(infos) < 3 {
|
||||
return nil, errors.New("not enough output")
|
||||
}
|
||||
|
||||
gitVer, err = ParseVersion(infos[2])
|
||||
return gitVer, err
|
||||
}
|
||||
@@ -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.
|
||||
@@ -1,33 +0,0 @@
|
||||
Basic LDAP v3 functionality for the GO programming language.
|
||||
|
||||
Required Librarys:
|
||||
github.com/johnweldon/asn1-ber
|
||||
|
||||
Working:
|
||||
Connecting to LDAP server
|
||||
Binding to LDAP server
|
||||
Searching for entries
|
||||
Compiling string filters to LDAP filters
|
||||
Paging Search Results
|
||||
Modify Requests / Responses
|
||||
|
||||
Examples:
|
||||
search
|
||||
modify
|
||||
|
||||
Tests Implemented:
|
||||
Filter Compile / Decompile
|
||||
|
||||
TODO:
|
||||
Add Requests / Responses
|
||||
Delete Requests / Responses
|
||||
Modify DN Requests / Responses
|
||||
Compare Requests / Responses
|
||||
Implement Tests / Benchmarks
|
||||
|
||||
This feature is disabled at the moment, because in some cases the "Search Request Done" packet will be handled before the last "Search Request Entry":
|
||||
Mulitple internal goroutines to handle network traffic
|
||||
Makes library goroutine safe
|
||||
Can perform multiple search requests at the same time and return
|
||||
the results to the proper goroutine. All requests are blocking
|
||||
requests, so the goroutine does not need special handling
|
||||
@@ -1,63 +0,0 @@
|
||||
dn: dc=enterprise,dc=org
|
||||
objectClass: dcObject
|
||||
objectClass: organization
|
||||
o: acme
|
||||
|
||||
dn: cn=admin,dc=enterprise,dc=org
|
||||
objectClass: person
|
||||
cn: admin
|
||||
sn: admin
|
||||
description: "LDAP Admin"
|
||||
|
||||
dn: ou=crew,dc=enterprise,dc=org
|
||||
ou: crew
|
||||
objectClass: organizationalUnit
|
||||
|
||||
|
||||
dn: cn=kirkj,ou=crew,dc=enterprise,dc=org
|
||||
cn: kirkj
|
||||
sn: Kirk
|
||||
gn: James Tiberius
|
||||
mail: james.kirk@enterprise.org
|
||||
objectClass: inetOrgPerson
|
||||
|
||||
dn: cn=spock,ou=crew,dc=enterprise,dc=org
|
||||
cn: spock
|
||||
sn: Spock
|
||||
mail: spock@enterprise.org
|
||||
objectClass: inetOrgPerson
|
||||
|
||||
dn: cn=mccoyl,ou=crew,dc=enterprise,dc=org
|
||||
cn: mccoyl
|
||||
sn: McCoy
|
||||
gn: Leonard
|
||||
mail: leonard.mccoy@enterprise.org
|
||||
objectClass: inetOrgPerson
|
||||
|
||||
dn: cn=scottm,ou=crew,dc=enterprise,dc=org
|
||||
cn: scottm
|
||||
sn: Scott
|
||||
gn: Montgomery
|
||||
mail: Montgomery.scott@enterprise.org
|
||||
objectClass: inetOrgPerson
|
||||
|
||||
dn: cn=uhuran,ou=crew,dc=enterprise,dc=org
|
||||
cn: uhuran
|
||||
sn: Uhura
|
||||
gn: Nyota
|
||||
mail: nyota.uhura@enterprise.org
|
||||
objectClass: inetOrgPerson
|
||||
|
||||
dn: cn=suluh,ou=crew,dc=enterprise,dc=org
|
||||
cn: suluh
|
||||
sn: Sulu
|
||||
gn: Hikaru
|
||||
mail: hikaru.sulu@enterprise.org
|
||||
objectClass: inetOrgPerson
|
||||
|
||||
dn: cn=chekovp,ou=crew,dc=enterprise,dc=org
|
||||
cn: chekovp
|
||||
sn: Chekov
|
||||
gn: pavel
|
||||
mail: pavel.chekov@enterprise.org
|
||||
objectClass: inetOrgPerson
|
||||
@@ -1,89 +0,0 @@
|
||||
// Copyright 2014 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/gogits/gogs/modules/ldap"
|
||||
)
|
||||
|
||||
var (
|
||||
LdapServer string = "localhost"
|
||||
LdapPort uint16 = 389
|
||||
BaseDN string = "dc=enterprise,dc=org"
|
||||
BindDN string = "cn=admin,dc=enterprise,dc=org"
|
||||
BindPW string = "enterprise"
|
||||
Filter string = "(cn=kirkj)"
|
||||
)
|
||||
|
||||
func search(l *ldap.Conn, filter string, attributes []string) (*ldap.Entry, *ldap.Error) {
|
||||
search := ldap.NewSearchRequest(
|
||||
BaseDN,
|
||||
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||
filter,
|
||||
attributes,
|
||||
nil)
|
||||
|
||||
sr, err := l.Search(search)
|
||||
if err != nil {
|
||||
log.Fatalf("ERROR: %s\n", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("Search: %s -> num of entries = %d\n", search.Filter, len(sr.Entries))
|
||||
if len(sr.Entries) == 0 {
|
||||
return nil, ldap.NewError(ldap.ErrorDebugging, errors.New(fmt.Sprintf("no entries found for: %s", filter)))
|
||||
}
|
||||
return sr.Entries[0], nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", LdapServer, LdapPort))
|
||||
if err != nil {
|
||||
log.Fatalf("ERROR: %s\n", err.Error())
|
||||
}
|
||||
defer l.Close()
|
||||
// l.Debug = true
|
||||
|
||||
l.Bind(BindDN, BindPW)
|
||||
|
||||
log.Printf("The Search for Kirk ... %s\n", Filter)
|
||||
entry, err := search(l, Filter, []string{})
|
||||
if err != nil {
|
||||
log.Fatal("could not get entry")
|
||||
}
|
||||
entry.PrettyPrint(0)
|
||||
|
||||
log.Printf("modify the mail address and add a description ... \n")
|
||||
modify := ldap.NewModifyRequest(entry.DN)
|
||||
modify.Add("description", []string{"Captain of the USS Enterprise"})
|
||||
modify.Replace("mail", []string{"captain@enterprise.org"})
|
||||
if err := l.Modify(modify); err != nil {
|
||||
log.Fatalf("ERROR: %s\n", err.Error())
|
||||
}
|
||||
|
||||
entry, err = search(l, Filter, []string{})
|
||||
if err != nil {
|
||||
log.Fatal("could not get entry")
|
||||
}
|
||||
entry.PrettyPrint(0)
|
||||
|
||||
log.Printf("reset the entry ... \n")
|
||||
modify = ldap.NewModifyRequest(entry.DN)
|
||||
modify.Delete("description", []string{})
|
||||
modify.Replace("mail", []string{"james.kirk@enterprise.org"})
|
||||
if err := l.Modify(modify); err != nil {
|
||||
log.Fatalf("ERROR: %s\n", err.Error())
|
||||
}
|
||||
|
||||
entry, err = search(l, Filter, []string{})
|
||||
if err != nil {
|
||||
log.Fatal("could not get entry")
|
||||
}
|
||||
entry.PrettyPrint(0)
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
// Copyright 2014 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/gogits/gogs/modules/ldap"
|
||||
)
|
||||
|
||||
var (
|
||||
ldapServer string = "adserver"
|
||||
ldapPort uint16 = 3268
|
||||
baseDN string = "dc=*,dc=*"
|
||||
filter string = "(&(objectClass=user)(sAMAccountName=*)(memberOf=CN=*,OU=*,DC=*,DC=*))"
|
||||
Attributes []string = []string{"memberof"}
|
||||
user string = "*"
|
||||
passwd string = "*"
|
||||
)
|
||||
|
||||
func main() {
|
||||
l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
|
||||
if err != nil {
|
||||
log.Fatalf("ERROR: %s\n", err.Error())
|
||||
}
|
||||
defer l.Close()
|
||||
// l.Debug = true
|
||||
|
||||
err = l.Bind(user, passwd)
|
||||
if err != nil {
|
||||
log.Printf("ERROR: Cannot bind: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
search := ldap.NewSearchRequest(
|
||||
baseDN,
|
||||
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||
filter,
|
||||
Attributes,
|
||||
nil)
|
||||
|
||||
sr, err := l.Search(search)
|
||||
if err != nil {
|
||||
log.Fatalf("ERROR: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Search: %s -> num of entries = %d\n", search.Filter, len(sr.Entries))
|
||||
sr.PrettyPrint(0)
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
// Copyright 2014 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/gogits/gogs/modules/ldap"
|
||||
)
|
||||
|
||||
var (
|
||||
LdapServer string = "localhost"
|
||||
LdapPort uint16 = 636
|
||||
BaseDN string = "dc=enterprise,dc=org"
|
||||
Filter string = "(cn=kirkj)"
|
||||
Attributes []string = []string{"mail"}
|
||||
)
|
||||
|
||||
func main() {
|
||||
l, err := ldap.DialSSL("tcp", fmt.Sprintf("%s:%d", LdapServer, LdapPort), nil)
|
||||
if err != nil {
|
||||
log.Fatalf("ERROR: %s\n", err.String())
|
||||
}
|
||||
defer l.Close()
|
||||
// l.Debug = true
|
||||
|
||||
search := ldap.NewSearchRequest(
|
||||
BaseDN,
|
||||
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||
Filter,
|
||||
Attributes,
|
||||
nil)
|
||||
|
||||
sr, err := l.Search(search)
|
||||
if err != nil {
|
||||
log.Fatalf("ERROR: %s\n", err.String())
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Search: %s -> num of entries = %d\n", search.Filter, len(sr.Entries))
|
||||
sr.PrettyPrint(0)
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
// Copyright 2014 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/gogits/gogs/modules/ldap"
|
||||
)
|
||||
|
||||
var (
|
||||
LdapServer string = "localhost"
|
||||
LdapPort uint16 = 389
|
||||
BaseDN string = "dc=enterprise,dc=org"
|
||||
Filter string = "(cn=kirkj)"
|
||||
Attributes []string = []string{"mail"}
|
||||
)
|
||||
|
||||
func main() {
|
||||
l, err := ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", LdapServer, LdapPort), nil)
|
||||
if err != nil {
|
||||
log.Fatalf("ERROR: %s\n", err.Error())
|
||||
}
|
||||
defer l.Close()
|
||||
// l.Debug = true
|
||||
|
||||
search := ldap.NewSearchRequest(
|
||||
BaseDN,
|
||||
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||
Filter,
|
||||
Attributes,
|
||||
nil)
|
||||
|
||||
sr, err := l.Search(search)
|
||||
if err != nil {
|
||||
log.Fatalf("ERROR: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Search: %s -> num of entries = %d\n", search.Filter, len(sr.Entries))
|
||||
sr.PrettyPrint(0)
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
#
|
||||
# See slapd.conf(5) for details on configuration options.
|
||||
# This file should NOT be world readable.
|
||||
#
|
||||
include /private/etc/openldap/schema/core.schema
|
||||
include /private/etc/openldap/schema/cosine.schema
|
||||
include /private/etc/openldap/schema/inetorgperson.schema
|
||||
|
||||
# Define global ACLs to disable default read access.
|
||||
|
||||
# Do not enable referrals until AFTER you have a working directory
|
||||
# service AND an understanding of referrals.
|
||||
#referral ldap://root.openldap.org
|
||||
|
||||
pidfile /private/var/db/openldap/run/slapd.pid
|
||||
argsfile /private/var/db/openldap/run/slapd.args
|
||||
|
||||
# Load dynamic backend modules:
|
||||
# modulepath /usr/libexec/openldap
|
||||
# moduleload back_bdb.la
|
||||
# moduleload back_hdb.la
|
||||
# moduleload back_ldap.la
|
||||
|
||||
# Sample security restrictions
|
||||
# Require integrity protection (prevent hijacking)
|
||||
# Require 112-bit (3DES or better) encryption for updates
|
||||
# Require 63-bit encryption for simple bind
|
||||
# security ssf=1 update_ssf=112 simple_bind=64
|
||||
|
||||
# Sample access control policy:
|
||||
# Root DSE: allow anyone to read it
|
||||
# Subschema (sub)entry DSE: allow anyone to read it
|
||||
# Other DSEs:
|
||||
# Allow self write access
|
||||
# Allow authenticated users read access
|
||||
# Allow anonymous users to authenticate
|
||||
# Directives needed to implement policy:
|
||||
# access to dn.base="" by * read
|
||||
# access to dn.base="cn=Subschema" by * read
|
||||
# access to *
|
||||
# by self write
|
||||
# by users read
|
||||
# by anonymous auth
|
||||
#
|
||||
# if no access controls are present, the default policy
|
||||
# allows anyone and everyone to read anything but restricts
|
||||
# updates to rootdn. (e.g., "access to * by * read")
|
||||
#
|
||||
# rootdn can always read and write EVERYTHING!
|
||||
|
||||
#######################################################################
|
||||
# BDB database definitions
|
||||
#######################################################################
|
||||
|
||||
database bdb
|
||||
suffix "dc=enterprise,dc=org"
|
||||
rootdn "cn=admin,dc=enterprise,dc=org"
|
||||
# Cleartext passwords, especially for the rootdn, should
|
||||
# be avoid. See slappasswd(8) and slapd.conf(5) for details.
|
||||
# Use of strong authentication encouraged.
|
||||
rootpw {SSHA}laO00HsgszhK1O0Z5qR0/i/US69Osfeu
|
||||
# The database directory MUST exist prior to running slapd AND
|
||||
# should only be accessible by the slapd and slap tools.
|
||||
# Mode 700 recommended.
|
||||
directory /private/var/db/openldap/openldap-data
|
||||
# Indices to maintain
|
||||
index objectClass eq
|
||||
@@ -1,55 +0,0 @@
|
||||
// Copyright 2011 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.
|
||||
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/gogits/gogs/modules/asn1-ber"
|
||||
)
|
||||
|
||||
func (l *Conn) Bind(username, password string) error {
|
||||
messageID := l.nextMessageID()
|
||||
|
||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
|
||||
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID"))
|
||||
bindRequest := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
|
||||
bindRequest.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
|
||||
bindRequest.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, username, "User Name"))
|
||||
bindRequest.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, password, "Password"))
|
||||
packet.AppendChild(bindRequest)
|
||||
|
||||
if l.Debug {
|
||||
ber.PrintPacket(packet)
|
||||
}
|
||||
|
||||
channel, err := l.sendMessage(packet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if channel == nil {
|
||||
return NewError(ErrorNetwork, errors.New("ldap: could not send message"))
|
||||
}
|
||||
defer l.finishMessage(messageID)
|
||||
|
||||
packet = <-channel
|
||||
if packet == nil {
|
||||
return NewError(ErrorNetwork, errors.New("ldap: could not retrieve response"))
|
||||
}
|
||||
|
||||
if l.Debug {
|
||||
if err := addLDAPDescriptions(packet); err != nil {
|
||||
return err
|
||||
}
|
||||
ber.PrintPacket(packet)
|
||||
}
|
||||
|
||||
resultCode, resultDescription := getLDAPResultCode(packet)
|
||||
if resultCode != 0 {
|
||||
return NewError(resultCode, errors.New(resultDescription))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user