mirror of
https://chromium.googlesource.com/chromium/tools/depot_tools.git
synced 2026-01-11 10:41:31 +00:00
Create the Next Generation of depot_tools. Eh.
Review URL: http://codereview.chromium.org/92087 git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@14349 0039d316-1c4b-4281-b951-d872f2087c98
This commit is contained in:
27
LICENSE
Normal file
27
LICENSE
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
39
README
Normal file
39
README
Normal file
@@ -0,0 +1,39 @@
|
||||
This package contains tools for working with Chromium development.
|
||||
|
||||
The "gclient" wrapper knows how to keep this repository updated to
|
||||
the latest versions of these tools as found at:
|
||||
|
||||
http://src.chromium.org/svn/trunk/tools/depot_tools
|
||||
|
||||
This package contains:
|
||||
|
||||
chrome-update-create-task.bat
|
||||
Creates a scheduled task to do an automatic local chromium build every day.
|
||||
|
||||
gcl
|
||||
A tool for uploading and managing code reviews on the Chromium
|
||||
project, using the Rietveld code review tool. More info at:
|
||||
http://code.google.com/p/rietveld/
|
||||
|
||||
gclient
|
||||
A script for managing a workspace with modular dependencies that
|
||||
are each checked out independently from different repositories.
|
||||
More info at:
|
||||
http://code.google.com/p/gclient/
|
||||
|
||||
hammer
|
||||
A wrapper script for building Chromium with the SCons software
|
||||
construction tool. More info at:
|
||||
http://www.scons.org/
|
||||
|
||||
revert
|
||||
A small tool to quickly revert a change.
|
||||
|
||||
|
||||
Note: svn and python will be installed automatically if not accessible (on
|
||||
Windows only).
|
||||
|
||||
To update this distribution manually, run bootstrap\update.bat on Windows,
|
||||
or bootstrap/update.sh on Linux or Mac.
|
||||
|
||||
To disable automatic updating, set the environment variable DEPOT_TOOLS_UPDATE=1
|
||||
52
README.git-cl
Normal file
52
README.git-cl
Normal file
@@ -0,0 +1,52 @@
|
||||
# git-cl -- a git-command for integrating reviews on Rietveld
|
||||
# Copyright (C) 2008 Evan Martin <martine@danga.com>
|
||||
|
||||
== Background
|
||||
Rietveld, also known as http://codereview.appspot.com, is a nice tool
|
||||
for code reviews. You upload a patch (and some other data) and it lets
|
||||
others comment on your patch.
|
||||
|
||||
For more on how this all works conceptually, please see README.codereview.
|
||||
The remainder of this document is the nuts and bolts of using git-cl.
|
||||
|
||||
== Install
|
||||
Copy (symlink) it into your path somewhere, along with Rietveld
|
||||
upload.py.
|
||||
|
||||
== Setup
|
||||
Run this from your git checkout and answer some questions:
|
||||
$ git cl config
|
||||
|
||||
== How to use it
|
||||
Make a new branch. Write some code. Commit it locally. Send it for
|
||||
review:
|
||||
$ git cl upload
|
||||
By default, it diffs against whatever branch the current branch is
|
||||
tracking (see "git checkout --track"). An optional last argument is
|
||||
passed to "git diff", allowing reviews against other heads.
|
||||
|
||||
You'll be asked some questions, and the review issue number will be
|
||||
associated with your current git branch, so subsequent calls to upload
|
||||
will update that review rather than making a new one.
|
||||
|
||||
== git-svn integration
|
||||
Review looks good? Commit the code:
|
||||
$ git cl dcommit
|
||||
This does a git-svn dcommit, with a twist: all changes in the diff
|
||||
will be squashed into a single commit, and the description of the commit
|
||||
is taken directly from the Rietveld description. This command also accepts
|
||||
arguments to "git diff", much like upload.
|
||||
Try "git cl dcommit --help" for more options.
|
||||
|
||||
== Extra commands
|
||||
Print some status info:
|
||||
$ git cl status
|
||||
|
||||
Edit the issue association on the current branch:
|
||||
$ git cl issue 1234
|
||||
|
||||
Patch in a review:
|
||||
$ git cl patch <url to full patch>
|
||||
Try "git cl patch --help" for more options.
|
||||
|
||||
vim: tw=72 :
|
||||
99
README.git-cl.codereview
Normal file
99
README.git-cl.codereview
Normal file
@@ -0,0 +1,99 @@
|
||||
The git-cl README describes the git-cl command set. This document
|
||||
describes how code review and git work together in general, intended
|
||||
for people familiar with git but unfamiliar with the code review
|
||||
process supported by Rietveld.
|
||||
|
||||
== Concepts and terms
|
||||
A Rietveld review is for discussion of a single change or patch. You
|
||||
upload a proposed change, the reviewer comments on your change, and
|
||||
then you can upload a revised version of your change. Rietveld stores
|
||||
the history of uploaded patches as well as the comments, and can
|
||||
compute diffs in between these patches. The history of a patch is
|
||||
very much like a small branch in git, but since Rietveld is
|
||||
VCS-agnostic the concepts don't map perfectly. The identifier for a
|
||||
single review+patches+comments in Rietveld is called an "issue".
|
||||
|
||||
Rietveld provides a basic uploader that understands git. This program
|
||||
is used by git-cl, and is included in the git-cl repo as upload.py.
|
||||
|
||||
== Basic interaction with git
|
||||
The fundamental problem you encounter when you try to mix git and code
|
||||
review is that with git it's nice to commit code locally, while during
|
||||
a code review you're often requested to change something about your
|
||||
code. There are a few different ways you can handle this workflow
|
||||
with git:
|
||||
|
||||
1) Rewriting a single commit. Say the origin commit is O, and you
|
||||
commit your initial work in a commit A, making your history like
|
||||
O--A. After review comments, you commit --amend, effectively
|
||||
erasing A and making a new commit A', so history is now O--A'.
|
||||
(Equivalently, you can use git reset --soft or git rebase -i.)
|
||||
|
||||
2) Writing follow-up commits. Initial work is again in A, and after
|
||||
review comments, you write a new commit B so your history looks
|
||||
like O--A--B. When you upload the revised patch, you upload the
|
||||
diff of O..B, not A..B; you always upload the full diff of what
|
||||
you're proposing to change.
|
||||
|
||||
The Rietveld patch uploader just takes arguments to "git diff", so
|
||||
either of the above workflows work fine. If all you want to do is
|
||||
upload a patch, you can use the upload.py provided by Rietveld with
|
||||
arguments like this:
|
||||
|
||||
upload.py --server server.com <args to "git diff">
|
||||
|
||||
The first time you upload, it creates a new issue; for follow-ups on
|
||||
the same issue, you need to provide the issue number:
|
||||
|
||||
upload.py --server server.com --issue 1234 <args to "git diff">
|
||||
|
||||
== git-cl to the rescue
|
||||
git-cl simplifies the above in the following ways:
|
||||
|
||||
1) "git cl config" puts a persistent --server setting in your .git/config.
|
||||
|
||||
2) The first time you upload an issue, the issue number is associated with
|
||||
the current *branch*. If you upload again, it will upload on the same
|
||||
issue. (Note that this association is tied to a branch, not a commit,
|
||||
which means you need a separate branch per review.)
|
||||
|
||||
3) If your branch is "tracking" (in the "git checkout --track" sense)
|
||||
another one (like origin/master), calls to "git cl upload" will
|
||||
diff against that branch by default. (You can still pass arguments
|
||||
to "git diff" on the command line, if necessary.)
|
||||
|
||||
In the common case, this means that calling simply "git cl upload"
|
||||
will always upload the correct diff to the correct place.
|
||||
|
||||
== Patch series
|
||||
The above is all you need to know for working on a single patch.
|
||||
|
||||
Things get much more complicated when you have a series of commits
|
||||
that you want to get reviewed. Say your history looks like
|
||||
O--A--B--C. If you want to upload that as a single review, everything
|
||||
works just as above.
|
||||
|
||||
But what if you upload each of A, B, and C as separate reviews?
|
||||
What if you then need to change A?
|
||||
|
||||
1) One option is rewriting history: write a new commit A', then use
|
||||
git rebase -i to insert that diff in as O--A--A'--B--C as well as
|
||||
squash it. This is sometimes not possible if B and C have touched
|
||||
some lines affected by A'.
|
||||
|
||||
2) Another option, and the one espoused by software like topgit, is for
|
||||
you to have separate branches for A, B, and C, and after writing A'
|
||||
you merge it into each of those branches. (topgit automates this
|
||||
merging process.) This is also what is recommended by git-cl, which
|
||||
likes having different branch identifiers to hang the issue number
|
||||
off of. Your history ends up looking like:
|
||||
|
||||
O---A---B---C
|
||||
\ \ \
|
||||
A'--B'--C'
|
||||
|
||||
Which is ugly, but it accurately tracks the real history of your work, can
|
||||
be thrown away at the end by committing A+A' as a single "squash" commit.
|
||||
|
||||
In practice, this comes up pretty rarely. Suggestions for better workflows
|
||||
are welcome.
|
||||
21
bootstrap/gclient.bat
Normal file
21
bootstrap/gclient.bat
Normal file
@@ -0,0 +1,21 @@
|
||||
@echo off
|
||||
:: Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
:: Use of this source code is governed by a BSD-style license that can be
|
||||
:: found in the LICENSE file.
|
||||
|
||||
:: This batch file will try to sync the root directory and call back gclient.
|
||||
|
||||
setlocal
|
||||
|
||||
:: Shall skip automatic update?
|
||||
IF "%DEPOT_TOOLS_UPDATE%" == "0" GOTO gclient
|
||||
|
||||
:: We can't sync if ..\.svn\. doesn't exist.
|
||||
IF NOT EXIST "%~dp0..\.svn" GOTO gclient
|
||||
|
||||
:: Sync the .. directory to update the bootstrap at the same time.
|
||||
call svn -q up "%~dp0.."
|
||||
|
||||
:gclient
|
||||
:: Defer control.
|
||||
python "%~dp0\..\gclient.py" %*
|
||||
18
bootstrap/gclient.sh
Executable file
18
bootstrap/gclient.sh
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
# Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
# This script will try to sync the root and bootstrap directories.
|
||||
|
||||
base_dir=$(dirname "$0")
|
||||
|
||||
# Skip if $DEPOT_TOOLS_UPDATE==0 or ../.svn/. doesn't exist.
|
||||
if [ "X$DEPOT_TOOLS_UPDATE" != "X0" -a -e "$base_dir/../.svn" ]
|
||||
then
|
||||
# Update the root directory.
|
||||
svn -q up "$base_dir/.."
|
||||
fi
|
||||
|
||||
exec python "$base_dir/../gclient.py" "$@"
|
||||
|
||||
504
bootstrap/win/7z.copying.txt
Normal file
504
bootstrap/win/7z.copying.txt
Normal file
@@ -0,0 +1,504 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
|
||||
|
||||
30
bootstrap/win/7z.license.txt
Normal file
30
bootstrap/win/7z.license.txt
Normal file
@@ -0,0 +1,30 @@
|
||||
7-Zip Command line version
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
License for use and distribution
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
7-Zip Copyright (C) 1999-2009 Igor Pavlov.
|
||||
|
||||
7za.exe is distributed under the GNU LGPL license
|
||||
|
||||
Notes:
|
||||
You can use 7-Zip on any computer, including a computer in a commercial
|
||||
organization. You don't need to register or pay for 7-Zip.
|
||||
|
||||
|
||||
GNU LGPL information
|
||||
--------------------
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
42
bootstrap/win/7z.readme.txt
Normal file
42
bootstrap/win/7z.readme.txt
Normal file
@@ -0,0 +1,42 @@
|
||||
7-Zip Command line version 4.65
|
||||
-------------------------------
|
||||
|
||||
7-Zip is a file archiver with high compression ratio.
|
||||
7za.exe is a standalone command line version of 7-Zip.
|
||||
|
||||
7-Zip Copyright (C) 1999-2009 Igor Pavlov.
|
||||
|
||||
Features of 7za.exe:
|
||||
- High compression ratio in new 7z format
|
||||
- Supported formats:
|
||||
- Packing / unpacking: 7z, ZIP, GZIP, BZIP2 and TAR
|
||||
- Unpacking only: Z
|
||||
- Highest compression ratio for ZIP and GZIP formats.
|
||||
- Fast compression and decompression
|
||||
- Strong AES-256 encryption in 7z and ZIP formats.
|
||||
|
||||
7za.exe is a free software distributed under the GNU LGPL.
|
||||
Read license.txt for more information.
|
||||
|
||||
Source code of 7za.exe and 7-Zip can be found at
|
||||
http://www.7-zip.org/
|
||||
|
||||
7za.exe can work in Windows 95/98/ME/NT/2000/XP/2003/Vista.
|
||||
|
||||
There is also port of 7za.exe for POSIX systems like Unix (Linux, Solaris, OpenBSD,
|
||||
FreeBSD, Cygwin, AIX, ...), MacOS X and BeOS:
|
||||
|
||||
http://p7zip.sourceforge.net/
|
||||
|
||||
|
||||
This distributive packet contains the following files:
|
||||
|
||||
7za.exe - 7-Zip standalone command line version.
|
||||
readme.txt - This file.
|
||||
copying.txt - GNU LGPL license.
|
||||
license.txt - License information.
|
||||
7-zip.chm - User's Manual in HTML Help format.
|
||||
|
||||
|
||||
---
|
||||
End of document
|
||||
BIN
bootstrap/win/7za.exe
Normal file
BIN
bootstrap/win/7za.exe
Normal file
Binary file not shown.
16
bootstrap/win/README.google
Normal file
16
bootstrap/win/README.google
Normal file
@@ -0,0 +1,16 @@
|
||||
Including the following third parties:
|
||||
|
||||
7zip version 4.65
|
||||
7za.exe is a free software distributed under the GNU LGPL.
|
||||
Read license.txt for more information.
|
||||
|
||||
Source code of 7za.exe and 7-Zip can be found at
|
||||
http://www.7-zip.org/
|
||||
|
||||
Note: packed with UPX to reduce code size.
|
||||
|
||||
|
||||
wget 1.11.4 from http://www.gnu.org/software/wget/
|
||||
|
||||
Note: local compile without openssl support to reduce code size.
|
||||
Note: packed with UPX to reduce code size.
|
||||
1
bootstrap/win/python.bat
Normal file
1
bootstrap/win/python.bat
Normal file
@@ -0,0 +1 @@
|
||||
@"%~dp0python\python.exe" %*
|
||||
1
bootstrap/win/svn.bat
Normal file
1
bootstrap/win/svn.bat
Normal file
@@ -0,0 +1 @@
|
||||
@"%~dp0svn\svn.exe" %*
|
||||
BIN
bootstrap/win/wget.exe
Normal file
BIN
bootstrap/win/wget.exe
Normal file
Binary file not shown.
65
bootstrap/win/win_tools.bat
Normal file
65
bootstrap/win/win_tools.bat
Normal file
@@ -0,0 +1,65 @@
|
||||
@echo off
|
||||
:: Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
:: Use of this source code is governed by a BSD-style license that can be
|
||||
:: found in the LICENSE file.
|
||||
|
||||
:: This script will try to find if svn and python are accessible and it not,
|
||||
:: it will try to download it and 'install' it in depot_tools.
|
||||
|
||||
SETLOCAL
|
||||
|
||||
set ROOT_URL=http://src.chromium.org/svn/trunk/tools
|
||||
set ROOT_DIR=%~dp0..\..
|
||||
|
||||
:: If the batch file exists, skip the svn check.
|
||||
if exist "%ROOT_DIR%\svn.bat" goto :PYTHON
|
||||
call svn --version 2>nul 1>nul
|
||||
if errorlevel 1 call :C_SVN
|
||||
|
||||
:PYTHON
|
||||
:: If the batch file exists, skip the python check.
|
||||
if exist "%ROOT_DIR%\python.bat" goto :EOF
|
||||
call python --version 2>nul 1>nul
|
||||
if errorlevel 1 call :C_PYTHON
|
||||
|
||||
:: We are done.
|
||||
goto :EOF
|
||||
|
||||
|
||||
:C_SVN
|
||||
echo Installing subversion ...
|
||||
:: svn is not accessible; check it out and create 'proxy' files.
|
||||
call "%~dp0wget" -q %ROOT_URL%/third_party/svn.7z -O "%~dp0svn.7z"
|
||||
if errorlevel 1 goto :SVN_FAIL
|
||||
call "%~dp07za" x "%~dp0svn.7z" %ROOT_DIR%
|
||||
if errorlevel 1 goto :SVN_FAIL
|
||||
del "%~dp0svn.7z"
|
||||
:: Create the batch file.
|
||||
copy "%~dp0svn.bat" "%ROOT_DIR%"
|
||||
goto :EOF
|
||||
|
||||
|
||||
:SVN_FAIL
|
||||
echo Failed to checkout svn automatically.
|
||||
echo Please visit http://subversion.tigris.org to download the latest subversion client
|
||||
echo before continuing.
|
||||
echo You can also get the "prebacked" version used at %ROOT_URL%/third_party/
|
||||
:: Still try python.
|
||||
goto :C_PYTHON
|
||||
|
||||
|
||||
:C_PYTHON
|
||||
echo Installing python ...
|
||||
call svn co %ROOT_URL%/third_party/python "%ROOT_DIR%\python"
|
||||
if errorlevel 1 goto :PYTHON_FAIL
|
||||
:: Create the batch file.
|
||||
copy "%~dp0python.bat" "%ROOT_DIR%"
|
||||
goto :EOF
|
||||
|
||||
|
||||
:PYTHON_FAIL
|
||||
echo Failed to checkout python automatically.
|
||||
echo Please visit http://python.org to download the latest python 2.x client before
|
||||
echo continuing.
|
||||
echo You can also get the "prebacked" version used at %ROOT_URL%/third_party/
|
||||
goto :EOF
|
||||
53
chrome-update-create-task.bat
Normal file
53
chrome-update-create-task.bat
Normal file
@@ -0,0 +1,53 @@
|
||||
@echo off
|
||||
:: Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
:: Use of this source code is governed by a BSD-style license that can be
|
||||
:: found in the LICENSE file.
|
||||
|
||||
setlocal
|
||||
|
||||
:: This script will create a scheduled task to run chrome-update every day
|
||||
:: at the time you specify. This script expects to be live in
|
||||
:: depot_tools\latest.
|
||||
::
|
||||
:: Usage: this-script <time to run task> <path to chrome trunk>
|
||||
|
||||
set Out=%USERPROFILE%\chrome-update-task.bat
|
||||
set TaskTime=%1
|
||||
set Trunk=%~f2
|
||||
|
||||
if not exist "%Trunk%" (
|
||||
echo Usage: %~n0 ^<time^> ^<c:\path\to\chrome\trunk^>
|
||||
echo ^<time^> is the time in HH:MM:SS format at which to run the task.
|
||||
echo Example: %~n0 02:00:00 c:\src\chrome\trunk
|
||||
exit 1
|
||||
)
|
||||
|
||||
if not exist "%Out%" goto CreateScript
|
||||
|
||||
echo WARNING: %Out% already exists.
|
||||
set Choice=
|
||||
set /P Choice=Overwrite file [Y/N]?
|
||||
if not "%Choice%"=="y" goto CreateTask
|
||||
|
||||
:CreateScript
|
||||
|
||||
echo.
|
||||
echo Creating %Out%
|
||||
|
||||
echo>"%Out%" @echo off
|
||||
echo>>"%Out%" call "%~dp0\bootstrap\update.bat"
|
||||
echo>>"%Out%" "%~dp0\chrome-update.bat" "%Trunk%" ^> "%Trunk%\chrome-update-results.txt"
|
||||
|
||||
:CreateTask
|
||||
|
||||
echo.
|
||||
echo ***********************************************************************
|
||||
echo Creating a Scheduled Task to run chrome-update each day at %TaskTime%.
|
||||
echo The batch file being run will live at %Out%.
|
||||
echo.
|
||||
echo WARNING: The password you enter will be displayed in cleartext.
|
||||
echo If you're paranoid, you can enter blank here and then fix the password
|
||||
echo by editing the scheduled task manually from the Control Panel.
|
||||
echo ***********************************************************************
|
||||
echo.
|
||||
schtasks /create /tn chrome-update /tr "\"%Out%\"" /sc daily /st %TaskTime%
|
||||
10
chrome-update.bat
Normal file
10
chrome-update.bat
Normal file
@@ -0,0 +1,10 @@
|
||||
@echo off
|
||||
|
||||
:: This batch file assumes that the correct version of python can be found in
|
||||
:: the current directory, and that you have Visual Studio 8 installed in the
|
||||
:: default location.
|
||||
|
||||
setlocal
|
||||
call vcvars32.bat
|
||||
|
||||
python "%~dp0chrome-update.py" %*
|
||||
170
chrome-update.py
Executable file
170
chrome-update.py
Executable file
@@ -0,0 +1,170 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
# Author: mpcomplete
|
||||
#
|
||||
# This script updates and does a clean build of chrome for you.
|
||||
# Usage: python chrome-update.py C:\path\to\chrome\trunk
|
||||
#
|
||||
# It assumes the following:
|
||||
# - You have gclient.bat and devenv.com in your path (use the wrapper batch
|
||||
# file to ensure this).
|
||||
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import httplib
|
||||
import re
|
||||
import shutil
|
||||
import optparse
|
||||
|
||||
def Message(str):
|
||||
"""Prints a status message."""
|
||||
print "[chrome-update]", str
|
||||
|
||||
def FixupPath(path):
|
||||
"""Returns the OS-ified version of a windows path."""
|
||||
return os.path.sep.join(path.split("\\"))
|
||||
|
||||
def GetRevision():
|
||||
"""Returns the revision number of the last build that was archived, or
|
||||
None on failure."""
|
||||
HOST = "build.chromium.org"
|
||||
PATH = "/buildbot/continuous/LATEST/REVISION"
|
||||
EXPR = r"(\d+)"
|
||||
|
||||
connection = httplib.HTTPConnection(HOST)
|
||||
connection.request("GET", PATH)
|
||||
response = connection.getresponse()
|
||||
text = response.read()
|
||||
match = re.search(EXPR, text)
|
||||
if match:
|
||||
return int(match.group(1))
|
||||
return None
|
||||
|
||||
def SetRevisionForUpdate(chrome_root):
|
||||
"""Prepares environment so gclient syncs to a good revision, if possible."""
|
||||
# Find a buildable revision.
|
||||
rev = GetRevision()
|
||||
if rev == None:
|
||||
Message("WARNING: Failed to find a buildable revision. Syncing to trunk.")
|
||||
return "trunk"
|
||||
|
||||
# Read the .gclient file.
|
||||
gclient_file = chrome_root + FixupPath("\\.gclient")
|
||||
if not os.path.exists(gclient_file):
|
||||
Message("WARNING: Failed to find .gclient file. Syncing to trunk.")
|
||||
return "trunk"
|
||||
scope = {}
|
||||
execfile(gclient_file, scope)
|
||||
solutions = scope["solutions"]
|
||||
|
||||
# Edit the url of the chrome 'src' solution, unless the user wants a
|
||||
# specific revision.
|
||||
for solution in solutions:
|
||||
if solution["name"] == "src":
|
||||
splitter = solution["url"].split("@")
|
||||
if len(splitter) == 1:
|
||||
solution["url"] = splitter[0] + "@" + str(rev)
|
||||
else:
|
||||
rev = int(splitter[1])
|
||||
break
|
||||
|
||||
# Write out the new .gclient file.
|
||||
gclient_override = gclient_file + "-update-chrome"
|
||||
f = open(gclient_override, "w")
|
||||
f.write("solutions = " + str(solutions))
|
||||
f.close()
|
||||
|
||||
# Set the env var that the gclient tool looks for.
|
||||
os.environ["GCLIENT_FILE"] = gclient_override
|
||||
return rev
|
||||
|
||||
def DoUpdate(chrome_root):
|
||||
"""gclient sync to the latest build."""
|
||||
# gclient sync
|
||||
rev = SetRevisionForUpdate(chrome_root)
|
||||
|
||||
cmd = ["gclient.bat", "sync"]
|
||||
Message("Updating to %s: %s" % (rev, cmd))
|
||||
sys.stdout.flush()
|
||||
return subprocess.call(cmd, cwd=chrome_root)
|
||||
|
||||
def DoClean(chrome_root, type):
|
||||
"""Clean our build dir."""
|
||||
# rm -rf src/chrome/Debug
|
||||
rv = [0]
|
||||
def onError(func, path, excinfo):
|
||||
Message("Couldn't remove '%s': %s" % (path, excinfo))
|
||||
rv[0] = [1]
|
||||
|
||||
build_path = chrome_root + FixupPath("\\src\\chrome\\" + type)
|
||||
Message("Cleaning: %s" % build_path)
|
||||
shutil.rmtree(build_path, False, onError)
|
||||
return rv[0]
|
||||
|
||||
def DoBuild(chrome_root, chrome_sln, clean, type):
|
||||
"""devenv /build what we just checked out."""
|
||||
if clean:
|
||||
rv = DoClean(chrome_root, type)
|
||||
if rv != 0:
|
||||
Message("WARNING: Clean failed. Doing a build without clean.")
|
||||
|
||||
# devenv chrome.sln /build Debug
|
||||
cmd = ["devenv.com", chrome_sln, "/build", type]
|
||||
|
||||
Message("Building: %s" % cmd)
|
||||
sys.stdout.flush()
|
||||
return subprocess.call(cmd, cwd=chrome_root)
|
||||
|
||||
def Main():
|
||||
parser = optparse.OptionParser()
|
||||
parser.add_option("", "--clean", action="store_true", default=False,
|
||||
help="wipe Debug output directory before building")
|
||||
parser.add_option("", "--solution", default="src\\chrome\\chrome.sln",
|
||||
help="path to the .sln file to build (absolute, or "
|
||||
"relative to chrome trunk")
|
||||
parser.add_option("", "--release", action="store_true", default=False,
|
||||
help="build the release configuration in addition of the "
|
||||
"debug configuration.")
|
||||
parser.add_option("", "--nosync", action="store_true", default=False,
|
||||
help="doesn't sync before building")
|
||||
parser.add_option("", "--print-latest", action="store_true", default=False,
|
||||
help="print the latest buildable revision and exit")
|
||||
options, args = parser.parse_args()
|
||||
|
||||
if options.print_latest:
|
||||
print GetRevision() or "HEAD"
|
||||
sys.exit(0)
|
||||
|
||||
if not args:
|
||||
Message("Usage: %s <path\\to\\chrome\\root> [options]" % sys.argv[0])
|
||||
sys.exit(1)
|
||||
|
||||
chrome_root = args[0]
|
||||
|
||||
if not options.nosync:
|
||||
rv = DoUpdate(chrome_root)
|
||||
if rv != 0:
|
||||
Message("Update Failed. Bailing.")
|
||||
sys.exit(rv)
|
||||
|
||||
chrome_sln = FixupPath(options.solution)
|
||||
rv = DoBuild(chrome_root, chrome_sln, options.clean, "Debug")
|
||||
if rv != 0:
|
||||
Message("Debug build failed. Sad face :(")
|
||||
|
||||
if options.release:
|
||||
rv = DoBuild(chrome_root, chrome_sln, options.clean, "Release")
|
||||
if rv != 0:
|
||||
Message("Release build failed. Sad face :(")
|
||||
|
||||
if rv != 0:
|
||||
sys.exit(rv)
|
||||
|
||||
Message("Success!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
Main()
|
||||
2725
cpplint.py
vendored
Executable file
2725
cpplint.py
vendored
Executable file
File diff suppressed because it is too large
Load Diff
5
gcl
Executable file
5
gcl
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
base_dir=$(dirname "$0")
|
||||
|
||||
exec python "$base_dir/gcl.py" "$@"
|
||||
21
gclient
Executable file
21
gclient
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
# Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
# This script will try to sync the bootstrap directories and then defer control.
|
||||
|
||||
base_dir=$(dirname "$0")
|
||||
|
||||
if [ "X$DEPOT_TOOLS_UPDATE" != "X0" -a -e "$base_dir/.svn" ]
|
||||
then
|
||||
# Update the bootstrap directory to stay up-to-date with the latest
|
||||
# depot_tools.
|
||||
svn -q up "$base_dir/bootstrap"
|
||||
|
||||
# Then defer the control to the bootstrapper.
|
||||
"$base_dir/bootstrap/gclient.sh"
|
||||
else
|
||||
exec "$base_dir/gclient.py" "$@"
|
||||
fi
|
||||
|
||||
36
gclient.bat
Normal file
36
gclient.bat
Normal file
@@ -0,0 +1,36 @@
|
||||
@echo off
|
||||
:: Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
:: Use of this source code is governed by a BSD-style license that can be
|
||||
:: found in the LICENSE file.
|
||||
|
||||
:: This file is a stub to sync .\bootstrap first and defer control to
|
||||
:: .\bootstrap\gclient.bat, which will sync back '.'. This is unless auto
|
||||
:: update is disabled, were gclient.py is directly called.
|
||||
|
||||
:: Shall skip automatic update?
|
||||
IF "%DEPOT_TOOLS_UPDATE%" == "0" GOTO :SKIP_UPDATE
|
||||
:: We can't sync if .\.svn\. doesn't exist.
|
||||
IF NOT EXIST "%~dp0.svn" GOTO :SKIP_UPDATE
|
||||
|
||||
:: Will download svn and python if not already installed on the system.
|
||||
call "%~dp0bootstrap\win\win_tools.bat"
|
||||
if errorlevel 1 goto :EOF
|
||||
|
||||
:: Sync the bootstrap directory *only after*.
|
||||
call svn up -q "%~dp0bootstrap"
|
||||
:: still continue even in case of error.
|
||||
goto :UPDATE
|
||||
|
||||
|
||||
:SKIP_UPDATE
|
||||
:: Don't bother to try to update any thing.
|
||||
python "%~dp0\gclient.py" %*
|
||||
goto :EOF
|
||||
|
||||
|
||||
:UPDATE
|
||||
:: Transfer control to ease the update process. The following lines won't be
|
||||
:: executed so don't add any! Specifically, don't use 'call' in the following
|
||||
:: line.
|
||||
"%~dp0bootstrap\gclient.bat" %*
|
||||
goto :EOF
|
||||
1649
gclient.py
Executable file
1649
gclient.py
Executable file
File diff suppressed because it is too large
Load Diff
682
git-cl.py
Executable file
682
git-cl.py
Executable file
@@ -0,0 +1,682 @@
|
||||
#!/usr/bin/python
|
||||
# git-cl -- a git-command for integrating reviews on Rietveld
|
||||
# Copyright (C) 2008 Evan Martin <martine@danga.com>
|
||||
|
||||
import getpass
|
||||
import optparse
|
||||
import os
|
||||
import re
|
||||
import readline
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import textwrap
|
||||
import upload
|
||||
import urllib2
|
||||
|
||||
DEFAULT_SERVER = 'codereview.appspot.com'
|
||||
|
||||
def DieWithError(message):
|
||||
print >>sys.stderr, message
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def RunGit(args, error_ok=False, error_message=None, exit_code=False):
|
||||
cmd = ['git'] + args
|
||||
# Useful for debugging:
|
||||
# print >>sys.stderr, ' '.join(cmd)
|
||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
||||
output = proc.communicate()[0]
|
||||
if exit_code:
|
||||
return proc.returncode
|
||||
if not error_ok and proc.returncode != 0:
|
||||
DieWithError('Command "%s" failed.\n' % (' '.join(cmd)) +
|
||||
(error_message or output))
|
||||
return output
|
||||
|
||||
|
||||
class Settings:
|
||||
def __init__(self):
|
||||
self.server = None
|
||||
self.cc = None
|
||||
self.is_git_svn = None
|
||||
self.svn_branch = None
|
||||
self.tree_status_url = None
|
||||
self.viewvc_url = None
|
||||
|
||||
def GetServer(self, error_ok=False):
|
||||
if not self.server:
|
||||
if not error_ok:
|
||||
error_message = ('You must configure your review setup by running '
|
||||
'"git cl config".')
|
||||
self.server = self._GetConfig('rietveld.server',
|
||||
error_message=error_message)
|
||||
else:
|
||||
self.server = self._GetConfig('rietveld.server', error_ok=True)
|
||||
return self.server
|
||||
|
||||
def GetCCList(self):
|
||||
if self.cc is None:
|
||||
self.cc = self._GetConfig('rietveld.cc', error_ok=True)
|
||||
return self.cc
|
||||
|
||||
def GetIsGitSvn(self):
|
||||
"""Return true if this repo looks like it's using git-svn."""
|
||||
if self.is_git_svn is None:
|
||||
# If you have any "svn-remote.*" config keys, we think you're using svn.
|
||||
self.is_git_svn = RunGit(['config', '--get-regexp', r'^svn-remote\.'],
|
||||
exit_code=True) == 0
|
||||
return self.is_git_svn
|
||||
|
||||
def GetSVNBranch(self):
|
||||
if self.svn_branch is None:
|
||||
if not self.GetIsGitSvn():
|
||||
raise "Repo doesn't appear to be a git-svn repo."
|
||||
|
||||
# Try to figure out which remote branch we're based on.
|
||||
# Strategy:
|
||||
# 1) find all git-svn branches and note their svn URLs.
|
||||
# 2) iterate through our branch history and match up the URLs.
|
||||
|
||||
# regexp matching the git-svn line that contains the URL.
|
||||
git_svn_re = re.compile(r'^\s*git-svn-id: (\S+)@', re.MULTILINE)
|
||||
|
||||
# Get the refname and svn url for all refs/remotes/*.
|
||||
remotes = RunGit(['for-each-ref', '--format=%(refname)',
|
||||
'refs/remotes']).splitlines()
|
||||
svn_refs = {}
|
||||
for ref in remotes:
|
||||
# git-svn remote refs are generally directly in the refs/remotes/dir,
|
||||
# not a subdirectory (like refs/remotes/origin/master).
|
||||
if '/' in ref[len('refs/remotes/'):]:
|
||||
continue
|
||||
match = git_svn_re.search(RunGit(['cat-file', '-p', ref]))
|
||||
if match:
|
||||
svn_refs[match.group(1)] = ref
|
||||
|
||||
if len(svn_refs) == 1:
|
||||
# Only one svn branch exists -- seems like a good candidate.
|
||||
self.svn_branch = svn_refs.values()[0]
|
||||
elif len(svn_refs) > 1:
|
||||
# We have more than one remote branch available. We don't
|
||||
# want to go through all of history, so read a line from the
|
||||
# pipe at a time.
|
||||
# The -100 is an arbitrary limit so we don't search forever.
|
||||
cmd = ['git', 'log', '-100']
|
||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
||||
for line in proc.stdout:
|
||||
match = git_svn_re.match(line)
|
||||
if match:
|
||||
url = match.group(1)
|
||||
if url in svn_refs:
|
||||
self.svn_branch = svn_refs[url]
|
||||
proc.stdout.close() # Cut pipe.
|
||||
break
|
||||
|
||||
if not self.svn_branch:
|
||||
raise "Can't guess svn branch -- try specifying it on the command line"
|
||||
|
||||
return self.svn_branch
|
||||
|
||||
def GetTreeStatusUrl(self, error_ok=False):
|
||||
if not self.tree_status_url:
|
||||
error_message = ('You must configure your tree status URL by running '
|
||||
'"git cl config".')
|
||||
self.tree_status_url = self._GetConfig('rietveld.tree-status-url',
|
||||
error_ok=error_ok,
|
||||
error_message=error_message)
|
||||
return self.tree_status_url
|
||||
|
||||
def GetViewVCUrl(self):
|
||||
if not self.viewvc_url:
|
||||
self.viewvc_url = self._GetConfig('rietveld.viewvc-url', error_ok=True)
|
||||
return self.viewvc_url
|
||||
|
||||
def _GetConfig(self, param, **kwargs):
|
||||
return RunGit(['config', param], **kwargs).strip()
|
||||
|
||||
|
||||
settings = Settings()
|
||||
|
||||
|
||||
did_migrate_check = False
|
||||
def CheckForMigration():
|
||||
"""Migrate from the old issue format, if found.
|
||||
|
||||
We used to store the branch<->issue mapping in a file in .git, but it's
|
||||
better to store it in the .git/config, since deleting a branch deletes that
|
||||
branch's entry there.
|
||||
"""
|
||||
|
||||
# Don't run more than once.
|
||||
global did_migrate_check
|
||||
if did_migrate_check:
|
||||
return
|
||||
|
||||
gitdir = RunGit(['rev-parse', '--git-dir']).strip()
|
||||
storepath = os.path.join(gitdir, 'cl-mapping')
|
||||
if os.path.exists(storepath):
|
||||
print "old-style git-cl mapping file (%s) found; migrating." % storepath
|
||||
store = open(storepath, 'r')
|
||||
for line in store:
|
||||
branch, issue = line.strip().split()
|
||||
RunGit(['config', 'branch.%s.rietveldissue' % ShortBranchName(branch),
|
||||
issue])
|
||||
store.close()
|
||||
os.remove(storepath)
|
||||
did_migrate_check = True
|
||||
|
||||
|
||||
def IssueURL(issue):
|
||||
"""Get the URL for a particular issue."""
|
||||
return 'http://%s/%s' % (settings.GetServer(), issue)
|
||||
|
||||
|
||||
def ShortBranchName(branch):
|
||||
"""Convert a name like 'refs/heads/foo' to just 'foo'."""
|
||||
return branch.replace('refs/heads/', '')
|
||||
|
||||
|
||||
class Changelist:
|
||||
def __init__(self, branchref=None):
|
||||
# Poke settings so we get the "configure your server" message if necessary.
|
||||
settings.GetServer()
|
||||
self.branchref = branchref
|
||||
if self.branchref:
|
||||
self.branch = ShortBranchName(self.branchref)
|
||||
else:
|
||||
self.branch = None
|
||||
self.upstream_branch = None
|
||||
self.has_issue = False
|
||||
self.issue = None
|
||||
self.has_description = False
|
||||
self.description = None
|
||||
|
||||
def GetBranch(self):
|
||||
"""Returns the short branch name, e.g. 'master'."""
|
||||
if not self.branch:
|
||||
self.branchref = RunGit(['symbolic-ref', 'HEAD']).strip()
|
||||
self.branch = ShortBranchName(self.branchref)
|
||||
return self.branch
|
||||
def GetBranchRef(self):
|
||||
"""Returns the full branch name, e.g. 'refs/heads/master'."""
|
||||
self.GetBranch() # Poke the lazy loader.
|
||||
return self.branchref
|
||||
|
||||
def GetUpstreamBranch(self):
|
||||
if self.upstream_branch is None:
|
||||
branch = self.GetBranch()
|
||||
upstream_branch = RunGit(['config', 'branch.%s.merge' % branch],
|
||||
error_ok=True).strip()
|
||||
if upstream_branch:
|
||||
remote = RunGit(['config', 'branch.%s.remote' % branch]).strip()
|
||||
# We have remote=origin and branch=refs/heads/foobar; convert to
|
||||
# refs/remotes/origin/foobar.
|
||||
self.upstream_branch = upstream_branch.replace('heads',
|
||||
'remotes/' + remote)
|
||||
|
||||
if not self.upstream_branch:
|
||||
# Fall back on trying a git-svn upstream branch.
|
||||
if settings.GetIsGitSvn():
|
||||
self.upstream_branch = settings.GetSVNBranch()
|
||||
|
||||
if not self.upstream_branch:
|
||||
DieWithError("""Unable to determine default branch to diff against.
|
||||
Either pass complete "git diff"-style arguments, like
|
||||
git cl upload origin/master
|
||||
or verify this branch is set up to track another (via the --track argument to
|
||||
"git checkout -b ...").""")
|
||||
|
||||
return self.upstream_branch
|
||||
|
||||
def GetIssue(self):
|
||||
if not self.has_issue:
|
||||
CheckForMigration()
|
||||
issue = RunGit(['config', self._IssueSetting()], error_ok=True).strip()
|
||||
if issue:
|
||||
self.issue = issue
|
||||
else:
|
||||
self.issue = None
|
||||
self.has_issue = True
|
||||
return self.issue
|
||||
|
||||
def GetIssueURL(self):
|
||||
return IssueURL(self.GetIssue())
|
||||
|
||||
def GetDescription(self, pretty=False):
|
||||
if not self.has_description:
|
||||
if self.GetIssue():
|
||||
url = self.GetIssueURL() + '/description'
|
||||
self.description = urllib2.urlopen(url).read().strip()
|
||||
self.has_description = True
|
||||
if pretty:
|
||||
wrapper = textwrap.TextWrapper()
|
||||
wrapper.initial_indent = wrapper.subsequent_indent = ' '
|
||||
return wrapper.fill(self.description)
|
||||
return self.description
|
||||
|
||||
def GetPatchset(self):
|
||||
if not self.has_patchset:
|
||||
patchset = RunGit(['config', self._PatchsetSetting()],
|
||||
error_ok=True).strip()
|
||||
if patchset:
|
||||
self.patchset = patchset
|
||||
else:
|
||||
self.patchset = None
|
||||
self.has_patchset = True
|
||||
return self.patchset
|
||||
|
||||
def SetPatchset(self, patchset):
|
||||
"""Set this branch's patchset. If patchset=0, clears the patchset."""
|
||||
if patchset:
|
||||
RunGit(['config', self._PatchsetSetting(), str(patchset)])
|
||||
else:
|
||||
RunGit(['config', '--unset', self._PatchsetSetting()])
|
||||
self.has_patchset = False
|
||||
|
||||
def SetIssue(self, issue):
|
||||
"""Set this branch's issue. If issue=0, clears the issue."""
|
||||
if issue:
|
||||
RunGit(['config', self._IssueSetting(), str(issue)])
|
||||
else:
|
||||
RunGit(['config', '--unset', self._IssueSetting()])
|
||||
self.SetPatchset(0)
|
||||
self.has_issue = False
|
||||
|
||||
def CloseIssue(self):
|
||||
def GetUserCredentials():
|
||||
email = raw_input('Email: ').strip()
|
||||
password = getpass.getpass('Password for %s: ' % email)
|
||||
return email, password
|
||||
|
||||
rpc_server = upload.HttpRpcServer(settings.GetServer(),
|
||||
GetUserCredentials,
|
||||
host_override=settings.GetServer(),
|
||||
save_cookies=True)
|
||||
# You cannot close an issue with a GET.
|
||||
# We pass an empty string for the data so it is a POST rather than a GET.
|
||||
data = [("description", self.description),]
|
||||
ctype, body = upload.EncodeMultipartFormData(data, [])
|
||||
rpc_server.Send('/' + self.GetIssue() + '/close', body, ctype)
|
||||
|
||||
def _IssueSetting(self):
|
||||
"""Return the git setting that stores this change's issue."""
|
||||
return 'branch.%s.rietveldissue' % self.GetBranch()
|
||||
|
||||
def _PatchsetSetting(self):
|
||||
"""Return the git setting that stores this change's most recent patchset."""
|
||||
return 'branch.%s.rietveldpatchset' % self.GetBranch()
|
||||
|
||||
|
||||
def CmdConfig(args):
|
||||
server = settings.GetServer(error_ok=True)
|
||||
prompt = 'Rietveld server (host[:port])'
|
||||
prompt += ' [%s]' % (server or DEFAULT_SERVER)
|
||||
newserver = raw_input(prompt + ': ')
|
||||
if not server and not newserver:
|
||||
newserver = DEFAULT_SERVER
|
||||
if newserver and newserver != server:
|
||||
RunGit(['config', 'rietveld.server', newserver])
|
||||
|
||||
def SetProperty(initial, caption, name):
|
||||
prompt = caption
|
||||
if initial:
|
||||
prompt += ' ("x" to clear) [%s]' % initial
|
||||
new_val = raw_input(prompt + ': ')
|
||||
if new_val == 'x':
|
||||
RunGit(['config', '--unset-all', 'rietveld.' + name], error_ok=True)
|
||||
elif new_val and new_val != initial:
|
||||
RunGit(['config', 'rietveld.' + name, new_val])
|
||||
|
||||
SetProperty(settings.GetCCList(), 'CC list', 'cc')
|
||||
SetProperty(settings.GetTreeStatusUrl(error_ok=True), 'Tree status URL',
|
||||
'tree-status-url')
|
||||
SetProperty(settings.GetViewVCUrl(), 'ViewVC URL', 'viewvc-url')
|
||||
|
||||
# TODO: configure a default branch to diff against, rather than this
|
||||
# svn-based hackery.
|
||||
|
||||
|
||||
def CmdStatus(args):
|
||||
branches = RunGit(['for-each-ref', '--format=%(refname)', 'refs/heads'])
|
||||
if branches:
|
||||
print 'Branches associated with reviews:'
|
||||
for branch in sorted(branches.splitlines()):
|
||||
cl = Changelist(branchref=branch)
|
||||
print " %10s: %s" % (cl.GetBranch(), cl.GetIssue())
|
||||
|
||||
cl = Changelist()
|
||||
print
|
||||
print 'Current branch:',
|
||||
if not cl.GetIssue():
|
||||
print 'no issue assigned.'
|
||||
return 0
|
||||
print cl.GetBranch()
|
||||
print 'Issue number:', cl.GetIssue(), '(%s)' % cl.GetIssueURL()
|
||||
print 'Issue description:'
|
||||
print cl.GetDescription(pretty=True)
|
||||
|
||||
|
||||
def CmdIssue(args):
|
||||
cl = Changelist()
|
||||
if len(args) > 0:
|
||||
cl.SetIssue(int(args[0]))
|
||||
print 'Issue number:', cl.GetIssue(), '(%s)' % cl.GetIssueURL()
|
||||
|
||||
|
||||
def UserEditedLog(starting_text):
|
||||
"""Given some starting text, let the user edit it and return the result."""
|
||||
editor = os.getenv('EDITOR', 'vi')
|
||||
|
||||
file = tempfile.NamedTemporaryFile()
|
||||
filename = file.name
|
||||
file.write(starting_text)
|
||||
file.flush()
|
||||
|
||||
ret = subprocess.call(editor + ' ' + filename, shell=True)
|
||||
if ret != 0:
|
||||
return
|
||||
|
||||
text = open(filename).read()
|
||||
file.close()
|
||||
stripcomment_re = re.compile(r'^#.*$', re.MULTILINE)
|
||||
return stripcomment_re.sub('', text).strip()
|
||||
|
||||
|
||||
def CmdUpload(args):
|
||||
parser = optparse.OptionParser(
|
||||
usage='git cl upload [options] [args to "git diff"]')
|
||||
parser.add_option('-m', dest='message', help='message for patch')
|
||||
parser.add_option('-r', '--reviewers',
|
||||
help='reviewer email addresses')
|
||||
parser.add_option('--send-mail', action='store_true',
|
||||
help='send email to reviewer immediately')
|
||||
(options, args) = parser.parse_args(args)
|
||||
|
||||
cl = Changelist()
|
||||
if not args:
|
||||
# Default to diffing against the "upstream" branch.
|
||||
args = [cl.GetUpstreamBranch()]
|
||||
# --no-ext-diff is broken in some versions of Git, so try to work around
|
||||
# this by overriding the environment (but there is still a problem if the
|
||||
# git config key "diff.external" is used).
|
||||
env = os.environ.copy()
|
||||
if 'GIT_EXTERNAL_DIFF' in env: del env['GIT_EXTERNAL_DIFF']
|
||||
subprocess.call(['git', 'diff', '--no-ext-diff', '--stat', ] + args, env=env)
|
||||
|
||||
upload_args = ['--assume_yes'] # Don't ask about untracked files.
|
||||
upload_args.extend(['--server', settings.GetServer()])
|
||||
if options.reviewers:
|
||||
upload_args.extend(['--reviewers', options.reviewers])
|
||||
upload_args.extend(['--cc', settings.GetCCList()])
|
||||
if options.message:
|
||||
upload_args.extend(['--message', options.message])
|
||||
if options.send_mail:
|
||||
if not options.reviewers:
|
||||
DieWithError("Must specify reviewers to send email.")
|
||||
upload_args.append('--send_mail')
|
||||
if cl.GetIssue():
|
||||
upload_args.extend(['--issue', cl.GetIssue()])
|
||||
print ("This branch is associated with issue %s. "
|
||||
"Adding patch to that issue." % cl.GetIssue())
|
||||
else:
|
||||
# Construct a description for this change from the log.
|
||||
# We need to convert diff options to log options.
|
||||
log_args = []
|
||||
if len(args) == 1 and not args[0].endswith('.'):
|
||||
log_args = [args[0] + '..']
|
||||
elif len(args) == 2:
|
||||
log_args = [args[0] + '..' + args[1]]
|
||||
else:
|
||||
log_args = args[:] # Hope for the best!
|
||||
desc = RunGit(['log', '--pretty=format:%s\n\n%b'] + log_args)
|
||||
initial_text = """# Enter a description of the change.
|
||||
# This will displayed on the codereview site.
|
||||
# The first line will also be used as the subject of the review."""
|
||||
desc = UserEditedLog(initial_text + '\n' + desc)
|
||||
if not desc:
|
||||
print "Description empty; aborting."
|
||||
return 1
|
||||
subject = desc.splitlines()[0]
|
||||
upload_args.extend(['--message', subject])
|
||||
upload_args.extend(['--description', desc])
|
||||
issue, patchset = upload.RealMain(['upload'] + upload_args + args)
|
||||
if not cl.GetIssue():
|
||||
cl.SetIssue(issue)
|
||||
cl.SetPatchset(patchset)
|
||||
|
||||
|
||||
def CmdDCommit(args):
|
||||
parser = optparse.OptionParser(
|
||||
usage='git cl dcommit [options] [git-svn branch to apply against]')
|
||||
parser.add_option('-f', action='store_true', dest='force',
|
||||
help="force yes to questions (don't prompt)")
|
||||
parser.add_option('-c', dest='contributor',
|
||||
help="external contributor for patch (appended to " +
|
||||
"description)")
|
||||
(options, args) = parser.parse_args(args)
|
||||
|
||||
cl = Changelist()
|
||||
|
||||
if not args:
|
||||
# Default to merging against our best guess of the upstream branch.
|
||||
args = [cl.GetUpstreamBranch()]
|
||||
|
||||
base_branch = args[0]
|
||||
|
||||
# It is important to have these checks at the top. Not only for user
|
||||
# convenience, but also because the cl object then caches the correct values
|
||||
# of these fields even as we're juggling branches for setting up the commit.
|
||||
if not cl.GetIssue():
|
||||
print 'Current issue unknown -- has this branch been uploaded?'
|
||||
return 1
|
||||
if not cl.GetDescription():
|
||||
print 'No description set.'
|
||||
print 'Visit %s/edit to set it.' % (cl.GetIssueURL())
|
||||
return 1
|
||||
|
||||
if RunGit(['diff-index', 'HEAD']):
|
||||
print 'Cannot dcommit with a dirty tree. You must commit locally first.'
|
||||
return 1
|
||||
|
||||
# This rev-list syntax means "show all commits not in my branch that
|
||||
# are in base_branch".
|
||||
upstream_commits = RunGit(['rev-list', '^' + cl.GetBranchRef(),
|
||||
base_branch]).splitlines()
|
||||
if upstream_commits:
|
||||
print ('Base branch "%s" has %d commits '
|
||||
'not in this branch.' % (base_branch, len(upstream_commits)))
|
||||
print 'Run "git merge %s" before attempting to dcommit.' % base_branch
|
||||
return 1
|
||||
|
||||
if not options.force:
|
||||
# Check the tree status if the tree status URL is set.
|
||||
status = GetTreeStatus()
|
||||
if 'closed' == status:
|
||||
print ('The tree is closed. Please wait for it to reopen. Use '
|
||||
'"git cl dcommit -f" to commit on a closed tree.')
|
||||
return 1
|
||||
elif 'unknown' == status:
|
||||
print ('Unable to determine tree status. Please verify manually and '
|
||||
'use "git cl dcommit -f" to commit on a closed tree.')
|
||||
|
||||
description = cl.GetDescription()
|
||||
|
||||
description += "\n\nReview URL: %s" % cl.GetIssueURL()
|
||||
if options.contributor:
|
||||
description += "\nPatch from %s." % options.contributor
|
||||
print 'Description:', repr(description)
|
||||
|
||||
branches = [base_branch, cl.GetBranchRef()]
|
||||
if not options.force:
|
||||
subprocess.call(['git', 'diff', '--stat'] + branches)
|
||||
raw_input("About to commit; enter to confirm.")
|
||||
|
||||
# We want to squash all this branch's commits into one commit with the
|
||||
# proper description.
|
||||
# We do this by doing a "merge --squash" into a new commit branch, then
|
||||
# dcommitting that.
|
||||
MERGE_BRANCH = 'git-cl-commit'
|
||||
# Delete the merge branch if it already exists.
|
||||
if RunGit(['show-ref', '--quiet', '--verify', 'refs/heads/' + MERGE_BRANCH],
|
||||
exit_code=True) == 0:
|
||||
RunGit(['branch', '-D', MERGE_BRANCH])
|
||||
# Stuff our change into the merge branch.
|
||||
RunGit(['checkout', '-q', '-b', MERGE_BRANCH, base_branch])
|
||||
RunGit(['merge', '--squash', cl.GetBranchRef()])
|
||||
RunGit(['commit', '-m', description])
|
||||
# dcommit the merge branch.
|
||||
output = RunGit(['svn', 'dcommit'])
|
||||
# And then swap back to the original branch and clean up.
|
||||
RunGit(['checkout', '-q', cl.GetBranch()])
|
||||
RunGit(['branch', '-D', MERGE_BRANCH])
|
||||
if output.find("Committed r") != -1:
|
||||
print "Closing issue (you may be prompted for your codereview password)..."
|
||||
if cl.has_issue:
|
||||
viewvc_url = settings.GetViewVCUrl()
|
||||
if viewvc_url:
|
||||
revision = re.compile(".*?\nCommitted r(\d+)",
|
||||
re.DOTALL).match(output).group(1)
|
||||
cl.description = (cl.description +
|
||||
"\n\nCommitted: " + viewvc_url + revision)
|
||||
cl.CloseIssue()
|
||||
cl.SetIssue(0)
|
||||
|
||||
|
||||
def CmdPatch(args):
|
||||
parser = optparse.OptionParser(usage=('git cl patch [options] '
|
||||
'<patch url or issue id>'))
|
||||
parser.add_option('-b', dest='newbranch',
|
||||
help='create a new branch off trunk for the patch')
|
||||
parser.add_option('-f', action='store_true', dest='force',
|
||||
help='with -b, clobber any existing branch')
|
||||
parser.add_option('--reject', action='store_true', dest='reject',
|
||||
help='allow failed patches and spew .rej files')
|
||||
parser.add_option('-n', '--no-commit', action='store_true', dest='nocommit',
|
||||
help="don't commit after patch applies")
|
||||
(options, args) = parser.parse_args(args)
|
||||
if len(args) != 1:
|
||||
return parser.print_help()
|
||||
input = args[0]
|
||||
|
||||
if re.match(r'\d+', input):
|
||||
# Input is an issue id. Figure out the URL.
|
||||
issue = input
|
||||
fetch = "curl --silent http://%s/%s" % (settings.GetServer(), issue)
|
||||
grep = "grep -E -o '/download/issue[0-9]+_[0-9]+.diff'"
|
||||
pipe = subprocess.Popen("%s | %s" % (fetch, grep), shell=True,
|
||||
stdout=subprocess.PIPE)
|
||||
path = pipe.stdout.read().strip()
|
||||
url = 'http://%s%s' % (settings.GetServer(), path)
|
||||
else:
|
||||
# Assume it's a URL to the patch.
|
||||
match = re.match(r'http://.*?/issue(\d+)_\d+.diff', input)
|
||||
if match:
|
||||
issue = match.group(1)
|
||||
url = input
|
||||
else:
|
||||
print "Must pass an issue ID or full URL for 'Download raw patch set'"
|
||||
return 1
|
||||
|
||||
if options.newbranch:
|
||||
if options.force:
|
||||
RunGit(['branch', '-D', options.newbranch], error_ok=True)
|
||||
RunGit(['checkout', '-b', options.newbranch])
|
||||
|
||||
# Switch up to the top-level directory, if necessary, in preparation for
|
||||
# applying the patch.
|
||||
top = RunGit(['rev-parse', '--show-cdup']).strip()
|
||||
if top:
|
||||
os.chdir(top)
|
||||
|
||||
# Construct a pipeline to feed the patch into "git apply".
|
||||
# We use "git apply" to apply the patch instead of "patch" so that we can
|
||||
# pick up file adds.
|
||||
# 1) Fetch the patch.
|
||||
fetch = "curl --silent %s" % url
|
||||
# 2) Munge the patch.
|
||||
# Git patches have a/ at the beginning of source paths. We strip that out
|
||||
# with a sed script rather than the -p flag to patch so we can feed either
|
||||
# Git or svn-style patches into the same apply command.
|
||||
gitsed = "sed -e 's|^--- a/|--- |; s|^+++ b/|+++ |'"
|
||||
# 3) Apply the patch.
|
||||
# The --index flag means: also insert into the index (so we catch adds).
|
||||
apply = "git apply --index -p0"
|
||||
if options.reject:
|
||||
apply += " --reject"
|
||||
subprocess.check_call(' | '.join([fetch, gitsed, apply]), shell=True)
|
||||
|
||||
# If we had an issue, commit the current state and register the issue.
|
||||
if not options.nocommit:
|
||||
RunGit(['commit', '-m', 'patch from issue %s' % issue])
|
||||
cl = Changelist()
|
||||
cl.SetIssue(issue)
|
||||
print "Committed patch."
|
||||
else:
|
||||
print "Patch applied to index."
|
||||
|
||||
def CmdRebase(args):
|
||||
# Provide a wrapper for git svn rebase to help avoid accidental
|
||||
# git svn dcommit.
|
||||
RunGit(['svn', 'rebase'])
|
||||
|
||||
def GetTreeStatus():
|
||||
"""Fetches the tree status and returns either 'open', 'closed',
|
||||
'unknown' or 'unset'."""
|
||||
url = settings.GetTreeStatusUrl(error_ok=True)
|
||||
if url:
|
||||
status = urllib2.urlopen(url).read().lower()
|
||||
if status.find('closed') != -1:
|
||||
return 'closed'
|
||||
elif status.find('open') != -1:
|
||||
return 'open'
|
||||
return 'unknown'
|
||||
|
||||
return 'unset'
|
||||
|
||||
def CmdTreeStatus(args):
|
||||
status = GetTreeStatus()
|
||||
if 'unset' == status:
|
||||
print 'You must configure your tree status URL by running "git cl config".'
|
||||
else:
|
||||
print "The tree is %s" % status
|
||||
|
||||
def CmdUpstream(args):
|
||||
cl = Changelist()
|
||||
print cl.GetUpstreamBranch()
|
||||
|
||||
COMMANDS = [
|
||||
('config', 'edit configuration for this tree', CmdConfig),
|
||||
('status', 'show status of changelists', CmdStatus),
|
||||
('issue', 'show/set current branch\'s issue number', CmdIssue),
|
||||
('upload', 'upload the current changelist to codereview', CmdUpload),
|
||||
('dcommit', 'commit the current changelist via git-svn', CmdDCommit),
|
||||
('patch', 'patch in a code review', CmdPatch),
|
||||
('rebase', 'rebase current branch on top of svn repo', CmdRebase),
|
||||
('tree', 'show the status of the tree', CmdTreeStatus),
|
||||
('upstream', 'print the name of the upstream branch, if any', CmdUpstream),
|
||||
]
|
||||
|
||||
|
||||
def Usage(name):
|
||||
print 'usage: %s <command>' % name
|
||||
print 'commands are:'
|
||||
for name, desc, _ in COMMANDS:
|
||||
print ' %-10s %s' % (name, desc)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main(argv):
|
||||
if len(argv) < 2:
|
||||
Usage(argv[0])
|
||||
|
||||
command = argv[1]
|
||||
for name, _, func in COMMANDS:
|
||||
if name == command:
|
||||
return func(argv[2:])
|
||||
print 'unknown command: %s' % command
|
||||
Usage(argv[0])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv))
|
||||
184
git-try.py
Executable file
184
git-try.py
Executable file
@@ -0,0 +1,184 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# Repo lives in ~evanm/projects/git-try -- feel free to send patches.
|
||||
|
||||
import getpass
|
||||
import optparse
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
import traceback
|
||||
import urllib
|
||||
import sys
|
||||
|
||||
|
||||
def Backquote(cmd):
|
||||
"""Like running `cmd` in a shell script."""
|
||||
return subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0].strip()
|
||||
|
||||
|
||||
def GetTryServerConfig():
|
||||
"""Returns the dictionary of try server options or None if they
|
||||
cannot be found."""
|
||||
script_path = 'tools/tryserver/tryserver.py'
|
||||
root_dir = Backquote(['git', 'rev-parse', '--show-cdup'])
|
||||
try:
|
||||
script_file = open(os.path.join(root_dir, script_path))
|
||||
except IOError:
|
||||
return None
|
||||
locals = {}
|
||||
try:
|
||||
exec(script_file, locals)
|
||||
except Exception, e:
|
||||
return None
|
||||
return locals
|
||||
|
||||
|
||||
def GetBranchName():
|
||||
"""Return name of current git branch."""
|
||||
branch = Backquote(['git', 'symbolic-ref', 'HEAD'])
|
||||
if not branch.startswith('refs/heads/'):
|
||||
raise "Couldn't figure out branch name"
|
||||
branch = branch[len('refs/heads/'):]
|
||||
return branch
|
||||
|
||||
|
||||
def GetPatchName():
|
||||
"""Construct a name for this patch."""
|
||||
# TODO: perhaps include the hash of the current commit, to distinguish
|
||||
# patches?
|
||||
return GetBranchName()
|
||||
|
||||
|
||||
def GetRevision():
|
||||
"""Get the latest Subversion revision number."""
|
||||
for line in Backquote(['git', 'svn', 'info']).split('\n'):
|
||||
if line.startswith('Revision:'):
|
||||
return line[len('Revision:'):].strip()
|
||||
raise "Couldn't figure out latest revision"
|
||||
|
||||
|
||||
def GetRietveldIssueNumber():
|
||||
return Backquote(['git', 'config',
|
||||
'branch.%s.rietveldissue' % GetBranchName()])
|
||||
|
||||
|
||||
def GetRietveldPatchsetNumber():
|
||||
return Backquote(['git', 'config',
|
||||
'branch.%s.rietveldpatchset' % GetBranchName()])
|
||||
|
||||
|
||||
def GetMungedDiff(branch):
|
||||
"""Get the diff we'll send to the try server. We munge paths to match svn."""
|
||||
# Make the following changes:
|
||||
# - Prepend "src/" to paths as svn is expecting
|
||||
# - In the case of added files, replace /dev/null with the path to the file
|
||||
# being added.
|
||||
output = []
|
||||
if not branch:
|
||||
# Try to guess the upstream branch.
|
||||
branch = Backquote(['git', 'cl', 'upstream'])
|
||||
diff = subprocess.Popen(['git', 'diff-tree', '-p', '--no-prefix',
|
||||
branch, 'HEAD'],
|
||||
stdout=subprocess.PIPE).stdout.readlines()
|
||||
for i in range(len(diff)):
|
||||
line = diff[i]
|
||||
if line.startswith('--- /dev/null'):
|
||||
line = '--- %s' % diff[i+1][4:]
|
||||
elif line.startswith('--- ') or line.startswith('+++ '):
|
||||
line = line[0:4] + 'src/' + line[4:]
|
||||
output.append(line)
|
||||
|
||||
return ''.join(output)
|
||||
|
||||
|
||||
def GetEmail():
|
||||
# TODO: check for errors here?
|
||||
return Backquote(['git', 'config', 'user.email'])
|
||||
|
||||
|
||||
def TryChange(args):
|
||||
"""Put a patch on the try server using SVN."""
|
||||
# TODO: figure out a better way to load trychange
|
||||
script_path = '../depot_tools/release'
|
||||
root_dir = Backquote(['git', 'rev-parse', '--show-cdup'])
|
||||
sys.path.append(os.path.join(root_dir, script_path))
|
||||
import trychange
|
||||
trychange.checkout_root = os.path.abspath(root_dir)
|
||||
trychange.TryChange(args, None, False)
|
||||
|
||||
|
||||
def WriteTryDiffHTTP(config, patch_name, diff, options):
|
||||
"""Put a patch on the try server."""
|
||||
params = {
|
||||
'user': getpass.getuser(),
|
||||
'name': patch_name,
|
||||
'patch': diff
|
||||
}
|
||||
|
||||
if options.bot:
|
||||
params['bot'] = options.bot
|
||||
|
||||
if options.clobber:
|
||||
params['clobber'] = 'true'
|
||||
|
||||
url = 'http://%s:%s/send_try_patch' % (config['try_server_http_host'],
|
||||
config['try_server_http_port'])
|
||||
connection = urllib.urlopen(url, urllib.urlencode(params))
|
||||
response = connection.read()
|
||||
if (response != 'OK'):
|
||||
print "Error posting to", url
|
||||
print response
|
||||
assert False
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = optparse.OptionParser(
|
||||
usage='git try [branch]',
|
||||
description='Upload the current diff of branch...HEAD to the try server.')
|
||||
parser.add_option("-b", "--bot",
|
||||
help="Force the use of a specific build slave (eg mac, "
|
||||
"win, or linux)")
|
||||
parser.add_option("-c", "--clobber", action="store_true",
|
||||
help="Make the try run use be a clobber build")
|
||||
(options, args) = parser.parse_args(sys.argv)
|
||||
|
||||
branch = None
|
||||
if len(args) > 1:
|
||||
branch = args[1]
|
||||
|
||||
patch_name = GetPatchName()
|
||||
diff = GetMungedDiff(branch)
|
||||
|
||||
# Send directly to try server if we can parse the config, otherwise
|
||||
# upload via SVN.
|
||||
config = GetTryServerConfig()
|
||||
if config is not None:
|
||||
print "Sending %s using HTTP..." % patch_name
|
||||
WriteTryDiffHTTP(config=config, patch_name=patch_name, diff=diff,
|
||||
options=options)
|
||||
else:
|
||||
print "Sending %s using SVN..." % patch_name
|
||||
|
||||
# Write the diff out to a temporary file
|
||||
diff_file = tempfile.NamedTemporaryFile()
|
||||
diff_file.write(diff)
|
||||
diff_file.flush()
|
||||
|
||||
email = GetEmail()
|
||||
user = email.partition('@')[0]
|
||||
args = [
|
||||
'--use_svn',
|
||||
'--svn_repo', 'svn://svn.chromium.org/chrome-try/try',
|
||||
'-u', user,
|
||||
'-e', email,
|
||||
'-n', patch_name,
|
||||
'-r', GetRevision(),
|
||||
'--diff', diff_file.name,
|
||||
]
|
||||
if GetRietveldPatchsetNumber():
|
||||
args.extend([
|
||||
'--issue', GetRietveldIssueNumber(),
|
||||
'--patchset', GetRietveldPatchsetNumber(),
|
||||
])
|
||||
TryChange(args=args)
|
||||
9
hammer
Normal file
9
hammer
Normal file
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
# The first expression catches when we're actually in the /src directory.
|
||||
# The second expressions strips everything after the last /src occurrence.
|
||||
SRC_DIR=`pwd | sed -e '\;/src$;q' -e 's;\(.*/src\)/.*;\1;'`
|
||||
SCONS="${SRC_DIR}/third_party/scons/scons.py"
|
||||
SITE_SCONS="${SRC_DIR}/site_scons"
|
||||
|
||||
exec python "${SCONS}" "--site-dir=${SITE_SCONS}" "$@"
|
||||
23
hammer.bat
Normal file
23
hammer.bat
Normal file
@@ -0,0 +1,23 @@
|
||||
@echo off
|
||||
|
||||
|
||||
@rem We're in a submodule directory, look relative to the parent.
|
||||
call python "%cd%\..\third_party\scons\scons.py" "--site-dir=..\site_scons" %*
|
||||
goto omega
|
||||
|
||||
:srcdir
|
||||
call python "%cd%\third_party\scons\scons.py" --site-dir=site_scons %*
|
||||
goto omega
|
||||
|
||||
@rem Per the following page:
|
||||
@rem http://code-bear.com/bearlog/2007/06/01/getting-the-exit-code-from-a-batch-file-that-is-run-from-a-python-program/
|
||||
@rem Just calling "exit /b" passes back an exit code, but in a way
|
||||
@rem that does NOT get picked up correctly when executing the .bat
|
||||
@rem file from the Python subprocess module. Using "call" as the
|
||||
@rem last command in the .bat file makes it work as expected.
|
||||
|
||||
:returncode
|
||||
exit /b %ERRORLEVEL%
|
||||
|
||||
:omega
|
||||
call :returncode %ERRORLEVEL%
|
||||
715
presubmit.py
Executable file
715
presubmit.py
Executable file
@@ -0,0 +1,715 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
"""Enables directory-specific presubmit checks to run at upload and/or commit.
|
||||
"""
|
||||
|
||||
__version__ = '1.0'
|
||||
|
||||
# TODO(joi) Add caching where appropriate/needed. The API is designed to allow
|
||||
# caching (between all different invocations of presubmit scripts for a given
|
||||
# change). We should add it as our presubmit scripts start feeling slow.
|
||||
|
||||
import cPickle # Exposed through the API.
|
||||
import cStringIO # Exposed through the API.
|
||||
import exceptions
|
||||
import fnmatch
|
||||
import glob
|
||||
import marshal # Exposed through the API.
|
||||
import optparse
|
||||
import os # Somewhat exposed through the API.
|
||||
import pickle # Exposed through the API.
|
||||
import re # Exposed through the API.
|
||||
import subprocess # Exposed through the API.
|
||||
import sys # Parts exposed through API.
|
||||
import tempfile # Exposed through the API.
|
||||
import types
|
||||
import urllib2 # Exposed through the API.
|
||||
|
||||
# Local imports.
|
||||
# TODO(joi) Would be cleaner to factor out utils in gcl to separate module, but
|
||||
# for now it would only be a couple of functions so hardly worth it.
|
||||
import gcl
|
||||
import presubmit_canned_checks
|
||||
|
||||
|
||||
# Matches key/value (or "tag") lines in changelist descriptions.
|
||||
_tag_line_re = re.compile(
|
||||
'^\s*(?P<key>[A-Z][A-Z_0-9]*)\s*=\s*(?P<value>.*?)\s*$')
|
||||
|
||||
|
||||
# Friendly names may be used for certain keys. All values for key-value pairs
|
||||
# in change descriptions (like BUG=123) can be retrieved from a change object
|
||||
# directly as if they were attributes, e.g. change.R (or equivalently because
|
||||
# we have a friendly name for it, change.Reviewers), change.BUG (or
|
||||
# change.BugIDs) and so forth.
|
||||
#
|
||||
# Add to this mapping as needed/desired.
|
||||
SPECIAL_KEYS = {
|
||||
'Reviewers' : 'R',
|
||||
'BugIDs' : 'BUG',
|
||||
'Tested': 'TESTED'
|
||||
}
|
||||
|
||||
|
||||
class NotImplementedException(Exception):
|
||||
"""We're leaving placeholders in a bunch of places to remind us of the
|
||||
design of the API, but we have not implemented all of it yet. Implement as
|
||||
the need arises.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def normpath(path):
|
||||
'''Version of os.path.normpath that also changes backward slashes to
|
||||
forward slashes when not running on Windows.
|
||||
'''
|
||||
# This is safe to always do because the Windows version of os.path.normpath
|
||||
# will replace forward slashes with backward slashes.
|
||||
path = path.replace(os.sep, '/')
|
||||
return os.path.normpath(path)
|
||||
|
||||
|
||||
|
||||
class OutputApi(object):
|
||||
"""This class (more like a module) gets passed to presubmit scripts so that
|
||||
they can specify various types of results.
|
||||
"""
|
||||
|
||||
class PresubmitResult(object):
|
||||
"""Base class for result objects."""
|
||||
|
||||
def __init__(self, message, items=None, long_text=''):
|
||||
"""
|
||||
message: A short one-line message to indicate errors.
|
||||
items: A list of short strings to indicate where errors occurred.
|
||||
long_text: multi-line text output, e.g. from another tool
|
||||
"""
|
||||
self._message = message
|
||||
self._items = []
|
||||
if items:
|
||||
self._items = items
|
||||
self._long_text = long_text.rstrip()
|
||||
|
||||
def _Handle(self, output_stream, input_stream, may_prompt=True):
|
||||
"""Writes this result to the output stream.
|
||||
|
||||
Args:
|
||||
output_stream: Where to write
|
||||
|
||||
Returns:
|
||||
True if execution may continue, False otherwise.
|
||||
"""
|
||||
output_stream.write(self._message)
|
||||
output_stream.write('\n')
|
||||
for item in self._items:
|
||||
output_stream.write(' %s\n' % item)
|
||||
if self._long_text:
|
||||
output_stream.write('\n***************\n%s\n***************\n\n' %
|
||||
self._long_text)
|
||||
|
||||
if self.ShouldPrompt() and may_prompt:
|
||||
output_stream.write('Are you sure you want to continue? (y/N): ')
|
||||
response = input_stream.readline()
|
||||
if response.strip().lower() != 'y':
|
||||
return False
|
||||
|
||||
return not self.IsFatal()
|
||||
|
||||
def IsFatal(self):
|
||||
"""An error that is fatal stops g4 mail/submit immediately, i.e. before
|
||||
other presubmit scripts are run.
|
||||
"""
|
||||
return False
|
||||
|
||||
def ShouldPrompt(self):
|
||||
"""Whether this presubmit result should result in a prompt warning."""
|
||||
return False
|
||||
|
||||
class PresubmitError(PresubmitResult):
|
||||
"""A hard presubmit error."""
|
||||
def IsFatal(self):
|
||||
return True
|
||||
|
||||
class PresubmitPromptWarning(PresubmitResult):
|
||||
"""An warning that prompts the user if they want to continue."""
|
||||
def ShouldPrompt(self):
|
||||
return True
|
||||
|
||||
class PresubmitNotifyResult(PresubmitResult):
|
||||
"""Just print something to the screen -- but it's not even a warning."""
|
||||
pass
|
||||
|
||||
class MailTextResult(PresubmitResult):
|
||||
"""A warning that should be included in the review request email."""
|
||||
def __init__(self, *args, **kwargs):
|
||||
raise NotImplementedException() # TODO(joi) Implement.
|
||||
|
||||
|
||||
class InputApi(object):
|
||||
"""An instance of this object is passed to presubmit scripts so they can
|
||||
know stuff about the change they're looking at.
|
||||
"""
|
||||
|
||||
def __init__(self, change, presubmit_path):
|
||||
"""Builds an InputApi object.
|
||||
|
||||
Args:
|
||||
change: A presubmit.GclChange object.
|
||||
presubmit_path: The path to the presubmit script being processed.
|
||||
"""
|
||||
self.change = change
|
||||
|
||||
# We expose various modules and functions as attributes of the input_api
|
||||
# so that presubmit scripts don't have to import them.
|
||||
self.basename = os.path.basename
|
||||
self.cPickle = cPickle
|
||||
self.cStringIO = cStringIO
|
||||
self.os_path = os.path
|
||||
self.pickle = pickle
|
||||
self.marshal = marshal
|
||||
self.re = re
|
||||
self.subprocess = subprocess
|
||||
self.tempfile = tempfile
|
||||
self.urllib2 = urllib2
|
||||
|
||||
# InputApi.platform is the platform you're currently running on.
|
||||
self.platform = sys.platform
|
||||
|
||||
# The local path of the currently-being-processed presubmit script.
|
||||
self.current_presubmit_path = presubmit_path
|
||||
|
||||
# We carry the canned checks so presubmit scripts can easily use them.
|
||||
self.canned_checks = presubmit_canned_checks
|
||||
|
||||
def PresubmitLocalPath(self):
|
||||
"""Returns the local path of the presubmit script currently being run.
|
||||
|
||||
This is useful if you don't want to hard-code absolute paths in the
|
||||
presubmit script. For example, It can be used to find another file
|
||||
relative to the PRESUBMIT.py script, so the whole tree can be branched and
|
||||
the presubmit script still works, without editing its content.
|
||||
"""
|
||||
return self.current_presubmit_path
|
||||
|
||||
@staticmethod
|
||||
def DepotToLocalPath(depot_path):
|
||||
"""Translate a depot path to a local path (relative to client root).
|
||||
|
||||
Args:
|
||||
Depot path as a string.
|
||||
|
||||
Returns:
|
||||
The local path of the depot path under the user's current client, or None
|
||||
if the file is not mapped.
|
||||
|
||||
Remember to check for the None case and show an appropriate error!
|
||||
"""
|
||||
local_path = gcl.GetSVNFileInfo(depot_path).get('Path')
|
||||
if not local_path:
|
||||
return None
|
||||
else:
|
||||
return local_path
|
||||
|
||||
@staticmethod
|
||||
def LocalToDepotPath(local_path):
|
||||
"""Translate a local path to a depot path.
|
||||
|
||||
Args:
|
||||
Local path (relative to current directory, or absolute) as a string.
|
||||
|
||||
Returns:
|
||||
The depot path (SVN URL) of the file if mapped, otherwise None.
|
||||
"""
|
||||
depot_path = gcl.GetSVNFileInfo(local_path).get('URL')
|
||||
if not depot_path:
|
||||
return None
|
||||
else:
|
||||
return depot_path
|
||||
|
||||
@staticmethod
|
||||
def FilterTextFiles(affected_files, include_deletes=True):
|
||||
"""Filters out all except text files and optionally also filters out
|
||||
deleted files.
|
||||
|
||||
Args:
|
||||
affected_files: List of AffectedFiles objects.
|
||||
include_deletes: If false, deleted files will be filtered out.
|
||||
|
||||
Returns:
|
||||
Filtered list of AffectedFiles objects.
|
||||
"""
|
||||
output_files = []
|
||||
for af in affected_files:
|
||||
if include_deletes or af.Action() != 'D':
|
||||
path = af.AbsoluteLocalPath()
|
||||
mime_type = gcl.GetSVNFileProperty(path, 'svn:mime-type')
|
||||
if not mime_type or mime_type.startswith('text/'):
|
||||
output_files.append(af)
|
||||
return output_files
|
||||
|
||||
def AffectedFiles(self, include_dirs=False, include_deletes=True):
|
||||
"""Same as input_api.change.AffectedFiles() except only lists files
|
||||
(and optionally directories) in the same directory as the current presubmit
|
||||
script, or subdirectories thereof.
|
||||
"""
|
||||
output_files = []
|
||||
dir_with_slash = normpath(
|
||||
"%s/" % os.path.dirname(self.current_presubmit_path))
|
||||
if len(dir_with_slash) == 1:
|
||||
dir_with_slash = ''
|
||||
for af in self.change.AffectedFiles(include_dirs, include_deletes):
|
||||
af_path = normpath(af.LocalPath())
|
||||
if af_path.startswith(dir_with_slash):
|
||||
output_files.append(af)
|
||||
return output_files
|
||||
|
||||
def LocalPaths(self, include_dirs=False):
|
||||
"""Returns local paths of input_api.AffectedFiles()."""
|
||||
return [af.LocalPath() for af in self.AffectedFiles(include_dirs)]
|
||||
|
||||
def AbsoluteLocalPaths(self, include_dirs=False):
|
||||
"""Returns absolute local paths of input_api.AffectedFiles()."""
|
||||
return [af.AbsoluteLocalPath() for af in self.AffectedFiles(include_dirs)]
|
||||
|
||||
def ServerPaths(self, include_dirs=False):
|
||||
"""Returns server paths of input_api.AffectedFiles()."""
|
||||
return [af.ServerPath() for af in self.AffectedFiles(include_dirs)]
|
||||
|
||||
def AffectedTextFiles(self, include_deletes=True):
|
||||
"""Same as input_api.change.AffectedTextFiles() except only lists files
|
||||
in the same directory as the current presubmit script, or subdirectories
|
||||
thereof.
|
||||
|
||||
Warning: This function retrieves the svn property on each file so it can be
|
||||
slow for large change lists.
|
||||
"""
|
||||
return InputApi.FilterTextFiles(self.AffectedFiles(include_dirs=False),
|
||||
include_deletes)
|
||||
|
||||
def RightHandSideLines(self):
|
||||
"""An iterator over all text lines in "new" version of changed files.
|
||||
|
||||
Only lists lines from new or modified text files in the change that are
|
||||
contained by the directory of the currently executing presubmit script.
|
||||
|
||||
This is useful for doing line-by-line regex checks, like checking for
|
||||
trailing whitespace.
|
||||
|
||||
Yields:
|
||||
a 3 tuple:
|
||||
the AffectedFile instance of the current file;
|
||||
integer line number (1-based); and
|
||||
the contents of the line as a string.
|
||||
"""
|
||||
return InputApi._RightHandSideLinesImpl(
|
||||
self.AffectedTextFiles(include_deletes=False))
|
||||
|
||||
@staticmethod
|
||||
def _RightHandSideLinesImpl(affected_files):
|
||||
"""Implements RightHandSideLines for InputApi and GclChange."""
|
||||
for af in affected_files:
|
||||
lines = af.NewContents()
|
||||
line_number = 0
|
||||
for line in lines:
|
||||
line_number += 1
|
||||
yield (af, line_number, line)
|
||||
|
||||
|
||||
class AffectedFile(object):
|
||||
"""Representation of a file in a change."""
|
||||
|
||||
def __init__(self, path, action, repository_root=''):
|
||||
self.path = path
|
||||
self.action = action.strip()
|
||||
self.repository_root = repository_root
|
||||
|
||||
def ServerPath(self):
|
||||
"""Returns a path string that identifies the file in the SCM system.
|
||||
|
||||
Returns the empty string if the file does not exist in SCM.
|
||||
"""
|
||||
return gcl.GetSVNFileInfo(self.AbsoluteLocalPath()).get('URL', '')
|
||||
|
||||
def LocalPath(self):
|
||||
"""Returns the path of this file on the local disk relative to client root.
|
||||
"""
|
||||
return normpath(self.path)
|
||||
|
||||
def AbsoluteLocalPath(self):
|
||||
"""Returns the absolute path of this file on the local disk.
|
||||
"""
|
||||
return normpath(os.path.join(self.repository_root, self.LocalPath()))
|
||||
|
||||
def IsDirectory(self):
|
||||
"""Returns true if this object is a directory."""
|
||||
if os.path.exists(self.path):
|
||||
# Retrieve directly from the file system; it is much faster than querying
|
||||
# subversion, especially on Windows.
|
||||
return os.path.isdir(self.path)
|
||||
else:
|
||||
return gcl.GetSVNFileInfo(self.path).get('Node Kind') == 'directory'
|
||||
|
||||
def SvnProperty(self, property_name):
|
||||
"""Returns the specified SVN property of this file, or the empty string
|
||||
if no such property.
|
||||
"""
|
||||
return gcl.GetSVNFileProperty(self.AbsoluteLocalPath(), property_name)
|
||||
|
||||
def Action(self):
|
||||
"""Returns the action on this opened file, e.g. A, M, D, etc."""
|
||||
return self.action
|
||||
|
||||
def NewContents(self):
|
||||
"""Returns an iterator over the lines in the new version of file.
|
||||
|
||||
The new version is the file in the user's workspace, i.e. the "right hand
|
||||
side".
|
||||
|
||||
Contents will be empty if the file is a directory or does not exist.
|
||||
"""
|
||||
if self.IsDirectory():
|
||||
return []
|
||||
else:
|
||||
return gcl.ReadFile(self.AbsoluteLocalPath()).splitlines()
|
||||
|
||||
def OldContents(self):
|
||||
"""Returns an iterator over the lines in the old version of file.
|
||||
|
||||
The old version is the file in depot, i.e. the "left hand side".
|
||||
"""
|
||||
raise NotImplementedError() # Implement when needed
|
||||
|
||||
def OldFileTempPath(self):
|
||||
"""Returns the path on local disk where the old contents resides.
|
||||
|
||||
The old version is the file in depot, i.e. the "left hand side".
|
||||
This is a read-only cached copy of the old contents. *DO NOT* try to
|
||||
modify this file.
|
||||
"""
|
||||
raise NotImplementedError() # Implement if/when needed.
|
||||
|
||||
|
||||
class GclChange(object):
|
||||
"""A gcl change. See gcl.ChangeInfo for more info."""
|
||||
|
||||
def __init__(self, change_info, repository_root=''):
|
||||
self.name = change_info.name
|
||||
self.full_description = change_info.description
|
||||
self.repository_root = repository_root
|
||||
|
||||
# From the description text, build up a dictionary of key/value pairs
|
||||
# plus the description minus all key/value or "tag" lines.
|
||||
self.description_without_tags = []
|
||||
self.tags = {}
|
||||
for line in change_info.description.splitlines():
|
||||
m = _tag_line_re.match(line)
|
||||
if m:
|
||||
self.tags[m.group('key')] = m.group('value')
|
||||
else:
|
||||
self.description_without_tags.append(line)
|
||||
|
||||
# Change back to text and remove whitespace at end.
|
||||
self.description_without_tags = '\n'.join(self.description_without_tags)
|
||||
self.description_without_tags = self.description_without_tags.rstrip()
|
||||
|
||||
self.affected_files = [AffectedFile(info[1], info[0], repository_root) for
|
||||
info in change_info.files]
|
||||
|
||||
def Change(self):
|
||||
"""Returns the change name."""
|
||||
return self.name
|
||||
|
||||
def Changelist(self):
|
||||
"""Synonym for Change()."""
|
||||
return self.Change()
|
||||
|
||||
def DescriptionText(self):
|
||||
"""Returns the user-entered changelist description, minus tags.
|
||||
|
||||
Any line in the user-provided description starting with e.g. "FOO="
|
||||
(whitespace permitted before and around) is considered a tag line. Such
|
||||
lines are stripped out of the description this function returns.
|
||||
"""
|
||||
return self.description_without_tags
|
||||
|
||||
def FullDescriptionText(self):
|
||||
"""Returns the complete changelist description including tags."""
|
||||
return self.full_description
|
||||
|
||||
def RepositoryRoot(self):
|
||||
"""Returns the repository root for this change, as an absolute path."""
|
||||
return self.repository_root
|
||||
|
||||
def __getattr__(self, attr):
|
||||
"""Return keys directly as attributes on the object.
|
||||
|
||||
You may use a friendly name (from SPECIAL_KEYS) or the actual name of
|
||||
the key.
|
||||
"""
|
||||
if attr in SPECIAL_KEYS:
|
||||
key = SPECIAL_KEYS[attr]
|
||||
if key in self.tags:
|
||||
return self.tags[key]
|
||||
if attr in self.tags:
|
||||
return self.tags[attr]
|
||||
|
||||
def AffectedFiles(self, include_dirs=False, include_deletes=True):
|
||||
"""Returns a list of AffectedFile instances for all files in the change.
|
||||
|
||||
Args:
|
||||
include_deletes: If false, deleted files will be filtered out.
|
||||
include_dirs: True to include directories in the list
|
||||
|
||||
Returns:
|
||||
[AffectedFile(path, action), AffectedFile(path, action)]
|
||||
"""
|
||||
if include_dirs:
|
||||
affected = self.affected_files
|
||||
else:
|
||||
affected = filter(lambda x: not x.IsDirectory(), self.affected_files)
|
||||
|
||||
if include_deletes:
|
||||
return affected
|
||||
else:
|
||||
return filter(lambda x: x.Action() != 'D', affected)
|
||||
|
||||
def AffectedTextFiles(self, include_deletes=True):
|
||||
"""Return a list of the text files in a change.
|
||||
|
||||
It's common to want to iterate over only the text files.
|
||||
|
||||
Args:
|
||||
include_deletes: Controls whether to return files with "delete" actions,
|
||||
which commonly aren't relevant to presubmit scripts.
|
||||
"""
|
||||
return InputApi.FilterTextFiles(self.AffectedFiles(include_dirs=False),
|
||||
include_deletes)
|
||||
|
||||
def LocalPaths(self, include_dirs=False):
|
||||
"""Convenience function."""
|
||||
return [af.LocalPath() for af in self.AffectedFiles(include_dirs)]
|
||||
|
||||
def AbsoluteLocalPaths(self, include_dirs=False):
|
||||
"""Convenience function."""
|
||||
return [af.AbsoluteLocalPath() for af in self.AffectedFiles(include_dirs)]
|
||||
|
||||
def ServerPaths(self, include_dirs=False):
|
||||
"""Convenience function."""
|
||||
return [af.ServerPath() for af in self.AffectedFiles(include_dirs)]
|
||||
|
||||
def RightHandSideLines(self):
|
||||
"""An iterator over all text lines in "new" version of changed files.
|
||||
|
||||
Lists lines from new or modified text files in the change.
|
||||
|
||||
This is useful for doing line-by-line regex checks, like checking for
|
||||
trailing whitespace.
|
||||
|
||||
Yields:
|
||||
a 3 tuple:
|
||||
the AffectedFile instance of the current file;
|
||||
integer line number (1-based); and
|
||||
the contents of the line as a string.
|
||||
"""
|
||||
return InputApi._RightHandSideLinesImpl(
|
||||
self.AffectedTextFiles(include_deletes=False))
|
||||
|
||||
|
||||
def ListRelevantPresubmitFiles(files):
|
||||
"""Finds all presubmit files that apply to a given set of source files.
|
||||
|
||||
Args:
|
||||
files: An iterable container containing file paths.
|
||||
|
||||
Return:
|
||||
['foo/blat/PRESUBMIT.py', 'mat/gat/PRESUBMIT.py']
|
||||
"""
|
||||
checked_dirs = {} # Keys are directory paths, values are ignored.
|
||||
source_dirs = [os.path.dirname(f) for f in files]
|
||||
presubmit_files = []
|
||||
for dir in source_dirs:
|
||||
while (True):
|
||||
if dir in checked_dirs:
|
||||
break # We've already walked up from this directory.
|
||||
|
||||
test_path = os.path.join(dir, 'PRESUBMIT.py')
|
||||
if os.path.isfile(test_path):
|
||||
presubmit_files.append(normpath(test_path))
|
||||
|
||||
checked_dirs[dir] = ''
|
||||
if dir in ['', '.']:
|
||||
break
|
||||
else:
|
||||
dir = os.path.dirname(dir)
|
||||
return presubmit_files
|
||||
|
||||
|
||||
class PresubmitExecuter(object):
|
||||
|
||||
def __init__(self, change_info, committing):
|
||||
"""
|
||||
Args:
|
||||
change_info: The ChangeInfo object for the change.
|
||||
committing: True if 'gcl commit' is running, False if 'gcl upload' is.
|
||||
"""
|
||||
self.change = GclChange(change_info, gcl.GetRepositoryRoot())
|
||||
self.committing = committing
|
||||
|
||||
def ExecPresubmitScript(self, script_text, presubmit_path):
|
||||
"""Executes a single presubmit script.
|
||||
|
||||
Args:
|
||||
script_text: The text of the presubmit script.
|
||||
presubmit_path: The path to the presubmit file (this will be reported via
|
||||
input_api.PresubmitLocalPath()).
|
||||
|
||||
Return:
|
||||
A list of result objects, empty if no problems.
|
||||
"""
|
||||
input_api = InputApi(self.change, presubmit_path)
|
||||
context = {}
|
||||
exec script_text in context
|
||||
|
||||
# These function names must change if we make substantial changes to
|
||||
# the presubmit API that are not backwards compatible.
|
||||
if self.committing:
|
||||
function_name = 'CheckChangeOnCommit'
|
||||
else:
|
||||
function_name = 'CheckChangeOnUpload'
|
||||
if function_name in context:
|
||||
context['__args'] = (input_api, OutputApi())
|
||||
result = eval(function_name + '(*__args)', context)
|
||||
if not (isinstance(result, types.TupleType) or
|
||||
isinstance(result, types.ListType)):
|
||||
raise exceptions.RuntimeError(
|
||||
'Presubmit functions must return a tuple or list')
|
||||
for item in result:
|
||||
if not isinstance(item, OutputApi.PresubmitResult):
|
||||
raise exceptions.RuntimeError(
|
||||
'All presubmit results must be of types derived from '
|
||||
'output_api.PresubmitResult')
|
||||
else:
|
||||
result = () # no error since the script doesn't care about current event.
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def DoPresubmitChecks(change_info,
|
||||
committing,
|
||||
verbose,
|
||||
output_stream,
|
||||
input_stream,
|
||||
default_presubmit):
|
||||
"""Runs all presubmit checks that apply to the files in the change.
|
||||
|
||||
This finds all PRESUBMIT.py files in directories enclosing the files in the
|
||||
change (up to the repository root) and calls the relevant entrypoint function
|
||||
depending on whether the change is being committed or uploaded.
|
||||
|
||||
Prints errors, warnings and notifications. Prompts the user for warnings
|
||||
when needed.
|
||||
|
||||
Args:
|
||||
change_info: The ChangeInfo object for the change.
|
||||
committing: True if 'gcl commit' is running, False if 'gcl upload' is.
|
||||
verbose: Prints debug info.
|
||||
output_stream: A stream to write output from presubmit tests to.
|
||||
input_stream: A stream to read input from the user.
|
||||
default_presubmit: A default presubmit script to execute in any case.
|
||||
|
||||
Return:
|
||||
True if execution can continue, False if not.
|
||||
"""
|
||||
presubmit_files = ListRelevantPresubmitFiles(change_info.FileList())
|
||||
if not presubmit_files and verbose:
|
||||
print "Warning, no presubmit.py found."
|
||||
results = []
|
||||
executer = PresubmitExecuter(change_info, committing)
|
||||
if default_presubmit:
|
||||
if verbose:
|
||||
print "Running default presubmit script"
|
||||
results += executer.ExecPresubmitScript(default_presubmit, 'PRESUBMIT.py')
|
||||
for filename in presubmit_files:
|
||||
if verbose:
|
||||
print "Running %s" % filename
|
||||
presubmit_script = gcl.ReadFile(filename)
|
||||
results += executer.ExecPresubmitScript(presubmit_script, filename)
|
||||
|
||||
errors = []
|
||||
notifications = []
|
||||
warnings = []
|
||||
for result in results:
|
||||
if not result.IsFatal() and not result.ShouldPrompt():
|
||||
notifications.append(result)
|
||||
elif result.ShouldPrompt():
|
||||
warnings.append(result)
|
||||
else:
|
||||
errors.append(result)
|
||||
|
||||
error_count = 0
|
||||
for name, items in (('Messages', notifications),
|
||||
('Warnings', warnings),
|
||||
('ERRORS', errors)):
|
||||
if items:
|
||||
output_stream.write('\n** Presubmit %s **\n\n' % name)
|
||||
for item in items:
|
||||
if not item._Handle(output_stream, input_stream,
|
||||
may_prompt=False):
|
||||
error_count += 1
|
||||
output_stream.write('\n')
|
||||
if not errors and warnings:
|
||||
output_stream.write(
|
||||
'There were presubmit warnings. Sure you want to continue? (y/N): ')
|
||||
response = input_stream.readline()
|
||||
if response.strip().lower() != 'y':
|
||||
error_count += 1
|
||||
return (error_count == 0)
|
||||
|
||||
|
||||
def ScanSubDirs(mask, recursive):
|
||||
if not recursive:
|
||||
return [x for x in glob.glob(mask) if '.svn' not in x]
|
||||
else:
|
||||
results = []
|
||||
for root, dirs, files in os.walk('.'):
|
||||
if '.svn' in dirs:
|
||||
dirs.remove('.svn')
|
||||
for name in files:
|
||||
if fnmatch.fnmatch(name, mask):
|
||||
results.append(os.path.join(root, name))
|
||||
return results
|
||||
|
||||
|
||||
def ParseFiles(args, recursive):
|
||||
files = []
|
||||
for arg in args:
|
||||
files.extend([('M', file) for file in ScanSubDirs(arg, recursive)])
|
||||
return files
|
||||
|
||||
|
||||
def Main(argv):
|
||||
parser = optparse.OptionParser(usage="%prog [options]",
|
||||
version="%prog " + str(__version__))
|
||||
parser.add_option("-c", "--commit", action="store_true",
|
||||
help="Use commit instead of upload checks")
|
||||
parser.add_option("-r", "--recursive", action="store_true",
|
||||
help="Act recursively")
|
||||
parser.add_option("-v", "--verbose", action="store_true",
|
||||
help="Verbose output")
|
||||
options, args = parser.parse_args(argv[1:])
|
||||
files = ParseFiles(args, options.recursive)
|
||||
if options.verbose:
|
||||
print "Found %d files." % len(files)
|
||||
return not DoPresubmitChecks(gcl.ChangeInfo(name='temp', files=files),
|
||||
options.commit,
|
||||
options.verbose,
|
||||
sys.stdout,
|
||||
sys.stdin,
|
||||
default_presubmit=None)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(Main(sys.argv))
|
||||
102
presubmit_canned_checks.py
Executable file
102
presubmit_canned_checks.py
Executable file
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
"""Generic presubmit checks that can be reused by other presubmit checks."""
|
||||
|
||||
|
||||
def CheckChangeHasTestedField(input_api, output_api):
|
||||
"""Requires that the changelist have a TESTED= field."""
|
||||
if input_api.change.Tested:
|
||||
return []
|
||||
else:
|
||||
return [output_api.PresubmitError("Changelist must have a TESTED= field.")]
|
||||
|
||||
|
||||
def CheckChangeHasQaField(input_api, output_api):
|
||||
"""Requires that the changelist have a QA= field."""
|
||||
if input_api.change.QA:
|
||||
return []
|
||||
else:
|
||||
return [output_api.PresubmitError("Changelist must have a QA= field.")]
|
||||
|
||||
|
||||
def CheckDoNotSubmitInDescription(input_api, output_api):
|
||||
"""Checks that the user didn't add 'DO NOT ' + 'SUBMIT' to the CL description.
|
||||
"""
|
||||
keyword = 'DO NOT ' + 'SUBMIT'
|
||||
if keyword in input_api.change.DescriptionText():
|
||||
return [output_api.PresubmitError(
|
||||
keyword + " is present in the changelist description.")]
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
def CheckDoNotSubmitInFiles(input_api, output_api):
|
||||
"""Checks that the user didn't add 'DO NOT ' + 'SUBMIT' to any files."""
|
||||
keyword = 'DO NOT ' + 'SUBMIT'
|
||||
for f, line_num, line in input_api.RightHandSideLines():
|
||||
if keyword in line:
|
||||
text = 'Found ' + keyword + ' in %s, line %s' % (f.LocalPath(), line_num)
|
||||
return [output_api.PresubmitError(text)]
|
||||
return []
|
||||
|
||||
|
||||
def CheckDoNotSubmit(input_api, output_api):
|
||||
return (
|
||||
CheckDoNotSubmitInDescription(input_api, output_api) +
|
||||
CheckDoNotSubmitInFiles(input_api, output_api)
|
||||
)
|
||||
|
||||
|
||||
def CheckChangeHasNoTabs(input_api, output_api):
|
||||
"""Checks that there are no tab characters in any of the text files to be
|
||||
submitted.
|
||||
"""
|
||||
for f, line_num, line in input_api.RightHandSideLines():
|
||||
if '\t' in line:
|
||||
return [output_api.PresubmitError(
|
||||
"Found a tab character in %s, line %s" %
|
||||
(f.LocalPath(), line_num))]
|
||||
return []
|
||||
|
||||
|
||||
def CheckLongLines(input_api, output_api, maxlen=80):
|
||||
"""Checks that there aren't any lines longer than maxlen characters in any of
|
||||
the text files to be submitted.
|
||||
"""
|
||||
basename = input_api.basename
|
||||
|
||||
bad = []
|
||||
for f, line_num, line in input_api.RightHandSideLines():
|
||||
if line.endswith('\n'):
|
||||
line = line[:-1]
|
||||
if len(line) > maxlen:
|
||||
bad.append(
|
||||
'%s, line %s, %s chars' %
|
||||
(basename(f.LocalPath()), line_num, len(line)))
|
||||
if len(bad) == 5: # Just show the first 5 errors.
|
||||
break
|
||||
|
||||
if bad:
|
||||
msg = "Found lines longer than %s characters (first 5 shown)." % maxlen
|
||||
return [output_api.PresubmitPromptWarning(msg, items=bad)]
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
def CheckTreeIsOpen(input_api, output_api, url, closed):
|
||||
"""Checks that an url's content doesn't match a regexp that would mean that
|
||||
the tree is closed."""
|
||||
try:
|
||||
connection = input_api.urllib2.urlopen(url)
|
||||
status = connection.read()
|
||||
connection.close()
|
||||
if input_api.re.match(closed, status):
|
||||
long_text = status + '\n' + url
|
||||
return [output_api.PresubmitError("The tree is closed.",
|
||||
long_text=long_text)]
|
||||
except IOError:
|
||||
pass
|
||||
return []
|
||||
8
profile.xml
Normal file
8
profile.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
||||
<Profile FormatVersion="1">
|
||||
<Tools>
|
||||
<Tool Filename="python" AllowIntercept="true" />
|
||||
<Tool Filename="cl" AllowRemote="true" VCCompiler="true" />
|
||||
<Tool Filename="link" AllowRemote="false" />
|
||||
</Tools>
|
||||
</Profile>
|
||||
5
revert
Executable file
5
revert
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
base_dir=$(dirname "$0")
|
||||
|
||||
exec python "$base_dir/revert.py" "$@"
|
||||
1
revert.bat
Normal file
1
revert.bat
Normal file
@@ -0,0 +1 @@
|
||||
@python "%~dp0revert.py" %*
|
||||
284
revert.py
Executable file
284
revert.py
Executable file
@@ -0,0 +1,284 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
#
|
||||
# Tool to quickly revert a change.
|
||||
|
||||
import exceptions
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
import xml
|
||||
|
||||
import gcl
|
||||
import gclient
|
||||
|
||||
class ModifiedFile(exceptions.Exception):
|
||||
pass
|
||||
class NoModifiedFile(exceptions.Exception):
|
||||
pass
|
||||
class NoBlameList(exceptions.Exception):
|
||||
pass
|
||||
class OutsideOfCheckout(exceptions.Exception):
|
||||
pass
|
||||
|
||||
|
||||
def getTexts(nodelist):
|
||||
"""Return a list of texts in the children of a list of DOM nodes."""
|
||||
rc = []
|
||||
for node in nodelist:
|
||||
if node.nodeType == node.TEXT_NODE:
|
||||
rc.append(node.data)
|
||||
else:
|
||||
rc.extend(getTexts(node.childNodes))
|
||||
return rc
|
||||
|
||||
|
||||
def RunShellXML(command, print_output=False, keys=None):
|
||||
output = gcl.RunShell(command, print_output)
|
||||
try:
|
||||
dom = xml.dom.minidom.parseString(output)
|
||||
if not keys:
|
||||
return dom
|
||||
result = {}
|
||||
for key in keys:
|
||||
result[key] = getTexts(dom.getElementsByTagName(key))
|
||||
except xml.parsers.expat.ExpatError:
|
||||
print "Failed to parse output:\n%s" % output
|
||||
raise
|
||||
return result
|
||||
|
||||
|
||||
def UniqueFast(list):
|
||||
list = [item for item in set(list)]
|
||||
list.sort()
|
||||
return list
|
||||
|
||||
|
||||
def GetRepoBase():
|
||||
"""Returns the repository base of the root local checkout."""
|
||||
xml_data = RunShellXML(['svn', 'info', '.', '--xml'], keys=['root', 'url'])
|
||||
root = xml_data['root'][0]
|
||||
url = xml_data['url'][0]
|
||||
if not root or not url:
|
||||
raise exceptions.Exception("I'm confused by your checkout")
|
||||
if not url.startswith(root):
|
||||
raise exceptions.Exception("I'm confused by your checkout", url, root)
|
||||
return url[len(root):] + '/'
|
||||
|
||||
|
||||
def Revert(revisions, force=False, commit=True, send_email=True, message=None,
|
||||
reviewers=None):
|
||||
"""Reverts many revisions in one change list.
|
||||
|
||||
If force is True, it will override local modifications.
|
||||
If commit is True, a commit is done after the revert.
|
||||
If send_mail is True, a review email is sent.
|
||||
If message is True, it is used as the change description.
|
||||
reviewers overrides the blames email addresses for review email."""
|
||||
|
||||
# Use the oldest revision as the primary revision.
|
||||
changename = "revert%d" % revisions[len(revisions)-1]
|
||||
if not force and os.path.exists(gcl.GetChangelistInfoFile(changename)):
|
||||
print "Error, change %s already exist." % changename
|
||||
return 1
|
||||
|
||||
# Move to the repository root and make the revision numbers sorted in
|
||||
# decreasing order.
|
||||
os.chdir(gcl.GetRepositoryRoot())
|
||||
revisions.sort(reverse=True)
|
||||
revisions_string = ",".join([str(rev) for rev in revisions])
|
||||
revisions_string_rev = ",".join([str(-rev) for rev in revisions])
|
||||
|
||||
repo_base = GetRepoBase()
|
||||
files = []
|
||||
blames = []
|
||||
# Get all the modified files by the revision. We'll use this list to optimize
|
||||
# the svn merge.
|
||||
for revision in revisions:
|
||||
log = RunShellXML(["svn", "log", "-r", str(revision), "-v", "--xml"],
|
||||
keys=['path', 'author'])
|
||||
for file in log['path']:
|
||||
# Remove the /trunk/src/ part. The + 1 is for the last slash.
|
||||
if not file.startswith(repo_base):
|
||||
raise OutsideOfCheckout(file)
|
||||
files.append(file[len(repo_base):])
|
||||
blames.extend(log['author'])
|
||||
|
||||
# On Windows, we need to fix the slashes once they got the url part removed.
|
||||
if sys.platform == 'win32':
|
||||
# On Windows, gcl expect the correct slashes.
|
||||
files = [file.replace('/', os.sep) for file in files]
|
||||
|
||||
# Keep unique.
|
||||
files = UniqueFast(files)
|
||||
blames = UniqueFast(blames)
|
||||
if not reviewers:
|
||||
reviewers = blames
|
||||
else:
|
||||
reviewers = UniqueFast(reviewers)
|
||||
|
||||
# Make sure there's something to revert.
|
||||
if not files:
|
||||
raise NoModifiedFile
|
||||
if not reviewers:
|
||||
raise NoBlameList
|
||||
|
||||
if blames:
|
||||
print "Blaming %s\n" % ",".join(blames)
|
||||
if reviewers != blames:
|
||||
print "Emailing %s\n" % ",".join(reviewers)
|
||||
print "These files were modified in %s:" % revisions_string
|
||||
print "\n".join(files)
|
||||
print ""
|
||||
|
||||
# Make sure these files are unmodified with svn status.
|
||||
status = gcl.RunShell(["svn", "status"] + files)
|
||||
if status:
|
||||
if force:
|
||||
# TODO(maruel): Use the tool to correctly revert '?' files.
|
||||
gcl.RunShell(["svn", "revert"] + files)
|
||||
else:
|
||||
raise ModifiedFile(status)
|
||||
# svn up on each of these files
|
||||
gcl.RunShell(["svn", "up"] + files)
|
||||
|
||||
files_status = {}
|
||||
# Extract the first level subpaths. Subversion seems to degrade
|
||||
# exponentially w.r.t. repository size during merges. Working at the root
|
||||
# directory is too rough for svn due to the repository size.
|
||||
roots = UniqueFast([file.split(os.sep)[0] for file in files])
|
||||
for root in roots:
|
||||
# Is it a subdirectory or a files?
|
||||
is_root_subdir = os.path.isdir(root)
|
||||
need_to_update = False
|
||||
if is_root_subdir:
|
||||
os.chdir(root)
|
||||
file_list = []
|
||||
# List the file directly since it is faster when there is only one file.
|
||||
for file in files:
|
||||
if file.startswith(root):
|
||||
file_list.append(file[len(root)+1:])
|
||||
if len(file_list) > 1:
|
||||
# Listing multiple files is not supported by svn merge.
|
||||
file_list = ['.']
|
||||
need_to_update = True
|
||||
else:
|
||||
# Oops, root was in fact a file in the root directory.
|
||||
file_list = [root]
|
||||
root = "."
|
||||
|
||||
print "Reverting %s in %s/" % (revisions_string, root)
|
||||
if need_to_update:
|
||||
# Make sure '.' revision is high enough otherwise merge will be
|
||||
# unhappy.
|
||||
retcode = gcl.RunShellWithReturnCode(['svn', 'up', '.', '-N'])[1]
|
||||
if retcode:
|
||||
print 'svn up . -N failed in %s/.' % root
|
||||
return retcode
|
||||
|
||||
command = ["svn", "merge", "-c", revisions_string_rev]
|
||||
command.extend(file_list)
|
||||
(output, retcode) = gcl.RunShellWithReturnCode(command, print_output=True)
|
||||
if retcode:
|
||||
print "'%s' failed:" % command
|
||||
return retcode
|
||||
|
||||
# Grab the status
|
||||
lines = output.split('\n')
|
||||
for line in lines:
|
||||
if line.startswith('---'):
|
||||
continue
|
||||
if line.startswith('Skipped'):
|
||||
print ""
|
||||
raise ModifiedFile(line[9:-1])
|
||||
# Update the status.
|
||||
status = line[:5] + ' '
|
||||
file = line[5:]
|
||||
if is_root_subdir:
|
||||
files_status[root + os.sep + file] = status
|
||||
else:
|
||||
files_status[file] = status
|
||||
|
||||
if is_root_subdir:
|
||||
os.chdir('..')
|
||||
|
||||
# Transform files_status from a dictionary to a list of tuple.
|
||||
files_status = [(files_status[file], file) for file in files]
|
||||
|
||||
description = "Reverting %s." % revisions_string
|
||||
if message:
|
||||
description += "\n\n"
|
||||
description += message
|
||||
# Don't use gcl.Change() since it prompts the user for infos.
|
||||
change_info = gcl.ChangeInfo(name=changename, issue='',
|
||||
description=description, files=files_status)
|
||||
change_info.Save()
|
||||
|
||||
upload_args = ['-r', ",".join(reviewers)]
|
||||
if send_email:
|
||||
upload_args.append('--send_mail')
|
||||
if commit:
|
||||
upload_args.append('--no_try')
|
||||
gcl.UploadCL(change_info, upload_args)
|
||||
|
||||
retcode = 0
|
||||
if commit:
|
||||
gcl.Commit(change_info, ['--force'])
|
||||
# TODO(maruel): gclient sync (to leave the local checkout in an usable
|
||||
# state)
|
||||
retcode = gclient.Main(["gclient.py", "sync"])
|
||||
return retcode
|
||||
|
||||
|
||||
def Main(argv):
|
||||
usage = (
|
||||
"""%prog [options] [revision numbers to revert]
|
||||
Revert a set of revisions, send the review to Rietveld, sends a review email
|
||||
and optionally commit the revert.""")
|
||||
|
||||
parser = optparse.OptionParser(usage=usage)
|
||||
parser.add_option("-c", "--commit", default=False, action="store_true",
|
||||
help="Commits right away.")
|
||||
parser.add_option("-f", "--force", default=False, action="store_true",
|
||||
help="Forces the local modification even if a file is "
|
||||
"already modified locally.")
|
||||
parser.add_option("-n", "--no_email", default=False, action="store_true",
|
||||
help="Inhibits from sending a review email.")
|
||||
parser.add_option("-m", "--message", default=None,
|
||||
help="Additional change description message.")
|
||||
parser.add_option("-r", "--reviewers", action="append",
|
||||
help="Reviewers to send the email to. By default, the list "
|
||||
"of commiters is used.")
|
||||
options, args = parser.parse_args(argv)
|
||||
revisions = []
|
||||
try:
|
||||
for item in args[1:]:
|
||||
revisions.append(int(item))
|
||||
except ValueError:
|
||||
parser.error("You need to pass revision numbers.")
|
||||
if not revisions:
|
||||
parser.error("You need to pass revision numbers.")
|
||||
retcode = 1
|
||||
try:
|
||||
if not os.path.exists(gcl.GetInfoDir()):
|
||||
os.mkdir(gcl.GetInfoDir())
|
||||
retcode = Revert(revisions, options.force, options.commit,
|
||||
not options.no_email, options.message, options.reviewers)
|
||||
except NoBlameList:
|
||||
print "Error: no one to blame."
|
||||
except NoModifiedFile:
|
||||
print "Error: no files to revert."
|
||||
except ModifiedFile, e:
|
||||
print "You need to revert these files since they were already modified:"
|
||||
print "".join(e.args)
|
||||
print "You can use the --force flag to revert the files."
|
||||
except OutsideOfCheckout, e:
|
||||
print "Your repository doesn't contain ", str(e)
|
||||
|
||||
return retcode
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(Main(sys.argv))
|
||||
41
tests/abandon.sh
Executable file
41
tests/abandon.sh
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Check that abandoning a branch also abandons its issue.
|
||||
|
||||
set -e
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
setup_initsvn
|
||||
setup_gitsvn
|
||||
|
||||
(
|
||||
set -e
|
||||
cd git-svn
|
||||
git config rietveld.server localhost:8080
|
||||
|
||||
# Create a branch and give it an issue.
|
||||
git checkout -q -b abandoned
|
||||
echo "some work done on a branch" >> test
|
||||
git add test; git commit -q -m "branch work"
|
||||
export EDITOR=/bin/true
|
||||
test_expect_success "upload succeeds" \
|
||||
"$GIT_CL upload -m test master... | grep -q 'Issue created'"
|
||||
|
||||
# Switch back to master, delete the branch.
|
||||
git checkout master
|
||||
git branch -D abandoned
|
||||
|
||||
# Verify that "status" doesn't know about it anymore.
|
||||
# The "exit" trickiness is inverting the exit status of grep.
|
||||
test_expect_success "git-cl status dropped abandoned branch" \
|
||||
"$GIT_CL status | grep -q abandoned && exit 1 || exit 0"
|
||||
)
|
||||
|
||||
SUCCESS=$?
|
||||
|
||||
cleanup
|
||||
|
||||
if [ $SUCCESS == 0 ]; then
|
||||
echo PASS
|
||||
fi
|
||||
62
tests/basic.sh
Executable file
62
tests/basic.sh
Executable file
@@ -0,0 +1,62 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
setup_initsvn
|
||||
setup_gitsvn
|
||||
|
||||
(
|
||||
set -e
|
||||
cd git-svn
|
||||
git checkout -q -b work
|
||||
echo "some work done on a branch" >> test
|
||||
git add test; git commit -q -m "branch work"
|
||||
echo "some other work done on a branch" >> test
|
||||
git add test; git commit -q -m "branch work"
|
||||
|
||||
test_expect_success "git-cl upload wants a server" \
|
||||
"$GIT_CL upload 2>&1 | grep -q 'You must configure'"
|
||||
|
||||
git config rietveld.server localhost:8080
|
||||
|
||||
test_expect_success "git-cl status has no issue" \
|
||||
"$GIT_CL status | grep -q 'no issue'"
|
||||
|
||||
# Prevent the editor from coming up when you upload.
|
||||
export EDITOR=/bin/true
|
||||
test_expect_success "upload succeeds (needs a server running on localhost)" \
|
||||
"$GIT_CL upload -m test master... | grep -q 'Issue created'"
|
||||
|
||||
test_expect_success "git-cl status now knows the issue" \
|
||||
"$GIT_CL status | grep -q 'Issue number'"
|
||||
|
||||
# Push a description to this URL.
|
||||
URL=$($GIT_CL status | sed -ne '/Issue number/s/[^(]*(\(.*\))/\1/p')
|
||||
curl --cookie dev_appserver_login="test@example.com:False" \
|
||||
--data-urlencode subject="test" \
|
||||
--data-urlencode description="foo-quux" \
|
||||
$URL/edit
|
||||
|
||||
test_expect_success "git-cl dcommits ok" \
|
||||
"$GIT_CL dcommit -f"
|
||||
|
||||
git checkout -q master
|
||||
git svn -q rebase >/dev/null 2>&1
|
||||
test_expect_success "dcommitted code has proper description" \
|
||||
"git show | grep -q 'foo-quux'"
|
||||
|
||||
test_expect_success "issue no longer has a branch" \
|
||||
"git cl status | grep -q 'work: None'"
|
||||
|
||||
test_expect_success "upstream svn has our commit" \
|
||||
"svn log $REPO_URL 2>/dev/null | grep -q 'foo-quux'"
|
||||
)
|
||||
SUCCESS=$?
|
||||
|
||||
cleanup
|
||||
|
||||
if [ $SUCCESS == 0 ]; then
|
||||
echo PASS
|
||||
fi
|
||||
699
tests/presubmit_unittest.py
Executable file
699
tests/presubmit_unittest.py
Executable file
@@ -0,0 +1,699 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
"""Unit tests for presubmit.py and presubmit_canned_checks.py."""
|
||||
|
||||
import os
|
||||
import StringIO
|
||||
import unittest
|
||||
|
||||
# Local imports
|
||||
import gcl
|
||||
import presubmit
|
||||
import presubmit_canned_checks
|
||||
|
||||
|
||||
class PresubmitTestsBase(unittest.TestCase):
|
||||
"""Setups and tear downs the mocks but doesn't test anything as-is."""
|
||||
def setUp(self):
|
||||
self.original_IsFile = os.path.isfile
|
||||
def MockIsFile(f):
|
||||
dir = os.path.dirname(f)
|
||||
return dir.endswith('haspresubmit') or dir == ''
|
||||
os.path.isfile = MockIsFile
|
||||
|
||||
self.original_GetSVNFileInfo = gcl.GetSVNFileInfo
|
||||
def MockGetSVNFileInfo(path):
|
||||
if path.count('notfound'):
|
||||
return {}
|
||||
results = {
|
||||
'Path': path[len('svn:/foo/'):],
|
||||
'URL': 'svn:/foo/%s' % path.replace('\\', '/'),
|
||||
}
|
||||
if path.endswith('isdir'):
|
||||
results['Node Kind'] = 'directory'
|
||||
else:
|
||||
results['Node Kind'] = 'file'
|
||||
return results
|
||||
gcl.GetSVNFileInfo = MockGetSVNFileInfo
|
||||
|
||||
self.original_GetSVNFileProperty = gcl.GetSVNFileProperty
|
||||
def MockGetSVNFileProperty(path, property_name):
|
||||
if property_name == 'svn:secret-property':
|
||||
return 'secret-property-value'
|
||||
elif path.count('binary'):
|
||||
return 'application/octet-stream'
|
||||
else:
|
||||
if len(path) % 2:
|
||||
return 'text/plain'
|
||||
else:
|
||||
return ''
|
||||
gcl.GetSVNFileProperty = MockGetSVNFileProperty
|
||||
|
||||
self.original_ReadFile = gcl.ReadFile
|
||||
def MockReadFile(path):
|
||||
if path.count('nosuchfile'):
|
||||
return None
|
||||
elif path.endswith('isdir'):
|
||||
self.fail('Should not attempt to read file that is directory.')
|
||||
elif path.endswith('PRESUBMIT.py'):
|
||||
# used in testDoPresubmitChecks
|
||||
return """
|
||||
def CheckChangeOnUpload(input_api, output_api):
|
||||
if not input_api.change.NOSUCHKEY:
|
||||
return [output_api.PresubmitError("!!")]
|
||||
elif not input_api.change.REALLYNOSUCHKEY:
|
||||
return [output_api.PresubmitPromptWarning("??")]
|
||||
elif not input_api.change.REALLYABSOLUTELYNOSUCHKEY:
|
||||
return [output_api.PresubmitPromptWarning("??"),
|
||||
output_api.PresubmitError("XX!!XX")]
|
||||
else:
|
||||
return ()
|
||||
"""
|
||||
else:
|
||||
return 'one:%s\r\ntwo:%s' % (path, path)
|
||||
gcl.ReadFile = MockReadFile
|
||||
|
||||
self.original_GetRepositoryRoot = gcl.GetRepositoryRoot
|
||||
def MockGetRepositoryRoot():
|
||||
return ''
|
||||
gcl.GetRepositoryRoot = MockGetRepositoryRoot
|
||||
|
||||
def tearDown(self):
|
||||
os.path.isfile = self.original_IsFile
|
||||
gcl.GetSVNFileInfo = self.original_GetSVNFileInfo
|
||||
gcl.GetSVNFileProperty = self.original_GetSVNFileProperty
|
||||
gcl.ReadFile = self.original_ReadFile
|
||||
gcl.GetRepositoryRoot = self.original_GetRepositoryRoot
|
||||
|
||||
@staticmethod
|
||||
def MakeBasicChange(name, description):
|
||||
ci = gcl.ChangeInfo(name=name,
|
||||
description=description,
|
||||
files=[])
|
||||
change = presubmit.GclChange(ci)
|
||||
return change
|
||||
|
||||
def compareMembers(self, object, members):
|
||||
"""If you add a member, be sure to add the relevant test!"""
|
||||
# Skip over members starting with '_' since they are usually not meant to
|
||||
# be for public use.
|
||||
actual_members = [x for x in sorted(dir(object))
|
||||
if not x.startswith('_')]
|
||||
self.assertEqual(actual_members, sorted(members))
|
||||
|
||||
|
||||
class PresubmitUnittest(PresubmitTestsBase):
|
||||
"""General presubmit.py tests (excluding InputApi and OutputApi)."""
|
||||
def testMembersChanged(self):
|
||||
members = [
|
||||
'AffectedFile', 'DoPresubmitChecks', 'GclChange', 'InputApi',
|
||||
'ListRelevantPresubmitFiles', 'Main', 'NotImplementedException',
|
||||
'OutputApi', 'ParseFiles', 'PresubmitExecuter', 'SPECIAL_KEYS',
|
||||
'ScanSubDirs', 'cPickle', 'cStringIO', 'exceptions',
|
||||
'fnmatch', 'gcl', 'glob', 'marshal', 'normpath', 'optparse', 'os',
|
||||
'pickle', 'presubmit_canned_checks', 're', 'subprocess', 'sys',
|
||||
'tempfile', 'types', 'urllib2',
|
||||
]
|
||||
# If this test fails, you should add the relevant test.
|
||||
self.compareMembers(presubmit, members)
|
||||
|
||||
def testListRelevantPresubmitFiles(self):
|
||||
presubmit_files = presubmit.ListRelevantPresubmitFiles([
|
||||
'blat.cc',
|
||||
'foo/haspresubmit/yodle/smart.h',
|
||||
'moo/mat/gat/yo.h',
|
||||
'foo/luck.h'])
|
||||
self.failUnless(len(presubmit_files) == 2)
|
||||
self.failUnless(presubmit.normpath('PRESUBMIT.py') in presubmit_files)
|
||||
self.failUnless(presubmit.normpath('foo/haspresubmit/PRESUBMIT.py') in
|
||||
presubmit_files)
|
||||
|
||||
def testTagLineRe(self):
|
||||
m = presubmit._tag_line_re.match(' BUG =1223, 1445 \t')
|
||||
self.failUnless(m)
|
||||
self.failUnlessEqual(m.group('key'), 'BUG')
|
||||
self.failUnlessEqual(m.group('value'), '1223, 1445')
|
||||
|
||||
def testGclChange(self):
|
||||
description_lines = ('Hello there',
|
||||
'this is a change',
|
||||
'BUG=123',
|
||||
' STORY =http://foo/ \t',
|
||||
'and some more regular text \t')
|
||||
files = [
|
||||
['A', 'foo/blat.cc'],
|
||||
['M', 'binary.dll'], # a binary file
|
||||
['A', 'isdir'], # a directory
|
||||
['M', 'flop/notfound.txt'], # not found in SVN, still exists locally
|
||||
['D', 'boo/flap.h'],
|
||||
]
|
||||
|
||||
ci = gcl.ChangeInfo(name='mychange',
|
||||
description='\n'.join(description_lines),
|
||||
files=files)
|
||||
change = presubmit.GclChange(ci)
|
||||
|
||||
self.failUnless(change.Change() == 'mychange')
|
||||
self.failUnless(change.Changelist() == 'mychange')
|
||||
self.failUnless(change.DescriptionText() ==
|
||||
'Hello there\nthis is a change\nand some more regular text')
|
||||
self.failUnless(change.FullDescriptionText() ==
|
||||
'\n'.join(description_lines))
|
||||
|
||||
self.failUnless(change.BugIDs == '123')
|
||||
self.failUnless(change.BUG == '123')
|
||||
self.failUnless(change.STORY == 'http://foo/')
|
||||
|
||||
self.failUnless(len(change.AffectedFiles()) == 4)
|
||||
self.failUnless(len(change.AffectedFiles(include_dirs=True)) == 5)
|
||||
self.failUnless(len(change.AffectedFiles(include_deletes=False)) == 3)
|
||||
self.failUnless(len(change.AffectedFiles(include_dirs=True,
|
||||
include_deletes=False)) == 4)
|
||||
|
||||
affected_text_files = change.AffectedTextFiles(include_deletes=True)
|
||||
self.failUnless(len(affected_text_files) == 3)
|
||||
self.failIf(filter(lambda x: x.LocalPath() == 'binary.dll',
|
||||
affected_text_files))
|
||||
|
||||
local_paths = change.LocalPaths()
|
||||
expected_paths = [presubmit.normpath(f[1]) for f in files]
|
||||
self.failUnless(
|
||||
len(filter(lambda x: x in expected_paths, local_paths)) == 4)
|
||||
|
||||
server_paths = change.ServerPaths()
|
||||
expected_paths = ['svn:/foo/%s' % f[1] for f in files if
|
||||
f[1] != 'flop/notfound.txt']
|
||||
expected_paths.append('') # one unknown file
|
||||
self.failUnless(
|
||||
len(filter(lambda x: x in expected_paths, server_paths)) == 4)
|
||||
|
||||
files = [[x[0], presubmit.normpath(x[1])] for x in files]
|
||||
|
||||
rhs_lines = []
|
||||
for line in change.RightHandSideLines():
|
||||
rhs_lines.append(line)
|
||||
self.failUnless(rhs_lines[0][0].LocalPath() == files[0][1])
|
||||
self.failUnless(rhs_lines[0][1] == 1)
|
||||
self.failUnless(rhs_lines[0][2] == 'one:%s' % files[0][1])
|
||||
|
||||
self.failUnless(rhs_lines[1][0].LocalPath() == files[0][1])
|
||||
self.failUnless(rhs_lines[1][1] == 2)
|
||||
self.failUnless(rhs_lines[1][2] == 'two:%s' % files[0][1])
|
||||
|
||||
self.failUnless(rhs_lines[2][0].LocalPath() == files[3][1])
|
||||
self.failUnless(rhs_lines[2][1] == 1)
|
||||
self.failUnless(rhs_lines[2][2] == 'one:%s' % files[3][1])
|
||||
|
||||
self.failUnless(rhs_lines[3][0].LocalPath() == files[3][1])
|
||||
self.failUnless(rhs_lines[3][1] == 2)
|
||||
self.failUnless(rhs_lines[3][2] == 'two:%s' % files[3][1])
|
||||
|
||||
def testAffectedFile(self):
|
||||
af = presubmit.AffectedFile('foo/blat.cc', 'M')
|
||||
self.failUnless(af.ServerPath() == 'svn:/foo/foo/blat.cc')
|
||||
self.failUnless(af.LocalPath() == presubmit.normpath('foo/blat.cc'))
|
||||
self.failUnless(af.Action() == 'M')
|
||||
self.failUnless(af.NewContents() == ['one:%s' % af.LocalPath(),
|
||||
'two:%s' % af.LocalPath()])
|
||||
|
||||
af = presubmit.AffectedFile('notfound.cc', 'A')
|
||||
self.failUnless(af.ServerPath() == '')
|
||||
|
||||
def testExecPresubmitScript(self):
|
||||
description_lines = ('Hello there',
|
||||
'this is a change',
|
||||
'STORY=http://tracker/123')
|
||||
files = [
|
||||
['A', 'foo\\blat.cc'],
|
||||
]
|
||||
ci = gcl.ChangeInfo(name='mychange',
|
||||
description='\n'.join(description_lines),
|
||||
files=files)
|
||||
|
||||
executer = presubmit.PresubmitExecuter(ci, False)
|
||||
self.failIf(executer.ExecPresubmitScript('', 'PRESUBMIT.py'))
|
||||
# No error if no on-upload entry point
|
||||
self.failIf(executer.ExecPresubmitScript(
|
||||
('def CheckChangeOnCommit(input_api, output_api):\n'
|
||||
' return (output_api.PresubmitError("!!"))\n'),
|
||||
'PRESUBMIT.py'
|
||||
))
|
||||
|
||||
executer = presubmit.PresubmitExecuter(ci, True)
|
||||
# No error if no on-commit entry point
|
||||
self.failIf(executer.ExecPresubmitScript(
|
||||
('def CheckChangeOnUpload(input_api, output_api):\n'
|
||||
' return (output_api.PresubmitError("!!"))\n'),
|
||||
'PRESUBMIT.py'
|
||||
))
|
||||
|
||||
self.failIf(executer.ExecPresubmitScript(
|
||||
('def CheckChangeOnUpload(input_api, output_api):\n'
|
||||
' if not input_api.change.STORY:\n'
|
||||
' return (output_api.PresubmitError("!!"))\n'
|
||||
' else:\n'
|
||||
' return ()'),
|
||||
'PRESUBMIT.py'
|
||||
))
|
||||
|
||||
self.failUnless(executer.ExecPresubmitScript(
|
||||
('def CheckChangeOnCommit(input_api, output_api):\n'
|
||||
' if not input_api.change.NOSUCHKEY:\n'
|
||||
' return [output_api.PresubmitError("!!")]\n'
|
||||
' else:\n'
|
||||
' return ()'),
|
||||
'PRESUBMIT.py'
|
||||
))
|
||||
|
||||
try:
|
||||
executer.ExecPresubmitScript(
|
||||
('def CheckChangeOnCommit(input_api, output_api):\n'
|
||||
' return "foo"'),
|
||||
'PRESUBMIT.py')
|
||||
self.fail()
|
||||
except:
|
||||
pass # expected case
|
||||
|
||||
try:
|
||||
executer.ExecPresubmitScript(
|
||||
('def CheckChangeOnCommit(input_api, output_api):\n'
|
||||
' return ["foo"]'),
|
||||
'PRESUBMIT.py')
|
||||
self.fail()
|
||||
except:
|
||||
pass # expected case
|
||||
|
||||
def testDoPresubmitChecks(self):
|
||||
description_lines = ('Hello there',
|
||||
'this is a change',
|
||||
'STORY=http://tracker/123')
|
||||
files = [
|
||||
['A', 'haspresubmit\\blat.cc'],
|
||||
]
|
||||
ci = gcl.ChangeInfo(name='mychange',
|
||||
description='\n'.join(description_lines),
|
||||
files=files)
|
||||
|
||||
output = StringIO.StringIO()
|
||||
input = StringIO.StringIO('y\n')
|
||||
|
||||
self.failIf(presubmit.DoPresubmitChecks(ci, False, False, output, input,
|
||||
None))
|
||||
self.assertEqual(output.getvalue().count('!!'), 2)
|
||||
|
||||
def testDoPresubmitChecksPromptsAfterWarnings(self):
|
||||
description_lines = ('Hello there',
|
||||
'this is a change',
|
||||
'NOSUCHKEY=http://tracker/123')
|
||||
files = [
|
||||
['A', 'haspresubmit\\blat.cc'],
|
||||
]
|
||||
ci = gcl.ChangeInfo(name='mychange',
|
||||
description='\n'.join(description_lines),
|
||||
files=files)
|
||||
|
||||
output = StringIO.StringIO()
|
||||
input = StringIO.StringIO('n\n') # say no to the warning
|
||||
|
||||
self.failIf(presubmit.DoPresubmitChecks(ci, False, False, output, input,
|
||||
None))
|
||||
self.assertEqual(output.getvalue().count('??'), 2)
|
||||
|
||||
output = StringIO.StringIO()
|
||||
input = StringIO.StringIO('y\n') # say yes to the warning
|
||||
|
||||
self.failUnless(presubmit.DoPresubmitChecks(ci,
|
||||
False,
|
||||
False,
|
||||
output,
|
||||
input,
|
||||
None))
|
||||
self.failUnless(output.getvalue().count('??'))
|
||||
|
||||
def testDoPresubmitChecksNoWarningPromptIfErrors(self):
|
||||
description_lines = ('Hello there',
|
||||
'this is a change',
|
||||
'NOSUCHKEY=http://tracker/123',
|
||||
'REALLYNOSUCHKEY=http://tracker/123')
|
||||
files = [
|
||||
['A', 'haspresubmit\\blat.cc'],
|
||||
]
|
||||
ci = gcl.ChangeInfo(name='mychange',
|
||||
description='\n'.join(description_lines),
|
||||
files=files)
|
||||
|
||||
output = StringIO.StringIO()
|
||||
input = StringIO.StringIO() # should be unused
|
||||
|
||||
self.failIf(presubmit.DoPresubmitChecks(ci, False, False, output, input,
|
||||
None))
|
||||
self.assertEqual(output.getvalue().count('??'), 2)
|
||||
self.assertEqual(output.getvalue().count('XX!!XX'), 2)
|
||||
self.assertEqual(output.getvalue().count('(y/N)'), 0)
|
||||
|
||||
def testDoDefaultPresubmitChecks(self):
|
||||
description_lines = ('Hello there',
|
||||
'this is a change',
|
||||
'STORY=http://tracker/123')
|
||||
files = [
|
||||
['A', 'haspresubmit\\blat.cc'],
|
||||
]
|
||||
ci = gcl.ChangeInfo(name='mychange',
|
||||
description='\n'.join(description_lines),
|
||||
files=files)
|
||||
|
||||
output = StringIO.StringIO()
|
||||
input = StringIO.StringIO('y\n')
|
||||
DEFAULT_SCRIPT = """
|
||||
def CheckChangeOnUpload(input_api, output_api):
|
||||
return [output_api.PresubmitError("!!")]
|
||||
"""
|
||||
def MockReadFile(dummy):
|
||||
return ''
|
||||
gcl.ReadFile = MockReadFile
|
||||
def MockIsFile(dummy):
|
||||
return False
|
||||
os.path.isfile = MockIsFile
|
||||
self.failUnless(presubmit.DoPresubmitChecks(ci, False, False, output, input,
|
||||
DEFAULT_SCRIPT))
|
||||
self.failIf(output.getvalue().count('!!') == 1)
|
||||
|
||||
def testDirectoryHandling(self):
|
||||
files = [
|
||||
['A', 'isdir'],
|
||||
['A', 'isdir\\blat.cc'],
|
||||
]
|
||||
ci = gcl.ChangeInfo(name='mychange',
|
||||
description='foo',
|
||||
files=files)
|
||||
change = presubmit.GclChange(ci)
|
||||
|
||||
affected_files = change.AffectedFiles(include_dirs=False)
|
||||
self.failUnless(len(affected_files) == 1)
|
||||
self.failUnless(affected_files[0].LocalPath().endswith('blat.cc'))
|
||||
|
||||
affected_files_and_dirs = change.AffectedFiles(include_dirs=True)
|
||||
self.failUnless(len(affected_files_and_dirs) == 2)
|
||||
|
||||
def testSvnProperty(self):
|
||||
affected_file = presubmit.AffectedFile('foo.cc', 'A')
|
||||
self.failUnless(affected_file.SvnProperty('svn:secret-property') ==
|
||||
'secret-property-value')
|
||||
|
||||
|
||||
class InputApiUnittest(PresubmitTestsBase):
|
||||
"""Tests presubmit.InputApi."""
|
||||
def testMembersChanged(self):
|
||||
members = [
|
||||
'AbsoluteLocalPaths', 'AffectedFiles', 'AffectedTextFiles',
|
||||
'DepotToLocalPath', 'FilterTextFiles', 'LocalPaths', 'LocalToDepotPath',
|
||||
'PresubmitLocalPath', 'RightHandSideLines', 'ServerPaths',
|
||||
'basename', 'cPickle', 'cStringIO', 'canned_checks', 'change',
|
||||
'current_presubmit_path', 'marshal', 'os_path', 'pickle', 'platform',
|
||||
're', 'subprocess', 'tempfile', 'urllib2',
|
||||
]
|
||||
# If this test fails, you should add the relevant test.
|
||||
self.compareMembers(presubmit.InputApi(None, None), members)
|
||||
|
||||
def testDepotToLocalPath(self):
|
||||
path = presubmit.InputApi.DepotToLocalPath('svn:/foo/smurf')
|
||||
self.failUnless(path == 'smurf')
|
||||
path = presubmit.InputApi.DepotToLocalPath('svn:/foo/notfound/burp')
|
||||
self.failUnless(path == None)
|
||||
|
||||
def testLocalToDepotPath(self):
|
||||
path = presubmit.InputApi.LocalToDepotPath('smurf')
|
||||
self.failUnless(path == 'svn:/foo/smurf')
|
||||
path = presubmit.InputApi.LocalToDepotPath('notfound-food')
|
||||
self.failUnless(path == None)
|
||||
|
||||
def testInputApiConstruction(self):
|
||||
# Fudge the change object, it's not used during construction anyway
|
||||
api = presubmit.InputApi(change=42, presubmit_path='foo/path')
|
||||
self.failUnless(api.PresubmitLocalPath() == 'foo/path')
|
||||
self.failUnless(api.change == 42)
|
||||
|
||||
def testFilterTextFiles(self):
|
||||
class MockAffectedFile(object):
|
||||
def __init__(self, path, action):
|
||||
self.path = path
|
||||
self.action = action
|
||||
def Action(self):
|
||||
return self.action
|
||||
def LocalPath(self):
|
||||
return self.path
|
||||
def AbsoluteLocalPath(self):
|
||||
return self.path
|
||||
|
||||
list = [MockAffectedFile('foo/blat.txt', 'M'),
|
||||
MockAffectedFile('foo/binary.blob', 'M'),
|
||||
MockAffectedFile('blat/flop.txt', 'D')]
|
||||
|
||||
output = presubmit.InputApi.FilterTextFiles(list, include_deletes=True)
|
||||
self.failUnless(len(output) == 2)
|
||||
self.failUnless(list[0] in output and list[2] in output)
|
||||
|
||||
output = presubmit.InputApi.FilterTextFiles(list, include_deletes=False)
|
||||
self.failUnless(len(output) == 1)
|
||||
self.failUnless(list[0] in output)
|
||||
|
||||
def testInputApiPresubmitScriptFiltering(self):
|
||||
description_lines = ('Hello there',
|
||||
'this is a change',
|
||||
'BUG=123',
|
||||
' STORY =http://foo/ \t',
|
||||
'and some more regular text')
|
||||
files = [
|
||||
['A', os.path.join('foo', 'blat.cc')],
|
||||
['M', os.path.join('foo', 'blat', 'binary.dll')],
|
||||
['D', 'foo/mat/beingdeleted.txt'],
|
||||
['M', 'flop/notfound.txt'],
|
||||
['A', 'boo/flap.h'],
|
||||
]
|
||||
|
||||
ci = gcl.ChangeInfo(name='mychange',
|
||||
description='\n'.join(description_lines),
|
||||
files=files)
|
||||
change = presubmit.GclChange(ci)
|
||||
|
||||
api = presubmit.InputApi(change, 'foo/PRESUBMIT.py')
|
||||
|
||||
affected_files = api.AffectedFiles()
|
||||
self.failUnless(len(affected_files) == 3)
|
||||
self.failUnless(affected_files[0].LocalPath() ==
|
||||
presubmit.normpath('foo/blat.cc'))
|
||||
self.failUnless(affected_files[1].LocalPath() ==
|
||||
presubmit.normpath('foo/blat/binary.dll'))
|
||||
self.failUnless(affected_files[2].LocalPath() ==
|
||||
presubmit.normpath('foo/mat/beingdeleted.txt'))
|
||||
|
||||
rhs_lines = []
|
||||
for line in api.RightHandSideLines():
|
||||
rhs_lines.append(line)
|
||||
self.failUnless(len(rhs_lines) == 2)
|
||||
self.failUnless(rhs_lines[0][0].LocalPath() ==
|
||||
presubmit.normpath('foo/blat.cc'))
|
||||
|
||||
def testGetAbsoluteLocalPath(self):
|
||||
# Regression test for bug of presubmit stuff that relies on invoking
|
||||
# SVN (e.g. to get mime type of file) not working unless gcl invoked
|
||||
# from the client root (e.g. if you were at 'src' and did 'cd base' before
|
||||
# invoking 'gcl upload' it would fail because svn wouldn't find the files
|
||||
# the presubmit script was asking about).
|
||||
files = [
|
||||
['A', 'isdir'],
|
||||
['A', os.path.join('isdir', 'blat.cc')]
|
||||
]
|
||||
ci = gcl.ChangeInfo(name='mychange',
|
||||
description='',
|
||||
files=files)
|
||||
# It doesn't make sense on non-Windows platform. This is somewhat hacky,
|
||||
# but it is needed since we can't just use os.path.join('c:', 'temp').
|
||||
change = presubmit.GclChange(ci, 'c:' + os.sep + 'temp')
|
||||
affected_files = change.AffectedFiles(include_dirs=True)
|
||||
# Local paths should remain the same
|
||||
self.failUnless(affected_files[0].LocalPath() ==
|
||||
presubmit.normpath('isdir'))
|
||||
self.failUnless(affected_files[1].LocalPath() ==
|
||||
presubmit.normpath('isdir/blat.cc'))
|
||||
# Absolute paths should be prefixed
|
||||
self.failUnless(affected_files[0].AbsoluteLocalPath() ==
|
||||
presubmit.normpath('c:/temp/isdir'))
|
||||
self.failUnless(affected_files[1].AbsoluteLocalPath() ==
|
||||
presubmit.normpath('c:/temp/isdir/blat.cc'))
|
||||
|
||||
# New helper functions need to work
|
||||
absolute_paths_from_change = change.AbsoluteLocalPaths(include_dirs=True)
|
||||
api = presubmit.InputApi(change=change, presubmit_path='isdir/PRESUBMIT.py')
|
||||
absolute_paths_from_api = api.AbsoluteLocalPaths(include_dirs=True)
|
||||
for absolute_paths in [absolute_paths_from_change,
|
||||
absolute_paths_from_api]:
|
||||
self.failUnless(absolute_paths[0] == presubmit.normpath('c:/temp/isdir'))
|
||||
self.failUnless(absolute_paths[1] ==
|
||||
presubmit.normpath('c:/temp/isdir/blat.cc'))
|
||||
|
||||
|
||||
class OuputApiUnittest(PresubmitTestsBase):
|
||||
"""Tests presubmit.OutputApi."""
|
||||
def testMembersChanged(self):
|
||||
members = [
|
||||
'MailTextResult', 'PresubmitError', 'PresubmitNotifyResult',
|
||||
'PresubmitPromptWarning', 'PresubmitResult',
|
||||
]
|
||||
# If this test fails, you should add the relevant test.
|
||||
self.compareMembers(presubmit.OutputApi(), members)
|
||||
|
||||
def testOutputApiBasics(self):
|
||||
self.failUnless(presubmit.OutputApi.PresubmitError('').IsFatal())
|
||||
self.failIf(presubmit.OutputApi.PresubmitError('').ShouldPrompt())
|
||||
|
||||
self.failIf(presubmit.OutputApi.PresubmitPromptWarning('').IsFatal())
|
||||
self.failUnless(
|
||||
presubmit.OutputApi.PresubmitPromptWarning('').ShouldPrompt())
|
||||
|
||||
self.failIf(presubmit.OutputApi.PresubmitNotifyResult('').IsFatal())
|
||||
self.failIf(presubmit.OutputApi.PresubmitNotifyResult('').ShouldPrompt())
|
||||
|
||||
# TODO(joi) Test MailTextResult once implemented.
|
||||
|
||||
def testOutputApiHandling(self):
|
||||
output = StringIO.StringIO()
|
||||
unused_input = StringIO.StringIO()
|
||||
error = presubmit.OutputApi.PresubmitError('!!!')
|
||||
self.failIf(error._Handle(output, unused_input))
|
||||
self.failUnless(output.getvalue().count('!!!'))
|
||||
|
||||
output = StringIO.StringIO()
|
||||
notify = presubmit.OutputApi.PresubmitNotifyResult('?see?')
|
||||
self.failUnless(notify._Handle(output, unused_input))
|
||||
self.failUnless(output.getvalue().count('?see?'))
|
||||
|
||||
output = StringIO.StringIO()
|
||||
input = StringIO.StringIO('y')
|
||||
warning = presubmit.OutputApi.PresubmitPromptWarning('???')
|
||||
self.failUnless(warning._Handle(output, input))
|
||||
self.failUnless(output.getvalue().count('???'))
|
||||
|
||||
output = StringIO.StringIO()
|
||||
input = StringIO.StringIO('n')
|
||||
warning = presubmit.OutputApi.PresubmitPromptWarning('???')
|
||||
self.failIf(warning._Handle(output, input))
|
||||
self.failUnless(output.getvalue().count('???'))
|
||||
|
||||
output = StringIO.StringIO()
|
||||
input = StringIO.StringIO('\n')
|
||||
warning = presubmit.OutputApi.PresubmitPromptWarning('???')
|
||||
self.failIf(warning._Handle(output, input))
|
||||
self.failUnless(output.getvalue().count('???'))
|
||||
|
||||
|
||||
class CannedChecksUnittest(PresubmitTestsBase):
|
||||
"""Tests presubmit_canned_checks.py."""
|
||||
class MockInputApi(object):
|
||||
class MockUrllib2(object):
|
||||
class urlopen(object):
|
||||
def __init__(self, url):
|
||||
if url == 'url_to_open':
|
||||
self.result = '1'
|
||||
else:
|
||||
self.result = '0'
|
||||
def read(self):
|
||||
return self.result
|
||||
def close(self):
|
||||
pass
|
||||
def __init__(self, lines=None):
|
||||
self.lines = lines
|
||||
self.basename = lambda x: x
|
||||
self.urllib2 = self.MockUrllib2()
|
||||
self.re = presubmit.re
|
||||
|
||||
def RightHandSideLines(self):
|
||||
for line in self.lines:
|
||||
yield (presubmit.AffectedFile('bingo', 'M'), 1, line)
|
||||
|
||||
def testMembersChanged(self):
|
||||
members = [
|
||||
'CheckChangeHasNoTabs', 'CheckChangeHasQaField',
|
||||
'CheckChangeHasTestedField', 'CheckDoNotSubmit',
|
||||
'CheckDoNotSubmitInDescription', 'CheckDoNotSubmitInFiles',
|
||||
'CheckLongLines', 'CheckTreeIsOpen',
|
||||
]
|
||||
# If this test fails, you should add the relevant test.
|
||||
self.compareMembers(presubmit_canned_checks, members)
|
||||
|
||||
def testCannedCheckChangeHasTestedField(self):
|
||||
change = self.MakeBasicChange('foo',
|
||||
'Foo\nTESTED=did some stuff')
|
||||
api = presubmit.InputApi(change, 'PRESUBMIT.py')
|
||||
self.failIf(presubmit_canned_checks.CheckChangeHasTestedField(
|
||||
api, presubmit.OutputApi))
|
||||
|
||||
change = self.MakeBasicChange('foo',
|
||||
'Foo\nNEVERTESTED=did some stuff')
|
||||
api = presubmit.InputApi(change, 'PRESUBMIT.py')
|
||||
self.failUnless(presubmit_canned_checks.CheckChangeHasTestedField(
|
||||
api, presubmit.OutputApi))
|
||||
|
||||
def testCannedCheckChangeHasQAField(self):
|
||||
change = self.MakeBasicChange('foo',
|
||||
'Foo\nQA=test floop feature very well')
|
||||
api = presubmit.InputApi(change, 'PRESUBMIT.py')
|
||||
self.failIf(presubmit_canned_checks.CheckChangeHasQaField(
|
||||
api, presubmit.OutputApi))
|
||||
|
||||
change = self.MakeBasicChange('foo',
|
||||
'Foo\nNOTFORQA=test floop feature very well')
|
||||
api = presubmit.InputApi(change, 'PRESUBMIT.py')
|
||||
self.failUnless(presubmit_canned_checks.CheckChangeHasQaField(
|
||||
api, presubmit.OutputApi))
|
||||
|
||||
def testCannedCheckDoNotSubmitInDescription(self):
|
||||
change = self.MakeBasicChange('foo', 'hello')
|
||||
api = presubmit.InputApi(change, 'PRESUBMIT.py')
|
||||
self.failIf(presubmit_canned_checks.CheckDoNotSubmitInDescription(
|
||||
api, presubmit.OutputApi))
|
||||
|
||||
change = self.MakeBasicChange('foo',
|
||||
'DO NOT ' + 'SUBMIT')
|
||||
api = presubmit.InputApi(change, 'PRESUBMIT.py')
|
||||
self.failUnless(presubmit_canned_checks.CheckDoNotSubmitInDescription(
|
||||
api, presubmit.OutputApi))
|
||||
|
||||
def testCannedCheckDoNotSubmitInFiles(self):
|
||||
self.failIf(presubmit_canned_checks.CheckDoNotSubmitInFiles(
|
||||
self.MockInputApi(['hello', 'there']), presubmit.OutputApi
|
||||
))
|
||||
self.failUnless(presubmit_canned_checks.CheckDoNotSubmitInFiles(
|
||||
self.MockInputApi(['hello', 'yo, DO NOT ' + 'SUBMIT']),
|
||||
presubmit.OutputApi))
|
||||
|
||||
def testCannedCheckChangeHasNoTabs(self):
|
||||
self.failIf(presubmit_canned_checks.CheckChangeHasNoTabs(
|
||||
self.MockInputApi(['hello', 'there']), presubmit.OutputApi
|
||||
))
|
||||
self.failUnless(presubmit_canned_checks.CheckChangeHasNoTabs(
|
||||
self.MockInputApi(['hello', 'there\tit is']), presubmit.OutputApi
|
||||
))
|
||||
|
||||
def testCannedCheckLongLines(self):
|
||||
self.failIf(presubmit_canned_checks.CheckLongLines(
|
||||
self.MockInputApi(['hello', 'there']), presubmit.OutputApi, 5
|
||||
))
|
||||
self.failUnless(presubmit_canned_checks.CheckLongLines(
|
||||
self.MockInputApi(['hello', 'there!']), presubmit.OutputApi, 5
|
||||
))
|
||||
|
||||
def testCannedCheckTreeIsOpen(self):
|
||||
self.failIf(presubmit_canned_checks.CheckTreeIsOpen(
|
||||
self.MockInputApi(), presubmit.OutputApi, url='url_to_open', closed='0'
|
||||
))
|
||||
self.failUnless(presubmit_canned_checks.CheckTreeIsOpen(
|
||||
self.MockInputApi(), presubmit.OutputApi, url='url_to_closed', closed='0'
|
||||
))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
50
tests/test-lib.sh
Executable file
50
tests/test-lib.sh
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Abort on error.
|
||||
set -e
|
||||
|
||||
PWD=`pwd`
|
||||
REPO_URL=file://$PWD/svnrepo
|
||||
GIT_CL=$PWD/../git-cl
|
||||
|
||||
# Set up an SVN repo that has a few commits to trunk.
|
||||
setup_initsvn() {
|
||||
echo "Setting up test SVN repo..."
|
||||
rm -rf svnrepo
|
||||
svnadmin create svnrepo
|
||||
|
||||
rm -rf svn
|
||||
svn co -q $REPO_URL svn
|
||||
(
|
||||
cd svn
|
||||
echo "test" > test
|
||||
svn add -q test
|
||||
svn commit -q -m "initial commit"
|
||||
echo "test2" >> test
|
||||
svn commit -q -m "second commit"
|
||||
)
|
||||
}
|
||||
|
||||
# Set up a git-svn checkout of the repo.
|
||||
setup_gitsvn() {
|
||||
echo "Setting up test git-svn repo..."
|
||||
rm -rf git-svn
|
||||
# There appears to be no way to make git-svn completely shut up, so we
|
||||
# redirect its output.
|
||||
git svn -q clone $REPO_URL git-svn >/dev/null 2>&1
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
rm -rf svnrepo svn git-svn
|
||||
}
|
||||
|
||||
# Usage: test_expect_success "description of test" "test code".
|
||||
test_expect_success() {
|
||||
echo "TESTING: $1"
|
||||
exit_code=0
|
||||
sh -c "$2" || exit_code=$?
|
||||
if [ $exit_code != 0 ]; then
|
||||
echo "FAILURE: $1"
|
||||
return $exit_code
|
||||
fi
|
||||
}
|
||||
505
trychange.py
Executable file
505
trychange.py
Executable file
@@ -0,0 +1,505 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Client-side script to send a try job to the try server. It communicates to
|
||||
the try server by either writting to a svn repository or by directly connecting
|
||||
to the server by HTTP.
|
||||
"""
|
||||
|
||||
|
||||
import datetime
|
||||
import getpass
|
||||
import logging
|
||||
import optparse
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import traceback
|
||||
import urllib
|
||||
|
||||
import gcl
|
||||
|
||||
__version__ = '1.1'
|
||||
|
||||
|
||||
# Constants
|
||||
HELP_STRING = "Sorry, Tryserver is not available."
|
||||
SCRIPT_PATH = os.path.join('tools', 'tryserver', 'tryserver.py')
|
||||
USAGE = r"""%prog [options]
|
||||
|
||||
Client-side script to send a try job to the try server. It communicates to
|
||||
the try server by either writting to a svn repository or by directly connecting
|
||||
to the server by HTTP.
|
||||
|
||||
|
||||
Examples:
|
||||
A git patch off a web site (git inserts a/ and b/) and fix the base dir:
|
||||
%prog --url http://url/to/patch.diff --patchlevel 1 --root src
|
||||
|
||||
Use svn to store the try job, specify an alternate email address and use a
|
||||
premade diff file on the local drive:
|
||||
%prog --email user@example.com
|
||||
--svn_repo svn://svn.chromium.org/chrome-try/try --diff foo.diff
|
||||
|
||||
Running only on a 'mac' slave with revision src@123 and clobber first; specify
|
||||
manually the 3 source files to use for the try job:
|
||||
%prog --bot mac --revision src@123 --clobber -f src/a.cc -f src/a.h
|
||||
-f include/b.h
|
||||
|
||||
"""
|
||||
|
||||
class InvalidScript(Exception):
|
||||
def __str__(self):
|
||||
return self.args[0] + '\n' + HELP_STRING
|
||||
|
||||
|
||||
class NoTryServerAccess(Exception):
|
||||
def __str__(self):
|
||||
return self.args[0] + '\n' + HELP_STRING
|
||||
|
||||
|
||||
def PathDifference(root, subpath):
|
||||
"""Returns the difference subpath minus root."""
|
||||
if subpath.find(root) != 0:
|
||||
return None
|
||||
# The + 1 is for the trailing / or \.
|
||||
return subpath[len(root) + len(os.sep):]
|
||||
|
||||
|
||||
def GetSourceRoot():
|
||||
"""Returns the absolute directory one level up from the repository root."""
|
||||
return os.path.abspath(os.path.join(gcl.GetRepositoryRoot(), '..'))
|
||||
|
||||
|
||||
def ExecuteTryServerScript():
|
||||
"""Locates the tryserver script, executes it and returns its dictionary.
|
||||
|
||||
The try server script contains the repository-specific try server commands."""
|
||||
script_locals = {}
|
||||
try:
|
||||
# gcl.GetRepositoryRoot() may throw an exception.
|
||||
script_path = os.path.join(gcl.GetRepositoryRoot(), SCRIPT_PATH)
|
||||
except Exception:
|
||||
return script_locals
|
||||
if os.path.exists(script_path):
|
||||
try:
|
||||
exec(gcl.ReadFile(script_path), script_locals)
|
||||
except Exception, e:
|
||||
# TODO(maruel): Need to specialize the exception trapper.
|
||||
traceback.print_exc()
|
||||
raise InvalidScript('%s is invalid.' % script_path)
|
||||
return script_locals
|
||||
|
||||
|
||||
def EscapeDot(name):
|
||||
return name.replace('.', '-')
|
||||
|
||||
|
||||
def RunCommand(command):
|
||||
output, retcode = gcl.RunShellWithReturnCode(command)
|
||||
if retcode:
|
||||
raise NoTryServerAccess(' '.join(command) + '\nOuput:\n' + output)
|
||||
return output
|
||||
|
||||
|
||||
class SCM(object):
|
||||
"""Simplistic base class to implement one function: ProcessOptions."""
|
||||
def __init__(self, options):
|
||||
self.options = options
|
||||
|
||||
def ProcessOptions(self):
|
||||
raise Unimplemented
|
||||
|
||||
|
||||
class SVN(SCM):
|
||||
"""Gathers the options and diff for a subversion checkout."""
|
||||
def GenerateDiff(self, files, root):
|
||||
"""Returns a string containing the diff for the given file list.
|
||||
|
||||
The files in the list should either be absolute paths or relative to the
|
||||
given root. If no root directory is provided, the repository root will be
|
||||
used.
|
||||
"""
|
||||
previous_cwd = os.getcwd()
|
||||
if root is None:
|
||||
os.chdir(gcl.GetRepositoryRoot())
|
||||
else:
|
||||
os.chdir(root)
|
||||
|
||||
diff = []
|
||||
for file in files:
|
||||
# Use svn info output instead of os.path.isdir because the latter fails
|
||||
# when the file is deleted.
|
||||
if gcl.GetSVNFileInfo(file).get("Node Kind") == "directory":
|
||||
continue
|
||||
# If the user specified a custom diff command in their svn config file,
|
||||
# then it'll be used when we do svn diff, which we don't want to happen
|
||||
# since we want the unified diff. Using --diff-cmd=diff doesn't always
|
||||
# work, since they can have another diff executable in their path that
|
||||
# gives different line endings. So we use a bogus temp directory as the
|
||||
# config directory, which gets around these problems.
|
||||
if sys.platform.startswith("win"):
|
||||
parent_dir = tempfile.gettempdir()
|
||||
else:
|
||||
parent_dir = sys.path[0] # tempdir is not secure.
|
||||
bogus_dir = os.path.join(parent_dir, "temp_svn_config")
|
||||
if not os.path.exists(bogus_dir):
|
||||
os.mkdir(bogus_dir)
|
||||
# Grabs the diff data.
|
||||
data = gcl.RunShell(["svn", "diff", "--config-dir", bogus_dir, file])
|
||||
|
||||
# We know the diff will be incorrectly formatted. Fix it.
|
||||
if gcl.IsSVNMoved(file):
|
||||
# The file is "new" in the patch sense. Generate a homebrew diff.
|
||||
# We can't use ReadFile() since it's not using binary mode.
|
||||
file_handle = open(file, 'rb')
|
||||
file_content = file_handle.read()
|
||||
file_handle.close()
|
||||
# Prepend '+ ' to every lines.
|
||||
file_content = ['+ ' + i for i in file_content.splitlines(True)]
|
||||
nb_lines = len(file_content)
|
||||
# We need to use / since patch on unix will fail otherwise.
|
||||
file = file.replace('\\', '/')
|
||||
data = "Index: %s\n" % file
|
||||
data += ("============================================================="
|
||||
"======\n")
|
||||
# Note: Should we use /dev/null instead?
|
||||
data += "--- %s\n" % file
|
||||
data += "+++ %s\n" % file
|
||||
data += "@@ -0,0 +1,%d @@\n" % nb_lines
|
||||
data += ''.join(file_content)
|
||||
diff.append(data)
|
||||
os.chdir(previous_cwd)
|
||||
return "".join(diff)
|
||||
|
||||
def ProcessOptions(self):
|
||||
if not self.options.diff:
|
||||
# Generate the diff with svn and write it to the submit queue path. The
|
||||
# files are relative to the repository root, but we need patches relative
|
||||
# to one level up from there (i.e., 'src'), so adjust both the file
|
||||
# paths and the root of the diff.
|
||||
source_root = GetSourceRoot()
|
||||
prefix = PathDifference(source_root, gcl.GetRepositoryRoot())
|
||||
adjusted_paths = [os.path.join(prefix, x) for x in self.options.files]
|
||||
self.options.diff = self.GenerateDiff(adjusted_paths, root=source_root)
|
||||
|
||||
|
||||
class GIT(SCM):
|
||||
"""Gathers the options and diff for a git checkout."""
|
||||
def GenerateDiff(self):
|
||||
"""Get the diff we'll send to the try server. We ignore the files list."""
|
||||
branch = upload.RunShell(['git', 'cl', 'upstream']).strip()
|
||||
diff = upload.RunShell(['git', 'diff-tree', '-p', '--no-prefix',
|
||||
branch, 'HEAD']).splitlines(True)
|
||||
for i in range(len(diff)):
|
||||
# In the case of added files, replace /dev/null with the path to the
|
||||
# file being added.
|
||||
if diff[i].startswith('--- /dev/null'):
|
||||
diff[i] = '--- %s' % diff[i+1][4:]
|
||||
return ''.join(diff)
|
||||
|
||||
def GetEmail(self):
|
||||
# TODO: check for errors here?
|
||||
return upload.RunShell(['git', 'config', 'user.email']).strip()
|
||||
|
||||
def GetPatchName(self):
|
||||
"""Construct a name for this patch."""
|
||||
# TODO: perhaps include the hash of the current commit, to distinguish
|
||||
# patches?
|
||||
branch = upload.RunShell(['git', 'symbolic-ref', 'HEAD']).strip()
|
||||
if not branch.startswith('refs/heads/'):
|
||||
raise "Couldn't figure out branch name"
|
||||
branch = branch[len('refs/heads/'):]
|
||||
return branch
|
||||
|
||||
def ProcessOptions(self):
|
||||
if not self.options.diff:
|
||||
self.options.diff = self.GenerateDiff()
|
||||
if not self.options.name:
|
||||
self.options.name = self.GetPatchName()
|
||||
if not self.options.email:
|
||||
self.options.email = self.GetEmail()
|
||||
|
||||
|
||||
def _ParseSendChangeOptions(options):
|
||||
"""Parse common options passed to _SendChangeHTTP and _SendChangeSVN."""
|
||||
values = {}
|
||||
if options.email:
|
||||
values['email'] = options.email
|
||||
values['user'] = options.user
|
||||
values['name'] = options.name
|
||||
if options.bot:
|
||||
values['bot'] = ','.join(options.bot)
|
||||
if options.revision:
|
||||
values['revision'] = options.revision
|
||||
if options.clobber:
|
||||
values['clobber'] = 'true'
|
||||
if options.tests:
|
||||
values['tests'] = ','.join(options.tests)
|
||||
if options.root:
|
||||
values['root'] = options.root
|
||||
if options.patchlevel:
|
||||
values['patchlevel'] = options.patchlevel
|
||||
if options.issue:
|
||||
values['issue'] = options.issue
|
||||
if options.patchset:
|
||||
values['patchset'] = options.patchset
|
||||
return values
|
||||
|
||||
|
||||
def _SendChangeHTTP(options):
|
||||
"""Send a change to the try server using the HTTP protocol."""
|
||||
script_locals = ExecuteTryServerScript()
|
||||
|
||||
if not options.host:
|
||||
options.host = script_locals.get('try_server_http_host', None)
|
||||
if not options.host:
|
||||
raise NoTryServerAccess('Please use the --host option to specify the try '
|
||||
'server host to connect to.')
|
||||
if not options.port:
|
||||
options.port = script_locals.get('try_server_http_port', None)
|
||||
if not options.port:
|
||||
raise NoTryServerAccess('Please use the --port option to specify the try '
|
||||
'server port to connect to.')
|
||||
|
||||
values = _ParseSendChangeOptions(options)
|
||||
values['patch'] = options.diff
|
||||
|
||||
url = 'http://%s:%s/send_try_patch' % (options.host, options.port)
|
||||
proxies = None
|
||||
if options.proxy:
|
||||
if options.proxy.lower() == 'none':
|
||||
# Effectively disable HTTP_PROXY or Internet settings proxy setup.
|
||||
proxies = {}
|
||||
else:
|
||||
proxies = {'http': options.proxy, 'https': options.proxy}
|
||||
try:
|
||||
connection = urllib.urlopen(url, urllib.urlencode(values), proxies=proxies)
|
||||
except IOError, e:
|
||||
# TODO(thestig) this probably isn't quite right.
|
||||
if values.get('bot') and e[2] == 'got a bad status line':
|
||||
raise NoTryServerAccess('%s is unaccessible. Bad --bot argument?' % url)
|
||||
else:
|
||||
raise NoTryServerAccess('%s is unaccessible.' % url)
|
||||
if not connection:
|
||||
raise NoTryServerAccess('%s is unaccessible.' % url)
|
||||
if connection.read() != 'OK':
|
||||
raise NoTryServerAccess('%s is unaccessible.' % url)
|
||||
return options.name
|
||||
|
||||
|
||||
def _SendChangeSVN(options):
|
||||
"""Send a change to the try server by committing a diff file on a subversion
|
||||
server."""
|
||||
script_locals = ExecuteTryServerScript()
|
||||
if not options.svn_repo:
|
||||
options.svn_repo = script_locals.get('try_server_svn', None)
|
||||
if not options.svn_repo:
|
||||
raise NoTryServerAccess('Please use the --svn_repo option to specify the'
|
||||
' try server svn repository to connect to.')
|
||||
|
||||
values = _ParseSendChangeOptions(options)
|
||||
description = ''
|
||||
for (k,v) in values.iteritems():
|
||||
description += "%s=%s\n" % (k,v)
|
||||
|
||||
# Do an empty checkout.
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
temp_file = tempfile.NamedTemporaryFile()
|
||||
temp_file_name = temp_file.name
|
||||
try:
|
||||
RunCommand(['svn', 'checkout', '--depth', 'empty', '--non-interactive',
|
||||
options.svn_repo, temp_dir])
|
||||
# TODO(maruel): Use a subdirectory per user?
|
||||
current_time = str(datetime.datetime.now()).replace(':', '.')
|
||||
file_name = (EscapeDot(options.user) + '.' + EscapeDot(options.name) +
|
||||
'.%s.diff' % current_time)
|
||||
full_path = os.path.join(temp_dir, file_name)
|
||||
full_url = options.svn_repo + '/' + file_name
|
||||
file_found = False
|
||||
try:
|
||||
RunCommand(['svn', 'ls', '--non-interactive', full_url])
|
||||
file_found = True
|
||||
except NoTryServerAccess:
|
||||
pass
|
||||
if file_found:
|
||||
# The file already exists in the repo. Note that commiting a file is a
|
||||
# no-op if the file's content (the diff) is not modified. This is why the
|
||||
# file name contains the date and time.
|
||||
RunCommand(['svn', 'update', '--non-interactive', full_path])
|
||||
file = open(full_path, 'wb')
|
||||
file.write(options.diff)
|
||||
file.close()
|
||||
else:
|
||||
# Add the file to the repo
|
||||
file = open(full_path, 'wb')
|
||||
file.write(options.diff)
|
||||
file.close()
|
||||
RunCommand(["svn", "add", '--non-interactive', full_path])
|
||||
temp_file.write(description)
|
||||
temp_file.flush()
|
||||
RunCommand(["svn", "commit", '--non-interactive', full_path, '--file',
|
||||
temp_file_name])
|
||||
finally:
|
||||
temp_file.close()
|
||||
shutil.rmtree(temp_dir, True)
|
||||
return options.name
|
||||
|
||||
|
||||
def GuessVCS(options):
|
||||
"""Helper to guess the version control system.
|
||||
|
||||
NOTE: Very similar to upload.GuessVCS. Doesn't look for hg since we don't
|
||||
support it yet.
|
||||
|
||||
This examines the current directory, guesses which SCM we're using, and
|
||||
returns an instance of the appropriate class. Exit with an error if we can't
|
||||
figure it out.
|
||||
|
||||
Returns:
|
||||
A SCM instance. Exits if the SCM can't be guessed.
|
||||
"""
|
||||
# Subversion has a .svn in all working directories.
|
||||
if os.path.isdir('.svn'):
|
||||
logging.info("Guessed VCS = Subversion")
|
||||
return SVN(options)
|
||||
|
||||
# Git has a command to test if you're in a git tree.
|
||||
# Try running it, but don't die if we don't have git installed.
|
||||
try:
|
||||
out, returncode = gcl.RunShellWithReturnCode(["git", "rev-parse",
|
||||
"--is-inside-work-tree"])
|
||||
if returncode == 0:
|
||||
logging.info("Guessed VCS = Git")
|
||||
return GIT(options)
|
||||
except OSError, (errno, message):
|
||||
if errno != 2: # ENOENT -- they don't have git installed.
|
||||
raise
|
||||
|
||||
raise NoTryServerAccess("Could not guess version control system. "
|
||||
"Are you in a working copy directory?")
|
||||
|
||||
|
||||
def TryChange(argv,
|
||||
file_list,
|
||||
swallow_exception,
|
||||
prog=None):
|
||||
# Parse argv
|
||||
parser = optparse.OptionParser(usage=USAGE,
|
||||
version=__version__,
|
||||
prog=prog)
|
||||
|
||||
group = optparse.OptionGroup(parser, "Result and status")
|
||||
group.add_option("-u", "--user", default=getpass.getuser(),
|
||||
help="Owner user name [default: %default]")
|
||||
group.add_option("-e", "--email", default=os.environ.get('EMAIL_ADDRESS'),
|
||||
help="Email address where to send the results. Use the "
|
||||
"EMAIL_ADDRESS environment variable to set the default "
|
||||
"email address [default: %default]")
|
||||
group.add_option("-n", "--name", default='Unnamed',
|
||||
help="Descriptive name of the try job")
|
||||
group.add_option("--issue", type='int',
|
||||
help="Update rietveld issue try job status")
|
||||
group.add_option("--patchset", type='int',
|
||||
help="Update rietveld issue try job status")
|
||||
parser.add_option_group(group)
|
||||
|
||||
group = optparse.OptionGroup(parser, "Try job options")
|
||||
group.add_option("-b", "--bot", action="append",
|
||||
help="Only use specifics build slaves, ex: '--bot win' to "
|
||||
"run the try job only on the 'win' slave; see the try "
|
||||
"server watefall for the slave's name")
|
||||
group.add_option("-r", "--revision",
|
||||
help="Revision to use for the try job; default: the "
|
||||
"revision will be determined by the try server; see "
|
||||
"its waterfall for more info")
|
||||
group.add_option("-c", "--clobber", action="store_true",
|
||||
help="Force a clobber before building; e.g. don't do an "
|
||||
"incremental build")
|
||||
# Override the list of tests to run, use multiple times to list many tests
|
||||
# (or comma separated)
|
||||
group.add_option("-t", "--tests", action="append",
|
||||
help=optparse.SUPPRESS_HELP)
|
||||
parser.add_option_group(group)
|
||||
|
||||
group = optparse.OptionGroup(parser, "Patch to run")
|
||||
group.add_option("-f", "--file", default=file_list, dest="files",
|
||||
metavar="FILE", action="append",
|
||||
help="Use many times to list the files to include in the "
|
||||
"try, relative to the repository root")
|
||||
group.add_option("--diff",
|
||||
help="File containing the diff to try")
|
||||
group.add_option("--url",
|
||||
help="Url where to grab a patch")
|
||||
group.add_option("--root",
|
||||
help="Root to use for the patch; base subdirectory for "
|
||||
"patch created in a subdirectory")
|
||||
group.add_option("--patchlevel", type='int', metavar="LEVEL",
|
||||
help="Used as -pN parameter to patch")
|
||||
parser.add_option_group(group)
|
||||
|
||||
group = optparse.OptionGroup(parser, "Access the try server by HTTP")
|
||||
group.add_option("--use_http", action="store_const", const=_SendChangeHTTP,
|
||||
dest="send_patch", default=_SendChangeHTTP,
|
||||
help="Use HTTP to talk to the try server [default]")
|
||||
group.add_option("--host",
|
||||
help="Host address")
|
||||
group.add_option("--port",
|
||||
help="HTTP port")
|
||||
group.add_option("--proxy",
|
||||
help="HTTP proxy")
|
||||
parser.add_option_group(group)
|
||||
|
||||
group = optparse.OptionGroup(parser, "Access the try server with SVN")
|
||||
group.add_option("--use_svn", action="store_const", const=_SendChangeSVN,
|
||||
dest="send_patch",
|
||||
help="Use SVN to talk to the try server")
|
||||
group.add_option("--svn_repo", metavar="SVN_URL",
|
||||
help="SVN url to use to write the changes in; --use_svn is "
|
||||
"implied when using --svn_repo")
|
||||
parser.add_option_group(group)
|
||||
|
||||
options, args = parser.parse_args(argv)
|
||||
# Switch the default accordingly.
|
||||
if options.svn_repo:
|
||||
options.send_patch = _SendChangeSVN
|
||||
|
||||
if len(args) == 1 and args[0] == 'help':
|
||||
parser.print_help()
|
||||
if (not options.files and (not options.issue and options.patchset) and
|
||||
not options.diff and not options.url):
|
||||
# TODO(maruel): It should just try the modified files showing up in a
|
||||
# svn status.
|
||||
print "Nothing to try, changelist is empty."
|
||||
return
|
||||
|
||||
try:
|
||||
# Convert options.diff into the content of the diff.
|
||||
if options.url:
|
||||
options.diff = urllib.urlopen(options.url).read()
|
||||
elif options.diff:
|
||||
options.diff = gcl.ReadFile(options.diff)
|
||||
# Process the VCS in any case at least to retrieve the email address.
|
||||
try:
|
||||
options.scm = GuessVCS(options)
|
||||
options.scm.ProcessOptions()
|
||||
except NoTryServerAccess, e:
|
||||
# If we got the diff, we don't care.
|
||||
if not options.diff:
|
||||
raise
|
||||
|
||||
# Send the patch.
|
||||
patch_name = options.send_patch(options)
|
||||
print 'Patch \'%s\' sent to try server.' % patch_name
|
||||
if patch_name == 'Unnamed':
|
||||
print "Note: use --name NAME to change the try's name."
|
||||
except (InvalidScript, NoTryServerAccess), e:
|
||||
if swallow_exception:
|
||||
return
|
||||
print e
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
TryChange(None, None, False)
|
||||
Reference in New Issue
Block a user