Initial Contribution

This commit is contained in:
The Android Open Source Project
2008-10-21 07:00:00 -07:00
commit 38966837f9
1168 changed files with 287603 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
/.java_bin
/.java_src
/.jgit_version
/.protobuf_version
/config.mak
/release
/test

202
COPYING Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

90
DEPLOY_GAE Normal file
View File

@@ -0,0 +1,90 @@
Deploying Gerrit to Google App Engine
=====================================
To deploy Gerrit to a new (or an upgrade for an existing) Google
App Engine instance:
----
make update APPID=your-application-id ADMIN=your@email
----
where "your-application-id" is the unique application id you chose
for the application instance at the time you registered it with
Google App Engine;
and where "your@email" is a Google Account username that is
registered as a developer/administrator of the Google App Engine
instance identified by $APPID.
Saving Configuration
--------------------
If you are frequently deploying via the same user account or to the
same instance you may want to save your configuration options into
a local config.mak file:
----
$ cat config.mak
ADMIN = your@email
APPID = your-application
----
as these parameters can then be left off of the make update
command line:
----
make update
----
Admin Only Lockout
------------------
When updating a Google App Engine instance Gerrit can be configured
to only permit admin logins. This may be useful when setting up
a testing/development environment, where external users are not
desired yet.
Simply set ADMIN_ONLY=1 when running the build:
----
make update ADMIN_ONLY=1 [other options...]
----
You may wish to have this configured in your config.mak file:
----
$ cat config.mak
ifeq ($(APPID),your-private-id)
ADMIN_ONLY=1
endif
----
This way the lockout is automatically enabled when you run a build.
To disable the lockout, run "make update" without ADMIN_ONLY set
(e.g. delete or comment it out from your config.mak file).
Special Options
---------------
When deploying to an internal Google App Engine instance you may
need to set APPCFG_OPTS to point to the internal admin console.
This is best configured in your config.mak file:
----
$ cat config.mak
ADMIN = you@google.com
APPID = your-application
ifeq ($(APPID),internal-app-id)
APPCFG_OPTS = -s admin-console.some.internal.host.name
endif
----
As a Googler you will need to search the intranet to locate the
correct hostname for the internal admin-console, or ask someone who
is involved with Gerrit or the Google App Engine team for the name.

182
DEPLOY_MGR Normal file
View File

@@ -0,0 +1,182 @@
Deploying Gerrit's Manager Backend
==================================
The manager backend is a Java process that performs batch operations
on local Git repositories, based on the metadata recorded into the
Gerrit instance running on Google App Engine.
To deploy the backend:
----
$ make release-mgr
$ scp -r release/mgr you@some.server:mgr
---
Running
-------
To start the backend server process:
----
$ ./bin/mgr mgr.config &
----
where mgr.config is the backend's configuration file (see below
for details on its format and options).
To stop the backend, send it a SIGINT:
----
kill -INT $mgrpid
----
Creating Gerrit Projects and Branches
-------------------------------------
To create new projects/branches in Gerrit to match those available
locally in Git run the mgr in the foreground with its sync option:
----
$ ./bin/mgr mgr.config sync
----
The sync subcommand will create a new Gerrit project for any new
Git repository found under codereview.basedir. It also creates
a new branch in Gerrit for any branch in any new or existing Git
repository.
Manager Configuration
---------------------
The manager backend requires a configuration file to designate
which Git repositories it will have access to, and which Gerrit
instance it communicates with.
The configuration file is in `git config` format and can thus be
automatically read/updated via `git config --file=<name>`. It can
also be hand-modified. For more details on the file format, see
`man git-config`.
----
$ cat mgr.config
[user]
name = Gerrit Code Review
email = gerrit@localhost
[codereview]
server = http://androidreview.appspot.com/
basedir = /pub/scm/git
username = role-account@gmail.com
secureconfig = .secure-mgr-config
sleep = 60
[log]
file = mgr.log
level = debug
----
A description of the major options follows:
user.name::
Author/committer name Gerrit records when creating an
automatic merge commit.
user.email::
Author/committer email Gerrit records when creating an
automatic merge commit.
codereview.server::
URL of the Gerrit Google App Engine instance providing
database support and web user interface to the code review
system. Presently only HTTP is supported. Future releases
of Gerrit may support HTTPS, if/when Google App Engine ever
supports HTTPS.
codereview.basedir::
Root directory that all Git repositories are relative to.
Repositories in this directory should be bare repositories.
The name of a project in Gerrit is appended to this directory
to determine the path of the corresponding Git repository
where change branches are stored and merges take place.
codereview.username::
(Optional) Google Account username to authenticate to
codereview.server with. This option is only required if the
Gerrit instance has been set to ADMIN_ONLY=1. (Normally the
manager backend uses a more secure authentication strategy.)
If set, make sure codereview.password is also in the secure
configuration file.
codereview.secureconfig::
Path to the secure configuration file (see below).
A relative path is evaluated relative to the main
configuration file.
codereview.sleep::
Number of seconds between queries when Gerrit says there
is nothing waiting to be processed. This is the maximum
latency between when an upload or merge request is first
stored in the data store and when the backend can process it.
codereview.memory::
Maximum amount of Java heap memory to give to the manager
process. Valid values are in bytes, with standard "m" and
"g" suffixes for "megabytes" and "gigabytes". E.g. "256m"
would assign up to 256 megabytes of memory to the manager.
log.file::
Path to the log file the server writes messages to. If not
set messages are sent to stdout.
log.level::
Set the verbosity of the logging. Supported values are:
fatal, error, warning, info, debug, trace. Later levels
include all messages from all earlier levels.
Secure Configuration File
-------------------------
The secure configuration file stores additional details which
should never be made public. This file should be protected with
filesystem level access controls, e.g. "chmod 0600" on UNIX prior
to writing any information into it.
----
$ cat .secure-mgr-config
[codereview]
password = g3rr1t1sc001
internalapikey = somebiglongkeystring
----
codereview.password::
(Optional) Password matching the Google Account listed
in codereview.username in the main configuration file.
This value is only required if the Gerrit instance is in
ADMIN_ONLY=1 mode and thus needs the backend to authenticate
with a Google Account.
codereview.internalapikey::
The private key used for HMAC authentication between the
manager backend and the Gerrit instance running on Google
App Engine. Any application developer/administrator can
get this key from http://APPID.appspot.com/admin/settings
Parallel Operation Concerns
---------------------------
The backend uses aggressive caching, and is not yet fully safe to
run concurrently with command line Git operations such as `git gc`,
`git repack` or `git pack-refs`.
Until the backend has been fully patched to support concurrent
operations, do not run Git operations in repositories under
codereview.basedir while a backend is running on that directory.
In order to repack safely, stop the backend, run the repack activity,
then start it back up again.

31
GIT-VERSION-GEN Executable file
View File

@@ -0,0 +1,31 @@
#!/bin/sh
#
# Copyright 2008 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
VN=$(git describe --abbrev=8 HEAD 2>/dev/null)
case "$VN" in
v[0-9]*) : happy ;;
*) exit 1 ;;
esac
dirty=$(sh -c 'git diff-index --name-only HEAD' 2>/dev/null) || dirty=
case "$dirty" in
'')
;;
*)
VN="$VN-dirty" ;;
esac
echo $VN

401
Makefile Normal file
View File

@@ -0,0 +1,401 @@
# Gerrit
#
# Copyright 2008 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#
# Define DATASTORE to the location where 'make serve' should store its
# runtime data files; by default this is /tmp/dev_appserver.datastore.
#
# Define REMOTE=1 to enable remote hosts to connect to the development
# web server started by 'make serve'. This may be a security risk.
#
# Define EMAIL=1 to enable sending email messages during 'make serve'.
# This may spam invalid addresses, so it is off by default.
#
# Define APPID to the unique Google App Engine application instance
# 'make update' will upload the application files to.
#
# Define SDK to the location of the Google App Engine SDK download.
#
# Define PYSDK to the location of a 'python2.5' interpreter.
#
# Define ADMIN_ONLY to lock the release application build to only
# its Google App Engine administrative accounts.
#
# Define ADMIN to the email address of an admin account that can
# run appcfg.py to update the code. This parameter is only used
# by the administrative targets.
#
ifeq ($(shell uname),Darwin)
SDK = /usr/local/bin
else
SDK = $(HOME)/google_appengine
endif
APPID = gerrit-code-review-tool-demo
APPNAME_HTML = <i>Gerrit</i> Code Review Tool
ADMIN = set.ADMIN@read.the.docs
PYSDK = python2.5
JAVAC = javac -target 1.5
CPIO = cpio -pd
DEV_APPSERVER = $(PYSDK) $(SDK)/dev_appserver.py
APPCFG = $(PYSDK) $(SDK)/appcfg.py $(APPCFG_OPTS) -e '$(ADMIN)'
-include config.mak
ifeq ($(APPID),android-codereview)
APPNAME_HTML = Android Code Review
endif
protobuf := ../protobuf
PROTOC := $(abspath $(protobuf)/src/protoc)
jgit := ../jgit
MGR_APP := mgrapp
WEB_APP := webapp
WEB_ARG :=
ifdef DATASTORE
WEB_ARG += --datastore_path=$(DATASTORE)
endif
ifeq (1,$(REMOTE))
WEB_ARG += --address 0.0.0.0
endif
ifeq (1,$(EMAIL))
WEB_ARG += --enable_sendmail
endif
R_WEB := release/web
R_MGR := release/mgr
R_PYCLIENT := release/pyclient
PYZIP_IGNORE := $(strip \
.gitignore \
.gitattributes \
\*.pyc \
\*.pyo \
)
django_IGNORE := $(strip \
.svn \
gis \
admin \
localflavor \
mysql \
mysql_old \
oracle \
postgresql \
postgresql_psycopg2 \
sqlite3 \
test \
\*.po \
\*.mo \
)
WEB_INCLUDE := $(strip \
__init__.py \
app.yaml \
index.yaml \
main.py \
settings.py \
static \
templates \
)
WEB_HASH := $(strip $(patsubst $(WEB_APP)/%,%,\
$(wildcard $(WEB_APP)/static/*.js) \
$(wildcard $(WEB_APP)/static/*.css) \
))
WEB_PYZIP := $(strip \
codereview \
django \
froofle \
)
PYCLIENT_INCLUDE := $(strip \
froofle \
codereview/__init__.py \
codereview/proto_client.py \
codereview/*_pb2.py \
)
PROTO_SRC := proto
PUBLIC_PROTO := $(wildcard $(PROTO_SRC)/*.proto)
INTERNAL_PROTO := $(wildcard $(PROTO_SRC)/internal/*.proto)
jgit_jar := $(MGR_APP)/lib/jgit.jar
jsch_jar := $(MGR_APP)/lib/jsch.jar
protobuf_jar := $(MGR_APP)/lib/protobuf.jar
protobuf_jar_src := $(protobuf)/java/src/main/java
codereview_proto_jar := $(MGR_APP)/lib/codereview_proto.jar
codereview_manager_jar := $(MGR_APP)/lib/codereview_manager.jar
ALL_PROTO := $(strip \
$(PUBLIC_PROTO) \
$(INTERNAL_PROTO) \
)
MGR_LIB := $(strip \
$(jsch_jar) \
$(jgit_jar) \
$(wildcard $(MGR_APP)/lib/*.jar) \
$(protobuf_jar) \
$(codereview_proto_jar) \
)
MGR_LIB := $(sort $(MGR_LIB))
MGR_LIB := $(filter-out $(codereview_manager_jar),$(MGR_LIB))
GEN_JAR := $(strip \
$(jsch_jar) \
$(jgit_jar) \
$(protobuf_jar) \
$(codereview_proto_jar) \
$(codereview_manager_jar) \
\
$(basename $(jsch_jar))_src.zip \
$(basename $(jgit_jar))_src.zip \
)
PROTO_PY := $(patsubst $(PROTO_SRC)/%,$(WEB_APP)/codereview/%,\
$(patsubst %.proto,%_pb2.py,$(ALL_PROTO))) \
$(WEB_APP)/froofle/__init__.py
## Top level targets
##
all: web mgr
release: release-web release-mgr release-pyclient
clean:
@rm -rf release .java_src .java_bin .proto_out
@rm -f .jgit_version .protobuf_version
@rm -f $(protobuf_jar_src)/com/google/protobuf/DescriptorProtos.java
@rm -f $(GEN_JAR)
@rm -rf $(WEB_APP)/froofle $(WEB_APP)/froofle+
@find $(WEB_APP)/codereview -name '*.pyc' | xargs rm -f
@find $(WEB_APP)/codereview -name '*_pb2.py' | xargs rm -f
## Web application
##
web: $(PROTO_PY)
serve: web
$(DEV_APPSERVER) $(WEB_ARG) $(WEB_APP)
release-web: web
@echo Building Gerrit `./GIT-VERSION-GEN` for $(APPID):
@rm -rf $(R_WEB)
@mkdir -p $(R_WEB)
@cd $(WEB_APP) && \
$(foreach a,$(WEB_PYZIP),\
echo " Packing $a.zip" && \
find $a \
$(foreach i,$(PYZIP_IGNORE), -name $i -prune -o) \
$(foreach i,$(value $(a)_IGNORE), -name $i -prune -o) \
-type f -print | \
zip -q9 $(abspath $(R_WEB)/$a.zip) -@ &&) \
echo " Copying loose files" && \
find $(WEB_INCLUDE) \
$(foreach i,$(WEB_HASH), -path $i -prune -o) \
-type f -print | $(CPIO) $(abspath $(R_WEB)) && \
echo " Hashing loose files" && \
$(foreach i,$(WEB_HASH),\
h=$$(openssl sha1 <$i) && \
d=$(basename $i)-$$h$(suffix $i) && \
echo " $$d" && \
cp $i $(abspath $(R_WEB))/$$d && \
find $(abspath $(R_WEB)/templates) -type f \
| xargs perl -pi -e "s,$i,$$d,g" && ) \
true
@./GIT-VERSION-GEN >$(R_WEB)/static/application_version
@echo "This is Gerrit `./GIT-VERSION-GEN`" >$(R_WEB)/templates/live_revision.html
@perl -pi -e 's{PUBLIC APPLICATION NAME}{$(APPNAME_HTML)}' $(R_WEB)/templates/base.html
@perl -pi -e 's/(application:).*/$$1 $(APPID)/' $(R_WEB)/app.yaml
ifdef ADMIN_ONLY
@echo '*** SETTING ADMIN_ONLY ***'
@perl -pi -e 's/^(.*)(script:.*main.py)/$$1login: admin\n$$1$$2/' $(R_WEB)/app.yaml
endif
@echo $(R_WEB) built for $(APPID).
update: release-web
$(APPCFG) update $(R_WEB)
version:
@printf '%s = ' '$(APPID)'
@curl http://$(APPID).appspot.com/application_version
## Python RPC client
##
release-pyclient: $(PROTO_PY)
@echo Building Python RPC client $(R_PYCLIENT)
@rm -rf $(R_PYCLIENT)
@mkdir -p $(R_PYCLIENT)
@cd $(WEB_APP) && \
find $(PYCLIENT_INCLUDE) \
$(foreach i,$(PYZIP_IGNORE), -name $i -prune -o) \
-type f -print \
| $(CPIO) $(abspath $(R_PYCLIENT))
@echo "__version__ = '`./GIT-VERSION-GEN`'" >>$(R_PYCLIENT)/codereview/__init__.py
## Manager backend
##
mgr: $(codereview_manager_jar)
release-mgr: mgr
@rm -rf $(R_MGR)
@mkdir -p $(R_MGR)
@cd $(MGR_APP) && \
find bin \
$(foreach i,.gitignore .gitattributes, -name $i -prune -o) \
-type f -print \
| $(CPIO) $(abspath $(R_MGR)) && \
find lib -name \*.jar -type f -print \
| $(CPIO) $(abspath $(R_MGR))
@echo $(R_MGR) built for `./GIT-VERSION-GEN`
## Basic productions
##
$(PROTOC): .protobuf_version $(protobuf)/Makefile
$(MAKE) -C $(protobuf)
@touch $(PROTOC)
$(protobuf)/configure: $(protobuf)/configure.ac .protobuf_version
cd $(protobuf) && ./autogen.sh
$(protobuf)/Makefile: $(protobuf)/configure .protobuf_version
cd $(protobuf) && ./configure
$(WEB_APP)/froofle/__init__.py: .protobuf_version $(PROTOC)
@echo Updating $(WEB_APP)/froofle ...
@rm -rf $(WEB_APP)/froofle $(WEB_APP)/froofle+
@mkdir $(WEB_APP)/froofle+
@cd $(protobuf)/python/google && \
find . \
$(foreach i,test_util.py \*_test.py, -name $i -prune -o) \
-type f -name \*.py -print \
| $(CPIO) $(abspath $(WEB_APP)/froofle+)
@cd $(protobuf)/python && \
t=$(abspath .proto_out) && \
rm -rf $$t && \
mkdir -p $$t/google/protobuf && \
$(PROTOC) -I../src -I. \
--python_out=$$t \
../src/google/protobuf/descriptor.proto
@cp .proto_out/google/protobuf/descriptor_pb2.py \
$(WEB_APP)/froofle+/protobuf
@rm -rf .proto_out
@find $(WEB_APP)/froofle+ -name __init__.py | xargs perl -ni -e ''
@find $(WEB_APP)/froofle+ -name '*.py' | xargs chmod a-x
@find $(WEB_APP)/froofle+ -name '*.py' \
| xargs perl -pi -e 's/google(?!\.com)/froofle/g'
@mv $(WEB_APP)/froofle+ $(WEB_APP)/froofle
$(WEB_APP)/codereview/%_pb2.py: $(PROTO_SRC)/%.proto $(PROTOC)
@echo protoc $< && \
$(PROTOC) --python_out=$(dir $@) --proto_path=$(dir $<) $< && \
perl -pi -e 's/google(?!\.com)/froofle/g' $@
$(codereview_manager_jar): \
$(MGR_LIB) \
$(shell find $(MGR_APP)/src -type f)
@echo build $@
@rm -rf .java_bin && \
b=$(abspath .java_bin) && \
cd $(MGR_APP)/src && \
mkdir $$b && \
mkdir $$b/META-INF && \
find META-INF/MANIFEST.MF | $(CPIO) $$b && \
CLASSPATH=$$b && \
$(foreach c,$(MGR_LIB),CLASSPATH=$$CLASSPATH:$(abspath $c) &&) \
export CLASSPATH && \
find . -name \*.java | xargs $(JAVAC) \
-encoding UTF-8 \
-g \
-d $$b
@cd .java_bin && jar cf $(abspath $@) .
@rm -rf .java_bin
$(codereview_proto_jar): $(ALL_PROTO) $(protobuf_jar) $(PROTOC)
@echo build $@
@rm -rf .java_src .java_bin && \
mkdir .java_src .java_bin && \
$(foreach p,$(ALL_PROTO),\
$(PROTOC) --java_out=.java_src --proto_path=$(dir $p) $p &&) \
unset CLASSPATH && \
cd .java_src && \
find . -name '*.java' | xargs $(JAVAC) \
-encoding UTF-8 \
-g:none \
-nowarn \
-classpath $(abspath $(protobuf_jar)) \
-d ../.java_bin && \
cd ../.java_bin && jar cf $(abspath $@) .
@rm -rf .java_src .java_bin
$(protobuf_jar): \
$(protobuf)/src/google/protobuf/descriptor.proto \
$(shell find $(protobuf_jar_src) -type f)
@echo build $@
@rm -rf .java_bin && \
b=$(abspath .java_bin) && \
cd $(protobuf_jar_src) && \
mkdir $$b && \
CLASSPATH=$$b && \
export CLASSPATH && \
$(PROTOC) \
--java_out=. \
-I$(abspath $(protobuf)/src) \
$(abspath $(protobuf)/src/google/protobuf/descriptor.proto) && \
find . -name \*.java | xargs $(JAVAC) \
-encoding UTF-8 \
-g \
-d $$b
@cd .java_bin && jar cf $(abspath $@) .
@rm -f $(protobuf_jar_src)/com/google/protobuf/DescriptorProtos.java
@rm -rf .java_bin
$(jsch_jar): $(jgit)/org.spearce.jgit/lib/jsch-0.1.37.jar
rm -f $@ $(basename $@)_src.zip
cp $< $@
cp $(basename $<).zip $(basename $@)_src.zip
$(jgit_jar): .jgit_version
rm -f $@ $(basename $@)_src.zip
cd $(jgit) && $(SHELL) ./make_jgit.sh
cp $(jgit)/jgit.jar $@
chmod 644 $@
cp $(jgit)/jgit_src.zip $(basename $@)_src.zip
.jgit_version: jgit_phony
@a=`git --git-dir=$(jgit)/.git rev-parse HEAD 2>/dev/null`; \
b=`cat .jgit_version 2>/dev/null`; \
if test z$$a = z$$b; then : up to date; \
else echo $$a >$@; fi
.protobuf_version: protobuf_phony
@a=`git --git-dir=$(protobuf)/.git rev-parse HEAD 2>/dev/null`; \
b=`cat .protobuf_version 2>/dev/null`; \
if test z$$a = z$$b; then : up to date; \
else echo $$a >$@; fi
.PHONY: all clean release
.PHONY: web release-web
.PHONY: mgr release-mgr
.PHONY: protobuf_phony
.PHONY: jgit_phony

BIN
crm-data.tar.gz Normal file

Binary file not shown.

14
mgrapp/.classpath Normal file
View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
<classpathentry exported="true" kind="lib" path="lib/codereview_proto.jar"/>
<classpathentry exported="true" kind="lib" path="lib/protobuf.jar" sourcepath="lib/protobuf_src.zip"/>
<classpathentry exported="true" kind="lib" path="lib/jgit.jar" sourcepath="lib/jgit_src.zip"/>
<classpathentry exported="true" kind="lib" path="lib/commons-httpclient.jar" sourcepath="lib/commons-httpclient_src.zip"/>
<classpathentry exported="true" kind="lib" path="lib/commons-codec.jar"/>
<classpathentry exported="true" kind="lib" path="lib/commons-logging.jar"/>
<classpathentry exported="true" kind="lib" path="lib/jsch.jar"/>
<classpathentry kind="lib" path="lib/commons-net.jar" sourcepath="lib/commons-net_src.zip"/>
<classpathentry kind="output" path="out_classes"/>
</classpath>

8
mgrapp/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
/lib/*_src.zip
/lib/codereview_proto.jar
/lib/codereview_manager.jar
/lib/protobuf.jar
/lib/jsch.jar
/lib/jgit.jar
/out_classes
/.password-*

17
mgrapp/.project Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>mgrapp</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,3 @@
#Tue Sep 02 16:59:24 PDT 2008
eclipse.preferences.version=1
encoding/<project>=UTF-8

View File

@@ -0,0 +1,3 @@
#Tue Sep 02 16:59:24 PDT 2008
eclipse.preferences.version=1
line.separator=\n

View File

@@ -0,0 +1,268 @@
#Thu Sep 04 11:18:51 PDT 2008
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.5
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.5
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_assignment=16
org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
org.eclipse.jdt.core.formatter.blank_lines_after_package=1
org.eclipse.jdt.core.formatter.blank_lines_before_field=0
org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
org.eclipse.jdt.core.formatter.blank_lines_before_method=1
org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
org.eclipse.jdt.core.formatter.blank_lines_before_package=0
org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
org.eclipse.jdt.core.formatter.comment.format_block_comments=true
org.eclipse.jdt.core.formatter.comment.format_header=true
org.eclipse.jdt.core.formatter.comment.format_html=true
org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
org.eclipse.jdt.core.formatter.comment.format_line_comments=true
org.eclipse.jdt.core.formatter.comment.format_source_code=true
org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
org.eclipse.jdt.core.formatter.comment.line_length=80
org.eclipse.jdt.core.formatter.compact_else_if=true
org.eclipse.jdt.core.formatter.continuation_indentation=2
org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
org.eclipse.jdt.core.formatter.indent_empty_lines=false
org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
org.eclipse.jdt.core.formatter.indentation.size=4
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
org.eclipse.jdt.core.formatter.lineSplit=80
org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
org.eclipse.jdt.core.formatter.tabulation.char=space
org.eclipse.jdt.core.formatter.tabulation.size=2
org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true

View File

@@ -0,0 +1,9 @@
#Tue Sep 02 17:00:18 PDT 2008
eclipse.preferences.version=1
formatter_profile=_Google Format
formatter_settings_version=11
org.eclipse.jdt.ui.ignorelowercasenames=true
org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax;
org.eclipse.jdt.ui.ondemandthreshold=99
org.eclipse.jdt.ui.staticondemandthreshold=99
org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>

64
mgrapp/bin/mgr Executable file
View File

@@ -0,0 +1,64 @@
#!/bin/sh
#
# Copyright 2008 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
if [ -z "$MGR_HOME" ]
then
MGR_HOME=`which $0 2>/dev/null`
MGR_HOME=`dirname $MGR_HOME`
MGR_HOME=`dirname $MGR_HOME`
fi
if [ -z "$MGR_HOME" ]
then
echo >&2 "error: MGR_HOME not set, cannot guess"
exit 1
fi
if ! [ -f "$MGR_HOME/lib/codereview_manager.jar" ]
then
echo >&2 "error: MGR_HOME $MGR_HOME not compiled"
exit 1
fi
if [ -z "$1" ]
then
echo >&2 "usage: $0 app.config [args]"
exit 1
fi
config_file=$1
shift
java_opts=
m=`GIT_CONFIG=$config_file git config --get codereview.memory`
if [ -n "$m" ]
then
java_opts="$java_opts -Xmx${m}"
fi
CLASSPATH=
for j in $MGR_HOME/lib/*.jar
do
if [ -z "$CLASSPATH" ]
then
CLASSPATH=$j
else
CLASSPATH=$CLASSPATH:$j
fi
done
export CLASSPATH
exec java $java_opts com.google.codereview.Main "$config_file" "$@"

31
mgrapp/compile.sh Executable file
View File

@@ -0,0 +1,31 @@
#!/bin/sh
#
# Copyright 2008 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
CLASSPATH=
for j in `pwd`/lib/*.jar
do
CLASSPATH=$CLASSPATH:$j
done
export CLASSPATH
(cd src &&
mkdir -p ../bin &&
mkdir -p ../bin/META-INF &&
cp -f META-INF/MANIFEST.MF ../bin/META-INF &&
find . -name \*.java | xargs javac -g -d ../bin) &&
(cd bin &&
rm -f ../codereview_manager.jar &&
jar cf ../codereview_manager.jar . ) || exit

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
mgrapp/lib/commons-net.jar Normal file

Binary file not shown.

View File

@@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: com.google.codereview.Main

View File

@@ -0,0 +1,284 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview;
import com.google.codereview.manager.Backend;
import com.google.codereview.manager.ProjectSync;
import com.google.codereview.manager.RepositoryCache;
import com.google.codereview.manager.merge.PendingMerger;
import com.google.codereview.manager.prune.BuildPruner;
import com.google.codereview.manager.prune.BundlePruner;
import com.google.codereview.manager.unpack.ReceivedBundleUnpacker;
import com.google.codereview.rpc.HttpRpc;
import com.google.githacks.BrokenShallowRepositoryCreator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.spearce.jgit.lib.PersonIdent;
import org.spearce.jgit.lib.RepositoryConfig;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.StreamHandler;
/** Server startup, invoked from the command line. */
public class Main {
private static final Log LOG = LogFactory.getLog("main");
private static final String SEC_CODEREVIEW = "codereview";
private static final String SEC_LOG = "log";
private static final int FOUR_HOURS = 4 * 60 * 60; // seconds
private static final int ONCE_PER_DAY = 24 * 60 * 60;// seconds
public static void main(final String[] args) {
if (args.length == 0) {
System.err.println("usage: " + Main.class.getName() + " configfile");
System.exit(1);
}
final File configPath = new File(args[0]).getAbsoluteFile();
final RepositoryConfig config = new RepositoryConfig(null, configPath);
try {
config.load();
} catch (FileNotFoundException e) {
System.err.println("error: " + configPath + " not found");
System.exit(1);
} catch (IOException e) {
System.err.println("error: " + configPath + " not readable");
e.printStackTrace(System.err);
System.exit(1);
}
configureLogging(config, args.length > 1);
LOG.info("Read " + configPath);
final Main me = new Main(configPath, config);
if (args.length == 1) {
me.addShutdownHook();
me.start();
} else {
final String cmd = args[1];
if (cmd.equals("sync")) {
new ProjectSync(me.backend).sync();
} else if (cmd.equals("bsclone")) {
try {
final File src = new File(args[2]);
final File dst = new File(args[3]);
BrokenShallowRepositoryCreator.createRecursive(src, dst);
} catch (IOException err) {
System.err.println("error: " + err);
}
} else {
System.err.println("error: " + cmd + " not recognized");
}
}
}
private final RepositoryConfig config;
private final ScheduledThreadPoolExecutor pool;
private final int taskSleep;
private final Backend backend;
private Main(final File configPath, final RepositoryConfig rc) {
config = rc;
final int threads = config.getInt(SEC_CODEREVIEW, "threads", 10);
taskSleep = config.getInt(SEC_CODEREVIEW, "sleep", 10);
LOG.info("Starting thread pool with " + threads + " initial threads.");
pool = new ScheduledThreadPoolExecutor(threads);
final RepositoryCache repoCache = createRepositoryCache();
final HttpRpc rpc = createHttpRpc(configPath.getParentFile());
backend = new Backend(repoCache, rpc, pool, createUserPersonIdent());
}
private RepositoryCache createRepositoryCache() {
final File basedir = new File(required(SEC_CODEREVIEW, "basedir"));
return new RepositoryCache(basedir);
}
private HttpRpc createHttpRpc(final File base) throws ThreadDeath {
final URL serverUrl;
try {
serverUrl = new URL(required(SEC_CODEREVIEW, "server"));
} catch (MalformedURLException err) {
System.err.println("error: Bad URL in " + SEC_CODEREVIEW + ".server");
System.exit(1);
throw new ThreadDeath();
}
final String roleUser = config.getString(SEC_CODEREVIEW, null, "username");
File pwf = new File(required(SEC_CODEREVIEW, "secureconfig"));
if (!pwf.isAbsolute()) {
pwf = new File(base, pwf.getPath());
}
final RepositoryConfig pwc = new RepositoryConfig(null, pwf);
try {
pwc.load();
} catch (IOException e) {
System.err.println("error: Cannot read secureconfig: " + pwf + ": "
+ e.getMessage());
System.exit(1);
throw new ThreadDeath();
}
final String rolePass = pwc.getString(SEC_CODEREVIEW, null, "password");
final String apiKey = pwc.getString(SEC_CODEREVIEW, null, "internalapikey");
return new HttpRpc(serverUrl, roleUser, rolePass, apiKey);
}
private PersonIdent createUserPersonIdent() {
final String name = required("user", "name");
final String email = required("user", "email");
return new PersonIdent(name, email, System.currentTimeMillis(), 0);
}
private void addShutdownHook() {
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
LOG.info("Shutting down thread pool.");
pool.shutdownNow();
}
});
}
private void start() {
schedule(new ReceivedBundleUnpacker(backend));
schedule(new PendingMerger(backend));
schedule(new BundlePruner(backend), FOUR_HOURS);
schedule(new BuildPruner(backend), ONCE_PER_DAY);
}
private void schedule(final Runnable t) {
schedule(t, taskSleep);
}
private void schedule(final Runnable t, final int sleep) {
pool.scheduleWithFixedDelay(t, 0, sleep, TimeUnit.SECONDS);
}
private String required(final String sec, final String key) {
final String r = config.getString(sec, null, key);
if (r == null || r.length() == 0) {
System.err.println("error: Missing required config " + sec + "." + key);
System.exit(1);
}
return r;
}
private static void configureLogging(final RepositoryConfig config,
final boolean interactive) {
final Logger root = Logger.getLogger("");
for (final Handler h : root.getHandlers())
root.removeHandler(h);
OutputStream out = System.out;
final String logfile = config.getString(SEC_LOG, null, "file");
if (logfile != null && !interactive) {
try {
out = new FileOutputStream(logfile, true);
} catch (IOException err) {
System.err.println("error: Cannot append to " + logfile);
System.err.println("error: " + err.toString());
System.exit(1);
throw new ThreadDeath();
}
}
final StreamHandler ch = new StreamHandler(out, new Formatter() {
private final SimpleDateFormat sdf;
private final StringWriter stringBuffer = new StringWriter();
private final PrintWriter p = new PrintWriter(stringBuffer);
{
sdf = new SimpleDateFormat("yyyyMMdd.HHmmss");
}
@Override
public String format(final LogRecord record) {
stringBuffer.getBuffer().setLength(0);
final String levelName = record.getLevel().getName();
p.print(sdf.format(new Date(record.getMillis())));
p.print(' ');
p.print(levelName);
for (int cnt = "WARNING".length() - levelName.length(); --cnt >= 0;) {
p.print(' ');
}
p.print(' ');
p.print(record.getLoggerName());
p.print(" - ");
p.print(record.getMessage());
p.print('\n');
if (record.getThrown() != null) {
record.getThrown().printStackTrace(p);
p.print('\n');
}
p.flush();
return stringBuffer.toString();
}
}) {
@Override
public synchronized void publish(final LogRecord record) {
super.publish(record);
flush();
}
};
root.addHandler(ch);
final Level levelObj;
final String levelStr = config.getString(SEC_LOG, null, "level");
if (levelStr == null || levelStr.length() == 0) {
levelObj = Level.INFO;
} else if ("trace".equalsIgnoreCase(levelStr)) {
levelObj = Level.FINEST;
} else if ("debug".equalsIgnoreCase(levelStr)) {
levelObj = Level.FINE;
} else if ("info".equalsIgnoreCase(levelStr)) {
levelObj = Level.INFO;
} else if ("warning".equalsIgnoreCase(levelStr)
|| "warn".equalsIgnoreCase(levelStr)) {
levelObj = Level.WARNING;
} else if ("error".equalsIgnoreCase(levelStr)) {
levelObj = Level.SEVERE;
} else if ("fatal".equalsIgnoreCase(levelStr)) {
levelObj = Level.SEVERE;
} else {
System.out.println("warning: Bad " + SEC_LOG + ".level " + levelStr
+ "; assuming info");
levelObj = Level.INFO;
}
ch.setLevel(levelObj);
root.setLevel(levelObj);
Logger.getLogger("httpclient").setLevel(Level.WARNING);
Logger.getLogger("org.apache.commons.httpclient").setLevel(Level.WARNING);
}
}

View File

@@ -0,0 +1,108 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.manager;
import com.google.codereview.internal.Admin.AdminService;
import com.google.codereview.internal.Build.BuildService;
import com.google.codereview.internal.BundleStore.BundleStoreService;
import com.google.codereview.internal.Change.ChangeService;
import com.google.codereview.internal.Merge.MergeService;
import com.google.protobuf.RpcChannel;
import org.spearce.jgit.lib.PersonIdent;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Configuration and state related to a single Gerrit backend process.
*/
public class Backend {
private final RepositoryCache repoCache;
private final RpcChannel rpc;
private final ScheduledExecutorService executor;
private final PersonIdent mergeIdent;
private final AdminService adminSvc;
private final BuildService buildSvc;
private final BundleStoreService bundleStoreSvc;
private final ChangeService changeSvc;
private final MergeService mergeSvc;
public Backend(final RepositoryCache cache, final RpcChannel api,
final ScheduledExecutorService threadPool,
final PersonIdent performMergsAs) {
repoCache = cache;
rpc = api;
executor = threadPool;
mergeIdent = performMergsAs;
adminSvc = AdminService.newStub(rpc);
buildSvc = BuildService.newStub(rpc);
bundleStoreSvc = BundleStoreService.newStub(rpc);
changeSvc = ChangeService.newStub(rpc);
mergeSvc = MergeService.newStub(rpc);
}
public RepositoryCache getRepositoryCache() {
return repoCache;
}
public RpcChannel getRpcChannel() {
return rpc;
}
public PersonIdent getMergeIdentity() {
return mergeIdent;
}
/**
* @return a copy of {@link #getMergeIdentity()} modified to use the current
* system clock as the time, in the GMT/UTC time zone.
*/
public PersonIdent newMergeIdentity() {
return new PersonIdent(getMergeIdentity(), System.currentTimeMillis(), 0);
}
/**
* Schedule a task for execution on a background thread.
*
* @param task runnable to perform the task. The task will be executed once,
* as soon as a thread is available.
*/
public void asyncExec(final Runnable task) {
executor.schedule(task, 0, TimeUnit.MILLISECONDS);
}
public AdminService getAdminService() {
return adminSvc;
}
public BuildService getBuildService() {
return buildSvc;
}
public BundleStoreService getBundleStoreService() {
return bundleStoreSvc;
}
public ChangeService getChangeService() {
return changeSvc;
}
public MergeService getMergeService() {
return mergeSvc;
}
}

View File

@@ -0,0 +1,26 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.manager;
/** Indicates a directory does not contain a valid Git repository. */
public class InvalidRepositoryException extends Exception {
public InvalidRepositoryException(final String name) {
super(name);
}
public InvalidRepositoryException(final String name, final Throwable err) {
super(name, err);
}
}

View File

@@ -0,0 +1,132 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.manager;
import com.google.codereview.internal.SyncProject.BranchSync;
import com.google.codereview.internal.SyncProject.SyncProjectRequest;
import com.google.codereview.internal.SyncProject.SyncProjectResponse;
import com.google.codereview.rpc.SimpleController;
import com.google.codereview.util.GitMetaUtil;
import com.google.protobuf.RpcCallback;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.spearce.jgit.errors.IncorrectObjectTypeException;
import org.spearce.jgit.lib.Constants;
import org.spearce.jgit.lib.Ref;
import org.spearce.jgit.lib.Repository;
import org.spearce.jgit.revwalk.RevCommit;
import org.spearce.jgit.revwalk.RevWalk;
import java.io.File;
import java.io.IOException;
public class ProjectSync {
private static final Log LOG = LogFactory.getLog(ProjectSync.class);
private final Backend server;
public ProjectSync(final Backend be) {
server = be;
}
public void sync() {
syncDirectoryImpl(server.getRepositoryCache().getBaseDirectory(), "");
}
private void syncDirectoryImpl(final File root, final String prefix) {
final File[] entries = root.listFiles();
if (entries == null) {
return;
}
for (final File f : entries) {
final String fName = f.getName();
if (fName.equals(".") || fName.equals("..")) {
continue;
}
if (!f.isDirectory()) {
continue;
}
String fullName = prefix + fName;
final Repository db;
try {
db = GitMetaUtil.open(f);
} catch (IOException e) {
LOG.error("Cannot open " + f + ": " + e.toString());
continue;
}
if (db != null) {
try {
if (fullName.endsWith(".git")) {
fullName = fullName.substring(0, fullName.length() - 4);
}
sync(fullName, db);
} finally {
db.close();
}
} else {
syncDirectoryImpl(f, fullName + "/");
}
}
}
public void sync(final String name, final Repository db) {
final SyncProjectRequest.Builder req = SyncProjectRequest.newBuilder();
req.setProjectName(name);
try {
final RevWalk rw = new RevWalk(db);
for (final Ref ref : db.getAllRefs().values()) {
if (ref.getName().startsWith(Constants.R_HEADS)) {
final RevCommit c;
try {
c = rw.parseCommit(ref.getObjectId());
} catch (IncorrectObjectTypeException e) {
continue;
}
req.addBranch(toBranch(ref, c));
}
}
} catch (IOException err) {
LOG.error("Cannot synchronize " + name, err);
}
send(req.build());
}
private BranchSync toBranch(final Ref r, final RevCommit c) {
final BranchSync.Builder bs = BranchSync.newBuilder();
bs.setBranchName(r.getName());
bs.setCommit(GitMetaUtil.toGitCommit(c));
return bs.build();
}
private void send(final SyncProjectRequest req) {
final SimpleController ctrl = new SimpleController();
server.getAdminService().syncProject(ctrl, req,
new RpcCallback<SyncProjectResponse>() {
public void run(final SyncProjectResponse rsp) {
}
});
if (ctrl.failed()) {
final String name = req.getProjectName();
LOG.error("syncProject " + name + ": " + ctrl.errorText());
}
}
}

View File

@@ -0,0 +1,89 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.manager;
import com.google.codereview.util.GitMetaUtil;
import org.spearce.jgit.lib.Repository;
import java.io.File;
import java.io.IOException;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
/** Cache of active Git repositories being used by the manager. */
public class RepositoryCache {
private static final Pattern REPO_NAME =
Pattern.compile("^[A-Za-z][A-Za-z0-9/_-]+$");
private final File base;
private final Map<String, Reference<Repository>> cache;
/**
* Create a new cache to manage a specific base directory (and below).
*
* @param basedir top level directory that contains all repositories.
*/
public RepositoryCache(final File basedir) {
base = basedir;
cache = new HashMap<String, Reference<Repository>>();
}
/**
* @return the base directory which contains all known repositories.
*/
public File getBaseDirectory() {
return base;
}
/**
* Get (or open) a repository by name.
*
* @param name the repository name, relative to the base directory supplied
* when the cache was created.
* @return the cached Repository instance.
* @throws InvalidRepositoryException the name does not denote an existing
* repository, or the name cannot be read as a repository.
*/
public synchronized Repository get(String name)
throws InvalidRepositoryException {
if (name.endsWith(".git")) {
name = name.substring(0, name.length() - 4);
}
if (!REPO_NAME.matcher(name).matches()) {
throw new InvalidRepositoryException(name);
}
final Reference<Repository> ref = cache.get(name);
Repository db = ref != null ? ref.get() : null;
if (db == null) {
try {
db = GitMetaUtil.open(new File(base, name));
if (db == null) {
throw new InvalidRepositoryException(name);
}
} catch (IOException err) {
throw new InvalidRepositoryException(name, err);
}
cache.put(name, new SoftReference<Repository>(db));
}
return db;
}
}

View File

@@ -0,0 +1,30 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.manager;
/** Indicates the worker task wants to be unscheduled and never run again. */
public class StopProcessingException extends RuntimeException {
public StopProcessingException(String message) {
super(message);
}
public StopProcessingException(Throwable cause) {
super(cause);
}
public StopProcessingException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,69 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.manager.merge;
import com.google.codereview.internal.PostBranchUpdate.PostBranchUpdateRequest;
import com.google.codereview.internal.PostBranchUpdate.PostBranchUpdateResponse;
import com.google.codereview.internal.PostBuildResult.PostBuildResultResponse;
import com.google.codereview.manager.Backend;
import com.google.codereview.rpc.SimpleController;
import com.google.protobuf.RpcCallback;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class BranchUpdater {
private static final Log LOG = LogFactory.getLog(BranchUpdater.class);
private final Backend server;
public BranchUpdater(final Backend be) {
server = be;
}
public void updateBranch(final PostBuildResultResponse buildInfo) {
if (new UpdateOp(server.getRepositoryCache(), buildInfo).update()) {
final PostBranchUpdateRequest.Builder req;
req = PostBranchUpdateRequest.newBuilder();
req.setBranchKey(buildInfo.getDestBranchKey());
req.addAllNewChange(buildInfo.getNewChangeList());
send(req.build());
} else {
final PostBranchUpdateRequest.Builder req;
req = PostBranchUpdateRequest.newBuilder();
req.setBranchKey(buildInfo.getDestBranchKey());
// Don't mark any changes merged.
send(req.build());
}
}
private void send(final PostBranchUpdateRequest msg) {
if (LOG.isDebugEnabled()) {
LOG.debug("\n" + msg);
}
final SimpleController ctrl = new SimpleController();
server.getMergeService().postBranchUpdate(ctrl, msg,
new RpcCallback<PostBranchUpdateResponse>() {
public void run(final PostBranchUpdateResponse rsp) {
}
});
if (ctrl.failed()) {
LOG.warn("postBranchUpdate failed: " + ctrl.errorText());
}
}
}

View File

@@ -0,0 +1,51 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.manager.merge;
import com.google.codereview.internal.PostMergeResult.MergeResultItem;
import org.spearce.jgit.lib.AnyObjectId;
import org.spearce.jgit.revwalk.RevCommit;
/** Extended commit entity with code review specific metadata. */
class CodeReviewCommit extends RevCommit {
/**
* Unique key of the PatchSet entity from the code review system.
* <p>
* This value is only available on commits that have a PatchSet represented in
* the code review system and whose PatchSet is in the current submit queue.
* Merge commits created during the merge or commits that aren't in the submit
* queue will keep this member null.
*/
String patchsetKey;
/**
* Ordinal position of this commit within the submit queue.
* <p>
* Only valid if {@link #patchsetKey} is not null.
*/
int originalOrder;
/**
* The result status for this commit.
* <p>
* Only valid if {@link #patchsetKey} is not null.
*/
MergeResultItem.CodeType statusCode;
CodeReviewCommit(final AnyObjectId id) {
super(id);
}
}

View File

@@ -0,0 +1,26 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.manager.merge;
/** Indicates the current branch's queue cannot be processed at this time. */
class MergeException extends Exception {
MergeException(final String msg) {
super(msg, null);
}
MergeException(final String msg, final Throwable why) {
super(msg, why);
}
}

View File

@@ -0,0 +1,386 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.manager.merge;
import com.google.codereview.internal.PendingMerge.PendingMergeItem;
import com.google.codereview.internal.PendingMerge.PendingMergeResponse;
import com.google.codereview.internal.PostMergeResult.MergeResultItem;
import com.google.codereview.internal.PostMergeResult.PostMergeResultRequest;
import com.google.codereview.manager.Backend;
import com.google.codereview.manager.InvalidRepositoryException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.spearce.jgit.errors.IncorrectObjectTypeException;
import org.spearce.jgit.errors.MissingObjectException;
import org.spearce.jgit.lib.AnyObjectId;
import org.spearce.jgit.lib.Commit;
import org.spearce.jgit.lib.Constants;
import org.spearce.jgit.lib.ObjectId;
import org.spearce.jgit.lib.PersonIdent;
import org.spearce.jgit.lib.Ref;
import org.spearce.jgit.lib.RefUpdate;
import org.spearce.jgit.lib.Repository;
import org.spearce.jgit.merge.MergeStrategy;
import org.spearce.jgit.merge.Merger;
import org.spearce.jgit.revwalk.RevCommit;
import org.spearce.jgit.revwalk.RevSort;
import org.spearce.jgit.revwalk.RevWalk;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* Merges changes in submission order into a single branch.
* <p>
* Branches are reduced to the minimum number of heads needed to merge
* everything. This allows commits to be entered into the queue in any order
* (such as ancestors before descendants) and only the most recent commit on any
* line of development will be merged. All unmerged commits along a line of
* development must be in the submission queue in order to merge the tip of that
* line.
* <p>
* Conflicts are handled by discarding the entire line of development and
* marking it as conflicting, even if an earlier commit along that same line can
* be merged cleanly.
*/
class MergeOp {
private static final Log LOG = LogFactory.getLog(MergeOp.class);
static String mergePinName(final AnyObjectId id) {
return mergePinName(id.name());
}
static String mergePinName(final String idstr) {
return "refs/merges/" + idstr;
}
private final Backend server;
private final PendingMergeResponse in;
private final PersonIdent mergeIdent;
private final Collection<MergeResultItem> updates;
private final List<CodeReviewCommit> toMerge;
private Repository db;
private RevWalk rw;
private CodeReviewCommit branchTip;
private CodeReviewCommit mergeTip;
private final List<CodeReviewCommit> newChanges;
MergeOp(final Backend be, final PendingMergeResponse mergeInfo) {
server = be;
in = mergeInfo;
mergeIdent = server.newMergeIdentity();
updates = new ArrayList<MergeResultItem>();
toMerge = new ArrayList<CodeReviewCommit>();
newChanges = new ArrayList<CodeReviewCommit>();
}
PostMergeResultRequest merge() {
final String loc = in.getDestProjectName() + " " + in.getDestBranchName();
LOG.debug("Merging " + loc);
try {
mergeImpl();
final PostMergeResultRequest.Builder update;
update = PostMergeResultRequest.newBuilder();
update.setDestBranchKey(in.getDestBranchKey());
update.addAllChange(updates);
return update.build();
} catch (MergeException ee) {
LOG.error("Error merging " + loc, ee);
mergeTip = null;
final PostMergeResultRequest.Builder update;
update = PostMergeResultRequest.newBuilder();
update.setDestBranchKey(in.getDestBranchKey());
for (final PendingMergeItem pmi : in.getChangeList()) {
update.addChange(suspend(pmi));
}
return update.build();
}
}
CodeReviewCommit getMergeTip() {
return mergeTip;
}
Collection<CodeReviewCommit> getNewChanges() {
return Collections.unmodifiableCollection(newChanges);
}
private void mergeImpl() throws MergeException {
openRepository();
openBranch();
validateChangeList();
reduceToMinimalMerge();
mergeTopics();
markCleanMerges();
pinMergeCommit();
}
private void openRepository() throws MergeException {
final String name = in.getDestProjectName();
try {
db = server.getRepositoryCache().get(name);
} catch (InvalidRepositoryException notGit) {
final String m = "Repository \"" + name + "\" unknown.";
throw new MergeException(m, notGit);
}
rw = new RevWalk(db) {
@Override
protected RevCommit createCommit(final AnyObjectId id) {
return new CodeReviewCommit(id);
}
};
}
private void openBranch() throws MergeException {
try {
final RefUpdate ru = db.updateRef(in.getDestBranchName());
if (ru.getOldObjectId() != null) {
branchTip = (CodeReviewCommit) rw.parseCommit(ru.getOldObjectId());
} else {
branchTip = null;
}
} catch (IOException e) {
throw new MergeException("Cannot open branch", e);
}
}
private void validateChangeList() throws MergeException {
final Set<ObjectId> tips = new HashSet<ObjectId>();
for (final Ref r : db.getAllRefs().values()) {
tips.add(r.getObjectId());
}
int commitOrder = 0;
for (final PendingMergeItem pmi : in.getChangeList()) {
final String idstr = pmi.getRevisionId();
final ObjectId id;
try {
id = ObjectId.fromString(idstr);
} catch (IllegalArgumentException iae) {
throw new MergeException("Invalid ObjectId: " + idstr);
}
if (!tips.contains(id)) {
// TODO Technically the proper way to do this test is to use a
// RevWalk on "$id --not --all" and test for an empty set. But
// that is way slower than looking for a ref directly pointing
// at the desired tip. We should always have a ref available.
//
// TODO this is actually an error, the branch is gone but we
// want to merge the issue. We can't safely do that if the
// tip is not reachable.
LOG.error("Cannot find branch head for " + id.name());
updates.add(suspend(pmi));
continue;
}
final CodeReviewCommit commit;
try {
commit = (CodeReviewCommit) rw.parseCommit(id);
} catch (IOException e) {
throw new MergeException("Invalid issue commit " + id, e);
}
commit.patchsetKey = pmi.getPatchsetKey();
commit.originalOrder = commitOrder++;
LOG.debug("Commit " + commit.name() + " is " + commit.patchsetKey);
if (branchTip != null) {
// If this commit is already merged its a bug in the queuing code
// that we got back here. Just mark it complete and move on. Its
// merged and that is all that mattered to the requestor.
//
try {
if (rw.isMergedInto(commit, branchTip)) {
commit.statusCode = MergeResultItem.CodeType.ALREADY_MERGED;
updates.add(toResult(commit));
LOG.debug("Already merged " + commit.name());
continue;
}
} catch (IOException err) {
throw new MergeException("Cannot perform merge base test", err);
}
}
toMerge.add(commit);
}
}
private void reduceToMinimalMerge() throws MergeException {
final Collection<CodeReviewCommit> heads;
try {
heads = new MergeSorter(rw, branchTip).sort(toMerge);
} catch (IOException e) {
throw new MergeException("Branch head sorting failed", e);
}
for (final CodeReviewCommit c : toMerge) {
if (c.statusCode != null) {
updates.add(toResult(c));
}
}
toMerge.clear();
toMerge.addAll(heads);
Collections.sort(toMerge, new Comparator<CodeReviewCommit>() {
public int compare(final CodeReviewCommit a, final CodeReviewCommit b) {
return a.originalOrder - b.originalOrder;
}
});
}
private void mergeTopics() throws MergeException {
mergeTip = branchTip;
// Take the first fast-forward available, if any is available in the set.
//
for (final Iterator<CodeReviewCommit> i = toMerge.iterator(); i.hasNext();) {
try {
final CodeReviewCommit n = i.next();
if (mergeTip == null || rw.isMergedInto(mergeTip, n)) {
mergeTip = n;
i.remove();
LOG.debug("Fast-forward to " + n.name());
break;
}
} catch (IOException e) {
throw new MergeException("Cannot fast-forward test during merge", e);
}
}
// For every other commit do a pair-wise merge.
//
while (!toMerge.isEmpty()) {
final CodeReviewCommit n = toMerge.remove(0);
final Merger m = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db);
try {
if (m.merge(new AnyObjectId[] {mergeTip, n})) {
writeMergeCommit(m, n);
LOG.debug("Merged " + n.name());
} else {
rw.reset();
rw.markStart(n);
rw.markUninteresting(mergeTip);
CodeReviewCommit failed;
while ((failed = (CodeReviewCommit) rw.next()) != null) {
if (failed.patchsetKey != null) {
failed.statusCode = MergeResultItem.CodeType.PATH_CONFLICT;
updates.add(toResult(failed));
}
}
LOG.debug("Rejected (path conflict) " + n.name());
}
} catch (IOException e) {
throw new MergeException("Cannot merge " + n.name(), e);
}
}
}
private void writeMergeCommit(final Merger m, final CodeReviewCommit n)
throws IOException, MissingObjectException, IncorrectObjectTypeException {
final Commit mergeCommit = new Commit(db);
mergeCommit.setTreeId(m.getResultTreeId());
mergeCommit.setParentIds(new ObjectId[] {mergeTip, n});
mergeCommit.setAuthor(mergeIdent);
mergeCommit.setCommitter(mergeCommit.getAuthor());
mergeCommit.setMessage("Merge");
final ObjectId id = m.getObjectWriter().writeCommit(mergeCommit);
mergeTip = (CodeReviewCommit) rw.parseCommit(id);
}
private void markCleanMerges() throws MergeException {
try {
rw.reset();
rw.sort(RevSort.REVERSE);
rw.markStart(mergeTip);
if (branchTip != null) {
rw.markUninteresting(branchTip);
} else {
for (final Ref r : db.getAllRefs().values()) {
if (r.getName().startsWith(Constants.R_HEADS)
|| r.getName().startsWith(Constants.R_TAGS)) {
try {
rw.markUninteresting(rw.parseCommit(r.getObjectId()));
} catch (IncorrectObjectTypeException iote) {
// Not a commit? Skip over it.
}
}
}
}
CodeReviewCommit c;
while ((c = (CodeReviewCommit) rw.next()) != null) {
if (c.patchsetKey != null) {
c.statusCode = MergeResultItem.CodeType.CLEAN_MERGE;
updates.add(toResult(c));
newChanges.add(c);
}
}
} catch (IOException e) {
throw new MergeException("Cannot mark clean merges", e);
}
}
private void pinMergeCommit() throws MergeException {
final String name = mergePinName(mergeTip.getId());
final RefUpdate.Result r;
try {
final RefUpdate u = db.updateRef(name);
u.setNewObjectId(mergeTip.getId());
u.setRefLogMessage("Merged submit queue", false);
r = u.update();
} catch (IOException err) {
final String m = "Failure creating " + name;
throw new MergeException(m, err);
}
if (r == RefUpdate.Result.NEW) {
} else if (r == RefUpdate.Result.FAST_FORWARD) {
} else if (r == RefUpdate.Result.FORCED) {
} else if (r == RefUpdate.Result.NO_CHANGE) {
} else {
final String m = "Failure creating " + name + ": " + r.name();
throw new MergeException(m);
}
}
private static MergeResultItem suspend(final PendingMergeItem pmi) {
final MergeResultItem.Builder delay = MergeResultItem.newBuilder();
delay.setStatusCode(MergeResultItem.CodeType.MISSING_DEPENDENCY);
delay.setPatchsetKey(pmi.getPatchsetKey());
return delay.build();
}
private static MergeResultItem toResult(final CodeReviewCommit c) {
final MergeResultItem.Builder delay = MergeResultItem.newBuilder();
delay.setStatusCode(c.statusCode);
delay.setPatchsetKey(c.patchsetKey);
return delay.build();
}
}

View File

@@ -0,0 +1,96 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.manager.merge;
import com.google.codereview.internal.PostMergeResult.MergeResultItem;
import org.spearce.jgit.revwalk.RevCommit;
import org.spearce.jgit.revwalk.RevCommitList;
import org.spearce.jgit.revwalk.RevFlag;
import org.spearce.jgit.revwalk.RevWalk;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
class MergeSorter {
private final RevWalk rw;
private final RevCommit base;
private final RevFlag CAN_MERGE;
MergeSorter(final RevWalk walk, final RevCommit branchHead) {
rw = walk;
CAN_MERGE = rw.newFlag("CAN_MERGE");
base = branchHead;
}
Collection<CodeReviewCommit> sort(final Collection<CodeReviewCommit> incoming)
throws IOException {
final Set<CodeReviewCommit> heads = new HashSet<CodeReviewCommit>();
final Set<CodeReviewCommit> sort = prepareList(incoming);
INCOMING: while (!sort.isEmpty()) {
final CodeReviewCommit n = removeOne(sort);
rw.resetRetain(CAN_MERGE);
rw.markStart(n);
if (base != null) {
rw.markUninteresting(base);
}
RevCommit c;
final RevCommitList<RevCommit> contents = new RevCommitList<RevCommit>();
while ((c = rw.next()) != null) {
if (!c.has(CAN_MERGE)) {
// We cannot merge n as it would bring something we
// aren't permitted to merge at this time. Drop n.
//
n.statusCode = MergeResultItem.CodeType.MISSING_DEPENDENCY;
continue INCOMING;
}
contents.add(c);
}
// Anything reachable through us is better merged by just
// merging us directly. So prune our ancestors out and let
// us merge instead.
//
sort.removeAll(contents);
heads.removeAll(contents);
heads.add(n);
}
return heads;
}
private Set<CodeReviewCommit> prepareList(
final Collection<CodeReviewCommit> in) {
final HashSet<CodeReviewCommit> sort = new HashSet<CodeReviewCommit>();
for (final CodeReviewCommit c : in) {
if (!c.has(CAN_MERGE)) {
c.add(CAN_MERGE);
sort.add(c);
}
}
return sort;
}
private static <T> T removeOne(final Collection<T> c) {
final Iterator<T> i = c.iterator();
final T r = i.next();
i.remove();
return r;
}
}

View File

@@ -0,0 +1,197 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.manager.merge;
import com.google.codereview.internal.PendingMerge.PendingMergeRequest;
import com.google.codereview.internal.PendingMerge.PendingMergeResponse;
import com.google.codereview.internal.PostBranchUpdate.PostBranchUpdateRequest;
import com.google.codereview.internal.PostBranchUpdate.PostBranchUpdateResponse;
import com.google.codereview.internal.PostBuildResult.PostBuildResultRequest;
import com.google.codereview.internal.PostBuildResult.PostBuildResultResponse;
import com.google.codereview.internal.PostMergeResult.PostMergeResultRequest;
import com.google.codereview.internal.PostMergeResult.PostMergeResultResponse;
import com.google.codereview.internal.SubmitBuild.SubmitBuildRequest;
import com.google.codereview.internal.SubmitBuild.SubmitBuildResponse;
import com.google.codereview.manager.Backend;
import com.google.codereview.manager.StopProcessingException;
import com.google.codereview.rpc.SimpleController;
import com.google.codereview.util.MutableBoolean;
import com.google.protobuf.RpcCallback;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.spearce.jgit.lib.ObjectId;
/** Merges changes from branches with changes waiting to be merged. */
public class PendingMerger implements Runnable {
private static final Log LOG = LogFactory.getLog(PendingMerger.class);
private static final PendingMergeRequest NEXT_REQ =
PendingMergeRequest.getDefaultInstance();
private final Backend server;
private final BranchUpdater updater;
public PendingMerger(final Backend be) {
server = be;
updater = new BranchUpdater(server);
}
public void run() {
try {
runImpl();
} catch (RuntimeException err) {
LOG.fatal("Unexpected runtime failure", err);
throw err;
} catch (Error err) {
LOG.fatal("Unexpected runtime failure", err);
throw err;
}
}
private void runImpl() {
boolean tryAnother;
do {
tryAnother = next();
} while (tryAnother);
}
private boolean next() {
final MutableBoolean tryAnother = new MutableBoolean();
final SimpleController ctrl = new SimpleController();
server.getMergeService().nextPendingMerge(ctrl, NEXT_REQ,
new RpcCallback<PendingMergeResponse>() {
public void run(final PendingMergeResponse rsp) {
tryAnother.value = merge(rsp);
}
});
if (ctrl.failed()) {
LOG.warn("nextPendingMerge failed: " + ctrl.errorText());
tryAnother.value = false;
}
return tryAnother.value;
}
private boolean merge(final PendingMergeResponse rsp) {
final PendingMergeResponse.CodeType sc = rsp.getStatusCode();
if (sc == PendingMergeResponse.CodeType.QUEUE_EMPTY) {
return false;
}
if (sc == PendingMergeResponse.CodeType.MERGE_READY) {
mergeImpl(rsp);
return true;
}
throw new StopProcessingException("unknown status " + sc.name());
}
protected void mergeImpl(final PendingMergeResponse rsp) {
final MergeOp mo = new MergeOp(server, rsp);
final PostMergeResultRequest result = mo.merge();
send(result);
if (mo.getMergeTip() != null && !mo.getNewChanges().isEmpty()) {
final SubmitBuildRequest.Builder b = SubmitBuildRequest.newBuilder();
b.setBranchKey(rsp.getDestBranchKey());
b.setRevisionId(mo.getMergeTip().name());
for (final CodeReviewCommit c : mo.getNewChanges()) {
if (c.patchsetKey != null) {
b.addNewChange(c.patchsetKey);
}
}
send(b.build(), mo.getMergeTip().getId());
} else {
final PostBranchUpdateRequest.Builder b;
b = PostBranchUpdateRequest.newBuilder();
b.setBranchKey(rsp.getDestBranchKey());
// Don't mark any changes merged.
send(b.build());
}
}
private void send(final PostMergeResultRequest msg) {
if (LOG.isDebugEnabled()) {
LOG.debug("\n" + msg);
}
final SimpleController ctrl = new SimpleController();
server.getMergeService().postMergeResult(ctrl, msg,
new RpcCallback<PostMergeResultResponse>() {
public void run(final PostMergeResultResponse rsp) {
}
});
if (ctrl.failed()) {
LOG.warn("postMergeResult failed: " + ctrl.errorText());
}
}
private void send(final SubmitBuildRequest msg, final ObjectId id) {
if (LOG.isDebugEnabled()) {
LOG.debug("\n" + msg);
}
final SimpleController ctrl = new SimpleController();
server.getBuildService().submitBuild(ctrl, msg,
new RpcCallback<SubmitBuildResponse>() {
public void run(final SubmitBuildResponse rsp) {
scheduleBuild(rsp, id);
}
});
if (ctrl.failed()) {
LOG.warn("submitBuild failed: " + ctrl.errorText());
}
}
private void scheduleBuild(final SubmitBuildResponse rsp, final ObjectId id) {
final int buildId = rsp.getBuildId();
LOG.debug("Merge commit " + id.name() + " is build " + buildId);
// For now assume the build was successful.
//
final PostBuildResultRequest.Builder req;
req = PostBuildResultRequest.newBuilder();
req.setBuildId(buildId);
req.setBuildStatus(PostBuildResultRequest.ResultType.SUCCESS);
final SimpleController ctrl = new SimpleController();
server.getBuildService().postBuildResult(ctrl, req.build(),
new RpcCallback<PostBuildResultResponse>() {
public void run(final PostBuildResultResponse rsp) {
updater.updateBranch(rsp);
}
});
if (ctrl.failed()) {
LOG.warn("postBuildResult failed: " + ctrl.errorText());
}
}
private void send(final PostBranchUpdateRequest msg) {
if (LOG.isDebugEnabled()) {
LOG.debug("\n" + msg);
}
final SimpleController ctrl = new SimpleController();
server.getMergeService().postBranchUpdate(ctrl, msg,
new RpcCallback<PostBranchUpdateResponse>() {
public void run(final PostBranchUpdateResponse rsp) {
}
});
if (ctrl.failed()) {
LOG.warn("postBranchUpdate failed: " + ctrl.errorText());
}
}
}

View File

@@ -0,0 +1,130 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.manager.merge;
import com.google.codereview.internal.PostBuildResult.PostBuildResultResponse;
import com.google.codereview.manager.InvalidRepositoryException;
import com.google.codereview.manager.RepositoryCache;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.spearce.jgit.lib.ObjectId;
import org.spearce.jgit.lib.RefUpdate;
import org.spearce.jgit.lib.Repository;
import org.spearce.jgit.revwalk.RevCommit;
import org.spearce.jgit.revwalk.RevWalk;
import java.io.IOException;
public class UpdateOp {
private static final Log LOG = LogFactory.getLog(UpdateOp.class);
private final RepositoryCache repoCache;
private final PostBuildResultResponse in;
private Repository db;
private RevCommit newCommit;
private RevWalk rw;
private RefUpdate branch;
UpdateOp(final RepositoryCache rc, final PostBuildResultResponse mergeInfo) {
repoCache = rc;
in = mergeInfo;
}
boolean update() {
final String loc = in.getDestProjectName() + " " + in.getDestBranchName();
LOG.debug("Updating " + loc);
try {
updateImpl();
return true;
} catch (MergeException ee) {
LOG.error("Error updating " + loc, ee);
return false;
} finally {
if (db != null && rw != null) {
unpinMerge();
}
}
}
private void unpinMerge() {
final String name = MergeOp.mergePinName(in.getRevisionId());
try {
final RefUpdate ru = db.updateRef(name);
ru.setNewObjectId(ru.getOldObjectId());
ru.delete(rw);
} catch (IOException err) {
LOG.warn("Cannot remove " + name, err);
}
}
private void updateImpl() throws MergeException {
openRepository();
openBranch();
parseCommit();
updateBranch();
}
private void openRepository() throws MergeException {
final String name = in.getDestProjectName();
try {
db = repoCache.get(name);
} catch (InvalidRepositoryException notGit) {
final String m = "Repository \"" + name + "\" unknown.";
throw new MergeException(m, notGit);
}
rw = new RevWalk(db);
}
private void openBranch() throws MergeException {
try {
branch = db.updateRef(in.getDestBranchName());
} catch (IOException e) {
throw new MergeException("Cannot open branch", e);
}
}
private void parseCommit() throws MergeException {
try {
newCommit = rw.parseCommit(ObjectId.fromString(in.getRevisionId()));
} catch (IllegalArgumentException e) {
throw new MergeException("Not a commit name: " + in.getRevisionId());
} catch (IOException e) {
throw new MergeException("Not a commit name: " + in.getRevisionId(), e);
}
}
private void updateBranch() throws MergeException {
branch.setForceUpdate(false);
branch.setNewObjectId(newCommit);
branch.setRefLogMessage(newCommit.getShortMessage(), false);
final RefUpdate.Result r;
try {
r = branch.update(rw);
} catch (IOException err) {
final String m = "Failure updating " + branch.getName();
throw new MergeException(m, err);
}
if (r == RefUpdate.Result.NEW) {
} else if (r == RefUpdate.Result.FAST_FORWARD) {
} else if (r == RefUpdate.Result.NO_CHANGE) {
} else {
final String m = "Failure updating " + branch.getName() + ": " + r.name();
throw new MergeException(m);
}
}
}

View File

@@ -0,0 +1,89 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.manager.prune;
import com.google.codereview.internal.PruneBuilds.PruneBuildsRequest;
import com.google.codereview.internal.PruneBuilds.PruneBuildsResponse;
import com.google.codereview.manager.Backend;
import com.google.codereview.manager.StopProcessingException;
import com.google.codereview.rpc.SimpleController;
import com.google.codereview.util.MutableBoolean;
import com.google.protobuf.RpcCallback;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/** Deletes unnecessary builds from the data store. */
public class BuildPruner implements Runnable {
private static final Log LOG = LogFactory.getLog(BuildPruner.class);
private static final PruneBuildsRequest NEXT_REQ =
PruneBuildsRequest.getDefaultInstance();
private final Backend server;
public BuildPruner(final Backend be) {
server = be;
}
public void run() {
try {
runImpl();
} catch (RuntimeException err) {
LOG.fatal("Unexpected runtime failure", err);
throw err;
} catch (Error err) {
LOG.fatal("Unexpected runtime failure", err);
throw err;
}
}
private void runImpl() {
boolean tryAnother;
do {
tryAnother = next();
} while (tryAnother);
}
private boolean next() {
final MutableBoolean tryAnother = new MutableBoolean();
final SimpleController ctrl = new SimpleController();
server.getBuildService().pruneBuilds(ctrl, NEXT_REQ,
new RpcCallback<PruneBuildsResponse>() {
public void run(final PruneBuildsResponse rsp) {
tryAnother.value = prune(rsp);
}
});
if (ctrl.failed()) {
LOG.warn("pruneBuilds failed: " + ctrl.errorText());
tryAnother.value = false;
}
return tryAnother.value;
}
private boolean prune(final PruneBuildsResponse rsp) {
final PruneBuildsResponse.CodeType sc = rsp.getStatusCode();
if (sc == PruneBuildsResponse.CodeType.QUEUE_EMPTY) {
return false;
}
if (sc == PruneBuildsResponse.CodeType.BUILDS_PRUNED) {
return true;
}
throw new StopProcessingException("unknown status " + sc.name());
}
}

View File

@@ -0,0 +1,89 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.manager.prune;
import com.google.codereview.internal.PruneBundles.PruneBundlesRequest;
import com.google.codereview.internal.PruneBundles.PruneBundlesResponse;
import com.google.codereview.manager.Backend;
import com.google.codereview.manager.StopProcessingException;
import com.google.codereview.rpc.SimpleController;
import com.google.codereview.util.MutableBoolean;
import com.google.protobuf.RpcCallback;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/** Deletes invalid bundles from the data store. */
public class BundlePruner implements Runnable {
private static final Log LOG = LogFactory.getLog(BundlePruner.class);
private static final PruneBundlesRequest NEXT_REQ =
PruneBundlesRequest.getDefaultInstance();
private final Backend server;
public BundlePruner(final Backend be) {
server = be;
}
public void run() {
try {
runImpl();
} catch (RuntimeException err) {
LOG.fatal("Unexpected runtime failure", err);
throw err;
} catch (Error err) {
LOG.fatal("Unexpected runtime failure", err);
throw err;
}
}
private void runImpl() {
boolean tryAnother;
do {
tryAnother = next();
} while (tryAnother);
}
private boolean next() {
final MutableBoolean tryAnother = new MutableBoolean();
final SimpleController ctrl = new SimpleController();
server.getBundleStoreService().pruneBundles(ctrl, NEXT_REQ,
new RpcCallback<PruneBundlesResponse>() {
public void run(final PruneBundlesResponse rsp) {
tryAnother.value = prune(rsp);
}
});
if (ctrl.failed()) {
LOG.warn("pruneBundles failed: " + ctrl.errorText());
tryAnother.value = false;
}
return tryAnother.value;
}
private boolean prune(final PruneBundlesResponse rsp) {
final PruneBundlesResponse.CodeType sc = rsp.getStatusCode();
if (sc == PruneBundlesResponse.CodeType.QUEUE_EMPTY) {
return false;
}
if (sc == PruneBundlesResponse.CodeType.BUNDLES_PRUNED) {
return true;
}
throw new StopProcessingException("unknown status " + sc.name());
}
}

View File

@@ -0,0 +1,149 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.manager.unpack;
import static org.spearce.jgit.lib.Constants.encodeASCII;
import com.google.codereview.internal.UploadPatchsetFile.UploadPatchsetFileRequest.StatusType;
import org.spearce.jgit.lib.Constants;
import org.spearce.jgit.lib.ObjectId;
import org.spearce.jgit.lib.ObjectWriter;
import org.spearce.jgit.lib.Repository;
import org.spearce.jgit.lib.Tree;
import org.spearce.jgit.revwalk.RevCommit;
import org.spearce.jgit.util.RawParseUtils;
import java.io.IOException;
/** Parses 'git diff-tree' output into {@link FileDiff} objects. */
class DiffReader {
private static final byte[] GIT_DIFF = encodeASCII("diff --git a/");
private static final byte[] H_DELETED_FILE = encodeASCII("deleted file ");
private static final byte[] H_NEW_FILE = encodeASCII("new file ");
private static final byte[] H_INDEX = encodeASCII("index ");
static final byte[] H_NEWPATH = encodeASCII("+++ b/");
private static final byte[] H_BINARY = encodeASCII("Binary file");
private static final int MAX_PATCH_SIZE = 1024 * 1024; // bytes
static boolean match(final byte[] key, final byte[] line, int offset) {
int remain = line.length - offset;
if (remain < key.length) {
return false;
}
for (int k = 0; k < key.length;) {
if (key[k++] != line[offset++]) {
return false;
}
}
return true;
}
private static String str(final byte[] b, final int off) {
return RawParseUtils.decode(Constants.CHARSET, b, off, b.length);
}
private Process proc;
private RecordInputStream in;
private FileDiff current;
DiffReader(final Repository db, final RevCommit c) throws IOException {
final String baseTree;
if (c.getParentCount() > 0) {
baseTree = c.getParent(0).getTree().getId().name();
} else {
baseTree = new ObjectWriter(db).writeTree(new Tree(db)).name();
}
final String headTree = c.getTree().getId().name();
proc =
Runtime.getRuntime().exec(
new String[] {"git", "--git-dir=.", "diff-tree", "--unified=1",
"-M", "--full-index", baseTree, headTree}, null,
db.getDirectory());
proc.getOutputStream().close();
proc.getErrorStream().close();
in = new RecordInputStream(proc.getInputStream());
}
FileDiff next() throws IOException {
return readOneDiff();
}
private FileDiff readOneDiff() throws IOException {
boolean consume = false;
for (;;) {
final byte[] hdr = in.readRecord('\n');
if (hdr == null) {
final FileDiff prior = current;
current = null;
return prior;
}
if (match(GIT_DIFF, hdr, 0)) {
final FileDiff prior = current;
current = new FileDiff();
current.appendLine(hdr);
// TODO(sop) This can split the old and new names wrong if the
// old name was "f b/c". Until we can do diffs in-core we'll
// just assume nobody uses spaces in filenames.
//
final String hdrStr = str(hdr, 0);
final int newpos = hdrStr.indexOf(" b/");
if (newpos > 0) {
current.setFilename(hdrStr.substring(newpos + 3));
}
if (prior != null) {
return prior;
} else {
continue;
}
}
if (match(H_INDEX, hdr, 0)) {
current.setBaseId(ObjectId.fromString(hdr, H_INDEX.length));
current.setFinalId(ObjectId.fromString(hdr, H_INDEX.length
+ Constants.OBJECT_ID_LENGTH * 2 + 2));
} else if (match(H_NEW_FILE, hdr, 0)) {
current.setStatus(StatusType.ADD);
} else if (match(H_DELETED_FILE, hdr, 0)) {
current.setStatus(StatusType.DELETE);
} else if (match(H_BINARY, hdr, 0)) {
current.setBinary(true);
} else if (match(H_NEWPATH, hdr, 0)) {
current.setFilename(str(hdr, H_NEWPATH.length));
}
if (consume) {
continue;
} else if (current.getPatchSize() + hdr.length >= MAX_PATCH_SIZE) {
current.truncatePatch();
consume = true;
} else {
current.appendLine(hdr);
}
}
}
void close() throws IOException {
in.close();
try {
proc.waitFor();
} catch (InterruptedException ie) {
//
}
}
}

View File

@@ -0,0 +1,119 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.manager.unpack;
import static org.spearce.jgit.lib.Constants.encodeASCII;
import com.google.codereview.internal.UploadPatchsetFile.UploadPatchsetFileRequest.StatusType;
import org.spearce.jgit.lib.AnyObjectId;
import org.spearce.jgit.lib.ObjectId;
import java.util.ArrayList;
import java.util.List;
/** Computed difference for a single file. */
class FileDiff {
private ObjectId baseId;
private ObjectId finalId;
private String filename;
private boolean binary;
private boolean truncated;
private List<byte[]> lines = new ArrayList<byte[]>();
private StatusType status = StatusType.MODIFY;
private int linesSize;
ObjectId getBaseId() {
return baseId;
}
void setBaseId(final AnyObjectId id) {
baseId = id.toObjectId();
}
ObjectId getFinalId() {
return finalId;
}
void setFinalId(final AnyObjectId id) {
finalId = id.toObjectId();
}
String getFilename() {
return filename;
}
void setFilename(final String name) {
filename = name;
}
StatusType getStatus() {
return status;
}
void setStatus(final StatusType t) {
status = t;
}
boolean isBinary() {
return binary;
}
void setBinary(final boolean b) {
binary = b;
}
boolean isTruncated() {
return truncated;
}
void setTruncated(final boolean b) {
truncated = b;
}
byte[] getPatch() {
final byte[] r = new byte[linesSize + lines.size()];
int pos = 0;
for (final byte[] line : lines) {
System.arraycopy(line, 0, r, pos, line.length);
pos += line.length;
r[pos++] = '\n';
}
return r;
}
int getPatchSize() {
return linesSize;
}
void appendLine(final byte[] line) {
lines.add(line);
linesSize += line.length;
}
void truncatePatch() {
final List<byte[]> oldLines = lines;
linesSize = 0;
lines = new ArrayList<byte[]>();
for (final byte[] b : oldLines) {
appendLine(b);
if (DiffReader.match(DiffReader.H_NEWPATH, b, 0)) {
appendLine(encodeASCII("File content is too large to display"));
break;
}
}
}
}

View File

@@ -0,0 +1,256 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.manager.unpack;
import com.google.codereview.internal.CompletePatchset.CompletePatchsetRequest;
import com.google.codereview.internal.CompletePatchset.CompletePatchsetResponse;
import com.google.codereview.internal.UploadPatchsetFile.UploadPatchsetFileRequest;
import com.google.codereview.internal.UploadPatchsetFile.UploadPatchsetFileResponse;
import com.google.codereview.manager.Backend;
import com.google.codereview.manager.StopProcessingException;
import com.google.codereview.rpc.SimpleController;
import com.google.protobuf.ByteString;
import com.google.protobuf.RpcCallback;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.spearce.jgit.lib.Constants;
import org.spearce.jgit.lib.ObjectId;
import org.spearce.jgit.lib.ObjectLoader;
import org.spearce.jgit.lib.Repository;
import org.spearce.jgit.revwalk.RevCommit;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.security.MessageDigest;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
class PatchSetUploader implements Runnable {
private static final Log LOG = LogFactory.getLog(PatchSetUploader.class);
private static final int MAX_DATA_SIZE = 1022 * 1024; // bytes
private static final String EMPTY_BLOB_ID;
private static final ByteString EMPTY_DEFLATE;
static {
final MessageDigest md = Constants.newMessageDigest();
md.update(Constants.encodeASCII("blob 0\0"));
EMPTY_BLOB_ID = ObjectId.fromRaw(md.digest()).name();
EMPTY_DEFLATE = deflate(new byte[0]);
}
private final Backend server;
private final Repository db;
private final RevCommit commit;
private final String commitName;
private final String patchsetKey;
private ByteString.Output compressedFilenames;
private Writer filenameOut;
PatchSetUploader(final Backend be, final Repository sourceRepo,
final RevCommit sourceCommit, final String destPatchsetKey) {
server = be;
db = sourceRepo;
commit = sourceCommit;
commitName = commit.getId().name();
patchsetKey = destPatchsetKey;
}
private String logkey() {
return db.getDirectory().getAbsolutePath() + " " + commitName;
}
public void run() {
LOG.debug(logkey() + " begin");
try {
runImpl();
} catch (RuntimeException e) {
LOG.fatal(logkey() + " failure", e);
} catch (Error e) {
LOG.fatal(logkey() + " failure", e);
}
}
private void runImpl() {
try {
compressedFilenames = ByteString.newOutput();
filenameOut =
new OutputStreamWriter(new DeflaterOutputStream(compressedFilenames,
new Deflater(Deflater.DEFAULT_COMPRESSION)), "UTF-8");
} catch (IOException e) {
LOG.error(logkey() + " cannot initialize filename compression", e);
return;
}
try {
final DiffReader dr = new DiffReader(db, commit);
try {
boolean first = true;
FileDiff file;
while ((file = dr.next()) != null) {
storeOneDiff(file);
if (first) {
first = false;
} else {
filenameOut.write('\0');
}
filenameOut.write(file.getFilename());
}
} finally {
dr.close();
}
filenameOut.close();
} catch (StopProcessingException halt) {
return;
} catch (IOException err) {
LOG.error(logkey() + " diff failed", err);
return;
}
final CompletePatchsetRequest.Builder req;
req = CompletePatchsetRequest.newBuilder();
req.setPatchsetKey(patchsetKey);
req.setCompressedFilenames(compressedFilenames.toByteString());
final SimpleController ctrl = new SimpleController();
server.getChangeService().completePatchset(ctrl, req.build(),
new RpcCallback<CompletePatchsetResponse>() {
public void run(final CompletePatchsetResponse rsp) {
LOG.debug(logkey() + " complete");
}
});
if (ctrl.failed()) {
final String why = ctrl.errorText();
LOG.error(logkey() + " completing failed: " + why);
}
}
private void storeOneDiff(final FileDiff diff) throws StopProcessingException {
final UploadPatchsetFileRequest req = toFileRequest(diff);
final SimpleController ctrl = new SimpleController();
server.getChangeService().uploadPatchsetFile(ctrl, req,
new RpcCallback<UploadPatchsetFileResponse>() {
public void run(final UploadPatchsetFileResponse rsp) {
final UploadPatchsetFileResponse.CodeType sc = rsp.getStatusCode();
final String fn = req.getFileName();
final String pk = req.getPatchsetKey();
if (sc == UploadPatchsetFileResponse.CodeType.CREATED) {
LOG.debug(logkey() + " uploaded " + fn);
} else if (sc == UploadPatchsetFileResponse.CodeType.CLOSED) {
ctrl.setFailed("patchset closed " + pk);
} else if (sc == UploadPatchsetFileResponse.CodeType.UNKNOWN_PATCHSET) {
ctrl.setFailed("patchset unknown " + pk);
} else if (sc == UploadPatchsetFileResponse.CodeType.PATCHING_ERROR) {
ctrl.setFailed("server cannot apply patch");
} else {
ctrl.setFailed("Unknown status " + sc.name() + " " + pk);
}
}
});
if (ctrl.failed()) {
final String fn = req.getFileName();
final String why = ctrl.errorText();
LOG.error(logkey() + " uploading " + fn + " failed: " + why);
throw new StopProcessingException(why);
}
}
private UploadPatchsetFileRequest toFileRequest(final FileDiff diff) {
final UploadPatchsetFileRequest.Builder req;
req = UploadPatchsetFileRequest.newBuilder();
req.setPatchsetKey(patchsetKey);
req.setFileName(diff.getFilename());
req.setStatus(diff.getStatus());
ByteString patchz = deflate(diff.getPatch());
if (!diff.isBinary() && !diff.isTruncated()) {
final ObjectId baseId = diff.getBaseId();
if (baseId == null || ObjectId.equals(baseId, ObjectId.zeroId())) {
req.setBaseId(EMPTY_BLOB_ID);
req.setBaseZ(EMPTY_DEFLATE);
} else {
try {
final ObjectLoader ldr = db.openBlob(baseId);
if (ldr == null) {
LOG.fatal(logkey() + " missing " + baseId.name());
throw new StopProcessingException("No " + baseId.name());
}
final ByteString basez = deflate(ldr.getCachedBytes());
if (basez.size() + patchz.size() > MAX_DATA_SIZE) {
diff.truncatePatch();
patchz = deflate(diff.getPatch());
} else {
req.setBaseId(baseId.name());
req.setBaseZ(basez);
}
} catch (IOException err) {
LOG.fatal(logkey() + " cannot read base " + baseId.name(), err);
throw new StopProcessingException("No " + baseId.name());
}
}
}
if (!diff.isBinary() && !diff.isTruncated()) {
final ObjectId finalId = diff.getFinalId();
if (finalId == null || ObjectId.equals(finalId, ObjectId.zeroId())) {
req.setFinalId(EMPTY_BLOB_ID);
} else {
req.setFinalId(finalId.name());
}
}
req.setPatchZ(patchz);
req.setPatchId(hashOfInflated(patchz));
return req.build();
}
private static ByteString deflate(final byte[] buf) {
final ByteString.Output r = ByteString.newOutput();
final DeflaterOutputStream out = new DeflaterOutputStream(r);
try {
out.write(buf);
out.close();
} catch (IOException err) {
// This should not happen.
throw new StopProcessingException("Unexpected IO error", err);
}
return r.toByteString();
}
private static String hashOfInflated(final ByteString in) {
final MessageDigest md = Constants.newMessageDigest();
final byte[] tmp = new byte[512];
final InflaterInputStream iis = new InflaterInputStream(in.newInput());
int cnt;
try {
while ((cnt = iis.read(tmp)) > 0) {
md.update(tmp, 0, cnt);
}
} catch (IOException err) {
// This should not happen.
throw new StopProcessingException("Unexpected IO error", err);
}
return ObjectId.fromRaw(md.digest()).name();
}
}

View File

@@ -0,0 +1,118 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.manager.unpack;
import com.google.codereview.internal.NextReceivedBundle.NextReceivedBundleRequest;
import com.google.codereview.internal.NextReceivedBundle.NextReceivedBundleResponse;
import com.google.codereview.internal.UpdateReceivedBundle.UpdateReceivedBundleRequest;
import com.google.codereview.internal.UpdateReceivedBundle.UpdateReceivedBundleResponse;
import com.google.codereview.manager.Backend;
import com.google.codereview.manager.StopProcessingException;
import com.google.codereview.rpc.SimpleController;
import com.google.codereview.util.MutableBoolean;
import com.google.protobuf.RpcCallback;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/** Obtains newly received bundles and unpacks them into Git. */
public class ReceivedBundleUnpacker implements Runnable {
private static final Log LOG =
LogFactory.getLog(ReceivedBundleUnpacker.class);
private static final NextReceivedBundleRequest NEXT_REQ =
NextReceivedBundleRequest.getDefaultInstance();
private final Backend server;
public ReceivedBundleUnpacker(final Backend be) {
server = be;
}
public void run() {
try {
runImpl();
} catch (RuntimeException err) {
LOG.fatal("Unexpected runtime failure", err);
throw err;
} catch (Error err) {
LOG.fatal("Unexpected runtime failure", err);
throw err;
}
}
private void runImpl() {
boolean tryAnother;
do {
tryAnother = next();
} while (tryAnother);
}
private boolean next() {
final MutableBoolean tryAnother = new MutableBoolean();
final SimpleController ctrl = new SimpleController();
server.getBundleStoreService().nextReceivedBundle(ctrl, NEXT_REQ,
new RpcCallback<NextReceivedBundleResponse>() {
public void run(final NextReceivedBundleResponse rsp) {
tryAnother.value = unpack(rsp);
}
});
if (ctrl.failed()) {
LOG.warn("nextReceivedBundle failed: " + ctrl.errorText());
tryAnother.value = false;
}
return tryAnother.value;
}
private boolean unpack(final NextReceivedBundleResponse rsp) {
final NextReceivedBundleResponse.CodeType sc = rsp.getStatusCode();
if (sc == NextReceivedBundleResponse.CodeType.QUEUE_EMPTY) {
return false;
}
if (sc == NextReceivedBundleResponse.CodeType.BUNDLE_AVAILABLE) {
send(unpackImpl(rsp));
return true;
}
throw new StopProcessingException("unknown status " + sc.name());
}
protected UpdateReceivedBundleRequest unpackImpl(
final NextReceivedBundleResponse rsp) {
return new UnpackBundleOp(server, rsp).unpack();
}
private void send(final UpdateReceivedBundleRequest req) {
final String key = req.getBundleKey();
final String sc = req.getStatusCode().name();
LOG.debug("Bundle " + key + ", status " + sc);
final SimpleController ctrl = new SimpleController();
server.getBundleStoreService().updateReceivedBundle(ctrl, req,
new RpcCallback<UpdateReceivedBundleResponse>() {
public void run(final UpdateReceivedBundleResponse rsp) {
final UpdateReceivedBundleResponse.CodeType sc =
rsp.getStatusCode();
if (sc != UpdateReceivedBundleResponse.CodeType.UPDATED) {
ctrl.setFailed(sc.name());
}
}
});
if (ctrl.failed()) {
LOG.error("Updating bundle " + key + " failed: " + ctrl.errorText());
}
}
}

View File

@@ -0,0 +1,144 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.manager.unpack;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
/** Buffering input stream which can read entire lines. */
class RecordInputStream extends InputStream {
private InputStream in;
private byte[] buf;
private int pos;
private int end;
RecordInputStream(final InputStream in) {
this.in = in;
buf = new byte[4096];
}
@Override
public void close() throws IOException {
try {
in.close();
} finally {
in = null;
buf = null;
}
}
private boolean fill() throws IOException {
pos = 0;
end = in.read(buf, pos, buf.length);
if (end < 0) {
end = 0;
return false;
}
return true;
}
@Override
public int read() throws IOException {
if (pos == end && !fill()) {
return -1;
}
return buf[pos++];
}
@Override
public int read(final byte[] b, final int off, final int len)
throws IOException {
if (pos == end && !fill()) {
return -1;
}
final int cnt = Math.min(len, end - pos);
System.arraycopy(buf, pos, b, off, cnt);
pos += cnt;
return cnt;
}
/**
* Read a record terminated by the separator byte.
*
* @param sep byte which delimits the end of a record.
* @return record content, with the separator removed from the end. The empty
* array indicates an empty record; null indicates stream EOF.
* @throws IOException the stream could not be read from.
*/
byte[] readRecord(final int sep) throws IOException {
if (pos == end && !fill()) {
return null;
}
byte[] line = fastReadRecord(sep);
if (line != null) {
return line;
}
if (end - pos <= buf.length / 2) {
int cnt = end - pos;
System.arraycopy(buf, pos, buf, 0, cnt);
pos = 0;
end = cnt;
cnt = in.read(buf, end, buf.length - end);
if (cnt < 0) {
final byte[] r = new byte[end];
System.arraycopy(buf, 0, r, 0, end);
pos = end = 0;
return r;
}
end += cnt;
line = fastReadRecord(sep);
if (line != null) {
return line;
}
}
final ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(buf, pos, end - pos);
for (;;) {
if (!fill()) {
return out.toByteArray();
}
for (int lf = pos; lf < end; lf++) {
if (buf[lf] == sep) {
out.write(buf, pos, lf - pos);
pos = lf + 1;
return out.toByteArray();
}
}
out.write(buf, pos, end - pos);
pos = end;
}
}
private byte[] fastReadRecord(final int sep) {
for (int lf = pos; lf < end; lf++) {
if (buf[lf] == sep) {
final int cnt = lf - pos;
final byte[] r = new byte[cnt];
System.arraycopy(buf, pos, r, 0, cnt);
pos = lf + 1;
return r;
}
}
return null;
}
}

View File

@@ -0,0 +1,331 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.manager.unpack;
import com.google.codereview.internal.NextReceivedBundle.BundleSegmentRequest;
import com.google.codereview.internal.NextReceivedBundle.BundleSegmentResponse;
import com.google.codereview.internal.NextReceivedBundle.NextReceivedBundleResponse;
import com.google.codereview.internal.SubmitChange.SubmitChangeRequest;
import com.google.codereview.internal.SubmitChange.SubmitChangeResponse;
import com.google.codereview.internal.UpdateReceivedBundle.UpdateReceivedBundleRequest;
import com.google.codereview.internal.UpdateReceivedBundle.UpdateReceivedBundleRequest.CodeType;
import com.google.codereview.manager.Backend;
import com.google.codereview.manager.InvalidRepositoryException;
import com.google.codereview.rpc.SimpleController;
import com.google.codereview.util.GitMetaUtil;
import com.google.codereview.util.MutableBoolean;
import com.google.protobuf.RpcCallback;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.spearce.jgit.errors.MissingBundlePrerequisiteException;
import org.spearce.jgit.lib.NullProgressMonitor;
import org.spearce.jgit.lib.ObjectId;
import org.spearce.jgit.lib.Ref;
import org.spearce.jgit.lib.RefUpdate;
import org.spearce.jgit.lib.Repository;
import org.spearce.jgit.revwalk.RevCommit;
import org.spearce.jgit.revwalk.RevSort;
import org.spearce.jgit.revwalk.RevWalk;
import org.spearce.jgit.transport.FetchConnection;
import org.spearce.jgit.transport.Transport;
import org.spearce.jgit.transport.TransportBundleStream;
import org.spearce.jgit.transport.URIish;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/** Unpacks a bundle and imports the commits into the code review system. */
class UnpackBundleOp {
private static final Log LOG = LogFactory.getLog(UnpackBundleOp.class);
private static String refOf(final int changeId, final int patchsetId) {
final StringBuilder b = new StringBuilder();
final int dh = changeId % 100;
b.append("refs/changes/");
if (dh < 10) {
b.append('0');
}
b.append(dh);
b.append('/');
b.append(changeId);
b.append('/');
b.append(patchsetId);
return b.toString();
}
private final Backend server;
private final NextReceivedBundleResponse in;
private Repository db;
private ObjectId tip;
private int changeId;
private int patchsetId;
private String patchsetKey;
UnpackBundleOp(final Backend be, final NextReceivedBundleResponse bundleInfo) {
server = be;
in = bundleInfo;
}
UpdateReceivedBundleRequest unpack() {
final UpdateReceivedBundleRequest.Builder update;
update = UpdateReceivedBundleRequest.newBuilder();
update.setBundleKey(in.getBundleKey());
try {
unpackImpl();
update.setStatusCode(CodeType.UNPACKED_OK);
} catch (UnpackException ue) {
update.setStatusCode(ue.status);
update.setErrorDetails(ue.details);
LOG.error("Unpacking bundle " + in.getBundleKey() + " failed.", ue);
}
return update.build();
}
private void unpackImpl() throws UnpackException {
LOG.debug("Unpacking bundle " + in.getBundleKey());
openRepository();
unpackTip();
createChanges(newCommits());
}
private void openRepository() throws UnpackException {
try {
db = server.getRepositoryCache().get(in.getDestProject());
} catch (InvalidRepositoryException notGit) {
final String m = "Repository \"" + in.getDestProject() + "\" unknown.";
throw new UnpackException(CodeType.UNKNOWN_PROJECT, m, notGit);
}
}
private void unpackTip() throws UnpackException {
final Transport bundleTransport = openBundle();
try {
final FetchConnection fc = bundleTransport.openFetch();
if (fc.getRefs().size() > 1) {
final String m = "Bundle contains more than one head";
throw new UnpackException(CodeType.INVALID_BUNDLE, m);
} else if (fc.getRefs().size() == 0) {
final String m = "Bundle contains no heads";
throw new UnpackException(CodeType.INVALID_BUNDLE, m);
}
fc.fetch(NullProgressMonitor.INSTANCE, fc.getRefs());
tip = fc.getRefs().iterator().next().getObjectId();
LOG.debug("Unpacked " + tip.name() + " from " + in.getBundleKey());
} catch (MissingBundlePrerequisiteException e) {
throw new UnpackException(CodeType.MISSING_BASE, e.getMessage(), e);
} catch (IOException readError) {
final String m = "Processing the bundle stream failed";
throw new UnpackException(CodeType.INVALID_BUNDLE, m, readError);
} finally {
bundleTransport.close();
}
}
private List<RevCommit> newCommits() throws UnpackException {
final RevWalk rw = new RevWalk(db);
rw.sort(RevSort.REVERSE, true);
rw.sort(RevSort.TOPO, true);
try {
rw.markStart(rw.parseCommit(tip));
} catch (IOException e) {
final String m = "Chain " + tip.name() + " is corrupt";
throw new UnpackException(CodeType.INVALID_BUNDLE, m, e);
}
for (final Ref r : db.getAllRefs().values()) {
try {
rw.markUninteresting(rw.parseCommit(r.getObjectId()));
} catch (IOException err) {
final String m = "Local ref is invalid";
throw new UnpackException(CodeType.SUSPEND_BUNDLE, m, err);
}
}
try {
final List<RevCommit> newList = new ArrayList<RevCommit>();
RevCommit c;
while ((c = rw.next()) != null) {
// Ensure the parents are parsed so we know the parent's tree.
// We need that later to compute a difference.
//
for (final RevCommit p : c.getParents()) {
rw.parse(p);
}
newList.add(c);
}
return newList;
} catch (IOException e) {
final String m = "Chain " + tip.name() + " is corrupt";
throw new UnpackException(CodeType.INVALID_BUNDLE, m, e);
}
}
private void createChanges(final List<RevCommit> newCommits)
throws UnpackException {
for (final RevCommit c : newCommits) {
if (submitChange(c)) {
createChangeRef(c);
server.asyncExec(new PatchSetUploader(server, db, c, patchsetKey));
}
}
}
private boolean submitChange(final RevCommit c) throws UnpackException {
final SubmitChangeRequest.Builder req = SubmitChangeRequest.newBuilder();
req.setOwner(in.getOwner());
req.setDestBranchKey(in.getDestBranchKey());
req.setCommit(GitMetaUtil.toGitCommit(c));
final MutableBoolean continueCreation = new MutableBoolean();
final SimpleController ctrl = new SimpleController();
server.getChangeService().submitChange(ctrl, req.build(),
new RpcCallback<SubmitChangeResponse>() {
public void run(final SubmitChangeResponse rsp) {
final SubmitChangeResponse.CodeType sc = rsp.getStatusCode();
if (sc == SubmitChangeResponse.CodeType.CREATED) {
changeId = rsp.getChangeId();
patchsetId = rsp.getPatchsetId();
patchsetKey = rsp.getPatchsetKey();
continueCreation.value = true;
LOG.debug("Commit " + c.getId().name() + " is change " + changeId
+ " patchset " + patchsetId);
} else if (sc == SubmitChangeResponse.CodeType.PATCHSET_EXISTS) {
LOG.debug("Commit " + c.getId().name() + " exists in data store");
} else {
ctrl.setFailed("Unknown status " + sc.name());
}
}
});
if (ctrl.failed()) {
throw new UnpackException(CodeType.SUSPEND_BUNDLE, ctrl.errorText());
}
return continueCreation.value;
}
private void createChangeRef(final RevCommit c) throws UnpackException {
final String name = refOf(changeId, patchsetId);
final RefUpdate.Result r;
try {
final RefUpdate u = db.updateRef(name);
u.setNewObjectId(c.getId());
u.setForceUpdate(true);
u.setRefLogMessage("Change submitted", false);
r = u.update();
} catch (IOException err) {
final String m = "Failure creating " + name;
throw new UnpackException(CodeType.SUSPEND_BUNDLE, m, err);
}
if (r == RefUpdate.Result.NEW) {
} else if (r == RefUpdate.Result.FAST_FORWARD) {
} else if (r == RefUpdate.Result.FORCED) {
} else if (r == RefUpdate.Result.NO_CHANGE) {
} else {
final String m = "Failure creating " + name + ": " + r.name();
throw new UnpackException(CodeType.SUSPEND_BUNDLE, m);
}
}
private TransportBundleStream openBundle() {
final URIish uri = makeURI(in);
return new TransportBundleStream(db, uri, new BundleStream());
}
private static URIish makeURI(final NextReceivedBundleResponse in) {
URIish u = new URIish();
u = u.setScheme("codereview-bundle");
u = u.setPath(in.getBundleKey());
return u;
}
private class BundleStream extends InputStream {
private int segmentId = 1;
private final int totalSegments = in.getNSegments();
private InputStream stream = in.getBundleData().newInput();
@Override
public int read() throws IOException {
for (;;) {
if (stream == null) {
return -1;
}
final int r = stream.read();
if (r < 0) {
openNextStream();
} else {
return r;
}
}
}
@Override
public int read(final byte[] b, final int off, final int len)
throws IOException {
for (;;) {
if (stream == null) {
return -1;
}
final int r = stream.read(b, off, len);
if (r < 0) {
openNextStream();
} else {
return r;
}
}
}
private void openNextStream() throws IOException {
if (segmentId >= totalSegments) {
stream = null;
return;
}
segmentId++;
final BundleSegmentRequest.Builder req;
req = BundleSegmentRequest.newBuilder();
req.setBundleKey(in.getBundleKey());
req.setSegmentId(segmentId);
final SimpleController ctrl = new SimpleController();
server.getBundleStoreService().bundleSegment(ctrl, req.build(),
new RpcCallback<BundleSegmentResponse>() {
public void run(final BundleSegmentResponse rsp) {
final BundleSegmentResponse.CodeType sc = rsp.getStatusCode();
if (sc == BundleSegmentResponse.CodeType.DATA) {
stream = rsp.getBundleData().newInput();
} else {
ctrl.setFailed(sc.name());
}
}
});
if (ctrl.failed()) {
throw new IOException("Bundle" + in.getBundleKey() + " segment "
+ segmentId + " unavailable: " + ctrl.errorText());
}
}
}
}

View File

@@ -0,0 +1,35 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.manager.unpack;
import com.google.codereview.internal.UpdateReceivedBundle.UpdateReceivedBundleRequest.CodeType;
/** Indicates unpacking failed and the bundle cannot be processed. */
class UnpackException extends Exception {
final CodeType status;
final String details;
UnpackException(final CodeType s, final String msg) {
super(msg, null);
status = s;
details = msg;
}
UnpackException(final CodeType s, final String msg, final Throwable why) {
super(msg, why);
status = s;
details = msg;
}
}

View File

@@ -0,0 +1,441 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.rpc;
import com.google.codereview.NeedRetry.RetryRequestLaterResponse;
import com.google.protobuf.Message;
import com.google.protobuf.RpcCallback;
import com.google.protobuf.RpcChannel;
import com.google.protobuf.RpcController;
import com.google.protobuf.Descriptors.MethodDescriptor;
import com.google.protobuf.Message.Builder;
import org.apache.commons.httpclient.Cookie;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
import org.spearce.jgit.util.Base64;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.URL;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.InflaterInputStream;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
/**
* Simple protocol buffer RPC embedded inside HTTP POST.
* <p>
* Request messages are POSTed to "/proto/$serviceName/$methodName".
* Authentication for the user is always obtained via the user's Google Account,
* with session cookies automatically renewing themselves if expired.
* <p>
* This implementation is thread safe. Callers may use a single HttpRpc over
* multiple threads in order to pool and reuse connections across threads.
* <p>
* All remote calls are performed synchronously. This is a simplification within
* the implementation, as it is unlikely the remote side can support parallel
* requests over the same top level entities.
* <p>
* Authentication is tied to Google accounts.
*/
public class HttpRpc implements RpcChannel {
private static final String ENC = "UTF-8";
private static final Message RETRY_LATER =
RetryRequestLaterResponse.getDefaultInstance();
/** URL required to authenticate the user into their Google account. */
private static final URI LOGIN;
/**
* Maximum number of connections we should make to 'production' servers.
* <p>
* If the servers are running on Google owned infrastructure such as the
* www.google.com or Google App Engine we can generally push at least one or
* two connections per thread without causing any trouble.
*/
private static final int MAX_PROD_CONNS = 10;
/** Special token used in authentication to verify we were successful. */
private static final String AUTH_CONTINUE_TOKEN = "http://localhost/";
static {
try {
LOGIN = new URI("https://www.google.com/accounts/ClientLogin", true);
} catch (URIException err) {
final ExceptionInInitializerError le;
le = new ExceptionInInitializerError("Bad LOGIN URL");
le.initCause(err);
throw le;
}
}
private final HttpClient http;
private final URI server;
private final String userEmail;
private final String userPassword;
private final byte[] apiKey;
private boolean authenticated;
/**
* Create a new RPC implementation.
*
* @param host protocol, server host, and port number. The path component is
* ignored as requests are made absolute ('/proto/$service/$method').
* @param user name to authenticate to Google with. This is very likely the
* user's email address.
* @param pass password for the user's Google account.
* @param apiKeyStr (optional) the internal API key. If supplied all requests
* will be signed with the key, in case the request requires the
* internal API authentication.
*/
public HttpRpc(final URL host, final String user, final String pass,
final String apiKeyStr) {
http = new HttpClient(new MultiThreadedHttpConnectionManager());
userEmail = user;
userPassword = pass;
apiKey = apiKeyStr != null ? Base64.decode(apiKeyStr) : null;
final HttpConnectionManagerParams params =
http.getHttpConnectionManager().getParams();
try {
server = new URI(host.toExternalForm(), true);
final HostConfiguration serverConfig = new HostConfiguration();
serverConfig.setHost(server);
// The development server cannot do normal authentication so we must
// fake it here by injecting a development server specific cookie.
//
if ("localhost".equals(server.getHost())) {
if (userEmail != null) {
final Cookie c = new Cookie();
c.setDomain(server.getHost());
c.setPath("/");
c.setName("dev_appserver_login");
c.setValue(userEmail + ":False");
http.getState().addCookie(c);
}
authenticated = true;
params.setMaxConnectionsPerHost(serverConfig, 1);
} else {
params.setMaxConnectionsPerHost(serverConfig, MAX_PROD_CONNS);
}
final HostConfiguration clientLoginConfig = new HostConfiguration();
clientLoginConfig.setHost(LOGIN);
params.setMaxConnectionsPerHost(clientLoginConfig, MAX_PROD_CONNS);
} catch (URIException e) {
throw new IllegalArgumentException("Bad URL: " + host.toExternalForm(), e);
}
}
public void callMethod(final MethodDescriptor method,
final RpcController controller, final Message request,
final Message responsePrototype, final RpcCallback<Message> done) {
final URI uri;
final String svcName = method.getService().getName();
final String methodName = method.getName();
try {
uri = new URI(server, "/proto/" + svcName + "/" + methodName, true);
} catch (URIException urlError) {
controller.setFailed(urlError.toString());
return;
}
final MessageRequestEntity entity;
try {
entity = new MessageRequestEntity(request);
} catch (IOException err) {
controller.setFailed("cannot encode request: " + err);
return;
}
if (controller instanceof SimpleController) {
((SimpleController) controller).markFirstRequest();
}
for (;;) {
final PostMethod conn = new PostMethod();
Message responseMessage = null;
try {
conn.setDoAuthentication(false);
conn.setURI(uri);
conn.setRequestEntity(entity);
ensureAuthenticated();
for (int attempts = 1; responseMessage == null; attempts++) {
if (apiKey != null) {
sign(conn);
}
final int status = http.executeMethod(conn);
if (HttpStatus.SC_OK == status) {
responseMessage = parseResponse(conn, responsePrototype);
} else if (HttpStatus.SC_UNAUTHORIZED == status) {
if (attempts == 2) {
responseMessage = null;
controller.setFailed("Authentication required");
break;
}
synchronized (this) {
authenticated = false;
ensureAuthenticated();
}
} else if (HttpStatus.SC_FORBIDDEN == status
|| HttpStatus.SC_NOT_FOUND == status
|| HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE == status) {
String body = conn.getResponseBodyAsString(60);
if (body.indexOf('\n') > 0)
body = body.substring(0, body.indexOf('\n'));
responseMessage = null;
controller.setFailed(svcName + ": " + body);
break;
} else {
responseMessage = null;
controller.setFailed("HTTP failure: " + status);
break;
}
}
} catch (ConnectException ce) {
final String why = ce.getMessage() + " " + server;
if (controller instanceof SimpleController) {
final SimpleController sc = (SimpleController) controller;
if (sc.retry()) {
continue;
} else {
controller.setFailed(controller.errorText() + ": " + why);
break;
}
} else {
controller.setFailed(why);
break;
}
} catch (IOException err) {
controller.setFailed("HTTP failure: " + err.getMessage());
break;
} finally {
conn.releaseConnection();
}
if (responseMessage instanceof RetryRequestLaterResponse) {
if (controller instanceof SimpleController) {
final SimpleController sc = (SimpleController) controller;
if (sc.retry()) {
continue;
}
}
controller.setFailed("remote requested retry");
break;
}
if (responseMessage != null) {
done.run(responseMessage);
}
break;
}
entity.destroy();
}
private Message parseResponse(final PostMethod conn,
final Message responsePrototype) throws IOException {
final Header typeHeader = conn.getResponseHeader("Content-Type");
if (typeHeader == null) {
throw new IOException("No Content-Type in response");
}
final String[] typeTokens = typeHeader.getValue().split("; ");
if (!MessageRequestEntity.TYPE.equals(typeTokens[0])) {
throw new IOException("Invalid Content-Type " + typeHeader.getValue());
}
String rspName = null;
String rspCompress = null;
for (int i = 1; i < typeTokens.length; i++) {
final String tok = typeTokens[i];
if (tok.startsWith("name=")) {
rspName = tok.substring("name=".length());
} else if (tok.startsWith("compress=")) {
rspCompress = tok.substring("compress=".length());
}
}
String expName = responsePrototype.getDescriptorForType().getFullName();
final Builder builder;
if (expName.equals(rspName)) {
builder = responsePrototype.newBuilderForType();
} else if (rspName.equals(RETRY_LATER.getDescriptorForType().getFullName())) {
builder = RETRY_LATER.newBuilderForType();
} else {
throw new IOException("Expected a " + expName + " got " + rspName);
}
InputStream in = conn.getResponseBodyAsStream();
if ("deflate".equals(rspCompress)) {
in = new InflaterInputStream(in);
} else if (rspCompress != null) {
throw new IOException("Unsupported compression " + rspCompress);
}
try {
builder.mergeFrom(in);
} finally {
in.close();
}
return builder.build();
}
private synchronized void ensureAuthenticated() throws IOException {
if (!authenticated) {
if (userEmail != null && userPassword != null) {
computeAuthCookie(computeAuthToken());
}
authenticated = true;
}
}
private String computeAuthToken() throws IOException {
final PostMethod conn = new PostMethod();
try {
conn.setDoAuthentication(false);
conn.setURI(LOGIN);
conn.setRequestBody(new NameValuePair[] {
new NameValuePair("Email", userEmail),
new NameValuePair("Passwd", userPassword),
new NameValuePair("service", "ah"),
new NameValuePair("source", "gerrit-codereview-manager"),
new NameValuePair("accountType", "HOSTED_OR_GOOGLE")});
final int status = http.executeMethod(conn);
final Map<String, String> rsp =
splitPairs(conn.getResponseBodyAsStream());
if (status != HttpStatus.SC_OK) {
throw new IOException("Authentication failed: " + rsp.get("Error"));
}
return rsp.get("Auth");
} finally {
conn.releaseConnection();
}
}
private void computeAuthCookie(final String authToken) throws IOException {
final GetMethod conn = new GetMethod();
try {
conn.setFollowRedirects(false);
conn.setDoAuthentication(false);
conn.setURI(new URI(server, "/_ah/login", true));
conn.setQueryString(new NameValuePair[] {
new NameValuePair("continue", AUTH_CONTINUE_TOKEN),
new NameValuePair("auth", authToken),});
final int status = http.executeMethod(conn);
final Header location = conn.getResponseHeader("Location");
if (status != HttpStatus.SC_MOVED_TEMPORARILY || location == null
|| !AUTH_CONTINUE_TOKEN.equals(location.getValue())) {
throw new IOException("Obtaining authentication cookie failed: "
+ status);
}
} finally {
conn.releaseConnection();
}
}
private Map<String, String> splitPairs(final InputStream cin)
throws IOException {
final HashMap<String, String> rsp = new HashMap<String, String>();
final BufferedReader in =
new BufferedReader(new InputStreamReader(cin, ENC));
try {
String line;
while ((line = in.readLine()) != null) {
final int eq = line.indexOf('=');
if (eq >= 0) {
rsp.put(line.substring(0, eq), line.substring(eq + 1));
}
}
} finally {
in.close();
}
return rsp;
}
private void sign(final PostMethod conn) throws IOException {
conn.setRequestHeader("X-Date-UTC", xDateUTC());
final StringBuilder b = new StringBuilder();
b.append("POST ");
b.append(conn.getPath());
b.append('\n');
b.append("X-Date-UTC: ");
b.append(conn.getRequestHeader("X-Date-UTC").getValue());
b.append('\n');
b.append("Content-Type: ");
b.append(conn.getRequestEntity().getContentType());
b.append('\n');
b.append('\n');
final String sec;
try {
final Mac m = Mac.getInstance("HmacSHA1");
m.init(new SecretKeySpec(apiKey, "HmacSHA1"));
m.update(b.toString().getBytes("UTF-8"));
conn.getRequestEntity().writeRequest(new OutputStream() {
@Override
public void write(byte[] b, int off, int len) {
m.update(b, off, len);
}
@Override
public void write(int b) {
m.update((byte) b);
}
});
sec = Base64.encodeBytes(m.doFinal());
} catch (NoSuchAlgorithmException e) {
throw new IOException("No HmacSHA1 support:" + e.getMessage());
} catch (InvalidKeyException e) {
throw new IOException("Invalid key: " + e.getMessage());
}
conn.setRequestHeader("Authorization", "proto :" + sec);
}
private String xDateUTC() {
return String.valueOf(System.currentTimeMillis() / 1000L);
}
}

View File

@@ -0,0 +1,88 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.rpc;
import com.google.protobuf.Message;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.spearce.jgit.lib.NullProgressMonitor;
import org.spearce.jgit.util.TemporaryBuffer;
import java.io.IOException;
import java.io.OutputStream;
import java.util.zip.DeflaterOutputStream;
/**
* Formats a protocol buffer Message into an HTTP request body.
* <p>
* The compressed binary serialized form is always used.
*/
public class MessageRequestEntity implements RequestEntity {
/** Content-Type for a protocol buffer. */
public static final String TYPE = "application/x-google-protobuf";
private final Message msg;
private final String name;
private final TemporaryBuffer temp;
/**
* Create a new request for a single message.
*
* @param m the message to serialize and transmit.
* @throws IOException the message could not be compressed into temporary
* storage. The local file system may be full, or the message is
* unable to compress itself.
*/
public MessageRequestEntity(final Message m) throws IOException {
msg = m;
name = msg.getDescriptorForType().getFullName();
temp = new TemporaryBuffer();
final DeflaterOutputStream dos = new DeflaterOutputStream(temp);
try {
msg.writeTo(dos);
} finally {
dos.close();
}
}
public long getContentLength() {
return (int) temp.length();
}
public String getContentType() {
return TYPE + "; name=" + name + "; compress=deflate";
}
public boolean isRepeatable() {
return true;
}
public void writeRequest(final OutputStream out) throws IOException {
temp.writeTo(out, NullProgressMonitor.INSTANCE);
}
public void destroy() {
temp.destroy();
}
@Override
public String toString() {
final StringBuilder r = new StringBuilder();
r.append(name);
r.append('\n');
r.append(msg.toString());
return r.toString();
}
}

View File

@@ -0,0 +1,97 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.rpc;
import com.google.protobuf.RpcCallback;
import com.google.protobuf.RpcController;
import java.util.Random;
/**
* A simple controller which does not support cancellation.
* <p>
* Users should check {@link #failed()} to determine if a call failed.
*/
public class SimpleController implements RpcController {
private static final int SLEEP_MIN = 100; // milliseconds
private static final int SLEEP_MAX = 2 * 60 * 1000; // milliseconds
private static final int MAX_ATTEMPT_PERIOD = 30 * 60 * 1000; // milliseconds
private static final ThreadLocal<Random> SLEEP_RNG =
new ThreadLocal<Random>() {
@Override
protected Random initialValue() {
return new Random();
}
};
private static int waitTime() {
return SLEEP_MIN + SLEEP_RNG.get().nextInt(SLEEP_MAX - SLEEP_MIN);
}
private String errorText;
private long start;
public String errorText() {
return errorText;
}
public boolean failed() {
return errorText != null;
}
public void reset() {
errorText = null;
}
public void setFailed(final String reason) {
errorText = reason;
}
public void startCancel() {
throw new UnsupportedOperationException();
}
public boolean isCanceled() {
return false;
}
public void notifyOnCancel(final RpcCallback<Object> callback) {
throw new UnsupportedOperationException();
}
boolean retry() {
if (System.currentTimeMillis() - start < MAX_ATTEMPT_PERIOD) {
try {
Thread.sleep(waitTime());
} catch (InterruptedException ie) {
// Just let the thread continue anyway.
}
return true;
}
if (start == 0) {
setFailed("retry not supported by the RpcChannel");
return false;
}
final int s = MAX_ATTEMPT_PERIOD / 1000;
setFailed("cannot complete in <" + s + " seconds");
return false;
}
void markFirstRequest() {
start = System.currentTimeMillis();
}
}

View File

@@ -0,0 +1,73 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.util;
import com.google.codereview.internal.GitMeta.GitCommit;
import com.google.codereview.internal.GitMeta.GitPerson;
import org.spearce.jgit.lib.PersonIdent;
import org.spearce.jgit.lib.Repository;
import org.spearce.jgit.revwalk.RevCommit;
import java.io.File;
import java.io.IOException;
public class GitMetaUtil {
public static GitCommit toGitCommit(final RevCommit c) {
final GitCommit.Builder b = GitCommit.newBuilder();
b.setId(c.getId().name());
for (final RevCommit p : c.getParents())
b.addParentId(p.getId().name());
b.setAuthor(toGitPerson(c.getAuthorIdent()));
b.setCommitter(toGitPerson(c.getCommitterIdent()));
b.setMessage(c.getFullMessage());
b.setSubject(c.getShortMessage());
return b.build();
}
public static GitPerson toGitPerson(final PersonIdent who) {
final GitPerson.Builder b = GitPerson.newBuilder();
b.setName(who.getName());
b.setEmail(who.getEmailAddress());
b.setWhen((int) (who.getWhen().getTime() / 1000L));
b.setTz(who.getTimeZoneOffset());
return b.build();
}
public static boolean isGitRepository(final File gitdir) {
return new File(gitdir, "config").isFile()
&& new File(gitdir, "HEAD").isFile()
&& new File(gitdir, "objects").isDirectory()
&& new File(gitdir, "refs/heads").isDirectory();
}
public static Repository open(final File gitdir) throws IOException {
if (isGitRepository(gitdir)) {
return new Repository(gitdir);
}
if (isGitRepository(new File(gitdir, ".git"))) {
return new Repository(new File(gitdir, ".git"));
}
final String name = gitdir.getName();
final File parent = gitdir.getParentFile();
if (isGitRepository(new File(parent, name + ".git"))) {
return new Repository(new File(parent, name + ".git"));
}
return null;
}
}

View File

@@ -0,0 +1,33 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.util;
import com.google.protobuf.RpcCallback;
/**
* A simple boolean which can be modified.
* <p>
* This can be useful in a {@link RpcCallback}.
*/
public class MutableBoolean {
public boolean value;
public MutableBoolean() {
}
public MutableBoolean(final boolean initial) {
value = initial;
}
}

View File

@@ -0,0 +1,270 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.githacks;
import com.google.codereview.util.GitMetaUtil;
import org.spearce.jgit.lib.Constants;
import org.spearce.jgit.lib.LockFile;
import org.spearce.jgit.lib.ObjectId;
import org.spearce.jgit.lib.PackWriter;
import org.spearce.jgit.lib.Ref;
import org.spearce.jgit.lib.RefUpdate;
import org.spearce.jgit.lib.Repository;
import org.spearce.jgit.lib.TextProgressMonitor;
import org.spearce.jgit.revwalk.RevCommit;
import org.spearce.jgit.revwalk.RevObject;
import org.spearce.jgit.revwalk.RevWalk;
import org.spearce.jgit.treewalk.TreeWalk;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Creates a broken shallow repository with a populated assumed base.
* <p>
* Broken shallow repositories contain a handful of commits (the assumed base)
* and the trees referenced by those commits, but not the blobs or the commit
* ancestors. These repositories are meant to only be used as a source to fetch
* an overlay from, where the native Git protocol negotiated the assumed base as
* the common ancestor.
* <p>
* The created repository is not marked as shallow and does not have any grafts
* in it. Clients (or applications) which attempt to walk back through history
* beyond the assumed base will encounter missing objects and crash. Not
* configuring the shallow or grafts file is a "data integrity" feature to
* ensure that clients fetching or cloning from this shallow repository will not
* be able to succeed, as they do not (and would not receive) the needed but
* missing objects.
*/
public class BrokenShallowRepositoryCreator {
/**
* Create (or update) broken shallow repositories, recursively.
*
* @param srcTop the root of the source tree.
* @param dstTop the root of the destination tree.
* @throws IOException a repository failed to be converted.
*/
public static void createRecursive(final File srcTop, final File dstTop)
throws IOException {
final File[] srcEntries = srcTop.listFiles();
if (srcEntries == null) {
return;
}
for (final File srcEnt : srcEntries) {
final String srcName = srcEnt.getName();
if (srcName.equals(".") || srcName.equals("..")) {
continue;
} else if (!srcEnt.isDirectory()) {
continue;
} else if (GitMetaUtil.isGitRepository(srcEnt)) {
create(srcEnt, new File(dstTop, srcEnt.getName()));
} else {
createRecursive(srcEnt, new File(dstTop, srcEnt.getName()));
}
}
}
/**
* Create (or update) a broken shallow repository.
*
* @param srcGitDir the source repository, where commits and trees can be
* copied from.
* @param dstGitDir the destination repository, where the tree data (but not
* blob data) will be packed into. If this directory does not exist it
* will be automatically created.
* @throws IOException there was an error reading from the source or writing
* to the destination repository.
*/
public static void create(final File srcGitDir, final File dstGitDir)
throws IOException {
final Repository srcdb = new Repository(srcGitDir);
final RevWalk srcwalk = new RevWalk(srcdb);
final List<ObjectId> assumed = readAssumedBase(srcdb);
final List<RevObject> toCopy = new ArrayList<RevObject>(assumed.size() * 2);
final TreeWalk tw = new TreeWalk(srcdb);
for (final ObjectId id : assumed) {
final RevCommit c = srcwalk.parseCommit(id);
toCopy.add(c);
toCopy.add(c.getTree());
tw.reset();
tw.addTree(c.getTree());
while (tw.next()) {
switch (tw.getFileMode(0).getObjectType()) {
case Constants.OBJ_TREE:
toCopy.add(srcwalk.lookupTree(tw.getObjectId(0)));
tw.enterSubtree();
break;
case Constants.OBJ_BLOB:
break;
default:
break;
}
}
}
final Repository destdb = new Repository(dstGitDir);
if (!destdb.getDirectory().exists()) {
destdb.create();
}
final List<ObjectId> destAssumed = readAssumedBase(destdb);
destAssumed.addAll(assumed);
writeAssumedBase(destdb, destAssumed);
if (destAssumed.isEmpty()) {
// Nothing assumed, it doesn't need special processing from us.
//
return;
}
System.out.println("Packing " + destdb.getDirectory());
// Prepare pack of the assumed base. Clients wouldn't need to
// fetch this pack, as they already have its contents.
//
PackWriter packer = new PackWriter(srcdb, new TextProgressMonitor());
packer.preparePack(toCopy.iterator());
storePack(destdb, packer);
// Prepare a pack of everything else not in the assumed base. This
// would need to be fetched. We build it second so it has a newer
// timestamp when it goes into the list of packs, and will therefore
// be searched first by clients.
//
final Map<String, Ref> srcrefs = srcdb.getAllRefs();
final List<ObjectId> need = new ArrayList<ObjectId>();
for (final Ref r : srcrefs.values()) {
need.add(r.getObjectId());
}
packer = new PackWriter(srcdb, new TextProgressMonitor());
packer.preparePack(need, destAssumed, false, false);
storePack(destdb, packer);
// Force all of the refs in destdb to match srcdb. We want full
// mirroring style semantics now that the objects are in place.
//
final RevWalk dstwalk = new RevWalk(destdb);
destdb.writeSymref(Constants.HEAD, srcdb.getFullBranch());
for (final Ref r : destdb.getAllRefs().values()) {
if (!srcrefs.containsKey(r.getName())) {
final RefUpdate u = destdb.updateRef(r.getName());
u.setForceUpdate(true);
u.delete(dstwalk);
}
}
for (final Ref r : srcrefs.values()) {
final RefUpdate u = destdb.updateRef(r.getName());
if (u.getOldObjectId() == null
|| !u.getOldObjectId().equals(r.getObjectId())) {
u.setNewObjectId(r.getObjectId());
u.setForceUpdate(true);
u.update(dstwalk);
}
}
srcdb.close();
destdb.close();
}
private static void storePack(final Repository destdb, PackWriter packer)
throws FileNotFoundException, IOException {
final String packName = "pack-" + packer.computeName().name();
final File packDir = new File(destdb.getObjectsDirectory(), "pack");
final File packPath = new File(packDir, packName + ".pack");
final File idxPath = new File(packDir, packName + ".idx");
if (!packPath.exists() && !idxPath.exists()) {
{
final OutputStream os = new FileOutputStream(packPath);
try {
packer.writePack(os);
} finally {
os.close();
}
packPath.setReadOnly();
}
{
final OutputStream os = new FileOutputStream(idxPath);
try {
packer.writeIndex(os);
} finally {
os.close();
}
idxPath.setReadOnly();
}
}
}
private static List<ObjectId> readAssumedBase(final Repository db)
throws IOException {
final List<ObjectId> list = new ArrayList<ObjectId>();
try {
final BufferedReader br;
br = new BufferedReader(new FileReader(infoAssumedBase(db)));
try {
String line;
while ((line = br.readLine()) != null) {
list.add(ObjectId.fromString(line));
}
} finally {
br.close();
}
} catch (FileNotFoundException noList) {
// Ignore it. We'll return an empty list to the caller.
}
return list;
}
private static void writeAssumedBase(final Repository db,
final List<ObjectId> newList) throws IOException {
if (newList == null || newList.isEmpty()) {
infoAssumedBase(db).delete();
return;
}
final LockFile lf = new LockFile(infoAssumedBase(db));
if (!lf.lock()) {
throw new IOException("Cannot lock " + infoAssumedBase(db));
}
final OutputStream ow = lf.getOutputStream();
for (final ObjectId id : newList) {
id.copyTo(ow);
}
ow.close();
if (!lf.commit()) {
throw new IOException("Cannot commit " + infoAssumedBase(db));
}
}
private static File infoAssumedBase(final Repository db) {
return new File(db.getDirectory(), "info/assumed-base");
}
}

View File

@@ -0,0 +1,23 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package codereview.internal;
option java_package = "com.google.codereview.internal";
import "sync_project.proto";
service AdminService {
rpc SyncProject(SyncProjectRequest)
returns (SyncProjectResponse);
}

View File

@@ -0,0 +1,37 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package codereview.internal;
option java_package = "com.google.codereview.internal";
import "submit_build.proto";
import "post_build_result.proto";
import "prune_builds.proto";
service BuildService {
// Denote that a project build is starting.
//
rpc SubmitBuild(SubmitBuildRequest)
returns (SubmitBuildResponse);
// Update an open build attempt with its status.
//
rpc PostBuildResult(PostBuildResultRequest)
returns (PostBuildResultResponse);
// Clear out any build logs we don't need any more.
//
rpc PruneBuilds(PruneBuildsRequest)
returns (PruneBuildsResponse);
}

View File

@@ -0,0 +1,43 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package codereview.internal;
option java_package = "com.google.codereview.internal";
import "next_received_bundle.proto";
import "prune_bundles.proto";
import "update_received_bundle.proto";
service BundleStoreService {
// Grab the next available bundle. The bundle is deferred from
// processing for a short period of time to avoid duplicate work.
//
rpc NextReceivedBundle(NextReceivedBundleRequest)
returns (NextReceivedBundleResponse);
// Get a single, specific segment of a bundle.
//
rpc BundleSegment(BundleSegmentRequest)
returns (BundleSegmentResponse);
// Acknowledge a bundle as received into Git.
//
rpc UpdateReceivedBundle(UpdateReceivedBundleRequest)
returns (UpdateReceivedBundleResponse);
// Clear out any bundles we don't need any longer.
//
rpc PruneBundles(PruneBundlesRequest)
returns (PruneBundlesResponse);
}

View File

@@ -0,0 +1,37 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package codereview.internal;
option java_package = "com.google.codereview.internal";
import "complete_patchset.proto";
import "submit_change.proto";
import "upload_patchset_file.proto";
service ChangeService {
// Create a change and (empty) patchset for a Git commit.
//
rpc SubmitChange(SubmitChangeRequest)
returns (SubmitChangeResponse);
// Attaches a file to an existing patchset
//
rpc UploadPatchsetFile(UploadPatchsetFileRequest)
returns (UploadPatchsetFileResponse);
// Mark a patchset as complete (fully uploaded)
//
rpc CompletePatchset(CompletePatchsetRequest)
returns (CompletePatchsetResponse);
}

View File

@@ -0,0 +1,31 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package codereview.internal;
option java_package = "com.google.codereview.internal";
message CompletePatchsetRequest {
// Unique key for the patchset.
//
required string patchset_key = 10;
// Complete list of paths in this patch. Paths are
// encoded in UTF-8, null delimited and the list is
// compressed with deflate to reduce storage space.
//
optional bytes compressed_filenames = 11;
}
message CompletePatchsetResponse {
}

View File

@@ -0,0 +1,63 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package codereview.internal;
option java_package = "com.google.codereview.internal";
message GitPerson {
// "Givenname Surname" of the person this identifies.
//
required string name = 1;
// "who@example.com"; email address of the person.
//
required string email = 2;
// UTC seconds since epoch of the time event
//
required int32 when = 3;
// Person's preferred timezone offset, minutes east of UTC.
//
required int32 tz = 4;
}
message GitCommit {
// Commit hash as a hex string.
//
required string id = 1;
// Parent commit(s) this commit depends upon in ancestry.
// Formatted as hex strings.
//
repeated string parent_id = 2;
// Identity of the author of the commit.
//
required GitPerson author = 3;
// Identity of the committer (creator) of the commit.
// If not present use author instead (as committer is
// identical to author, a very common case).
//
optional GitPerson committer = 4;
// Message body of the commit.
//
optional string message = 5;
// First line (no LF) of the message.
//
optional string subject = 6;
}

View File

@@ -0,0 +1,38 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package codereview.internal;
option java_package = "com.google.codereview.internal";
import "pending_merge.proto";
import "post_branch_update.proto";
import "post_merge_result.proto";
service MergeService {
// Grab the next available pending merge.
//
rpc NextPendingMerge(PendingMergeRequest)
returns (PendingMergeResponse);
// Updates the status of a pending merge.
//
rpc PostMergeResult(PostMergeResultRequest)
returns (PostMergeResultResponse);
// Notify the data store that a branch has
// updated to a new revision.
//
rpc PostBranchUpdate(PostBranchUpdateRequest)
returns (PostBranchUpdateResponse);
}

View File

@@ -0,0 +1,73 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package codereview.internal;
option java_package = "com.google.codereview.internal";
message NextReceivedBundleRequest {
}
message NextReceivedBundleResponse {
enum CodeType {
BUNDLE_AVAILABLE = 1;
QUEUE_EMPTY = 2;
}
required CodeType status_code = 1;
// Unique identification of this bundle record.
//
optional string bundle_key = 2;
// Name of the project this bundle is applied to.
//
optional string dest_project = 3;
// Key of the project this bundle is applied to.
//
optional string dest_project_key = 4;
// Key of the branch the uploader thought the bundle
// should be applied to.
//
optional string dest_branch_key = 5;
// The unmodified bundle file, including its header.
//
optional bytes bundle_data = 6;
// Total number of segments in the bundle.
//
optional int32 n_segments = 8;
// Identification key for the owner of the bundle.
// Issues created will be tied back to them.
//
optional string owner = 7;
}
message BundleSegmentRequest {
required string bundle_key = 10;
required int32 segment_id = 11;
}
message BundleSegmentResponse {
enum CodeType {
DATA = 1;
UNKNOWN_BUNDLE = 2;
UNKNOWN_SEGMENT = 3;
}
required CodeType status_code = 10;
optional bytes bundle_data = 11;
}

View File

@@ -0,0 +1,64 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package codereview.internal;
option java_package = "com.google.codereview.internal";
// Request for the next available pending merge
//
message PendingMergeRequest {
}
// A single revision needing to be merged
//
message PendingMergeItem {
// Unique key of the PatchSet to merge
//
required string patchset_key = 1;
// Revision in the VCS
//
required string revision_id = 2;
}
// A repository+branch pair, and the commits to merge into it
//
message PendingMergeResponse {
enum CodeType {
QUEUE_EMPTY = 1;
MERGE_READY = 2;
}
required CodeType status_code = 10;
// The repository the merge should take place in
//
optional string dest_project_name = 11;
// Unique key for the repository
//
optional string dest_project_key = 12;
// The branch the result of the merge goes into
//
optional string dest_branch_name = 13;
// Unique key for the branch (and repository).
//
optional string dest_branch_key = 14;
// One or more commits to merge into dest_branch
// (all commits must be in dest_repository already)
//
repeated PendingMergeItem change = 1;
}

View File

@@ -0,0 +1,30 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package codereview.internal;
option java_package = "com.google.codereview.internal";
message PostBranchUpdateRequest {
// Unique key for the branch (and repository).
//
required string branch_key = 10;
// Unique keys of the PatchSets which were added
// into the branch.
//
repeated string new_change = 1;
}
message PostBranchUpdateResponse {
}

View File

@@ -0,0 +1,68 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package codereview.internal;
option java_package = "com.google.codereview.internal";
// Update a build entity with the results of the build
// (status of build, log file)
//
message PostBuildResultRequest {
// Unique number assigned by a previous SubmitBuildResponse
//
required int32 build_id = 10;
enum ResultType {
SUCCESS = 1; // the build (and/or tests) completed normally
FAILURE = 2; // the build (and/or tests) did not pass
}
required ResultType build_status = 11;
// Complete output (log file and/or captured stdout/stderr)
// This message usually contains embedded newlines and is
// the direct output of 'make' or some other process.
//
optional string build_log = 12;
}
// Information about a branch that needs to finish merging
//
message PostBuildResultResponse {
// The repository the merge should take place in
//
optional string dest_project_name = 10;
// Unique key for the repository
//
optional string dest_project_key = 11;
// The branch the result of the merge goes into
//
optional string dest_branch_name = 12;
// Unique key for the branch (and repository).
//
optional string dest_branch_key = 13;
// Revision the branch should advance to if the branch
// wants to advance based on the build result.
//
optional string revision_id = 14;
// Unique keys of the PatchSets which would be added
// to the branch if the branch is updated to match
// revision_id
//
repeated string new_change = 1;
}

View File

@@ -0,0 +1,43 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package codereview.internal;
option java_package = "com.google.codereview.internal";
message MergeResultItem {
enum CodeType {
CLEAN_MERGE = 1; // change merged clean
ALREADY_MERGED = 2; // previously merged into the branch
MISSING_DEPENDENCY = 3; // another issue is still required
PATH_CONFLICT = 4; // path level conflict; not trivial
}
required CodeType status_code = 1;
// Unique key of the PatchSet to merge
//
required string patchset_key = 2;
}
message PostMergeResultRequest {
// Unique key of the branch this merge submits into.
//
required string dest_branch_key = 10;
// Individual patch set update responses
//
repeated MergeResultItem change = 1;
}
message PostMergeResultResponse {
}

View File

@@ -0,0 +1,27 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package codereview.internal;
option java_package = "com.google.codereview.internal";
message PruneBuildsRequest {
}
message PruneBuildsResponse {
enum CodeType {
BUILDS_PRUNED = 1;
QUEUE_EMPTY = 2;
}
required CodeType status_code = 10;
}

View File

@@ -0,0 +1,27 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package codereview.internal;
option java_package = "com.google.codereview.internal";
message PruneBundlesRequest {
}
message PruneBundlesResponse {
enum CodeType {
BUNDLES_PRUNED = 1;
QUEUE_EMPTY = 2;
}
required CodeType status_code = 10;
}

View File

@@ -0,0 +1,26 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package codereview.internal;
option java_package = "com.google.codereview.internal";
message SubmitBuildRequest {
required string branch_key = 10;
required string revision_id = 11;
repeated string new_change = 1;
}
message SubmitBuildResponse {
optional int32 build_id = 10;
}

View File

@@ -0,0 +1,55 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package codereview.internal;
option java_package = "com.google.codereview.internal";
import "git_meta.proto";
message SubmitChangeRequest {
// Key of the branch the owner wants the change on.
//
required string dest_branch_key = 10;
// Parsed description of the commit at the tip of topic_branch.
// Identity of the change owner (usually this is the
// bundle owner).
//
required string owner = 11;
// Dependencies are inferred by commit.parent_id.
//
required GitCommit commit = 12;
}
message SubmitChangeResponse {
enum CodeType {
CREATED = 1;
PATCHSET_EXISTS = 2;
UNKNOWN_BRANCH = 3;
}
required CodeType status_code = 10;
// The identifier for the change.
//
optional int32 change_id = 11;
// Identifier for the patchset within the change.
//
optional int32 patchset_id = 12;
// Unique key for the patchset.
//
optional string patchset_key = 13;
}

View File

@@ -0,0 +1,31 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package codereview.internal;
option java_package = "com.google.codereview.internal";
import "git_meta.proto";
message BranchSync {
required string branch_name = 1;
required GitCommit commit = 2;
}
message SyncProjectRequest {
required string project_name = 10;
repeated BranchSync branch = 1;
}
message SyncProjectResponse {
}

View File

@@ -0,0 +1,44 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package codereview.internal;
option java_package = "com.google.codereview.internal";
message UpdateReceivedBundleRequest {
enum CodeType {
UNPACKED_OK = 1;
UNKNOWN_PROJECT = 2;
INVALID_BUNDLE = 3;
MISSING_BASE = 4;
SUSPEND_BUNDLE = 5;
}
required CodeType status_code = 1;
// Unique identification of the bundle record.
//
required string bundle_key = 2;
// Exception details related to why the bundle is bad.
//
optional string error_details = 3;
}
message UpdateReceivedBundleResponse {
enum CodeType {
UPDATED = 1;
UNKNOWN_BUNDLE = 2;
INVALID_STATE = 3;
}
required CodeType status_code = 1;
}

View File

@@ -0,0 +1,61 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package codereview.internal;
option java_package = "com.google.codereview.internal";
import "git_meta.proto";
message UploadPatchsetFileRequest {
// Unique key for the patchset.
//
required string patchset_key = 10;
// Path of the file once the patch is applied
//
required string file_name = 11;
// Type of change made to the file
//
enum StatusType {
ADD = 1;
MODIFY = 2;
DELETE = 3;
}
required StatusType status = 13;
// Deflated base content for the file (pre-image), encoding
// is not specified, but should be UTF-8. base_id is the
// Git style blob SHA-1 hash of the inflated base_data.
//
optional bytes base_z = 14;
optional string base_id = 15;
// Deflated gerrit friendly patch format for the file
//
optional bytes patch_z = 16;
optional string patch_id = 17;
optional string final_id = 18;
}
message UploadPatchsetFileResponse {
enum CodeType {
CREATED = 1;
CLOSED = 2; // the patchset is not accepting files
UNKNOWN_PATCHSET = 3; // the patchset key is invalid
PATCHING_ERROR = 4; // the patch could not be applied
}
required CodeType status_code = 10;
}

29
proto/need_retry.proto Normal file
View File

@@ -0,0 +1,29 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package codereview;
option java_package = "com.google.codereview";
// Message sent in reply to any "atomic" RPC asking
// the client to sleep a bit, then resubmit the same
// request message to the server.
//
// If any work was performed before the retry was
// returned, the server assures the client that
// duplicate records won't be created as a result
// of the resubmission.
//
message RetryRequestLaterResponse {
}

32
proto/review.proto Normal file
View File

@@ -0,0 +1,32 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package codereview;
option java_package = "com.google.codereview";
import "upload_bundle.proto";
// Review service operations available to users.
//
service ReviewService {
// Upload a Git bundle file for review and merging.
//
rpc UploadBundle (UploadBundleRequest)
returns (UploadBundleResponse);
// Continue uploading a partial bundle.
//
rpc ContinueBundle (UploadBundleContinue)
returns (UploadBundleResponse);
}

98
proto/upload_bundle.proto Normal file
View File

@@ -0,0 +1,98 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package codereview;
option java_package = "com.google.codereview";
// Uploads a bundle to start a review process.
//
message UploadBundleRequest {
// Name of the project this bundle is applied to.
//
required string dest_project = 10;
// Name of the branch the uploader thinks it should
// have its tip commit merged into.
//
required string dest_branch = 11;
// Set if this is a partial bundle upload. Remaining
// segments can be supplied with UploadBundleContinue.
//
required bool partial_upload = 12;
// The unmodified bundle file, including its text based header.
//
// The bundle must have exactly 1 ref available, but the
// name of the ref does not matter. It only needs to be
// a valid ref name ("refs/..." or "HEAD").
//
// This may be a partial fragment of the bundle, in which
// case the partial_upload flag must be set.
//
required bytes bundle_data = 13;
// An object id known to be in this bundle. Typically only
// commits and/or annotated tags would be enumerated here.
//
repeated string contained_object = 1;
}
message UploadBundleResponse {
enum CodeType {
RECEIVED = 1; // the bundle is fully uploaded
CONTINUE = 4; // client should use UploadBundleContinue
UNAUTHORIZED_USER = 7; // the user account is not allowed to upload
UNKNOWN_PROJECT = 2; // the project is invalid
UNKNOWN_BRANCH = 3; // the branch is invalid
UNKNOWN_BUNDLE = 5; // the bundle id is invalid
NOT_BUNDLE_OWNER = 6; // the bundle is not your bundle
BUNDLE_CLOSED = 8; // the bundle is already uploaded
}
required CodeType status_code = 10;
// Unique identification of this bundle record.
// (RECEIVED, CONTINUE)
//
optional string bundle_id = 11;
}
message UploadBundleContinue {
// The bundle identifier supplied by UploadBundleResponse
//
required string bundle_id = 10;
// The segment number of this segment in the bundle.
//
// The first segment (created by UploadBundleRequest)
// is always 1. The first UploadBundleContinue should
// use a segment_id of 2.
//
required int32 segment_id = 11;
// Set if this is a partial bundle upload. Remaining
// segments can be supplied with UploadBundleContinue.
//
required bool partial_upload = 12;
// The next data segment for the bundle.
//
// This may be a partial fragment of the bundle, in which
// case the partial_upload flag must be set.
//
optional bytes bundle_data = 13;
}

165
test-utils.sh Normal file
View File

@@ -0,0 +1,165 @@
#
# Copyright 2008 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
## Source this script into your shell:
##
## . test-utils.sh
##
## so that you can run:
##
## gerrit clean
## gerrit reset
## gerrit gae
## gerrit crm
##
## to setup a local testing environment using the Google App Engine
## SDK and a local Java installation.
##
TEST_DIR=test
TEST_CRM_DATA_DIR=$TEST_DIR/crm-data
TEST_GERRIT_DATASTORE_DIR=$TEST_DIR/gerrit-datastore
TEST_CLIENT0=$TEST_DIR/client0
TEST_CLIENT1=$TEST_DIR/client1
TEST_LOCAL_CONFIG=$TEST_DIR/localhost.config
TEST_CRM_PASSWORD_FILE=$TEST_DIR/.crm-password
function gerrit-clean()
{
rm -rf $TEST_DIR
}
function gerrit-reset()
{
FULL_GIT_BASE=`pwd`/$TEST_CRM_DATA_DIR
# delete the old data
gerrit-clean
# make the crm data
FULL_CRM_DATA_FILE=`pwd`/crm-data.tar.gz
mkdir -p $TEST_DIR/crm-data
( cd $TEST_CRM_DATA_DIR ; tar zxf $FULL_CRM_DATA_FILE )
# make two git clients (should use repo)
mkdir -p $TEST_CLIENT0
( cd $TEST_CLIENT0 ; git clone $FULL_GIT_BASE/test.git > /dev/null)
mkdir -p $TEST_CLIENT1
( cd $TEST_CLIENT1 ; git clone $FULL_GIT_BASE/test.git > /dev/null)
make all
# make localhost.config
echo "[user]" > $TEST_LOCAL_CONFIG
echo " name = Gerrit Code Review" >> $TEST_LOCAL_CONFIG
echo " email = gerrit@localhost" >> $TEST_LOCAL_CONFIG
echo "" >> $TEST_LOCAL_CONFIG
echo "[codereview]" >> $TEST_LOCAL_CONFIG
echo " server = http://localhost:8080/" >> $TEST_LOCAL_CONFIG
echo " basedir = $(pwd)/$TEST_CRM_DATA_DIR" >> $TEST_LOCAL_CONFIG
echo " username = android-git@google.com" >> $TEST_LOCAL_CONFIG
echo " secureconfig = .crm-password" \
>> $TEST_LOCAL_CONFIG
echo " sleep = 10" >> $TEST_LOCAL_CONFIG
echo " threads = 1" >> $TEST_LOCAL_CONFIG
echo
echo "Finished. Now you can run:"
echo " gerrit gae to run the app engine server"
echo " gerrit crm to run the crm server"
echo
}
# pack the git repository into a new crm-data.tar.gz
function gerrit-pack-crm-data()
{
FULL_CRM_DATA_FILE=`pwd`/crm-data.tar.gz
rm -f crm-data.tar.gz
( cd $TEST_CRM_DATA_DIR ; tar czf $FULL_CRM_DATA_FILE * )
}
# run the google app engine dev server
function gerrit-gae()
{
FULL_GERRIT_DATASTORE_DIR=`pwd`/$TEST_GERRIT_DATASTORE_DIR
make serve DATASTORE=$FULL_GERRIT_DATASTORE_DIR
}
# run the crm server
function gerrit-crm()
{
if [ ! -f $TEST_CRM_PASSWORD_FILE ] ; then
( curl http://localhost:8080/dev_init > $TEST_CRM_PASSWORD_FILE )
( ./mgrapp/bin/mgr $TEST_LOCAL_CONFIG sync)
fi
( ./mgrapp/bin/mgr $TEST_LOCAL_CONFIG )
}
function gerrit-help()
{
echo "commands:"
echo " clean"
echo " reset"
echo " pack-crm-data"
echo " gae"
echo " crm"
echo " upload"
}
function gerrit-upload()
{
python2.5 ../../../webapp/git_upload.py -s localhost:8080 \
-e author@example.com -p test -b refs/heads/master -B HEAD^
}
# main gerrit command
function gerrit()
{
if [ ! -f test-utils.sh ] ; then
echo Run gerrit from the directory that contains test-utils.sh
return
fi
case $1 in
clean)
gerrit-clean
;;
reset)
gerrit-reset
;;
pack-crm-data)
gerrit-pack-crm-data
;;
gae)
gerrit-gae
;;
crm)
gerrit-crm
;;
help)
gerrit-help
;;
*)
echo invalid gerrit command $1
;;
esac
}
# vi: sts=2 ts=2 sw=2 nocindent

8
tests_mgrapp/.classpath Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry combineaccessrules="false" kind="src" path="/mgrapp"/>
<classpathentry kind="output" path="out_classes"/>
</classpath>

1
tests_mgrapp/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/out_classes

17
tests_mgrapp/.project Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>tests_mgrapp</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,3 @@
#Tue Sep 02 16:59:24 PDT 2008
eclipse.preferences.version=1
encoding/<project>=UTF-8

View File

@@ -0,0 +1,3 @@
#Tue Sep 02 16:59:24 PDT 2008
eclipse.preferences.version=1
line.separator=\n

View File

@@ -0,0 +1,268 @@
#Thu Sep 04 11:18:51 PDT 2008
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.5
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.5
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_assignment=16
org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
org.eclipse.jdt.core.formatter.blank_lines_after_package=1
org.eclipse.jdt.core.formatter.blank_lines_before_field=0
org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
org.eclipse.jdt.core.formatter.blank_lines_before_method=1
org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
org.eclipse.jdt.core.formatter.blank_lines_before_package=0
org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
org.eclipse.jdt.core.formatter.comment.format_block_comments=true
org.eclipse.jdt.core.formatter.comment.format_header=true
org.eclipse.jdt.core.formatter.comment.format_html=true
org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
org.eclipse.jdt.core.formatter.comment.format_line_comments=true
org.eclipse.jdt.core.formatter.comment.format_source_code=true
org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
org.eclipse.jdt.core.formatter.comment.line_length=80
org.eclipse.jdt.core.formatter.compact_else_if=true
org.eclipse.jdt.core.formatter.continuation_indentation=2
org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
org.eclipse.jdt.core.formatter.indent_empty_lines=false
org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
org.eclipse.jdt.core.formatter.indentation.size=4
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
org.eclipse.jdt.core.formatter.lineSplit=80
org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
org.eclipse.jdt.core.formatter.tabulation.char=space
org.eclipse.jdt.core.formatter.tabulation.size=2
org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true

View File

@@ -0,0 +1,9 @@
#Tue Sep 02 17:00:18 PDT 2008
eclipse.preferences.version=1
formatter_profile=_Google Format
formatter_settings_version=11
org.eclipse.jdt.ui.ignorelowercasenames=true
org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax;
org.eclipse.jdt.ui.ondemandthreshold=99
org.eclipse.jdt.ui.staticondemandthreshold=99
org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>

View File

@@ -0,0 +1,59 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview;
import junit.framework.TestCase;
import java.io.File;
/**
* JUnit TestCase with support for creating a temporary directory.
*/
public abstract class TrashTestCase extends TestCase {
protected File tempRoot;
@Override
protected void setUp() throws Exception {
super.setUp();
tempRoot = File.createTempFile("codereview", "test");
tempRoot.delete();
tempRoot.mkdir();
}
@Override
protected void tearDown() throws Exception {
if (tempRoot != null) {
rm(tempRoot);
}
super.tearDown();
}
protected static void rm(final File dir) {
final File[] list = dir.listFiles();
for (int i = 0; list != null && i < list.length; i++) {
final File f = list[i];
if (f.getName().equals(".") || f.getName().equals("..")) {
continue;
}
if (f.isDirectory()) {
rm(f);
} else {
f.delete();
}
}
dir.delete();
}
}

View File

@@ -0,0 +1,84 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.manager;
import com.google.codereview.TrashTestCase;
import org.spearce.jgit.lib.Repository;
import java.io.File;
public class RepositoryCacheTest extends TrashTestCase {
public void testCreateCache() {
final RepositoryCache rc = new RepositoryCache(tempRoot);
}
public void testLookupInvalidNames() {
final RepositoryCache rc = new RepositoryCache(tempRoot);
assertInvalidRepository(rc, "");
assertInvalidRepository(rc, "^");
assertInvalidRepository(rc, ".");
assertInvalidRepository(rc, "..");
assertInvalidRepository(rc, "../foo");
assertInvalidRepository(rc, "/foo");
assertInvalidRepository(rc, "/foo/bar");
assertInvalidRepository(rc, "bar/../foo");
assertInvalidRepository(rc, "bar\\..\\foo");
}
private void assertInvalidRepository(final RepositoryCache rc, final String n) {
try {
rc.get(n);
fail("Cache accepted name " + n);
} catch (InvalidRepositoryException err) {
assertEquals(n, err.getMessage());
}
}
public void testLookupNotCreatedRepository() {
final String name = "test.git";
final RepositoryCache rc = new RepositoryCache(tempRoot);
try {
rc.get(name);
} catch (InvalidRepositoryException err) {
assertEquals(name, err.getMessage());
}
}
public void testLookupExistingEmptyRepository() throws Exception {
final RepositoryCache rc = new RepositoryCache(tempRoot);
// Create after the cache is built, to test creation-on-the-fly.
//
final String[] names = {"test.git", "foo/bar/test.git"};
final File[] gitdir = new File[names.length];
for (int i = 0; i < names.length; i++) {
gitdir[i] = new File(tempRoot, names[i]);
final Repository r = new Repository(gitdir[i]);
r.create();
assertTrue(gitdir[i].isDirectory());
}
final Repository[] cached = new Repository[names.length];
for (int i = 0; i < names.length; i++) {
cached[i] = rc.get(names[i]);
assertNotNull(cached[i]);
assertEquals(gitdir[i], cached[i].getDirectory());
}
for (int i = 0; i < names.length; i++) {
assertSame(cached[i], rc.get(names[i]));
}
}
}

View File

@@ -0,0 +1,193 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.manager.unpack;
import com.google.codereview.TrashTestCase;
import com.google.codereview.internal.UploadPatchsetFile.UploadPatchsetFileRequest.StatusType;
import org.spearce.jgit.lib.Commit;
import org.spearce.jgit.lib.ObjectId;
import org.spearce.jgit.lib.ObjectWriter;
import org.spearce.jgit.lib.PersonIdent;
import org.spearce.jgit.lib.Repository;
import org.spearce.jgit.lib.Tree;
import org.spearce.jgit.revwalk.RevCommit;
import org.spearce.jgit.revwalk.RevWalk;
import java.io.File;
import java.io.IOException;
public class DiffReaderTest extends TrashTestCase {
private Repository db;
private ObjectWriter writer;
@Override
protected void setUp() throws Exception {
super.setUp();
db = new Repository(new File(tempRoot, ".git"));
db.create();
writer = new ObjectWriter(db);
}
public void testAddOneTextFile_RootCommit() throws Exception {
final Tree top = new Tree(db);
final ObjectId blobId = blob("a\nb\n");
top.addFile("foo").setId(blobId);
final RevCommit c = commit(top);
final DiffReader dr = new DiffReader(db, c);
final FileDiff foo = dr.next();
assertNotNull(foo);
assertEquals("foo", foo.getFilename());
assertSame(StatusType.ADD, foo.getStatus());
assertEquals(ObjectId.zeroId(), foo.getBaseId());
assertEquals("diff --git a/foo b/foo\n" + "new file mode 100644\n"
+ "index " + ObjectId.zeroId().name() + ".." + blobId.name() + "\n"
+ "--- /dev/null\n" + "+++ b/foo\n" + "@@ -0,0 +1,2 @@\n" + "+a\n"
+ "+b\n", foo.getPatch());
assertNull(dr.next());
dr.close();
}
public void testAddOneTextFile_ExistingTree() throws Exception {
final Tree top = new Tree(db);
final RevCommit parent = commit(top);
final ObjectId blobId = blob("a\nb\n");
top.addFile("foo").setId(blobId);
final RevCommit c = commit(top, parent);
final DiffReader dr = new DiffReader(db, c);
final FileDiff foo = dr.next();
assertNotNull(foo);
assertEquals("foo", foo.getFilename());
assertSame(StatusType.ADD, foo.getStatus());
assertEquals(ObjectId.zeroId(), foo.getBaseId());
assertEquals("diff --git a/foo b/foo\n" + "new file mode 100644\n"
+ "index " + ObjectId.zeroId().name() + ".." + blobId.name() + "\n"
+ "--- /dev/null\n" + "+++ b/foo\n" + "@@ -0,0 +1,2 @@\n" + "+a\n"
+ "+b\n", foo.getPatch());
assertNull(dr.next());
dr.close();
}
public void testDeleteOneTextFile() throws Exception {
final Tree top = new Tree(db);
final ObjectId blobId = blob("a\nb\n");
top.addFile("foo").setId(blobId);
final RevCommit parent = commit(top);
top.findBlobMember("foo").delete();
final RevCommit c = commit(top, parent);
final DiffReader dr = new DiffReader(db, c);
final FileDiff foo = dr.next();
assertNotNull(foo);
assertEquals("foo", foo.getFilename());
assertSame(StatusType.DELETE, foo.getStatus());
assertEquals(blobId, foo.getBaseId());
assertEquals("diff --git a/foo b/foo\n" + "deleted file mode 100644\n"
+ "index " + blobId.name() + ".." + ObjectId.zeroId().name() + "\n"
+ "--- a/foo\n" + "+++ /dev/null\n" + "@@ -1,2 +0,0 @@\n" + "-a\n"
+ "-b\n", foo.getPatch());
assertNull(dr.next());
dr.close();
}
public void testModifyTwoTextFiles() throws Exception {
final Tree top = new Tree(db);
final ObjectId barBaseId = blob("a\nc\n");
top.addFile("bar").setId(barBaseId);
final ObjectId fooBaseId = blob("a\nb\n");
top.addFile("foo").setId(fooBaseId);
final RevCommit parent = commit(top);
final ObjectId barNewId = blob("a\nd\nc\n");
top.findBlobMember("bar").setId(barNewId);
final ObjectId fooNewId = blob("a\nc\nb\n");
top.findBlobMember("foo").setId(fooNewId);
final RevCommit c = commit(top, parent);
final DiffReader dr = new DiffReader(db, c);
final FileDiff bar = dr.next();
assertNotNull(bar);
assertEquals("bar", bar.getFilename());
assertSame(StatusType.MODIFY, bar.getStatus());
assertEquals(barBaseId, bar.getBaseId());
assertEquals("diff --git a/bar b/bar\n" + "index " + barBaseId.name()
+ ".." + barNewId.name() + " 100644\n" + "--- a/bar\n" + "+++ b/bar\n"
+ "@@ -1,2 +1,3 @@\n" + " a\n" + "+d\n" + " c\n", bar.getPatch());
final FileDiff foo = dr.next();
assertNotNull(foo);
assertEquals("foo", foo.getFilename());
assertSame(StatusType.MODIFY, foo.getStatus());
assertEquals(fooBaseId, foo.getBaseId());
assertEquals("diff --git a/foo b/foo\n" + "index " + fooBaseId.name()
+ ".." + fooNewId.name() + " 100644\n" + "--- a/foo\n" + "+++ b/foo\n"
+ "@@ -1,2 +1,3 @@\n" + " a\n" + "+c\n" + " b\n", foo.getPatch());
assertNull(dr.next());
dr.close();
}
public void testAddBinaryFile() throws Exception {
final Tree top = new Tree(db);
final ObjectId blobId = blob("\0\1\2\0\1\2\0\1\2");
top.addFile("foo").setId(blobId);
final RevCommit c = commit(top);
final DiffReader dr = new DiffReader(db, c);
final FileDiff foo = dr.next();
assertNotNull(foo);
assertEquals("foo", foo.getFilename());
assertSame(StatusType.ADD, foo.getStatus());
assertEquals(ObjectId.zeroId(), foo.getBaseId());
assertEquals("diff --git a/foo b/foo\n" + "new file mode 100644\n"
+ "index " + ObjectId.zeroId().name() + ".." + blobId.name() + "\n"
+ "Binary files /dev/null and b/foo differ\n", foo.getPatch());
assertNull(dr.next());
dr.close();
}
private ObjectId blob(final String content) throws IOException {
return writer.writeBlob(content.getBytes("UTF-8"));
}
private RevCommit commit(final Tree top, final RevCommit... parents)
throws IOException {
final Commit c = new Commit(db);
c.setTreeId(writer.writeTree(top));
final ObjectId parentIds[] = new ObjectId[parents.length];
for (int i = 0; i < parents.length; i++) {
parentIds[i] = parents[i].getId();
}
c.setParentIds(parentIds);
c.setAuthor(new PersonIdent("A U Thor <a@example.com> 1 +0000"));
c.setCommitter(c.getAuthor());
c.setMessage("");
c.setCommitId(writer.writeCommit(c));
final RevWalk rw = new RevWalk(db);
final RevCommit r = rw.parseCommit(c.getCommitId());
for (final RevCommit p : r.getParents()) {
rw.parse(p);
}
return r;
}
}

View File

@@ -0,0 +1,88 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.manager.unpack;
import com.google.codereview.internal.UploadPatchsetFile.UploadPatchsetFileRequest.StatusType;
import junit.framework.TestCase;
import org.spearce.jgit.lib.ObjectId;
import java.io.UnsupportedEncodingException;
public class FileDiffTest extends TestCase {
public void testConstructor() {
final FileDiff fd = new FileDiff();
assertNull(fd.getBaseId());
assertNull(fd.getFilename());
assertSame(StatusType.MODIFY, fd.getStatus());
assertEquals("", fd.getPatch());
}
public void testBaseId() {
final ObjectId id1 =
ObjectId.fromString("fc5ac44497e0548c32506b9c584248fc49bb9f97");
final ObjectId id2 =
ObjectId.fromString("8abf2492d8c5228192a3cba5528e47b3a4bb87e0");
final FileDiff fd = new FileDiff();
fd.setBaseId(id1);
assertSame(id1, fd.getBaseId());
fd.setBaseId(id2);
assertSame(id2, fd.getBaseId());
}
public void testFilename() {
final FileDiff fd = new FileDiff();
final String name1 = "foo";
final String name2 = "foo/bar/baz";
fd.setFilename(name1);
assertSame(name1, fd.getFilename());
fd.setFilename(name2);
assertSame(name2, fd.getFilename());
}
public void testStatus() {
final FileDiff fd = new FileDiff();
fd.setStatus(StatusType.ADD);
assertSame(StatusType.ADD, fd.getStatus());
fd.setStatus(StatusType.MODIFY);
assertSame(StatusType.MODIFY, fd.getStatus());
fd.setStatus(StatusType.DELETE);
assertSame(StatusType.DELETE, fd.getStatus());
}
public void testPatchBody() {
final FileDiff fd = new FileDiff();
final String n1 = "diff --git a/foo b/foo";
final String n2 = "--- a/foo";
final String n3 = "+++ b/foo";
final String n4 = "@@ -20,7 + 20,7 @@";
fd.appendLine(toBytes(n1));
fd.appendLine(toBytes(n2));
fd.appendLine(toBytes(n3));
fd.appendLine(toBytes(n4));
assertEquals(n1 + "\n" + n2 + "\n" + n3 + "\n" + n4 + "\n", fd.getPatch());
}
private static byte[] toBytes(final String s) {
final String enc = "UTF-8";
try {
return s.getBytes(enc);
} catch (UnsupportedEncodingException uee) {
throw new RuntimeException("No " + enc + " support?", uee);
}
}
}

View File

@@ -0,0 +1,205 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.manager.unpack;
import com.google.codereview.TrashTestCase;
import com.google.codereview.internal.NextReceivedBundle.NextReceivedBundleRequest;
import com.google.codereview.internal.NextReceivedBundle.NextReceivedBundleResponse;
import com.google.codereview.internal.UpdateReceivedBundle.UpdateReceivedBundleRequest;
import com.google.codereview.internal.UpdateReceivedBundle.UpdateReceivedBundleResponse;
import com.google.codereview.manager.Backend;
import com.google.codereview.manager.RepositoryCache;
import com.google.codereview.rpc.MockRpcChannel;
import com.google.protobuf.ByteString;
import com.google.protobuf.Message;
import com.google.protobuf.RpcCallback;
import com.google.protobuf.RpcChannel;
import com.google.protobuf.RpcController;
import com.google.protobuf.Descriptors.MethodDescriptor;
import org.spearce.jgit.lib.Repository;
import java.io.File;
public class ReceivedBundleUnpackerTest extends TrashTestCase {
private MockRpcChannel rpc;
private RepositoryCache repoCache;
private Backend server;
@Override
protected void setUp() throws Exception {
super.setUp();
rpc = new MockRpcChannel();
repoCache = new RepositoryCache(tempRoot);
server = new Backend(repoCache, rpc, null, null);
new Repository(new File(tempRoot, "foo.git")).create();
}
public void testNextReceivedBundle_EmptyQueue() throws Exception {
final ReceivedBundleUnpacker rbu = newRBU();
rpc.add(new RpcChannel() {
public void callMethod(final MethodDescriptor method,
final RpcController controller, final Message request,
final Message responsePrototype, final RpcCallback<Message> done) {
assertEquals("NextReceivedBundle", method.getName());
assertSame(NextReceivedBundleRequest.getDefaultInstance(), request);
final NextReceivedBundleResponse.Builder r =
NextReceivedBundleResponse.newBuilder();
r.setStatusCode(NextReceivedBundleResponse.CodeType.QUEUE_EMPTY);
done.run(r.build());
}
});
rbu.run();
rpc.assertAllCallsMade();
}
public void testNextReceivedBundle_GetBundle() throws Exception {
final String bundleKey = "bundle-key-abcd123efg";
final NextReceivedBundleResponse nrb[] = new NextReceivedBundleResponse[1];
final UpdateReceivedBundleRequest urbr[] =
new UpdateReceivedBundleRequest[1];
final ReceivedBundleUnpacker rbu =
new ReceivedBundleUnpacker(server) {
@Override
protected UpdateReceivedBundleRequest unpackImpl(
final NextReceivedBundleResponse rsp) {
assertNotNull("unpackImpl only after bundle available", nrb[0]);
assertSame(nrb[0], rsp);
final UpdateReceivedBundleRequest.Builder r =
UpdateReceivedBundleRequest.newBuilder();
r.setBundleKey(bundleKey);
r.setStatusCode(UpdateReceivedBundleRequest.CodeType.UNPACKED_OK);
urbr[0] = r.build();
return urbr[0];
}
};
rpc.add(new RpcChannel() {
public void callMethod(final MethodDescriptor method,
final RpcController controller, final Message request,
final Message responsePrototype, final RpcCallback<Message> done) {
assertEquals("NextReceivedBundle", method.getName());
assertSame(NextReceivedBundleRequest.getDefaultInstance(), request);
final NextReceivedBundleResponse.Builder r =
NextReceivedBundleResponse.newBuilder();
r.setStatusCode(NextReceivedBundleResponse.CodeType.BUNDLE_AVAILABLE);
r.setBundleKey(bundleKey);
r.setBundleData(ByteString.EMPTY);
r.setDestProject("foo.git");
r.setDestProjectKey("project:foo.git");
r.setDestBranchKey("branch:refs/heads/master");
r.setOwner("author@example.com");
nrb[0] = r.build();
done.run(nrb[0]);
}
});
rpc.add(new RpcChannel() {
public void callMethod(final MethodDescriptor method,
final RpcController controller, final Message request,
final Message responsePrototype, final RpcCallback<Message> done) {
assertEquals("UpdateReceivedBundle", method.getName());
assertSame(urbr[0], request);
final UpdateReceivedBundleResponse.Builder r =
UpdateReceivedBundleResponse.newBuilder();
r.setStatusCode(UpdateReceivedBundleResponse.CodeType.UPDATED);
done.run(r.build());
}
});
rpc.add(new RpcChannel() {
public void callMethod(final MethodDescriptor method,
final RpcController controller, final Message request,
final Message responsePrototype, final RpcCallback<Message> done) {
assertEquals("NextReceivedBundle", method.getName());
assertSame(NextReceivedBundleRequest.getDefaultInstance(), request);
final NextReceivedBundleResponse.Builder r =
NextReceivedBundleResponse.newBuilder();
r.setStatusCode(NextReceivedBundleResponse.CodeType.QUEUE_EMPTY);
done.run(r.build());
}
});
rbu.run();
assertNotNull(nrb[0]);
assertNotNull(urbr[0]);
rpc.assertAllCallsMade();
}
public void testNextReceivedBundle_RpcFailure() throws Exception {
final ReceivedBundleUnpacker rbu = newRBU();
rpc.add(new RpcChannel() {
public void callMethod(final MethodDescriptor method,
final RpcController controller, final Message request,
final Message responsePrototype, final RpcCallback<Message> done) {
assertEquals("NextReceivedBundle", method.getName());
controller.setFailed("mock failure");
}
});
rbu.run();
rpc.assertAllCallsMade();
}
public void testNextReceivedBundle_RuntimeException() throws Exception {
final String msg = "test-a-message-win-a-prize";
final ReceivedBundleUnpacker rbu = newRBU();
rpc.add(new RpcChannel() {
public void callMethod(final MethodDescriptor method,
final RpcController controller, final Message request,
final Message responsePrototype, final RpcCallback<Message> done) {
assertEquals("NextReceivedBundle", method.getName());
throw new RuntimeException(msg);
}
});
try {
rbu.run();
fail("Unpacker did not rethrow an unexpected runtime exception");
} catch (RuntimeException re) {
assertEquals(msg, re.getMessage());
}
rpc.assertAllCallsMade();
}
public void testNextReceivedBundle_RuntimeError() throws Exception {
final String msg = "test-a-message-win-a-prize";
final ReceivedBundleUnpacker rbu = newRBU();
rpc.add(new RpcChannel() {
public void callMethod(final MethodDescriptor method,
final RpcController controller, final Message request,
final Message responsePrototype, final RpcCallback<Message> done) {
assertEquals("NextReceivedBundle", method.getName());
throw new OutOfMemoryError(msg);
}
});
try {
rbu.run();
fail("Unpacker did not rethrow an unexpected OutOfMemoryError");
} catch (OutOfMemoryError re) {
assertEquals(msg, re.getMessage());
}
rpc.assertAllCallsMade();
}
private ReceivedBundleUnpacker newRBU() {
return new ReceivedBundleUnpacker(server);
}
}

View File

@@ -0,0 +1,135 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.manager.unpack;
import junit.framework.TestCase;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
public class RecordInputStreamTest extends TestCase {
public void testEmptyStream() throws IOException {
final RecordInputStream in = b("");
assertEquals(-1, in.read());
assertEquals(-1, in.read());
assertEquals(-1, in.read(new byte[8], 0, 8));
assertNull(in.readRecord('\n'));
in.close();
}
public void testReadOneByte() throws IOException {
final String exp = "abc";
final RecordInputStream in = b(exp);
assertEquals(exp.charAt(0), in.read());
assertEquals(exp.charAt(1), in.read());
assertEquals(exp.charAt(2), in.read());
assertEquals(-1, in.read());
in.close();
}
public void testReadBlockOffset0() throws IOException {
final String exp = "abc";
final RecordInputStream in = b(exp);
final byte[] act = new byte[exp.length()];
assertEquals(act.length, in.read(act, 0, act.length));
assertEquals(exp.charAt(0), act[0]);
assertEquals(exp.charAt(1), act[1]);
assertEquals(exp.charAt(2), act[2]);
assertEquals(-1, in.read(act, 0, act.length));
in.close();
}
public void testReadBlockOffset1() throws IOException {
final String exp = "abc";
final RecordInputStream in = b(exp);
assertEquals(exp.charAt(0), in.read());
final byte[] act = new byte[exp.length() - 1];
assertEquals(act.length, in.read(act, 0, act.length));
assertEquals(exp.charAt(1), act[0]);
assertEquals(exp.charAt(2), act[1]);
assertEquals(-1, in.read(act, 0, act.length));
in.close();
}
public void testReadRecord_SeparatorOnly() throws IOException {
final String rec1 = "foo";
final String rec2 = "bar";
final char sep = '\0';
final RecordInputStream in = b(rec1 + sep + rec2);
assertTrue(Arrays.equals(toBytes(rec1), in.readRecord(sep)));
assertTrue(Arrays.equals(toBytes(rec2), in.readRecord(sep)));
assertNull(in.readRecord(sep));
in.close();
}
public void testReadRecord_Terminated() throws IOException {
final String rec1 = "foo";
final String rec2 = "bar";
final char sep = '\0';
final RecordInputStream in = b(rec1 + sep + rec2 + sep);
assertTrue(Arrays.equals(toBytes(rec1), in.readRecord(sep)));
assertTrue(Arrays.equals(toBytes(rec2), in.readRecord(sep)));
assertNull(in.readRecord(sep));
in.close();
}
public void testReadRecord_EmptyRecords() throws IOException {
final char sep = '\0';
final RecordInputStream in = b("" + sep + "" + sep);
assertTrue(Arrays.equals(new byte[0], in.readRecord(sep)));
assertTrue(Arrays.equals(new byte[0], in.readRecord(sep)));
assertNull(in.readRecord(sep));
in.close();
}
public void testReadRecord_PartialRecordInBuffer() throws IOException {
final int huge = 16 * 1024;
final StringBuilder temp = new StringBuilder(huge);
for (int i = 0; i < huge; i++) {
temp.append('x');
}
final char sep = '\n';
final String ts = temp.toString();
final RecordInputStream in = b(ts + "1" + sep + ts + "2");
assertEquals(ts + "1", toString(in.readRecord(sep)));
assertEquals(ts + "2", toString(in.readRecord(sep)));
assertNull(in.readRecord(sep));
in.close();
}
private static RecordInputStream b(final String s) {
return new RecordInputStream(new ByteArrayInputStream(toBytes(s)));
}
private static byte[] toBytes(final String s) {
final String enc = "UTF-8";
try {
return s.getBytes(enc);
} catch (UnsupportedEncodingException uee) {
throw new RuntimeException("No " + enc + " support?", uee);
}
}
private static String toString(final byte[] b) {
final String enc = "UTF-8";
try {
return new String(b, 0, b.length, enc);
} catch (UnsupportedEncodingException uee) {
throw new RuntimeException("No " + enc + " support?", uee);
}
}
}

View File

@@ -0,0 +1,61 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.rpc;
import com.google.codereview.internal.SubmitChange.SubmitChangeResponse;
import com.google.protobuf.Message;
import junit.framework.TestCase;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.zip.DeflaterOutputStream;
public class MessageRequestEntityTest extends TestCase {
public void testCompressedEntity() throws Exception {
final Message msg = buildMessage();
final byte[] bin = compress(msg);
final String contentType =
"application/x-google-protobuf"
+ "; name=codereview.internal.SubmitChangeResponse"
+ "; compress=deflate";
final MessageRequestEntity mre = new MessageRequestEntity(msg);
assertTrue(mre.isRepeatable());
assertEquals(bin.length, mre.getContentLength());
assertEquals(contentType, mre.getContentType());
for (int i = 0; i < 5; i++) {
final ByteArrayOutputStream compressed = new ByteArrayOutputStream();
mre.writeRequest(compressed);
assertTrue(Arrays.equals(bin, compressed.toByteArray()));
}
}
private static Message buildMessage() {
final SubmitChangeResponse.Builder r = SubmitChangeResponse.newBuilder();
r.setStatusCode(SubmitChangeResponse.CodeType.PATCHSET_EXISTS);
return r.build();
}
private static byte[] compress(final Message m) throws IOException {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
final DeflaterOutputStream dos = new DeflaterOutputStream(out);
m.writeTo(dos);
dos.close();
return out.toByteArray();
}
}

View File

@@ -0,0 +1,48 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.rpc;
import com.google.protobuf.Message;
import com.google.protobuf.RpcCallback;
import com.google.protobuf.RpcChannel;
import com.google.protobuf.RpcController;
import com.google.protobuf.Descriptors.MethodDescriptor;
import junit.framework.TestCase;
import java.util.LinkedList;
public class MockRpcChannel implements RpcChannel {
private LinkedList<RpcChannel> calls = new LinkedList<RpcChannel>();
public void add(final RpcChannel c) {
calls.add(c);
}
public void callMethod(final MethodDescriptor method,
final RpcController controller, final Message request,
final Message responsePrototype, final RpcCallback<Message> done) {
if (calls.isEmpty()) {
TestCase.fail("Incorrect call for " + method.getFullName());
}
final RpcChannel c = calls.removeFirst();
c.callMethod(method, controller, request, responsePrototype, done);
}
public void assertAllCallsMade() {
TestCase.assertTrue(calls.isEmpty());
}
}

View File

@@ -0,0 +1,68 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.rpc;
import com.google.protobuf.RpcCallback;
import junit.framework.TestCase;
public class SimpleControllerTest extends TestCase {
public void testDefaultConstructor() {
final SimpleController sc = new SimpleController();
assertNull(sc.errorText());
assertFalse(sc.failed());
assertFalse(sc.isCanceled());
}
public void testNotifyCancelUnsupported() {
try {
new SimpleController().notifyOnCancel(new RpcCallback<Object>() {
public void run(Object parameter) {
fail("Callback invoked during notifyOnCancel setup");
}
});
fail("notifyOnCancel accepted a callback");
} catch (UnsupportedOperationException e) {
// Pass
}
}
public void testStartCancelUnsupported() {
try {
new SimpleController().startCancel();
fail("startCancel did not fail");
} catch (UnsupportedOperationException e) {
// Pass
}
}
public void testSetFailed() {
final String reason = "we failed, yes we did";
final SimpleController sc = new SimpleController();
sc.setFailed(reason);
assertTrue(sc.failed());
assertSame(reason, sc.errorText());
}
public void testResetClearedFailure() {
final String reason = "we failed, yes we did";
final SimpleController sc = new SimpleController();
sc.setFailed(reason);
sc.reset();
assertNull(sc.errorText());
assertFalse(sc.failed());
assertFalse(sc.isCanceled());
}
}

View File

@@ -0,0 +1,44 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.codereview.util;
import junit.framework.TestCase;
public class MutableBooleanTest extends TestCase {
public void testCreateDefault() {
final MutableBoolean b = new MutableBoolean();
assertFalse(b.value);
}
public void testCreateTrue() {
final MutableBoolean b = new MutableBoolean(true);
assertTrue(b.value);
}
public void testCreateFalse() {
final MutableBoolean b = new MutableBoolean(false);
assertFalse(b.value);
}
public void testMutable() {
final MutableBoolean b = new MutableBoolean();
assertFalse(b.value);
b.value = true;
assertTrue(b.value);
b.value = false;
assertFalse(b.value);
}
}

4
webapp/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
*.pyc
/froofle
/codereview/*_pb2.py
/codereview/internal/*_pb2.py

64
webapp/TODO Normal file
View File

@@ -0,0 +1,64 @@
Bugs
----
If a user never logs in, someone else can grab their nickname
See the issue tracker at code.google.com/p/gerrit for more bugs
Data Cleaning
-------------
Email addresses should be lowercased before comparing
Nicknames too???
Nicknames should not be allowed to contain multiple internal spaces
nor internal whitespace other than space
Sending Mail
------------
Should allow registered nicknames in Reviewers and CC fields
Normalize emails to nicknames in these fields even when editing, if known
Issues
------
Archive issues (that's per user rather than per issue)
Patch Sets
----------
Edit patch set message
Delete patch sets
Searching, Organizing
---------------------
View all open issues by base, or by repository
View all closed issues (issue 15 in the tracker)
Commenting
----------
Add stars
Add non-inline comments per file, per patchset, per issue
Keyboard shortcut for entering a comment at the blue line
(isn't this already in the JS code?)
Diffs and Patches
-----------------
Improve UI for selecting patch set deltas; handle missing files better
Syntax colorization
Record revision and show in UI; indicate action (add/edit/delete)
Add line length option
Need a more powerful way to specify the URL for finding a revision (?)
User Experience
---------------
Make Edit Issue show the form inline instead of opening a new page?
Right-justify the "Id" and "Drafts (mine)" headings in the Issues list table
Software Engineering
--------------------
Unittests

0
webapp/__init__.py Normal file
View File

28
webapp/app.yaml Normal file
View File

@@ -0,0 +1,28 @@
application: gerrit-code-review-tool-demo
version: 1
runtime: python
api_version: 1
default_expiration: 7d # This is good for images, which never change
handlers:
- url: /(robots.txt|favicon.ico)
static_files: static/\1
upload: static/(robots.txt|favicon.ico)
- url: /google7db36eb2cc527940.html
static_files: static/robots.txt
upload: static/robots.txt
- url: /(application_version)
static_files: static/\1
mime_type: text/plain
expiration: 1s
upload: static/application_version
- url: /static
static_dir: static
- url: .*
script: main.py

View File

View File

@@ -0,0 +1,31 @@
# Copyright 2008 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Init the dev instance."""
from django.http import HttpResponse
import models
from view_util import devenv_required
@devenv_required
def dev_init(request):
settings = models.Settings.get_settings()
key = settings.internal_api_key
return HttpResponse(
"[codereview]\n"
" internalapikey = %s\n"
"\n"
% (key))

View File

@@ -0,0 +1,92 @@
# Copyright 2008 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
from google.appengine.ext import db
from google.appengine.api import mail
from google.appengine.api import users
import django.template
import models
import email
def get_default_sender():
return models.Settings.get_settings().from_email
def _encode_safely(s):
"""Helper to turn a unicode string into 8-bit bytes."""
if isinstance(s, unicode):
s = s.encode('utf-8')
return s
def _to_email_string(obj):
if isinstance(obj, str):
return obj
elif isinstance(obj, unicode):
return obj.encode('utf-8')
elif isinstance(obj, db.Email):
account = models.Account.get_account_for_email(obj)
elif isinstance(obj, users.User):
account = models.Account.get_account_for_user(obj)
elif isinstance(obj, models.Account):
account = obj
if account:
result = account.get_email_formatted()
else:
result = str(email)
return _encode_safely(result)
def send(sender, to, subject, template, template_args):
"""Sends an email based on a template.
All email address parameters can be: strings, unicode, db.Email users.User
or models.Account objects.
Args:
sender: The From address. Null if it should be sent from the role acct.
to: An email address or a list of email address to be in the To field.
subject: The subject line.
template: The name of the template file to use from the mail dir.
template_args: A map of args to be pasaed to the template
"""
if not sender:
sender_string = get_default_sender()
if not sender_string:
logging.warn('not sending email because there is no from address')
return 'not sending email because there is no from address'
else:
sender_string = _to_email_string(sender)
to_string = [_to_email_string(e) for e in to]
body = django.template.loader.render_to_string(template, template_args)
mail.send_mail(sender=sender_string, to=to_string, subject=subject, body=body)
def make_change_subject(change):
subject = "(%s) %s" % (change.dest_project.name, change.subject)
if change.message_set.count(1) > 0:
subject = 'Re: ' + subject
return subject
def send_change_message(request, change, template, template_args):
to_users = set([change.owner] + change.reviewers + change.cc)
subject = make_change_subject(change)
args = {
'url': request.build_absolute_uri('/%s' % change.key().id()),
}
if template_args:
args.update(template_args)
email.send(request.user, to_users, subject, template, args)

709
webapp/codereview/engine.py Normal file
View File

@@ -0,0 +1,709 @@
# Copyright 2008 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Diff rendering in HTML for Gerrit."""
# Python imports
import re
import cgi
import difflib
import logging
import urlparse
# AppEngine imports
from google.appengine.api import urlfetch
from google.appengine.api import users
from google.appengine.ext import db
# Django imports
from django.template import loader
# Local imports
import library
import models
import patching
import intra_region_diff
# NOTE: this function is duplicated in upload.py, keep them in sync.
def SplitPatch(data):
"""Splits a patch into separate pieces for each file.
Args:
data: A string containing the output of svn diff.
Returns:
A list of 2-tuple (filename, text) where text is the svn diff output
pertaining to filename.
"""
patches = []
filename = None
diff = []
for line in data.splitlines(True):
new_filename = None
if line.startswith('Index:'):
unused, new_filename = line.split(':', 1)
new_filename = new_filename.strip()
elif line.startswith('Property changes on:'):
unused, temp_filename = line.split(':', 1)
# When a file is modified, paths use '/' between directories, however
# when a property is modified '\' is used on Windows. Make them the same
# otherwise the file shows up twice.
temp_filename = temp_filename.strip().replace('\\', '/')
if temp_filename != filename:
# File has property changes but no modifications, create a new diff.
new_filename = temp_filename
if new_filename:
if filename and diff:
patches.append((filename, ''.join(diff)))
filename = new_filename
diff = [line]
continue
if diff is not None:
diff.append(line)
if filename and diff:
patches.append((filename, ''.join(diff)))
return patches
def RenderDiffTableRows(request, old_lines, chunks, patch,
colwidth=80,
debug=False,
context=models.DEFAULT_CONTEXT):
"""Render the HTML table rows for a side-by-side diff for a patch.
Args:
request: Django Request object.
old_lines: List of lines representing the original file.
chunks: List of chunks as returned by patching.ParsePatchToChunks().
patch: A models.Patch instance.
colwidth: Optional column width (default 80).
debug: Optional debugging flag (default False).
context: Maximum number of rows surrounding a change (default CONTEXT).
Yields:
Strings, each of which represents the text rendering one complete
pair of lines of the side-by-side diff, possibly including comments.
Each yielded string may consist of several <tr> elements.
"""
rows = _RenderDiffTableRows(request, old_lines, chunks, patch,
colwidth, debug)
return _CleanupTableRowsGenerator(rows, context)
def RenderDiff2TableRows(request, old_lines, old_patch, new_lines, new_patch,
colwidth=80,
debug=False,
context=models.DEFAULT_CONTEXT):
"""Render the HTML table rows for a side-by-side diff between two patches.
Args:
request: Django Request object.
old_lines: List of lines representing the patched file on the left.
old_patch: The models.Patch instance corresponding to old_lines.
new_lines: List of lines representing the patched file on the right.
new_patch: The models.Patch instance corresponding to new_lines.
colwidth: Optional column width (default 80).
debug: Optional debugging flag (default False).
context: Maximum number of visible context lines (default models.DEFAULT_CONTEXT).
Yields:
Strings, each of which represents the text rendering one complete
pair of lines of the side-by-side diff, possibly including comments.
Each yielded string may consist of several <tr> elements.
"""
rows = _RenderDiff2TableRows(request, old_lines, old_patch,
new_lines, new_patch, colwidth, debug)
return _CleanupTableRowsGenerator(rows, context)
def _CleanupTableRowsGenerator(rows, context):
"""Cleanup rows returned by _TableRowGenerator for output.
Args:
rows: List of tuples (tag, text)
context: Maximum number of visible context lines.
Yields:
Rows marked as 'equal' are possibly contracted using _ShortenBuffer().
Stops on rows marked as 'error'.
"""
buffer = []
for tag, text in rows:
if tag == 'equal':
buffer.append(text)
continue
else:
for t in _ShortenBuffer(buffer, context):
yield t
buffer = []
yield text
if tag == 'error':
yield None
break
if buffer:
for t in _ShortenBuffer(buffer, context):
yield t
def _ShortenBuffer(buffer, context):
"""Render a possibly contracted series of HTML table rows.
Args:
buffer: a list of strings representing HTML table rows.
context: Maximum number of visible context lines.
Yields:
If the buffer has fewer than 3 times context items, yield all
the items. Otherwise, yield the first context items, a single
table row representing the contraction, and the last context
items.
"""
if len(buffer) < 3*context:
for t in buffer:
yield t
else:
last_id = None
for t in buffer[:context]:
m = re.match('^<tr( name="hook")? id="pair-(?P<rowcount>\d+)">', t)
if m:
last_id = int(m.groupdict().get("rowcount"))
yield t
skip = len(buffer) - 2*context
if skip <= 10:
expand_link = ('<a href="javascript:M_expandSkipped(%(before)d, '
'%(after)d, \'b\', %(skip)d)">Show</a>')
else:
expand_link = ('<a href="javascript:M_expandSkipped(%(before)d, '
'%(after)d, \'t\', %(skip)d)">Show 10 above</a> '
'<a href="javascript:M_expandSkipped(%(before)d, '
'%(after)d, \'b\', %(skip)d)">Show 10 below</a> ')
expand_link = expand_link % {'before': last_id+1,
'after': last_id+skip,
'skip': last_id}
yield ('<tr id="skip-%d"><td colspan="2" align="center" '
'style="background:lightblue">'
'(...skipping <span id="skipcount-%d">%d</span> matching lines...) '
'<span id="skiplinks-%d">%s</span>'
'</td></tr>\n' % (last_id, last_id, skip,
last_id, expand_link))
for t in buffer[-context:]:
yield t
def _RenderDiff2TableRows(request, old_lines, old_patch, new_lines, new_patch,
colwidth=80, debug=False):
"""Internal version of RenderDiff2TableRows().
Args:
The same as for RenderDiff2TableRows.
Yields:
Tuples (tag, row) where tag is an indication of the row type.
"""
old_dict = {}
new_dict = {}
for patch, dct in [(old_patch, old_dict), (new_patch, new_dict)]:
# XXX GQL doesn't support OR yet... Otherwise we'd be using that.
for comment in models.Comment.gql(
'WHERE patch = :1 AND left = FALSE ORDER BY date', patch):
if comment.draft and comment.author != request.user:
continue # Only show your own drafts
comment.complete(patch)
lst = dct.setdefault(comment.lineno, [])
lst.append(comment)
library.prefetch_names([comment.author])
return _TableRowGenerator(old_patch, old_dict, len(old_lines)+1, 'new',
new_patch, new_dict, len(new_lines)+1, 'new',
_GenerateTriples(old_lines, new_lines),
colwidth, debug)
def _GenerateTriples(old_lines, new_lines):
"""Helper for _RenderDiff2TableRows yielding input for _TableRowGenerator.
Args:
old_lines: List of lines representing the patched file on the left.
new_lines: List of lines representing the patched file on the right.
Yields:
Tuples (tag, old_slice, new_slice) where tag is a tag as returned by
difflib.SequenceMatchser.get_opcodes(), and old_slice and new_slice
are lists of lines taken from old_lines and new_lines.
"""
sm = difflib.SequenceMatcher(None, old_lines, new_lines)
for tag, i1, i2, j1, j2 in sm.get_opcodes():
yield tag, old_lines[i1:i2], new_lines[j1:j2]
def _GetComments(request):
"""Helper that returns comments for a patch.
Args:
request: Django Request object.
Returns:
A 2-tuple of (old, new) where old/new are dictionaries that holds comments
for that file, mapping from line number to a Comment entity.
"""
old_dict = {}
new_dict = {}
# XXX GQL doesn't support OR yet... Otherwise we'd be using
# .gql('WHERE patch = :1 AND (draft = FALSE OR author = :2) ORDER BY data',
# patch, request.user)
for comment in models.Comment.gql('WHERE patch = :1 ORDER BY date',
request.patch):
if comment.draft and comment.author != request.user:
continue # Only show your own drafts
comment.complete(request.patch)
if comment.left:
dct = old_dict
else:
dct = new_dict
dct.setdefault(comment.lineno, []).append(comment)
library.prefetch_names([comment.author])
return old_dict, new_dict
def _RenderDiffTableRows(request, old_lines, chunks, patch,
colwidth=80, debug=False):
"""Internal version of RenderDiffTableRows().
Args:
The same as for RenderDiffTableRows.
Yields:
Tuples (tag, row) where tag is an indication of the row type.
"""
old_dict = {}
new_dict = {}
if patch:
old_dict, new_dict = _GetComments(request)
old_max, new_max = _ComputeLineCounts(old_lines, chunks)
return _TableRowGenerator(patch, old_dict, old_max, 'old',
patch, new_dict, new_max, 'new',
patching.PatchChunks(old_lines, chunks),
colwidth, debug)
def _TableRowGenerator(old_patch, old_dict, old_max, old_snapshot,
new_patch, new_dict, new_max, new_snapshot,
triple_iterator, colwidth=80, debug=False):
"""Helper function to render side-by-side table rows.
Args:
old_patch: First models.Patch instance.
old_dict: Dictionary with line numbers as keys and comments as values (left)
old_max: Line count of the patch on the left.
old_snapshot: A tag used in the comments form.
new_patch: Second models.Patch instance.
new_dict: Same as old_dict, but for the right side.
new_max: Line count of the patch on the right.
new_snapshot: A tag used in the comments form.
triple_iterator: Iterator that yields (tag, old, new) triples.
colwidth: Optional column width (default 80).
debug: Optional debugging flag (default False).
Yields:
Tuples (tag, row) where tag is an indication of the row type and
row is an HTML fragment representing one or more <td> elements.
"""
diff_params = intra_region_diff.GetDiffParams(dbg=debug)
ndigits = 1 + max(len(str(old_max)), len(str(new_max)))
indent = 1 + ndigits
old_offset = new_offset = 0
row_count = 0
# Render a row with a message if a side is empty or both sides are equal.
if old_patch == new_patch and (old_max == 0 or new_max == 0):
if old_max == 0:
msg_old = '(Empty)'
else:
msg_old = ''
if new_max == 0:
msg_new = '(Empty)'
else:
msg_new = ''
yield '', ('<tr><td class="info">%s</td>'
'<td class="info">%s</td></tr>' % (msg_old, msg_new))
# TODO(sop)
#elif old_patch == new_patch:
# old_patch.patch_hash == new_patch.patch_hash
# yield '', ('<tr><td class="info" colspan="2">'
# '(Both sides are equal)</td></tr>')
for tag, old, new in triple_iterator:
if tag.startswith('error'):
yield 'error', '<tr><td><h3>%s</h3></td></tr>\n' % cgi.escape(tag)
return
old1 = old_offset
old_offset = old2 = old1 + len(old)
new1 = new_offset
new_offset = new2 = new1 + len(new)
old_buff = []
new_buff = []
frag_list = []
do_ir_diff = tag == 'replace' and intra_region_diff.CanDoIRDiff(old, new)
for i in xrange(max(len(old), len(new))):
row_count += 1
old_lineno = old1 + i + 1
new_lineno = new1 + i + 1
old_valid = old1+i < old2
new_valid = new1+i < new2
# Start rendering the first row
frags = []
if i == 0 and tag != 'equal':
# Mark the first row of each non-equal chunk as a 'hook'.
frags.append('<tr name="hook"')
else:
frags.append('<tr')
frags.append(' id="pair-%d">' % row_count)
old_intra_diff = ''
new_intra_diff = ''
if old_valid:
old_intra_diff = old[i]
if new_valid:
new_intra_diff = new[i]
frag_list.append(frags)
if do_ir_diff:
# Don't render yet. Keep saving state necessary to render the whole
# region until we have encountered all the lines in the region.
old_buff.append([old_valid, old_lineno, old_intra_diff])
new_buff.append([new_valid, new_lineno, new_intra_diff])
else:
# We render line by line as usual if do_ir_diff is false
old_intra_diff = intra_region_diff.Fold(
old_intra_diff, colwidth + indent, indent, indent)
new_intra_diff = intra_region_diff.Fold(
new_intra_diff, colwidth + indent, indent, indent)
old_buff_out = [[old_valid, old_lineno,
(old_intra_diff, True, None)]]
new_buff_out = [[new_valid, new_lineno,
(new_intra_diff, True, None)]]
for tg, frag in _RenderDiffInternal(old_buff_out, new_buff_out,
ndigits, tag, frag_list,
do_ir_diff,
old_dict, new_dict,
old_patch, new_patch,
old_snapshot, new_snapshot,
colwidth, debug):
yield tg, frag
frag_list = []
if do_ir_diff:
# So this was a replace block which means that the whole region still
# needs to be rendered.
old_lines = [b[2] for b in old_buff]
new_lines = [b[2] for b in new_buff]
ret = intra_region_diff.IntraRegionDiff(old_lines, new_lines,
diff_params)
old_chunks, new_chunks, ratio = ret
old_tag = 'old'
new_tag = 'new'
old_diff_out = intra_region_diff.RenderIntraRegionDiff(
old_lines, old_chunks, old_tag, ratio,
limit=colwidth, indent=indent,
dbg=debug)
new_diff_out = intra_region_diff.RenderIntraRegionDiff(
new_lines, new_chunks, new_tag, ratio,
limit=colwidth, indent=indent,
dbg=debug)
for (i, b) in enumerate(old_buff):
b[2] = old_diff_out[i]
for (i, b) in enumerate(new_buff):
b[2] = new_diff_out[i]
for tg, frag in _RenderDiffInternal(old_buff, new_buff,
ndigits, tag, frag_list,
do_ir_diff,
old_dict, new_dict,
old_patch, new_patch,
old_snapshot, new_snapshot,
colwidth, debug):
yield tg, frag
old_buff = []
new_buff = []
def _CleanupTableRows(rows):
"""Cleanup rows returned by _TableRowGenerator.
Args:
rows: Sequence of (tag, text) tuples.
Yields:
Rows marked as 'equal' are possibly contracted using _ShortenBuffer().
Stops on rows marked as 'error'.
"""
buffer = []
for tag, text in rows:
if tag == 'equal':
buffer.append(text)
continue
else:
for t in _ShortenBuffer(buffer):
yield t
buffer = []
yield text
if tag == 'error':
yield None
break
if buffer:
for t in _ShortenBuffer(buffer):
yield t
def _RenderDiffInternal(old_buff, new_buff, ndigits, tag, frag_list,
do_ir_diff, old_dict, new_dict,
old_patch, new_patch,
old_snapshot, new_snapshot,
colwidth, debug):
"""Helper for _TableRowGenerator()."""
obegin = (intra_region_diff.BEGIN_TAG %
intra_region_diff.COLOR_SCHEME['old']['match'])
nbegin = (intra_region_diff.BEGIN_TAG %
intra_region_diff.COLOR_SCHEME['new']['match'])
oend = intra_region_diff.END_TAG
nend = oend
user = users.get_current_user()
for i in xrange(len(old_buff)):
tg = tag
old_valid, old_lineno, old_out = old_buff[i]
new_valid, new_lineno, new_out = new_buff[i]
old_intra_diff, old_has_newline, old_debug_info = old_out
new_intra_diff, new_has_newline, new_debug_info = new_out
frags = frag_list[i]
# Render left text column
frags.append(_RenderDiffColumn(old_patch, old_valid, tag, ndigits,
old_lineno, obegin, oend, old_intra_diff,
do_ir_diff, old_has_newline, 'old'))
# Render right text column
frags.append(_RenderDiffColumn(new_patch, new_valid, tag, ndigits,
new_lineno, nbegin, nend, new_intra_diff,
do_ir_diff, new_has_newline, 'new'))
# End rendering the first row
frags.append('</tr>\n')
if debug:
frags.append('<tr>')
if old_debug_info:
frags.append('<td class="debug-info">%s</td>' %
old_debug_info.replace('\n', '<br>'))
else:
frags.append('<td></td>')
if new_debug_info:
frags.append('<td class="debug-info">%s</td>' %
new_debug_info.replace('\n', '<br>'))
else:
frags.append('<td></td>')
frags.append('</tr>\n')
if old_patch or new_patch:
# Start rendering the second row
if ((old_valid and old_lineno in old_dict) or
(new_valid and new_lineno in new_dict)):
tg += '_comment'
frags.append('<tr class="inline-comments" name="hook">')
else:
frags.append('<tr class="inline-comments">')
# Render left inline comments
frags.append(_RenderInlineComments(old_valid, old_lineno, old_dict,
user, old_patch, old_snapshot, 'old'))
# Render right inline comments
frags.append(_RenderInlineComments(new_valid, new_lineno, new_dict,
user, new_patch, new_snapshot, 'new'))
# End rendering the second row
frags.append('</tr>\n')
# Yield the combined fragments
yield tg, ''.join(frags)
def _RenderDiffColumn(patch, line_valid, tag, ndigits, lineno, begin, end,
intra_diff, do_ir_diff, has_newline, prefix):
"""Helper function for _RenderDiffInternal().
Returns:
A rendered column.
"""
if line_valid:
cls_attr = '%s%s' % (prefix, tag)
if tag == 'equal':
lno = '%*d' % (ndigits, lineno)
else:
lno = _MarkupNumber(ndigits, lineno, 'u')
if tag == 'replace':
col_content = ('%s%s %s%s' % (begin, lno, end, intra_diff))
# If IR diff has been turned off or there is no matching new line at
# the end then switch to dark background CSS style.
if not do_ir_diff or not has_newline:
cls_attr = cls_attr + '1'
else:
col_content = '%s %s' % (lno, intra_diff)
return '<td class="%s" id="%scode%d">%s</td>' % (cls_attr, prefix,
lineno, col_content)
else:
return '<td class="%sblank"></td>' % prefix
def _RenderInlineComments(line_valid, lineno, data, user,
patch, snapshot, prefix):
"""Helper function for _RenderDiffInternal().
Returns:
Rendered comments.
"""
comments = []
if line_valid:
comments.append('<td id="%s-line-%s">' % (prefix, lineno))
if lineno in data:
comments.append(
_ExpandTemplate('inline_comment.html',
inline_draft_url='/inline_draft',
user=user,
patch=patch,
patchset=patch.patchset,
change=patch.patchset.change,
snapshot=snapshot,
side='a' if prefix == 'old' else 'b',
comments=data[lineno],
lineno=lineno,
))
comments.append('</td>')
else:
comments.append('<td></td>')
return ''.join(comments)
def RenderUnifiedTableRows(request, parsed_lines):
"""Render the HTML table rows for a unified diff for a patch.
Args:
request: Django Request object.
parsed_lines: List of tuples for each line that contain the line number,
if they exist, for the old and new file.
Returns:
A list of html table rows.
"""
old_dict, new_dict = _GetComments(request)
rows = []
for old_line_no, new_line_no, line_text in parsed_lines:
row1_id = row2_id = ''
# When a line is unchanged (i.e. both old_line_no and new_line_no aren't 0)
# pick the old column line numbers when adding a comment.
if old_line_no:
row1_id = 'id="oldcode%d"' % old_line_no
row2_id = 'id="old-line-%d"' % old_line_no
elif new_line_no:
row1_id = 'id="newcode%d"' % new_line_no
row2_id = 'id="new-line-%d"' % new_line_no
rows.append('<tr><td class="udiff" %s>%s</td></tr>' %
(row1_id, cgi.escape(line_text)))
frags = []
if old_line_no in old_dict or new_line_no in new_dict:
frags.append('<tr class="inline-comments" name="hook">')
if old_line_no in old_dict:
dct = old_dict
line_no = old_line_no
snapshot = 'old'
else:
dct = new_dict
line_no = new_line_no
snapshot = 'new'
frags.append(_RenderInlineComments(True, line_no, dct, request.user,
request.patch, snapshot, snapshot))
else:
frags.append('<tr class="inline-comments">')
frags.append('<td ' + row2_id +'></td>')
frags.append('</tr>')
rows.append(''.join(frags))
return rows
def _ComputeLineCounts(old_lines, chunks):
"""Compute the length of the old and new sides of a diff.
Args:
old_lines: List of lines representing the original file.
chunks: List of chunks as returned by patching.ParsePatchToChunks().
Returns:
A tuple (old_len, new_len) representing len(old_lines) and
len(new_lines), where new_lines is the list representing the
result of applying the patch chunks to old_lines, however, without
actually computing new_lines.
"""
old_len = len(old_lines)
new_len = old_len
if chunks:
(old_a, old_b), (new_a, new_b), old_lines, new_lines = chunks[-1]
new_len += new_b - old_b
return old_len, new_len
def _MarkupNumber(ndigits, number, tag):
"""Format a number in HTML in a given width with extra markup.
Args:
ndigits: the total width available for formatting
number: the number to be formatted
tag: HTML tag name, e.g. 'u'
Returns:
An HTML string that displays as ndigits wide, with the
number right-aligned and surrounded by an HTML tag; for example,
_MarkupNumber(42, 4, 'u') returns ' <u>42</u>'.
"""
formatted_number = str(number)
space_prefix = ' ' * (ndigits - len(formatted_number))
return '%s<%s>%s</%s>' % (space_prefix, tag, formatted_number, tag)
def _ExpandTemplate(name, **params):
"""Wrapper around django.template.loader.render_to_string().
For convenience, this takes keyword arguments instead of a dict.
"""
return loader.render_to_string(name, params)
def ToText(text):
"""Helper to turn a string into a db.Text instance.
Args:
text: a string.
Returns:
A db.Text instance.
"""
try:
return db.Text(text, encoding='utf-8')
except UnicodeDecodeError:
return db.Text(text, encoding='latin-1')

337
webapp/codereview/fields.py Normal file
View File

@@ -0,0 +1,337 @@
# Copyright 2008 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Custom fields and widgets for Gerrit.
This requires Django 0.97.pre.
"""
### Imports ###
import string
import logging
import django.forms.widgets
import django.forms.fields
from django import forms
from django.utils import encoding
from django.utils import safestring
from django.forms import util
from django.utils import html
from django.utils import simplejson
from google.appengine.ext import db
import models
def person_to_dict(v):
entry = {}
if isinstance(v, models.Account):
entry["type"] = "user"
entry["key"] = "user/" + v.email
entry["email"] = v.email
entry["real_name"] = v.real_name
entry["sort_key"] = "2/" + unicode(v.user)
elif isinstance(v, models.AccountGroup):
entry["type"] = "group"
entry["key"] = "group/" + str(v.key())
entry["name"] = v.name
entry["sort_key"] = "1/" + unicode(v.name)
else:
raise AssertionError("bad value: " + str(v))
return entry
def people_to_dicts(value):
data = []
for v in value:
if isinstance(v, list):
data.extend(people_to_dicts(v))
elif v:
data.append(person_to_dict(v))
data.sort(lambda a,b: cmp(a["sort_key"], b["sort_key"]))
return data
### User/Group Field ###
class UserGroupWidget(django.forms.widgets.Widget):
"""The widget that is used with UserGroupField."""
def __init__(self, allow_users=True, allow_groups=True, attrs=None):
self.attrs = {'cols': '40', 'rows': '10'}
self.allow_users = allow_users
self.allow_groups = allow_groups
if attrs:
self.attrs.update(attrs)
def render(self, name, value, attrs=None):
if value is None:
value = []
return safestring.mark_safe(
u"""
<div id="%(name)s_mom"></div>
<script>
UserGroupField_insertField(document.getElementById('%(name)s_mom'),
'%(name)s', %(allow_users)s, %(allow_groups)s, %(initial)s);
</script>
""" % { "name":name,
"initial":self._render_initial_js(value),
"allow_users": ("true" if self.allow_users else "false"),
"allow_groups": ("true" if self.allow_groups else "false"),
})
def _render_initial_js(self, value):
data = people_to_dicts(value)
return "[%s]" % ','.join(map(simplejson.dumps, data))
def value_from_datadict(self, data, files, name):
return data.getlist(name + "_keys")
class UserGroupField(django.forms.fields.Field):
"""A Field that picks a list of users and groups."""
def __init__(self, *args, **kwargs):
self.allow_users = kwargs.pop("allow_users", True)
self.allow_groups = kwargs.pop("allow_groups", True)
self.widget = UserGroupWidget(self.allow_users, self.allow_groups)
super(UserGroupField, self).__init__(*args, **kwargs)
def clean(self, data, initial=None):
def get_correct_model(key):
(type,id) = key.split("/", 1)
if id:
try:
if type == "user":
return models.Account.get_account_for_email(id)
elif type == "group":
return models.AccountGroup.get(id)
except db.BadKeyError, v:
pass
raise forms.ValidationError("invalid key")
keys = data
result = [get_correct_model(key) for key in keys]
super(UserGroupField, self).clean(initial or result)
return result
@classmethod
def get_users(cls, cleaned):
"""Returns the users, given the cleaned data from the form.
e.g.
model_obj.usrs = fields.UserGroupField.get_users(form.cleaned_data['field'])
"""
return [x.user for x in cleaned if isinstance(x, models.Account)]
@classmethod
def get_groups(cls, cleaned):
"""Returns the groups, given the cleaned data from the form.
e.g.
groups = fields.UserGroupField.get_users(form.cleaned_data['field'])
"""
return [x for x in cleaned if isinstance(x, models.AccountGroup)]
@classmethod
def get_group_keys(cls, cleaned):
"""Returns keys for the groups, given the cleaned data from the form.
e.g.
groups = fields.UserGroupField.get_users(form.cleaned_data['field'])
"""
return [x.key() for x in cleaned if isinstance(x, models.AccountGroup)]
@classmethod
def get_user_and_group_keys(cls, cleaned):
"""Returns the users and the groups for the cleaned data from the form.
e.g.
(model_obj.users,model_obj.groups
) = fields.UserGroupField.get_user_and_group_keys(
form.cleaned_data['field'])
"""
return (UserGroupField.get_users(cleaned),
UserGroupField.get_group_keys(cleaned))
@classmethod
def field_value_for_keys(cls, users=[], groups=[]):
"""Return the value suitable for this field from a list keys.
e.g.
form_initial_values['field'] = fields.UserGroupField.field_value_for_keys(
users, group_keys)
"""
return ([models.AccountGroup.get(k) for k in groups]
+ [models.Account.get_account_for_user(u) for u in users])
### Approvers Field ###
class ApproversWidget(django.forms.widgets.Widget):
"""The widget for ApproversField"""
def __init__(self, allow_users=True, allow_groups=True, attrs=None,
approvers=None, verifiers=None):
self.attrs = {'cols': '40', 'rows': '10'}
if attrs:
self.attrs.update(attrs)
self.approvers = approvers or UserGroupWidget();
self.verifiers = verifiers or UserGroupWidget();
def render(self, name, value, attrs=None):
if value is None:
value = []
styles = self.attrs.get("styles", {})
return safestring.mark_safe(
u"""
<div id="%(name)s_mom"></div>
<script>
ApproversField_insertField('%(name)s_mom', '%(name)s', %(initial)s,
%(styles)s);
</script>
""" % {
"name": name,
"initial": self._render_initial_js(name, value),
"styles": simplejson.dumps(styles),
})
def _render_initial_js(self, name, value):
data = []
index = 0
for v in value:
# BEGIN DEBUGGING
key = "initial_%d" % index
files = v["files"]
bad_files = v.get("bad_files", [])
approvers = v["approvers"]
verifiers = v["verifiers"]
# END DEBUGGING
entry = {}
entry["key"] = "initial_%d" % index
entry["files"] = encoding.force_unicode("\n".join(files))
entry["bad_files"] = map(encoding.force_unicode, bad_files)
entry["approvers"] = people_to_dicts(approvers)
entry["verifiers"] = people_to_dicts(verifiers)
data.append(entry)
index = index + 1
rows = []
for entry in data:
rows.append(simplejson.dumps(entry))
return "[%s]" % ','.join(rows)
def value_from_datadict(self, data, files, name):
result = []
keys = data.getlist(name + "_keys")
for key in keys:
field_key = "%s_%s" % (name, key)
files = filter(string.strip, data.get(field_key + "_files").splitlines())
bad_files = []
for f in files:
if not models.ApprovalRight.validate_file(f):
err = True
bad_files.append(f)
approvers = self.approvers.value_from_datadict(data, files,
field_key + "_approvers")
verifiers = self.verifiers.value_from_datadict(data, files,
field_key + "_verifiers")
result.append({
"key": key,
"files": files,
"bad_files": bad_files,
"approvers": approvers,
"verifiers": verifiers,
})
return result
class ApproversField(django.forms.fields.Field):
"""A Field to pick which users/groups can edit which field"""
approvers = UserGroupField();
verifiers = UserGroupField();
widget = ApproversWidget(approvers=approvers.widget,
verifiers=verifiers.widget, attrs={"styles": {
"approval": "approval"
}})
def __init__(self, *args, **kwargs):
super(ApproversField, self).__init__(*args, **kwargs)
def clean(self, data, initial=None):
result = []
err = False
for d in data:
files = d["files"]
if len(d["bad_files"]) > 0:
err = True
approvers = self.approvers.clean(d["approvers"])
verifiers = self.verifiers.clean(d["verifiers"])
result.append({"files": files, "approvers": approvers,
"verifiers": verifiers})
if False:
for r in result:
logging.info("clean: files=" + str(r["files"]))
logging.info(" approvers=" + str(r["approvers"]))
logging.info(" verifiers=" + str(r["verifiers"]))
super(ApproversField, self).clean(initial or result)
if err:
raise forms.ValidationError("invalid files")
return result
### Project field ###
class ProjectSelectWidget(django.forms.widgets.Widget):
"""A widget that lets a user pick a set of projects."""
def __init__(self, attrs=None):
super(ProjectSelectWidget, self).__init__(attrs)
if attrs:
self.attrs.update(attrs)
def render(self, name, value, attrs=None):
if value is None:
value = []
project_list = [{'name': p.name, 'key': str(p.key())}
for p in models.Project.get_all_projects()]
return safestring.mark_safe(
u"""
<div id="%(name)s_mom"></div>
<script>
ProjectField_insertField(document.getElementById('%(name)s_mom'),
'%(name)s', %(project_list)s, %(initial)s);
</script>
""" % { "name": name,
"project_list": self._render_js_list(project_list),
"initial": self._render_js_list([str(v) for v in value]),
})
def _render_js_list(self, value):
return "[%s]" % ','.join(map(simplejson.dumps, value))
def value_from_datadict(self, data, files, name):
return set([v.strip() for v in data.getlist(name) if len(v.strip()) > 0])
class ProjectSelectField(django.forms.fields.Field):
"""A Field that lets a user pick a set of projects."""
def __init__(self, *args, **kwargs):
self.widget = ProjectSelectWidget()
super(ProjectSelectField, self).__init__(*args, **kwargs)
def clean(self, data, initial=None):
objects = models.Project.get(data)
result = [o.key() for o in objects if o]
super(ProjectSelectField, self).clean(initial or result)
return result

View File

@@ -0,0 +1,148 @@
# Copyright 2008 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Git specific data model (schema) for Gerrit."""
# Python imports
import hashlib
import logging
import re
# AppEngine imports
from google.appengine.ext import db
from google.appengine.api import users
# Gerrit imports
from models import gql
import models
import memcache
### Exceptions ###
class InvalidBundleId(Exception):
"""Bundle does not exist in data store."""
class InvalidBundleState(Exception):
"""Bundle has different state than expected."""
### Bundles ###
class ReceivedBundleSegment(models.BackedUpModel):
"""Binary segment of a submitted bundle."""
# parent == ReceivedBundle
segment_id = db.IntegerProperty(required=True) # == key
bundle_data = db.BlobProperty()
class ReceivedBundle(models.BackedUpModel):
"""A Git bundle submitted for review."""
_NewIsEmpty = memcache.Key("ReceivedBundle.NewIsEmpty")
STATE_UPLOADING = "UPLOADING"
STATE_NEW = "NEW"
STATE_UNPACKING = "UNPACKING"
STATE_UNPACKED = "UNPACKED"
STATE_INVALID = "INVALID"
STATE_SUSPENDED = "SUSPENDED"
state = db.StringProperty(required=True, default=STATE_UPLOADING,
choices=(STATE_UPLOADING,
STATE_NEW,
STATE_UNPACKING,
STATE_UNPACKED,
STATE_INVALID,
STATE_SUSPENDED))
invalid_details = db.TextProperty()
# Where does this bundle merge to?
dest_project = db.ReferenceProperty(models.Project, required=True)
dest_branch = db.ReferenceProperty(models.Branch, required=True)
# Who submitted this bundle, and when.
owner = db.UserProperty(required=True)
created = db.DateTimeProperty(required=True, auto_now_add=True)
modified = db.DateTimeProperty(required=True, auto_now=True)
# How much bundle is there?
n_segments = db.IntegerProperty(required=True, default=0)
contained_objects = db.StringListProperty()
@classmethod
def lock_next_new(cls):
if ReceivedBundle._NewIsEmpty.get() == 1:
return None
try:
rb = cls._lock_next_new_imp()
except Timeout:
return None
except TransactionFailedError:
return None
if rb is None:
ReceivedBundle._NewIsEmpty.set(1)
return rb
def ready(self):
ReceivedBundle._NewIsEmpty.clear()
@classmethod
def _lock_next_new_imp(cls):
for attempt in xrange(5):
ro_rb = gql(cls, "WHERE state = :1 ORDER BY created",
ReceivedBundle.STATE_NEW).get()
if ro_rb is None:
return None
def trans(key):
rb = db.get(key)
if rb.state == ReceivedBundle.STATE_NEW:
rb.state = ReceivedBundle.STATE_UNPACKING
rb.put()
return True
return False
if db.run_in_transaction(trans, ro_rb.key()):
return ro_rb
return None
@classmethod
def update_state(cls, keystr, old_state, new_state, err_msg):
key = db.Key(keystr)
if key.kind() != cls.__name__:
raise InvalidBundleId, keystr
def trans():
rb = db.get(key)
if rb is None:
raise InvalidBundleId, keystr
if rb.state != old_state:
raise InvalidBundleState, "%s != %s" % (rb.state, old_state)
rb.state = new_state
rb.invalid_details = err_msg
rb.put()
db.run_in_transaction(trans)
def set_segment(self, segment_id, data):
key = 's%d' % segment_id
ReceivedBundleSegment.get_or_insert(
key,
segment_id = segment_id,
bundle_data = db.Blob(data),
parent = self)
def get_segment(self, segment_id):
key = 's%d' % segment_id
return ReceivedBundleSegment.get_by_key_name(key, parent = self)

View File

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