Initial Contribution
This commit is contained in:
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
/.java_bin
|
||||
/.java_src
|
||||
/.jgit_version
|
||||
/.protobuf_version
|
||||
/config.mak
|
||||
/release
|
||||
/test
|
||||
202
COPYING
Normal file
202
COPYING
Normal 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
90
DEPLOY_GAE
Normal 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
182
DEPLOY_MGR
Normal 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
31
GIT-VERSION-GEN
Executable 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
401
Makefile
Normal 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
BIN
crm-data.tar.gz
Normal file
Binary file not shown.
14
mgrapp/.classpath
Normal file
14
mgrapp/.classpath
Normal 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
8
mgrapp/.gitignore
vendored
Normal 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
17
mgrapp/.project
Normal 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>
|
||||
3
mgrapp/.settings/org.eclipse.core.resources.prefs
Normal file
3
mgrapp/.settings/org.eclipse.core.resources.prefs
Normal file
@@ -0,0 +1,3 @@
|
||||
#Tue Sep 02 16:59:24 PDT 2008
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=UTF-8
|
||||
3
mgrapp/.settings/org.eclipse.core.runtime.prefs
Normal file
3
mgrapp/.settings/org.eclipse.core.runtime.prefs
Normal file
@@ -0,0 +1,3 @@
|
||||
#Tue Sep 02 16:59:24 PDT 2008
|
||||
eclipse.preferences.version=1
|
||||
line.separator=\n
|
||||
268
mgrapp/.settings/org.eclipse.jdt.core.prefs
Normal file
268
mgrapp/.settings/org.eclipse.jdt.core.prefs
Normal 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
|
||||
9
mgrapp/.settings/org.eclipse.jdt.ui.prefs
Normal file
9
mgrapp/.settings/org.eclipse.jdt.ui.prefs
Normal 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
64
mgrapp/bin/mgr
Executable 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
31
mgrapp/compile.sh
Executable 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
|
||||
BIN
mgrapp/lib/commons-codec.jar
Normal file
BIN
mgrapp/lib/commons-codec.jar
Normal file
Binary file not shown.
BIN
mgrapp/lib/commons-httpclient.jar
Normal file
BIN
mgrapp/lib/commons-httpclient.jar
Normal file
Binary file not shown.
BIN
mgrapp/lib/commons-logging.jar
Normal file
BIN
mgrapp/lib/commons-logging.jar
Normal file
Binary file not shown.
BIN
mgrapp/lib/commons-net.jar
Normal file
BIN
mgrapp/lib/commons-net.jar
Normal file
Binary file not shown.
3
mgrapp/src/META-INF/MANIFEST.MF
Normal file
3
mgrapp/src/META-INF/MANIFEST.MF
Normal file
@@ -0,0 +1,3 @@
|
||||
Manifest-Version: 1.0
|
||||
Main-Class: com.google.codereview.Main
|
||||
|
||||
284
mgrapp/src/com/google/codereview/Main.java
Normal file
284
mgrapp/src/com/google/codereview/Main.java
Normal 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);
|
||||
}
|
||||
}
|
||||
108
mgrapp/src/com/google/codereview/manager/Backend.java
Normal file
108
mgrapp/src/com/google/codereview/manager/Backend.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
132
mgrapp/src/com/google/codereview/manager/ProjectSync.java
Normal file
132
mgrapp/src/com/google/codereview/manager/ProjectSync.java
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
386
mgrapp/src/com/google/codereview/manager/merge/MergeOp.java
Normal file
386
mgrapp/src/com/google/codereview/manager/merge/MergeOp.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
130
mgrapp/src/com/google/codereview/manager/merge/UpdateOp.java
Normal file
130
mgrapp/src/com/google/codereview/manager/merge/UpdateOp.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
149
mgrapp/src/com/google/codereview/manager/unpack/DiffReader.java
Normal file
149
mgrapp/src/com/google/codereview/manager/unpack/DiffReader.java
Normal 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) {
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
||||
119
mgrapp/src/com/google/codereview/manager/unpack/FileDiff.java
Normal file
119
mgrapp/src/com/google/codereview/manager/unpack/FileDiff.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
441
mgrapp/src/com/google/codereview/rpc/HttpRpc.java
Normal file
441
mgrapp/src/com/google/codereview/rpc/HttpRpc.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
97
mgrapp/src/com/google/codereview/rpc/SimpleController.java
Normal file
97
mgrapp/src/com/google/codereview/rpc/SimpleController.java
Normal 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();
|
||||
}
|
||||
}
|
||||
73
mgrapp/src/com/google/codereview/util/GitMetaUtil.java
Normal file
73
mgrapp/src/com/google/codereview/util/GitMetaUtil.java
Normal 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;
|
||||
}
|
||||
}
|
||||
33
mgrapp/src/com/google/codereview/util/MutableBoolean.java
Normal file
33
mgrapp/src/com/google/codereview/util/MutableBoolean.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
23
proto/internal/admin.proto
Normal file
23
proto/internal/admin.proto
Normal 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);
|
||||
}
|
||||
37
proto/internal/build.proto
Normal file
37
proto/internal/build.proto
Normal 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);
|
||||
}
|
||||
43
proto/internal/bundle_store.proto
Normal file
43
proto/internal/bundle_store.proto
Normal 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);
|
||||
}
|
||||
37
proto/internal/change.proto
Normal file
37
proto/internal/change.proto
Normal 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);
|
||||
}
|
||||
31
proto/internal/complete_patchset.proto
Normal file
31
proto/internal/complete_patchset.proto
Normal 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 {
|
||||
}
|
||||
63
proto/internal/git_meta.proto
Normal file
63
proto/internal/git_meta.proto
Normal 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;
|
||||
}
|
||||
38
proto/internal/merge.proto
Normal file
38
proto/internal/merge.proto
Normal 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);
|
||||
}
|
||||
73
proto/internal/next_received_bundle.proto
Normal file
73
proto/internal/next_received_bundle.proto
Normal 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;
|
||||
}
|
||||
64
proto/internal/pending_merge.proto
Normal file
64
proto/internal/pending_merge.proto
Normal 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;
|
||||
}
|
||||
30
proto/internal/post_branch_update.proto
Normal file
30
proto/internal/post_branch_update.proto
Normal 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 {
|
||||
}
|
||||
68
proto/internal/post_build_result.proto
Normal file
68
proto/internal/post_build_result.proto
Normal 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;
|
||||
}
|
||||
43
proto/internal/post_merge_result.proto
Normal file
43
proto/internal/post_merge_result.proto
Normal 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 {
|
||||
}
|
||||
27
proto/internal/prune_builds.proto
Normal file
27
proto/internal/prune_builds.proto
Normal 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;
|
||||
}
|
||||
27
proto/internal/prune_bundles.proto
Normal file
27
proto/internal/prune_bundles.proto
Normal 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;
|
||||
}
|
||||
26
proto/internal/submit_build.proto
Normal file
26
proto/internal/submit_build.proto
Normal 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;
|
||||
}
|
||||
55
proto/internal/submit_change.proto
Normal file
55
proto/internal/submit_change.proto
Normal 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;
|
||||
}
|
||||
31
proto/internal/sync_project.proto
Normal file
31
proto/internal/sync_project.proto
Normal 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 {
|
||||
}
|
||||
44
proto/internal/update_received_bundle.proto
Normal file
44
proto/internal/update_received_bundle.proto
Normal 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;
|
||||
}
|
||||
61
proto/internal/upload_patchset_file.proto
Normal file
61
proto/internal/upload_patchset_file.proto
Normal 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
29
proto/need_retry.proto
Normal 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
32
proto/review.proto
Normal 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
98
proto/upload_bundle.proto
Normal 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
165
test-utils.sh
Normal 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
8
tests_mgrapp/.classpath
Normal 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
1
tests_mgrapp/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/out_classes
|
||||
17
tests_mgrapp/.project
Normal file
17
tests_mgrapp/.project
Normal 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>
|
||||
3
tests_mgrapp/.settings/org.eclipse.core.resources.prefs
Normal file
3
tests_mgrapp/.settings/org.eclipse.core.resources.prefs
Normal file
@@ -0,0 +1,3 @@
|
||||
#Tue Sep 02 16:59:24 PDT 2008
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=UTF-8
|
||||
3
tests_mgrapp/.settings/org.eclipse.core.runtime.prefs
Normal file
3
tests_mgrapp/.settings/org.eclipse.core.runtime.prefs
Normal file
@@ -0,0 +1,3 @@
|
||||
#Tue Sep 02 16:59:24 PDT 2008
|
||||
eclipse.preferences.version=1
|
||||
line.separator=\n
|
||||
268
tests_mgrapp/.settings/org.eclipse.jdt.core.prefs
Normal file
268
tests_mgrapp/.settings/org.eclipse.jdt.core.prefs
Normal 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
|
||||
9
tests_mgrapp/.settings/org.eclipse.jdt.ui.prefs
Normal file
9
tests_mgrapp/.settings/org.eclipse.jdt.ui.prefs
Normal 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/>
|
||||
59
tests_mgrapp/src/com/google/codereview/TrashTestCase.java
Normal file
59
tests_mgrapp/src/com/google/codereview/TrashTestCase.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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
4
webapp/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
*.pyc
|
||||
/froofle
|
||||
/codereview/*_pb2.py
|
||||
/codereview/internal/*_pb2.py
|
||||
64
webapp/TODO
Normal file
64
webapp/TODO
Normal 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
0
webapp/__init__.py
Normal file
28
webapp/app.yaml
Normal file
28
webapp/app.yaml
Normal 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
|
||||
0
webapp/codereview/__init__.py
Normal file
0
webapp/codereview/__init__.py
Normal file
31
webapp/codereview/dev_init.py
Normal file
31
webapp/codereview/dev_init.py
Normal 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))
|
||||
92
webapp/codereview/email.py
Normal file
92
webapp/codereview/email.py
Normal 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
709
webapp/codereview/engine.py
Normal 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
337
webapp/codereview/fields.py
Normal 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
|
||||
|
||||
|
||||
148
webapp/codereview/git_models.py
Normal file
148
webapp/codereview/git_models.py
Normal 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)
|
||||
0
webapp/codereview/internal/__init__.py
Normal file
0
webapp/codereview/internal/__init__.py
Normal file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user